cc2im 0.2.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 (86) hide show
  1. package/LICENSE +21 -0
  2. package/README.en.md +120 -0
  3. package/README.md +120 -0
  4. package/dist/cli.d.ts +16 -0
  5. package/dist/cli.js +314 -0
  6. package/dist/hub/agent-manager.d.ts +63 -0
  7. package/dist/hub/agent-manager.js +311 -0
  8. package/dist/hub/hub-context.d.ts +27 -0
  9. package/dist/hub/hub-context.js +57 -0
  10. package/dist/hub/index.d.ts +6 -0
  11. package/dist/hub/index.js +234 -0
  12. package/dist/hub/launchd.d.ts +7 -0
  13. package/dist/hub/launchd.js +151 -0
  14. package/dist/hub/plugin-manager.d.ts +7 -0
  15. package/dist/hub/plugin-manager.js +29 -0
  16. package/dist/hub/router.d.ts +21 -0
  17. package/dist/hub/router.js +35 -0
  18. package/dist/hub/socket-server.d.ts +23 -0
  19. package/dist/hub/socket-server.js +191 -0
  20. package/dist/plugins/channel-manager/index.d.ts +10 -0
  21. package/dist/plugins/channel-manager/index.js +387 -0
  22. package/dist/plugins/cron-scheduler/db.d.ts +12 -0
  23. package/dist/plugins/cron-scheduler/db.js +160 -0
  24. package/dist/plugins/cron-scheduler/index.d.ts +4 -0
  25. package/dist/plugins/cron-scheduler/index.js +22 -0
  26. package/dist/plugins/cron-scheduler/scheduler.d.ts +20 -0
  27. package/dist/plugins/cron-scheduler/scheduler.js +129 -0
  28. package/dist/plugins/persistence/db.d.ts +24 -0
  29. package/dist/plugins/persistence/db.js +121 -0
  30. package/dist/plugins/persistence/index.d.ts +2 -0
  31. package/dist/plugins/persistence/index.js +93 -0
  32. package/dist/plugins/web-monitor/api-routes.d.ts +33 -0
  33. package/dist/plugins/web-monitor/api-routes.js +474 -0
  34. package/dist/plugins/web-monitor/index.d.ts +2 -0
  35. package/dist/plugins/web-monitor/index.js +21 -0
  36. package/dist/plugins/web-monitor/log-tailer.d.ts +13 -0
  37. package/dist/plugins/web-monitor/log-tailer.js +74 -0
  38. package/dist/plugins/web-monitor/monitor-client.d.ts +17 -0
  39. package/dist/plugins/web-monitor/monitor-client.js +68 -0
  40. package/dist/plugins/web-monitor/server.d.ts +14 -0
  41. package/dist/plugins/web-monitor/server.js +205 -0
  42. package/dist/plugins/web-monitor/stats-reader.d.ts +22 -0
  43. package/dist/plugins/web-monitor/stats-reader.js +17 -0
  44. package/dist/plugins/web-monitor/token-stats.d.ts +19 -0
  45. package/dist/plugins/web-monitor/token-stats.js +86 -0
  46. package/dist/plugins/web-monitor/usage-stats.d.ts +13 -0
  47. package/dist/plugins/web-monitor/usage-stats.js +56 -0
  48. package/dist/plugins/weixin/chunker.d.ts +16 -0
  49. package/dist/plugins/weixin/chunker.js +142 -0
  50. package/dist/plugins/weixin/connection.d.ts +46 -0
  51. package/dist/plugins/weixin/connection.js +270 -0
  52. package/dist/plugins/weixin/index.d.ts +10 -0
  53. package/dist/plugins/weixin/index.js +198 -0
  54. package/dist/plugins/weixin/media-upload.d.ts +22 -0
  55. package/dist/plugins/weixin/media-upload.js +134 -0
  56. package/dist/plugins/weixin/media.d.ts +6 -0
  57. package/dist/plugins/weixin/media.js +83 -0
  58. package/dist/plugins/weixin/permission.d.ts +35 -0
  59. package/dist/plugins/weixin/permission.js +96 -0
  60. package/dist/plugins/weixin/qr-login.d.ts +23 -0
  61. package/dist/plugins/weixin/qr-login.js +77 -0
  62. package/dist/plugins/weixin/weixin-channel.d.ts +33 -0
  63. package/dist/plugins/weixin/weixin-channel.js +123 -0
  64. package/dist/shared/channel-config.d.ts +8 -0
  65. package/dist/shared/channel-config.js +14 -0
  66. package/dist/shared/channel.d.ts +37 -0
  67. package/dist/shared/channel.js +8 -0
  68. package/dist/shared/mcp-config.d.ts +5 -0
  69. package/dist/shared/mcp-config.js +44 -0
  70. package/dist/shared/plugin.d.ts +32 -0
  71. package/dist/shared/plugin.js +1 -0
  72. package/dist/shared/socket.d.ts +5 -0
  73. package/dist/shared/socket.js +31 -0
  74. package/dist/shared/types.d.ts +136 -0
  75. package/dist/shared/types.js +1 -0
  76. package/dist/spoke/channel-server.d.ts +48 -0
  77. package/dist/spoke/channel-server.js +383 -0
  78. package/dist/spoke/index.d.ts +13 -0
  79. package/dist/spoke/index.js +115 -0
  80. package/dist/spoke/permission.d.ts +28 -0
  81. package/dist/spoke/permission.js +142 -0
  82. package/dist/spoke/socket-client.d.ts +22 -0
  83. package/dist/spoke/socket-client.js +83 -0
  84. package/dist/web-frontend/assets/index-CU9vxw8F.js +9 -0
  85. package/dist/web-frontend/index.html +82 -0
  86. package/package.json +54 -0
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 roxorlt
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.en.md ADDED
@@ -0,0 +1,120 @@
1
+ # cc2im
2
+
3
+ English | [中文](./README.md)
4
+
5
+ WeChat IM gateway for multiple local Claude Code instances. Control your AI agents remotely via WeChat messages.
6
+
7
+ ## Architecture
8
+
9
+ ```
10
+ WeChat (mobile)
11
+ ↓ iLink Bot API (long-poll)
12
+ ┌──────────────────────────────────┐
13
+ │ cc2im hub (launchd daemon) │
14
+ │ · WeChat connection (multi-acct)│
15
+ │ · @mention routing │
16
+ │ · Agent lifecycle management │
17
+ │ · Web Dashboard (:3721) │
18
+ │ · Cron task scheduler │
19
+ └──────────┬───────────────────────┘
20
+ │ Unix socket
21
+ ┌──────┴──────┐
22
+ ↓ ↓
23
+ ┌─────────┐ ┌─────────┐
24
+ │ Spoke 1 │ │ Spoke N │
25
+ │ (MCP) │ │ (MCP) │
26
+ │ CC #1 │ │ CC #N │
27
+ │ ~/brain │ │ ~/proj │
28
+ └─────────┘ └─────────┘
29
+ ```
30
+
31
+ ## Install
32
+
33
+ ```bash
34
+ npm install -g cc2im
35
+ ```
36
+
37
+ **Requirements:** macOS, Node.js 22+, [Claude Code CLI](https://docs.anthropic.com/en/docs/claude-code)
38
+
39
+ ## Quick Start
40
+
41
+ ```bash
42
+ # 1. Scan QR code to log in to WeChat
43
+ cc2im login
44
+
45
+ # 2. Register your first agent
46
+ cc2im agent register brain ~/brain
47
+
48
+ # 3. Install as background service
49
+ cc2im install
50
+
51
+ # Done! Send a message via WeChat.
52
+ ```
53
+
54
+ ## Commands
55
+
56
+ | Command | Description |
57
+ |---------|-------------|
58
+ | `cc2im login` | WeChat QR code login |
59
+ | `cc2im start` | Start hub + all autoStart agents |
60
+ | `cc2im hub` | Start hub only (debug) |
61
+ | `cc2im web` | Start Web Dashboard only |
62
+ | `cc2im agent register <name> <dir>` | Register an agent |
63
+ | `cc2im agent list` | List agent configs |
64
+ | `cc2im agent start <name>` | Start agent in foreground (debug) |
65
+ | `cc2im install` | Install launchd background service |
66
+ | `cc2im uninstall` | Uninstall launchd service |
67
+ | `cc2im status` | Check service status |
68
+ | `cc2im logs` | Tail live logs |
69
+
70
+ ## WeChat Commands
71
+
72
+ - `message` — Send to default agent
73
+ - `@agent message` — Route to a specific agent
74
+ - `@agent restart` — Restart agent (clears context)
75
+
76
+ Management commands are handled by the default agent's Claude via natural language:
77
+
78
+ - `start/stop <agent>` — Manage agent lifecycle
79
+ - `status` — List all agents and their status
80
+ - `register agent named X at /path` — Register a new agent
81
+
82
+ ## Web Dashboard
83
+
84
+ Visit `http://127.0.0.1:3721` after starting to access:
85
+
86
+ - Real-time message flow and agent status
87
+ - Token usage and cost tracking
88
+ - Multi-account WeChat management (QR login/disconnect)
89
+ - Cron scheduled task management
90
+ - Live log viewer
91
+
92
+ ## Multi-Account WeChat
93
+
94
+ Supports multiple WeChat accounts simultaneously. Add new channels in the Dashboard's Channels page and scan QR codes to log in. Each channel can have a default agent, and messages are routed based on source channel.
95
+
96
+ ## Configuration
97
+
98
+ All state is stored in `~/.cc2im/`:
99
+
100
+ ```
101
+ ~/.cc2im/
102
+ ├── hub.sock Unix socket
103
+ ├── agents.json Agent registry
104
+ ├── channels.json Channel config
105
+ ├── cc2im.db Message persistence + cron data (SQLite)
106
+ ├── hub.log Hub log
107
+ └── agents/
108
+ └── <name>/
109
+ └── spoke.log Spoke log
110
+ ```
111
+
112
+ WeChat credentials are stored in `~/.weixin-bot/` (separate file per channel).
113
+
114
+ ## Known Limitations
115
+
116
+ cc2im is designed for **single-user scenarios** (one person controlling multiple agents via WeChat). Under multi-user concurrency, permission approvals and reply routing may race.
117
+
118
+ ## License
119
+
120
+ [MIT](./LICENSE)
package/README.md ADDED
@@ -0,0 +1,120 @@
1
+ # cc2im
2
+
3
+ [English](./README.en.md) | 中文
4
+
5
+ 微信 IM 网关,连接多个本地 Claude Code 实例。通过微信消息远程控制你的 AI agent。
6
+
7
+ ## 架构
8
+
9
+ ```
10
+ 手机微信
11
+ ↓ iLink Bot API (long-poll)
12
+ ┌──────────────────────────────────┐
13
+ │ cc2im hub (launchd daemon) │
14
+ │ · WeChat 连接(多账号支持) │
15
+ │ · @mention 路由 │
16
+ │ · Agent 生命周期管理 │
17
+ │ · Web Dashboard (:3721) │
18
+ │ · Cron 定时任务调度 │
19
+ └──────────┬───────────────────────┘
20
+ │ Unix socket
21
+ ┌──────┴──────┐
22
+ ↓ ↓
23
+ ┌─────────┐ ┌─────────┐
24
+ │ Spoke 1 │ │ Spoke N │
25
+ │ (MCP) │ │ (MCP) │
26
+ │ CC #1 │ │ CC #N │
27
+ │ ~/brain │ │ ~/proj │
28
+ └─────────┘ └─────────┘
29
+ ```
30
+
31
+ ## 安装
32
+
33
+ ```bash
34
+ npm install -g cc2im
35
+ ```
36
+
37
+ **环境要求:** macOS, Node.js 22+, [Claude Code CLI](https://docs.anthropic.com/en/docs/claude-code)
38
+
39
+ ## 快速开始
40
+
41
+ ```bash
42
+ # 1. 微信扫码登录
43
+ cc2im login
44
+
45
+ # 2. 注册第一个 agent
46
+ cc2im agent register brain ~/brain
47
+
48
+ # 3. 安装为后台服务
49
+ cc2im install
50
+
51
+ # 完成!在微信里发消息即可。
52
+ ```
53
+
54
+ ## 命令
55
+
56
+ | 命令 | 说明 |
57
+ |------|------|
58
+ | `cc2im login` | 微信扫码登录 |
59
+ | `cc2im start` | 启动 hub + 所有 autoStart agent |
60
+ | `cc2im hub` | 仅启动 hub(调试用) |
61
+ | `cc2im web` | 仅启动 Web Dashboard |
62
+ | `cc2im agent register <name> <dir>` | 注册 agent |
63
+ | `cc2im agent list` | 列出 agent 配置 |
64
+ | `cc2im agent start <name>` | 前台启动 agent(调试用) |
65
+ | `cc2im install` | 安装 launchd 后台服务 |
66
+ | `cc2im uninstall` | 卸载 launchd 服务 |
67
+ | `cc2im status` | 查看运行状态 |
68
+ | `cc2im logs` | 查看实时日志 |
69
+
70
+ ## 微信指令
71
+
72
+ - `消息` — 发送到默认 agent
73
+ - `@agent 消息` — 路由到指定 agent
74
+ - `@agent 重启` — 重启 agent(清空上下文)
75
+
76
+ 管理指令由默认 agent 的 Claude 通过自然语言处理:
77
+
78
+ - `启动/停止 <agent>` — 管理 agent 生命周期
79
+ - `状态` — 列出所有 agent 及状态
80
+ - `注册 agent 叫 X,目录是 /path` — 注册新 agent
81
+
82
+ ## Web Dashboard
83
+
84
+ 启动后访问 `http://127.0.0.1:3721`,可查看:
85
+
86
+ - 实时消息流和 agent 状态
87
+ - Token 用量和费用统计
88
+ - 多微信账号管理(扫码登录/断开)
89
+ - Cron 定时任务管理
90
+ - 实时日志
91
+
92
+ ## 多微信账号
93
+
94
+ 支持同时登录多个微信账号。在 Dashboard 的 Channels 页面添加新 channel,扫码登录即可。每个 channel 可以绑定默认 agent,消息会根据来源 channel 路由。
95
+
96
+ ## 配置
97
+
98
+ 所有状态存储在 `~/.cc2im/`:
99
+
100
+ ```
101
+ ~/.cc2im/
102
+ ├── hub.sock Unix socket
103
+ ├── agents.json Agent 注册表
104
+ ├── channels.json Channel 配置
105
+ ├── cc2im.db 消息持久化 + Cron 数据(SQLite)
106
+ ├── hub.log Hub 日志
107
+ └── agents/
108
+ └── <name>/
109
+ └── spoke.log Spoke 日志
110
+ ```
111
+
112
+ 微信凭证存储在 `~/.weixin-bot/`(每个 channel 独立文件)。
113
+
114
+ ## 已知限制
115
+
116
+ cc2im 为**单用户场景**设计(一个人通过微信控制自己的多个 agent)。多用户并发时,权限审批和回复路由可能出现竞争。
117
+
118
+ ## License
119
+
120
+ [MIT](./LICENSE)
package/dist/cli.d.ts ADDED
@@ -0,0 +1,16 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * cc2im CLI
4
+ *
5
+ * cc2im login — 微信扫码登录
6
+ * cc2im hub — 启动 hub(前台,调试用)
7
+ * cc2im start — 启动 hub + 所有 autoStart agent
8
+ * cc2im agent start <name> — 手动启动一个 agent(前台,调试用)
9
+ * cc2im agent stop <name> — 停止一个 agent
10
+ * cc2im agent list — 列出所有 agent
11
+ * cc2im install — 安装 launchd 服务(后台运行)
12
+ * cc2im uninstall — 卸载 launchd 服务
13
+ * cc2im status — 查看运行状态
14
+ * cc2im logs — 查看日志
15
+ */
16
+ export {};
package/dist/cli.js ADDED
@@ -0,0 +1,314 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * cc2im CLI
4
+ *
5
+ * cc2im login — 微信扫码登录
6
+ * cc2im hub — 启动 hub(前台,调试用)
7
+ * cc2im start — 启动 hub + 所有 autoStart agent
8
+ * cc2im agent start <name> — 手动启动一个 agent(前台,调试用)
9
+ * cc2im agent stop <name> — 停止一个 agent
10
+ * cc2im agent list — 列出所有 agent
11
+ * cc2im install — 安装 launchd 服务(后台运行)
12
+ * cc2im uninstall — 卸载 launchd 服务
13
+ * cc2im status — 查看运行状态
14
+ * cc2im logs — 查看日志
15
+ */
16
+ import { existsSync, readFileSync, writeFileSync, mkdirSync } from 'node:fs';
17
+ import { join } from 'node:path';
18
+ import { homedir } from 'node:os';
19
+ import { spawn } from 'node:child_process';
20
+ import qrterm from 'qrcode-terminal';
21
+ import { SOCKET_DIR, ensureSocketDir } from './shared/socket.js';
22
+ import { ensureMcpJson } from './shared/mcp-config.js';
23
+ const AGENTS_JSON_PATH = join(SOCKET_DIR, 'agents.json');
24
+ const BASE_URL = 'https://ilinkai.weixin.qq.com';
25
+ const BANNER = [
26
+ '',
27
+ '\x1b[36m\x1b[1m ██████╗ ██████╗██████╗ ██╗███╗ ███╗\x1b[0m',
28
+ '\x1b[36m\x1b[1m ██╔════╝██╔════╝╚════██╗██║████╗ ████║\x1b[0m',
29
+ '\x1b[36m\x1b[1m ██║ ██║ █████╔╝██║██╔████╔██║\x1b[0m',
30
+ '\x1b[36m\x1b[1m ██║ ██║ ██╔═══╝ ██║██║╚██╔╝██║\x1b[0m',
31
+ '\x1b[36m\x1b[1m ╚██████╗╚██████╗███████╗██║██║ ╚═╝ ██║\x1b[0m',
32
+ '\x1b[36m\x1b[1m ╚═════╝ ╚═════╝╚══════╝╚═╝╚═╝ ╚═╝\x1b[0m',
33
+ '\x1b[2m Claude Code ↔ WeChat IM Gateway\x1b[0m',
34
+ '\x1b[2m by \x1b[0m\x1b[33mroxorlt\x1b[0m',
35
+ '',
36
+ ].join('\n');
37
+ const CRED_DIR = join(homedir(), '.weixin-bot');
38
+ const CRED_PATH = join(CRED_DIR, 'credentials.json');
39
+ const POLL_INTERVAL = 2000;
40
+ // --- Helpers ---
41
+ function loadAgentsJson() {
42
+ if (existsSync(AGENTS_JSON_PATH)) {
43
+ return JSON.parse(readFileSync(AGENTS_JSON_PATH, 'utf8'));
44
+ }
45
+ return null;
46
+ }
47
+ function ensureDefaultConfig() {
48
+ ensureSocketDir();
49
+ if (!existsSync(AGENTS_JSON_PATH)) {
50
+ const defaultConfig = {
51
+ defaultAgent: 'brain',
52
+ agents: {
53
+ brain: {
54
+ name: 'brain',
55
+ cwd: join(homedir(), 'brain'),
56
+ claudeArgs: ['--effort', 'max'],
57
+ createdAt: new Date().toISOString().split('T')[0],
58
+ autoStart: true,
59
+ },
60
+ },
61
+ };
62
+ writeFileSync(AGENTS_JSON_PATH, JSON.stringify(defaultConfig, null, 2) + '\n');
63
+ console.log(`[cc2im] Created default config: ${AGENTS_JSON_PATH}`);
64
+ }
65
+ }
66
+ // --- Commands ---
67
+ async function login() {
68
+ console.log(BANNER);
69
+ console.log('正在获取登录二维码...\n');
70
+ let qrData;
71
+ try {
72
+ const qrResp = await fetch(`${BASE_URL}/ilink/bot/get_bot_qrcode?bot_type=3`);
73
+ qrData = await qrResp.json();
74
+ }
75
+ catch (err) {
76
+ console.error(`无法连接 iLink 服务器: ${err.message}`);
77
+ process.exit(1);
78
+ }
79
+ const qrUrl = qrData.qrcode_img_content;
80
+ const qrToken = qrData.qrcode;
81
+ console.log('请用微信扫一扫下方二维码:\n');
82
+ qrterm.generate(qrUrl, { small: true });
83
+ console.log(`\n(也可手动复制链接在微信内打开: ${qrUrl})\n`);
84
+ let lastStatus = '';
85
+ while (true) {
86
+ const statusResp = await fetch(`${BASE_URL}/ilink/bot/get_qrcode_status?qrcode=${encodeURIComponent(qrToken)}`, { headers: { 'iLink-App-ClientVersion': '1' } });
87
+ const status = await statusResp.json();
88
+ if (status.status !== lastStatus) {
89
+ if (status.status === 'scaned')
90
+ console.log('已扫码,请在微信中确认授权...');
91
+ if (status.status === 'expired') {
92
+ console.log('二维码已过期,请重新运行: cc2im login');
93
+ process.exit(1);
94
+ }
95
+ lastStatus = status.status;
96
+ }
97
+ if (status.status === 'confirmed') {
98
+ if (!status.bot_token || !status.ilink_bot_id || !status.ilink_user_id) {
99
+ console.error('授权成功但未返回凭证,请重试');
100
+ process.exit(1);
101
+ }
102
+ const credentials = {
103
+ token: status.bot_token,
104
+ baseUrl: status.baseurl || BASE_URL,
105
+ accountId: status.ilink_bot_id,
106
+ userId: status.ilink_user_id,
107
+ };
108
+ mkdirSync(CRED_DIR, { recursive: true, mode: 0o700 });
109
+ writeFileSync(CRED_PATH, JSON.stringify(credentials, null, 2) + '\n', { mode: 0o600 });
110
+ console.log(`\n登录成功!`);
111
+ console.log(` accountId: ${credentials.accountId}`);
112
+ console.log(` userId: ${credentials.userId}`);
113
+ console.log(` 凭证已保存到 ${CRED_PATH}`);
114
+ break;
115
+ }
116
+ await new Promise(r => setTimeout(r, POLL_INTERVAL));
117
+ }
118
+ }
119
+ async function runHub() {
120
+ ensureDefaultConfig();
121
+ const { startHub } = await import('./hub/index.js');
122
+ await startHub({ autoStartAgents: false });
123
+ }
124
+ async function runStart() {
125
+ ensureDefaultConfig();
126
+ console.log('[cc2im] Starting hub + auto-start agents...');
127
+ const { startHub } = await import('./hub/index.js');
128
+ await startHub({ autoStartAgents: true });
129
+ }
130
+ function startAgentForeground(name) {
131
+ const config = loadAgentsJson();
132
+ if (!config?.agents?.[name]) {
133
+ console.error(`Agent "${name}" not found in ${AGENTS_JSON_PATH}`);
134
+ process.exit(1);
135
+ }
136
+ const agent = config.agents[name];
137
+ // Resolve spoke script path (works for both tsx/src and compiled/dist)
138
+ const dir = import.meta.dirname;
139
+ const spokeTs = join(dir, 'spoke', 'index.ts');
140
+ const spokeJs = join(dir, 'spoke', 'index.js');
141
+ const spokeScript = existsSync(spokeTs) ? spokeTs : spokeJs;
142
+ ensureMcpJson(agent.cwd, spokeScript, name);
143
+ const claudeArgs = [
144
+ '--dangerously-load-development-channels', 'server:cc2im',
145
+ ...(agent.claudeArgs || []),
146
+ ];
147
+ const cmd = process.platform === 'darwin' ? 'caffeinate' : 'claude';
148
+ const args = process.platform === 'darwin' ? ['-i', 'claude', ...claudeArgs] : claudeArgs;
149
+ console.log(`[cc2im] Starting agent "${name}" in ${agent.cwd} (foreground)`);
150
+ const child = spawn(cmd, args, {
151
+ cwd: agent.cwd,
152
+ stdio: 'inherit',
153
+ });
154
+ child.on('exit', (code) => {
155
+ console.log(`[cc2im] Agent "${name}" exited with code ${code}`);
156
+ process.exit(code ?? 0);
157
+ });
158
+ }
159
+ function agentRegister(name, cwd) {
160
+ ensureDefaultConfig();
161
+ const config = JSON.parse(readFileSync(AGENTS_JSON_PATH, 'utf8'));
162
+ if (config.agents[name]) {
163
+ console.error(`Agent "${name}" already exists`);
164
+ process.exit(1);
165
+ }
166
+ if (!existsSync(cwd)) {
167
+ console.error(`Directory "${cwd}" does not exist`);
168
+ process.exit(1);
169
+ }
170
+ config.agents[name] = {
171
+ name,
172
+ cwd,
173
+ claudeArgs: [],
174
+ createdAt: new Date().toISOString().split('T')[0],
175
+ autoStart: false,
176
+ };
177
+ writeFileSync(AGENTS_JSON_PATH, JSON.stringify(config, null, 2) + '\n');
178
+ console.log(`[cc2im] Registered agent "${name}" → ${cwd}`);
179
+ }
180
+ function agentDeregister(name) {
181
+ ensureDefaultConfig();
182
+ const config = JSON.parse(readFileSync(AGENTS_JSON_PATH, 'utf8'));
183
+ if (!config.agents[name]) {
184
+ console.error(`Agent "${name}" not found`);
185
+ process.exit(1);
186
+ }
187
+ delete config.agents[name];
188
+ if (config.defaultAgent === name) {
189
+ config.defaultAgent = Object.keys(config.agents)[0] || '';
190
+ }
191
+ writeFileSync(AGENTS_JSON_PATH, JSON.stringify(config, null, 2) + '\n');
192
+ console.log(`[cc2im] Deregistered agent "${name}"`);
193
+ }
194
+ function agentList() {
195
+ const config = loadAgentsJson();
196
+ if (!config) {
197
+ console.log('No agents configured. Run `cc2im hub` to create default config.');
198
+ return;
199
+ }
200
+ console.log(`Default agent: ${config.defaultAgent}\n`);
201
+ for (const [name, agent] of Object.entries(config.agents)) {
202
+ const isDefault = name === config.defaultAgent ? ' ★' : '';
203
+ console.log(` ${name}${isDefault}`);
204
+ console.log(` cwd: ${agent.cwd}`);
205
+ console.log(` autoStart: ${agent.autoStart ?? false}`);
206
+ console.log(` claudeArgs: ${(agent.claudeArgs || []).join(' ')}`);
207
+ console.log();
208
+ }
209
+ }
210
+ // --- Main ---
211
+ const command = process.argv[2];
212
+ const subcommand = process.argv[3];
213
+ const arg = process.argv[4];
214
+ switch (command) {
215
+ case '--version':
216
+ case '-v':
217
+ console.log('cc2im v0.1.0');
218
+ break;
219
+ case 'login':
220
+ await login();
221
+ break;
222
+ case 'hub':
223
+ await runHub();
224
+ break;
225
+ case 'start':
226
+ await runStart();
227
+ break;
228
+ case 'agent':
229
+ switch (subcommand) {
230
+ case 'register': {
231
+ const regCwd = process.argv[5];
232
+ if (!arg || !regCwd) {
233
+ console.error('Usage: cc2im agent register <name> <cwd>');
234
+ process.exit(1);
235
+ }
236
+ agentRegister(arg, regCwd);
237
+ break;
238
+ }
239
+ case 'deregister':
240
+ if (!arg) {
241
+ console.error('Usage: cc2im agent deregister <name>');
242
+ process.exit(1);
243
+ }
244
+ agentDeregister(arg);
245
+ break;
246
+ case 'start':
247
+ if (!arg) {
248
+ console.error('Usage: cc2im agent start <name>');
249
+ process.exit(1);
250
+ }
251
+ startAgentForeground(arg);
252
+ break;
253
+ case 'stop':
254
+ console.log('Use `cc2im start` to run hub-managed agents, then stop via WeChat or MCP tools.');
255
+ break;
256
+ case 'list':
257
+ agentList();
258
+ break;
259
+ default:
260
+ console.error(`Unknown agent command: ${subcommand}`);
261
+ console.error('Usage: cc2im agent [register|deregister|start|stop|list] [name] [cwd]');
262
+ process.exit(1);
263
+ }
264
+ break;
265
+ case 'install': {
266
+ const { install } = await import('./hub/launchd.js');
267
+ install();
268
+ break;
269
+ }
270
+ case 'uninstall': {
271
+ const { uninstall } = await import('./hub/launchd.js');
272
+ uninstall();
273
+ break;
274
+ }
275
+ case 'status': {
276
+ const { status } = await import('./hub/launchd.js');
277
+ status();
278
+ break;
279
+ }
280
+ case 'logs': {
281
+ const { logs } = await import('./hub/launchd.js');
282
+ logs();
283
+ break;
284
+ }
285
+ case 'web': {
286
+ const webPort = parseInt(process.argv.find((a, i) => process.argv[i - 1] === '--port') || '3721');
287
+ const { startWeb } = await import('./plugins/web-monitor/server.js');
288
+ const handle = await startWeb({ port: webPort });
289
+ const shutdown = () => { handle.shutdown(); process.exit(0); };
290
+ process.on('SIGTERM', shutdown);
291
+ process.on('SIGINT', shutdown);
292
+ break;
293
+ }
294
+ case '--help':
295
+ case '-h':
296
+ default:
297
+ console.log(`cc2im v0.1.0 — IM gateway for multiple Claude Code instances
298
+
299
+ Usage:
300
+ cc2im login 微信扫码登录
301
+ cc2im hub 启动 hub(前台调试,不启动 agent)
302
+ cc2im start 启动 hub + 所有 autoStart agent
303
+ cc2im agent start <name> 前台启动指定 agent(调试用)
304
+ cc2im agent list 列出所有 agent 配置
305
+
306
+ cc2im install 安装 launchd 后台服务
307
+ cc2im uninstall 卸载 launchd 服务
308
+ cc2im status 查看运行状态
309
+ cc2im logs 查看实时日志
310
+
311
+ cc2im web 监控面板(默认端口 3721)
312
+ cc2im web --port 8080 指定端口
313
+ `);
314
+ }
@@ -0,0 +1,63 @@
1
+ /**
2
+ * Agent Lifecycle Manager — hub 侧
3
+ * 管理 agent 的注册/注销、启动/停止、健康检查
4
+ */
5
+ import type { AgentsConfig } from '../shared/types.js';
6
+ export declare class AgentManager {
7
+ private processes;
8
+ private config;
9
+ private getConnectedAgents;
10
+ private onEvent?;
11
+ private stoppedManually;
12
+ private shuttingDown;
13
+ private restartAttempts;
14
+ constructor(getConnectedAgents: () => string[], onEvent?: (kind: string, agentId: string, extra?: Record<string, any>) => void);
15
+ private loadConfig;
16
+ private saveConfig;
17
+ getConfig(): AgentsConfig;
18
+ reloadConfig(): void;
19
+ register(name: string, cwd: string, claudeArgs?: string[]): {
20
+ success: boolean;
21
+ error?: string;
22
+ };
23
+ deregister(name: string): Promise<{
24
+ success: boolean;
25
+ error?: string;
26
+ }>;
27
+ start(name: string): {
28
+ success: boolean;
29
+ error?: string;
30
+ };
31
+ /** Stop an agent and wait for the process to exit (with timeout).
32
+ * Marks as manually stopped — will NOT auto-restart. */
33
+ stop(name: string): Promise<{
34
+ success: boolean;
35
+ error?: string;
36
+ }>;
37
+ list(): Array<{
38
+ name: string;
39
+ cwd: string;
40
+ status: 'connected' | 'starting' | 'stopped';
41
+ autoStart: boolean;
42
+ claudeArgs: string[];
43
+ isDefault: boolean;
44
+ }>;
45
+ /** Kill an agent's entire process tree (caffeinate → expect → claude).
46
+ * Uses negative PID to kill the process group created by detached: true. */
47
+ private killProcessTree;
48
+ /** Kill an agent's process for restart (e.g., after heartbeat eviction).
49
+ * Does NOT mark as manually stopped — child.on('exit') will auto-restart. */
50
+ killForRestart(name: string): void;
51
+ restart(name: string): Promise<{
52
+ success: boolean;
53
+ error?: string;
54
+ }>;
55
+ updateEffort(name: string, effort: string): {
56
+ success: boolean;
57
+ error?: string;
58
+ };
59
+ startAutoAgents(): void;
60
+ /** Check if this agent's process is managed (spawned) by the hub. */
61
+ isManaged(name: string): boolean;
62
+ stopAll(): Promise<void>;
63
+ }