@indigoai-us/hq-cli 5.60.0 → 5.62.0
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/commands/agents.d.ts +109 -0
- package/dist/commands/agents.js +385 -0
- package/dist/commands/db-migrate.d.ts +6 -0
- package/dist/commands/db-migrate.js +42 -0
- package/dist/commands/db-provision.d.ts +15 -0
- package/dist/commands/db-provision.js +78 -0
- package/dist/commands/db-sql.d.ts +9 -0
- package/dist/commands/db-sql.js +81 -0
- package/dist/commands/db-status.d.ts +7 -0
- package/dist/commands/db-status.js +70 -0
- package/dist/commands/db.d.ts +9 -0
- package/dist/commands/db.js +23 -0
- package/dist/commands/integrations.d.ts +78 -0
- package/dist/commands/integrations.js +309 -0
- package/dist/commands/members.js +4 -4
- package/dist/commands/outposts.d.ts +60 -0
- package/dist/commands/outposts.js +255 -0
- package/dist/commands/pack-install.d.ts +7 -1
- package/dist/commands/pack-install.js +86 -15
- package/dist/commands/packs.d.ts +2 -1
- package/dist/commands/packs.js +13 -8
- package/dist/commands/secrets.d.ts +13 -0
- package/dist/commands/secrets.js +149 -10
- package/dist/commands/skill.d.ts +153 -0
- package/dist/commands/skill.js +593 -0
- package/dist/commands/workers.d.ts +48 -0
- package/dist/commands/workers.js +229 -0
- package/dist/index.d.ts +5 -3
- package/dist/index.js +14 -240
- package/dist/lib/db/control-plane.d.ts +45 -0
- package/dist/lib/db/control-plane.js +81 -0
- package/dist/lib/db/local.d.ts +49 -0
- package/dist/lib/db/local.js +106 -0
- package/dist/lib/db/migrate.d.ts +41 -0
- package/dist/lib/db/migrate.js +104 -0
- package/dist/lib/db/paths.d.ts +56 -0
- package/dist/lib/db/paths.js +103 -0
- package/dist/lib/db/remote-engine.d.ts +58 -0
- package/dist/lib/db/remote-engine.js +90 -0
- package/dist/lib/db/remote-sql.d.ts +22 -0
- package/dist/lib/db/remote-sql.js +39 -0
- package/dist/lib/db/sql.d.ts +49 -0
- package/dist/lib/db/sql.js +132 -0
- package/dist/main.d.ts +7 -0
- package/dist/main.js +272 -0
- package/dist/utils/cognito-session.js +3 -3
- package/dist/utils/sandbox-runner-client.d.ts +13 -0
- package/dist/utils/sandbox-runner-client.js +83 -6
- package/dist/utils/version-check.d.ts +6 -0
- package/dist/utils/version-check.js +78 -2
- package/package.json +9 -1
- package/pnpm-workspace.yaml +2 -0
- package/src/commands/agents.test.ts +297 -0
- package/src/commands/agents.ts +561 -0
- package/src/commands/db-migrate.ts +55 -0
- package/src/commands/db-provision.ts +102 -0
- package/src/commands/db-sql.ts +124 -0
- package/src/commands/db-status.ts +100 -0
- package/src/commands/db.ts +26 -0
- package/src/commands/integrations.test.ts +284 -0
- package/src/commands/integrations.ts +438 -0
- package/src/commands/members.ts +2 -2
- package/src/commands/outposts.test.ts +177 -0
- package/src/commands/outposts.ts +338 -0
- package/src/commands/pack-install.ts +115 -18
- package/src/commands/pack-update-cache.test.ts +149 -0
- package/src/commands/packs.ts +28 -7
- package/src/commands/secrets.parse-destination.test.ts +38 -0
- package/src/commands/secrets.test.ts +342 -0
- package/src/commands/secrets.ts +227 -13
- package/src/commands/skill.test.ts +770 -0
- package/src/commands/skill.ts +796 -0
- package/src/commands/workers.test.ts +158 -0
- package/src/commands/workers.ts +298 -0
- package/src/index.test.ts +32 -0
- package/src/index.ts +11 -274
- package/src/lib/db/control-plane.test.ts +59 -0
- package/src/lib/db/control-plane.ts +113 -0
- package/src/lib/db/local.test.ts +81 -0
- package/src/lib/db/local.ts +148 -0
- package/src/lib/db/migrate.test.ts +133 -0
- package/src/lib/db/migrate.ts +137 -0
- package/src/lib/db/paths.test.ts +112 -0
- package/src/lib/db/paths.ts +128 -0
- package/src/lib/db/remote-engine.test.ts +44 -0
- package/src/lib/db/remote-engine.ts +148 -0
- package/src/lib/db/remote-sql.test.ts +32 -0
- package/src/lib/db/remote-sql.ts +62 -0
- package/src/lib/db/sql.test.ts +106 -0
- package/src/lib/db/sql.ts +192 -0
- package/src/main.ts +314 -0
- package/src/utils/cognito-session.ts +1 -1
- package/src/utils/sandbox-runner-client.test.ts +128 -0
- package/src/utils/sandbox-runner-client.ts +100 -4
- package/src/utils/version-check.test.ts +30 -0
- package/src/utils/version-check.ts +72 -0
- package/test/commands/db-tenant-isolation.test.ts +94 -0
- package/test/commands/db.test.ts +85 -0
package/src/commands/secrets.ts
CHANGED
|
@@ -279,6 +279,121 @@ function describeSecretAclPrincipal(principal: SecretAclPrincipal): string {
|
|
|
279
279
|
: principal.granteeId;
|
|
280
280
|
}
|
|
281
281
|
|
|
282
|
+
// secrets-proxy-per-secret-destination US-006: an InjectionRecipe as sent on
|
|
283
|
+
// the create/update POST body. Mirrors hq-pro's `InjectionRecipe`
|
|
284
|
+
// (src/vault-service/handlers/secrets.ts) — kept as a local shape (not
|
|
285
|
+
// imported) since the CLI does not depend on the hq-pro package.
|
|
286
|
+
export interface SecretInjectionRecipe {
|
|
287
|
+
header: string;
|
|
288
|
+
scheme: "raw" | "bearer";
|
|
289
|
+
extraHeaders?: Record<string, string>;
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
// Mirrors hq-pro's server-authoritative KNOWN_DESTINATION_REGISTRY
|
|
293
|
+
// (src/vault-service/handlers/destination-registry.ts) — a KNOWN host's
|
|
294
|
+
// --auth-style is optional because the server resolves the recipe itself.
|
|
295
|
+
// This client-side copy exists purely so an UNKNOWN host with no
|
|
296
|
+
// --auth-style can be rejected immediately with a clear, actionable message
|
|
297
|
+
// instead of a round trip; the server remains the authoritative validator
|
|
298
|
+
// (this list drifting stale merely means one extra CLI round trip, not a
|
|
299
|
+
// security gap — the server still 400s an unrecognized host with no recipe).
|
|
300
|
+
const KNOWN_DESTINATION_HOSTS = new Set([
|
|
301
|
+
"api.anthropic.com",
|
|
302
|
+
"api.openai.com",
|
|
303
|
+
"api.stripe.com",
|
|
304
|
+
]);
|
|
305
|
+
|
|
306
|
+
// Parses `--auth-style` into the InjectionRecipe shape the server expects.
|
|
307
|
+
// Returns `null` (with a printed error) for an unrecognized value.
|
|
308
|
+
function parseAuthStyle(authStyle: string): SecretInjectionRecipe | null {
|
|
309
|
+
if (authStyle === "bearer") {
|
|
310
|
+
return { header: "authorization", scheme: "bearer" };
|
|
311
|
+
}
|
|
312
|
+
if (authStyle === "x-api-key") {
|
|
313
|
+
return { header: "x-api-key", scheme: "raw" };
|
|
314
|
+
}
|
|
315
|
+
const headerMatch = authStyle.match(/^header:(.+)$/);
|
|
316
|
+
if (headerMatch) {
|
|
317
|
+
const headerName = headerMatch[1].trim();
|
|
318
|
+
if (!headerName) {
|
|
319
|
+
console.error(
|
|
320
|
+
chalk.red(
|
|
321
|
+
`Invalid --auth-style 'header:': must name a header, e.g. header:X-Custom-Key`,
|
|
322
|
+
),
|
|
323
|
+
);
|
|
324
|
+
return null;
|
|
325
|
+
}
|
|
326
|
+
return { header: headerName, scheme: "raw" };
|
|
327
|
+
}
|
|
328
|
+
console.error(
|
|
329
|
+
chalk.red(
|
|
330
|
+
`Invalid --auth-style '${authStyle}': must be one of bearer, x-api-key, or header:NAME`,
|
|
331
|
+
),
|
|
332
|
+
);
|
|
333
|
+
return null;
|
|
334
|
+
}
|
|
335
|
+
|
|
336
|
+
// Validates `--destination` is a bare HTTPS scheme+host URL (no path, query,
|
|
337
|
+
// port). Mirrors hq-pro's `validateDestinations` server-side check
|
|
338
|
+
// (src/vault-service/handlers/secrets.ts) so a malformed URL is caught
|
|
339
|
+
// locally with an actionable message rather than a round trip — the server
|
|
340
|
+
// re-validates and remains authoritative.
|
|
341
|
+
export function parseDestinationUrl(
|
|
342
|
+
raw: string,
|
|
343
|
+
): { ok: true; url: string; hostname: string } | { ok: false } {
|
|
344
|
+
let parsed: URL;
|
|
345
|
+
try {
|
|
346
|
+
parsed = new URL(raw);
|
|
347
|
+
} catch {
|
|
348
|
+
console.error(
|
|
349
|
+
chalk.red(`Invalid --destination '${raw}': must be a valid URL`),
|
|
350
|
+
);
|
|
351
|
+
return { ok: false };
|
|
352
|
+
}
|
|
353
|
+
if (parsed.protocol !== "https:") {
|
|
354
|
+
console.error(
|
|
355
|
+
chalk.red(`Invalid --destination '${raw}': must use https://`),
|
|
356
|
+
);
|
|
357
|
+
return { ok: false };
|
|
358
|
+
}
|
|
359
|
+
if (!parsed.hostname) {
|
|
360
|
+
console.error(
|
|
361
|
+
chalk.red(`Invalid --destination '${raw}': missing hostname`),
|
|
362
|
+
);
|
|
363
|
+
return { ok: false };
|
|
364
|
+
}
|
|
365
|
+
// Reject an embedded `user:pass@host` credential segment explicitly rather
|
|
366
|
+
// than letting `new URL()` silently drop it (mirrors hq-pro's authoritative
|
|
367
|
+
// `validateDestinations`; the server re-validates and remains authoritative).
|
|
368
|
+
if (parsed.username !== "" || parsed.password !== "") {
|
|
369
|
+
console.error(
|
|
370
|
+
chalk.red(
|
|
371
|
+
`Invalid --destination '${raw}': must not contain embedded userinfo (user:pass@) credentials`,
|
|
372
|
+
),
|
|
373
|
+
);
|
|
374
|
+
return { ok: false };
|
|
375
|
+
}
|
|
376
|
+
if (
|
|
377
|
+
(parsed.pathname !== "" && parsed.pathname !== "/") ||
|
|
378
|
+
parsed.search !== "" ||
|
|
379
|
+
parsed.hash !== ""
|
|
380
|
+
) {
|
|
381
|
+
console.error(
|
|
382
|
+
chalk.red(
|
|
383
|
+
`Invalid --destination '${raw}': must be a bare scheme+host URL with no path, query, or fragment (e.g. https://api.openai.com)`,
|
|
384
|
+
),
|
|
385
|
+
);
|
|
386
|
+
return { ok: false };
|
|
387
|
+
}
|
|
388
|
+
if (parsed.port !== "") {
|
|
389
|
+
console.error(
|
|
390
|
+
chalk.red(`Invalid --destination '${raw}': must not specify a port`),
|
|
391
|
+
);
|
|
392
|
+
return { ok: false };
|
|
393
|
+
}
|
|
394
|
+
return { ok: true, url: `https://${parsed.hostname}`, hostname: parsed.hostname };
|
|
395
|
+
}
|
|
396
|
+
|
|
282
397
|
function normalizeSecretTier(tier?: string): SecretTier {
|
|
283
398
|
return tier === "sensitive" || tier === "nuclear" ? tier : "standard";
|
|
284
399
|
}
|
|
@@ -323,8 +438,17 @@ async function buildSecretUsage(
|
|
|
323
438
|
};
|
|
324
439
|
}
|
|
325
440
|
|
|
326
|
-
|
|
327
|
-
|
|
441
|
+
// Commander collector for `--only`: accumulates across REPEATED flags instead
|
|
442
|
+
// of the last one silently winning, and each value may still be
|
|
443
|
+
// comma-separated. So `--only A,B`, `--only A --only B`, and
|
|
444
|
+
// `--only A,B --only C` all resolve to the full list.
|
|
445
|
+
export function collectSecretNames(value: string, previous?: string[]): string[] {
|
|
446
|
+
const parsed = value.split(",").map((k) => k.trim()).filter(Boolean);
|
|
447
|
+
return (previous ?? []).concat(parsed);
|
|
448
|
+
}
|
|
449
|
+
|
|
450
|
+
function parseSecretNameList(input: string[] | undefined): string[] {
|
|
451
|
+
const keys = input ?? [];
|
|
328
452
|
if (keys.length === 0) {
|
|
329
453
|
console.error(chalk.red("Error: --only requires at least one secret name."));
|
|
330
454
|
process.exit(1);
|
|
@@ -572,13 +696,87 @@ export function registerSecretsCommand(program: Command): void {
|
|
|
572
696
|
.command("set <name>")
|
|
573
697
|
.description("Create or update a secret")
|
|
574
698
|
.option("--from-stdin", "Read secret value from piped stdin")
|
|
575
|
-
.
|
|
699
|
+
.option(
|
|
700
|
+
"--high-security",
|
|
701
|
+
"Mark the secret high-security: it can never be revealed or injected locally, only used through the HQ secret proxy (requires --destination)",
|
|
702
|
+
)
|
|
703
|
+
.option(
|
|
704
|
+
"--destination <https-url>",
|
|
705
|
+
"Approved scheme+host HTTPS URL the proxy may forward this secret to (e.g. https://api.openai.com); required with --high-security",
|
|
706
|
+
)
|
|
707
|
+
.option(
|
|
708
|
+
"--auth-style <style>",
|
|
709
|
+
"How the proxy attaches the key upstream: bearer | x-api-key | header:NAME. Optional for known destinations (auto-resolved server-side); required for unknown ones",
|
|
710
|
+
)
|
|
711
|
+
.action(async (
|
|
712
|
+
name: string,
|
|
713
|
+
opts: {
|
|
714
|
+
fromStdin?: boolean;
|
|
715
|
+
highSecurity?: boolean;
|
|
716
|
+
destination?: string;
|
|
717
|
+
authStyle?: string;
|
|
718
|
+
},
|
|
719
|
+
) => {
|
|
576
720
|
try {
|
|
577
721
|
if (!SECRET_NAME_PATTERN.test(name)) {
|
|
578
722
|
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)`));
|
|
579
723
|
process.exit(1);
|
|
580
724
|
}
|
|
581
725
|
|
|
726
|
+
// secrets-proxy-per-secret-destination US-006: --high-security marks
|
|
727
|
+
// the secret so it can only ever be used through the server-side
|
|
728
|
+
// proxy (never revealed/injected locally — that refusal is the
|
|
729
|
+
// pre-existing consumption-side behavior in `get`/`exec`/`env` above,
|
|
730
|
+
// unchanged by this story). It REQUIRES a --destination: the proxy
|
|
731
|
+
// (hq-pro US-002) fails closed with no destination configured, so
|
|
732
|
+
// catching the missing pin here is a clear, immediate CLI error
|
|
733
|
+
// rather than a deferred proxy-time failure.
|
|
734
|
+
let destinations: string[] | undefined;
|
|
735
|
+
let injection: SecretInjectionRecipe | undefined;
|
|
736
|
+
if (opts.highSecurity) {
|
|
737
|
+
if (!opts.destination) {
|
|
738
|
+
console.error(
|
|
739
|
+
chalk.red(
|
|
740
|
+
"Error: --high-security requires --destination <https-url> (e.g. --destination https://api.openai.com).",
|
|
741
|
+
),
|
|
742
|
+
);
|
|
743
|
+
process.exit(1);
|
|
744
|
+
}
|
|
745
|
+
|
|
746
|
+
const destResult = parseDestinationUrl(opts.destination);
|
|
747
|
+
if (!destResult.ok) {
|
|
748
|
+
process.exit(1);
|
|
749
|
+
}
|
|
750
|
+
destinations = [destResult.url];
|
|
751
|
+
|
|
752
|
+
if (opts.authStyle) {
|
|
753
|
+
const recipe = parseAuthStyle(opts.authStyle);
|
|
754
|
+
if (!recipe) {
|
|
755
|
+
process.exit(1);
|
|
756
|
+
}
|
|
757
|
+
injection = recipe;
|
|
758
|
+
} else if (!KNOWN_DESTINATION_HOSTS.has(destResult.hostname)) {
|
|
759
|
+
// Unknown host + no explicit recipe: the server would reject this
|
|
760
|
+
// 400 anyway (US-004 registry lookup only, never guesses) — fail
|
|
761
|
+
// fast locally with an actionable message instead of a round trip.
|
|
762
|
+
console.error(
|
|
763
|
+
chalk.red(
|
|
764
|
+
`Error: unknown destination host '${destResult.hostname}' — provide --auth-style <bearer|x-api-key|header:NAME> (known hosts auto-resolve: ${[...KNOWN_DESTINATION_HOSTS].join(", ")}).`,
|
|
765
|
+
),
|
|
766
|
+
);
|
|
767
|
+
process.exit(1);
|
|
768
|
+
}
|
|
769
|
+
// Known host + no --auth-style: leave `injection` undefined so the
|
|
770
|
+
// server (US-004) auto-resolves the recipe from its registry.
|
|
771
|
+
} else if (opts.destination || opts.authStyle) {
|
|
772
|
+
console.error(
|
|
773
|
+
chalk.red(
|
|
774
|
+
"Error: --destination/--auth-style require --high-security.",
|
|
775
|
+
),
|
|
776
|
+
);
|
|
777
|
+
process.exit(1);
|
|
778
|
+
}
|
|
779
|
+
|
|
582
780
|
let value: string;
|
|
583
781
|
if (opts.fromStdin) {
|
|
584
782
|
if (process.stdin.isTTY) {
|
|
@@ -617,19 +815,35 @@ export function registerSecretsCommand(program: Command): void {
|
|
|
617
815
|
token,
|
|
618
816
|
path: `/secrets/${encodeURIComponent(companyUid)}`,
|
|
619
817
|
method: "POST",
|
|
620
|
-
body: {
|
|
818
|
+
body: {
|
|
819
|
+
name,
|
|
820
|
+
value,
|
|
821
|
+
// Only present when --high-security was passed — an ordinary
|
|
822
|
+
// `set` with no flags sends exactly `{ name, value }`, byte-for-
|
|
823
|
+
// byte unchanged from before this story.
|
|
824
|
+
...(opts.highSecurity ? { highSecurity: true } : {}),
|
|
825
|
+
...(destinations ? { destinations } : {}),
|
|
826
|
+
...(injection ? { injection } : {}),
|
|
827
|
+
},
|
|
621
828
|
});
|
|
622
829
|
|
|
623
830
|
if (!res.ok) {
|
|
624
|
-
const body = await res.json().catch(() => ({}))
|
|
831
|
+
const body = (await res.json().catch(() => ({}))) as Record<string, unknown>;
|
|
625
832
|
console.error(
|
|
626
|
-
chalk.red(`Failed to set secret: ${(body
|
|
833
|
+
chalk.red(`Failed to set secret: ${extractApiMessage(body, res.statusText)}`),
|
|
627
834
|
);
|
|
628
835
|
process.exit(1);
|
|
629
836
|
}
|
|
630
837
|
|
|
631
838
|
removeCacheEntry(companyUid, name);
|
|
632
839
|
console.log(chalk.green(formatSecretSaved(name, scopeLabel)));
|
|
840
|
+
if (opts.highSecurity) {
|
|
841
|
+
console.log(
|
|
842
|
+
chalk.dim(
|
|
843
|
+
` High-security: destination pinned to ${destinations?.[0]}. This value can never be revealed or injected locally — only used through the HQ secret proxy.`,
|
|
844
|
+
),
|
|
845
|
+
);
|
|
846
|
+
}
|
|
633
847
|
} catch (err) {
|
|
634
848
|
console.error(
|
|
635
849
|
chalk.red("Error:"),
|
|
@@ -1253,11 +1467,11 @@ export function registerSecretsCommand(program: Command): void {
|
|
|
1253
1467
|
"--personal",
|
|
1254
1468
|
"Operate on the caller's personal vault (no sharing)",
|
|
1255
1469
|
)
|
|
1256
|
-
.option("--only <keys>", "
|
|
1470
|
+
.option("--only <keys>", "Secret names to inject (comma-separated; may be repeated) (required)", collectSecretNames)
|
|
1257
1471
|
.allowUnknownOption(true)
|
|
1258
|
-
.action(async (opts: { company?: string; personal?: boolean; only?: string }, cmd: Command) => {
|
|
1472
|
+
.action(async (opts: { company?: string; personal?: boolean; only?: string[] }, cmd: Command) => {
|
|
1259
1473
|
try {
|
|
1260
|
-
if (!opts.only || opts.only.
|
|
1474
|
+
if (!opts.only || opts.only.length === 0) {
|
|
1261
1475
|
console.error(chalk.red("Error: --only is required and must name at least one secret."));
|
|
1262
1476
|
process.exit(1);
|
|
1263
1477
|
}
|
|
@@ -1307,10 +1521,10 @@ export function registerSecretsCommand(program: Command): void {
|
|
|
1307
1521
|
secrets
|
|
1308
1522
|
.command("exec")
|
|
1309
1523
|
.description("Run a command with secrets injected as env vars")
|
|
1310
|
-
.requiredOption("--only <keys>", "
|
|
1524
|
+
.requiredOption("--only <keys>", "Secret names to inject (comma-separated; may be repeated) (required)", collectSecretNames)
|
|
1311
1525
|
.option("--script <path>", "Attach local script identity for script-locked secrets")
|
|
1312
1526
|
.allowUnknownOption(true)
|
|
1313
|
-
.action(async (_opts: { only: string; script?: string }, cmd: Command) => {
|
|
1527
|
+
.action(async (_opts: { only: string[]; script?: string }, cmd: Command) => {
|
|
1314
1528
|
try {
|
|
1315
1529
|
const rawArgs = cmd.args;
|
|
1316
1530
|
const dashIndex = process.argv.indexOf("--");
|
|
@@ -1381,9 +1595,9 @@ export function registerSecretsCommand(program: Command): void {
|
|
|
1381
1595
|
secrets
|
|
1382
1596
|
.command("env")
|
|
1383
1597
|
.description("Print 'export KEY=VALUE' lines suitable for: source <(hq secrets env --only K1,K2)")
|
|
1384
|
-
.requiredOption("--only <keys>", "
|
|
1598
|
+
.requiredOption("--only <keys>", "Secret names to print (comma-separated; may be repeated) (required)", collectSecretNames)
|
|
1385
1599
|
.option("--script <path>", "Attach local script identity for script-locked secrets")
|
|
1386
|
-
.action(async (opts: { only: string; script?: string }) => {
|
|
1600
|
+
.action(async (opts: { only: string[]; script?: string }) => {
|
|
1387
1601
|
try {
|
|
1388
1602
|
const redact = process.stdout.isTTY;
|
|
1389
1603
|
if (redact) {
|