Skip to main content

scaffold

Creates handlers, CLI commands, MCP tools, and daemon services following Outfitter Dev Kit conventions. Use when adding new components to a project, scaffolding code, or when "create handler", "new command", "add tool", or "daemon service" are mentioned.

Datos de origen

Repositorio
outfitter-dev/outfitter
Última actividad en el origen
24 de marzo de 2026 a las 20:42
Idioma detectado de SKILL.md
inglés
Estrellas
6
Forks
1

Opciones de instalación

De forma predeterminada está seleccionado el prompt que primero revisa el origen. Puedes cambiar a un comando directo o descargar una copia local.

Revisa los archivos de origen

Lee SKILL.md y los archivos complementarios que muestra SkillsMP antes de decidir si quieres instalarlo.

Explorador de archivos
5 archivos

Mostrando SKILL.md

SKILL.md
Instrucciones de origen · Vista previa de solo lectura
name
scaffold
version
0.1.0
description
Creates handlers, CLI commands, MCP tools, and daemon services following Outfitter Dev Kit conventions. Use when adding new components to a project, scaffolding code, or when "create handler", "new command", "add tool", or "daemon service" are mentioned.
allowed-tools
Read Write Edit Glob Grep
argument-hint
["component type"]
# Scaffold Stack Components Templates for creating @outfitter/\* components. ## Component Types | Type | Package | Template | | ----------- | ---------------------- | --------------------------------- | | Handler | `@outfitter/contracts` | [handler](#handler) | | CLI Command | `@outfitter/cli` | [cli-command](#cli-command) | | MCP Tool | `@outfitter/mcp` | [mcp-tool](#mcp-tool) | | Daemon | `@outfitter/daemon` | [daemon-service](#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(NotFoundError.create("resource", input.id)); } return Result.ok(resource); }; ``` ## CLI Command For Outfitter apps, define CLI behavior in the action registry and let `buildCliCommands()` wire Commander for you. ```typescript import { actionCliPresets } from "@outfitter/cli/actions"; import { output } from "@outfitter/cli"; import { cwdPreset, verbosePreset } from "@outfitter/cli/flags"; import { jqPreset, outputModePreset } from "@outfitter/cli/query"; import { defineAction, Result } from "@outfitter/contracts"; import { z } from "zod"; import { myHandler } from "../handlers/my-handler.js"; const shared = actionCliPresets(verbosePreset(), cwdPreset()); const mode = outputModePreset({ includeJsonl: true }); const jq = jqPreset(); export const myAction = defineAction({ id: "my.get", description: "Get a resource", surfaces: ["cli"], input: z.object({ id: z.string().min(1), verbose: z.boolean().optional(), cwd: z.string(), outputMode: z.enum(["human", "json", "jsonl"]).default("human"), jq: z.string().optional(), }), output: z.object({ id: z.string(), name: z.string(), }), cli: { group: "my", command: "get <id>", options: [...shared.options, ...mode.options, ...jq.options], mapInput: ({ args, flags }) => ({ id: String(args[0] ?? ""), ...shared.resolve(flags), ...mode.resolve(flags), ...jq.resolve(flags), }), }, handler: async (input, ctx) => { const result = await myHandler({ id: input.id }, ctx); if (result.isErr()) { return result; } await output(result.value, { mode: input.outputMode }); return Result.ok(result.value); }, }); ``` Register in CLI: ```typescript import { buildCliCommands } from "@outfitter/cli/actions"; import { createCLI } from "@outfitter/cli/command"; import { createActionRegistry } from "@outfitter/contracts"; import { myAction } from "./actions/my-action.js"; const cli = createCLI({ name: "myapp", version: "1.0.0" }); const registry = createActionRegistry([myAction]); for (const command of buildCliCommands(registry, { schema: { programName: "myapp", surface: {} }, })) { cli.register(command); } await cli.parse(); ``` After adding actions: ```bash myapp schema generate myapp schema diff ``` ## MCP Tool Zod schema with Result return: ```typescript 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 = { name: "my_tool", description: "Tool description for AI agent", inputSchema: InputSchema, handler: async ( input: z.infer<typeof InputSchema> ): Promise<Result<Output, ValidationError>> => { 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. **Action Registry First** — Add CLI behavior via `defineAction()` and `mapInput()` 3. **Preset Composition** — Prefer shared presets over manual flag parsing 4. **Schema Drift Guard** — Regenerate and diff `.outfitter/surface.lock` after CLI changes 5. **Validate Early** — Use `createValidator` at handler entry 6. **Type Errors** — List all error types in handler signature 7. **Context Propagation** — Pass context through all handler calls 8. **Test Handlers** — Test handlers directly without transport layer ## References - [templates/handler.md](templates/handler.md) - [templates/cli-command.md](templates/cli-command.md) - [templates/mcp-tool.md](templates/mcp-tool.md) - [templates/daemon-service.md](templates/daemon-service.md)
Ver en GitHub