create-ai-tool
The create-ai-tool skill provides step-by-step guidance for building a complete AI tool in Rust, covering implementation structure, context setup, toolset wiring, and integration with the ai_toolset framework. Use this when adding a new tool capability to the macro platform that requires domain-specific logic, database access, or service client dependencies.
git clone --depth 1 https://github.com/macro-inc/macro /tmp/create-ai-tool && cp -r /tmp/create-ai-tool/.claude/skills/create-ai-tool ~/.claude/skills/create-ai-toolSKILL.md
# Create AI Tool This skill walks through building a new AI tool from scratch. Before writing any code, read the design guide at `rust/cloud-storage/ai_toolset/TOOL_DESIGN.md` and the framework docs/examples in `rust/cloud-storage/ai_toolset/src/lib.rs`. **IMPORTANT:** Never modify the `rust/cloud-storage/ai_toolset/` crate. It is the framework — you build tools that use it. ## Step 1: Write the tool Decide which domain crate the tool belongs in. Tools live at `rust/cloud-storage/<crate>/src/inbound/toolset/`. Study an existing tool for patterns: - `rust/cloud-storage/documents/src/inbound/toolset/` — tools: `read_content.rs`, `read_metadata.rs`, `create_document.rs` - `rust/cloud-storage/email/src/inbound/toolset/` — tools: `send_email.rs`, `get_thread.rs`, `update_thread_labels.rs` - `rust/cloud-storage/soup/src/inbound/toolset/` — tool: `list_entities.rs` - `rust/cloud-storage/call/src/inbound/toolset/` — call-related tools Each tool is a struct that derives `JsonSchema` and `Deserialize`, with `#[schemars(title = "...", description = "...")]` on the struct and `#[schemars(description = "...")]` on each field. The struct implements `AsyncTool<Context>` from the `ai_toolset` crate. Create a new file for your tool (e.g. `my_tool.rs`), add it as a `mod` in the toolset's `mod.rs`, and wire it into the toolset's `AsyncToolSet::new().add_tool::<...>()` chain. ## Step 2: Create the tool context If your tool needs dependencies (DB connections, service clients) that aren't already in an existing context, define a new context struct in the toolset's `mod.rs`. See `rust/cloud-storage/documents/src/inbound/toolset/mod.rs` for the `DocumentToolContext` pattern. The context must be `Clone` and derivable from the parent `ToolServiceContext` via `FromRef`. If the tool's dependencies are already available in an existing context (e.g. it only needs `Arc<ToolScribe>`), you can use that context directly — no new struct needed. ## Step 3: Add the toolset to `all_tools` in the ai_tools crate Edit `rust/cloud-storage/ai_tools/src/lib.rs`: - Import your toolset function and context type - Add `.add_tool::<YourTool, YourContext>()` or `.add_subtoolset::<YourToolContext>(your_toolset())` to the `all_tools()` function ## Step 4: Add the context to ToolServiceContext Edit `rust/cloud-storage/ai_tools/src/tool_context.rs`: - Add any new type aliases for your service implementations (follow the `Tool*` naming pattern) - Add your tool context field to the `ToolServiceContext` struct - Implement `FromRef<ToolServiceContext>` for your context if needed (or derive it — the struct uses `#[derive(FromRef)]`) ## Step 5: Wire up env vars and service construction Edit `rust/cloud-storage/ai_tools/src/build_context.rs`: - Add any new env vars to the `env_var!` or `maybe_env_var!` blocks - Construct your service/context in `build_tool_service_context_from_env` - Add it to the returned `ToolServiceContext` ## Step 6: Update infra (if new env vars or AWS resources are needed) Edit `infra/packages/shared/src/ai_tools.ts`: - Add new env vars to the `envVars` array in `getAiToolsInfra()` - Add any new secret ARNs, queue ARNs, or bucket ARNs to the respective arrays - Add any new Pulumi stack references needed to resolve the values ## Step 7: Rust checks Run from `rust/cloud-storage/`: ```bash cargo fmt cargo clippy -p ai_tools cargo test -p <your_domain_crate> ``` Fix any warnings or errors before proceeding. ## Step 8: Generate frontend types Run from `js/app/`: ```bash bun gen-tools ``` This builds `rust/cloud-storage/ai_tools/src/bin/gen_tool_schemas.rs`, generates `rust/cloud-storage/ai_tools/schemas/tools.json`, and transpiles the schemas into TypeScript at `js/app/packages/service-clients/service-cognition/generated/tools/`. ## Step 9: Check what frontend UI is needed Run from `js/app/`: ```bash bun check ``` This runs `tsc --noEmit` and will report type errors — specifically, the `toolHandlers` map in `js/app/packages/core/component/AI/component/tool/handler.tsx` will be missing your new tool name. The errors tell you exactly what to implement. ## Step 10: Read existing tool UI for patterns The tool UI components live at `js/app/packages/core/component/AI/component/tool/`. Study existing renderers: - `Search.tsx` — search results rendering - `ReadContent.tsx` / `ReadMetadata.tsx` — document tool UI - `SendEmail.tsx` — email tool UI - `ListEntities.tsx` — list display - `Properties.tsx` — property get/set tools - `ListCallRecords.tsx` / `ReadCallRecord.tsx` — call tools - `BaseTool.tsx` — shared base component Each tool needs a handler object implementing `ToolHandler` (from `ToolRenderer.tsx`) with at minimum a `render` component. Use `createToolRenderer` to create it. ## Step 11: Write the tool UI 1. Create a new component file at `js/app/packages/core/component/AI/component/tool/YourTool.tsx` 2. Export a handler using `createToolRenderer` 3. Register it in `js/app/packages/core/component/AI/component/tool/handler.tsx`: - Import your handler - Add it to the `toolHandlers` map with the key matching your tool's schema title Every tool renderer must show results. Use the expandable dropdown pattern from `Search.tsx` / `ListEntities.tsx`: - Use `BaseTool` (from `BaseTool.tsx`) as the wrapper. It accepts a `response` prop for expandable content. - Create `const [isExpanded, setIsExpanded] = createSignal(false)` to track open/closed state. - Access the response via `ctx.response?.data` (typed from the generated schema). - Show a status summary (e.g. hit count, "No Results") on the right side of the tool row. - Render a `CaretRight` toggle button (`@icon/regular/caret-right.svg?component-solid`) that rotates 90deg when expanded. - Pass the expanded content into `BaseTool`'s `response` prop, gated on `isExpanded()`. For tools that return text/string results (not entity lists), render the response string in a `<pre>` or similar block inside the `response` prop. The key point: the respon
Find all open Dependabot alerts for this repo and create a plan to resolve them using the appropriate package manager overrides (pnpm, bun, npm, cargo).
Dump clean Postgres schema to a file and copy path to clipboard.
Quality gate. 5 parallel agents review changes. All must pass.
Upgrade an AI chat model (fast or good) across backend and frontend.
Launch local Swagger UI for a service's OpenAPI spec
Validate Rust work after substantial Rust code changes by running `just check`, `just clippy`, then `just format`. Use before the final response after a significant Rust implementation or cleanup task; batch edits first instead of running after every small change.
Create SQLx migration files with `sqlx migrate add <name>`. Use when asked to add, create, or generate a sqlx/sqlx-cli database migration.