@logicseek/hello-cc 0.1.0 → 0.1.2

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/bin/hcc.mjs CHANGED
@@ -16,9 +16,10 @@ 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.0';
19
+ const VERSION = '0.1.2';
20
20
  const PRODUCT_NAME = 'hello-cc';
21
21
  const CLI_NAME = 'hcc';
22
+ const NPM_PACKAGE_NAME = '@logicseek/hello-cc';
22
23
  const DEFAULT_LOCK_TTL = 900;
23
24
  const ACTIVE_PEER_TTL = 600;
24
25
  // Directory under .hello-cc/ for optional external PTY buffer files.
@@ -128,6 +129,14 @@ function parseOpts(args, spec = {}) {
128
129
  return opts;
129
130
  }
130
131
 
132
+ function wantsHelp(args) {
133
+ const stop = args.indexOf('--');
134
+ const scan = stop >= 0 ? args.slice(0, stop) : args;
135
+ const index = scan.findIndex((arg) => arg === '--help' || arg === '-h');
136
+ if (index < 0) return false;
137
+ return index <= 1 || !String(scan[index - 1] || '').startsWith('--');
138
+ }
139
+
131
140
  function validateOpts(command, opts, allowed = []) {
132
141
  const allowedSet = new Set(['_', ...allowed]);
133
142
  for (const key of Object.keys(opts)) {
@@ -2289,7 +2298,7 @@ async function cmdPeers(ctx, args) {
2289
2298
 
2290
2299
  async function cmdTask(ctx, args) {
2291
2300
  const sub = args[0];
2292
- if (!sub || sub === '--help' || sub === '-h') return helpTask();
2301
+ if (!sub || wantsHelp(args)) return helpTask();
2293
2302
  if (sub === 'create') return taskCreate(ctx, args.slice(1));
2294
2303
  if (sub === 'list') return taskList(ctx, args.slice(1));
2295
2304
  if (sub === 'claim') return taskClaim(ctx, args.slice(1));
@@ -2467,7 +2476,7 @@ async function taskDone(ctx, args) {
2467
2476
 
2468
2477
  async function cmdMsg(ctx, args) {
2469
2478
  const sub = args[0];
2470
- if (!sub || sub === '--help' || sub === '-h') return helpMsg();
2479
+ if (!sub || wantsHelp(args)) return helpMsg();
2471
2480
  if (sub === 'send') return msgSend(ctx, args.slice(1));
2472
2481
  if (sub === 'inbox') return msgInbox(ctx, args.slice(1));
2473
2482
  if (sub === 'ack') return msgAck(ctx, args.slice(1));
@@ -2598,7 +2607,7 @@ async function cmdInject(ctx, args) {
2598
2607
 
2599
2608
  async function cmdPeer(ctx, args) {
2600
2609
  const sub = args[0];
2601
- if (!sub || sub === '--help' || sub === '-h') return helpPeer();
2610
+ if (!sub || wantsHelp(args)) return helpPeer();
2602
2611
  if (sub === 'list') return peerList(ctx, args.slice(1));
2603
2612
  if (sub === 'start') return peerStart(ctx, args.slice(1));
2604
2613
  if (sub === 'attach') return peerAttach(ctx, args.slice(1));
@@ -2748,7 +2757,7 @@ async function peerStop(ctx, args) {
2748
2757
 
2749
2758
  async function cmdLock(ctx, args) {
2750
2759
  const sub = args[0];
2751
- if (!sub || sub === '--help' || sub === '-h') return helpLock();
2760
+ if (!sub || wantsHelp(args)) return helpLock();
2752
2761
  if (sub === 'acquire') return lockAcquire(ctx, args.slice(1));
2753
2762
  if (sub === 'release') return lockRelease(ctx, args.slice(1));
2754
2763
  if (sub === 'renew') return lockRenew(ctx, args.slice(1));
@@ -2847,7 +2856,7 @@ async function lockList(ctx, args) {
2847
2856
 
2848
2857
  async function cmdHandoff(ctx, args) {
2849
2858
  const sub = args[0];
2850
- if (!sub || sub === '--help' || sub === '-h') return helpHandoff();
2859
+ if (!sub || wantsHelp(args)) return helpHandoff();
2851
2860
  if (sub === 'create') return handoffCreate(ctx, args.slice(1));
2852
2861
  if (sub === 'list') return handoffList(ctx, args.slice(1));
2853
2862
  throw new CliError('BAD_ARGS', `Unknown handoff command: ${sub}`);
@@ -2900,7 +2909,7 @@ async function handoffList(ctx, args) {
2900
2909
 
2901
2910
  async function cmdEvent(ctx, args) {
2902
2911
  const sub = args[0];
2903
- if (!sub || sub === '--help' || sub === '-h') return helpEvent();
2912
+ if (!sub || wantsHelp(args)) return helpEvent();
2904
2913
  if (sub === 'tail') return eventTail(ctx, args.slice(1));
2905
2914
  throw new CliError('BAD_ARGS', `Unknown event command: ${sub}`);
2906
2915
  }
@@ -4107,7 +4116,7 @@ function webIndexHtml() {
4107
4116
  // The server streams the tmux pane's raw output, so xterm renders
4108
4117
  // incrementally (no reset/redraw → no flicker) and the program's own
4109
4118
  // escape sequences carry the cursor.
4110
- if (msg.type === 'snapshot') { term.write(msg.data || '', () => { term.scrollToBottom(); }); }
4119
+ if (msg.type === 'snapshot') { term.reset(); term.write(msg.data || '', () => { term.scrollToBottom(); }); }
4111
4120
  if (msg.type === 'data') { term.write(msg.data || '', pinned ? () => { term.scrollToBottom(); } : undefined); }
4112
4121
  if (msg.type === 'replace') { term.reset(); term.write(msg.data || '', pinned ? () => { term.scrollToBottom(); } : undefined); }
4113
4122
  if (msg.type === 'exit') { refreshSessions().catch(console.error); }
@@ -4535,6 +4544,30 @@ async function cmdWeb(ctx, args, startMeta = {}) {
4535
4544
  (payload.visible ? '[?25h' : '[?25l');
4536
4545
  }
4537
4546
 
4547
+ function tmuxSnapshot(session) {
4548
+ const captured = tmuxCapturePane(session.pane);
4549
+ return captured + cursorEscape(tmuxCursorPayload(captured, tmuxCursorInfo(session.pane)));
4550
+ }
4551
+
4552
+ function refreshTmuxSnapshot(session) {
4553
+ if (session.type !== 'tmux' || !session.pane) return session.buffer || '';
4554
+ try {
4555
+ session.buffer = tmuxSnapshot(session);
4556
+ } catch {
4557
+ // Keep the previous buffer if the pane disappears during capture.
4558
+ }
4559
+ return session.buffer || '';
4560
+ }
4561
+
4562
+ function scheduleTmuxReplace(session) {
4563
+ if (session.type !== 'tmux' || !session.pane) return;
4564
+ if (session.replaceTimer) clearTimeout(session.replaceTimer);
4565
+ session.replaceTimer = setTimeout(() => {
4566
+ session.replaceTimer = null;
4567
+ broadcast(session, { type: 'replace', data: refreshTmuxSnapshot(session) });
4568
+ }, 80);
4569
+ }
4570
+
4538
4571
  // Stream the tmux pane's RAW output (escape sequences and all) into the
4539
4572
  // browser via `tmux pipe-pane`, so xterm.js renders incrementally — no
4540
4573
  // screenshot-poll, no full-screen reset, no flicker — and the program's own
@@ -4547,8 +4580,7 @@ async function cmdWeb(ctx, args, startMeta = {}) {
4547
4580
  // Capture the existing screen once for the initial paint; pipe-pane only
4548
4581
  // forwards output produced after it is enabled.
4549
4582
  try {
4550
- const captured = tmuxCapturePane(session.pane);
4551
- session.buffer = captured + cursorEscape(tmuxCursorPayload(captured, tmuxCursorInfo(session.pane)));
4583
+ session.buffer = tmuxSnapshot(session);
4552
4584
  } catch {}
4553
4585
 
4554
4586
  // Use a FIFO rather than an append-only regular file. The old file-backed
@@ -4612,6 +4644,7 @@ async function cmdWeb(ctx, args, startMeta = {}) {
4612
4644
 
4613
4645
  function stopTmuxStream(session) {
4614
4646
  if (session.streamPoller) { clearInterval(session.streamPoller); session.streamPoller = null; }
4647
+ if (session.replaceTimer) { clearTimeout(session.replaceTimer); session.replaceTimer = null; }
4615
4648
  // Turn piping back off for this pane (no command toggles it off).
4616
4649
  try { runTmux(['pipe-pane', '-t', session.pane]); } catch {}
4617
4650
  if (session.streamFd !== null && session.streamFd !== undefined) {
@@ -5202,7 +5235,7 @@ async function cmdWeb(ctx, args, startMeta = {}) {
5202
5235
  }
5203
5236
  wss.handleUpgrade(req, socket, head, (ws) => {
5204
5237
  session.clients.add(ws);
5205
- ws.send(JSON.stringify({ type: 'snapshot', data: session.buffer }));
5238
+ ws.send(JSON.stringify({ type: 'snapshot', data: refreshTmuxSnapshot(session) }));
5206
5239
  ws.on('message', (raw) => {
5207
5240
  try {
5208
5241
  const msg = JSON.parse(String(raw));
@@ -5213,6 +5246,7 @@ async function cmdWeb(ctx, args, startMeta = {}) {
5213
5246
  const cols = Math.max(20, Number.parseInt(msg.cols || 100, 10));
5214
5247
  const rows = Math.max(8, Number.parseInt(msg.rows || 30, 10));
5215
5248
  resizeSession(session, cols, rows);
5249
+ scheduleTmuxReplace(session);
5216
5250
  }
5217
5251
  } catch {
5218
5252
  // Ignore malformed terminal frames.
@@ -5514,6 +5548,8 @@ Commands:
5514
5548
  web Start coordination, shims, tmux, and browser console
5515
5549
  up Initialize local coordination only
5516
5550
  down Stop the running hello-cc web runtime
5551
+ update Update the global npm install of hello-cc
5552
+ uninstall Remove hooks, shims, and optional project data
5517
5553
  init Initialize .hello-cc/mesh.db and guidance
5518
5554
  register --peer ID Register or update a peer session identity
5519
5555
  join --peer ID Register this shell and print eval-able env
@@ -5755,6 +5791,24 @@ Stops the web runtime started by hcc web for this project.
5755
5791
  `);
5756
5792
  }
5757
5793
 
5794
+ function helpUpdate() {
5795
+ console.log(`${CLI_NAME} update
5796
+
5797
+ Usage:
5798
+ ${CLI_NAME} update [--tag TAG] [--registry URL] [--dry-run]
5799
+
5800
+ Examples:
5801
+ ${CLI_NAME} update
5802
+ ${CLI_NAME} update --tag latest
5803
+ ${CLI_NAME} update --dry-run
5804
+
5805
+ Updates the global npm install by running:
5806
+ npm install -g ${NPM_PACKAGE_NAME}@TAG
5807
+
5808
+ The default TAG is latest.
5809
+ `);
5810
+ }
5811
+
5758
5812
  function helpWeb() {
5759
5813
  console.log(`${CLI_NAME} web
5760
5814
 
@@ -6078,6 +6132,46 @@ async function cmdSetup(ctx, args) {
6078
6132
  log('\nDone. Run `hcc web` for the default coordinated terminal experience.');
6079
6133
  }
6080
6134
 
6135
+ // ─── hcc update ──────────────────────────────────────────────────────────────
6136
+
6137
+ async function cmdUpdate(ctx, args) {
6138
+ if (args[0] === '--help' || args[0] === '-h') return helpUpdate();
6139
+
6140
+ const opts = parseOpts(args, { booleans: ['dry-run'] });
6141
+ validateOpts('update', opts, ['tag', 'registry', 'dry-run']);
6142
+ const tag = opts.tag || 'latest';
6143
+ if (!/^[A-Za-z0-9._+-]+$/.test(tag)) {
6144
+ throw new CliError('BAD_ARGS', '--tag must be an npm dist-tag or version');
6145
+ }
6146
+
6147
+ const packageSpec = `${NPM_PACKAGE_NAME}@${tag}`;
6148
+ const npmArgs = ['install', '-g', packageSpec];
6149
+ if (opts.registry) npmArgs.push('--registry', opts.registry);
6150
+ const command = shellCommand(['npm', ...npmArgs]);
6151
+ const data = { package: NPM_PACKAGE_NAME, tag, registry: opts.registry || null, command, dry_run: Boolean(opts['dry-run']) };
6152
+
6153
+ if (opts['dry-run']) {
6154
+ printResult(ctx, data, () => `would run: ${command}`);
6155
+ return;
6156
+ }
6157
+
6158
+ const result = spawnSync('npm', npmArgs, {
6159
+ encoding: ctx.json ? 'utf8' : undefined,
6160
+ stdio: ctx.json ? ['ignore', 'pipe', 'pipe'] : 'inherit'
6161
+ });
6162
+ if (result.error) {
6163
+ throw new CliError('UPDATE_FAILED', `npm update command failed: ${result.error.message}`);
6164
+ }
6165
+ if (result.status !== 0) {
6166
+ const output = ctx.json
6167
+ ? [result.stdout, result.stderr].filter(Boolean).join('\n').trim()
6168
+ : '';
6169
+ throw new CliError('UPDATE_FAILED', output || `npm exited with status ${result.status}`);
6170
+ }
6171
+
6172
+ printResult(ctx, data, () => `updated ${packageSpec}`);
6173
+ }
6174
+
6081
6175
  // ─── hcc uninstall ───────────────────────────────────────────────────────────
6082
6176
 
6083
6177
  async function cmdUninstall(ctx, args) {
@@ -6301,6 +6395,7 @@ async function dispatch(ctx, rest) {
6301
6395
  if (command === '--version' || command === 'version') return console.log(VERSION);
6302
6396
  if (command === 'up') return cmdUp(ctx, args);
6303
6397
  if (command === 'down') return cmdDown(ctx, args);
6398
+ if (command === 'update') return cmdUpdate(ctx, args);
6304
6399
  if (command === 'uninstall') return cmdUninstall(ctx, args);
6305
6400
  if (command === 'init') return cmdInit(ctx, args);
6306
6401
  if (command === 'register') return cmdRegister(ctx, args);
package/docs/README.md ADDED
@@ -0,0 +1,22 @@
1
+ # hello-cc Documentation
2
+
3
+ Start with the [project README](../README.md) when you only need the project
4
+ summary and first command. Use these docs when you need more detail.
5
+
6
+ ## User Docs
7
+
8
+ - [User Guide](guide.md): install, start, Web console, coordination semantics,
9
+ workflow, stable peer identity, and environment behavior.
10
+ - [Command Reference](commands.md): compact list of public commands and the
11
+ intended use of each command group.
12
+ - [Changelog](../CHANGELOG.md): release notes for published versions.
13
+ - Release notes: run `npm run release:check` before publishing, and use
14
+ `npm run release:notes` to print the current changelog section for GitHub
15
+ Release notes.
16
+
17
+ ## Design And Implementation
18
+
19
+ - [Design Notes](design.md): product boundary, project boundary, capability
20
+ levels, coordination semantics, and provider-session binding.
21
+ - [Implementation Notes](implementation.md): architecture, protocol, command
22
+ surface, stack, shim behavior, and implementation plan.
@@ -0,0 +1,22 @@
1
+ # hello-cc 文档
2
+
3
+ 只需要项目简介和第一条命令时,先看[仓库 README](../README.zh-CN.md)。
4
+ 需要更多细节时,再看下面的文档。
5
+
6
+ ## 用户文档
7
+
8
+ - [用户指南](guide.zh-CN.md):安装、启动、Web 控制台、协作语义、工作流、稳定
9
+ peer 身份和环境变量行为。
10
+ - [命令参考](commands.zh-CN.md):公共命令的紧凑清单,以及每组命令的用途。
11
+ - [更新日志](../CHANGELOG.md):已发布版本的 release notes。
12
+ - 发行说明:发布前运行 `npm run release:check`,用
13
+ `npm run release:notes` 输出当前版本的 changelog 小节,作为 GitHub
14
+ Release 描述。
15
+
16
+ ## 设计和实现
17
+
18
+ - [设计说明](design.md):产品边界、项目边界、能力层级、协作语义和 provider
19
+ session 绑定。
20
+ - [实现说明](implementation.md):架构、协议、命令面、技术栈、shim 行为和实现计划。
21
+
22
+ 设计和实现文档目前只有英文版。
@@ -0,0 +1,104 @@
1
+ # hello-cc Command Reference
2
+
3
+ Use `hcc --help` for the current top-level command list. Most subcommands also
4
+ accept `--help`, for example `hcc update --help`, `hcc peer --help`, and
5
+ `hcc task --help`.
6
+
7
+ ## Install Maintenance
8
+
9
+ ```text
10
+ hcc update [--tag TAG] [--registry URL] [--dry-run]
11
+ hcc uninstall [--purge --yes]
12
+ ```
13
+
14
+ `hcc update` updates the global npm install. `hcc uninstall` removes local
15
+ hooks and shims; add `--purge --yes` only when you also want to remove the
16
+ current project's `.hello-cc` data and guidance blocks.
17
+
18
+ ## Start And Stop
19
+
20
+ ```text
21
+ hcc web [--host HOST] [--port N] [--token TEXT] [--local] [--no-discover] [--no-guidance]
22
+ hcc down
23
+ hcc up [--no-discover] [--no-guidance]
24
+ ```
25
+
26
+ `hcc web` is the default entry. It initializes coordination, installs hooks and
27
+ shims, starts or reuses the Web console, and returns the terminal to you. Use
28
+ `hcc up` only when you want coordination without the Web console or shims.
29
+
30
+ ## Peers And Status
31
+
32
+ ```text
33
+ hcc peers
34
+ hcc status [--peer ID]
35
+ hcc scan [--register]
36
+ hcc prompt --peer ID [--kind codex|claude|shell|other] [--role ROLE]
37
+ hcc join --peer ID [--kind codex|claude|shell|other] [--role ROLE]
38
+ hcc env --peer ID
39
+ hcc heartbeat [--peer ID] [--renew-locks --ttl 900]
40
+ hcc run --peer ID --kind codex|claude|shell --role ROLE -- COMMAND [ARGS...]
41
+ ```
42
+
43
+ Use these commands to inspect live project state, register terminals, and run a
44
+ CLI with `HCC_PEER`, `HCC_ROOT`, and `HCC_DB` set.
45
+
46
+ ## Browser-Controllable Terminals
47
+
48
+ ```text
49
+ hcc peer list
50
+ hcc peer start PEER [--kind K] [--role R] [--cwd DIR] [--restart-env] -- COMMAND [ARGS...]
51
+ hcc peer start PEER --kind codex --resume SESSION_ID [--restart-env]
52
+ hcc peer start PEER --kind codex --last
53
+ hcc peer start PEER --kind claude --resume SESSION_ID [--restart-env]
54
+ hcc peer start PEER --kind claude --continue
55
+ hcc peer attach PEER [--pane PANE] [--kind K] [--role R] [--cwd DIR]
56
+ hcc peer stop PEER
57
+ hcc inject PEER TEXT [--no-enter]
58
+ ```
59
+
60
+ These commands create or attach tmux-backed terminals that can be viewed and
61
+ controlled from the Web console.
62
+
63
+ ## Messages
64
+
65
+ ```text
66
+ hcc msg send [--from ID] [--to ID|all] --body TEXT [--task N] [--kind note|task|handoff]
67
+ hcc msg inbox [--peer ID] [--wait SEC] [--all] [--limit N]
68
+ hcc msg ack [--peer ID] --id N
69
+ hcc ask PEER MESSAGE [--from ID] [--task N] [--inject]
70
+ hcc broadcast MESSAGE [--from ID] [--task N] [--inject]
71
+ ```
72
+
73
+ Messages are addressed mailbox items. `ask` and `broadcast` also support live
74
+ terminal injection with `--inject`.
75
+
76
+ ## Tasks
77
+
78
+ ```text
79
+ hcc task create --title TEXT [--body TEXT] [--from ID] [--to ID] [--priority N]
80
+ hcc task list [--status S] [--peer ID] [--all]
81
+ hcc task claim [--peer ID] --id N
82
+ hcc task next [--peer ID]
83
+ hcc task update [--peer ID] --id N --status running|review|blocked|done|abandoned [--summary TEXT] [--body TEXT] [--to ID]
84
+ hcc task done [--peer ID] --id N --summary TEXT
85
+ ```
86
+
87
+ Tasks are shared project facts. They remain visible until marked `done` or
88
+ `abandoned`.
89
+
90
+ ## Locks, Handoffs, And Events
91
+
92
+ ```text
93
+ hcc lock acquire [--peer ID] --resource PATH [--task N] [--ttl SEC] [--reason TEXT]
94
+ hcc lock renew [--peer ID] --resource PATH [--ttl SEC]
95
+ hcc lock release [--peer ID] --resource PATH [--force]
96
+ hcc lock list [--all]
97
+ hcc handoff create [--from ID] --summary TEXT [--task N] [--to ID] [--changed-files JSON_OR_CSV] [--tests TEXT] [--risks TEXT]
98
+ hcc handoff list [--task N] [--limit N]
99
+ hcc event tail [--limit N]
100
+ hcc gc [--older-than DAYS] [--yes]
101
+ ```
102
+
103
+ Locks are advisory coordination locks with TTLs. Handoffs preserve the result,
104
+ tests, changed files, and remaining risks when work moves between peers.
@@ -0,0 +1,101 @@
1
+ # hello-cc 命令参考
2
+
3
+ 使用 `hcc --help` 查看当前顶层命令列表。大多数子命令也支持 `--help`,
4
+ 例如 `hcc update --help`、`hcc peer --help` 和 `hcc task --help`。
5
+
6
+ ## 安装维护
7
+
8
+ ```text
9
+ hcc update [--tag TAG] [--registry URL] [--dry-run]
10
+ hcc uninstall [--purge --yes]
11
+ ```
12
+
13
+ `hcc update` 更新全局 npm 安装。`hcc uninstall` 移除本机 hooks 和 shims;
14
+ 只有在确定也要删除当前项目的 `.hello-cc` 数据和指导块时,才加
15
+ `--purge --yes`。
16
+
17
+ ## 启动和停止
18
+
19
+ ```text
20
+ hcc web [--host HOST] [--port N] [--token TEXT] [--local] [--no-discover] [--no-guidance]
21
+ hcc down
22
+ hcc up [--no-discover] [--no-guidance]
23
+ ```
24
+
25
+ `hcc web` 是默认入口。它会初始化协作状态,安装 hooks 和 shims,启动或复用
26
+ Web 控制台,然后把终端还给你。只想使用本地协作、不需要 Web 或 shims 时,
27
+ 再使用 `hcc up`。
28
+
29
+ ## Peers 和状态
30
+
31
+ ```text
32
+ hcc peers
33
+ hcc status [--peer ID]
34
+ hcc scan [--register]
35
+ hcc prompt --peer ID [--kind codex|claude|shell|other] [--role ROLE]
36
+ hcc join --peer ID [--kind codex|claude|shell|other] [--role ROLE]
37
+ hcc env --peer ID
38
+ hcc heartbeat [--peer ID] [--renew-locks --ttl 900]
39
+ hcc run --peer ID --kind codex|claude|shell --role ROLE -- COMMAND [ARGS...]
40
+ ```
41
+
42
+ 这些命令用于查看项目状态、注册终端,以及用 `HCC_PEER`、`HCC_ROOT`、
43
+ `HCC_DB` 环境运行 CLI。
44
+
45
+ ## Web 可控终端
46
+
47
+ ```text
48
+ hcc peer list
49
+ hcc peer start PEER [--kind K] [--role R] [--cwd DIR] [--restart-env] -- COMMAND [ARGS...]
50
+ hcc peer start PEER --kind codex --resume SESSION_ID [--restart-env]
51
+ hcc peer start PEER --kind codex --last
52
+ hcc peer start PEER --kind claude --resume SESSION_ID [--restart-env]
53
+ hcc peer start PEER --kind claude --continue
54
+ hcc peer attach PEER [--pane PANE] [--kind K] [--role R] [--cwd DIR]
55
+ hcc peer stop PEER
56
+ hcc inject PEER TEXT [--no-enter]
57
+ ```
58
+
59
+ 这些命令创建或接入 tmux-backed 终端,让 Web 控制台可以观察和操作它们。
60
+
61
+ ## 消息
62
+
63
+ ```text
64
+ hcc msg send [--from ID] [--to ID|all] --body TEXT [--task N] [--kind note|task|handoff]
65
+ hcc msg inbox [--peer ID] [--wait SEC] [--all] [--limit N]
66
+ hcc msg ack [--peer ID] --id N
67
+ hcc ask PEER MESSAGE [--from ID] [--task N] [--inject]
68
+ hcc broadcast MESSAGE [--from ID] [--task N] [--inject]
69
+ ```
70
+
71
+ 消息是带收件人的邮箱记录。`ask` 和 `broadcast` 加 `--inject` 后,也会把内容
72
+ 实时注入到终端。
73
+
74
+ ## 任务
75
+
76
+ ```text
77
+ hcc task create --title TEXT [--body TEXT] [--from ID] [--to ID] [--priority N]
78
+ hcc task list [--status S] [--peer ID] [--all]
79
+ hcc task claim [--peer ID] --id N
80
+ hcc task next [--peer ID]
81
+ hcc task update [--peer ID] --id N --status running|review|blocked|done|abandoned [--summary TEXT] [--body TEXT] [--to ID]
82
+ hcc task done [--peer ID] --id N --summary TEXT
83
+ ```
84
+
85
+ 任务是项目共享事实。任务会一直可见,直到被标记为 `done` 或 `abandoned`。
86
+
87
+ ## 锁、交接和事件
88
+
89
+ ```text
90
+ hcc lock acquire [--peer ID] --resource PATH [--task N] [--ttl SEC] [--reason TEXT]
91
+ hcc lock renew [--peer ID] --resource PATH [--ttl SEC]
92
+ hcc lock release [--peer ID] --resource PATH [--force]
93
+ hcc lock list [--all]
94
+ hcc handoff create [--from ID] --summary TEXT [--task N] [--to ID] [--changed-files JSON_OR_CSV] [--tests TEXT] [--risks TEXT]
95
+ hcc handoff list [--task N] [--limit N]
96
+ hcc event tail [--limit N]
97
+ hcc gc [--older-than DAYS] [--yes]
98
+ ```
99
+
100
+ 锁是带 TTL 的协作式 advisory lock。交接记录用于保存结果、测试、变更文件和
101
+ 剩余风险,方便工作在多个 peer 之间继续。