Overview

Create a CLI tool in packages/ to invoke agents defined in .claude/agents/ directory programmatically.

Agent File Structure

.claude/agents/{agent-name}.md:

---
name: my-agent
description: What this agent does
model: sonnet # or haiku, opus
tools:
- Read
- Write
- Bash
---
# Agent Instructions
Your agent's system prompt and behavior goes here.
## Capabilities
- What it can do
- How it should respond

CLI Implementation Approaches

1. Direct Claude CLI Invocation

Terminal window
# One-shot execution
claude --dangerously-skip-permissions -p "$(cat .claude/agents/my-agent.md)"
# With additional prompt
claude -p "$(cat .claude/agents/my-agent.md)\n\nTask: $1"

2. Claude Agent SDK (TypeScript/Node.js)

The Claude Agent SDK provides a high-level interface for building autonomous agents with tool access.

import { query } from "@anthropic-ai/claude-agent-sdk";
import { readFileSync } from "fs";
import matter from "gray-matter";
async function runAgent(agentName: string, task: string) {
const agentPath = `.claude/agents/${agentName}.md`;
const fileContent = readFileSync(agentPath, "utf-8");
const { data: frontmatter, content: systemPrompt } = matter(fileContent);
// Map model shorthand to full model ID
const modelMap: Record<string, string> = {
haiku: "claude-haiku-4-20250514",
sonnet: "claude-sonnet-4-20250514",
opus: "claude-opus-4-20250514",
};
const model = modelMap[frontmatter.model] || "claude-sonnet-4-20250514";
// Run agent using SDK query function
const result = query({
prompt: task,
options: {
model,
systemPrompt,
tools: frontmatter.tools || ["Read", "Write", "Bash"],
permissionMode: "bypassPermissions",
allowDangerouslySkipPermissions: true,
},
});
// Stream through messages and collect result
let output = "";
for await (const message of result) {
if (message.type === "result" && message.subtype === "success") {
output = message.result;
}
}
return output;
}
// Usage: await runAgent("code-reviewer", "Review the auth module")

Key SDK Features:

  • Tool Access: Built-in support for Read, Write, Bash, Glob, Grep, etc.
  • Autonomous Execution: Agent loops, makes decisions, and completes complex tasks
  • Streaming: AsyncGenerator yields messages as they arrive
  • Permission Control: permissionMode for automated or interactive execution

3. Shell Script Wrapper

packages/agent-cli/bin/agent
#!/bin/bash
AGENT_NAME=$1
TASK="${@:2}"
if [ -z "$AGENT_NAME" ]; then
echo "Usage: agent <agent-name> <task>"
exit 1
fi
AGENT_FILE=".claude/agents/${AGENT_NAME}.md"
if [ ! -f "$AGENT_FILE" ]; then
echo "Agent not found: $AGENT_FILE"
exit 1
fi
claude --dangerously-skip-permissions -p "$(cat $AGENT_FILE)
Task: $TASK"

Package Structure

NestJS + nest-commander pattern (consistent with x-cli, google-cli):

packages/agent/
├── package.json
├── tsconfig.json
├── bunfig.toml
├── src/
│ ├── main.ts # CommandFactory entry point
│ ├── app.module.ts # Root module
│ ├── commands/
│ │ ├── index.ts
│ │ ├── list.command.ts # List available agents
│ │ └── run.command.ts # Run agent with task
│ └── agent/
│ ├── agent.module.ts
│ ├── agent.service.ts # Load/parse agent files
│ └── runner.service.ts # Claude Agent SDK invocation

Example package.json

{
"name": "@research/agent",
"version": "1.0.0",
"type": "module",
"bin": {
"agent": "./dist/main.js"
},
"scripts": {
"build": "bun build src/main.ts --outdir dist --target node",
"dev": "bun run src/main.ts"
},
"dependencies": {
"@anthropic-ai/claude-agent-sdk": "^0.1.0",
"@nestjs/common": "^11.0.0",
"@nestjs/core": "^11.0.0",
"nest-commander": "^3.15.0",
"gray-matter": "^4.0.3",
"reflect-metadata": "^0.2.2"
}
}

Usage

Terminal window
# List available agents
bun run agent list
# Run an agent with a task
bun run agent run code-reviewer "Review the changes in src/"
# Interactive mode
bun run agent chat code-reviewer

Reference