@logicseek/hello-cc 0.1.1 → 0.1.3

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.
@@ -1,8 +1,9 @@
1
1
  # hello-cc 用户指南
2
2
 
3
- 这份文档保存详细使用说明,README 只保留项目入口和快速开始。
3
+ 这份文档说明安装、启动和日常使用流程。紧凑命令清单见
4
+ [命令参考](commands.zh-CN.md)。
4
5
 
5
- ## 安装和更新
6
+ ## 安装、更新和卸载
6
7
 
7
8
  全局安装 hello-cc:
8
9
 
@@ -13,10 +14,29 @@ npm install -g @logicseek/hello-cc
13
14
  更新已有的全局安装:
14
15
 
15
16
  ```bash
16
- npm install -g @logicseek/hello-cc@latest
17
+ hcc update
17
18
  ```
18
19
 
19
- `hcc` 没有自更新命令。`hcc task update` 更新的是协作任务状态,不是安装包版本。
20
+ `hcc update` 会执行 `npm install -g @logicseek/hello-cc@latest`。使用
21
+ `hcc update --help` 可以查看更新选项。`hcc task update` 更新的是协作任务状态,不是安装包版本。
22
+
23
+ 移除本机 hooks 和 shims:
24
+
25
+ ```bash
26
+ hcc uninstall
27
+ ```
28
+
29
+ 同时移除当前项目的 `.hello-cc` 数据和指导块:
30
+
31
+ ```bash
32
+ hcc uninstall --purge --yes
33
+ ```
34
+
35
+ 移除全局 npm 包:
36
+
37
+ ```bash
38
+ npm uninstall -g @logicseek/hello-cc
39
+ ```
20
40
 
21
41
  ## 默认入口
22
42
 
@@ -47,8 +67,8 @@ project: /path/to/project
47
67
  database: /path/to/project/.hello-cc/mesh.db
48
68
  runtime: /path/to/project/.hello-cc/runtime.json
49
69
  log: /path/to/project/.hello-cc/web.log
50
- open: http://<machine-ip>:8787/?project=%2Fpath%2Fto%2Fproject
51
- local: http://127.0.0.1:8787/?project=%2Fpath%2Fto%2Fproject
70
+ open: http://<machine-ip>:8787/?token=<token>&project=%2Fpath%2Fto%2Fproject
71
+ local: http://127.0.0.1:8787/?token=<token>&project=%2Fpath%2Fto%2Fproject
52
72
  shims: installed claude, codex
53
73
  PATH updated in ~/.bashrc; open a new terminal or source it
54
74
  stop: hcc down
@@ -123,6 +143,8 @@ Claude/Codex/普通 shell
123
143
 
124
144
  - 任务是项目事实。所有 peer 默认都能看到未 `done` 且未 `abandoned` 的任务。
125
145
  - 消息是带收件人的邮箱消息。`hcc msg inbox` 默认读取当前 peer 的未读消息,`hcc msg ack` 记录每个 peer 的已读状态。
146
+ - 消息回复有 thread。回复某条消息时使用 `hcc msg reply --id N --body "..."`,
147
+ 查看完整线程时使用 `hcc msg thread --id N`。
126
148
  - `ask` 和 `broadcast` 会写入持久消息;加 `--inject` 时,还会把内容实时注入到 Web runtime 已接管的终端。
127
149
  - 锁使用 SQLite 事务和 TTL,是协作式冲突避免,不是文件系统强制沙箱。
128
150
 
@@ -137,7 +159,8 @@ Claude/Codex/普通 shell
137
159
  - 未完成任务
138
160
  - 当前 peer 的未读消息
139
161
  - 活跃锁
140
- - 建议执行的 `hcc status`、`hcc peers`、`hcc task list`、`hcc msg inbox`、`hcc lock list`
162
+ - 来自 `hcc state` 的机器可读下一步动作
163
+ - 建议执行的 `hcc status`、`hcc state`、`hcc peers`、`hcc task list`、`hcc msg inbox`、`hcc lock list`
141
164
 
