@indigoai-us/hq-cli 5.328.0 → 5.330.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (54) hide show
  1. package/CHANGELOG.md +13 -0
  2. package/dist/command-catalog.generated.d.ts +3699 -3
  3. package/dist/command-catalog.generated.js +5462 -1759
  4. package/dist/command-help.d.ts +2 -0
  5. package/dist/command-help.js +13 -0
  6. package/dist/commands/cloud.d.ts +5 -0
  7. package/dist/commands/cloud.js +51 -1
  8. package/dist/commands/install-global.d.ts +21 -0
  9. package/dist/commands/install-global.js +267 -32
  10. package/dist/commands/lanes-message.js +59 -6
  11. package/dist/commands/lanes-resume.js +66 -15
  12. package/dist/commands/lanes-shared.d.ts +9 -2
  13. package/dist/commands/lanes.js +97 -8
  14. package/dist/commands/mcp-status.js +58 -1
  15. package/dist/commands/pack-install.d.ts +3 -1
  16. package/dist/commands/pack-install.js +17 -4
  17. package/dist/commands/packs.js +17 -0
  18. package/dist/commands/pkg-install.js +13 -1
  19. package/dist/fast-scaffold.d.ts +2 -0
  20. package/dist/fast-scaffold.js +16 -0
  21. package/dist/index.js +19 -2
  22. package/dist/lazy-commands.d.ts +1 -0
  23. package/dist/lazy-commands.js +64 -4
  24. package/dist/lib/cli-flag-keys.d.ts +3 -0
  25. package/dist/lib/cli-flag-keys.js +3 -0
  26. package/dist/lib/daemon/ops/mcp.js +11 -10
  27. package/dist/lib/daemon/ops/policy.d.ts +3 -2
  28. package/dist/lib/daemon/ops/policy.js +12 -6
  29. package/dist/lib/daemon/server.d.ts +3 -1
  30. package/dist/lib/daemon/server.js +63 -7
  31. package/dist/lib/doctor/checks/anywhere.js +58 -4
  32. package/dist/lib/install/chatgpt-targets.d.ts +8 -0
  33. package/dist/lib/install/chatgpt-targets.js +15 -0
  34. package/dist/lib/install/codex-pack-contributions.d.ts +8 -0
  35. package/dist/lib/install/codex-pack-contributions.js +205 -0
  36. package/dist/lib/install/codex-targets.d.ts +1 -1
  37. package/dist/lib/install/codex-targets.js +2 -1
  38. package/dist/lib/install/grok-targets.d.ts +8 -0
  39. package/dist/lib/install/grok-targets.js +15 -0
  40. package/dist/lib/lanes/flag-keys.d.ts +2 -0
  41. package/dist/lib/lanes/flag-keys.js +2 -0
  42. package/dist/lib/lanes/keep-alive-audit.d.ts +3 -2
  43. package/dist/lib/lanes/keep-alive-audit.js +36 -8
  44. package/dist/lib/lanes/spawn.js +14 -10
  45. package/dist/lib/lanes/store.js +29 -2
  46. package/dist/lib/lanes/types.d.ts +10 -0
  47. package/dist/lib/lanes/types.js +1 -1
  48. package/dist/lib/sync-invalid-entity-warning.d.ts +32 -0
  49. package/dist/lib/sync-invalid-entity-warning.js +205 -0
  50. package/dist/main.d.ts +2 -6
  51. package/dist/main.js +3 -41
  52. package/dist/stream-error-boundary.d.ts +7 -0
  53. package/dist/stream-error-boundary.js +33 -0
  54. package/package.json +1 -1
