@indigoai-us/hq-cli 5.345.159 → 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 (54) hide show
  1. package/CHANGELOG.md +24 -0
  2. package/dist/command-catalog.generated.d.ts +4 -4
  3. package/dist/command-catalog.generated.js +4 -4
  4. package/dist/commands/access.d.ts +6 -0
  5. package/dist/commands/access.js +18 -1
  6. package/dist/commands/acl-lock.d.ts +41 -0
  7. package/dist/commands/acl-lock.js +28 -0
  8. package/dist/commands/agent-enroll.d.ts +2 -0
  9. package/dist/commands/agent-enroll.js +9 -4
  10. package/dist/commands/agents.js +5 -11
  11. package/dist/commands/billing-sponsor.d.ts +4 -4
  12. package/dist/commands/billing-sponsor.js +11 -11
  13. package/dist/commands/billing-usage.d.ts +22 -8
  14. package/dist/commands/billing-usage.js +57 -22
  15. package/dist/commands/billing.d.ts +7 -7
  16. package/dist/commands/billing.js +13 -13
  17. package/dist/commands/cloud-provision.js +2 -2
  18. package/dist/commands/cloud.js +7 -7
  19. package/dist/commands/db-remote-deps.js +12 -1
  20. package/dist/commands/files.js +19 -7
  21. package/dist/commands/meetings.d.ts +4 -4
  22. package/dist/commands/meetings.js +17 -8
  23. package/dist/commands/skill.js +6 -6
  24. package/dist/commands/whoami.js +1 -1
  25. package/dist/hook-engine-path.js +13 -2
  26. package/dist/hq-cloud-sync-runner/bin/sync-runner.js +982 -426
  27. package/dist/hq-cloud-sync-runner/package.json +1 -1
  28. package/dist/lib/billing/plan-limit-refusals.d.ts +13 -3
  29. package/dist/lib/billing/plan-limit-refusals.js +26 -3
  30. package/dist/lib/billing/plan-limit-upgrade-url.d.ts +8 -0
  31. package/dist/lib/billing/plan-limit-upgrade-url.js +18 -0
  32. package/dist/lib/billing/plan-lock.d.ts +22 -6
  33. package/dist/lib/billing/plan-lock.js +58 -30
  34. package/dist/lib/billing/usage-cost.d.ts +24 -2
  35. package/dist/lib/billing/usage-cost.js +103 -3
  36. package/dist/lib/billing/usage-only.d.ts +37 -1
  37. package/dist/lib/billing/usage-only.js +89 -4
  38. package/dist/lib/billing/usage-pause.d.ts +27 -0
  39. package/dist/lib/billing/usage-pause.js +50 -0
  40. package/dist/lib/db/control-plane.d.ts +5 -0
  41. package/dist/lib/db/control-plane.js +10 -0
  42. package/dist/lib/flag-registry-worker-source.generated.d.ts +1 -1
  43. package/dist/lib/flag-registry-worker-source.generated.js +1 -1
  44. package/dist/lib/hook-engine/resolve.d.ts +14 -0
  45. package/dist/lib/hook-engine/resolve.js +55 -1
  46. package/dist/lib/plan-limit-nag.d.ts +0 -16
  47. package/dist/lib/plan-limit-nag.js +17 -18
  48. package/dist/main.d.ts +0 -2
  49. package/dist/main.js +19 -57
  50. package/dist/utils/plan-gate-error.d.ts +30 -14
  51. package/dist/utils/plan-gate-error.js +60 -46
  52. package/dist/utils/team-upgrade.d.ts +19 -6
  53. package/dist/utils/team-upgrade.js +37 -39
  54. package/package.json +8 -8
@@ -24,8 +24,44 @@
24
24
  */
25
25
  import { type UsageCard } from "./usage-card.js";
26
26
  export type PricingPlan = "usage-only" | "current";
