@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.
Files changed (308) hide show
  1. package/CHANGELOG.md +23 -0
  2. package/assets/scaffold/core/scripts/archive-old-threads.sh +86 -0
  3. package/assets/scaffold/core/scripts/backfill-company-skill-mirrors.sh +73 -0
  4. package/assets/scaffold/core/scripts/backfill-workspace-mirror.sh +51 -0
  5. package/assets/scaffold/core/scripts/codex-skill-bridge.sh +550 -0
  6. package/assets/scaffold/core/scripts/detect-stale-core-policy-mirror.sh +185 -0
  7. package/assets/scaffold/core/scripts/hq-status-summary.sh +187 -0
  8. package/assets/scaffold/core/scripts/ontology-readme-drift.sh +264 -0
  9. package/assets/scaffold/core/scripts/qmd-reindex-after-sync.sh +93 -0
  10. package/assets/scaffold/core/scripts/rebuild-all-indexes.sh +69 -0
  11. package/assets/scaffold/core/scripts/rebuild-companies-index.sh +206 -0
  12. package/assets/scaffold/core/scripts/rebuild-company-knowledge-index.sh +127 -0
  13. package/assets/scaffold/core/scripts/rebuild-orchestrator-index.sh +86 -0
  14. package/assets/scaffold/core/scripts/rebuild-projects-index.sh +105 -0
  15. package/assets/scaffold/core/scripts/rebuild-public-knowledge-index.sh +83 -0
  16. package/assets/scaffold/core/scripts/rebuild-reports-index.sh +114 -0
  17. package/assets/scaffold/core/scripts/rebuild-social-drafts-index.sh +135 -0
  18. package/assets/scaffold/core/scripts/rebuild-threads-index.sh +82 -0
  19. package/assets/scaffold/core/scripts/rebuild-workers-index.sh +97 -0
  20. package/assets/scaffold/core/scripts/resize-screenshot.sh +47 -0
  21. package/assets/scaffold/core/scripts/token-usage-report.sh +254 -0
  22. package/assets/scaffold/core/scripts/worktree.sh +136 -0
  23. package/dist/commands/agents.js +28 -12
  24. package/dist/commands/api-keys.js +100 -20
  25. package/dist/commands/core.d.ts +90 -0
  26. package/dist/commands/core.js +279 -0
  27. package/dist/commands/files-browse.d.ts +21 -0
  28. package/dist/commands/files-browse.js +145 -24
  29. package/dist/commands/files.js +33 -7
  30. package/dist/commands/members.js +16 -5
  31. package/dist/commands/pack-install.d.ts +14 -0
  32. package/dist/commands/pack-install.js +42 -2
  33. package/dist/commands/pkg-install.js +39 -2
  34. package/dist/commands/run.js +6 -2
  35. package/dist/commands/secrets.js +120 -65
  36. package/dist/main.js +6 -0
  37. package/dist/utils/api-key-command-gate.d.ts +45 -0
  38. package/dist/utils/api-key-command-gate.js +51 -0
  39. package/dist/utils/hq-roots.d.ts +91 -0
  40. package/dist/utils/hq-roots.js +212 -0
  41. package/dist/utils/resolve-vault-credential.d.ts +63 -3
  42. package/dist/utils/resolve-vault-credential.js +152 -6
  43. package/dist/utils/run-bundled-script.d.ts +66 -0
  44. package/dist/utils/run-bundled-script.js +80 -0
  45. package/dist/utils/vault-api.d.ts +21 -0
  46. package/dist/utils/vault-api.js +74 -3
  47. package/package.json +7 -1
  48. package/.claude/policies/hq-cli-interactive-reads-use-sts-vend.md +0 -49
  49. package/.github/workflows/ci.yml +0 -46
  50. package/.github/workflows/publish.yml +0 -36
  51. package/.github/workflows/scripts/smoke-test-pkg.sh +0 -97
  52. package/e2e/cli.test.ts +0 -128
  53. package/eslint.config.js +0 -37
  54. package/pnpm-workspace.yaml +0 -4
  55. package/scripts/chmod-bins.mjs +0 -49
  56. package/scripts/generate-dsn.mjs +0 -26
  57. package/scripts/generate-scan-packages-table.mjs +0 -113
  58. package/scripts/smoke-sources-signals.sh +0 -103
  59. package/src/bin/hq-auth-refresh.ts +0 -43
  60. package/src/cli-version.ts +0 -13
  61. package/src/commands/__fixtures__/make-tar.ts +0 -126
  62. package/src/commands/_patterns.ts +0 -10
  63. package/src/commands/add.ts +0 -75
  64. package/src/commands/agents.test.ts +0 -527
  65. package/src/commands/agents.ts +0 -923
  66. package/src/commands/api-keys.test.ts +0 -291
  67. package/src/commands/api-keys.ts +0 -382
  68. package/src/commands/artifact-verify.test.ts +0 -177
  69. package/src/commands/auth.test.ts +0 -82
  70. package/src/commands/auth.ts +0 -213
  71. package/src/commands/billing.test.ts +0 -158
  72. package/src/commands/billing.ts +0 -146
  73. package/src/commands/channels.test.ts +0 -74
  74. package/src/commands/channels.ts +0 -91
  75. package/src/commands/cloud-demote.test.ts +0 -401
  76. package/src/commands/cloud-demote.ts +0 -278
  77. package/src/commands/cloud-provision.test.ts +0 -1117
  78. package/src/commands/cloud-provision.ts +0 -942
  79. package/src/commands/cloud.pull-all.test.ts +0 -711
  80. package/src/commands/cloud.pull-per-company.test.ts +0 -188
  81. package/src/commands/cloud.push-all.test.ts +0 -485
  82. package/src/commands/cloud.scope-excluded-warning.test.ts +0 -22
  83. package/src/commands/cloud.selectors.test.ts +0 -208
  84. package/src/commands/cloud.status.test.ts +0 -160
  85. package/src/commands/cloud.test.ts +0 -41
  86. package/src/commands/cloud.ts +0 -2130
  87. package/src/commands/company.test.ts +0 -202
  88. package/src/commands/company.ts +0 -148
  89. package/src/commands/creators.test.ts +0 -60
  90. package/src/commands/creators.ts +0 -117
  91. package/src/commands/crm.test.ts +0 -179
  92. package/src/commands/crm.ts +0 -236
  93. package/src/commands/db-migrate.ts +0 -55
  94. package/src/commands/db-provision.ts +0 -114
  95. package/src/commands/db-sql.ts +0 -124
  96. package/src/commands/db-status.ts +0 -108
  97. package/src/commands/db.ts +0 -26
  98. package/src/commands/dm.test.ts +0 -748
  99. package/src/commands/dm.ts +0 -1226
  100. package/src/commands/feedback.test.ts +0 -413
  101. package/src/commands/feedback.ts +0 -169
  102. package/src/commands/files-browse.test.ts +0 -1249
  103. package/src/commands/files-browse.ts +0 -1506
  104. package/src/commands/files-delete.test.ts +0 -484
  105. package/src/commands/files-recovery.test.ts +0 -370
  106. package/src/commands/files.test.ts +0 -722
  107. package/src/commands/files.ts +0 -1446
  108. package/src/commands/group-grants.test.ts +0 -330
  109. package/src/commands/group-grants.ts +0 -455
  110. package/src/commands/groups.test.ts +0 -44
  111. package/src/commands/groups.ts +0 -368
  112. package/src/commands/integrations.test.ts +0 -633
  113. package/src/commands/integrations.ts +0 -507
  114. package/src/commands/list.ts +0 -85
  115. package/src/commands/login.ts +0 -52
  116. package/src/commands/logout.ts +0 -30
  117. package/src/commands/marketplace-install.test.ts +0 -543
  118. package/src/commands/marketplace-security.test.ts +0 -646
  119. package/src/commands/mcp-registration.test.ts +0 -2787
  120. package/src/commands/mcp-registration.ts +0 -2612
  121. package/src/commands/mcp-status.test.ts +0 -483
  122. package/src/commands/mcp-status.ts +0 -575
  123. package/src/commands/mcp-status.us011.test.ts +0 -243
  124. package/src/commands/meetings.test.ts +0 -522
  125. package/src/commands/meetings.ts +0 -789
  126. package/src/commands/members.test.ts +0 -1474
  127. package/src/commands/members.ts +0 -1181
  128. package/src/commands/onboard-identity-guard.test.ts +0 -99
  129. package/src/commands/onboard-identity-guard.ts +0 -103
  130. package/src/commands/onboard-join.test.ts +0 -96
  131. package/src/commands/onboard-join.ts +0 -101
  132. package/src/commands/onboard-warning.test.ts +0 -26
  133. package/src/commands/onboard-warning.ts +0 -12
  134. package/src/commands/onboard.ts +0 -267
  135. package/src/commands/outposts-heartbeat.test.ts +0 -299
  136. package/src/commands/outposts-heartbeat.ts +0 -310
  137. package/src/commands/outposts-replica-sync.test.ts +0 -272
  138. package/src/commands/outposts-self-deploy.test.ts +0 -243
  139. package/src/commands/outposts.test.ts +0 -1039
  140. package/src/commands/outposts.ts +0 -1935
  141. package/src/commands/pack-install-secret-authorization.test.ts +0 -115
  142. package/src/commands/pack-install.test.ts +0 -1469
  143. package/src/commands/pack-install.ts +0 -2256
  144. package/src/commands/pack-update-cache.test.ts +0 -149
  145. package/src/commands/packs-update-api-key.test.ts +0 -105
  146. package/src/commands/packs.test.ts +0 -88
  147. package/src/commands/packs.ts +0 -622
  148. package/src/commands/people.test.ts +0 -807
  149. package/src/commands/people.ts +0 -422
  150. package/src/commands/pkg-install.dispatch.test.ts +0 -103
  151. package/src/commands/pkg-install.ts +0 -221
  152. package/src/commands/pkg-list.test.ts +0 -79
  153. package/src/commands/pkg-list.ts +0 -13
  154. package/src/commands/pkg-remove.ts +0 -71
  155. package/src/commands/pkg-update.ts +0 -189
  156. package/src/commands/publish.test.ts +0 -650
  157. package/src/commands/publish.ts +0 -580
  158. package/src/commands/reindex.test.ts +0 -259
  159. package/src/commands/reindex.ts +0 -274
  160. package/src/commands/rescue.test.ts +0 -39
  161. package/src/commands/rescue.ts +0 -210
  162. package/src/commands/run.env-local.test.ts +0 -88
  163. package/src/commands/run.test.ts +0 -45
  164. package/src/commands/run.ts +0 -190
  165. package/src/commands/safe-extract.test.ts +0 -459
  166. package/src/commands/safe-extract.ts +0 -444
  167. package/src/commands/secrets-scope.test.ts +0 -56
  168. package/src/commands/secrets-scope.ts +0 -32
  169. package/src/commands/secrets.parse-destination.test.ts +0 -38
  170. package/src/commands/secrets.test.ts +0 -2050
  171. package/src/commands/secrets.ts +0 -2249
  172. package/src/commands/signals.ts +0 -351
  173. package/src/commands/skill.test.ts +0 -770
  174. package/src/commands/skill.ts +0 -796
  175. package/src/commands/sources.ts +0 -362
  176. package/src/commands/sync-mode.test.ts +0 -366
  177. package/src/commands/sync-mode.ts +0 -387
  178. package/src/commands/sync-narrow.test.ts +0 -633
  179. package/src/commands/sync-narrow.ts +0 -555
  180. package/src/commands/sync.ts +0 -165
  181. package/src/commands/team-sync.ts +0 -628
  182. package/src/commands/update.ts +0 -71
  183. package/src/commands/whoami.ts +0 -83
  184. package/src/commands/workers.test.ts +0 -158
  185. package/src/commands/workers.ts +0 -301
  186. package/src/index.test.ts +0 -32
  187. package/src/index.ts +0 -20
  188. package/src/lib/db/control-plane.test.ts +0 -59
  189. package/src/lib/db/control-plane.ts +0 -113
  190. package/src/lib/db/local.test.ts +0 -81
  191. package/src/lib/db/local.ts +0 -148
  192. package/src/lib/db/migrate.test.ts +0 -133
  193. package/src/lib/db/migrate.ts +0 -137
  194. package/src/lib/db/paths.test.ts +0 -112
  195. package/src/lib/db/paths.ts +0 -128
  196. package/src/lib/db/remote-engine.test.ts +0 -44
  197. package/src/lib/db/remote-engine.ts +0 -148
  198. package/src/lib/db/remote-sql.test.ts +0 -32
  199. package/src/lib/db/remote-sql.ts +0 -62
  200. package/src/lib/db/sql.test.ts +0 -106
  201. package/src/lib/db/sql.ts +0 -192
  202. package/src/lib/hq-cloud-dep.smoke.test.ts +0 -75
  203. package/src/lib/local-tree-diff.test.ts +0 -301
  204. package/src/lib/local-tree-diff.ts +0 -362
  205. package/src/lib/narrow-hint-banner.test.ts +0 -235
  206. package/src/lib/narrow-hint-banner.ts +0 -212
  207. package/src/lib/plan-limit-nag.test.ts +0 -317
  208. package/src/lib/plan-limit-nag.ts +0 -264
  209. package/src/main.ts +0 -371
  210. package/src/node-preflight.test.ts +0 -60
  211. package/src/node-preflight.ts +0 -67
  212. package/src/outpost/session-heartbeat-bounds.test.ts +0 -195
  213. package/src/outpost/session-heartbeat-guard.test.ts +0 -105
  214. package/src/outpost/session-heartbeat-publisher.test.ts +0 -178
  215. package/src/outpost/session-heartbeat-publisher.ts +0 -186
  216. package/src/outpost/session-heartbeat-retain-guard.test.ts +0 -126
  217. package/src/outpost/session-heartbeat.test.ts +0 -459
  218. package/src/outpost/session-heartbeat.ts +0 -877
  219. package/src/packaging.test.ts +0 -109
  220. package/src/run/__fixtures__/discover-schemas/example.env.schema +0 -4
  221. package/src/run/discover-schemas.test.ts +0 -153
  222. package/src/run/discover-schemas.ts +0 -79
  223. package/src/run/hq-plugin.test.ts +0 -371
  224. package/src/run/hq-plugin.ts +0 -277
  225. package/src/run/varlock-shape.test.ts +0 -57
  226. package/src/schemas/hq-package.schema.json +0 -155
  227. package/src/sentry-before-send.test.ts +0 -359
  228. package/src/sentry-before-send.ts +0 -221
  229. package/src/sentry-epipe.test.ts +0 -37
  230. package/src/sentry-release.test.ts +0 -54
  231. package/src/sentry.test.ts +0 -24
  232. package/src/sentry.ts +0 -54
  233. package/src/strategies/link.ts +0 -62
  234. package/src/strategies/merge.ts +0 -142
  235. package/src/types.ts +0 -146
  236. package/src/utils/__fixtures__/scan-packages.generated-block.sh +0 -23
  237. package/src/utils/auth-error.test.ts +0 -40
  238. package/src/utils/auth-error.ts +0 -42
  239. package/src/utils/billing-gate.test.ts +0 -95
  240. package/src/utils/billing-gate.ts +0 -228
  241. package/src/utils/breadcrumb-buffer.ts +0 -18
  242. package/src/utils/cli-telemetry.test.ts +0 -153
  243. package/src/utils/cli-telemetry.ts +0 -61
  244. package/src/utils/cognito-session.machine.test.ts +0 -109
  245. package/src/utils/cognito-session.refresh.test.ts +0 -84
  246. package/src/utils/cognito-session.test.ts +0 -328
  247. package/src/utils/cognito-session.ts +0 -448
  248. package/src/utils/company-selection-error.test.ts +0 -42
  249. package/src/utils/company-selection-error.ts +0 -45
  250. package/src/utils/contribution-table.ts +0 -83
  251. package/src/utils/environmental-error.test.ts +0 -45
  252. package/src/utils/environmental-error.ts +0 -39
  253. package/src/utils/epipe.test.ts +0 -28
  254. package/src/utils/epipe.ts +0 -29
  255. package/src/utils/expected-cli-error.test.ts +0 -28
  256. package/src/utils/expected-cli-error.ts +0 -39
  257. package/src/utils/feedback-diagnostics.test.ts +0 -183
  258. package/src/utils/feedback-diagnostics.ts +0 -123
  259. package/src/utils/feedback-screenshots.test.ts +0 -134
  260. package/src/utils/feedback-screenshots.ts +0 -124
  261. package/src/utils/feedback-versions.test.ts +0 -98
  262. package/src/utils/feedback-versions.ts +0 -68
  263. package/src/utils/git.ts +0 -74
  264. package/src/utils/integrity.ts +0 -59
  265. package/src/utils/intercepted-process-exit.test.ts +0 -37
  266. package/src/utils/intercepted-process-exit.ts +0 -36
  267. package/src/utils/login-provider.test.ts +0 -42
  268. package/src/utils/login-provider.ts +0 -30
  269. package/src/utils/manifest.test.ts +0 -167
  270. package/src/utils/manifest.ts +0 -131
  271. package/src/utils/pack-contributions.test.ts +0 -603
  272. package/src/utils/pack-contributions.ts +0 -450
  273. package/src/utils/people.ts +0 -215
  274. package/src/utils/registry-client.ts +0 -204
  275. package/src/utils/registry.ts +0 -90
  276. package/src/utils/resolve-vault-credential.test.ts +0 -69
  277. package/src/utils/resolve-vault-credential.ts +0 -60
  278. package/src/utils/sandbox-runner-client.test.ts +0 -279
  279. package/src/utils/sandbox-runner-client.ts +0 -260
  280. package/src/utils/secrets-cache.ts +0 -183
  281. package/src/utils/sentry-identity.ts +0 -45
  282. package/src/utils/settle-with-timeout.test.ts +0 -21
  283. package/src/utils/settle-with-timeout.ts +0 -22
  284. package/src/utils/user-yaml-error.test.ts +0 -24
  285. package/src/utils/user-yaml-error.ts +0 -30
  286. package/src/utils/vault-api.test.ts +0 -536
  287. package/src/utils/vault-api.ts +0 -382
  288. package/src/utils/version-check.test.ts +0 -176
  289. package/src/utils/version-check.ts +0 -155
  290. package/src/utils/version-gate.test.ts +0 -1024
  291. package/src/utils/version-gate.ts +0 -702
  292. package/test/commands/db-tenant-isolation.test.ts +0 -94
  293. package/test/commands/db.test.ts +0 -85
  294. package/test/commands/signals.test.ts +0 -207
  295. package/test/commands/sources.test.ts +0 -231
  296. package/test/e2e/smoke-install-mcp.sh +0 -113
  297. package/test/fixtures/hq-pack-smoke-mcp/mcp/smoke-http.json +0 -1
  298. package/test/fixtures/hq-pack-smoke-mcp/package.yaml +0 -11
  299. package/test/fixtures/signals/action_item/sample.md +0 -16
  300. package/test/fixtures/signals/summary/sample.md +0 -12
  301. package/test/fixtures/sources/meetings/sample.md +0 -25
  302. package/test/helpers/cli-runner.ts +0 -150
  303. package/test/helpers/s3-list-mock.ts +0 -79
  304. package/test/helpers/vault-service-mock.ts +0 -295
  305. package/test/sources-signals/smoke.test.ts +0 -228
  306. package/tsconfig.json +0 -20
  307. package/vitest.config.ts +0 -11
  308. package/vitest.e2e.config.ts +0 -7
