@indigoai-us/hq-cli 5.101.3 → 5.101.5

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
@@ -1,11 +1,24 @@
1
1
  # Changelog
2
2
 
3
- ## [Unreleased]
3
+ ## [5.101.5] — 2026-08-17
4
+
5
+ - No user-facing changes recorded.
6
+
7
+ ## [5.101.4] — 2026-08-16
8
+
9
+ - No user-facing changes recorded.
4
10
 
5
11
  ## [5.101.3] — 2026-08-15
6
12
 
7
13
  ## [5.101.2] — 2026-08-15
8
14
 
15
+ ### Added
16
+
17
+ - `hq billing upgrade` opens the Stripe-hosted HQ Team checkout for the active
18
+ company. When an HQ API call returns `PLAN_LIMIT_EXCEEDED` (402), interactive
19
+ CLI flows now offer the same upgrade path; noninteractive clients are not
20
+ prompted. (#383, #376)
21
+
9
22
  ### Fixed
10
23
 
11
24
  - Self-update now detects Bun-managed global installs and runs `bun add -g`
package/dist/main.js CHANGED
@@ -65,6 +65,7 @@ import { sanitizeArgv } from "./utils/feedback-diagnostics.js";
65
65
  import { environmentalFsErrorMessage } from "./utils/environmental-error.js";
66
66
  import { networkTransportErrorMessage } from "./utils/network-transport-error.js";
67
67
  import { qmdNativeBindingErrorMessage } from "./utils/qmd-native-binding-error.js";
68
+ import { qmdMissingCollectionMessage } from "./utils/qmd-collection-missing-error.js";
68
69
  import { isExpectedUserError } from "./utils/expected-cli-error.js";
69
70
  import { isEpipe } from "./utils/epipe.js";
70
71
  import { isInterceptedProcessExit } from "./utils/intercepted-process-exit.js";
@@ -438,7 +439,21 @@ export async function handleTopLevelError(err, deps = defaultTopLevelErrorDepend
438
439
  // Ordered FIRST so the narrow native-binding signature wins over the
439
440
  // broader environmental / transport / generic branches.
440
441
  const qmdMsg = qmdNativeBindingErrorMessage(err);
441
- const envMsg = qmdMsg ? null : environmentalFsErrorMessage(err);
442
+ // A `hq search <query> -c <collection>` against a collection that is not
443
+ // indexed on THIS machine is the user's local index state, not an hq-cli
444
+ // defect — the remedy is an explicit `hq index sync`. finishRunQmd already
445
+ // types the condition QmdCollectionMissingError, but nothing caught it, so
446
+ // it fell through to the capture below; because the synthesized message
447
+ // embeds the caller's whole argv, each distinct query minted a brand-new
448
+ // permanent issue. Print an actionable line naming the missing collection
449
+ // and skip capture (HQ-CLI-S, Sentry 7672722729). Evaluated AFTER the
450
+ // native-binding check so that narrower signature keeps priority, and
451
+ // BEFORE the environmental / transport / generic branches. Scoped to
452
+ // caller-supplied reads (search/vsearch/query/get); a collection-missing
453
+ // raised by hq's OWN reconciliation (`collection add`/`context add`) names
454
+ // a collection hq built itself, so it stays a captured internal error.
455
+ const collectionMsg = qmdMsg ? null : qmdMissingCollectionMessage(err);
456
+ const envMsg = qmdMsg || collectionMsg ? null : environmentalFsErrorMessage(err);
442
457
  // A raw network transport failure (undici's `TypeError: fetch failed`
443
458
  // with a ConnectTimeoutError / ECONNREFUSED / ENOTFOUND cause) is the
444
459
  // caller's connectivity, not an hq-cli defect. Before this branch it fell
@@ -449,10 +464,13 @@ export async function handleTopLevelError(err, deps = defaultTopLevelErrorDepend
449
464
  // message that names the unreachable host, exit 1, and skip Sentry.
450
465
  // Ordered after the environmental check so a full disk keeps its exact
451
466
  // existing message.
452
- const transportMsg = qmdMsg || envMsg ? null : networkTransportErrorMessage(err);
467
+ const transportMsg = qmdMsg || collectionMsg || envMsg ? null : networkTransportErrorMessage(err);
453
468
  if (qmdMsg) {
454
469
  deps.stderr.write(`hq: ${qmdMsg}\n`);
455
470
  }
471
+ else if (collectionMsg) {
472
+ deps.stderr.write(`hq: ${collectionMsg}\n`);
473
+ }
456
474
  else if (envMsg) {
457
475
  deps.stderr.write(`hq: ${envMsg}\n`);
458
476
  }
@@ -0,0 +1,10 @@
1
+ /**
2
+ * If `err` is a caller-driven "collection not found" from `hq search` (or its
3
+ * `get` sibling), return an actionable, query-free remedy naming the missing
4
+ * collection; otherwise return `null`. Mirrors qmdNativeBindingErrorMessage /
5
+ * environmentalFsErrorMessage / networkTransportErrorMessage so the top-level
6
+ * handler can branch on it the same way: a non-null result means
7
+ * print-and-skip-Sentry, null means "handle as usual (capture to Sentry)".
8
+ */
9
+ export declare function qmdMissingCollectionMessage(err: unknown): string | null;
10
+ //# sourceMappingURL=qmd-collection-missing-error.d.ts.map
@@ -0,0 +1,95 @@
1
+ // src/utils/qmd-collection-missing-error.ts
2
+ //
3
+ // Classify a "collection not found" qmd failure that came from a caller-supplied
4
+ // READ — `hq search`, its `--mode` siblings (vsearch/query), or `hq search get`
5
+ // — as the user's LOCAL index state, not an hq-cli code defect. The collection
6
+ // the caller named is simply not indexed on this machine; the remedy is an
7
+ // explicit `hq index sync`, so the CLI prints an actionable line and SKIPS
8
+ // Sentry capture. Sibling of qmd-native-binding-error.ts (HQ-CLI-J, unbuilt
9
+ // bindings), environmental-error.ts (HQ-CLI-2, full disk), and
10
+ // network-transport-error.ts (HQ-CLI-G, connectivity): a failure that is NOT an
11
+ // hq-cli defect is printed with an actionable message and never filed as a crash.
12
+ //
13
+ // HQ-CLI-S (Sentry 7672722729): `hq search <query> -c <collection>` against a
14
+ // collection that isn't indexed locally made qmd exit 1 with `Collection not
15
+ // found: <collection>`. finishRunQmd already classifies that wording into the
16
+ // dedicated QmdCollectionMissingError subclass, but nothing caught it, so it
17
+ // unwound to the top-level handler's final else and was captured. Worse, the
18
+ // synthesized message interpolates the caller's whole argv, so each distinct
19
+ // query minted a brand-new permanent issue. This classifier closes that boundary.
20
+ //
21
+ // The gate is deliberately narrow on TWO axes so it can neither be tripped by
22
+ // user input nor silence a real bug:
23
+ // 1. CLASS: only a QmdCollectionMissingError (the typed subclass finishRunQmd
24
+ // raises for the collection-not-found wording) qualifies — never a plain
25
+ // QmdExitError, a native-binding failure, or any other error.
26
+ // 2. INVOCATION: only qmd's caller-supplied-collection READS — `search`,
27
+ // `vsearch`, `query`, `get` — qualify. A collection-missing raised by hq's
28
+ // OWN reconciliation (`collection add`/`collection list`, `context add`)
29
+ // names a collection hq constructed itself, so a not-found there is an
30
+ // internal inconsistency worth reporting: it must keep crashing and
31
+ // capturing, so the classifier returns null for those invocations.
32
+ //
33
+ // The collection name is read STRUCTURALLY from the error's own `args` (the
34
+ // token after `-c`/`--collection`), never by parsing qmd's stderr, and the
35
+ // caller's free-text query (args[1]) is never echoed — the same unbounded-
36
+ // fingerprint discipline that motivated the fix. The name is itself a
37
+ // user-supplied `-c` value, so before it is interpolated it is passed through
38
+ // redactErrorText — the same credential-redaction + control-character-flattening
39
+ // + length-bounding chain the generic error path uses — so a newline, terminal
40
+ // escape sequence, credential-like token, or oversized value can neither forge a
41
+ // second `hq:` line nor leak; if nothing survives redaction the message falls
42
+ // back to the name-free variant.
43
+ import { redactErrorText } from "./redact-error-text.js";
44
+ /** qmd subcommands whose collection is chosen by the CALLER (a read surface). */
45
+ const CALLER_COLLECTION_READS = new Set(["search", "vsearch", "query", "get"]);
46
+ /**
47
+ * The two-pronged, factual remedy. `hq index sync` builds the collections HQ can
48
+ * derive from this local root; `hq index collections` shows what is actually
49
+ * registered (covering a name that maps to a company not present here, which
50
+ * sync will never create). Neither claims the named collection will appear.
51
+ */
52
+ function remedyMessage(collection) {
53
+ return collection
54
+ ? `No local search collection named '${collection}'. Run 'hq index sync' to build it, or 'hq index collections' to see what is registered.`
55
+ : "No local search collection was found for that query. Run 'hq index sync' to build the local index, or 'hq index collections' to see what is registered.";
56
+ }
57
+ /** The collection name the caller passed after `-c`/`--collection`, or null. */
58
+ function collectionFromArgs(args) {
59
+ for (let i = 0; i < args.length - 1; i += 1) {
60
+ if (args[i] === "-c" || args[i] === "--collection") {
61
+ const value = args[i + 1];
62
+ if (typeof value === "string" && value.length > 0)
63
+ return value;
64
+ }
65
+ }
66
+ return null;
67
+ }
68
+ /**
69
+ * If `err` is a caller-driven "collection not found" from `hq search` (or its
70
+ * `get` sibling), return an actionable, query-free remedy naming the missing
71
+ * collection; otherwise return `null`. Mirrors qmdNativeBindingErrorMessage /
72
+ * environmentalFsErrorMessage / networkTransportErrorMessage so the top-level
73
+ * handler can branch on it the same way: a non-null result means
74
+ * print-and-skip-Sentry, null means "handle as usual (capture to Sentry)".
75
+ */
76
+ export function qmdMissingCollectionMessage(err) {
77
+ if (err === null || typeof err !== "object")
78
+ return null;
79
+ const record = err;
80
+ if (record.name !== "QmdCollectionMissingError")
81
+ return null;
82
+ const args = Array.isArray(record.args) ? record.args : [];
83
+ const subcommand = args[0];
84
+ if (typeof subcommand !== "string" || !CALLER_COLLECTION_READS.has(subcommand)) {
85
+ return null;
86
+ }
87
+ const rawCollection = collectionFromArgs(args);
88
+ // The name is a user-supplied `-c` value: redact + flatten + bound it before
89
+ // printing so it cannot forge a second `hq:` line, emit terminal escapes, or
90
+ // leak a credential-shaped token. redactErrorText returns "" when nothing
91
+ // survives, in which case we fall back to the name-free variant.
92
+ const collection = rawCollection ? redactErrorText(rawCollection) || null : null;
93
+ return remedyMessage(collection);
94
+ }
95
+ //# sourceMappingURL=qmd-collection-missing-error.js.map
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@indigoai-us/hq-cli",
3
- "version": "5.101.3",
3
+ "version": "5.101.5",
4
4
  "description": "HQ by Indigo management CLI — modules and cloud sync",
5
5
  "main": "dist/index.js",
6
6
  "bin": {