cc-claw 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +246 -0
- package/dist/agents/mcp-server.js +342 -0
- package/dist/cli.js +14450 -0
- package/package.json +63 -0
- package/skills/agent-claude.md +42 -0
- package/skills/agent-codex.md +39 -0
- package/skills/agent-gemini.md +40 -0
- package/skills/onboard-cli.md +156 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Jacob Ben David
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,246 @@
|
|
|
1
|
+
# CC-Claw — Coding CLI Claw
|
|
2
|
+
|
|
3
|
+
<p align="center">
|
|
4
|
+
<img src="assets/cc_claw.png" alt="CC-Claw Logo" width="300" />
|
|
5
|
+
</p>
|
|
6
|
+
|
|
7
|
+
Your personal AI assistant on Telegram, powered by the best coding CLIs. CC-Claw uses **Claude Code**, **Gemini CLI**, and **Codex** as its backend intelligence — giving you a full personal and productivity assistant accessible from any device.
|
|
8
|
+
|
|
9
|
+
Send text, voice messages, photos, documents, or videos. The agent handles coding, research, email, scheduling, content creation, document analysis, and any task you need. Switch backends on the fly with `/claude`, `/gemini`, or `/codex`. Spawn sub-agents across different CLIs to work in parallel.
|
|
10
|
+
|
|
11
|
+
## Supported Backends
|
|
12
|
+
|
|
13
|
+
| Backend | CLI | Models |
|
|
14
|
+
|---------|-----|--------|
|
|
15
|
+
| **Claude** | `claude` | Opus 4.6, Sonnet 4.6, Haiku 4.5 |
|
|
16
|
+
| **Gemini** | `gemini` | 3.1 Pro Preview, 3 Flash |
|
|
17
|
+
| **Codex** | `codex` | GPT-5.4, GPT-5.3 Codex, GPT-5.2 Codex |
|
|
18
|
+
|
|
19
|
+
Each backend supports per-model thinking/reasoning level control, session resumption, and configurable permission modes.
|
|
20
|
+
|
|
21
|
+
## Prerequisites
|
|
22
|
+
|
|
23
|
+
- **Node.js 20+**
|
|
24
|
+
- **At least one coding CLI** installed:
|
|
25
|
+
- Claude Code CLI — `npm install -g @anthropic-ai/claude-code`
|
|
26
|
+
- Gemini CLI — `npm install -g @google/gemini-cli` (or via Homebrew)
|
|
27
|
+
- Codex CLI — `npm install -g @openai/codex` (or via Homebrew)
|
|
28
|
+
- **Telegram bot token** from [@BotFather](https://t.me/BotFather)
|
|
29
|
+
|
|
30
|
+
Backend-specific setup (e.g., Vertex AI for Claude, Google Cloud for Gemini, ChatGPT subscription for Codex) is handled during `cc-claw setup`.
|
|
31
|
+
|
|
32
|
+
## Install
|
|
33
|
+
|
|
34
|
+
```bash
|
|
35
|
+
npm install -g cc-claw
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
## Setup
|
|
39
|
+
|
|
40
|
+
```bash
|
|
41
|
+
cc-claw setup
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
The interactive wizard walks you through:
|
|
45
|
+
|
|
46
|
+
1. Creating a Telegram bot and pasting the token
|
|
47
|
+
2. Getting your Telegram chat ID (for security — only you can use the bot)
|
|
48
|
+
3. Selecting your default backend and configuring its credentials
|
|
49
|
+
4. Optional features: voice (Groq STT + ElevenLabs TTS), web dashboard, video analysis (Gemini)
|
|
50
|
+
5. Installing as a background service
|
|
51
|
+
|
|
52
|
+
Configuration is saved to `~/.cc-claw/.env`. Data is organized under `~/.cc-claw/`:
|
|
53
|
+
|
|
54
|
+
```
|
|
55
|
+
~/.cc-claw/
|
|
56
|
+
.env config (Telegram token, backend credentials)
|
|
57
|
+
data/cc-claw.db SQLite database
|
|
58
|
+
logs/cc-claw.log stdout log
|
|
59
|
+
logs/cc-claw.error.log stderr log
|
|
60
|
+
runners/ config-driven CLI runner definitions (JSON)
|
|
61
|
+
workspace/
|
|
62
|
+
SOUL.md agent identity and personality (customizable)
|
|
63
|
+
USER.md your profile and preferences
|
|
64
|
+
CLAUDE.md / GEMINI.md native CLI awareness notes (auto-generated)
|
|
65
|
+
context/ on-demand context files
|
|
66
|
+
skills/ skill repository (available to all backends)
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
## Run
|
|
70
|
+
|
|
71
|
+
```bash
|
|
72
|
+
# Foreground (see output in terminal)
|
|
73
|
+
cc-claw start
|
|
74
|
+
|
|
75
|
+
# Or install as a background service (launchd on macOS, systemd on Linux)
|
|
76
|
+
cc-claw install
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
## CLI
|
|
80
|
+
|
|
81
|
+
CC-Claw has a comprehensive CLI with 50+ commands. Every command supports `--json` for AI agents and scripts.
|
|
82
|
+
|
|
83
|
+
```bash
|
|
84
|
+
cc-claw status # System status (backend, model, usage)
|
|
85
|
+
cc-claw doctor # Health checks + auto-fix
|
|
86
|
+
cc-claw logs -f # Follow daemon logs
|
|
87
|
+
cc-claw chat send "hello" # Talk to the AI from terminal
|
|
88
|
+
cc-claw tui # Interactive terminal chat
|
|
89
|
+
cc-claw backend set gemini # Switch backend
|
|
90
|
+
cc-claw model list # List models
|
|
91
|
+
cc-claw cron list # List scheduled jobs
|
|
92
|
+
cc-claw agents list # List active sub-agents
|
|
93
|
+
cc-claw agents spawn --runner claude --task "review code"
|
|
94
|
+
cc-claw db stats # Database health
|
|
95
|
+
cc-claw completion --shell zsh # Shell completions
|
|
96
|
+
cc-claw --ai # Generate AI skill file
|
|
97
|
+
```
|
|
98
|
+
|
|
99
|
+
| Command Group | Description |
|
|
100
|
+
|---------------|-------------|
|
|
101
|
+
| `service` | start, stop, restart, status, install, uninstall |
|
|
102
|
+
| `status` / `doctor` / `logs` | Diagnostics and monitoring |
|
|
103
|
+
| `chat` / `tui` | Talk to the AI from terminal |
|
|
104
|
+
| `backend` / `model` / `thinking` | Backend and model management |
|
|
105
|
+
| `memory` | list, search, add, forget, history |
|
|
106
|
+
| `cron` / `schedule` | Scheduler management (create, cancel, pause, resume, run) |
|
|
107
|
+
| `agents` / `tasks` / `runners` | Agent orchestration |
|
|
108
|
+
| `config` | Runtime and static configuration |
|
|
109
|
+
| `usage` / `cost` | Cost tracking and usage limits |
|
|
110
|
+
| `permissions` / `tools` / `verbose` | Permission and tool management |
|
|
111
|
+
| `skills` / `mcps` | Skills and MCP servers |
|
|
112
|
+
| `db` | Database stats, backup, prune |
|
|
113
|
+
| `completion` | Shell completions (bash/zsh/fish) |
|
|
114
|
+
| `setup` | Interactive configuration wizard |
|
|
115
|
+
|
|
116
|
+
Read commands work offline (direct DB). Write commands require the daemon running.
|
|
117
|
+
|
|
118
|
+
### Telegram Commands
|
|
119
|
+
|
|
120
|
+
Once running, send these to your bot:
|
|
121
|
+
|
|
122
|
+
| Command | Description |
|
|
123
|
+
|---------|-------------|
|
|
124
|
+
| `/help` | Show available commands |
|
|
125
|
+
| `/backend` | Switch AI backend (Claude / Gemini / Codex) |
|
|
126
|
+
| `/model` | Switch model for active backend (with thinking level picker) |
|
|
127
|
+
| `/summarizer` | Configure session summarization model |
|
|
128
|
+
| `/status` | Backend, model, thinking level, session, context usage |
|
|
129
|
+
| `/cost` | Estimated API cost for this chat (per-model breakdown) |
|
|
130
|
+
| `/cost all` | All-time cost breakdown by model across all chats |
|
|
131
|
+
| `/usage` | Usage per backend with limit warnings |
|
|
132
|
+
| `/limits` | Configure usage limits per backend |
|
|
133
|
+
| `/newchat` | Start a fresh conversation |
|
|
134
|
+
| `/cwd <path>` | Set the working directory |
|
|
135
|
+
| `/memory` | List stored memories |
|
|
136
|
+
| `/forget <keyword>` | Remove a memory |
|
|
137
|
+
| `/voice` | Toggle voice responses |
|
|
138
|
+
| `/cron <desc>` | Schedule a task (or `/schedule`) |
|
|
139
|
+
| `/cron` | List scheduled jobs (or `/jobs`) |
|
|
140
|
+
| `/cron cancel <id>` | Cancel a scheduled job |
|
|
141
|
+
| `/cron pause <id>` | Pause a job |
|
|
142
|
+
| `/cron resume <id>` | Resume a job |
|
|
143
|
+
| `/cron run <id>` | Trigger a job now |
|
|
144
|
+
| `/cron runs [id]` | View run history |
|
|
145
|
+
| `/cron edit <id>` | Edit a job |
|
|
146
|
+
| `/cron health` | Scheduler health |
|
|
147
|
+
| `/skills` | List skills from all backends |
|
|
148
|
+
| `/skill-install <url>` | Install a skill from GitHub |
|
|
149
|
+
| `/setup-profile` | Set up your user profile |
|
|
150
|
+
| `/chats` | List authorized chats and aliases |
|
|
151
|
+
| `/heartbeat` | Proactive awareness (on/off/interval/hours) |
|
|
152
|
+
| `/history` | List recent session summaries |
|
|
153
|
+
| `/stop` | Cancel the current task |
|
|
154
|
+
| `/tools` | Configure which tools the agent can use |
|
|
155
|
+
| `/permissions` | Switch permission mode |
|
|
156
|
+
| `/verbose` | Tool visibility (off / normal / verbose) |
|
|
157
|
+
| `/claude` `/gemini` `/codex` | Quick backend switch |
|
|
158
|
+
| `/agents` | List active sub-agents |
|
|
159
|
+
| `/tasks` | Show task board for current orchestration |
|
|
160
|
+
| `/stopagent <id>` | Cancel a specific sub-agent |
|
|
161
|
+
| `/stopall` | Cancel all sub-agents |
|
|
162
|
+
| `/runners` | List registered CLI runners |
|
|
163
|
+
| `/mcp` | List all MCP servers (central + backends) |
|
|
164
|
+
|
|
165
|
+
### Permission Modes
|
|
166
|
+
|
|
167
|
+
- **yolo** — Full autopilot, all tools auto-approved
|
|
168
|
+
- **safe** — Only user-configured tools allowed
|
|
169
|
+
- **readonly** — Read, Glob, Grep only
|
|
170
|
+
- **plan** — Plan only, no execution
|
|
171
|
+
|
|
172
|
+
## Architecture
|
|
173
|
+
|
|
174
|
+
```
|
|
175
|
+
Telegram --> TelegramChannel --> router (handleMessage)
|
|
176
|
+
|-- command --> handleCommand
|
|
177
|
+
|-- voice --> Groq Whisper STT --> askAgent
|
|
178
|
+
|-- photo/document/video --> handleMedia --> askAgent
|
|
179
|
+
'-- text --> askAgent --> sendResponse
|
|
180
|
+
'--> [SEND_FILE:/path] --> channel.sendFile
|
|
181
|
+
```
|
|
182
|
+
|
|
183
|
+
The core `askAgent()` function delegates to the active backend's adapter, which handles CLI arg construction, NDJSON parsing, and env setup. All state is persisted in SQLite with FTS5 full-text search.
|
|
184
|
+
|
|
185
|
+
### Agent Orchestration
|
|
186
|
+
|
|
187
|
+
CC-Claw can spawn and manage multiple sub-agents across different coding CLIs simultaneously:
|
|
188
|
+
|
|
189
|
+
```
|
|
190
|
+
User: "Have Claude review the code while Gemini researches the API docs"
|
|
191
|
+
→ Main agent calls spawn_agent(runner: "claude", task: "Review...", role: "reviewer")
|
|
192
|
+
→ Main agent calls spawn_agent(runner: "gemini", task: "Research...", role: "worker")
|
|
193
|
+
→ Sub-agents communicate via inbox, share data via whiteboard
|
|
194
|
+
→ Results flow back to the main agent for synthesis
|
|
195
|
+
```
|
|
196
|
+
|
|
197
|
+
Sub-agents support task dependencies, role-based prompts (worker/reviewer/planner), idle detection, timeout enforcement, and per-orchestration cost tracking. See `docs/plans/2026-03-09-agent-orchestration-design.md` for the full design.
|
|
198
|
+
|
|
199
|
+
### MCP Management
|
|
200
|
+
|
|
201
|
+
MCP (Model Context Protocol) servers are managed centrally. `/mcp` shows all MCPs across all backends. Each backend's native MCPs are available when running through CC-Claw. The orchestrator MCP server gives agents tools for spawning sub-agents, messaging, and task management.
|
|
202
|
+
|
|
203
|
+
### Adding a CLI Runner
|
|
204
|
+
|
|
205
|
+
Users can add any coding CLI as a sub-agent runner without code changes:
|
|
206
|
+
|
|
207
|
+
1. Tell CC-Claw: *"Onboard cursor CLI as a sub-agent"*
|
|
208
|
+
2. The agent discovers capabilities, tests the CLI, and creates a JSON config at `~/.cc-claw/runners/`
|
|
209
|
+
3. The new runner is immediately available via `/runners`
|
|
210
|
+
|
|
211
|
+
For developers: create a JSON runner config (see `src/agents/runners/config-types.ts`) or a TypeScript adapter.
|
|
212
|
+
|
|
213
|
+
### Adding a Channel
|
|
214
|
+
|
|
215
|
+
1. Create `src/channels/<name>.ts` implementing the `Channel` interface
|
|
216
|
+
2. Register it in `src/index.ts`
|
|
217
|
+
|
|
218
|
+
The router handles all message logic — the channel only delivers and sends messages.
|
|
219
|
+
|
|
220
|
+
## Environment Variables
|
|
221
|
+
|
|
222
|
+
Set in `~/.cc-claw/.env` (created by `cc-claw setup`):
|
|
223
|
+
|
|
224
|
+
| Variable | Required | Description |
|
|
225
|
+
|----------|----------|-------------|
|
|
226
|
+
| `TELEGRAM_BOT_TOKEN` | Yes | From @BotFather |
|
|
227
|
+
| `ALLOWED_CHAT_ID` | Yes | Comma-separated chat IDs (your user ID + group IDs) |
|
|
228
|
+
| `CLAUDE_CODE_USE_VERTEX` | For Claude | Set to `1` |
|
|
229
|
+
| `CLOUD_ML_REGION` | For Claude | e.g. `us-east5` |
|
|
230
|
+
| `ANTHROPIC_VERTEX_PROJECT_ID` | For Claude | Your GCP project |
|
|
231
|
+
| `GROQ_API_KEY` | No | Voice transcription |
|
|
232
|
+
| `ELEVENLABS_API_KEY` | No | Voice replies (TTS) |
|
|
233
|
+
| `GEMINI_API_KEY` | No | Video analysis |
|
|
234
|
+
| `DASHBOARD_ENABLED` | No | Set to `1` for web dashboard |
|
|
235
|
+
| `DASHBOARD_TOKEN` | No | Fixed auth token for dashboard API (auto-generated if not set) |
|
|
236
|
+
| `CLAUDE_CODE_EXECUTABLE` | No | Path to claude CLI (auto-detected) |
|
|
237
|
+
| `GEMINI_CLI_EXECUTABLE` | No | Path to gemini CLI (auto-detected) |
|
|
238
|
+
| `CODEX_EXECUTABLE` | No | Path to codex CLI (auto-detected) |
|
|
239
|
+
| `EMBEDDING_PROVIDER` | No | Embedding provider: `ollama` (default), `gemini`, `openai`, `off` |
|
|
240
|
+
| `MEMORY_VECTOR_WEIGHT` | No | Vector vs keyword weight for memory search (default: `0.7`) |
|
|
241
|
+
| `OLLAMA_URL` | No | Ollama server URL (default: `http://localhost:11434`) |
|
|
242
|
+
| `CC_CLAW_HOME` | No | Config dir (default: `~/.cc-claw`) |
|
|
243
|
+
|
|
244
|
+
## License
|
|
245
|
+
|
|
246
|
+
MIT
|
|
@@ -0,0 +1,342 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
// src/agents/mcp-server.ts
|
|
4
|
+
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
5
|
+
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
|
|
6
|
+
import { z } from "zod";
|
|
7
|
+
var BASE_URL = `http://127.0.0.1:${process.env.CC_CLAW_PORT ?? "3141"}`;
|
|
8
|
+
var CHAT_ID = process.env.CC_CLAW_CHAT_ID ?? "";
|
|
9
|
+
var AGENT_ID = process.env.CC_CLAW_AGENT_ID ?? "main";
|
|
10
|
+
var DASHBOARD_TOKEN = process.env.CC_CLAW_DASHBOARD_TOKEN ?? "";
|
|
11
|
+
var IS_SUB_AGENT = AGENT_ID !== "main";
|
|
12
|
+
async function callApi(path, body) {
|
|
13
|
+
const url = `${BASE_URL}${path}`;
|
|
14
|
+
const headers = { "Content-Type": "application/json" };
|
|
15
|
+
if (DASHBOARD_TOKEN) {
|
|
16
|
+
headers["Authorization"] = `Bearer ${DASHBOARD_TOKEN}`;
|
|
17
|
+
}
|
|
18
|
+
const opts = body ? { method: "POST", headers, body: JSON.stringify(body) } : { method: "GET", headers };
|
|
19
|
+
const res = await fetch(url, opts);
|
|
20
|
+
if (!res.ok) {
|
|
21
|
+
const text = await res.text();
|
|
22
|
+
throw new Error(`API error ${res.status}: ${text}`);
|
|
23
|
+
}
|
|
24
|
+
return res.json();
|
|
25
|
+
}
|
|
26
|
+
var server = new McpServer({
|
|
27
|
+
name: "cc-claw-orchestrator",
|
|
28
|
+
version: "1.0.0"
|
|
29
|
+
});
|
|
30
|
+
if (!IS_SUB_AGENT) {
|
|
31
|
+
server.tool(
|
|
32
|
+
"spawn_agent",
|
|
33
|
+
`Spawn a sub-agent using a specific CLI runner to execute a task.
|
|
34
|
+
|
|
35
|
+
CRITICAL \u2014 Path verification:
|
|
36
|
+
Before calling this tool with a cwd, you MUST verify the path exists using your file system tools (Glob, Read, ls, etc.).
|
|
37
|
+
User-provided paths are often approximate (wrong name, missing suffix, typo). Always confirm the exact path yourself.
|
|
38
|
+
A non-existent cwd will cause the agent to fail immediately. Never pass an unverified path.
|
|
39
|
+
|
|
40
|
+
Typical workflow:
|
|
41
|
+
1. User says "scan the foo-bar folder under dev_projects"
|
|
42
|
+
2. You run: ls /Users/.../dev_projects/ (or Glob) to find the actual folder name
|
|
43
|
+
3. You confirm the exact path exists (e.g., it might be foo-bars, foobar, foo_bar, etc.)
|
|
44
|
+
4. Only then call spawn_agent with the verified cwd`,
|
|
45
|
+
{
|
|
46
|
+
runner: z.string().describe("CLI runner ID (e.g., 'claude', 'gemini', 'codex', 'opencode')"),
|
|
47
|
+
task: z.string().describe("Task description for the sub-agent"),
|
|
48
|
+
name: z.string().optional().describe("Human-readable agent name (e.g., 'security-reviewer', 'linkedin-writer')"),
|
|
49
|
+
description: z.string().optional().describe("Short description of what this agent does"),
|
|
50
|
+
model: z.string().optional().describe("Model override for the sub-agent"),
|
|
51
|
+
skills: z.array(z.string()).optional().describe("Skills to inject into the sub-agent"),
|
|
52
|
+
permMode: z.string().optional().describe("Permission mode: yolo, safe, readonly, plan (defaults to chat's mode)"),
|
|
53
|
+
role: z.string().optional().describe("Agent role. Presets: worker, reviewer, planner, researcher, writer, analyst, debugger, critic, synthesizer. Custom roles accepted as labels."),
|
|
54
|
+
persona: z.string().optional().describe("Custom system prompt for the agent. Replaces the role's default prompt while preserving orchestrator tools and task."),
|
|
55
|
+
allowedTools: z.array(z.string()).optional().describe("Restrict agent to specific tools (Claude-only). E.g., ['Read', 'Grep', 'Glob']"),
|
|
56
|
+
maxRuntimeMs: z.number().optional().describe("Max runtime in ms before timeout (default: 600000 = 10 min)"),
|
|
57
|
+
summarizeResult: z.boolean().optional().describe("Summarize agent output before posting to inbox"),
|
|
58
|
+
mcps: z.array(z.string()).optional().describe("MCP servers to inject into the sub-agent"),
|
|
59
|
+
cwd: z.string().optional().describe("Working directory for the sub-agent. MUST be a verified, existing absolute path \u2014 check it before calling."),
|
|
60
|
+
template: z.string().optional().describe("Name of an agent template from ~/.cc-claw/agents/ to use as defaults. Call list_templates to discover available templates. Call-time params override template defaults.")
|
|
61
|
+
},
|
|
62
|
+
async (params) => {
|
|
63
|
+
try {
|
|
64
|
+
const result = await callApi("/api/orchestrator/spawn", { chatId: CHAT_ID, ...params });
|
|
65
|
+
const status = result.queued ? "queued (at capacity, will start when a slot opens)" : "spawning";
|
|
66
|
+
return {
|
|
67
|
+
content: [{ type: "text", text: `Agent ${result.agentId} \u2014 ${status}` }]
|
|
68
|
+
};
|
|
69
|
+
} catch (err) {
|
|
70
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
71
|
+
return {
|
|
72
|
+
content: [{ type: "text", text: `spawn_agent failed: ${msg}` }],
|
|
73
|
+
isError: true
|
|
74
|
+
};
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
);
|
|
78
|
+
server.tool(
|
|
79
|
+
"list_templates",
|
|
80
|
+
"List available agent templates from ~/.cc-claw/agents/. Use a template name with spawn_agent's template parameter to spawn a pre-configured agent.",
|
|
81
|
+
{},
|
|
82
|
+
async () => {
|
|
83
|
+
const templates = await callApi("/api/orchestrator/list-templates");
|
|
84
|
+
if (!templates.length) {
|
|
85
|
+
return { content: [{ type: "text", text: "No agent templates found. Create .md files in ~/.cc-claw/agents/ with YAML frontmatter." }] };
|
|
86
|
+
}
|
|
87
|
+
const lines = templates.map(
|
|
88
|
+
(t) => `\u2022 **${t.name}**${t.description ? ` \u2014 ${t.description}` : ""}${t.runner ? ` (runner: ${t.runner})` : ""}${t.model ? ` [${t.model}]` : ""}`
|
|
89
|
+
);
|
|
90
|
+
return { content: [{ type: "text", text: lines.join("\n") }] };
|
|
91
|
+
}
|
|
92
|
+
);
|
|
93
|
+
server.tool(
|
|
94
|
+
"cancel_agent",
|
|
95
|
+
"Cancel a specific sub-agent",
|
|
96
|
+
{
|
|
97
|
+
agentId: z.string().describe("The agent ID to cancel"),
|
|
98
|
+
reason: z.string().optional().describe("Reason for cancellation")
|
|
99
|
+
},
|
|
100
|
+
async ({ agentId, reason }) => {
|
|
101
|
+
const result = await callApi("/api/orchestrator/cancel", { agentId, reason });
|
|
102
|
+
return {
|
|
103
|
+
content: [{ type: "text", text: result.success ? `Agent ${agentId} cancelled.` : `Agent ${agentId} not found or already completed.` }]
|
|
104
|
+
};
|
|
105
|
+
}
|
|
106
|
+
);
|
|
107
|
+
server.tool(
|
|
108
|
+
"create_task",
|
|
109
|
+
"Add a task to the shared task list",
|
|
110
|
+
{
|
|
111
|
+
subject: z.string().describe("Short imperative title for the task"),
|
|
112
|
+
description: z.string().describe("Detailed task requirements"),
|
|
113
|
+
assignee: z.string().optional().describe("Agent ID to assign the task to"),
|
|
114
|
+
blockedBy: z.array(z.number()).optional().describe("Task IDs that must complete before this task can start")
|
|
115
|
+
},
|
|
116
|
+
async (params) => {
|
|
117
|
+
const result = await callApi("/api/orchestrator/create-task", {
|
|
118
|
+
chatId: CHAT_ID,
|
|
119
|
+
task: { ...params, createdBy: AGENT_ID }
|
|
120
|
+
});
|
|
121
|
+
return {
|
|
122
|
+
content: [{ type: "text", text: `Task #${result.taskId} created.` }]
|
|
123
|
+
};
|
|
124
|
+
}
|
|
125
|
+
);
|
|
126
|
+
}
|
|
127
|
+
server.tool(
|
|
128
|
+
"list_agents",
|
|
129
|
+
"List all active and queued sub-agents",
|
|
130
|
+
{},
|
|
131
|
+
async () => {
|
|
132
|
+
const agents = await callApi("/api/agents");
|
|
133
|
+
if (!agents.length) {
|
|
134
|
+
return { content: [{ type: "text", text: "No active agents." }] };
|
|
135
|
+
}
|
|
136
|
+
const lines = agents.map((a) => {
|
|
137
|
+
const label = a.name ?? a.id.slice(0, 8);
|
|
138
|
+
return `\u2022 ${label} (${a.runnerId}) \u2014 ${a.status}${a.task ? ` \u2192 ${a.task.slice(0, 80)}` : ""}`;
|
|
139
|
+
});
|
|
140
|
+
return { content: [{ type: "text", text: lines.join("\n") }] };
|
|
141
|
+
}
|
|
142
|
+
);
|
|
143
|
+
server.tool(
|
|
144
|
+
"check_agent",
|
|
145
|
+
"Get detailed status of a specific agent",
|
|
146
|
+
{
|
|
147
|
+
agentId: z.string().describe("The agent ID to check")
|
|
148
|
+
},
|
|
149
|
+
async ({ agentId }) => {
|
|
150
|
+
const agent = await callApi("/api/orchestrator/check-agent", { agentId });
|
|
151
|
+
if (!agent) {
|
|
152
|
+
return { content: [{ type: "text", text: `Agent ${agentId} not found.` }] };
|
|
153
|
+
}
|
|
154
|
+
return {
|
|
155
|
+
content: [{
|
|
156
|
+
type: "text",
|
|
157
|
+
text: [
|
|
158
|
+
`Agent: ${agent.name ?? agent.id.slice(0, 8)} (${agent.runnerId})`,
|
|
159
|
+
agent.name ? `ID: ${agent.id.slice(0, 8)}` : null,
|
|
160
|
+
agent.description ? `Description: ${agent.description}` : null,
|
|
161
|
+
`Status: ${agent.status}`,
|
|
162
|
+
`Task: ${agent.task ?? "none"}`,
|
|
163
|
+
`Role: ${agent.role}`,
|
|
164
|
+
`Tokens: ${agent.tokenInput} in / ${agent.tokenOutput} out`,
|
|
165
|
+
agent.resultSummary ? `Result: ${agent.resultSummary.slice(0, 500)}` : null
|
|
166
|
+
].filter(Boolean).join("\n")
|
|
167
|
+
}]
|
|
168
|
+
};
|
|
169
|
+
}
|
|
170
|
+
);
|
|
171
|
+
server.tool(
|
|
172
|
+
"list_tasks",
|
|
173
|
+
"Get the full task board",
|
|
174
|
+
{},
|
|
175
|
+
async () => {
|
|
176
|
+
const tasks = await callApi("/api/tasks");
|
|
177
|
+
if (!tasks.length) {
|
|
178
|
+
return { content: [{ type: "text", text: "No tasks." }] };
|
|
179
|
+
}
|
|
180
|
+
const lines = tasks.map(
|
|
181
|
+
(t) => `#${t.id} [${t.status}] ${t.subject}${t.assignee ? ` (\u2192 ${t.assignee.slice(0, 8)})` : ""}`
|
|
182
|
+
);
|
|
183
|
+
return { content: [{ type: "text", text: lines.join("\n") }] };
|
|
184
|
+
}
|
|
185
|
+
);
|
|
186
|
+
server.tool(
|
|
187
|
+
"update_task",
|
|
188
|
+
"Update a task's status",
|
|
189
|
+
{
|
|
190
|
+
taskId: z.number().describe("The task ID to update"),
|
|
191
|
+
status: z.enum(["pending", "in_progress", "completed", "failed", "abandoned"]).describe("New status"),
|
|
192
|
+
result: z.string().optional().describe("Result summary (for completed/failed)")
|
|
193
|
+
},
|
|
194
|
+
async ({ taskId, status, result }) => {
|
|
195
|
+
await callApi("/api/orchestrator/update-task", { taskId, status, result });
|
|
196
|
+
return { content: [{ type: "text", text: `Task #${taskId} \u2192 ${status}` }] };
|
|
197
|
+
}
|
|
198
|
+
);
|
|
199
|
+
server.tool(
|
|
200
|
+
"send_message",
|
|
201
|
+
"Send a message to another agent's inbox",
|
|
202
|
+
{
|
|
203
|
+
toAgentId: z.string().describe("Recipient agent ID (or 'main' for the main agent)"),
|
|
204
|
+
content: z.string().describe("Message content"),
|
|
205
|
+
messageType: z.enum(["task_result", "question", "status_update", "direct_message"]).optional().describe("Message type (default: direct_message)")
|
|
206
|
+
},
|
|
207
|
+
async ({ toAgentId, content, messageType }) => {
|
|
208
|
+
await callApi("/api/orchestrator/send-message", {
|
|
209
|
+
chatId: CHAT_ID,
|
|
210
|
+
message: {
|
|
211
|
+
toAgentId,
|
|
212
|
+
fromAgentId: AGENT_ID,
|
|
213
|
+
messageType: messageType ?? "direct_message",
|
|
214
|
+
content
|
|
215
|
+
}
|
|
216
|
+
});
|
|
217
|
+
return { content: [{ type: "text", text: `Message sent to ${toAgentId}.` }] };
|
|
218
|
+
}
|
|
219
|
+
);
|
|
220
|
+
server.tool(
|
|
221
|
+
"read_inbox",
|
|
222
|
+
"Read pending messages from your inbox. Messages remain unread unless markRead is true.",
|
|
223
|
+
{
|
|
224
|
+
markRead: z.boolean().optional().describe("Mark messages as read after returning them (default: false)")
|
|
225
|
+
},
|
|
226
|
+
async ({ markRead }) => {
|
|
227
|
+
const messages = await callApi("/api/orchestrator/read-inbox", {
|
|
228
|
+
chatId: CHAT_ID,
|
|
229
|
+
agentId: AGENT_ID,
|
|
230
|
+
markRead: markRead ?? false
|
|
231
|
+
});
|
|
232
|
+
if (!messages.length) {
|
|
233
|
+
return { content: [{ type: "text", text: "No new messages." }] };
|
|
234
|
+
}
|
|
235
|
+
const lines = messages.map(
|
|
236
|
+
(m) => `[${m.messageType}] from ${m.fromAgentId.slice(0, 8)}: ${m.content.slice(0, 500)}`
|
|
237
|
+
);
|
|
238
|
+
return { content: [{ type: "text", text: lines.join("\n\n") }] };
|
|
239
|
+
}
|
|
240
|
+
);
|
|
241
|
+
server.tool(
|
|
242
|
+
"set_state",
|
|
243
|
+
"Write a key-value pair to the shared state whiteboard",
|
|
244
|
+
{
|
|
245
|
+
key: z.string().describe("State key (1-256 chars)"),
|
|
246
|
+
value: z.string().describe("State value (JSON-serialized data)")
|
|
247
|
+
},
|
|
248
|
+
async ({ key, value }) => {
|
|
249
|
+
await callApi("/api/orchestrator/set-state", {
|
|
250
|
+
chatId: CHAT_ID,
|
|
251
|
+
key,
|
|
252
|
+
value,
|
|
253
|
+
setBy: AGENT_ID
|
|
254
|
+
});
|
|
255
|
+
return { content: [{ type: "text", text: `State '${key}' set.` }] };
|
|
256
|
+
}
|
|
257
|
+
);
|
|
258
|
+
server.tool(
|
|
259
|
+
"get_state",
|
|
260
|
+
"Read a value from the shared state whiteboard",
|
|
261
|
+
{
|
|
262
|
+
key: z.string().describe("State key to read")
|
|
263
|
+
},
|
|
264
|
+
async ({ key }) => {
|
|
265
|
+
const entry = await callApi("/api/orchestrator/get-state", {
|
|
266
|
+
chatId: CHAT_ID,
|
|
267
|
+
key
|
|
268
|
+
});
|
|
269
|
+
if (!entry) {
|
|
270
|
+
return { content: [{ type: "text", text: `State '${key}' not set.` }] };
|
|
271
|
+
}
|
|
272
|
+
return { content: [{ type: "text", text: `${key} = ${entry.value} (set by ${entry.setBy})` }] };
|
|
273
|
+
}
|
|
274
|
+
);
|
|
275
|
+
server.tool(
|
|
276
|
+
"list_state",
|
|
277
|
+
"List all key-value entries on the shared whiteboard for this orchestration",
|
|
278
|
+
{},
|
|
279
|
+
async () => {
|
|
280
|
+
const entries = await callApi("/api/orchestrator/list-state", {
|
|
281
|
+
chatId: CHAT_ID
|
|
282
|
+
});
|
|
283
|
+
if (!entries.length) {
|
|
284
|
+
return { content: [{ type: "text", text: "Whiteboard is empty." }] };
|
|
285
|
+
}
|
|
286
|
+
const lines = entries.map((e) => `${e.setBy}: ${e.key} = ${e.value}`);
|
|
287
|
+
return { content: [{ type: "text", text: lines.join("\n") }] };
|
|
288
|
+
}
|
|
289
|
+
);
|
|
290
|
+
server.tool(
|
|
291
|
+
"broadcast",
|
|
292
|
+
"Send a message to ALL other agents in the orchestration",
|
|
293
|
+
{
|
|
294
|
+
content: z.string().describe("Message content to broadcast"),
|
|
295
|
+
messageType: z.enum(["status_update", "direct_message"]).optional().describe("Message type (default: direct_message)")
|
|
296
|
+
},
|
|
297
|
+
async ({ content, messageType }) => {
|
|
298
|
+
const result = await callApi("/api/orchestrator/broadcast", {
|
|
299
|
+
chatId: CHAT_ID,
|
|
300
|
+
fromAgentId: AGENT_ID,
|
|
301
|
+
content,
|
|
302
|
+
messageType: messageType ?? "direct_message"
|
|
303
|
+
});
|
|
304
|
+
return { content: [{ type: "text", text: `Broadcast sent to ${result.sent} agent(s).` }] };
|
|
305
|
+
}
|
|
306
|
+
);
|
|
307
|
+
server.tool(
|
|
308
|
+
"list_runners",
|
|
309
|
+
"List all registered CLI runners and their capabilities",
|
|
310
|
+
{},
|
|
311
|
+
async () => {
|
|
312
|
+
const runners = await callApi("/api/orchestrator/list-runners");
|
|
313
|
+
const lines = runners.map(
|
|
314
|
+
(r) => `\u2022 ${r.id} (${r.displayName}) \u2014 ${r.capabilities.specialties.join(", ")}`
|
|
315
|
+
);
|
|
316
|
+
return { content: [{ type: "text", text: lines.join("\n") }] };
|
|
317
|
+
}
|
|
318
|
+
);
|
|
319
|
+
server.tool(
|
|
320
|
+
"list_mcps",
|
|
321
|
+
"List all MCP servers in the global registry",
|
|
322
|
+
{},
|
|
323
|
+
async () => {
|
|
324
|
+
const mcps = await callApi("/api/orchestrator/list-mcps");
|
|
325
|
+
if (!mcps.length) {
|
|
326
|
+
return { content: [{ type: "text", text: "No MCP servers registered." }] };
|
|
327
|
+
}
|
|
328
|
+
const lines = mcps.map(
|
|
329
|
+
(m) => `\u2022 ${m.name} (${m.transport})${m.description ? ` \u2014 ${m.description}` : ""}${m.enabledByDefault ? " [auto]" : ""}`
|
|
330
|
+
);
|
|
331
|
+
return { content: [{ type: "text", text: lines.join("\n") }] };
|
|
332
|
+
}
|
|
333
|
+
);
|
|
334
|
+
async function main() {
|
|
335
|
+
const transport = new StdioServerTransport();
|
|
336
|
+
await server.connect(transport);
|
|
337
|
+
console.error(`[cc-claw-mcp] Server running on stdio (chatId=${CHAT_ID}, agentId=${AGENT_ID})`);
|
|
338
|
+
}
|
|
339
|
+
main().catch((err) => {
|
|
340
|
+
console.error("[cc-claw-mcp] Fatal error:", err);
|
|
341
|
+
process.exit(1);
|
|
342
|
+
});
|