@indigoai-us/hq-cli 5.345.156 → 5.345.160

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 (188) hide show
  1. package/CHANGELOG.md +255 -0
  2. package/assets/scaffold/core/scripts/rebuild-company-knowledge-index.sh +110 -2
  3. package/assets/scaffold/core/scripts/rebuild-projects-index.sh +101 -5
  4. package/assets/scaffold/core/scripts/worktree.sh +4 -0
  5. package/dist/command-catalog.generated.d.ts +535 -10
  6. package/dist/command-catalog.generated.js +678 -31
  7. package/dist/command-registration-plan.d.ts +18 -0
  8. package/dist/command-registration-plan.js +4 -0
  9. package/dist/commands/_patterns.d.ts +17 -0
  10. package/dist/commands/_patterns.js +22 -0
  11. package/dist/commands/access.d.ts +6 -0
  12. package/dist/commands/access.js +18 -1
  13. package/dist/commands/acl-lock.d.ts +41 -0
  14. package/dist/commands/acl-lock.js +28 -0
  15. package/dist/commands/agent-enroll.d.ts +2 -0
  16. package/dist/commands/agent-enroll.js +9 -4
  17. package/dist/commands/agent-kit.js +8 -6
  18. package/dist/commands/agents.js +5 -11
  19. package/dist/commands/billing-sponsor.d.ts +4 -4
  20. package/dist/commands/billing-sponsor.js +11 -11
  21. package/dist/commands/billing-usage.d.ts +22 -8
  22. package/dist/commands/billing-usage.js +57 -22
  23. package/dist/commands/billing.d.ts +7 -7
  24. package/dist/commands/billing.js +13 -13
  25. package/dist/commands/bot.js +7 -4
  26. package/dist/commands/channel-suggest.d.ts +56 -0
  27. package/dist/commands/channel-suggest.js +170 -0
  28. package/dist/commands/channels.js +7 -1
  29. package/dist/commands/cloud-provision.js +2 -2
  30. package/dist/commands/cloud.d.ts +45 -0
  31. package/dist/commands/cloud.js +157 -17
  32. package/dist/commands/company.js +4 -3
  33. package/dist/commands/core-checkpoint-native.d.ts +18 -0
  34. package/dist/commands/core-checkpoint-native.js +67 -2
  35. package/dist/commands/core-pipeline-driver-native.d.ts +12 -1
  36. package/dist/commands/core-pipeline-driver-native.js +131 -5
  37. package/dist/commands/core.js +12 -0
  38. package/dist/commands/crm.js +83 -6
  39. package/dist/commands/db-remote-deps.js +12 -1
  40. package/dist/commands/deploy-access.d.ts +33 -0
  41. package/dist/commands/deploy-access.js +322 -0
  42. package/dist/commands/dm-attach.d.ts +106 -0
  43. package/dist/commands/dm-attach.js +184 -0
  44. package/dist/commands/dm.d.ts +37 -0
  45. package/dist/commands/dm.js +172 -10
  46. package/dist/commands/feedback-show.d.ts +40 -0
  47. package/dist/commands/feedback-show.js +261 -0
  48. package/dist/commands/feedback.d.ts +21 -0
  49. package/dist/commands/feedback.js +152 -2
  50. package/dist/commands/files.d.ts +24 -0
  51. package/dist/commands/files.js +71 -8
  52. package/dist/commands/integrations-connect.js +7 -3
  53. package/dist/commands/lanes-accounts.js +16 -1
  54. package/dist/commands/lanes-loop.js +14 -4
  55. package/dist/commands/lanes-message.js +6 -4
  56. package/dist/commands/lanes-resume.js +36 -13
  57. package/dist/commands/lanes-shared.d.ts +5 -0
  58. package/dist/commands/lanes-shared.js +13 -3
  59. package/dist/commands/lanes-watch.js +1 -0
  60. package/dist/commands/lanes.d.ts +20 -1
  61. package/dist/commands/lanes.js +201 -51
  62. package/dist/commands/meetings.d.ts +4 -4
  63. package/dist/commands/meetings.js +17 -8
  64. package/dist/commands/mesh.js +149 -1
  65. package/dist/commands/monitor.d.ts +1 -0
  66. package/dist/commands/monitor.js +4 -1
  67. package/dist/commands/repo.d.ts +41 -0
  68. package/dist/commands/repo.js +331 -0
  69. package/dist/commands/scan.d.ts +3 -1
  70. package/dist/commands/scan.js +7 -3
  71. package/dist/commands/secrets.d.ts +21 -0
  72. package/dist/commands/secrets.js +114 -14
  73. package/dist/commands/signals.js +17 -5
  74. package/dist/commands/skill.js +6 -6
  75. package/dist/commands/staff/listings.d.ts +41 -0
  76. package/dist/commands/staff/listings.js +160 -0
  77. package/dist/commands/staff/reset-test-account.d.ts +3 -2
  78. package/dist/commands/staff/reset-test-account.js +4 -3
  79. package/dist/commands/whoami.js +1 -1
  80. package/dist/hook-engine-path.js +13 -2
  81. package/dist/hq-cloud-sync-runner/bin/sync-runner.js +3392 -1243
  82. package/dist/hq-cloud-sync-runner/package.json +1 -1
  83. package/dist/lib/agent-kit/run/inbox.js +2 -1
  84. package/dist/lib/agent-kit/wake-secrets.d.ts +7 -0
  85. package/dist/lib/agent-kit/wake-secrets.js +32 -0
  86. package/dist/lib/agent-kit/wake.d.ts +4 -0
  87. package/dist/lib/agent-kit/wake.js +49 -3
  88. package/dist/lib/billing/plan-limit-refusals.d.ts +13 -3
  89. package/dist/lib/billing/plan-limit-refusals.js +26 -3
  90. package/dist/lib/billing/plan-limit-upgrade-url.d.ts +8 -0
  91. package/dist/lib/billing/plan-limit-upgrade-url.js +18 -0
  92. package/dist/lib/billing/plan-lock.d.ts +22 -6
  93. package/dist/lib/billing/plan-lock.js +58 -30
  94. package/dist/lib/billing/usage-cost.d.ts +24 -2
  95. package/dist/lib/billing/usage-cost.js +103 -3
  96. package/dist/lib/billing/usage-only.d.ts +37 -1
  97. package/dist/lib/billing/usage-only.js +89 -4
  98. package/dist/lib/billing/usage-pause.d.ts +27 -0
  99. package/dist/lib/billing/usage-pause.js +50 -0
  100. package/dist/lib/bot/inflight.d.ts +7 -1
  101. package/dist/lib/bot/inflight.js +10 -2
  102. package/dist/lib/bot/prompt.js +3 -0
  103. package/dist/lib/bot/run.js +72 -20
  104. package/dist/lib/bot/session.d.ts +14 -0
  105. package/dist/lib/bot/session.js +70 -0
  106. package/dist/lib/capacity/collectors/claude-oauth.d.ts +6 -0
  107. package/dist/lib/capacity/collectors/claude-oauth.js +12 -0
  108. package/dist/lib/capacity/lane-login.d.ts +6 -0
  109. package/dist/lib/capacity/lane-login.js +5 -0
  110. package/dist/lib/core-utils/worktree.js +2 -0
  111. package/dist/lib/daemon/exit-cause.d.ts +43 -0
  112. package/dist/lib/daemon/exit-cause.js +88 -0
  113. package/dist/lib/daemon/handoff.d.ts +2 -0
  114. package/dist/lib/daemon/handoff.js +6 -1
  115. package/dist/lib/daemon/hq-child-bootstrap.mjs +20 -3
  116. package/dist/lib/daemon/paths.d.ts +2 -0
  117. package/dist/lib/daemon/paths.js +1 -0
  118. package/dist/lib/daemon/run.js +13 -0
  119. package/dist/lib/daemon/supervisor.d.ts +54 -1
  120. package/dist/lib/daemon/supervisor.js +297 -21
  121. package/dist/lib/daemon/unit-holds.d.ts +22 -0
  122. package/dist/lib/daemon/unit-holds.js +86 -0
  123. package/dist/lib/db/control-plane.d.ts +5 -0
  124. package/dist/lib/db/control-plane.js +10 -0
  125. package/dist/lib/flag-registry-worker-source.generated.d.ts +1 -1
  126. package/dist/lib/flag-registry-worker-source.generated.js +1 -1
  127. package/dist/lib/hook-engine/resolve.d.ts +14 -0
  128. package/dist/lib/hook-engine/resolve.js +55 -1
  129. package/dist/lib/index-render/company-knowledge.js +1 -3
  130. package/dist/lib/index-render/projects.js +1 -3
  131. package/dist/lib/lanes/account-company.d.ts +28 -0
  132. package/dist/lib/lanes/account-company.js +53 -0
  133. package/dist/lib/lanes/accounts.d.ts +22 -3
  134. package/dist/lib/lanes/accounts.js +38 -2
  135. package/dist/lib/lanes/claude-balance.d.ts +6 -1
  136. package/dist/lib/lanes/claude-balance.js +6 -1
  137. package/dist/lib/lanes/claude-credential.d.ts +78 -0
  138. package/dist/lib/lanes/claude-credential.js +163 -0
  139. package/dist/lib/lanes/compaction.d.ts +2 -0
  140. package/dist/lib/lanes/compaction.js +9 -0
  141. package/dist/lib/lanes/config.js +6 -0
  142. package/dist/lib/lanes/envelope-repair-dispatch.js +2 -1
  143. package/dist/lib/lanes/hooks-install.js +2 -1
  144. package/dist/lib/lanes/host-capacity.d.ts +2 -0
  145. package/dist/lib/lanes/host-capacity.js +1 -0
  146. package/dist/lib/lanes/keep-alive.d.ts +1 -1
  147. package/dist/lib/lanes/keep-alive.js +3 -1
  148. package/dist/lib/lanes/links.js +10 -4
  149. package/dist/lib/lanes/loop.js +16 -1
  150. package/dist/lib/lanes/node-binary.d.ts +17 -0
  151. package/dist/lib/lanes/node-binary.js +30 -0
  152. package/dist/lib/lanes/outpost-dispatch.d.ts +2 -1
  153. package/dist/lib/lanes/outpost-dispatch.js +1 -0
  154. package/dist/lib/lanes/reconcile.js +4 -0
  155. package/dist/lib/lanes/remote-control-lane.js +2 -0
  156. package/dist/lib/lanes/spawn.d.ts +3 -0
  157. package/dist/lib/lanes/spawn.js +119 -9
  158. package/dist/lib/lanes/store.d.ts +6 -0
  159. package/dist/lib/lanes/store.js +107 -18
  160. package/dist/lib/lanes/types.d.ts +2 -0
  161. package/dist/lib/lanes/watch.js +28 -5
  162. package/dist/lib/lanes/watcher-main.js +4 -3
  163. package/dist/lib/lanes/watcher.d.ts +4 -0
  164. package/dist/lib/lanes/watcher.js +110 -21
  165. package/dist/lib/mesh/api.d.ts +32 -0
  166. package/dist/lib/mesh/api.js +75 -0
  167. package/dist/lib/plan-limit-nag.d.ts +0 -16
  168. package/dist/lib/plan-limit-nag.js +17 -18
  169. package/dist/lib/secret-scan/detect.js +9 -0
  170. package/dist/lib/secret-scan/repo-config.d.ts +41 -0
  171. package/dist/lib/secret-scan/repo-config.js +237 -0
  172. package/dist/lib/secret-scan/rules.d.ts +7 -1
  173. package/dist/lib/secret-scan/rules.js +3 -1
  174. package/dist/lib/secret-scan/staged.d.ts +5 -0
  175. package/dist/lib/secret-scan/staged.js +19 -2
  176. package/dist/lib/secret-scan/types.d.ts +2 -0
  177. package/dist/lib/sync/server-owned-local-only.d.ts +29 -0
  178. package/dist/lib/sync/server-owned-local-only.js +75 -0
  179. package/dist/main.d.ts +0 -2
  180. package/dist/main.js +19 -57
  181. package/dist/utils/package-use-lease.js +27 -9
  182. package/dist/utils/plan-gate-error.d.ts +30 -14
  183. package/dist/utils/plan-gate-error.js +60 -46
  184. package/dist/utils/team-upgrade.d.ts +19 -6
  185. package/dist/utils/team-upgrade.js +37 -39
  186. package/dist/utils/version-gate.d.ts +7 -0
  187. package/dist/utils/version-gate.js +66 -0
  188. package/package.json +8 -8
