@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.
- package/CHANGELOG.md +24 -0
- package/dist/command-catalog.generated.d.ts +4 -4
- package/dist/command-catalog.generated.js +4 -4
- package/dist/commands/access.d.ts +6 -0
- package/dist/commands/access.js +18 -1
- package/dist/commands/acl-lock.d.ts +41 -0
- package/dist/commands/acl-lock.js +28 -0
- package/dist/commands/agent-enroll.d.ts +2 -0
- package/dist/commands/agent-enroll.js +9 -4
- package/dist/commands/agents.js +5 -11
- package/dist/commands/billing-sponsor.d.ts +4 -4
- package/dist/commands/billing-sponsor.js +11 -11
- package/dist/commands/billing-usage.d.ts +22 -8
- package/dist/commands/billing-usage.js +57 -22
- package/dist/commands/billing.d.ts +7 -7
- package/dist/commands/billing.js +13 -13
- package/dist/commands/cloud-provision.js +2 -2
- package/dist/commands/cloud.js +7 -7
- package/dist/commands/db-remote-deps.js +12 -1
- package/dist/commands/files.js +19 -7
- package/dist/commands/meetings.d.ts +4 -4
- package/dist/commands/meetings.js +17 -8
- package/dist/commands/skill.js +6 -6
- package/dist/commands/whoami.js +1 -1
- package/dist/hook-engine-path.js +13 -2
- package/dist/hq-cloud-sync-runner/bin/sync-runner.js +982 -426
- package/dist/hq-cloud-sync-runner/package.json +1 -1
- package/dist/lib/billing/plan-limit-refusals.d.ts +13 -3
- package/dist/lib/billing/plan-limit-refusals.js +26 -3
- package/dist/lib/billing/plan-limit-upgrade-url.d.ts +8 -0
- package/dist/lib/billing/plan-limit-upgrade-url.js +18 -0
- package/dist/lib/billing/plan-lock.d.ts +22 -6
- package/dist/lib/billing/plan-lock.js +58 -30
- package/dist/lib/billing/usage-cost.d.ts +24 -2
- package/dist/lib/billing/usage-cost.js +103 -3
- package/dist/lib/billing/usage-only.d.ts +37 -1
- package/dist/lib/billing/usage-only.js +89 -4
- package/dist/lib/billing/usage-pause.d.ts +27 -0
- package/dist/lib/billing/usage-pause.js +50 -0
- package/dist/lib/db/control-plane.d.ts +5 -0
- package/dist/lib/db/control-plane.js +10 -0
- package/dist/lib/flag-registry-worker-source.generated.d.ts +1 -1
- package/dist/lib/flag-registry-worker-source.generated.js +1 -1
- package/dist/lib/hook-engine/resolve.d.ts +14 -0
- package/dist/lib/hook-engine/resolve.js +55 -1
- package/dist/lib/plan-limit-nag.d.ts +0 -16
- package/dist/lib/plan-limit-nag.js +17 -18
- package/dist/main.d.ts +0 -2
- package/dist/main.js +19 -57
- package/dist/utils/plan-gate-error.d.ts +30 -14
- package/dist/utils/plan-gate-error.js +60 -46
- package/dist/utils/team-upgrade.d.ts +19 -6
- package/dist/utils/team-upgrade.js +37 -39
- 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
|
-
*
|
|
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
|
|
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
|
|
115
|
-
|
|
116
|
-
|
|
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) {
|