From routecraft-skills
Authors new Routecraft capabilities like workflows, automations, MCP tools, webhook handlers, or scheduled jobs. Use to compose adapters into typed pipelines with sources, transforms, and destinations.
npx claudepluginhub routecraftjs/routecraft --plugin routecraft-skillsThis skill is limited to using the following tools:
A capability is the user-facing unit of automation in Routecraft. It is a typed pipeline that starts at a source (`from(...)`), flows through operations (`transform`, `enrich`, `filter`, `validate`, `split`, `aggregate`, `choice`, `process`, `tap`), and lands at one or more destinations (`to`). The codebase calls these "routes" internally because that is what the `craft()` builder returns; in u...
Suggests manual /compact at logical task boundaries in long Claude Code sessions and multi-phase tasks to avoid arbitrary auto-compaction losses.
Share bugs, ideas, or general feedback.
A capability is the user-facing unit of automation in Routecraft. It is a typed pipeline that starts at a source (from(...)), flows through operations (transform, enrich, filter, validate, split, aggregate, choice, process, tap), and lands at one or more destinations (to). The codebase calls these "routes" internally because that is what the craft() builder returns; in user-facing language and in the docs they are capabilities.
You are writing this capability for the user. Treat the linter (bun run lint) as authoritative once you have written the code: if it disagrees, the linter wins.
Use this skill when the user asks to:
If the user only needs a small utility function with no I/O and no orchestration, that does not need to be a capability. If it crosses systems, has retry semantics, or should be discoverable, it does.
Confirm answers to these questions before writing. Ask the user only the ones that are not already obvious from context.
input and output if the user knows what they wantsplit then aggregate)? Branch (choice)? Conditional drop (filter)? Schema check (validate)?.batch({...}) before .from(...).error(...) is built in; others are coming)Read reference/examples-index.md and pick the row that best matches the answers above. The index maps intent to a public doc page and the closest existing capability on GitHub.
Then, in this order:
WebFetch the linked doc page (raw markdown variant on routecraft.dev/raw/docs/...)WebFetch the linked example file on GitHub (use the raw.githubusercontent.com URL)Read examples/src/<closest>.ts end to endDo not write from memory. Capabilities look small but the operator order, schema placement, and direct-call id linking are easy to get wrong without a reference.
The DSL is fluent and mostly self-documenting once you have an example open. Common shape:
import { craft, simple, http, log } from "@routecraft/routecraft";
import { z } from "zod";
const Input = z.object({ /* ... */ });
type Input = z.infer<typeof Input>;
export default craft()
.id("my-capability") // required for direct-call routing
.title("Human-readable title") // surfaced in MCP tools and the TUI
.description("What this does") // surfaced in MCP tools
.input({ body: Input }) // typed and validated at the boundary
.from<Input>(/* source */)
// operations
.to(/* destination */);
Authoring rules to keep in mind:
.id(), .title(), .description(), .input(), .output(), .error(), .batch() come before .from(...). Once you call .from(...), you are in the pipeline and metadata methods no longer apply.from<Input>(...) so the operations downstream stay typed without casts.tap(destination).to(dest) -- send and ignore the destination's result body (terminal or pass-through with original body).enrich(dest) -- merge the destination's result into the body.tap(dest) -- fire and forget; do not wait, do not change the body.split() you can chain operations on each item; close with .aggregate() to fan back in.error(handler) at route scope catches anything that escapes the pipeline; at step scope, attach it to a single step@standard-schema/spec). Zod and Valibot both work because both implement Standard Schema. Use @routecraft/routecraft's helpers in shared code, not Zod directlyTests live in the package's test/ directory (packages/<pkg>/test/<name>.test.ts if you are inside this monorepo, or your project's existing test/ directory otherwise). Do not colocate tests next to source. Every test must have JSDoc with @case, @preconditions, @expectedResult. Use @routecraft/testing and follow the canonical lifecycle:
import { describe, it, expect, afterEach } from "vitest";
import { testContext, type TestContext } from "@routecraft/testing";
import myCapability from "../src/my-capability";
describe("my-capability", () => {
let t: TestContext | undefined;
afterEach(async () => {
if (t) await t.stop();
t = undefined;
});
/**
* @case happy path
* @preconditions a valid input body
* @expectedResult the capability completes without errors
*/
it("transforms the body and sends to destination", async () => {
t = await testContext().routes([myCapability]).build();
await t.test();
expect(t.errors).toHaveLength(0);
});
});
Errors thrown inside handlers are caught at the boundary and surfaced on t.errors; do not expect t.test() to reject. Full test pattern: https://routecraft.dev/raw/docs/introduction/testing.md
Run, in this order, until each is clean:
bun run typecheck
bun run lint
bun run test
Use bun run <script> (not bun <script>) so Bun invokes the package.json script rather than its built-in test runner. If bun run lint complains, fix the capability rather than silencing the rule. The linter encodes Routecraft's authoring rules. If it does not catch something the user expected it to catch, that is a follow-up for the lint package.