@sleep2agi/agent-network 0.0.48 → 0.0.49

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -1,145 +1,112 @@
1
1
  # @sleep2agi/agent-network
2
2
 
3
- AI Agent 通信网络 — Server + Agent + CLI,一个包搞定。
3
+ AI Agent 通信网络 — CLI + SDK + Channel 插件,一个包搞定。
4
4
 
5
- 支持 MiniMax / 书生 Intern-S1 / Claude 等任意 Anthropic API 兼容模型。
6
-
7
- ## 安装
8
-
9
- ```bash
5
+ ```
10
6
  npm install -g @sleep2agi/agent-network
11
7
  ```
12
8
 
13
- ## 一分钟上手
9
+ 当前版本:v0.0.48 | [agent-node](https://www.npmjs.com/package/@sleep2agi/agent-node) v0.7.0 | [commhub-server](https://www.npmjs.com/package/@sleep2agi/commhub-server) v0.4.3
14
10
 
15
- ### 1. 启动 Server
11
+ ## 快速开始
16
12
 
17
13
  ```bash
14
+ # 1. 启动通信服务器(首次自动生成 auth token)
18
15
  anet server start --port 9200
19
- ```
20
16
 
21
- ### 2. 启动 Agent
17
+ # 2. 配置(交互式,填 hub URL 和 token)
18
+ anet init
22
19
 
23
- **Claude Code(交互式):**
24
- ```bash
25
- anet init --hub http://YOUR_IP:9200
20
+ # 3. 启动 Claude Code Agent
21
+ cd ~/your-project
26
22
  anet init project
27
23
  anet start 指挥室
28
- ```
29
24
 
30
- **MiniMax(低成本自动化):**
31
- ```bash
32
- anet init --hub http://YOUR_IP:9200
33
- anet init profile 小明 --runtime agent-sdk --alias 小明 --model MiniMax-M2.7 --tools all \
34
- --env "ANTHROPIC_BASE_URL=https://api.minimaxi.com/anthropic" \
35
- --env "ANTHROPIC_AUTH_TOKEN=your-key"
36
- anet start 小明
37
- ```
38
-
39
- **书生 Intern-S1-Pro:**
40
- ```bash
41
- anet init profile 书生 --runtime agent-sdk --alias 书生 --model intern-s1-pro --tools all \
42
- --env "ANTHROPIC_BASE_URL=https://chat.intern-ai.org.cn" \
43
- --env "ANTHROPIC_AUTH_TOKEN=your-key"
44
- anet start 书生
45
- ```
46
-
47
- ### 3. 查看状态
25
+ # 4. 快速接入已有 session(无需 init profile)
26
+ anet resume 你的Agent --session <session-id>
48
27
 
49
- ```bash
28
+ # 5. 查看状态
50
29
  anet ls
30
+ anet session ls # 列出当前项目的 Claude Code session
31
+ anet -v # 查看版本
51
32
  ```
52
33
 
53
34
  ## CLI 命令
54
35
 
55
- ### Server
36
+ | 命令 | 说明 |
37
+ |------|------|
38
+ | `anet init` | 配 hub URL + token(全局,交互式) |
39
+ | `anet init project` | 配当前项目(channel 插件 + .mcp.json + CLAUDE.md) |
40
+ | `anet init profile <id>` | 创建 node 启动配置 |
41
+ | `anet start <id>` | 新建 session |
42
+ | `anet resume <id>` | 恢复 session |
43
+ | `anet resume <id> --session <sid>` | 快速接入已有 session(自动创建配置) |
44
+ | `anet session ls` | 列出当前项目的 session(ID / 大小 / 时间) |
45
+ | `anet ls` | nodes + sessions + 网络状态 |
46
+ | `anet server start` | 启动 CommHub Server |
47
+ | `anet server config` | 查看/设置 server 配置 |
48
+ | `anet import` | 从 CommHub 导入在线 session |
49
+ | `anet -v` | 查看版本 |
50
+
51
+ ## 配置体系
52
+
53
+ ### 全局配置
56
54
 
57
- ```bash
58
- anet server start # 启动 CommHub Server(自动拉 @sleep2agi/commhub-server)
59
- anet server start --port 9200 # 指定端口
60
- anet server start --token my-secret # 加认证
61
55
  ```
62
-
63
- 需要 Bun 运行时(`curl -fsSL https://bun.sh/install | bash`)。
64
-
65
- ### Agent 初始化
66
-
67
- ```bash
68
- anet init # 配 hub URL(全局,一次性)
69
- anet init project # 配项目(claude-code 用:channel 插件 + .mcp.json + CLAUDE.md)
70
- anet init profile <id> [options] # 创建启动 profile
56
+ ~/.anet/config.json # hub URL + token(anet init 写入)
57
+ ~/.anet/server/config.json # server 配置(port/host/token)
71
58
  ```
72
59
 
73
- ### Agent 启动
60
+ ### 项目配置
74
61
 
75
- ```bash
76
- anet start <id> # 新建 session
77
- anet resume <id> # 恢复上次 session
78
- anet start # 列出所有 node 配置
79
- anet <id> # 快捷启动
80
62
  ```
81
-
82
- profile 不存在时自动进入交互式创建。
83
-
84
- `anet start` 根据 config `runtime` 自动选择:
85
- - `claude-code` → spawn claude CLI(自动配置 `.mcp.json`)
86
- - `agent-sdk` → spawn @sleep2agi/agent-node
87
-
88
- ### 快速接入已有 session
89
-
90
- 已有 Claude Code session 想接入 anet?一条命令:
91
-
92
- ```bash
93
- cd ~/your-project
94
- anet resume 你的Agent --session <session-id>
95
- # 自动创建 .anet/nodes/你的Agent/config.json + 配置 .mcp.json + resume
63
+ {project}/
64
+ ├── .mcp.json # commhub MCP server
65
+ └── .anet/
66
+ ├── node-server.ts # channel 插件(自动从 npm 包同步)
67
+ ├── package.json
68
+ └── nodes/
69
+ └── 指挥室/
70
+ └── config.json # 启动配置
96
71
  ```
97
72
 
98
- 不需要先 `init profile`,直接 resume 即可。
73
+ ### 配置继承规则
99
74
 
100
- ### 状态查看
75
+ 两个 config.json 都会读,**字段级合并**:
101
76
 
102
- ```bash
103
- anet ls # profiles + sessions + 网络状态
77
+ ```
78
+ 项目 .anet/nodes/<id>/config.json 有值的字段优先
79
+ ↓ fallback
80
+ 全局 ~/.anet/config.json 缺失字段兜底
104
81
  ```
105
82
 
106
- ## anet init profile 参数
107
-
108
- **共用:**
109
-
110
- | 参数 | 说明 |
111
- |------|------|
112
- | `--alias` | Agent 名称 |
113
- | `--runtime` | `claude-code`(默认)或 `agent-sdk` |
114
- | `--env` | 环境变量 K=V(可重复) |
115
-
116
- **claude-code:**
117
-
118
- | 参数 | 说明 |
119
- |------|------|
120
- | `--channel` | Channel(可重复,默认 server:commhub) |
121
- | `--teammate-mode` | 默认 in-process |
122
-
123
- **agent-sdk:**
83
+ 项目 config 不需要写 token/hub,全局配一份所有项目共用。
124
84
 
125
- | 参数 | 说明 |
126
- |------|------|
127
- | `--model` | 模型名 |
128
- | `--tools` | `all` 或逗号分隔(Read,Write,Edit,Bash,Glob,Grep,WebSearch,WebFetch) |
129
- | `--max-turns` | 每任务最大轮次 |
130
- | `--max-budget` | 每任务预算(美元) |
85
+ ### Node 配置示例
131
86
 
132
- ## Node 配置
87
+ 路径:`.anet/nodes/<id>/config.json`
133
88
 
134
- 路径:`.anet/nodes/<id>/config.json`,anet 和 agent-node 共用。
89
+ **Claude Code:**
90
+ ```json
91
+ {
92
+ "runtime": "claude-code",
93
+ "alias": "指挥室",
94
+ "hub": "http://YOUR_IP:9200",
95
+ "channels": ["server:commhub"],
96
+ "env": {},
97
+ "flags": { "dangerouslySkipPermissions": true, "teammateMode": "in-process" },
98
+ "resume": "98039093-xxxx-xxxx-xxxx-xxxxxxxxxxxx"
99
+ }
100
+ ```
135
101
 
102
+ **MiniMax(agent-sdk):**
136
103
  ```json
