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