@indigoai-us/hq-cli 5.345.147 → 5.345.149

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 (168) hide show
  1. package/CHANGELOG.md +65 -0
  2. package/README.md +2 -0
  3. package/assets/bot-workers/setup/worker.yaml +11 -0
  4. package/dist/command-catalog.generated.d.ts +341 -11
  5. package/dist/command-catalog.generated.js +421 -11
  6. package/dist/command-help.js +2 -1
  7. package/dist/commands/access.d.ts +45 -2
  8. package/dist/commands/access.js +537 -223
  9. package/dist/commands/agent-inbox.js +2 -1
  10. package/dist/commands/billing-usage.d.ts +33 -2
  11. package/dist/commands/billing-usage.js +75 -4
  12. package/dist/commands/bot.js +2 -2
  13. package/dist/commands/channels.d.ts +27 -0
  14. package/dist/commands/channels.js +108 -4
  15. package/dist/commands/cloud.js +31 -1
  16. package/dist/commands/core-refresh-vault-access-native.d.ts +33 -0
  17. package/dist/commands/core-refresh-vault-access-native.js +356 -28
  18. package/dist/commands/core-work-mesh-bind-trusted-native.d.ts +9 -0
  19. package/dist/commands/core-work-mesh-bind-trusted-native.js +26 -19
  20. package/dist/commands/core.js +9 -0
  21. package/dist/commands/daemon.d.ts +7 -0
  22. package/dist/commands/daemon.js +37 -3
  23. package/dist/commands/dm.js +18 -3
  24. package/dist/commands/doctor.d.ts +11 -1
  25. package/dist/commands/doctor.js +56 -1
  26. package/dist/commands/install-global.d.ts +8 -8
  27. package/dist/commands/install-global.js +139 -61
  28. package/dist/commands/lanes-shared.d.ts +3 -1
  29. package/dist/commands/lanes.d.ts +4 -4
  30. package/dist/commands/lanes.js +380 -25
  31. package/dist/commands/library-add.d.ts +31 -1
  32. package/dist/commands/library-add.js +69 -2
  33. package/dist/commands/library.js +37 -3
  34. package/dist/commands/pack-install.d.ts +2 -2
  35. package/dist/commands/pack-install.js +133 -35
  36. package/dist/commands/reindex.js +19 -0
  37. package/dist/commands/sync-manifest.d.ts +2 -9
  38. package/dist/commands/sync-manifest.js +4 -15
  39. package/dist/hook-engine-path.d.ts +4 -0
  40. package/dist/hook-engine-path.js +43 -0
  41. package/dist/hq-cloud-sync-runner/bin/sync-runner.js +2783 -2354
  42. package/dist/hq-cloud-sync-runner/package.json +1 -1
  43. package/dist/index.d.ts +10 -0
  44. package/dist/index.js +76 -5
  45. package/dist/lazy-commands.js +2 -0
  46. package/dist/lib/agent-kit/inbox-redact.d.ts +44 -0
  47. package/dist/lib/agent-kit/inbox-redact.js +361 -0
  48. package/dist/lib/agent-kit/inbox-state.js +11 -2
  49. package/dist/lib/agent-kit/mcp/tools.js +2 -1
  50. package/dist/lib/agent-kit/run/inbox.js +36 -7
  51. package/dist/lib/anywhere-runtime-flag-key.d.ts +3 -0
  52. package/dist/lib/anywhere-runtime-flag-key.js +9 -0
  53. package/dist/lib/billing/usage-cost.d.ts +31 -1
  54. package/dist/lib/billing/usage-cost.js +46 -0
  55. package/dist/lib/company-skill-wrapper.js +6 -4
  56. package/dist/lib/daemon/install.d.ts +47 -1
  57. package/dist/lib/daemon/install.js +181 -15
  58. package/dist/lib/daemon/lock.d.ts +32 -0
  59. package/dist/lib/daemon/lock.js +55 -2
  60. package/dist/lib/db/local.d.ts +2 -2
  61. package/dist/lib/db/local.js +2 -2
  62. package/dist/lib/db/sql.js +2 -2
  63. package/dist/lib/db/sqlite-binding.d.ts +22 -0
  64. package/dist/lib/db/sqlite-binding.js +168 -0
  65. package/dist/lib/doctor/__testing__/run-doctor.js +4 -0
  66. package/dist/lib/doctor/checks/anywhere.d.ts +15 -0
  67. package/dist/lib/doctor/checks/anywhere.js +77 -11
  68. package/dist/lib/doctor/checks/cli-path.d.ts +12 -0
  69. package/dist/lib/doctor/checks/cli-path.js +46 -0
  70. package/dist/lib/doctor/checks/hook-engine.d.ts +4 -0
  71. package/dist/lib/doctor/checks/hook-engine.js +17 -0
  72. package/dist/lib/doctor/checks/sync-health.d.ts +7 -0
  73. package/dist/lib/doctor/checks/sync-health.js +38 -2
  74. package/dist/lib/doctor/checks/temp-homes.d.ts +19 -0
  75. package/dist/lib/doctor/checks/temp-homes.js +54 -0
  76. package/dist/lib/doctor/fix/apply.d.ts +90 -1
  77. package/dist/lib/doctor/fix/apply.js +176 -10
  78. package/dist/lib/doctor/fix/json-output.d.ts +4 -1
  79. package/dist/lib/doctor/fix/json-output.js +6 -0
  80. package/dist/lib/doctor/fix/remediation.d.ts +7 -1
  81. package/dist/lib/doctor/fix/remediation.js +23 -1
  82. package/dist/lib/doctor/registry.js +10 -0
  83. package/dist/lib/doctor/temp-home-sweeper.d.ts +149 -0
  84. package/dist/lib/doctor/temp-home-sweeper.js +418 -0
  85. package/dist/lib/flag-registry-worker-source.generated.d.ts +1 -1
  86. package/dist/lib/flag-registry-worker-source.generated.js +1 -1
  87. package/dist/lib/flag-registry.js +9 -1
  88. package/dist/lib/flag-snapshot-file.js +8 -3
  89. package/dist/lib/hook-engine/reconcile.d.ts +9 -0
  90. package/dist/lib/hook-engine/reconcile.js +134 -0
  91. package/dist/lib/hook-engine/registration.d.ts +17 -0
  92. package/dist/lib/hook-engine/registration.js +50 -0
  93. package/dist/lib/hook-engine/resolve.d.ts +31 -0
  94. package/dist/lib/hook-engine/resolve.js +134 -0
  95. package/dist/lib/install/claude-targets.d.ts +2 -1
  96. package/dist/lib/install/claude-targets.js +8 -3
  97. package/dist/lib/install/codex-targets.d.ts +3 -0
  98. package/dist/lib/install/codex-targets.js +15 -4
  99. package/dist/lib/install/hook-target.d.ts +9 -0
  100. package/dist/lib/install/hook-target.js +39 -0
  101. package/dist/lib/lanes/admission-host.d.ts +1 -0
  102. package/dist/lib/lanes/admission-host.js +8 -1
  103. package/dist/lib/lanes/admission.d.ts +4 -0
  104. package/dist/lib/lanes/admission.js +6 -2
  105. package/dist/lib/lanes/envelope-repair-dispatch.d.ts +5 -0
  106. package/dist/lib/lanes/envelope-repair-dispatch.js +12 -1
  107. package/dist/lib/lanes/index.d.ts +2 -2
  108. package/dist/lib/lanes/index.js +1 -1
  109. package/dist/lib/lanes/keep-alive.js +4 -1
  110. package/dist/lib/lanes/local-host-identity.d.ts +40 -0
  111. package/dist/lib/lanes/local-host-identity.js +314 -0
  112. package/dist/lib/lanes/reaper.d.ts +1 -1
  113. package/dist/lib/lanes/reaper.js +7 -0
  114. package/dist/lib/lanes/reconcile.d.ts +4 -0
  115. package/dist/lib/lanes/reconcile.js +16 -8
  116. package/dist/lib/lanes/spawn.d.ts +20 -0
  117. package/dist/lib/lanes/spawn.js +48 -2
  118. package/dist/lib/lanes/status-fields.js +6 -1
  119. package/dist/lib/lanes/store.js +8 -1
  120. package/dist/lib/lanes/throttle.d.ts +49 -6
  121. package/dist/lib/lanes/throttle.js +154 -17
  122. package/dist/lib/lanes/types.d.ts +22 -1
  123. package/dist/lib/lanes/types.js +27 -3
  124. package/dist/lib/lanes/watcher.js +27 -2
  125. package/dist/lib/lanes/worker-continuity.d.ts +2 -0
  126. package/dist/lib/lanes/worker-continuity.js +18 -11
  127. package/dist/lib/manifest-disabled.d.ts +13 -0
  128. package/dist/lib/manifest-disabled.js +19 -0
  129. package/dist/lib/service-manager/launchd.d.ts +1 -0
  130. package/dist/lib/service-manager/launchd.js +1 -1
  131. package/dist/lib/windows/console-encoding.d.ts +24 -0
  132. package/dist/lib/windows/console-encoding.js +83 -0
  133. package/dist/lib/windows/install-followup.d.ts +35 -0
  134. package/dist/lib/windows/install-followup.js +142 -0
  135. package/dist/lib/windows/user-path.d.ts +125 -0
  136. package/dist/lib/windows/user-path.js +275 -0
  137. package/dist/lib/work-context/auto-bind.d.ts +1 -1
  138. package/dist/lib/work-context/auto-bind.js +22 -3
  139. package/dist/lib/work-context/lane-bind.d.ts +31 -0
  140. package/dist/lib/work-context/lane-bind.js +101 -0
  141. package/dist/lib/work-context/organize.js +1 -0
  142. package/dist/lib/work-context/prd-sync.js +7 -0
  143. package/dist/lib/work-context/project.d.ts +36 -10
  144. package/dist/lib/work-context/project.js +130 -41
  145. package/dist/lib/work-context/reconcile.d.ts +6 -0
  146. package/dist/lib/work-context/reconcile.js +25 -5
  147. package/dist/lib/work-context/state.d.ts +6 -0
  148. package/dist/lib/work-context/state.js +29 -1
  149. package/dist/main.js +2 -1
  150. package/dist/unknown-command.d.ts +18 -0
  151. package/dist/unknown-command.js +88 -0
  152. package/dist/utils/cognito-session.d.ts +8 -0
  153. package/dist/utils/cognito-session.js +14 -2
  154. package/dist/utils/copy-tree.d.ts +61 -0
  155. package/dist/utils/copy-tree.js +249 -0
  156. package/dist/utils/environmental-error.js +7 -3
  157. package/dist/utils/hook-trust.d.ts +9 -0
  158. package/dist/utils/hook-trust.js +30 -2
  159. package/dist/utils/npm-invocation.d.ts +27 -0
  160. package/dist/utils/npm-invocation.js +54 -0
  161. package/dist/utils/qmd-native-binding-error.js +3 -1
  162. package/dist/utils/resolve-executable.d.ts +2 -0
  163. package/dist/utils/resolve-executable.js +6 -2
  164. package/dist/utils/safe-link.d.ts +50 -0
  165. package/dist/utils/safe-link.js +300 -0
  166. package/package.json +17 -5
  167. package/scripts/hook-engine-manifest.mjs +22 -0
  168. package/scripts/install-hook-engine.mjs +10 -0