@@ -312,6 +312,10 @@ export interface LaneStopHistoryEntry {
312
312
  export interface LaneSettingsHistoryEntry {
313
313
  at: string;
314
314
  actor: string;
315
+ provider?: {
316
+ from: LaneProvider;
317
+ to: LaneProvider;
318
+ };
315
319
  model?: {
316
320
  from: string | null;
317
321
  to: string;
@@ -578,7 +582,13 @@ export interface LaneRecord {
578
582
  /** How policy files were delivered on the most recent profile spawn. */
579
583
  worker_policy_delivery?: "hook" | "prompt";
580
584
  provider: LaneProvider;
585
+ /** Whether provider came from an explicit create override or the worker profile/default. */
586
+ provider_source?: "cli" | "profile" | "default";
581
587
  account?: string;
588
+ /** Explicit create account choices remain pinned during profile refreshes. */
589
+ account_source?: "cli" | "profile" | "balanced";
590
+ /** Reuse snapshots the worker profile's resolved settings for the new story. */
591
+ profile_settings_pinned?: boolean;
582
592
  /** Recent Claude account-selection evidence for a create or automatic resume move. */
583
593
  account_selection?: LaneAccountSelection;
584
594
  /** Eligible accounts for a queue entry created without an explicit account. */
@@ -231,7 +231,7 @@ export const LANE_FIELD_WRITERS = {
231
231
  pickup_validation_pending_since: ["creator", "command", "spawn", "reconcile"],
232
232
  priority: ["command"], worker: ["creator", "command"], worker_path: ["creator", "command"], worker_pack: ["creator", "command"], worker_profile: ["creator", "command"], worker_profile_hash: ["creator", "command", "spawn"], registry_worker: ["creator", "command", "spawn"], registry_worker_approval_history: ["command", "watcher", "reconcile"], worker_profile_repin_history: ["command"], worker_profile_ignored: ["creator", "command", "spawn"], worker_policy_delivery: ["command", "spawn"],
233
233
  keep_alive: ["creator", "command"], keep_alive_standing: ["command"], keep_alive_opted_in_at: ["creator", "command"], keep_alive_blocked_reason: ["command", "reconcile"], keep_alive_resume_count: ["command", "reconcile"], keep_alive_history: ["reconcile"], keep_alive_blocked_resume_streak: ["reconcile", "spawn", "watcher"], keep_alive_last_resume_at: ["command", "reconcile"], keep_alive_next_eligible_at: ["command", "reconcile"], keep_alive_exhausted_at: ["command", "reconcile"],
234
- provider: ["creator"], account: ["creator", "spawn", "command", "watcher", "reconcile"], account_selection: ["creator", "command"], admission_accounts: ["creator", "command"], account_history: ["command", "spawn", "watcher", "reconcile"], failed_resume_history: ["command"], fallback_question_cancellation_failures: ["command"], pending_resume: ["command", "spawn", "watcher", "reconcile"], placement_default: ["creator"], host: ["creator", "command"], origin_host: ["creator"], origin_host_source: ["creator"],
234
+ provider: ["creator", "command"], provider_source: ["creator", "command"], account: ["creator", "spawn", "command", "watcher", "reconcile"], account_source: ["creator", "command"], profile_settings_pinned: ["command"], account_selection: ["creator", "command"], admission_accounts: ["creator", "command"], account_history: ["command", "spawn", "watcher", "reconcile"], failed_resume_history: ["command"], fallback_question_cancellation_failures: ["command"], pending_resume: ["command", "spawn", "watcher", "reconcile"], placement_default: ["creator"], host: ["creator", "command"], origin_host: ["creator"], origin_host_source: ["creator"],
235
235
  version: ["creator"], created_at: ["creator"], updated_at: ["creator"], ended_at: ["reconcile"], envelope_summary: ["reconcile"], attribution_history: ["command", "watcher", "reconcile"], story_switch_pending: ["command", "watcher", "reconcile"], story_switch_unacknowledged: ["watcher", "reconcile"],
236
236
  last_envelope: ["watcher", "reconcile"], model: ["creator", "spawn", "command"], model_source: ["creator", "spawn", "command"], effort: ["creator", "spawn", "command"], effort_source: ["creator", "spawn", "command"], fast_mode: ["spawn"],
237
237
  last_message_at: ["command"], status_gate: ["spawn", "watcher"], status_gate_history: ["watcher", "reconcile"], blocker_freshness_state: ["watcher"], kr_freshness_state: ["watcher"], last_compaction: ["spawn"], story_complete: ["watcher", "reconcile"], story_complete_reason: ["watcher", "reconcile"], story_status: ["watcher", "reconcile"],
@@ -0,0 +1,32 @@
1
+ import type { FlagClient, FlagClientOptions } from "@indigoai-us/hq-flags-client";
2
+ export declare const SYNC_INVALID_ENTITY_WARNING_FLAG = "sync.warn-invalid-ontology-entities";
3
+ export declare const SYNC_INVALID_ENTITY_WARNING_FALLBACK = false;
4
+ export declare const SYNC_INVALID_ENTITY_WARNING_TIMEOUT_MS = 1500;
5
+ type FlagClientLike = Pick<FlagClient, "ready" | "isEnabled" | "close">;
6
+ export interface InvalidEntityFrontmatter {
7
+ path: string;
8
+ fields: string[];
9
+ }
10
+ export interface SyncInvalidEntityWarningDependencies {
11
+ env?: NodeJS.ProcessEnv;
12
+ createClient?: (options: FlagClientOptions) => FlagClientLike;
13
+ idToken?: string;
14
+ writeLine?: (line: string) => void;
15
+ }
16
+ /** Check only the entity directory that this sync push will upload. */
17
+ export declare function findInvalidOntologyEntities(args: {
18
+ companyRoot: string;
19
+ hqRoot: string;
20
+ }): InvalidEntityFrontmatter[];
21
+ export declare function companySlugFromSyncPaths(hqRoot: string, syncPaths: string[]): string | undefined;
22
+ /** Warns by path and field names only. Any diagnostic failure leaves sync running. */
23
+ export declare function warnAboutInvalidOntologyEntities(args: {
24
+ hqRoot: string;
25
+ companyRoot: string;
26
+ companyUid: string;
27
+ companySlug: string;
28
+ idToken?: string;
29
+ dependencies?: Omit<SyncInvalidEntityWarningDependencies, "idToken">;
30
+ }): Promise<void>;
31
+ export {};
32
+ //# sourceMappingURL=sync-invalid-entity-warning.d.ts.map
@@ -0,0 +1,205 @@
1
+ import { createFlagClient, readLocalOverride } from "@indigoai-us/hq-flags-client";
2
+ import { createHash } from "node:crypto";
3
+ import * as fs from "node:fs";
4
+ import * as path from "node:path";
5
+ import * as yaml from "js-yaml";
6
+ import { resolveFlagRegistryEndpoint } from "./flag-registry-endpoint.js";
7
+ export const SYNC_INVALID_ENTITY_WARNING_FLAG = "sync.warn-invalid-ontology-entities";
8
+ export const SYNC_INVALID_ENTITY_WARNING_FALLBACK = false;
9
+ export const SYNC_INVALID_ENTITY_WARNING_TIMEOUT_MS = 1500;
10
+ const ENTITY_TYPES = new Set([
11
+ "person", "project", "company", "concept", "product", "tool",
12
+ "tool/technology", "model", "platform", "contact", "deal", "contract",
13
+ "invoice", "account", "journal-entry", "ar-invoice", "ap-bill",
14
+ ]);
15
+ const REQUIRED_FIELDS = [
16
+ "id", "type", "canonical_name", "confidence", "status",
17
+ "domain", "first_seen", "last_updated", "signal_count",
18
+ ];
19
+ function isRecord(value) {
20
+ return typeof value === "object" && value !== null && !Array.isArray(value);
21
+ }
22
+ function validRequiredFields(frontmatter) {
23
+ const invalid = new Set();
24
+ for (const field of REQUIRED_FIELDS) {
25
+ if (!(field in frontmatter))
26
+ invalid.add(field);
27
+ }
28
+ if (typeof frontmatter.id !== "string" || frontmatter.id.length === 0) {
29
+ invalid.add("id");
30
+ }
31
+ if (typeof frontmatter.type !== "string" || !ENTITY_TYPES.has(frontmatter.type)) {
32
+ invalid.add("type");
33
+ }
34
+ if (typeof frontmatter.canonical_name !== "string" ||
35
+ frontmatter.canonical_name.length === 0) {
36
+ invalid.add("canonical_name");
37
+ }
38
+ if ("aliases" in frontmatter &&
39
+ (!Array.isArray(frontmatter.aliases) ||
40
+ !frontmatter.aliases.every((value) => typeof value === "string"))) {
41
+ invalid.add("aliases");
42
+ }
43
+ if (typeof frontmatter.confidence !== "number" ||
44
+ !Number.isFinite(frontmatter.confidence) ||
45
+ frontmatter.confidence < 0 ||
46
+ frontmatter.confidence > 1) {
47
+ invalid.add("confidence");
48
+ }
49
+ if (frontmatter.status !== "draft" && frontmatter.status !== "confirmed") {
50
+ invalid.add("status");
51
+ }
52
+ if (!Array.isArray(frontmatter.domain) ||
53
+ !frontmatter.domain.every((value) => typeof value === "string")) {
54
+ invalid.add("domain");
55
+ }
56
+ if (typeof frontmatter.first_seen !== "string" || frontmatter.first_seen.length === 0) {
57
+ invalid.add("first_seen");
58
+ }
59
+ if (typeof frontmatter.last_updated !== "string" ||
60
+ frontmatter.last_updated.length === 0) {
61
+ invalid.add("last_updated");
62
+ }
63
+ if (typeof frontmatter.signal_count !== "number" ||
64
+ !Number.isInteger(frontmatter.signal_count) ||
65
+ frontmatter.signal_count < 0) {
66
+ invalid.add("signal_count");
67
+ }
68
+ if (invalid.size === 0 &&
69
+ frontmatter.identity_version !== 2 &&
70
+ frontmatter.id !== createHash("sha256")
71
+ .update(`${frontmatter.type}:${frontmatter.canonical_name}`)
72
+ .digest("hex")) {
73
+ invalid.add("id");
74
+ }
75
+ return [...invalid].sort();
76
+ }
77
+ function parseInvalidEntityFile(filePath, displayPath) {
78
+ try {
79
+ const content = fs.readFileSync(filePath, "utf8");
80
+ const match = content.match(/^---\r?\n([\s\S]*?)\r?\n---(?:\r?\n|$)/);
81
+ if (!match)
82
+ return { path: displayPath, fields: ["_frontmatter"] };
83
+ // Match the ontology reader's YAML parser semantics: ISO date scalars stay
84
+ // strings so Zod's `z.string()` fields are checked as the server checks them.
85
+ const parsed = yaml.load(match[1], { schema: yaml.CORE_SCHEMA });
86
+ if (!isRecord(parsed))
87
+ return { path: displayPath, fields: ["_root"] };
88
+ const fields = validRequiredFields(parsed);
89
+ return fields.length ? { path: displayPath, fields } : null;
90
+ }
91
+ catch {
92
+ // YAML parser errors can embed user content; report the location only.
93
+ return { path: displayPath, fields: ["_frontmatter"] };
94
+ }
95
+ }
96
+ /** Check only the entity directory that this sync push will upload. */
97
+ export function findInvalidOntologyEntities(args) {
98
+ const entityRoot = path.join(args.companyRoot, "ontology", "entities");
99
+ if (!fs.existsSync(entityRoot))
100
+ return [];
101
+ const found = [];
102
+ const typeEntries = fs.readdirSync(entityRoot, { withFileTypes: true });
103
+ for (const typeEntry of typeEntries) {
104
+ if (!typeEntry.isDirectory())
105
+ continue;
106
+ const typeRoot = path.join(entityRoot, typeEntry.name);
107
+ for (const entityEntry of fs.readdirSync(typeRoot, { withFileTypes: true })) {
108
+ if (!entityEntry.isFile() || !entityEntry.name.endsWith(".md"))
109
+ continue;
110
+ const filePath = path.join(typeRoot, entityEntry.name);
111
+ const relativePath = path.relative(args.hqRoot, filePath).split(path.sep).join("/");
112
+ const invalid = parseInvalidEntityFile(filePath, relativePath);
113
+ if (invalid)
114
+ found.push(invalid);
115
+ }
116
+ }
117
+ return found.sort((a, b) => a.path.localeCompare(b.path));
118
+ }
119
+ export function companySlugFromSyncPaths(hqRoot, syncPaths) {
120
+ const companiesRoot = path.resolve(hqRoot, "companies");
121
+ for (const syncPath of syncPaths) {
122
+ const relative = path.relative(companiesRoot, path.resolve(syncPath));
123
+ if (!relative || relative.startsWith(`..${path.sep}`) || path.isAbsolute(relative))
124
+ continue;
125
+ const slug = relative.split(path.sep)[0];
126
+ if (slug && slug !== ".")
127
+ return slug;
128
+ }
129
+ return undefined;
130
+ }
131
+ function safeErrorClass(error) {
132
+ const name = error instanceof Error ? error.name : "UnknownError";
133
+ return /^[A-Za-z][A-Za-z0-9]{0,63}$/.test(name) ? name : "UnknownError";
134
+ }
135
+ async function warningEnabled(args) {
136
+ const env = args.dependencies.env ?? process.env;
137
+ const override = readLocalOverride(SYNC_INVALID_ENTITY_WARNING_FLAG, env);
138
+ if (override !== null)
139
+ return override;
140
+ const companyUid = args.companyUid || env.HQ_COMPANY_UID?.trim() || "";
141
+ const companySlug = args.companySlug || env.HQ_COMPANY_SLUG?.trim() || "";
142
+ if (!/^cmp_[A-Za-z0-9]{3,128}$/.test(companyUid)) {
143
+ return SYNC_INVALID_ENTITY_WARNING_FALLBACK;
144
+ }
145
+ const token = args.dependencies.idToken ?? "";
146
+ if (!token)
147
+ return SYNC_INVALID_ENTITY_WARNING_FALLBACK;
148
+ let client;
149
+ let failed = false;
150
+ try {
151
+ client = (args.dependencies.createClient ?? createFlagClient)({
152
+ endpoint: resolveFlagRegistryEndpoint(undefined, env),
153
+ companyUid,
154
+ ...(/^[A-Za-z0-9][A-Za-z0-9-]{0,63}$/.test(companySlug)
155
+ ? { companyIdentifiers: [companySlug] }
156
+ : {}),
157
+ env: {},
158
+ getToken: () => token,
159
+ refreshIntervalMs: 0,
160
+ requestTimeoutMs: SYNC_INVALID_ENTITY_WARNING_TIMEOUT_MS,
161
+ onError: (error) => {
162
+ failed = true;
163
+ (args.dependencies.writeLine ?? ((line) => process.stderr.write(line)))(`HQ Sync entity-warning flag lookup failed (${safeErrorClass(error)}); defaulting off.\n`);
164
+ },
165
+ });
166
+ await client.ready();
167
+ return !failed && client.isEnabled(SYNC_INVALID_ENTITY_WARNING_FLAG, {
168
+ fallback: SYNC_INVALID_ENTITY_WARNING_FALLBACK,
169
+ });
170
+ }
171
+ catch (error) {
172
+ (args.dependencies.writeLine ?? ((line) => process.stderr.write(line)))(`HQ Sync entity-warning flag lookup failed (${safeErrorClass(error)}); defaulting off.\n`);
173
+ return SYNC_INVALID_ENTITY_WARNING_FALLBACK;
174
+ }
175
+ finally {
176
+ client?.close();
177
+ }
178
+ }
179
+ /** Warns by path and field names only. Any diagnostic failure leaves sync running. */
180
+ export async function warnAboutInvalidOntologyEntities(args) {
181
+ try {
182
+ const enabled = await warningEnabled({
183
+ companyUid: args.companyUid,
184
+ companySlug: args.companySlug,
185
+ dependencies: { ...args.dependencies, idToken: args.idToken },
186
+ });
187
+ if (!enabled)
188
+ return;
189
+ const invalid = findInvalidOntologyEntities(args);
190
+ const writeLine = args.dependencies?.writeLine ?? ((line) => process.stderr.write(line));
191
+ for (const item of invalid) {
192
+ writeLine(`HQ Sync warning: ${item.path} has frontmatter rejected by ontology matching: ${item.fields.join(", ")}. Sync will continue and leave the file unchanged. Fix the entity if you want it included in ontology results.\n`);
193
+ }
194
+ }
195
+ catch (error) {
196
+ const writeLine = args.dependencies?.writeLine ?? ((line) => process.stderr.write(line));
197
+ try {
198
+ writeLine(`HQ Sync entity diagnostic failed (${safeErrorClass(error)}); sync will continue.\n`);
199
+ }
200
+ catch {
201
+ // A closed stderr must not prevent the upload from preserving user data.
202
+ }
203
+ }
204
+ }
205
+ //# sourceMappingURL=sync-invalid-entity-warning.js.map
package/dist/main.d.ts CHANGED
@@ -5,6 +5,8 @@
5
5
  import "./node-preflight.js";
6
6
  import "./node-network-compat.js";
7
7
  import { Sentry } from "./sentry.js";
8
+ export { handleStreamError } from "./stream-error-boundary.js";
9
+ export type { StreamErrorDependencies } from "./stream-error-boundary.js";
8
10
  import { upgradeToTeam } from "./utils/team-upgrade.js";
9
11
  /**
10
12
  * Total time one CLI invocation may await foreground network work. This spans
@@ -31,11 +33,6 @@ export declare function createForegroundNetworkBudget(options?: {
31
33
  budgetMs?: number;
32
34
  now?: () => number;
33
35
  }): ForegroundNetworkBudget;
34
- export type StreamErrorDependencies = {
35
- stderr: Pick<typeof process.stderr, "write">;
36
- exit: (code: number) => void;
37
- };
38
- export declare function handleStreamError(err: NodeJS.ErrnoException, deps?: StreamErrorDependencies): void;
39
36
  export declare function runCli(): Promise<void>;
40
37
  export type TopLevelErrorDependencies = {
41
38
  sentry: Pick<typeof Sentry, "captureException">;
@@ -49,5 +46,4 @@ export type TopLevelErrorDependencies = {
49
46
  };
50
47
  /** Classify a top-level failure without making the CLI process boundary opaque to tests. */
51
48
  export declare function handleTopLevelError(err: unknown, deps?: TopLevelErrorDependencies): Promise<void>;
52
- export {};
53
49
  //# sourceMappingURL=main.d.ts.map
package/dist/main.js CHANGED
@@ -31,6 +31,8 @@ import { incompleteInstallMessage, incompleteInstallCaptureContext, } from "./ut
31
31
  import { isExpectedUserError } from "./utils/expected-cli-error.js";
32
32
  import { isVarlockEnvError } from "./run/env-graph-guard.js";
33
33
  import { isEpipe } from "./utils/epipe.js";
34
+ import { installStreamErrorBoundary } from "./stream-error-boundary.js";
35
+ export { handleStreamError } from "./stream-error-boundary.js";
34
36
  import { isInterceptedProcessExit } from "./utils/intercepted-process-exit.js";
35
37
  import { isAuthError } from "./utils/auth-error.js";
36
38
  import { browserLoginAbandonedMessage } from "./utils/browser-login-abandoned.js";
@@ -123,47 +125,7 @@ let activeForegroundNetworkBudget;
123
125
  * the rewrite window, while this path now holds the settled tree.
124
126
  */
125
127
  const RUNNING_ENTRY_PATH = path.join(path.dirname(fileURLToPath(import.meta.url)), "index.js");
126
- const defaultStreamErrorDependencies = {
127
- stderr: process.stderr,
128
- exit: (code) => process.exit(code),
129
- };
130
- // Handle an 'error' event on process.stdout/stderr without turning a benign
131
- // machine-state condition into a Sentry fatal.
132
- // - EPIPE: a downstream reader closed the pipe early (`hq … | head`,
133
- // `source <(hq …)`, a parent that exited) — a clean exit 0, no capture. The
134
- // SYNCHRONOUS path (a `write EPIPE` thrown straight out of console.log) is
135
- // handled in the top-level catch below; both share `isEpipe` (HQ-6B).
136
- // - ENOSPC / EDQUOT / EROFS: a full disk, exhausted quota, or read-only mount
137
- // (e.g. stdout redirected to a file that fills up) — the user's machine,
138
- // not an hq-cli defect. Print one actionable line (best-effort; the same
139
- // disk may reject it) and exit 1 WITHOUT rethrowing, so it never becomes an
140
- // uncaughtException that ships a fatal (HQ-CLI-R). sentry.ts's
141
- // path-independent beforeSend also drops it, covering the buffered-write
142
- // route that never surfaces here.
143
- // - Anything else: rethrow — an unknown stream failure must stay loud.
144
- // Exported in dependency-injectable form (mirroring handleTopLevelError) so the
145
- // process boundary stays unit-testable through the ./main.js import seam.
146
- export function handleStreamError(err, deps = defaultStreamErrorDependencies) {
147
- if (isEpipe(err)) {
148
- deps.exit(0);
149
- return;
150
- }
151
- const envMessage = environmentalFsErrorMessage(err);
152
- if (envMessage) {
153
- try {
154
- deps.stderr.write(`hq: ${envMessage}\n`);
155
- }
156
- catch {
157
- // The same full disk / read-only mount can reject this write too; there
158
- // is nothing more to surface, but we still exit non-zero below.
159
- }
160
- deps.exit(1);
161
- return;
162
- }
163
- throw err;
164
- }
165
- process.stdout.on("error", (err) => handleStreamError(err));
166
- process.stderr.on("error", (err) => handleStreamError(err));
128
+ installStreamErrorBoundary();
167
129
  runGlobalNetworkLifecycle(process.argv, () => initSentry());
168
130
  const program = new Command();
169
131
  program
@@ -0,0 +1,7 @@
1
+ export type StreamErrorDependencies = {
2
+ stderr: Pick<typeof process.stderr, "write">;
3
+ exit: (code: number) => void;
4
+ };
5
+ export declare function handleStreamError(err: NodeJS.ErrnoException, deps?: StreamErrorDependencies): void;
6
+ export declare function installStreamErrorBoundary(): void;
7
+ //# sourceMappingURL=stream-error-boundary.d.ts.map
@@ -0,0 +1,33 @@
1
+ import { environmentalFsErrorMessage } from "./utils/environmental-error.js";
2
+ import { isEpipe } from "./utils/epipe.js";
3
+ const defaults = {
4
+ stderr: process.stderr,
5
+ exit: (code) => process.exit(code),
6
+ };
7
+ export function handleStreamError(err, deps = defaults) {
8
+ if (isEpipe(err)) {
9
+ deps.exit(0);
10
+ return;
11
+ }
12
+ const message = environmentalFsErrorMessage(err);
13
+ if (message) {
14
+ try {
15
+ deps.stderr.write(`hq: ${message}\n`);
16
+ }
17
+ catch {
18
+ // The same full disk or read-only mount can reject the diagnostic write.
19
+ }
20
+ deps.exit(1);
21
+ return;
22
+ }
23
+ throw err;
24
+ }
25
+ let installed = false;
26
+ export function installStreamErrorBoundary() {
27
+ if (installed)
28
+ return;
29
+ installed = true;
30
+ process.stdout.on("error", (err) => handleStreamError(err));
31
+ process.stderr.on("error", (err) => handleStreamError(err));
32
+ }
33
+ //# sourceMappingURL=stream-error-boundary.js.map
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@indigoai-us/hq-cli",
3
- "version": "5.328.0",
3
+ "version": "5.330.0",
4
4
  "description": "HQ by Indigo management CLI — modules and cloud sync",
5
5
  "main": "dist/index.js",
6
6
  "bin": {