142
165
  因此,当你在已接入的 Claude/Codex 里问“其他会话在做什么”,模型应该基于 hello-cc 注入的最新状态回答,而不是回答“我无法知道其他会话”。
143
166
 
@@ -158,31 +181,41 @@ claude -p 'Do you see hello-cc open tasks?' --debug hooks --debug-file /tmp/hcc-
158
181
 
159
182
  debug 日志里应该出现 `Hook UserPromptSubmit ... provided additionalContext`,并包含 `hello-cc coordination` 块。
160
183
 
184
+ `hcc state --peer <peer> [--resource PATH]` 会读取同一份协作状态,但不会执行
185
+ 确认消息、认领任务、获取锁、发送消息、创建 handoff 或标记完成这类协作动作。
186
+ 它返回现有项目数组、统一 `timeline`,以及 `automation` 对象。关键机器字段是
187
+ `automation.next_action.argv`:agent 可以显式执行这条 hcc 命令,让协作流程留下
188
+ 可审计记录,而不是只依赖 prompt 里的自然语言规则。`automation.current_task`
189
+ 会携带当前 peer 的活动任务,让恢复后的会话继续同一个任务,直到交接、完成或阻塞。
190
+
161
191
  ## Web 控制台
162
192
 
163
- 默认本机和内网访问:
193
+ 默认开启本机和内网访问,并启用 token 认证:
164
194
 
165
195
  ```bash
166
196
  hcc web
167
197
  ```
168
198
 
169
- 需要令牌时再显式开启:
199
+ 裸 `hcc web` 会保存自动生成的 token,并在重启后复用。需要替换保存值时可显式设置:
170
200
 
171
201
  ```bash
172
- HCC_WEB_TOKEN='choose-a-long-token' hcc web --host 0.0.0.0 --port 8787
202
+ HCC_WEB_TOKEN='choose-a-long-token' hcc web --port 8787
173
203
  ```
174
204
 
175
205
  同一内网机器打开:
176
206
 
177
207
  ```text
178
- http://<machine-ip>:8787/
208
+ http://<machine-ip>:8787/?token=<token>
179
209
  ```
180
210
 
211
+ 使用 `hcc web --local` 可只监听 `127.0.0.1`。只有在可信本地/测试环境才使用
212
+ `hcc web --no-token`。
213
+
181
214
  Web 控制台能做这些事:
182
215
 
183
216
  - 在一个端口里切换多个已注册项目 root
184
217
  - 直接在浏览器里注册新的项目路径
185
- - 查看项目状态:peers、tasks、messages、locks、handoffs、events
218
+ - 查看项目状态:下一步动作、timeline、peers、tasks、locks
186
219
  - 操作本地终端:启动 Claude/Codex/shell、发送键盘输入、查看输出、停止接管
187
220
 
188
221
  Web 操作的是 hello-cc 管理的本机 tmux 终端:
@@ -193,63 +226,12 @@ Web 操作的是 hello-cc 管理的本机 tmux 终端:
193
226
  - 通过 `hcc peer attach` 接入的已有 tmux pane
194
227
 
195
228
  已经打开的普通 raw 终端不能被无条件捕获或控制,除非它原本就在 tmux、screen 或 hello-cc shim 之下。它仍然可以通过 hooks 和 `hcc` 命令参与任务、消息和锁。
229
+ 对于这类 coordination-only peer,Web 会通过 Timeline 展示协作轨迹,而不是显示终端流。
196
230
 
197
- ## 常用命令
231
+ ## 命令参考
198
232
 
