@indigoai-us/hq-cli 5.77.13 → 5.78.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 (307) hide show
  1. package/CHANGELOG.md +34 -0
  2. package/assets/scaffold/core/scripts/archive-old-threads.sh +86 -0
  3. package/assets/scaffold/core/scripts/backfill-company-skill-mirrors.sh +73 -0
  4. package/assets/scaffold/core/scripts/backfill-workspace-mirror.sh +51 -0
  5. package/assets/scaffold/core/scripts/codex-skill-bridge.sh +550 -0
  6. package/assets/scaffold/core/scripts/detect-stale-core-policy-mirror.sh +185 -0
  7. package/assets/scaffold/core/scripts/hq-status-summary.sh +187 -0
  8. package/assets/scaffold/core/scripts/ontology-readme-drift.sh +264 -0
  9. package/assets/scaffold/core/scripts/qmd-reindex-after-sync.sh +93 -0
  10. package/assets/scaffold/core/scripts/rebuild-all-indexes.sh +69 -0
  11. package/assets/scaffold/core/scripts/rebuild-companies-index.sh +206 -0
  12. package/assets/scaffold/core/scripts/rebuild-company-knowledge-index.sh +127 -0
  13. package/assets/scaffold/core/scripts/rebuild-orchestrator-index.sh +86 -0
  14. package/assets/scaffold/core/scripts/rebuild-projects-index.sh +105 -0
  15. package/assets/scaffold/core/scripts/rebuild-public-knowledge-index.sh +83 -0
  16. package/assets/scaffold/core/scripts/rebuild-reports-index.sh +114 -0
  17. package/assets/scaffold/core/scripts/rebuild-social-drafts-index.sh +135 -0
  18. package/assets/scaffold/core/scripts/rebuild-threads-index.sh +82 -0
  19. package/assets/scaffold/core/scripts/rebuild-workers-index.sh +97 -0
  20. package/assets/scaffold/core/scripts/resize-screenshot.sh +47 -0
  21. package/assets/scaffold/core/scripts/token-usage-report.sh +254 -0
  22. package/assets/scaffold/core/scripts/worktree.sh +136 -0
  23. package/dist/commands/agents.js +28 -12
  24. package/dist/commands/api-keys.js +100 -20
  25. package/dist/commands/core.d.ts +90 -0
  26. package/dist/commands/core.js +279 -0
  27. package/dist/commands/files-browse.d.ts +21 -0
  28. package/dist/commands/files-browse.js +145 -24
  29. package/dist/commands/files.js +33 -7
  30. package/dist/commands/members.js +16 -5
  31. package/dist/commands/pack-install.d.ts +14 -0
  32. package/dist/commands/pack-install.js +51 -2
  33. package/dist/commands/pkg-install.js +43 -0
  34. package/dist/commands/run.js +8 -0
  35. package/dist/commands/secrets.js +125 -65
  36. package/dist/main.js +6 -0
  37. package/dist/utils/api-key-command-gate.d.ts +45 -0
  38. package/dist/utils/api-key-command-gate.js +51 -0
  39. package/dist/utils/hq-roots.d.ts +91 -0
  40. package/dist/utils/hq-roots.js +212 -0
  41. package/dist/utils/resolve-vault-credential.d.ts +63 -3
  42. package/dist/utils/resolve-vault-credential.js +152 -6
  43. package/dist/utils/run-bundled-script.d.ts +66 -0
  44. package/dist/utils/run-bundled-script.js +80 -0
  45. package/dist/utils/vault-api.d.ts +21 -0
  46. package/dist/utils/vault-api.js +74 -3
  47. package/package.json +7 -1
  48. package/.claude/policies/hq-cli-interactive-reads-use-sts-vend.md +0 -49
  49. package/.github/workflows/ci.yml +0 -46
  50. package/.github/workflows/publish.yml +0 -36
  51. package/.github/workflows/scripts/smoke-test-pkg.sh +0 -97
  52. package/e2e/cli.test.ts +0 -128
  53. package/eslint.config.js +0 -37
  54. package/pnpm-workspace.yaml +0 -4
  55. package/scripts/chmod-bins.mjs +0 -49
  56. package/scripts/generate-dsn.mjs +0 -26
  57. package/scripts/generate-scan-packages-table.mjs +0 -113
  58. package/scripts/smoke-sources-signals.sh +0 -103
  59. package/src/bin/hq-auth-refresh.ts +0 -43
  60. package/src/cli-version.ts +0 -13
  61. package/src/commands/__fixtures__/make-tar.ts +0 -126
  62. package/src/commands/_patterns.ts +0 -10
  63. package/src/commands/add.ts +0 -75
  64. package/src/commands/agents.test.ts +0 -527
  65. package/src/commands/agents.ts +0 -923
  66. package/src/commands/api-keys.test.ts +0 -291
  67. package/src/commands/api-keys.ts +0 -382
  68. package/src/commands/artifact-verify.test.ts +0 -177
  69. package/src/commands/auth.test.ts +0 -82
  70. package/src/commands/auth.ts +0 -213
  71. package/src/commands/billing.test.ts +0 -158
  72. package/src/commands/billing.ts +0 -146
  73. package/src/commands/channels.test.ts +0 -74
  74. package/src/commands/channels.ts +0 -91
  75. package/src/commands/cloud-demote.test.ts +0 -401
  76. package/src/commands/cloud-demote.ts +0 -278
  77. package/src/commands/cloud-provision.test.ts +0 -1117
  78. package/src/commands/cloud-provision.ts +0 -942
  79. package/src/commands/cloud.pull-all.test.ts +0 -711
  80. package/src/commands/cloud.pull-per-company.test.ts +0 -188
  81. package/src/commands/cloud.push-all.test.ts +0 -485
  82. package/src/commands/cloud.scope-excluded-warning.test.ts +0 -22
  83. package/src/commands/cloud.selectors.test.ts +0 -208
  84. package/src/commands/cloud.status.test.ts +0 -160
  85. package/src/commands/cloud.test.ts +0 -41
  86. package/src/commands/cloud.ts +0 -2130
  87. package/src/commands/company.test.ts +0 -202
  88. package/src/commands/company.ts +0 -148
  89. package/src/commands/creators.test.ts +0 -60
  90. package/src/commands/creators.ts +0 -117
  91. package/src/commands/crm.test.ts +0 -179
  92. package/src/commands/crm.ts +0 -236
  93. package/src/commands/db-migrate.ts +0 -55
  94. package/src/commands/db-provision.ts +0 -114
  95. package/src/commands/db-sql.ts +0 -124
  96. package/src/commands/db-status.ts +0 -108
  97. package/src/commands/db.ts +0 -26
  98. package/src/commands/dm.test.ts +0 -748
  99. package/src/commands/dm.ts +0 -1226
  100. package/src/commands/feedback.test.ts +0 -413
  101. package/src/commands/feedback.ts +0 -169
  102. package/src/commands/files-browse.test.ts +0 -1249
  103. package/src/commands/files-browse.ts +0 -1506
  104. package/src/commands/files-delete.test.ts +0 -484
  105. package/src/commands/files-recovery.test.ts +0 -370
  106. package/src/commands/files.test.ts +0 -722
  107. package/src/commands/files.ts +0 -1446
  108. package/src/commands/group-grants.test.ts +0 -330
  109. package/src/commands/group-grants.ts +0 -455
  110. package/src/commands/groups.test.ts +0 -44
  111. package/src/commands/groups.ts +0 -368
  112. package/src/commands/integrations.test.ts +0 -633
  113. package/src/commands/integrations.ts +0 -507
  114. package/src/commands/list.ts +0 -85
  115. package/src/commands/login.ts +0 -52
  116. package/src/commands/logout.ts +0 -30
  117. package/src/commands/marketplace-install.test.ts +0 -543
  118. package/src/commands/marketplace-security.test.ts +0 -646
  119. package/src/commands/mcp-registration.test.ts +0 -2787
  120. package/src/commands/mcp-registration.ts +0 -2612
  121. package/src/commands/mcp-status.test.ts +0 -483
  122. package/src/commands/mcp-status.ts +0 -575
  123. package/src/commands/mcp-status.us011.test.ts +0 -243
  124. package/src/commands/meetings.test.ts +0 -522
  125. package/src/commands/meetings.ts +0 -789
  126. package/src/commands/members.test.ts +0 -1474
  127. package/src/commands/members.ts +0 -1181
  128. package/src/commands/onboard-identity-guard.test.ts +0 -99
  129. package/src/commands/onboard-identity-guard.ts +0 -103
  130. package/src/commands/onboard-join.test.ts +0 -96
  131. package/src/commands/onboard-join.ts +0 -101
  132. package/src/commands/onboard-warning.test.ts +0 -26
  133. package/src/commands/onboard-warning.ts +0 -12
  134. package/src/commands/onboard.ts +0 -267
  135. package/src/commands/outposts-heartbeat.test.ts +0 -299
  136. package/src/commands/outposts-heartbeat.ts +0 -310
  137. package/src/commands/outposts-replica-sync.test.ts +0 -272
  138. package/src/commands/outposts-self-deploy.test.ts +0 -243
  139. package/src/commands/outposts.test.ts +0 -1039
  140. package/src/commands/outposts.ts +0 -1935
  141. package/src/commands/pack-install-secret-authorization.test.ts +0 -115
  142. package/src/commands/pack-install.test.ts +0 -1469
  143. package/src/commands/pack-install.ts +0 -2247
  144. package/src/commands/pack-update-cache.test.ts +0 -149
  145. package/src/commands/packs.test.ts +0 -88
  146. package/src/commands/packs.ts +0 -622
  147. package/src/commands/people.test.ts +0 -807
  148. package/src/commands/people.ts +0 -422
  149. package/src/commands/pkg-install.dispatch.test.ts +0 -71
  150. package/src/commands/pkg-install.ts +0 -215
  151. package/src/commands/pkg-list.test.ts +0 -79
  152. package/src/commands/pkg-list.ts +0 -13
  153. package/src/commands/pkg-remove.ts +0 -71
  154. package/src/commands/pkg-update.ts +0 -189
  155. package/src/commands/publish.test.ts +0 -650
  156. package/src/commands/publish.ts +0 -580
  157. package/src/commands/reindex.test.ts +0 -259
  158. package/src/commands/reindex.ts +0 -274
  159. package/src/commands/rescue.test.ts +0 -39
  160. package/src/commands/rescue.ts +0 -210
  161. package/src/commands/run.env-local.test.ts +0 -88
  162. package/src/commands/run.test.ts +0 -45
  163. package/src/commands/run.ts +0 -184
  164. package/src/commands/safe-extract.test.ts +0 -459
  165. package/src/commands/safe-extract.ts +0 -444
  166. package/src/commands/secrets-scope.test.ts +0 -56
  167. package/src/commands/secrets-scope.ts +0 -32
  168. package/src/commands/secrets.parse-destination.test.ts +0 -38
  169. package/src/commands/secrets.test.ts +0 -2037
  170. package/src/commands/secrets.ts +0 -2240
  171. package/src/commands/signals.ts +0 -351
  172. package/src/commands/skill.test.ts +0 -770
  173. package/src/commands/skill.ts +0 -796
  174. package/src/commands/sources.ts +0 -362
  175. package/src/commands/sync-mode.test.ts +0 -366
  176. package/src/commands/sync-mode.ts +0 -387
  177. package/src/commands/sync-narrow.test.ts +0 -633
  178. package/src/commands/sync-narrow.ts +0 -555
  179. package/src/commands/sync.ts +0 -165
  180. package/src/commands/team-sync.ts +0 -628
  181. package/src/commands/update.ts +0 -71
  182. package/src/commands/whoami.ts +0 -83
  183. package/src/commands/workers.test.ts +0 -158
  184. package/src/commands/workers.ts +0 -301
  185. package/src/index.test.ts +0 -32
  186. package/src/index.ts +0 -20
  187. package/src/lib/db/control-plane.test.ts +0 -59
  188. package/src/lib/db/control-plane.ts +0 -113
  189. package/src/lib/db/local.test.ts +0 -81
  190. package/src/lib/db/local.ts +0 -148
  191. package/src/lib/db/migrate.test.ts +0 -133
  192. package/src/lib/db/migrate.ts +0 -137
  193. package/src/lib/db/paths.test.ts +0 -112
  194. package/src/lib/db/paths.ts +0 -128
  195. package/src/lib/db/remote-engine.test.ts +0 -44
  196. package/src/lib/db/remote-engine.ts +0 -148
  197. package/src/lib/db/remote-sql.test.ts +0 -32
  198. package/src/lib/db/remote-sql.ts +0 -62
  199. package/src/lib/db/sql.test.ts +0 -106
  200. package/src/lib/db/sql.ts +0 -192
  201. package/src/lib/hq-cloud-dep.smoke.test.ts +0 -75
  202. package/src/lib/local-tree-diff.test.ts +0 -301
  203. package/src/lib/local-tree-diff.ts +0 -362
  204. package/src/lib/narrow-hint-banner.test.ts +0 -235
  205. package/src/lib/narrow-hint-banner.ts +0 -212
  206. package/src/lib/plan-limit-nag.test.ts +0 -317
  207. package/src/lib/plan-limit-nag.ts +0 -264
  208. package/src/main.ts +0 -371
  209. package/src/node-preflight.test.ts +0 -60
  210. package/src/node-preflight.ts +0 -67
  211. package/src/outpost/session-heartbeat-bounds.test.ts +0 -195
  212. package/src/outpost/session-heartbeat-guard.test.ts +0 -105
  213. package/src/outpost/session-heartbeat-publisher.test.ts +0 -178
  214. package/src/outpost/session-heartbeat-publisher.ts +0 -186
  215. package/src/outpost/session-heartbeat-retain-guard.test.ts +0 -126
  216. package/src/outpost/session-heartbeat.test.ts +0 -459
  217. package/src/outpost/session-heartbeat.ts +0 -877
  218. package/src/packaging.test.ts +0 -109
  219. package/src/run/__fixtures__/discover-schemas/example.env.schema +0 -4
  220. package/src/run/discover-schemas.test.ts +0 -153
  221. package/src/run/discover-schemas.ts +0 -79
  222. package/src/run/hq-plugin.test.ts +0 -371
  223. package/src/run/hq-plugin.ts +0 -277
  224. package/src/run/varlock-shape.test.ts +0 -57
  225. package/src/schemas/hq-package.schema.json +0 -155
  226. package/src/sentry-before-send.test.ts +0 -359
  227. package/src/sentry-before-send.ts +0 -221
  228. package/src/sentry-epipe.test.ts +0 -37
  229. package/src/sentry-release.test.ts +0 -54
  230. package/src/sentry.test.ts +0 -24
  231. package/src/sentry.ts +0 -54
  232. package/src/strategies/link.ts +0 -62
  233. package/src/strategies/merge.ts +0 -142
  234. package/src/types.ts +0 -146
  235. package/src/utils/__fixtures__/scan-packages.generated-block.sh +0 -23
  236. package/src/utils/auth-error.test.ts +0 -40
  237. package/src/utils/auth-error.ts +0 -42
  238. package/src/utils/billing-gate.test.ts +0 -95
  239. package/src/utils/billing-gate.ts +0 -228
  240. package/src/utils/breadcrumb-buffer.ts +0 -18
  241. package/src/utils/cli-telemetry.test.ts +0 -153
  242. package/src/utils/cli-telemetry.ts +0 -61
  243. package/src/utils/cognito-session.machine.test.ts +0 -109
  244. package/src/utils/cognito-session.refresh.test.ts +0 -84
  245. package/src/utils/cognito-session.test.ts +0 -328
  246. package/src/utils/cognito-session.ts +0 -448
  247. package/src/utils/company-selection-error.test.ts +0 -42
  248. package/src/utils/company-selection-error.ts +0 -45
  249. package/src/utils/contribution-table.ts +0 -83
  250. package/src/utils/environmental-error.test.ts +0 -45
  251. package/src/utils/environmental-error.ts +0 -39
  252. package/src/utils/epipe.test.ts +0 -28
  253. package/src/utils/epipe.ts +0 -29
  254. package/src/utils/expected-cli-error.test.ts +0 -28
  255. package/src/utils/expected-cli-error.ts +0 -39
  256. package/src/utils/feedback-diagnostics.test.ts +0 -183
  257. package/src/utils/feedback-diagnostics.ts +0 -123
  258. package/src/utils/feedback-screenshots.test.ts +0 -134
  259. package/src/utils/feedback-screenshots.ts +0 -124
  260. package/src/utils/feedback-versions.test.ts +0 -98
  261. package/src/utils/feedback-versions.ts +0 -68
  262. package/src/utils/git.ts +0 -74
  263. package/src/utils/integrity.ts +0 -59
  264. package/src/utils/intercepted-process-exit.test.ts +0 -37
  265. package/src/utils/intercepted-process-exit.ts +0 -36
  266. package/src/utils/login-provider.test.ts +0 -42
  267. package/src/utils/login-provider.ts +0 -30
  268. package/src/utils/manifest.test.ts +0 -167
  269. package/src/utils/manifest.ts +0 -131
  270. package/src/utils/pack-contributions.test.ts +0 -603
  271. package/src/utils/pack-contributions.ts +0 -450
  272. package/src/utils/people.ts +0 -215
  273. package/src/utils/registry-client.ts +0 -204
  274. package/src/utils/registry.ts +0 -90
  275. package/src/utils/resolve-vault-credential.test.ts +0 -69
  276. package/src/utils/resolve-vault-credential.ts +0 -60
  277. package/src/utils/sandbox-runner-client.test.ts +0 -279
  278. package/src/utils/sandbox-runner-client.ts +0 -260
  279. package/src/utils/secrets-cache.ts +0 -183
  280. package/src/utils/sentry-identity.ts +0 -45
  281. package/src/utils/settle-with-timeout.test.ts +0 -21
  282. package/src/utils/settle-with-timeout.ts +0 -22
  283. package/src/utils/user-yaml-error.test.ts +0 -24
  284. package/src/utils/user-yaml-error.ts +0 -30
  285. package/src/utils/vault-api.test.ts +0 -536
  286. package/src/utils/vault-api.ts +0 -382
  287. package/src/utils/version-check.test.ts +0 -176
  288. package/src/utils/version-check.ts +0 -155
  289. package/src/utils/version-gate.test.ts +0 -1024
  290. package/src/utils/version-gate.ts +0 -702
  291. package/test/commands/db-tenant-isolation.test.ts +0 -94
  292. package/test/commands/db.test.ts +0 -85
  293. package/test/commands/signals.test.ts +0 -207
  294. package/test/commands/sources.test.ts +0 -231
  295. package/test/e2e/smoke-install-mcp.sh +0 -113
  296. package/test/fixtures/hq-pack-smoke-mcp/mcp/smoke-http.json +0 -1
  297. package/test/fixtures/hq-pack-smoke-mcp/package.yaml +0 -11
  298. package/test/fixtures/signals/action_item/sample.md +0 -16
  299. package/test/fixtures/signals/summary/sample.md +0 -12
  300. package/test/fixtures/sources/meetings/sample.md +0 -25
  301. package/test/helpers/cli-runner.ts +0 -150
  302. package/test/helpers/s3-list-mock.ts +0 -79
  303. package/test/helpers/vault-service-mock.ts +0 -295
  304. package/test/sources-signals/smoke.test.ts +0 -228
  305. package/tsconfig.json +0 -20
  306. package/vitest.config.ts +0 -11
  307. package/vitest.e2e.config.ts +0 -7