@@ -33,7 +33,7 @@
33
33
  */
34
34
  import * as fs from "node:fs";
35
35
  import * as path from "node:path";
36
- import { LanesError } from "./errors.js";
36
+ import { LaneSchemaError, LanesError } from "./errors.js";
37
37
  import { compareEnvelopeNames } from "./envelope-sequence.js";
38
38
  import { questionRecordRefusalCode, raiseEnvelopeQuestions, resolveQuestionCompanyWithFallback, } from "./questions.js";
39
39
  import { isTerminalLaneState, ENVELOPE_INVALID_REASON, parseLaneEnvelope, STORY_STATUSES, LANE_PROVIDERS, } from "./types.js";
@@ -738,7 +738,7 @@ export function readLatestEnvelopeFile(hqRoot, laneId, minName) {
738
738
  ref,
739
739
  path: filePath,
740
740
  text,
741
- envelope: parseLaneEnvelope(JSON.parse(text)),
741
+ envelope: parseLaneEnvelopeForLane(hqRoot, laneId, JSON.parse(text)),
742
742
  error: null,
743
743
  };
744
744
  }
@@ -752,6 +752,31 @@ export function readLatestEnvelopeFile(hqRoot, laneId, minName) {
752
752
  };
753
753
  }
754
754
  }