199
- ```text
200
- # 默认入口
201
- hcc web [--host HOST] [--port N] [--token TEXT] [--local] [--no-discover] [--no-guidance]
202
- hcc down
203
-
204
- # 本地协调,无 Web
205
- hcc up [--no-discover] [--no-guidance]
206
-
207
- # 对等节点和状态
208
- hcc peers
209
- hcc status [--peer ID]
210
- hcc scan [--register]
211
- hcc prompt --peer ID [--kind codex|claude|shell|other] [--role ROLE]
212
- hcc join --peer ID [--kind codex|claude|shell|other] [--role ROLE]
213
- hcc env --peer ID
214
- hcc heartbeat [--peer ID] [--renew-locks --ttl 900]
215
- hcc run --peer ID --kind codex|claude|shell --role ROLE -- COMMAND [ARGS...]
216
-
217
- # Web 可控终端
218
- hcc peer list
219
- hcc peer start PEER [--kind K] [--role R] [--cwd DIR] [--restart-env] -- COMMAND [ARGS...]
220
- hcc peer start PEER --kind codex --resume SESSION_ID [--restart-env]
221
- hcc peer start PEER --kind codex --last
222
- hcc peer start PEER --kind claude --resume SESSION_ID [--restart-env]
223
- hcc peer start PEER --kind claude --continue
224
- hcc peer attach PEER [--pane PANE] [--kind K] [--role R] [--cwd DIR]
225
- hcc peer stop PEER
226
- hcc inject PEER TEXT [--no-enter]
227
-
228
- # 消息
229
- hcc msg send [--from ID] [--to ID|all] --body TEXT [--task N] [--kind note|task|handoff]
230
- hcc msg inbox [--peer ID] [--wait SEC] [--all] [--limit N]
231
- hcc msg ack [--peer ID] --id N
232
- hcc ask PEER MESSAGE [--from ID] [--task N] [--inject]
233
- hcc broadcast MESSAGE [--from ID] [--task N] [--inject]
234
-
235
- # 任务
236
- hcc task create --title TEXT [--body TEXT] [--from ID] [--to ID] [--priority N]
237
- hcc task list [--status S] [--peer ID] [--all]
238
- hcc task claim [--peer ID] --id N
239
- hcc task next [--peer ID]
240
- hcc task update [--peer ID] --id N --status running|review|blocked|done|abandoned [--summary TEXT] [--body TEXT] [--to ID]
241
- hcc task done [--peer ID] --id N --summary TEXT
242
-
243
- # 锁和交接
244
- hcc lock acquire [--peer ID] --resource PATH [--task N] [--ttl SEC] [--reason TEXT]
245
- hcc lock renew [--peer ID] --resource PATH [--ttl SEC]
246
- hcc lock release [--peer ID] --resource PATH [--force]
247
- hcc lock list [--all]
248
- hcc handoff create [--from ID] --summary TEXT [--task N] [--to ID] [--changed-files JSON_OR_CSV] [--tests TEXT] [--risks TEXT]
249
- hcc handoff list [--task N] [--limit N]
250
- hcc event tail [--limit N]
251
- hcc gc [--older-than DAYS] [--yes]
252
- ```
233
+ 使用 `hcc --help` 查看命令列表,使用 `hcc <command> --help` 查看单个命令。
234
+ 维护中的命令摘要见 [命令参考](commands.zh-CN.md)。
253
235
 
254
236
  ## 稳定 peer 身份
255
237
 
@@ -284,10 +266,34 @@ hcc task next
284
266
  hcc lock acquire --resource vllm/router --ttl 900 --reason "实现路由诊断"
285
267
  ```
286
268
 
269
+ `task next` 会先返回当前 peer 已经认领、运行、审查或阻塞中的任务。只有明确
270
+ 要再接一个 pending 任务时,才使用 `hcc task next --force`。
271
+
272
+ 当工作确实可以并行时,用显式团队把父任务拆成子任务:
273
+
274
+ ```bash
275
+ hcc team plan --from-task 12 \
276
+ --item "docs:更新用户指南" \
277
+ --item "tests:增加回归测试" \
278
+ --workers codex:1,claude:1
279
+
280
+ hcc team start --from-task 12 \
281
+ --item "docs:更新用户指南" \
282
+ --item "tests:增加回归测试" \
283
+ --workers codex:1,claude:1
284
+
285
+ hcc team status --task 12
286
+ ```
287
+
288
+ `team plan` 是只读命令。`team start` 会创建子任务和分配消息,但不会隐藏地启动
289
+ 模型进程。每个 agent 仍然通过普通任务、锁、交接和完成命令处理自己的子任务。
290
+
287
291
  发消息或实时注入:
288
292
 
289
293
  ```bash
