@indigoai-us/hq-cli 5.94.0 → 5.94.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,53 @@
2
2
 
3
3
  ## [Unreleased]
4
4
 
5
+ ## [5.94.2]
6
+
7
+ ### Fixed
8
+
9
+ - `hq search <query>` works. It has been broken since 5.86.0: the query was
10
+ registered as a subcommand literally named `<query>`, so every search
11
+ returned `error: unknown command '<the query>'` and only `hq search get`
12
+ functioned. Queries are now an argument on `search` itself, so
13
+ `hq search release gate` needs no quoting. (#337)
14
+ - Commands no longer fail silently. The top-level handler printed a message
15
+ only for one error class, so a crashing qmd made `hq index status` exit 1
16
+ with zero bytes on stdout and stderr — indistinguishable from success. Every
17
+ failure now emits a diagnostic, routed through the existing redaction chain
18
+ so visibility widens without disclosure widening. (#337)
19
+ - The CLI no longer prefers a bundled qmd it cannot run. 5.94.1 made
20
+ package-local resolution work and thereby started preferring a global
21
+ install's qmd whose native better-sqlite3 bindings were never compiled,
22
+ breaking search on hosts that had previously fallen through to a working qmd
23
+ on `PATH`. Resolution now verifies the candidate actually runs. The probe
24
+ costs a ~0.3s spawn, so it runs only when a fallback exists — with nothing
25
+ better available the answer is the bundled binary either way, and a failure
26
+ there is now loud rather than silent. (#337)
27
+
28
+ ## [5.94.1]
29
+
30
+ ### Fixed
31
+
32
+ - The CLI now actually uses its bundled qmd. Package-local resolution had never
33
+ worked, so every install silently fell through to whatever qmd was on `PATH`,
34
+ and a host without a global qmd reported `skipped` — defeating the reason
35
+ `@tobilu/qmd` is a dependency and undoing in practice what 5.93.x set out to
36
+ fix for Node 26 global installs. Two independent causes, both silent: the
37
+ resolver looked for `<pkg>/qmd` while the package declares `bin/qmd`, and the
38
+ package-manifest lookup throws `ERR_PACKAGE_PATH_NOT_EXPORTED` on qmd 2.x,
39
+ which also blanked the version in `hq index status`. Both now follow the
40
+ package-manager contract: the binary via `node_modules/.bin/qmd`, the version
41
+ read from `node_modules/@tobilu/qmd/package.json`. (#333)
42
+
43
+ ### Added
44
+
45
+ - A shell-vs-native differential for `hq index background`, run against a frozen
46
+ copy of the pre-migration `qmd-reindex-bg.sh`: agent-box short-circuit before
47
+ any qmd lookup or lock, empty-`HOME` silence, `cleanup → update → embed`
48
+ ordering with completion stamp, no stamp after a failed update, and no
49
+ competing writer behind a held lock. It is what surfaced the resolution bug
50
+ above. (#333)
51
+
5
52
  ## [5.94.0]
6
53
 
7
54
  ### Removed
@@ -0,0 +1,185 @@
1
+ import { Command } from "commander";
2
+ import { type ActiveMember } from "./members.js";
3
+ /**
4
+ * `hq company transfer` — company ownership handover (client-service-pack
5
+ * US-010). The server adds ZERO new API routes (vault-api-hq-prod sits at the
6
+ * 600-route quota), so the CLI half multiplexes onto two EXISTING routes:
7
+ *
8
+ * - WRITES ride `POST /membership/role` with a body discriminator
9
+ * `action: "transfer-initiate" | "transfer-accept" | "transfer-decline" |
10
+ * "transfer-cancel"`. The ordinary role payload has never carried an
11
+ * `action` field, so the shapes cannot collide.
12
+ * - The READ rides `GET /membership/company/{companyUid}?view=transfers`;
13
+ * without that exact `view` value the route returns the member roster as
14
+ * before.
15
+ *
16
+ * TWO-PARTY BY CONSTRUCTION. `initiate` NOMINATES; it does not transfer
17
+ * anything. Ownership moves only when the nominee runs `accept`. The four write
18
+ * transitions (initiate / accept / decline / cancel) all multiplex through one
19
+ * POST with an explicit `action`, matching the server's route shape.
20
+ *
21
+ * ABSENT-FIELD DISCIPLINE (policy `hq-absent-field-never-means-constraining-value`).
22
+ * This flow spans two repos that deploy in EITHER order, so absence is load
23
+ * bearing in both directions and is handled in both directions here:
24
+ *
25
+ * - REQUESTS: a flag the operator did not pass is OMITTED from the body — it
26
+ * is never defaulted to a value on the wire. `--initiator-role` unset sends
27
+ * no `initiatorRole` at all, so the server applies its own reversible
28
+ * "keep them as admin" default; the CLI can therefore never turn silence
29
+ * into `remove`. Same for `--transfer-id` (absent = "the pending one").
30
+ * - RESPONSES: fields are read with presence and value SEPARATELY
31
+ * ({@link readWireField}), because an OLDER server omits fields a newer CLI
32
+ * knows about. An absent `effects.initiator` renders as "not reported",
33
+ * never as "removed" — the CLI must not narrate a destructive outcome it
34
+ * was never actually told about.
35
+ *
36
+ * Conventions mirror `company.ts` / `files.ts`: ensureCognitoToken() →
37
+ * getEntityUid() → vaultApiFetch() → error-check → chalk output, with the
38
+ * confirmation prompt behind an injectable seam so tests can drive it.
39
+ */
40
+ /** The four write transitions the server accepts. */
41
+ export declare const TRANSFER_ACTIONS: readonly ["initiate", "accept", "decline", "cancel"];
42
+ export type TransferAction = (typeof TRANSFER_ACTIONS)[number];
43
+ /**
44
+ * Legal `--initiator-role` values. `owner` is deliberately absent: a handover
45
+ * where the outgoing owner keeps `owner` is not a handover. `remove` is the
46
+ * ONLY value that takes them off the company, and it can only ever arrive from
47
+ * an explicit flag.
48
+ */
49
+ export declare const INITIATOR_ROLE_VALUES: readonly ["admin", "member", "guest", "remove"];
50
+ export type InitiatorRoleFlag = (typeof INITIATOR_ROLE_VALUES)[number];
51
+ /**
52
+ * What the SERVER falls back to when `initiatorRole` is absent. Mirrored here
53
+ * only so the confirmation preview can tell the operator what will happen; the
54
+ * CLI never puts this value on the wire, because sending it would defeat the
55
+ * point of leaving the field absent.
56
+ */
57
+ export declare const DEFAULT_INITIATOR_ROLE = "admin";
58
+ /** A response field's presence, kept separate from its value. */
59
+ export interface WireRead<T = unknown> {
60
+ present: boolean;
61
+ value: T | undefined;
62
+ }
63
+ /**
64
+ * Read a field off a server response reporting presence and value SEPARATELY.
65
+ *
66
+ * The CLI-side twin of hq-pro's `readField`. `body.x ?? fallback` is wrong here
67
+ * because it makes an OLDER server's omitted field indistinguishable from a
68
+ * newer server explicitly sending `null`/`false`/`""` — and every caller below
69
+ * needs to say "this server didn't tell me" rather than invent an answer.
70
+ */
71
+ export declare function readWireField<T = unknown>(body: Record<string, unknown> | null | undefined, key: string): WireRead<T>;
72
+ /**
73
+ * Parse `--initiator-role`.
74
+ *
75
+ * Returns `undefined` when the flag was NOT passed, and that `undefined` means
76
+ * "omit the field entirely" — never "remove", and never a substituted default.
77
+ * A present-but-invalid value throws instead of falling back, so a typo can
78
+ * never be silently reinterpreted as a different disposition.
79
+ */
80
+ export declare function parseInitiatorRole(raw: string | undefined): InitiatorRoleFlag | undefined;
81
+ export interface TransferBodyParams {
82
+ companyUid: string;
83
+ action: TransferAction;
84
+ targetPersonUid?: string;
85
+ initiatorRole?: InitiatorRoleFlag;
86
+ transferId?: string;
87
+ reason?: string;
88
+ }
89
+ /**
90
+ * The wire prefix that turns `POST /membership/role` into a transfer request.
91
+ * Mirrors the server's `TRANSFER_ACTION_PREFIX` in `_ownership-transfer.ts`:
92
+ * a body whose `action` starts with `transfer-` is a transfer; anything else
93
+ * (including no `action` at all) is an ordinary role change.
94
+ */
95
+ export declare const TRANSFER_ACTION_PREFIX = "transfer-";
96
+ /**
97
+ * The query discriminator on `GET /membership/company/{companyUid}` that
98
+ * selects the transfer surface instead of the member roster. Mirrors the
99
+ * server's `TRANSFER_HISTORY_VIEW` (exact match on the server side).
100
+ */
101
+ export declare const TRANSFER_HISTORY_VIEW = "transfers";
102
+ /**
103
+ * Build the `POST /membership/role` transfer body. The CLI-side `action`
104
+ * ("initiate" | …) is prefixed to the wire discriminator ("transfer-initiate"
105
+ * | …) HERE, so no call site can accidentally post an unprefixed action that
106
+ * the server would route to the ordinary role-change handler.
107
+ *
108
+ * Every optional field is included ONLY when the caller actually supplied it.
109
+ * This is the single choke point for the request half of the absent-field rule:
110
+ * if a value is not here, the server sees no key at all and applies its own
111
+ * safe default, which keeps the CLI correct against a server that predates any
112
+ * of these fields.
113
+ */
114
+ export declare function buildTransferBody(params: TransferBodyParams): Record<string, unknown>;
115
+ /**
116
+ * Describe what the confirmation prompt should say happens to the OUTGOING
117
+ * owner. Absence resolves to the reversible, non-destructive sentence.
118
+ */
119
+ export declare function describeInitiatorOutcome(role: InitiatorRoleFlag | undefined): string;
120
+ /**
121
+ * Render the `effects` block of an accepted transfer.
122
+ *
123
+ * Reads every field for PRESENCE first. An older server that does not send
124
+ * `effects`, or omits a field inside it, gets an honest "not reported" line —
125
+ * inventing "removed" or "unchanged" here would be the CLI asserting a
126
+ * destructive outcome it has no evidence for.
127
+ */
128
+ export declare function describeEffects(rawEffects: unknown): string[];
129
+ /** Injectable yes/no confirmation seam (stubbed in tests). */
130
+ export type ConfirmFn = (message: string) => Promise<boolean>;
131
+ /**
132
+ * The real prompt. Refuses (rather than assuming yes) when stdin is not a TTY,
133
+ * so a transfer can never be initiated or accepted by a pipeline that simply
134
+ * had nobody to ask. `--yes` is the only non-interactive path, and it is an
135
+ * explicit operator act.
136
+ *
137
+ * Exported ONLY so the non-TTY refusal — the one confirmation path the
138
+ * injectable seam hides from the command tests — can be covered directly.
139
+ */
140
+ export declare function realConfirm(message: string): Promise<boolean>;
141
+ /**
142
+ * Resolve `--to` to a `personUid`. A uid is used as-is; anything else is
143
+ * matched (case-insensitively) against the company's ACTIVE roster by email,
144
+ * slug, or name, because the server requires a uid and only accepts a target
145
+ * who is already an active member.
146
+ */
147
+ export declare function matchMemberTarget(members: ActiveMember[], target: string): ActiveMember | undefined;
148
+ export declare function resolveTargetPersonUid(token: string, companyUid: string, target: string): Promise<{
149
+ personUid: string;
150
+ label: string;
151
+ }>;
152
+ export interface TransferDeps {
153
+ confirm?: ConfirmFn;
154
+ }
155
+ export interface RunTransferParams {
156
+ companySlug: string;
157
+ action: TransferAction;
158
+ /** `--to` (initiate only): email, slug, name, or prs_… uid. */
159
+ to?: string;
160
+ /** Raw `--initiator-role`; `undefined` means the flag was NOT passed. */
161
+ initiatorRoleRaw?: string;
162
+ /** Raw `--transfer-id`; `undefined` means "the pending one". */
163
+ transferId?: string;
164
+ reason?: string;
165
+ yes?: boolean;
166
+ }
167
+ /**
168
+ * `hq company transfer initiate|accept|decline|cancel`.
169
+ *
170
+ * `initiate` and `accept` are the two transitions that move (or commit to
171
+ * moving) ownership, so both stop for an explicit confirmation that spells out
172
+ * who becomes owner and what happens to the outgoing owner. `decline` and
173
+ * `cancel` only ever ABORT a nomination, so they do not prompt.
174
+ */
175
+ export declare function runCompanyTransfer(params: RunTransferParams, deps?: TransferDeps): Promise<void>;
176
+ export interface RunTransferStatusParams {
177
+ companySlug: string;
178
+ limit?: string;
179
+ cursor?: string;
180
+ json?: boolean;
181
+ }
182
+ /** `hq company transfer status` — the pending nomination plus the audit trail. */
183
+ export declare function runCompanyTransferStatus(params: RunTransferStatusParams): Promise<void>;
184
+ export declare function registerCompanyTransferCommand(company: Command, deps?: TransferDeps): void;
185
+ //# sourceMappingURL=company-transfer.d.ts.map