@indigoai-us/hq-cli 5.103.29 → 5.103.30
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 +2 -0
- package/dist/commands/skill.d.ts +42 -0
- package/dist/commands/skill.js +63 -3
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
package/dist/commands/skill.d.ts
CHANGED
|
@@ -40,6 +40,48 @@ export declare function makeSkillTemplate(input: {
|
|
|
40
40
|
/** Replace SKILL.md without exposing a partially-written identity to agents. */
|
|
41
41
|
export declare function writeSkillFileAtomically(filePath: string, content: string): void;
|
|
42
42
|
export declare function mapSkillError(status: number, body: Record<string, unknown>): string;
|
|
43
|
+
/**
|
|
44
|
+
* Type a failed skills-API response by its HTTP status so the top-level error
|
|
45
|
+
* boundary can tell a correctly-denied client 4xx (ordinary caller state) from a
|
|
46
|
+
* genuine fault (an hq-cli defect or a server error). The message is built from
|
|
47
|
+
* the UNCHANGED `mapSkillError(status, body)` mapping; only the error's CLASS
|
|
48
|
+
* varies:
|
|
49
|
+
*
|
|
50
|
+
* 401 (human) -> AuthError (HQ-CLI-8: the caller runs `hq login`)
|
|
51
|
+
* 401 (machine) -> unmarked Error (a company agent never runs `hq login`)
|
|
52
|
+
* 403, 404, 409, 429 -> ExpectedUserError (HQ-CLI-6: printed, exit 1, not captured)
|
|
53
|
+
* everything else -> unmarked Error (still captured to Sentry)
|
|
54
|
+
*
|
|
55
|
+
* The 4xx allowlist is closed and ENUMERATED, not a `status < 500` range: a 400
|
|
56
|
+
* or 422 means the CLI itself built a malformed request, and every 5xx is a real
|
|
57
|
+
* server fault — both must keep reaching Sentry so a genuine defect is never
|
|
58
|
+
* hidden behind a "caller error" label (HQ-CLI-Z, Sentry 7694457056). Before
|
|
59
|
+
* this, both call sites threw a bare `new Error(...)`, so a correctly-denied 403
|
|
60
|
+
* (e.g. an agent principal with no person entity) fell through the boundary's
|
|
61
|
+
* closed allowlist and filed a crash report for what hq-pro itself deliberately
|
|
62
|
+
* exempts from Sentry capture.
|
|
63
|
+
*
|
|
64
|
+
* `opts.machineIdentity` distinguishes a company agent (`isMachineIdentity()`).
|
|
65
|
+
* A machine session mints automatically and is never repaired by `hq login`, so
|
|
66
|
+
* a machine 401 is a disabled-credential or backend-authorizer fault, not
|
|
67
|
+
* login-repairable caller state — it stays an unmarked, reportable Error rather
|
|
68
|
+
* than a suppressed `AuthError`.
|
|
69
|
+
*
|
|
70
|
+
* The mapped server text is scrubbed and length-bounded through the shared
|
|
71
|
+
* `redactErrorText` chain BEFORE it is placed on any error, because the
|
|
72
|
+
* boundary's AuthError/expected branches print `err.message` verbatim — without
|
|
73
|
+
* this, an upstream 4xx diagnostic would bypass the output-safety boundary that
|
|
74
|
+
* the unmarked path still gets from `fallbackOperatorMessage`.
|
|
75
|
+
*
|
|
76
|
+
* Additive remedy for the observed condition only: a 403 whose machine-readable
|
|
77
|
+
* body `code` is NO_PERSON_ENTITY gets a CLI-actionable `hq onboard` sentence
|
|
78
|
+
* appended (wording precedent: src/commands/cloud-provision.ts). Keyed on
|
|
79
|
+
* `body.code`, never on the prose, so the message is unchanged when the field is
|
|
80
|
+
* absent.
|
|
81
|
+
*/
|
|
82
|
+
export declare function skillApiError(status: number, body: Record<string, unknown>, opts?: {
|
|
83
|
+
machineIdentity?: boolean;
|
|
84
|
+
}): Error;
|
|
43
85
|
interface SkillCommandDeps {
|
|
44
86
|
ensureToken?: typeof ensureCognitoToken;
|
|
45
87
|
apiFetch?: typeof vaultApiFetch;
|
package/dist/commands/skill.js
CHANGED
|
@@ -13,9 +13,11 @@ import * as path from "node:path";
|
|
|
13
13
|
import chalk from "chalk";
|
|
14
14
|
import yaml from "js-yaml";
|
|
15
15
|
import { share } from "@indigoai-us/hq-cloud";
|
|
16
|
-
import { ensureCognitoToken, DEFAULT_HQ_ROOT, buildVaultConfig, } from "../utils/cognito-session.js";
|
|
16
|
+
import { ensureCognitoToken, DEFAULT_HQ_ROOT, buildVaultConfig, isMachineIdentity, } from "../utils/cognito-session.js";
|
|
17
17
|
import { vaultApiFetch } from "../utils/vault-api.js";
|
|
18
18
|
import { surfaceCompanySkill } from "../lib/company-skill-wrapper.js";
|
|
19
|
+
import { AuthError } from "../utils/auth-error.js";
|
|
20
|
+
import { redactErrorText } from "../utils/redact-error-text.js";
|
|
19
21
|
export const SKILL_UID_PATTERN = /^skl_[A-Za-z0-9]+$/;
|
|
20
22
|
export const SKILL_SLUG_PATTERN = /^[a-z0-9][a-z0-9-]*$/;
|
|
21
23
|
const COMPANY_SLUG_PATTERN = /^[a-z0-9][a-z0-9-]*$/;
|
|
@@ -179,6 +181,60 @@ export function mapSkillError(status, body) {
|
|
|
179
181
|
return `Server error: ${server || status}`;
|
|
180
182
|
return server || `Request failed (${status})`;
|
|
181
183
|
}
|
|
184
|
+
/**
|
|
185
|
+
* Type a failed skills-API response by its HTTP status so the top-level error
|
|
186
|
+
* boundary can tell a correctly-denied client 4xx (ordinary caller state) from a
|
|
187
|
+
* genuine fault (an hq-cli defect or a server error). The message is built from
|
|
188
|
+
* the UNCHANGED `mapSkillError(status, body)` mapping; only the error's CLASS
|
|
189
|
+
* varies:
|
|
190
|
+
*
|
|
191
|
+
* 401 (human) -> AuthError (HQ-CLI-8: the caller runs `hq login`)
|
|
192
|
+
* 401 (machine) -> unmarked Error (a company agent never runs `hq login`)
|
|
193
|
+
* 403, 404, 409, 429 -> ExpectedUserError (HQ-CLI-6: printed, exit 1, not captured)
|
|
194
|
+
* everything else -> unmarked Error (still captured to Sentry)
|
|
195
|
+
*
|
|
196
|
+
* The 4xx allowlist is closed and ENUMERATED, not a `status < 500` range: a 400
|
|
197
|
+
* or 422 means the CLI itself built a malformed request, and every 5xx is a real
|
|
198
|
+
* server fault — both must keep reaching Sentry so a genuine defect is never
|
|
199
|
+
* hidden behind a "caller error" label (HQ-CLI-Z, Sentry 7694457056). Before
|
|
200
|
+
* this, both call sites threw a bare `new Error(...)`, so a correctly-denied 403
|
|
201
|
+
* (e.g. an agent principal with no person entity) fell through the boundary's
|
|
202
|
+
* closed allowlist and filed a crash report for what hq-pro itself deliberately
|
|
203
|
+
* exempts from Sentry capture.
|
|
204
|
+
*
|
|
205
|
+
* `opts.machineIdentity` distinguishes a company agent (`isMachineIdentity()`).
|
|
206
|
+
* A machine session mints automatically and is never repaired by `hq login`, so
|
|
207
|
+
* a machine 401 is a disabled-credential or backend-authorizer fault, not
|
|
208
|
+
* login-repairable caller state — it stays an unmarked, reportable Error rather
|
|
209
|
+
* than a suppressed `AuthError`.
|
|
210
|
+
*
|
|
211
|
+
* The mapped server text is scrubbed and length-bounded through the shared
|
|
212
|
+
* `redactErrorText` chain BEFORE it is placed on any error, because the
|
|
213
|
+
* boundary's AuthError/expected branches print `err.message` verbatim — without
|
|
214
|
+
* this, an upstream 4xx diagnostic would bypass the output-safety boundary that
|
|
215
|
+
* the unmarked path still gets from `fallbackOperatorMessage`.
|
|
216
|
+
*
|
|
217
|
+
* Additive remedy for the observed condition only: a 403 whose machine-readable
|
|
218
|
+
* body `code` is NO_PERSON_ENTITY gets a CLI-actionable `hq onboard` sentence
|
|
219
|
+
* appended (wording precedent: src/commands/cloud-provision.ts). Keyed on
|
|
220
|
+
* `body.code`, never on the prose, so the message is unchanged when the field is
|
|
221
|
+
* absent.
|
|
222
|
+
*/
|
|
223
|
+
export function skillApiError(status, body, opts = {}) {
|
|
224
|
+
let message = redactErrorText(mapSkillError(status, body)) || `Request failed (${status})`;
|
|
225
|
+
if (status === 403 && body.code === "NO_PERSON_ENTITY") {
|
|
226
|
+
message += " Run `hq onboard` first to create your HQ identity, then retry.";
|
|
227
|
+
}
|
|
228
|
+
if (status === 401) {
|
|
229
|
+
return opts.machineIdentity ? new Error(message) : new AuthError(message);
|
|
230
|
+
}
|
|
231
|
+
if (status === 403 || status === 404 || status === 409 || status === 429) {
|
|
232
|
+
return Object.assign(new Error(message), {
|
|
233
|
+
expected: true,
|
|
234
|
+
});
|
|
235
|
+
}
|
|
236
|
+
return new Error(message);
|
|
237
|
+
}
|
|
182
238
|
export function registerSkillCommand(program, deps = {}) {
|
|
183
239
|
const ensureToken = deps.ensureToken ?? ensureCognitoToken;
|
|
184
240
|
const apiFetch = deps.apiFetch ?? vaultApiFetch;
|
|
@@ -245,7 +301,9 @@ export function registerSkillCommand(program, deps = {}) {
|
|
|
245
301
|
});
|
|
246
302
|
if (!response.ok) {
|
|
247
303
|
const body = (await response.json().catch(() => ({})));
|
|
248
|
-
throw
|
|
304
|
+
throw skillApiError(response.status, body, {
|
|
305
|
+
machineIdentity: isMachineIdentity(),
|
|
306
|
+
});
|
|
249
307
|
}
|
|
250
308
|
const registered = (await response.json());
|
|
251
309
|
if (typeof registered.skillUid !== "string" ||
|
|
@@ -316,7 +374,9 @@ export function registerSkillCommand(program, deps = {}) {
|
|
|
316
374
|
});
|
|
317
375
|
if (!response.ok) {
|
|
318
376
|
const body = (await response.json().catch(() => ({})));
|
|
319
|
-
throw
|
|
377
|
+
throw skillApiError(response.status, body, {
|
|
378
|
+
machineIdentity: isMachineIdentity(),
|
|
379
|
+
});
|
|
320
380
|
}
|
|
321
381
|
const posted = (await response.json());
|
|
322
382
|
console.log(chalk.green(`Improvement posted: ${posted.commentId}`));
|