27
+ /** Whether a server supplied shared status (including an unreadable/null value). */
28
+ export declare function hasBillingStateField(body: unknown): boolean;
29
+ export type BillingStateKind = "usage" | "usage-trial" | "legacy-payer" | "legacy-free" | "enterprise" | "comped";
27
30
  /**
28
- * Read the pricing plan out of a decoded hq-pro body. `pricingPlan` wins;
31
+ * hq-pro's shared plan status (`billingState`, hq-pro #5088
32
+ * src/billing/billing-state.ts), the fields the CLI reads. Version 1 only.
33
+ */
34
+ export interface SharedBillingState {
35
+ kind: BillingStateKind;
36
+ scope: "company" | "person";
37
+ view: "full" | "member";
38
+ trial: {
39
+ daysLeft: number;
40
+ endsAt: string;
41
+ } | null;
42
+ /** A pause the server is applying now, or null. */
43
+ paused: {
44
+ reason: string;
45
+ resumesOn: string | null;
46
+ } | null;
47
+ /** What this caller may do. All false in the member view. */
48
+ actions: {
49
+ addCard: boolean;
50
+ raiseLimit: boolean;
51
+ switchToUsage: boolean;
52
+ manageBilling: boolean;
53
+ };
54
+ }
55
+ /**
56
+ * Read a known v1 `billingState` off a decoded body. An unknown version,
57
+ * scope, view or kind (or a malformed trial) is null: it is never read as a
58
+ * legacy plan. A person state is always the full view.
59
+ */
60
+ export declare function readBillingState(body: unknown): SharedBillingState | null;
61
+ export { usagePauseFromBody, type UsagePause } from "./usage-pause.js";
62
+ /**
63
+ * Read the pricing plan out of a decoded hq-pro body. Known `billingState` wins;
64
+ * only a server without that field uses the legacy `pricingPlan` signal;
29
65
  * a `personalUsage` object (person summary) means usage-only. Anything else
30
66
  * is UNKNOWN.
31
67
  */
@@ -40,8 +40,84 @@ function asRecord(value) {
40
40
  ? value
41
41
  : null;
42
42
  }
43
+ /** Whether a server supplied shared status (including an unreadable/null value). */
44
+ export function hasBillingStateField(body) {
45
+ const rec = asRecord(body);
46
+ return rec !== null && Object.prototype.hasOwnProperty.call(rec, "billingState");
47
+ }
48
+ const BILLING_STATE_KINDS = [
49
+ "usage",
50
+ "usage-trial",
51
+ "legacy-payer",
52
+ "legacy-free",
53
+ "enterprise",
54
+ "comped",
55
+ ];
56
+ function readTrial(value) {
57
+ if (value === null || value === undefined)
58
+ return null;
59
+ const trial = asRecord(value);
60
+ if (!trial ||
61
+ typeof trial.daysLeft !== "number" ||
62
+ !Number.isFinite(trial.daysLeft) ||
63
+ trial.daysLeft < 0 ||
64
+ typeof trial.endsAt !== "string" ||
65
+ !Number.isFinite(Date.parse(trial.endsAt))) {
66
+ return undefined;
67
+ }
68
+ return { daysLeft: trial.daysLeft, endsAt: trial.endsAt };
69
+ }
70
+ function readPaused(value) {
71
+ const paused = asRecord(value);
72
+ if (!paused || typeof paused.reason !== "string" || !paused.reason)
73
+ return null;
74
+ return {
75
+ reason: paused.reason,
76
+ resumesOn: typeof paused.resumesOn === "string" ? paused.resumesOn : null,
77
+ };
78
+ }
43
79
  /**
44
- * Read the pricing plan out of a decoded hq-pro body. `pricingPlan` wins;
80
+ * Read a known v1 `billingState` off a decoded body. An unknown version,
81
+ * scope, view or kind (or a malformed trial) is null: it is never read as a
82
+ * legacy plan. A person state is always the full view.
83
+ */
84
+ export function readBillingState(body) {
85
+ const state = asRecord(asRecord(body)?.billingState);
86
+ if (!state || state.version !== 1)
87
+ return null;
88
+ if (state.scope !== "company" && state.scope !== "person")
89
+ return null;
90
+ if (state.view !== "full" && state.view !== "member")
91
+ return null;
92
+ if (state.scope === "person" && state.view !== "full")
93
+ return null;
94
+ if (typeof state.kind !== "string" || !BILLING_STATE_KINDS.includes(state.kind))
95
+ return null;
96
+ const trial = readTrial(state.trial);
97
+ if (trial === undefined)
98
+ return null;
99
+ const actions = asRecord(state.actions);
100
+ return {
101
+ kind: state.kind,
102
+ scope: state.scope,
103
+ view: state.view,
104
+ trial,
105
+ paused: readPaused(state.paused),
106
+ actions: {
107
+ addCard: actions?.addCard === true,
108
+ raiseLimit: actions?.raiseLimit === true,
109
+ switchToUsage: actions?.switchToUsage === true,
110
+ manageBilling: actions?.manageBilling === true,
111
+ },
112
+ };
113
+ }
114
+ function sharedBillingState(body) {
115
+ return readBillingState(body);
116
+ }
117
+ export { usagePauseFromBody } from "./usage-pause.js";
118
+ /**
119
+ * Read the pricing plan out of a decoded hq-pro body. Known `billingState` wins;
120
+ * only a server without that field uses the legacy `pricingPlan` signal;
45
121
  * a `personalUsage` object (person summary) means usage-only. Anything else
46
122
  * is UNKNOWN.
47
123
  */
