@indigoai-us/hq-cli 5.5.2 → 5.5.3

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.
Files changed (121) hide show
  1. package/dist/bin/hq-auth-refresh.js +26 -13
  2. package/dist/commands/_patterns.d.ts +3 -0
  3. package/dist/commands/_patterns.js +6 -0
  4. package/dist/commands/add.js +4 -1
  5. package/dist/commands/auth.js +4 -1
  6. package/dist/commands/cloud.js +4 -1
  7. package/dist/commands/groups.d.ts +3 -0
  8. package/dist/commands/groups.js +344 -0
  9. package/dist/commands/list.js +4 -1
  10. package/dist/commands/login.js +4 -1
  11. package/dist/commands/logout.js +4 -1
  12. package/dist/commands/onboard.js +4 -1
  13. package/dist/commands/pack-install.js +4 -1
  14. package/dist/commands/pkg-install.js +4 -1
  15. package/dist/commands/pkg-list.js +4 -1
  16. package/dist/commands/pkg-remove.js +4 -1
  17. package/dist/commands/pkg-update.js +4 -1
  18. package/dist/commands/secrets.d.ts +9 -0
  19. package/dist/commands/secrets.js +263 -25
  20. package/dist/commands/sync.js +4 -1
  21. package/dist/commands/team-sync.js +4 -1
  22. package/dist/commands/update.js +4 -1
  23. package/dist/commands/whoami.js +4 -1
  24. package/dist/index.js +21 -2
  25. package/dist/sentry-before-send.d.ts +3 -0
  26. package/dist/sentry-before-send.js +219 -0
  27. package/dist/sentry-dsn.generated.d.ts +2 -0
  28. package/dist/sentry-dsn.generated.js +5 -0
  29. package/dist/sentry.d.ts +4 -0
  30. package/dist/sentry.js +22 -0
  31. package/dist/strategies/link.js +4 -1
  32. package/dist/strategies/merge.js +4 -1
  33. package/dist/types.js +4 -1
  34. package/dist/utils/cognito-session.js +4 -1
  35. package/dist/utils/git.js +4 -1
  36. package/dist/utils/hq-root.js +4 -1
  37. package/dist/utils/integrity.js +4 -1
  38. package/dist/utils/manifest.js +4 -1
  39. package/dist/utils/registry-client.js +4 -1
  40. package/dist/utils/registry.js +4 -1
  41. package/dist/utils/secrets-cache.js +6 -2
  42. package/package.json +4 -3
  43. package/scripts/generate-dsn.mjs +26 -0
  44. package/src/bin/hq-auth-refresh.ts +23 -16
  45. package/src/commands/_patterns.ts +2 -0
  46. package/src/commands/groups.ts +357 -0
  47. package/src/commands/secrets.ts +306 -26
  48. package/src/index.ts +17 -1
  49. package/src/sentry-before-send.test.ts +359 -0
  50. package/src/sentry-before-send.ts +221 -0
  51. package/src/sentry.test.ts +24 -0
  52. package/src/sentry.ts +19 -0
  53. package/src/utils/secrets-cache.ts +2 -1
  54. package/dist/bin/hq-auth-refresh.d.ts.map +0 -1
  55. package/dist/bin/hq-auth-refresh.js.map +0 -1
  56. package/dist/commands/add.d.ts.map +0 -1
  57. package/dist/commands/add.js.map +0 -1
  58. package/dist/commands/auth.d.ts.map +0 -1
  59. package/dist/commands/auth.js.map +0 -1
  60. package/dist/commands/cloud.d.ts.map +0 -1
  61. package/dist/commands/cloud.js.map +0 -1
  62. package/dist/commands/list.d.ts.map +0 -1
  63. package/dist/commands/list.js.map +0 -1
  64. package/dist/commands/login.d.ts.map +0 -1
  65. package/dist/commands/login.js.map +0 -1
  66. package/dist/commands/logout.d.ts.map +0 -1
  67. package/dist/commands/logout.js.map +0 -1
  68. package/dist/commands/onboard.d.ts.map +0 -1
  69. package/dist/commands/onboard.js.map +0 -1
  70. package/dist/commands/pack-install.d.ts.map +0 -1
  71. package/dist/commands/pack-install.js.map +0 -1
  72. package/dist/commands/pkg-install.d.ts.map +0 -1
  73. package/dist/commands/pkg-install.js.map +0 -1
  74. package/dist/commands/pkg-list.d.ts.map +0 -1
  75. package/dist/commands/pkg-list.js.map +0 -1
  76. package/dist/commands/pkg-remove.d.ts.map +0 -1
  77. package/dist/commands/pkg-remove.js.map +0 -1
  78. package/dist/commands/pkg-update.d.ts.map +0 -1
  79. package/dist/commands/pkg-update.js.map +0 -1
  80. package/dist/commands/secrets.d.ts.map +0 -1
  81. package/dist/commands/secrets.js.map +0 -1
  82. package/dist/commands/sync.d.ts.map +0 -1
  83. package/dist/commands/sync.js.map +0 -1
  84. package/dist/commands/team-sync.d.ts.map +0 -1
  85. package/dist/commands/team-sync.js.map +0 -1
  86. package/dist/commands/update.d.ts.map +0 -1
  87. package/dist/commands/update.js.map +0 -1
  88. package/dist/commands/whoami.d.ts.map +0 -1
  89. package/dist/commands/whoami.js.map +0 -1
  90. package/dist/index.d.ts.map +0 -1
  91. package/dist/index.js.map +0 -1
  92. package/dist/strategies/link.d.ts.map +0 -1
  93. package/dist/strategies/link.js.map +0 -1
  94. package/dist/strategies/merge.d.ts.map +0 -1
  95. package/dist/strategies/merge.js.map +0 -1
  96. package/dist/types.d.ts.map +0 -1
  97. package/dist/types.js.map +0 -1
  98. package/dist/utils/auth.d.ts +0 -27
  99. package/dist/utils/auth.d.ts.map +0 -1
  100. package/dist/utils/auth.js +0 -155
  101. package/dist/utils/auth.js.map +0 -1
  102. package/dist/utils/cognito-session.d.ts.map +0 -1
  103. package/dist/utils/cognito-session.js.map +0 -1
  104. package/dist/utils/git.d.ts.map +0 -1
  105. package/dist/utils/git.js.map +0 -1
  106. package/dist/utils/hq-root.d.ts.map +0 -1
  107. package/dist/utils/hq-root.js.map +0 -1
  108. package/dist/utils/integrity.d.ts.map +0 -1
  109. package/dist/utils/integrity.js.map +0 -1
  110. package/dist/utils/manifest.d.ts.map +0 -1
  111. package/dist/utils/manifest.js.map +0 -1
  112. package/dist/utils/registry-client.d.ts.map +0 -1
  113. package/dist/utils/registry-client.js.map +0 -1
  114. package/dist/utils/registry.d.ts.map +0 -1
  115. package/dist/utils/registry.js.map +0 -1
  116. package/dist/utils/secrets-cache.d.ts.map +0 -1
  117. package/dist/utils/secrets-cache.js.map +0 -1
  118. package/dist/utils/token-store.d.ts +0 -28
  119. package/dist/utils/token-store.d.ts.map +0 -1
  120. package/dist/utils/token-store.js +0 -68
  121. package/dist/utils/token-store.js.map +0 -1
