@indigoai-us/hq-cli 5.77.14 → 5.78.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/CHANGELOG.md +23 -0
- package/assets/scaffold/core/scripts/archive-old-threads.sh +86 -0
- package/assets/scaffold/core/scripts/backfill-company-skill-mirrors.sh +73 -0
- package/assets/scaffold/core/scripts/backfill-workspace-mirror.sh +51 -0
- package/assets/scaffold/core/scripts/codex-skill-bridge.sh +550 -0
- package/assets/scaffold/core/scripts/detect-stale-core-policy-mirror.sh +185 -0
- package/assets/scaffold/core/scripts/hq-status-summary.sh +187 -0
- package/assets/scaffold/core/scripts/ontology-readme-drift.sh +264 -0
- package/assets/scaffold/core/scripts/qmd-reindex-after-sync.sh +93 -0
- package/assets/scaffold/core/scripts/rebuild-all-indexes.sh +69 -0
- package/assets/scaffold/core/scripts/rebuild-companies-index.sh +206 -0
- package/assets/scaffold/core/scripts/rebuild-company-knowledge-index.sh +127 -0
- package/assets/scaffold/core/scripts/rebuild-orchestrator-index.sh +86 -0
- package/assets/scaffold/core/scripts/rebuild-projects-index.sh +105 -0
- package/assets/scaffold/core/scripts/rebuild-public-knowledge-index.sh +83 -0
- package/assets/scaffold/core/scripts/rebuild-reports-index.sh +114 -0
- package/assets/scaffold/core/scripts/rebuild-social-drafts-index.sh +135 -0
- package/assets/scaffold/core/scripts/rebuild-threads-index.sh +82 -0
- package/assets/scaffold/core/scripts/rebuild-workers-index.sh +97 -0
- package/assets/scaffold/core/scripts/resize-screenshot.sh +47 -0
- package/assets/scaffold/core/scripts/token-usage-report.sh +254 -0
- package/assets/scaffold/core/scripts/worktree.sh +136 -0
- package/dist/commands/agents.js +28 -12
- package/dist/commands/api-keys.js +100 -20
- package/dist/commands/core.d.ts +90 -0
- package/dist/commands/core.js +279 -0
- package/dist/commands/files-browse.d.ts +21 -0
- package/dist/commands/files-browse.js +145 -24
- package/dist/commands/files.js +33 -7
- package/dist/commands/members.js +16 -5
- package/dist/commands/pack-install.d.ts +14 -0
- package/dist/commands/pack-install.js +42 -2
- package/dist/commands/pkg-install.js +39 -2
- package/dist/commands/run.js +6 -2
- package/dist/commands/secrets.js +120 -65
- package/dist/main.js +6 -0
- package/dist/utils/api-key-command-gate.d.ts +45 -0
- package/dist/utils/api-key-command-gate.js +51 -0
- package/dist/utils/hq-roots.d.ts +91 -0
- package/dist/utils/hq-roots.js +212 -0
- package/dist/utils/resolve-vault-credential.d.ts +63 -3
- package/dist/utils/resolve-vault-credential.js +152 -6
- package/dist/utils/run-bundled-script.d.ts +66 -0
- package/dist/utils/run-bundled-script.js +80 -0
- package/dist/utils/vault-api.d.ts +21 -0
- package/dist/utils/vault-api.js +74 -3
- package/package.json +7 -1
- package/.claude/policies/hq-cli-interactive-reads-use-sts-vend.md +0 -49
- package/.github/workflows/ci.yml +0 -46
- package/.github/workflows/publish.yml +0 -36
- package/.github/workflows/scripts/smoke-test-pkg.sh +0 -97
- package/e2e/cli.test.ts +0 -128
- package/eslint.config.js +0 -37
- package/pnpm-workspace.yaml +0 -4
- package/scripts/chmod-bins.mjs +0 -49
- package/scripts/generate-dsn.mjs +0 -26
- package/scripts/generate-scan-packages-table.mjs +0 -113
- package/scripts/smoke-sources-signals.sh +0 -103
- package/src/bin/hq-auth-refresh.ts +0 -43
- package/src/cli-version.ts +0 -13
- package/src/commands/__fixtures__/make-tar.ts +0 -126
- package/src/commands/_patterns.ts +0 -10
- package/src/commands/add.ts +0 -75
- package/src/commands/agents.test.ts +0 -527
- package/src/commands/agents.ts +0 -923
- package/src/commands/api-keys.test.ts +0 -291
- package/src/commands/api-keys.ts +0 -382
- package/src/commands/artifact-verify.test.ts +0 -177
- package/src/commands/auth.test.ts +0 -82
- package/src/commands/auth.ts +0 -213
- package/src/commands/billing.test.ts +0 -158
- package/src/commands/billing.ts +0 -146
- package/src/commands/channels.test.ts +0 -74
- package/src/commands/channels.ts +0 -91
- package/src/commands/cloud-demote.test.ts +0 -401
- package/src/commands/cloud-demote.ts +0 -278
- package/src/commands/cloud-provision.test.ts +0 -1117
- package/src/commands/cloud-provision.ts +0 -942
- package/src/commands/cloud.pull-all.test.ts +0 -711
- package/src/commands/cloud.pull-per-company.test.ts +0 -188
- package/src/commands/cloud.push-all.test.ts +0 -485
- package/src/commands/cloud.scope-excluded-warning.test.ts +0 -22
- package/src/commands/cloud.selectors.test.ts +0 -208
- package/src/commands/cloud.status.test.ts +0 -160
- package/src/commands/cloud.test.ts +0 -41
- package/src/commands/cloud.ts +0 -2130
- package/src/commands/company.test.ts +0 -202
- package/src/commands/company.ts +0 -148
- package/src/commands/creators.test.ts +0 -60
- package/src/commands/creators.ts +0 -117
- package/src/commands/crm.test.ts +0 -179
- package/src/commands/crm.ts +0 -236
- package/src/commands/db-migrate.ts +0 -55
- package/src/commands/db-provision.ts +0 -114
- package/src/commands/db-sql.ts +0 -124
- package/src/commands/db-status.ts +0 -108
- package/src/commands/db.ts +0 -26
- package/src/commands/dm.test.ts +0 -748
- package/src/commands/dm.ts +0 -1226
- package/src/commands/feedback.test.ts +0 -413
- package/src/commands/feedback.ts +0 -169
- package/src/commands/files-browse.test.ts +0 -1249
- package/src/commands/files-browse.ts +0 -1506
- package/src/commands/files-delete.test.ts +0 -484
- package/src/commands/files-recovery.test.ts +0 -370
- package/src/commands/files.test.ts +0 -722
- package/src/commands/files.ts +0 -1446
- package/src/commands/group-grants.test.ts +0 -330
- package/src/commands/group-grants.ts +0 -455
- package/src/commands/groups.test.ts +0 -44
- package/src/commands/groups.ts +0 -368
- package/src/commands/integrations.test.ts +0 -633
- package/src/commands/integrations.ts +0 -507
- package/src/commands/list.ts +0 -85
- package/src/commands/login.ts +0 -52
- package/src/commands/logout.ts +0 -30
- package/src/commands/marketplace-install.test.ts +0 -543
- package/src/commands/marketplace-security.test.ts +0 -646
- package/src/commands/mcp-registration.test.ts +0 -2787
- package/src/commands/mcp-registration.ts +0 -2612
- package/src/commands/mcp-status.test.ts +0 -483
- package/src/commands/mcp-status.ts +0 -575
- package/src/commands/mcp-status.us011.test.ts +0 -243
- package/src/commands/meetings.test.ts +0 -522
- package/src/commands/meetings.ts +0 -789
- package/src/commands/members.test.ts +0 -1474
- package/src/commands/members.ts +0 -1181
- package/src/commands/onboard-identity-guard.test.ts +0 -99
- package/src/commands/onboard-identity-guard.ts +0 -103
- package/src/commands/onboard-join.test.ts +0 -96
- package/src/commands/onboard-join.ts +0 -101
- package/src/commands/onboard-warning.test.ts +0 -26
- package/src/commands/onboard-warning.ts +0 -12
- package/src/commands/onboard.ts +0 -267
- package/src/commands/outposts-heartbeat.test.ts +0 -299
- package/src/commands/outposts-heartbeat.ts +0 -310
- package/src/commands/outposts-replica-sync.test.ts +0 -272
- package/src/commands/outposts-self-deploy.test.ts +0 -243
- package/src/commands/outposts.test.ts +0 -1039
- package/src/commands/outposts.ts +0 -1935
- package/src/commands/pack-install-secret-authorization.test.ts +0 -115
- package/src/commands/pack-install.test.ts +0 -1469
- package/src/commands/pack-install.ts +0 -2256
- package/src/commands/pack-update-cache.test.ts +0 -149
- package/src/commands/packs-update-api-key.test.ts +0 -105
- package/src/commands/packs.test.ts +0 -88
- package/src/commands/packs.ts +0 -622
- package/src/commands/people.test.ts +0 -807
- package/src/commands/people.ts +0 -422
- package/src/commands/pkg-install.dispatch.test.ts +0 -103
- package/src/commands/pkg-install.ts +0 -221
- package/src/commands/pkg-list.test.ts +0 -79
- package/src/commands/pkg-list.ts +0 -13
- package/src/commands/pkg-remove.ts +0 -71
- package/src/commands/pkg-update.ts +0 -189
- package/src/commands/publish.test.ts +0 -650
- package/src/commands/publish.ts +0 -580
- package/src/commands/reindex.test.ts +0 -259
- package/src/commands/reindex.ts +0 -274
- package/src/commands/rescue.test.ts +0 -39
- package/src/commands/rescue.ts +0 -210
- package/src/commands/run.env-local.test.ts +0 -88
- package/src/commands/run.test.ts +0 -45
- package/src/commands/run.ts +0 -190
- package/src/commands/safe-extract.test.ts +0 -459
- package/src/commands/safe-extract.ts +0 -444
- package/src/commands/secrets-scope.test.ts +0 -56
- package/src/commands/secrets-scope.ts +0 -32
- package/src/commands/secrets.parse-destination.test.ts +0 -38
- package/src/commands/secrets.test.ts +0 -2050
- package/src/commands/secrets.ts +0 -2249
- package/src/commands/signals.ts +0 -351
- package/src/commands/skill.test.ts +0 -770
- package/src/commands/skill.ts +0 -796
- package/src/commands/sources.ts +0 -362
- package/src/commands/sync-mode.test.ts +0 -366
- package/src/commands/sync-mode.ts +0 -387
- package/src/commands/sync-narrow.test.ts +0 -633
- package/src/commands/sync-narrow.ts +0 -555
- package/src/commands/sync.ts +0 -165
- package/src/commands/team-sync.ts +0 -628
- package/src/commands/update.ts +0 -71
- package/src/commands/whoami.ts +0 -83
- package/src/commands/workers.test.ts +0 -158
- package/src/commands/workers.ts +0 -301
- package/src/index.test.ts +0 -32
- package/src/index.ts +0 -20
- package/src/lib/db/control-plane.test.ts +0 -59
- package/src/lib/db/control-plane.ts +0 -113
- package/src/lib/db/local.test.ts +0 -81
- package/src/lib/db/local.ts +0 -148
- package/src/lib/db/migrate.test.ts +0 -133
- package/src/lib/db/migrate.ts +0 -137
- package/src/lib/db/paths.test.ts +0 -112
- package/src/lib/db/paths.ts +0 -128
- package/src/lib/db/remote-engine.test.ts +0 -44
- package/src/lib/db/remote-engine.ts +0 -148
- package/src/lib/db/remote-sql.test.ts +0 -32
- package/src/lib/db/remote-sql.ts +0 -62
- package/src/lib/db/sql.test.ts +0 -106
- package/src/lib/db/sql.ts +0 -192
- package/src/lib/hq-cloud-dep.smoke.test.ts +0 -75
- package/src/lib/local-tree-diff.test.ts +0 -301
- package/src/lib/local-tree-diff.ts +0 -362
- package/src/lib/narrow-hint-banner.test.ts +0 -235
- package/src/lib/narrow-hint-banner.ts +0 -212
- package/src/lib/plan-limit-nag.test.ts +0 -317
- package/src/lib/plan-limit-nag.ts +0 -264
- package/src/main.ts +0 -371
- package/src/node-preflight.test.ts +0 -60
- package/src/node-preflight.ts +0 -67
- package/src/outpost/session-heartbeat-bounds.test.ts +0 -195
- package/src/outpost/session-heartbeat-guard.test.ts +0 -105
- package/src/outpost/session-heartbeat-publisher.test.ts +0 -178
- package/src/outpost/session-heartbeat-publisher.ts +0 -186
- package/src/outpost/session-heartbeat-retain-guard.test.ts +0 -126
- package/src/outpost/session-heartbeat.test.ts +0 -459
- package/src/outpost/session-heartbeat.ts +0 -877
- package/src/packaging.test.ts +0 -109
- package/src/run/__fixtures__/discover-schemas/example.env.schema +0 -4
- package/src/run/discover-schemas.test.ts +0 -153
- package/src/run/discover-schemas.ts +0 -79
- package/src/run/hq-plugin.test.ts +0 -371
- package/src/run/hq-plugin.ts +0 -277
- package/src/run/varlock-shape.test.ts +0 -57
- package/src/schemas/hq-package.schema.json +0 -155
- package/src/sentry-before-send.test.ts +0 -359
- package/src/sentry-before-send.ts +0 -221
- package/src/sentry-epipe.test.ts +0 -37
- package/src/sentry-release.test.ts +0 -54
- package/src/sentry.test.ts +0 -24
- package/src/sentry.ts +0 -54
- package/src/strategies/link.ts +0 -62
- package/src/strategies/merge.ts +0 -142
- package/src/types.ts +0 -146
- package/src/utils/__fixtures__/scan-packages.generated-block.sh +0 -23
- package/src/utils/auth-error.test.ts +0 -40
- package/src/utils/auth-error.ts +0 -42
- package/src/utils/billing-gate.test.ts +0 -95
- package/src/utils/billing-gate.ts +0 -228
- package/src/utils/breadcrumb-buffer.ts +0 -18
- package/src/utils/cli-telemetry.test.ts +0 -153
- package/src/utils/cli-telemetry.ts +0 -61
- package/src/utils/cognito-session.machine.test.ts +0 -109
- package/src/utils/cognito-session.refresh.test.ts +0 -84
- package/src/utils/cognito-session.test.ts +0 -328
- package/src/utils/cognito-session.ts +0 -448
- package/src/utils/company-selection-error.test.ts +0 -42
- package/src/utils/company-selection-error.ts +0 -45
- package/src/utils/contribution-table.ts +0 -83
- package/src/utils/environmental-error.test.ts +0 -45
- package/src/utils/environmental-error.ts +0 -39
- package/src/utils/epipe.test.ts +0 -28
- package/src/utils/epipe.ts +0 -29
- package/src/utils/expected-cli-error.test.ts +0 -28
- package/src/utils/expected-cli-error.ts +0 -39
- package/src/utils/feedback-diagnostics.test.ts +0 -183
- package/src/utils/feedback-diagnostics.ts +0 -123
- package/src/utils/feedback-screenshots.test.ts +0 -134
- package/src/utils/feedback-screenshots.ts +0 -124
- package/src/utils/feedback-versions.test.ts +0 -98
- package/src/utils/feedback-versions.ts +0 -68
- package/src/utils/git.ts +0 -74
- package/src/utils/integrity.ts +0 -59
- package/src/utils/intercepted-process-exit.test.ts +0 -37
- package/src/utils/intercepted-process-exit.ts +0 -36
- package/src/utils/login-provider.test.ts +0 -42
- package/src/utils/login-provider.ts +0 -30
- package/src/utils/manifest.test.ts +0 -167
- package/src/utils/manifest.ts +0 -131
- package/src/utils/pack-contributions.test.ts +0 -603
- package/src/utils/pack-contributions.ts +0 -450
- package/src/utils/people.ts +0 -215
- package/src/utils/registry-client.ts +0 -204
- package/src/utils/registry.ts +0 -90
- package/src/utils/resolve-vault-credential.test.ts +0 -69
- package/src/utils/resolve-vault-credential.ts +0 -60
- package/src/utils/sandbox-runner-client.test.ts +0 -279
- package/src/utils/sandbox-runner-client.ts +0 -260
- package/src/utils/secrets-cache.ts +0 -183
- package/src/utils/sentry-identity.ts +0 -45
- package/src/utils/settle-with-timeout.test.ts +0 -21
- package/src/utils/settle-with-timeout.ts +0 -22
- package/src/utils/user-yaml-error.test.ts +0 -24
- package/src/utils/user-yaml-error.ts +0 -30
- package/src/utils/vault-api.test.ts +0 -536
- package/src/utils/vault-api.ts +0 -382
- package/src/utils/version-check.test.ts +0 -176
- package/src/utils/version-check.ts +0 -155
- package/src/utils/version-gate.test.ts +0 -1024
- package/src/utils/version-gate.ts +0 -702
- package/test/commands/db-tenant-isolation.test.ts +0 -94
- package/test/commands/db.test.ts +0 -85
- package/test/commands/signals.test.ts +0 -207
- package/test/commands/sources.test.ts +0 -231
- package/test/e2e/smoke-install-mcp.sh +0 -113
- package/test/fixtures/hq-pack-smoke-mcp/mcp/smoke-http.json +0 -1
- package/test/fixtures/hq-pack-smoke-mcp/package.yaml +0 -11
- package/test/fixtures/signals/action_item/sample.md +0 -16
- package/test/fixtures/signals/summary/sample.md +0 -12
- package/test/fixtures/sources/meetings/sample.md +0 -25
- package/test/helpers/cli-runner.ts +0 -150
- package/test/helpers/s3-list-mock.ts +0 -79
- package/test/helpers/vault-service-mock.ts +0 -295
- package/test/sources-signals/smoke.test.ts +0 -228
- package/tsconfig.json +0 -20
- package/vitest.config.ts +0 -11
- package/vitest.e2e.config.ts +0 -7
package/dist/commands/secrets.js
CHANGED
|
@@ -7,13 +7,18 @@ import { DEFAULT_SECRETS_CACHE_TTL_MS, writeCache, removeCacheEntry, clearAllCac
|
|
|
7
7
|
import { computeSha256 } from "../utils/integrity.js";
|
|
8
8
|
import { SECRET_NAME_PATTERN, GROUP_ID_PATTERN, EMAIL_PATTERN } from "./_patterns.js";
|
|
9
9
|
import { describeSecretsScope, formatSecretSaved, formatSecretsListEmpty, formatSecretsListHeader, } from "./secrets-scope.js";
|
|
10
|
-
import { vaultApiFetch, getCompanyUid, getEntityUid, } from "../utils/vault-api.js";
|
|
11
|
-
import { HQ_API_KEY_PREFIX,
|
|
10
|
+
import { vaultApiFetch, getCompanyUid, getEntityUid, looksLikeCompanyUid, } from "../utils/vault-api.js";
|
|
11
|
+
import { HQ_API_KEY_PREFIX, formatApiKeyCapabilityDenial, requireApiKeyCapability, resolveVaultCredentialForCapability, } from "../utils/resolve-vault-credential.js";
|
|
12
12
|
import { SandboxRunnerClient, } from "../utils/sandbox-runner-client.js";
|
|
13
13
|
export { vaultApiFetch, getCompanyUid, getEntityUid };
|
|
14
|
-
/**
|
|
15
|
-
|
|
16
|
-
|
|
14
|
+
/**
|
|
15
|
+
* Cognito session for secrets surfaces hq-pro serves on the JWT routes only —
|
|
16
|
+
* the capability would apply, but there is no Bearer `hqk_` route (no HEAD
|
|
17
|
+
* probe, no policy/script/sandbox/submit-link parallel). Fails closed under
|
|
18
|
+
* HQ_API_KEY instead of quietly using the cached session.
|
|
19
|
+
*/
|
|
20
|
+
async function requireCognitoTokenForSecrets(commandLabel, capability = null) {
|
|
21
|
+
requireApiKeyCapability(capability, commandLabel, { routeAvailable: false });
|
|
17
22
|
return ensureCognitoToken();
|
|
18
23
|
}
|
|
19
24
|
function scopeOpts(opts) {
|
|
@@ -35,12 +40,71 @@ function shellSingleQuote(value) {
|
|
|
35
40
|
// API Gateway v2 URL-decodes `%2F` before route matching, so a full-name
|
|
36
41
|
// `encodeURIComponent` would collapse structural `/` separators into a single
|
|
37
42
|
// segment the `/name/{proxy+}` route can't match.
|
|
43
|
+
function encodeSecretNameSegments(name) {
|
|
44
|
+
return name.split("/").map(encodeURIComponent).join("/");
|
|
45
|
+
}
|
|
38
46
|
function buildSecretNamePath(companyUid, name) {
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
47
|
+
return `/secrets/${encodeURIComponent(companyUid)}/name/${encodeSecretNameSegments(name)}`;
|
|
48
|
+
}
|
|
49
|
+
/**
|
|
50
|
+
* Company uid a Bearer `hqk_` secrets call targets.
|
|
51
|
+
*
|
|
52
|
+
* The `/v1/keys/secrets/{companyUid}` routes take the uid in the path, and the
|
|
53
|
+
* server 403s when it is not the key's own company. Slug resolution (and the
|
|
54
|
+
* "my single active membership" fallback) runs through Cognito-only entity
|
|
55
|
+
* routes, so under an API key the uid must be supplied explicitly.
|
|
56
|
+
*/
|
|
57
|
+
function resolveApiKeyCompanyUid(scope, commandLabel) {
|
|
58
|
+
if (scope.personal) {
|
|
59
|
+
throw new Error(`--personal is not supported with HQ_API_KEY ('${commandLabel}'): a vault ` +
|
|
60
|
+
`API key is bound to one company. Unset HQ_API_KEY to use your personal vault.`);
|
|
61
|
+
}
|
|
62
|
+
const ref = scope.companySlug ?? process.env.HQ_COMPANY_UID?.trim();
|
|
63
|
+
if (!ref) {
|
|
64
|
+
throw new Error(`HQ_API_KEY is set; '${commandLabel}' needs the company uid the key is bound ` +
|
|
65
|
+
`to. Pass --company cmp_… or set HQ_COMPANY_UID (\`hq api-keys list\` shows ` +
|
|
66
|
+
`it with a Cognito session).`);
|
|
67
|
+
}
|
|
68
|
+
if (!looksLikeCompanyUid(ref)) {
|
|
69
|
+
throw new Error(`HQ_API_KEY is set; --company must be a company uid (cmp_…) — resolving a ` +
|
|
70
|
+
`slug needs a Cognito session. Got '${ref}'.`);
|
|
71
|
+
}
|
|
72
|
+
return ref;
|
|
73
|
+
}
|
|
74
|
+
async function resolveSecretsTarget(capability, commandLabel, scopeOptions) {
|
|
75
|
+
const cred = await resolveVaultCredentialForCapability(capability, commandLabel);
|
|
76
|
+
const scope = scopeOpts(scopeOptions);
|
|
77
|
+
const isApiKey = cred.kind === "api-key";
|
|
78
|
+
const companyUid = isApiKey
|
|
79
|
+
? resolveApiKeyCompanyUid(scope, commandLabel)
|
|
80
|
+
: await getEntityUid(cred.token, scope);
|
|
81
|
+
const basePath = isApiKey
|
|
82
|
+
? `/v1/keys/secrets/${encodeURIComponent(companyUid)}`
|
|
83
|
+
: `/secrets/${encodeURIComponent(companyUid)}`;
|
|
84
|
+
return {
|
|
85
|
+
token: cred.token,
|
|
86
|
+
isApiKey,
|
|
87
|
+
companyUid,
|
|
88
|
+
scopeLabel: describeSecretsScope({
|
|
89
|
+
personal: scope.personal,
|
|
90
|
+
companySlug: scope.companySlug,
|
|
91
|
+
companyUid,
|
|
92
|
+
}),
|
|
93
|
+
basePath,
|
|
94
|
+
namePath: (name) => `${basePath}/name/${encodeSecretNameSegments(name)}`,
|
|
95
|
+
aclPath: (action) => action ? `${basePath}/acl/${action}` : `${basePath}/acl`,
|
|
96
|
+
};
|
|
97
|
+
}
|
|
98
|
+
/**
|
|
99
|
+
* Failure message for a secrets call. A 403 on an API-key route may carry
|
|
100
|
+
* hq-pro's `{ granted, missing }` capability report — surface it verbatim so
|
|
101
|
+
* the caller sees the key's real grants, not just "forbidden".
|
|
102
|
+
*/
|
|
103
|
+
function describeSecretsFailure(target, status, body, fallback) {
|
|
104
|
+
if (target.isApiKey && status === 403) {
|
|
105
|
+
return formatApiKeyCapabilityDenial(body, fallback);
|
|
106
|
+
}
|
|
107
|
+
return extractApiMessage(body, fallback);
|
|
44
108
|
}
|
|
45
109
|
function parseDuration(input) {
|
|
46
110
|
const match = input.match(/^(\d+)(m|h|d)$/);
|
|
@@ -685,17 +749,11 @@ export function registerSecretsCommand(program) {
|
|
|
685
749
|
console.error(chalk.red(`Secret value exceeds 4096-byte SSM limit (got ${Buffer.byteLength(value, "utf8")} bytes).`));
|
|
686
750
|
process.exit(1);
|
|
687
751
|
}
|
|
688
|
-
const
|
|
689
|
-
const
|
|
690
|
-
const companyUid = await getEntityUid(token, scope);
|
|
691
|
-
const scopeLabel = describeSecretsScope({
|
|
692
|
-
personal: scope.personal,
|
|
693
|
-
companySlug: scope.companySlug,
|
|
694
|
-
companyUid,
|
|
695
|
-
});
|
|
752
|
+
const target = await resolveSecretsTarget("secrets:write", "secrets set", secrets.opts());
|
|
753
|
+
const { token, companyUid, scopeLabel } = target;
|
|
696
754
|
const res = await vaultApiFetch({
|
|
697
755
|
token,
|
|
698
|
-
path:
|
|
756
|
+
path: target.basePath,
|
|
699
757
|
method: "POST",
|
|
700
758
|
body: {
|
|
701
759
|
name,
|
|
@@ -710,7 +768,7 @@ export function registerSecretsCommand(program) {
|
|
|
710
768
|
});
|
|
711
769
|
if (!res.ok) {
|
|
712
770
|
const body = (await res.json().catch(() => ({})));
|
|
713
|
-
console.error(chalk.red(`Failed to set secret: ${
|
|
771
|
+
console.error(chalk.red(`Failed to set secret: ${describeSecretsFailure(target, res.status, body, res.statusText)}`));
|
|
714
772
|
process.exit(1);
|
|
715
773
|
}
|
|
716
774
|
removeCacheEntry(companyUid, name);
|
|
@@ -730,7 +788,7 @@ export function registerSecretsCommand(program) {
|
|
|
730
788
|
.option("--reveal", "Include the decrypted secret value")
|
|
731
789
|
.action(async (name, opts) => {
|
|
732
790
|
try {
|
|
733
|
-
const cred = await
|
|
791
|
+
const cred = await resolveVaultCredentialForCapability("secrets:read", "secrets get");
|
|
734
792
|
if (cred.kind === "api-key") {
|
|
735
793
|
if (!opts.reveal) {
|
|
736
794
|
console.error(chalk.red("HQ_API_KEY is set; `hq secrets get` without --reveal is not supported for API keys. " +
|
|
@@ -749,7 +807,7 @@ export function registerSecretsCommand(program) {
|
|
|
749
807
|
console.error(chalk.red(highSecuritySandboxOnlyMessage(name)));
|
|
750
808
|
process.exit(1);
|
|
751
809
|
}
|
|
752
|
-
console.error(chalk.red(`Failed to get secret: ${
|
|
810
|
+
console.error(chalk.red(`Failed to get secret: ${describeSecretsFailure({ isApiKey: true }, res.status, body, res.statusText)}`));
|
|
753
811
|
process.exit(1);
|
|
754
812
|
}
|
|
755
813
|
const secret = typeof body.secret === "object" && body.secret !== null
|
|
@@ -851,7 +909,7 @@ export function registerSecretsCommand(program) {
|
|
|
851
909
|
.option("--quiet", "Suppress the present/absent line (use the exit code only)")
|
|
852
910
|
.action(async (name, opts) => {
|
|
853
911
|
try {
|
|
854
|
-
const token = await requireCognitoTokenForSecrets("secrets exists");
|
|
912
|
+
const token = await requireCognitoTokenForSecrets("secrets exists", "secrets:read");
|
|
855
913
|
const companyUid = await getEntityUid(token, scopeOpts(secrets.opts()));
|
|
856
914
|
const res = await vaultApiFetch({
|
|
857
915
|
token,
|
|
@@ -894,26 +952,20 @@ export function registerSecretsCommand(program) {
|
|
|
894
952
|
}
|
|
895
953
|
normalizedPrefix = normalized;
|
|
896
954
|
}
|
|
897
|
-
const
|
|
898
|
-
const
|
|
899
|
-
const companyUid = await getEntityUid(token, scope);
|
|
900
|
-
const scopeLabel = describeSecretsScope({
|
|
901
|
-
personal: scope.personal,
|
|
902
|
-
companySlug: scope.companySlug,
|
|
903
|
-
companyUid,
|
|
904
|
-
});
|
|
955
|
+
const target = await resolveSecretsTarget("secrets:read", "secrets list", secrets.opts());
|
|
956
|
+
const { scopeLabel } = target;
|
|
905
957
|
const query = {};
|
|
906
958
|
if (normalizedPrefix) {
|
|
907
959
|
query.prefix = normalizedPrefix;
|
|
908
960
|
}
|
|
909
961
|
const res = await vaultApiFetch({
|
|
910
|
-
token,
|
|
911
|
-
path:
|
|
962
|
+
token: target.token,
|
|
963
|
+
path: target.basePath,
|
|
912
964
|
query: Object.keys(query).length > 0 ? query : undefined,
|
|
913
965
|
});
|
|
914
966
|
if (!res.ok) {
|
|
915
|
-
const body = await res.json().catch(() => ({}));
|
|
916
|
-
console.error(chalk.red(`Failed to list secrets: ${
|
|
967
|
+
const body = (await res.json().catch(() => ({})));
|
|
968
|
+
console.error(chalk.red(`Failed to list secrets: ${describeSecretsFailure(target, res.status, body, res.statusText)}`));
|
|
917
969
|
process.exit(1);
|
|
918
970
|
}
|
|
919
971
|
const data = (await res.json());
|
|
@@ -968,7 +1020,7 @@ export function registerSecretsCommand(program) {
|
|
|
968
1020
|
console.error(chalk.red(`Invalid secret path '${secretPath}': must match ^[A-Z][A-Z0-9_]*(/[A-Z][A-Z0-9_]+)*$ (e.g. MY_KEY or PROD/DB_PASSWORD)`));
|
|
969
1021
|
process.exit(1);
|
|
970
1022
|
}
|
|
971
|
-
const token = await requireCognitoTokenForSecrets("secrets");
|
|
1023
|
+
const token = await requireCognitoTokenForSecrets("secrets policy get", "secrets:read");
|
|
972
1024
|
const companyUid = await getEntityUid(token, scopeOpts(secrets.opts()));
|
|
973
1025
|
const res = await vaultApiFetch({
|
|
974
1026
|
token,
|
|
@@ -1029,7 +1081,7 @@ export function registerSecretsCommand(program) {
|
|
|
1029
1081
|
console.error(chalk.red("Error: provide at least one of --tier or --lock-script."));
|
|
1030
1082
|
process.exit(1);
|
|
1031
1083
|
}
|
|
1032
|
-
const token = await requireCognitoTokenForSecrets("secrets");
|
|
1084
|
+
const token = await requireCognitoTokenForSecrets("secrets policy set", "secrets:write");
|
|
1033
1085
|
const companyUid = await getEntityUid(token, scopeOpts(secrets.opts()));
|
|
1034
1086
|
const res = await vaultApiFetch({
|
|
1035
1087
|
token,
|
|
@@ -1074,7 +1126,7 @@ export function registerSecretsCommand(program) {
|
|
|
1074
1126
|
process.exit(1);
|
|
1075
1127
|
}
|
|
1076
1128
|
const usage = await buildSecretUsage("exec", opts.script, opts.id, opts.attestation);
|
|
1077
|
-
const token = await requireCognitoTokenForSecrets("secrets");
|
|
1129
|
+
const token = await requireCognitoTokenForSecrets("secrets script approve", "secrets:write");
|
|
1078
1130
|
const companyUid = await getEntityUid(token, scopeOpts(secrets.opts()));
|
|
1079
1131
|
const res = await vaultApiFetch({
|
|
1080
1132
|
token,
|
|
@@ -1112,7 +1164,7 @@ export function registerSecretsCommand(program) {
|
|
|
1112
1164
|
console.error(chalk.red(`Invalid secret path '${secretPath}': must match ^[A-Z][A-Z0-9_]*(/[A-Z][A-Z0-9_]+)*$ (e.g. MY_KEY or PROD/DB_PASSWORD)`));
|
|
1113
1165
|
process.exit(1);
|
|
1114
1166
|
}
|
|
1115
|
-
const token = await requireCognitoTokenForSecrets("secrets");
|
|
1167
|
+
const token = await requireCognitoTokenForSecrets("secrets script revoke", "secrets:write");
|
|
1116
1168
|
const companyUid = await getEntityUid(token, scopeOpts(secrets.opts()));
|
|
1117
1169
|
const res = await vaultApiFetch({
|
|
1118
1170
|
token,
|
|
@@ -1143,7 +1195,7 @@ export function registerSecretsCommand(program) {
|
|
|
1143
1195
|
console.error(chalk.red(`Invalid secret path '${secretPath}': must match ^[A-Z][A-Z0-9_]*(/[A-Z][A-Z0-9_]+)*$ (e.g. MY_KEY or PROD/DB_PASSWORD)`));
|
|
1144
1196
|
process.exit(1);
|
|
1145
1197
|
}
|
|
1146
|
-
const token = await requireCognitoTokenForSecrets("secrets");
|
|
1198
|
+
const token = await requireCognitoTokenForSecrets("secrets script list", "secrets:read");
|
|
1147
1199
|
const companyUid = await getEntityUid(token, scopeOpts(secrets.opts()));
|
|
1148
1200
|
const res = await vaultApiFetch({
|
|
1149
1201
|
token,
|
|
@@ -1190,16 +1242,16 @@ export function registerSecretsCommand(program) {
|
|
|
1190
1242
|
return;
|
|
1191
1243
|
}
|
|
1192
1244
|
}
|
|
1193
|
-
const
|
|
1194
|
-
const companyUid =
|
|
1245
|
+
const target = await resolveSecretsTarget("secrets:write", "secrets delete", secrets.opts());
|
|
1246
|
+
const companyUid = target.companyUid;
|
|
1195
1247
|
const res = await vaultApiFetch({
|
|
1196
|
-
token,
|
|
1197
|
-
path:
|
|
1248
|
+
token: target.token,
|
|
1249
|
+
path: target.namePath(name),
|
|
1198
1250
|
method: "DELETE",
|
|
1199
1251
|
});
|
|
1200
1252
|
if (!res.ok) {
|
|
1201
|
-
const body = await res.json().catch(() => ({}));
|
|
1202
|
-
console.error(chalk.red(`Failed to delete secret: ${
|
|
1253
|
+
const body = (await res.json().catch(() => ({})));
|
|
1254
|
+
console.error(chalk.red(`Failed to delete secret: ${describeSecretsFailure(target, res.status, body, res.statusText)}`));
|
|
1203
1255
|
process.exit(1);
|
|
1204
1256
|
}
|
|
1205
1257
|
removeCacheEntry(companyUid, name);
|
|
@@ -1234,7 +1286,7 @@ export function registerSecretsCommand(program) {
|
|
|
1234
1286
|
process.exit(1);
|
|
1235
1287
|
}
|
|
1236
1288
|
const keys = parseSecretNameList(opts.only);
|
|
1237
|
-
const token = await requireCognitoTokenForSecrets("secrets");
|
|
1289
|
+
const token = await requireCognitoTokenForSecrets("secrets sandbox", "secrets:read");
|
|
1238
1290
|
const scope = scopeOpts(mergeScopeOpts(secrets.opts(), opts));
|
|
1239
1291
|
const companyUid = await getEntityUid(token, scope);
|
|
1240
1292
|
const client = new SandboxRunnerClient();
|
|
@@ -1287,7 +1339,7 @@ export function registerSecretsCommand(program) {
|
|
|
1287
1339
|
process.exit(1);
|
|
1288
1340
|
}
|
|
1289
1341
|
const keys = parseSecretNameList(_opts.only);
|
|
1290
|
-
const cred = await
|
|
1342
|
+
const cred = await resolveVaultCredentialForCapability("secrets:read", "secrets exec");
|
|
1291
1343
|
const companyUid = cred.kind === "api-key"
|
|
1292
1344
|
? "__api_key__"
|
|
1293
1345
|
: await getEntityUid(cred.token, scopeOpts(secrets.opts()));
|
|
@@ -1338,7 +1390,7 @@ export function registerSecretsCommand(program) {
|
|
|
1338
1390
|
console.error(chalk.yellow("stdout is a terminal — values redacted. Use: source <(hq secrets env --only KEY1,KEY2)"));
|
|
1339
1391
|
}
|
|
1340
1392
|
const keys = parseSecretNameList(opts.only);
|
|
1341
|
-
const cred = await
|
|
1393
|
+
const cred = await resolveVaultCredentialForCapability("secrets:read", "secrets env");
|
|
1342
1394
|
const companyUid = cred.kind === "api-key"
|
|
1343
1395
|
? "__api_key__"
|
|
1344
1396
|
: await getEntityUid(cred.token, scopeOpts(secrets.opts()));
|
|
@@ -1381,7 +1433,7 @@ export function registerSecretsCommand(program) {
|
|
|
1381
1433
|
console.error(chalk.red("Maximum expiry is 7 days (7d)."));
|
|
1382
1434
|
process.exit(1);
|
|
1383
1435
|
}
|
|
1384
|
-
const token = await requireCognitoTokenForSecrets("secrets");
|
|
1436
|
+
const token = await requireCognitoTokenForSecrets("secrets generate-link", "secrets:write");
|
|
1385
1437
|
const companyUid = await getEntityUid(token, scopeOpts(secrets.opts()));
|
|
1386
1438
|
const res = await vaultApiFetch({
|
|
1387
1439
|
token,
|
|
@@ -1427,11 +1479,10 @@ export function registerSecretsCommand(program) {
|
|
|
1427
1479
|
if (!principal) {
|
|
1428
1480
|
process.exit(1);
|
|
1429
1481
|
}
|
|
1430
|
-
const
|
|
1431
|
-
const companyUid = await getEntityUid(token, scopeOpts(secrets.opts()));
|
|
1482
|
+
const target = await resolveSecretsTarget("secrets:share", "secrets share", secrets.opts());
|
|
1432
1483
|
const res = await vaultApiFetch({
|
|
1433
|
-
token,
|
|
1434
|
-
path:
|
|
1484
|
+
token: target.token,
|
|
1485
|
+
path: target.aclPath("grant"),
|
|
1435
1486
|
method: "POST",
|
|
1436
1487
|
body: {
|
|
1437
1488
|
path,
|
|
@@ -1446,7 +1497,9 @@ export function registerSecretsCommand(program) {
|
|
|
1446
1497
|
console.error(chalk.red("Not authenticated — please run `hq login`"));
|
|
1447
1498
|
}
|
|
1448
1499
|
else if (res.status === 403) {
|
|
1449
|
-
console.error(chalk.red(
|
|
1500
|
+
console.error(chalk.red(target.isApiKey
|
|
1501
|
+
? formatApiKeyCapabilityDenial(body, "Not authorized to share this secret")
|
|
1502
|
+
: "Not authorized to share this secret"));
|
|
1450
1503
|
}
|
|
1451
1504
|
else if (res.status === 404) {
|
|
1452
1505
|
console.error(chalk.red("ACL record not found for this path — has the secret been created?"));
|
|
@@ -1484,11 +1537,10 @@ export function registerSecretsCommand(program) {
|
|
|
1484
1537
|
if (!principal) {
|
|
1485
1538
|
process.exit(1);
|
|
1486
1539
|
}
|
|
1487
|
-
const
|
|
1488
|
-
const companyUid = await getEntityUid(token, scopeOpts(secrets.opts()));
|
|
1540
|
+
const target = await resolveSecretsTarget("secrets:share", "secrets unshare", secrets.opts());
|
|
1489
1541
|
const res = await vaultApiFetch({
|
|
1490
|
-
token,
|
|
1491
|
-
path:
|
|
1542
|
+
token: target.token,
|
|
1543
|
+
path: target.aclPath("revoke"),
|
|
1492
1544
|
method: "POST",
|
|
1493
1545
|
body: {
|
|
1494
1546
|
path,
|
|
@@ -1502,7 +1554,9 @@ export function registerSecretsCommand(program) {
|
|
|
1502
1554
|
console.error(chalk.red("Not authenticated — please run `hq login`"));
|
|
1503
1555
|
}
|
|
1504
1556
|
else if (res.status === 403) {
|
|
1505
|
-
console.error(chalk.red(
|
|
1557
|
+
console.error(chalk.red(target.isApiKey
|
|
1558
|
+
? formatApiKeyCapabilityDenial(body, "Not authorized to modify this secret's ACL")
|
|
1559
|
+
: "Not authorized to modify this secret's ACL"));
|
|
1506
1560
|
}
|
|
1507
1561
|
else if (res.status === 404) {
|
|
1508
1562
|
console.log(chalk.green(`Grant already absent for '${path}' / ${describeSecretAclPrincipal(principal)}`));
|
|
@@ -1533,12 +1587,11 @@ export function registerSecretsCommand(program) {
|
|
|
1533
1587
|
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)`));
|
|
1534
1588
|
process.exit(1);
|
|
1535
1589
|
}
|
|
1536
|
-
const
|
|
1537
|
-
const companyUid = await getEntityUid(token, scopeOpts(secrets.opts()));
|
|
1590
|
+
const target = await resolveSecretsTarget("secrets:share", "secrets acl", secrets.opts());
|
|
1538
1591
|
const secretPath = path;
|
|
1539
1592
|
const res = await vaultApiFetch({
|
|
1540
|
-
token,
|
|
1541
|
-
path:
|
|
1593
|
+
token: target.token,
|
|
1594
|
+
path: target.aclPath(),
|
|
1542
1595
|
query: { path: secretPath },
|
|
1543
1596
|
});
|
|
1544
1597
|
if (!res.ok) {
|
|
@@ -1547,7 +1600,9 @@ export function registerSecretsCommand(program) {
|
|
|
1547
1600
|
console.error(chalk.red("Not authenticated — please run `hq login`"));
|
|
1548
1601
|
}
|
|
1549
1602
|
else if (res.status === 403) {
|
|
1550
|
-
console.error(chalk.red(
|
|
1603
|
+
console.error(chalk.red(target.isApiKey
|
|
1604
|
+
? formatApiKeyCapabilityDenial(body, "Not authorized to view this secret's ACL")
|
|
1605
|
+
: "Not authorized to view this secret's ACL"));
|
|
1551
1606
|
}
|
|
1552
1607
|
else if (res.status === 404) {
|
|
1553
1608
|
console.error(chalk.red(`No ACL record exists for '${path}'`));
|
package/dist/main.js
CHANGED
|
@@ -56,6 +56,7 @@ import { registerAgentsCommand } from "./commands/agents.js";
|
|
|
56
56
|
import { registerOutpostsCommand } from "./commands/outposts.js";
|
|
57
57
|
import { registerBillingCommand } from "./commands/billing.js";
|
|
58
58
|
import { registerDbCommand } from "./commands/db.js";
|
|
59
|
+
import { registerCoreCommands } from "./commands/core.js";
|
|
59
60
|
import { sanitizeArgv } from "./utils/feedback-diagnostics.js";
|
|
60
61
|
import { environmentalFsErrorMessage } from "./utils/environmental-error.js";
|
|
61
62
|
import { isExpectedUserError } from "./utils/expected-cli-error.js";
|
|
@@ -220,6 +221,11 @@ registerOutpostsCommand(program);
|
|
|
220
221
|
// mint a shareable Stripe card-capture link — the client side of the paid-
|
|
221
222
|
// provisioning gate for agents & Outposts.
|
|
222
223
|
registerBillingCommand(program);
|
|
224
|
+
// HQ scaffold scripts hosted by the CLI (hidden group — `hq core …`). Not a
|
|
225
|
+
// public surface: every entry is invoked by an HQ skill, hook, or forwarder, and
|
|
226
|
+
// the source-root entries are maintainer tools that must never touch a live
|
|
227
|
+
// install. Registered from a manifest in the module, not wired per script here.
|
|
228
|
+
registerCoreCommands(program);
|
|
223
229
|
program.hook("preAction", async () => {
|
|
224
230
|
await emitCliSessionStarted();
|
|
225
231
|
});
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import type { Command } from "commander";
|
|
2
|
+
import { type ApiKeyCapability } from "./resolve-vault-credential.js";
|
|
3
|
+
/**
|
|
4
|
+
* Capability gate for a whole command group.
|
|
5
|
+
*
|
|
6
|
+
* Command groups such as `hq files`, `hq agents`, and `hq members` call
|
|
7
|
+
* `ensureCognitoToken()` from many subcommand actions. Without a gate, a
|
|
8
|
+
* caller who exported `HQ_API_KEY` for CI silently gets their cached Cognito
|
|
9
|
+
* session instead — the key's capability scope is ignored, which is the exact
|
|
10
|
+
* fail-open the capability model exists to prevent. One inherited `preAction`
|
|
11
|
+
* hook on the group covers every current and future subcommand rather than
|
|
12
|
+
* every `ensureCognitoToken()` call site.
|
|
13
|
+
*/
|
|
14
|
+
/**
|
|
15
|
+
* A capability that names the surface correctly but whose specific route has
|
|
16
|
+
* no `/v1/keys` parallel in hq-pro — e.g. `hq files acl` is a `files:read`
|
|
17
|
+
* shape, yet only `/files/{companyUid}/acl` (Cognito-only) serves it. The gate
|
|
18
|
+
* still names the capability so the message explains WHY the surface exists,
|
|
19
|
+
* and refuses because the wire does not.
|
|
20
|
+
*/
|
|
21
|
+
export interface ApiKeyUnroutedCapability {
|
|
22
|
+
capability: ApiKeyCapability;
|
|
23
|
+
routeAvailable: false;
|
|
24
|
+
}
|
|
25
|
+
/** `null` = no capability governs the surface at all. */
|
|
26
|
+
export type ApiKeyCapabilityRule = ApiKeyCapability | null | ApiKeyUnroutedCapability;
|
|
27
|
+
export interface ApiKeyCapabilityRules {
|
|
28
|
+
/** Rule for subcommands with no specific override. */
|
|
29
|
+
default: ApiKeyCapabilityRule;
|
|
30
|
+
/**
|
|
31
|
+
* Overrides keyed by the subcommand path RELATIVE to the gated group, e.g.
|
|
32
|
+
* `share` for `hq files share`, `policy get` for `hq secrets policy get`.
|
|
33
|
+
*/
|
|
34
|
+
bySubcommand?: Record<string, ApiKeyCapabilityRule>;
|
|
35
|
+
}
|
|
36
|
+
/** Full user-facing command path, e.g. `hq files share`. */
|
|
37
|
+
export declare function commandLabel(command: Command): string;
|
|
38
|
+
export declare function capabilityForAction(group: Command, actionCommand: Command, rules: ApiKeyCapabilityRules): ApiKeyCapabilityRule;
|
|
39
|
+
/**
|
|
40
|
+
* Attach the capability gate to `group`. Commander runs `preAction` hooks
|
|
41
|
+
* collected from the actioned command AND its ancestors, so one hook here
|
|
42
|
+
* fires for every nested subcommand.
|
|
43
|
+
*/
|
|
44
|
+
export declare function gateApiKeyCapabilities(group: Command, rules: ApiKeyCapabilityRules): void;
|
|
45
|
+
//# sourceMappingURL=api-key-command-gate.d.ts.map
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
import { requireApiKeyCapability, } from "./resolve-vault-credential.js";
|
|
2
|
+
/** Full user-facing command path, e.g. `hq files share`. */
|
|
3
|
+
export function commandLabel(command) {
|
|
4
|
+
const parts = [];
|
|
5
|
+
let current = command;
|
|
6
|
+
while (current) {
|
|
7
|
+
const name = current.name();
|
|
8
|
+
if (name)
|
|
9
|
+
parts.unshift(name);
|
|
10
|
+
current = current.parent ?? undefined;
|
|
11
|
+
}
|
|
12
|
+
if (parts[0] !== "hq")
|
|
13
|
+
parts.unshift("hq");
|
|
14
|
+
return parts.join(" ");
|
|
15
|
+
}
|
|
16
|
+
/** Subcommand path relative to `group` (empty when `command` IS the group). */
|
|
17
|
+
function relativePath(group, command) {
|
|
18
|
+
const parts = [];
|
|
19
|
+
let current = command;
|
|
20
|
+
while (current && current !== group) {
|
|
21
|
+
const name = current.name();
|
|
22
|
+
if (name)
|
|
23
|
+
parts.unshift(name);
|
|
24
|
+
current = current.parent ?? undefined;
|
|
25
|
+
}
|
|
26
|
+
return parts.join(" ");
|
|
27
|
+
}
|
|
28
|
+
export function capabilityForAction(group, actionCommand, rules) {
|
|
29
|
+
const path = relativePath(group, actionCommand);
|
|
30
|
+
const override = rules.bySubcommand?.[path];
|
|
31
|
+
return override !== undefined ? override : rules.default;
|
|
32
|
+
}
|
|
33
|
+
/**
|
|
34
|
+
* Attach the capability gate to `group`. Commander runs `preAction` hooks
|
|
35
|
+
* collected from the actioned command AND its ancestors, so one hook here
|
|
36
|
+
* fires for every nested subcommand.
|
|
37
|
+
*/
|
|
38
|
+
export function gateApiKeyCapabilities(group, rules) {
|
|
39
|
+
group.hook("preAction", (_thisCommand, actionCommand) => {
|
|
40
|
+
const rule = capabilityForAction(group, actionCommand, rules);
|
|
41
|
+
const label = commandLabel(actionCommand);
|
|
42
|
+
if (rule !== null && typeof rule === "object") {
|
|
43
|
+
requireApiKeyCapability(rule.capability, label, {
|
|
44
|
+
routeAvailable: false,
|
|
45
|
+
});
|
|
46
|
+
return;
|
|
47
|
+
}
|
|
48
|
+
requireApiKeyCapability(rule, label);
|
|
49
|
+
});
|
|
50
|
+
}
|
|
51
|
+
//# sourceMappingURL=api-key-command-gate.js.map
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Root resolution for CLI-hosted HQ scaffold code.
|
|
3
|
+
*
|
|
4
|
+
* When shell that used to live inside the HQ scaffold moves into this npm
|
|
5
|
+
* package, it loses the one thing it relied on: `BASH_SOURCE`/`$0` pointed at
|
|
6
|
+
* the user's live HQ tree, so "where am I" and "where is HQ" were the same
|
|
7
|
+
* answer. Inside a globally installed package they are three different answers,
|
|
8
|
+
* and conflating any two of them silently corrupts the wrong tree.
|
|
9
|
+
*
|
|
10
|
+
* liveRoot The user's HQ installation — companies/, workspace/, personal/,
|
|
11
|
+
* the installed .claude/ and core/. This is runtime state. Hidden
|
|
12
|
+
* runtime commands operate here.
|
|
13
|
+
*
|
|
14
|
+
* packageRoot This npm package's own directory, where bundled scaffold assets
|
|
15
|
+
* ship. Read-only in practice: a global install may sit in a
|
|
16
|
+
* root-owned prefix. NEVER usable as liveRoot.
|
|
17
|
+
*
|
|
18
|
+
* There is deliberately no third "source root". Release-source tooling — the
|
|
19
|
+
* linters, generators, and validators that rewrite checked-in artifacts — stays
|
|
20
|
+
* scaffold-owned, so nothing here ever needs to resolve an hq-core checkout. See
|
|
21
|
+
* the scaffold-vs-cli-code-ownership policy for why.
|
|
22
|
+
*
|
|
23
|
+
* The invariant that matters most: `resolveLiveRoot` never consults
|
|
24
|
+
* `import.meta.url`. A relocated script asking "where is HQ" must get the live
|
|
25
|
+
* tree, not the package it happens to be bundled in.
|
|
26
|
+
*/
|
|
27
|
+
/** Where bundled scaffold assets live, relative to the package root. */
|
|
28
|
+
export declare const BUNDLED_ASSET_DIR: string;
|
|
29
|
+
/**
|
|
30
|
+
* Does this directory look like an HQ tree root?
|
|
31
|
+
*
|
|
32
|
+
* `companies/` is the load-bearing marker — it is the source of truth for
|
|
33
|
+
* tenants and exists in every real install. The second clause tolerates the
|
|
34
|
+
* three shapes a tree can legitimately be in: a full install, a scaffold
|
|
35
|
+
* checkout without a personal overlay, and a personal-only overlay.
|
|
36
|
+
*/
|
|
37
|
+
export declare function isHqRoot(dir: string): boolean;
|
|
38
|
+
/**
|
|
39
|
+
* This package's installed root — the directory holding its `package.json`.
|
|
40
|
+
*
|
|
41
|
+
* Walks up from the compiled module (`dist/utils/hq-roots.js`) until it finds a
|
|
42
|
+
* `package.json` whose name matches, rather than hard-coding `..` hops, so the
|
|
43
|
+
* answer survives a change in output layout.
|
|
44
|
+
*/
|
|
45
|
+
export declare function packageRoot(): string;
|
|
46
|
+
/** Reset the memoized package root. Tests only. */
|
|
47
|
+
export declare function __resetPackageRootCache(): void;
|
|
48
|
+
export type LiveRootOptions = {
|
|
49
|
+
/** Explicit `--hq-root` value. Highest precedence. */
|
|
50
|
+
hqRoot?: string;
|
|
51
|
+
/** Start the walk-up here instead of `process.cwd()`. Tests only. */
|
|
52
|
+
cwd?: string;
|
|
53
|
+
/** Environment to read. Tests only. */
|
|
54
|
+
env?: NodeJS.ProcessEnv;
|
|
55
|
+
};
|
|
56
|
+
/**
|
|
57
|
+
* Resolve the user's live HQ installation.
|
|
58
|
+
*
|
|
59
|
+
* Precedence: `--hq-root` → `HQ_ROOT` → `CLAUDE_PROJECT_DIR` → walk up from cwd.
|
|
60
|
+
*
|
|
61
|
+
* ## An explicit root is trusted; a discovered one must be recognized
|
|
62
|
+
*
|
|
63
|
+
* The FIRST explicit candidate present wins outright — there is no fall-through
|
|
64
|
+
* past it, because silently operating on a different tree than the caller named
|
|
65
|
+
* is the worst available outcome.
|
|
66
|
+
*
|
|
67
|
+
* That explicit candidate is checked only for the two things that are always
|
|
68
|
+
* wrong: it must be a directory, and it must not be inside {@link packageRoot}
|
|
69
|
+
* (that is this package, not anyone's HQ). It is deliberately NOT required to
|
|
70
|
+
* satisfy {@link isHqRoot}.
|
|
71
|
+
*
|
|
72
|
+
* Requiring markers here was tried and was wrong: the scripts these roots feed
|
|
73
|
+
* accepted any `HQ_ROOT` before they moved, and several callers legitimately pass
|
|
74
|
+
* a minimal tree — e.g. `rebuild-threads-index` against a fixture holding nothing
|
|
75
|
+
* but `workspace/threads/`. Enforcing markers on an explicit value turned a
|
|
76
|
+
* relocation into a silent contract change and broke those callers.
|
|
77
|
+
*
|
|
78
|
+
* The cwd walk is the opposite case: there the marker check is the only thing
|
|
79
|
+
* that can identify a root at all, so it stays mandatory.
|
|
80
|
+
*/
|
|
81
|
+
export declare function resolveLiveRoot(opts?: LiveRootOptions): string;
|
|
82
|
+
/**
|
|
83
|
+
* Absolute path to a scaffold asset bundled in this package.
|
|
84
|
+
*
|
|
85
|
+
* Refuses to resolve outside the asset directory so a manifest entry can never
|
|
86
|
+
* be coaxed into executing an arbitrary path, and fails loudly when the asset
|
|
87
|
+
* is missing — an absent asset means the packlist dropped it, which is a
|
|
88
|
+
* packaging bug worth surfacing rather than a runtime fallback.
|
|
89
|
+
*/
|
|
90
|
+
export declare function resolveBundledAsset(relativePath: string): string;
|
|
91
|
+
//# sourceMappingURL=hq-roots.d.ts.map
|