@indigoai-us/hq-cli 5.108.12 → 5.108.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,8 @@
2
2
 
3
3
  ## [Unreleased]
4
4
 
5
+ ## [5.108.13] — 2026-09-06
6
+
5
7
  ## [5.108.12] - 2026-09-05
6
8
 
7
9
  ### Fixed
@@ -52,14 +52,30 @@ export declare function mapSkillError(status: number, body: Record<string, unkno
52
52
  * 403, 404, 409, 429 -> ExpectedUserError (HQ-CLI-6: printed, exit 1, not captured)
53
53
  * everything else -> unmarked Error (still captured to Sentry)
54
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.
55
+ * The 4xx allowlist is closed and ENUMERATED, not a `status < 500` range, and
56
+ * every 5xx is a real server fault that must keep reaching Sentry so a genuine
57
+ * defect is never hidden behind a "caller error" label (HQ-CLI-Z, Sentry
58
+ * 7694457056). Before this, both call sites threw a bare `new Error(...)`, so a
59
+ * correctly-denied 403 (e.g. an agent principal with no person entity) fell
60
+ * through the boundary's closed allowlist and filed a crash for what hq-pro
61
+ * itself deliberately exempts from Sentry capture.
62
+ *
63
+ * A 400 is DISCRIMINATED BY ITS MACHINE-READABLE `code`, never by status alone
64
+ * and never by the prose (Sentry 7710011866). On the register route the
65
+ * observed 400s reject the CALLER'S OWN SKILL.md CONTENT
66
+ * (`SKILL_REGISTER_FRONTMATTER_INVALID`, `SKILL_REGISTER_TOO_LARGE`, …) — the
67
+ * caller fixes the file and retries; there is no hq-cli defect and no operator
68
+ * action — and hq-pro returns each of those through its NON-capturing
69
+ * `expectedValidationResponse` builder, so hq-cli was the only party filing a
70
+ * crash for them. Those codes (see `CALLER_CONTENT_REGISTER_400_CODES`) are
71
+ * marked `expected`. A 400 whose `code` is absent, non-string, or unlisted —
72
+ * including `SKILL_REGISTER_PATH_INVALID`, the ONE register field hq-cli builds
73
+ * itself (`path: skills/<slug>/SKILL.md`) — stays an unmarked, captured Error,
74
+ * as does every 422: an envelope the CLI built wrong is a genuine hq-cli defect.
75
+ * Client-side suppression cannot blind a real fault: hq-pro retains its
76
+ * capturing `response()` builder for every register 400 it still considers a
77
+ * fault, so this only stops hq-cli from double-filing what the server already
78
+ * exempts.
63
79
  *
64
80
  * `opts.machineIdentity` distinguishes a company agent (`isMachineIdentity()`).
65
81
  * A machine session mints automatically and is never repaired by `hq login`, so
@@ -18,6 +18,7 @@ import { vaultApiFetch } from "../utils/vault-api.js";
18
18
  import { surfaceCompanySkill } from "../lib/company-skill-wrapper.js";
19
19
  import { AuthError } from "../utils/auth-error.js";
20
20
  import { redactErrorText } from "../utils/redact-error-text.js";
21
+ import { stampVaultAccessDenied, vaultAccessDeniedDiagnostics, } from "../utils/vault-access-denied-error.js";
21
22
  export const SKILL_UID_PATTERN = /^skl_[A-Za-z0-9]+$/;
22
23
  export const SKILL_SLUG_PATTERN = /^[a-z0-9][a-z0-9-]*$/;
23
24
  const COMPANY_SLUG_PATTERN = /^[a-z0-9][a-z0-9-]*$/;
@@ -208,6 +209,36 @@ export function mapSkillError(status, body) {
208
209
  return `Server error: ${server || status}`;
209
210
  return server || `Request failed (${status})`;
210
211
  }
212
+ /**
213
+ * The CLOSED, ENUMERATED set of skills-API register `code` values whose 400
214
+ * refuses the CALLER-AUTHORED SKILL.md CONTENT rather than the CLI-built request
215
+ * envelope. hq-pro returns each of these through its NON-capturing
216
+ * `expectedValidationResponse` builder (hq-pro src/vault-service/write-routes.ts)
217
+ * — a deliberate, correctly-handled client 400 where the caller fixes the file
218
+ * and retries and there is no operator action:
219
+ *
220
+ * SKILL_REGISTER_FRONTMATTER_INVALID `SKILL.md must contain valid YAML frontmatter`
221
+ * SKILL_REGISTER_TOO_LARGE `Skill content exceeds <N> bytes`
222
+ * SKILL_REGISTER_CONTENT_INVALID malformed skill body / structure
223
+ * SKILL_REGISTER_METADATA_INVALID bad frontmatter metadata (e.g. description)
224
+ * SKILL_REGISTER_GOVERNANCE_INVALID `normalizeSkillTags` rejected the caller's tags
225
+ *
226
+ * DELIBERATELY EXCLUDES `SKILL_REGISTER_PATH_INVALID`: the `path` field is built
227
+ * by hq-cli (`skills/${slug}/SKILL.md`), so a rejection there is a genuine
228
+ * hq-cli defect and must keep reaching Sentry. The two evidenced codes are the
229
+ * first two; the rest are the register route's other caller-content validators,
230
+ * covered so a content refusal never files a crash. Client suppression is safe
231
+ * either way — hq-pro still captures every register 400 it classifies as a
232
+ * fault through its own `response()` builder, so narrowing this set to the two
233
+ * evidenced codes would only re-expose siblings the server already exempts.
234
+ */
235
+ const CALLER_CONTENT_REGISTER_400_CODES = new Set([
236
+ "SKILL_REGISTER_FRONTMATTER_INVALID",
237
+ "SKILL_REGISTER_TOO_LARGE",
238
+ "SKILL_REGISTER_CONTENT_INVALID",
239
+ "SKILL_REGISTER_METADATA_INVALID",
240
+ "SKILL_REGISTER_GOVERNANCE_INVALID",
241
+ ]);
211
242
  /**
212
243
  * Type a failed skills-API response by its HTTP status so the top-level error
213
244
  * boundary can tell a correctly-denied client 4xx (ordinary caller state) from a
@@ -220,14 +251,30 @@ export function mapSkillError(status, body) {
220
251
  * 403, 404, 409, 429 -> ExpectedUserError (HQ-CLI-6: printed, exit 1, not captured)
221
252
  * everything else -> unmarked Error (still captured to Sentry)
222
253
  *
223
- * The 4xx allowlist is closed and ENUMERATED, not a `status < 500` range: a 400
224
- * or 422 means the CLI itself built a malformed request, and every 5xx is a real
225
- * server fault both must keep reaching Sentry so a genuine defect is never
226
- * hidden behind a "caller error" label (HQ-CLI-Z, Sentry 7694457056). Before
227
- * this, both call sites threw a bare `new Error(...)`, so a correctly-denied 403
228
- * (e.g. an agent principal with no person entity) fell through the boundary's
229
- * closed allowlist and filed a crash report for what hq-pro itself deliberately
230
- * exempts from Sentry capture.
254
+ * The 4xx allowlist is closed and ENUMERATED, not a `status < 500` range, and
255
+ * every 5xx is a real server fault that must keep reaching Sentry so a genuine
256
+ * defect is never hidden behind a "caller error" label (HQ-CLI-Z, Sentry
257
+ * 7694457056). Before this, both call sites threw a bare `new Error(...)`, so a
258
+ * correctly-denied 403 (e.g. an agent principal with no person entity) fell
259
+ * through the boundary's closed allowlist and filed a crash for what hq-pro
260
+ * itself deliberately exempts from Sentry capture.
261
+ *
262
+ * A 400 is DISCRIMINATED BY ITS MACHINE-READABLE `code`, never by status alone
263
+ * and never by the prose (Sentry 7710011866). On the register route the
264
+ * observed 400s reject the CALLER'S OWN SKILL.md CONTENT
265
+ * (`SKILL_REGISTER_FRONTMATTER_INVALID`, `SKILL_REGISTER_TOO_LARGE`, …) — the
266
+ * caller fixes the file and retries; there is no hq-cli defect and no operator
267
+ * action — and hq-pro returns each of those through its NON-capturing
268
+ * `expectedValidationResponse` builder, so hq-cli was the only party filing a
269
+ * crash for them. Those codes (see `CALLER_CONTENT_REGISTER_400_CODES`) are
270
+ * marked `expected`. A 400 whose `code` is absent, non-string, or unlisted —
271
+ * including `SKILL_REGISTER_PATH_INVALID`, the ONE register field hq-cli builds
272
+ * itself (`path: skills/<slug>/SKILL.md`) — stays an unmarked, captured Error,
273
+ * as does every 422: an envelope the CLI built wrong is a genuine hq-cli defect.
274
+ * Client-side suppression cannot blind a real fault: hq-pro retains its
275
+ * capturing `response()` builder for every register 400 it still considers a
276
+ * fault, so this only stops hq-cli from double-filing what the server already
277
+ * exempts.
231
278
  *
232
279
  * `opts.machineIdentity` distinguishes a company agent (`isMachineIdentity()`).
233
280
  * A machine session mints automatically and is never repaired by `hq login`, so
@@ -255,6 +302,19 @@ export function skillApiError(status, body, opts = {}) {
255
302
  if (status === 401) {
256
303
  return opts.machineIdentity ? new Error(message) : new AuthError(message);
257
304
  }
305
+ if (status === 400 &&
306
+ typeof body.code === "string" &&
307
+ CALLER_CONTENT_REGISTER_400_CODES.has(body.code)) {
308
+ // A register 400 refusing the caller's own SKILL.md content — expected
309
+ // caller state hq-pro itself does not capture (see the code allowlist
310
+ // above). The message is the UNCHANGED `redactErrorText(mapSkillError(...))`
311
+ // remedy; only the class changes. A 400 with an absent, non-string, or
312
+ // unlisted `code` (including the CLI-built `SKILL_REGISTER_PATH_INVALID`)
313
+ // falls through to the unmarked, captured Error below.
314
+ return Object.assign(new Error(message), {
315
+ expected: true,
316
+ });
317
+ }
258
318
  if (status === 403 || status === 404 || status === 409 || status === 429) {
259
319
  return Object.assign(new Error(message), {
260
320
  expected: true,
@@ -387,6 +447,23 @@ export function registerSkillCommand(program, deps = {}) {
387
447
  // sync failure exactly as it does everywhere else, and a genuine
388
448
  // fault is captured under its own type and stack.
389
449
  console.warn(chalk.yellow(`⚠ Skill ${registered.skillUid} is stamped locally at '${filePath}', but sync failed.`));
450
+ // ARM B (Sentry 7709408531): when the upload was DENIED by S3 (an
451
+ // AWS SDK v3 403 from a truncated IAM session policy that dropped the
452
+ // skills/ prefix), stamp bounded, hq-derived diagnostics onto the
453
+ // ORIGINAL error so the boundary can print an attributable remedy and
454
+ // capture WITH a bounded context. This is additive only: a
455
+ // non-enumerable field, no new Error, no change to `message`/`name`/
456
+ // `cause` — rewrapping is the exact HQ-CLI-14 defect this block
457
+ // prevents. A non-403 sync failure is left untouched and captures (or
458
+ // is suppressed) under its own type exactly as before.
459
+ if (err && typeof err === "object") {
460
+ const diagnostics = vaultAccessDeniedDiagnostics(err, {
461
+ companySlug,
462
+ objectKey: `skills/${slug}/SKILL.md`,
463
+ });
464
+ if (diagnostics)
465
+ stampVaultAccessDenied(err, diagnostics);
466
+ }
390
467
  throw err;
391
468
  }
392
469
  if (syncResult.aborted) {
package/dist/main.js CHANGED
@@ -45,6 +45,7 @@ import { settleWithin } from "./utils/settle-with-timeout.js";
45
45
  import { emitPlanLimitNag } from "./lib/plan-limit-nag.js";
46
46
  import { kickFlagRegistryReadiness } from "./lib/flag-registry.js";
47
47
  import { isPackageRootResolutionError, packageRootCaptureContext, } from "./utils/package-root-diagnostics.js";
48
+ import { isVaultAccessDeniedError, vaultAccessDeniedMessage, } from "./utils/vault-access-denied-error.js";
48
49
  import { fallbackOperatorMessage, unexpectedCliErrorMessage } from "./utils/unexpected-cli-error.js";
49
50
  /** Hard upper bound for non-user-visible release-health finalization. */
50
51
  const RELEASE_HEALTH_SETTLE_TIMEOUT_MS = 3_000;
@@ -335,6 +336,27 @@ export async function handleTopLevelError(err, deps = defaultTopLevelErrorDepend
335
336
  });