@@ -1,12 +1,25 @@
1
+
2
+ !function(){try{var e="undefined"!=typeof window?window:"undefined"!=typeof global?global:"undefined"!=typeof globalThis?globalThis:"undefined"!=typeof self?self:{},n=(new e.Error).stack;n&&(e._sentryDebugIds=e._sentryDebugIds||{},e._sentryDebugIds[n]="70f0907f-bfcb-5d2e-958a-8813d3bbaf96")}catch(e){}}();
1
3
  import chalk from "chalk";
2
4
  import * as readline from "node:readline";
3
5
  import { spawn } from "node:child_process";
4
6
  import { ensureCognitoToken, DEFAULT_VAULT_API_URL, } from "../utils/cognito-session.js";
5
7
  import { readCache, writeCache, removeCacheEntry, clearAllCache, } from "../utils/secrets-cache.js";
8
+ import { SECRET_NAME_PATTERN, GROUP_ID_PATTERN } from "./_patterns.js";
6
9
  function shellSingleQuote(value) {
7
10
  return "'" + value.replace(/'/g, "'\\''") + "'";
8
11
  }
9
- async function vaultApiFetch(opts) {
12
+ // API Gateway v2 URL-decodes `%2F` before route matching, so a full-name
13
+ // `encodeURIComponent` would collapse structural `/` separators into a single
14
+ // segment the `/name/{proxy+}` route can't match.
15
+ function buildSecretNamePath(companyUid, name) {
16
+ const encodedName = name
17
+ .split("/")
18
+ .map(encodeURIComponent)
19
+ .join("/");
20
+ return `/secrets/${encodeURIComponent(companyUid)}/name/${encodedName}`;
21
+ }
22
+ export async function vaultApiFetch(opts) {
10
23
  const url = new URL(opts.path, DEFAULT_VAULT_API_URL);
11
24
  if (opts.query) {
12
25
  for (const [k, v] of Object.entries(opts.query)) {
@@ -53,7 +66,7 @@ async function resolveCompanyFromMemberships(token) {
53
66
  const uids = active.map((m) => m.companyUid).join(", ");
54
67
  throw new Error(`Multiple companies found (${uids}). Use --company <slug> to specify which one.`);
55
68
  }
56
- async function getCompanyUid(token, companySlug) {
69
+ export async function getCompanyUid(token, companySlug) {
57
70
  if (companySlug) {
58
71
  return resolveCompanyUid(token, companySlug);
59
72
  }
@@ -160,8 +173,8 @@ export function registerSecretsCommand(program) {
160
173
  .option("--from-stdin", "Read secret value from piped stdin")
161
174
  .action(async (name, opts) => {
162
175
  try {
163
- if (!/^[A-Z][A-Z0-9_]*$/.test(name)) {
164
- console.error(chalk.red(`Invalid secret name '${name}': must match ^[A-Z][A-Z0-9_]*$ (e.g. MY_API_KEY)`));
176
+ if (!SECRET_NAME_PATTERN.test(name)) {
177
+ console.error(chalk.red(`Invalid secret name '${name}': must match ^[A-Z][A-Z0-9_]*(/[A-Z][A-Z0-9_]+)*$ (e.g. MY_API_KEY or DEV/MY_KEY)`));
165
178
  process.exit(1);
166
179
  }
167
180
  let value;
@@ -224,7 +237,7 @@ export function registerSecretsCommand(program) {
224
237
  }
225
238
  const res = await vaultApiFetch({
226
239
  token,
227
- path: `/secrets/${encodeURIComponent(companyUid)}/${encodeURIComponent(name)}`,
240
+ path: buildSecretNamePath(companyUid, name),
228
241
  query: Object.keys(query).length > 0 ? query : undefined,
229
242
  });
230
243
  if (!res.ok) {
@@ -255,15 +268,32 @@ export function registerSecretsCommand(program) {
255
268
  });
256
269
  secrets
257
270
  .command("list")
258
- .description("List all secrets for the company")
259
- .action(async () => {
271
+ .description("List all secrets for the company (including nested path-based names)")
272
+ .option("--prefix <path>", "Filter by path prefix (e.g. DEV or DEV/SUB); omit or pass empty string for all secrets")
273
+ .action(async (opts) => {
260
274
  try {
275
+ // MUST match SECRET_PREFIX_PATTERN in hq-pro/src/vault-service/handlers/secrets.ts
276
+ let normalizedPrefix;
277
+ if (opts.prefix) {
278
+ const normalized = opts.prefix.replace(/^\/+|\/+$/g, "");
279
+ if (normalized.length === 0 ||
280
+ !SECRET_NAME_PATTERN.test(normalized)) {
281
+ console.error(chalk.red(`Invalid prefix '${opts.prefix}': must match ^[A-Z][A-Z0-9_]*(/[A-Z][A-Z0-9_]+)*$ (e.g. DEV or DEV/SUB)`));
282
+ process.exit(1);
283
+ }
284
+ normalizedPrefix = normalized;
285
+ }
261
286
  const token = await ensureCognitoToken();
262
287
  const companySlug = secrets.opts().company;
263
288
  const companyUid = await getCompanyUid(token, companySlug);
289
+ const query = {};
290
+ if (normalizedPrefix) {
291
+ query.prefix = normalizedPrefix;
292
+ }
264
293
  const res = await vaultApiFetch({
265
294
  token,
266
295
  path: `/secrets/${encodeURIComponent(companyUid)}`,
296
+ query: Object.keys(query).length > 0 ? query : undefined,
267
297
  });
268
298
  if (!res.ok) {
269
299
  const body = await res.json().catch(() => ({}));
@@ -276,11 +306,24 @@ export function registerSecretsCommand(program) {
276
306
  return;
277
307
  }
278
308
  const nameWidth = Math.max(4, ...data.secrets.map((s) => s.name.length));
279
- const header = `${"NAME".padEnd(nameWidth)} LAST MODIFIED`;
280
- console.log(chalk.bold(header));
281
- for (const s of data.secrets) {
282
- const modified = s.lastModifiedDate ?? "-";
283
- console.log(`${s.name.padEnd(nameWidth)} ${modified}`);
309
+ const hasPermission = data.secrets.some((s) => s.permission !== undefined);
310
+ if (hasPermission) {
311
+ const accessWidth = Math.max(6, ...data.secrets.map((s) => (s.permission ?? "-").length));
312
+ const header = `${"NAME".padEnd(nameWidth)} ${"ACCESS".padEnd(accessWidth)} LAST MODIFIED`;
313
+ console.log(chalk.bold(header));
314
+ for (const s of data.secrets) {
315
+ const access = s.permission ?? "-";
316
+ const modified = s.lastModifiedDate ?? "-";
317
+ console.log(`${s.name.padEnd(nameWidth)} ${access.padEnd(accessWidth)} ${modified}`);
318
+ }
319
+ }
320
+ else {
321
+ const header = `${"NAME".padEnd(nameWidth)} LAST MODIFIED`;
322
+ console.log(chalk.bold(header));
323
+ for (const s of data.secrets) {
324
+ const modified = s.lastModifiedDate ?? "-";
325
+ console.log(`${s.name.padEnd(nameWidth)} ${modified}`);
326
+ }
284
327
  }
285
328
  }
286
329
  catch (err) {
@@ -294,8 +337,8 @@ export function registerSecretsCommand(program) {
294
337
  .option("--force", "Skip confirmation prompt")
295
338
  .action(async (name, opts) => {
296
339
  try {
297
- if (!/^[A-Z][A-Z0-9_]*$/.test(name)) {
298
- console.error(chalk.red(`Invalid secret name '${name}': must match ^[A-Z][A-Z0-9_]*$ (e.g. MY_API_KEY)`));
340
+ if (!SECRET_NAME_PATTERN.test(name)) {
341
+ console.error(chalk.red(`Invalid secret name '${name}': must match ^[A-Z][A-Z0-9_]*(/[A-Z][A-Z0-9_]+)*$ (e.g. MY_API_KEY or DEV/MY_KEY)`));
299
342
  process.exit(1);
300
343
  }
301
344
  if (!opts.force) {
@@ -310,7 +353,7 @@ export function registerSecretsCommand(program) {
310
353
  const companyUid = await getCompanyUid(token, companySlug);
311
354
  const res = await vaultApiFetch({
312
355
  token,
313
- path: `/secrets/${encodeURIComponent(companyUid)}/${encodeURIComponent(name)}`,
356
+ path: buildSecretNamePath(companyUid, name),
314
357
  method: "DELETE",
315
358
  });
316
359
  if (!res.ok) {
@@ -352,8 +395,8 @@ export function registerSecretsCommand(program) {
352
395
  process.exit(1);
353
396
  }
354
397
  for (const key of keys) {
355
- if (!/^[A-Z][A-Z0-9_]*$/.test(key)) {
356
- console.error(chalk.red(`Invalid secret name '${key}': must match ^[A-Z][A-Z0-9_]*$`));
398
+ if (!SECRET_NAME_PATTERN.test(key)) {
399
+ console.error(chalk.red(`Invalid secret name '${key}': must match ^[A-Z][A-Z0-9_]*(/[A-Z][A-Z0-9_]+)*$`));
357
400
  process.exit(1);
358
401
  }
359
402
  }
@@ -367,7 +410,7 @@ export function registerSecretsCommand(program) {
367
410
  }
368
411
  const res = await vaultApiFetch({
369
412
  token,
370
- path: `/secrets/${encodeURIComponent(companyUid)}/${encodeURIComponent(key)}`,
413
+ path: buildSecretNamePath(companyUid, key),
371
414
  query: { reveal: "true" },
372
415
  });
373
416
  if (!res.ok) {
@@ -422,8 +465,8 @@ export function registerSecretsCommand(program) {
422
465
  process.exit(1);
423
466
  }
424
467
  for (const key of keys) {
425
- if (!/^[A-Z][A-Z0-9_]*$/.test(key)) {
426
- console.error(chalk.red(`Invalid secret name '${key}': must match ^[A-Z][A-Z0-9_]*$`));
468
+ if (!SECRET_NAME_PATTERN.test(key)) {
469
+ console.error(chalk.red(`Invalid secret name '${key}': must match ^[A-Z][A-Z0-9_]*(/[A-Z][A-Z0-9_]+)*$`));
427
470
  process.exit(1);
428
471
  }
429
472
  }
@@ -437,7 +480,7 @@ export function registerSecretsCommand(program) {
437
480
  }
438
481
  const res = await vaultApiFetch({
439
482
  token,
440
- path: `/secrets/${encodeURIComponent(companyUid)}/${encodeURIComponent(key)}`,
483
+ path: buildSecretNamePath(companyUid, key),
441
484
  query: { reveal: "true" },
442
485
  });
443
486
  if (!res.ok) {
@@ -467,8 +510,8 @@ export function registerSecretsCommand(program) {
467
510
  .option("--expires <duration>", "Token expiry duration (e.g. 24h, 2d, 30m)", "24h")
468
511
  .action(async (name, opts) => {
469
512
  try {
470
- if (!/^[A-Z][A-Z0-9_]*$/.test(name)) {
471
- console.error(chalk.red(`Invalid secret name '${name}': must match ^[A-Z][A-Z0-9_]*$ (e.g. MY_API_KEY)`));
513
+ if (!SECRET_NAME_PATTERN.test(name)) {
514
+ console.error(chalk.red(`Invalid secret name '${name}': must match ^[A-Z][A-Z0-9_]*(/[A-Z][A-Z0-9_]+)*$ (e.g. MY_API_KEY or DEV/MY_KEY)`));
472
515
  process.exit(1);
473
516
  }
474
517
  const expiresInMs = parseDuration(opts.expires);
@@ -486,9 +529,10 @@ export function registerSecretsCommand(program) {
486
529
  const companyUid = await getCompanyUid(token, companySlug);
487
530
  const res = await vaultApiFetch({
488
531
  token,
489
- path: `/secrets/${encodeURIComponent(companyUid)}/${encodeURIComponent(name)}/generate-token`,
532
+ path: buildSecretNamePath(companyUid, name),
490
533
  method: "POST",
491
534
  body: { expiresInMs },
535
+ query: { action: "generate-token" },
492
536
  });
493
537
  if (!res.ok) {
494
538
  const body = await res.json().catch(() => ({}));
@@ -507,6 +551,199 @@ export function registerSecretsCommand(program) {
507
551
  process.exit(1);
508
552
  }
509
553
  });
554
+ secrets
555
+ .command("share <path>")
556
+ .description("Share a secret with a person or group")
557
+ .requiredOption("--with <principal>", "Email address or group id to share with")
558
+ .requiredOption("--permission <level>", "Permission level: read | write | admin")
559
+ .action(async (path, opts) => {
560
+ try {
561
+ if (!SECRET_NAME_PATTERN.test(path)) {
562
+ console.error(chalk.red(`Invalid secret path '${path}': must match ^[A-Z][A-Z0-9_]*(/[A-Z][A-Z0-9_]+)*$ (e.g. MY_KEY or PROD/DB_PASSWORD)`));
563
+ process.exit(1);
564
+ }
565
+ if (!["read", "write", "admin"].includes(opts.permission)) {
566
+ console.error(chalk.red(`Invalid permission '${opts.permission}': must be one of read, write, admin`));
567
+ process.exit(1);
568
+ }
569
+ const isEmail = /^[^\s]+@[^\s]+$/.test(opts.with);
570
+ if (!isEmail && !GROUP_ID_PATTERN.test(opts.with)) {
571
+ console.error(chalk.red(`Invalid principal '${opts.with}': must be an email address or a group id matching grp_<alphanumeric>`));
572
+ process.exit(1);
573
+ }
574
+ const granteeType = isEmail ? "email" : "group";
575
+ const granteeId = opts.with;
576
+ const token = await ensureCognitoToken();
577
+ const companySlug = secrets.opts().company;
578
+ const companyUid = await getCompanyUid(token, companySlug);
579
+ const res = await vaultApiFetch({
580
+ token,
581
+ path: `/secrets/${encodeURIComponent(companyUid)}/acl/grant`,
582
+ method: "POST",
583
+ body: { path, granteeType, granteeId, permission: opts.permission },
584
+ });
585
+ if (!res.ok) {
586
+ const body = await res.json().catch(() => ({}));
587
+ if (res.status === 401) {
588
+ console.error(chalk.red("Not authenticated — please run `hq login`"));
589
+ }
590
+ else if (res.status === 403) {
591
+ console.error(chalk.red("Not authorized to share this secret"));
592
+ }
593
+ else if (res.status === 404) {
594
+ console.error(chalk.red("ACL record not found for this path — has the secret been created?"));
595
+ }
596
+ else if (res.status === 409) {
597
+ console.error(chalk.red("Concurrent modification — please retry"));
598
+ }
599
+ else if (res.status >= 500) {
600
+ console.error(chalk.red(`Server error: ${body.error ?? res.statusText}`));
601
+ }
602
+ else {
603
+ console.error(chalk.red(body.message ?? body.error ?? "Invalid request"));
604
+ }
605
+ process.exit(1);
606
+ }
607
+ console.log(chalk.green(`Shared '${path}' with ${opts.with} (${opts.permission})`));
608
+ }
609
+ catch (err) {
610
+ console.error(chalk.red("Error:"), err instanceof Error ? err.message : String(err));
611
+ process.exit(1);
612
+ }
613
+ });
614
+ secrets
615
+ .command("unshare <path>")
616
+ .description("Remove a grant from a secret")
617
+ .requiredOption("--from <principal>", "Email address or group id to remove")
618
+ .action(async (path, opts) => {
619
+ try {
620
+ if (!SECRET_NAME_PATTERN.test(path)) {
621
+ console.error(chalk.red(`Invalid secret path '${path}': must match ^[A-Z][A-Z0-9_]*(/[A-Z][A-Z0-9_]+)*$ (e.g. MY_KEY or PROD/DB_PASSWORD)`));
622
+ process.exit(1);
623
+ }
624
+ const isEmailFrom = /^[^\s]+@[^\s]+$/.test(opts.from);
625
+ if (!isEmailFrom && !GROUP_ID_PATTERN.test(opts.from)) {
626
+ console.error(chalk.red(`Invalid principal '${opts.from}': must be an email address or a group id matching grp_<alphanumeric>`));
627
+ process.exit(1);
628
+ }
629
+ const granteeType = isEmailFrom ? "email" : "group";
630
+ const granteeId = opts.from;
631
+ const token = await ensureCognitoToken();
632
+ const companySlug = secrets.opts().company;
633
+ const companyUid = await getCompanyUid(token, companySlug);
634
+ const res = await vaultApiFetch({
635
+ token,
636
+ path: `/secrets/${encodeURIComponent(companyUid)}/acl/revoke`,
637
+ method: "POST",
638
+ body: { path, granteeType, granteeId },
639
+ });
640
+ if (!res.ok) {
641
+ const body = await res.json().catch(() => ({}));
642
+ if (res.status === 401) {
643
+ console.error(chalk.red("Not authenticated — please run `hq login`"));
644
+ }
645
+ else if (res.status === 403) {
646
+ console.error(chalk.red("Not authorized to modify this secret's ACL"));
647
+ }
648
+ else if (res.status === 404) {
649
+ console.log(chalk.green(`Grant already absent for '${path}' / ${opts.from}`));
650
+ return;
651
+ }
652
+ else if (res.status >= 500) {
653
+ console.error(chalk.red(`Server error: ${body.error ?? res.statusText}`));
654
+ }
655
+ else {
656
+ console.error(chalk.red(body.message ?? body.error ?? "Invalid request"));
657
+ }
658
+ process.exit(1);
659
+ }
660
+ console.log(chalk.green(`Removed grant for ${opts.from} on '${path}'`));
661
+ }
662
+ catch (err) {
663
+ console.error(chalk.red("Error:"), err instanceof Error ? err.message : String(err));
664
+ process.exit(1);
665
+ }
666
+ });
667
+ secrets
668
+ .command("acl <path>")
669
+ .description("Show the ACL (access control list) for a secret path")
670
+ .action(async (path) => {
671
+ try {
672
+ if (!SECRET_NAME_PATTERN.test(path)) {
673
+ console.error(chalk.red(`Invalid secret path '${path}': must match ^[A-Z][A-Z0-9_]*(/[A-Z][A-Z0-9_]+)*$ (e.g. MY_KEY or PROD/DB_PASSWORD)`));
674
+ process.exit(1);
675
+ }
676
+ const token = await ensureCognitoToken();
677
+ const companySlug = secrets.opts().company;
678
+ const companyUid = await getCompanyUid(token, companySlug);
679
+ const secretPath = path;
680
+ const res = await vaultApiFetch({
681
+ token,
682
+ path: `/secrets/${encodeURIComponent(companyUid)}/acl`,
683
+ query: { path: secretPath },
684
+ });
685
+ if (!res.ok) {
686
+ const body = await res.json().catch(() => ({}));
687
+ if (res.status === 401) {
688
+ console.error(chalk.red("Not authenticated — please run `hq login`"));
689
+ }
690
+ else if (res.status === 403) {
691
+ console.error(chalk.red("Not authorized to view this secret's ACL"));
692
+ }
693
+ else if (res.status === 404) {
694
+ console.error(chalk.red(`No ACL record exists for '${path}'`));
695
+ }
696
+ else if (res.status >= 500) {
697
+ console.error(chalk.red(`Server error: ${body.error ?? res.statusText}`));
698
+ }
699
+ else {
700
+ console.error(chalk.red(body.message ?? body.error ?? "Invalid request"));
701
+ }
702
+ process.exit(1);
703
+ }
704
+ const data = (await res.json());
705
+ const acl = data.acl;
706
+ const aclStatus = acl.open ? "open" : "restricted";
707
+ console.log(chalk.green(`ACL for ${acl.path} (${aclStatus})`));
708
+ console.log(`Creator: ${acl.creatorUid}`);
709
+ if (acl.entries.length === 0) {
710
+ if (acl.open) {
711
+ console.log(chalk.gray("Open ACL — all active members have read access."));
712
+ }
713
+ else {
714
+ console.log(chalk.gray("No explicit grants — only creator has access."));
715
+ }
716
+ return;
717
+ }
718
+ console.log("Entries:");
719
+ const TYPE_W = Math.max(4, ...acl.entries.map((e) => e.granteeType.length));
720
+ const GRANTEE_W = Math.max(7, ...acl.entries.map((e) => e.granteeId.length));
721
+ const PERM_W = Math.max(10, ...acl.entries.map((e) => e.permission.length));
722
+ const BY_W = Math.max(10, ...acl.entries.map((e) => e.grantedBy.length));
723
+ const tableHeader = [
724
+ "TYPE".padEnd(TYPE_W),
725
+ "GRANTEE".padEnd(GRANTEE_W),
726
+ "PERMISSION".padEnd(PERM_W),
727
+ "GRANTED_BY".padEnd(BY_W),
728
+ "GRANTED_AT",
729
+ ].join(" ");
730
+ console.log(chalk.bold(tableHeader));
731
+ for (const e of acl.entries) {
732
+ const grantedAt = e.grantedAt.slice(0, 10);
733
+ console.log([
734
+ e.granteeType.padEnd(TYPE_W),
735
+ e.granteeId.padEnd(GRANTEE_W),
736
+ e.permission.padEnd(PERM_W),
737
+ e.grantedBy.padEnd(BY_W),
738
+ grantedAt,
739
+ ].join(" "));
740
+ }
741
+ }
742
+ catch (err) {
743
+ console.error(chalk.red("Error:"), err instanceof Error ? err.message : String(err));
744
+ process.exit(1);
745
+ }
746
+ });
510
747
  const cache = secrets
511
748
  .command("cache")
512
749
  .description("Manage the local secrets cache");
@@ -518,4 +755,5 @@ export function registerSecretsCommand(program) {
518
755
  console.log(chalk.green("Secrets cache cleared."));
519
756
  });
520
757
  }
521
- //# sourceMappingURL=secrets.js.map
758
+ //# sourceMappingURL=secrets.js.map
759
+ //# debugId=70f0907f-bfcb-5d2e-958a-8813d3bbaf96
@@ -1,6 +1,8 @@
1
1
  /**
2
2
  * hq modules sync command (US-004)
3
3
  */
4
+
5
+ !function(){try{var e="undefined"!=typeof window?window:"undefined"!=typeof global?global:"undefined"!=typeof globalThis?globalThis:"undefined"!=typeof self?self:{},n=(new e.Error).stack;n&&(e._sentryDebugIds=e._sentryDebugIds||{},e._sentryDebugIds[n]="abaf6ba7-702e-53e3-9d38-21182f05e529")}catch(e){}}();
4
6
  import * as fs from 'fs';
5
7
  import * as path from 'path';
6
8
  import { findHqRoot, readManifest, readLock, writeLock, getModulesDir, } from '../utils/manifest.js';
@@ -109,4 +111,5 @@ export function registerSyncCommand(program) {
109
111
  }
110
112
  });
111
113
  }
112
- //# sourceMappingURL=sync.js.map
114
+ //# sourceMappingURL=sync.js.map
115
+ //# debugId=abaf6ba7-702e-53e3-9d38-21182f05e529
@@ -9,6 +9,8 @@
9
9
  * 5. Report what changed (new, updated, removed files)
10
10
  * 6. Gracefully handle conflicts (warn, don't force overwrite)
11
11
  */
12
+
13
+ !function(){try{var e="undefined"!=typeof window?window:"undefined"!=typeof global?global:"undefined"!=typeof globalThis?globalThis:"undefined"!=typeof self?self:{},n=(new e.Error).stack;n&&(e._sentryDebugIds=e._sentryDebugIds||{},e._sentryDebugIds[n]="4c9a650e-05ed-5e33-b5de-b6d521ecdf4f")}catch(e){}}();
12
14
  import * as fs from 'fs';
13
15
  import * as path from 'path';
14
16
  import { execSync } from 'child_process';
@@ -419,4 +421,5 @@ export function registerTeamSyncCommand(program) {
419
421
  }
420
422
  });
421
423
  }
422
- //# sourceMappingURL=team-sync.js.map
424
+ //# sourceMappingURL=team-sync.js.map
425
+ //# debugId=4c9a650e-05ed-5e33-b5de-b6d521ecdf4f
@@ -2,6 +2,8 @@
2
2
  * hq modules update command (US-008)
3
3
  * Updates lock for specific module
4
4
  */
5
+
6
+ !function(){try{var e="undefined"!=typeof window?window:"undefined"!=typeof global?global:"undefined"!=typeof globalThis?globalThis:"undefined"!=typeof self?self:{},n=(new e.Error).stack;n&&(e._sentryDebugIds=e._sentryDebugIds||{},e._sentryDebugIds[n]="ed0bfb79-4403-57a0-85eb-766bc4110b67")}catch(e){}}();
5
7
  import * as path from 'path';
6
8
  import { findHqRoot, readManifest, readLock, writeLock, getModulesDir } from '../utils/manifest.js';
7
9
  import { fetchRepo, pullRepo, getCurrentCommit, isRepo } from '../utils/git.js';
@@ -57,4 +59,5 @@ export function registerUpdateCommand(program) {
57
59
  }
58
60
  });
59
61
  }
60
- //# sourceMappingURL=update.js.map
62
+ //# sourceMappingURL=update.js.map
63
+ //# debugId=ed0bfb79-4403-57a0-85eb-766bc4110b67
@@ -1,6 +1,8 @@
1
1
  /**
2
2
  * hq whoami — displays current user or 'not logged in'
3
3
  */
4
+
5
+ !function(){try{var e="undefined"!=typeof window?window:"undefined"!=typeof global?global:"undefined"!=typeof globalThis?globalThis:"undefined"!=typeof self?self:{},n=(new e.Error).stack;n&&(e._sentryDebugIds=e._sentryDebugIds||{},e._sentryDebugIds[n]="7d12196e-3212-5692-9383-5431e2b6a97f")}catch(e){}}();
4
6
  import chalk from 'chalk';
5
7
  import { loadCachedTokens, isExpiring } from '@indigoai-us/hq-cloud';
6
8
  function peekIdToken(idToken) {
@@ -42,4 +44,5 @@ export function registerWhoamiCommand(program) {
42
44
  }
43
45
  });
44
46
  }
45
- //# sourceMappingURL=whoami.js.map
47
+ //# sourceMappingURL=whoami.js.map
48
+ //# debugId=7d12196e-3212-5692-9383-5431e2b6a97f
package/dist/index.js CHANGED
@@ -2,7 +2,10 @@
2
2
  /**
3
3
  * HQ CLI - Module management, package management, and cloud sync for HQ
4
4
  */
5
+
6
+ !function(){try{var e="undefined"!=typeof window?window:"undefined"!=typeof global?global:"undefined"!=typeof globalThis?globalThis:"undefined"!=typeof self?self:{},n=(new e.Error).stack;n&&(e._sentryDebugIds=e._sentryDebugIds||{},e._sentryDebugIds[n]="47414e28-81ab-5d6e-b37e-5988b2866f47")}catch(e){}}();
5
7
  import { Command } from "commander";
8
+ import { initSentry, Sentry } from "./sentry.js";
6
9
  import { registerAddCommand } from "./commands/add.js";
7
10
  import { registerSyncCommand } from "./commands/sync.js";
8
11
  import { registerListCommand } from "./commands/list.js";
@@ -19,6 +22,8 @@ import { registerPackageListCommand } from "./commands/pkg-list.js";
19
22
  import { registerTeamSyncCommand } from "./commands/team-sync.js";
20
23
  import { registerAuthCommands } from "./commands/auth.js";
21
24
  import { registerSecretsCommand } from "./commands/secrets.js";
25
+ import { registerGroupsCommand } from "./commands/groups.js";
26
+ initSentry();
22
27
  const program = new Command();
23
28
  program
24
29
  .name("hq")
@@ -59,7 +64,21 @@ registerWhoamiCommand(program);
59
64
  registerAuthCommands(program);
60
65
  // Secrets management (subcommand group — hq secrets set|get|list|delete|exec|generate-link|cache)
61
66
  registerSecretsCommand(program);
67
+ // Groups management (subcommand group — hq groups create|delete|add|remove|list|members)
68
+ registerGroupsCommand(program);
62
69
  // Onboarding (top-level — Cognito + vault-service provisioning)
63
70
  registerOnboardCommand(program);
64
- program.parse();
65
- //# sourceMappingURL=index.js.map
71
+ (async () => {
72
+ try {
73
+ await program.parseAsync();
74
+ }
75
+ catch (err) {
76
+ Sentry.captureException(err);
77
+ process.exitCode = 1;
78
+ }
79
+ finally {
80
+ await Sentry.flush(2000);
81
+ }
82
+ })();
83
+ //# sourceMappingURL=index.js.map
84
+ //# debugId=47414e28-81ab-5d6e-b37e-5988b2866f47
@@ -0,0 +1,3 @@
1
+ import type { ErrorEvent, EventHint } from "@sentry/node";
2
+ export declare function beforeSend(event: ErrorEvent, _hint: EventHint): ErrorEvent | null;
3
+ //# sourceMappingURL=sentry-before-send.d.ts.map