@indigoai-us/hq-cli 5.108.8 → 5.108.10

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/CHANGELOG.md CHANGED
@@ -2,6 +2,10 @@
2
2
 
3
3
  ## [Unreleased]
4
4
 
5
+ ## [5.108.10] — 2026-09-05
6
+
7
+ ## [5.108.9] — 2026-09-05
8
+
5
9
  ## [5.108.8] — 2026-09-05
6
10
 
7
11
  ## [5.108.7] — 2026-09-05
@@ -0,0 +1,55 @@
1
+ /**
2
+ * Hot-command manifest for the entrypoint's lazy registration.
3
+ *
4
+ * main.ts used to import the entire ~60-module command graph at module scope,
5
+ * so every invocation paid for every command. That is fine for a human typing
6
+ * one command and ruinous for the calls automation makes in a loop: the
7
+ * hq-sentry agent fleet on an outpost runs `hq secrets` about 39 times a minute
8
+ * (each worker resolves credentials before every external command it executes),
9
+ * and each of those processes spent ~0.75 CPU-seconds importing commands it
10
+ * never ran — around half a core, continuously. Measured on Outpost 2
11
+ * (i-09424eff61920a4ac) 2026-09-05; see register-all.ts for the numbers.
12
+ *
13
+ * A command listed here registers ITSELF and nothing else. Anything not listed
14
+ * — `--help`, a bare `hq`, an unknown command, every other command — falls back
15
+ * to `registerAllCommands`, the complete unchanged graph. That fallback is what
16
+ * makes this safe to extend one command at a time: an entry is a performance
17
+ * opt-in, never a behaviour change, and a command that is absent here is simply
18
+ * as fast as it was before.
19
+ *
20
+ * This mirrors commands/scaffold-fast.ts, which does the same thing for the
21
+ * relocated `hq core …` scripts, and carries the same anti-drift discipline: a
22
+ * parity test registers each entry BOTH ways and asserts the resulting command
23
+ * shape is identical, so a manifest entry cannot silently diverge from the real
24
+ * registration.
25
+ *
26
+ * TO ADD A COMMAND: it must register exactly one top-level command, be
27
+ * registered onto `program` (not onto a subcommand group), and have no other
28
+ * module contributing subcommands to it. The parity test enforces the shape;
29
+ * these three conditions are what make the entry correct in the first place.
30
+ */
31
+ import type { Command } from "commander";
32
+ export type LazyCommand = {
33
+ /** The top-level token this matches — `hq <name> …`. */
34
+ name: string;
35
+ /** Registers this one command onto `program`, importing only its module. */
36
+ register: (program: Command) => Promise<void>;
37
+ };
38
+ /**
39
+ * The hot paths, in descending order of how often automation calls them.
40
+ *
41
+ * `secrets` and `run` are the two commands HQ's own tooling puts on the inner
42
+ * loop: every fleet worker shells through one of them before each external
43
+ * command, which is exactly the shape that makes eager import expensive.
44
+ */
45
+ export declare const LAZY_COMMANDS: readonly LazyCommand[];
46
+ /**
47
+ * Resolve `process.argv` to a manifest entry, or null to use the full graph.
48
+ *
49
+ * argv is `[node, hq, <name>, ...rest]`. `hq` declares no program-level options
50
+ * before the command name, so argv[2] is the command token when there is one;
51
+ * `--help`, `--version`, and a bare `hq` all fail the lookup and take the
52
+ * fallback, which is the intended behaviour — help must list every command.
53
+ */
54
+ export declare function findLazyCommand(argv: readonly string[]): LazyCommand | null;
55
+ //# sourceMappingURL=lazy-commands.d.ts.map
@@ -0,0 +1,68 @@
1
+ /**
2
+ * Hot-command manifest for the entrypoint's lazy registration.
3
+ *
4
+ * main.ts used to import the entire ~60-module command graph at module scope,
5
+ * so every invocation paid for every command. That is fine for a human typing
6
+ * one command and ruinous for the calls automation makes in a loop: the
7
+ * hq-sentry agent fleet on an outpost runs `hq secrets` about 39 times a minute
8
+ * (each worker resolves credentials before every external command it executes),
9
+ * and each of those processes spent ~0.75 CPU-seconds importing commands it
10
+ * never ran — around half a core, continuously. Measured on Outpost 2
11
+ * (i-09424eff61920a4ac) 2026-09-05; see register-all.ts for the numbers.
12
+ *
13
+ * A command listed here registers ITSELF and nothing else. Anything not listed
14
+ * — `--help`, a bare `hq`, an unknown command, every other command — falls back
15
+ * to `registerAllCommands`, the complete unchanged graph. That fallback is what
16
+ * makes this safe to extend one command at a time: an entry is a performance
17
+ * opt-in, never a behaviour change, and a command that is absent here is simply
18
+ * as fast as it was before.
19
+ *
20
+ * This mirrors commands/scaffold-fast.ts, which does the same thing for the
21
+ * relocated `hq core …` scripts, and carries the same anti-drift discipline: a
22
+ * parity test registers each entry BOTH ways and asserts the resulting command
23
+ * shape is identical, so a manifest entry cannot silently diverge from the real
24
+ * registration.
25
+ *
26
+ * TO ADD A COMMAND: it must register exactly one top-level command, be
27
+ * registered onto `program` (not onto a subcommand group), and have no other
28
+ * module contributing subcommands to it. The parity test enforces the shape;
29
+ * these three conditions are what make the entry correct in the first place.
30
+ */
31
+ /**
32
+ * The hot paths, in descending order of how often automation calls them.
33
+ *
34
+ * `secrets` and `run` are the two commands HQ's own tooling puts on the inner
35
+ * loop: every fleet worker shells through one of them before each external
36
+ * command, which is exactly the shape that makes eager import expensive.
37
+ */
38
+ export const LAZY_COMMANDS = [
39
+ {
40
+ name: "secrets",
41
+ register: async (program) => {
42
+ const { registerSecretsCommand } = await import("./commands/secrets.js");
43
+ registerSecretsCommand(program);
44
+ },
45
+ },
46
+ {
47
+ name: "run",
48
+ register: async (program) => {
49
+ const { registerRunCommand } = await import("./commands/run.js");
50
+ registerRunCommand(program);
51
+ },
52
+ },
53
+ ];
54
+ /**
55
+ * Resolve `process.argv` to a manifest entry, or null to use the full graph.
56
+ *
57
+ * argv is `[node, hq, <name>, ...rest]`. `hq` declares no program-level options
58
+ * before the command name, so argv[2] is the command token when there is one;
59
+ * `--help`, `--version`, and a bare `hq` all fail the lookup and take the
60
+ * fallback, which is the intended behaviour — help must list every command.
61
+ */
62
+ export function findLazyCommand(argv) {
63
+ const name = argv[2];
64
+ if (typeof name !== "string")
65
+ return null;
66
+ return LAZY_COMMANDS.find((candidate) => candidate.name === name) ?? null;
67
+ }
68
+ //# sourceMappingURL=lazy-commands.js.map
@@ -49,6 +49,25 @@ export declare const RUNTIME_PROBE_PREFIX = "hooks.runtime";
49
49
  export declare const POLICY_TRIGGER_LEDGER_RELPATH = "workspace/orchestrator/policy-trigger-state";
50
50
  /** The two lifecycle events `check-hq-hooks.sh` requires a command hook on. */
51
51
  export declare const REQUIRED_HOOK_EVENTS: readonly ["SessionStart", "PreToolUse"];
52
+ /**
53
+ * Default runtime marker path an agents-v2 (hermes) box writes its runtime mode
54
+ * to. Overridable via `HQ_RUNTIME_MARKER_FILE`, exactly as the shell reads
55
+ * `${HQ_RUNTIME_MARKER_FILE:-/var/lib/hq-agent/runtime.json}`.
56
+ */
57
+ export declare const HQ_RUNTIME_MARKER_DEFAULT = "/var/lib/hq-agent/runtime.json";
58
+ /**
59
+ * Path (relative to the HQ root) of the on-box agents-v2 hook adapter. Its
60
+ * presence under the tree is one of the two signals that the runtime is
61
+ * agents-v2, mirroring `hq_runtime_mode` in `check-hq-hooks.sh`.
62
+ */
63
+ export declare const AGENTS_V2_ADAPTER_RELPATH = ".agents-v2-hooks/hq-agents-v2-hook-adapter.sh";
64
+ /**
65
+ * Default hours a policy-trigger ledger may age and still evidence a live
66
+ * agents-v2 turn when no exact session id is given. Overridable via
67
+ * `HQ_V2_LEDGER_MAX_AGE_HOURS`, matching the shell's
68
+ * `HQ_V2_LEDGER_MAX_AGE_HOURS="${HQ_V2_LEDGER_MAX_AGE_HOURS:-24}"`.
69
+ */
70
+ export declare const HQ_V2_LEDGER_MAX_AGE_HOURS_DEFAULT = 24;
52
71
  /** Whether the ledger was found. Mirrors the script's `present`/`missing`. */
