@logicseek/hello-cc 0.1.9 → 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (108) hide show
  1. package/CHANGELOG.md +59 -0
  2. package/README.md +18 -3
  3. package/README.zh-CN.md +17 -3
  4. package/bin/hcc.mjs +3 -5656
  5. package/docs/README.md +8 -0
  6. package/docs/README.zh-CN.md +6 -0
  7. package/docs/architecture.md +29 -18
  8. package/docs/architecture.zh-CN.md +20 -10
  9. package/docs/commands.md +13 -4
  10. package/docs/commands.zh-CN.md +13 -4
  11. package/docs/current-state-review-2026-08.md +341 -0
  12. package/docs/defect-review-2026-07.md +382 -0
  13. package/docs/guide.md +39 -3
  14. package/docs/guide.zh-CN.md +34 -2
  15. package/docs/superpowers/plans/2026-08-03-runtime-safety-plan.md +294 -0
  16. package/docs/superpowers/plans/2026-08-03-v1-data-identity-plan.md +400 -0
  17. package/docs/superpowers/plans/2026-08-03-v1-release-verification-plan.md +284 -0
  18. package/docs/superpowers/plans/2026-08-03-web-api-hardening-plan.md +303 -0
  19. package/docs/superpowers/plans/2026-08-04-runtime-buffer-gc-two-phase.md +71 -0
  20. package/docs/superpowers/specs/2026-08-03-runtime-safety-design.md +117 -0
  21. package/docs/superpowers/specs/2026-08-03-v1-data-identity-design.md +120 -0
  22. package/docs/superpowers/specs/2026-08-03-v1-release-verification-design.md +117 -0
  23. package/docs/superpowers/specs/2026-08-03-web-api-hardening-design.md +104 -0
  24. package/lib/cli/app.mjs +642 -0
  25. package/lib/cli/commands/coordination.mjs +247 -0
  26. package/lib/cli/commands/doctor.mjs +81 -0
  27. package/lib/cli/commands/gc.mjs +852 -0
  28. package/lib/cli/commands/hook.mjs +205 -0
  29. package/lib/cli/commands/install.mjs +294 -0
  30. package/lib/cli/commands/lifecycle.mjs +101 -0
  31. package/lib/cli/commands/lock.mjs +197 -0
  32. package/lib/cli/commands/misc.mjs +61 -0
  33. package/lib/cli/commands/msg.mjs +152 -0
  34. package/lib/cli/commands/peer.mjs +233 -0
  35. package/lib/cli/commands/query.mjs +96 -0
  36. package/lib/cli/commands/run.mjs +463 -0
  37. package/lib/cli/commands/task.mjs +405 -0
  38. package/lib/cli/commands/team.mjs +131 -0
  39. package/lib/cli/commands/tmux.mjs +838 -0
  40. package/lib/cli/commands/up.mjs +135 -0
  41. package/lib/cli-args.mjs +16 -2
  42. package/lib/coordination-state.mjs +112 -51
  43. package/lib/core/coordination/automation.mjs +5 -8
  44. package/lib/core/coordination/clock-safety.mjs +323 -0
  45. package/lib/core/coordination/event-retention.mjs +17 -0
  46. package/lib/core/coordination/gc-plan.mjs +614 -0
  47. package/lib/core/coordination/lease-renewal.mjs +102 -0
  48. package/lib/core/coordination/lock-evidence.mjs +77 -0
  49. package/lib/core/coordination/optimistic-evidence.mjs +26 -0
  50. package/lib/core/coordination/tasks.mjs +156 -36
  51. package/lib/core/peers/evidence-runtime.mjs +312 -0
  52. package/lib/core/peers/evidence.mjs +103 -0
  53. package/lib/core/peers/hook-owner.mjs +32 -0
  54. package/lib/core/peers/liveness.mjs +44 -4
  55. package/lib/core/peers/peer-helpers.mjs +121 -0
  56. package/lib/core/peers/session.mjs +8 -2
  57. package/lib/core/peers/tmux-safety.mjs +290 -0
  58. package/lib/core/sessions/launch.mjs +13 -1
  59. package/lib/db/connection.mjs +104 -0
  60. package/lib/db/events.mjs +34 -0
  61. package/lib/db/migration-backup.mjs +225 -0
  62. package/lib/db/schema.mjs +102 -5
  63. package/lib/db/stores/peers.mjs +34 -0
  64. package/lib/integrations/peers/identity.mjs +62 -16
  65. package/lib/integrations/shims/script.mjs +111 -9
  66. package/lib/process/identity.mjs +192 -0
  67. package/lib/process/pty-lifecycle.mjs +149 -0
  68. package/lib/runtime/buffer-directory-lease.mjs +47 -0
  69. package/lib/runtime/buffer-evidence.mjs +336 -0
  70. package/lib/runtime/buffer-gc-protocol.mjs +167 -0
  71. package/lib/runtime/buffer-gc.mjs +270 -0
  72. package/lib/runtime/client.mjs +60 -7
  73. package/lib/runtime/fatal-shutdown.mjs +54 -0
  74. package/lib/runtime/project-path.mjs +139 -0
  75. package/lib/runtime/projects.mjs +320 -40
  76. package/lib/runtime/state.mjs +227 -27
  77. package/lib/shared/cleanup-error.mjs +54 -0
  78. package/lib/shared/clock-grace.mjs +106 -0
  79. package/lib/shared/errors.mjs +9 -0
  80. package/lib/shared/file-lock.mjs +370 -0
  81. package/lib/shared/json-file.mjs +6 -2
  82. package/lib/shared/redact.mjs +60 -0
  83. package/lib/shared/socket-lock-worker.mjs +201 -0
  84. package/lib/terminal/tmux.mjs +78 -4
  85. package/lib/tmux.mjs +4 -0
  86. package/lib/ui/help.mjs +53 -15
  87. package/lib/web/api-version.mjs +42 -0
  88. package/lib/web/auto-attach.mjs +185 -0
  89. package/lib/web/buffer-gc-runtime.mjs +175 -0
  90. package/lib/web/cookie-auth.mjs +119 -0
  91. package/lib/web/external-sessions.mjs +254 -0
  92. package/lib/web/http-routes.mjs +542 -0
  93. package/lib/web/http.mjs +135 -6
  94. package/lib/web/liveness-reaper.mjs +145 -0
  95. package/lib/web/peer-actions.mjs +132 -23
  96. package/lib/web/project-contexts.mjs +178 -0
  97. package/lib/web/pty-sessions.mjs +203 -0
  98. package/lib/web/runtime-main.mjs +714 -0
  99. package/lib/web/runtime.mjs +91 -36
  100. package/lib/web/session-serialize.mjs +0 -0
  101. package/lib/web/startup.mjs +333 -0
  102. package/lib/web/tls.mjs +365 -0
  103. package/lib/web/tmux-sessions.mjs +1361 -0
  104. package/lib/web/tmux-stream.mjs +166 -0
  105. package/lib/web/ui-template.mjs +152 -20
  106. package/package.json +5 -2
  107. package/scripts/check-free-identifiers.mjs +290 -0
  108. package/scripts/regression.mjs +5397 -263
