@indigoai-us/hq-cli 5.77.13 → 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 +34 -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 +51 -2
- package/dist/commands/pkg-install.js +43 -0
- package/dist/commands/run.js +8 -0
- package/dist/commands/secrets.js +125 -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 -2247
- package/src/commands/pack-update-cache.test.ts +0 -149
- 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 -71
- package/src/commands/pkg-install.ts +0 -215
- 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 -184
- 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 -2037
- package/src/commands/secrets.ts +0 -2240
- 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,8 +788,13 @@ 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") {
|
|
793
|
+
if (!opts.reveal) {
|
|
794
|
+
console.error(chalk.red("HQ_API_KEY is set; `hq secrets get` without --reveal is not supported for API keys. " +
|
|
795
|
+
"Use --reveal to fetch the value, or unset HQ_API_KEY for metadata-only reads."));
|
|
796
|
+
process.exit(1);
|
|
797
|
+
}
|
|
735
798
|
const res = await vaultApiFetch({
|
|
736
799
|
token: cred.token,
|
|
737
800
|
path: "/v1/keys/secrets/fetch",
|
|
@@ -744,7 +807,7 @@ export function registerSecretsCommand(program) {
|
|
|
744
807
|
console.error(chalk.red(highSecuritySandboxOnlyMessage(name)));
|
|
745
808
|
process.exit(1);
|
|
746
809
|
}
|
|
747
|
-
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)}`));
|
|
748
811
|
process.exit(1);
|
|
749
812
|
}
|
|
750
813
|
const secret = typeof body.secret === "object" && body.secret !== null
|
|
@@ -846,7 +909,7 @@ export function registerSecretsCommand(program) {
|
|
|
846
909
|
.option("--quiet", "Suppress the present/absent line (use the exit code only)")
|
|
847
910
|
.action(async (name, opts) => {
|
|
848
911
|
try {
|
|
849
|
-
const token = await requireCognitoTokenForSecrets("secrets exists");
|
|
912
|
+
const token = await requireCognitoTokenForSecrets("secrets exists", "secrets:read");
|
|
850
913
|
const companyUid = await getEntityUid(token, scopeOpts(secrets.opts()));
|
|
851
914
|
const res = await vaultApiFetch({
|
|
852
915
|
token,
|
|
@@ -889,26 +952,20 @@ export function registerSecretsCommand(program) {
|
|
|
889
952
|
}
|
|
890
953
|
normalizedPrefix = normalized;
|
|
891
954
|
}
|
|
892
|
-
const
|
|
893
|
-
const
|
|
894
|
-
const companyUid = await getEntityUid(token, scope);
|
|
895
|
-
const scopeLabel = describeSecretsScope({
|
|
896
|
-
personal: scope.personal,
|
|
897
|
-
companySlug: scope.companySlug,
|
|
898
|
-
companyUid,
|
|
899
|
-
});
|
|
955
|
+
const target = await resolveSecretsTarget("secrets:read", "secrets list", secrets.opts());
|
|
956
|
+
const { scopeLabel } = target;
|
|
900
957
|
const query = {};
|
|
901
958
|
if (normalizedPrefix) {
|
|
902
959
|
query.prefix = normalizedPrefix;
|
|
903
960
|
}
|
|
904
961
|
const res = await vaultApiFetch({
|
|
905
|
-
token,
|
|
906
|
-
path:
|
|
962
|
+
token: target.token,
|
|
963
|
+
path: target.basePath,
|
|
907
964
|
query: Object.keys(query).length > 0 ? query : undefined,
|
|
908
965
|
});
|
|
909
966
|
if (!res.ok) {
|
|
910
|
-
const body = await res.json().catch(() => ({}));
|
|
911
|
-
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)}`));
|
|
912
969
|
process.exit(1);
|
|
913
970
|
}
|
|
914
971
|
const data = (await res.json());
|
|
@@ -963,7 +1020,7 @@ export function registerSecretsCommand(program) {
|
|
|
963
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)`));
|
|
964
1021
|
process.exit(1);
|
|
965
1022
|
}
|
|
966
|
-
const token = await requireCognitoTokenForSecrets("secrets");
|
|
1023
|
+
const token = await requireCognitoTokenForSecrets("secrets policy get", "secrets:read");
|
|
967
1024
|
const companyUid = await getEntityUid(token, scopeOpts(secrets.opts()));
|
|
968
1025
|
const res = await vaultApiFetch({
|
|
969
1026
|
token,
|
|
@@ -1024,7 +1081,7 @@ export function registerSecretsCommand(program) {
|
|
|
1024
1081
|
console.error(chalk.red("Error: provide at least one of --tier or --lock-script."));
|
|
1025
1082
|
process.exit(1);
|
|
1026
1083
|
}
|
|
1027
|
-
const token = await requireCognitoTokenForSecrets("secrets");
|
|
1084
|
+
const token = await requireCognitoTokenForSecrets("secrets policy set", "secrets:write");
|
|
1028
1085
|
const companyUid = await getEntityUid(token, scopeOpts(secrets.opts()));
|
|
1029
1086
|
const res = await vaultApiFetch({
|
|
1030
1087
|
token,
|
|
@@ -1069,7 +1126,7 @@ export function registerSecretsCommand(program) {
|
|
|
1069
1126
|
process.exit(1);
|
|
1070
1127
|
}
|
|
1071
1128
|
const usage = await buildSecretUsage("exec", opts.script, opts.id, opts.attestation);
|
|
1072
|
-
const token = await requireCognitoTokenForSecrets("secrets");
|
|
1129
|
+
const token = await requireCognitoTokenForSecrets("secrets script approve", "secrets:write");
|
|
1073
1130
|
const companyUid = await getEntityUid(token, scopeOpts(secrets.opts()));
|
|
1074
1131
|
const res = await vaultApiFetch({
|
|
1075
1132
|
token,
|
|
@@ -1107,7 +1164,7 @@ export function registerSecretsCommand(program) {
|
|
|
1107
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)`));
|
|
1108
1165
|
process.exit(1);
|
|
1109
1166
|
}
|
|
1110
|
-
const token = await requireCognitoTokenForSecrets("secrets");
|
|
1167
|
+
const token = await requireCognitoTokenForSecrets("secrets script revoke", "secrets:write");
|
|
1111
1168
|
const companyUid = await getEntityUid(token, scopeOpts(secrets.opts()));
|
|
1112
1169
|
const res = await vaultApiFetch({
|
|
1113
1170
|
token,
|
|
@@ -1138,7 +1195,7 @@ export function registerSecretsCommand(program) {
|
|
|
1138
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)`));
|
|
1139
1196
|
process.exit(1);
|
|
1140
1197
|
}
|
|
1141
|
-
const token = await requireCognitoTokenForSecrets("secrets");
|
|
1198
|
+
const token = await requireCognitoTokenForSecrets("secrets script list", "secrets:read");
|
|
1142
1199
|
const companyUid = await getEntityUid(token, scopeOpts(secrets.opts()));
|
|
1143
1200
|
const res = await vaultApiFetch({
|
|
1144
1201
|
token,
|
|
@@ -1185,16 +1242,16 @@ export function registerSecretsCommand(program) {
|
|
|
1185
1242
|
return;
|
|
1186
1243
|
}
|
|
1187
1244
|
}
|
|
1188
|
-
const
|
|
1189
|
-
const companyUid =
|
|
1245
|
+
const target = await resolveSecretsTarget("secrets:write", "secrets delete", secrets.opts());
|
|
1246
|
+
const companyUid = target.companyUid;
|
|
1190
1247
|
const res = await vaultApiFetch({
|
|
1191
|
-
token,
|
|
1192
|
-
path:
|
|
1248
|
+
token: target.token,
|
|
1249
|
+
path: target.namePath(name),
|
|
1193
1250
|
method: "DELETE",
|
|
1194
1251
|
});
|
|
1195
1252
|
if (!res.ok) {
|
|
1196
|
-
const body = await res.json().catch(() => ({}));
|
|
1197
|
-
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)}`));
|
|
1198
1255
|
process.exit(1);
|
|
1199
1256
|
}
|
|
1200
1257
|
removeCacheEntry(companyUid, name);
|
|
@@ -1229,7 +1286,7 @@ export function registerSecretsCommand(program) {
|
|
|
1229
1286
|
process.exit(1);
|
|
1230
1287
|
}
|
|
1231
1288
|
const keys = parseSecretNameList(opts.only);
|
|
1232
|
-
const token = await requireCognitoTokenForSecrets("secrets");
|
|
1289
|
+
const token = await requireCognitoTokenForSecrets("secrets sandbox", "secrets:read");
|
|
1233
1290
|
const scope = scopeOpts(mergeScopeOpts(secrets.opts(), opts));
|
|
1234
1291
|
const companyUid = await getEntityUid(token, scope);
|
|
1235
1292
|
const client = new SandboxRunnerClient();
|
|
@@ -1282,7 +1339,7 @@ export function registerSecretsCommand(program) {
|
|
|
1282
1339
|
process.exit(1);
|
|
1283
1340
|
}
|
|
1284
1341
|
const keys = parseSecretNameList(_opts.only);
|
|
1285
|
-
const cred = await
|
|
1342
|
+
const cred = await resolveVaultCredentialForCapability("secrets:read", "secrets exec");
|
|
1286
1343
|
const companyUid = cred.kind === "api-key"
|
|
1287
1344
|
? "__api_key__"
|
|
1288
1345
|
: await getEntityUid(cred.token, scopeOpts(secrets.opts()));
|
|
@@ -1333,7 +1390,7 @@ export function registerSecretsCommand(program) {
|
|
|
1333
1390
|
console.error(chalk.yellow("stdout is a terminal — values redacted. Use: source <(hq secrets env --only KEY1,KEY2)"));
|
|
1334
1391
|
}
|
|
1335
1392
|
const keys = parseSecretNameList(opts.only);
|
|
1336
|
-
const cred = await
|
|
1393
|
+
const cred = await resolveVaultCredentialForCapability("secrets:read", "secrets env");
|
|
1337
1394
|
const companyUid = cred.kind === "api-key"
|
|
1338
1395
|
? "__api_key__"
|
|
1339
1396
|
: await getEntityUid(cred.token, scopeOpts(secrets.opts()));
|
|
@@ -1376,7 +1433,7 @@ export function registerSecretsCommand(program) {
|
|
|
1376
1433
|
console.error(chalk.red("Maximum expiry is 7 days (7d)."));
|
|
1377
1434
|
process.exit(1);
|
|
1378
1435
|
}
|
|
1379
|
-
const token = await requireCognitoTokenForSecrets("secrets");
|
|
1436
|
+
const token = await requireCognitoTokenForSecrets("secrets generate-link", "secrets:write");
|
|
1380
1437
|
const companyUid = await getEntityUid(token, scopeOpts(secrets.opts()));
|
|
1381
1438
|
const res = await vaultApiFetch({
|
|
1382
1439
|
token,
|
|
@@ -1422,11 +1479,10 @@ export function registerSecretsCommand(program) {
|
|
|
1422
1479
|
if (!principal) {
|
|
1423
1480
|
process.exit(1);
|
|
1424
1481
|
}
|
|
1425
|
-
const
|
|
1426
|
-
const companyUid = await getEntityUid(token, scopeOpts(secrets.opts()));
|
|
1482
|
+
const target = await resolveSecretsTarget("secrets:share", "secrets share", secrets.opts());
|
|
1427
1483
|
const res = await vaultApiFetch({
|
|
1428
|
-
token,
|
|
1429
|
-
path:
|
|
1484
|
+
token: target.token,
|
|
1485
|
+
path: target.aclPath("grant"),
|
|
1430
1486
|
method: "POST",
|
|
1431
1487
|
body: {
|
|
1432
1488
|
path,
|
|
@@ -1441,7 +1497,9 @@ export function registerSecretsCommand(program) {
|
|
|
1441
1497
|
console.error(chalk.red("Not authenticated — please run `hq login`"));
|
|
1442
1498
|
}
|
|
1443
1499
|
else if (res.status === 403) {
|
|
1444
|
-
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"));
|
|
1445
1503
|
}
|
|
1446
1504
|
else if (res.status === 404) {
|
|
1447
1505
|
console.error(chalk.red("ACL record not found for this path — has the secret been created?"));
|
|
@@ -1479,11 +1537,10 @@ export function registerSecretsCommand(program) {
|
|
|
1479
1537
|
if (!principal) {
|
|
1480
1538
|
process.exit(1);
|
|
1481
1539
|
}
|
|
1482
|
-
const
|
|
1483
|
-
const companyUid = await getEntityUid(token, scopeOpts(secrets.opts()));
|
|
1540
|
+
const target = await resolveSecretsTarget("secrets:share", "secrets unshare", secrets.opts());
|
|
1484
1541
|
const res = await vaultApiFetch({
|
|
1485
|
-
token,
|
|
1486
|
-
path:
|
|
1542
|
+
token: target.token,
|
|
1543
|
+
path: target.aclPath("revoke"),
|
|
1487
1544
|
method: "POST",
|
|
1488
1545
|
body: {
|
|
1489
1546
|
path,
|
|
@@ -1497,7 +1554,9 @@ export function registerSecretsCommand(program) {
|
|
|
1497
1554
|
console.error(chalk.red("Not authenticated — please run `hq login`"));
|
|
1498
1555
|
}
|
|
1499
1556
|
else if (res.status === 403) {
|
|
1500
|
-
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"));
|
|
1501
1560
|
}
|
|
1502
1561
|
else if (res.status === 404) {
|
|
1503
1562
|
console.log(chalk.green(`Grant already absent for '${path}' / ${describeSecretAclPrincipal(principal)}`));
|
|
@@ -1528,12 +1587,11 @@ export function registerSecretsCommand(program) {
|
|
|
1528
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)`));
|
|
1529
1588
|
process.exit(1);
|
|
1530
1589
|
}
|
|
1531
|
-
const
|
|
1532
|
-
const companyUid = await getEntityUid(token, scopeOpts(secrets.opts()));
|
|
1590
|
+
const target = await resolveSecretsTarget("secrets:share", "secrets acl", secrets.opts());
|
|
1533
1591
|
const secretPath = path;
|
|
1534
1592
|
const res = await vaultApiFetch({
|
|
1535
|
-
token,
|
|
1536
|
-
path:
|
|
1593
|
+
token: target.token,
|
|
1594
|
+
path: target.aclPath(),
|
|
1537
1595
|
query: { path: secretPath },
|
|
1538
1596
|
});
|
|
1539
1597
|
if (!res.ok) {
|
|
@@ -1542,7 +1600,9 @@ export function registerSecretsCommand(program) {
|
|
|
1542
1600
|
console.error(chalk.red("Not authenticated — please run `hq login`"));
|
|
1543
1601
|
}
|
|
1544
1602
|
else if (res.status === 403) {
|
|
1545
|
-
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"));
|
|
1546
1606
|
}
|
|
1547
1607
|
else if (res.status === 404) {
|
|
1548
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
|