53
72
  export type LedgerState = "present" | "missing";
54
73
  /** Options for {@link reproduceCheckHqHooks}. */
@@ -92,6 +111,43 @@ export interface HookLoadReproduction {
92
111
  * exception, so the probe degrades exactly like the defensively-written script.
93
112
  */
94
113
  export declare function reproduceCheckHqHooks(opts: ReproduceOptions): HookLoadReproduction;
114
+ /** Inputs for the agents-v2 attestation helpers. */
115
+ export interface AgentsV2AttestationOptions {
116
+ /** Absolute HQ tree root. */
117
+ hqRoot: string;
118
+ /** Optional session scope for the ledger check (the `--session-id` flag). */
119
+ sessionId?: string;
120
+ /** Environment to read (marker path, freshness window). Default: process.env. */
121
+ env?: NodeJS.ProcessEnv;
122
+ }
123
+ /**
124
+ * Whether the runtime is agents-v2. Two signals, either sufficient — the same
125
+ * two `hq_runtime_mode` uses: the runtime marker
126
+ * (`HQ_RUNTIME_MARKER_FILE`, default {@link HQ_RUNTIME_MARKER_DEFAULT}) reads
127
+ * `runtimeMode == "agents-v2"`, OR the on-box adapter is installed under the
128
+ * tree at {@link AGENTS_V2_ADAPTER_RELPATH}.
129
+ */
130
+ export declare function isAgentsV2Runtime(hqRoot: string, env?: NodeJS.ProcessEnv): boolean;
131
+ /**
132
+ * Whether `.claude/settings.json` wires the on-box agents-v2 hook adapter — a
133
+ * raw substring match on the file, exactly like the shell's
134
+ * `grep -q 'hq-agents-v2-hook-adapter\.sh'` in `hq_settings_wires_v2_adapter`.
135
+ */
136
+ export declare function settingsWireV2Adapter(hqRoot: string): boolean;
137
+ /**
138
+ * Whether a policy-trigger ledger evidencing a live agents-v2 turn is present:
139
+ * the exact session's ledger when a session id is given (session identity
140
+ * implies freshness), otherwise any ledger modified within the freshness window
141
+ * so a long-dead tree cannot self-attest off a stale file. Mirrors
142
+ * `hq_v2_ledger_present`.
143
+ */
144
+ export declare function v2LedgerPresent(opts: AgentsV2AttestationOptions): boolean;
145
+ /**
146
+ * All three agents-v2 self-attestation conditions. Used only to GRANT PASS to a
147
+ * hermes box that host detection leaves platform-unknown; never to withhold it.
148
+ * The exact conjunction of `agents_v2_attested` in `check-hq-hooks.sh`.
149
+ */
150
+ export declare function agentsV2Attested(opts: AgentsV2AttestationOptions): boolean;
95
151
  /**
96
152
  * The runtime-probe check family entry. Reproduces the script verdict, then
97
153
  * renders it as a single platform-aware doctor result. See the module header for
@@ -51,6 +51,25 @@ export const RUNTIME_PROBE_PREFIX = "hooks.runtime";
51
51
  export const POLICY_TRIGGER_LEDGER_RELPATH = "workspace/orchestrator/policy-trigger-state";
52
52
  /** The two lifecycle events `check-hq-hooks.sh` requires a command hook on. */
53
53
  export const REQUIRED_HOOK_EVENTS = ["SessionStart", "PreToolUse"];
54
+ /**
55
+ * Default runtime marker path an agents-v2 (hermes) box writes its runtime mode
56
+ * to. Overridable via `HQ_RUNTIME_MARKER_FILE`, exactly as the shell reads
57
+ * `${HQ_RUNTIME_MARKER_FILE:-/var/lib/hq-agent/runtime.json}`.
58
+ */
59
+ export const HQ_RUNTIME_MARKER_DEFAULT = "/var/lib/hq-agent/runtime.json";
60
+ /**
61
+ * Path (relative to the HQ root) of the on-box agents-v2 hook adapter. Its
62
+ * presence under the tree is one of the two signals that the runtime is
63
+ * agents-v2, mirroring `hq_runtime_mode` in `check-hq-hooks.sh`.
64
+ */
65
+ export const AGENTS_V2_ADAPTER_RELPATH = ".agents-v2-hooks/hq-agents-v2-hook-adapter.sh";
66
+ /**
67
+ * Default hours a policy-trigger ledger may age and still evidence a live
68
+ * agents-v2 turn when no exact session id is given. Overridable via
69
+ * `HQ_V2_LEDGER_MAX_AGE_HOURS`, matching the shell's
70
+ * `HQ_V2_LEDGER_MAX_AGE_HOURS="${HQ_V2_LEDGER_MAX_AGE_HOURS:-24}"`.
71
+ */
72
+ export const HQ_V2_LEDGER_MAX_AGE_HOURS_DEFAULT = 24;
54
73
  /**
55
74
  * Faithfully reproduce `core/scripts/check-hq-hooks.sh --require-ledger` in
56
75
  * TypeScript. The checks, in the script's order:
@@ -110,6 +129,117 @@ export function reproduceCheckHqHooks(opts) {
110
129
  }
111
130
  return { ok: issues.length === 0, ledgerState, issues };
112
131
  }
132
+ /**
133
+ * Whether the runtime is agents-v2. Two signals, either sufficient — the same
134
+ * two `hq_runtime_mode` uses: the runtime marker
135
+ * (`HQ_RUNTIME_MARKER_FILE`, default {@link HQ_RUNTIME_MARKER_DEFAULT}) reads
136
+ * `runtimeMode == "agents-v2"`, OR the on-box adapter is installed under the
137
+ * tree at {@link AGENTS_V2_ADAPTER_RELPATH}.
138
+ */
139
+ export function isAgentsV2Runtime(hqRoot, env = process.env) {
140
+ const markerPath = env.HQ_RUNTIME_MARKER_FILE?.trim() || HQ_RUNTIME_MARKER_DEFAULT;
141
+ if (readRuntimeMarkerMode(markerPath) === "agents-v2")
142
+ return true;
143
+ return isFile(path.join(hqRoot, ...AGENTS_V2_ADAPTER_RELPATH.split("/")));
144
+ }
145
+ /** The `runtimeMode` field of the runtime marker JSON, or null when unreadable. */
146
+ function readRuntimeMarkerMode(markerPath) {
147
+ let raw;
148
+ try {
149
+ raw = fs.readFileSync(markerPath, "utf8");
150
+ }
151
+ catch {
152
+ return null;
153
+ }
154
+ try {
155
+ const parsed = JSON.parse(raw);
156
+ return typeof parsed.runtimeMode === "string" ? parsed.runtimeMode : null;
157
+ }
158
+ catch {
159
+ return null;
160
+ }
161
+ }
162
+ /**
163
+ * Whether `.claude/settings.json` wires the on-box agents-v2 hook adapter — a
164
+ * raw substring match on the file, exactly like the shell's
165
+ * `grep -q 'hq-agents-v2-hook-adapter\.sh'` in `hq_settings_wires_v2_adapter`.
166
+ */
167
+ export function settingsWireV2Adapter(hqRoot) {
168
+ let raw;
169
+ try {
170
+ raw = fs.readFileSync(path.join(hqRoot, ".claude", "settings.json"), "utf8");
171
+ }
172
+ catch {
173
+ return false;
174
+ }
175
+ return raw.includes("hq-agents-v2-hook-adapter.sh");
176
+ }
177
+ /**
178
+ * Whether a policy-trigger ledger evidencing a live agents-v2 turn is present:
179
+ * the exact session's ledger when a session id is given (session identity
180
+ * implies freshness), otherwise any ledger modified within the freshness window
181
+ * so a long-dead tree cannot self-attest off a stale file. Mirrors
182
+ * `hq_v2_ledger_present`.
183
+ */
184
+ export function v2LedgerPresent(opts) {
185
+ const env = opts.env ?? process.env;
186
+ const dir = path.join(opts.hqRoot, ...POLICY_TRIGGER_LEDGER_RELPATH.split("/"));
187
+ if (!isDir(dir))
188
+ return false;
189
+ if (opts.sessionId) {
190
+ return isFile(path.join(dir, `${opts.sessionId}.txt`));
191
+ }
192
+ const cutoffMs = Date.now() - resolveMaxLedgerAgeHours(env) * 60 * 60 * 1000;
193
+ return ledgerDirHasFreshTxt(dir, cutoffMs);
194
+ }
195
+ /** The freshness window in hours, from the env override or the default. */
196
+ function resolveMaxLedgerAgeHours(env) {
197
+ const raw = env.HQ_V2_LEDGER_MAX_AGE_HOURS?.trim();
198
+ if (!raw)
199
+ return HQ_V2_LEDGER_MAX_AGE_HOURS_DEFAULT;
200
+ const parsed = Number.parseInt(raw, 10);
201
+ return Number.isFinite(parsed) && parsed >= 0
202
+ ? parsed
203
+ : HQ_V2_LEDGER_MAX_AGE_HOURS_DEFAULT;
204
+ }
205
+ /** True when any `*.txt` under `dir` (recursive) was modified at/after `cutoffMs`. */
206
+ function ledgerDirHasFreshTxt(dir, cutoffMs) {
207
+ let entries;
208
+ try {
209
+ entries = fs.readdirSync(dir, { withFileTypes: true });
210
+ }
211
+ catch {
212
+ return false;
213
+ }
214
+ for (const entry of entries) {
215
+ const full = path.join(dir, entry.name);
216
+ if (entry.isDirectory()) {
217
+ if (ledgerDirHasFreshTxt(full, cutoffMs))
218
+ return true;
219
+ }
220
+ else if (entry.isFile() && entry.name.endsWith(".txt")) {
221
+ try {
222
+ if (fs.statSync(full).mtimeMs >= cutoffMs)
223
+ return true;
224
+ }
225
+ catch {
226
+ // Unreadable entry: ignore, keep scanning.
227
+ }
228
+ }
229
+ }
230
+ return false;
231
+ }
232
+ /**
233
+ * All three agents-v2 self-attestation conditions. Used only to GRANT PASS to a
234
+ * hermes box that host detection leaves platform-unknown; never to withhold it.
235
+ * The exact conjunction of `agents_v2_attested` in `check-hq-hooks.sh`.
236
+ */
237
+ export function agentsV2Attested(opts) {
238
+ const env = opts.env ?? process.env;
239
+ return (isAgentsV2Runtime(opts.hqRoot, env) &&
240
+ settingsWireV2Adapter(opts.hqRoot) &&
241
+ v2LedgerPresent({ ...opts, env }));
242
+ }
113
243
  /**
114
244
  * The runtime-probe check family entry. Reproduces the script verdict, then
115
245
  * renders it as a single platform-aware doctor result. See the module header for
@@ -122,6 +252,26 @@ export function checkRuntimeProbe(context) {
122
252
  const checkId = `${RUNTIME_PROBE_PREFIX}.enforcement`;
123
253
  const target = POLICY_TRIGGER_LEDGER_RELPATH;
124
254
  const scope = sessionId ? ` for session ${sessionId}` : "";
255
+ // agents-v2 (hermes) self-attestation. hq doctor leaves the hermes host
256
+ // platform-unknown and, on an unknown host, the probe would report UNKNOWN
257
+ // below. But the on-box adapter provably wrote the ledger through the same
258
+ // .claude hooks, so grant PASS — and report platform "agents-v2" — when, and
259
+ // only when, the runtime is agents-v2, settings wire the on-box adapter, and a
260
+ // ledger exists (the exact session's under --session-id; otherwise any ledger
261
+ // fresh within the window). Requires the on-box marker/adapter, so this never
262
+ // changes the verdict for any other host. See agentsV2Attested().
263
+ if (agentsV2Attested({ hqRoot, sessionId })) {
264
+ return [
265
+ {
266
+ status: "PASS",
267
+ checkId,
268
+ target,
269
+ message: `Host platform is agents-v2 (hermes fleet): the on-box adapter wrote the ` +
270
+ `policy-trigger ledger through the same .claude hooks, so hook dispatch was ` +
271
+ `observed this session — the ledger has an entry${scope}.`,
272
+ },
273
+ ];
274
+ }
125
275
  const repro = reproduceCheckHqHooks({ hqRoot, sessionId });
126
276
  // Unknown host: the label cannot be trusted, so live enforcement cannot be
127
277
  // verified in either direction. UNKNOWN (never PASS or FAIL) is the honest
@@ -332,4 +482,12 @@ function isFile(file) {
332
482
  return false;
333
483
  }
334
484
  }
485
+ function isDir(dir) {
486
+ try {
487
+ return fs.statSync(dir).isDirectory();
488
+ }
489
+ catch {
490
+ return false;
491
+ }
492
+ }
335
493
  //# sourceMappingURL=runtime-probe.js.map
@@ -74,6 +74,22 @@ export interface DeriveVerdictOptions {
74
74
  * inline script treats a missing ledger — regardless of platform.
75
75
  */
76
76
  requireLedger?: boolean;
77
+ /**
78
+ * Whether this is an attested agents-v2 (hermes) box: the runtime is
79
+ * agents-v2, `.claude/settings.json` wires the on-box adapter, and a
80
+ * policy-trigger ledger exists (the exact session's when a session id is
81
+ * given; otherwise a ledger fresh within the freshness window). When true,
82
+ * OBSERVED is granted even though the doctor's runtime check did not report
83
+ * PASS — because hq doctor leaves the hermes host platform-unknown, yet the
84
+ * on-box adapter provably wrote the ledger through the same .claude hooks.
85
+ *
86
+ * This is the exact twin of the `agents_v2_attested` override in
87
+ * `render_from_doctor` (`core/scripts/check-hq-hooks.sh`). Establish it with
88
+ * {@link agentsV2Attested} in `checks/runtime-probe.ts` — the same three
89
+ * signals the shell's `agents_v2_attested` reads. It can only GRANT OBSERVED,
90
+ * never withhold it, so every non-agents-v2 caller is unaffected.
91
+ */
92
+ agentsV2Attested?: boolean;
77
93
  }
