@indigoai-us/hq-cli 5.119.11 → 5.119.13

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.119.13] — 2026-09-18
6
+
7
+ ### Added
8
+
9
+ - `hq version` (and `hq --version`) print the installed HQ version, the CLI
10
+ version labelled as CLI, and the latest hq-core release. If a newer HQ is
11
+ out it says to run `/update-hq`; if the lookup cannot reach the network it
12
+ says so instead of going quiet. `hq version --json` prints the same payload.
13
+
14
+ ## [5.119.12] — 2026-09-18
15
+
16
+ ### Fixed
17
+
18
+ - `hq files cat … --company prs_<person-uid>` now reads the file instead of
19
+ refusing with "No company found for slug". Reading a DM attachment needs the
20
+ sender's vault as the scope, and `cat` resolved that value as a company slug
21
+ of its own, after the same fix landed on the presign path.
22
+
5
23
  ## [5.119.11] — 2026-09-18
6
24
 
7
25
  ### Added
@@ -805,6 +805,18 @@ export declare const COMMAND_CATALOG: readonly [{
805
805
  readonly description: "Also report this company's workspace plan state (Starter lock)";
806
806
  }];
807
807
  readonly subcommands: readonly [];
808
+ }, {
809
+ readonly name: "version";
810
+ readonly description: "Show HQ, CLI, and latest hq-core versions";
811
+ readonly aliases: readonly [];
812
+ readonly hidden: false;
813
+ readonly usage: "[options]";
814
+ readonly arguments: readonly [];
815
+ readonly options: readonly [{
816
+ readonly flags: "--json";
817
+ readonly description: "Output the same payload as JSON";
818
+ }];
819
+ readonly subcommands: readonly [];
808
820
  }, {
809
821
  readonly name: "auth";
810
822
  readonly description: "Manage the local HQ Cognito session";
@@ -1029,6 +1029,21 @@ export const COMMAND_CATALOG = [
1029
1029
  ],
1030
1030
  "subcommands": []
1031
1031
  },
