@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.
Files changed (307) hide show
  1. package/CHANGELOG.md +34 -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 +51 -2
  33. package/dist/commands/pkg-install.js +43 -0
  34. package/dist/commands/run.js +8 -0
  35. package/dist/commands/secrets.js +125 -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 -2247
  144. package/src/commands/pack-update-cache.test.ts +0 -149
  145. package/src/commands/packs.test.ts +0 -88
  146. package/src/commands/packs.ts +0 -622
  147. package/src/commands/people.test.ts +0 -807
  148. package/src/commands/people.ts +0 -422
  149. package/src/commands/pkg-install.dispatch.test.ts +0 -71
  150. package/src/commands/pkg-install.ts +0 -215
  151. package/src/commands/pkg-list.test.ts +0 -79
  152. package/src/commands/pkg-list.ts +0 -13
  153. package/src/commands/pkg-remove.ts +0 -71
  154. package/src/commands/pkg-update.ts +0 -189
  155. package/src/commands/publish.test.ts +0 -650
  156. package/src/commands/publish.ts +0 -580
  157. package/src/commands/reindex.test.ts +0 -259
  158. package/src/commands/reindex.ts +0 -274
  159. package/src/commands/rescue.test.ts +0 -39
  160. package/src/commands/rescue.ts +0 -210
  161. package/src/commands/run.env-local.test.ts +0 -88
  162. package/src/commands/run.test.ts +0 -45
  163. package/src/commands/run.ts +0 -184
  164. package/src/commands/safe-extract.test.ts +0 -459
  165. package/src/commands/safe-extract.ts +0 -444
  166. package/src/commands/secrets-scope.test.ts +0 -56
  167. package/src/commands/secrets-scope.ts +0 -32
  168. package/src/commands/secrets.parse-destination.test.ts +0 -38
  169. package/src/commands/secrets.test.ts +0 -2037
  170. package/src/commands/secrets.ts +0 -2240
  171. package/src/commands/signals.ts +0 -351
  172. package/src/commands/skill.test.ts +0 -770
  173. package/src/commands/skill.ts +0 -796
  174. package/src/commands/sources.ts +0 -362
  175. package/src/commands/sync-mode.test.ts +0 -366
  176. package/src/commands/sync-mode.ts +0 -387
  177. package/src/commands/sync-narrow.test.ts +0 -633
  178. package/src/commands/sync-narrow.ts +0 -555
  179. package/src/commands/sync.ts +0 -165
  180. package/src/commands/team-sync.ts +0 -628
  181. package/src/commands/update.ts +0 -71
  182. package/src/commands/whoami.ts +0 -83
  183. package/src/commands/workers.test.ts +0 -158
  184. package/src/commands/workers.ts +0 -301
  185. package/src/index.test.ts +0 -32
  186. package/src/index.ts +0 -20
  187. package/src/lib/db/control-plane.test.ts +0 -59
  188. package/src/lib/db/control-plane.ts +0 -113
  189. package/src/lib/db/local.test.ts +0 -81
  190. package/src/lib/db/local.ts +0 -148
  191. package/src/lib/db/migrate.test.ts +0 -133
  192. package/src/lib/db/migrate.ts +0 -137
  193. package/src/lib/db/paths.test.ts +0 -112
  194. package/src/lib/db/paths.ts +0 -128
  195. package/src/lib/db/remote-engine.test.ts +0 -44
  196. package/src/lib/db/remote-engine.ts +0 -148
  197. package/src/lib/db/remote-sql.test.ts +0 -32
  198. package/src/lib/db/remote-sql.ts +0 -62
  199. package/src/lib/db/sql.test.ts +0 -106
  200. package/src/lib/db/sql.ts +0 -192
  201. package/src/lib/hq-cloud-dep.smoke.test.ts +0 -75
  202. package/src/lib/local-tree-diff.test.ts +0 -301
  203. package/src/lib/local-tree-diff.ts +0 -362
  204. package/src/lib/narrow-hint-banner.test.ts +0 -235
  205. package/src/lib/narrow-hint-banner.ts +0 -212
  206. package/src/lib/plan-limit-nag.test.ts +0 -317
  207. package/src/lib/plan-limit-nag.ts +0 -264
  208. package/src/main.ts +0 -371
  209. package/src/node-preflight.test.ts +0 -60
  210. package/src/node-preflight.ts +0 -67
  211. package/src/outpost/session-heartbeat-bounds.test.ts +0 -195
  212. package/src/outpost/session-heartbeat-guard.test.ts +0 -105
  213. package/src/outpost/session-heartbeat-publisher.test.ts +0 -178
  214. package/src/outpost/session-heartbeat-publisher.ts +0 -186
  215. package/src/outpost/session-heartbeat-retain-guard.test.ts +0 -126
  216. package/src/outpost/session-heartbeat.test.ts +0 -459
  217. package/src/outpost/session-heartbeat.ts +0 -877
  218. package/src/packaging.test.ts +0 -109
  219. package/src/run/__fixtures__/discover-schemas/example.env.schema +0 -4
  220. package/src/run/discover-schemas.test.ts +0 -153
  221. package/src/run/discover-schemas.ts +0 -79
  222. package/src/run/hq-plugin.test.ts +0 -371
  223. package/src/run/hq-plugin.ts +0 -277
  224. package/src/run/varlock-shape.test.ts +0 -57
  225. package/src/schemas/hq-package.schema.json +0 -155
  226. package/src/sentry-before-send.test.ts +0 -359
  227. package/src/sentry-before-send.ts +0 -221
  228. package/src/sentry-epipe.test.ts +0 -37
  229. package/src/sentry-release.test.ts +0 -54
  230. package/src/sentry.test.ts +0 -24
  231. package/src/sentry.ts +0 -54
  232. package/src/strategies/link.ts +0 -62
  233. package/src/strategies/merge.ts +0 -142
  234. package/src/types.ts +0 -146
  235. package/src/utils/__fixtures__/scan-packages.generated-block.sh +0 -23
  236. package/src/utils/auth-error.test.ts +0 -40
  237. package/src/utils/auth-error.ts +0 -42
  238. package/src/utils/billing-gate.test.ts +0 -95
  239. package/src/utils/billing-gate.ts +0 -228
  240. package/src/utils/breadcrumb-buffer.ts +0 -18
  241. package/src/utils/cli-telemetry.test.ts +0 -153
  242. package/src/utils/cli-telemetry.ts +0 -61
  243. package/src/utils/cognito-session.machine.test.ts +0 -109
  244. package/src/utils/cognito-session.refresh.test.ts +0 -84
  245. package/src/utils/cognito-session.test.ts +0 -328
  246. package/src/utils/cognito-session.ts +0 -448
  247. package/src/utils/company-selection-error.test.ts +0 -42
  248. package/src/utils/company-selection-error.ts +0 -45
  249. package/src/utils/contribution-table.ts +0 -83
  250. package/src/utils/environmental-error.test.ts +0 -45
  251. package/src/utils/environmental-error.ts +0 -39
  252. package/src/utils/epipe.test.ts +0 -28
  253. package/src/utils/epipe.ts +0 -29
  254. package/src/utils/expected-cli-error.test.ts +0 -28
  255. package/src/utils/expected-cli-error.ts +0 -39
  256. package/src/utils/feedback-diagnostics.test.ts +0 -183
  257. package/src/utils/feedback-diagnostics.ts +0 -123
  258. package/src/utils/feedback-screenshots.test.ts +0 -134
  259. package/src/utils/feedback-screenshots.ts +0 -124
  260. package/src/utils/feedback-versions.test.ts +0 -98
  261. package/src/utils/feedback-versions.ts +0 -68
  262. package/src/utils/git.ts +0 -74
  263. package/src/utils/integrity.ts +0 -59
  264. package/src/utils/intercepted-process-exit.test.ts +0 -37
  265. package/src/utils/intercepted-process-exit.ts +0 -36
  266. package/src/utils/login-provider.test.ts +0 -42
  267. package/src/utils/login-provider.ts +0 -30
  268. package/src/utils/manifest.test.ts +0 -167
  269. package/src/utils/manifest.ts +0 -131
  270. package/src/utils/pack-contributions.test.ts +0 -603
  271. package/src/utils/pack-contributions.ts +0 -450
  272. package/src/utils/people.ts +0 -215
  273. package/src/utils/registry-client.ts +0 -204
  274. package/src/utils/registry.ts +0 -90
  275. package/src/utils/resolve-vault-credential.test.ts +0 -69
  276. package/src/utils/resolve-vault-credential.ts +0 -60
  277. package/src/utils/sandbox-runner-client.test.ts +0 -279
  278. package/src/utils/sandbox-runner-client.ts +0 -260
  279. package/src/utils/secrets-cache.ts +0 -183
  280. package/src/utils/sentry-identity.ts +0 -45
  281. package/src/utils/settle-with-timeout.test.ts +0 -21
  282. package/src/utils/settle-with-timeout.ts +0 -22
  283. package/src/utils/user-yaml-error.test.ts +0 -24
  284. package/src/utils/user-yaml-error.ts +0 -30
  285. package/src/utils/vault-api.test.ts +0 -536
  286. package/src/utils/vault-api.ts +0 -382
  287. package/src/utils/version-check.test.ts +0 -176
  288. package/src/utils/version-check.ts +0 -155
  289. package/src/utils/version-gate.test.ts +0 -1024
  290. package/src/utils/version-gate.ts +0 -702
  291. package/test/commands/db-tenant-isolation.test.ts +0 -94
  292. package/test/commands/db.test.ts +0 -85
  293. package/test/commands/signals.test.ts +0 -207
  294. package/test/commands/sources.test.ts +0 -231
  295. package/test/e2e/smoke-install-mcp.sh +0 -113
  296. package/test/fixtures/hq-pack-smoke-mcp/mcp/smoke-http.json +0 -1
  297. package/test/fixtures/hq-pack-smoke-mcp/package.yaml +0 -11
  298. package/test/fixtures/signals/action_item/sample.md +0 -16
  299. package/test/fixtures/signals/summary/sample.md +0 -12
  300. package/test/fixtures/sources/meetings/sample.md +0 -25
  301. package/test/helpers/cli-runner.ts +0 -150
  302. package/test/helpers/s3-list-mock.ts +0 -79
  303. package/test/helpers/vault-service-mock.ts +0 -295
  304. package/test/sources-signals/smoke.test.ts +0 -228
  305. package/tsconfig.json +0 -20
  306. package/vitest.config.ts +0 -11
  307. 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,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 resolveVaultCredential();
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: ${extractApiMessage(body, res.statusText)}`));
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 token = await requireCognitoTokenForSecrets("secrets list");
893
- const scope = scopeOpts(secrets.opts());
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: `/secrets/${encodeURIComponent(companyUid)}`,
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: ${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)}`));
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 token = await requireCognitoTokenForSecrets("secrets");
1189
- const companyUid = await getEntityUid(token, scopeOpts(secrets.opts()));
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: buildSecretNamePath(companyUid, name),
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: ${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)}`));
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 resolveVaultCredential();
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 resolveVaultCredential();
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 token = await requireCognitoTokenForSecrets("secrets");
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: `/secrets/${encodeURIComponent(companyUid)}/acl/grant`,
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("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"));
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 token = await requireCognitoTokenForSecrets("secrets");
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: `/secrets/${encodeURIComponent(companyUid)}/acl/revoke`,
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("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"));
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 token = await requireCognitoTokenForSecrets("secrets");
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: `/secrets/${encodeURIComponent(companyUid)}/acl`,
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("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"));
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