290
294
  hcc msg send --to claude-a --body "实现已准备好供审查。"
295
+ hcc msg reply --id 12 --body "已审查,未发现阻塞问题。"
296
+ hcc msg thread --id 12
291
297
  hcc ask claude-a "请审查当前实现。" --inject
292
298
  hcc broadcast "暂停编辑 vllm/router,等待任务完成。" --inject
293
299
  ```
@@ -311,7 +317,7 @@ hcc lock release --resource vllm/router
311
317
  | `HCC_PEER` | 默认 peer 身份 |
312
318
  | `HCC_ROOT` | 覆盖项目根目录 |
313
319
  | `HCC_DB` | 覆盖数据库路径 |
314
- | `HCC_WEB_TOKEN` | 可选 Web 访问令牌 |
320
+ | `HCC_WEB_TOKEN` | 替换并保存固定 Web 访问令牌 |
315
321
  | `HCC_RUNTIME_URL` | 直接指定 runtime URL |
316
322
  | `HCC_NO_AUTO_INSTALL_TMUX=1` | 禁用 tmux 自动安装,主要用于测试 |
317
323
 
@@ -119,7 +119,9 @@ Messages:
119
119
  ```text
120
120
  from peer
121
121
  to peer | all | tag:<tag> | task:<task-id>
122
- kind note | ask | handoff | system
122
+ kind note | ask | reply | handoff | system
123
+ reply_to optional source message id
124
+ thread_id root message id for threaded collaboration history
123
125
  delivery store-only | inject-terminal | both
124
126
  ```
125
127
 
@@ -128,11 +130,29 @@ Tasks:
128
130
  ```text
129
131
  pending -> claimed -> running -> review/block/done|abandoned
130
132
  owner is dynamic; there is no fixed dispatcher
133
+ parent_id/team_role record explicit team child tasks
131
134
  ```
132
135
 
133
136
  Default task reads are lifecycle-based: all peers see tasks until they are
134
137
  `done` or `abandoned`. Read/ack state exists only for messages.
135
138
 
139
+ `hcc team plan` is read-only. `hcc team start` creates child tasks under the
140
+ parent and assignment messages; it does not spawn hidden agents or bypass the
141
+ normal claim/lock/handoff protocol.
142
+
143
+ State:
144
+
145
+ ```text
146
+ hcc state / /api/state
147
+ timeline derived from messages, handoffs, tasks, locks, and selected events
148
+ automation.next_action.argv suggests the next explicit hcc command
149
+ automation.current_task preserves sticky task context across prompts
150
+ ```
151
+
152
+ State inspection does not perform coordination actions such as acking messages,
153
+ claiming tasks, acquiring locks, sending messages, creating handoffs, or marking
154
+ tasks done. Opening a DB may still run schema/version maintenance.
155
+
136
156
  Locks:
137
157
 
138
158
  ```text
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@logicseek/hello-cc",
3
- "version": "0.1.1",
3
+ "version": "0.1.3",
4
4
  "description": "Local control plane for coordinating Claude Code, Codex, and other coding CLI sessions.",
5
5
  "type": "module",
6
6
  "bin": {
@@ -11,8 +11,10 @@
11
11
  "bin/",
12
12
  "lib/",
13
13
  "scripts/",
14
+ "assets/",
14
15
  "README.md",
15
16
  "README.zh-CN.md",
17
+ "CHANGELOG.md",
16
18
  "LICENSE",
17
19
  "docs/"
18
20
  ],
@@ -20,6 +22,10 @@
20
22
  "node": ">=24.0.0"
21
23
  },
