@0xmaxma/claude-gateway 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.
Files changed (192) hide show
  1. package/README.md +791 -0
  2. package/config.template.json +87 -0
  3. package/dist/agent/context-isolation.d.ts +28 -0
  4. package/dist/agent/context-isolation.d.ts.map +1 -0
  5. package/dist/agent/context-isolation.js +80 -0
  6. package/dist/agent/context-isolation.js.map +1 -0
  7. package/dist/agent/runner.d.ts +211 -0
  8. package/dist/agent/runner.d.ts.map +1 -0
  9. package/dist/agent/runner.js +1802 -0
  10. package/dist/agent/runner.js.map +1 -0
  11. package/dist/agent/workspace-loader.d.ts +31 -0
  12. package/dist/agent/workspace-loader.d.ts.map +1 -0
  13. package/dist/agent/workspace-loader.js +207 -0
  14. package/dist/agent/workspace-loader.js.map +1 -0
  15. package/dist/api/auth.d.ts +25 -0
  16. package/dist/api/auth.d.ts.map +1 -0
  17. package/dist/api/auth.js +77 -0
  18. package/dist/api/auth.js.map +1 -0
  19. package/dist/api/cron-router.d.ts +22 -0
  20. package/dist/api/cron-router.d.ts.map +1 -0
  21. package/dist/api/cron-router.js +197 -0
  22. package/dist/api/cron-router.js.map +1 -0
  23. package/dist/api/gateway-router.d.ts +52 -0
  24. package/dist/api/gateway-router.d.ts.map +1 -0
  25. package/dist/api/gateway-router.js +215 -0
  26. package/dist/api/gateway-router.js.map +1 -0
  27. package/dist/api/router.d.ts +5 -0
  28. package/dist/api/router.d.ts.map +1 -0
  29. package/dist/api/router.js +1098 -0
  30. package/dist/api/router.js.map +1 -0
  31. package/dist/api/skills-router.d.ts +5 -0
  32. package/dist/api/skills-router.d.ts.map +1 -0
  33. package/dist/api/skills-router.js +424 -0
  34. package/dist/api/skills-router.js.map +1 -0
  35. package/dist/api/workspace-router.d.ts +4 -0
  36. package/dist/api/workspace-router.d.ts.map +1 -0
  37. package/dist/api/workspace-router.js +149 -0
  38. package/dist/api/workspace-router.js.map +1 -0
  39. package/dist/config/loader.d.ts +16 -0
  40. package/dist/config/loader.d.ts.map +1 -0
  41. package/dist/config/loader.js +235 -0
  42. package/dist/config/loader.js.map +1 -0
  43. package/dist/config/migrator.d.ts +70 -0
  44. package/dist/config/migrator.d.ts.map +1 -0
  45. package/dist/config/migrator.js +343 -0
  46. package/dist/config/migrator.js.map +1 -0
  47. package/dist/config/watcher.d.ts +30 -0
  48. package/dist/config/watcher.d.ts.map +1 -0
  49. package/dist/config/watcher.js +170 -0
  50. package/dist/config/watcher.js.map +1 -0
  51. package/dist/cron/manager.d.ts +48 -0
  52. package/dist/cron/manager.d.ts.map +1 -0
  53. package/dist/cron/manager.js +580 -0
  54. package/dist/cron/manager.js.map +1 -0
  55. package/dist/cron/scheduler.d.ts +50 -0
  56. package/dist/cron/scheduler.d.ts.map +1 -0
  57. package/dist/cron/scheduler.js +231 -0
  58. package/dist/cron/scheduler.js.map +1 -0
  59. package/dist/discord/receiver.d.ts +17 -0
  60. package/dist/discord/receiver.d.ts.map +1 -0
  61. package/dist/discord/receiver.js +108 -0
  62. package/dist/discord/receiver.js.map +1 -0
  63. package/dist/heartbeat/history.d.ts +24 -0
  64. package/dist/heartbeat/history.d.ts.map +1 -0
  65. package/dist/heartbeat/history.js +50 -0
  66. package/dist/heartbeat/history.js.map +1 -0
  67. package/dist/heartbeat/parser.d.ts +26 -0
  68. package/dist/heartbeat/parser.d.ts.map +1 -0
  69. package/dist/heartbeat/parser.js +283 -0
  70. package/dist/heartbeat/parser.js.map +1 -0
  71. package/dist/history/cleanup.d.ts +21 -0
  72. package/dist/history/cleanup.d.ts.map +1 -0
  73. package/dist/history/cleanup.js +171 -0
  74. package/dist/history/cleanup.js.map +1 -0
  75. package/dist/history/db.d.ts +27 -0
  76. package/dist/history/db.d.ts.map +1 -0
  77. package/dist/history/db.js +323 -0
  78. package/dist/history/db.js.map +1 -0
  79. package/dist/history/media-store.d.ts +27 -0
  80. package/dist/history/media-store.d.ts.map +1 -0
  81. package/dist/history/media-store.js +158 -0
  82. package/dist/history/media-store.js.map +1 -0
  83. package/dist/history/types.d.ts +62 -0
  84. package/dist/history/types.d.ts.map +1 -0
  85. package/dist/history/types.js +3 -0
  86. package/dist/history/types.js.map +1 -0
  87. package/dist/index.d.ts +3 -0
  88. package/dist/index.d.ts.map +1 -0
  89. package/dist/index.js +483 -0
  90. package/dist/index.js.map +1 -0
  91. package/dist/logger.d.ts +3 -0
  92. package/dist/logger.d.ts.map +1 -0
  93. package/dist/logger.js +81 -0
  94. package/dist/logger.js.map +1 -0
  95. package/dist/memory/manager.d.ts +45 -0
  96. package/dist/memory/manager.d.ts.map +1 -0
  97. package/dist/memory/manager.js +178 -0
  98. package/dist/memory/manager.js.map +1 -0
  99. package/dist/session/compactor.d.ts +22 -0
  100. package/dist/session/compactor.d.ts.map +1 -0
  101. package/dist/session/compactor.js +151 -0
  102. package/dist/session/compactor.js.map +1 -0
  103. package/dist/session/process.d.ts +76 -0
  104. package/dist/session/process.d.ts.map +1 -0
  105. package/dist/session/process.js +729 -0
  106. package/dist/session/process.js.map +1 -0
  107. package/dist/session/store.d.ts +102 -0
  108. package/dist/session/store.d.ts.map +1 -0
  109. package/dist/session/store.js +533 -0
  110. package/dist/session/store.js.map +1 -0
  111. package/dist/skills/index.d.ts +9 -0
  112. package/dist/skills/index.d.ts.map +1 -0
  113. package/dist/skills/index.js +15 -0
  114. package/dist/skills/index.js.map +1 -0
  115. package/dist/skills/invoker.d.ts +16 -0
  116. package/dist/skills/invoker.d.ts.map +1 -0
  117. package/dist/skills/invoker.js +36 -0
  118. package/dist/skills/invoker.js.map +1 -0
  119. package/dist/skills/loader.d.ts +23 -0
  120. package/dist/skills/loader.d.ts.map +1 -0
  121. package/dist/skills/loader.js +183 -0
  122. package/dist/skills/loader.js.map +1 -0
  123. package/dist/skills/parser.d.ts +55 -0
  124. package/dist/skills/parser.d.ts.map +1 -0
  125. package/dist/skills/parser.js +178 -0
  126. package/dist/skills/parser.js.map +1 -0
  127. package/dist/skills/sync.d.ts +10 -0
  128. package/dist/skills/sync.d.ts.map +1 -0
  129. package/dist/skills/sync.js +130 -0
  130. package/dist/skills/sync.js.map +1 -0
  131. package/dist/skills/watcher.d.ts +16 -0
  132. package/dist/skills/watcher.d.ts.map +1 -0
  133. package/dist/skills/watcher.js +25 -0
  134. package/dist/skills/watcher.js.map +1 -0
  135. package/dist/telegram/markdown.d.ts +25 -0
  136. package/dist/telegram/markdown.d.ts.map +1 -0
  137. package/dist/telegram/markdown.js +237 -0
  138. package/dist/telegram/markdown.js.map +1 -0
  139. package/dist/telegram/receiver.d.ts +17 -0
  140. package/dist/telegram/receiver.d.ts.map +1 -0
  141. package/dist/telegram/receiver.js +104 -0
  142. package/dist/telegram/receiver.js.map +1 -0
  143. package/dist/types.d.ts +224 -0
  144. package/dist/types.d.ts.map +1 -0
  145. package/dist/types.js +3 -0
  146. package/dist/types.js.map +1 -0
  147. package/dist/ui/web-ui.d.ts +6 -0
  148. package/dist/ui/web-ui.d.ts.map +1 -0
  149. package/dist/ui/web-ui.js +227 -0
  150. package/dist/ui/web-ui.js.map +1 -0
  151. package/dist/watch/factory.d.ts +26 -0
  152. package/dist/watch/factory.d.ts.map +1 -0
  153. package/dist/watch/factory.js +43 -0
  154. package/dist/watch/factory.js.map +1 -0
  155. package/dist/webhook/manager.d.ts +34 -0
  156. package/dist/webhook/manager.d.ts.map +1 -0
  157. package/dist/webhook/manager.js +120 -0
  158. package/dist/webhook/manager.js.map +1 -0
  159. package/mcp/bun.lock +264 -0
  160. package/mcp/channel-manager.ts +115 -0
  161. package/mcp/package.json +15 -0
  162. package/mcp/router.ts +69 -0
  163. package/mcp/server.ts +129 -0
  164. package/mcp/tools/agent/handlers.ts +578 -0
  165. package/mcp/tools/agent/module.ts +163 -0
  166. package/mcp/tools/browser/module.ts +321 -0
  167. package/mcp/tools/browser/skills/open-browser/SKILL.md +89 -0
  168. package/mcp/tools/cron/client.ts +65 -0
  169. package/mcp/tools/cron/module.ts +158 -0
  170. package/mcp/tools/cron/skills/cron/SKILL.md +46 -0
  171. package/mcp/tools/discord/access.ts +197 -0
  172. package/mcp/tools/discord/client.ts +31 -0
  173. package/mcp/tools/discord/commands.ts +48 -0
  174. package/mcp/tools/discord/inbound.ts +53 -0
  175. package/mcp/tools/discord/module.ts +442 -0
  176. package/mcp/tools/discord/outbound.ts +90 -0
  177. package/mcp/tools/discord/receiver-server.ts +58 -0
  178. package/mcp/tools/discord/skills/access/SKILL.md +86 -0
  179. package/mcp/tools/discord/skills/configure/SKILL.md +53 -0
  180. package/mcp/tools/discord/threading.ts +37 -0
  181. package/mcp/tools/discord/types.ts +106 -0
  182. package/mcp/tools/skills/handlers.ts +196 -0
  183. package/mcp/tools/skills/module.ts +163 -0
  184. package/mcp/tools/telegram/module.ts +454 -0
  185. package/mcp/tools/telegram/pure.ts +224 -0
  186. package/mcp/tools/telegram/receiver-server.ts +1656 -0
  187. package/mcp/tools/telegram/skills/access/SKILL.md +169 -0
  188. package/mcp/tools/telegram/skills/configure/SKILL.md +118 -0
  189. package/mcp/tools/telegram/typing.ts +327 -0
  190. package/mcp/types.ts +128 -0
  191. package/package.json +62 -0
  192. package/resource/claude_gateway.svg +112 -0
