| 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
Handler
Transport-agnostic business logic returning Result<T, E>:
import {
Result,
ValidationError,
NotFoundError,
createValidator,
type Handler,
} from "@outfitter/contracts";
import { z } from "zod";
const InputSchema = z.object({
id: z.string().min(1),
});
type Input = z.infer<typeof InputSchema>;
interface Output {
id: string;
name: string;
}
const validateInput = createValidator(InputSchema);
export const myHandler: Handler<
unknown,
Output,
ValidationError | NotFoundError
> = async (rawInput, ctx) => {
const inputResult = validateInput(rawInput);
if (inputResult.isErr()) return inputResult;
input = inputResult.;
ctx..(, { : input. });
resource = (input.);
(!resource) {
.(.(, input.));
}
.(resource);
};
CLI Command
For Outfitter apps, define CLI behavior in the action registry and let
buildCliCommands() wire Commander for you.
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(),
: z.(),
: z.([, , ]).(),
: z.().(),
}),
: z.({
: z.(),
: z.(),
}),
: {
: ,
: ,
: [...shared., ...mode., ...jq.],
: ({
: (args[] ?? ),
...shared.(flags),
...mode.(flags),
...jq.(flags),
}),
},
: (input, ctx) => {
result = ({ : input. }, ctx);
(result.()) {
result;
}
(result., { : input. });
.(result.);
},
});
Register in CLI:
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:
myapp schema generate
myapp schema diff
MCP Tool
Zod schema with Result return:
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 = (input., input.);
.({ results, : results. });
},
};
Register in server:
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:
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 ());
},
},
]);
ipcServer = (());
ipcServer.( (msg) => {
message = msg { : };
(message.) {
:
{ : , : process.() };
:
healthChecker.();
:
{ : };
}
});
daemon.( () => {
logger.();
ipcServer.();
});
() {
startResult = daemon.();
(startResult.()) {
logger.(, { : startResult. });
process.();
}
ipcServer.();
logger.(, { : () });
}
();
Best Practices
- Handler First — Write handler before adapter (CLI/MCP/API)
- Action Registry First — Add CLI behavior via
defineAction() and mapInput()
- Preset Composition — Prefer shared presets over manual flag parsing
- Schema Drift Guard — Regenerate and diff
.outfitter/surface.lock after CLI changes
- Validate Early — Use
createValidator at handler entry
- Type Errors — List all error types in handler signature
- Context Propagation — Pass context through all handler calls
- Test Handlers — Test handlers directly without transport layer
References