Agent skill

stack-templates

Templates for creating handlers, CLI commands, MCP tools, and daemon services following Outfitter Stack conventions. Use when scaffolding new components, creating handlers, adding commands, or when "create handler", "new command", "add tool", "scaffold", "template", or "daemon service" are mentioned.

Stars 26
Forks 0

Install this agent skill to your Project

npx add-skill https://github.com/outfitter-dev/agents/tree/main/plugins/outfitter-stack/skills/stack-templates

SKILL.md

Stack Templates

Templates for creating @outfitter/* components.

Component Types

Type Package Template
Handler @outfitter/contracts handler
Handler Test @outfitter/testing handler-test
CLI Command @outfitter/cli cli-command
MCP Tool @outfitter/mcp mcp-tool
Daemon @outfitter/daemon daemon-service

Handler

Transport-agnostic business logic returning Result<T, E>:

typescript
import {
  Result,
  ValidationError,
  NotFoundError,
  createValidator,
  type Handler,
} from "@outfitter/contracts";
import { z } from "zod";

// 1. Input schema
const InputSchema = z.object({
  id: z.string().min(1),
});
type Input = z.infer<typeof InputSchema>;

// 2. Output type
interface Output {
  id: string;
  name: string;
}

// 3. Validator
const validateInput = createValidator(InputSchema);

// 4. Handler
export const myHandler: Handler<unknown, Output, ValidationError | NotFoundError> = async (
  rawInput,
  ctx
) => {
  const inputResult = validateInput(rawInput);
  if (inputResult.isErr()) return inputResult;
  const input = inputResult.value;

  ctx.logger.debug("Processing", { id: input.id });

  const resource = await fetchResource(input.id);
  if (!resource) {
    return Result.err(new NotFoundError("resource", input.id));
  }

  return Result.ok(resource);
};

Handler Test

Test handlers directly without transport layer:

typescript
import { describe, test, expect } from "bun:test";
import { createContext } from "@outfitter/contracts";
import { myHandler } from "../handlers/my-handler.js";

describe("myHandler", () => {
  test("returns success for valid input", async () => {
    const ctx = createContext({});
    const result = await myHandler({ id: "valid-id" }, ctx);

    expect(result.isOk()).toBe(true);
    expect(result.value).toMatchObject({ id: "valid-id" });
  });

  test("returns NotFoundError for missing resource", async () => {
    const ctx = createContext({});
    const result = await myHandler({ id: "missing" }, ctx);

    expect(result.isErr()).toBe(true);
    expect(result.error._tag).toBe("NotFoundError");
    expect(result.error.resourceId).toBe("missing");
  });

  test("returns ValidationError for invalid input", async () => {
    const ctx = createContext({});
    const result = await myHandler({ id: "" }, ctx);

    expect(result.isErr()).toBe(true);
    expect(result.error._tag).toBe("ValidationError");
  });
});

CLI Command

Commander.js command calling a handler:

typescript
import { command, output, exitWithError } from "@outfitter/cli";
import { createContext } from "@outfitter/contracts";
import { myHandler } from "../handlers/my-handler.js";

export const myCommand = command("my-command")
  .description("What this command does")
  .argument("<id>", "Resource ID")
  .option("-l, --limit <n>", "Limit results", parseInt)
  .action(async ({ args, flags }) => {
    const ctx = createContext({});

    const result = await myHandler({ id: args.id, limit: flags.limit }, ctx);

    if (result.isErr()) {
      exitWithError(result.error);
    }

    await output(result.value);
  })
  .build();

Register in CLI:

typescript
import { createCLI } from "@outfitter/cli";
import { myCommand } from "./commands/my-command.js";

const cli = createCLI({ name: "myapp", version: "1.0.0" });
cli.program.addCommand(myCommand);
cli.program.parse();

MCP Tool

Use defineTool() for type-safe tool definitions with automatic schema inference:

typescript
import { defineTool } from "@outfitter/mcp";
import { Result, ValidationError } from "@outfitter/contracts";
import { z } from "zod";

const InputSchema = z.object({
  query: z.string().describe("Search query"),
  limit: z.number().int().positive().default(10).describe("Max results"),
});

interface Output {
  results: Array<{ id: string; title: string }>;
  total: number;
}

export const myTool = defineTool({
  name: "my_tool",
  description: "Tool description for AI agent",
  inputSchema: InputSchema,

  handler: async (input): Promise<Result<Output, ValidationError>> => {
    // input is automatically typed as z.infer<typeof InputSchema>
    const results = await search(input.query, input.limit);
    return Result.ok({ results, total: results.length });
  },
});

Register in server:

typescript
import { createMcpServer } from "@outfitter/mcp";
import { myTool } from "./tools/my-tool.js";

const server = createMcpServer({ name: "my-server", version: "0.1.0" });
server.registerTool(myTool);
server.start();

Daemon Service

Background service with health checks and IPC:

typescript
import {
  createDaemon,
  createIpcServer,
  createHealthChecker,
  getSocketPath,
  getLockPath,
} from "@outfitter/daemon";
import { createLogger, createConsoleSink } from "@outfitter/logging";
import { Result } from "@outfitter/contracts";

const logger = createLogger({
  name: "my-daemon",
  level: "info",
  sinks: [createConsoleSink()],
  redaction: { enabled: true },
});

const daemon = createDaemon({
  name: "my-daemon",
  pidFile: getLockPath("my-daemon"),
  logger,
  shutdownTimeout: 10000,
});

const healthChecker = createHealthChecker([
  {
    name: "memory",
    check: async () => {
      const used = process.memoryUsage().heapUsed / 1024 / 1024;
      return used < 500
        ? Result.ok(undefined)
        : Result.err(new Error(`High memory: ${used.toFixed(2)}MB`));
    },
  },
]);

const ipcServer = createIpcServer(getSocketPath("my-daemon"));

ipcServer.onMessage(async (msg) => {
  const message = msg as { type: string };
  switch (message.type) {
    case "status": return { status: "ok", uptime: process.uptime() };
    case "health": return await healthChecker.check();
    default: return { error: "Unknown command" };
  }
});

daemon.onShutdown(async () => {
  logger.info("Shutting down...");
  await ipcServer.close();
});

async function main() {
  const startResult = await daemon.start();
  if (startResult.isErr()) {
    logger.error("Failed to start", { error: startResult.error });
    process.exit(1);
  }
  await ipcServer.listen();
  logger.info("Started", { socket: getSocketPath("my-daemon") });
}

main();

Best Practices

  1. Handler First - Write handler before adapter (CLI/MCP/API)
  2. Validate Early - Use createValidator at handler entry
  3. Type Errors - List all error types in handler signature
  4. Context Propagation - Pass context through all handler calls
  5. Test Handlers - Test handlers directly without transport layer

References

  • templates/handler.md
  • templates/handler-test.md
  • templates/cli-command.md
  • templates/mcp-tool.md
  • templates/daemon-service.md

Expand your agent's capabilities with these related and highly-rated skills.

outfitter-dev/agents

stack-feedback

Creates GitHub issues for problems discovered while using @outfitter/* packages. Use when finding bugs, missing features, unclear documentation, or improvement opportunities.

26 0
Explore
outfitter-dev/agents

stack-architecture

Design stack-based systems using @outfitter/* packages. Use when planning new projects, choosing packages, designing handler architecture, or when "architecture", "design", "structure", "plan handlers", or "error taxonomy" are mentioned.

26 0
Explore
outfitter-dev/agents

stack-audit

Scan codebase for Outfitter Stack adoption candidates. Identifies throw statements, console usage, hardcoded paths, and custom errors. Use when assessing adoption scope or checking readiness.

26 0
Explore
outfitter-dev/agents

stack-review

Audits code for Outfitter Stack compliance including Result types, error handling, logging patterns, and path safety. Use for pre-commit reviews, code quality checks, migration validation, or when "audit", "check compliance", "review stack", or "stack patterns" are mentioned.

26 0
Explore
outfitter-dev/agents

stack-patterns

Reference for Outfitter Stack patterns including Result types, Handler contract, Error taxonomy, and @outfitter/* package conventions. Use when learning the stack, looking up patterns, understanding packages, or when "Result", "Handler", "error taxonomy", "OutfitterError", "CLI output", "pagination", "MCP server", "MCP tool", "structured logging", "redaction", "test handler", "daemon", "IPC", or "@outfitter/*" are mentioned.

26 0
Explore
outfitter-dev/agents

stack-debug

Troubleshoots Outfitter Stack issues including Result handling, MCP problems, CLI output, exit codes, and logging. Use when debugging stack-specific issues, unexpected errors, wrong output modes, or when "debug Result", "MCP not working", "wrong exit code", or "logging issue" are mentioned.

26 0
Explore

Didn't find tool you were looking for?

Be as detailed as possible for better results