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.
- package/LICENSE +21 -0
- package/README.en.md +120 -0
- package/README.md +120 -0
- package/dist/cli.d.ts +16 -0
- package/dist/cli.js +314 -0
- package/dist/hub/agent-manager.d.ts +63 -0
- package/dist/hub/agent-manager.js +311 -0
- package/dist/hub/hub-context.d.ts +27 -0
- package/dist/hub/hub-context.js +57 -0
- package/dist/hub/index.d.ts +6 -0
- package/dist/hub/index.js +234 -0
- package/dist/hub/launchd.d.ts +7 -0
- package/dist/hub/launchd.js +151 -0
- package/dist/hub/plugin-manager.d.ts +7 -0
- package/dist/hub/plugin-manager.js +29 -0
- package/dist/hub/router.d.ts +21 -0
- package/dist/hub/router.js +35 -0
- package/dist/hub/socket-server.d.ts +23 -0
- package/dist/hub/socket-server.js +191 -0
- package/dist/plugins/channel-manager/index.d.ts +10 -0
- package/dist/plugins/channel-manager/index.js +387 -0
- package/dist/plugins/cron-scheduler/db.d.ts +12 -0
- package/dist/plugins/cron-scheduler/db.js +160 -0
- package/dist/plugins/cron-scheduler/index.d.ts +4 -0
- package/dist/plugins/cron-scheduler/index.js +22 -0
- package/dist/plugins/cron-scheduler/scheduler.d.ts +20 -0
- package/dist/plugins/cron-scheduler/scheduler.js +129 -0
- package/dist/plugins/persistence/db.d.ts +24 -0
- package/dist/plugins/persistence/db.js +121 -0
- package/dist/plugins/persistence/index.d.ts +2 -0
- package/dist/plugins/persistence/index.js +93 -0
- package/dist/plugins/web-monitor/api-routes.d.ts +33 -0
- package/dist/plugins/web-monitor/api-routes.js +474 -0
- package/dist/plugins/web-monitor/index.d.ts +2 -0
- package/dist/plugins/web-monitor/index.js +21 -0
- package/dist/plugins/web-monitor/log-tailer.d.ts +13 -0
- package/dist/plugins/web-monitor/log-tailer.js +74 -0
- package/dist/plugins/web-monitor/monitor-client.d.ts +17 -0
- package/dist/plugins/web-monitor/monitor-client.js +68 -0
- package/dist/plugins/web-monitor/server.d.ts +14 -0
- package/dist/plugins/web-monitor/server.js +205 -0
- package/dist/plugins/web-monitor/stats-reader.d.ts +22 -0
- package/dist/plugins/web-monitor/stats-reader.js +17 -0
- package/dist/plugins/web-monitor/token-stats.d.ts +19 -0
- package/dist/plugins/web-monitor/token-stats.js +86 -0
- package/dist/plugins/web-monitor/usage-stats.d.ts +13 -0
- package/dist/plugins/web-monitor/usage-stats.js +56 -0
- package/dist/plugins/weixin/chunker.d.ts +16 -0
- package/dist/plugins/weixin/chunker.js +142 -0
- package/dist/plugins/weixin/connection.d.ts +46 -0
- package/dist/plugins/weixin/connection.js +270 -0
- package/dist/plugins/weixin/index.d.ts +10 -0
- package/dist/plugins/weixin/index.js +198 -0
- package/dist/plugins/weixin/media-upload.d.ts +22 -0
- package/dist/plugins/weixin/media-upload.js +134 -0
- package/dist/plugins/weixin/media.d.ts +6 -0
- package/dist/plugins/weixin/media.js +83 -0
- package/dist/plugins/weixin/permission.d.ts +35 -0
- package/dist/plugins/weixin/permission.js +96 -0
- package/dist/plugins/weixin/qr-login.d.ts +23 -0
- package/dist/plugins/weixin/qr-login.js +77 -0
- package/dist/plugins/weixin/weixin-channel.d.ts +33 -0
- package/dist/plugins/weixin/weixin-channel.js +123 -0
- package/dist/shared/channel-config.d.ts +8 -0
- package/dist/shared/channel-config.js +14 -0
- package/dist/shared/channel.d.ts +37 -0
- package/dist/shared/channel.js +8 -0
- package/dist/shared/mcp-config.d.ts +5 -0
- package/dist/shared/mcp-config.js +44 -0
- package/dist/shared/plugin.d.ts +32 -0
- package/dist/shared/plugin.js +1 -0
- package/dist/shared/socket.d.ts +5 -0
- package/dist/shared/socket.js +31 -0
- package/dist/shared/types.d.ts +136 -0
- package/dist/shared/types.js +1 -0
- package/dist/spoke/channel-server.d.ts +48 -0
- package/dist/spoke/channel-server.js +383 -0
- package/dist/spoke/index.d.ts +13 -0
- package/dist/spoke/index.js +115 -0
- package/dist/spoke/permission.d.ts +28 -0
- package/dist/spoke/permission.js +142 -0
- package/dist/spoke/socket-client.d.ts +22 -0
- package/dist/spoke/socket-client.js +83 -0
- package/dist/web-frontend/assets/index-CU9vxw8F.js +9 -0
- package/dist/web-frontend/index.html +82 -0
- 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
|
+
}
|