@@ -49,6 +125,10 @@ export function pricingPlanFromBody(body) {
49
125
  const rec = asRecord(body);
50
126
  if (!rec)
51
127
  return null;
128
+ if (hasBillingStateField(rec)) {
129
+ const state = sharedBillingState(rec);
130
+ return state ? (state.kind === "usage" || state.kind === "usage-trial" ? "usage-only" : "current") : null;
131
+ }
52
132
  if (rec.pricingPlan === "usage-only")
53
133
  return "usage-only";
54
134
  if (rec.pricingPlan === "current")
@@ -62,6 +142,8 @@ export function isUsageOnly(plan) {
62
142
  }
63
143
  /** True when a company summary says a usage-only trial is running. Absent is false. */
64
144
  export function usageTrialActiveFromBody(body) {
145
+ if (hasBillingStateField(body))
146
+ return sharedBillingState(body)?.kind === "usage-trial";
65
147
  return asRecord(asRecord(body)?.usageTrial)?.status === "active";
66
148
  }
67
149
  async function defaultGetJson(token, path, query, timeoutMs) {
@@ -111,9 +193,12 @@ export function pricingPlanFromMembership(body, companyRef) {
111
193
  const slug = typeof row.companySlug === "string" ? row.companySlug.toLowerCase() : "";
112
194
  if (uid !== ref && slug !== ref)
113
195
  continue;
114
- if (row.pricingPlan === "usage-only" || row.pricingPlan === "current")
115
- return row.pricingPlan;
116
- return null;
196
+ if (hasBillingStateField(row)) {
197
+ const state = sharedBillingState(row);
198
+ if (!state || state.scope !== "company")
199
+ return null;
200
+ }
201
+ return pricingPlanFromBody(row);
117
202
  }
118
203
  return null;
119
204
  }
@@ -0,0 +1,27 @@
1
+ /**
2
+ * The usage pause block on a 402 body (hq-pro src/billing/usage-pause.ts,
3
+ * US-024): `{ code: "usage_paused", reason, message, actionUrl }`.
4
+ *
5
+ * Every refusal hq-pro makes because of an account's usage state (the trial
6
+ * cap, a card needed past the monthly allowance, the monthly spend limit)
7
+ * carries it. A client shows `message` as written and links `actionUrl`; it
8
+ * adds no plan name and no upgrade offer.
9
+ *
10
+ * Leaf module (no imports) so the sync refusal tracker and the 402 decoder
11
+ * can share it without loading the HTTP layer.
12
+ */
13
+ export declare const USAGE_PAUSE_CODE = "usage_paused";
14
+ export interface UsagePause {
15
+ /** `usage_trial_cap` | `usage_card_required` | `usage_spend_limit` (or a newer code). */
16
+ reason: string;
17
+ /** One plain-language paragraph from the server. */
18
+ message: string;
19
+ /** Where to act: the company or personal Billing page (https). */
20
+ actionUrl?: string;
21
+ }
22
+ /**
23
+ * Read `usagePause` off a decoded 402 body, or off any record that carries
24
+ * it. Null when absent or malformed (wrong code, empty message).
25
+ */
26
+ export declare function usagePauseFromBody(body: unknown): UsagePause | null;
27
+ //# sourceMappingURL=usage-pause.d.ts.map
@@ -0,0 +1,50 @@
1
+ /**
2
+ * The usage pause block on a 402 body (hq-pro src/billing/usage-pause.ts,
3
+ * US-024): `{ code: "usage_paused", reason, message, actionUrl }`.
4
+ *
5
+ * Every refusal hq-pro makes because of an account's usage state (the trial
6
+ * cap, a card needed past the monthly allowance, the monthly spend limit)
7
+ * carries it. A client shows `message` as written and links `actionUrl`; it
8
+ * adds no plan name and no upgrade offer.
9
+ *
10
+ * Leaf module (no imports) so the sync refusal tracker and the 402 decoder
11
+ * can share it without loading the HTTP layer.
12
+ */
13
+ export const USAGE_PAUSE_CODE = "usage_paused";
14
+ function asRecord(value) {
15
+ return value !== null && typeof value === "object" && !Array.isArray(value)
16
+ ? value
17
+ : null;
18
+ }
19
+ function httpsUrl(value) {
20
+ if (typeof value !== "string" || value.trim() !== value)
21
+ return undefined;
22
+ try {
23
+ const url = new URL(value);
24
+ return url.protocol === "https:" && url.hostname && !url.username && !url.password
25
+ ? value
26
+ : undefined;
27
+ }
28
+ catch {
29
+ return undefined;
30
+ }
31
+ }
32
+ /**
33
+ * Read `usagePause` off a decoded 402 body, or off any record that carries
34
+ * it. Null when absent or malformed (wrong code, empty message).
35
+ */
36
+ export function usagePauseFromBody(body) {
37
+ const pause = asRecord(asRecord(body)?.usagePause);
38
+ if (!pause || pause.code !== USAGE_PAUSE_CODE)
39
+ return null;
40
+ const message = typeof pause.message === "string" ? pause.message.trim() : "";
41
+ if (!message)
42
+ return null;
43
+ const actionUrl = httpsUrl(pause.actionUrl);
44
+ return {
45
+ reason: typeof pause.reason === "string" ? pause.reason : "",
46
+ message,
47
+ ...(actionUrl ? { actionUrl } : {}),
48
+ };
49
+ }
50
+ //# sourceMappingURL=usage-pause.js.map
@@ -1,3 +1,4 @@
1
+ import { type UsagePause } from "../billing/usage-pause.js";
1
2
  /**
2
3
  * Control-plane client for remote vault DB (US-009).
3
4
  * Injectable fetch for tests — never logs response bodies that might hold secrets.
@@ -167,5 +168,9 @@ export type ControlPlaneHttpError = Error & {
167
168
  status?: number;
168
169
  code?: string;
169
170
  reason?: string;
171
+ /** The 402 usage pause (hq-pro `usagePause`), when the server sent one. */
172
+ usagePause?: UsagePause;
173
+ /** The body's customer sentence (`message`), when it sent one. */
174
+ serverMessage?: string;
170
175
  };