22
24
  "scripts": {
25
+ "release:notes": "node ./scripts/release-notes.mjs",
26
+ "release:check": "node ./scripts/release-notes.mjs --check",
27
+ "release:github": "node ./scripts/github-release.mjs",
28
+ "release:github:dry-run": "node ./scripts/github-release.mjs --dry-run",
23
29
  "smoke": "node ./bin/hcc.mjs --help",
24
30
  "test": "node ./scripts/regression.mjs",
25
31
  "test:regression": "node ./scripts/regression.mjs"
@@ -0,0 +1,190 @@
1
+ #!/usr/bin/env node
2
+
3
+ import fs from 'node:fs';
4
+ import path from 'node:path';
5
+ import process from 'node:process';
6
+
7
+ const repoRoot = path.resolve(import.meta.dirname, '..');
8
+ const githubApi = 'https://api.github.com';
9
+
10
+ function usage() {
11
+ console.log(`github-release
12
+
13
+ Usage:
14
+ node scripts/github-release.mjs [--version VERSION] [--repo OWNER/REPO] [--dry-run]
15
+
16
+ Creates or updates the GitHub Release for vVERSION using the matching
17
+ CHANGELOG.md section as the release body.
18
+
19
+ Authentication:
20
+ Set GH_TOKEN or GITHUB_TOKEN to a token with repo release permissions.
21
+ `);
22
+ }
23
+
24
+ function readJson(file) {
25
+ return JSON.parse(fs.readFileSync(file, 'utf8'));
26
+ }
27
+
28
+ function parseArgs(argv) {
29
+ const opts = { dryRun: false, help: false, repo: null, version: null };
30
+ for (let i = 0; i < argv.length; i += 1) {
31
+ const arg = argv[i];
32
+ if (arg === '--help' || arg === '-h') {
33
+ opts.help = true;
34
+ } else if (arg === '--dry-run') {
35
+ opts.dryRun = true;
36
+ } else if (arg === '--repo') {
37
+ opts.repo = argv[++i];
38
+ if (opts.repo === undefined) throw new Error('Missing value for --repo');
39
+ } else if (arg.startsWith('--repo=')) {
40
+ opts.repo = arg.slice('--repo='.length);
41
+ } else if (arg === '--version') {
42
+ opts.version = argv[++i];
43
+ if (opts.version === undefined) throw new Error('Missing value for --version');
44
+ } else if (arg.startsWith('--version=')) {
45
+ opts.version = arg.slice('--version='.length);
46
+ } else {
47
+ throw new Error(`Unknown argument: ${arg}`);
48
+ }
49
+ }
50
+ return opts;
51
+ }
52
+
53
+ function normalizeVersion(version) {
54
+ return String(version || '').replace(/^v/, '');
55
+ }
56
+
57
+ function releaseSection(markdown, version) {
58
+ const lines = markdown.split(/\r?\n/);
59
+ const heading = `## ${version}`;
60
+ const start = lines.findIndex((line) => line.trim() === heading);
61
+ if (start < 0) return null;
62
+
63
+ let end = lines.length;
64
+ for (let i = start + 1; i < lines.length; i += 1) {
65
+ if (/^##\s+/.test(lines[i])) {
66
+ end = i;
67
+ break;
68
+ }
69
+ }
70
+ return lines.slice(start, end).join('\n').trimEnd();
71
+ }
72
+
73
+ function validateSection(section, version) {
74
+ const problems = [];
75
+ if (!section) {
76
+ problems.push(`CHANGELOG.md is missing section ## ${version}`);
77
+ return problems;
78
+ }
79
+ if (!section.includes('### Summary')) problems.push(`## ${version} is missing ### Summary`);
80
+ if (!section.includes('### Highlights')) problems.push(`## ${version} is missing ### Highlights`);
81
+ if (section.includes('TODO') || section.includes('TBD')) problems.push(`## ${version} still contains TODO/TBD text`);
82
+ const bulletCount = (section.match(/^- /gm) || []).length;
83
+ if (bulletCount < 2) problems.push(`## ${version} should include at least two release-note bullets`);
84
+ return problems;
85
+ }
86
+
87
+ function repoFromPackage(pkg) {
88
+ const repo = typeof pkg.repository === 'string' ? pkg.repository : pkg.repository?.url;
89
+ if (!repo) return null;
90
+ const match = repo.match(/github\.com[:/](.+?\/.+?)(?:\.git)?$/);
91
+ return match ? match[1] : null;
92
+ }
93
+
94
+ function envToken() {
95
+ return process.env.GH_TOKEN || process.env.GITHUB_TOKEN || '';
96
+ }
97
+
98
+ async function githubRequest(method, route, token, body = null) {
99
+ const response = await fetch(`${githubApi}${route}`, {
100
+ method,
101
+ headers: {
102
+ Accept: 'application/vnd.github+json',
103
+ Authorization: `Bearer ${token}`,
104
+ 'Content-Type': 'application/json',
105
+ 'User-Agent': 'hello-cc-release-script'
106
+ },
107
+ body: body ? JSON.stringify(body) : undefined
108
+ });
109
+ const text = await response.text();
110
+ const data = text ? JSON.parse(text) : null;
111
+ if (!response.ok) {
112
+ const detail = data?.message ? `: ${data.message}` : '';
113
+ const error = new Error(`GitHub API ${method} ${route} failed with ${response.status}${detail}`);
114
+ error.status = response.status;
115
+ throw error;
116
+ }
117
+ return data;
118
+ }
119
+
120
+ async function main() {
121
+ const opts = parseArgs(process.argv.slice(2));
122
+ if (opts.help) {
123
+ usage();
124
+ return;
125
+ }
126
+
127
+ const pkg = readJson(path.join(repoRoot, 'package.json'));
128
+ const version = normalizeVersion(opts.version || pkg.version);
129
+ const tag = `v${version}`;
130
+ const repo = opts.repo || process.env.GITHUB_REPOSITORY || repoFromPackage(pkg);
131
+ if (!repo) throw new Error('Missing repository; pass --repo OWNER/REPO');
132
+
133
+ const changelog = fs.readFileSync(path.join(repoRoot, 'CHANGELOG.md'), 'utf8');
134
+ const body = releaseSection(changelog, version);
135
+ const problems = validateSection(body, version);
136
+ if (problems.length) {
137
+ console.error(problems.join('\n'));
138
+ process.exitCode = 1;
139
+ return;
140
+ }
141
+
142
+ const payload = {
143
+ tag_name: tag,
144
+ name: tag,
145
+ body,
146
+ draft: false,
147
+ prerelease: false
148
+ };
149
+
150
+ if (opts.dryRun) {
151
+ console.log(JSON.stringify({
152
+ ok: true,
153
+ dry_run: true,
154
+ repo,
155
+ tag,
156
+ name: payload.name,
157
+ body_length: body.length,
158
+ body_preview: body.split(/\r?\n/).slice(0, 8).join('\n')
159
+ }, null, 2));
160
+ return;
161
+ }
162
+
163
+ const token = envToken();
164
+ if (!token) throw new Error('Missing GH_TOKEN or GITHUB_TOKEN');
165
+
166
+ let existing = null;
167
+ try {
168
+ existing = await githubRequest('GET', `/repos/${repo}/releases/tags/${tag}`, token);
169
+ } catch (err) {
170
+ if (err.status !== 404) throw err;
171
+ }
172
+
173
+ const result = existing
174
+ ? await githubRequest('PATCH', `/repos/${repo}/releases/${existing.id}`, token, payload)
175
+ : await githubRequest('POST', `/repos/${repo}/releases`, token, payload);
176
+
177
+ console.log(JSON.stringify({
178
+ ok: true,
179
+ action: existing ? 'updated' : 'created',
180
+ repo,
181
+ tag,
182
+ url: result.html_url,
183
+ body_length: body.length
184
+ }, null, 2));
185
+ }
186
+
187
+ main().catch((err) => {
188
+ console.error(err.message || String(err));
189
+ process.exitCode = 1;
190
+ });