78
94
  /**
79
95
  * Derive the check-hq-hooks.sh verdict from a `hq doctor --json` document,
@@ -80,7 +80,18 @@ export function deriveCheckHqHooksVerdict(doc, options = {}) {
80
80
  // for --require-ledger, matching the inline script's "fail on a missing
81
81
  // ledger" contract.
82
82
  const runtimeResult = doc.results.find((result) => result.checkId === RUNTIME_ENFORCEMENT_CHECK_ID);
83
- const runtime = observeRuntime(runtimeResult?.status, requireLedger);
83
+ let runtime = observeRuntime(runtimeResult?.status, requireLedger);
84
+ // agents-v2 self-attestation: hq doctor leaves the hermes host
85
+ // platform-unknown and so does not report the runtime check as PASS, but the
86
+ // on-box adapter provably wrote the ledger through the same .claude hooks.
87
+ // Grant the identical OBSERVED verdict rather than relaying the host-unknown
88
+ // status as a failure. Mirrors render_from_doctor()'s agents_v2_attested
89
+ // override in check-hq-hooks.sh; only ever grants OBSERVED, never withholds it.
90
+ if (requireLedger &&
91
+ runtime !== "OBSERVED" &&
92
+ options.agentsV2Attested === true) {
93
+ runtime = "OBSERVED";
94
+ }
84
95
  if (requireLedger && runtime !== "OBSERVED") {
85
96
  messages.push(runtimeResult?.message ??
86
97
  "policy-trigger ledger was not found under workspace/orchestrator/policy-trigger-state");
@@ -5,7 +5,7 @@ export { CREDENTIAL_RENEWAL_FRACTION, DEFAULT_REFUSED_RETRY_MS, MAX_RETRY_DELAY_
5
5
  export type { BoundedTimeoutHandle, Contract3Bundle, CredentialRenewalErrorInfo, CredentialVendFailureKind, CredentialVendPostResult, CredentialsFetcher, PresenceCompany, TimerHost, } from "./credentials.js";
6
6
  export { amzDateOf, hex, presignIotWssUrl, rfc3986Encode } from "./presign.js";
7
7
  export type { IotCredentials } from "./presign.js";
8
- export { PresenceClient, backoffDelayMs, buildPresencePayload, defaultMqttConnect, isOwnPresenceTopic, } from "./presence.js";
8
+ export { CLOSE_LOG_ALWAYS_COUNT, CLOSE_LOG_INTERVAL_MS, CONNECT_STABLE_GRACE_MS, PresenceClient, backoffDelayMs, buildPresencePayload, defaultMqttConnect, isOwnPresenceTopic, } from "./presence.js";
9
9
  export type { MeshMqttClientLike, MqttConnectFn, MqttConnectionState, PresenceClientOptions, PresencePayload, PresenceRefusal, } from "./presence.js";
10
10
  export { defaultDaemonState, patchDaemonState, readDaemonState, writeDaemonState, } from "./state.js";
11
11
  export type { DaemonStateFile, PresenceRefusalState } from "./state.js";
@@ -2,7 +2,7 @@ export { DAEMON_DIRNAME, DAEMON_LOG_MAX_BYTES, DAEMON_LOG_NAME, DAEMON_PID_NAME,
2
2
  export { acquirePidLock, defaultPidLockDeps, parsePidLock, pidLockStatus, readPidLock, releasePidLock, resolveDaemonDir, } from "./pid-lock.js";
3
3
  export { CREDENTIAL_RENEWAL_FRACTION, DEFAULT_REFUSED_RETRY_MS, MAX_RETRY_DELAY_MS, MAX_TIMER_DELAY_MS, MQTT_KEEPALIVE_SECONDS, REALTIME_CREDENTIALS_PATH, REFUSED_RETRY_ENV, CredentialRenewalManager, CredentialVendError, classifyCredentialVendFailure, clampRetryDelayMs, createContract3Fetcher, defaultRefusedRetryMs, extractVendErrorCode, isCredentialVendRefused, normalizeContract3Bundle, parseRetryAfterMs, realTimerHost, refusedRetryDelayMs, renewalDelayMs, scheduleBoundedTimeout, } from "./credentials.js";
4
4
  export { amzDateOf, hex, presignIotWssUrl, rfc3986Encode } from "./presign.js";
5
- export { PresenceClient, backoffDelayMs, buildPresencePayload, defaultMqttConnect, isOwnPresenceTopic, } from "./presence.js";
5
+ export { CLOSE_LOG_ALWAYS_COUNT, CLOSE_LOG_INTERVAL_MS, CONNECT_STABLE_GRACE_MS, PresenceClient, backoffDelayMs, buildPresencePayload, defaultMqttConnect, isOwnPresenceTopic, } from "./presence.js";
6
6
  export { defaultDaemonState, patchDaemonState, readDaemonState, writeDaemonState, } from "./state.js";
7
7
  export { appendDaemonLog, daemonLogLine, ensureDaemonLog, resolveDaemonAssetDir, rotateDaemonLogIfNeeded, } from "./log.js";
8
8
  export { BOARD_REFRESH_INTERVAL_MS, createVaultBoardReader, formatBoardMarkdown, refreshBoundSessionBoards, writeBoardMarkdown, } from "./board-refresh.js";
@@ -7,11 +7,22 @@
7
7
  * - Offline is server-only (IoT lifecycle → PresenceIngestFunction)
8
8
  * - Never subscribes to thread topics
9
9
  * - Reconnects with monotonic full-jitter backoff 1s–60s on close / network
10
+ * - Backoff resets only after the connection stays up past
11
+ * {@link CONNECT_STABLE_GRACE_MS} (broker accept-then-close must not reset)
10
12
  * - Server credential refusals use a long retry (default 10m ±20%)
11
13
  */