@@ -1,702 +0,0 @@
1
- /**
2
- * Hard version-gate: ask hq-pro whether the current CLI is below the minimum
3
- * acceptable version and, if so, run `npm install -g …@latest` synchronously
4
- * before any commander parsing happens. Distinct from the existing
5
- * `version-check.ts` which is a passive (cached, opt-in) stderr nag against
6
- * the npm registry.
7
- *
8
- * Why both?
9
- * - `version-check.ts` answers "is there something newer?" by polling npm
10
- * directly. It's a soft hint, lives on a 24h cache, and never blocks.
11
- * - `version-gate.ts` answers "is the team currently allowing your version
12
- * to run?" via an authoritative hq-pro endpoint. The server can yank a
13
- * known-bad release without waiting for the npm `latest` dist-tag move.
14
- *
15
- * The endpoint is reusable across clients (hq-sync, hq-installer, create-hq).
16
- * See `apps/hq-pro/src/vault-service/handlers/client-version-check.ts` for the
17
- * source-of-truth table.
18
- *
19
- * Trust model: anonymous. The CLI may be running pre-login (e.g. fresh
20
- * install) so we never send credentials. The endpoint identifies the client
21
- * by `clientId` + `currentVersion`.
22
- *
23
- * Failure mode: silent. Network down, hq-pro returning 5xx, malformed body —
24
- * the gate must never break the CLI for a user who's otherwise fine. We log
25
- * to Sentry as a breadcrumb (best-effort) and return.
26
- *
27
- * Opt-out: `HQ_NO_UPDATE_CHECK=1` (same env as `version-check.ts` — one knob
28
- * to silence both check + gate).
29
- */
30
-
31
- import { spawnSync } from "node:child_process";
32
- import { existsSync, readdirSync, readFileSync, rmSync } from "node:fs";
33
- import path from "node:path";
34
- import { fileURLToPath } from "node:url";
35
- import chalk from "chalk";
36
- import { CLI_NAME, CLI_VERSION } from "../cli-version.js";
37
- import { DEFAULT_VAULT_API_URL } from "./cognito-session.js";
38
-
39
- const CLIENT_ID = "hq-cli";
40
- const ENDPOINT_PATH = "/v1/client-version/check";
41
- const FETCH_TIMEOUT_MS = 3_000;
42
- const LATEST_PACKAGE_SPEC = `${CLI_NAME}@latest`;
43
-
44
- /** Which package manager owns the running global install. */
45
- export type InstallManager = "npm" | "pnpm";
46
-
47
- interface VersionCheckResponse {
48
- clientId: string;
49
- currentVersion: string;
50
- minVersion: string;
51
- latestVersion: string;
52
- updateRequired: boolean;
53
- updateRecommended: boolean;
54
- updateCommand?: string;
55
- downloadUrl?: string;
56
- message?: string;
57
- }
58
-
59
- function isOptedOut(): boolean {
60
- return process.env.HQ_NO_UPDATE_CHECK === "1";
61
- }
62
-
63
- function findRunningPackageRoot(): string | null {
64
- let dir = path.dirname(fileURLToPath(import.meta.url));
65
- while (true) {
66
- try {
67
- const pkg = JSON.parse(
68
- readFileSync(path.join(dir, "package.json"), "utf-8"),
69
- ) as { name?: unknown };
70
- if (pkg.name === CLI_NAME) return dir;
71
- } catch {
72
- // Keep walking; compiled installs usually start under dist/.
73
- }
74
-
75
- const parent = path.dirname(dir);
76
- if (parent === dir) return null;
77
- dir = parent;
78
- }
79
- }
80
-
81
- export function npmPrefixFromPackageDir(pkgDir: string): string | null {
82
- const normalized = pkgDir.replace(/\\/g, "/").replace(/\/+$/, "");
83
- const segments = normalized.split("/");
84
- const nodeModulesIndex = segments.lastIndexOf("node_modules");
85
- if (nodeModulesIndex === -1) return null;
86
-
87
- const prefixEnd =
88
- segments[nodeModulesIndex - 1] === "lib"
89
- ? nodeModulesIndex - 1
90
- : nodeModulesIndex;
91
- const prefix = segments.slice(0, prefixEnd).join("/");
92
- if (prefix === "" && normalized.startsWith("/")) return "/";
93
- return prefix || null;
94
- }
95
-
96
- /** `$PNPM_HOME/global`, normalised to forward slashes, when PNPM_HOME is set. */
97
- function pnpmHomeGlobalRoot(): string | null {
98
- const home = process.env.PNPM_HOME;
99
- if (!home) return null;
100
- const normalized = home.replace(/\\/g, "/").replace(/\/+$/, "");
101
- return normalized ? `${normalized}/global` : null;
102
- }
103
-
104
- /**
105
- * Whether the running package lives inside a pnpm-managed **global** install.
106
- *
107
- * pnpm does not use npm's `<prefix>/lib/node_modules` layout. A global
108
- * `pnpm add -g` puts the package in a versioned content store under the
109
- * `global/<store-layout-version>` root and exposes it through a generated shim
110
- * on PATH:
111
- *
112
- * $PNPM_HOME/hq <- shim on PATH
113
- * $PNPM_HOME/global/5/node_modules/@indigoai-us/hq-cli <- symlink
114
- * $PNPM_HOME/global/5/.pnpm/@indigoai-us+hq-cli@5.61.0/node_modules/…
115
- *
116
- * Both the symlinked and the resolved (`.pnpm`) form are recognised, because
117
- * whether `import.meta.url` reports the link or its target depends on how node
118
- * resolved the entrypoint.
119
- *
120
- * A bare `.pnpm` segment is deliberately NOT enough. It also appears in a local
121
- * project dependency (`<proj>/node_modules/.pnpm/@indigoai-us+hq-cli@…`) and in
122
- * a `pnpm dlx` cache. Neither of those is what `pnpm add -g` updates, so
123
- * treating them as global would mutate the user's global install as a side
124
- * effect of a local invocation while the copy actually running stayed stale —
125
- * i.e. the gate would re-fire and re-install globally on every subsequent run.
126
- * Those layouts fall through to the ordinary npm-prefix/manual handling.
127
- */
128
- export function isPnpmManagedPackageDir(pkgDir: string): boolean {
129
- const normalized = pkgDir.replace(/\\/g, "/").replace(/\/+$/, "");
130
- const segments = normalized.split("/").filter(Boolean);
131
-
132
- // `global/<digits>` — pnpm's global root, in either the symlinked or the
133
- // resolved (`.pnpm`) form, and independent of where PNPM_HOME points.
134
- for (let i = 0; i < segments.length - 1; i += 1) {
135
- if (segments[i] === "global" && /^\d+$/.test(segments[i + 1]!)) return true;
136
- }
137
-
138
- // Belt-and-braces for a future pnpm layout whose store dir is not numeric:
139
- // anything under `$PNPM_HOME/global` is global by construction. Still keyed
140
- // on the `global` segment so `$PNPM_HOME/store/**` (dlx caches) stays out.
141
- const globalRoot = pnpmHomeGlobalRoot();
142
- if (globalRoot) {
143
- if (normalized === globalRoot || normalized.startsWith(`${globalRoot}/`)) {
144
- return true;
145
- }
146
- }
147
-
148
- return false;
149
- }
150
-
151
- /**
152
- * Where the running CLI is installed and who owns it. Resolved in ONE pass so
153
- * the package-root walk (which reads and parses a `package.json` per directory
154
- * level) happens once per invocation, and so the manager and the prefix can
155
- * never disagree because the filesystem shifted between two separate walks.
156
- *
157
- * `prefix` is `null` for a pnpm-managed install even though a prefix-shaped
158
- * string *can* be derived from those paths: `npmPrefixFromPackageDir` would
159
- * happily hand back the pnpm store directory, and `npm install -g --prefix
160
- * <store>` then unpacks a fresh copy into `<store>/lib/node_modules`, which
161
- * pnpm's shim never reads. npm exits 0, the gate reports success, and the next
162
- * `hq` invocation still runs the old version — the reported loop where a stale
163
- * pnpm shim kept resolving 5.61.0 after every "successful" update.
164
- *
165
- * Defaults to npm with no prefix when the layout can't be determined — that is
166
- * the historical behaviour and the common case.
167
- */
168
- export interface RunningInstall {
169
- manager: InstallManager;
170
- prefix: string | null;
171
- packageRoot: string | null;
172
- }
173
-
174
- export function resolveRunningInstall(): RunningInstall {
175
- try {
176
- const packageRoot = findRunningPackageRoot();
177
- if (!packageRoot) return { manager: "npm", prefix: null, packageRoot: null };
178
- if (isPnpmManagedPackageDir(packageRoot)) {
179
- return { manager: "pnpm", prefix: null, packageRoot };
180
- }
181
- return {
182
- manager: "npm",
183
- prefix: npmPrefixFromPackageDir(packageRoot),
184
- packageRoot,
185
- };
186
- } catch {
187
- return { manager: "npm", prefix: null, packageRoot: null };
188
- }
189
- }
190
-
191
- /** Convenience view of {@link resolveRunningInstall} for callers needing one field. */
192
- export function resolveRunningManager(): InstallManager {
193
- return resolveRunningInstall().manager;
194
- }
195
-
196
- /** Convenience view of {@link resolveRunningInstall} for callers needing one field. */
197
- export function resolveRunningPrefix(): string | null {
198
- return resolveRunningInstall().prefix;
199
- }
200
-
201
- export function buildPrefixedInstallArgv(prefix: string): string[] {
202
- return ["install", "-g", "--prefix", prefix, LATEST_PACKAGE_SPEC];
203
- }
204
-
205
- /**
206
- * Argv for updating a pnpm-managed global install. `pnpm add -g` rewrites the
207
- * PATH shim as part of the install, so the next invocation genuinely resolves
208
- * the new version — which is the whole point of routing here instead of npm.
209
- */
210
- export function buildPnpmInstallArgv(): string[] {
211
- return ["add", "-g", LATEST_PACKAGE_SPEC];
212
- }
213
-
214
- /**
215
- * Filesystem surface used by {@link cleanStalePartialInstall}. Injected so the
216
- * cleanup logic is unit-testable without touching a real global prefix.
217
- */
218
- export interface StaleInstallFs {
219
- readdirSync: (dir: string) => string[];
220
- existsSync: (target: string) => boolean;
221
- readFileSync: (target: string, encoding: "utf-8") => string;
222
- rmSync: (target: string, options: { recursive: boolean; force: boolean }) => void;
223
- }
224
-
225
- const nodeStaleInstallFs: StaleInstallFs = {
226
- readdirSync: (dir) => readdirSync(dir),
227
- existsSync,
228
- readFileSync: (target, encoding) => readFileSync(target, encoding),
229
- rmSync,
230
- };
231
-
232
- function isHealthyPackageDir(pkgDir: string, fs: StaleInstallFs): boolean {
233
- try {
234
- const pkg = JSON.parse(
235
- fs.readFileSync(path.join(pkgDir, "package.json"), "utf-8"),
236
- ) as { name?: unknown };
237
- return pkg.name === CLI_NAME;
238
- } catch {
239
- return false; // missing / unreadable / malformed package.json ⇒ partial
240
- }
241
- }
242
-
243
- /**
244
- * Remove leftover artifacts from an interrupted `npm install -g` so a retry can
245
- * succeed. npm unpacks a package into a `.<pkg>-<rand>` staging dir alongside
246
- * the final location and then renames it into place; if a previous run was
247
- * killed mid-rename (or a half-written package dir survives), every subsequent
248
- * install fails with `ENOTEMPTY` because npm cannot atomically rename over the
249
- * non-empty leftover. npm does not self-heal this — the stale dir must be
250
- * removed first.
251
- *
252
- * To stay safe we only ever delete:
253
- * - dot-prefixed npm staging dirs for THIS package (`.hq-cli-*`), and
254
- * - a package dir whose `package.json` is missing/unreadable or whose `name`
255
- * is not exactly {@link CLI_NAME} (i.e. a genuinely partial/foreign dir).
256
- *
257
- * A healthy install (valid `package.json`, `name === CLI_NAME`) is never
258
- * touched, so an ordinary version bump still flows through npm untouched.
259
- *
260
- * Returns the list of removed paths — empty when there was nothing to clean, so
261
- * callers can gate a reinstall retry on `removed.length > 0`.
262
- */
263
- export function cleanStalePartialInstall(
264
- prefix: string,
265
- fs: StaleInstallFs = nodeStaleInstallFs,
266
- ): string[] {
267
- const removed: string[] = [];
268
- const slash = CLI_NAME.indexOf("/");
269
- const scope = slash === -1 ? null : CLI_NAME.slice(0, slash);
270
- const leaf = slash === -1 ? CLI_NAME : CLI_NAME.slice(slash + 1);
271
- const stagingPrefix = `.${leaf}-`;
272
-
273
- // Global npm keeps packages under `<prefix>/lib/node_modules` (unix) while a
274
- // bare `--prefix` dir (windows / some sandboxes) uses `<prefix>/node_modules`.
275
- const nmRoots = [
276
- path.join(prefix, "lib", "node_modules"),
277
- path.join(prefix, "node_modules"),
278
- ];
279
-
280
- for (const nmRoot of nmRoots) {
281
- // For a scoped package the staging dir + final dir both live inside the
282
- // scope dir (`.../@indigoai-us/.hq-cli-<rand>`, `.../@indigoai-us/hq-cli`).
283
- const parentDir = scope ? path.join(nmRoot, scope) : nmRoot;
284
- let entries: string[];
285
- try {
286
- entries = fs.readdirSync(parentDir);
287
- } catch {
288
- continue; // this node_modules / scope dir doesn't exist here
289
- }
290
-
291
- for (const entry of entries) {
292
- if (!entry.startsWith(stagingPrefix)) continue;
293
- const target = path.join(parentDir, entry);
294
- try {
295
- fs.rmSync(target, { recursive: true, force: true });
296
- removed.push(target);
297
- } catch {
298
- // best-effort: a dir we can't remove (perms) just means the retry
299
- // still fails and we fall through to the sudo / manual path.
300
- }
301
- }
302
-
303
- const pkgDir = path.join(parentDir, leaf);
304
- if (fs.existsSync(pkgDir) && !isHealthyPackageDir(pkgDir, fs)) {
305
- try {
306
- fs.rmSync(pkgDir, { recursive: true, force: true });
307
- removed.push(pkgDir);
308
- } catch {
309
- // best-effort (see above)
310
- }
311
- }
312
- }
313
-
314
- return removed;
315
- }
316
-
317
- /**
318
- * Hit POST /v1/client-version/check. Returns the parsed body on 200, or
319
- * `null` on any failure (caller treats as "no gate"). Tight 3s timeout —
320
- * a hung server must not delay CLI startup.
321
- */
322
- async function fetchVersionDecision(): Promise<VersionCheckResponse | null> {
323
- try {
324
- const url = `${DEFAULT_VAULT_API_URL}${ENDPOINT_PATH}`;
325
- const res = await fetch(url, {
326
- method: "POST",
327
- headers: {
328
- "Content-Type": "application/json",
329
- Accept: "application/json",
330
- },
331
- body: JSON.stringify({
332
- clientId: CLIENT_ID,
333
- currentVersion: CLI_VERSION,
334
- platform: `${process.platform}-${process.arch}`,
335
- }),
336
- signal: AbortSignal.timeout(FETCH_TIMEOUT_MS),
337
- });
338
- if (!res.ok) return null;
339
- const body = (await res.json()) as Partial<VersionCheckResponse>;
340
- if (
341
- typeof body.minVersion !== "string" ||
342
- typeof body.latestVersion !== "string" ||
343
- typeof body.updateRequired !== "boolean"
344
- ) {
345
- return null;
346
- }
347
- return body as VersionCheckResponse;
348
- } catch {
349
- return null;
350
- }
351
- }
352
-
353
- /**
354
- * Run the upgrade command in a blocking subprocess. Inherits stdio so the
355
- * user sees the npm progress. We do NOT auto-rerun the CLI on completion —
356
- * forcing a re-invocation would run twice on the same process and feel
357
- * janky; instead we print a clear "rerun your command" message and exit.
358
- */
359
- type UpdateResult = { ok: boolean; detail?: string; code?: string };
360
- type UpdateRunner = (cmd: string, args: string[]) => UpdateResult;
361
-
362
- /**
363
- * Quote an argv entry for a Windows `cmd.exe` invocation. Needed because Node
364
- * does NOT quote argv when spawning with `shell: true` on Windows — it joins
365
- * the array with spaces — so an npm prefix like `C:\Program Files\…` would be
366
- * split into two arguments.
367
- */
368
- export function quoteForWindowsShell(arg: string): string {
369
- if (arg === "") return '""';
370
- if (!/[\s"^&|<>()]/.test(arg)) return arg;
371
- return `"${arg.replace(/"/g, '\\"')}"`;
372
- }
373
-
374
- /**
375
- * How to hand `<cmd> <args…>` to `spawnSync` on this platform.
376
- *
377
- * On Windows both `npm` and `pnpm` are `.cmd` shims, and since the
378
- * CVE-2024-27980 hardening Node refuses to spawn a `.cmd`/`.bat` file without
379
- * a shell. Without this the update would fail with EINVAL/ENOENT on every
380
- * Windows install — including the pnpm layouts this gate claims to detect.
381
- */
382
- export function buildSpawnPlan(
383
- cmd: string,
384
- args: readonly string[],
385
- platform: NodeJS.Platform = process.platform,
386
- ): { cmd: string; args: string[]; shell: boolean } {
387
- if (platform !== "win32") return { cmd, args: [...args], shell: false };
388
- return { cmd, args: args.map(quoteForWindowsShell), shell: true };
389
- }
390
-
391
- function runUpdateCommand(cmd: string, args: string[]): UpdateResult {
392
- try {
393
- const plan = buildSpawnPlan(cmd, args);
394
- const result = spawnSync(plan.cmd, plan.args, {
395
- stdio: "inherit",
396
- shell: plan.shell,
397
- });
398
- // spawnSync reports a missing executable via `error`, not a throw.
399
- if (result.error) {
400
- const code = (result.error as NodeJS.ErrnoException).code;
401
- return { ok: false, code, detail: result.error.message };
402
- }
403
- if (result.status !== 0) {
404
- return {
405
- ok: false,
406
- detail: `exit ${result.status ?? "signal"}`,
407
- };
408
- }
409
- return { ok: true };
410
- } catch (err) {
411
- return {
412
- ok: false,
413
- code: (err as NodeJS.ErrnoException | undefined)?.code,
414
- detail: err instanceof Error ? err.message : String(err),
415
- };
416
- }
417
- }
418
-
419
- function performUpdateCommand(
420
- cmd: string,
421
- args: string[],
422
- runner: UpdateRunner = runUpdateCommand,
423
- ): UpdateResult {
424
- return runner(cmd, args);
425
- }
426
-
427
- function performUpdate(
428
- command: string,
429
- runner: UpdateRunner = runUpdateCommand,
430
- ): UpdateResult {
431
- const parts = command.split(/\s+/).filter(Boolean);
432
- if (parts.length === 0) return { ok: false, detail: "empty command" };
433
- const cmd = parts[0]!;
434
- const args = parts.slice(1);
435
- return performUpdateCommand(cmd, args, runner);
436
- }
437
-
438
- /**
439
- * The command a user should run by hand for this install layout. hq-pro's
440
- * `updateCommand` is npm-shaped for every client, so a pnpm-managed install
441
- * must never be told to run it: `npm install -g …` drops a fresh copy under the
442
- * npm global prefix while pnpm's PATH shim keeps resolving the old build. That
443
- * is verbatim the reported symptom (a stale shim still reporting 5.61.0 after a
444
- * "successful" update), so both the hard gate and the soft nudge below have to
445
- * speak the running manager's language.
446
- */
447
- function manualUpdateCommand(
448
- install: RunningInstall,
449
- decision: VersionCheckResponse,
450
- ): string | undefined {
451
- if (install.manager === "pnpm") return `pnpm ${buildPnpmInstallArgv().join(" ")}`;
452
- if (install.prefix) return `npm ${buildPrefixedInstallArgv(install.prefix).join(" ")}`;
453
- return decision.updateCommand;
454
- }
455
-
456
- /**
457
- * Soft notify when the server says we're below `latestVersion` but still ≥
458
- * `minVersion`. Single chalk-yellow line on stderr; never blocks.
459
- *
460
- * This path fires for EVERY version below latest (the hard gate only fires
461
- * below `minVersion`), so it is the far more frequently seen of the two and
462
- * must be manager-aware for the same reason the gate is.
463
- */
464
- function nudgeUpdateRecommended(
465
- decision: VersionCheckResponse,
466
- install: RunningInstall = resolveRunningInstall(),
467
- ): void {
468
- const msg = chalk.yellow(
469
- `⚠ A new version of hq-cli is available: ${decision.latestVersion} (current: ${decision.currentVersion}).`,
470
- );
471
- console.error(msg);
472
- const command =
473
- install.manager === "pnpm"
474
- ? `pnpm ${buildPnpmInstallArgv().join(" ")}`
475
- : decision.updateCommand;
476
- if (command) {
477
- console.error(chalk.dim(` Update: ${command}`));
478
- }
479
- }
480
-
481
- /**
482
- * Hard enforcement when the server says we're below `minVersion`. Print a
483
- * red banner, attempt the update, then exit so the user reruns against the
484
- * fresh binary. Sequence chosen so a user with a broken `npm` global prefix
485
- * still gets a clear error rather than an opaque silent failure.
486
- *
487
- * Exit codes:
488
- * 0 — update succeeded; user must rerun their command
489
- * 75 — update failed (EX_TEMPFAIL; common for sudo/EACCES on system npm)
490
- */
491
- function enforceUpdateRequired(
492
- decision: VersionCheckResponse,
493
- deps: {
494
- performUpdateString?: (command: string) => UpdateResult;
495
- resolveInstall?: () => RunningInstall;
496
- runner?: UpdateRunner;
497
- cleanStale?: (prefix: string) => string[];
498
- } = {},
499
- ): never {
500
- const banner = chalk.red.bold(
501
- `✗ hq-cli ${decision.currentVersion} is below the minimum required version (${decision.minVersion}).`,
502
- );
503
- console.error(banner);
504
- if (decision.message) console.error(chalk.dim(` ${decision.message}`));
505
-
506
- const command = decision.updateCommand;
507
- // ONE package-root walk decides both the manager and the prefix. A
508
- // pnpm-managed install must be updated by pnpm; the server's `updateCommand`
509
- // and any npm prefix are both wrong for that layout, so `resolveRunningInstall`
510
- // already reports `prefix: null` there and neither is consulted below.
511
- const install = (deps.resolveInstall ?? resolveRunningInstall)();
512
- const isPnpm = install.manager === "pnpm";
513
- const prefix = install.prefix;
514
- if (!isPnpm && !command && !prefix) {
515
- console.error(
516
- chalk.red(
517
- " No updateCommand provided by hq-pro — see https://hq.indigo.ai/docs/cli-update for manual steps.",
518
- ),
519
- );
520
- if (decision.downloadUrl) {
521
- console.error(chalk.dim(` Download: ${decision.downloadUrl}`));
522
- }
523
- process.exit(75);
524
- }
525
-
526
- const runner = deps.runner ?? runUpdateCommand;
527
- // Resolve the concrete install argv once so the sudo fallback below can re-run
528
- // the EXACT same command under elevation.
529
- let primaryCmd: string;
530
- let primaryArgs: string[];
531
- if (isPnpm) {
532
- primaryCmd = "pnpm";
533
- primaryArgs = buildPnpmInstallArgv();
534
- } else if (prefix) {
535
- primaryCmd = "npm";
536
- primaryArgs = buildPrefixedInstallArgv(prefix);
537
- } else {
538
- const parts = (command ?? "").split(/\s+/).filter(Boolean);
539
- primaryCmd = parts[0] ?? "";
540
- primaryArgs = parts.slice(1);
541
- }
542
-
543
- let result: UpdateResult;
544
- if (isPnpm) {
545
- console.error(
546
- chalk.dim(" Detected a pnpm-managed global install; updating with pnpm"),
547
- );
548
- console.error(chalk.dim(` Running: pnpm ${primaryArgs.join(" ")}`));
549
- result = performUpdateCommand("pnpm", primaryArgs, runner);
550
- } else if (prefix) {
551
- console.error(chalk.dim(` Installing into npm prefix: ${prefix}`));
552
- console.error(chalk.dim(` Running: npm ${primaryArgs.join(" ")}`));
553
- result = performUpdateCommand("npm", primaryArgs, runner);
554
- } else {
555
- console.error(chalk.dim(` Running: ${command}`));
556
- result = deps.performUpdateString
557
- ? deps.performUpdateString(command!)
558
- : performUpdate(command!, runner);
559
- }
560
-
561
- // A partial/corrupt global install leaves npm unable to atomically rename its
562
- // freshly-unpacked package over a leftover directory, so the install above
563
- // fails with ENOTEMPTY (e.g. a prior interrupted `npm install -g` left a
564
- // half-written `hq-cli` package dir or a `.hq-cli-<rand>` staging dir under
565
- // the prefix's node_modules). npm cannot self-heal this. Remove the stale
566
- // artifacts and retry the install ONCE. Guarded on `cleaned.length > 0` so a
567
- // plain EACCES on an otherwise-healthy prefix falls straight through to the
568
- // sudo retry below without a redundant reinstall attempt.
569
- if (!result.ok && prefix) {
570
- const cleaner = deps.cleanStale ?? cleanStalePartialInstall;
571
- const cleaned = cleaner(prefix);
572
- if (cleaned.length > 0) {
573
- console.error(
574
- chalk.dim(
575
- ` Removing stale partial install artifacts and retrying: ${cleaned.join(", ")}`,
576
- ),
577
- );
578
- result = performUpdateCommand("npm", primaryArgs, runner);
579
- }
580
- }
581
-
582
- // A root-owned global install (e.g. a system `/usr` install where the CLI runs
583
- // unprivileged — the outpost agent boxes) can't rewrite the prefix's bin dir,
584
- // so the install above fails with EACCES (`rename /usr/bin/hq`). Retry ONCE
585
- // under non-interactive sudo: `sudo -n` fails fast WITHOUT a password prompt
586
- // when passwordless sudo isn't configured, so interactive installs (e.g.
587
- // Homebrew on macOS, where the first attempt already succeeded anyway) fall
588
- // through to the manual path unchanged, while headless boxes with passwordless
589
- // sudo self-update cleanly.
590
- //
591
- // Never for pnpm: `sudo -n pnpm add -g` installs into ROOT's PNPM_HOME, which
592
- // leaves the user's shim untouched while reporting success — the same silent
593
- // no-op this fix exists to remove. A failed pnpm update must surface instead.
594
- if (!result.ok && primaryCmd && !isPnpm) {
595
- console.error(
596
- chalk.dim(
597
- ` Update failed unprivileged; retrying with: sudo -n ${primaryCmd} ${primaryArgs.join(" ")}`,
598
- ),
599
- );
600
- const sudoResult = performUpdateCommand(
601
- "sudo",
602
- ["-n", primaryCmd, ...primaryArgs],
603
- runner,
604
- );
605
- if (sudoResult.ok) result = sudoResult;
606
- }
607
-
608
- if (!result.ok) {
609
- console.error(
610
- chalk.red(`✗ Update failed${result.detail ? `: ${result.detail}` : ""}.`),
611
- );
612
- // The package manager itself is missing from this environment — the usual
613
- // cause is a minimal-PATH parent (launchd, cron, a bare systemd unit) that
614
- // never sourced the shell profile which puts PNPM_HOME (or nvm's npm) on
615
- // PATH. Say so, because "exit 75" alone reads as a permissions problem.
616
- if (result.code === "ENOENT") {
617
- console.error(
618
- chalk.red(
619
- ` \`${primaryCmd}\` was not found on PATH in this environment (launchd, cron and other minimal-PATH parents commonly lack it).`,
620
- ),
621
- );
622
- }
623
- const manual = manualUpdateCommand(install, decision) ?? command!;
624
- console.error(chalk.dim(` Try manually: ${manual}`));
625
- // Only informational, and only when we could not run the right manager at
626
- // all: hq-pro's command is npm-shaped, so following it on a pnpm layout is
627
- // what produced the stale-shim loop in the first place.
628
- if (isPnpm && result.code === "ENOENT" && command) {
629
- console.error(
630
- chalk.dim(
631
- ` (hq-pro suggests \`${command}\` — that is for npm-managed installs; use it only if you have switched this install to npm.)`,
632
- ),
633
- );
634
- }
635
- process.exit(75);
636
- }
637
-
638
- console.error(
639
- chalk.green(
640
- `✓ Updated to hq-cli ${decision.latestVersion}. Rerun your command.`,
641
- ),
642
- );
643
- process.exit(0);
644
- }
645
-
646
- /**
647
- * Public entry point. Call before commander parses argv. Blocks the CLI on
648
- * network IO for up to FETCH_TIMEOUT_MS — acceptable because the alternative
649
- * (a fire-and-forget background check) gives the user no chance to bail out
650
- * of a known-bad version before it does damage.
651
- *
652
- * `--version` / `-v` callers MUST skip the gate (the user is debugging a
653
- * broken install and shouldn't be force-upgraded mid-investigation). Caller
654
- * is responsible for checking argv before invoking us — see index.ts.
655
- */
656
- export async function enforceVersionGate(): Promise<void> {
657
- if (isOptedOut()) return;
658
- const decision = await fetchVersionDecision();
659
- if (!decision) return; // best-effort: silent on any failure
660
- if (!decision.updateRequired && !decision.updateRecommended) return;
661
- // Resolved once, here, so the up-to-date case never pays for the walk and
662
- // neither downstream path repeats it.
663
- const install = resolveRunningInstall();
664
- if (decision.updateRequired) {
665
- enforceUpdateRequired(decision, { resolveInstall: () => install }); // exits process
666
- }
667
- if (decision.updateRecommended) {
668
- nudgeUpdateRecommended(decision, install);
669
- }
670
- }
671
-
672
- /**
673
- * Cheap argv pre-check: skip the gate for `--version` / `-V` so users
674
- * inspecting a broken install can still see what they have without being
675
- * force-upgraded.
676
- */
677
- export function shouldSkipGate(argv: readonly string[]): boolean {
678
- return argv.some(
679
- (a) => a === "--version" || a === "-V" || a === "-v" || a === "--help" || a === "-h",
680
- );
681
- }
682
-
683
- export const __test__ = {
684
- CLIENT_ID,
685
- ENDPOINT_PATH,
686
- FETCH_TIMEOUT_MS,
687
- buildPnpmInstallArgv,
688
- buildPrefixedInstallArgv,
689
- buildSpawnPlan,
690
- cleanStalePartialInstall,
691
- enforceUpdateRequired,
692
- isPnpmManagedPackageDir,
693
- npmPrefixFromPackageDir,
694
- nudgeUpdateRecommended,
695
- performUpdate,
696
- performUpdateCommand,
697
- quoteForWindowsShell,
698
- runUpdateCommand,
699
- resolveRunningInstall,
700
- resolveRunningManager,
701
- resolveRunningPrefix,
702
- };