@suzuke/agend 0.0.1 → 1.0.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/README.md +557 -1
- package/README.zh-TW.md +504 -0
- package/dist/access-path.d.ts +7 -0
- package/dist/access-path.js +12 -0
- package/dist/access-path.js.map +1 -0
- package/dist/approval/approval-server.d.ts +30 -0
- package/dist/approval/approval-server.js +156 -0
- package/dist/approval/approval-server.js.map +1 -0
- package/dist/approval/tmux-prompt-detector.d.ts +34 -0
- package/dist/approval/tmux-prompt-detector.js +264 -0
- package/dist/approval/tmux-prompt-detector.js.map +1 -0
- package/dist/backend/approval-strategy.d.ts +14 -0
- package/dist/backend/approval-strategy.js +2 -0
- package/dist/backend/approval-strategy.js.map +1 -0
- package/dist/backend/claude-code.d.ts +13 -0
- package/dist/backend/claude-code.js +114 -0
- package/dist/backend/claude-code.js.map +1 -0
- package/dist/backend/codex.d.ts +10 -0
- package/dist/backend/codex.js +58 -0
- package/dist/backend/codex.js.map +1 -0
- package/dist/backend/factory.d.ts +2 -0
- package/dist/backend/factory.js +19 -0
- package/dist/backend/factory.js.map +1 -0
- package/dist/backend/gemini-cli.d.ts +10 -0
- package/dist/backend/gemini-cli.js +68 -0
- package/dist/backend/gemini-cli.js.map +1 -0
- package/dist/backend/hook-based-approval.d.ts +20 -0
- package/dist/backend/hook-based-approval.js +41 -0
- package/dist/backend/hook-based-approval.js.map +1 -0
- package/dist/backend/index.d.ts +6 -0
- package/dist/backend/index.js +6 -0
- package/dist/backend/index.js.map +1 -0
- package/dist/backend/opencode.d.ts +10 -0
- package/dist/backend/opencode.js +63 -0
- package/dist/backend/opencode.js.map +1 -0
- package/dist/backend/types.d.ts +26 -0
- package/dist/backend/types.js +2 -0
- package/dist/backend/types.js.map +1 -0
- package/dist/channel/access-manager.d.ts +18 -0
- package/dist/channel/access-manager.js +149 -0
- package/dist/channel/access-manager.js.map +1 -0
- package/dist/channel/adapters/discord.d.ts +45 -0
- package/dist/channel/adapters/discord.js +366 -0
- package/dist/channel/adapters/discord.js.map +1 -0
- package/dist/channel/adapters/telegram.d.ts +58 -0
- package/dist/channel/adapters/telegram.js +569 -0
- package/dist/channel/adapters/telegram.js.map +1 -0
- package/dist/channel/attachment-handler.d.ts +15 -0
- package/dist/channel/attachment-handler.js +55 -0
- package/dist/channel/attachment-handler.js.map +1 -0
- package/dist/channel/factory.d.ts +12 -0
- package/dist/channel/factory.js +38 -0
- package/dist/channel/factory.js.map +1 -0
- package/dist/channel/ipc-bridge.d.ts +26 -0
- package/dist/channel/ipc-bridge.js +170 -0
- package/dist/channel/ipc-bridge.js.map +1 -0
- package/dist/channel/mcp-server.d.ts +10 -0
- package/dist/channel/mcp-server.js +196 -0
- package/dist/channel/mcp-server.js.map +1 -0
- package/dist/channel/mcp-tools.d.ts +909 -0
- package/dist/channel/mcp-tools.js +346 -0
- package/dist/channel/mcp-tools.js.map +1 -0
- package/dist/channel/message-bus.d.ts +17 -0
- package/dist/channel/message-bus.js +86 -0
- package/dist/channel/message-bus.js.map +1 -0
- package/dist/channel/message-queue.d.ts +39 -0
- package/dist/channel/message-queue.js +248 -0
- package/dist/channel/message-queue.js.map +1 -0
- package/dist/channel/tool-router.d.ts +6 -0
- package/dist/channel/tool-router.js +69 -0
- package/dist/channel/tool-router.js.map +1 -0
- package/dist/channel/tool-tracker.d.ts +13 -0
- package/dist/channel/tool-tracker.js +58 -0
- package/dist/channel/tool-tracker.js.map +1 -0
- package/dist/channel/types.d.ts +116 -0
- package/dist/channel/types.js +2 -0
- package/dist/channel/types.js.map +1 -0
- package/dist/cli.d.ts +2 -0
- package/dist/cli.js +782 -0
- package/dist/cli.js.map +1 -0
- package/dist/config.d.ts +8 -0
- package/dist/config.js +85 -0
- package/dist/config.js.map +1 -0
- package/dist/container-manager.d.ts +24 -0
- package/dist/container-manager.js +148 -0
- package/dist/container-manager.js.map +1 -0
- package/dist/context-guardian.d.ts +29 -0
- package/dist/context-guardian.js +123 -0
- package/dist/context-guardian.js.map +1 -0
- package/dist/cost-guard.d.ts +21 -0
- package/dist/cost-guard.js +113 -0
- package/dist/cost-guard.js.map +1 -0
- package/dist/daemon-entry.d.ts +1 -0
- package/dist/daemon-entry.js +29 -0
- package/dist/daemon-entry.js.map +1 -0
- package/dist/daemon.d.ts +88 -0
- package/dist/daemon.js +820 -0
- package/dist/daemon.js.map +1 -0
- package/dist/daily-summary.d.ts +13 -0
- package/dist/daily-summary.js +55 -0
- package/dist/daily-summary.js.map +1 -0
- package/dist/db.d.ts +10 -0
- package/dist/db.js +43 -0
- package/dist/db.js.map +1 -0
- package/dist/event-log.d.ts +22 -0
- package/dist/event-log.js +66 -0
- package/dist/event-log.js.map +1 -0
- package/dist/export-import.d.ts +2 -0
- package/dist/export-import.js +110 -0
- package/dist/export-import.js.map +1 -0
- package/dist/fleet-context.d.ts +36 -0
- package/dist/fleet-context.js +4 -0
- package/dist/fleet-context.js.map +1 -0
- package/dist/fleet-manager.d.ts +115 -0
- package/dist/fleet-manager.js +1742 -0
- package/dist/fleet-manager.js.map +1 -0
- package/dist/fleet-system-prompt.d.ts +11 -0
- package/dist/fleet-system-prompt.js +60 -0
- package/dist/fleet-system-prompt.js.map +1 -0
- package/dist/hang-detector.d.ts +16 -0
- package/dist/hang-detector.js +53 -0
- package/dist/hang-detector.js.map +1 -0
- package/dist/index.d.ts +8 -0
- package/dist/index.js +6 -0
- package/dist/index.js.map +1 -0
- package/dist/install-recorder.d.ts +30 -0
- package/dist/install-recorder.js +159 -0
- package/dist/install-recorder.js.map +1 -0
- package/dist/logger.d.ts +3 -0
- package/dist/logger.js +63 -0
- package/dist/logger.js.map +1 -0
- package/dist/meeting/orchestrator.d.ts +30 -0
- package/dist/meeting/orchestrator.js +355 -0
- package/dist/meeting/orchestrator.js.map +1 -0
- package/dist/meeting/prompt-builder.d.ts +12 -0
- package/dist/meeting/prompt-builder.js +96 -0
- package/dist/meeting/prompt-builder.js.map +1 -0
- package/dist/meeting/role-assigner.d.ts +2 -0
- package/dist/meeting/role-assigner.js +25 -0
- package/dist/meeting/role-assigner.js.map +1 -0
- package/dist/meeting/types.d.ts +21 -0
- package/dist/meeting/types.js +2 -0
- package/dist/meeting/types.js.map +1 -0
- package/dist/meeting-manager.d.ts +10 -0
- package/dist/meeting-manager.js +38 -0
- package/dist/meeting-manager.js.map +1 -0
- package/dist/memory-layer.d.ts +13 -0
- package/dist/memory-layer.js +44 -0
- package/dist/memory-layer.js.map +1 -0
- package/dist/plugin/agend/.claude-plugin/plugin.json +5 -0
- package/dist/plugin/agend/.mcp.json +9 -0
- package/dist/plugin/ccd-channel/.claude-plugin/plugin.json +5 -0
- package/dist/plugin/ccd-channel/.mcp.json +9 -0
- package/dist/process-manager.d.ts +31 -0
- package/dist/process-manager.js +264 -0
- package/dist/process-manager.js.map +1 -0
- package/dist/scheduler/db.d.ts +16 -0
- package/dist/scheduler/db.js +132 -0
- package/dist/scheduler/db.js.map +1 -0
- package/dist/scheduler/db.test.d.ts +1 -0
- package/dist/scheduler/db.test.js +92 -0
- package/dist/scheduler/db.test.js.map +1 -0
- package/dist/scheduler/index.d.ts +4 -0
- package/dist/scheduler/index.js +4 -0
- package/dist/scheduler/index.js.map +1 -0
- package/dist/scheduler/scheduler.d.ts +25 -0
- package/dist/scheduler/scheduler.js +119 -0
- package/dist/scheduler/scheduler.js.map +1 -0
- package/dist/scheduler/scheduler.test.d.ts +1 -0
- package/dist/scheduler/scheduler.test.js +119 -0
- package/dist/scheduler/scheduler.test.js.map +1 -0
- package/dist/scheduler/types.d.ts +47 -0
- package/dist/scheduler/types.js +7 -0
- package/dist/scheduler/types.js.map +1 -0
- package/dist/service-installer.d.ts +14 -0
- package/dist/service-installer.js +91 -0
- package/dist/service-installer.js.map +1 -0
- package/dist/setup-wizard.d.ts +14 -0
- package/dist/setup-wizard.js +517 -0
- package/dist/setup-wizard.js.map +1 -0
- package/dist/stt.d.ts +10 -0
- package/dist/stt.js +33 -0
- package/dist/stt.js.map +1 -0
- package/dist/tmux-manager.d.ts +22 -0
- package/dist/tmux-manager.js +131 -0
- package/dist/tmux-manager.js.map +1 -0
- package/dist/topic-commands.d.ts +22 -0
- package/dist/topic-commands.js +176 -0
- package/dist/topic-commands.js.map +1 -0
- package/dist/transcript-monitor.d.ts +21 -0
- package/dist/transcript-monitor.js +149 -0
- package/dist/transcript-monitor.js.map +1 -0
- package/dist/types.d.ts +153 -0
- package/dist/types.js +2 -0
- package/dist/types.js.map +1 -0
- package/dist/webhook-emitter.d.ts +15 -0
- package/dist/webhook-emitter.js +41 -0
- package/dist/webhook-emitter.js.map +1 -0
- package/package.json +60 -4
- package/templates/launchd.plist.ejs +29 -0
- package/templates/systemd.service.ejs +15 -0
- package/index.js +0 -1
package/README.md
CHANGED
|
@@ -1 +1,557 @@
|
|
|
1
|
-
#
|
|
1
|
+
# claude-channel-daemon
|
|
2
|
+
|
|
3
|
+
[](LICENSE)
|
|
4
|
+
[](https://nodejs.org)
|
|
5
|
+
[](https://docs.anthropic.com/en/docs/claude-code)
|
|
6
|
+
|
|
7
|
+
**Run a fleet of Claude Code agents from your phone.** One Telegram bot, unlimited projects — each Forum Topic is an independent Claude session with crash recovery and zero babysitting.
|
|
8
|
+
|
|
9
|
+
[繁體中文](README.zh-TW.md)
|
|
10
|
+
|
|
11
|
+
> **⚠️** The daemon uses Claude Code's native permission relay — permission requests are forwarded to Telegram as inline buttons (Allow/Deny). See [Permission System](#permission-system).
|
|
12
|
+
|
|
13
|
+
## Why this exists
|
|
14
|
+
|
|
15
|
+
Claude Code's official Telegram plugin gives you **1 bot = 1 session**. Close the terminal and it goes offline. No scheduling. No multi-project support.
|
|
16
|
+
|
|
17
|
+
**claude-channel-daemon** turns Claude Code into an always-on, multi-project AI engineering team you control from Telegram:
|
|
18
|
+
|
|
19
|
+
| Feature | Official Plugin | claude-channel-daemon |
|
|
20
|
+
|---------|:-:|:-:|
|
|
21
|
+
| Multiple projects simultaneously | — | **N sessions, 1 bot** |
|
|
22
|
+
| Survives terminal close / SSH disconnect | — | **tmux persistence** |
|
|
23
|
+
| Cron-based scheduled tasks | Session-scoped (expires in 3 days) | **Persistent (SQLite-backed)** |
|
|
24
|
+
| Auto context rotation (prevent stale sessions) | — | **Built-in** |
|
|
25
|
+
| Permission requests via Telegram | Text-based reply | **Inline buttons** |
|
|
26
|
+
| Voice messages → Claude | — | **Groq Whisper** |
|
|
27
|
+
| Dynamic instance creation via General topic | — | **Built-in** |
|
|
28
|
+
| Install as system service (launchd/systemd) | — | **One command** |
|
|
29
|
+
| Crash recovery | — | **Auto-restart** |
|
|
30
|
+
| Cost guard (daily spending limits) | Platform-level (`--max-budget-usd`) | **Per-instance daily limits** |
|
|
31
|
+
| Fleet status from Telegram | — | **/status command** |
|
|
32
|
+
| Daily fleet summary | — | **Scheduled report** |
|
|
33
|
+
| Hang detection | — | **Auto-detect + notify** |
|
|
34
|
+
| Peer-to-peer agent collaboration | — | **Built-in** |
|
|
35
|
+
|
|
36
|
+
## Who is this for
|
|
37
|
+
|
|
38
|
+
- **Solo developers** who want Claude working on multiple repos around the clock
|
|
39
|
+
- **Small teams** sharing a single bot — each team member gets their own Forum Topic
|
|
40
|
+
- **CI/CD power users** who want cron-scheduled Claude tasks (daily PR reviews, deploy checks)
|
|
41
|
+
- **Security-conscious users** who need explicit permission approval for tool use
|
|
42
|
+
- Anyone who's tired of keeping a terminal window open just to talk to Claude
|
|
43
|
+
|
|
44
|
+
## How it compares
|
|
45
|
+
|
|
46
|
+
| | claude-channel-daemon | Claude Code Telegram Plugin | Cursor | Cline (VS Code) |
|
|
47
|
+
|---|:-:|:-:|:-:|:-:|
|
|
48
|
+
| Runs headless (no IDE/terminal) | **Yes** | Needs terminal | No | No |
|
|
49
|
+
| Multi-project fleet | **Yes** | 1 session | 1 window | 1 window |
|
|
50
|
+
| Multi-channel (Telegram, Discord) | **Yes** | Telegram only | N/A | N/A |
|
|
51
|
+
| Scheduled tasks | **Persistent** | Session-scoped | No | No |
|
|
52
|
+
| Context auto-rotation | **Yes** | No | N/A | No |
|
|
53
|
+
| Permission approval flow | **Inline buttons** | Text-based | N/A | Limited |
|
|
54
|
+
| Mobile-first (Telegram) | **Yes** | Yes | No | No |
|
|
55
|
+
| Voice input | **Yes** | No | No | No |
|
|
56
|
+
| System service | **Yes** | No | N/A | N/A |
|
|
57
|
+
| Cost controls | **Per-instance** | Platform-level | N/A | N/A |
|
|
58
|
+
| Model failover | **Auto-switch** | No | No | No |
|
|
59
|
+
| Crash recovery | **Yes** | No | N/A | N/A |
|
|
60
|
+
|
|
61
|
+
## Architecture
|
|
62
|
+
|
|
63
|
+
```
|
|
64
|
+
┌──────────────────────────────────────────────────────────────┐
|
|
65
|
+
│ Fleet Manager │
|
|
66
|
+
│ │
|
|
67
|
+
Telegram ◄──long-poll──► │ ChannelAdapter Scheduler (croner) │
|
|
68
|
+
Discord ◄──gateway────► │ (Telegram/Discord) │ │
|
|
69
|
+
│ │ │ cron triggers │
|
|
70
|
+
│ threadId routing table │ │
|
|
71
|
+
│ #277→proj-a #672→proj-b │ │
|
|
72
|
+
│ │ │ CostGuard HangDetector │
|
|
73
|
+
│ ┌────┴────┐ ┌────┴────┐ ┌────┴────┐ WebhookEmitter │
|
|
74
|
+
│ │Daemon A │ │Daemon B │ │Daemon C │ │
|
|
75
|
+
│ │Permission│ │Permission│ │Permission│ │
|
|
76
|
+
│ │Relay │ │Relay │ │Relay │ │
|
|
77
|
+
│ │Context │ │Context │ │Context │ │
|
|
78
|
+
│ │Guardian │ │Guardian │ │Guardian │ │
|
|
79
|
+
│ └────┬─────┘ └────┬─────┘ └────┬─────┘ │
|
|
80
|
+
│ │ │ │ │
|
|
81
|
+
│ ┌────┴─────┐ ┌────┴─────┐ ┌────┴─────┐ │
|
|
82
|
+
│ │tmux win │ │tmux win │ │tmux win │ │
|
|
83
|
+
│ │Claude │ │Claude │ │Claude │ │
|
|
84
|
+
│ │+MCP srv │ │+MCP srv │ │+MCP srv │ │
|
|
85
|
+
│ └──────────┘ └──────────┘ └──────────┘ │
|
|
86
|
+
└──────────────────────────────────────────────────────────────┘
|
|
87
|
+
```
|
|
88
|
+
|
|
89
|
+
## Key features
|
|
90
|
+
|
|
91
|
+
### Fleet mode — one bot, many projects
|
|
92
|
+
|
|
93
|
+
Each Telegram Forum Topic maps to an independent Claude Code session. Create a topic, pick a project directory, and Claude starts working. Delete the topic, instance stops. Scale to as many projects as your machine can handle.
|
|
94
|
+
|
|
95
|
+
### Scheduled tasks
|
|
96
|
+
|
|
97
|
+
Claude can create cron-based schedules via MCP tools. Schedules survive daemon restarts (SQLite-backed).
|
|
98
|
+
|
|
99
|
+
```
|
|
100
|
+
User: "Every morning at 9am, check if there are any open PRs that need review"
|
|
101
|
+
Claude: → create_schedule(cron: "0 9 * * *", message: "Check open PRs needing review")
|
|
102
|
+
```
|
|
103
|
+
|
|
104
|
+
Available MCP tools: `create_schedule`, `list_schedules`, `update_schedule`, `delete_schedule`
|
|
105
|
+
|
|
106
|
+
Collaboration MCP tools: `list_instances`, `send_to_instance`, `start_instance`, `create_instance`, `delete_instance`
|
|
107
|
+
|
|
108
|
+
Schedules can target a specific instance or the same instance that created them. When a schedule triggers, the daemon pushes the message to Claude as if a user sent it.
|
|
109
|
+
|
|
110
|
+
### Context rotation
|
|
111
|
+
|
|
112
|
+
Watches Claude's status line JSON. When context usage exceeds the threshold or the session reaches its max age, the daemon performs a simple restart:
|
|
113
|
+
|
|
114
|
+
```
|
|
115
|
+
NORMAL → RESTARTING → GRACE
|
|
116
|
+
```
|
|
117
|
+
|
|
118
|
+
1. **Trigger** — context exceeds threshold (default 80%) or `max_age_hours` reached (default 8h)
|
|
119
|
+
2. **Idle barrier** — waits up to 5 seconds for current activity to settle (best-effort, not a handover)
|
|
120
|
+
3. **Snapshot** — daemon collects recent user messages, tool activity, and statusline data into `rotation-state.json`
|
|
121
|
+
4. **Restart** — kills tmux window, spawns fresh session with the snapshot injected into the system prompt
|
|
122
|
+
5. **Grace** — 10-minute cooldown to prevent rapid re-rotation
|
|
123
|
+
|
|
124
|
+
No handover prompt is sent to Claude. Recovery context comes entirely from the daemon-side snapshot.
|
|
125
|
+
|
|
126
|
+
### Peer-to-peer agent collaboration
|
|
127
|
+
|
|
128
|
+
Every instance is an equal peer that can discover, wake, create, and message other instances. No dispatcher needed — collaboration emerges from the tools available to each agent.
|
|
129
|
+
|
|
130
|
+
**Core MCP tools:**
|
|
131
|
+
|
|
132
|
+
- `list_instances` — discover all configured instances (running or stopped) with status, working directory, tags, and last activity
|
|
133
|
+
- `send_to_instance` — send a message to another instance or external session; supports structured metadata (`request_kind`, `requires_reply`, `correlation_id`, `task_summary`)
|
|
134
|
+
- `start_instance` — wake a stopped instance so you can message it
|
|
135
|
+
- `create_instance` — create a new instance with a topic from a project directory (supports `--branch` for git worktree isolation)
|
|
136
|
+
- `delete_instance` — remove an instance and its topic
|
|
137
|
+
- `describe_instance` — get detailed info about a specific instance (description, tags, model, last activity)
|
|
138
|
+
|
|
139
|
+
**High-level collaboration tools** (prefer these over raw `send_to_instance`):
|
|
140
|
+
|
|
141
|
+
- `request_information` — ask another instance a question and expect a reply (`request_kind=query`, `requires_reply=true`)
|
|
142
|
+
- `delegate_task` — assign work to another instance with success criteria (`request_kind=task`, `requires_reply=true`)
|
|
143
|
+
- `report_result` — return results to the requester, echoing `correlation_id` to link the response to its request
|
|
144
|
+
|
|
145
|
+
Messages are posted to the recipient's Telegram topic for visibility. Sender topic notifications are only posted for instance-to-instance messages (not from external sessions).
|
|
146
|
+
|
|
147
|
+
If you `send_to_instance` a stopped instance, the error tells you to use `start_instance()` first — agents self-correct without human intervention.
|
|
148
|
+
|
|
149
|
+
#### Fleet context system prompt
|
|
150
|
+
|
|
151
|
+
On startup, each instance automatically receives a fleet context system prompt that tells it:
|
|
152
|
+
|
|
153
|
+
- Its own identity (`instanceName`) and working directory
|
|
154
|
+
- The full list of fleet tools and how to use them
|
|
155
|
+
- Collaboration rules: how to handle `from_instance` messages, when to echo `correlation_id`, scope awareness (never assume direct file access to another instance's repo)
|
|
156
|
+
|
|
157
|
+
This means instances understand their role in the fleet from the first message, without any manual configuration.
|
|
158
|
+
|
|
159
|
+
### General Topic instance
|
|
160
|
+
|
|
161
|
+
A regular instance bound to the Telegram General Topic. Auto-created on fleet startup, it serves as a natural language entry point for tasks that don't belong to a specific project. Its behavior is defined entirely by its project's `CLAUDE.md`:
|
|
162
|
+
|
|
163
|
+
- Simple tasks (web search, translation, general questions) — handles directly
|
|
164
|
+
- Project-specific tasks — uses `list_instances()` to find the right agent, `start_instance()` if needed, then `send_to_instance()` to delegate
|
|
165
|
+
- New project requests — uses `create_instance()` to set up a new agent
|
|
166
|
+
|
|
167
|
+
Use `/status` in the General topic for a fleet overview. All other project management is handled by the General instance through natural language.
|
|
168
|
+
|
|
169
|
+
### External session support
|
|
170
|
+
|
|
171
|
+
You can connect a local Claude Code session to the daemon's channel tools (reply, send_to_instance, etc.) by pointing `.mcp.json` at an instance's IPC socket:
|
|
172
|
+
|
|
173
|
+
```json
|
|
174
|
+
{
|
|
175
|
+
"mcpServers": {
|
|
176
|
+
"ccd-channel": {
|
|
177
|
+
"command": "node",
|
|
178
|
+
"args": ["path/to/dist/channel/mcp-server.js"],
|
|
179
|
+
"env": {
|
|
180
|
+
"CCD_SOCKET_PATH": "~/.claude-channel-daemon/instances/<name>/channel.sock"
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
```
|
|
186
|
+
|
|
187
|
+
The daemon automatically isolates external sessions from internal ones using env var layering:
|
|
188
|
+
|
|
189
|
+
| Session type | Identity source | Example |
|
|
190
|
+
|---|---|---|
|
|
191
|
+
| Internal (daemon-managed) | `CCD_INSTANCE_NAME` via tmux env | `ccplugin` |
|
|
192
|
+
| External (custom name) | `CCD_SESSION_NAME` in `.mcp.json` env | `dev` |
|
|
193
|
+
| External (zero-config) | `external-<basename(cwd)>` fallback | `external-myproject` |
|
|
194
|
+
|
|
195
|
+
Internal sessions get `CCD_INSTANCE_NAME` injected by the daemon into the tmux shell environment. External sessions don't have this, so they fall through to `CCD_SESSION_NAME` (if set) or an auto-generated name based on the working directory. This means the same `.mcp.json` produces different identities for internal vs external sessions — no configuration conflicts.
|
|
196
|
+
|
|
197
|
+
External sessions appear in `list_instances` and can be targeted by `send_to_instance`.
|
|
198
|
+
|
|
199
|
+
### Graceful restart
|
|
200
|
+
|
|
201
|
+
`ccd fleet restart` sends SIGUSR2 to the fleet manager. It waits for all instances to go idle (no transcript activity for 10s), then restarts them one by one. A 5-minute timeout prevents hanging on stuck instances.
|
|
202
|
+
|
|
203
|
+
### Telegram commands
|
|
204
|
+
|
|
205
|
+
In topic mode, the bot responds to commands in the General topic:
|
|
206
|
+
|
|
207
|
+
- `/status` — show fleet status and costs
|
|
208
|
+
|
|
209
|
+
Project management commands (`/open`, `/new`, `/meets`, `/debate`, `/collab`) were removed in v0.3.4. The General instance now handles these tasks via natural language — just tell it what you need and it will use `create_instance`, `start_instance`, or `send_to_instance` as appropriate.
|
|
210
|
+
|
|
211
|
+
### Permission system
|
|
212
|
+
|
|
213
|
+
Uses Claude Code's native permission relay — permission requests are forwarded to Telegram as inline buttons (Allow/Deny). When Claude requests a sensitive tool use, the daemon surfaces it to you in Telegram and waits for your response before proceeding.
|
|
214
|
+
|
|
215
|
+
### Voice transcription
|
|
216
|
+
|
|
217
|
+
Telegram voice messages are transcribed via Groq Whisper API and sent to Claude as text. Works in both topic mode and DM mode. Requires `GROQ_API_KEY` in `.env`.
|
|
218
|
+
|
|
219
|
+
### Dynamic instance management
|
|
220
|
+
|
|
221
|
+
Instances are created through the General instance using `create_instance`. Tell the General instance what project you want to work on — it creates a Telegram topic, binds the project directory, and starts Claude automatically. Instances can also be created with `--branch` to spawn a git worktree for feature branch isolation. Deleting a topic auto-unbinds and stops the instance. Use `delete_instance` to fully remove an instance and its topic.
|
|
222
|
+
|
|
223
|
+
### Cost guard
|
|
224
|
+
|
|
225
|
+
Prevent bill shock when running unattended. Configure daily spending limits in `fleet.yaml`:
|
|
226
|
+
|
|
227
|
+
```yaml
|
|
228
|
+
defaults:
|
|
229
|
+
cost_guard:
|
|
230
|
+
daily_limit_usd: 50
|
|
231
|
+
warn_at_percentage: 80
|
|
232
|
+
timezone: "Asia/Taipei"
|
|
233
|
+
```
|
|
234
|
+
|
|
235
|
+
When an instance approaches the limit, a warning is posted to its Telegram topic. When the limit is reached, the instance is automatically paused and a notification is sent. Paused instances resume the next day or when manually restarted.
|
|
236
|
+
|
|
237
|
+
### Fleet status
|
|
238
|
+
|
|
239
|
+
Use `/status` in the General topic to see a live overview:
|
|
240
|
+
|
|
241
|
+
```
|
|
242
|
+
🟢 proj-a — ctx 42%, $3.20 today
|
|
243
|
+
🟢 proj-b — ctx 67%, $8.50 today
|
|
244
|
+
⏸ proj-c — paused (cost limit)
|
|
245
|
+
|
|
246
|
+
Fleet: $11.70 / $50.00 daily
|
|
247
|
+
```
|
|
248
|
+
|
|
249
|
+
### Daily summary
|
|
250
|
+
|
|
251
|
+
A daily report is posted to the General topic at a configurable time (default 21:00):
|
|
252
|
+
|
|
253
|
+
```
|
|
254
|
+
📊 Daily Report — 2026-03-26
|
|
255
|
+
|
|
256
|
+
proj-a: $8.20, 2 restarts
|
|
257
|
+
proj-b: $2.10
|
|
258
|
+
proj-c: $0.00 ⚠️ 1 hang
|
|
259
|
+
|
|
260
|
+
Total: $10.30
|
|
261
|
+
```
|
|
262
|
+
|
|
263
|
+
### Hang detection
|
|
264
|
+
|
|
265
|
+
If an instance shows no activity for 15 minutes (configurable), the daemon posts a notification with inline buttons:
|
|
266
|
+
|
|
267
|
+
- **Force restart** — stops and restarts the instance
|
|
268
|
+
- **Keep waiting** — dismisses the alert
|
|
269
|
+
|
|
270
|
+
Uses multi-signal detection: checks both transcript activity and statusline freshness to avoid false positives during long-running tool calls.
|
|
271
|
+
|
|
272
|
+
### Rate limit-aware scheduling
|
|
273
|
+
|
|
274
|
+
When the 5-hour API rate limit exceeds 85%, scheduled triggers are automatically deferred instead of firing. A notification is posted to the instance's topic. Deferred schedules are not lost — they will fire on the next cron tick when rate limits are below threshold.
|
|
275
|
+
|
|
276
|
+
### Model failover
|
|
277
|
+
|
|
278
|
+
When the primary model hits a rate limit, the daemon automatically switches to a backup model on the next context rotation. Configure a fallback chain in `fleet.yaml`:
|
|
279
|
+
|
|
280
|
+
```yaml
|
|
281
|
+
instances:
|
|
282
|
+
my-project:
|
|
283
|
+
model_failover: ["opus", "sonnet"]
|
|
284
|
+
```
|
|
285
|
+
|
|
286
|
+
The daemon notifies you in Telegram when a failover occurs and switches back to the primary model when rate limits recover.
|
|
287
|
+
|
|
288
|
+
### Topic icon + idle archive
|
|
289
|
+
|
|
290
|
+
Running instances get a visual icon indicator in Telegram. When an instance stops or crashes, the icon changes. Idle instances are automatically archived — sending a message to an archived topic re-opens it automatically.
|
|
291
|
+
|
|
292
|
+
### Permission countdown + Always Allow
|
|
293
|
+
|
|
294
|
+
Permission prompts now show a countdown timer that updates every 30 seconds. An "Always Allow" button lets you approve all future uses of a specific tool for the current session. Decisions are shown inline after you respond ("✅ Approved" / "❌ Denied").
|
|
295
|
+
|
|
296
|
+
### Daemon-side restart snapshot
|
|
297
|
+
|
|
298
|
+
Before each context restart, the daemon saves a `rotation-state.json` with recent user messages, tool activity, context usage, and statusline data. The next session receives this snapshot in its system prompt, providing continuity without relying on Claude to write a handover report.
|
|
299
|
+
|
|
300
|
+
### Service message filter
|
|
301
|
+
|
|
302
|
+
Telegram system events (topic rename, pin, member join, etc.) are filtered out before reaching Claude, saving context window tokens.
|
|
303
|
+
|
|
304
|
+
### Health endpoint
|
|
305
|
+
|
|
306
|
+
A lightweight HTTP endpoint for external monitoring tools:
|
|
307
|
+
|
|
308
|
+
```
|
|
309
|
+
GET /health → { status: "ok", instances: 3, uptime: 86400 }
|
|
310
|
+
GET /status → { instances: [{ name, status, context_pct, cost_today }] }
|
|
311
|
+
```
|
|
312
|
+
|
|
313
|
+
Configure in `fleet.yaml`:
|
|
314
|
+
|
|
315
|
+
```yaml
|
|
316
|
+
health_port: 19280 # top-level, default 19280, binds to 127.0.0.1
|
|
317
|
+
```
|
|
318
|
+
|
|
319
|
+
### Webhook notifications
|
|
320
|
+
|
|
321
|
+
Push fleet events to external endpoints (Slack, custom dashboards, etc.):
|
|
322
|
+
|
|
323
|
+
```yaml
|
|
324
|
+
defaults:
|
|
325
|
+
webhooks:
|
|
326
|
+
- url: https://hooks.slack.com/...
|
|
327
|
+
events: ["restart", "hang", "cost_warn"]
|
|
328
|
+
- url: https://custom.endpoint/ccd
|
|
329
|
+
events: ["*"]
|
|
330
|
+
```
|
|
331
|
+
|
|
332
|
+
### Discord adapter (MVP)
|
|
333
|
+
|
|
334
|
+
Connect your fleet to Discord instead of (or alongside) Telegram. Configure in `fleet.yaml`:
|
|
335
|
+
|
|
336
|
+
```yaml
|
|
337
|
+
channel:
|
|
338
|
+
type: discord
|
|
339
|
+
bot_token_env: CCD_DISCORD_TOKEN
|
|
340
|
+
guild_id: "123456789"
|
|
341
|
+
```
|
|
342
|
+
|
|
343
|
+
### External adapter plugin system
|
|
344
|
+
|
|
345
|
+
Community adapters can be installed via npm and loaded automatically:
|
|
346
|
+
|
|
347
|
+
```bash
|
|
348
|
+
npm install ccd-adapter-slack
|
|
349
|
+
```
|
|
350
|
+
|
|
351
|
+
The daemon discovers adapters matching the `ccd-adapter-*` naming convention. Channel types are exported from the package entry point for adapter authors.
|
|
352
|
+
|
|
353
|
+
## Quick start
|
|
354
|
+
|
|
355
|
+
```bash
|
|
356
|
+
# Prerequisites
|
|
357
|
+
brew install tmux # macOS
|
|
358
|
+
|
|
359
|
+
# Install
|
|
360
|
+
npm install -g claude-channel-daemon
|
|
361
|
+
|
|
362
|
+
# Interactive setup
|
|
363
|
+
ccd init
|
|
364
|
+
|
|
365
|
+
# Start the fleet
|
|
366
|
+
ccd fleet start
|
|
367
|
+
```
|
|
368
|
+
|
|
369
|
+
## Commands
|
|
370
|
+
|
|
371
|
+
### Telegram commands (General topic)
|
|
372
|
+
|
|
373
|
+
| Command | Description |
|
|
374
|
+
|---------|-------------|
|
|
375
|
+
| `/status` | Show fleet status, context %, and costs |
|
|
376
|
+
| `/reload` | Restart fleet with new code (requires launchd service) |
|
|
377
|
+
|
|
378
|
+
All other operations (create/delete/start instances, delegate tasks) are handled by the General instance through natural language.
|
|
379
|
+
|
|
380
|
+
### Fleet management
|
|
381
|
+
|
|
382
|
+
```bash
|
|
383
|
+
ccd fleet start # Start all instances (not needed with launchd)
|
|
384
|
+
ccd fleet stop # Stop all instances
|
|
385
|
+
ccd fleet restart # Graceful restart (wait for idle, same code)
|
|
386
|
+
ccd fleet restart --reload # Restart with new code (launchd auto-restarts)
|
|
387
|
+
ccd fleet status # Show instance status
|
|
388
|
+
ccd fleet logs <name> # Show instance logs
|
|
389
|
+
ccd fleet history # Show event history (cost, rotations, hangs)
|
|
390
|
+
ccd fleet start <name> # Start specific instance
|
|
391
|
+
ccd fleet stop <name> # Stop specific instance
|
|
392
|
+
ccd fleet cleanup # Remove orphaned instance directories
|
|
393
|
+
ccd fleet cleanup --dry-run # Preview cleanup without deleting
|
|
394
|
+
```
|
|
395
|
+
|
|
396
|
+
### Schedules
|
|
397
|
+
|
|
398
|
+
```bash
|
|
399
|
+
ccd schedule list # List all schedules
|
|
400
|
+
ccd schedule add # Add a schedule from CLI
|
|
401
|
+
ccd schedule delete <id> # Delete a schedule
|
|
402
|
+
ccd schedule enable <id> # Enable a schedule
|
|
403
|
+
ccd schedule disable <id> # Disable a schedule
|
|
404
|
+
ccd schedule history <id> # Show schedule run history
|
|
405
|
+
```
|
|
406
|
+
|
|
407
|
+
### Topic bindings
|
|
408
|
+
|
|
409
|
+
```bash
|
|
410
|
+
ccd topic list # List topic bindings
|
|
411
|
+
ccd topic bind <name> <tid> # Bind instance to topic
|
|
412
|
+
ccd topic unbind <name> # Unbind instance from topic
|
|
413
|
+
```
|
|
414
|
+
|
|
415
|
+
### Access control
|
|
416
|
+
|
|
417
|
+
```bash
|
|
418
|
+
ccd access lock <name> # Lock instance access
|
|
419
|
+
ccd access unlock <name> # Unlock instance access
|
|
420
|
+
ccd access list <name> # List allowed users
|
|
421
|
+
ccd access remove <name> <uid> # Remove user
|
|
422
|
+
ccd access pair <name> <uid> # Generate pairing code
|
|
423
|
+
```
|
|
424
|
+
|
|
425
|
+
### Setup & service
|
|
426
|
+
|
|
427
|
+
```bash
|
|
428
|
+
ccd init # Interactive setup wizard
|
|
429
|
+
ccd install # Install as system service (launchd/systemd)
|
|
430
|
+
ccd install --activate # Install and start immediately
|
|
431
|
+
ccd uninstall # Remove system service
|
|
432
|
+
ccd export [path] # Export config for device migration
|
|
433
|
+
ccd export --full [path] # Export config + all instance data
|
|
434
|
+
ccd import <file> # Import config from export file
|
|
435
|
+
```
|
|
436
|
+
|
|
437
|
+
## Configuration
|
|
438
|
+
|
|
439
|
+
Fleet config at `~/.claude-channel-daemon/fleet.yaml`:
|
|
440
|
+
|
|
441
|
+
```yaml
|
|
442
|
+
project_roots:
|
|
443
|
+
- ~/Projects
|
|
444
|
+
|
|
445
|
+
channel:
|
|
446
|
+
type: telegram # telegram or discord
|
|
447
|
+
mode: topic # topic (recommended) or dm
|
|
448
|
+
bot_token_env: CCD_BOT_TOKEN
|
|
449
|
+
group_id: -100xxxxxxxxxx
|
|
450
|
+
access:
|
|
451
|
+
mode: locked # locked or pairing
|
|
452
|
+
allowed_users:
|
|
453
|
+
- 123456789
|
|
454
|
+
|
|
455
|
+
defaults:
|
|
456
|
+
cost_guard:
|
|
457
|
+
daily_limit_usd: 50
|
|
458
|
+
warn_at_percentage: 80
|
|
459
|
+
timezone: "Asia/Taipei"
|
|
460
|
+
daily_summary:
|
|
461
|
+
enabled: true
|
|
462
|
+
hour: 21
|
|
463
|
+
minute: 0
|
|
464
|
+
context_guardian:
|
|
465
|
+
restart_threshold_pct: 80
|
|
466
|
+
max_age_hours: 8
|
|
467
|
+
model_failover: ["opus", "sonnet"]
|
|
468
|
+
webhooks:
|
|
469
|
+
- url: https://hooks.slack.com/...
|
|
470
|
+
events: ["rotation", "hang", "cost_warn"]
|
|
471
|
+
log_level: info
|
|
472
|
+
|
|
473
|
+
instances:
|
|
474
|
+
my-project:
|
|
475
|
+
working_directory: /path/to/project
|
|
476
|
+
topic_id: 277
|
|
477
|
+
description: "Main backend service"
|
|
478
|
+
tags: ["backend", "api"] # searchable labels; visible in list_instances
|
|
479
|
+
cost_guard:
|
|
480
|
+
daily_limit_usd: 30
|
|
481
|
+
model: opus
|
|
482
|
+
```
|
|
483
|
+
|
|
484
|
+
Secrets in `~/.claude-channel-daemon/.env`:
|
|
485
|
+
```
|
|
486
|
+
CCD_BOT_TOKEN=123456789:AAH...
|
|
487
|
+
GROQ_API_KEY=gsk_... # optional, for voice transcription
|
|
488
|
+
```
|
|
489
|
+
|
|
490
|
+
## Data directory
|
|
491
|
+
|
|
492
|
+
`~/.claude-channel-daemon/`:
|
|
493
|
+
|
|
494
|
+
| Path | Purpose |
|
|
495
|
+
|------|---------|
|
|
496
|
+
| `fleet.yaml` | Fleet configuration |
|
|
497
|
+
| `.env` | Bot token + API keys |
|
|
498
|
+
| `fleet.log` | Fleet log (JSON) |
|
|
499
|
+
| `fleet.pid` | Fleet manager PID |
|
|
500
|
+
| `scheduler.db` | Schedule database (SQLite) |
|
|
501
|
+
| `events.db` | Event log (cost snapshots, rotations, hangs) |
|
|
502
|
+
| `instances/<name>/` | Per-instance data |
|
|
503
|
+
| `instances/<name>/daemon.log` | Instance log |
|
|
504
|
+
| `instances/<name>/session-id` | Session UUID for `--resume` |
|
|
505
|
+
| `instances/<name>/statusline.json` | Latest Claude status line |
|
|
506
|
+
| `instances/<name>/channel.sock` | IPC Unix socket |
|
|
507
|
+
| `instances/<name>/claude-settings.json` | Per-instance Claude settings |
|
|
508
|
+
| `instances/<name>/rotation-state.json` | Context restart snapshot |
|
|
509
|
+
| `instances/<name>/output.log` | Claude tmux output capture |
|
|
510
|
+
|
|
511
|
+
## Requirements
|
|
512
|
+
|
|
513
|
+
- Node.js >= 20
|
|
514
|
+
- tmux
|
|
515
|
+
- Claude Code CLI (`claude`)
|
|
516
|
+
- Telegram bot token ([@BotFather](https://t.me/BotFather))
|
|
517
|
+
- Groq API key (optional, for voice transcription)
|
|
518
|
+
|
|
519
|
+
## Security considerations
|
|
520
|
+
|
|
521
|
+
Running Claude Code remotely via Telegram changes the trust model compared to sitting at a terminal. Be aware of the following:
|
|
522
|
+
|
|
523
|
+
### Telegram account = shell access
|
|
524
|
+
|
|
525
|
+
Any user in `allowed_users` can instruct Claude to run arbitrary shell commands on the host machine. If your Telegram account is compromised (stolen session, social engineering, borrowed phone), the attacker effectively has shell access. Mitigations:
|
|
526
|
+
|
|
527
|
+
- Enable Telegram 2FA
|
|
528
|
+
- Keep `allowed_users` minimal
|
|
529
|
+
- Use `pairing` mode instead of pre-configuring user IDs when possible
|
|
530
|
+
- Review the Claude Code permission allow/deny lists in `claude-settings.json`
|
|
531
|
+
|
|
532
|
+
### Permission bypass (`skipPermissions`)
|
|
533
|
+
|
|
534
|
+
The `skipPermissions` config option passes `--dangerously-skip-permissions` to Claude Code, which disables all tool-use permission prompts. This means Claude can read/write any file, run any command, and make network requests without asking. This is Claude Code's official flag for automation scenarios, but in a remote Telegram context it means **zero human-in-the-loop for any operation**. Only enable this if you fully trust the deployment environment.
|
|
535
|
+
|
|
536
|
+
### `Bash(*)` in the allow list
|
|
537
|
+
|
|
538
|
+
By default (when `skipPermissions` is false), ccd configures `Bash(*)` in Claude Code's permission allow list so that shell commands don't require individual approval. The deny list blocks a few destructive patterns (`rm -rf /`, `dd`, `mkfs`), but this is a blocklist — it cannot cover all dangerous commands. This matches Claude Code's own permission model, where `Bash(*)` is a supported power-user configuration.
|
|
539
|
+
|
|
540
|
+
If you want tighter control, edit the `allow` list in `claude-settings.json` (generated per-instance in `~/.claude-channel-daemon/instances/<name>/`) to use specific patterns like `Bash(npm test)`, `Bash(git *)` instead of `Bash(*)`.
|
|
541
|
+
|
|
542
|
+
### IPC socket
|
|
543
|
+
|
|
544
|
+
The daemon communicates with Claude's MCP server via a Unix socket at `~/.claude-channel-daemon/instances/<name>/channel.sock`. The socket is restricted to owner-only access (`0600`) and requires a shared secret handshake. These measures prevent other local processes from injecting messages, but do not protect against a compromised user account on the same machine.
|
|
545
|
+
|
|
546
|
+
### Secrets storage
|
|
547
|
+
|
|
548
|
+
Bot tokens and API keys are stored in plaintext at `~/.claude-channel-daemon/.env`. The `ccd export` command includes this file and warns about secure transfer. Consider filesystem encryption if the host is shared.
|
|
549
|
+
|
|
550
|
+
## Known limitations
|
|
551
|
+
|
|
552
|
+
- Only tested on macOS
|
|
553
|
+
- Official telegram plugin in global `enabledPlugins` causes 409 polling conflicts (daemon retries with backoff)
|
|
554
|
+
|
|
555
|
+
## License
|
|
556
|
+
|
|
557
|
+
MIT
|