12
14
  import { type IClientOptions, type MqttClient } from "mqtt";
13
15
  import { type Contract3Bundle, type CredentialsFetcher, type TimerHost } from "./credentials.js";
14
16
  export type MqttConnectionState = "idle" | "connecting" | "connected" | "reconnecting" | "closed";
17
+ /**
18
+ * A connection that drops before this grace window after `connect` is treated
19
+ * as a failed attempt: backoff counters are not reset (AWS IoT often accepts
20
+ * then closes immediately after a denied publish).
21
+ */
22
+ export declare const CONNECT_STABLE_GRACE_MS = 5000;
23
+ /** Always emit the first N close info lines, then at most one per interval. */
24
+ export declare const CLOSE_LOG_ALWAYS_COUNT = 3;
25
+ export declare const CLOSE_LOG_INTERVAL_MS = 60000;
15
26
  export interface PresencePayload {
16
27
  v: 1;
17
28
  status: "online" | "offline";
@@ -49,6 +60,8 @@ export interface PresenceClientOptions {
49
60
  onOnline?: (companies: string[]) => void;
50
61
  onState?: (state: MqttConnectionState) => void;
51
62
  onError?: (err: unknown) => void;
63
+ /** Non-error info lines (e.g. rate-limited mqtt close / backoff). */
64
+ onInfo?: (message: string) => void;
52
65
  /** Fired when refusal appears, code changes, clears, or nextRetryAt updates. */
53
66
  onRefusal?: (refusal: PresenceRefusal | null) => void;
54
67
  now?: () => Date;
@@ -78,6 +91,10 @@ export declare class PresenceClient {
78
91
  private attempt;
79
92
  private lastBackoffMs;
80
93
  private reconnectHandle;
94
+ private stableHandle;
95
+ private connectedAtMs;
96
+ private closeLogCount;
97
+ private lastCloseLogAtMs;
81
98
  private stopped;
82
99
  private generation;
83
100
  private publishedTopics;
@@ -118,7 +135,9 @@ export declare class PresenceClient {
118
135
  /** Publish retained online to every own presence topic (connect / renew). */
119
136
  private publishAll;
120
137
  private scheduleReconnect;
138
+ private maybeLogClose;
121
139
  private clearReconnect;
140
+ private clearStableTimer;
122
141
  }
123
142
  /** Production mqtt.connect wrapper (typed). */
124
143
  export declare function defaultMqttConnect(url: string, opts: IClientOptions): MqttClient;
@@ -7,11 +7,22 @@
7
7
  * - Offline is server-only (IoT lifecycle → PresenceIngestFunction)
8
8
  * - Never subscribes to thread topics
9
9
  * - Reconnects with monotonic full-jitter backoff 1s–60s on close / network
10
+ * - Backoff resets only after the connection stays up past
11
+ * {@link CONNECT_STABLE_GRACE_MS} (broker accept-then-close must not reset)
10
12
  * - Server credential refusals use a long retry (default 10m ±20%)
11
13
  */
12
14
  import mqtt from "mqtt";
13
15
  import { CredentialVendError, CredentialRenewalManager, MQTT_KEEPALIVE_SECONDS, clampRetryDelayMs, defaultRefusedRetryMs, realTimerHost, refusedRetryDelayMs, scheduleBoundedTimeout, } from "./credentials.js";
14
16
  import { presignIotWssUrl } from "./presign.js";
17
+ /**
18
+ * A connection that drops before this grace window after `connect` is treated
19
+ * as a failed attempt: backoff counters are not reset (AWS IoT often accepts
20
+ * then closes immediately after a denied publish).
21
+ */
22
+ export const CONNECT_STABLE_GRACE_MS = 5_000;
23
+ /** Always emit the first N close info lines, then at most one per interval. */
24
+ export const CLOSE_LOG_ALWAYS_COUNT = 3;
25
+ export const CLOSE_LOG_INTERVAL_MS = 60_000;
15
26
  /**
16
27
  * Full-jitter capped exponential backoff (1s base → 60s cap by default),
17
28
  * floored at `previousMs` so consecutive failures never shrink the delay.
@@ -50,6 +61,10 @@ export class PresenceClient {
50
61
  attempt = 0;
51
62
  lastBackoffMs = 0;
52
63
  reconnectHandle = null;
64
+ stableHandle = null;
65
+ connectedAtMs = null;
66
+ closeLogCount = 0;
67
+ lastCloseLogAtMs = Number.NEGATIVE_INFINITY;
53
68
  stopped = false;
54
69
  generation = 0;
55
70
  publishedTopics = [];
@@ -113,6 +128,8 @@ export class PresenceClient {
113
128
  this.generation += 1;
114
129
  this.renewal.stop();
115
130
  this.clearReconnect();
131
+ this.clearStableTimer();
132
+ this.connectedAtMs = null;
116
133
  this.teardownClient(false);
117
134
  this.clearRefusal(/* logClear */ false);
118
135
  this.setState("closed");
@@ -128,7 +145,7 @@ export class PresenceClient {
128
145
  return;
129
146
  }
130
147
  }
131
- // Backoff counters reset only on MQTT connect — not here.
148
+ // Backoff counters reset only after a stable connection — not here.
132
149
  this.clearReconnect();
133
150
  void this.connectOnce();
134
151
  }
@@ -155,7 +172,7 @@ export class PresenceClient {
155
172
  this.clearRefusal(true);
156
173
  if (this.stopped)
157
174
  return;
158
- // Reconnect with the new presigned URL; backoff resets only on MQTT connect.
175
+ // Reconnect with the new presigned URL; backoff resets only after stable connect.
159
176
  await this.connectOnce();
160
177
  }