336
337
  deps.setExitCode(1);
337
338
  }
339
+ else if (isVaultAccessDeniedError(err)) {
340
+ // ARM B (Sentry 7709408531): `hq skill create`'s post-register vault sync
341
+ // was DENIED by S3 — an AWS SDK v3 403 from an hq-pro IAM session-policy
342
+ // truncation that dropped the skills/ prefix. Because the HEAD carried no
343
+ // body, the SDK minted an untyped `Unknown http=403 UnknownError` with a
344
+ // system-only stack, so it reached the final else, printed
345
+ // `hq: Unknown: Unknown http=403 UnknownError`, and captured an
346
+ // un-attributable event. skill.ts stamped bounded, hq-derived diagnostics
347
+ // onto the error at the failure site; print the attributable remedy naming
348
+ // the object and company, and STILL capture WITH that bounded context —
349
+ // this is a real hq-pro platform fault that must stay REPORTED and become
350
+ // actionable, never silenced. Mirrors the package-root branch's
351
+ // print-and-capture shape. Keyed on hq-cli's own stamped field, so it is
352
+ // disjoint from every neighbour and changes no existing ordering.
353
+ const { vaultAccessDenied } = err;
354
+ deps.stderr.write(`hq: ${vaultAccessDeniedMessage(vaultAccessDenied)}\n`);
355
+ deps.sentry.captureException(err, {
356
+ contexts: { vault_access_denied: vaultAccessDenied },
357
+ });
358
+ deps.setExitCode(1);
359
+ }
338
360
  else {
339
361
  // A full disk / exhausted quota / read-only filesystem is the user's
340
362
  // machine, not an HQ code defect. Surface a clear, actionable message and
@@ -0,0 +1,59 @@
1
+ /**
2
+ * The finite, hq-derived diagnostics for a denied vault object write. A `type`
3
+ * (not an `interface`) so it carries the implicit index signature Sentry's
4
+ * `Contexts` requires, exactly as `PackageRootResolutionDiagnostics` does.
5
+ */
6
+ export type VaultAccessDeniedDiagnostics = {
7
+ /** The only operation that stamps this today; a closed literal, not free text. */
8
+ operation: "skill-sync";
9
+ /** hq-derived, already `COMPANY_SLUG_PATTERN`-validated at the call site. */
10
+ companySlug: string;
11
+ /** hq-built vault key (`skills/<slug>/SKILL.md`); slug is pattern-validated. */
12
+ objectKey: string;
13
+ /** Always 403 here; kept explicit so the context self-describes. */
14
+ httpStatusCode: number;
15
+ /** AWS request correlation id (`$metadata.requestId`), or null when absent. */
16
+ requestId: string | null;
17
+ /** AWS extended request id (`$metadata.extendedRequestId`), or null. */
18
+ extendedRequestId: string | null;
19
+ };
20
+ /** The hq-derived context the sync failure site already holds. */
21
+ export interface VaultAccessDeniedHint {
22
+ companySlug: string;
23
+ objectKey: string;
24
+ }
25
+ /**
26
+ * If `err` is an AWS SDK v3 authorization failure — an object whose
27
+ * `$metadata.httpStatusCode` is 403 — return bounded diagnostics built from the
28
+ * hq-derived `hint` plus the AWS-minted request identifiers; otherwise `null`.
29
+ *
30
+ * A null result means "not an S3 403" and the caller must leave the error
31
+ * un-stamped so it captures under its own type, exactly as today.
32
+ */
33
+ export declare function vaultAccessDeniedDiagnostics(err: unknown, hint: VaultAccessDeniedHint): VaultAccessDeniedDiagnostics | null;
34
+ /**
35
+ * Stamp bounded diagnostics onto the ORIGINAL sync error as a NON-ENUMERABLE
36
+ * field, preserving the error's identity, `name`, `message` and `cause`
37
+ * (HQ-CLI-14 forbids rewrapping). Non-enumerable so the field never widens the
38
+ * error's own serialization, a `JSON.stringify`, or Sentry's default capture.
39
+ */
40
+ export declare function stampVaultAccessDenied(err: object, diagnostics: VaultAccessDeniedDiagnostics): void;
41
+ /** The carrier the top-level boundary matches: an error hq stamped itself. */
42
+ export interface VaultAccessDeniedError {
43
+ vaultAccessDenied: VaultAccessDeniedDiagnostics;
44
+ }
45
+ /**
46
+ * True when `err` carries the diagnostics {@link stampVaultAccessDenied} put
47
+ * there. Keyed on hq-cli's OWN stamped field (not on `$metadata`), so the
48
+ * boundary branch is disjoint from every neighbour and an AWS 403 that reached
49
+ * the boundary by some other path — un-stamped — still captures bare.
50
+ */
51
+ export declare function isVaultAccessDeniedError(err: unknown): err is VaultAccessDeniedError;
52
+ /**
53
+ * One fixed-shape, input-free actionable line: it names the denied vault object
54
+ * and company, states that the local stamp succeeded, and gives the remedy
55
+ * (grants + re-run). Built only from hq-derived, pattern-validated fields, so —
56
+ * like `syncStateLockMessage` — it needs no redaction or length cap.
57
+ */
58
+ export declare function vaultAccessDeniedMessage(diagnostics: VaultAccessDeniedDiagnostics): string;
59
+ //# sourceMappingURL=vault-access-denied-error.d.ts.map
@@ -0,0 +1,122 @@
1
+ // src/utils/vault-access-denied-error.ts
2
+ //
3
+ // Recognise — and make ATTRIBUTABLE — an AWS S3 AUTHORIZATION failure raised
4
+ // inside `hq skill create`'s post-register vault sync. Sibling in spirit to
5
+ // `sync-state-lock-error.ts` (HQ-CLI-14) and `package-root-diagnostics.ts`: a
6
+ // structurally-matched carrier plus a bounded, hq-derived diagnostics payload.
7
+ //
8
+ // UNLIKE the suppression siblings, this class stays CAPTURED. The reported
9
+ // event (Sentry 7709408531) is a REAL platform fault: hq-pro's STS vendor
10
+ // TRUNCATED the IAM session policy and dropped the very `skills/` prefix being
11
+ // written, so S3 answered the sync HEAD with 403. Because a HEAD carries no
12
+ // body, the AWS SDK v3 could not parse an XML error code and minted an untyped
13
+ // `Unknown http=403 UnknownError` whose `$metadata.httpStatusCode` is 403 and
14
+ // whose stack is system-only. That reached the top-level boundary's final else,
15
+ // which printed `hq: Unknown: Unknown http=403 UnknownError` and captured an
16
+ // event carrying nothing that identifies the object, the company, or the
17
+ // truncation. This module does NOT silence the 403 — it keeps it reported while
18
+ // giving the user an actionable line and the tracker a bounded context.
19
+ //
20
+ // Two hard rules, mirroring the diagnostics siblings:
21
+ // 1. STRUCTURAL match, never `instanceof`. The carrier is whatever the AWS SDK
22
+ // threw; the only signal read is `$metadata.httpStatusCode === 403`, so a
23
+ // future SDK rename cannot break it. A miss degrades to today's behaviour
24
+ // (still captured, just uncontextualised) — it can never suppress.
25
+ // 2. NO caller free text, NO secrets. The company slug and object key are
26
+ // hq-DERIVED (both are already pattern-validated upstream); the AWS request
27
+ // identifiers are opaque correlation tokens, length-bounded here. STS
28
+ // credentials, presigned URLs and message prose never enter the payload.
29
+ /** The non-enumerable field name the sync catch stamps and the boundary reads. */
30
+ const VAULT_ACCESS_DENIED_FIELD = "vaultAccessDenied";
31
+ /** Upper bound on an AWS request identifier before it enters a Sentry context. */
32
+ const REQUEST_ID_MAX_LENGTH = 256;
33
+ /** An AWS request identifier, coerced to a bounded string or null. */
34
+ function boundedRequestId(value) {
35
+ if (typeof value !== "string" || value.length === 0)
36
+ return null;
37
+ return value.slice(0, REQUEST_ID_MAX_LENGTH);
38
+ }
39
+ /**
40
+ * If `err` is an AWS SDK v3 authorization failure — an object whose
41
+ * `$metadata.httpStatusCode` is 403 — return bounded diagnostics built from the
42
+ * hq-derived `hint` plus the AWS-minted request identifiers; otherwise `null`.
43
+ *
44
+ * A null result means "not an S3 403" and the caller must leave the error
45
+ * un-stamped so it captures under its own type, exactly as today.
46
+ */
47
+ export function vaultAccessDeniedDiagnostics(err, hint) {
48
+ if (err === null || typeof err !== "object")
49
+ return null;
50
+ const metadata = err.$metadata;
51
+ if (metadata === null || typeof metadata !== "object")
52
+ return null;
53
+ if (metadata.httpStatusCode !== 403) {
54
+ return null;
55
+ }
56
+ return {
57
+ operation: "skill-sync",
58
+ companySlug: hint.companySlug,
59
+ objectKey: hint.objectKey,
60
+ httpStatusCode: 403,
61
+ requestId: boundedRequestId(metadata.requestId),
62
+ extendedRequestId: boundedRequestId(metadata.extendedRequestId),
63
+ };
64
+ }
65
+ /** True only for a diagnostics object of the exact closed shape. */
66
+ function isVaultAccessDeniedDiagnostics(value) {
67
+ if (value === null || typeof value !== "object")
68
+ return false;
69
+ const d = value;
70
+ return (d.operation === "skill-sync" &&
71
+ typeof d.companySlug === "string" &&
72
+ typeof d.objectKey === "string" &&
73
+ d.httpStatusCode === 403);
74
+ }
75
+ /**
76
+ * Stamp bounded diagnostics onto the ORIGINAL sync error as a NON-ENUMERABLE
77
+ * field, preserving the error's identity, `name`, `message` and `cause`
78
+ * (HQ-CLI-14 forbids rewrapping). Non-enumerable so the field never widens the
79
+ * error's own serialization, a `JSON.stringify`, or Sentry's default capture.
80
+ */
81
+ export function stampVaultAccessDenied(err, diagnostics) {
82
+ Object.defineProperty(err, VAULT_ACCESS_DENIED_FIELD, {
83
+ value: diagnostics,
84
+ enumerable: false,
85
+ configurable: true,
86
+ writable: true,
87
+ });
88
+ }
89
+ /**
90
+ * True when `err` carries the diagnostics {@link stampVaultAccessDenied} put
91
+ * there. Keyed on hq-cli's OWN stamped field (not on `$metadata`), so the
92
+ * boundary branch is disjoint from every neighbour and an AWS 403 that reached
93
+ * the boundary by some other path — un-stamped — still captures bare.
94
+ */
95
+ export function isVaultAccessDeniedError(err) {
96
+ if (err === null || typeof err !== "object")
97
+ return false;
98
+ return isVaultAccessDeniedDiagnostics(err.vaultAccessDenied);
99
+ }
100
+ /** Recover the create `<slug>` from an hq-built `skills/<slug>/SKILL.md` key. */
101
+ function slugFromObjectKey(objectKey) {
102
+ const match = /^skills\/([a-z0-9][a-z0-9-]*)\/SKILL\.md$/.exec(objectKey);
103
+ return match ? match[1] : null;
104
+ }
105
+ /**
106
+ * One fixed-shape, input-free actionable line: it names the denied vault object
107
+ * and company, states that the local stamp succeeded, and gives the remedy
108
+ * (grants + re-run). Built only from hq-derived, pattern-validated fields, so —
109
+ * like `syncStateLockMessage` — it needs no redaction or length cap.
110
+ */
111
+ export function vaultAccessDeniedMessage(diagnostics) {
112
+ const slug = slugFromObjectKey(diagnostics.objectKey);
113
+ const reRun = slug
114
+ ? `hq skill --company ${diagnostics.companySlug} create ${slug}`
115
+ : `hq skill --company ${diagnostics.companySlug} create <slug>`;
116
+ return (`The skill was stamped locally, but the vault upload of ` +
117
+ `'${diagnostics.objectKey}' was denied (HTTP 403) for company ` +
118
+ `'${diagnostics.companySlug}'. Your file grants don't cover that path. Ask ` +
119
+ `a ${diagnostics.companySlug} owner to confirm your file grants cover the ` +
120
+ `skills/ prefix, then re-run \`${reRun}\`.`);
121
+ }
122
+ //# sourceMappingURL=vault-access-denied-error.js.map
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@indigoai-us/hq-cli",
3
- "version": "5.108.12",
3
+ "version": "5.108.13",
4
4
  "description": "HQ by Indigo management CLI — modules and cloud sync",
5
5
  "main": "dist/index.js",
6
6
  "bin": {