@indigoai-us/hq-cli 5.77.14 → 5.79.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 (309) hide show
  1. package/CHANGELOG.md +31 -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/meetings.js +16 -9
  31. package/dist/commands/members.js +16 -5
  32. package/dist/commands/pack-install.d.ts +14 -0
  33. package/dist/commands/pack-install.js +42 -2
  34. package/dist/commands/pkg-install.js +39 -2
  35. package/dist/commands/run.js +6 -2
  36. package/dist/commands/secrets.js +120 -65
  37. package/dist/main.js +6 -0
  38. package/dist/utils/api-key-command-gate.d.ts +45 -0
  39. package/dist/utils/api-key-command-gate.js +51 -0
  40. package/dist/utils/hq-roots.d.ts +91 -0
  41. package/dist/utils/hq-roots.js +212 -0
  42. package/dist/utils/resolve-vault-credential.d.ts +63 -3
  43. package/dist/utils/resolve-vault-credential.js +152 -6
  44. package/dist/utils/run-bundled-script.d.ts +66 -0
  45. package/dist/utils/run-bundled-script.js +80 -0
  46. package/dist/utils/vault-api.d.ts +21 -0
  47. package/dist/utils/vault-api.js +74 -3
  48. package/package.json +7 -1
  49. package/.claude/policies/hq-cli-interactive-reads-use-sts-vend.md +0 -49
  50. package/.github/workflows/ci.yml +0 -46
  51. package/.github/workflows/publish.yml +0 -36
  52. package/.github/workflows/scripts/smoke-test-pkg.sh +0 -97
  53. package/e2e/cli.test.ts +0 -128
  54. package/eslint.config.js +0 -37
  55. package/pnpm-workspace.yaml +0 -4
  56. package/scripts/chmod-bins.mjs +0 -49
  57. package/scripts/generate-dsn.mjs +0 -26
  58. package/scripts/generate-scan-packages-table.mjs +0 -113
  59. package/scripts/smoke-sources-signals.sh +0 -103
  60. package/src/bin/hq-auth-refresh.ts +0 -43
  61. package/src/cli-version.ts +0 -13
  62. package/src/commands/__fixtures__/make-tar.ts +0 -126
  63. package/src/commands/_patterns.ts +0 -10
  64. package/src/commands/add.ts +0 -75
  65. package/src/commands/agents.test.ts +0 -527
  66. package/src/commands/agents.ts +0 -923
  67. package/src/commands/api-keys.test.ts +0 -291
  68. package/src/commands/api-keys.ts +0 -382
  69. package/src/commands/artifact-verify.test.ts +0 -177
  70. package/src/commands/auth.test.ts +0 -82
  71. package/src/commands/auth.ts +0 -213
  72. package/src/commands/billing.test.ts +0 -158
  73. package/src/commands/billing.ts +0 -146
  74. package/src/commands/channels.test.ts +0 -74
  75. package/src/commands/channels.ts +0 -91
  76. package/src/commands/cloud-demote.test.ts +0 -401
  77. package/src/commands/cloud-demote.ts +0 -278
  78. package/src/commands/cloud-provision.test.ts +0 -1117
  79. package/src/commands/cloud-provision.ts +0 -942
  80. package/src/commands/cloud.pull-all.test.ts +0 -711
  81. package/src/commands/cloud.pull-per-company.test.ts +0 -188
  82. package/src/commands/cloud.push-all.test.ts +0 -485
  83. package/src/commands/cloud.scope-excluded-warning.test.ts +0 -22
  84. package/src/commands/cloud.selectors.test.ts +0 -208
  85. package/src/commands/cloud.status.test.ts +0 -160
  86. package/src/commands/cloud.test.ts +0 -41
  87. package/src/commands/cloud.ts +0 -2130
  88. package/src/commands/company.test.ts +0 -202
  89. package/src/commands/company.ts +0 -148
  90. package/src/commands/creators.test.ts +0 -60
  91. package/src/commands/creators.ts +0 -117
  92. package/src/commands/crm.test.ts +0 -179
  93. package/src/commands/crm.ts +0 -236
  94. package/src/commands/db-migrate.ts +0 -55
  95. package/src/commands/db-provision.ts +0 -114
  96. package/src/commands/db-sql.ts +0 -124
  97. package/src/commands/db-status.ts +0 -108
  98. package/src/commands/db.ts +0 -26
  99. package/src/commands/dm.test.ts +0 -748
  100. package/src/commands/dm.ts +0 -1226
  101. package/src/commands/feedback.test.ts +0 -413
  102. package/src/commands/feedback.ts +0 -169
  103. package/src/commands/files-browse.test.ts +0 -1249
  104. package/src/commands/files-browse.ts +0 -1506
  105. package/src/commands/files-delete.test.ts +0 -484
  106. package/src/commands/files-recovery.test.ts +0 -370
  107. package/src/commands/files.test.ts +0 -722
  108. package/src/commands/files.ts +0 -1446
  109. package/src/commands/group-grants.test.ts +0 -330
  110. package/src/commands/group-grants.ts +0 -455
  111. package/src/commands/groups.test.ts +0 -44
  112. package/src/commands/groups.ts +0 -368
  113. package/src/commands/integrations.test.ts +0 -633
  114. package/src/commands/integrations.ts +0 -507
  115. package/src/commands/list.ts +0 -85
  116. package/src/commands/login.ts +0 -52
  117. package/src/commands/logout.ts +0 -30
  118. package/src/commands/marketplace-install.test.ts +0 -543
  119. package/src/commands/marketplace-security.test.ts +0 -646
  120. package/src/commands/mcp-registration.test.ts +0 -2787
  121. package/src/commands/mcp-registration.ts +0 -2612
  122. package/src/commands/mcp-status.test.ts +0 -483
  123. package/src/commands/mcp-status.ts +0 -575
  124. package/src/commands/mcp-status.us011.test.ts +0 -243
  125. package/src/commands/meetings.test.ts +0 -522
  126. package/src/commands/meetings.ts +0 -789
  127. package/src/commands/members.test.ts +0 -1474
  128. package/src/commands/members.ts +0 -1181
  129. package/src/commands/onboard-identity-guard.test.ts +0 -99
  130. package/src/commands/onboard-identity-guard.ts +0 -103
  131. package/src/commands/onboard-join.test.ts +0 -96
  132. package/src/commands/onboard-join.ts +0 -101
  133. package/src/commands/onboard-warning.test.ts +0 -26
  134. package/src/commands/onboard-warning.ts +0 -12
  135. package/src/commands/onboard.ts +0 -267
  136. package/src/commands/outposts-heartbeat.test.ts +0 -299
  137. package/src/commands/outposts-heartbeat.ts +0 -310
  138. package/src/commands/outposts-replica-sync.test.ts +0 -272
  139. package/src/commands/outposts-self-deploy.test.ts +0 -243
  140. package/src/commands/outposts.test.ts +0 -1039
  141. package/src/commands/outposts.ts +0 -1935
  142. package/src/commands/pack-install-secret-authorization.test.ts +0 -115
  143. package/src/commands/pack-install.test.ts +0 -1469
  144. package/src/commands/pack-install.ts +0 -2256
  145. package/src/commands/pack-update-cache.test.ts +0 -149
  146. package/src/commands/packs-update-api-key.test.ts +0 -105
  147. package/src/commands/packs.test.ts +0 -88
  148. package/src/commands/packs.ts +0 -622
  149. package/src/commands/people.test.ts +0 -807
  150. package/src/commands/people.ts +0 -422
  151. package/src/commands/pkg-install.dispatch.test.ts +0 -103
  152. package/src/commands/pkg-install.ts +0 -221
  153. package/src/commands/pkg-list.test.ts +0 -79
  154. package/src/commands/pkg-list.ts +0 -13
  155. package/src/commands/pkg-remove.ts +0 -71
  156. package/src/commands/pkg-update.ts +0 -189
  157. package/src/commands/publish.test.ts +0 -650
  158. package/src/commands/publish.ts +0 -580
  159. package/src/commands/reindex.test.ts +0 -259
  160. package/src/commands/reindex.ts +0 -274
  161. package/src/commands/rescue.test.ts +0 -39
  162. package/src/commands/rescue.ts +0 -210
  163. package/src/commands/run.env-local.test.ts +0 -88
  164. package/src/commands/run.test.ts +0 -45
  165. package/src/commands/run.ts +0 -190
  166. package/src/commands/safe-extract.test.ts +0 -459
  167. package/src/commands/safe-extract.ts +0 -444
  168. package/src/commands/secrets-scope.test.ts +0 -56
  169. package/src/commands/secrets-scope.ts +0 -32
  170. package/src/commands/secrets.parse-destination.test.ts +0 -38
  171. package/src/commands/secrets.test.ts +0 -2050
  172. package/src/commands/secrets.ts +0 -2249
  173. package/src/commands/signals.ts +0 -351
  174. package/src/commands/skill.test.ts +0 -770
  175. package/src/commands/skill.ts +0 -796
  176. package/src/commands/sources.ts +0 -362
  177. package/src/commands/sync-mode.test.ts +0 -366
  178. package/src/commands/sync-mode.ts +0 -387
  179. package/src/commands/sync-narrow.test.ts +0 -633
  180. package/src/commands/sync-narrow.ts +0 -555
  181. package/src/commands/sync.ts +0 -165
  182. package/src/commands/team-sync.ts +0 -628
  183. package/src/commands/update.ts +0 -71
  184. package/src/commands/whoami.ts +0 -83
  185. package/src/commands/workers.test.ts +0 -158
  186. package/src/commands/workers.ts +0 -301
  187. package/src/index.test.ts +0 -32
  188. package/src/index.ts +0 -20
  189. package/src/lib/db/control-plane.test.ts +0 -59
  190. package/src/lib/db/control-plane.ts +0 -113
  191. package/src/lib/db/local.test.ts +0 -81
  192. package/src/lib/db/local.ts +0 -148
  193. package/src/lib/db/migrate.test.ts +0 -133
  194. package/src/lib/db/migrate.ts +0 -137
  195. package/src/lib/db/paths.test.ts +0 -112
  196. package/src/lib/db/paths.ts +0 -128
  197. package/src/lib/db/remote-engine.test.ts +0 -44
  198. package/src/lib/db/remote-engine.ts +0 -148
  199. package/src/lib/db/remote-sql.test.ts +0 -32
  200. package/src/lib/db/remote-sql.ts +0 -62
  201. package/src/lib/db/sql.test.ts +0 -106
  202. package/src/lib/db/sql.ts +0 -192
  203. package/src/lib/hq-cloud-dep.smoke.test.ts +0 -75
  204. package/src/lib/local-tree-diff.test.ts +0 -301
  205. package/src/lib/local-tree-diff.ts +0 -362
  206. package/src/lib/narrow-hint-banner.test.ts +0 -235
  207. package/src/lib/narrow-hint-banner.ts +0 -212
  208. package/src/lib/plan-limit-nag.test.ts +0 -317
  209. package/src/lib/plan-limit-nag.ts +0 -264
  210. package/src/main.ts +0 -371
  211. package/src/node-preflight.test.ts +0 -60
  212. package/src/node-preflight.ts +0 -67
  213. package/src/outpost/session-heartbeat-bounds.test.ts +0 -195
  214. package/src/outpost/session-heartbeat-guard.test.ts +0 -105
  215. package/src/outpost/session-heartbeat-publisher.test.ts +0 -178
  216. package/src/outpost/session-heartbeat-publisher.ts +0 -186
  217. package/src/outpost/session-heartbeat-retain-guard.test.ts +0 -126
  218. package/src/outpost/session-heartbeat.test.ts +0 -459
  219. package/src/outpost/session-heartbeat.ts +0 -877
  220. package/src/packaging.test.ts +0 -109
  221. package/src/run/__fixtures__/discover-schemas/example.env.schema +0 -4
  222. package/src/run/discover-schemas.test.ts +0 -153
  223. package/src/run/discover-schemas.ts +0 -79
  224. package/src/run/hq-plugin.test.ts +0 -371
  225. package/src/run/hq-plugin.ts +0 -277
  226. package/src/run/varlock-shape.test.ts +0 -57
  227. package/src/schemas/hq-package.schema.json +0 -155
  228. package/src/sentry-before-send.test.ts +0 -359
  229. package/src/sentry-before-send.ts +0 -221
  230. package/src/sentry-epipe.test.ts +0 -37
  231. package/src/sentry-release.test.ts +0 -54
  232. package/src/sentry.test.ts +0 -24
  233. package/src/sentry.ts +0 -54
  234. package/src/strategies/link.ts +0 -62
  235. package/src/strategies/merge.ts +0 -142
  236. package/src/types.ts +0 -146
  237. package/src/utils/__fixtures__/scan-packages.generated-block.sh +0 -23
  238. package/src/utils/auth-error.test.ts +0 -40
  239. package/src/utils/auth-error.ts +0 -42
  240. package/src/utils/billing-gate.test.ts +0 -95
  241. package/src/utils/billing-gate.ts +0 -228
  242. package/src/utils/breadcrumb-buffer.ts +0 -18
  243. package/src/utils/cli-telemetry.test.ts +0 -153
  244. package/src/utils/cli-telemetry.ts +0 -61
  245. package/src/utils/cognito-session.machine.test.ts +0 -109
  246. package/src/utils/cognito-session.refresh.test.ts +0 -84
  247. package/src/utils/cognito-session.test.ts +0 -328
  248. package/src/utils/cognito-session.ts +0 -448
  249. package/src/utils/company-selection-error.test.ts +0 -42
  250. package/src/utils/company-selection-error.ts +0 -45
  251. package/src/utils/contribution-table.ts +0 -83
  252. package/src/utils/environmental-error.test.ts +0 -45
  253. package/src/utils/environmental-error.ts +0 -39
  254. package/src/utils/epipe.test.ts +0 -28
  255. package/src/utils/epipe.ts +0 -29
  256. package/src/utils/expected-cli-error.test.ts +0 -28
  257. package/src/utils/expected-cli-error.ts +0 -39
  258. package/src/utils/feedback-diagnostics.test.ts +0 -183
  259. package/src/utils/feedback-diagnostics.ts +0 -123
  260. package/src/utils/feedback-screenshots.test.ts +0 -134
  261. package/src/utils/feedback-screenshots.ts +0 -124
  262. package/src/utils/feedback-versions.test.ts +0 -98
  263. package/src/utils/feedback-versions.ts +0 -68
  264. package/src/utils/git.ts +0 -74
  265. package/src/utils/integrity.ts +0 -59
  266. package/src/utils/intercepted-process-exit.test.ts +0 -37
  267. package/src/utils/intercepted-process-exit.ts +0 -36
  268. package/src/utils/login-provider.test.ts +0 -42
  269. package/src/utils/login-provider.ts +0 -30
  270. package/src/utils/manifest.test.ts +0 -167
  271. package/src/utils/manifest.ts +0 -131
  272. package/src/utils/pack-contributions.test.ts +0 -603
  273. package/src/utils/pack-contributions.ts +0 -450
  274. package/src/utils/people.ts +0 -215
  275. package/src/utils/registry-client.ts +0 -204
  276. package/src/utils/registry.ts +0 -90
  277. package/src/utils/resolve-vault-credential.test.ts +0 -69
  278. package/src/utils/resolve-vault-credential.ts +0 -60
  279. package/src/utils/sandbox-runner-client.test.ts +0 -279
  280. package/src/utils/sandbox-runner-client.ts +0 -260
  281. package/src/utils/secrets-cache.ts +0 -183
  282. package/src/utils/sentry-identity.ts +0 -45
  283. package/src/utils/settle-with-timeout.test.ts +0 -21
  284. package/src/utils/settle-with-timeout.ts +0 -22
  285. package/src/utils/user-yaml-error.test.ts +0 -24
  286. package/src/utils/user-yaml-error.ts +0 -30
  287. package/src/utils/vault-api.test.ts +0 -536
  288. package/src/utils/vault-api.ts +0 -382
  289. package/src/utils/version-check.test.ts +0 -176
  290. package/src/utils/version-check.ts +0 -155
  291. package/src/utils/version-gate.test.ts +0 -1024
  292. package/src/utils/version-gate.ts +0 -702
  293. package/test/commands/db-tenant-isolation.test.ts +0 -94
  294. package/test/commands/db.test.ts +0 -85
  295. package/test/commands/signals.test.ts +0 -207
  296. package/test/commands/sources.test.ts +0 -231
  297. package/test/e2e/smoke-install-mcp.sh +0 -113
  298. package/test/fixtures/hq-pack-smoke-mcp/mcp/smoke-http.json +0 -1
  299. package/test/fixtures/hq-pack-smoke-mcp/package.yaml +0 -11
  300. package/test/fixtures/signals/action_item/sample.md +0 -16
  301. package/test/fixtures/signals/summary/sample.md +0 -12
  302. package/test/fixtures/sources/meetings/sample.md +0 -25
  303. package/test/helpers/cli-runner.ts +0 -150
  304. package/test/helpers/s3-list-mock.ts +0 -79
  305. package/test/helpers/vault-service-mock.ts +0 -295
  306. package/test/sources-signals/smoke.test.ts +0 -228
  307. package/tsconfig.json +0 -20
  308. package/vitest.config.ts +0 -11
  309. 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
- };