161
178
  handleRenewalError(err, info) {
@@ -200,6 +217,8 @@ export class PresenceClient {
200
217
  if (this.stopped)
201
218
  return;
202
219
  this.clearReconnect();
220
+ this.clearStableTimer();
221
+ this.connectedAtMs = null;
203
222
  const generation = ++this.generation;
204
223
  this.teardownClient(true);
205
224
  try {
@@ -233,8 +252,17 @@ export class PresenceClient {
233
252
  client.on("connect", () => {
234
253
  if (this.stopped || generation !== this.generation)
235
254
  return;
236
- this.attempt = 0;
237
- this.lastBackoffMs = 0;
255
+ // Do not reset attempt here — broker may accept then close immediately
256
+ // (denied publish / policy). Reset only after CONNECT_STABLE_GRACE_MS.
257
+ this.connectedAtMs = this.timers.now();
258
+ this.clearStableTimer();
259
+ this.stableHandle = scheduleBoundedTimeout(this.timers, () => {
260
+ this.stableHandle = null;
261
+ if (this.stopped || generation !== this.generation)
262
+ return;
263
+ this.attempt = 0;
264
+ this.lastBackoffMs = 0;
265
+ }, CONNECT_STABLE_GRACE_MS);
238
266
  this.setState("connected");
239
267
  void this.publishAll("online").then(() => {
240
268
  this.options.onOnline?.(bundle.companies.map((c) => c.companyUid));
@@ -248,7 +276,13 @@ export class PresenceClient {
248
276
  client.on("close", () => {
249
277
  if (this.stopped || generation !== this.generation)
250
278
  return;
251
- this.scheduleReconnect();
279
+ const elapsedMs = this.connectedAtMs != null
280
+ ? Math.max(0, this.timers.now() - this.connectedAtMs)
281
+ : 0;
282
+ this.connectedAtMs = null;
283
+ this.clearStableTimer();
284
+ const delay = this.scheduleReconnect();
285
+ this.maybeLogClose(elapsedMs, delay);
252
286
  });
253
287
  }
254
288
  catch (err) {
@@ -295,7 +329,7 @@ export class PresenceClient {
295
329
  }
296
330
  scheduleReconnect(opts) {
297
331
  if (this.stopped)
298
- return;
332
+ return 0;
299
333
  this.setState("reconnecting");
300
334
  this.clearReconnect();
301
335
  let delay;
@@ -326,6 +360,17 @@ export class PresenceClient {
326
360
  }
327
361
  void this.connectOnce();
328
362
  }, delay);
363
+ return delay;
364
+ }
365
+ maybeLogClose(elapsedMs, delayMs) {
366
+ const now = this.timers.now();
367
+ const withinAlways = this.closeLogCount < CLOSE_LOG_ALWAYS_COUNT;
368
+ const intervalElapsed = now - this.lastCloseLogAtMs >= CLOSE_LOG_INTERVAL_MS;
369
+ if (!withinAlways && !intervalElapsed)
370
+ return;
371
+ this.closeLogCount += 1;
372
+ this.lastCloseLogAtMs = now;
373
+ this.options.onInfo?.(`presence mqtt closed after ${elapsedMs}ms; reconnect in ${delayMs}ms`);
329
374
  }
330
375
  clearReconnect() {
331
376
  if (this.reconnectHandle !== null) {
@@ -333,6 +378,12 @@ export class PresenceClient {
333
378
  this.reconnectHandle = null;
334
379
  }
335
380
  }
381
+ clearStableTimer() {
382
+ if (this.stableHandle !== null) {
383
+ this.stableHandle.clear();
384
+ this.stableHandle = null;
385
+ }
386
+ }
336
387
  }
337
388
  /** Production mqtt.connect wrapper (typed). */
338
389
  export function defaultMqttConnect(url, opts) {
@@ -153,6 +153,9 @@ export async function runMeshDaemon(deps = {}) {
153
153
  patchDaemonState(dir, { lastErrorCode: msg.slice(0, 120) }, now);
154
154
  log(dir, `presence error: ${msg.slice(0, 200)}`);
155
155
  },
156
+ onInfo: (message) => {
157
+ log(dir, message.slice(0, 200));
158
+ },
156
159
  };
157
160
  presence = new PresenceClient(presenceOpts);
158
161
  try {
package/dist/main.js CHANGED
@@ -8,60 +8,6 @@ import "./node-preflight.js";
8
8
  import "./node-network-compat.js";
9
9
  import { Command } from "commander";
10
10
  import { initSentry, Sentry } from "./sentry.js";
11
- import { registerAddCommand } from "./commands/add.js";
12
- import { registerSyncCommand } from "./commands/sync.js";
13
- import { registerListCommand } from "./commands/list.js";
14
- import { registerUpdateCommand } from "./commands/update.js";
15
- import { registerCloudCommands } from "./commands/cloud.js";
16
- import { registerSyncModeCommand } from "./commands/sync-mode.js";
17
- import { registerSyncNarrowCommand } from "./commands/sync-narrow.js";
18
- import { registerCloudProvisionCommands } from "./commands/cloud-provision.js";
19
- import { registerCloudDemoteCommands } from "./commands/cloud-demote.js";
20
- import { registerLoginCommand } from "./commands/login.js";
21
- import { registerLogoutCommand } from "./commands/logout.js";
22
- import { registerWhoamiCommand } from "./commands/whoami.js";
23
- import { registerOnboardCommand } from "./commands/onboard.js";
24
- import { registerPackageInstallCommand } from "./commands/pkg-install.js";
25
- import { registerPackageRemoveCommand } from "./commands/pkg-remove.js";
26
- import { registerPackageUpdateCommand } from "./commands/pkg-update.js";
27
- import { registerPackageListCommand } from "./commands/pkg-list.js";
28
- import { registerPacksCommand } from "./commands/packs.js";
29
- import { registerPublishCommand } from "./commands/publish.js";
30
- import { registerCreatorsCommand } from "./commands/creators.js";
31
- import { registerTeamSyncCommand } from "./commands/team-sync.js";
32
- import { registerAuthCommands } from "./commands/auth.js";
33
- import { registerApiKeysCommand } from "./commands/api-keys.js";
34
- import { registerSecretsCommand } from "./commands/secrets.js";
35
- import { registerRunCommand } from "./commands/run.js";
36
- import { registerGroupsCommand } from "./commands/groups.js";
37
- import { registerWorkersCommand } from "./commands/workers.js";
38
- import { registerGroupGrantsCommand } from "./commands/group-grants.js";
39
- import { registerFilesCommand } from "./commands/files.js";
40
- import { registerFilesBrowseCommands } from "./commands/files-browse.js";
41
- import { registerSkillCommand } from "./commands/skill.js";
42
- import { registerMembersCommand } from "./commands/members.js";
43
- import { registerPeopleCommand } from "./commands/people.js";
44
- import { registerDmCommand } from "./commands/dm.js";
45
- import { registerChannelsCommand } from "./commands/channels.js";
46
- import { registerFeedbackCommand } from "./commands/feedback.js";
47
- import { registerMeetingsCommand } from "./commands/meetings.js";
48
- import { registerSourcesCommand } from "./commands/sources.js";
49
- import { registerSignalsCommand } from "./commands/signals.js";
50
- import { registerIntegrationsCommand } from "./commands/integrations.js";
51
- import { registerReindexCommand } from "./commands/reindex.js";
52
- import { registerRescueCommand } from "./commands/rescue.js";
53
- import { registerMcpCommand } from "./commands/mcp-status.js";
54
- import { registerCrmCommand } from "./commands/crm.js";
55
- import { registerCompanyCommand } from "./commands/company.js";
56
- import { registerAgentsCommand } from "./commands/agents.js";
57
- import { registerOutpostsCommand } from "./commands/outposts.js";
58
- import { registerBillingCommand } from "./commands/billing.js";
59
- import { registerDbCommand } from "./commands/db.js";
60
- import { registerCoreCommands } from "./commands/core.js";
61
- import { registerSearchCommand } from "./commands/search.js";
62
- import { registerIndexCommand } from "./commands/index-cmd.js";
63
- import { registerDoctorCommand } from "./commands/doctor.js";
64
- import { registerMeshCommand } from "./commands/mesh.js";
65
11
  import { sanitizeArgv } from "./utils/feedback-diagnostics.js";
66
12
  import { environmentalFsErrorMessage } from "./utils/environmental-error.js";
67
13
  import { syncStateLockMessage } from "./utils/sync-state-lock-error.js";
@@ -92,6 +38,7 @@ import { refreshVersionCache, staleAgainstCachedLatest, } from "./utils/version-
92
38
  import { enforceVersionGate, shouldSkipGate, } from "./utils/version-gate.js";
93
39
  import { autoUpdateAndReexec } from "./utils/self-update.js";
94
40
  import { CLI_VERSION } from "./cli-version.js";
41
+ import { findLazyCommand } from "./lazy-commands.js";
95
42
  import { emitCliSessionStarted } from "./utils/cli-telemetry.js";
96
43
  import { reportCliClientHealthInvocation } from "./utils/client-health.js";
97
44
  import { settleWithin } from "./utils/settle-with-timeout.js";
@@ -148,151 +95,6 @@ program
148
95
  .name("hq")
149
96
  .description("HQ management CLI — modules, packages, and cloud sync")
150
97
  .version(CLI_VERSION);
151
- // Module management subcommand group
152
- const modulesCmd = program
153
- .command("modules")
154
- .description("Module management commands");
155
- registerAddCommand(modulesCmd);
156
- registerSyncCommand(modulesCmd);
157
- registerListCommand(modulesCmd);
158
- registerUpdateCommand(modulesCmd);
159
- // Package management subcommand group
160
- const packagesCmd = program
161
- .command("packages")
162
- .description("Package management commands");
163
- registerPackageInstallCommand(packagesCmd);
164
- registerPackageRemoveCommand(packagesCmd);
165
- registerPackageUpdateCommand(packagesCmd);
166
- registerPackageListCommand(packagesCmd);
167
- // Content-pack lifecycle (core/packages/hq-pack-*). Distinct from the registry
168
- // `packages` system above. Available as both `hq packages packs …` (grouped)
169
- // and `hq packs …` (top-level convenience).
170
- registerPacksCommand(packagesCmd);
171
- registerPacksCommand(program);
172
- // Top-level shortcuts for package commands
173
- // "hq install <slug>" = "hq packages install <slug>"
174
- // "hq remove <slug>" = "hq packages remove <slug>"
175
- registerPackageInstallCommand(program);
176
- registerPackageRemoveCommand(program);
177
- // Marketplace publish (top-level — packer + authenticated upload, US-004)
178
- // "hq publish <skill-or-worker-path>" packages and submits a pack to the
179
- // marketplace via POST /v1/listings.
180
- registerPublishCommand(program);
181
- // `hq creators apply` — request verified-creator access (required to publish).
182
- registerCreatorsCommand(program);
183
- // Cloud sync subcommand group
184
- const syncCmd = program
185
- .command("sync")
186
- .description("Cloud sync commands — sync HQ to S3 for mobile access");
187
- registerCloudCommands(syncCmd);
188
- registerSyncModeCommand(syncCmd);
189
- registerSyncNarrowCommand(syncCmd);
190
- // Cloud provisioning subcommand group (entity + bucket + initial sync)
191
- // Distinct from `hq sync` which assumes provisioning has already happened.
192
- const cloudCmd = program
193
- .command("cloud")
194
- .description("Cloud commands — provision entities and manage cloud-backed companies");
195
- registerCloudProvisionCommands(cloudCmd);
196
- registerCloudDemoteCommands(cloudCmd);
197
- // Team commands (top-level)
198
- registerTeamSyncCommand(program);
199
- // Auth commands (top-level — Cognito OAuth)
200
- registerLoginCommand(program);
201
- registerLogoutCommand(program);
202
- registerWhoamiCommand(program);
203
- registerAuthCommands(program);
204
- // Secrets management (subcommand group — hq secrets set|get|list|delete|exec|generate-link|cache)
205
- registerSecretsCommand(program);
206
- // Vault databases (subcommand group — hq db status|sql|migrate|provision)
207
- registerDbCommand(program);
208
- // API key management (subcommand group — hq api-keys create|list|revoke)
209
- registerApiKeysCommand(program);
210
- // Schema-driven dev runner — hq run [options] -- <cmd>
211
- registerRunCommand(program);
212
- // Groups management (subcommand group — hq groups create|delete|add|remove|list|members)
213
- registerGroupsCommand(program);
214
- // Worker discovery + sharing (subcommand group — hq workers list|share)
215
- registerWorkersCommand(program);
216
- // Cross-company group grants (subcommand group —
217
- // hq group-grants grant|revoke|outbound|inbound)
218
- registerGroupGrantsCommand(program);
219
- // Files ACL management (subcommand group — hq files share|unshare|acl)
220
- // `registerFilesCommand` returns the `files` group so we can attach the
221
- // browse-vs-sync subcommands (`hq files browse`/`cat`) onto the same group.
222
- const filesCmd = registerFilesCommand(program);
223
- registerFilesBrowseCommands(filesCmd);
224
- // Comment-only skill improvement loop. Structured suggestion/review commands are
225
- // intentionally absent; live content changes remain governed by FILE_ACL sync.
226
- registerSkillCommand(program);
227
- // Membership management (subcommand group — hq members invite|list|revoke)
228
- registerMembersCommand(program);
229
- // People directory (subcommand group — hq people list|search|resolve), reading
230
- // the local companies/<co>/people store scoped to one company.
231
- registerPeopleCommand(program);
232
- registerDmCommand(program);
233
- registerChannelsCommand(program);
234
- // Onboarding (top-level — Cognito + vault-service provisioning)
235
- registerOnboardCommand(program);
236
- // Feedback (subcommand group — hq feedback bug|feature)
237
- registerFeedbackCommand(program);
238
- // Meetings (subcommand group — hq meetings list|get|search|transcript|notes)
239
- registerMeetingsCommand(program);
240
- // Sources read surface (subcommand group — hq sources list|get|channels|entities)
241
- registerSourcesCommand(program);
242
- // Signals read surface (subcommand group — hq signals list|get|types|entities)
243
- registerSignalsCommand(program);
244
- // Company-connected apps via the governed integration gateway
245
- // (subcommand group — hq integrations list|tools|call|approve|reject)
246
- registerIntegrationsCommand(program);
247
- // Skill/personal-overlay mirroring + workers-registry regen. Invoked by the
248
- // hq-core reindex hook shim (Stop / PostToolUse) and by sync()/rescue() after
249
- // they change on-disk sources. Keeps a `master-sync` alias for one release.
250
- // Implementation lives in @indigoai-us/hq-cloud.
251
- registerReindexCommand(program);
252
- // Drift-preserving HQ-core re-sync (top-level — `hq rescue`). CLI sibling of
253
- // the HQ Sync app's "Update / Restore" pill; drives the same replace-rescue.sh
254
- // shipped from @indigoai-us/hq-cloud.
255
- registerRescueCommand(program);
256
- // MCP pack observability (subcommand group — `hq mcp status`). Read-only
257
- // provenance-based status across BOTH Claude + Codex runtimes (reads `_hqPack`
258
- // off the configs, NOT linkStatus), with secret-redacted output + `--json`.
259
- registerMcpCommand(program);
260
- // Native CRM entity upsert (subcommand group — `hq crm entity upsert`). Wraps
261
- // POST /crm/entities (the ontology write gate) so an authenticated company
262
- // member can create/update canonical CRM entities in the company vault.
263
- registerCrmCommand(program);
264
- // Company settings (subcommand group — `hq company settings set`). Owner-only
265
- // toggles for crmEnabled / ontologyEnabled via PUT /company-settings.
266
- registerCompanyCommand(program);
267
- // Cloud agent management (subcommand group — `hq agents …`). Rename, reconfigure,
268
- // start/stop, and tear down a company's fleet agents via the hq-pro /v1/agents
269
- // control plane — the same routes the web console's agents panel calls.
270
- registerAgentsCommand(program);
271
- // Personal Outpost management (subcommand group — `hq outposts …`). List, inspect,
272
- // enable Codex on, refresh login for, and destroy your EC2 boxes via the hq-pro
273
- // /outpost/* control plane.
274
- registerOutpostsCommand(program);
275
- // Billing (subcommand group — `hq billing …`). Check subscription/card state and
276
- // mint a shareable Stripe card-capture link — the client side of the paid-
277
- // provisioning gate for agents & Outposts.
278
- registerBillingCommand(program);
279
- // HQ scaffold scripts hosted by the CLI (hidden group — `hq core …`). Not a
280
- // public surface: every entry is invoked by an HQ skill, hook, or forwarder, and
281
- // the source-root entries are maintainer tools that must never touch a live
282
- // install. Registered from a manifest in the module, not wired per script here.
283
- registerCoreCommands(program);
284
- // Local qmd search and index management. Kept distinct from `hq reindex`,
285
- // which converges scaffold-owned files and hooks rather than search data.
286
- registerSearchCommand(program);
287
- registerIndexCommand(program);
288
- // Hook guardrail diagnostics (top-level — `hq doctor`). Read-only, offline
289
- // verification that HQ's hooks are wired and firing, backed by an extensible
290
- // check registry so later check families (vault, sync, MCP, …) plug in without
291
- // engine changes.
292
- registerDoctorCommand(program);
293
- // Work mesh (subcommand group — `hq mesh …`). Native REST + cache. Distinct
294
- // from `hq doctor` (hook guardrails). Does not start MQTT listen.
295
- registerMeshCommand(program);
296
98
  program.hook("preAction", async () => {
297
99
  // Both are best-effort and fully swallowed: neither can change the command's
298
100
  // result or exit code. The 1.2s bound they carry is a TIMER, so it only
@@ -351,6 +153,19 @@ export async function runCli() {
351
153
  }
352
154
  }
353
155
  }
156
+ // Register only what this invocation needs. A hot command named in the
157
+ // lazy manifest imports its own module and nothing else; everything else —
158
+ // `--help`, a bare `hq`, an unknown command, any command not on the
159
+ // manifest — falls back to the complete graph, so its behaviour is
160
+ // unchanged. See register-all.ts for the measurements that motivated this.
161
+ const lazy = findLazyCommand(process.argv);
162
+ if (lazy) {
163
+ await lazy.register(program);
164
+ }
165
+ else {
166
+ const { registerAllCommands } = await import("./register-all.js");
167
+ registerAllCommands(program);
168
+ }
354
169
  await program.parseAsync();
355
170
  }
356
171
  catch (err) {
@@ -0,0 +1,32 @@
1
+ /**
2
+ * The full hq command graph — every `register*Command` call, moved here verbatim
3
+ * from main.ts.
4
+ *
5
+ * WHY IT IS ITS OWN MODULE: these 54 imports pull ~60 command modules and their
6
+ * dependency subtrees, and main.ts used to load all of them at module scope. On
7
+ * an outpost that cost real CPU, because the agent fleet calls `hq secrets`
8
+ * about 39 times a minute and each invocation paid for the entire graph before
9
+ * running one command. Measured on Outpost 2 (i-09424eff61920a4ac), CPU-seconds
10
+ * per process:
11
+ *
12
+ * node -e "" 0.03
13
+ * dist/commands/secrets.js 1.13 <- what `hq secrets` actually needs
14
+ * dist/main.js (full graph) 1.88 <- what it used to pay
15
+ *
16
+ * So ~0.75 CPU-seconds of every `hq secrets` was spent importing commands it
17
+ * never ran. At 39 invocations/minute that is ~0.5 of a core, continuously.
18
+ *
19
+ * Splitting the graph out lets the entrypoint import ONE command module for the
20
+ * hot paths named in lazy-commands.ts, and fall back to this module — the
21
+ * complete, unchanged registration — for everything else: `--help`, a bare
22
+ * `hq`, an unknown command, and every command not in that manifest. Anything
23
+ * not on the manifest therefore behaves exactly as before.
24
+ *
25
+ * Keep this list and lazy-commands.ts in sync through the parity test in
26
+ * lazy-commands.test.ts, which registers both ways and compares the resulting
27
+ * command shapes. Same discipline as commands/scaffold-fast.ts and core.ts.
28
+ */
29
+ import type { Command } from "commander";
30
+ /** Register the complete hq command graph onto `program`. */
31
+ export declare function registerAllCommands(program: Command): void;
32
+ //# sourceMappingURL=register-all.d.ts.map
@@ -0,0 +1,231 @@
1
+ /**
2
+ * The full hq command graph — every `register*Command` call, moved here verbatim
3
+ * from main.ts.
4
+ *
5
+ * WHY IT IS ITS OWN MODULE: these 54 imports pull ~60 command modules and their
6
+ * dependency subtrees, and main.ts used to load all of them at module scope. On
7
+ * an outpost that cost real CPU, because the agent fleet calls `hq secrets`
8
+ * about 39 times a minute and each invocation paid for the entire graph before
9
+ * running one command. Measured on Outpost 2 (i-09424eff61920a4ac), CPU-seconds
10
+ * per process:
11
+ *
12
+ * node -e "" 0.03
13
+ * dist/commands/secrets.js 1.13 <- what `hq secrets` actually needs
14
+ * dist/main.js (full graph) 1.88 <- what it used to pay
15
+ *
16
+ * So ~0.75 CPU-seconds of every `hq secrets` was spent importing commands it
17
+ * never ran. At 39 invocations/minute that is ~0.5 of a core, continuously.
18
+ *
19
+ * Splitting the graph out lets the entrypoint import ONE command module for the
20
+ * hot paths named in lazy-commands.ts, and fall back to this module — the
21
+ * complete, unchanged registration — for everything else: `--help`, a bare
22
+ * `hq`, an unknown command, and every command not in that manifest. Anything
23
+ * not on the manifest therefore behaves exactly as before.
24
+ *
25
+ * Keep this list and lazy-commands.ts in sync through the parity test in
26
+ * lazy-commands.test.ts, which registers both ways and compares the resulting
27
+ * command shapes. Same discipline as commands/scaffold-fast.ts and core.ts.
28
+ */
29
+ import { registerAddCommand } from "./commands/add.js";
30
+ import { registerSyncCommand } from "./commands/sync.js";
31
+ import { registerListCommand } from "./commands/list.js";
32
+ import { registerUpdateCommand } from "./commands/update.js";
33
+ import { registerCloudCommands } from "./commands/cloud.js";
34
+ import { registerSyncModeCommand } from "./commands/sync-mode.js";
35
+ import { registerSyncNarrowCommand } from "./commands/sync-narrow.js";
36
+ import { registerCloudProvisionCommands } from "./commands/cloud-provision.js";
37
+ import { registerCloudDemoteCommands } from "./commands/cloud-demote.js";
38
+ import { registerLoginCommand } from "./commands/login.js";
39
+ import { registerLogoutCommand } from "./commands/logout.js";
40
+ import { registerWhoamiCommand } from "./commands/whoami.js";
41
+ import { registerOnboardCommand } from "./commands/onboard.js";
42
+ import { registerPackageInstallCommand } from "./commands/pkg-install.js";
43
+ import { registerPackageRemoveCommand } from "./commands/pkg-remove.js";
44
+ import { registerPackageUpdateCommand } from "./commands/pkg-update.js";
45
+ import { registerPackageListCommand } from "./commands/pkg-list.js";
46
+ import { registerPacksCommand } from "./commands/packs.js";
47
+ import { registerPublishCommand } from "./commands/publish.js";
48
+ import { registerCreatorsCommand } from "./commands/creators.js";
49
+ import { registerTeamSyncCommand } from "./commands/team-sync.js";
50
+ import { registerAuthCommands } from "./commands/auth.js";
51
+ import { registerApiKeysCommand } from "./commands/api-keys.js";
52
+ import { registerSecretsCommand } from "./commands/secrets.js";
53
+ import { registerRunCommand } from "./commands/run.js";
54
+ import { registerGroupsCommand } from "./commands/groups.js";
55
+ import { registerWorkersCommand } from "./commands/workers.js";
56
+ import { registerGroupGrantsCommand } from "./commands/group-grants.js";
57
+ import { registerFilesCommand } from "./commands/files.js";
58
+ import { registerFilesBrowseCommands } from "./commands/files-browse.js";
59
+ import { registerSkillCommand } from "./commands/skill.js";
60
+ import { registerMembersCommand } from "./commands/members.js";
61
+ import { registerPeopleCommand } from "./commands/people.js";
62
+ import { registerDmCommand } from "./commands/dm.js";
63
+ import { registerChannelsCommand } from "./commands/channels.js";
64
+ import { registerFeedbackCommand } from "./commands/feedback.js";
65
+ import { registerMeetingsCommand } from "./commands/meetings.js";
66
+ import { registerSourcesCommand } from "./commands/sources.js";
67
+ import { registerSignalsCommand } from "./commands/signals.js";
68
+ import { registerIntegrationsCommand } from "./commands/integrations.js";
69
+ import { registerReindexCommand } from "./commands/reindex.js";
70
+ import { registerRescueCommand } from "./commands/rescue.js";
71
+ import { registerMcpCommand } from "./commands/mcp-status.js";
72
+ import { registerCrmCommand } from "./commands/crm.js";
73
+ import { registerCompanyCommand } from "./commands/company.js";
74
+ import { registerAgentsCommand } from "./commands/agents.js";
75
+ import { registerOutpostsCommand } from "./commands/outposts.js";
76
+ import { registerBillingCommand } from "./commands/billing.js";
77
+ import { registerDbCommand } from "./commands/db.js";
78
+ import { registerCoreCommands } from "./commands/core.js";
79
+ import { registerSearchCommand } from "./commands/search.js";
80
+ import { registerIndexCommand } from "./commands/index-cmd.js";
81
+ import { registerDoctorCommand } from "./commands/doctor.js";
82
+ import { registerMeshCommand } from "./commands/mesh.js";
83
+ /** Register the complete hq command graph onto `program`. */
84
+ export function registerAllCommands(program) {
85
+ // Module management subcommand group
86
+ const modulesCmd = program
87
+ .command("modules")
88
+ .description("Module management commands");
89
+ registerAddCommand(modulesCmd);
90
+ registerSyncCommand(modulesCmd);
91
+ registerListCommand(modulesCmd);
92
+ registerUpdateCommand(modulesCmd);
93
+ // Package management subcommand group
94
+ const packagesCmd = program
95
+ .command("packages")
96
+ .description("Package management commands");
97
+ registerPackageInstallCommand(packagesCmd);
98
+ registerPackageRemoveCommand(packagesCmd);
99
+ registerPackageUpdateCommand(packagesCmd);
100
+ registerPackageListCommand(packagesCmd);
101
+ // Content-pack lifecycle (core/packages/hq-pack-*). Distinct from the registry
102
+ // `packages` system above. Available as both `hq packages packs …` (grouped)
103
+ // and `hq packs …` (top-level convenience).
104
+ registerPacksCommand(packagesCmd);
105
+ registerPacksCommand(program);
106
+ // Top-level shortcuts for package commands
107
+ // "hq install <slug>" = "hq packages install <slug>"
108
+ // "hq remove <slug>" = "hq packages remove <slug>"
109
+ registerPackageInstallCommand(program);
110
+ registerPackageRemoveCommand(program);
111
+ // Marketplace publish (top-level — packer + authenticated upload, US-004)
112
+ // "hq publish <skill-or-worker-path>" packages and submits a pack to the
113
+ // marketplace via POST /v1/listings.
114
+ registerPublishCommand(program);
115
+ // `hq creators apply` — request verified-creator access (required to publish).
116
+ registerCreatorsCommand(program);
117
+ // Cloud sync subcommand group
118
+ const syncCmd = program
119
+ .command("sync")
120
+ .description("Cloud sync commands — sync HQ to S3 for mobile access");
121
+ registerCloudCommands(syncCmd);
122
+ registerSyncModeCommand(syncCmd);
123
+ registerSyncNarrowCommand(syncCmd);
124
+ // Cloud provisioning subcommand group (entity + bucket + initial sync)
125
+ // Distinct from `hq sync` which assumes provisioning has already happened.
126
+ const cloudCmd = program
127
+ .command("cloud")
128
+ .description("Cloud commands — provision entities and manage cloud-backed companies");
129
+ registerCloudProvisionCommands(cloudCmd);
130
+ registerCloudDemoteCommands(cloudCmd);
131
+ // Team commands (top-level)
132
+ registerTeamSyncCommand(program);
133
+ // Auth commands (top-level — Cognito OAuth)
134
+ registerLoginCommand(program);
135
+ registerLogoutCommand(program);
136
+ registerWhoamiCommand(program);
137
+ registerAuthCommands(program);
138
+ // Secrets management (subcommand group — hq secrets set|get|list|delete|exec|generate-link|cache)
139
+ registerSecretsCommand(program);
140
+ // Vault databases (subcommand group — hq db status|sql|migrate|provision)
141
+ registerDbCommand(program);
142
+ // API key management (subcommand group — hq api-keys create|list|revoke)
143
+ registerApiKeysCommand(program);
144
+ // Schema-driven dev runner — hq run [options] -- <cmd>
145
+ registerRunCommand(program);
146
+ // Groups management (subcommand group — hq groups create|delete|add|remove|list|members)
147
+ registerGroupsCommand(program);
148
+ // Worker discovery + sharing (subcommand group — hq workers list|share)
149
+ registerWorkersCommand(program);
150
+ // Cross-company group grants (subcommand group —
151
+ // hq group-grants grant|revoke|outbound|inbound)
152
+ registerGroupGrantsCommand(program);
153
+ // Files ACL management (subcommand group — hq files share|unshare|acl)
154
+ // `registerFilesCommand` returns the `files` group so we can attach the
155
+ // browse-vs-sync subcommands (`hq files browse`/`cat`) onto the same group.
156
+ const filesCmd = registerFilesCommand(program);
157
+ registerFilesBrowseCommands(filesCmd);
158
+ // Comment-only skill improvement loop. Structured suggestion/review commands are
159
+ // intentionally absent; live content changes remain governed by FILE_ACL sync.
160
+ registerSkillCommand(program);
161
+ // Membership management (subcommand group — hq members invite|list|revoke)
162
+ registerMembersCommand(program);
163
+ // People directory (subcommand group — hq people list|search|resolve), reading
164
+ // the local companies/<co>/people store scoped to one company.
165
+ registerPeopleCommand(program);
166
+ registerDmCommand(program);
167
+ registerChannelsCommand(program);
168
+ // Onboarding (top-level — Cognito + vault-service provisioning)
169
+ registerOnboardCommand(program);
170
+ // Feedback (subcommand group — hq feedback bug|feature)
171
+ registerFeedbackCommand(program);
172
+ // Meetings (subcommand group — hq meetings list|get|search|transcript|notes)
173
+ registerMeetingsCommand(program);
174
+ // Sources read surface (subcommand group — hq sources list|get|channels|entities)
175
+ registerSourcesCommand(program);
176
+ // Signals read surface (subcommand group — hq signals list|get|types|entities)
177
+ registerSignalsCommand(program);
178
+ // Company-connected apps via the governed integration gateway
179
+ // (subcommand group — hq integrations list|tools|call|approve|reject)
180
+ registerIntegrationsCommand(program);
181
+ // Skill/personal-overlay mirroring + workers-registry regen. Invoked by the
182
+ // hq-core reindex hook shim (Stop / PostToolUse) and by sync()/rescue() after
183
+ // they change on-disk sources. Keeps a `master-sync` alias for one release.
184
+ // Implementation lives in @indigoai-us/hq-cloud.
185
+ registerReindexCommand(program);
186
+ // Drift-preserving HQ-core re-sync (top-level — `hq rescue`). CLI sibling of
187
+ // the HQ Sync app's "Update / Restore" pill; drives the same replace-rescue.sh
188
+ // shipped from @indigoai-us/hq-cloud.
189
+ registerRescueCommand(program);
190
+ // MCP pack observability (subcommand group — `hq mcp status`). Read-only
191
+ // provenance-based status across BOTH Claude + Codex runtimes (reads `_hqPack`
192
+ // off the configs, NOT linkStatus), with secret-redacted output + `--json`.
193
+ registerMcpCommand(program);
194
+ // Native CRM entity upsert (subcommand group — `hq crm entity upsert`). Wraps
195
+ // POST /crm/entities (the ontology write gate) so an authenticated company
196
+ // member can create/update canonical CRM entities in the company vault.
197
+ registerCrmCommand(program);
198
+ // Company settings (subcommand group — `hq company settings set`). Owner-only
199
+ // toggles for crmEnabled / ontologyEnabled via PUT /company-settings.
200
+ registerCompanyCommand(program);
201
+ // Cloud agent management (subcommand group — `hq agents …`). Rename, reconfigure,
202
+ // start/stop, and tear down a company's fleet agents via the hq-pro /v1/agents
203
+ // control plane — the same routes the web console's agents panel calls.
204
+ registerAgentsCommand(program);
205
+ // Personal Outpost management (subcommand group — `hq outposts …`). List, inspect,
206
+ // enable Codex on, refresh login for, and destroy your EC2 boxes via the hq-pro
207
+ // /outpost/* control plane.
208
+ registerOutpostsCommand(program);
209
+ // Billing (subcommand group — `hq billing …`). Check subscription/card state and
210
+ // mint a shareable Stripe card-capture link — the client side of the paid-
211
+ // provisioning gate for agents & Outposts.
212
+ registerBillingCommand(program);
213
+ // HQ scaffold scripts hosted by the CLI (hidden group — `hq core …`). Not a
214
+ // public surface: every entry is invoked by an HQ skill, hook, or forwarder, and
215
+ // the source-root entries are maintainer tools that must never touch a live
216
+ // install. Registered from a manifest in the module, not wired per script here.
217
+ registerCoreCommands(program);
218
+ // Local qmd search and index management. Kept distinct from `hq reindex`,
219
+ // which converges scaffold-owned files and hooks rather than search data.
220
+ registerSearchCommand(program);
221
+ registerIndexCommand(program);
222
+ // Hook guardrail diagnostics (top-level — `hq doctor`). Read-only, offline
223
+ // verification that HQ's hooks are wired and firing, backed by an extensible
224
+ // check registry so later check families (vault, sync, MCP, …) plug in without
225
+ // engine changes.
226
+ registerDoctorCommand(program);
227
+ // Work mesh (subcommand group — `hq mesh …`). Native REST + cache. Distinct
228
+ // from `hq doctor` (hook guardrails). Does not start MQTT listen.
229
+ registerMeshCommand(program);
230
+ }
231
+ //# sourceMappingURL=register-all.js.map
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@indigoai-us/hq-cli",
3
- "version": "5.108.8",
3
+ "version": "5.108.10",
4
4
  "description": "HQ by Indigo management CLI — modules and cloud sync",
5
5
  "main": "dist/index.js",
6
6
  "bin": {