@@ -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, assertCognitoOnlyCommand, resolveVaultCredential, } from "../utils/resolve-vault-credential.js";
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
- /** Cognito session for secrets commands that do not support HQ_API_KEY. */
15
- async function requireCognitoTokenForSecrets(commandLabel) {
16
- assertCognitoOnlyCommand(commandLabel);
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
- const encodedName = name
40
- .split("/")
41
- .map(encodeURIComponent)
42
- .join("/");
43
- return `/secrets/${encodeURIComponent(companyUid)}/name/${encodedName}`;
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 token = await requireCognitoTokenForSecrets("secrets set");
689
- const scope = scopeOpts(secrets.opts());
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: `/secrets/${encodeURIComponent(companyUid)}`,
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: ${extractApiMessage(body, res.statusText)}`));
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 resolveVaultCredential();
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: ${extractApiMessage(body, res.statusText)}`));
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 token = await requireCognitoTokenForSecrets("secrets list");
898
- const scope = scopeOpts(secrets.opts());
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: `/secrets/${encodeURIComponent(companyUid)}`,
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: ${body.error ?? res.statusText}`));
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 token = await requireCognitoTokenForSecrets("secrets");
1194
- const companyUid = await getEntityUid(token, scopeOpts(secrets.opts()));
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: buildSecretNamePath(companyUid, name),
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: ${body.error ?? res.statusText}`));
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 resolveVaultCredential();
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 resolveVaultCredential();
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 token = await requireCognitoTokenForSecrets("secrets");
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: `/secrets/${encodeURIComponent(companyUid)}/acl/grant`,
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("Not authorized to share this secret"));
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 token = await requireCognitoTokenForSecrets("secrets");
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: `/secrets/${encodeURIComponent(companyUid)}/acl/revoke`,
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("Not authorized to modify this secret's ACL"));
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 token = await requireCognitoTokenForSecrets("secrets");
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: `/secrets/${encodeURIComponent(companyUid)}/acl`,
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("Not authorized to view this secret's ACL"));
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