@indigoai-us/hq-cli 5.121.0 → 5.121.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -2,6 +2,24 @@
2
2
 
3
3
  ## [Unreleased]
4
4
 
5
+ ## [5.121.2] — 2026-09-18
6
+
7
+ ### Added
8
+
9
+ - `hq cloud retire company <slug>` soft-tombstones a cloud company (owner
10
+ only, confirm unless `--yes`) so `hq cloud demote company` can run without
11
+ `--propagate-deletes`. Vault files stay; local folders are not touched.
12
+
13
+ ## [5.121.1] — 2026-09-18
14
+
15
+ ### Fixed
16
+
17
+ - `hq --version` prints only the CLI version again. The labelled HQ vs CLI vs
18
+ latest hq-core report stays on `hq version` (and `--json`). Older updaters
19
+ compare the whole `--version` output to the CLI they just installed; reading
20
+ the HQ line made every successful update look like a second install was
21
+ shadowing it.
22
+
5
23
  ### Documentation
6
24
 
7
25
  - The 2026-09-18 fleet-bots GA notes group the published CLI changes for plan
@@ -722,6 +722,37 @@ export declare const COMMAND_CATALOG: readonly [{
722
722
  }];
723
723
  readonly subcommands: readonly [];
724
724
  }];
725
+ }, {
726
+ readonly name: "retire";
727
+ readonly description: "Soft-tombstone a cloud-backed company (owner only)";
728
+ readonly aliases: readonly [];
729
+ readonly hidden: false;
730
+ readonly usage: "[options] [command]";
731
+ readonly arguments: readonly [];
732
+ readonly options: readonly [];
733
+ readonly subcommands: readonly [{
734
+ readonly name: "company";
735
+ readonly description: "Soft-tombstone a cloud company so `hq cloud demote company` can run. Does not delete vault files or local folders. Prompts unless --yes.";
736
+ readonly aliases: readonly [];
737
+ readonly hidden: false;
738
+ readonly usage: "[options] <slug>";
739
+ readonly arguments: readonly [{
740
+ readonly name: "slug";
741
+ readonly required: true;
742
+ readonly variadic: false;
743
+ }];
744
+ readonly options: readonly [{
745
+ readonly flags: "--hq-root <path>";
746
+ readonly description: "Local HQ tree root (default: <runtime-path>)";
747
+ }, {
748
+ readonly flags: "--vault-api-url <url>";
749
+ readonly description: "Vault API URL (default: https://hqapi.hq.computer)";
750
+ }, {
751
+ readonly flags: "-y, --yes";
752
+ readonly description: "Skip the confirmation prompt (for scripts)";
753
+ }];
754
+ readonly subcommands: readonly [];
755
+ }];
725
756
  }, {
726
757
  readonly name: "demote";
727
758
  readonly description: "Demote a cloud-backed entity back to local-only";
@@ -732,7 +763,7 @@ export declare const COMMAND_CATALOG: readonly [{
732
763
  readonly options: readonly [];
733
764
  readonly subcommands: readonly [{
734
765
  readonly name: "company";
735
- readonly description: "Demote a cloud-backed company to local-only after the cloud entity has been soft-tombstoned in hq-console. Removes .hq/config.json, flips company.yaml `cloud: false`, and strips the manifest cloud refs.";
766
+ readonly description: "Demote a cloud-backed company to local-only after the cloud entity has been soft-tombstoned (`hq cloud retire company` or hq-console). Removes .hq/config.json, flips company.yaml `cloud: false`, and strips the manifest cloud refs.";
736
767
  readonly aliases: readonly [];
737
768
  readonly hidden: false;
738
769
  readonly usage: "[options] <slug>";
@@ -924,6 +924,46 @@ export const COMMAND_CATALOG = [
924
924
  }
925
925
  ]
926
926
  },
927
+ {
928
+ "name": "retire",
929
+ "description": "Soft-tombstone a cloud-backed company (owner only)",
930
+ "aliases": [],
931
+ "hidden": false,
932
+ "usage": "[options] [command]",
933
+ "arguments": [],
934
+ "options": [],
935
+ "subcommands": [
936
+ {
937
+ "name": "company",
938
+ "description": "Soft-tombstone a cloud company so `hq cloud demote company` can run. Does not delete vault files or local folders. Prompts unless --yes.",
939
+ "aliases": [],
940
+ "hidden": false,
941
+ "usage": "[options] <slug>",
942
+ "arguments": [
943
+ {
944
+ "name": "slug",
945
+ "required": true,
946
+ "variadic": false
947
+ }
948
+ ],
949
+ "options": [
950
+ {
951
+ "flags": "--hq-root <path>",
952
+ "description": "Local HQ tree root (default: <runtime-path>)"
953
+ },
954
+ {
955
+ "flags": "--vault-api-url <url>",
956
+ "description": "Vault API URL (default: https://hqapi.hq.computer)"
957
+ },
958
+ {
959
+ "flags": "-y, --yes",
960
+ "description": "Skip the confirmation prompt (for scripts)"
961
+ }
962
+ ],
963
+ "subcommands": []
964
+ }
965
+ ]
966
+ },
927
967
  {
928
968
  "name": "demote",
929
969
  "description": "Demote a cloud-backed entity back to local-only",
@@ -935,7 +975,7 @@ export const COMMAND_CATALOG = [
935
975
  "subcommands": [
936
976
  {
937
977
  "name": "company",
938
- "description": "Demote a cloud-backed company to local-only after the cloud entity has been soft-tombstoned in hq-console. Removes .hq/config.json, flips company.yaml `cloud: false`, and strips the manifest cloud refs.",
978
+ "description": "Demote a cloud-backed company to local-only after the cloud entity has been soft-tombstoned (`hq cloud retire company` or hq-console). Removes .hq/config.json, flips company.yaml `cloud: false`, and strips the manifest cloud refs.",
939
979
  "aliases": [],
940
980
  "hidden": false,
941
981
  "usage": "[options] <slug>",
@@ -141,6 +141,12 @@ export declare const REGISTRATION_PLAN: readonly [{
141
141
  readonly parent: "cloud";
142
142
  readonly module: "./commands/cloud-provision.js";
143
143
  readonly exportName: "registerCloudProvisionCommands";
144
+ }, {
145
+ readonly type: "registrar";
146
+ readonly root: "cloud";
147
+ readonly parent: "cloud";
148
+ readonly module: "./commands/cloud-retire.js";
149
+ readonly exportName: "registerCloudRetireCommands";
144
150
  }, {
145
151
  readonly type: "registrar";
146
152
  readonly root: "cloud";
@@ -26,6 +26,7 @@ export const REGISTRATION_PLAN = [
26
26
  { type: "registrar", root: "sync", parent: "sync", module: "./commands/sync-manifest.js", exportName: "registerSyncManifestCommand" },
27
27
  { type: "group", root: "cloud", id: "cloud", name: "cloud", description: "Cloud commands — provision entities and manage cloud-backed companies" },
28
28
  { type: "registrar", root: "cloud", parent: "cloud", module: "./commands/cloud-provision.js", exportName: "registerCloudProvisionCommands" },
29
+ { type: "registrar", root: "cloud", parent: "cloud", module: "./commands/cloud-retire.js", exportName: "registerCloudRetireCommands" },
29
30
  { type: "registrar", root: "cloud", parent: "cloud", module: "./commands/cloud-demote.js", exportName: "registerCloudDemoteCommands" },
30
31
  { type: "registrar", root: "team-sync", parent: "program", module: "./commands/team-sync.js", exportName: "registerTeamSyncCommand" },
31
32
  { type: "registrar", root: "login", parent: "program", module: "./commands/login.js", exportName: "registerLoginCommand" },
@@ -160,8 +160,9 @@ export function registerCloudDemoteCommands(program) {
160
160
  demoteCmd
161
161
  .command("company")
162
162
  .description("Demote a cloud-backed company to local-only after the cloud entity " +
163
- "has been soft-tombstoned in hq-console. Removes .hq/config.json, " +
164
- "flips company.yaml `cloud: false`, and strips the manifest cloud refs.")
163
+ "has been soft-tombstoned (`hq cloud retire company` or hq-console). " +
164
+ "Removes .hq/config.json, flips company.yaml `cloud: false`, and " +
165
+ "strips the manifest cloud refs.")
165
166
  .argument("<slug>", "Company slug")
166
167
  .option("--hq-root <path>", `Local HQ tree root (default: ${DEFAULT_HQ_ROOT})`, DEFAULT_HQ_ROOT)
167
168
  .option("--vault-api-url <url>", `Vault API URL (default: ${DEFAULT_VAULT_API_URL})`, DEFAULT_VAULT_API_URL)
@@ -38,6 +38,8 @@ export interface VaultEntity {
38
38
  kmsKeyId?: string | null;
39
39
  status?: string;
40
40
  ownerUid?: string;
41
+ /** Set by hq-pro after a soft tombstone. Absent on live companies. */
42
+ deleted?: boolean;
41
43
  }
42
44
  /** Per-company `.hq/config.json` schema (matches AppBar `provision.rs::CompanyConfig`). */
43
45
  export interface CompanyConfig {
@@ -142,6 +144,15 @@ export interface VaultClient {
142
144
  name: string;
143
145
  ownerUid?: string;
144
146
  }): Promise<VaultEntity>;
147
+ /**
148
+ * Soft-tombstone a company (`DELETE /entity/{uid}`). Owner-only. Leaves the
149
+ * row, S3 bucket, KMS key, and memberships intact. Idempotent on an already
150
+ * deleted company. Used by `hq cloud retire company`.
151
+ */
152
+ softDeleteCompany(uid: string): Promise<{
153
+ deleted: true;
154
+ uid: string;
155
+ }>;
145
156
  }
146
157
  /** Custom error class so the CLI runner can map to exit codes. */
147
158
  export declare class ProvisionError extends Error {
@@ -364,6 +364,31 @@ export function createDefaultVaultClient(apiUrl, accessToken) {
364
364
  }
365
365
  return data.entity;
366
366
  },
367
+ async softDeleteCompany(uid) {
368
+ const url = `${apiUrl.replace(/\/$/, "")}/entity/${encodeURIComponent(uid)}`;
369
+ const res = await fetch(url, { method: "DELETE", headers });
370
+ if (res.status === 403) {
371
+ const body = await safeBody(res);
372
+ throw new ProvisionError(2, `Refusing to retire '${uid}': only the company owner can soft-tombstone it. ${body}`);
373
+ }
374
+ if (res.status === 404) {
375
+ const body = await safeBody(res);
376
+ throw new ProvisionError(2, `Vault DELETE /entity/${uid} failed: not found — ${body}`);
377
+ }
378
+ if (res.status === 400) {
379
+ const body = await safeBody(res);
380
+ throw new ProvisionError(2, `Vault DELETE /entity/${uid} failed: ${res.status} ${res.statusText} — ${body}`);
381
+ }
382
+ if (!res.ok) {
383
+ const body = await safeBody(res);
384
+ throw new ProvisionError(1, `Vault DELETE /entity/${uid} failed: ${res.status} ${res.statusText} — ${body}`);
385
+ }
386
+ const data = (await res.json());
387
+ if (data.deleted !== true || typeof data.uid !== "string") {
388
+ throw new ProvisionError(1, `Vault DELETE /entity/${uid} returned ${res.status} with no deleted confirmation`);
389
+ }
390
+ return { deleted: true, uid: data.uid };
391
+ },
367
392
  };
368
393
  }
369
394
  async function safeBody(res) {
@@ -0,0 +1,51 @@
1
+ /**
2
+ * `hq cloud retire company <slug>` — soft-tombstone a cloud company so
3
+ * `hq cloud demote company` can run without `--propagate-deletes`.
4
+ *
5
+ * Console equivalent: Settings → Delete company (`DELETE /entity/{uid}`).
6
+ * Owner-only. The entity row, S3 bucket, KMS key, and memberships stay;
7
+ * `/membership/me` stops listing the company. Local files are not touched —
8
+ * demote is the follow-up that flips the local tree back to local-only.
9
+ *
10
+ * Confirm step is on by default (`[y/N]`). `--yes` skips it for scripts.
11
+ * A non-TTY stdin without `--yes` refuses rather than hanging.
12
+ *
13
+ * Exit codes (mirrors cloud-provision / cloud-demote):
14
+ * 0 — success, already tombstoned, or the operator cancelled.
15
+ * 1 — vault HTTP failure (auth/network/5xx).
16
+ * 2 — validation (bad slug, no entity, not owner, missing confirm).
17
+ */
18
+ import { Command } from "commander";
19
+ import { type VaultClient } from "./cloud-provision.js";
20
+ /** Final stdout JSON shape. */
21
+ export interface RetireResult {
22
+ ok: boolean;
23
+ company_slug: string;
24
+ cloud_uid: string | null;
25
+ /** True when hq-pro already had deleted=true (no DELETE sent). */
26
+ already_deleted: boolean;
27
+ cancelled?: boolean;
28
+ }
29
+ export interface RetireCompanyOptions {
30
+ slug: string;
31
+ hqRoot: string;
32
+ vaultApiUrl: string;
33
+ /** Skip the interactive confirmation prompt. */
34
+ yes?: boolean;
35
+ vaultClient?: VaultClient;
36
+ resolveAccessToken?: () => Promise<string>;
37
+ confirm?: (question: string) => Promise<boolean>;
38
+ stdinIsTTY?: boolean;
39
+ }
40
+ export declare function defaultConfirm(question: string): Promise<boolean>;
41
+ /**
42
+ * Read `companyUid` from `companies/<slug>/.hq/config.json` when the by-slug
43
+ * lookup misses (a tombstoned company is filtered from that route).
44
+ */
45
+ export declare function readLocalCompanyUid(hqRoot: string, slug: string): string | null;
46
+ export declare function retireCompany(options: RetireCompanyOptions): Promise<RetireResult>;
47
+ /**
48
+ * Register `retire company <slug>` under the `cloud` command group.
49
+ */
50
+ export declare function registerCloudRetireCommands(program: Command): void;
51
+ //# sourceMappingURL=cloud-retire.d.ts.map
@@ -0,0 +1,154 @@
1
+ /**
2
+ * `hq cloud retire company <slug>` — soft-tombstone a cloud company so
3
+ * `hq cloud demote company` can run without `--propagate-deletes`.
4
+ *
5
+ * Console equivalent: Settings → Delete company (`DELETE /entity/{uid}`).
6
+ * Owner-only. The entity row, S3 bucket, KMS key, and memberships stay;
7
+ * `/membership/me` stops listing the company. Local files are not touched —
8
+ * demote is the follow-up that flips the local tree back to local-only.
9
+ *
10
+ * Confirm step is on by default (`[y/N]`). `--yes` skips it for scripts.
11
+ * A non-TTY stdin without `--yes` refuses rather than hanging.
12
+ *
13
+ * Exit codes (mirrors cloud-provision / cloud-demote):
14
+ * 0 — success, already tombstoned, or the operator cancelled.
15
+ * 1 — vault HTTP failure (auth/network/5xx).
16
+ * 2 — validation (bad slug, no entity, not owner, missing confirm).
17
+ */
18
+ import * as fs from "node:fs";
19
+ import * as readline from "node:readline";
20
+ import chalk from "chalk";
21
+ import { ProvisionError, companyConfigPath, createDefaultVaultClient, validateSlug, } from "./cloud-provision.js";
22
+ import { DEFAULT_HQ_ROOT, DEFAULT_VAULT_API_URL, ensureCognitoToken, } from "../utils/cognito-session.js";
23
+ export function defaultConfirm(question) {
24
+ const rl = readline.createInterface({
25
+ input: process.stdin,
26
+ output: process.stdout,
27
+ });
28
+ return new Promise((resolve) => {
29
+ rl.question(`${question} [y/N] `, (answer) => {
30
+ rl.close();
31
+ resolve(/^y(es)?$/i.test(answer.trim()));
32
+ });
33
+ });
34
+ }
35
+ /**
36
+ * Read `companyUid` from `companies/<slug>/.hq/config.json` when the by-slug
37
+ * lookup misses (a tombstoned company is filtered from that route).
38
+ */
39
+ export function readLocalCompanyUid(hqRoot, slug) {
40
+ const cPath = companyConfigPath(hqRoot, slug);
41
+ if (!fs.existsSync(cPath))
42
+ return null;
43
+ try {
44
+ const parsed = JSON.parse(fs.readFileSync(cPath, "utf-8"));
45
+ return typeof parsed.companyUid === "string" && parsed.companyUid.length > 0
46
+ ? parsed.companyUid
47
+ : null;
48
+ }
49
+ catch {
50
+ return null;
51
+ }
52
+ }
53
+ export async function retireCompany(options) {
54
+ validateSlug(options.slug);
55
+ const accessToken = options.resolveAccessToken
56
+ ? await options.resolveAccessToken()
57
+ : await ensureCognitoToken();
58
+ const client = options.vaultClient ??
59
+ createDefaultVaultClient(options.vaultApiUrl, accessToken);
60
+ let entity;
61
+ try {
62
+ entity = await client.findCompanyBySlug(options.slug);
63
+ }
64
+ catch (err) {
65
+ if (err instanceof ProvisionError)
66
+ throw err;
67
+ throw new ProvisionError(1, `Vault GET by-slug failed: ${err instanceof Error ? err.message : String(err)}`);
68
+ }
69
+ if (entity?.deleted === true) {
70
+ return {
71
+ ok: true,
72
+ company_slug: options.slug,
73
+ cloud_uid: entity.uid,
74
+ already_deleted: true,
75
+ };
76
+ }
77
+ const uid = entity?.uid ?? readLocalCompanyUid(options.hqRoot, options.slug);
78
+ if (!uid) {
79
+ throw new ProvisionError(2, `Refusing to retire '${options.slug}': no cloud entity found.`);
80
+ }
81
+ if (options.yes !== true) {
82
+ const tty = options.stdinIsTTY ?? process.stdin.isTTY === true;
83
+ if (!tty) {
84
+ throw new ProvisionError(2, `Refusing to retire '${options.slug}' without confirmation. Re-run with --yes.`);
85
+ }
86
+ const confirm = options.confirm ?? defaultConfirm;
87
+ const ok = await confirm(`Soft-tombstone cloud company '${options.slug}' (${uid})? ` +
88
+ `It will disappear from every member's picker. Vault files stay. ` +
89
+ `Then run: hq cloud demote company ${options.slug}`);
90
+ if (!ok) {
91
+ return {
92
+ ok: false,
93
+ company_slug: options.slug,
94
+ cloud_uid: uid,
95
+ already_deleted: false,
96
+ cancelled: true,
97
+ };
98
+ }
99
+ }
100
+ try {
101
+ await client.softDeleteCompany(uid);
102
+ }
103
+ catch (err) {
104
+ if (err instanceof ProvisionError)
105
+ throw err;
106
+ throw new ProvisionError(1, `Vault DELETE /entity/${uid} failed: ${err instanceof Error ? err.message : String(err)}`);
107
+ }
108
+ return {
109
+ ok: true,
110
+ company_slug: options.slug,
111
+ cloud_uid: uid,
112
+ already_deleted: false,
113
+ };
114
+ }
115
+ /**
116
+ * Register `retire company <slug>` under the `cloud` command group.
117
+ */
118
+ export function registerCloudRetireCommands(program) {
119
+ const retireCmd = program
120
+ .command("retire")
121
+ .description("Soft-tombstone a cloud-backed company (owner only)");
122
+ retireCmd
123
+ .command("company")
124
+ .description("Soft-tombstone a cloud company so `hq cloud demote company` can run. " +
125
+ "Does not delete vault files or local folders. Prompts unless --yes.")
126
+ .argument("<slug>", "Company slug")
127
+ .option("--hq-root <path>", `Local HQ tree root (default: ${DEFAULT_HQ_ROOT})`, DEFAULT_HQ_ROOT)
128
+ .option("--vault-api-url <url>", `Vault API URL (default: ${DEFAULT_VAULT_API_URL})`, DEFAULT_VAULT_API_URL)
129
+ .option("-y, --yes", "Skip the confirmation prompt (for scripts)")
130
+ .action(async (slug, options) => {
131
+ try {
132
+ const result = await retireCompany({
133
+ slug,
134
+ hqRoot: options.hqRoot,
135
+ vaultApiUrl: options.vaultApiUrl,
136
+ yes: options.yes,
137
+ });
138
+ process.stdout.write(JSON.stringify(result) + "\n");
139
+ if (result.cancelled) {
140
+ process.stderr.write(chalk.dim("[hq cloud retire] Cancelled — nothing was tombstoned.\n"));
141
+ }
142
+ process.exit(0);
143
+ }
144
+ catch (err) {
145
+ if (err instanceof ProvisionError) {
146
+ process.stderr.write(chalk.red(`[hq cloud retire] ${err.message}\n`));
147
+ process.exit(err.code);
148
+ }
149
+ process.stderr.write(chalk.red(`[hq cloud retire] Unexpected error: ${err instanceof Error ? err.message : String(err)}\n`));
150
+ process.exit(1);
151
+ }
152
+ });
153
+ }
154
+ //# sourceMappingURL=cloud-retire.js.map
@@ -1,9 +1,9 @@
1
1
  /**
2
2
  * hq version — labelled HQ vs CLI vs latest hq-core.
3
3
  *
4
- * `hq --version` is intercepted on the fast path in index.ts and prints the
5
- * same report. This registrar exists so `hq --help` lists the command and so
6
- * `hq version --json` still works if the fast path is skipped.
4
+ * Bare `hq --version` is intercepted on the fast path in index.ts and prints
5
+ * only the CLI semver. This registrar exists so `hq --help` lists the command
6
+ * and so `hq version` / `hq version --json` still work if the fast path is skipped.
7
7
  */
8
8
  import { Command } from "commander";
9
9
  export declare function registerVersionCommand(program: Command): void;
package/dist/index.js CHANGED
@@ -3,7 +3,7 @@
3
3
  // Node 20+ API (e.g. util.styleText) or a newer native ABI is evaluated.
4
4
  import "./node-preflight.js";
5
5
  import "./node-network-compat.js";
6
- import { isVersionRequest } from "./version-request.js";
6
+ import { isVersionRequest, wantsLabelledVersionReport } from "./version-request.js";
7
7
  // Dependency-light: a pure parser + table, no command graph. Safe to load on
8
8
  // every path (including --version) without reintroducing the heavy startup.
9
9
  import { isFastCoreRequest } from "./commands/scaffold-fast.js";
@@ -18,9 +18,17 @@ import { installProcessRejectionBoundary } from "./unhandled-rejection-boundary.
18
18
  // so runCli's finally still finalizes release health and flushes Sentry.
19
19
  installProcessRejectionBoundary();
20
20
  if (isVersionRequest(process.argv)) {
21
- // Same report as `hq version`: HQ, CLI, latest hq-core. Lazy so --version
22
- // still skips the command graph.
23
- void import("./utils/version-report.js").then(({ runVersionCommand }) => runVersionCommand(process.argv));
21
+ // Bare `--version` is the CLI semver only (the contract field updaters
22
+ // compare). `hq version` and `--json` print the labelled HQ/CLI report.
23
+ // Lazy so this path still skips the command graph.
24
+ if (wantsLabelledVersionReport(process.argv)) {
25
+ void import("./utils/version-report.js").then(({ runVersionCommand }) => runVersionCommand(process.argv));
26
+ }
27
+ else {
28
+ void import("./cli-version.js").then(({ CLI_VERSION }) => {
29
+ process.stdout.write(`${CLI_VERSION}\n`);
30
+ });
31
+ }
24
32
  }
25
33
  else if (isFastCoreRequest(process.argv)) {
26
34
  // Hot relocated plumbing (`hq core hq-session`, `hq core checkpoint-stop-gate`)
@@ -348,11 +348,16 @@ declare function nudgeUpdateRecommended(decision: VersionCheckResponse, install?
348
348
  */
349
349
  export declare function resolveHqOnPath(): string | null;
350
350
  /**
351
- * Pull the CLI version out of `hq --version` output.
351
+ * Pull the CLI version out of `hq --version` / `hq version` output.
352
352
  *
353
- * Older builds printed a bare semver. Current builds print a labelled report
354
- * (`HQ …` / `CLI 5.119.12` / …). Convergence must accept both, otherwise a
355
- * successful install looks like a PATH shadow.
353
+ * Compare CLI to CLI. Never take the first version-shaped token: 5.119.13–
354
+ * 5.121.0 printed `HQ <core>` above `CLI <cli>`, and scraping the HQ line
355
+ * made a successful install look like a PATH shadow.
356
+ *
357
+ * Accepts:
358
+ * - bare semver (historical `--version`, and `--version` again after this fix)
359
+ * - labelled `CLI x.y.z` (those 5.119.13–5.121.0 builds)
360
+ * - JSON `{ "cli": "x.y.z", ... }` from `--json`
356
361
  */
357
362
  export declare function parseReportedCliVersion(stdout: string): string | null;
358
363
  /** `<bin> --version` output (trimmed), or null on any failure/timeout. */
@@ -811,17 +811,33 @@ export function resolveHqOnPath() {
811
811
  }
812
812
  }
813
813
  /**
814
- * Pull the CLI version out of `hq --version` output.
814
+ * Pull the CLI version out of `hq --version` / `hq version` output.
815
815
  *
816
- * Older builds printed a bare semver. Current builds print a labelled report
817
- * (`HQ …` / `CLI 5.119.12` / …). Convergence must accept both, otherwise a
818
- * successful install looks like a PATH shadow.
816
+ * Compare CLI to CLI. Never take the first version-shaped token: 5.119.13–
817
+ * 5.121.0 printed `HQ <core>` above `CLI <cli>`, and scraping the HQ line
818
+ * made a successful install look like a PATH shadow.
819
+ *
820
+ * Accepts:
821
+ * - bare semver (historical `--version`, and `--version` again after this fix)
822
+ * - labelled `CLI x.y.z` (those 5.119.13–5.121.0 builds)
823
+ * - JSON `{ "cli": "x.y.z", ... }` from `--json`
819
824
  */
820
825
  export function parseReportedCliVersion(stdout) {
821
826
  const text = stdout.trim();
822
827
  if (!text)
823
828
  return null;
824
- const labelled = /^CLI\s+(\S+)/m.exec(text);
829
+ if (text.startsWith("{")) {
830
+ try {
831
+ const parsed = JSON.parse(text);
832
+ if (typeof parsed.cli === "string" && parsed.cli.trim()) {
833
+ return parsed.cli.trim();
834
+ }
835
+ }
836
+ catch {
837
+ // Fall through to labelled / bare parsers.
838
+ }
839
+ }
840
+ const labelled = /^[ \t]*CLI[ \t]+(\d+\.\d+\.\d+(?:-[0-9A-Za-z.-]+)?)/m.exec(text);
825
841
  if (labelled)
826
842
  return labelled[1];
827
843
  const lines = text
@@ -19,8 +19,7 @@ export declare function fetchLatestHqCoreRelease(fetchImpl?: typeof fetch, timeo
19
19
  export declare function collectVersionReport(deps?: VersionReportDeps): Promise<VersionReport>;
20
20
  export declare function formatVersionReport(report: VersionReport): string;
21
21
  export declare function versionReportJson(report: VersionReport): string;
22
- export { isVersionRequest } from "../version-request.js";
23
- export declare function wantsJson(argv: readonly string[]): boolean;
22
+ export { isVersionRequest, wantsJson } from "../version-request.js";
24
23
  export declare function runVersionCommand(argv?: readonly string[], deps?: VersionReportDeps, stdout?: {
25
24
  write(chunk: string): unknown;
26
25
  }): Promise<void>;
@@ -1,12 +1,16 @@
1
1
  /**
2
- * The payload behind `hq version` / labelled `hq --version`.
2
+ * The payload behind `hq version` and `hq --version --json`.
3
3
  *
4
4
  * Prints the installed HQ scaffold version, the CLI version (labelled so it
5
5
  * cannot be confused with HQ), and the latest hq-core GitHub release. A
6
6
  * network failure is an explicit line, never a silent omission.
7
+ *
8
+ * Bare `hq --version` / `-V` / `-v` do not use this report: they print the
9
+ * CLI semver alone so older updaters that compare whole stdout still work.
7
10
  */
8
11
  import semver from "semver";
9
12
  import { CLI_VERSION } from "../cli-version.js";
13
+ import { wantsJson } from "../version-request.js";
10
14
  import { findHqRoot } from "./manifest.js";
11
15
  import { readHqVersion } from "./pack-contributions.js";
12
16
  export const HQ_CORE_LATEST_URL = "https://api.github.com/repos/indigoai-us/hq-core/releases/latest";
@@ -86,10 +90,7 @@ export function versionReportJson(report) {
86
90
  latestLookup: report.latestLookup,
87
91
  }, null, 2)}\n`;
88
92
  }
89
- export { isVersionRequest } from "../version-request.js";
90
- export function wantsJson(argv) {
91
- return argv.includes("--json");
92
- }
93
+ export { isVersionRequest, wantsJson } from "../version-request.js";
93
94
  export async function runVersionCommand(argv = process.argv, deps = {}, stdout = process.stdout) {
94
95
  const report = await collectVersionReport(deps);
95
96
  stdout.write(wantsJson(argv) ? versionReportJson(report) : formatVersionReport(report));
@@ -4,4 +4,11 @@
4
4
  * yaml, semver, or the command graph.
5
5
  */
6
6
  export declare function isVersionRequest(argv: readonly string[]): boolean;
7
+ export declare function wantsJson(argv: readonly string[]): boolean;
8
+ /**
9
+ * The labelled HQ / CLI / latest-hq-core report (and JSON). Bare
10
+ * `--version` / `-V` / `-v` stay a single CLI semver so field updaters that
11
+ * compare the whole stdout to the version they just installed still converge.
12
+ */
13
+ export declare function wantsLabelledVersionReport(argv: readonly string[]): boolean;
7
14
  //# sourceMappingURL=version-request.d.ts.map
@@ -19,4 +19,18 @@ export function isVersionRequest(argv) {
19
19
  }
20
20
  return positionals.length === 1 && positionals[0] === "version";
21
21
  }
22
+ export function wantsJson(argv) {
23
+ return argv.includes("--json");
24
+ }
25
+ /**
26
+ * The labelled HQ / CLI / latest-hq-core report (and JSON). Bare
27
+ * `--version` / `-V` / `-v` stay a single CLI semver so field updaters that
28
+ * compare the whole stdout to the version they just installed still converge.
29
+ */
30
+ export function wantsLabelledVersionReport(argv) {
31
+ if (wantsJson(argv))
32
+ return true;
33
+ const positionals = argv.slice(2).filter((a) => !a.startsWith("-"));
34
+ return positionals.length === 1 && positionals[0] === "version";
35
+ }
22
36
  //# sourceMappingURL=version-request.js.map
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@indigoai-us/hq-cli",
3
- "version": "5.121.0",
3
+ "version": "5.121.2",
4
4
  "description": "HQ by Indigo management CLI \u2014 modules and cloud sync",
5
5
  "main": "dist/index.js",
6
6
  "bin": {