@@ -400,6 +400,12 @@ export declare const REGISTRATION_PLAN: readonly [{
400
400
  readonly parent: "program";
401
401
  readonly module: "./commands/mcp-status.js";
402
402
  readonly exportName: "registerMcpCommand";
403
+ }, {
404
+ readonly type: "registrar";
405
+ readonly root: "repo";
406
+ readonly parent: "program";
407
+ readonly module: "./commands/repo.js";
408
+ readonly exportName: "registerRepoCommand";
403
409
  }, {
404
410
  readonly type: "registrar";
405
411
  readonly root: "crm";
@@ -533,6 +539,12 @@ export declare const REGISTRATION_PLAN: readonly [{
533
539
  readonly parent: "deploy";
534
540
  readonly module: "./commands/deploy-list.js";
535
541
  readonly exportName: "registerDeployListCommand";
542
+ }, {
543
+ readonly type: "registrar";
544
+ readonly root: "deploy";
545
+ readonly parent: "deploy";
546
+ readonly module: "./commands/deploy-access.js";
547
+ readonly exportName: "registerDeployAccessCommand";
536
548
  }, {
537
549
  readonly type: "group";
538
550
  readonly root: "staff";
@@ -551,6 +563,12 @@ export declare const REGISTRATION_PLAN: readonly [{
551
563
  readonly parent: "staff";
552
564
  readonly module: "./commands/staff/test-account.js";
553
565
  readonly exportName: "registerTestAccountCommand";
566
+ }, {
567
+ readonly type: "registrar";
568
+ readonly root: "staff";
569
+ readonly parent: "staff";
570
+ readonly module: "./commands/staff/listings.js";
571
+ readonly exportName: "registerListingsCommand";
554
572
  }];
555
573
  export declare function registerAllCommands(program: Command): Promise<void>;
556
574
  export declare function registerCommandRoot(program: Command, root: string): Promise<boolean>;
@@ -69,6 +69,7 @@ export const REGISTRATION_PLAN = [
69
69
  { type: "registrar", root: "reindex", parent: "program", module: "./commands/reindex.js", exportName: "registerReindexCommand" },
70
70
  { type: "registrar", root: "rescue", parent: "program", module: "./commands/rescue.js", exportName: "registerRescueCommand" },
71
71
  { type: "registrar", root: "mcp", parent: "program", module: "./commands/mcp-status.js", exportName: "registerMcpCommand" },
72
+ { type: "registrar", root: "repo", parent: "program", module: "./commands/repo.js", exportName: "registerRepoCommand" },
72
73
  { type: "registrar", root: "crm", parent: "program", module: "./commands/crm.js", exportName: "registerCrmCommand" },
73
74
  { type: "registrar", root: "company", parent: "program", module: "./commands/company.js", exportName: "registerCompanyCommand" },
74
75
  { type: "registrar", root: "agents", parent: "program", module: "./commands/agents.js", exportName: "registerAgentsCommand" },
@@ -91,12 +92,15 @@ export const REGISTRATION_PLAN = [
91
92
  { type: "group", root: "deploy", id: "deploy", name: "deploy", description: "Manage deployments in hq-deploy" },
92
93
  { type: "registrar", root: "deploy", parent: "deploy", module: "./commands/deploy-comments.js", exportName: "registerDeployCommentsCommand" },
93
94
  { type: "registrar", root: "deploy", parent: "deploy", module: "./commands/deploy-list.js", exportName: "registerDeployListCommand" },
95
+ { type: "registrar", root: "deploy", parent: "deploy", module: "./commands/deploy-access.js", exportName: "registerDeployAccessCommand" },
94
96
  // onboarding-test-accounts US-007 — super-admin staff CLI wrappers over the
95
97
  // hq-pro admin surface. New wrappers land under this group; the reset
96
98
  // command mirrors POST /v1/admin/test-account/reset.
97
99
  { type: "group", root: "staff", id: "staff", name: "staff", description: "Staff-only commands (super-admin required)" },
98
100
  { type: "registrar", root: "staff", parent: "staff", module: "./commands/staff/reset-test-account.js", exportName: "registerResetTestAccountCommand" },
99
101
  { type: "registrar", root: "staff", parent: "staff", module: "./commands/staff/test-account.js", exportName: "registerTestAccountCommand" },
102
+ // Marketplace moderation: queue / approve / reject over /v1/moderation.
103
+ { type: "registrar", root: "staff", parent: "staff", module: "./commands/staff/listings.js", exportName: "registerListingsCommand" },
100
104
  ];
101
105
  async function executePlan(program, steps) {
102
106
  const parents = new Map([["program", program]]);
@@ -7,6 +7,23 @@ export declare const EMAIL_PATTERN: RegExp;
7
7
  * grantee slot on the wire.
8
8
  */
9
9
  export declare const OUTPOST_UID_PATTERN: RegExp;
10
+ /**
11
+ * A fleet agent uid: `agt_` plus a 26-character Crockford ULID, upper-case,
12
+ * the same shape as OUTPOST_UID_PATTERN. A typo (wrong length, lower-case,
13
+ * or a letter outside the Crockford alphabet: I, L, O, U) fails locally.
14
+ */
15
+ export declare const AGENT_UID_PATTERN: RegExp;
16
+ /**
17
+ * The machine email a fleet agent signs in with:
18
+ * `agt-<ulid>@agents.getindigo.ai`, lower-cased. The CLI sends this email as
19
+ * the grantee; hq-pro canonicalizes it onto the agent's `agt_…` principal
20
+ * when the agent is an active member of the company.
21
+ */
22
+ export declare function agentMachineEmail(agentUid: string): string;
23
+ /** Matches a fleet agent machine email; group 1 is the lower-case ULID. */
24
+ export declare const AGENT_MACHINE_EMAIL_PATTERN: RegExp;
25
+ /** The `agt_…` uid behind a machine email, or null when it is not one. */
26
+ export declare function agentUidFromMachineEmail(email: string): string | null;
10
27
  /**
11
28
  * Keep an ACL pattern exactly as the caller supplied it.
12
29
  *
@@ -7,6 +7,28 @@ export const EMAIL_PATTERN = /^[^\s]+@[^\s]+$/;
7
7
  * grantee slot on the wire.
8
8
  */
9
9
  export const OUTPOST_UID_PATTERN = /^out_[0-9A-HJKMNP-TV-Z]{26}$/;
10
+ /**
11
+ * A fleet agent uid: `agt_` plus a 26-character Crockford ULID, upper-case,
12
+ * the same shape as OUTPOST_UID_PATTERN. A typo (wrong length, lower-case,
13
+ * or a letter outside the Crockford alphabet: I, L, O, U) fails locally.
14
+ */
15
+ export const AGENT_UID_PATTERN = /^agt_[0-9A-HJKMNP-TV-Z]{26}$/;
16
+ /**
17
+ * The machine email a fleet agent signs in with:
18
+ * `agt-<ulid>@agents.getindigo.ai`, lower-cased. The CLI sends this email as
19
+ * the grantee; hq-pro canonicalizes it onto the agent's `agt_…` principal
20
+ * when the agent is an active member of the company.
21
+ */
22
+ export function agentMachineEmail(agentUid) {
23
+ return `agt-${agentUid.slice(4).toLowerCase()}@agents.getindigo.ai`;
24
+ }
25
+ /** Matches a fleet agent machine email; group 1 is the lower-case ULID. */
26
+ export const AGENT_MACHINE_EMAIL_PATTERN = /^agt-([0-9a-hjkmnp-tv-z]{26})@agents\.getindigo\.ai$/i;
27
+ /** The `agt_…` uid behind a machine email, or null when it is not one. */
28
+ export function agentUidFromMachineEmail(email) {
29
+ const m = AGENT_MACHINE_EMAIL_PATTERN.exec(email.trim());
30
+ return m ? `agt_${m[1].toUpperCase()}` : null;
31
+ }
10
32
  /**
11
33
  * Keep an ACL pattern exactly as the caller supplied it.
12
34
  *
@@ -15,6 +15,7 @@
15
15
  */
16
16
  import { Command } from "commander";
17
17
  import { type CompanyBrowseClientFactory, type FilesBrowseVaultClient, type RunGetResult } from "./files-browse.js";
18
+ import { type AclLock } from "./acl-lock.js";
18
19
  export type AccessOutcome = "never-existed" | "local" | "not-synced" | "no-access" | "ambiguous" | "pending-confirmation";
19
20
  export interface AccessAclTree {
20
21
  prefix: string;
@@ -145,6 +146,11 @@ export interface RunAccessResult {
145
146
  requestSentAt?: string;
146
147
  alreadyAskedAt?: string;
147
148
  requestNote?: string;
149
+ /**
150
+ * Most-specific ACL row covering the path when it has no grants. Broader
151
+ * grants (including a root `*` company-wide grant) do not apply.
152
+ */
153
+ lockedBy?: AclLock;
148
154
  }
149
155
  export interface AccessAsResult {
150
156
  personUid: string;
@@ -23,6 +23,7 @@ import { getCompanyUid, vaultApiFetch, resolveCallerUid } from "../utils/vault-a
23
23
  import { parseCompanySlugFromPath, classifyAclSource, resolveBrowseSession, makeCompanyPresignFactory, runGet, toBucketRelative, } from "./files-browse.js";
24
24
  import { listJournals, syncDoctor, } from "@indigoai-us/hq-cloud";
25
25
  import { listActiveMembers } from "./members.js";
26
+ import { aclLockedBy } from "./acl-lock.js";
26
27
  import { buildDmBody } from "./dm.js";
27
28
  import { peekIdToken } from "../utils/id-token.js";
28
29
  import { findRecentAccessRequest, formatTimeAgo, recordAccessRequest, } from "../utils/access-requests.js";
@@ -458,10 +459,23 @@ async function completeTarget(input, deps, located) {
458
459
  const { resolvedKey, directory, vendDenied, listDenied, verdict, steps } = located;
459
460
  const resolvedPath = `companies/${slug}/${resolvedKey}`;
460
461
  let noAccess = vendDenied || listDenied;
462
+ // Name the empty fencing row only when the tree denies access. A role
463
+ // bypass or creator grant still reads the same as the acl/resolve fast
464
+ // path, which never fetches the tree.
465
+ const lockRow = verdict.kind === "tree" && aclDenied(verdict.tree) ? aclLockedBy(verdict.tree) : null;
466
+ const lockedBy = lockRow
467
+ ? { ...lockRow, path: `companies/${slug}/${toBucketRelative(lockRow.path, slug)}` }
468
+ : undefined;
469
+ if (lockedBy) {
470
+ steps.push(lockedBy.readUnknown
471
+ ? `acl: locked by ${lockedBy.path} unless that row is open (it has no grants; broader grants such as * apply only if it is open)`
472
+ : `acl: locked by ${lockedBy.path} (that row has no grants and is not open; broader grants such as * do not apply)`);
473
+ }
461
474
  if (verdict.kind === "resolved") {
462
475
  steps.push(`acl: ${verdict.permission}`);
463
476
  }
464
- else if (verdict.tree == null || treeIsEmpty(verdict.tree)) {
477
+ else if (verdict.tree == null ||
478
+ (treeIsEmpty(verdict.tree) && !verdict.tree.coveringAclPath)) {
465
479
  steps.push("acl: no record (membership-distributed)");
466
480
  }
467
481
  else if (aclDenied(verdict.tree)) {
@@ -687,6 +701,7 @@ async function completeTarget(input, deps, located) {
687
701
  ...(requestSentAt ? { requestSentAt } : {}),
688
702
  ...(alreadyAskedAt ? { alreadyAskedAt } : {}),
689
703
  ...(requestNote ? { requestNote } : {}),
704
+ ...(lockedBy ? { lockedBy } : {}),
690
705
  }, finishCtx);
691
706
  return result;
692
707
  }
@@ -834,6 +849,8 @@ function jsonPayload(result) {
834
849
  payload.requestSentAt = result.requestSentAt;
835
850
  if (result.alreadyAskedAt)
836
851
  payload.alreadyAskedAt = result.alreadyAskedAt;
852
+ if (result.lockedBy)
853
+ payload.lockedBy = result.lockedBy;
837
854
  return payload;
838
855
  }
839
856
  function emit(input, result, log) {
@@ -0,0 +1,41 @@
1
+ /**
2
+ * Empty-row lock detection for ACL tree responses (feedback dd287b55).
3
+ *
4
+ * The server resolves a path against its most-specific matching FILE_ACL row
5
+ * (`coveringAclPath` on the ACL tree response). Enforcement (files-presign,
6
+ * files-list, content) runs resolveEffectivePermission in hq-pro-core
7
+ * src/tenant-contracts/acl-permission.ts with `unionGrants: true`: grants on
8
+ * every matching row are unioned, except that an EMPTY, NON-OPEN winning row
9
+ * fences the path and grants nothing (only the row's creator and the
10
+ * owner/admin role bypass keep access). An OPEN empty winning row is not a
11
+ * fence: it floors every member at read and broader grants still apply.
12
+ */
13
+ export interface AclLockTree {
14
+ prefix: string;
15
+ direct: unknown[];
16
+ inherited: Array<{
17
+ sourcePrefix: string;
18
+ }>;
19
+ directRow: {
20
+ open: boolean;
21
+ } | null;
22
+ coveringAclPath?: string | null;
23
+ }
24
+ export interface AclLock {
25
+ /** Path of the empty most-specific row. */
26
+ path: string;
27
+ /**
28
+ * True when the row's `open` flag is unknown. The tree reports `open` only
29
+ * for the exact prefix row (directRow), not for ancestor rows. Such a row is
30
+ * a lock if it is not open; if it is open, broader grants still apply.
31
+ */
32
+ readUnknown: boolean;
33
+ }
34
+ /**
35
+ * The empty, non-open row that fences `tree.prefix`, or null when the
36
+ * most-specific row grants something, is open, or no row covers the path.
37
+ */
38
+ export declare function aclLockedBy(tree: AclLockTree | null | undefined): AclLock | null;
39
+ /** One-line description of the lock for human output. */
40
+ export declare function describeAclLock(lock: AclLock, display: string): string;
41
+ //# sourceMappingURL=acl-lock.d.ts.map
@@ -0,0 +1,28 @@
1
+ /**
2
+ * The empty, non-open row that fences `tree.prefix`, or null when the
3
+ * most-specific row grants something, is open, or no row covers the path.
4
+ */
5
+ export function aclLockedBy(tree) {
6
+ const covering = tree?.coveringAclPath;
7
+ if (!tree || typeof covering !== "string" || covering === "")
8
+ return null;
9
+ if (covering === tree.prefix) {
10
+ if (!tree.directRow || tree.directRow.open === true)
11
+ return null;
12
+ if (!Array.isArray(tree.direct) || tree.direct.length > 0)
13
+ return null;
14
+ return { path: covering, readUnknown: false };
15
+ }
16
+ const inherited = Array.isArray(tree.inherited) ? tree.inherited : [];
17
+ if (inherited.some((entry) => entry.sourcePrefix === covering))
18
+ return null;
19
+ return { path: covering, readUnknown: true };
20
+ }
21
+ /** One-line description of the lock for human output. */
22
+ export function describeAclLock(lock, display) {
23
+ if (lock.readUnknown) {
24
+ return `Locked by ${display} unless that row is open: it has no grants, so grants on broader prefixes apply only if it is open.`;
25
+ }
26
+ return `Locked by ${display}: that ACL row has no grants and is not open, so grants on broader prefixes do not apply. Only the row's creator and the owner/admin role bypass have access.`;
27
+ }
28
+ //# sourceMappingURL=acl-lock.js.map
@@ -60,6 +60,8 @@ export declare function mapEnrollFailure(status: number, body: {
60
60
  attemptsRemaining?: unknown;
61
61
  upgradeUrl?: unknown;
62
62
  pricingPlan?: unknown;
63
+ usagePause?: unknown;
64
+ billingState?: unknown;
63
65
  }, headers?: {
64
66
  retryAfter?: string | null;
65
67
  }): EnrollError;
@@ -21,7 +21,7 @@ import * as path from "node:path";
21
21
  import { CLI_VERSION } from "../cli-version.js";
22
22
  import { DEFAULT_VAULT_API_URL, personTokenCacheFile, } from "../utils/cognito-session.js";
23
23
  import { HQ_CLIENT_NAME } from "../utils/vault-api.js";
24
- import { isUsageOnly, pricingPlanFromBody, usageOnlyCardRequiredLines, } from "../lib/billing/usage-only.js";
24
+ import { isUsageOnly, pricingPlanFromBody, usageOnlyCardRequiredLines, usagePauseFromBody, } from "../lib/billing/usage-only.js";
25
25
  import { queueCliLimitNoticeEngagement, queueCliLimitNoticeExposure, } from "../utils/cli-telemetry.js";
26
26
  import { networkTransportErrorCode } from "../utils/network-transport-error.js";
27
27
  import { canOfferTeamUpgrade, formatUpgradeRefusal, isPlanGateError, offerUpgradeLink, planGateErrorFromPayload, } from "../utils/plan-gate-error.js";
@@ -139,6 +139,11 @@ export function mapEnrollFailure(status, body, headers) {
139
139
  return new EnrollError(`This agent was revoked; its enrollment code no longer works. An admin must ` +
140
140
  `create a new external agent or rotate this one.${suffix}`, status, code);
141
141
  }
142
+ // US-024: a usage pause is the server's own answer: its message and link.
143
+ const usagePause = status === 402 ? usagePauseFromBody(body) : null;
144
+ if (usagePause) {
145
+ return new EnrollError(usagePause.message, status, code, usagePause.actionUrl);
146
+ }
142
147
  if ((status === 402 || code === "billing_required") &&
143
148
  isUsageOnly(pricingPlanFromBody(body))) {
144
149
  // Usage pricing (US-012): no paid plan to upgrade to. A company admin adds
@@ -146,8 +151,8 @@ export function mapEnrollFailure(status, body, headers) {
146
151
  return new EnrollError(usageOnlyCardRequiredLines("An external agent").join(" ") + suffix, status, code);
147
152
  }
148
153
  if (status === 402 || code === "billing_required") {
149
- return new EnrollError(`External agents are available on paid HQ plans only. A company admin must ` +
150
- `upgrade the plan (\`hq billing\` or the console billing page) before enrolling.${suffix}`, status, code, typeof body.upgradeUrl === "string" ? body.upgradeUrl : undefined);
154
+ return new EnrollError(`External agents need billing set up for this company. A company admin can ` +
155
+ `add a card with \`hq billing checkout\` or on the console Billing page, then enroll again.${suffix}`, status, code, typeof body.upgradeUrl === "string" ? body.upgradeUrl : undefined);
151
156
  }
152
157
  if (status === 429 || code === "rate_limited") {
153
158
  const retryAfter = headers?.retryAfter?.trim();
@@ -425,7 +430,7 @@ export function registerAgentEnrollCommand(agent) {
425
430
  }
426
431
  catch (openErr) {
427
432
  const message = openErr instanceof Error ? openErr.message : String(openErr);
428
- console.error(chalk.dim(`Could not open the upgrade link automatically: ${message}`));
433
+ console.error(chalk.dim(`Could not open the billing link automatically: ${message}`));
429
434
  }
430
435
  }
431
436
  }
@@ -17,6 +17,7 @@
17
17
  * output in ~/.hq-agent/logs/kit.log). Logs: ~/.hq-agent/logs/<service>.log. Skills:
18
18
  * ~/.hq-agent/skills/<name>/SKILL.md.
19
19
  */
20
+ import { createWakeSecretResolver } from "../lib/agent-kit/wake-secrets.js";
20
21
  import chalk from "chalk";
21
22
  import * as fs from "node:fs";
22
23
  import * as os from "node:os";
@@ -37,7 +38,7 @@ import { serviceLogPath } from "../lib/agent-kit/paths.js";
37
38
  import { runResidentCommandWithPackageLease } from "../utils/resident-package-preload.js";
38
39
  import { spawn } from "node:child_process";
39
40
  import { pendingInbox } from "../lib/agent-kit/inbox-state.js";
40
- import { buildWakeNotice, clearWakeConfig, currentWakeState, DEFAULT_WAKE_RETRY_MS, describeWake, fireWake, readWakeConfig, validateWakeUrl, writeWakeConfig, } from "../lib/agent-kit/wake.js";
41
+ import { parseHeaderSecrets, buildWakeNotice, clearWakeConfig, currentWakeState, DEFAULT_WAKE_RETRY_MS, describeWake, fireWake, readWakeConfig, validateWakeUrl, writeWakeConfig, } from "../lib/agent-kit/wake.js";
41
42
  import { runSyncLoop } from "../lib/agent-kit/run/sync.js";
42
43
  import { resolveHqBinary, resolveNodeBinary, } from "../lib/mesh/live/daemon/install.js";
43
44
  import { installServices, servicesStatus, uninstallServices, } from "../lib/service-manager/index.js";
@@ -380,6 +381,7 @@ export function registerAgentKitCommand(agent) {
380
381
  .description("POST a notice to a webhook URL (e.g. a Grok Bot routine webhook) when mail is pending. Prefer --stdin so the URL stays out of shell history")
381
382
  .option("--stdin", "Read the URL from stdin")
382
383
  .option("--retry-minutes <n>", "Re-wake while items stay pending (default 10, minimum 1)")
384
+ .option("--header-secret <Header=SECRET>", "Send a header using a stored company secret (repeatable; value resolved at wake time)", (value, previous) => [...previous, value], [])
383
385
  .action(async (url, opts) => {
384
386
  try {
385
387
  const paths = agentKitPaths(os.homedir(), process.env);
@@ -393,7 +395,8 @@ export function registerAgentKitCommand(agent) {
393
395
  }
394
396
  if (!raw)
395
397
  throw new Error("Provide the URL as an argument or with --stdin");
396
- const config = { kind: "url", url: validateWakeUrl(raw), retryMs: retryMsFrom(opts.retryMinutes) };
398
+ const headerSecrets = parseHeaderSecrets(opts.headerSecret);
399
+ const config = { kind: "url", url: validateWakeUrl(raw), retryMs: retryMsFrom(opts.retryMinutes), ...(Object.keys(headerSecrets).length ? { headerSecrets } : {}) };
397
400
  writeWakeConfig(paths, config);
398
401
  console.log(chalk.green(`Wake set: ${describeWake(config)}`));
399
402
  console.log("Test it: hq agent kit wake test");
@@ -450,15 +453,14 @@ export function registerAgentKitCommand(agent) {
450
453
  .description("Fire the wake once now with the current pending count")
451
454
  .action(async () => {
452
455
  try {
453
- const paths = agentKitPaths(os.homedir(), process.env);
454
- const creds = requireExternalCreds(paths);
455
- ensureKitDirs(paths);
456
+ const runtime = buildKitRuntime("inbox");
457
+ const { paths, creds } = runtime;
456
458
  const config = readWakeConfig(paths);
457
459
  if (!config)
458
460
  throw new Error("No wake configured. Run hq agent kit wake set-url --stdin (or set-command).");
459
461
  const log = createKitLogger(paths, "wake", { echo: false });
460
462
  const ids = pendingInbox(paths).map((e) => e.id);
461
- const result = await fireWake(config, buildWakeNotice(creds.entityUid, "test", ids, new Date()), { paths, agentUid: creds.entityUid, log });
463
+ const result = await fireWake(config, buildWakeNotice(creds.entityUid, "test", ids, new Date()), { paths, agentUid: creds.entityUid, log, resolveHeaderSecrets: createWakeSecretResolver(creds.companySlug, runtime.getToken, creds.apiBaseUrl) });
462
464
  console.log(result.ok ? chalk.green(result.detail) : chalk.red(result.detail));
463
465
  if (!result.ok)
464
466
  process.exit(1);
@@ -131,12 +131,6 @@ export function agentCreatePlanLimitFromPayload(status, body) {
131
131
  : {}),
132
132
  };
133
133
  }
134
- function formatServerPrice(amountMinor, currency) {
135
- return new Intl.NumberFormat("en-US", {
136
- style: "currency",
137
- currency,
138
- }).format(amountMinor / 100);
139
- }
140
134
  async function offerAgentCheckout(url) {
141
135
  const rl = readline.createInterface({ input: process.stdin, output: process.stderr });
142
136
  try {
@@ -146,7 +140,7 @@ async function offerAgentCheckout(url) {
146
140
  });
147
141
  }
148
142
  catch {
149
- console.error("Could not open the upgrade page automatically. Use the URL above.");
143
+ console.error("Could not open the billing page automatically. Use the URL above.");
150
144
  return false;
151
145
  }
152
146
  finally {
@@ -156,7 +150,7 @@ async function offerAgentCheckout(url) {
156
150
  /** Print a server-priced plan block without minting or rewriting its URL. */
157
151
  export async function surfaceAgentCreatePlanLimit(planLimit, opts = {}) {
158
152
  if (isUsageOnly(opts.pricingPlan)) {
159
- // Usage pricing (US-012): no Workforce plan to buy. The company adds a
153
+ // Usage pricing (US-012): no flat plan to buy. The company adds a
160
154
  // card and each agent box bills by size from the usage card.
161
155
  const card = opts.usageCard ?? BUNDLED_USAGE_CARD;
162
156
  for (const line of usageOnlyCardRequiredLines("An agent box", card)) {
@@ -166,13 +160,13 @@ export async function surfaceAgentCreatePlanLimit(planLimit, opts = {}) {
166
160
  queueCliLimitNoticeExposure("agents", "refusal", opts.companyUid);
167
161
  return;
168
162
  }
169
- const price = `${formatServerPrice(planLimit.amountMinor, planLimit.currency)}/month`;
170
- const reason = `HQ Workforce plan required to create an agent (${price}).`;
163
+ // The server's price was the flat plan's, which is closed: name no plan or price.
164
+ const reason = "Agents are not included on this company's current billing.";
171
165
  const argv = opts.json ? [...process.argv, "--json"] : process.argv;
172
166
  const interactive = canOfferTeamUpgrade(argv);
173
167
  if (!planLimit.checkoutUrl) {
174
168
  console.error(reason);
175
- console.error("Ask a company owner to upgrade before creating an agent.");
169
+ console.error("Ask a company owner to check the company's Billing page before creating an agent.");
176
170
  queueCliLimitNoticeExposure("agents", "refusal", opts.companyUid);
177
171
  return;
178
172
  }
@@ -2,9 +2,9 @@
2
2
  * `hq billing sponsor` — the individual-pull half of plan sponsorship, from the
3
3
  * terminal ("ask a company to sponsor me", hq-pro #3609).
4
4
  *
5
- * A person who is over a personal limit has exactly three ways out: pay $50
6
- * themselves, reduce what they store, or ask a company to cover the $50 plan
7
- * line. This command is the third one, plus the admin side that answers it.
5
+ * A person who is over a personal limit has exactly three ways out: pay for
6
+ * the personal plan themselves, reduce what they store, or ask a company to
7
+ * cover it. This command is the third one, plus the admin side that answers it.
8
8
  *
9
9
  * hq billing sponsor request <company> — ask that company to cover my plan
10
10
  * hq billing sponsor list — the queue (admins), or my own
@@ -36,7 +36,7 @@ export declare const PLAN_RESOURCE: "plan";
36
36
  /** What a person is told when hq-pro's plan-sponsorship switch is off. */
37
37
  export declare const FEATURE_OFF_MESSAGE = "Asking a company to sponsor your plan isn't available yet.";
38
38
  /** What v1 sponsorship covers, stated wherever a person confirms or reads it. */
39
- export declare const COVERAGE_SENTENCE = "Your company pays your $50 plan. Storage above your plan's allowance is still yours.";
39
+ export declare const COVERAGE_SENTENCE = "Your company pays your personal plan. Storage above your plan's allowance is still yours.";
40
40
  /** What a person is told while an approval waits on the sponsor's card. */
41
41
  export declare const HELD_FOR_CARD_SENTENCE = "Your company said yes and is adding a card. Your limits still apply until then.";
42
42
  /** One request row as hq-pro persists and returns it. */
@@ -2,9 +2,9 @@
2
2
  * `hq billing sponsor` — the individual-pull half of plan sponsorship, from the
3
3
  * terminal ("ask a company to sponsor me", hq-pro #3609).
4
4
  *
5
- * A person who is over a personal limit has exactly three ways out: pay $50
6
- * themselves, reduce what they store, or ask a company to cover the $50 plan
7
- * line. This command is the third one, plus the admin side that answers it.
5
+ * A person who is over a personal limit has exactly three ways out: pay for
6
+ * the personal plan themselves, reduce what they store, or ask a company to
7
+ * cover it. This command is the third one, plus the admin side that answers it.
8
8
  *
9
9
  * hq billing sponsor request <company> — ask that company to cover my plan
10
10
  * hq billing sponsor list — the queue (admins), or my own
@@ -41,7 +41,7 @@ export const PLAN_RESOURCE = "plan";
41
41
  /** What a person is told when hq-pro's plan-sponsorship switch is off. */
42
42
  export const FEATURE_OFF_MESSAGE = "Asking a company to sponsor your plan isn't available yet.";
43
43
  /** What v1 sponsorship covers, stated wherever a person confirms or reads it. */
44
- export const COVERAGE_SENTENCE = "Your company pays your $50 plan. Storage above your plan's allowance is still yours.";
44
+ export const COVERAGE_SENTENCE = "Your company pays your personal plan. Storage above your plan's allowance is still yours.";
45
45
  /** What a person is told while an approval waits on the sponsor's card. */
46
46
  export const HELD_FOR_CARD_SENTENCE = "Your company said yes and is adding a card. Your limits still apply until then.";
47
47
  /**
@@ -85,7 +85,7 @@ export function messageForError(status, body) {
85
85
  if (status === 401)
86
86
  return "Your sign-in has expired. Run `hq login` and try again.";
87
87
  if (status === 403 && code === "SPONSOR_PLAN_REQUIRED") {
88
- return "That company's plan can't cover an individual plan. Only HQ Workforce and Enterprise companies can.";
88
+ return "That company can't cover a personal plan on its current billing. Ask an owner of that company to check its Billing page.";
89
89
  }
90
90
  if (status === 403) {
91
91
  return "You're not allowed to do that. Only an owner or admin of the company can answer a sponsorship request.";
@@ -209,7 +209,7 @@ export function registerBillingSponsorCommands(billing) {
209
209
  process.stdout.write(JSON.stringify(data.request, null, 2) + "\n");
210
210
  return;
211
211
  }
212
- console.log(`Asked ${company} to cover your $50 HQ plan.`);
212
+ console.log(`Asked ${company} to cover your personal HQ plan.`);
213
213
  console.log(COVERAGE_SENTENCE);
214
214
  console.log(`An owner or admin there answers next. Check with \`hq billing sponsor list\`.`);
215
215
  }
@@ -276,11 +276,11 @@ export function registerBillingSponsorCommands(billing) {
276
276
  }
277
277
  const where = named ?? "that company";
278
278
  if (!state.active) {
279
- console.log(`No company is covering your $50 HQ plan right now.`);
279
+ console.log(`No company is covering your personal HQ plan right now.`);
280
280
  console.log(`If you asked ${where}, they haven't answered yet — an owner or admin there answers next.`);
281
281
  return;
282
282
  }
283
- console.log(`Your $50 HQ plan: ${statusLabel(state.status ?? "active")}`);
283
+ console.log(`Your personal HQ plan: ${statusLabel(state.status ?? "active")}`);
284
284
  if (state.requestId)
285
285
  console.log(` ${chalk.bold(state.requestId)}`);
286
286
  console.log(COVERAGE_SENTENCE);
@@ -328,7 +328,7 @@ export function registerBillingSponsorCommands(billing) {
328
328
  }
329
329
  console.log((await companyIsUsageOnly(result.companyUid))
330
330
  ? "Approved. Their personal plan is now on the company bill."
331
- : "Approved. Their $50 plan is now on the company bill.");
331
+ : "Approved. Their personal plan is now on the company bill.");
332
332
  }
333
333
  catch (err) {
334
334
  fail(err);
@@ -370,7 +370,7 @@ export function registerBillingSponsorCommands(billing) {
370
370
  }
371
371
  console.log((await companyIsUsageOnly(result.companyUid))
372
372
  ? "Ended. Their personal plan goes back to them from the next bill."
373
- : "Ended. Their $50 plan goes back to them from the next bill.");
373
+ : "Ended. Their personal plan goes back to them from the next bill.");
374
374
  }
375
375
  catch (err) {
376
376
  fail(err);
@@ -396,7 +396,7 @@ export function registerBillingSponsorCommands(billing) {
396
396
  console.log(usageOnlySponsorLines()[2]);
397
397
  return;
398
398
  }
399
- console.log("Withdrawn. You can pay the $50 yourself, reduce what you store, or ask another company.");
399
+ console.log("Withdrawn. You can pay for your plan yourself, reduce what you store, or ask another company.");
400
400
  }
401
401
  catch (err) {
402
402
  fail(err);
@@ -12,18 +12,18 @@
12
12
  *
13
13
  * Tenant boundary: the company comes from the caller's own active memberships
14
14
  * (`/membership/me`); a slug outside them is refused before any read. The
15
- * usage-limits routes are member-gated on the server as well. With no cloud
15
+ * company statement is owner-gated on the server as well. With no cloud
16
16
  * company (or `--personal`) it reads the caller's personal scope.
17
17
  *
18
- * Usage numbers come through `readMonthUsage` (src/lib/billing/usage-cost.ts),
19
- * the single seam to switch to the US-005 daily rollup once it has a read route.
18
+ * Usage numbers come through `readMonthUsage` (src/lib/billing/usage-cost.ts)
19
+ * from the usage statement, `GET /v1/billing/usage` (hq-pro US-006). Only a
20
+ * server without that route (404) falls back to the usage-limits readings.
20
21
  *
21
22
  * Only an account on usage pricing gets costs. The plan comes from the
22
- * server: `pricingPlan` / `usageOnly` on the usage-limits body (hq-pro #5042,
23
- * computed with `isOnUsagePricing`). Only when that body does not say, the
24
- * billing summary the command already reads for the trial. A flat-plan account (for example a Workforce
25
- * company that subscribed before the 2026-10-16 cutover) and an account the
26
- * server says nothing about get readings only, with no rate, cost or
23
+ * server's shared plan status (`billingState`, hq-pro #5088) on the billing
24
+ * summary, with the legacy `pricingPlan` / `usageOnly` fields for a server
25
+ * that does not send it. An account on an earlier flat plan and an account
26
+ * the server says nothing about get readings only, with no rate, cost or
27
27
  * projected bill.
28
28
  */
29
29
  import type { Command } from "commander";
@@ -34,8 +34,19 @@ export interface MembershipRef {
34
34
  slug: string;
35
35
  role?: string;
36
36
  }
37
+ /** One `GET /v1/billing/usage` answer: the HTTP status and the decoded body. */
38
+ export interface UsageStatementResponse {
39
+ status: number;
40
+ body: unknown | null;
41
+ }
37
42
  export interface BillingUsageDeps {
38
43
  listCompanies(): Promise<MembershipRef[]>;
44
+ /**
45
+ * `GET /v1/billing/usage` for the scope (US-015). Absent, or a 404 from a
46
+ * server without the route, falls back to {@link fetchUsageLimits}.
47
+ */
48
+ fetchUsageStatement?: (scope: UsageScope) => Promise<UsageStatementResponse>;
49
+ /** Legacy readings for a server without the statement route. */
39
50
  fetchUsageLimits: ReadMonthUsageDeps["fetchUsageLimits"];
40
51
  readStorageSplit?: (company: MembershipRef, opts: {
41
52
  fresh: boolean;
@@ -77,6 +88,9 @@ export declare function pricingPlanFromSummary(body: unknown): PricingPlan | nul
77
88
  * only when usage-limits does not say.
78
89
  */
79
90
  export declare function resolveOnUsagePricing(limitsPlan: PricingPlan | null, summaryPlan: PricingPlan | null): boolean | null;
91
+ /** Thrown when the company statement is refused to a caller who is not its owner. */
92
+ export declare class UsageStatementForbiddenError extends Error {
93
+ }
80
94
  export declare function runBillingUsage(opts: BillingUsageOptions, deps: BillingUsageDeps): Promise<BillingUsageReport>;
81
95
  /**
82
96
  * `hq billing usage --card`: the card from GET /v1/pricing, or the bundled