137
104
  {
138
105
  "runtime": "agent-sdk",
139
106
  "alias": "小明",
140
107
  "hub": "http://YOUR_IP:9200",
141
108
  "model": "MiniMax-M2.7",
142
- "tools": ["Read", "Write", "Edit", "Bash", "Glob", "Grep", "WebSearch", "WebFetch"],
109
+ "tools": ["Read", "Bash", "Grep"],
143
110
  "env": {
144
111
  "ANTHROPIC_BASE_URL": "https://api.minimaxi.com/anthropic",
145
112
  "ANTHROPIC_AUTH_TOKEN": "your-key"
@@ -147,21 +114,50 @@ anet ls # profiles + sessions + 网络状态
147
114
  }
148
115
  ```
149
116
 
150
- 配置优先级:`CLI 参数 > profile env > 系统环境变量 > ~/.anet/config.json > 默认值`
117
+ ## Token 认证
151
118
 
152
- ## 支持的模型
119
+ ```bash
120
+ # 方式 1:anet init 交互式填写
121
+ anet init
153
122
 
154
- | 模型 | ANTHROPIC_BASE_URL | 已验证 |
155
- |------|-------------------|--------|
156
- | MiniMax M2.7(国际) | `https://api.minimaxi.com/anthropic` | ✅ 对话 + tool_use |
157
- | MiniMax M2.7(国内) | `https://api.minimaxi.com/anthropic` | ✅ |
158
- | 书生 Intern-S1-Pro | `https://chat.intern-ai.org.cn` | ✅ |
159
- | Claude(默认) | 不设 | ✅ |
160
- | 任意 Anthropic 兼容 | 对应端点 | — |
123
+ # 方式 2:命令行参数
124
+ anet init --hub http://YOUR_IP:9200 --token your-secret
161
125
 
162
- 两个环境变量切模型,零代码修改。
126
+ # 方式 3:server 首次启动自动生成
127
+ anet server start
128
+ # → Generated auth token: xxxx(自动存到全局 + server config)
129
+ ```
130
+
131
+ Token 流转:
132
+
133
+ ```
134
+ ~/.anet/config.json (token)
135
+ ↓ 自动传递
136
+ ├→ anet start/resume → COMMHUB_TOKEN env → channel 插件
137
+ ├→ anet ls/import → Authorization header → CommHub API
138
+ └→ channel 插件启动时也会直接读 ~/.anet/config.json
139
+ ```
140
+
141
+ node config 可以单独配 token(覆盖全局),适用于连不同 CommHub 的场景。
142
+
143
+ ## 自动配置行为
144
+
145
+ `anet start`/`anet resume` 对 `runtime: "claude-code"` 自动确保:
163
146
 
164
- ## SDK 代码引用
147
+ 1. `.anet/node-server.ts` 从 npm 包同步(对比内容,不同才更新)
148
+ 2. `.anet/package.json` + 依赖安装
149
+ 3. `.mcp.json` 包含 commhub(**已有配置不覆盖**)
150
+
151
+ ## 支持的模型
152
+
153
+ | 模型 | ANTHROPIC_BASE_URL | runtime |
154
+ |------|-------------------|---------|
155
+ | Claude | 不设 | claude-code |
156
+ | MiniMax M2.7 | `https://api.minimaxi.com/anthropic` | agent-sdk |
157
+ | 书生 Intern-S1-Pro | `https://chat.intern-ai.org.cn` | agent-sdk |
158
+ | 任意 Anthropic 兼容 | 对应端点 | agent-sdk |
159
+
160
+ ## SDK
165
161
 
166
162
  ```typescript
167
163
  import { CommHub } from '@sleep2agi/agent-network';
@@ -180,37 +176,21 @@ hub.on('task', async (msg) => {
180
176
  | `hub.status(state, extra?)` | 更新状态 |
181
177
  | `hub.broadcast(content)` | 广播 |
182
178
  | `hub.getAllStatus()` | 查看所有 session |
183
- | `hub.disconnect()` | 断开 |
184
-
185
- | 事件 | 说明 |
186
- |------|------|
187
- | `task` | 收到任务(已自动 ACK) |
188
- | `connected` | SSE 连接成功 |
189
- | `disconnected` | SSE 断开(自动重连) |
190
-
191
- ## 依赖
192
-
193
- | 组件 | 什么时候需要 | 安装 |
194
- |------|------------|------|
195
- | @sleep2agi/agent-network | 所有人 | `npm i -g @sleep2agi/agent-network` |
196
- | @anthropic-ai/claude-code | Claude Code Agent | `npm i -g @anthropic-ai/claude-code` |
197
- | @sleep2agi/agent-node | agent-sdk Agent | `npm i -g @sleep2agi/agent-node` |
198
- | @anthropic-ai/claude-agent-sdk | agent-sdk Agent(运行时依赖) | `npm i @anthropic-ai/claude-agent-sdk` |
199
- | Bun | 启动 CommHub Server | `curl -fsSL https://bun.sh/install \| bash` |
200
179
 
201
180
  ## npm 包
202
181
 
203
- | 包 | 说明 | 大小 |
182
+ | 包 | 说明 | 版本 |
204
183
  |---|------|------|
205
- | [@sleep2agi/agent-network](https://www.npmjs.com/package/@sleep2agi/agent-network) | anet CLI + CommHub SDK | ~15KB |
206
- | [@sleep2agi/agent-node](https://www.npmjs.com/package/@sleep2agi/agent-node) | Agent 运行时 | ~5KB |
207
- | [@sleep2agi/commhub-server](https://www.npmjs.com/package/@sleep2agi/commhub-server) | CommHub Server | ~10KB |
184
+ | [@sleep2agi/agent-network](https://www.npmjs.com/package/@sleep2agi/agent-network) | anet CLI + CommHub SDK + Channel 插件 | v0.0.48 |
185
+ | [@sleep2agi/agent-node](https://www.npmjs.com/package/@sleep2agi/agent-node) | Agent 运行时(MiniMax/书生/Claude) | v0.7.0 |
186
+ | [@sleep2agi/commhub-server](https://www.npmjs.com/package/@sleep2agi/commhub-server) | CommHub 通信服务器 | v0.4.3 |
208
187
 
209
188
  ## 文档
210
189
 
211
- - [CLI 设计](docs/cli-design.md) — 命令 + Profile 规范
212
- - [架构设计](docs/architecture.md) — 系统架构
213
- - [操作手册](https://github.com/sleep2agi/agent-ops)(private) — 服务器/启动命令/Key
190
+ - [快速上手](docs/anet-quickstart.md)
191
+ - [CLI 设计](docs/cli-design.md) — 命令 + 配置规范
192
+ - [架构设计](docs/architecture.md)
193
+ - [踩坑经验](docs/pitfalls.md) — Channel 插件开发注意事项
214
194
 
215
195
  ## License
216
196
 
package/dist/bin/cli.js CHANGED
@@ -1,9 +1,9 @@
1
1
  #!/usr/bin/env node
2
- var QB=Object.defineProperty;var WB=(Q)=>Q;function XB(Q,W){this[Q]=WB.bind(null,W)}var YB=(Q,W)=>{for(var B in W)QB(Q,B,{get:W[B],enumerable:!0,configurable:!0,set:XB.bind(W,B)})};var ZB=(Q,W)=>()=>(Q&&(W=Q(Q=0)),W);var g={};YB(g,{default:()=>zB,CommHub:()=>D});import{EventEmitter as $B}from"events";import{hostname as f}from"os";var D,zB;var m=ZB(()=>{D=class D extends $B{url;alias;token;agent;resumeId;heartbeatInterval;reconnectDelay;heartbeatTimer;sseAbort;running=!1;constructor(Q){super();if(this.url=Q.url.replace(/\/$/,""),this.alias=Q.alias,this.token=Q.token,this.agent=Q.agent||"sdk",this.resumeId=`sdk-${Q.alias}-${Date.now().toString(36)}`,this.heartbeatInterval=Q.heartbeatInterval??180000,this.reconnectDelay=Q.reconnectDelay??3000,Q.autoConnect!==!1)this.connect()}log(Q){console.log(`[${new Date().toTimeString().slice(0,8)}] [commhub:${this.alias}] ${Q}`)}async call(Q,W){let B={"Content-Type":"application/json",Accept:"application/json, text/event-stream"};if(this.token)B.Authorization=`Bearer ${this.token}`;let z=await(await fetch(`${this.url}/mcp`,{method:"POST",headers:B,body:JSON.stringify({jsonrpc:"2.0",id:Date.now(),method:"tools/call",params:{name:Q,arguments:W}})})).text(),Y=z.match(/data: (.+)/),U=Y?JSON.parse(Y[1]):JSON.parse(z),Z=U?.result?.content?.[0]?.text;return Z?JSON.parse(Z):U}async connect(){if(this.running)return;this.running=!0,await this.status("idle"),this.log("registered"),this.heartbeatTimer=setInterval(()=>{this.status("idle").catch((Q)=>this.log(`heartbeat failed: ${Q.message}`))},this.heartbeatInterval),this.connectSSE()}async disconnect(){if(this.running=!1,this.sseAbort?.abort(),this.heartbeatTimer)clearInterval(this.heartbeatTimer);await this.status("offline").catch(()=>{}),this.log("disconnected")}async send(Q,W,B="normal"){return this.call("send_task",{alias:Q,task:W,priority:B,from_session:this.alias})}async message(Q,W){return this.call("send_message",{alias:Q,message:W,from_session:this.alias})}async reply(Q,W,B="completed"){return this.call("reply",{task_id:Q,text:W,status:B})}async status(Q,W){return this.call("report_status",{resume_id:this.resumeId,alias:this.alias,status:Q,server:f(),hostname:f(),agent:this.agent,project_dir:process.cwd(),...W})}async getAllStatus(){return this.call("get_all_status",{})}async broadcast(Q,W){return this.call("broadcast",{message:Q,filter_server:W?.server,filter_status:W?.status})}async connectSSE(){let Q=encodeURIComponent(this.alias),W=`${this.url}/events/${Q}`,B=this.reconnectDelay;while(this.running){try{this.sseAbort=new AbortController;let X={Accept:"text/event-stream"};if(this.token)X.Authorization=`Bearer ${this.token}`;let z=await fetch(W,{headers:X,signal:this.sseAbort.signal});if(!z.ok||!z.body){this.log(`SSE failed: ${z.status}`),await this.sleep(B),B=Math.min(B*1.5,60000);continue}B=this.reconnectDelay;let Y=z.body.getReader(),U=new TextDecoder,Z="";while(this.running){let{done:$,value:N}=await Y.read();if($)break;Z+=U.decode(N,{stream:!0});let R=Z.split(`
3
- `);Z=R.pop()||"";for(let L of R){if(!L.startsWith("data: "))continue;try{let E=JSON.parse(L.slice(6));if(E.type==="connected"){this.log("SSE connected"),this.emit("connected");continue}if(E.type==="new_task"||E.type==="new_message"||E.type==="broadcast")await this.processInbox()}catch{}}}}catch(X){if(X.name==="AbortError")break;this.emit("error",X),this.log(`SSE error: ${X.message}`)}if(this.running)this.emit("disconnected"),this.log(`SSE reconnecting in ${B/1000}s...`),await this.sleep(B),B=Math.min(B*1.5,60000)}}async processInbox(){try{let W=(await this.call("get_inbox",{alias:this.alias,limit:10}))?.messages||[];for(let B of W)await this.call("ack_inbox",{alias:this.alias,message_id:B.id}),this.log(`← ${B.from_session}: ${B.content.slice(0,60)}`),this.emit("task",B),this.emit("message",B)}catch(Q){this.log(`inbox error: ${Q.message}`)}}sleep(Q){return new Promise((W)=>setTimeout(W,Q))}};zB=D});import{readFileSync as H,writeFileSync as _,existsSync as V,mkdirSync as M,readdirSync as j,statSync as w}from"fs";import{join as K}from"path";import{spawn as C,execSync as n}from"child_process";import{createInterface as LB}from"readline";var T=process.argv.slice(2),P=T[0],I=process.env.HOME||process.env.USERPROFILE||"~";function a(){return K(I,".anet","config.json")}function b(){return K(I,".anet","server","config.json")}function y(){return K(process.cwd(),".anet","nodes")}function r(){return G().token||process.env.COMMHUB_TOKEN||O().token||""}function v(Q){let W=Q||r();return W?{Authorization:`Bearer ${W}`}:{}}function O(){let Q=a();if(V(Q))try{return JSON.parse(H(Q,"utf-8"))}catch{}return{}}function t(Q){let W=K(I,".anet");M(W,{recursive:!0}),_(K(W,"config.json"),JSON.stringify(Q,null,2)+`
4
- `)}function p(){let Q=b();if(V(Q))try{return JSON.parse(H(Q,"utf-8"))}catch{}return{}}function d(Q){let W=K(I,".anet","server");M(W,{recursive:!0}),_(K(W,"config.json"),JSON.stringify(Q,null,2)+`
5
- `)}function F(Q){let W=K(y(),Q,"config.json");if(!V(W))return null;try{let B=JSON.parse(H(W,"utf-8")),X=O();return{...B,hub:B.hub||X.hub||"",token:B.token||X.token||"",env:{...B.env},flags:{...B.flags}}}catch{return null}}function S(Q,W){let B=K(y(),Q);M(B,{recursive:!0}),_(K(B,"config.json"),JSON.stringify(W,null,2)+`
6
- `)}function i(){let Q=y();if(!V(Q))return[];return j(Q).filter((W)=>V(K(Q,W,"config.json")))}function G(){let Q={_channels:[],_envs:[]};for(let W=0;W<T.length;W++){if(T[W]==="--channel"&&T[W+1]){Q._channels.push(T[++W]);continue}if(T[W]==="--env"&&T[W+1]){Q._envs.push(T[++W]);continue}if(T[W].startsWith("--")&&T[W+1]&&!T[W+1].startsWith("--"))Q[T[W].slice(2)]=T[++W]}return Q}function c(){console.log(`
2
+ var QB=Object.defineProperty;var WB=(Q)=>Q;function XB(Q,W){this[Q]=WB.bind(null,W)}var YB=(Q,W)=>{for(var B in W)QB(Q,B,{get:W[B],enumerable:!0,configurable:!0,set:XB.bind(W,B)})};var ZB=(Q,W)=>()=>(Q&&(W=Q(Q=0)),W);var m={};YB(m,{default:()=>zB,CommHub:()=>b});import{EventEmitter as $B}from"events";import{hostname as g}from"os";var b,zB;var p=ZB(()=>{b=class b extends $B{url;alias;token;agent;resumeId;heartbeatInterval;reconnectDelay;heartbeatTimer;sseAbort;running=!1;constructor(Q){super();if(this.url=Q.url.replace(/\/$/,""),this.alias=Q.alias,this.token=Q.token,this.agent=Q.agent||"sdk",this.resumeId=`sdk-${Q.alias}-${Date.now().toString(36)}`,this.heartbeatInterval=Q.heartbeatInterval??180000,this.reconnectDelay=Q.reconnectDelay??3000,Q.autoConnect!==!1)this.connect()}log(Q){console.log(`[${new Date().toTimeString().slice(0,8)}] [commhub:${this.alias}] ${Q}`)}async call(Q,W){let B={"Content-Type":"application/json",Accept:"application/json, text/event-stream"};if(this.token)B.Authorization=`Bearer ${this.token}`;let $=await(await fetch(`${this.url}/mcp`,{method:"POST",headers:B,body:JSON.stringify({jsonrpc:"2.0",id:Date.now(),method:"tools/call",params:{name:Q,arguments:W}})})).text(),Y=$.match(/data: (.+)/),U=Y?JSON.parse(Y[1]):JSON.parse($),Z=U?.result?.content?.[0]?.text;return Z?JSON.parse(Z):U}async connect(){if(this.running)return;this.running=!0,await this.status("idle"),this.log("registered"),this.heartbeatTimer=setInterval(()=>{this.status("idle").catch((Q)=>this.log(`heartbeat failed: ${Q.message}`))},this.heartbeatInterval),this.connectSSE()}async disconnect(){if(this.running=!1,this.sseAbort?.abort(),this.heartbeatTimer)clearInterval(this.heartbeatTimer);await this.status("offline").catch(()=>{}),this.log("disconnected")}async send(Q,W,B="normal"){return this.call("send_task",{alias:Q,task:W,priority:B,from_session:this.alias})}async message(Q,W){return this.call("send_message",{alias:Q,message:W,from_session:this.alias})}async reply(Q,W,B="completed"){return this.call("reply",{task_id:Q,text:W,status:B})}async status(Q,W){return this.call("report_status",{resume_id:this.resumeId,alias:this.alias,status:Q,server:g(),hostname:g(),agent:this.agent,project_dir:process.cwd(),...W})}async getAllStatus(){return this.call("get_all_status",{})}async broadcast(Q,W){return this.call("broadcast",{message:Q,filter_server:W?.server,filter_status:W?.status})}async connectSSE(){let Q=encodeURIComponent(this.alias),W=`${this.url}/events/${Q}`,B=this.reconnectDelay;while(this.running){try{this.sseAbort=new AbortController;let X={Accept:"text/event-stream"};if(this.token)X.Authorization=`Bearer ${this.token}`;let $=await fetch(W,{headers:X,signal:this.sseAbort.signal});if(!$.ok||!$.body){this.log(`SSE failed: ${$.status}`),await this.sleep(B),B=Math.min(B*1.5,60000);continue}B=this.reconnectDelay;let Y=$.body.getReader(),U=new TextDecoder,Z="";while(this.running){let{done:z,value:N}=await Y.read();if(z)break;Z+=U.decode(N,{stream:!0});let K=Z.split(`
3
+ `);Z=K.pop()||"";for(let V of K){if(!V.startsWith("data: "))continue;try{let _=JSON.parse(V.slice(6));if(_.type==="connected"){this.log("SSE connected"),this.emit("connected");continue}if(_.type==="new_task"||_.type==="new_message"||_.type==="broadcast")await this.processInbox()}catch{}}}}catch(X){if(X.name==="AbortError")break;this.emit("error",X),this.log(`SSE error: ${X.message}`)}if(this.running)this.emit("disconnected"),this.log(`SSE reconnecting in ${B/1000}s...`),await this.sleep(B),B=Math.min(B*1.5,60000)}}async processInbox(){try{let W=(await this.call("get_inbox",{alias:this.alias,limit:10}))?.messages||[];for(let B of W)await this.call("ack_inbox",{alias:this.alias,message_id:B.id}),this.log(`← ${B.from_session}: ${B.content.slice(0,60)}`),this.emit("task",B),this.emit("message",B)}catch(Q){this.log(`inbox error: ${Q.message}`)}}sleep(Q){return new Promise((W)=>setTimeout(W,Q))}};zB=b});import{readFileSync as O,writeFileSync as E,existsSync as q,mkdirSync as M,readdirSync as D,statSync as w}from"fs";import{join as L}from"path";import{spawn as k,execSync as a}from"child_process";import{createInterface as KB}from"readline";var R=process.argv.slice(2),j=R[0],G=process.env.HOME||process.env.USERPROFILE||"~";function t(){return L(G,".anet","config.json")}function S(){return L(G,".anet","server","config.json")}function x(){return L(process.cwd(),".anet","nodes")}function n(){return A().token||process.env.COMMHUB_TOKEN||J().token||""}function u(Q){let W=Q||n();return W?{Authorization:`Bearer ${W}`}:{}}function J(){let Q=t();if(q(Q))try{return JSON.parse(O(Q,"utf-8"))}catch{}return{}}function i(Q){let W=L(G,".anet");M(W,{recursive:!0}),E(L(W,"config.json"),JSON.stringify(Q,null,2)+`
4
+ `)}function c(){let Q=S();if(q(Q))try{return JSON.parse(O(Q,"utf-8"))}catch{}return{}}function d(Q){let W=L(G,".anet","server");M(W,{recursive:!0}),E(L(W,"config.json"),JSON.stringify(Q,null,2)+`
5
+ `)}function F(Q){let W=L(x(),Q,"config.json");if(!q(W))return null;try{let B=JSON.parse(O(W,"utf-8")),X=J();return{...B,hub:B.hub||X.hub||"",token:B.token||X.token||"",env:{...B.env},flags:{...B.flags}}}catch{return null}}function v(Q,W){let B=L(x(),Q);M(B,{recursive:!0}),E(L(B,"config.json"),JSON.stringify(W,null,2)+`
6
+ `)}function y(){let Q=x();if(!q(Q))return[];return D(Q).filter((W)=>q(L(Q,W,"config.json")))}function A(){let Q={_channels:[],_envs:[]};for(let W=0;W<R.length;W++){if(R[W]==="--channel"&&R[W+1]){Q._channels.push(R[++W]);continue}if(R[W]==="--env"&&R[W+1]){Q._envs.push(R[++W]);continue}if(R[W].startsWith("--")&&R[W+1]&&!R[W+1].startsWith("--"))Q[R[W].slice(2)]=R[++W]}return Q}function l(){console.log(`
7
7
  anet — AI Agent Network CLI
8
8
 
9
9
  anet init Configure hub URL (global, once)
@@ -23,12 +23,12 @@ Quick start:
23
23
  anet init --hub http://IP:9200
24
24
  anet start 指挥室 # Claude Code Agent
25
25
  anet start 小明 # MiniMax Agent (runtime: agent-sdk)
26
- `)}async function NB(){let Q=G(),W=Q.hub;if(!W)W=await J("CommHub URL (e.g. http://YOUR_IP:9200)");if(!W)k(),console.error("Error: hub URL required"),process.exit(1);W=W.replace(/\/+$/,"");let B=Q.token||"";if(!B)B=await J("Auth token (empty to skip)");k();try{let Y=await(await fetch(`${W}/health`,{headers:B?{Authorization:`Bearer ${B}`}:{}})).json();console.log(`✅ CommHub v${Y.version} — ${Y.sessions} sessions, ${Y.sse_connections} SSE`)}catch(z){console.error(`❌ Cannot reach ${W}: ${z.message}`),process.exit(1)}let X=O();if(X.hub=W,B)X.token=B;else if(!X.token)delete X.token;t(X),console.log(`
27
- Saved to ${a()}`),console.log("Next: anet init project")}async function UB(){let Q=O(),W=Q.hub;if(!W)console.error("Run 'anet init' first to configure hub URL"),process.exit(1);let B=K(process.cwd(),".anet");M(B,{recursive:!0});let X=K(B,"node-server.ts");if(!V(X)){let L=[K(new URL(".",import.meta.url).pathname,"..","..","src","node-server.ts"),K(new URL(".",import.meta.url).pathname,"..","src","node-server.ts"),K(process.argv[1],"..","..","src","node-server.ts")],E=!1;for(let q of L)if(V(q)){_(X,H(q,"utf-8")),console.log(" ✅ .anet/node-server.ts"),E=!0;break}if(!E)console.log(" ❌ Cannot find node-server.ts"),console.log(" Fix: cp $(npm root -g)/@sleep2agi/agent-network/src/node-server.ts .anet/node-server.ts")}else console.log(" Channel plugin: exists");let z=K(B,"package.json");if(!V(z)){_(z,JSON.stringify({private:!0,dependencies:{"@modelcontextprotocol/sdk":"^1.12.0"}},null,2)+`
28
- `);try{n("bun install",{cwd:B,stdio:"pipe"}),console.log(" ✅ Dependencies installed")}catch{console.log(" ⚠️ Run: cd .anet && bun install")}}let Y=K(B,".env"),U=Q.token||"",Z=`COMMHUB_URL=${W}
26
+ `)}async function UB(){let Q=A(),W=Q.hub;if(!W)W=await T("CommHub URL (e.g. http://YOUR_IP:9200)");if(!W)C(),console.error("Error: hub URL required"),process.exit(1);W=W.replace(/\/+$/,"");let B=Q.token||"";if(!B)B=await T("Auth token (empty to skip)");C();try{let Y=await(await fetch(`${W}/health`,{headers:B?{Authorization:`Bearer ${B}`}:{}})).json();console.log(`✅ CommHub v${Y.version} — ${Y.sessions} sessions, ${Y.sse_connections} SSE`)}catch($){console.error(`❌ Cannot reach ${W}: ${$.message}`),process.exit(1)}let X=J();if(X.hub=W,B)X.token=B;else if(!X.token)delete X.token;i(X),console.log(`
27
+ Saved to ${t()}`),console.log("Next: anet init project")}async function NB(){let Q=J(),W=Q.hub;if(!W)console.error("Run 'anet init' first to configure hub URL"),process.exit(1);let B=L(process.cwd(),".anet");M(B,{recursive:!0});let X=L(B,"node-server.ts");if(!q(X)){let V=[L(new URL(".",import.meta.url).pathname,"..","..","src","node-server.ts"),L(new URL(".",import.meta.url).pathname,"..","src","node-server.ts"),L(process.argv[1],"..","..","src","node-server.ts")],_=!1;for(let H of V)if(q(H)){E(X,O(H,"utf-8")),console.log(" ✅ .anet/node-server.ts"),_=!0;break}if(!_)console.log(" ❌ Cannot find node-server.ts"),console.log(" Fix: cp $(npm root -g)/@sleep2agi/agent-network/src/node-server.ts .anet/node-server.ts")}else console.log(" Channel plugin: exists");let $=L(B,"package.json");if(!q($)){E($,JSON.stringify({private:!0,dependencies:{"@modelcontextprotocol/sdk":"^1.12.0"}},null,2)+`
28
+ `);try{a("bun install",{cwd:B,stdio:"pipe"}),console.log(" ✅ Dependencies installed")}catch{console.log(" ⚠️ Run: cd .anet && bun install")}}let Y=L(B,".env"),U=Q.token||"",Z=`COMMHUB_URL=${W}
29
29
  `;if(U)Z+=`COMMHUB_TOKEN=${U}
30
- `;_(Y,Z),console.log(`CommHub URL: ${W}${U?" (with token)":""}`);let $=K(process.cwd(),".mcp.json"),N={};if(V($))try{N=JSON.parse(H($,"utf-8"))}catch{}if(!N.mcpServers?.commhub)N.mcpServers=N.mcpServers||{},N.mcpServers.commhub={type:"stdio",command:"bun",args:[".anet/node-server.ts"]},_($,JSON.stringify(N,null,2)+`
31
- `),console.log(".mcp.json: commhub → .anet/node-server.ts");else console.log(".mcp.json: commhub already set");let R=K(process.cwd(),"CLAUDE.md");if(!V(R))_(R,`# Agent Network (CommHub)
30
+ `;E(Y,Z),console.log(`CommHub URL: ${W}${U?" (with token)":""}`);let z=L(process.cwd(),".mcp.json"),N={};if(q(z))try{N=JSON.parse(O(z,"utf-8"))}catch{}if(!N.mcpServers?.commhub)N.mcpServers=N.mcpServers||{},N.mcpServers.commhub={type:"stdio",command:"bun",args:[".anet/node-server.ts"]},E(z,JSON.stringify(N,null,2)+`
31
+ `),console.log(".mcp.json: commhub → .anet/node-server.ts");else console.log(".mcp.json: commhub already set");let K=L(process.cwd(),"CLAUDE.md");if(!q(K))E(K,`# Agent Network (CommHub)
32
32
 
33
33
  ## 通信方式
34
34
 
@@ -67,32 +67,32 @@ commhub_get_all_status()
67
67
  - 回复指挥室用 commhub_send_task(不是 commhub_reply,reply 不推送)
68
68
  - 不要猜 alias,用 get_all_status 查
69
69
  `),console.log("CLAUDE.md: created");else console.log("CLAUDE.md: already exists");console.log(`
70
- ✅ Project ready. Next: anet init profile <id> --alias <名字> --channel server:commhub`)}function KB(){let Q=T[2];if(!Q)console.error("Usage: anet init profile <id> --alias <名字> [--channel ...] [--env ...]"),process.exit(1);let W=O(),B=G(),X=B.alias||Q,z=B.hub||W.hub;if(!z)console.error("Run 'anet init' first to configure hub URL"),process.exit(1);let Y={};for(let L of B._envs){let E=L.indexOf("=");if(E>0)Y[L.slice(0,E)]=L.slice(E+1)}let U=B.runtime||"claude-code",Z={anet_version:"0.0.24",...B.name?{name:B.name}:{},runtime:U,alias:X,hub:z,...B.model?{model:B.model}:{},...B.tools?{tools:B.tools.split(",").map((L)=>L.trim())}:{},channels:B._channels.length>0?B._channels:U==="claude-code"?["server:commhub"]:[],env:Y,flags:{dangerouslySkipPermissions:!0,...U==="claude-code"?{teammateMode:B["teammate-mode"]||"in-process"}:{},...B["max-turns"]?{maxTurns:parseInt(B["max-turns"])}:{}},...B.resume?{resume:B.resume}:{},...B["resume-alias"]?{resumeAlias:B["resume-alias"]}:{}},$=K(I,".claude","channels","commhub"),N=process.cwd().replace(/\//g,"-"),R=K($,N);if(M(R,{recursive:!0}),_(K(R,".env"),`COMMHUB_ALIAS=${X}
71
- `),S(Q,Z),console.log(`
70
+ ✅ Project ready. Next: anet init profile <id> --alias <名字> --channel server:commhub`)}function LB(){let Q=R[2];if(!Q)console.error("Usage: anet init profile <id> --alias <名字> [--channel ...] [--env ...]"),process.exit(1);let W=J(),B=A(),X=B.alias||Q,$=B.hub||W.hub;if(!$)console.error("Run 'anet init' first to configure hub URL"),process.exit(1);let Y={};for(let V of B._envs){let _=V.indexOf("=");if(_>0)Y[V.slice(0,_)]=V.slice(_+1)}let U=B.runtime||"claude-code",Z={anet_version:"0.0.24",...B.name?{name:B.name}:{},runtime:U,alias:X,hub:$,...B.model?{model:B.model}:{},...B.tools?{tools:B.tools.split(",").map((V)=>V.trim())}:{},channels:B._channels.length>0?B._channels:U==="claude-code"?["server:commhub"]:[],env:Y,flags:{dangerouslySkipPermissions:!0,...U==="claude-code"?{teammateMode:B["teammate-mode"]||"in-process"}:{},...B["max-turns"]?{maxTurns:parseInt(B["max-turns"])}:{}},...B.resume?{resume:B.resume}:{},...B["resume-alias"]?{resumeAlias:B["resume-alias"]}:{}},z=L(G,".claude","channels","commhub"),N=process.cwd().replace(/\//g,"-"),K=L(z,N);if(M(K,{recursive:!0}),E(L(K,".env"),`COMMHUB_ALIAS=${X}
71
+ `),v(Q,Z),console.log(`
72
72
  ✅ Profile "${Q}" saved`),console.log(` alias: ${X}`),console.log(` channels: ${Z.channels.join(", ")}`),Object.keys(Y).length)console.log(` env: ${Object.keys(Y).join(", ")}`);console.log(`
73
- Start: anet start ${Q}`)}var A=null;function EB(){if(!A)A=LB({input:process.stdin,output:process.stdout});return A}function k(){if(A)A.close(),A=null}function J(Q,W){let B=W?` [${W}]`:"";return new Promise((X)=>{EB().question(`${Q}${B}: `,(z)=>{X(z.trim()||W||"")})})}async function RB(Q){let W=O();console.log(`
73
+ Start: anet start ${Q}`)}var I=null;function RB(){if(!I)I=KB({input:process.stdin,output:process.stdout});return I}function C(){if(I)I.close(),I=null}function T(Q,W){let B=W?` [${W}]`:"";return new Promise((X)=>{RB().question(`${Q}${B}: `,($)=>{X($.trim()||W||"")})})}async function VB(Q){let W=J();console.log(`
74
74
  Profile "${Q}" not found. Let's create it:
75
- `);let B=await J("Runtime (claude-code / agent-sdk)","claude-code"),X=await J("Alias",Q),z,Y=[],U=[],Z="";if(B==="agent-sdk")z=await J("Model","MiniMax-M2.7"),Y=(await J("Tools (comma-separated)","Read,Bash,Grep")).split(",").map((q)=>q.trim()).filter(Boolean);else U=(await J("Channels (comma-separated)","server:commhub")).split(",").map((q)=>q.trim()).filter(Boolean),Z=await J("Teammate mode","in-process");let $=await J("Extra env (K=V, comma-separated, empty to skip)"),N={};if($)for(let E of $.split(",")){let q=E.trim().indexOf("=");if(q>0)N[E.trim().slice(0,q)]=E.trim().slice(q+1)}let R=W.hub;if(!R)console.error(`
76
- Run 'anet init' first to configure hub URL`),process.exit(1);let L={anet_version:"0.0.23",alias:X,hub:R,runtime:B,...z?{model:z}:{},...Y.length?{tools:Y}:{},channels:U,env:N,flags:{dangerouslySkipPermissions:!0,...Z?{teammateMode:Z}:{}}};return S(Q,L),k(),console.log(`
75
+ `);let B=await T("Runtime (claude-code / agent-sdk)","claude-code"),X=await T("Alias",Q),$,Y=[],U=[],Z="";if(B==="agent-sdk")$=await T("Model","MiniMax-M2.7"),Y=(await T("Tools (comma-separated)","Read,Bash,Grep")).split(",").map((H)=>H.trim()).filter(Boolean);else U=(await T("Channels (comma-separated)","server:commhub")).split(",").map((H)=>H.trim()).filter(Boolean),Z=await T("Teammate mode","in-process");let z=await T("Extra env (K=V, comma-separated, empty to skip)"),N={};if(z)for(let _ of z.split(",")){let H=_.trim().indexOf("=");if(H>0)N[_.trim().slice(0,H)]=_.trim().slice(H+1)}let K=W.hub;if(!K)console.error(`
76
+ Run 'anet init' first to configure hub URL`),process.exit(1);let V={anet_version:"0.0.23",alias:X,hub:K,runtime:B,...$?{model:$}:{},...Y.length?{tools:Y}:{},channels:U,env:N,flags:{dangerouslySkipPermissions:!0,...Z?{teammateMode:Z}:{}}};return v(Q,V),C(),console.log(`
77
77
  ✅ Profile "${Q}" saved
78
- `),L}function TB(Q){if((Q.runtime||"claude-code")!=="claude-code")return;if(!Q.channels?.some((L)=>L.includes("commhub")))return;let W=K(process.cwd(),".mcp.json"),B={};if(V(W))try{B=JSON.parse(H(W,"utf-8"))}catch{}let X=K(process.cwd(),".anet"),z=K(X,"node-server.ts"),Y=[K(new URL(".",import.meta.url).pathname,"..","..","src","node-server.ts"),K(new URL(".",import.meta.url).pathname,"..","src","node-server.ts"),K(process.argv[1],"..","..","src","node-server.ts")];for(let L of Y)if(V(L)){M(X,{recursive:!0});let E=H(L,"utf-8"),q=V(z)?H(z,"utf-8"):"";if(E!==q)_(z,E),console.log("[anet] Updated .anet/node-server.ts");break}let U=K(X,"package.json");if(!V(U)){M(X,{recursive:!0}),_(U,JSON.stringify({private:!0,dependencies:{"@modelcontextprotocol/sdk":"^1.12.0"}},null,2)+`
79
- `);try{n("bun install",{cwd:X,stdio:"pipe"})}catch{}}if(B.mcpServers=B.mcpServers||{},!Object.keys(B.mcpServers).some((L)=>L.includes("commhub")))B.mcpServers.commhub={type:"stdio",command:"bun",args:[".anet/node-server.ts"]},_(W,JSON.stringify(B,null,2)+`
80
- `),console.log("[anet] .mcp.json: added commhub");let $=K(X,".env"),N=Q.token||"",R=`COMMHUB_URL=${Q.hub||"http://127.0.0.1:9200"}
81
- `;if(N)R+=`COMMHUB_TOKEN=${N}
82
- `;_($,R),B.mcpServers=B.mcpServers||{},B.mcpServers.commhub={type:"stdio",command:"bun",args:[".anet/node-server.ts"]},_(W,JSON.stringify(B,null,2)+`
83
- `),console.log("[anet] .mcp.json: added commhub channel server")}async function o(Q,W){let B=F(Q);if(!B)B=await RB(Q);let X=B.runtime||"claude-code";console.log(`[anet] ${W==="start"?"Starting new":"Resuming"} "${Q}" (${B.alias}) [${X}]...
84
- `),TB(B);let Y=B.token||"";if(X==="agent-sdk"){let U=["@sleep2agi/agent-node","--alias",B.alias,"--hub",B.hub];if(B.model)U.push("--model",B.model);if(B.tools?.length)U.push("--tools",B.tools.join(","));if(B.flags?.maxTurns)U.push("--max-turns",String(B.flags.maxTurns));let Z={...process.env,...Y?{COMMHUB_TOKEN:Y}:{}};for(let[N,R]of Object.entries(B.env))Z[N]=R.replace(/^~/,I);C("npx",U,{env:Z,stdio:"inherit",shell:!0}).on("exit",(N)=>process.exit(N||0))}else{let U={...process.env,COMMHUB_ALIAS:B.alias,...Y?{COMMHUB_TOKEN:Y}:{}};for(let[N,R]of Object.entries(B.env))U[N]=R.replace(/^~/,I);let Z=[];if(B.flags.dangerouslySkipPermissions)Z.push("--dangerously-skip-permissions");for(let N of B.channels)if(N.startsWith("server:"))Z.push("--dangerously-load-development-channels",N);else Z.push("--channels",N);if(B.flags.teammateMode)Z.push("--teammate-mode",B.flags.teammateMode);if(W==="resume"){let N=B.resume||B.resumeAlias||B.name||B.alias;Z.push("--resume",N)}Z.push("-n",B.name||B.alias),C("claude",Z,{env:U,stdio:"inherit",shell:!0}).on("exit",(N)=>process.exit(N||0))}}async function l(){let Q=T[1];if(!Q){s("start");return}await o(Q,"start")}async function VB(){let Q=T[1];if(!Q){s("resume");return}let W=F(Q);if(!W){let B=G(),X=O(),z=B.hub||X.hub,Y=B.session;if(!Y)console.log(`Profile "${Q}" not found.
85
- `),console.log(`Quick setup: anet resume ${Q} --session <session-id>`),console.log(`Or create: anet init profile ${Q} --alias ${Q} --resume <session-id>`),process.exit(1);if(!z)console.error("Run 'anet init' first"),process.exit(1);W={runtime:"claude-code",alias:B.alias||Q,hub:z,channels:["server:commhub"],env:{},flags:{dangerouslySkipPermissions:!0,teammateMode:"in-process"},resume:Y},S(Q,W),console.log(`[anet] Created .anet/nodes/${Q}/config.json (resume: ${Y.slice(0,8)}...)
86
- `)}await o(Q,"resume")}function s(Q){let W=i();if(W.length===0){console.log("No profiles. Run: anet init profile <id> --alias <名字>");return}console.log(`
78
+ `),V}function _B(Q){if((Q.runtime||"claude-code")!=="claude-code")return;if(!Q.channels?.some((V)=>V.includes("commhub")))return;let W=L(process.cwd(),".mcp.json"),B={};if(q(W))try{B=JSON.parse(O(W,"utf-8"))}catch{}let X=L(process.cwd(),".anet"),$=L(X,"node-server.ts"),Y=[L(new URL(".",import.meta.url).pathname,"..","..","src","node-server.ts"),L(new URL(".",import.meta.url).pathname,"..","src","node-server.ts"),L(process.argv[1],"..","..","src","node-server.ts")];for(let V of Y)if(q(V)){M(X,{recursive:!0});let _=O(V,"utf-8"),H=q($)?O($,"utf-8"):"";if(_!==H)E($,_),console.log("[anet] Updated .anet/node-server.ts");break}let U=L(X,"package.json");if(!q(U)){M(X,{recursive:!0}),E(U,JSON.stringify({private:!0,dependencies:{"@modelcontextprotocol/sdk":"^1.12.0"}},null,2)+`
79
+ `);try{a("bun install",{cwd:X,stdio:"pipe"})}catch{}}if(B.mcpServers=B.mcpServers||{},!Object.keys(B.mcpServers).some((V)=>V.includes("commhub")))B.mcpServers.commhub={type:"stdio",command:"bun",args:[".anet/node-server.ts"]},E(W,JSON.stringify(B,null,2)+`
80
+ `),console.log("[anet] .mcp.json: added commhub");let z=L(X,".env"),N=Q.token||"",K=`COMMHUB_URL=${Q.hub||"http://127.0.0.1:9200"}
81
+ `;if(N)K+=`COMMHUB_TOKEN=${N}
82
+ `;E(z,K),B.mcpServers=B.mcpServers||{},B.mcpServers.commhub={type:"stdio",command:"bun",args:[".anet/node-server.ts"]},E(W,JSON.stringify(B,null,2)+`
83
+ `),console.log("[anet] .mcp.json: added commhub channel server")}async function o(Q,W){let B=F(Q);if(!B)B=await VB(Q);let X=B.runtime||"claude-code";console.log(`[anet] ${W==="start"?"Starting new":"Resuming"} "${Q}" (${B.alias}) [${X}]...
84
+ `),_B(B);let Y=B.token||"";if(X==="agent-sdk"){let U=["@sleep2agi/agent-node","--alias",B.alias,"--hub",B.hub];if(B.model)U.push("--model",B.model);if(B.tools?.length)U.push("--tools",B.tools.join(","));if(B.flags?.maxTurns)U.push("--max-turns",String(B.flags.maxTurns));let Z={...process.env,...Y?{COMMHUB_TOKEN:Y}:{}};for(let[N,K]of Object.entries(B.env))Z[N]=K.replace(/^~/,G);k("npx",U,{env:Z,stdio:"inherit",shell:!0}).on("exit",(N)=>process.exit(N||0))}else{let U={...process.env,COMMHUB_ALIAS:B.alias,...Y?{COMMHUB_TOKEN:Y}:{}};for(let[N,K]of Object.entries(B.env))U[N]=K.replace(/^~/,G);let Z=[];if(B.flags.dangerouslySkipPermissions)Z.push("--dangerously-skip-permissions");for(let N of B.channels)if(N.startsWith("server:"))Z.push("--dangerously-load-development-channels",N);else Z.push("--channels",N);if(B.flags.teammateMode)Z.push("--teammate-mode",B.flags.teammateMode);if(W==="resume"){let N=B.resume||B.resumeAlias||B.name||B.alias;Z.push("--resume",N)}Z.push("-n",B.name||B.alias),k("claude",Z,{env:U,stdio:"inherit",shell:!0}).on("exit",(N)=>process.exit(N||0))}}async function r(){let Q=R[1];if(!Q){s("start");return}await o(Q,"start")}async function qB(){let Q=R[1];if(!Q){s("resume");return}let W=F(Q);if(!W){let B=A(),X=J(),$=B.hub||X.hub,Y=B.session;if(!Y)console.log(`Profile "${Q}" not found.
85
+ `),console.log(`Quick setup: anet resume ${Q} --session <session-id>`),console.log(`Or create: anet init profile ${Q} --alias ${Q} --resume <session-id>`),process.exit(1);if(!$)console.error("Run 'anet init' first"),process.exit(1);W={runtime:"claude-code",alias:B.alias||Q,hub:$,channels:["server:commhub"],env:{},flags:{dangerouslySkipPermissions:!0,teammateMode:"in-process"},resume:Y},v(Q,W),console.log(`[anet] Created .anet/nodes/${Q}/config.json (resume: ${Y.slice(0,8)}...)
86
+ `)}await o(Q,"resume")}function s(Q){let W=y();if(W.length===0){console.log("No profiles. Run: anet init profile <id> --alias <名字>");return}console.log(`
87
87
  Profiles:
88
88
  `);for(let B of W){let X=F(B);console.log(` ${B}${X?.name?` (${X.name})`:""} → ${X?.alias} [${X?.channels.join(", ")}]`)}console.log(`
89
89
  anet ${Q} <id>
90
- `)}async function _B(){let Q=i();if(Q.length>0){console.log(`
90
+ `)}async function EB(){let Q=y();if(Q.length>0){console.log(`
91
91
  Profiles:
92
- `);for(let Z of Q){let $=F(Z);console.log(` ${Z}${$?.name?` (${$.name})`:""} → ${$?.alias} [${$?.channels.join(", ")}]`)}console.log()}let W=process.cwd(),B=K(I,".claude","sessions"),X=[];if(V(B))for(let Z of j(B).filter(($)=>$.endsWith(".json")))try{let $=JSON.parse(H(K(B,Z),"utf-8"));if($.cwd===W)X.push($)}catch{}if(X.length===0&&Q.length===0){console.log("No sessions or profiles in this directory."),console.log(`Get started: anet init
93
- `);return}let z=O(),Y=[],U={};if(z.hub)try{let[Z,$]=await Promise.all([fetch(`${z.hub}/api/status`,{headers:v()}).then((N)=>N.json()),fetch(`${z.hub}/health`,{headers:v()}).then((N)=>N.json())]);Y=Z.sessions||[],U=$.sse_sessions||{}}catch{}if(X.length>0){console.log(`Sessions (${W}):
94
- `),console.log(" SESSION PID NETWORK"),console.log(" ──────────────────── ─────── ─────────────────────");for(let Z of X){let $=Z.sessionId.slice(0,18),N=!1;try{process.kill(Z.pid,0),N=!0}catch{}let R="(not in network)",L=W.replace(/\//g,"-"),E=K(I,".claude","channels","commhub",L,".env");if(V(E)){let u=H(E,"utf-8").match(/COMMHUB_ALIAS=(.+)/);if(u){let x=u[1].trim(),h=Y.find((BB)=>BB.alias===x),e=U[x]?"●":"○";R=h?`${x} ${h.status} ${e}`:`${x} (not registered)`}}console.log(` ${$} ${(N?`${Z.pid}`:`${Z.pid}✕`).padEnd(7)} ${R}`)}console.log()}}async function qB(){let Q=O(),W=G(),B=process.env.COMMHUB_URL||W.hub||Q.hub||"http://127.0.0.1:9200",X=process.env.COMMHUB_ALIAS||W.alias;if(!X)console.error("Error: --alias required"),process.exit(1);let{CommHub:z}=await Promise.resolve().then(() => (m(),g)),Y=new z({url:B,alias:X});Y.on("task",async(U)=>{console.log(`[${X}] ← ${U.from_session}: ${U.content.slice(0,100)}`),await Y.send(U.from_session,`[${X}] 收到: ${U.content.slice(0,200)}`)}),Y.on("connected",()=>console.log(`[${X}] Connected`)),Y.on("disconnected",()=>console.log(`[${X}] Reconnecting...`)),process.on("SIGINT",()=>Y.disconnect().then(()=>process.exit(0))),console.log(`[${X}] Listening on ${B}`)}async function HB(){let Q=T[1];if(Q==="start"){let W=G(),B=p(),X=W.port||B.port||"9200",z=W.host||B.host||"0.0.0.0",Y=W.token||B.token||r();if(!Y)Y=crypto.randomUUID().replace(/-/g,""),console.log(`[anet] Generated auth token: ${Y}`),console.log(`[anet] Save this token — agents need it to connect.
95
- `);d({port:X,host:z,token:Y});let U=O();if(!U.token)U.token=Y,t(U);console.log(`[anet] Starting CommHub Server on ${z}:${X}${Y?" (auth enabled)":""}...`);let Z={...process.env,PORT:X,HOST:z};if(Y)Z.COMMHUB_AUTH_TOKEN=Y;C("bunx",["@sleep2agi/commhub-server"],{env:Z,stdio:"inherit",shell:!0}).on("exit",(N)=>process.exit(N||0))}else if(Q==="config"){let W=G(),B=p();if(W.port)B.port=W.port;if(W.host)B.host=W.host;if(W.token)B.token=W.token;if(W.port||W.host||W.token)d(B),console.log(`Server config saved: ${b()}`);console.log(JSON.stringify(B,null,2))}else console.log(`
92
+ `);for(let Z of Q){let z=F(Z);console.log(` ${Z}${z?.name?` (${z.name})`:""} → ${z?.alias} [${z?.channels.join(", ")}]`)}console.log()}let W=process.cwd(),B=L(G,".claude","sessions"),X=[];if(q(B))for(let Z of D(B).filter((z)=>z.endsWith(".json")))try{let z=JSON.parse(O(L(B,Z),"utf-8"));if(z.cwd===W)X.push(z)}catch{}if(X.length===0&&Q.length===0){console.log("No sessions or profiles in this directory."),console.log(`Get started: anet init
93
+ `);return}let $=J(),Y=[],U={};if($.hub)try{let[Z,z]=await Promise.all([fetch(`${$.hub}/api/status`,{headers:u()}).then((N)=>N.json()),fetch(`${$.hub}/health`,{headers:u()}).then((N)=>N.json())]);Y=Z.sessions||[],U=z.sse_sessions||{}}catch{}if(X.length>0){console.log(`Sessions (${W}):
94
+ `),console.log(" SESSION PID NETWORK"),console.log(" ──────────────────── ─────── ─────────────────────");for(let Z of X){let z=Z.sessionId.slice(0,18),N=!1;try{process.kill(Z.pid,0),N=!0}catch{}let K="(not in network)",V=W.replace(/\//g,"-"),_=L(G,".claude","channels","commhub",V,".env");if(q(_)){let h=O(_,"utf-8").match(/COMMHUB_ALIAS=(.+)/);if(h){let P=h[1].trim(),f=Y.find((BB)=>BB.alias===P),e=U[P]?"●":"○";K=f?`${P} ${f.status} ${e}`:`${P} (not registered)`}}console.log(` ${z} ${(N?`${Z.pid}`:`${Z.pid}✕`).padEnd(7)} ${K}`)}console.log()}}async function HB(){let Q=J(),W=A(),B=process.env.COMMHUB_URL||W.hub||Q.hub||"http://127.0.0.1:9200",X=process.env.COMMHUB_ALIAS||W.alias;if(!X)console.error("Error: --alias required"),process.exit(1);let{CommHub:$}=await Promise.resolve().then(() => (p(),m)),Y=new $({url:B,alias:X});Y.on("task",async(U)=>{console.log(`[${X}] ← ${U.from_session}: ${U.content.slice(0,100)}`),await Y.send(U.from_session,`[${X}] 收到: ${U.content.slice(0,200)}`)}),Y.on("connected",()=>console.log(`[${X}] Connected`)),Y.on("disconnected",()=>console.log(`[${X}] Reconnecting...`)),process.on("SIGINT",()=>Y.disconnect().then(()=>process.exit(0))),console.log(`[${X}] Listening on ${B}`)}async function OB(){let Q=R[1];if(Q==="start"){let W=A(),B=c(),X=W.port||B.port||"9200",$=W.host||B.host||"0.0.0.0",Y=W.token||B.token||n();if(!Y)Y=crypto.randomUUID().replace(/-/g,""),console.log(`[anet] Generated auth token: ${Y}`),console.log(`[anet] Save this token — agents need it to connect.
95
+ `);d({port:X,host:$,token:Y});let U=J();if(!U.token)U.token=Y,i(U);console.log(`[anet] Starting CommHub Server on ${$}:${X}${Y?" (auth enabled)":""}...`);let Z={...process.env,PORT:X,HOST:$};if(Y)Z.COMMHUB_AUTH_TOKEN=Y;k("bunx",["@sleep2agi/commhub-server"],{env:Z,stdio:"inherit",shell:!0}).on("exit",(N)=>process.exit(N||0))}else if(Q==="config"){let W=A(),B=c();if(W.port)B.port=W.port;if(W.host)B.host=W.host;if(W.token)B.token=W.token;if(W.port||W.host||W.token)d(B),console.log(`Server config saved: ${S()}`);console.log(JSON.stringify(B,null,2))}else console.log(`
96
96
  anet server <command>
97
97
 
98
98
  start [options] Start CommHub Server
@@ -103,19 +103,43 @@ Options:
103
103
  --host <host> Bind address (default: 0.0.0.0)
104
104
  --token <token> Auth token
105
105
 
106
- Config: ${b()}
106
+ Config: ${S()}
107
107
  First 'anet server start' saves config, after that just 'anet server start'.
108
108
 
109
109
  Example:
110
110
  anet server start --port 9200 --token my-secret # 首次,保存配置
111
111
  anet server start # 之后直接启动
112
112
  anet server config # 查看配置
113
- `)}async function OB(){let Q=O(),B=G().hub||Q.hub;if(!B)console.error("Run 'anet init' first"),process.exit(1);let X=[];try{X=(await(await fetch(`${B}/api/status`,{headers:v()})).json()).sessions||[]}catch($){console.error(`Cannot reach ${B}: ${$.message}`),process.exit(1)}if(X.length===0){console.log("No sessions in CommHub.");return}let z=X.filter(($)=>$.agent==="claude-code"&&$.project_dir);if(z.length===0){console.log("No claude-code sessions found.");return}let Y=T[1],U=Y?z.filter(($)=>$.alias===Y):z;if(U.length===0){console.log(`No session found for "${Y}".`);return}let Z=0;for(let $ of U){let N=$.project_dir,R=K(N,".anet","nodes",$.alias),L=K(R,"config.json");if(V(L)){console.log(` ⏭ ${$.alias} — already exists (${N})`);continue}if(!V(N)){console.log(` ⚠ ${$.alias} — project_dir not found: ${N}`);continue}let E={runtime:"claude-code",alias:$.alias,hub:B,channels:["server:commhub"],env:{},flags:{dangerouslySkipPermissions:!0,teammateMode:"in-process"},resume:$.resume_id};M(R,{recursive:!0}),_(L,JSON.stringify(E,null,2)+`
114
- `),console.log(` ✅ ${$.alias} → ${N}/.anet/nodes/${$.alias}/config.json`),Z++}console.log(`
115
- Imported ${Z} session(s). Use: cd <project> && anet resume <alias>`)}function IB(){let Q=T[1];if(Q==="ls"||Q==="list"||!Q){let W=process.cwd(),B=W.replace(/\//g,"-"),X=K(I,".claude","projects",B);if(!V(X)){console.log(`No sessions for ${W}`);return}let z=j(X).filter((Y)=>Y.endsWith(".jsonl")).sort((Y,U)=>{let Z=w(K(X,Y));return w(K(X,U)).mtimeMs-Z.mtimeMs});if(z.length===0){console.log("No sessions.");return}console.log(`
116
- Sessions in ${W} (${z.length} total):
117
- `),console.log(" SESSION ID SIZE MODIFIED"),console.log(" ────────────────────────────────────── ──────── ────────────────");for(let Y of z){let U=Y.replace(".jsonl",""),Z=w(K(X,Y)),$=Z.size<1024?`${Z.size}B`:Z.size<1048576?`${(Z.size/1024).toFixed(0)}KB`:`${(Z.size/1024/1024).toFixed(1)}MB`,N=Z.mtime.toISOString().replace("T"," ").slice(0,16);console.log(` ${U} ${$.padStart(8)} ${N}`)}console.log()}else console.log(`
113
+ `)}async function TB(){let Q=J(),B=A().hub||Q.hub;if(!B)console.error("Run 'anet init' first"),process.exit(1);let X=[];try{X=(await(await fetch(`${B}/api/status`,{headers:u()})).json()).sessions||[]}catch(z){console.error(`Cannot reach ${B}: ${z.message}`),process.exit(1)}if(X.length===0){console.log("No sessions in CommHub.");return}let $=X.filter((z)=>z.agent==="claude-code"&&z.project_dir);if($.length===0){console.log("No claude-code sessions found.");return}let Y=R[1],U=Y?$.filter((z)=>z.alias===Y):$;if(U.length===0){console.log(`No session found for "${Y}".`);return}let Z=0;for(let z of U){let N=z.project_dir,K=L(N,".anet","nodes",z.alias),V=L(K,"config.json");if(q(V)){console.log(` ⏭ ${z.alias} — already exists (${N})`);continue}if(!q(N)){console.log(` ⚠ ${z.alias} — project_dir not found: ${N}`);continue}let _={runtime:"claude-code",alias:z.alias,hub:B,channels:["server:commhub"],env:{},flags:{dangerouslySkipPermissions:!0,teammateMode:"in-process"},resume:z.resume_id};M(K,{recursive:!0}),E(V,JSON.stringify(_,null,2)+`
114
+ `),console.log(` ✅ ${z.alias} → ${N}/.anet/nodes/${z.alias}/config.json`),Z++}console.log(`
115
+ Imported ${Z} session(s). Use: cd <project> && anet resume <alias>`)}function JB(){let Q=R[1];if(Q==="ls"||Q==="list"||!Q){let W=process.cwd(),B=W.replace(/\//g,"-"),X=L(G,".claude","projects",B);if(!q(X)){console.log(`No sessions for ${W}`);return}let $=D(X).filter((Y)=>Y.endsWith(".jsonl")).sort((Y,U)=>{let Z=w(L(X,Y));return w(L(X,U)).mtimeMs-Z.mtimeMs});if($.length===0){console.log("No sessions.");return}console.log(`
116
+ Sessions in ${W} (${$.length} total):
117
+ `),console.log(" SESSION ID SIZE MODIFIED"),console.log(" ────────────────────────────────────── ──────── ────────────────");for(let Y of $){let U=Y.replace(".jsonl",""),Z=w(L(X,Y)),z=Z.size<1024?`${Z.size}B`:Z.size<1048576?`${(Z.size/1024).toFixed(0)}KB`:`${(Z.size/1024/1024).toFixed(1)}MB`,N=Z.mtime.toISOString().replace("T"," ").slice(0,16);console.log(` ${U} ${z.padStart(8)} ${N}`)}console.log()}else console.log(`
118
118
  anet session <command>
119
119
 
120
120
  ls List Claude Code sessions in current project
121
- `)}switch(P){case"init":if(T[1]==="project")UB();else if(T[1]==="profile")KB();else NB();break;case"server":HB();break;case"start":l();break;case"resume":VB();break;case"import":OB();break;case"session":IB();break;case"ls":case"list":_B();break;case"run":qB();break;case"-v":case"--version":case"version":{let Q=JSON.parse(H(K(new URL(".",import.meta.url).pathname,"..","..","package.json"),"utf-8"));console.log(`anet v${Q.version}`);break}case"--help":case"-h":case void 0:c();break;default:if(F(P))T.unshift("start"),l();else console.error(`Unknown: ${P}`),c(),process.exit(1)}
121
+ `)}async function MB(){let Q=R[1],W=A();if(Q==="add"){let B=R[2],X=R[3];if(!B||!X){console.log(`
122
+ anet channel add <type> <node-id> [options]
123
+
124
+ Types: telegram | wechat | feishu
125
+
126
+ Options:
127
+ --bot-token <token> Bot token
128
+ --allow <user-id> Allow user ID
129
+
130
+ Example:
131
+ anet channel add telegram 指挥室 --bot-token 123:ABC --allow 7612221352
132
+ anet channel add telegram 指挥室 # 交互式
133
+ `);return}let $=F(X);if(!$)console.error(`Node "${X}" not found. Create it first: anet start ${X}`),process.exit(1);let Y=W["bot-token"],U=W.allow;if(!Y)Y=await T(`${B} Bot Token`);if(!U)U=await T("Allow User ID");if(C(),!Y||!U)console.error("Error: bot-token and allow required"),process.exit(1);let Z=L(x(),X,"channels",B);M(Z,{recursive:!0}),M(L(Z,"inbox"),{recursive:!0});let z=B==="telegram"?"TELEGRAM_BOT_TOKEN":B==="wechat"?"WECHAT_BOT_TOKEN":"FEISHU_BOT_TOKEN";E(L(Z,".env"),`${z}=${Y}
134
+ `),E(L(Z,"access.json"),JSON.stringify({dmPolicy:"allowlist",allowFrom:[U],groups:{},pending:{}},null,2)+`
135
+ `);let N=B==="telegram"?"plugin:telegram@claude-plugins-official":B==="wechat"?"plugin:wechat":"plugin:feishu",K=B==="telegram"?"TELEGRAM_STATE_DIR":B==="wechat"?"WECHAT_STATE_DIR":"FEISHU_STATE_DIR";if(!$.channels.includes(N))$.channels.push(N);$.env[K]=Z,v(X,$),console.log(`
136
+ ✅ ${B} channel added to "${X}"`),console.log(` ${Z}/`),console.log(" config.json updated")}else if(Q==="ls"){let B=R[2],X=B?[B]:y(),$=!1;for(let Y of X){let U=L(x(),Y,"channels");if(!q(U))continue;let Z=D(U).filter((z)=>{try{return w(L(U,z)).isDirectory()}catch{return!1}});if(Z.length===0)continue;if(!$)console.log(`
137
+ Node Channels:
138
+ `),$=!0;for(let z of Z){let N=L(U,z,"access.json"),K="";if(q(N))try{K=JSON.parse(O(N,"utf-8")).allowFrom?.join(", ")||""}catch{}console.log(` ${Y.padEnd(20)} ${z.padEnd(12)} allow: ${K||"(none)"}`)}}if(!$)console.log("No channels. Add one: anet channel add telegram <node-id>");console.log()}else console.log(`
139
+ anet channel <command>
140
+
141
+ add <type> <node-id> Add channel to a node
142
+ ls [node-id] List channels
143
+
144
+ Data: .anet/nodes/<node-id>/channels/<type>/
145
+ `)}switch(j){case"init":if(R[1]==="project")NB();else if(R[1]==="profile")LB();else UB();break;case"server":OB();break;case"start":r();break;case"resume":qB();break;case"import":TB();break;case"channel":MB();break;case"session":JB();break;case"ls":case"list":EB();break;case"run":HB();break;case"-v":case"--version":case"version":{let Q=JSON.parse(O(L(new URL(".",import.meta.url).pathname,"..","..","package.json"),"utf-8"));console.log(`anet v${Q.version}`);break}case"--help":case"-h":case void 0:l();break;default:if(F(j))R.unshift("start"),r();else console.error(`Unknown: ${j}`),l(),process.exit(1)}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@sleep2agi/agent-network",
3
- "version": "0.0.48",
3
+ "version": "0.0.49",
4
4
  "description": "AI Agent Network — Server + Client + Setup in one package. SSE real-time communication for multi-agent orchestration.",
5
5
  "type": "module",
6
6
  "main": "dist/src/client.js",
@@ -86,6 +86,8 @@ function sleep(ms: number): Promise<void> {
86
86
  log(`ENV: URL=${COMMHUB_URL} ALIAS=${ALIAS} RESUME_ID=${RESUME_ID.slice(0, 8)}... TMUX=${TMUX_NAME || "none"} CWD=${process.cwd()} PROJECT_ENV=${projectPath}`);
87
87
 
88
88
  // ── MCP Server with Channel capability ──────────────
89
+ // name 不要拼 alias!Claude Code 用 meta.user 自动加 "· xxx" 后缀
90
+ // 参考: telegram 插件 name 也只是 "telegram",不是 "telegram · vansinhu"
89
91
  const mcp = new Server(
90
92
  {
91
93
  name: "commhub-channel",
@@ -360,7 +362,7 @@ async function handleSSEEvent(event: any) {
360
362
  meta: {
361
363
  sender: event.from || "hub",
362
364
  sender_id: "commhub",
363
- user: event.from || "hub",
365
+ user: event.from || "hub", // Claude Code 用 meta.user 显示 "commhub · {user}"
364
366
  priority: "normal",
365
367
  },
366
368
  },
@@ -386,7 +388,7 @@ async function handleSSEEvent(event: any) {
386
388
  const meta: Record<string, string> = {
387
389
  sender: msg.from_session || "hub",
388
390
  sender_id: "commhub",
389
- user: msg.from_session || "hub",
391
+ user: msg.from_session || "hub", // Claude Code 用 meta.user 显示 "commhub · {user}"
390
392
  task_id: msg.id,
391
393
  priority: msg.priority || "normal",
392
394
  };