package/README.md ADDED
@@ -0,0 +1,791 @@
1
+
2
+ # Claude Gateway
3
+
4
+ A self-hosted multi-agent gateway for Claude Code. Connect Claude agents to Telegram, HTTP APIs, and scheduled tasks — each agent runs in an isolated session with its own personality, memory, and tools.
5
+
6
+ <p align="center">
7
+ <img src="resource/claude_gateway.svg" alt="Claude Gateway" width="680" />
8
+ </p>
9
+
10
+ ---
11
+
12
+ ## Features
13
+
14
+ - **Multi-agent** — run multiple bots from a single gateway, each with isolated sessions
15
+ - **Multi-channel MCP** — modular tool system per channel (Telegram, Discord, Cron, Skills, extensible to Slack/WhatsApp)
16
+ - **Agent skills** — extensible skill system via SKILL.md files; agents can create, delete, and install skills from URLs at runtime with hot-reload
17
+ - **Agent identity** — define personality, tone, and rules via workspace markdown files
18
+ - **Live status messages** — real-time status updates showing tool usage, thinking, and progress
19
+ - **Typing indicators** — continuous typing animation while the agent is working (Telegram and Discord)
20
+ - **Streaming API** — SSE (Server-Sent Events) endpoint for real-time response streaming
21
+ - **Auto-forward** — agent text output automatically forwarded to Telegram even without explicit reply tool calls
22
+ - **Heartbeat / scheduled tasks** — cron-based proactive messages and recurring tasks via HEARTBEAT.md + REST API; agent jobs deliver output to Telegram, Discord, or both
23
+ - **Persistent chat history** — two-layer storage: session context (`.jsonl`) + permanent SQLite DB with FTS5 full-text search; survives `/compact` and session eviction
24
+ - **Auto-cleanup** — configurable retention policy prunes messages and media files older than N days on a daily schedule
25
+ - **Long-term memory** — persistent memory system across sessions
26
+ - **Config auto-migration** — automatic schema migration when config format changes
27
+ - **Access control** — allowlist, open, or pairing-based Telegram access policies
28
+ - **HTTP API** — REST API with key-based auth for external integrations
29
+ - **Session persistence** — conversation history saved and restored across restarts
30
+
31
+ ---
32
+
33
+ ## Requirements
34
+
35
+ - Node.js 18+
36
+ - [Claude Code CLI](https://claude.ai/code) v2.1.0+ installed and authenticated — `channels mode` is required (`claude --version`)
37
+ - [Bun](https://bun.sh) — runs the MCP server subprocess (`mcp/server.ts`)
38
+ - A bot token per agent — Telegram (from [@BotFather](https://t.me/BotFather)) or Discord (from [Discord Developer Portal](https://discord.com/developers/applications))
39
+
40
+ ---
41
+
42
+ ## Quick Start
43
+
44
+ ### 1. Install
45
+
46
+ ```bash
47
+ git clone <repo>
48
+ cd claude-gateway
49
+ npm install
50
+ npm run build
51
+ ```
52
+
53
+ ### 2. Install MCP server dependencies
54
+
55
+ The gateway MCP server uses Bun with its own `package.json`. Install once:
56
+
57
+ ```bash
58
+ make mcp-install # runs: cd mcp && bun install
59
+ ```
60
+
61
+ This installs `grammy` (Telegram Bot API) and `@modelcontextprotocol/sdk` into `mcp/node_modules/`.
62
+
63
+ ### 3. Create an agent
64
+
65
+ The interactive wizard handles everything — workspace files, config, bot token, and pairing:
66
+
67
+ ```bash
68
+ make create-agent
69
+ ```
70
+
71
+ Steps:
72
+ 1. Choose an agent name
73
+ 2. Describe the agent — Claude generates workspace files
74
+ 3. Review and accept generated files
75
+ 4. Choose a channel: **Telegram** or **Discord**
76
+ 5. Paste the bot token — wizard verifies it automatically
77
+ 6. Send any message to the bot to complete pairing
78
+ 7. Agent sends a welcome message
79
+
80
+ ### 4. Start the gateway
81
+
82
+ ```bash
83
+ npm start
84
+ ```
85
+
86
+ Config is auto-loaded from `~/.claude-gateway/config.json`. Bot tokens are auto-loaded from `~/.claude-gateway/agents/<id>/.env`.
87
+
88
+ ---
89
+
90
+ ## Workspace Files
91
+
92
+ Each agent has a workspace directory with markdown files that define its behaviour:
93
+
94
+ | File | Required | Purpose |
95
+ |------|----------|---------|
96
+ | `AGENTS.md` | **Yes** | Core identity, rules, capabilities |
97
+ | `IDENTITY.md` | No | Agent name, emoji, avatar, personality identity |
98
+ | `SOUL.md` | No | Tone, personality, speaking style |
99
+ | `USER.md` | No | User profile and preferences |
100
+ | `MEMORY.md` | No | Long-term memory (auto-appended by the agent) |
101
+ | `HEARTBEAT.md` | No | Scheduled/proactive tasks |
102
+ | `skills/` | No | Directory of SKILL.md files — agent-specific skills |
103
+
104
+ On startup (and on any file change), all files are assembled into `CLAUDE.md` which the Claude subprocess reads as its system prompt. Do not edit `CLAUDE.md` directly.
105
+
106
+ ---
107
+
108
+ ## Configuration Reference
109
+
110
+ Config lives at `~/.claude-gateway/config.json` (or set `GATEWAY_CONFIG` env var / `--config` flag).
111
+
112
+ ```json
113
+ {
114
+ "configVersion": "1.0.0",
115
+ "gateway": {
116
+ "logDir": "~/.claude-gateway/logs",
117
+ "timezone": "Asia/Bangkok",
118
+ "api": {
119
+ "keys": [
120
+ {
121
+ "key": "${MY_API_KEY}",
122
+ "description": "Internal app",
123
+ "agents": ["alfred"]
124
+ },
125
+ {
126
+ "key": "${ADMIN_API_KEY}",
127
+ "description": "Admin",
128
+ "agents": "*"
129
+ }
130
+ ]
131
+ }
132
+ },
133
+ "agents": [
134
+ {
135
+ "id": "alfred",
136
+ "description": "Personal assistant",
137
+ "workspace": "~/.claude-gateway/agents/alfred/workspace",
138
+ "env": "",
139
+ "session": {
140
+ "idleTimeoutMinutes": 30,
141
+ "maxConcurrent": 20
142
+ },
143
+ "telegram": {
144
+ "botToken": "${ALFRED_BOT_TOKEN}"
145
+ },
146
+ "claude": {
147
+ "model": "claude-sonnet-4-6",
148
+ "dangerouslySkipPermissions": true,
149
+ "extraFlags": []
150
+ },
151
+ "heartbeat": {
152
+ "rateLimitMinutes": 30
153
+ }
154
+ }
155
+ ]
156
+ }
157
+ ```
158
+
159
+ ### `session`
160
+
161
+ | Field | Default | Description |
162
+ |-------|---------|-------------|
163
+ | `idleTimeoutMinutes` | `30` | Kill idle session subprocess after N minutes of inactivity |
164
+ | `maxConcurrent` | `20` | Max simultaneous active sessions per agent; oldest idle is evicted when exceeded |
165
+
166
+ ### `gateway.history` (optional)
167
+
168
+ Global default retention policy. Can be overridden per-agent with an `history` key inside the agent config.
169
+
170
+ ```json
171
+ {
172
+ "gateway": {
173
+ "history": {
174
+ "retentionDays": 90,
175
+ "cleanupHour": 3,
176
+ "cleanupTimezone": "Asia/Bangkok"
177
+ }
178
+ }
179
+ }
180
+ ```
181
+
182
+ | Field | Default | Description |
183
+ |-------|---------|-------------|
184
+ | `retentionDays` | `null` (keep forever) | Delete messages older than N days on each cleanup cycle |
185
+ | `cleanupHour` | `3` | Hour of day to run cleanup (24h, in `cleanupTimezone`) |
186
+ | `cleanupTimezone` | `"UTC"` | IANA timezone for the cleanup schedule |
187
+
188
+ Per-agent override example:
189
+ ```json
190
+ {
191
+ "agents": [
192
+ {
193
+ "id": "alfred",
194
+ "history": { "retentionDays": 30 }
195
+ }
196
+ ]
197
+ }
198
+ ```
199
+
200
+ ### `dmPolicy`
201
+
202
+ Access policy is configured per-channel in the agent's workspace state file, not in `config.json`:
203
+
204
+ | File | Path |
205
+ |------|------|
206
+ | Telegram | `~/.claude-gateway/agents/<id>/workspace/.telegram-state/access.json` |
207
+ | Discord | `~/.claude-gateway/agents/<id>/workspace/.discord-state/access.json` |
208
+
209
+ | Value | Behaviour |
210
+ |-------|-----------|
211
+ | `allowlist` | Only user IDs in `allowFrom` can DM the agent (**default**) |
212
+ | `open` | Anyone can DM the agent |
213
+ | `pairing` | New users DM the bot to receive a pairing code; approve with `npm run pair` |
214
+
215
+ ### `dangerouslySkipPermissions`
216
+
217
+ Set to `true` for all agents running headless (no interactive terminal). Without it the agent cannot use MCP tools like sending Telegram replies.
218
+
219
+ ### `gateway.api.keys`
220
+
221
+ Each key has a `key` string (supports `${ENV_VAR}` interpolation), an optional `description`, and an `agents` field — either an array of agent IDs or `"*"` for full access. Keys support both `Authorization: Bearer` and `X-Api-Key` headers.
222
+
223
+ ### Bot tokens
224
+
225
+ Tokens are stored per-agent at `~/.claude-gateway/agents/<id>/.env` and auto-loaded at startup. Use `${AGENT_BOT_TOKEN}` syntax in config to reference them, or set them as shell environment variables.
226
+
227
+ ---
228
+
229
+ ## Architecture
230
+
231
+ ```
232
+ ┌─────────────────────────────────────────────────┐
233
+ │ Claude Gateway │
234
+ │ │
235
+ Telegram Bot A ──► TelegramReceiver(A) ──► AgentRunner(A) ─┬─► Session(chat:111) ──► Claude + MCP
236
+ ├─► Session(chat:222) ──► Claude + MCP
237
+ Telegram Bot B ──► TelegramReceiver(B) ──► AgentRunner(B) ──┴─► Session(chat:333) ──► Claude + MCP
238
+
239
+ HTTP Client ──► POST /api/v1/.../messages ────────────────┴─► Session(api:uuid) ──► Claude
240
+ (sync JSON or SSE stream)
241
+ │ │
242
+ │ GatewayRouter (/health, /status, /ui, /api) │
243
+ │ CronScheduler (HEARTBEAT.md + REST API) │
244
+ │ TypingManager (live status indicators) │
245
+ └─────────────────────────────────────────────────┘
246
+
247
+ ┌───────────────────────────────────┐
248
+ │ MCP Server (per session) │
249
+ │ mcp/server.ts │
250
+ │ │
251
+ │ telegram_reply │
252
+ │ telegram_react │
253
+ │ telegram_edit_message │
254
+ │ telegram_download_attachment │
255
+ │ cron_list / cron_create / ... │
256
+ │ skill_create / skill_delete / ... │
257
+ └───────────────────────────────────┘
258
+ ```
259
+
260
+ Each agent runs a **dedicated TelegramReceiver** (single poller per bot token) and a **session pool** of isolated Claude subprocesses — one per chat or API session. Each session gets its own **MCP server** (`mcp/server.ts`) exposing channel-specific tools (Telegram reply, react, cron management, skill management). Sessions persist history via `SessionStore`, so Claude remembers the conversation even after idle restart.
261
+
262
+ ### Session Pool
263
+
264
+ Each agent maintains a **session pool** — a separate Claude subprocess per chat ID (Telegram) or session UUID (API). Sessions are fully isolated: Claude sees only its own conversation history with no cross-session leakage.
265
+
266
+ ```
267
+ TelegramReceiver (1 per agent, spawned by gateway)
268
+ - single long-poll connection per bot token
269
+ - handles access control (allowlist / pairing)
270
+ - runs as: bun mcp/tools/telegram/receiver-server.ts (RECEIVER_MODE)
271
+ - POSTs incoming messages to AgentRunner callback
272
+
273
+ AgentRunner (session pool manager)
274
+ ├── SessionProcess(chat:111) ──► Claude subprocess + MCP server (SEND_ONLY)
275
+ ├── SessionProcess(chat:222) ──► Claude subprocess + MCP server (SEND_ONLY)
276
+ └── SessionProcess(api:uuid) ──► Claude subprocess (no MCP — API-only)
277
+ ```
278
+
279
+ ### MCP Tool System
280
+
281
+ The MCP server (`mcp/server.ts`) uses a **modular multi-channel architecture**. Each channel is a separate module implementing `ChannelModule` or `ToolModule` interfaces:
282
+
283
+ | Module | Interface | Tools | Purpose |
284
+ |--------|-----------|-------|---------|
285
+ | `telegram` | `ChannelModule` | `telegram_reply`, `telegram_react`, `telegram_edit_message`, `telegram_download_attachment` | Send messages, reactions, edit messages in Telegram |
286
+ | `discord` | `ChannelModule` | `discord_reply`, `discord_react`, `discord_edit_message` | Send messages, reactions, edit messages in Discord |
287
+ | `cron` | `ToolModule` | `cron_list`, `cron_create`, `cron_delete`, `cron_run`, `cron_get_runs` | Manage scheduled jobs via gateway REST API |
288
+ | `skills` | `ToolModule` | `skill_create`, `skill_delete`, `skill_install` | Create, delete, and install agent skills at runtime |
289
+
290
+ Tools are **prefixed by channel name** to avoid collisions. Each module controls its own visibility and lifecycle.
291
+
292
+ **Adding a new channel** (e.g. Slack) means implementing `ChannelModule` interface in `mcp/tools/slack/module.ts` and registering it in `server.ts`.
293
+
294
+ ### Process Modes
295
+
296
+ | Mode | Process | Behaviour |
297
+ |------|---------|-----------|
298
+ | `TELEGRAM_RECEIVER_MODE` | `receiver-server.ts` | Polls Telegram, handles commands, POSTs to callback — **no MCP** |
299
+ | `TELEGRAM_SEND_ONLY` | `server.ts` | Exposes MCP tools (`telegram_*`, `cron_*`) — **no polling** |
300
+
301
+ ### Session Persistence
302
+
303
+ History is persisted to `SessionStore` (`.jsonl` files) after each message. When a session is spawned after an idle restart, history is injected into the initial prompt so Claude resumes the conversation seamlessly.
304
+
305
+ ---
306
+
307
+ ## Live Status Messages
308
+
309
+ While an agent is working, the gateway sends real-time status updates to Telegram showing what the agent is doing:
310
+
311
+ ```
312
+ ☑️ : 🧠 Analyzing the codebase structure...
313
+ ☑️ : 📖 Reading: src/agent/runner.ts
314
+ ☑️ : 🔍 Searching for: "sendMessage" in src/
315
+ 🕐 : ✏️ Editing: mcp/tools/telegram/typing.ts
316
+ (elapsed: 2m 30s)
317
+ ```
318
+
319
+ - **Tool tracking** — each tool call is displayed with a descriptive label (e.g. `📖 Reading: config.ts`, `⚡ Running: npm test`)
320
+ - **History** — previous steps shown with ☑️, current step with 🕐
321
+ - **Thinking** — agent's reasoning shown with 🧠
322
+ - **Elapsed time** — total time since the agent started working
323
+ - **Auto-cleanup** — status message is deleted when the agent finishes
324
+
325
+ Status updates are sent every 5-10 seconds (first update at 5s, then every 10s).
326
+
327
+ ---
328
+
329
+ ## HTTP API
330
+
331
+ When `gateway.api.keys` is configured, the gateway exposes a REST API for external clients.
332
+
333
+ Pass API key via `X-Api-Key: <key>` or `Authorization: Bearer <key>` header.
334
+
335
+ **Endpoints:**
336
+
337
+ | Method | Path | Description |
338
+ |--------|------|-------------|
339
+ | `GET` | `/api/v1/agents` | List agents accessible by the provided key |
340
+ | `POST` | `/api/v1/agents/:agentId/messages` | Send a message — sync JSON or SSE stream |
341
+ | `GET` | `/api/v1/crons` | List cron jobs accessible by key |
342
+ | `GET` | `/api/v1/crons/status` | Scheduler status |
343
+ | `POST` | `/api/v1/crons` | Create a scheduled job |
344
+ | `GET` | `/api/v1/crons/:id` | Get a single job |
345
+ | `PUT` | `/api/v1/crons/:id` | Update a job |
346
+ | `DELETE` | `/api/v1/crons/:id` | Delete a job |
347
+ | `POST` | `/api/v1/crons/:id/run` | Trigger a job manually |
348
+ | `GET` | `/api/v1/crons/:id/runs` | Get run history |
349
+ | `GET` | `/api/v1/agents/sessions` | List all sessions across all agents (admin key) |
350
+ | `GET` | `/api/v1/agents/:agentId/chats` | List chats for an agent |
351
+ | `DELETE` | `/api/v1/agents/:agentId/chats/:chatId` | Delete a chat and all its messages |
352
+ | `GET` | `/api/v1/agents/:agentId/chats/:chatId/sessions` | List sessions for a specific chat |
353
+ | `GET` | `/api/v1/agents/:agentId/chats/:chatId/messages` | Paginated message history (cursor-based) |
354
+ | `POST` | `/api/v1/agents/:agentId/chats/:chatId/sessions/:sessionId/messages` | Inject a message into an existing session |
355
+ | `POST` | `/api/v1/agents/:agentId/media` | Upload a media file (image or PDF) |
356
+ | `GET` | `/api/v1/agents/:agentId/media/*` | Serve a media file by path |
357
+
358
+ See **[API.md](./API.md)** for full reference with request/response schemas and curl examples.
359
+
360
+ ---
361
+
362
+ ## File Structure
363
+
364
+ ### Project
365
+
366
+ ```
367
+ claude-gateway/
368
+ ├── Makefile ← make start / create-agent / update-agent / pair / mcp-install
369
+ ├── config.template.json ← config template (source of truth for migration)
370
+
371
+ ├── src/ ← Gateway core (TypeScript, compiled to dist/)
372
+ │ ├── index.ts ← entrypoint — loads config, starts agents
373
+ │ ├── types.ts ← shared TypeScript types
374
+ │ ├── logger.ts ← structured logging with per-agent files
375
+ │ │
376
+ │ ├── agent/ ← Agent management
377
+ │ │ ├── runner.ts ← session pool manager (spawn/evict sessions)
378
+ │ │ ├── workspace-loader.ts ← assembles CLAUDE.md from workspace files + skills
379
+ │ │ └── context-isolation.ts ← context guard for session isolation
380
+ │ │
381
+ │ ├── session/ ← Session lifecycle
382
+ │ │ ├── process.ts ← single Claude subprocess per session
383
+ │ │ ├── store.ts ← persist/load conversation history (.jsonl)
384
+ │ │ └── compactor.ts ← summarise + compact old history
385
+ │ │
386
+ │ ├── telegram/ ← Telegram integration
387
+ │ │ ├── receiver.ts ← spawns TelegramReceiver subprocess per agent
388
+ │ │ └── markdown.ts ← markdown/HTML utilities
389
+ │ │
390
+ │ ├── api/ ← HTTP API
391
+ │ │ ├── gateway-router.ts ← HTTP server (/health, /status, /ui, /api)
392
+ │ │ ├── router.ts ← REST API router (sync + SSE streaming)
393
+ │ │ ├── auth.ts ← API key auth middleware (timing-safe)
394
+ │ │ └── cron-router.ts ← Cron API router (auth + agent-scoped access)
395
+ │ │
396
+ │ ├── config/ ← Configuration
397
+ │ │ ├── loader.ts ← load + validate config.json
398
+ │ │ ├── migrator.ts ← auto-migration for config schema changes
399
+ │ │ └── watcher.ts ← hot-reload config on file change
400
+ │ │
401
+ │ ├── cron/ ← Cron scheduling
402
+ │ │ ├── manager.ts ← persistent cron job manager (REST + agentTurn)
403
+ │ │ └── scheduler.ts ← heartbeat task scheduler
404
+ │ │
405
+ │ ├── heartbeat/ ← Proactive tasks
406
+ │ │ ├── parser.ts ← parse HEARTBEAT.md YAML
407
+ │ │ └── history.ts ← track scheduled task execution
408
+ │ │
409
+ │ ├── skills/ ← Agent skills system
410
+ │ │ ├── index.ts ← re-exports (parser, loader, invoker, watcher)
411
+ │ │ ├── parser.ts ← parse SKILL.md frontmatter + body
412
+ │ │ ├── loader.ts ← load skills from directories, build registry
413
+ │ │ ├── invoker.ts ← detect /skill-name in messages, inject context
414
+ │ │ └── watcher.ts ← hot-reload skills on file changes (chokidar)
415
+ │ │
416
+ │ ├── history/ ← Persistent chat history (Layer 2)
417
+ │ │ ├── db.ts ← SQLite WAL + FTS5 history DB (pruneOlderThan, listChats, search)
418
+ │ │ ├── cleanup.ts ← daily retention scheduler (scheduleCleanup, resolveRetentionDays)
419
+ │ │ ├── media-store.ts ← media file store with MIME allowlist and path traversal guard
420
+ │ │ └── types.ts ← HistoryMessage, ChatSummary, SessionSummary types
421
+ │ │
422
+ │ ├── memory/ ← Long-term memory
423
+ │ │ └── manager.ts ← memory persistence
424
+ │ │
425
+ │ ├── webhook/ ← Webhooks
426
+ │ │ └── manager.ts ← webhook event dispatch
427
+ │ │
428
+ │ └── ui/ ← Dashboard
429
+ │ └── web-ui.ts ← live HTML dashboard
430
+
431
+ ├── scripts/
432
+ │ ├── create-agent.ts ← interactive agent creation wizard (with channel selection)
433
+ │ ├── create-agent-prompts.ts ← agent workspace generation prompts
434
+ │ ├── update-agent.ts ← update agent.md or manage channels (add/remove)
435
+ │ ├── interactive-select.ts ← interactive selection UI helper
436
+ │ ├── pair.ts ← approve channel pairing (Telegram / Discord)
437
+ │ └── setup-claude-settings.js ← enables channelsEnabled in Claude Code
438
+
439
+ └── mcp/ ← MCP server (runs in Bun, separate node_modules)
440
+ ├── package.json ← dependencies: grammy, @modelcontextprotocol/sdk
441
+ ├── server.ts ← MCP entry point — registers all tool modules
442
+ ├── types.ts ← ChannelModule / ToolModule interfaces
443
+ ├── channel-manager.ts ← module lifecycle (init, start, stop, restart)
444
+ ├── router.ts ← route resolution + channel context rendering
445
+
446
+ └── tools/
447
+ ├── telegram/ ← Telegram channel module
448
+ │ ├── module.ts ← ChannelModule: telegram_reply, react, edit, download
449
+ │ ├── receiver-server.ts ← standalone receiver (polling mode, no MCP)
450
+ │ ├── pure.ts ← markdown → Telegram HTML conversion
451
+ │ ├── typing.ts ← typing indicator state
452
+ │ └── skills/
453
+ │ ├── access/SKILL.md ← /telegram:access skill
454
+ │ └── configure/SKILL.md ← /telegram:configure skill
455
+
456
+ ├── cron/ ← Cron tool module
457
+ │ ├── module.ts ← ToolModule: cron_list, create, delete, run, get_runs
458
+ │ ├── client.ts ← HTTP client for gateway cron REST API
459
+ │ └── skills/
460
+ │ └── cron/SKILL.md ← /cron skill
461
+
462
+ └── skills/ ← Skills tool module
463
+ ├── module.ts ← ToolModule: skill_create, skill_delete, skill_install
464
+ └── handlers.ts ← skill CRUD + URL install handlers
465
+ ```
466
+
467
+ ### Runtime data (`~/.claude-gateway/`)
468
+
469
+ ```
470
+ ~/.claude-gateway/
471
+ ├── config.json ← gateway config
472
+ ├── logs/
473
+ │ ├── alfred.log
474
+ │ └── warrior.log
475
+ ├── shared-skills/ ← shared skills (synced to ~/.claude/skills/ on boot and on change)
476
+ │ └── <skill-name>/
477
+ │ └── SKILL.md ← skill definition (same format as agent skills)
478
+ └── agents/
479
+ └── alfred/
480
+ ├── .env ← bot token (auto-created by wizard)
481
+ ├── sessions/
482
+ │ └── <chat_id>.jsonl ← conversation history (SessionStore)
483
+ ├── history.db ← SQLite chat history (Layer 2 — survives /compact)
484
+ ├── history-cleanup.log ← cleanup run log (max 1 MB, auto-rotated)
485
+ ├── media/ ← uploaded media files (served via /api/v1/agents/:id/media/*)
486
+ └── workspace/
487
+ ├── CLAUDE.md ← auto-generated from workspace files, do not edit
488
+ ├── AGENTS.md ← agent identity, rules, capabilities
489
+ ├── IDENTITY.md ← name, emoji, avatar
490
+ ├── SOUL.md ← tone, personality, speaking style
491
+ ├── USER.md ← user profile and preferences
492
+ ├── MEMORY.md ← long-term memory (auto-appended)
493
+ ├── HEARTBEAT.md ← scheduled/proactive tasks
494
+ ├── skills/ ← agent-specific skills (hot-reloaded)
495
+ │ └── <skill-name>/
496
+ │ └── SKILL.md ← skill definition with frontmatter
497
+ ├── .sessions/ ← per-session MCP config
498
+ │ └── <session_id>/
499
+ │ └── mcp-config.json ← auto-generated MCP config for this session
500
+ ├── .telegram-state/
501
+ │ └── access.json ← Telegram allowlist and pairing state
502
+ └── .discord-state/
503
+ └── access.json ← Discord allowlist and pairing state
504
+ ```
505
+
506
+ ---
507
+
508
+ ## Heartbeat / Scheduled Tasks
509
+
510
+ Define proactive tasks in `HEARTBEAT.md`:
511
+
512
+ ```yaml
513
+ tasks:
514
+ - name: morning-brief
515
+ cron: "0 8 * * *"
516
+ prompt: "Give a brief morning summary."
517
+
518
+ - name: check-in
519
+ interval: 6h
520
+ prompt: "Check if there are any reminders to send."
521
+ ```
522
+
523
+ - `cron` — standard 5-field cron expression
524
+ - `interval` — shorthand: `30m`, `1h`, `6h`, `1d`, `1w`
525
+ - If the agent replies with `HEARTBEAT_OK` (case-insensitive), no message is sent to Telegram
526
+ - `rateLimitMinutes` in config suppresses tasks if a proactive message was already sent recently (default: 30 min)
527
+
528
+ ---
529
+
530
+ ## Agent Skills
531
+
532
+ Skills are reusable capabilities defined as `SKILL.md` files with YAML frontmatter. They are injected into the agent's system prompt and can be invoked via `/skill-name` commands.
533
+
534
+ ### Skill locations
535
+
536
+ | Location | Scope | Description |
537
+ |----------|-------|-------------|
538
+ | `workspace/skills/<name>/SKILL.md` | Per-agent | Agent-specific skills |
539
+ | `~/.claude-gateway/shared-skills/<name>/SKILL.md` | All agents | Shared skills — synced to `~/.claude/skills/` at boot and on change |
540
+ | `mcp/tools/<channel>/skills/<name>/SKILL.md` | All agents | Built-in channel skills (e.g. `/telegram:access`) |
541
+
542
+ ### SKILL.md format
543
+
544
+ ```yaml
545
+ ---
546
+ name: my-skill
547
+ description: What this skill does
548
+ user_invocable: true # false = system-only, not shown to user
549
+ argument_description: "[args]" # optional, shown in /skill-name [args]
550
+ ---
551
+
552
+ Skill instructions go here. Claude follows these instructions
553
+ when the user invokes /my-skill.
554
+ ```
555
+
556
+ ### Runtime skill management
557
+
558
+ Agents can manage skills at runtime via MCP tools:
559
+
560
+ | Tool | Description |
561
+ |------|-------------|
562
+ | `skill_create` | Create a new skill in the workspace |
563
+ | `skill_delete` | Delete an existing skill |
564
+ | `skill_install` | Install a skill from a GitHub URL or raw URL |
565
+
566
+ Skills are **hot-reloaded** — changes to skill files are detected automatically and the skill registry is updated without restarting the session.
567
+
568
+ ### Shared skills sync
569
+
570
+ Skills placed in `~/.claude-gateway/shared-skills/` are automatically synced to `~/.claude/skills/` — the user-level directory that Claude Code scans for every session:
571
+
572
+ - **At boot** — gateway copies all shared skills before spawning any agent
573
+ - **On change** — any add, edit, or delete under `shared-skills/` triggers a re-sync
574
+ - **Cleanup** — each synced skill is tagged with a `.shared` marker file; if a skill is removed from `shared-skills/`, the marker is used to delete the stale copy from `~/.claude/skills/` automatically (user-installed skills without the marker are never touched)
575
+
576
+ This means adding a skill to `shared-skills/` makes it available to **all agents** without per-agent setup or a gateway restart.
577
+
578
+ ---
579
+
580
+ ## Config Auto-Migration
581
+
582
+ When the config schema changes (new fields added in `config.template.json`), the gateway automatically detects and migrates your `config.json`:
583
+
584
+ - Preserves all existing values
585
+ - Adds missing fields with defaults from the template
586
+ - Prompts for confirmation before writing (use `--auto-migrate` to skip)
587
+ - Tracks schema version for future migrations
588
+
589
+ ---
590
+
591
+ ## Pairing New Users
592
+
593
+ 1. Set `dmPolicy` to `pairing` in `access.json`:
594
+ ```json
595
+ { "dmPolicy": "pairing" }
596
+ ```
597
+ 2. Ask the user to DM the bot — they receive a 6-character pairing code
598
+ 3. Approve it:
599
+ ```bash
600
+ npm run pair -- --agent=alfred --code=abc123
601
+ ```
602
+ 4. The bot confirms pairing within 5 seconds
603
+ 5. Lock down after everyone is paired:
604
+ ```bash
605
+ npm run pair -- --agent=alfred --policy=allowlist
606
+ ```
607
+
608
+ To manage channels (add/remove Telegram or Discord) on an existing agent:
609
+ ```bash
610
+ make update-agent # choose "Manage channels"
611
+ ```
612
+
613
+ ---
614
+
615
+ ## Telegram Groups
616
+
617
+ The bot can respond in Telegram groups and supergroups. Groups must be registered before the bot will respond.
618
+
619
+ **Step 1 — Add the bot to the group as Admin**
620
+
621
+ Add your bot to the group and **promote it to Admin**. Without admin rights, Telegram does not deliver group messages to the bot — it will appear online but never respond.
622
+
623
+ Minimum required admin permission: **"Read Messages"** (or any admin role — even the most restricted works).
624
+
625
+ **Step 2 — Get the group ID**
626
+
627
+ Forward any message from the group to [@userinfobot](https://t.me/userinfobot). It will reply with the chat ID — a negative number like `-1001234567890`.
628
+
629
+ Alternatively, send a message in the group and visit:
630
+ ```
631
+ https://api.telegram.org/bot<YOUR_BOT_TOKEN>/getUpdates
632
+ ```
633
+ Look for `"chat":{"id": ...}` in the result.
634
+
635
+ **Step 3 — Register the group**
636
+
637
+ Edit the agent's `access.json` directly:
638
+
639
+ ```
640
+ ~/.claude-gateway/agents/<your-agent-id>/workspace/.telegram-state/access.json
641
+ ```
642
+
643
+ Add the group under `"groups"`:
644
+
645
+ ```json
646
+ {
647
+ "dmPolicy": "allowlist",
648
+ "allowFrom": ["..."],
649
+ "groups": {
650
+ "-1001234567890": {
651
+ "requireMention": true,
652
+ "allowFrom": []
653
+ }
654
+ }
655
+ }
656
+ ```
657
+
658
+ Set `"requireMention": false` if you want the bot to respond to all messages without needing an @mention.
659
+ To restrict to specific members only, add their Telegram user IDs to `"allowFrom"`.
660
+
661
+ **Step 4 — Start chatting**
662
+
663
+ @mention the bot in the group (or reply to one of its messages). Changes to `access.json` take effect immediately — no restart needed.
664
+
665
+ **Managing groups**
666
+
667
+ Edit `access.json` to add or remove entries from the `"groups"` object. The gateway re-reads the file on every inbound message.
668
+
669
+ > **Note:** `/telegram:access` skill is available when running inside a gateway agent session (TELEGRAM_STATE_DIR is set automatically). For standalone terminal use, edit `access.json` directly as shown above.
670
+
671
+ **Optional — Let the bot read all messages (disable Privacy Mode)**
672
+
673
+ By default, Telegram bots in groups only receive messages that start with `/` or directly @mention the bot. If you want the bot to respond to every message without an @mention (and have set `"requireMention": false` in `access.json`), you also need to disable Privacy Mode at the bot level:
674
+
675
+ 1. Open [@BotFather](https://t.me/BotFather)
676
+ 2. Send `/setprivacy`
677
+ 3. Select your bot
678
+ 4. Choose **Disable**
679
+
680
+ This is a bot-level setting — it applies to all groups the bot joins. If @mention-only is fine, skip this step and keep `"requireMention": true`.
681
+
682
+ ---
683
+
684
+ ## Telegram Commands
685
+
686
+ Once paired, the following bot commands are available in a private chat:
687
+
688
+ **Session management**
689
+
690
+ | Command | Description |
691
+ |---------|-------------|
692
+ | `/session` | Show current session info (name, message count, context %) |
693
+ | `/sessions` | List all sessions with inline keyboard — switch or delete |
694
+ | `/new <name>` | Create a new session, optionally with a name |
695
+ | `/rename <name>` | Rename the current session |
696
+ | `/clear` | Clear current session history (with confirmation) |
697
+ | `/compact` | Summarise old history and keep only recent messages |
698
+ | `/stop` | Interrupt the in-flight turn (gateway sends SIGINT to the subprocess) |
699
+ | `/restart` | Graceful session restart — shows a confirmation button; confirms and notifies when the session is back online |
700
+
701
+ **Agent**
702
+
703
+ | Command | Description |
704
+ |---------|-------------|
705
+ | `/model` | Show the current AI model |
706
+ | `/models` | Switch AI model — shows an inline keyboard; selecting a model triggers a graceful restart and notifies when back online |
707
+
708
+ **Account**
709
+
710
+ | Command | Description |
711
+ |---------|-------------|
712
+ | `/start` | Pairing instructions |
713
+ | `/status` | Check your pairing state |
714
+ | `/help` | Show available commands |
715
+
716
+ ---
717
+
718
+ ## Monitoring
719
+
720
+ The gateway runs an HTTP server on port 3000 (set `PORT` env var to change):
721
+
722
+ | Endpoint | Description |
723
+ |----------|-------------|
724
+ | `GET /health` | All agent IDs and running status |
725
+ | `GET /status` | JSON stats per agent (sessions, uptime) |
726
+ | `GET /ui` | Live HTML dashboard (auto-refreshes every 5s) |
727
+ | `POST /api/v1/agents/:id/messages` | Send a message to an agent (requires API key) |
728
+ | `GET /api/v1/agents` | List accessible agents (requires API key) |
729
+ | `/api/v1/crons/*` | Cron job management — see [API.md](./API.md) |
730
+
731
+ ---
732
+
733
+ ## Development
734
+
735
+ ```bash
736
+ # Build TypeScript
737
+ npm run build
738
+
739
+ # Unit tests only (fast, no external deps)
740
+ npm run test:unit
741
+
742
+ # Integration tests
743
+ npm run integration
744
+
745
+ # All tests
746
+ npm test
747
+
748
+ # Type check without building
749
+ npm run typecheck
750
+ ```
751
+
752
+ ---
753
+
754
+ ## Troubleshooting
755
+
756
+ **Agent fails to start**
757
+ - Check workspace path exists and contains `AGENTS.md`
758
+ - Check `dangerouslySkipPermissions: true` is set in config
759
+ - Check logs in `~/.claude-gateway/logs/<id>.log`
760
+
761
+ **Agent not responding to messages**
762
+ - Verify `dmPolicy` in `access.json` — if `allowlist`, check the user's ID is in `allowFrom`
763
+ - Ensure no other process is polling the same bot token (causes 409 Conflict)
764
+ - Only `TelegramReceiver` polls Telegram — MCP session subprocesses run in `SEND_ONLY` mode (no polling)
765
+
766
+ **Session loses memory after restart**
767
+ - History is persisted in `~/.claude-gateway/agents/<id>/sessions/<chat_id>.jsonl`
768
+ - If the file is missing, the session starts fresh (no error)
769
+
770
+ **Personality not applied**
771
+ - `CLAUDE.md` is auto-regenerated from workspace files on startup and on any file change
772
+ - Trigger a reload by saving any `.md` file in the workspace
773
+
774
+ **Heartbeat not firing**
775
+ - Verify `HEARTBEAT.md` YAML is valid
776
+ - Check cron expression (5 fields: `min hour day month weekday`)
777
+ - Check rate limit — default 30 min between proactive messages
778
+
779
+ **API returns 403**
780
+ - Check the key value matches exactly (env var interpolation uses `${VAR}` syntax)
781
+ - Verify the key's `agents` list includes the target agent ID, or set `"agents": "*"`
782
+
783
+ **MCP tools not working (telegram_reply, cron_list, etc.)**
784
+ - Ensure `mcp/node_modules/` exists — run `make mcp-install` if not
785
+ - Check that `mcp-config.json` is generated in the session directory
786
+ - Verify Bun is installed (`bun --version`)
787
+
788
+ **Status messages not appearing in Telegram**
789
+ - First status update is sent after 5 seconds — very fast tasks may complete before it fires
790
+ - Check that the MCP server is running in `SEND_ONLY` mode for session subprocesses
791
+ - Verify the bot has permission to send messages in the chat