@indigoai-us/hq-cli 5.5.2 → 5.5.4
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/dist/bin/hq-auth-refresh.js +26 -13
- package/dist/commands/_patterns.d.ts +3 -0
- package/dist/commands/_patterns.js +6 -0
- package/dist/commands/add.js +4 -1
- package/dist/commands/auth.js +4 -1
- package/dist/commands/cloud.js +4 -1
- package/dist/commands/groups.d.ts +3 -0
- package/dist/commands/groups.js +344 -0
- package/dist/commands/list.js +4 -1
- package/dist/commands/login.js +4 -1
- package/dist/commands/logout.js +4 -1
- package/dist/commands/onboard.js +4 -1
- package/dist/commands/pack-install.js +4 -1
- package/dist/commands/pkg-install.js +4 -1
- package/dist/commands/pkg-list.js +4 -1
- package/dist/commands/pkg-remove.js +4 -1
- package/dist/commands/pkg-update.js +4 -1
- package/dist/commands/secrets.d.ts +9 -0
- package/dist/commands/secrets.js +263 -25
- package/dist/commands/sync.js +4 -1
- package/dist/commands/team-sync.js +4 -1
- package/dist/commands/update.js +4 -1
- package/dist/commands/whoami.js +4 -1
- package/dist/index.js +21 -2
- package/dist/sentry-before-send.d.ts +3 -0
- package/dist/sentry-before-send.js +219 -0
- package/dist/sentry-dsn.generated.d.ts +2 -0
- package/dist/sentry-dsn.generated.js +5 -0
- package/dist/sentry.d.ts +4 -0
- package/dist/sentry.js +22 -0
- package/dist/strategies/link.js +4 -1
- package/dist/strategies/merge.js +4 -1
- package/dist/types.js +4 -1
- package/dist/utils/cognito-session.d.ts +1 -1
- package/dist/utils/cognito-session.js +8 -5
- package/dist/utils/git.js +4 -1
- package/dist/utils/hq-root.js +4 -1
- package/dist/utils/integrity.js +4 -1
- package/dist/utils/manifest.js +4 -1
- package/dist/utils/registry-client.js +4 -1
- package/dist/utils/registry.js +4 -1
- package/dist/utils/secrets-cache.js +6 -2
- package/package.json +4 -3
- package/scripts/generate-dsn.mjs +26 -0
- package/src/bin/hq-auth-refresh.ts +23 -16
- package/src/commands/_patterns.ts +2 -0
- package/src/commands/groups.ts +357 -0
- package/src/commands/secrets.ts +306 -26
- package/src/index.ts +17 -1
- package/src/sentry-before-send.test.ts +359 -0
- package/src/sentry-before-send.ts +221 -0
- package/src/sentry.test.ts +24 -0
- package/src/sentry.ts +19 -0
- package/src/utils/cognito-session.ts +4 -4
- package/src/utils/secrets-cache.ts +2 -1
- package/dist/bin/hq-auth-refresh.d.ts.map +0 -1
- package/dist/bin/hq-auth-refresh.js.map +0 -1
- package/dist/commands/add.d.ts.map +0 -1
- package/dist/commands/add.js.map +0 -1
- package/dist/commands/auth.d.ts.map +0 -1
- package/dist/commands/auth.js.map +0 -1
- package/dist/commands/cloud.d.ts.map +0 -1
- package/dist/commands/cloud.js.map +0 -1
- package/dist/commands/list.d.ts.map +0 -1
- package/dist/commands/list.js.map +0 -1
- package/dist/commands/login.d.ts.map +0 -1
- package/dist/commands/login.js.map +0 -1
- package/dist/commands/logout.d.ts.map +0 -1
- package/dist/commands/logout.js.map +0 -1
- package/dist/commands/onboard.d.ts.map +0 -1
- package/dist/commands/onboard.js.map +0 -1
- package/dist/commands/pack-install.d.ts.map +0 -1
- package/dist/commands/pack-install.js.map +0 -1
- package/dist/commands/pkg-install.d.ts.map +0 -1
- package/dist/commands/pkg-install.js.map +0 -1
- package/dist/commands/pkg-list.d.ts.map +0 -1
- package/dist/commands/pkg-list.js.map +0 -1
- package/dist/commands/pkg-remove.d.ts.map +0 -1
- package/dist/commands/pkg-remove.js.map +0 -1
- package/dist/commands/pkg-update.d.ts.map +0 -1
- package/dist/commands/pkg-update.js.map +0 -1
- package/dist/commands/secrets.d.ts.map +0 -1
- package/dist/commands/secrets.js.map +0 -1
- package/dist/commands/sync.d.ts.map +0 -1
- package/dist/commands/sync.js.map +0 -1
- package/dist/commands/team-sync.d.ts.map +0 -1
- package/dist/commands/team-sync.js.map +0 -1
- package/dist/commands/update.d.ts.map +0 -1
- package/dist/commands/update.js.map +0 -1
- package/dist/commands/whoami.d.ts.map +0 -1
- package/dist/commands/whoami.js.map +0 -1
- package/dist/index.d.ts.map +0 -1
- package/dist/index.js.map +0 -1
- package/dist/strategies/link.d.ts.map +0 -1
- package/dist/strategies/link.js.map +0 -1
- package/dist/strategies/merge.d.ts.map +0 -1
- package/dist/strategies/merge.js.map +0 -1
- package/dist/types.d.ts.map +0 -1
- package/dist/types.js.map +0 -1
- package/dist/utils/auth.d.ts +0 -27
- package/dist/utils/auth.d.ts.map +0 -1
- package/dist/utils/auth.js +0 -155
- package/dist/utils/auth.js.map +0 -1
- package/dist/utils/cognito-session.d.ts.map +0 -1
- package/dist/utils/cognito-session.js.map +0 -1
- package/dist/utils/git.d.ts.map +0 -1
- package/dist/utils/git.js.map +0 -1
- package/dist/utils/hq-root.d.ts.map +0 -1
- package/dist/utils/hq-root.js.map +0 -1
- package/dist/utils/integrity.d.ts.map +0 -1
- package/dist/utils/integrity.js.map +0 -1
- package/dist/utils/manifest.d.ts.map +0 -1
- package/dist/utils/manifest.js.map +0 -1
- package/dist/utils/registry-client.d.ts.map +0 -1
- package/dist/utils/registry-client.js.map +0 -1
- package/dist/utils/registry.d.ts.map +0 -1
- package/dist/utils/registry.js.map +0 -1
- package/dist/utils/secrets-cache.d.ts.map +0 -1
- package/dist/utils/secrets-cache.js.map +0 -1
- package/dist/utils/token-store.d.ts +0 -28
- package/dist/utils/token-store.d.ts.map +0 -1
- package/dist/utils/token-store.js +0 -68
- package/dist/utils/token-store.js.map +0 -1
package/dist/commands/secrets.js
CHANGED
|
@@ -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
|
-
|
|
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 (
|
|
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:
|
|
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
|
-
.
|
|
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
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
const
|
|
283
|
-
console.log(
|
|
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 (
|
|
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:
|
|
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 (
|
|
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:
|
|
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 (
|
|
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:
|
|
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 (
|
|
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:
|
|
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
|
package/dist/commands/sync.js
CHANGED
|
@@ -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
|
package/dist/commands/update.js
CHANGED
|
@@ -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
|
package/dist/commands/whoami.js
CHANGED
|
@@ -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
|
-
|
|
65
|
-
|
|
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
|