@indigoai-us/hq-cli 5.50.1 → 5.51.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/bin/hq-auth-refresh.d.ts +1 -1
- package/dist/bin/hq-auth-refresh.js +5 -2
- package/dist/commands/members.d.ts +17 -0
- package/dist/commands/members.js +65 -28
- package/dist/commands/onboard-warning.d.ts +7 -0
- package/dist/commands/onboard-warning.js +14 -0
- package/dist/commands/onboard.js +5 -5
- package/dist/commands/pack-install.d.ts +12 -0
- package/dist/commands/pack-install.js +74 -3
- package/dist/commands/packs.js +17 -3
- package/dist/commands/people.d.ts +26 -1
- package/dist/commands/people.js +70 -7
- package/dist/commands/secrets-scope.d.ts +20 -0
- package/dist/commands/secrets-scope.js +19 -0
- package/dist/commands/secrets.js +21 -6
- package/dist/index.d.ts +1 -1
- package/dist/index.js +44 -14
- package/dist/node-preflight.d.ts +39 -0
- package/dist/node-preflight.js +55 -0
- package/dist/sentry.d.ts +12 -0
- package/dist/sentry.js +19 -3
- package/dist/types.d.ts +18 -0
- package/dist/utils/epipe.d.ts +8 -0
- package/dist/utils/epipe.js +30 -0
- package/dist/utils/intercepted-process-exit.d.ts +7 -0
- package/dist/utils/intercepted-process-exit.js +38 -0
- package/dist/utils/pack-contributions.d.ts +7 -0
- package/dist/utils/pack-contributions.js +12 -2
- package/dist/utils/version-gate.d.ts +40 -1
- package/dist/utils/version-gate.js +91 -20
- package/e2e/cli.test.ts +35 -0
- package/package.json +1 -1
- package/src/bin/hq-auth-refresh.ts +3 -0
- package/src/commands/members.test.ts +176 -0
- package/src/commands/members.ts +113 -28
- package/src/commands/onboard-warning.test.ts +26 -0
- package/src/commands/onboard-warning.ts +12 -0
- package/src/commands/onboard.ts +4 -7
- package/src/commands/pack-install.test.ts +144 -0
- package/src/commands/pack-install.ts +86 -1
- package/src/commands/packs.ts +19 -0
- package/src/commands/people.test.ts +212 -5
- package/src/commands/people.ts +141 -5
- package/src/commands/secrets-scope.test.ts +56 -0
- package/src/commands/secrets-scope.ts +32 -0
- package/src/commands/secrets.ts +24 -10
- package/src/index.ts +40 -12
- package/src/node-preflight.test.ts +60 -0
- package/src/node-preflight.ts +67 -0
- package/src/sentry-epipe.test.ts +37 -0
- package/src/sentry-release.test.ts +54 -0
- package/src/sentry.ts +21 -1
- package/src/types.ts +19 -1
- package/src/utils/epipe.test.ts +28 -0
- package/src/utils/epipe.ts +29 -0
- package/src/utils/intercepted-process-exit.test.ts +37 -0
- package/src/utils/intercepted-process-exit.ts +36 -0
- package/src/utils/pack-contributions.test.ts +53 -0
- package/src/utils/pack-contributions.ts +17 -0
- package/src/utils/version-gate.test.ts +122 -0
- package/src/utils/version-gate.ts +109 -13
|
@@ -10,8 +10,11 @@
|
|
|
10
10
|
* machine-token mint). Exit 1 if no valid session could be ensured
|
|
11
11
|
* non-interactively.
|
|
12
12
|
*/
|
|
13
|
+
// MUST be first: guard the Node version before any dependency that needs a
|
|
14
|
+
// Node 20+ API (e.g. util.styleText) or a newer native ABI is evaluated.
|
|
13
15
|
|
|
14
|
-
!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]="
|
|
16
|
+
!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]="3c7e1b4e-66dc-5fe8-b828-cd3636df1fbb")}catch(e){}}();
|
|
17
|
+
import "../node-preflight.js";
|
|
15
18
|
import { initSentry, Sentry } from "../sentry.js";
|
|
16
19
|
import { refreshCachedSession } from "../utils/cognito-session.js";
|
|
17
20
|
initSentry();
|
|
@@ -38,4 +41,4 @@ initSentry();
|
|
|
38
41
|
}
|
|
39
42
|
})();
|
|
40
43
|
//# sourceMappingURL=hq-auth-refresh.js.map
|
|
41
|
-
//# debugId=
|
|
44
|
+
//# debugId=3c7e1b4e-66dc-5fe8-b828-cd3636df1fbb
|
|
@@ -12,6 +12,22 @@ export interface PendingInvite {
|
|
|
12
12
|
invitedBy: string;
|
|
13
13
|
invitedAt: string;
|
|
14
14
|
}
|
|
15
|
+
/**
|
|
16
|
+
* An ACTIVE member of a company, as returned by
|
|
17
|
+
* `GET /membership/company/{companyUid}`. The server filters to
|
|
18
|
+
* `status: "active"` and enriches each row with resolved person metadata
|
|
19
|
+
* (`personEmail` / `personName` / `personSlug`) when available.
|
|
20
|
+
*/
|
|
21
|
+
export interface ActiveMember {
|
|
22
|
+
membershipKey: string;
|
|
23
|
+
personUid: string;
|
|
24
|
+
companyUid: string;
|
|
25
|
+
role: string;
|
|
26
|
+
status: string;
|
|
27
|
+
personEmail?: string;
|
|
28
|
+
personName?: string;
|
|
29
|
+
personSlug?: string;
|
|
30
|
+
}
|
|
15
31
|
export interface InviteOptions {
|
|
16
32
|
target: string;
|
|
17
33
|
role: string;
|
|
@@ -114,6 +130,7 @@ export declare class InviteHttpError extends Error {
|
|
|
114
130
|
}
|
|
115
131
|
export declare function formatInviteHttpError(status: number, fallback: string, code?: string): string;
|
|
116
132
|
export declare function listPendingInvites(token: string, companyUid: string): Promise<PendingInvite[]>;
|
|
133
|
+
export declare function listActiveMembers(token: string, companyUid: string): Promise<ActiveMember[]>;
|
|
117
134
|
/**
|
|
118
135
|
* Resolve a `revoke` CLI argument into the canonical `membershipKey` shape
|
|
119
136
|
* the server requires. Accepts three input forms:
|
package/dist/commands/members.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
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]="
|
|
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]="8244d720-094e-5d4e-b813-75086ed0849d")}catch(e){}}();
|
|
3
3
|
import chalk from "chalk";
|
|
4
4
|
import { ensureCognitoToken } from "../utils/cognito-session.js";
|
|
5
5
|
import { vaultApiFetch, getCompanyUid } from "../utils/vault-api.js";
|
|
@@ -196,6 +196,20 @@ export async function listPendingInvites(token, companyUid) {
|
|
|
196
196
|
const data = (await res.json());
|
|
197
197
|
return data?.pending ?? data?.invites ?? [];
|
|
198
198
|
}
|
|
199
|
+
export async function listActiveMembers(token, companyUid) {
|
|
200
|
+
const res = await vaultApiFetch({
|
|
201
|
+
token,
|
|
202
|
+
path: `/membership/company/${encodeURIComponent(companyUid)}`,
|
|
203
|
+
});
|
|
204
|
+
if (!res.ok) {
|
|
205
|
+
const err = (await res.json().catch(() => ({})));
|
|
206
|
+
throw new InviteHttpError(res.status, err.message ?? err.error ?? res.statusText, err.code);
|
|
207
|
+
}
|
|
208
|
+
// Server schema: `{ members: [...] }` — active members only, enriched with
|
|
209
|
+
// resolved person metadata (personEmail / personName / personSlug).
|
|
210
|
+
const data = (await res.json());
|
|
211
|
+
return data?.members ?? [];
|
|
212
|
+
}
|
|
199
213
|
/**
|
|
200
214
|
* Resolve a `revoke` CLI argument into the canonical `membershipKey` shape
|
|
201
215
|
* the server requires. Accepts three input forms:
|
|
@@ -422,43 +436,66 @@ export function registerMembersCommand(program) {
|
|
|
422
436
|
});
|
|
423
437
|
members
|
|
424
438
|
.command("list")
|
|
425
|
-
.description("List pending
|
|
426
|
-
.
|
|
439
|
+
.description("List the company's active members (use --pending for pending invites)")
|
|
440
|
+
.option("--pending", "List pending invites instead of active members")
|
|
441
|
+
.action(async (opts) => {
|
|
427
442
|
try {
|
|
428
443
|
const token = await ensureCognitoToken();
|
|
429
444
|
const companySlug = members.opts().company;
|
|
430
445
|
const companyUid = await getCompanyUid(token, companySlug);
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
446
|
+
if (opts.pending) {
|
|
447
|
+
// --pending: preserve the original pending-invites view verbatim.
|
|
448
|
+
const invites = await listPendingInvites(token, companyUid);
|
|
449
|
+
if (invites.length === 0) {
|
|
450
|
+
console.log(chalk.gray("No pending invites for this company."));
|
|
451
|
+
return;
|
|
452
|
+
}
|
|
453
|
+
const targetW = Math.max(6, ...invites.map((i) => (i.inviteeEmail ?? i.personUid ?? "").length));
|
|
454
|
+
const roleW = Math.max(4, ...invites.map((i) => i.role.length));
|
|
455
|
+
const byW = Math.max(10, ...invites.map((i) => i.invitedBy.length));
|
|
456
|
+
const keyW = Math.max(14, ...invites.map((i) => i.membershipKey.length));
|
|
457
|
+
console.log(chalk.bold([
|
|
458
|
+
"TARGET".padEnd(targetW),
|
|
459
|
+
"ROLE".padEnd(roleW),
|
|
460
|
+
"INVITED_BY".padEnd(byW),
|
|
461
|
+
"INVITED_AT",
|
|
462
|
+
"MEMBERSHIP_KEY".padEnd(keyW),
|
|
463
|
+
].join(" ")));
|
|
464
|
+
for (const inv of invites) {
|
|
465
|
+
const target = inv.inviteeEmail ?? inv.personUid ?? "";
|
|
466
|
+
console.log([
|
|
467
|
+
target.padEnd(targetW),
|
|
468
|
+
inv.role.padEnd(roleW),
|
|
469
|
+
inv.invitedBy.padEnd(byW),
|
|
470
|
+
shortDate(inv.invitedAt),
|
|
471
|
+
inv.membershipKey.padEnd(keyW),
|
|
472
|
+
].join(" "));
|
|
473
|
+
}
|
|
474
|
+
return;
|
|
475
|
+
}
|
|
476
|
+
// Default: list ACTIVE members so their emails drop straight into
|
|
477
|
+
// `hq secrets share <path> --with <email>`.
|
|
478
|
+
const activeMembers = await listActiveMembers(token, companyUid);
|
|
479
|
+
if (activeMembers.length === 0) {
|
|
480
|
+
console.log(chalk.gray("No active members found for this company."));
|
|
434
481
|
return;
|
|
435
482
|
}
|
|
436
|
-
const
|
|
437
|
-
const roleW = Math.max(4, ...
|
|
438
|
-
|
|
439
|
-
const
|
|
440
|
-
|
|
441
|
-
"
|
|
442
|
-
|
|
443
|
-
"INVITED_BY".padEnd(byW),
|
|
444
|
-
"INVITED_AT",
|
|
445
|
-
"MEMBERSHIP_KEY".padEnd(keyW),
|
|
446
|
-
].join(" ")));
|
|
447
|
-
for (const inv of invites) {
|
|
448
|
-
const target = inv.inviteeEmail ?? inv.personUid ?? "";
|
|
449
|
-
console.log([
|
|
450
|
-
target.padEnd(targetW),
|
|
451
|
-
inv.role.padEnd(roleW),
|
|
452
|
-
inv.invitedBy.padEnd(byW),
|
|
453
|
-
shortDate(inv.invitedAt),
|
|
454
|
-
inv.membershipKey.padEnd(keyW),
|
|
455
|
-
].join(" "));
|
|
483
|
+
const emailW = Math.max(5, ...activeMembers.map((m) => (m.personEmail ?? m.personUid).length));
|
|
484
|
+
const roleW = Math.max(4, ...activeMembers.map((m) => m.role.length));
|
|
485
|
+
console.log(chalk.bold(["EMAIL".padEnd(emailW), "ROLE".padEnd(roleW), "NAME"].join(" ")));
|
|
486
|
+
for (const m of activeMembers) {
|
|
487
|
+
const email = m.personEmail ?? m.personUid;
|
|
488
|
+
const name = m.personName ?? m.personSlug ?? "";
|
|
489
|
+
console.log([email.padEnd(emailW), m.role.padEnd(roleW), name].join(" "));
|
|
456
490
|
}
|
|
491
|
+
console.log(chalk.gray("Share a secret with a member: hq secrets share <path> --with <email>"));
|
|
457
492
|
}
|
|
458
493
|
catch (err) {
|
|
459
494
|
if (err instanceof InviteHttpError) {
|
|
460
495
|
const msg = err.status === 403
|
|
461
|
-
?
|
|
496
|
+
? opts.pending
|
|
497
|
+
? "Not authorized — only admins and owners can list invites"
|
|
498
|
+
: "Not authorized — only company members can list members"
|
|
462
499
|
: formatInviteHttpError(err.status, err.message);
|
|
463
500
|
console.error(chalk.red(msg));
|
|
464
501
|
process.exit(1);
|
|
@@ -497,4 +534,4 @@ export function registerMembersCommand(program) {
|
|
|
497
534
|
});
|
|
498
535
|
}
|
|
499
536
|
//# sourceMappingURL=members.js.map
|
|
500
|
-
//# debugId=
|
|
537
|
+
//# debugId=8244d720-094e-5d4e-b813-75086ed0849d
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Builds the static create-time warning surfaced by both `create-company` and
|
|
3
|
+
* `dry-run`, because users may accidentally create duplicate companies instead
|
|
4
|
+
* of joining the existing company they were invited to.
|
|
5
|
+
*/
|
|
6
|
+
export declare function buildCreateCompanyWarning(): string;
|
|
7
|
+
//# sourceMappingURL=onboard-warning.d.ts.map
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Builds the static create-time warning surfaced by both `create-company` and
|
|
3
|
+
* `dry-run`, because users may accidentally create duplicate companies instead
|
|
4
|
+
* of joining the existing company they were invited to.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
!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]="853f43ae-d902-54b9-9279-8a4b1d5f7693")}catch(e){}}();
|
|
8
|
+
export function buildCreateCompanyWarning() {
|
|
9
|
+
return (" This creates a NEW company that you own — it does NOT join an existing one.\n" +
|
|
10
|
+
" If your company already uses HQ, do NOT create it again: ask your admin or owner to send you an invite, then accept it and run `hq sync` to pull the existing company.\n" +
|
|
11
|
+
" Creating a same-named company makes a separate one you'd be alone in.\n");
|
|
12
|
+
}
|
|
13
|
+
//# sourceMappingURL=onboard-warning.js.map
|
|
14
|
+
//# debugId=853f43ae-d902-54b9-9279-8a4b1d5f7693
|
package/dist/commands/onboard.js
CHANGED
|
@@ -19,13 +19,14 @@
|
|
|
19
19
|
* browser-OAuth flow opens automatically.
|
|
20
20
|
*/
|
|
21
21
|
|
|
22
|
-
!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]="
|
|
22
|
+
!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]="235a4623-0834-5428-957c-0c8bbe96d089")}catch(e){}}();
|
|
23
23
|
import chalk from "chalk";
|
|
24
24
|
import { runOnboardCli, readCheckpoint } from "@indigoai-us/hq-onboarding";
|
|
25
25
|
import { DEFAULT_HQ_ROOT, DEFAULT_VAULT_API_URL, ensureCognitoToken, buildVaultConfig, } from "../utils/cognito-session.js";
|
|
26
26
|
import { createDefaultVaultClient } from "./cloud-provision.js";
|
|
27
27
|
import { detectOnboardingIdentityMismatch } from "./onboard-identity-guard.js";
|
|
28
28
|
import { planOnboardJoin } from "./onboard-join.js";
|
|
29
|
+
import { buildCreateCompanyWarning } from "./onboard-warning.js";
|
|
29
30
|
// ---------------------------------------------------------------------------
|
|
30
31
|
// Command registration
|
|
31
32
|
// ---------------------------------------------------------------------------
|
|
@@ -49,9 +50,7 @@ export function registerOnboardCommand(program) {
|
|
|
49
50
|
console.log(` Company: ${options.name} (${options.slug})`);
|
|
50
51
|
console.log(` Person: ${options.personName} <${options.email}>`);
|
|
51
52
|
console.log(` HQ root: ${options.hqRoot}\n`);
|
|
52
|
-
console.log(chalk.gray(
|
|
53
|
-
" company? Stop — accept your invite, then run `hq sync` to pull it.\n" +
|
|
54
|
-
" Creating a same-named company leaves you alone in a separate one.\n"));
|
|
53
|
+
console.log(chalk.gray(buildCreateCompanyWarning()));
|
|
55
54
|
const accessToken = await ensureCognitoToken();
|
|
56
55
|
const result = await runOnboardCli({
|
|
57
56
|
mode: "create-company",
|
|
@@ -179,6 +178,7 @@ export function registerOnboardCommand(program) {
|
|
|
179
178
|
.option("--hq-root <path>", `Local HQ tree root (default: ${DEFAULT_HQ_ROOT})`, DEFAULT_HQ_ROOT)
|
|
180
179
|
.action(async (options) => {
|
|
181
180
|
try {
|
|
181
|
+
console.log(chalk.gray(buildCreateCompanyWarning()));
|
|
182
182
|
// No auth needed for dry-run — runOnboardCli handles this branch
|
|
183
183
|
// without touching the vault-service.
|
|
184
184
|
const result = await runOnboardCli({
|
|
@@ -198,4 +198,4 @@ export function registerOnboardCommand(program) {
|
|
|
198
198
|
});
|
|
199
199
|
}
|
|
200
200
|
//# sourceMappingURL=onboard.js.map
|
|
201
|
-
//# debugId=
|
|
201
|
+
//# debugId=235a4623-0834-5428-957c-0c8bbe96d089
|
|
@@ -259,6 +259,18 @@ export declare function verifyArtifact(input: VerifyArtifactInput): void;
|
|
|
259
259
|
*/
|
|
260
260
|
export declare function validateMcpManifest(payloadDir: string, item: string): void;
|
|
261
261
|
export declare function validateManifest(payloadDir: string, hqVersion: string | null): PackManifest;
|
|
262
|
+
/**
|
|
263
|
+
* Enforce a pack's `requires.packs`: every named dependency MUST already be
|
|
264
|
+
* installed (and satisfy its optional semver RANGE) before we write anything.
|
|
265
|
+
*
|
|
266
|
+
* Installed packs are discovered by FILESYSTEM PRESENCE via `listInstalledPacks`
|
|
267
|
+
* — deliberately NOT `modules.yaml`, which `installPack` no longer writes under
|
|
268
|
+
* the v12+ layout (a modules.yaml-based check would silently ignore every modern
|
|
269
|
+
* pack). Throws on the first unmet dependency so the install aborts with NO
|
|
270
|
+
* partial state (it is called before installToPackages). No-op when
|
|
271
|
+
* `requires.packs` is absent/empty, keeping legacy packs unaffected.
|
|
272
|
+
*/
|
|
273
|
+
export declare function assertPackDependencies(hqRoot: string, pkg: PackManifest): void;
|
|
262
274
|
/**
|
|
263
275
|
* Derive the safe, auto-generated "get started" line for a freshly installed
|
|
264
276
|
* pack from its `initialization.entrypoint` ONLY. PHASE 1 deliberately ignores
|
|
@@ -34,7 +34,7 @@
|
|
|
34
34
|
* from each pack's package.yaml; rationale lives in the layout-fix PR.)
|
|
35
35
|
*/
|
|
36
36
|
|
|
37
|
-
!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]="
|
|
37
|
+
!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]="0ec07d3a-ee86-51fd-adbd-2608c620ae99")}catch(e){}}();
|
|
38
38
|
import * as fs from 'fs';
|
|
39
39
|
import * as os from 'os';
|
|
40
40
|
import * as path from 'path';
|
|
@@ -48,7 +48,7 @@ import semverValid from 'semver/functions/valid.js';
|
|
|
48
48
|
import semverValidRange from 'semver/ranges/valid.js';
|
|
49
49
|
import semverGt from 'semver/functions/gt.js';
|
|
50
50
|
import { findHqRoot } from '../utils/manifest.js';
|
|
51
|
-
import { readHqVersion, routeContribution } from '../utils/pack-contributions.js';
|
|
51
|
+
import { readHqVersion, routeContribution, listInstalledPacks } from '../utils/pack-contributions.js';
|
|
52
52
|
import { CONTRIBUTION_TABLE, payloadFor } from '../utils/contribution-table.js';
|
|
53
53
|
import { safeExtractTarball } from './safe-extract.js';
|
|
54
54
|
import { vaultApiFetchPublic } from '../utils/vault-api.js';
|
|
@@ -907,6 +907,32 @@ export function validateManifest(payloadDir, hqVersion) {
|
|
|
907
907
|
if (hqVersion && !semverSatisfies(hqVersion, range, { includePrerelease: true })) {
|
|
908
908
|
throw new Error(`Host hqCore ${hqVersion} does not satisfy pack requirement ${range}`);
|
|
909
909
|
}
|
|
910
|
+
// 6b. requires.packs (M0) — OPTIONAL pack-to-pack dependencies. Absent → legacy
|
|
911
|
+
// behavior (hqCore is the only prerequisite). Present → a list of
|
|
912
|
+
// { name, version? } where `name` is a valid hq-pack name and `version` (if
|
|
913
|
+
// given) is a valid semver RANGE. SHAPE validation only (no filesystem) so a
|
|
914
|
+
// malformed dependency can't masquerade as valid; whether the named packs are
|
|
915
|
+
// actually INSTALLED is enforced at install time by assertPackDependencies
|
|
916
|
+
// (which needs hqRoot — a pure manifest validator doesn't have it).
|
|
917
|
+
const reqPacks = m.requires?.packs;
|
|
918
|
+
if (reqPacks !== undefined) {
|
|
919
|
+
if (!Array.isArray(reqPacks)) {
|
|
920
|
+
throw new Error('requires.packs must be a list of { name, version? } entries');
|
|
921
|
+
}
|
|
922
|
+
for (const dep of reqPacks) {
|
|
923
|
+
if (!dep || typeof dep !== 'object' || Array.isArray(dep)) {
|
|
924
|
+
throw new Error('requires.packs entries must be mappings with a name (and optional version)');
|
|
925
|
+
}
|
|
926
|
+
const d = dep;
|
|
927
|
+
if (typeof d.name !== 'string' || !/^hq-pack-[a-z0-9][a-z0-9-]*$/.test(d.name)) {
|
|
928
|
+
throw new Error(`requires.packs[].name "${d.name}" must match ^hq-pack-[a-z0-9][a-z0-9-]*$`);
|
|
929
|
+
}
|
|
930
|
+
if (d.version !== undefined &&
|
|
931
|
+
(typeof d.version !== 'string' || !semverValidRange(d.version))) {
|
|
932
|
+
throw new Error(`requires.packs entry for "${d.name}" has an invalid version range "${d.version}"`);
|
|
933
|
+
}
|
|
934
|
+
}
|
|
935
|
+
}
|
|
910
936
|
// 7. contributes has at least one non-empty subfield
|
|
911
937
|
const contributes = (m.contributes ?? {});
|
|
912
938
|
const nonEmpty = Object.values(contributes).some((v) => Array.isArray(v) && v.length > 0);
|
|
@@ -1007,6 +1033,46 @@ export function validateManifest(payloadDir, hqVersion) {
|
|
|
1007
1033
|
return m;
|
|
1008
1034
|
}
|
|
1009
1035
|
// ---------------------------------------------------------------------------
|
|
1036
|
+
// Pack-to-pack dependency pre-flight (M0)
|
|
1037
|
+
// ---------------------------------------------------------------------------
|
|
1038
|
+
/**
|
|
1039
|
+
* Enforce a pack's `requires.packs`: every named dependency MUST already be
|
|
1040
|
+
* installed (and satisfy its optional semver RANGE) before we write anything.
|
|
1041
|
+
*
|
|
1042
|
+
* Installed packs are discovered by FILESYSTEM PRESENCE via `listInstalledPacks`
|
|
1043
|
+
* — deliberately NOT `modules.yaml`, which `installPack` no longer writes under
|
|
1044
|
+
* the v12+ layout (a modules.yaml-based check would silently ignore every modern
|
|
1045
|
+
* pack). Throws on the first unmet dependency so the install aborts with NO
|
|
1046
|
+
* partial state (it is called before installToPackages). No-op when
|
|
1047
|
+
* `requires.packs` is absent/empty, keeping legacy packs unaffected.
|
|
1048
|
+
*/
|
|
1049
|
+
export function assertPackDependencies(hqRoot, pkg) {
|
|
1050
|
+
const deps = pkg.requires?.packs ?? [];
|
|
1051
|
+
if (deps.length === 0)
|
|
1052
|
+
return;
|
|
1053
|
+
const installed = new Map();
|
|
1054
|
+
for (const p of listInstalledPacks(hqRoot)) {
|
|
1055
|
+
// Key on the manifest name when readable, else the directory name.
|
|
1056
|
+
installed.set(p.manifest?.name ?? p.name, p.manifest?.version);
|
|
1057
|
+
}
|
|
1058
|
+
for (const dep of deps) {
|
|
1059
|
+
if (dep.name === pkg.name) {
|
|
1060
|
+
throw new Error(`Pack ${pkg.name} cannot list itself in requires.packs.`);
|
|
1061
|
+
}
|
|
1062
|
+
if (!installed.has(dep.name)) {
|
|
1063
|
+
throw new Error(`Pack ${pkg.name} requires ${dep.name}, which is not installed. ` +
|
|
1064
|
+
`Install it first, e.g.: hq install marketplace:${dep.name}`);
|
|
1065
|
+
}
|
|
1066
|
+
if (dep.version) {
|
|
1067
|
+
const have = installed.get(dep.name);
|
|
1068
|
+
if (!have || !semverSatisfies(have, dep.version, { includePrerelease: true })) {
|
|
1069
|
+
throw new Error(`Pack ${pkg.name} requires ${dep.name} ${dep.version}, but ` +
|
|
1070
|
+
`${dep.name}${have ? ` ${have}` : ' (version unknown)'} is installed.`);
|
|
1071
|
+
}
|
|
1072
|
+
}
|
|
1073
|
+
}
|
|
1074
|
+
}
|
|
1075
|
+
// ---------------------------------------------------------------------------
|
|
1010
1076
|
// Post-install get-started line (US-005)
|
|
1011
1077
|
// ---------------------------------------------------------------------------
|
|
1012
1078
|
/**
|
|
@@ -1482,6 +1548,11 @@ export async function installPack(source, opts = {}) {
|
|
|
1482
1548
|
break;
|
|
1483
1549
|
}
|
|
1484
1550
|
const pkg = validateManifest(fetched.payloadDir, hqVersion);
|
|
1551
|
+
// M0 — pack-to-pack dependency pre-flight. Runs BEFORE any prompts or writes
|
|
1552
|
+
// so a missing/unsatisfied `requires.packs` aborts with no partial state, and
|
|
1553
|
+
// before we bother the operator with hook/MCP trust prompts for a pack that
|
|
1554
|
+
// can't install anyway.
|
|
1555
|
+
assertPackDependencies(hqRoot, pkg);
|
|
1485
1556
|
if (pkg.conditional) {
|
|
1486
1557
|
const allowed = await confirmConditional(pkg, opts.allowHooks ?? false);
|
|
1487
1558
|
if (!allowed) {
|
|
@@ -1558,4 +1629,4 @@ export async function installPack(source, opts = {}) {
|
|
|
1558
1629
|
}
|
|
1559
1630
|
}
|
|
1560
1631
|
//# sourceMappingURL=pack-install.js.map
|
|
1561
|
-
//# debugId=
|
|
1632
|
+
//# debugId=0ec07d3a-ee86-51fd-adbd-2608c620ae99
|
package/dist/commands/packs.js
CHANGED
|
@@ -18,7 +18,7 @@
|
|
|
18
18
|
* Spec: knowledge/public/hq-core/package-yaml-spec.md.
|
|
19
19
|
*/
|
|
20
20
|
|
|
21
|
-
!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]="
|
|
21
|
+
!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]="69746edd-f9d8-528e-8902-de799c58bd8a")}catch(e){}}();
|
|
22
22
|
import * as fs from 'fs';
|
|
23
23
|
import * as path from 'path';
|
|
24
24
|
import * as readline from 'readline';
|
|
@@ -27,7 +27,7 @@ import chalk from 'chalk';
|
|
|
27
27
|
import semverSatisfies from 'semver/functions/satisfies.js';
|
|
28
28
|
import { findHqRoot } from '../utils/manifest.js';
|
|
29
29
|
import { classify, resolveLatest, resolveLatestMarketplace, runScanPackages, installPack, } from './pack-install.js';
|
|
30
|
-
import { contributionLinks, linkStatus, listInstalledPacks, readPackManifest, unwirePack, unwirePackMcp, readHqVersion, readRecommendedPackages, packagesDir, } from '../utils/pack-contributions.js';
|
|
30
|
+
import { contributionLinks, linkStatus, listInstalledPacks, findDependentPacks, readPackManifest, unwirePack, unwirePackMcp, readHqVersion, readRecommendedPackages, packagesDir, } from '../utils/pack-contributions.js';
|
|
31
31
|
function resolveRoot(opts) {
|
|
32
32
|
return opts.hqRoot ? path.resolve(opts.hqRoot) : findHqRoot();
|
|
33
33
|
}
|
|
@@ -278,6 +278,19 @@ async function runUninstall(name, opts) {
|
|
|
278
278
|
if (!manifest) {
|
|
279
279
|
warnings.push('package.yaml unreadable -- host symlinks could not be computed precisely; ran a re-scan to reconcile.');
|
|
280
280
|
}
|
|
281
|
+
// 0. Dependents guard (M0): refuse to remove a pack that another installed pack
|
|
282
|
+
// lists in its `requires.packs`, unless --force. Filesystem presence is the
|
|
283
|
+
// source of truth (listInstalledPacks), consistent with the install-time
|
|
284
|
+
// assertPackDependencies check. Runs BEFORE any un-wiring so a blocked uninstall
|
|
285
|
+
// leaves the pack fully intact.
|
|
286
|
+
if (!opts.force) {
|
|
287
|
+
const dependents = findDependentPacks(listInstalledPacks(hqRoot), name);
|
|
288
|
+
if (dependents.length > 0) {
|
|
289
|
+
const who = dependents.map((p) => p.manifest?.name ?? p.name).join(', ');
|
|
290
|
+
throw new Error(`Cannot uninstall "${name}": required by ${who}. ` +
|
|
291
|
+
`Uninstall the dependent pack(s) first, or pass --force to override.`);
|
|
292
|
+
}
|
|
293
|
+
}
|
|
281
294
|
// 1. Un-wire only our symlinks.
|
|
282
295
|
const { unlinked, skipped } = unwirePack(hqRoot, packDir, contributes);
|
|
283
296
|
// 1b. Un-register the pack's MCP (`wire: 'merge'`) servers — invisible to the
|
|
@@ -417,6 +430,7 @@ export function registerPacksCommand(parent) {
|
|
|
417
430
|
.option('--hq-root <path>', 'HQ root (default: auto-detect)')
|
|
418
431
|
.option('-y, --yes', 'Skip confirmation')
|
|
419
432
|
.option('--no-archive', 'Delete instead of archiving')
|
|
433
|
+
.option('--force', 'Uninstall even if other installed packs require this one')
|
|
420
434
|
.action(async (name, opts) => {
|
|
421
435
|
try {
|
|
422
436
|
if (!opts.yes) {
|
|
@@ -449,4 +463,4 @@ export function registerPacksCommand(parent) {
|
|
|
449
463
|
});
|
|
450
464
|
}
|
|
451
465
|
//# sourceMappingURL=packs.js.map
|
|
452
|
-
//# debugId=
|
|
466
|
+
//# debugId=69746edd-f9d8-528e-8902-de799c58bd8a
|
|
@@ -11,6 +11,16 @@
|
|
|
11
11
|
* named by `--company`); nothing reads across company boundaries.
|
|
12
12
|
*/
|
|
13
13
|
import { Command } from "commander";
|
|
14
|
+
import { resolveNameToEmail, type PersonRecord } from "../utils/people.js";
|
|
15
|
+
export type RefreshPeopleRoster = (hqRoot: string, companySlug: string) => Promise<void>;
|
|
16
|
+
interface PeopleCommandDeps {
|
|
17
|
+
refreshRoster?: RefreshPeopleRoster;
|
|
18
|
+
}
|
|
19
|
+
interface PeopleLookupOpts {
|
|
20
|
+
localOnly?: boolean;
|
|
21
|
+
json?: boolean;
|
|
22
|
+
}
|
|
23
|
+
export declare function refreshPeopleRosterFromCloud(hqRoot: string, companySlug: string): Promise<void>;
|
|
14
24
|
/**
|
|
15
25
|
* Resolve the single company to operate on. Explicit `--company` always wins
|
|
16
26
|
* (after a path-safety check). Otherwise the active company is inferred from
|
|
@@ -18,5 +28,20 @@ import { Command } from "commander";
|
|
|
18
28
|
* do, the caller must disambiguate with `--company`.
|
|
19
29
|
*/
|
|
20
30
|
export declare function resolveCompanySlug(hqRoot: string, explicit: string | undefined): string;
|
|
21
|
-
export declare function
|
|
31
|
+
export declare function resolvePersonWithRosterFallback(input: {
|
|
32
|
+
hqRoot: string;
|
|
33
|
+
slug: string;
|
|
34
|
+
name: string;
|
|
35
|
+
opts?: PeopleLookupOpts;
|
|
36
|
+
refreshRoster?: RefreshPeopleRoster;
|
|
37
|
+
}): Promise<ReturnType<typeof resolveNameToEmail>>;
|
|
38
|
+
export declare function searchPeopleWithRosterFallback(input: {
|
|
39
|
+
hqRoot: string;
|
|
40
|
+
slug: string;
|
|
41
|
+
keyword: string;
|
|
42
|
+
opts?: PeopleLookupOpts;
|
|
43
|
+
refreshRoster?: RefreshPeopleRoster;
|
|
44
|
+
}): Promise<PersonRecord[]>;
|
|
45
|
+
export declare function registerPeopleCommand(program: Command, deps?: PeopleCommandDeps): void;
|
|
46
|
+
export {};
|
|
22
47
|
//# sourceMappingURL=people.d.ts.map
|
package/dist/commands/people.js
CHANGED
|
@@ -11,14 +11,31 @@
|
|
|
11
11
|
* named by `--company`); nothing reads across company boundaries.
|
|
12
12
|
*/
|
|
13
13
|
|
|
14
|
-
!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]="
|
|
14
|
+
!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]="79abc1f5-4f5a-5c95-b29b-10f04417355f")}catch(e){}}();
|
|
15
15
|
import * as fs from "fs";
|
|
16
16
|
import { Option } from "commander";
|
|
17
17
|
import chalk from "chalk";
|
|
18
|
+
import { VaultClient } from "@indigoai-us/hq-cloud";
|
|
18
19
|
import * as yaml from "js-yaml";
|
|
19
20
|
import { findHqRoot } from "../utils/manifest.js";
|
|
20
21
|
import { manifestPath } from "./cloud-provision.js";
|
|
22
|
+
import { DEFAULT_COGNITO, buildVaultConfig, ensureCognitoToken, } from "../utils/cognito-session.js";
|
|
23
|
+
import { getCompanyUid } from "../utils/vault-api.js";
|
|
24
|
+
import { createCompanyPresignClient, runGet } from "./files-browse.js";
|
|
21
25
|
import { assertSafeCompanySlug, listCompanyPeople, searchPeople, resolveNameToEmail, companyPeopleDir, } from "../utils/people.js";
|
|
26
|
+
export async function refreshPeopleRosterFromCloud(hqRoot, companySlug) {
|
|
27
|
+
const accessToken = await ensureCognitoToken();
|
|
28
|
+
const client = new VaultClient(buildVaultConfig(accessToken));
|
|
29
|
+
await getCompanyUid(accessToken, companySlug);
|
|
30
|
+
await runGet({
|
|
31
|
+
path: `companies/${companySlug}/people/`,
|
|
32
|
+
hqRoot,
|
|
33
|
+
companySlug,
|
|
34
|
+
vaultClient: client,
|
|
35
|
+
companyClient: ({ companyUid }) => createCompanyPresignClient({ token: accessToken, companyUid }),
|
|
36
|
+
region: DEFAULT_COGNITO.region,
|
|
37
|
+
});
|
|
38
|
+
}
|
|
22
39
|
/** Companies that still exist (anything not explicitly `status: archived`). */
|
|
23
40
|
function activeCompanySlugs(manifest) {
|
|
24
41
|
const companies = manifest.companies ?? {};
|
|
@@ -86,7 +103,39 @@ function fail(message) {
|
|
|
86
103
|
console.error(chalk.red(message));
|
|
87
104
|
process.exit(1);
|
|
88
105
|
}
|
|
89
|
-
|
|
106
|
+
function logRefreshFailure(companySlug, err) {
|
|
107
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
108
|
+
console.error(chalk.dim(` Could not refresh people roster for '${companySlug}': ${message}`));
|
|
109
|
+
}
|
|
110
|
+
async function tryRefreshRoster(refreshRoster, hqRoot, slug) {
|
|
111
|
+
try {
|
|
112
|
+
await refreshRoster(hqRoot, slug);
|
|
113
|
+
return true;
|
|
114
|
+
}
|
|
115
|
+
catch (err) {
|
|
116
|
+
logRefreshFailure(slug, err);
|
|
117
|
+
return false;
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
export async function resolvePersonWithRosterFallback(input) {
|
|
121
|
+
const local = resolveNameToEmail(listCompanyPeople(input.hqRoot, input.slug), input.name);
|
|
122
|
+
if (local.status !== "not_found" || input.opts?.localOnly)
|
|
123
|
+
return local;
|
|
124
|
+
const refreshed = await tryRefreshRoster(input.refreshRoster ?? refreshPeopleRosterFromCloud, input.hqRoot, input.slug);
|
|
125
|
+
if (!refreshed)
|
|
126
|
+
return local;
|
|
127
|
+
return resolveNameToEmail(listCompanyPeople(input.hqRoot, input.slug), input.name);
|
|
128
|
+
}
|
|
129
|
+
export async function searchPeopleWithRosterFallback(input) {
|
|
130
|
+
const local = searchPeople(listCompanyPeople(input.hqRoot, input.slug), input.keyword);
|
|
131
|
+
if (local.length > 0 || input.opts?.localOnly)
|
|
132
|
+
return local;
|
|
133
|
+
const refreshed = await tryRefreshRoster(input.refreshRoster ?? refreshPeopleRosterFromCloud, input.hqRoot, input.slug);
|
|
134
|
+
if (!refreshed)
|
|
135
|
+
return local;
|
|
136
|
+
return searchPeople(listCompanyPeople(input.hqRoot, input.slug), input.keyword);
|
|
137
|
+
}
|
|
138
|
+
export function registerPeopleCommand(program, deps = {}) {
|
|
90
139
|
const people = program
|
|
91
140
|
.command("people")
|
|
92
141
|
.description("List, search, and resolve a company's people (from companies/<co>/people)")
|
|
@@ -122,12 +171,19 @@ export function registerPeopleCommand(program) {
|
|
|
122
171
|
.command("search <keyword>")
|
|
123
172
|
.description("Keyword search over people names and emails")
|
|
124
173
|
.option("--json", "Output JSON instead of a table")
|
|
125
|
-
.
|
|
174
|
+
.option("--local-only", "Skip cloud fallback; search only the local people roster")
|
|
175
|
+
.action(async (keyword, opts) => {
|
|
126
176
|
try {
|
|
127
177
|
const scope = people.opts();
|
|
128
178
|
const hqRoot = resolveHqRoot(scope);
|
|
129
179
|
const slug = resolveCompanySlug(hqRoot, scope.company);
|
|
130
|
-
const matches =
|
|
180
|
+
const matches = await searchPeopleWithRosterFallback({
|
|
181
|
+
hqRoot,
|
|
182
|
+
slug,
|
|
183
|
+
keyword,
|
|
184
|
+
opts,
|
|
185
|
+
refreshRoster: deps.refreshRoster,
|
|
186
|
+
});
|
|
131
187
|
if (opts.json) {
|
|
132
188
|
console.log(JSON.stringify(matches, null, 2));
|
|
133
189
|
return;
|
|
@@ -146,12 +202,19 @@ export function registerPeopleCommand(program) {
|
|
|
146
202
|
.command("resolve <name>")
|
|
147
203
|
.description("Resolve a person name to their email address")
|
|
148
204
|
.option("--json", "Output JSON instead of plain text")
|
|
149
|
-
.
|
|
205
|
+
.option("--local-only", "Skip cloud fallback; resolve only from the local people roster")
|
|
206
|
+
.action(async (name, opts) => {
|
|
150
207
|
try {
|
|
151
208
|
const scope = people.opts();
|
|
152
209
|
const hqRoot = resolveHqRoot(scope);
|
|
153
210
|
const slug = resolveCompanySlug(hqRoot, scope.company);
|
|
154
|
-
const result =
|
|
211
|
+
const result = await resolvePersonWithRosterFallback({
|
|
212
|
+
hqRoot,
|
|
213
|
+
slug,
|
|
214
|
+
name,
|
|
215
|
+
opts,
|
|
216
|
+
refreshRoster: deps.refreshRoster,
|
|
217
|
+
});
|
|
155
218
|
if (opts.json) {
|
|
156
219
|
console.log(JSON.stringify(result, null, 2));
|
|
157
220
|
if (result.status === "found")
|
|
@@ -184,4 +247,4 @@ export function registerPeopleCommand(program) {
|
|
|
184
247
|
});
|
|
185
248
|
}
|
|
186
249
|
//# sourceMappingURL=people.js.map
|
|
187
|
-
//# debugId=
|
|
250
|
+
//# debugId=79abc1f5-4f5a-5c95-b29b-10f04417355f
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Pure helpers describing WHICH secrets scope a command acted on, so `set` and
|
|
3
|
+
* `list` can echo it. Users were setting a secret in one scope (personal vs a
|
|
4
|
+
* company, or company A vs B) and listing another, then seeing "no secrets" with
|
|
5
|
+
* no indication the scopes differed (feedback_70e059da).
|
|
6
|
+
*/
|
|
7
|
+
export interface SecretsScopeRef {
|
|
8
|
+
/** True when --personal was used (caller's personal vault). */
|
|
9
|
+
personal: boolean;
|
|
10
|
+
/** The slug the user passed via --company, if any. */
|
|
11
|
+
companySlug?: string;
|
|
12
|
+
/** Resolved entity uid: prs_* for personal, cmp_* for a company. */
|
|
13
|
+
companyUid: string;
|
|
14
|
+
}
|
|
15
|
+
/** Human label for a secrets scope: "your personal vault" or "company <slug-or-uid>". */
|
|
16
|
+
export declare function describeSecretsScope(ref: SecretsScopeRef): string;
|
|
17
|
+
export declare function formatSecretSaved(name: string, scope: string): string;
|
|
18
|
+
export declare function formatSecretsListHeader(scope: string): string;
|
|
19
|
+
export declare function formatSecretsListEmpty(scope: string): string;
|
|
20
|
+
//# sourceMappingURL=secrets-scope.d.ts.map
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
/** Human label for a secrets scope: "your personal vault" or "company <slug-or-uid>". */
|
|
2
|
+
|
|
3
|
+
!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]="718913a3-f80f-5bff-928e-a7aa9f51dd0d")}catch(e){}}();
|
|
4
|
+
export function describeSecretsScope(ref) {
|
|
5
|
+
if (ref.personal)
|
|
6
|
+
return "your personal vault";
|
|
7
|
+
return `company ${ref.companySlug ?? ref.companyUid}`;
|
|
8
|
+
}
|
|
9
|
+
export function formatSecretSaved(name, scope) {
|
|
10
|
+
return `Secret '${name}' saved to ${scope}.`;
|
|
11
|
+
}
|
|
12
|
+
export function formatSecretsListHeader(scope) {
|
|
13
|
+
return `Secrets for ${scope}:`;
|
|
14
|
+
}
|
|
15
|
+
export function formatSecretsListEmpty(scope) {
|
|
16
|
+
return `No secrets found for ${scope}.`;
|
|
17
|
+
}
|
|
18
|
+
//# sourceMappingURL=secrets-scope.js.map
|
|
19
|
+
//# debugId=718913a3-f80f-5bff-928e-a7aa9f51dd0d
|