@@ -0,0 +1,642 @@
1
+ // CLI application composition, extracted from bin/hcc.mjs.
2
+ // This module owns the dependency graph: constants, lazy loaders, shared
3
+ // helpers, every command factory, dispatch, and main().
4
+
5
+
6
+ import fs from 'node:fs';
7
+ import path from 'node:path';
8
+ import process from 'node:process';
9
+ import { randomBytes } from 'node:crypto';
10
+
11
+ import { setTimeout as sleep } from 'node:timers/promises';
12
+ import { fileURLToPath } from 'node:url';
13
+
14
+ import { CliError } from '../shared/errors.mjs';
15
+ import { publicCliFailure } from '../shared/errors.mjs';
16
+ import { redactSecrets } from '../shared/redact.mjs';
17
+ import { clockGraceSuppressed, readClockGraceUntil } from '../shared/clock-grace.mjs';
18
+ import { clockSafetyUnavailable, observeClockSafety, observeClockSafetyInTransaction } from '../core/coordination/clock-safety.mjs';
19
+ import { DB_SCHEMA_VERSION, readSchemaVersion, tx } from '../db/schema.mjs';
20
+
21
+ import { createEventHelpers } from '../db/events.mjs';
22
+ import { createConnectionHelpers } from '../db/connection.mjs';
23
+ import { createPeerHelpers } from '../core/peers/peer-helpers.mjs';
24
+
25
+ import { createMsgCommands } from '../cli/commands/msg.mjs';
26
+ import { createCoordinationCommands } from '../cli/commands/coordination.mjs';
27
+ import { createLockCommands } from '../cli/commands/lock.mjs';
28
+ import { createTaskCommands } from '../cli/commands/task.mjs';
29
+ import { createDoctorCommand } from '../cli/commands/doctor.mjs';
30
+ import { createQueryCommands } from '../cli/commands/query.mjs';
31
+ import { createLifecycleCommands } from '../cli/commands/lifecycle.mjs';
32
+ import { createTeamCommands } from '../cli/commands/team.mjs';
33
+ import { createMiscCommands } from '../cli/commands/misc.mjs';
34
+ import { createInstallCommands } from '../cli/commands/install.mjs';
35
+ import { createHookCommand } from '../cli/commands/hook.mjs';
36
+ import { createPeerCommands } from '../cli/commands/peer.mjs';
37
+ import { createRunCommands } from '../cli/commands/run.mjs';
38
+ import { createTmuxCommands } from '../cli/commands/tmux.mjs';
39
+ import { createGcCommands } from '../cli/commands/gc.mjs';
40
+ import { createUpCommand } from '../cli/commands/up.mjs';
41
+ import { createEvidenceRuntime } from '../core/peers/evidence-runtime.mjs';
42
+ import { createWebStartup } from '../web/startup.mjs';
43
+ import { createWebRuntime } from '../web/runtime-main.mjs';
44
+
45
+ import { intOpt, parseOpts, positiveSafeIntOpt, required, splitGlobalArgs, validateOpts, wantsHelp } from '../cli-args.mjs';
46
+ import { leaseDeadline, renewOwnedLocks } from '../core/coordination/lease-renewal.mjs';
47
+ import { commandPath, createContext as createCliContext, shellCommand as shellCommandWithQuote } from '../cli-runtime.mjs';
48
+ import { createCoordinationState } from '../coordination-state.mjs';
49
+ import { formatJson, printResult, shellExports, shellQuoteArg, table } from '../format.mjs';
50
+ import { readPackageMeta } from '../package-meta.mjs';
51
+ import { removeGuidanceBlocks as removeGuidanceBlocksForRoot, writeGuidance as writeGuidanceForRoot } from '../guidance.mjs';
52
+ import { globalRuntimePath, runtimePath } from '../runtime/paths.mjs';
53
+ import { readRuntime, reclaimRuntimePointerFiles } from '../runtime/state.mjs';
54
+
55
+ import { runtimeRequest } from '../runtime/client.mjs';
56
+
57
+ import { detectBranch, detectRoot } from '../project-context.mjs';
58
+ import { changedFiles, normalizeListText } from '../handoff.mjs';
59
+ import { annotateTasksWithLiveness, taskOwnerStateText } from '../core/peers/liveness.mjs';
60
+ import { normalizeStateResources, renderStateSummary, renderStatusSummary } from '../ui/state-render.mjs';
61
+ import { createHelpFunctions } from '../ui/help.mjs';
62
+ import { createMessageStore } from '../core/coordination/messages.mjs';
63
+ import { createTaskStore } from '../core/coordination/tasks.mjs';
64
+ import { classifyPeerActivity } from '../core/peers/evidence.mjs';
65
+ import { refreshHookOwnerIdentity } from '../core/peers/hook-owner.mjs';
66
+
67
+ import { parseTaskIds, positiveIntOpt, taskRowsText } from '../task-cli.mjs';
68
+ import { childSessionEnv } from '../core/sessions/launch.mjs';
69
+
70
+ import { sendHttp } from '../web/http.mjs';
71
+ import { createWebPeerActions } from '../web/peer-actions.mjs';
72
+
73
+ import * as webUiTemplate from '../web/ui-template.mjs';
74
+ import { buildPeerCommand, inferPeerKind } from '../integrations/providers.mjs';
75
+ import { providerSessionParts, providerSessionPeerId } from '../core/peers/session.mjs';
76
+ import { bindingHasRuntime } from '../core/peers/bindings.mjs';
77
+ import { reconcileRunningPeerBindings } from '../core/peers/reconcile.mjs';
78
+ import { createPeerBindingStore } from '../db/stores/peers.mjs';
79
+
80
+ import { lockBaseResource, lockLabel, lockScope, locksConflict, scopedLockResource } from '../core/coordination/locks.mjs';
81
+ import { captureLockAcquireSubject, observeLockOwnerEvidence, sameLockAcquireSubject } from '../core/coordination/lock-evidence.mjs';
82
+ import { runOptimisticEvidenceMutation } from '../core/coordination/optimistic-evidence.mjs';
83
+
84
+ import { assignTeamWorkers, expandTeamWorkers, inferTeamItems } from '../core/coordination/teams.mjs';
85
+ import { autoPeerBasis, autoPeerKind, autoPeerProviderSession, autoPeerResumeId, autoPeerSessionId, readAncestorCliInfo, resolveCurrentPeer, resumeIdFromArgs, shortHash } from '../integrations/peers/identity.mjs';
86
+ import { inspectProviderProcess } from '../integrations/peers/processes.mjs';
87
+ import { registerProjectActivity } from '../runtime/projects.mjs';
88
+
89
+ // Lazy-load lib modules (they may import node-pty which needs to be optional)
90
+ const _libDir = path.resolve(fileURLToPath(import.meta.url), '..', '..');
91
+ async function loadDiscover() { return import(path.join(_libDir, 'discover.mjs')); }
92
+ async function loadSetup() { return import(path.join(_libDir, 'setup.mjs')); }
93
+
94
+ const PACKAGE_META = readPackageMeta(path.resolve(fileURLToPath(import.meta.url), '..', '..', '..'));
95
+ const VERSION = PACKAGE_META.version;
96
+ const PRODUCT_NAME = 'hello-cc';
97
+ const CLI_NAME = 'hcc';
98
+ const NPM_PACKAGE_NAME = PACKAGE_META.name;
99
+ const DEFAULT_LOCK_TTL = 900;
100
+ const ACTIVE_PEER_TTL = 600;
101
+ const UNKNOWN_EVIDENCE_GRACE_SEC = 120;
102
+ // Detected peers older than this (seconds, last_seen) or already exited are
103
+ // hidden from the Web "Detected" list so it reflects recent activity instead of
104
+ // accumulating every peer/test fixture that ever registered.
105
+ const DETECTED_PEER_MAX_AGE = 3600;
106
+
107
+ const {
108
+ isProjectManagedTmuxSession, liveProcessIdentity, storedPeerIdentity,
109
+ processEvidenceFromRow, canonicalRoot, rootEvidence, tmuxTargetMissing,
110
+ inspectTmuxTarget, observePeerEvidence, peerEvidenceFromDb,
111
+ providerOwnerEvidenceFromDb, peerMutationSubject, mutatePeerWithEvidence,
112
+ mutateConfirmedDeadPeer, observeClockSafetyOrThrow,
113
+ observeClockSafetyInTransactionOrThrow, prepareLockClockObservation,
114
+ observeLockClockSafety, observeTaskTakeoverClockSafety
115
+ } = createEvidenceRuntime({ now });
116
+
117
+ const {
118
+ helpMain,
119
+ helpTask,
120
+ helpTeam,
121
+ helpState,
122
+ helpJoin,
123
+ helpEnv,
124
+ helpMsg,
125
+ helpAsk,
126
+ helpBroadcast,
127
+ helpInject,
128
+ helpPeer,
129
+ helpTmux,
130
+ helpGc,
131
+ helpLock,
132
+ helpHandoff,
133
+ helpEvent,
134
+ helpRun,
135
+ helpUp,
136
+ helpDown,
137
+ helpUpdate,
138
+ helpUninstall,
139
+ helpInstallHooks,
140
+ helpShim,
141
+ helpWeb
142
+ } = createHelpFunctions({
143
+ productName: PRODUCT_NAME,
144
+ version: VERSION,
145
+ cliName: CLI_NAME,
146
+ npmPackageName: NPM_PACKAGE_NAME
147
+ });
148
+
149
+ function splitProcessArgs(line) {
150
+ const args = [];
151
+ let current = '';
152
+ let quote = null;
153
+ let escaped = false;
154
+ for (const ch of String(line || '')) {
155
+ if (escaped) {
156
+ current += ch;
157
+ escaped = false;
158
+ continue;
159
+ }
160
+ if (ch === '\\') {
161
+ escaped = true;
162
+ continue;
163
+ }
164
+ if (quote) {
165
+ if (ch === quote) quote = null;
166
+ else current += ch;
167
+ continue;
168
+ }
169
+ if (ch === '"' || ch === "'") {
170
+ quote = ch;
171
+ continue;
172
+ }
173
+ if (/\s/.test(ch)) {
174
+ if (current) {
175
+ args.push(current);
176
+ current = '';
177
+ }
178
+ continue;
179
+ }
180
+ current += ch;
181
+ }
182
+ if (escaped) current += '\\';
183
+ if (current) args.push(current);
184
+ return args;
185
+ }
186
+
187
+ function sameResolvedPath(a, b) {
188
+ if (!a || !b) return false;
189
+ function key(value) {
190
+ try { return fs.realpathSync(value); }
191
+ catch { return path.resolve(value); }
192
+ }
193
+ return key(a) === key(b);
194
+ }
195
+
196
+ const { addEvent, auditPayload, requestActorPeer, requestSource } = createEventHelpers({ now });
197
+ const {
198
+ dedupePeerBindings,
199
+ findProviderSessionBinding,
200
+ upsertCanonicalPeerBinding
201
+ } = createPeerBindingStore({ now, addEvent });
202
+
203
+ const { connect, connectReadOnly, migrateRegisteredProjectDbs } = createConnectionHelpers({ now, dedupePeerBindings, redactedLogText });
204
+ const { upsertPeer, touchPeer, touchCurrentPeer } = createPeerHelpers({ now, addEvent, liveProcessIdentity, detectBranch, registerProjectActivity, upsertCanonicalPeerBinding, autoPeerKind, autoPeerSessionId, autoPeerResumeId, autoPeerDefaults, autoPeerBasis, providerSessionParts });
205
+
206
+ const {
207
+ ackMessage,
208
+ getMessage,
209
+ queryInbox,
210
+ queryMessageThread,
211
+ queryTimelineMessages,
212
+ sendMessage
213
+ } = createMessageStore({ now, addEvent });
214
+ const { cmdMsg } = createMsgCommands({
215
+ connect, now, tx, iso, touchCurrentPeer, resolveCurrentPeer, registerProjectActivity,
216
+ parseOpts, intOpt, required, wantsHelp, helpMsg, printResult, table, sleep, CliError,
217
+ ackMessage, getMessage, queryInbox, queryMessageThread, sendMessage
218
+ });
219
+ const { cmdHandoff, cmdEvent, cmdHeartbeat, cmdAsk, cmdBroadcast, cmdInject, injectPeer } = createCoordinationCommands({
220
+ connect, now, tx, iso, tx, addEvent, auditPayload, touchCurrentPeer, resolveCurrentPeer, registerProjectActivity,
221
+ parseOpts, intOpt, required, positiveSafeIntOpt, wantsHelp,
222
+ helpHandoff, helpEvent, helpAsk, helpBroadcast, helpInject,
223
+ printResult, table, CliError, DEFAULT_LOCK_TTL, leaseDeadline,
224
+ sendMessage, readRuntime, runtimeRequest,
225
+ observeLockClockSafety, peerEvidenceFromDb, renewOwnedLocks,
226
+ normalizeListText, changedFiles
227
+ });
228
+ const {
229
+ assertTaskOwnerForMutation,
230
+ claimNextTasksForPeer,
231
+ claimTaskRowsForPeer,
232
+ queryOpenTasks,
233
+ takeOverTaskForPeer,
234
+ taskById,
235
+ teamChildren,
236
+ teamSummary
237
+ } = createTaskStore({
238
+ activePeerTtl: ACTIVE_PEER_TTL,
239
+ addEvent,
240
+ now,
241
+ observeClockSafety: observeTaskTakeoverClockSafety,
242
+ sendMessage
243
+ });
244
+ const { cmdTask, notifyTaskOwnerConflict } = createTaskCommands({
245
+ connect, now, tx, tx, addEvent, auditPayload, touchCurrentPeer, resolveCurrentPeer,
246
+ parseOpts, intOpt, required, positiveIntOpt, parseTaskIds, wantsHelp, helpTask,
247
+ printResult, table, CliError, ACTIVE_PEER_TTL,
248
+ sendMessage, readRuntime, runtimeRequest, injectPeer,
249
+ queryOpenTasks, claimNextTasksForPeer, claimTaskRowsForPeer,
250
+ takeOverTaskForPeer, assertTaskOwnerForMutation,
251
+ annotateTasksWithLiveness, taskOwnerStateText, taskRowsText,
252
+ observePeerEvidence, clockGraceSuppressed, readClockGraceUntil
253
+ });
254
+ const { cmdDoctor } = createDoctorCommand({ connectReadOnly, readSchemaVersion, DB_SCHEMA_VERSION, CLI_NAME });
255
+ const { cmdLock } = createLockCommands({
256
+ connect, now, tx, iso, tx, addEvent, touchCurrentPeer, resolveCurrentPeer,
257
+ parseOpts, intOpt, required, positiveSafeIntOpt, wantsHelp, helpLock,
258
+ printResult, table, CliError, DEFAULT_LOCK_TTL, leaseDeadline,
259
+ scopedLockResource, lockLabel, lockScope, lockBaseResource, locksConflict,
260
+ clockGraceSuppressed, readClockGraceUntil, clockSafetyUnavailable,
261
+ captureLockAcquireSubject, sameLockAcquireSubject,
262
+ observeLockOwnerEvidence, observePeerEvidence,
263
+ prepareLockClockObservation, runOptimisticEvidenceMutation,
264
+ assertTaskOwnerForMutation, notifyTaskOwnerConflict
265
+ });
266
+ const {
267
+ ackMessages,
268
+ buildHookCoordinationContext,
269
+ statusSnapshot,
270
+ statusSummary
271
+ } = createCoordinationState({
272
+ activePeerTtl: ACTIVE_PEER_TTL,
273
+ cliName: CLI_NAME,
274
+ connect,
275
+ defaultLockTtl: DEFAULT_LOCK_TTL,
276
+ now,
277
+ queryInbox,
278
+ queryOpenTasks,
279
+ queryTimelineMessages,
280
+ observePeerEvidence
281
+ });
282
+
283
+ const { cmdPeers, cmdStatus, cmdState, cmdPrompt } = createQueryCommands({
284
+ connect, now, tx, printResult, table, CliError, ACTIVE_PEER_TTL,
285
+ parseOpts, required, wantsHelp, helpState,
286
+ resolveTargetPeer, resolveCurrentPeer,
287
+ statusSummary, statusSnapshot, normalizeStateResources,
288
+ renderStatusSummary, renderStateSummary,
289
+ observePeerEvidence, classifyPeerActivity,
290
+ clockGraceSuppressed, readClockGraceUntil, commandPath
291
+ });
292
+
293
+ const { cmdInit, cmdRegister, cmdEnv, cmdJoin } = createLifecycleCommands({
294
+ connect, now, tx, addEvent, printResult, CliError, parseOpts, intOpt,
295
+ registerProjectActivity, resolveCurrentPeer, resolveTargetPeer,
296
+ upsertPeer, upsertCanonicalPeerBinding, storedPeerIdentity, detectBranch,
297
+ writeGuidance, helpEnv, helpJoin, shellExports, path, process
298
+ });
299
+
300
+ const { cmdTeam } = createTeamCommands({
301
+ connect, now, tx, tx, addEvent, touchCurrentPeer, resolveCurrentPeer,
302
+ parseOpts, intOpt, wantsHelp, helpTeam, printResult, CliError,
303
+ CLI_NAME, sendMessage,
304
+ taskById, teamChildren, teamSummary,
305
+ inferTeamItems, assignTeamWorkers, expandTeamWorkers,
306
+ shellQuoteArg
307
+ });
308
+
309
+ const { cmdDown, cmdFindRoot, cmdWhichReal } = createMiscCommands({
310
+ path, fs, process, CliError, parseOpts, printResult,
311
+ readRuntime, runtimeRequest, runtimePath, globalRuntimePath,
312
+ reclaimRuntimePointerFiles, helpDown, loadSetup,
313
+ PRODUCT_NAME
314
+ });
315
+
316
+ const { cmdInstallHooks, cmdShim, cmdSetup, cmdUpdate, cmdUninstall } = createInstallCommands({
317
+ path, fs, process, CliError, parseOpts, validateOpts, wantsHelp,
318
+ printResult, commandPath, connect, readRuntime, runtimeRequest,
319
+ runtimePath, globalRuntimePath, PRODUCT_NAME, VERSION, NPM_PACKAGE_NAME,
320
+ helpInstallHooks, helpShim, helpUninstall, helpUpdate,
321
+ loadSetup, shellCommand,
322
+
323
+ sameResolvedPath, writeGuidance, removeGuidanceBlocks,
324
+
325
+ });
326
+
327
+ const { cmdHook } = createHookCommand({
328
+ connect, now, tx, addEvent, auditPayload,
329
+ registerProjectActivity, touchCurrentPeer,
330
+ liveProcessIdentity, detectBranch,
331
+ resolveCurrentPeer, providerSessionPeerId, providerSessionParts,
332
+ readAncestorCliInfo, latestHookProviderSession, formatHookEventName,
333
+ upsertPeer, upsertCanonicalPeerBinding, storedPeerIdentity,
334
+ autoPeerKind, autoPeerBasis, autoPeerProviderSession,
335
+ observeLockClockSafety, observePeerEvidence,
336
+ buildHookCoordinationContext, ackMessages,
337
+ reconcileRunningPeerBindings, inspectProviderProcess,
338
+ resumeIdFromArgs, shortHash, renewOwnedLocks, refreshHookOwnerIdentity,
339
+ path, fs, process, CliError
340
+ });
341
+
342
+ const { cmdPeer } = createPeerCommands({
343
+ connect, now, tx, addEvent, auditPayload, printResult, CliError, parseOpts,
344
+ detectBranch, buildPeerCommand, childSessionEnv, inferPeerKind,
345
+ findProviderSessionBinding, bindingHasRuntime,
346
+ resolveCurrentPeer, helpPeer, wantsHelp, CLI_NAME, cmdPeers,
347
+ runtimeRequest, readRuntime,
348
+ upsertPeer, upsertCanonicalPeerBinding, storedPeerIdentity,
349
+ path, process, Map, table
350
+ });
351
+ const {
352
+ webPeerAction
353
+ } = createWebPeerActions({
354
+ activePeerTtl: ACTIVE_PEER_TTL,
355
+ addEvent,
356
+ assertTaskOwnerForMutation,
357
+ claimNextTasksForPeer,
358
+ connect,
359
+ defaultLockTtl: DEFAULT_LOCK_TTL,
360
+ detectBranch,
361
+ now,
362
+ observeClockSafetyInTransaction,
363
+ observePeerEvidence,
364
+ positiveIntOpt,
365
+ peerEvidenceFromDb,
366
+ queryInbox,
367
+ statusSnapshot,
368
+ statusSummary,
369
+ takeOverTaskForPeer,
370
+ touchPeer,
371
+ tx,
372
+ upsertPeer
373
+ });
374
+ // Directory under .hello-cc/ for optional external PTY buffer files.
375
+ const BUFS_DIR_NAME = 'bufs';
376
+
377
+ function now() {
378
+ return Math.floor(Date.now() / 1000);
379
+ }
380
+
381
+ function iso(ts) {
382
+ if (!ts) return '';
383
+ return new Date(ts * 1000).toISOString();
384
+ }
385
+
386
+ function redactedLogText(value) {
387
+ const redacted = redactSecrets(value);
388
+ if (typeof redacted === 'string') return redacted;
389
+ const serialized = JSON.stringify(redacted);
390
+ return serialized === undefined ? String(redacted) : serialized;
391
+ }
392
+
393
+ const { cmdRun } = createRunCommands({
394
+ connect, now, addEvent, auditPayload,
395
+ upsertPeer, upsertCanonicalPeerBinding,
396
+ helpRun, redactedLogText, CLI_NAME, BUFS_DIR_NAME
397
+ });
398
+
399
+ const {
400
+ cmdScan, cmdTmux,
401
+ tmuxSessionCreationToken, tmuxSessionId, tmuxPaneForTarget,
402
+ strictTmuxClientObservation, tmuxAttachmentAuthority
403
+ } = createTmuxCommands({
404
+ connect, now, addEvent, auditPayload,
405
+ upsertPeer, upsertCanonicalPeerBinding,
406
+ loadDiscover, sameResolvedPath,
407
+ helpTmux,
408
+ canonicalRoot, rootEvidence, processEvidenceFromRow,
409
+ inspectTmuxTarget, isProjectManagedTmuxSession
410
+ });
411
+
412
+ const {
413
+ cmdGc, runGc, bufferDirectory,
414
+ observeGcClockSafety, previewGcClockSafety, runManualGc
415
+ } = createGcCommands({
416
+ connect, now, helpGc,
417
+ UNKNOWN_EVIDENCE_GRACE_SEC, BUFS_DIR_NAME,
418
+ peerMutationSubject, mutatePeerWithEvidence,
419
+ observeClockSafetyInTransactionOrThrow, observePeerEvidence
420
+ });
421
+
422
+ const { cmdUp, prepareLocalBus } = createUpCommand({
423
+ connect, helpUp, PRODUCT_NAME,
424
+ loadSetup, loadDiscover,
425
+ writeGuidance, sameResolvedPath,
426
+ upsertPeer, upsertCanonicalPeerBinding
427
+ });
428
+
429
+ const {
430
+ startWebBackground, assertWebTokenForHost, webExposureWarning,
431
+ webSocketOriginAllowed, proxyOriginForOpts
432
+ } = createWebStartup({
433
+ splitProcessArgs, sameResolvedPath,
434
+ redactedLogText, CLI_NAME, PRODUCT_NAME, now,
435
+ prepareLocalBus
436
+ });
437
+
438
+ const { cmdWeb } = createWebRuntime({
439
+ // constants
440
+ ACTIVE_PEER_TTL, BUFS_DIR_NAME, CLI_NAME, DEFAULT_LOCK_TTL,
441
+ DETECTED_PEER_MAX_AGE, PRODUCT_NAME, UNKNOWN_EVIDENCE_GRACE_SEC, VERSION,
442
+ // db helpers
443
+ connect, addEvent, auditPayload,
444
+ // peer helpers
445
+ touchPeer, upsertPeer, upsertCanonicalPeerBinding,
446
+ // coordination state
447
+ assertTaskOwnerForMutation, claimNextTasksForPeer,
448
+ queryInbox, queryOpenTasks, queryTimelineMessages,
449
+ requestActorPeer, requestSource, sendMessage,
450
+ statusSnapshot, statusSummary, takeOverTaskForPeer,
451
+ webPeerAction,
452
+ // evidence runtime
453
+ canonicalRoot, isProjectManagedTmuxSession, liveProcessIdentity,
454
+ mutatePeerWithEvidence, observeClockSafetyOrThrow, observePeerEvidence,
455
+ peerEvidenceFromDb, providerOwnerEvidenceFromDb, rootEvidence,
456
+ // tmux evidence helpers
457
+ strictTmuxClientObservation, tmuxAttachmentAuthority,
458
+ tmuxPaneForTarget, tmuxSessionCreationToken, tmuxSessionId,
459
+ // gc helpers
460
+ bufferDirectory, runGc,
461
+ // web startup helpers
462
+ assertWebTokenForHost, proxyOriginForOpts, startWebBackground,
463
+ webExposureWarning, webSocketOriginAllowed,
464
+ // local bus
465
+ prepareLocalBus,
466
+ // misc bin-local helpers
467
+ findProviderSessionBinding, helpWeb, latestHookProviderSession,
468
+ now, redactedLogText, renderWebIndex, renderWebLogin,
469
+ sameResolvedPath, sendWebHtml, shellCommand, webErrorStatus
470
+ });
471
+
472
+ function renderWebIndex(nonce) {
473
+ return webUiTemplate.webIndexHtml({ nonce });
474
+ }
475
+
476
+ function renderWebLogin(nonce) {
477
+ if (typeof webUiTemplate.webLoginPage === 'function') return webUiTemplate.webLoginPage({ nonce });
478
+ return `<!doctype html>
479
+ <html lang="en"><head><meta charset="utf-8"><meta name="viewport" content="width=device-width,initial-scale=1">
480
+ <title>hello-cc - sign in</title></head><body>
481
+ <main><h1>hello-cc</h1><form action="/" method="get"><label>Access token <input name="token" type="password" required></label><button type="submit">Sign in</button></form></main>
482
+ </body></html>`;
483
+ }
484
+
485
+ function sendWebHtml(res, render) {
486
+ const nonce = randomBytes(18).toString('base64url');
487
+ sendHttp(res, 200, 'text/html; charset=utf-8', render(nonce), { nonce });
488
+ }
489
+
490
+ function webErrorStatus(err) {
491
+ if (!(err instanceof CliError)) return 500;
492
+ if (err.code === 'REGISTRY_BUSY') return 503;
493
+ if (['BAD_ARGS', 'BAD_REQUEST', 'PEER_IDENTITY_REQUIRED', 'REQUEST_TOO_LARGE'].includes(err.code)) return 400;
494
+ if (['PEER_IDENTITY_MISMATCH', 'TASK_OWNED', 'LOCK_OWNED', 'PROJECT_NOT_REGISTERED', 'PROJECT_PATH_FORBIDDEN'].includes(err.code)) return 403;
495
+ if (['NOT_FOUND'].includes(err.code)) return 404;
496
+ if (['LOCK_HELD', 'SESSION_NOT_RUNNING'].includes(err.code)) return 409;
497
+ return 500;
498
+ }
499
+
500
+ function autoPeerDefaults(ctx, kindHint = 'shell', status = 'working') {
501
+ const kind = autoPeerKind(kindHint);
502
+ const ancestor = readAncestorCliInfo();
503
+ return {
504
+ kind,
505
+ role: 'peer',
506
+ worktree: ctx.cwd,
507
+ branch: detectBranch(ctx.cwd),
508
+ pid: ancestor?.kind === kind ? ancestor.pid : process.ppid,
509
+ status,
510
+ capabilities: 'auto-shell'
511
+ };
512
+ }
513
+
514
+ function shellCommand(args) {
515
+ return shellCommandWithQuote(args, shellQuoteArg);
516
+ }
517
+
518
+ function resolveTargetPeer(ctx, opts = {}, key = 'peer', kindHint = 'shell') {
519
+ if (opts[key]) return { id: opts[key], auto: false, target: true };
520
+ return resolveCurrentPeer(ctx, opts, key, kindHint);
521
+ }
522
+
523
+ function latestHookProviderSession(db, peer) {
524
+ if (!peer) return null;
525
+ try {
526
+ const row = db.prepare(`
527
+ SELECT COALESCE(
528
+ json_extract(payload, '$.session_id'),
529
+ json_extract(payload, '$.sessionId'),
530
+ json_extract(payload, '$.conversation_id'),
531
+ json_extract(payload, '$.conversationId')
532
+ ) AS session_id
533
+ FROM events
534
+ WHERE actor = ?
535
+ AND type LIKE 'hook.%'
536
+ ORDER BY id DESC
537
+ LIMIT 1
538
+ `).get(peer);
539
+ return row?.session_id || null;
540
+ } catch {
541
+ return null;
542
+ }
543
+ }
544
+
545
+ function formatHookEventName(hookType) {
546
+ const known = {
547
+ sessionstart: 'SessionStart',
548
+ userpromptsubmit: 'UserPromptSubmit',
549
+ stop: 'Stop',
550
+ posttooluse: 'PostToolUse',
551
+ pretooluse: 'PreToolUse'
552
+ };
553
+ const compact = String(hookType || '').replace(/[^a-z]/gi, '').toLowerCase();
554
+ return known[compact] || String(hookType || 'unknown');
555
+ }
556
+
557
+ function writeGuidance(ctx) {
558
+ return writeGuidanceForRoot(ctx.root);
559
+ }
560
+
561
+ function removeGuidanceBlocks(ctx) {
562
+ return removeGuidanceBlocksForRoot(ctx.root);
563
+ }
564
+
565
+ // ─── hcc doctor ─────────────────────────────────────────────────────────────
566
+ // Health self-check: SQLite integrity, schema version, WAL/DB size, row counts.
567
+ async function dispatch(ctx, rest) {
568
+ const command = rest[0];
569
+ const args = rest.slice(1);
570
+ if (!command || command === '--help' || command === '-h' || command === 'help') return helpMain();
571
+ if (command === '--version' || command === 'version') return console.log(VERSION);
572
+ if (command === 'up') return cmdUp(ctx, args);
573
+ if (command === 'down') return cmdDown(ctx, args);
574
+ if (command === 'update') return cmdUpdate(ctx, args);
575
+ if (command === 'uninstall') return cmdUninstall(ctx, args);
576
+ if (command === 'init') return cmdInit(ctx, args);
577
+ if (command === 'register') return cmdRegister(ctx, args);
578
+ if (command === 'join') return cmdJoin(ctx, args);
579
+ if (command === 'env') return cmdEnv(ctx, args);
580
+ if (command === 'heartbeat') return cmdHeartbeat(ctx, args);
581
+ if (command === 'peers') return cmdPeers(ctx, args);
582
+ if (command === 'status') return cmdStatus(ctx, args);
583
+ if (command === 'state') return cmdState(ctx, args);
584
+ if (command === 'prompt') return cmdPrompt(ctx, args);
585
+ if (command === 'run') return cmdRun(ctx, args);
586
+ if (command === 'peer') return cmdPeer(ctx, args);
587
+ if (command === 'tmux') return cmdTmux(ctx, args);
588
+ if (command === 'inject') return cmdInject(ctx, args);
589
+ if (command === 'ask') return cmdAsk(ctx, args);
590
+ if (command === 'broadcast') return cmdBroadcast(ctx, args);
591
+ if (command === 'task') return cmdTask(ctx, args);
592
+ if (command === 'team') return cmdTeam(ctx, args);
593
+ if (command === 'msg') return cmdMsg(ctx, args);
594
+ if (command === 'lock') return cmdLock(ctx, args);
595
+ if (command === 'handoff') return cmdHandoff(ctx, args);
596
+ if (command === 'event') return cmdEvent(ctx, args);
597
+ if (command === 'web') return cmdWeb(ctx, args);
598
+ if (command === 'hook') return cmdHook(ctx, args);
599
+ if (command === 'install-hooks') return cmdInstallHooks(ctx, args);
600
+ if (command === 'shim') return cmdShim(ctx, args);
601
+ if (command === 'setup') return cmdSetup(ctx, args);
602
+ if (command === 'scan') return cmdScan(ctx, args);
603
+ if (command === 'doctor') return cmdDoctor(ctx, args);
604
+ if (command === 'gc') return cmdGc(ctx, args);
605
+ if (command === 'find-root') return cmdFindRoot(ctx, args);
606
+ if (command === 'which-real') return cmdWhichReal(ctx, args);
607
+ throw new CliError('BAD_ARGS', `Unknown command: ${command}`);
608
+ }
609
+
610
+ async function main() {
611
+ const { global, rest } = splitGlobalArgs(process.argv.slice(2));
612
+ const ctx = createCliContext(global, { detectRoot });
613
+ try {
614
+ await dispatch(ctx, rest);
615
+ } catch (err) {
616
+ const publicFailure = publicCliFailure(err);
617
+ if (publicFailure) {
618
+ const publicError = publicFailure.error;
619
+ if (ctx.json) {
620
+ console.error(formatJson(false, redactSecrets({
621
+ code: publicError.code,
622
+ message: publicError.message,
623
+ ...publicError.extra,
624
+ ...(publicFailure.cleanupFailed ? { cleanup_failed: true } : {})
625
+ })));
626
+ } else {
627
+ console.error(redactedLogText(`${CLI_NAME}: ${publicError.message}`));
628
+ if (Object.keys(publicError.extra).length) console.error(redactedLogText(publicError.extra));
629
+ if (publicFailure.cleanupFailed) {
630
+ console.error(`${CLI_NAME}: an additional internal cleanup failed`);
631
+ }
632
+ }
633
+ process.exitCode = 1;
634
+ return;
635
+ }
636
+ throw err;
637
+ }
638
+ }
639
+
640
+ export async function runCli() {
641
+ await main();
642
+ }