Add a capability
SkillWeb & browsingLearn how one contract and its handler become a command, HTTP route, MCP tool, browser RPC, and sandbox call.
Use Add a capability in Claude, ChatGPT or Ahel Desktop
Free. Sign in, add Add a capability and connect your AI. About a minute.
Also: Claude Code · Cursor · Codex
Then ask your AI: use the Add a capability skill
Details
Instructions available. Your AI can read the instructions. Execution depends on the setup they require.
Account requirements not reviewed. Check the skill instructions before use; ahel provides instructions and does not run this skill.
No other account needed.
Add ahel to your AI once: Claude, ChatGPT, Cursor, Claude Code or Codex. Then ask it to use this.
What this skill tells your AI
The instructions your AI receives, as published by joelhooks/rat-stack in skills/add-a-capability/SKILL.md and read by ahel’s review.
A capability is one named action with a contract and a server-side handler. The contract defines its name, input, output, failure, and metadata. The handler implements the action.
Define the shared contract once. Implement its handler where the data and infrastructure live. Do not write separate business logic for each interface.
Effect describes work with typed failures and required dependencies. Schema defines accepted values and their runtime checks. A service is a named interface for one job. A Layer builds services and supplies their construction dependencies.
1. Define the contract
Put cross-process contracts in packages/core/src/contracts.ts and import defineContract from @rat-stack/capability/contract.
- Define the input, output, and expected failure schemas.
- Give the contract a stable name and short description.
- Use
Schema.Structfor the input. - Set honest annotations such as
readOnly,idempotent,destructive, andopenWorld. - Set
needsApproval: truewhen the action needs approval.implementadds theApprovalrequirement andApprovalDeniedfailure; the handler itself declares only the contract's failures.
export const doThingContract = defineContract("doThing", {
annotations: { idempotent: true, readOnly: true },
description: "Do one concrete thing",
failure: ThingError,
input: Schema.Struct({ id: Schema.String }),
output: ThingResult,
});
Schemas must encode and decode without services. Keep server dependencies out of the contract module.
2. Implement it
Import implement from @rat-stack/capability/implement. Put the handler next to its service or server-side data. Keep it small; put real work in a service or lifecycle machine.
export const inspectFile = implement(inspectFileContract, ({ path }) =>
runInspectMachine(path)
);
The handler input comes from the contract. Its Effect requirements and expected failures stay typed. implement supplies the approval gate when the contract requires it.
If the action needs a service, copy packages/core/src/file-inspector.ts. Use a Context.Service class. Capture dependencies in make and keep static layer beside it.
3. Register it
Add the implementation to the capabilities tuple consumed by its composition root. For the CLI example, that tuple lives in packages/core/src/inspect-file.ts:
import { doThing } from "./do-thing.js";
export const capabilities = [inspectFile, doThing] as const;
The order is public. The tuple feeds the projections and code-mode declarations. A projection builds a callable interface from capabilities.
4. Project the implementation
The CLI, HTTP, MCP, RPC, and code-mode projections take implemented capabilities. They read names, schemas, annotations, and approval settings from capability.contract.
- HTTP adds
POST /doThingand updates OpenAPI. - MCP (Model Context Protocol) adds a
doThingtool with the same schemas and flags. - The sandbox catalogue adds
tools.doThing(input). - Sandbox calls decode input, run the same handler, then encode the result.
toCommand builds one CLI command from the registered tuple. Open apps/cli/src/command.ts only when the command needs a positional argument, custom renderer, or alias. Use name, positional, and render for those cases. toCommand adds --json; do not parse fields again or call the service directly.
RPC means remote procedure call: a client calls a named server operation. Browser clients import contracts from @rat-stack/core/contracts and toRpcGroup from @rat-stack/capability/rpc-group; they do not import a handler or the server-side toRpc projection.
5. Test it
Use @effect/vitest and run Effects with it.effect or it.layer. Do not call Effect.run* or ManagedRuntime.make in tests.
- Test the handler's output, expected failures, annotations, and approval behavior.
- Add projection tests when the projection changes. Check that client RPC groups can be built from contracts alone.
- Add a command-line end-to-end test when the new capability changes the command tree or a public interface.
Use Schema.encodeEffect to check encoded results and Effect.flip to inspect expected errors.
6. Finish
pnpm turbo run check test build
Fix failures. Do not loosen the checks, hooks, or pinned versions. These checks and hooks form the fence that rejects prohibited code and shortcuts.
Signals
- GitHub stars
- 95
- Forks
- 5
- Last commit
- Oct 2026
Advanced
- Item type
- skill
- Key
add-a-capability- Source
- github.com/joelhooks/rat-stack
github.com/joelhooks/rat-stack
Related picks
Skill · thedaviddias
The pick for JavaScriptmodern-javascript-patterns
Skill · wshobson
The pick for JavaScriptsetup-ts-deep-modules
Skill · mattpocock
The pick for TypeScripttypescript-pro
Skill · jeffallan
The pick for TypeScripthandsontable-playwright-e2e
Skill · handsontable
The pick for End-to-end testingmstar-e2e
Skill · btspoony
The pick for End-to-end testing