1032
+ {
1033
+ "name": "version",
1034
+ "description": "Show HQ, CLI, and latest hq-core versions",
1035
+ "aliases": [],
1036
+ "hidden": false,
1037
+ "usage": "[options]",
1038
+ "arguments": [],
1039
+ "options": [
1040
+ {
1041
+ "flags": "--json",
1042
+ "description": "Output the same payload as JSON"
1043
+ }
1044
+ ],
1045
+ "subcommands": []
1046
+ },
1032
1047
  {
1033
1048
  "name": "auth",
1034
1049
  "description": "Manage the local HQ Cognito session",
@@ -171,6 +171,12 @@ export declare const REGISTRATION_PLAN: readonly [{
171
171
  readonly parent: "program";
172
172
  readonly module: "./commands/whoami.js";
173
173
  readonly exportName: "registerWhoamiCommand";
174
+ }, {
175
+ readonly type: "registrar";
176
+ readonly root: "version";
177
+ readonly parent: "program";
178
+ readonly module: "./commands/version.js";
179
+ readonly exportName: "registerVersionCommand";
174
180
  }, {
175
181
  readonly type: "registrar";
176
182
  readonly root: "auth";
@@ -31,6 +31,7 @@ export const REGISTRATION_PLAN = [
31
31
  { type: "registrar", root: "login", parent: "program", module: "./commands/login.js", exportName: "registerLoginCommand" },
32
32
  { type: "registrar", root: "logout", parent: "program", module: "./commands/logout.js", exportName: "registerLogoutCommand" },
33
33
  { type: "registrar", root: "whoami", parent: "program", module: "./commands/whoami.js", exportName: "registerWhoamiCommand" },
34
+ { type: "registrar", root: "version", parent: "program", module: "./commands/version.js", exportName: "registerVersionCommand" },
34
35
  { type: "registrar", root: "auth", parent: "program", module: "./commands/auth.js", exportName: "registerAuthCommands" },
35
36
  { type: "registrar", root: "secrets", parent: "program", module: "./commands/secrets.js", exportName: "registerSecretsCommand" },
36
37
  { type: "registrar", root: "db", parent: "program", module: "./commands/db.js", exportName: "registerDbCommand" },
@@ -43,7 +43,7 @@ import { S3Client, ListObjectsV2Command, GetObjectCommand, } from "@aws-sdk/clie
43
43
  import { VaultClient, grantPathToPrefix, } from "@indigoai-us/hq-cloud";
44
44
  import { DEFAULT_HQ_ROOT, DEFAULT_COGNITO, buildVaultConfig, } from "../utils/cognito-session.js";
45
45
  import { resolveVaultCredential } from "../utils/resolve-vault-credential.js";
46
- import { getCompanyUid, looksLikeCompanyUid, vaultApiFetch, } from "../utils/vault-api.js";
46
+ import { getCompanyUid, looksLikeCompanyUid, looksLikePersonVaultUid, vaultApiFetch, } from "../utils/vault-api.js";
47
47
  import { resolveCanonicalPersonUid } from "./cloud.js";
48
48
  // ── Pure helpers ────────────────────────────────────────────────────────────
49
49
  /**
@@ -306,18 +306,37 @@ export async function runCat(input) {
306
306
  }
307
307
  else {
308
308
  const slug = input.companySlug ?? parseCompanySlugFromPath(key);
309
- const entity = await vaultClient.entity.findInMyNamespace("company", slug);
310
- if (!entity) {
311
- throw new Error(`No company found for slug '${slug}' in your namespace. Confirm you have an active membership.`);
309
+ if (looksLikePersonVaultUid(slug)) {
310
+ // A `prs_` scope names a PERSON'S vault, which is where a DM attachment
311
+ // lives (`chat/attachments/dm/<uidA>--<uidB>/…`). There is no company
312
+ // entity to look up — `findInMyNamespace("company", …)` can only ever
313
+ // find a company, so it refuses the one scope the server accepts
314
+ // (hq-pro #3571 authorizes either member of the pair; hq-cli #644 made
315
+ // the presign path carry the ref). Go straight to the presign path and
316
+ // let the vault API's per-key gate decide.
317
+ //
318
+ // The key is already bucket-relative: a person vault has no
319
+ // `companies/<slug>/` anchor to strip. `Bucket` is inert on this path —
320
+ // the browse-vend company client presigns by companyUid + key and never
321
+ // reads it — so the scope ref is carried through for error messages.
322
+ bucket = slug;
323
+ s3Key = key;
324
+ s3 = requireCompanyClient(input.companyClient)({ companyUid: slug });
312
325
  }
313
- if (!entity.bucketName) {
314
- throw new Error(`Company '${slug}' (${entity.uid}) has no provisioned bucket.`);
326
+ else {
327
+ const entity = await vaultClient.entity.findInMyNamespace("company", slug);
328
+ if (!entity) {
329
+ throw new Error(`No company found for slug '${slug}' in your namespace. Confirm you have an active membership.`);
330
+ }
331
+ if (!entity.bucketName) {
332
+ throw new Error(`Company '${slug}' (${entity.uid}) has no provisioned bucket.`);
333
+ }
334
+ bucket = entity.bucketName;
335
+ // Translate the anchored CLI key into the company-relative bucket key.
336
+ s3Key = toBucketRelative(key, slug);
337
+ // COMPANY mode (HQ-59): GetObject → presign GET. No STS vend, no direct S3.
338
+ s3 = requireCompanyClient(input.companyClient)({ companyUid: entity.uid });
315
339
  }
316
- bucket = entity.bucketName;
317
- // Translate the anchored CLI key into the company-relative bucket key.
318
- s3Key = toBucketRelative(key, slug);
319
- // COMPANY mode (HQ-59): GetObject → presign GET. No STS vend, no direct S3.
320
- s3 = requireCompanyClient(input.companyClient)({ companyUid: entity.uid });
321
340
  }
322
341
  // HQ-CLI-5: when writing to --out, create the parent directory BEFORE issuing
323
342
  // the presigned GET, so no synchronous filesystem call sits between receiving
@@ -0,0 +1,10 @@
1
+ /**
2
+ * hq version — labelled HQ vs CLI vs latest hq-core.
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.
7
+ */
8
+ import { Command } from "commander";
9
+ export declare function registerVersionCommand(program: Command): void;
10
+ //# sourceMappingURL=version.d.ts.map
@@ -0,0 +1,11 @@
1
+ import { runVersionCommand } from "../utils/version-report.js";
2
+ export function registerVersionCommand(program) {
3
+ program
4
+ .command("version")
5
+ .description("Show HQ, CLI, and latest hq-core versions")
6
+ .option("--json", "Output the same payload as JSON")
7
+ .action(async () => {
8
+ await runVersionCommand(process.argv);
9
+ });
10
+ }
11
+ //# sourceMappingURL=version.js.map
package/dist/index.d.ts CHANGED
@@ -1,11 +1,10 @@
1
1
  #!/usr/bin/env node
2
2
  import "./node-preflight.js";
3
3
  import "./node-network-compat.js";
4
+ import { isVersionRequest } from "./version-request.js";
4
5
  import { isFastCoreRequest } from "./commands/scaffold-fast.js";
5
- declare function isVersionRequest(argv: readonly string[]): boolean;
6
6
  export declare const __test__: {
7
7
  isVersionRequest: typeof isVersionRequest;
8
8
  isFastCoreRequest: typeof isFastCoreRequest;
9
9
  };
10
- export {};
11
10
  //# sourceMappingURL=index.d.ts.map
package/dist/index.js CHANGED
@@ -3,17 +3,13 @@
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 { CLI_VERSION } from "./cli-version.js";
6
+ import { isVersionRequest } 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";
10
10
  // Also dependency-light: registers a process-level rejection boundary; its only
11
11
  // heavy import (main.js) is lazy and reached only when a rejection fires.
12
12
  import { installProcessRejectionBoundary } from "./unhandled-rejection-boundary.js";
13
- function isVersionRequest(argv) {
14
- const args = argv.slice(2);
15
- return args.length === 1 && (args[0] === "--version" || args[0] === "-V" || args[0] === "-v");
16
- }
17
13
  // Install BEFORE any dispatch so a floated rejection on ANY path (the command
18
14
  // graph or the fast-core forwarder) becomes a non-zero exit. @sentry/node's
19
15
  // onUnhandledRejection integration defaults to 'warn' — it captures but does
@@ -22,7 +18,9 @@ function isVersionRequest(argv) {
22
18
  // so runCli's finally still finalizes release health and flushes Sentry.
23
19
  installProcessRejectionBoundary();
24
20
  if (isVersionRequest(process.argv)) {
25
- process.stdout.write(`${CLI_VERSION}\n`);
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));
26
24
  }
27
25
  else if (isFastCoreRequest(process.argv)) {
28
26
  // Hot relocated plumbing (`hq core hq-session`, `hq core checkpoint-stop-gate`)
@@ -347,6 +347,14 @@ declare function nudgeUpdateRecommended(decision: VersionCheckResponse, install?
347
347
  * see what the USER's next invocation resolves.
348
348
  */
349
349
  export declare function resolveHqOnPath(): string | null;
350
+ /**
351
+ * Pull the CLI version out of `hq --version` output.
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.
356
+ */
357
+ export declare function parseReportedCliVersion(stdout: string): string | null;
350
358
  /** `<bin> --version` output (trimmed), or null on any failure/timeout. */
351
359
  export declare function probeCliVersion(bin: string): string | null;
352
360
  /**
@@ -810,6 +810,28 @@ export function resolveHqOnPath() {
810
810
  return null;
811
811
  }
812
812
  }
813
+ /**
814
+ * Pull the CLI version out of `hq --version` output.
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.
819
+ */
820
+ export function parseReportedCliVersion(stdout) {
821
+ const text = stdout.trim();
822
+ if (!text)
823
+ return null;
824
+ const labelled = /^CLI\s+(\S+)/m.exec(text);
825
+ if (labelled)
826
+ return labelled[1];
827
+ const lines = text
828
+ .split(/\r?\n/)
829
+ .map((line) => line.trim())
830
+ .filter(Boolean);
831
+ if (lines.length === 1)
832
+ return lines[0];
833
+ return null;
834
+ }
813
835
  /** `<bin> --version` output (trimmed), or null on any failure/timeout. */
814
836
  export function probeCliVersion(bin) {
815
837
  try {
@@ -819,8 +841,7 @@ export function probeCliVersion(bin) {
819
841
  });
820
842
  if (result.error || result.status !== 0)
821
843
  return null;
822
- const out = (result.stdout ?? "").trim();
823
- return out || null;
844
+ return parseReportedCliVersion(result.stdout ?? "");
824
845
  }
825
846
  catch {
826
847
  return null;
@@ -1140,7 +1161,12 @@ export async function enforceVersionGate(onUpdateRecommended, options = {}) {
1140
1161
  * force-upgraded.
1141
1162
  */
1142
1163
  export function shouldSkipGate(argv) {
1143
- return argv.some((a) => a === "--version" || a === "-V" || a === "-v" || a === "--help" || a === "-h");
1164
+ return argv.some((a) => a === "--version" ||
1165
+ a === "-V" ||
1166
+ a === "-v" ||
1167
+ a === "--help" ||
1168
+ a === "-h" ||
1169
+ a === "version");
1144
1170
  }
1145
1171
  export const __test__ = {
1146
1172
  CLIENT_ID,
@@ -0,0 +1,27 @@
1
+ export declare const HQ_CORE_LATEST_URL = "https://api.github.com/repos/indigoai-us/hq-core/releases/latest";
2
+ export declare const LATEST_FETCH_TIMEOUT_MS = 3000;
3
+ export type LatestLookup = "ok" | "network-error";
4
+ export interface VersionReport {
5
+ hq: string | null;
6
+ cli: string;
7
+ latestHqCore: string | null;
8
+ updateAvailable: boolean;
9
+ latestLookup: LatestLookup;
10
+ }
11
+ export interface VersionReportDeps {
12
+ cliVersion?: string;
13
+ hqRoot?: string;
14
+ readHq?: (hqRoot: string) => string | null;
15
+ fetchLatest?: () => Promise<string | null>;
16
+ }
17
+ export declare function normalizeHqCoreTag(tag: string): string | null;
18
+ export declare function fetchLatestHqCoreRelease(fetchImpl?: typeof fetch, timeoutMs?: number): Promise<string | null>;
19
+ export declare function collectVersionReport(deps?: VersionReportDeps): Promise<VersionReport>;
20
+ export declare function formatVersionReport(report: VersionReport): string;
21
+ export declare function versionReportJson(report: VersionReport): string;
22
+ export { isVersionRequest } from "../version-request.js";
23
+ export declare function wantsJson(argv: readonly string[]): boolean;
24
+ export declare function runVersionCommand(argv?: readonly string[], deps?: VersionReportDeps, stdout?: {
25
+ write(chunk: string): unknown;
26
+ }): Promise<void>;
27
+ //# sourceMappingURL=version-report.d.ts.map
@@ -0,0 +1,97 @@
1
+ /**
2
+ * The payload behind `hq version` / labelled `hq --version`.
3
+ *
4
+ * Prints the installed HQ scaffold version, the CLI version (labelled so it
5
+ * cannot be confused with HQ), and the latest hq-core GitHub release. A
6
+ * network failure is an explicit line, never a silent omission.
7
+ */
8
+ import semver from "semver";
9
+ import { CLI_VERSION } from "../cli-version.js";
10
+ import { findHqRoot } from "./manifest.js";
11
+ import { readHqVersion } from "./pack-contributions.js";
12
+ export const HQ_CORE_LATEST_URL = "https://api.github.com/repos/indigoai-us/hq-core/releases/latest";
13
+ export const LATEST_FETCH_TIMEOUT_MS = 3_000;
14
+ export function normalizeHqCoreTag(tag) {
15
+ const version = tag.trim().replace(/^v/i, "");
16
+ return semver.valid(version) ? version : null;
17
+ }
18
+ export async function fetchLatestHqCoreRelease(fetchImpl = fetch, timeoutMs = LATEST_FETCH_TIMEOUT_MS) {
19
+ try {
20
+ const res = await fetchImpl(HQ_CORE_LATEST_URL, {
21
+ headers: {
22
+ Accept: "application/vnd.github+json",
23
+ "User-Agent": "hq-cli-version",
24
+ "X-GitHub-Api-Version": "2022-11-28",
25
+ },
26
+ signal: AbortSignal.timeout(timeoutMs),
27
+ });
28
+ if (!res.ok)
29
+ return null;
30
+ const body = (await res.json());
31
+ if (typeof body.tag_name !== "string")
32
+ return null;
33
+ return normalizeHqCoreTag(body.tag_name);
34
+ }
35
+ catch {
36
+ return null;
37
+ }
38
+ }
39
+ export async function collectVersionReport(deps = {}) {
40
+ const cli = deps.cliVersion ?? CLI_VERSION;
41
+ let hq;
42
+ try {
43
+ const hqRoot = deps.hqRoot ?? findHqRoot();
44
+ hq = (deps.readHq ?? readHqVersion)(hqRoot);
45
+ }
46
+ catch {
47
+ hq = null;
48
+ }
49
+ const latestHqCore = deps.fetchLatest
50
+ ? await deps.fetchLatest()
51
+ : await fetchLatestHqCoreRelease();
52
+ const latestLookup = latestHqCore ? "ok" : "network-error";
53
+ const hqValid = hq ? semver.valid(hq) : null;
54
+ const latestValid = latestHqCore ? semver.valid(latestHqCore) : null;
55
+ const updateAvailable = Boolean(hqValid && latestValid && semver.gt(latestValid, hqValid));
56
+ return { hq, cli, latestHqCore, updateAvailable, latestLookup };
57
+ }
58
+ export function formatVersionReport(report) {
59
+ const lines = [
60
+ `HQ ${report.hq ?? "unknown"}`,
61
+ `CLI ${report.cli}`,
62
+ ];
63
+ if (report.latestLookup === "network-error") {
64
+ lines.push("Latest hq-core unknown");
65
+ lines.push("could not reach the network to check for a newer HQ");
66
+ return `${lines.join("\n")}\n`;
67
+ }
68
+ lines.push(`Latest hq-core ${report.latestHqCore}`);
69
+ if (!report.hq) {
70
+ lines.push("installed HQ version unknown, cannot tell if an update is available");
71
+ }
72
+ else if (report.updateAvailable) {
73
+ lines.push("update available, run /update-hq");
74
+ }
75
+ else {
76
+ lines.push("up to date");
77
+ }
78
+ return `${lines.join("\n")}\n`;
79
+ }
80
+ export function versionReportJson(report) {
81
+ return `${JSON.stringify({
82
+ hq: report.hq,
83
+ cli: report.cli,
84
+ latestHqCore: report.latestHqCore,
85
+ updateAvailable: report.updateAvailable,
86
+ latestLookup: report.latestLookup,
87
+ }, null, 2)}\n`;
88
+ }
89
+ export { isVersionRequest } from "../version-request.js";
90
+ export function wantsJson(argv) {
91
+ return argv.includes("--json");
92
+ }
93
+ export async function runVersionCommand(argv = process.argv, deps = {}, stdout = process.stdout) {
94
+ const report = await collectVersionReport(deps);
95
+ stdout.write(wantsJson(argv) ? versionReportJson(report) : formatVersionReport(report));
96
+ }
97
+ //# sourceMappingURL=version-report.js.map
@@ -0,0 +1,7 @@
1
+ /**
2
+ * Argv predicate for the version fast path. Keep this file dependency-free so
3
+ * every `hq` invocation can classify `--version` / `version` without loading
4
+ * yaml, semver, or the command graph.
5
+ */
6
+ export declare function isVersionRequest(argv: readonly string[]): boolean;
7
+ //# sourceMappingURL=version-request.d.ts.map
@@ -0,0 +1,22 @@
1
+ /**
2
+ * Argv predicate for the version fast path. Keep this file dependency-free so
3
+ * every `hq` invocation can classify `--version` / `version` without loading
4
+ * yaml, semver, or the command graph.
5
+ */
6
+ export function isVersionRequest(argv) {
7
+ const args = argv.slice(2);
8
+ if (args.length === 0)
9
+ return false;
10
+ if (args.includes("--help") || args.includes("-h"))
11
+ return false;
12
+ const positionals = args.filter((a) => !a.startsWith("-"));
13
+ const flags = args.filter((a) => a.startsWith("-"));
14
+ const allowedFlags = new Set(["--version", "-V", "-v", "--json"]);
15
+ if (!flags.every((f) => allowedFlags.has(f)))
16
+ return false;
17
+ if (positionals.length === 0) {
18
+ return flags.some((f) => f === "--version" || f === "-V" || f === "-v");
19
+ }
20
+ return positionals.length === 1 && positionals[0] === "version";
21
+ }
22
+ //# 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.119.11",
3
+ "version": "5.119.13",
4
4
  "description": "HQ by Indigo management CLI \u2014 modules and cloud sync",
5
5
  "main": "dist/index.js",
6
6
  "bin": {