@logicseek/hello-cc 0.1.0 → 0.1.1
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 +60 -391
- package/README.zh-CN.md +40 -336
- package/bin/hcc.mjs +30 -5
- package/docs/guide.md +372 -0
- package/docs/guide.zh-CN.md +324 -0
- package/package.json +2 -2
- package/scripts/regression.mjs +26 -0
package/README.zh-CN.md
CHANGED
|
@@ -6,89 +6,64 @@
|
|
|
6
6
|
|
|
7
7
|
<p align="center">
|
|
8
8
|
<a href="https://github.com/Dullne/hello-cc"><img src="https://img.shields.io/github/stars/Dullne/hello-cc?style=flat-square&color=40c4aa" alt="GitHub stars"></a>
|
|
9
|
+
<a href="https://www.npmjs.com/package/@logicseek/hello-cc"><img src="https://img.shields.io/npm/v/@logicseek/hello-cc?style=flat-square&color=40c4aa" alt="npm version"></a>
|
|
9
10
|
<a href="https://nodejs.org"><img src="https://img.shields.io/badge/node-%3E%3D24.0.0-brightgreen?style=flat-square" alt="node >=24"></a>
|
|
10
11
|
<a href="./LICENSE"><img src="https://img.shields.io/badge/license-Apache%202.0-blue?style=flat-square" alt="license Apache 2.0"></a>
|
|
11
12
|
</p>
|
|
12
13
|
|
|
13
14
|
<p align="center"><a href="README.md">English</a> | <b>中文</b></p>
|
|
14
15
|
|
|
15
|
-
`hello-cc`
|
|
16
|
+
`hello-cc` 是 Claude Code、Codex 和其他编程 CLI 会话的本地控制平面。它让同一个项目里的终端共享任务板、消息、锁、交接和浏览器控制台,同时保留真实本地终端作为主要交互入口。
|
|
16
17
|
|
|
17
|
-
|
|
18
|
+
它适合在同一个仓库里同时运行多个 AI 编程 agent 的场景:让它们知道彼此在做什么,而不是各自猜测。
|
|
18
19
|
|
|
19
|
-
|
|
20
|
-
<project>/.hello-cc/mesh.db
|
|
21
|
-
```
|
|
20
|
+
## 特色
|
|
22
21
|
|
|
23
|
-
|
|
22
|
+
- **项目本地共享状态**:peers、tasks、messages、locks、handoffs 和 events 写入 `<project>/.hello-cc/mesh.db`。
|
|
23
|
+
- **Web 控制真实终端**:浏览器 attach 到和本地终端相同的 tmux pane,不是浏览器里另开的临时 shell。
|
|
24
|
+
- **Claude/Codex 感知项目状态**:hooks 会在模型回答前注入实时 `hcc` 状态。
|
|
25
|
+
- **减少编辑冲突**:通过 advisory lock 和 handoff 显式协调多 agent 修改。
|
|
26
|
+
- **resume 友好的稳定身份**:当 provider 暴露 session id 时,恢复会话会映射回稳定 peer。
|
|
27
|
+
- **一个控制台管理多个项目**:单个本地 Web runtime 可以在多个 project root 之间切换。
|
|
24
28
|
|
|
25
|
-
|
|
29
|
+
## 安装
|
|
26
30
|
|
|
27
|
-
|
|
31
|
+
需要 Node.js 24 或更新版本。
|
|
28
32
|
|
|
29
33
|
```bash
|
|
30
34
|
npm install -g @logicseek/hello-cc
|
|
31
35
|
```
|
|
32
36
|
|
|
33
|
-
|
|
37
|
+
更新已有的全局安装:
|
|
34
38
|
|
|
35
39
|
```bash
|
|
36
|
-
|
|
40
|
+
npm install -g @logicseek/hello-cc@latest
|
|
37
41
|
```
|
|
38
42
|
|
|
39
|
-
|
|
43
|
+
也可以不全局安装,直接运行:
|
|
40
44
|
|
|
41
45
|
```bash
|
|
42
|
-
|
|
46
|
+
npx @logicseek/hello-cc web
|
|
43
47
|
```
|
|
44
48
|
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
## 默认入口
|
|
49
|
+
## 快速开始
|
|
48
50
|
|
|
49
|
-
|
|
51
|
+
在需要多个 agent 共享状态的项目中运行:
|
|
50
52
|
|
|
51
53
|
```bash
|
|
52
54
|
cd /path/to/project
|
|
53
55
|
hcc web
|
|
54
56
|
```
|
|
55
57
|
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
1. 在当前目录初始化 `.hello-cc/mesh.db`
|
|
59
|
-
2. 写入受控项目指导块:`.hello-cc/HCC.md`、`CLAUDE.md`、`AGENTS.md`
|
|
60
|
-
3. 安装或刷新 Claude Code hooks:`~/.claude/settings.json`
|
|
61
|
-
4. 安装或刷新 Codex hooks:`~/.codex/hooks.json`
|
|
62
|
-
5. 安装 `~/.hcc-shims/claude` 和 `~/.hcc-shims/codex`
|
|
63
|
-
6. 将 `~/.hcc-shims` 加入 shell PATH
|
|
64
|
-
7. 检查 `tmux`,缺失时尽量自动安装;安装失败会打印明确的系统安装命令
|
|
65
|
-
8. 后台启动或复用全局 Web 控制台,注册当前项目,打印 URL、PID、runtime 文件和日志文件,然后把当前终端还给用户
|
|
66
|
-
|
|
67
|
-
输出示例:
|
|
68
|
-
|
|
69
|
-
```text
|
|
70
|
-
hello-cc web started in background
|
|
71
|
-
pid: 12345
|
|
72
|
-
project: /path/to/project
|
|
73
|
-
database: /path/to/project/.hello-cc/mesh.db
|
|
74
|
-
runtime: /path/to/project/.hello-cc/runtime.json
|
|
75
|
-
log: /path/to/project/.hello-cc/web.log
|
|
76
|
-
open: http://<machine-ip>:8787/?project=%2Fpath%2Fto%2Fproject
|
|
77
|
-
local: http://127.0.0.1:8787/?project=%2Fpath%2Fto%2Fproject
|
|
78
|
-
shims: installed claude, codex
|
|
79
|
-
PATH updated in ~/.bashrc; open a new terminal or source it
|
|
80
|
-
stop: hcc down
|
|
81
|
-
```
|
|
82
|
-
|
|
83
|
-
`hcc web` 使用单个全局 Web runtime。你在另一个项目目录再次运行 `hcc web` 时,不会再开一个端口,而是把那个项目注册到同一个 Web 控制台。前端左侧的 Project 下拉框可以在不同项目 root 之间切换;每个项目仍然使用自己的 `.hello-cc/mesh.db`,任务、消息、锁和 peers 不会混在一起。
|
|
58
|
+
然后打开命令输出里的本地 URL。`hcc web` 会初始化项目总线,安装 Claude/Codex hooks 和 shims,启动或复用 Web 控制台,然后把终端还给你。
|
|
84
59
|
|
|
85
|
-
第一次安装 shim
|
|
60
|
+
第一次安装 shim 后,打开新终端或重新加载 shell:
|
|
86
61
|
|
|
87
62
|
```bash
|
|
88
63
|
source ~/.bashrc
|
|
89
64
|
```
|
|
90
65
|
|
|
91
|
-
|
|
66
|
+
在项目目录中正常启动 agent:
|
|
92
67
|
|
|
93
68
|
```bash
|
|
94
69
|
claude
|
|
@@ -97,257 +72,33 @@ claude --resume <session-id>
|
|
|
97
72
|
codex resume <session-id>
|
|
98
73
|
```
|
|
99
74
|
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
- 本地终端会 attach 到同一个 tmux 会话,仍然可以像普通 Claude/Codex 一样交互。
|
|
103
|
-
- Web 控制台看到并操作的是同一个 tmux pane,不是浏览器里临时创建的假终端。
|
|
104
|
-
- `hcc down` 只停止 Web runtime,不会杀掉这些 tmux 会话。
|
|
105
|
-
- 重新运行 `hcc web` 后,只要 tmux pane 还活着,Web 会重新接管。
|
|
106
|
-
|
|
107
|
-
如果只想初始化本地通信、不启动 Web、不安装 shim,可使用低级命令:
|
|
108
|
-
|
|
109
|
-
```bash
|
|
110
|
-
hcc up
|
|
111
|
-
```
|
|
112
|
-
|
|
113
|
-
普通用户优先使用 `hcc web`。
|
|
114
|
-
|
|
115
|
-
## 项目边界
|
|
116
|
-
|
|
117
|
-
默认项目边界就是**当前工作目录**。如果当前目录没有 `.hello-cc/mesh.db`,hello-cc 会在首次使用时创建:
|
|
118
|
-
|
|
119
|
-
```text
|
|
120
|
-
/repo-a/.hello-cc/mesh.db
|
|
121
|
-
/repo-a/subdir/.hello-cc/mesh.db
|
|
122
|
-
```
|
|
123
|
-
|
|
124
|
-
这两个目录默认是两个不同项目,不会互相看到任务和消息。也就是说,想让多个终端自然通信,就在同一个项目目录启动它们。
|
|
125
|
-
|
|
126
|
-
如果确实要让多个路径或 worktree 共用一个总线,需要显式指定:
|
|
127
|
-
|
|
128
|
-
```bash
|
|
129
|
-
hcc --root /path/to/project web
|
|
130
|
-
export HCC_ROOT=/path/to/project
|
|
131
|
-
export HCC_DB=/path/to/project/.hello-cc/mesh.db
|
|
132
|
-
```
|
|
133
|
-
|
|
134
|
-
hello-cc 不会自动向父目录继承项目边界,这样可以避免子目录会话误加入上层项目。
|
|
135
|
-
|
|
136
|
-
## 通信模型
|
|
137
|
-
|
|
138
|
-
hello-cc 的通信不是 CLI 之间点对点互发,也不是必须依赖 MCP。核心是项目本地 SQLite WAL 总线:
|
|
139
|
-
|
|
140
|
-
```text
|
|
141
|
-
Claude/Codex/普通 shell
|
|
142
|
-
-> hooks / hcc 命令 / Web runtime
|
|
143
|
-
-> .hello-cc/mesh.db
|
|
144
|
-
-> peers / tasks / messages / locks / handoffs / events
|
|
145
|
-
-> tmux-backed terminal control
|
|
146
|
-
```
|
|
147
|
-
|
|
148
|
-
任务和消息的读取规则不同:
|
|
149
|
-
|
|
150
|
-
- **任务**是项目事实。所有 peer 默认都能看到未 `done` 且未 `abandoned` 的任务。任务不因为某个终端读过就消失。
|
|
151
|
-
- **消息**是带收件人的邮箱消息。`hcc msg inbox` 默认读取当前 peer 的未读消息,`hcc msg ack` 记录每个 peer 的已读状态。
|
|
152
|
-
- `ask` 和 `broadcast` 会写入持久消息;加 `--inject` 时,还会把内容实时注入到 Web runtime 已接管的终端。
|
|
153
|
-
- 锁使用 SQLite 事务和 TTL,是协作式冲突避免,不是文件系统强制沙箱。
|
|
154
|
-
|
|
155
|
-
## Claude/Codex 如何知道其他会话状态
|
|
156
|
-
|
|
157
|
-
主链路是 hooks 注入,不是依赖 `CLAUDE.md`。
|
|
158
|
-
|
|
159
|
-
`hcc web` 会安装 Claude Code 和 Codex hooks。在会话启动、用户提交 prompt、工具调用前后、会话空闲等事件中,hooks 会把 hello-cc 当前快照注入给模型,包括:
|
|
160
|
-
|
|
161
|
-
- 当前 peer 身份
|
|
162
|
-
- 活跃 peer 列表
|
|
163
|
-
- 未完成任务
|
|
164
|
-
- 当前 peer 的未读消息
|
|
165
|
-
- 活跃锁
|
|
166
|
-
- 建议执行的 `hcc status`、`hcc peers`、`hcc task list`、`hcc msg inbox`、`hcc lock list`
|
|
167
|
-
|
|
168
|
-
因此,当你在已接入的 Claude/Codex 里问“其他会话在做什么”,模型应该基于 hello-cc 注入的最新状态回答,而不是回答“我无法知道其他会话”。
|
|
169
|
-
|
|
170
|
-
如果仍然回答不知道,通常是这些原因:
|
|
171
|
-
|
|
172
|
-
- 没有先在项目目录运行 `hcc web`
|
|
173
|
-
- 当前终端还没有加载 `~/.hcc-shims` PATH
|
|
174
|
-
- Codex hooks 没有开启,或安装后的 hook 还没有在 Codex 的 hook review 流程里信任
|
|
175
|
-
- Claude/Codex 是在不同项目目录启动的
|
|
176
|
-
- 当前会话是 `hcc web` 之前打开的 raw 终端,只能靠下一次 hook 事件加入通信,不能被 Web 视觉接管
|
|
177
|
-
- 对应 CLI 版本没有触发预期 hook
|
|
178
|
-
|
|
179
|
-
可以在同一个项目目录用一次性 debug 调用确认 Claude Code 是否真实注入:
|
|
180
|
-
|
|
181
|
-
```bash
|
|
182
|
-
claude -p 'Do you see hello-cc open tasks?' --debug hooks --debug-file /tmp/hcc-claude-hooks.log
|
|
183
|
-
```
|
|
184
|
-
|
|
185
|
-
debug 日志里应该出现 `Hook UserPromptSubmit ... provided additionalContext`,并包含
|
|
186
|
-
`hello-cc coordination` 块。如果这里已经存在,但模型仍然按通用知识回答“会话隔离”,请通过
|
|
187
|
-
shim 重新启动或 resume 会话后再问;hello-cc 会在每次新用户 prompt 时注入更强的约束。
|
|
188
|
-
|
|
189
|
-
## Web 控制台
|
|
190
|
-
|
|
191
|
-
默认本机和内网访问:
|
|
192
|
-
|
|
193
|
-
```bash
|
|
194
|
-
hcc web
|
|
195
|
-
```
|
|
196
|
-
|
|
197
|
-
需要令牌时再显式开启:
|
|
198
|
-
|
|
199
|
-
```bash
|
|
200
|
-
HCC_WEB_TOKEN='choose-a-long-token' hcc web --host 0.0.0.0 --port 8787
|
|
201
|
-
```
|
|
202
|
-
|
|
203
|
-
同一内网机器打开:
|
|
204
|
-
|
|
205
|
-
```text
|
|
206
|
-
http://<machine-ip>:8787/
|
|
207
|
-
```
|
|
208
|
-
|
|
209
|
-
Web 控制台能做两类事情:
|
|
210
|
-
|
|
211
|
-
- 在一个端口里切换多个已注册项目 root
|
|
212
|
-
- 直接在浏览器里注册新的项目路径,不需要为每个项目再启动一个 Web 服务
|
|
213
|
-
- 查看项目状态:peers、tasks、messages、locks、handoffs、events
|
|
214
|
-
- 操作本地终端:启动 Claude/Codex/shell、发送键盘输入、查看输出、停止接管
|
|
215
|
-
- 启动新会话时只需要选择 kind;表单会自动生成当前项目内的下一个别名,使用当前选中的 Project root 作为工作目录,并运行该 kind 的默认命令
|
|
216
|
-
|
|
217
|
-
Web 操作的是 hello-cc 管理的本机 tmux 终端:
|
|
218
|
-
|
|
219
|
-
- 通过 Web 表单启动的新会话:tmux-backed
|
|
220
|
-
- 通过 `hcc peer start` 启动的新会话:tmux-backed
|
|
221
|
-
- 通过 shim 直接输入 `claude` / `codex` 启动的新会话:tmux-backed
|
|
222
|
-
- 通过 `hcc peer attach` 接入的已有 tmux pane:tmux-backed
|
|
223
|
-
|
|
224
|
-
已经打开的普通 raw 终端不能被无条件捕获或控制,除非它原本就在 tmux、screen 或 hello-cc shim 之下。它仍然可以通过 hooks 和 `hcc` 命令参与任务、消息和锁。
|
|
225
|
-
|
|
226
|
-
## 常用命令
|
|
227
|
-
|
|
228
|
-
```text
|
|
229
|
-
# 默认入口
|
|
230
|
-
hcc web [--host HOST] [--port N] [--token TEXT] [--local] [--no-discover] [--no-guidance]
|
|
231
|
-
hcc down
|
|
232
|
-
|
|
233
|
-
# 本地协调,无 Web
|
|
234
|
-
hcc up [--no-discover] [--no-guidance]
|
|
235
|
-
|
|
236
|
-
# 对等节点和状态
|
|
237
|
-
hcc peers
|
|
238
|
-
hcc status [--peer ID]
|
|
239
|
-
hcc scan [--register]
|
|
240
|
-
hcc prompt --peer ID [--kind codex|claude|shell|other] [--role ROLE]
|
|
241
|
-
hcc join --peer ID [--kind codex|claude|shell|other] [--role ROLE]
|
|
242
|
-
hcc env --peer ID
|
|
243
|
-
hcc heartbeat [--peer ID] [--renew-locks --ttl 900]
|
|
244
|
-
hcc run --peer ID --kind codex|claude|shell --role ROLE -- COMMAND [ARGS...]
|
|
245
|
-
|
|
246
|
-
# Web 可控终端
|
|
247
|
-
hcc peer list
|
|
248
|
-
hcc peer start PEER [--kind K] [--role R] [--cwd DIR] [--restart-env] -- COMMAND [ARGS...]
|
|
249
|
-
hcc peer start PEER --kind codex --resume SESSION_ID [--restart-env]
|
|
250
|
-
hcc peer start PEER --kind codex --last
|
|
251
|
-
hcc peer start PEER --kind claude --resume SESSION_ID [--restart-env]
|
|
252
|
-
hcc peer start PEER --kind claude --continue
|
|
253
|
-
hcc peer attach PEER [--pane PANE] [--kind K] [--role R] [--cwd DIR]
|
|
254
|
-
hcc peer stop PEER
|
|
255
|
-
hcc inject PEER TEXT [--no-enter]
|
|
256
|
-
|
|
257
|
-
# 消息
|
|
258
|
-
hcc msg send [--from ID] [--to ID|all] --body TEXT [--task N] [--kind note|task|handoff]
|
|
259
|
-
hcc msg inbox [--peer ID] [--wait SEC] [--all] [--limit N]
|
|
260
|
-
hcc msg ack [--peer ID] --id N
|
|
261
|
-
hcc ask PEER MESSAGE [--from ID] [--task N] [--inject]
|
|
262
|
-
hcc broadcast MESSAGE [--from ID] [--task N] [--inject]
|
|
263
|
-
|
|
264
|
-
# 任务
|
|
265
|
-
hcc task create --title TEXT [--body TEXT] [--from ID] [--to ID] [--priority N]
|
|
266
|
-
hcc task list [--status S] [--peer ID] [--all]
|
|
267
|
-
hcc task claim [--peer ID] --id N
|
|
268
|
-
hcc task next [--peer ID]
|
|
269
|
-
hcc task update [--peer ID] --id N --status running|review|blocked|done|abandoned [--summary TEXT] [--body TEXT] [--to ID]
|
|
270
|
-
hcc task done [--peer ID] --id N --summary TEXT
|
|
271
|
-
|
|
272
|
-
# 锁和交接
|
|
273
|
-
hcc lock acquire [--peer ID] --resource PATH [--task N] [--ttl SEC] [--reason TEXT]
|
|
274
|
-
hcc lock renew [--peer ID] --resource PATH [--ttl SEC]
|
|
275
|
-
hcc lock release [--peer ID] --resource PATH [--force]
|
|
276
|
-
hcc lock list [--all]
|
|
277
|
-
hcc handoff create [--from ID] --summary TEXT [--task N] [--to ID] [--changed-files JSON_OR_CSV] [--tests TEXT] [--risks TEXT]
|
|
278
|
-
hcc handoff list [--task N] [--limit N]
|
|
279
|
-
hcc event tail [--limit N]
|
|
280
|
-
hcc gc [--older-than DAYS] [--yes]
|
|
281
|
-
```
|
|
282
|
-
|
|
283
|
-
## 稳定 peer 身份
|
|
284
|
-
|
|
285
|
-
hello-cc 会尽量让恢复会话复用稳定 peer ID:
|
|
286
|
-
|
|
287
|
-
| 启动方式 | peer ID |
|
|
288
|
-
|---|---|
|
|
289
|
-
| `claude --resume abc12345...` | `claude-abc12345` |
|
|
290
|
-
| `claude --session-id abc12345...` | `claude-abc12345` |
|
|
291
|
-
| `claude --continue` | 先使用终端派生 ID,随后 hooks 记录真实 session 元数据 |
|
|
292
|
-
| `claude --fork-session --resume abc12345...` | 先使用终端派生 ID,随后 hooks 记录新 session 元数据 |
|
|
293
|
-
| `codex resume abc12345...` | `codex-abc12345` |
|
|
294
|
-
| `codex resume --last` | 先使用终端派生 ID,随后 discovery 记录真实 session 元数据 |
|
|
295
|
-
| 普通 shell | `shell-<terminal>` |
|
|
296
|
-
| 手动 `HCC_PEER=xxx` | 使用 `xxx` |
|
|
297
|
-
|
|
298
|
-
已知 resume ID 的会话重启后会保持同一个 peer ID。启动前不知道真实 provider session ID 的模式,会先用终端派生 ID,启动后再记录真实 provider session 元数据。
|
|
75
|
+
这些会话会成为 tmux-backed peer,可以继续在本地终端里使用,也可以被 Web 控制台观察和操作。
|
|
299
76
|
|
|
300
|
-
##
|
|
301
|
-
|
|
302
|
-
创建任务:
|
|
303
|
-
|
|
304
|
-
```bash
|
|
305
|
-
hcc task create --title "重构路由诊断" \
|
|
306
|
-
--body "保持变更范围小。" --priority 20
|
|
307
|
-
```
|
|
308
|
-
|
|
309
|
-
认领并加锁:
|
|
77
|
+
## 基本流程
|
|
310
78
|
|
|
311
79
|
```bash
|
|
80
|
+
hcc task create --title "Review router changes" --priority 20
|
|
312
81
|
hcc task next
|
|
313
|
-
hcc lock acquire --resource
|
|
82
|
+
hcc lock acquire --resource src/router --ttl 900 --reason "edit router"
|
|
83
|
+
hcc status
|
|
84
|
+
hcc handoff create --summary "Router change ready for review" --tests "npm test"
|
|
85
|
+
hcc task done --id 1 --summary "Done"
|
|
314
86
|
```
|
|
315
87
|
|
|
316
|
-
|
|
88
|
+
在已接入的 Claude/Codex 会话中可以直接问:
|
|
317
89
|
|
|
318
|
-
```
|
|
319
|
-
|
|
320
|
-
hcc ask claude-a "请审查当前实现。" --inject
|
|
321
|
-
hcc broadcast "暂停编辑 vllm/router,等待任务完成。" --inject
|
|
322
|
-
```
|
|
323
|
-
|
|
324
|
-
完成并交接:
|
|
325
|
-
|
|
326
|
-
```bash
|
|
327
|
-
hcc handoff create --to claude-a \
|
|
328
|
-
--summary "已实现路由诊断变更。" \
|
|
329
|
-
--tests "pytest tests/router" \
|
|
330
|
-
--risks "未跑完整基准"
|
|
331
|
-
|
|
332
|
-
hcc task done --id 1 --summary "已完成并交接。"
|
|
333
|
-
hcc lock release --resource vllm/router
|
|
90
|
+
```text
|
|
91
|
+
其他 hello-cc 会话现在在做什么?
|
|
334
92
|
```
|
|
335
93
|
|
|
336
|
-
|
|
94
|
+
它应该基于实时 `hcc` 状态回答,而不是泛泛地说“会话隔离”。
|
|
337
95
|
|
|
338
|
-
|
|
339
|
-
Node.js >= 24
|
|
340
|
-
node:sqlite + SQLite WAL
|
|
341
|
-
Node HTTP server
|
|
342
|
-
ws WebSocket
|
|
343
|
-
xterm.js
|
|
344
|
-
tmux create/capture/send
|
|
345
|
-
Claude Code hooks
|
|
346
|
-
Codex hooks
|
|
347
|
-
shell shim wrappers
|
|
348
|
-
```
|
|
96
|
+
## 文档
|
|
349
97
|
|
|
350
|
-
|
|
98
|
+
- [用户指南](docs/guide.zh-CN.md):安装、Web 控制台、常用命令、协作流程和环境变量行为。
|
|
99
|
+
- [设计说明](docs/design.md):产品边界和协作模型。
|
|
100
|
+
- [实现说明](docs/implementation.md):架构和内部协议。
|
|
101
|
+
- [English User Guide](docs/guide.md)
|
|
351
102
|
|
|
352
103
|
## 测试
|
|
353
104
|
|
|
@@ -355,55 +106,8 @@ A2A 和 MCP 不是核心必需项。A2A 更适合远程 agent 互操作,MCP
|
|
|
355
106
|
npm test
|
|
356
107
|
```
|
|
357
108
|
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
## 架构
|
|
361
|
-
|
|
362
|
-
```text
|
|
363
|
-
hcc CLI
|
|
364
|
-
├── lib/discover.mjs Claude/Codex 会话发现
|
|
365
|
-
├── lib/setup.mjs hooks、shims、PATH 安装
|
|
366
|
-
├── bin/hcc.mjs CLI、SQLite、Web runtime、tmux 控制
|
|
367
|
-
└── .hello-cc/
|
|
368
|
-
├── mesh.db 协调状态
|
|
369
|
-
├── HCC.md 项目指导规则
|
|
370
|
-
├── runtime.json Web runtime 发现文件
|
|
371
|
-
└── web.log Web runtime 日志
|
|
372
|
-
```
|
|
373
|
-
|
|
374
|
-
## 环境变量
|
|
375
|
-
|
|
376
|
-
| 变量 | 用途 |
|
|
377
|
-
|---|---|
|
|
378
|
-
| `HCC_PEER` | 默认 peer 身份 |
|
|
379
|
-
| `HCC_ROOT` | 覆盖项目根目录 |
|
|
380
|
-
| `HCC_DB` | 覆盖数据库路径 |
|
|
381
|
-
| `HCC_WEB_TOKEN` | 可选 Web 访问令牌 |
|
|
382
|
-
| `HCC_RUNTIME_URL` | 直接指定 runtime URL |
|
|
383
|
-
| `HCC_NO_AUTO_INSTALL_TMUX=1` | 禁用 tmux 自动安装,主要用于测试 |
|
|
109
|
+
回归测试会创建临时项目、fake Claude/Codex、临时 tmux session 和临时 Web runtime,覆盖主要流程。
|
|
384
110
|
|
|
385
|
-
|
|
386
|
-
不使用后台 Web runtime 启动时的旧环境。这样你可以在新终端里先 `export ANTHROPIC_*`、
|
|
387
|
-
`CLAUDE_CODE_*` 或其他 provider 变量,再运行 `claude` / `codex`。
|
|
111
|
+
## License
|
|
388
112
|
|
|
389
|
-
|
|
390
|
-
`--restart-env` 调用 `hcc peer start`,因此 hello-cc 会为同一个 resume/session peer
|
|
391
|
-
记录启动环境指纹。当你从新终端再次启动它且环境已变化时,如果对应 tmux 会话没有本地
|
|
392
|
-
tmux client 或 Web client 正在连接,hello-cc 会自动重启这个 tmux 会话并应用新环境。
|
|
393
|
-
如果会话正在被操作,会提示先 detach/关闭 Web 连接,或运行 `hcc peer stop <peer>` 后再启动。
|
|
394
|
-
|
|
395
|
-
如果直接调用 `hcc peer start`,需要加 `--restart-env` 才会启用同样的环境变化自动重启行为。
|
|
396
|
-
|
|
397
|
-
shim 只接管交互式终端会话。元命令和维护命令会直通真实 provider CLI,例如
|
|
398
|
-
`claude --help`、`claude --version`、`claude --print`、`claude --bare`、`claude --safe-mode`、
|
|
399
|
-
`codex --help`、`codex --version`,以及 `codex exec`、`codex review`、`codex doctor`、
|
|
400
|
-
`codex mcp` 等非交互 Codex 子命令。交互式的 `claude`、`claude --resume ...`、`codex`、
|
|
401
|
-
`codex resume ...`、`codex fork ...` 会走 tmux-backed,会出现在 Web 控制台里。
|
|
402
|
-
|
|
403
|
-
---
|
|
404
|
-
|
|
405
|
-
<p align="center">
|
|
406
|
-
<a href="https://star-history.com/#Dullne/hello-cc&Date">
|
|
407
|
-
<img src="https://api.star-history.com/svg?repos=Dullne/hello-cc&type=Date" width="600" alt="Star History Chart">
|
|
408
|
-
</a>
|
|
409
|
-
</p>
|
|
113
|
+
[Apache-2.0](LICENSE)
|
package/bin/hcc.mjs
CHANGED
|
@@ -16,7 +16,7 @@ const _libDir = path.resolve(fileURLToPath(import.meta.url), '..', '..', 'lib');
|
|
|
16
16
|
async function loadDiscover() { return import(path.join(_libDir, 'discover.mjs')); }
|
|
17
17
|
async function loadSetup() { return import(path.join(_libDir, 'setup.mjs')); }
|
|
18
18
|
|
|
19
|
-
const VERSION = '0.1.
|
|
19
|
+
const VERSION = '0.1.1';
|
|
20
20
|
const PRODUCT_NAME = 'hello-cc';
|
|
21
21
|
const CLI_NAME = 'hcc';
|
|
22
22
|
const DEFAULT_LOCK_TTL = 900;
|
|
@@ -4107,7 +4107,7 @@ function webIndexHtml() {
|
|
|
4107
4107
|
// The server streams the tmux pane's raw output, so xterm renders
|
|
4108
4108
|
// incrementally (no reset/redraw → no flicker) and the program's own
|
|
4109
4109
|
// escape sequences carry the cursor.
|
|
4110
|
-
if (msg.type === 'snapshot') { term.write(msg.data || '', () => { term.scrollToBottom(); }); }
|
|
4110
|
+
if (msg.type === 'snapshot') { term.reset(); term.write(msg.data || '', () => { term.scrollToBottom(); }); }
|
|
4111
4111
|
if (msg.type === 'data') { term.write(msg.data || '', pinned ? () => { term.scrollToBottom(); } : undefined); }
|
|
4112
4112
|
if (msg.type === 'replace') { term.reset(); term.write(msg.data || '', pinned ? () => { term.scrollToBottom(); } : undefined); }
|
|
4113
4113
|
if (msg.type === 'exit') { refreshSessions().catch(console.error); }
|
|
@@ -4535,6 +4535,30 @@ async function cmdWeb(ctx, args, startMeta = {}) {
|
|
|
4535
4535
|
(payload.visible ? '[?25h' : '[?25l');
|
|
4536
4536
|
}
|
|
4537
4537
|
|
|
4538
|
+
function tmuxSnapshot(session) {
|
|
4539
|
+
const captured = tmuxCapturePane(session.pane);
|
|
4540
|
+
return captured + cursorEscape(tmuxCursorPayload(captured, tmuxCursorInfo(session.pane)));
|
|
4541
|
+
}
|
|
4542
|
+
|
|
4543
|
+
function refreshTmuxSnapshot(session) {
|
|
4544
|
+
if (session.type !== 'tmux' || !session.pane) return session.buffer || '';
|
|
4545
|
+
try {
|
|
4546
|
+
session.buffer = tmuxSnapshot(session);
|
|
4547
|
+
} catch {
|
|
4548
|
+
// Keep the previous buffer if the pane disappears during capture.
|
|
4549
|
+
}
|
|
4550
|
+
return session.buffer || '';
|
|
4551
|
+
}
|
|
4552
|
+
|
|
4553
|
+
function scheduleTmuxReplace(session) {
|
|
4554
|
+
if (session.type !== 'tmux' || !session.pane) return;
|
|
4555
|
+
if (session.replaceTimer) clearTimeout(session.replaceTimer);
|
|
4556
|
+
session.replaceTimer = setTimeout(() => {
|
|
4557
|
+
session.replaceTimer = null;
|
|
4558
|
+
broadcast(session, { type: 'replace', data: refreshTmuxSnapshot(session) });
|
|
4559
|
+
}, 80);
|
|
4560
|
+
}
|
|
4561
|
+
|
|
4538
4562
|
// Stream the tmux pane's RAW output (escape sequences and all) into the
|
|
4539
4563
|
// browser via `tmux pipe-pane`, so xterm.js renders incrementally — no
|
|
4540
4564
|
// screenshot-poll, no full-screen reset, no flicker — and the program's own
|
|
@@ -4547,8 +4571,7 @@ async function cmdWeb(ctx, args, startMeta = {}) {
|
|
|
4547
4571
|
// Capture the existing screen once for the initial paint; pipe-pane only
|
|
4548
4572
|
// forwards output produced after it is enabled.
|
|
4549
4573
|
try {
|
|
4550
|
-
|
|
4551
|
-
session.buffer = captured + cursorEscape(tmuxCursorPayload(captured, tmuxCursorInfo(session.pane)));
|
|
4574
|
+
session.buffer = tmuxSnapshot(session);
|
|
4552
4575
|
} catch {}
|
|
4553
4576
|
|
|
4554
4577
|
// Use a FIFO rather than an append-only regular file. The old file-backed
|
|
@@ -4612,6 +4635,7 @@ async function cmdWeb(ctx, args, startMeta = {}) {
|
|
|
4612
4635
|
|
|
4613
4636
|
function stopTmuxStream(session) {
|
|
4614
4637
|
if (session.streamPoller) { clearInterval(session.streamPoller); session.streamPoller = null; }
|
|
4638
|
+
if (session.replaceTimer) { clearTimeout(session.replaceTimer); session.replaceTimer = null; }
|
|
4615
4639
|
// Turn piping back off for this pane (no command toggles it off).
|
|
4616
4640
|
try { runTmux(['pipe-pane', '-t', session.pane]); } catch {}
|
|
4617
4641
|
if (session.streamFd !== null && session.streamFd !== undefined) {
|
|
@@ -5202,7 +5226,7 @@ async function cmdWeb(ctx, args, startMeta = {}) {
|
|
|
5202
5226
|
}
|
|
5203
5227
|
wss.handleUpgrade(req, socket, head, (ws) => {
|
|
5204
5228
|
session.clients.add(ws);
|
|
5205
|
-
ws.send(JSON.stringify({ type: 'snapshot', data: session
|
|
5229
|
+
ws.send(JSON.stringify({ type: 'snapshot', data: refreshTmuxSnapshot(session) }));
|
|
5206
5230
|
ws.on('message', (raw) => {
|
|
5207
5231
|
try {
|
|
5208
5232
|
const msg = JSON.parse(String(raw));
|
|
@@ -5213,6 +5237,7 @@ async function cmdWeb(ctx, args, startMeta = {}) {
|
|
|
5213
5237
|
const cols = Math.max(20, Number.parseInt(msg.cols || 100, 10));
|
|
5214
5238
|
const rows = Math.max(8, Number.parseInt(msg.rows || 30, 10));
|
|
5215
5239
|
resizeSession(session, cols, rows);
|
|
5240
|
+
scheduleTmuxReplace(session);
|
|
5216
5241
|
}
|
|
5217
5242
|
} catch {
|
|
5218
5243
|
// Ignore malformed terminal frames.
|