755
+ /**
756
+ * Parse strictly first. Only a story_complete / story_status mismatch reads the
757
+ * lane record: a --no-story lane (no_story_reason set) has no board story, so
758
+ * that mismatch is repaired instead of failing the lane. An unreadable lane
759
+ * record keeps the strict error.
760
+ */
761
+ function parseLaneEnvelopeForLane(hqRoot, laneId, raw) {
762
+ try {
763
+ return parseLaneEnvelope(raw);
764
+ }
765
+ catch (err) {
766
+ if (!(err instanceof LaneSchemaError) || err.details.error !== "story_complete_status_mismatch")
767
+ throw err;
768
+ let noStory = false;
769
+ try {
770
+ noStory = readLane(hqRoot, laneId).no_story_reason !== undefined;
771
+ }
772
+ catch {
773
+ throw err;
774
+ }
775
+ if (!noStory)
776
+ throw err;
777
+ return parseLaneEnvelope(raw, { storyBound: false });
778
+ }
779
+ }
755
780
  /** A descriptor for this run's manifest slot when no envelope file was written. */
756
781
  export function missingExpectedEnvelopeFile(hqRoot, laneId, envelopePath) {
757
782
  if (!envelopePath || !path.isAbsolute(envelopePath))
@@ -5,6 +5,8 @@ export interface WorkerContinuityLookup {
5
5
  provider: LaneProvider;
6
6
  company: LaneRecord["company"];
7
7
  freshThread?: boolean;
8
+ /** When set, a live lane on another machine names that machine and how to free the worker. */
9
+ runsHere?: (lane: LaneRecord) => boolean;
8
10
  }
9
11
  export interface WorkerContinuityResult {
10
12
  previous?: LaneRecord;
@@ -14,6 +14,22 @@ function sameCompany(left, right) {
14
14
  function compareNewest(left, right) {
15
15
  return right.created_at.localeCompare(left.created_at) || right.lane_id.localeCompare(left.lane_id);
16
16
  }
17
+ function workerBusyError(lookup, lane, lead, extra = {}) {
18
+ const remote = lookup.runsHere !== undefined && lookup.runsHere(lane) === false;
19
+ const fix = `hq lanes stop ${lane.lane_id}`;
20
+ const message = remote
21
+ ? `${lead} on ${lane.host.kind}:${lane.host.id} (state ${lane.state}). That lane is on another machine. Free the worker on that machine with \`${fix}\`.`
22
+ : `${lead} (state ${lane.state}). Free the worker with \`${fix}\`.`;
23
+ return new LanesError("worker_lane_busy", message, {
24
+ worker: lookup.worker,
25
+ lane_id: lane.lane_id,
26
+ state: lane.state,
27
+ host: lane.host,
28
+ remote,
29
+ fix_command: fix,
30
+ ...extra,
31
+ });
32
+ }
17
33
  export function sameLaneHost(left, right) {
18
34
  return left.kind === right.kind && left.id === right.id;
19
35
  }
@@ -25,11 +41,7 @@ export function findWorkerContinuityLane(lanes, lookup) {
25
41
  .sort(compareNewest);
26
42
  const live = scoped.find((lane) => !isTerminalLaneState(lane.state));
27
43
  if (live) {
28
- throw new LanesError("worker_lane_busy", `Worker ${lookup.worker} already has live lane ${live.lane_id}`, {
29
- worker: lookup.worker,
30
- lane_id: live.lane_id,
31
- state: live.state,
32
- });
44
+ throw workerBusyError(lookup, live, `Worker ${lookup.worker} already has live lane ${live.lane_id}`);
33
45
  }
34
46
  const previous = scoped[0];
35
47
  if (!previous)
@@ -45,12 +57,7 @@ export function findWorkerContinuityLane(lanes, lookup) {
45
57
  (lane.state === "queued" || lane.state === "running"))
46
58
  .sort(compareNewest)[0];
47
59
  if (liveDescendant) {
48
- throw new LanesError("worker_lane_busy", `Provider thread lineage is active in lane ${liveDescendant.lane_id}`, {
49
- worker: lookup.worker,
50
- lane_id: liveDescendant.lane_id,
51
- state: liveDescendant.state,
52
- provider_session_id: previousSessionId,
53
- });
60
+ throw workerBusyError(lookup, liveDescendant, `Provider thread lineage is active in lane ${liveDescendant.lane_id}`, { provider_session_id: previousSessionId });
54
61
  }
55
62
  }
56
63
  // Only clean, resumable terminal outcomes are safe for automatic reuse.
@@ -0,0 +1,13 @@
1
+ /**
2
+ * The kill switch hq-cloud's manifest pass honours. Restated locally (rather
3
+ * than imported) for two reasons: it lives in the same unreleased module as
4
+ * the manifest exports, and — more importantly — we must be able to read it
5
+ * BEFORE deciding to do any work at all.
6
+ *
7
+ * Kept in its own leaf module so `hq doctor` can read the switch without
8
+ * loading `hq sync manifest` and its imports.
9
+ */
10
+ export declare const MANIFEST_DISABLED_ENV = "HQ_SYNC_MANIFEST_DISABLED";
11
+ /** True when the kill switch is set to anything other than empty/0/false. */
12
+ export declare function manifestUploadsDisabled(env?: NodeJS.ProcessEnv): boolean;
13
+ //# sourceMappingURL=manifest-disabled.d.ts.map
@@ -0,0 +1,19 @@
1
+ /**
2
+ * The kill switch hq-cloud's manifest pass honours. Restated locally (rather
3
+ * than imported) for two reasons: it lives in the same unreleased module as
4
+ * the manifest exports, and — more importantly — we must be able to read it
5
+ * BEFORE deciding to do any work at all.
6
+ *
7
+ * Kept in its own leaf module so `hq doctor` can read the switch without
8
+ * loading `hq sync manifest` and its imports.
9
+ */
10
+ export const MANIFEST_DISABLED_ENV = "HQ_SYNC_MANIFEST_DISABLED";
11
+ /** True when the kill switch is set to anything other than empty/0/false. */
12
+ export function manifestUploadsDisabled(env = process.env) {
13
+ const raw = env[MANIFEST_DISABLED_ENV];
14
+ if (raw === undefined)
15
+ return false;
16
+ const value = raw.trim().toLowerCase();
17
+ return value !== "" && value !== "0" && value !== "false";
18
+ }
19
+ //# sourceMappingURL=manifest-disabled.js.map
@@ -62,6 +62,7 @@ export declare const LAUNCHD_BOOTSTRAP_BACKOFF_MS: readonly number[];
62
62
  /** Bootstrap attempts after bootout (first try + EIO retries). */
63
63
  export declare const LAUNCHD_BOOTSTRAP_ATTEMPTS: number;
64
64
  export declare const defaultRunCommand: RunCommand;
65
+ export declare function blockingSleep(ms: number): void;
65
66
  export declare function launchdServiceTarget(plistPath: string, uid?: number): string;
66
67
  /** ProgramArguments from a LaunchAgent plist. */
67
68
  export declare function launchdPlistProgramArguments(plist: string): string[];
@@ -177,7 +177,7 @@ export const defaultRunCommand = (cmd, args) => {
177
177
  stderr: r.error ? r.error.message : (typeof r.stderr === "string" ? r.stderr : ""),
178
178
  };
179
179
  };
180
- function blockingSleep(ms) {
180
+ export function blockingSleep(ms) {
181
181
  Atomics.wait(new Int32Array(new SharedArrayBuffer(4)), 0, 0, ms);
182
182
  }
183
183
  function guiDomain(uid = os.userInfo().uid) {
@@ -0,0 +1,24 @@
1
+ /** ASCII stand-ins for characters cmd.exe cannot show on code page 437. */
2
+ export declare function toAscii(text: string): string;
3
+ /** Parse `chcp` output (`Active code page: 437`). Null when the number is missing. */
4
+ export declare function parseChcpOutput(text: string): number | null;
5
+ type ChcpRun = () => {
6
+ status: number | null;
7
+ stdout: string;
8
+ };
9
+ export declare function readConsoleCodePage(run?: ChcpRun): number | null;
10
+ export interface ConsoleEncodingDeps {
11
+ platform?: NodeJS.Platform;
12
+ stdout?: NodeJS.WriteStream;
13
+ stderr?: NodeJS.WriteStream;
14
+ /** Known code page. Null means the page could not be read and is treated as not UTF-8. */
15
+ codePage?: number | null;
16
+ readCodePage?: () => number | null;
17
+ }
18
+ /**
19
+ * Wrap console stdout and stderr on Windows when the code page is not UTF-8.
20
+ * Returns false when no wrap was installed.
21
+ */
22
+ export declare function installWindowsConsoleEncoding(deps?: ConsoleEncodingDeps): boolean;
23
+ export {};
24
+ //# sourceMappingURL=console-encoding.d.ts.map
@@ -0,0 +1,83 @@
1
+ /**
2
+ * cmd.exe on a non-UTF-8 code page prints UTF-8 em dashes as mojibake.
3
+ * When stdout or stderr is a console and the code page is not 65001, string
4
+ * writes are transliterated to ASCII. Pipes are left as UTF-8 so captured
5
+ * logs stay intact. Buffer writes are not modified.
6
+ */
7
+ import { spawnSync } from "node:child_process";
8
+ const WRAP = Symbol.for("hq.windowsConsoleEncoding");
9
+ const REPLACEMENTS = [
10
+ [/\u2014/g, "--"],
11
+ [/\u2013/g, "-"],
12
+ [/\u2018|\u2019|\u201A/g, "'"],
13
+ [/\u201C|\u201D|\u201E/g, "\""],
14
+ [/\u2026/g, "..."],
15
+ [/\u00A0/g, " "],
16
+ [/\u2022/g, "*"],
17
+ [/\u2192/g, "->"],
18
+ [/\u2190/g, "<-"],
19
+ [/\u21D2/g, "=>"],
20
+ [/\u00D7/g, "x"],
21
+ ];
22
+ /** ASCII stand-ins for characters cmd.exe cannot show on code page 437. */
23
+ export function toAscii(text) {
24
+ let out = text;
25
+ for (const [pattern, replacement] of REPLACEMENTS)
26
+ out = out.replace(pattern, replacement);
27
+ return out.replace(/[^\t\n\r\x20-\x7E]/g, "?");
28
+ }
29
+ /** Parse `chcp` output (`Active code page: 437`). Null when the number is missing. */
30
+ export function parseChcpOutput(text) {
31
+ const match = /:\s*(\d+)/.exec(text);
32
+ if (!match)
33
+ return null;
34
+ const codePage = Number(match[1]);
35
+ return Number.isInteger(codePage) ? codePage : null;
36
+ }
37
+ function defaultChcp() {
38
+ const result = spawnSync("chcp", [], { encoding: "utf8", windowsHide: true, timeout: 2000 });
39
+ return { status: result.status, stdout: result.stdout ?? "" };
40
+ }
41
+ export function readConsoleCodePage(run = defaultChcp) {
42
+ try {
43
+ const result = run();
44
+ if (result.status !== 0)
45
+ return null;
46
+ return parseChcpOutput(result.stdout);
47
+ }
48
+ catch {
49
+ return null;
50
+ }
51
+ }
52
+ function wrapStream(stream) {
53
+ if (stream[WRAP])
54
+ return;
55
+ const original = stream.write.bind(stream);
56
+ stream.write = ((chunk, encoding, cb) => {
57
+ if (typeof chunk === "string")
58
+ return original(toAscii(chunk), encoding, cb);
59
+ return original(chunk, encoding, cb);
60
+ });
61
+ stream[WRAP] = true;
62
+ }
63
+ /**
64
+ * Wrap console stdout and stderr on Windows when the code page is not UTF-8.
65
+ * Returns false when no wrap was installed.
66
+ */
67
+ export function installWindowsConsoleEncoding(deps = {}) {
68
+ if ((deps.platform ?? process.platform) !== "win32")
69
+ return false;
70
+ const stdout = (deps.stdout ?? process.stdout);
71
+ const stderr = (deps.stderr ?? process.stderr);
72
+ if (!stdout.isTTY && !stderr.isTTY)
73
+ return false;
74
+ const codePage = deps.codePage !== undefined ? deps.codePage : (deps.readCodePage ?? readConsoleCodePage)();
75
+ if (codePage === 65001)
76
+ return false;
77
+ if (stdout.isTTY)
78
+ wrapStream(stdout);
79
+ if (stderr.isTTY)
80
+ wrapStream(stderr);
81
+ return true;
82
+ }
83
+ //# sourceMappingURL=console-encoding.js.map
@@ -0,0 +1,35 @@
1
+ export declare function isInstallFollowupSkipped(argv: readonly string[], env: NodeJS.ProcessEnv): boolean;
2
+ export declare function isInstalledPackageEntry(entry: string | undefined): boolean;
3
+ /**
4
+ * `version`: the CLI version whose mesh daemon install succeeded.
5
+ * `path`: the shim folder and CLI version whose PATH step already ran, so a
6
+ * refused or locked HKCU is not retried on every command.
7
+ * `meshFailedAt`: when a daemon install for `meshFailedVersion` last failed.
8
+ * A failure is retried after MESH_RETRY_MS rather than on every command.
9
+ */
10
+ export interface InstallFollowupMarker {
11
+ version?: string;
12
+ path?: {
13
+ dir: string;
14
+ version: string;
15
+ };
16
+ meshFailedVersion?: string;
17
+ meshFailedAt?: number;
18
+ }
19
+ /** A failed daemon install blocks for up to 30 s, so it is retried at most hourly. */
20
+ export declare const MESH_RETRY_MS: number;
21
+ export interface InstallFollowupDeps {
22
+ argv: readonly string[];
23
+ env?: NodeJS.ProcessEnv;
24
+ /** The npm shim folder for this entry, or null when hq is not an npm global install. */
25
+ shimDir?: () => string | null;
26
+ ensurePath?: () => void;
27
+ /** True when the daemon install succeeded. */
28
+ spawnMesh?: () => boolean;
29
+ version?: string;
30
+ readMarker?: () => InstallFollowupMarker;
31
+ writeMarker?: (marker: InstallFollowupMarker) => void;
32
+ now?: () => number;
33
+ }
34
+ export declare function runWindowsInstallFollowup(deps: InstallFollowupDeps): void;
35
+ //# sourceMappingURL=install-followup.d.ts.map
@@ -0,0 +1,142 @@
1
+ /**
2
+ * Work that npm 11 will not run from postinstall: put the hq shim folder on
3
+ * the user PATH, and point the mesh daemon at this install once per CLI version.
4
+ * Failures are ignored. Hook, version, and staged-install routes do not run it.
5
+ *
6
+ * Both steps record their result in ~/.hq/install-followup.json, so later
7
+ * commands start no PowerShell or daemon process for the same version.
8
+ */
9
+ import { spawnSync } from "node:child_process";
10
+ import fs from "node:fs";
11
+ import os from "node:os";
12
+ import path from "node:path";
13
+ import { fileURLToPath } from "node:url";
14
+ import { ensureShimOnUserPath, npmShimDir } from "./user-path.js";
15
+ const MARKER_NAME = "install-followup.json";
16
+ export function isInstallFollowupSkipped(argv, env) {
17
+ if (env.HQ_INSTALL_FOLLOWUP === "1" || env.HQ_STAGED_INSTALL === "1")
18
+ return true;
19
+ const args = argv.slice(2).filter((arg) => arg !== "--verbose");
20
+ if (args[0] === "mesh" && args[1] === "daemon")
21
+ return true;
22
+ if (args[0] === "daemon" && (args[1] === "run" || args[1] === "install"))
23
+ return true;
24
+ return false;
25
+ }
26
+ export function isInstalledPackageEntry(entry) {
27
+ return Boolean(entry && /node_modules[/\\]@indigoai-us[/\\]hq-cli[/\\]/i.test(entry));
28
+ }
29
+ function packageVersion() {
30
+ try {
31
+ const pkgPath = fileURLToPath(new URL("../../../package.json", import.meta.url));
32
+ const pkg = JSON.parse(fs.readFileSync(pkgPath, "utf8"));
33
+ return typeof pkg.version === "string" ? pkg.version : "";
34
+ }
35
+ catch {
36
+ return "";
37
+ }
38
+ }
39
+ /** A failed daemon install blocks for up to 30 s, so it is retried at most hourly. */
40
+ export const MESH_RETRY_MS = 60 * 60 * 1000;
41
+ function markerFile(env) {
42
+ const home = env.USERPROFILE || env.HOME || os.homedir();
43
+ return path.join(home, ".hq", MARKER_NAME);
44
+ }
45
+ function readMarker(env) {
46
+ try {
47
+ const parsed = JSON.parse(fs.readFileSync(markerFile(env), "utf8"));
48
+ const marker = {};
49
+ if (typeof parsed.version === "string")
50
+ marker.version = parsed.version;
51
+ if (typeof parsed.path?.dir === "string" && typeof parsed.path.version === "string") {
52
+ marker.path = { dir: parsed.path.dir, version: parsed.path.version };
53
+ }
54
+ if (typeof parsed.meshFailedVersion === "string" && typeof parsed.meshFailedAt === "number") {
55
+ marker.meshFailedVersion = parsed.meshFailedVersion;
56
+ marker.meshFailedAt = parsed.meshFailedAt;
57
+ }
58
+ return marker;
59
+ }
60
+ catch {
61
+ return {};
62
+ }
63
+ }
64
+ function writeMarker(env, marker) {
65
+ const file = markerFile(env);
66
+ fs.mkdirSync(path.dirname(file), { recursive: true });
67
+ fs.writeFileSync(file, `${JSON.stringify(marker)}\n`);
68
+ }
69
+ /** True only when `hq mesh daemon install --if-stale` exited 0. */
70
+ function defaultSpawnMesh(argv, env) {
71
+ const entry = argv[1];
72
+ if (!entry)
73
+ return false;
74
+ const result = spawnSync(process.execPath, [entry, "mesh", "daemon", "install", "--if-stale"], {
75
+ env: { ...env, HQ_INSTALL_FOLLOWUP: "1" },
76
+ timeout: 30_000,
77
+ windowsHide: true,
78
+ stdio: "ignore",
79
+ });
80
+ return result.status === 0 && !result.error;
81
+ }
82
+ export function runWindowsInstallFollowup(deps) {
83
+ const env = deps.env ?? process.env;
84
+ if (isInstallFollowupSkipped(deps.argv, env))
85
+ return;
86
+ const version = deps.version ?? packageVersion();
87
+ if (!version)
88
+ return;
89
+ const read = deps.readMarker ?? (() => readMarker(env));
90
+ const write = deps.writeMarker ?? ((marker) => writeMarker(env, marker));
91
+ const save = (update) => {
92
+ try {
93
+ write({ ...read(), ...update });
94
+ }
95
+ catch {
96
+ // A marker failure only means the next command tries again.
97
+ }
98
+ };
99
+ let shimDir;
100
+ try {
101
+ shimDir = (deps.shimDir ?? (() => npmShimDir(deps.argv[1], fs.existsSync)))();
102
+ }
103
+ catch {
104
+ shimDir = null;
105
+ }
106
+ const marker = read();
107
+ if (shimDir && !(marker.path?.dir === shimDir && marker.path.version === version)) {
108
+ try {
109
+ (deps.ensurePath ?? (() => {
110
+ ensureShimOnUserPath({ entry: deps.argv[1], env });
111
+ }))();
112
+ }
113
+ catch {
114
+ // PATH repair must not block the command the user asked for.
115
+ }
116
+ // Recorded whatever the outcome: a refused or locked HKCU is retried after
117
+ // the next upgrade, and `hq doctor` keeps reporting the missing folder.
118
+ save({ path: { dir: shimDir, version } });
119
+ }
120
+ if (!isInstalledPackageEntry(deps.argv[1]))
121
+ return;
122
+ const mesh = read();
123
+ if (mesh.version === version)
124
+ return;
125
+ const now = (deps.now ?? Date.now)();
126
+ if (mesh.meshFailedVersion === version && mesh.meshFailedAt !== undefined && now - mesh.meshFailedAt < MESH_RETRY_MS)
127
+ return;
128
+ let ok = false;
129
+ try {
130
+ ok = (deps.spawnMesh ?? (() => defaultSpawnMesh(deps.argv, env)))();
131
+ }
132
+ catch {
133
+ // The daemon install is best-effort, same as the old postinstall `|| true`.
134
+ }
135
+ // Only a successful install marks the version done. A failure is retried
136
+ // after MESH_RETRY_MS.
137
+ if (ok)
138
+ save({ version, meshFailedVersion: undefined, meshFailedAt: undefined });
139
+ else
140
+ save({ meshFailedVersion: version, meshFailedAt: now });
141
+ }
142
+ //# sourceMappingURL=install-followup.js.map
@@ -0,0 +1,125 @@
1
+ export type PowerShellRun = (script: string, env: NodeJS.ProcessEnv) => {
2
+ status: number | null;
3
+ stdout: string;
4
+ };
5
+ export interface ShimPathCheck {
6
+ /** The folder holding `hq.cmd`. */
7
+ shimDir: string;
8
+ onPath: boolean;
9
+ /** Where the PATH came from: the persisted registry values, or this process's PATH when those could not be read. */
10
+ source: "registry" | "process";
11
+ }
12
+ export interface ShimPathDeps {
13
+ platform?: NodeJS.Platform;
14
+ /** The running CLI entry script (process.argv[1]). */
15
+ entry?: string;
16
+ exists?: (p: string) => boolean;
17
+ powerShell?: PowerShellRun;
18
+ env?: NodeJS.ProcessEnv;
19
+ }
20
+ /** The longest user Path HQ will write. Longer values break `setx` and some installers. */
21
+ export declare const MAX_USER_PATH_LENGTH = 2047;
22
+ /**
23
+ * Tests on Windows point the scripts at a scratch key with this variable. The
24
+ * scripts accept only keys under HKCU:\Software\HQTest\ and skip the
25
+ * broadcast for them.
26
+ */
27
+ export declare const USER_PATH_TEST_KEY_ENV = "HQ_USER_PATH_TEST_KEY";
28
+ /** The npm prefix folder above the package's `node_modules`, when `hq.cmd` is there. */
29
+ export declare function npmShimDir(entry: string | undefined, exists: (p: string) => boolean): string | null;
30
+ /** Expand `%NAME%` references the way Windows does, matching names case-insensitively. */
31
+ export declare function expandWindowsEnv(value: string, env: NodeJS.ProcessEnv): string;
32
+ /**
33
+ * Prints `U:<base64>` for the unexpanded user Path and `M:<base64>` for the
34
+ * machine Path, base64 of the UTF-16LE bytes, or `U-` / `M-` when a value
35
+ * cannot be read. Base64 is ASCII, so the console code page cannot change it.
36
+ */
37
+ export declare const READ_PATHS_SCRIPT: string;
38
+ /** Decode READ_PATHS_SCRIPT output. Null for a value that could not be read. */
39
+ export declare function parseReadPathsOutput(stdout: string): {
40
+ user: string | null;
41
+ machine: string | null;
42
+ };
43
+ /**
44
+ * The PATH a new terminal will get: the user Path then the machine Path, as
45
+ * stored in the registry. Falls back to this process's PATH when neither can
46
+ * be read.
47
+ */
48
+ export declare function readPersistedPath(run: PowerShellRun, env: NodeJS.ProcessEnv): {
49
+ value: string;
50
+ source: "registry" | "process";
51
+ };
52
+ export declare function isDirOnPath(dir: string, pathValue: string): boolean;
53
+ /** Null when not on Windows or when hq was not installed as an npm global shim. */
54
+ export declare function checkShimOnPath(deps?: ShimPathDeps): ShimPathCheck | null;
55
+ /** Dummy user variable set and cleared so Windows tells running programs that the environment changed. */
56
+ export declare const PATH_REFRESH_VAR = "HQ_PATH_REFRESH";
57
+ /**
58
+ * A PowerShell command that appends `dir` to the user Path, keeping the value
59
+ * as REG_EXPAND_SZ so existing `%VAR%` entries still expand. Writing the
60
+ * registry alone does not notify Explorer, so terminals it opens would keep the
61
+ * old PATH until the next sign-in. Setting and clearing a dummy user variable
62
+ * through [Environment]::SetEnvironmentVariable broadcasts WM_SETTINGCHANGE.
63
+ */
64
+ export declare function pathFixCommand(dir: string): string;
65
+ /** Lines to print when the shim folder is missing from PATH; empty when it is present. */
66
+ export declare function shimPathWarning(check: ShimPathCheck): string[];
67
+ export type UserPathWriteReason = "unreadable" | "present" | "written" | "write-failed" | "unsafe-value" | "too-long";
68
+ export interface UserPathWriteResult {
69
+ written: boolean;
70
+ reason: UserPathWriteReason;
71
+ }
72
+ /** Adds a folder to HKCU Path in one step: read, check, and write happen together. */
73
+ export interface UserPathStore {
74
+ appendUserPath(dir: string): UserPathWriteResult;
75
+ }
76
+ export type UserPathAppendPlan = {
77
+ action: "write";
78
+ value: string;
79
+ } | {
80
+ action: "skip";
81
+ reason: "present" | "unsafe-value" | "too-long";
82
+ };
83
+ /**
84
+ * Decide the new unexpanded user Path. Membership is decided on the expanded
85
+ * value so `%APPDATA%\npm` counts. The stored text keeps `%VARS%`. A value
86
+ * that already holds U+FFFD was garbled by an earlier decode and is never
87
+ * written back. The ensure script applies the same rules inside PowerShell.
88
+ */
89
+ export declare function planUserPathAppend(current: string, dir: string, env: NodeJS.ProcessEnv): UserPathAppendPlan;
90
+ /** Exit codes of ENSURE_USER_PATH_SCRIPT. */
91
+ export declare const ENSURE_EXIT: {
92
+ readonly written: 0;
93
+ readonly badInput: 2;
94
+ readonly unreadable: 3;
95
+ readonly unsafeValue: 4;
96
+ readonly tooLong: 5;
97
+ readonly writtenNoBroadcast: 6;
98
+ readonly present: 10;
99
+ };
100
+ /**
101
+ * Appends $env:HQ_SHIM_DIR to the HKCU Path as REG_EXPAND_SZ, then broadcasts
102
+ * the change. The value is read, checked, and written inside this script, so
103
+ * no console encoding touches it. Mirrors planUserPathAppend.
104
+ */
105
+ export declare const ENSURE_USER_PATH_SCRIPT: string;
106
+ export declare function ensureExitReason(status: number | null): UserPathWriteResult;
107
+ export declare function powershellUserPathStore(run?: PowerShellRun, extraEnv?: NodeJS.ProcessEnv): UserPathStore;
108
+ export interface EnsureShimResult {
109
+ shimDir: string;
110
+ written: boolean;
111
+ onPath: boolean;
112
+ source: ShimPathCheck["source"];
113
+ reason: UserPathWriteReason;
114
+ }
115
+ export interface EnsureShimDeps extends ShimPathDeps {
116
+ store?: UserPathStore;
117
+ }
118
+ export declare function resetUserPathEnsureCacheForTests(): void;
119
+ /**
120
+ * Add the npm shim folder to HKCU Path when it is missing. Null off Windows
121
+ * or when this process is not an npm global install. A failed read does not
122
+ * write. Machine Path is never written.
123
+ */
124
+ export declare function ensureShimOnUserPath(deps?: EnsureShimDeps): EnsureShimResult | null;
125
+ //# sourceMappingURL=user-path.d.ts.map