171
176
  //# sourceMappingURL=control-plane.d.ts.map
@@ -1,4 +1,5 @@
1
1
  import { planGateErrorFromPayload } from "../../utils/plan-gate-error.js";
2
+ import { usagePauseFromBody } from "../billing/usage-pause.js";
2
3
  // Provision may allocate remote resources, so allow two minutes and never retry.
3
4
  const DB_PROVISION_TIMEOUT_MS = 120_000;
4
5
  // Status is a read-only probe; 15 seconds is longer than its normal response time.
@@ -195,6 +196,8 @@ function httpError(label, status, text) {
195
196
  let msg = `${label} failed (${status})`;
196
197
  let code;
197
198
  let reason;
199
+ let usagePause = null;
200
+ let serverMessage;
198
201
  try {
199
202
  const j = JSON.parse(text);
200
203
  if (typeof j.error === "string")
@@ -203,6 +206,9 @@ function httpError(label, status, text) {
203
206
  code = j.code;
204
207
  if (typeof j.reason === "string")
205
208
  reason = j.reason;
209
+ if (typeof j.message === "string" && j.message.trim())
210
+ serverMessage = j.message.trim();
211
+ usagePause = usagePauseFromBody(j);
206
212
  }
207
213
  catch {
208
214
  /* keep */
@@ -213,6 +219,10 @@ function httpError(label, status, text) {
213
219
  err.code = code;
214
220
  if (reason)
215
221
  err.reason = reason;
222
+ if (usagePause)
223
+ err.usagePause = usagePause;
224
+ if (serverMessage)
225
+ err.serverMessage = serverMessage;
216
226
  return err;
217
227
  }
218
228
  function personalHttpError(label, status, text) {