@indigoai-us/hq-cli 5.345.162 → 5.345.164
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 +33 -0
- package/dist/command-catalog.generated.d.ts +341 -1
- package/dist/command-catalog.generated.js +422 -1
- package/dist/command-registration-plan.d.ts +6 -0
- package/dist/command-registration-plan.js +1 -0
- package/dist/commands/billing-usage.d.ts +16 -2
- package/dist/commands/billing-usage.js +63 -9
- package/dist/commands/billing.d.ts +34 -0
- package/dist/commands/billing.js +108 -1
- package/dist/commands/deploy-domains.d.ts +53 -0
- package/dist/commands/deploy-domains.js +802 -0
- package/dist/commands/feedback-triage.d.ts +67 -0
- package/dist/commands/feedback-triage.js +294 -0
- package/dist/commands/feedback.d.ts +13 -1
- package/dist/commands/feedback.js +32 -8
- package/dist/lib/billing/billing-status-lines.d.ts +14 -0
- package/dist/lib/billing/billing-status-lines.js +43 -0
- package/dist/lib/billing/usage-only.d.ts +18 -0
- package/dist/lib/billing/usage-only.js +18 -1
- package/dist/lib/doctor/checks/sync-runtime.d.ts +34 -0
- package/dist/lib/doctor/checks/sync-runtime.js +217 -8
- package/dist/lib/doctor/fix/apply.js +1 -0
- package/dist/lib/doctor/fix/refresh-sync-outcome.d.ts +4 -1
- package/dist/lib/doctor/fix/refresh-sync-outcome.js +15 -0
- package/dist/lib/doctor/fix/refresh-sync-progress.d.ts +42 -0
- package/dist/lib/doctor/fix/refresh-sync-progress.js +100 -0
- package/dist/lib/doctor/json-output.d.ts +7 -0
- package/dist/lib/doctor/json-output.js +8 -1
- package/dist/lib/doctor/types.d.ts +1 -1
- package/dist/lib/doctor/types.js +1 -0
- package/dist/lib/mesh/live/daemon/work-subscribe.d.ts +1 -1
- package/dist/lib/mesh/live/daemon/work-subscribe.js +121 -38
- package/dist/lib/mesh/live/paths.d.ts +1 -0
- package/dist/lib/mesh/live/paths.js +1 -0
- package/package.json +7 -7
|
@@ -25,6 +25,17 @@
|
|
|
25
25
|
* pricing: the flat-plan checkout is closed, so the CLI never opens it.
|
|
26
26
|
*/
|
|
27
27
|
import { Command } from "commander";
|
|
28
|
+
/** Shape of `GET /v1/billing/summary` we render. */
|
|
29
|
+
interface BillingSummary {
|
|
30
|
+
subscriptionStatus: string | null;
|
|
31
|
+
defaultPaymentMethod: {
|
|
32
|
+
present: boolean;
|
|
33
|
+
brand: string | null;
|
|
34
|
+
last4: string | null;
|
|
35
|
+
};
|
|
36
|
+
databases?: DatabaseBilling | null;
|
|
37
|
+
[key: string]: unknown;
|
|
38
|
+
}
|
|
28
39
|
interface DatabaseBilling {
|
|
29
40
|
period: string;
|
|
30
41
|
activeApps: number;
|
|
@@ -37,6 +48,29 @@ interface DatabaseBilling {
|
|
|
37
48
|
}>;
|
|
38
49
|
}
|
|
39
50
|
export declare function formatDatabaseBilling(db: DatabaseBilling | null | undefined): string[];
|
|
51
|
+
/**
|
|
52
|
+
* The plan a person can act on, in words. The summary's `billingStatus.plan`
|
|
53
|
+
* is the Stripe webhook's label and says "usage" for ANY non-Workforce
|
|
54
|
+
* subscription (a metered usage subscription on a company that still prices
|
|
55
|
+
* as a flat plan reads "usage" there, hq-pro summary.ts). The plan the server
|
|
56
|
+
* actually prices by is `billingState.kind` (hq-pro #5088) or, on an older
|
|
57
|
+
* server, `pricingPlan`; `billingStatus.plan` only separates Workforce from
|
|
58
|
+
* the rest. DEV-5179: a reporter read "usage" off the status JSON while
|
|
59
|
+
* `hq billing usage` said "flat plan"; both were right about different fields.
|
|
60
|
+
*/
|
|
61
|
+
export declare function describePlan(summary: BillingSummary): string;
|
|
62
|
+
export declare const PLAN_UNAVAILABLE = "unavailable right now";
|
|
63
|
+
/**
|
|
64
|
+
* A scheduled switch to usage pricing (hq-pro US-014 `GET
|
|
65
|
+
* /v1/billing/usage-switch`), or null. The route is owner-only and absent on
|
|
66
|
+
* older servers. A refusal (403, 404) reads as "nothing scheduled"; any other
|
|
67
|
+
* failure is "unreadable" so the status view can say so instead of implying
|
|
68
|
+
* no switch. The status view never fails on it.
|
|
69
|
+
*/
|
|
70
|
+
export declare function fetchScheduledUsageSwitch(token: string, companyUid: string): Promise<{
|
|
71
|
+
switchDate: string;
|
|
72
|
+
phase: "scheduled" | "preview";
|
|
73
|
+
} | "unreadable" | null>;
|
|
40
74
|
export declare function registerBillingCommand(program: Command): void;
|
|
41
75
|
export {};
|
|
42
76
|
//# sourceMappingURL=billing.d.ts.map
|
package/dist/commands/billing.js
CHANGED
|
@@ -30,8 +30,9 @@ import { vaultApiFetch, getCompanyUid } from "../utils/vault-api.js";
|
|
|
30
30
|
import { mintPaymentLink } from "../utils/billing-gate.js";
|
|
31
31
|
import { openUsageSwitch } from "../utils/team-upgrade.js";
|
|
32
32
|
import { registerBillingSponsorCommands } from "./billing-sponsor.js";
|
|
33
|
-
import { fetchCompanyPricingPlan, isUsageOnly, usageOnlyUpgradeLines, } from "../lib/billing/usage-only.js";
|
|
33
|
+
import { fetchCompanyPricingPlan, isUsageOnly, pricingPlanFromBody, readBillingState, usageOnlyUpgradeLines, } from "../lib/billing/usage-only.js";
|
|
34
34
|
import { fetchUsageCard } from "../lib/billing/usage-card.js";
|
|
35
|
+
import { accountStatusLines } from "../lib/billing/billing-status-lines.js";
|
|
35
36
|
import { registerBillingUsageCommand } from "./billing-usage.js";
|
|
36
37
|
const CHARGED_ON = {
|
|
37
38
|
team: "the Team subscription",
|
|
@@ -62,6 +63,94 @@ export function formatDatabaseBilling(db) {
|
|
|
62
63
|
}
|
|
63
64
|
return lines;
|
|
64
65
|
}
|
|
66
|
+
function asRecord(value) {
|
|
67
|
+
return value !== null && typeof value === "object" && !Array.isArray(value)
|
|
68
|
+
? value
|
|
69
|
+
: null;
|
|
70
|
+
}
|
|
71
|
+
/**
|
|
72
|
+
* The plan a person can act on, in words. The summary's `billingStatus.plan`
|
|
73
|
+
* is the Stripe webhook's label and says "usage" for ANY non-Workforce
|
|
74
|
+
* subscription (a metered usage subscription on a company that still prices
|
|
75
|
+
* as a flat plan reads "usage" there, hq-pro summary.ts). The plan the server
|
|
76
|
+
* actually prices by is `billingState.kind` (hq-pro #5088) or, on an older
|
|
77
|
+
* server, `pricingPlan`; `billingStatus.plan` only separates Workforce from
|
|
78
|
+
* the rest. DEV-5179: a reporter read "usage" off the status JSON while
|
|
79
|
+
* `hq billing usage` said "flat plan"; both were right about different fields.
|
|
80
|
+
*/
|
|
81
|
+
export function describePlan(summary) {
|
|
82
|
+
const state = readBillingState(summary);
|
|
83
|
+
if (state) {
|
|
84
|
+
switch (state.kind) {
|
|
85
|
+
case "usage":
|
|
86
|
+
return "usage pricing";
|
|
87
|
+
case "usage-trial":
|
|
88
|
+
return state.trial
|
|
89
|
+
? `usage pricing (trial, ${state.trial.daysLeft} days left)`
|
|
90
|
+
: "usage pricing (trial)";
|
|
91
|
+
case "legacy-payer":
|
|
92
|
+
// legacy-payer also covers invoice-billed companies, which have no
|
|
93
|
+
// Workforce subscription behind them.
|
|
94
|
+
return asRecord(summary.billingStatus)?.plan === "team"
|
|
95
|
+
? "Workforce (flat plan)"
|
|
96
|
+
: "flat plan (billed by invoice)";
|
|
97
|
+
case "legacy-free":
|
|
98
|
+
return "Starter (free)";
|
|
99
|
+
case "enterprise":
|
|
100
|
+
return "Enterprise";
|
|
101
|
+
case "comped":
|
|
102
|
+
return "comped (not billed)";
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
if (pricingPlanFromBody(summary) === "usage-only")
|
|
106
|
+
return "usage pricing";
|
|
107
|
+
if (pricingPlanFromBody(summary) === "current") {
|
|
108
|
+
// An older server says "flat plan" but not which one: only a Workforce
|
|
109
|
+
// subscription is visible here, so anything else is named no further.
|
|
110
|
+
return asRecord(summary.billingStatus)?.plan === "team" ? "Workforce (flat plan)" : "flat plan";
|
|
111
|
+
}
|
|
112
|
+
// The server did not say which plan prices this company. Naming one here
|
|
113
|
+
// would guess, and a guess of "Starter (free)" told a paying company it was
|
|
114
|
+
// on the free plan.
|
|
115
|
+
return PLAN_UNAVAILABLE;
|
|
116
|
+
}
|
|
117
|
+
export const PLAN_UNAVAILABLE = "unavailable right now";
|
|
118
|
+
/** True when the plan description already says the company prices by usage. */
|
|
119
|
+
function isUsagePricingLabel(label) {
|
|
120
|
+
return label.startsWith("usage pricing");
|
|
121
|
+
}
|
|
122
|
+
/**
|
|
123
|
+
* A scheduled switch to usage pricing (hq-pro US-014 `GET
|
|
124
|
+
* /v1/billing/usage-switch`), or null. The route is owner-only and absent on
|
|
125
|
+
* older servers. A refusal (403, 404) reads as "nothing scheduled"; any other
|
|
126
|
+
* failure is "unreadable" so the status view can say so instead of implying
|
|
127
|
+
* no switch. The status view never fails on it.
|
|
128
|
+
*/
|
|
129
|
+
export async function fetchScheduledUsageSwitch(token, companyUid) {
|
|
130
|
+
try {
|
|
131
|
+
const res = await vaultApiFetch({
|
|
132
|
+
token,
|
|
133
|
+
path: "/v1/billing/usage-switch",
|
|
134
|
+
query: { companyUid },
|
|
135
|
+
});
|
|
136
|
+
if (res.status === 403 || res.status === 404)
|
|
137
|
+
return null;
|
|
138
|
+
if (!res.ok)
|
|
139
|
+
return "unreadable";
|
|
140
|
+
const body = asRecord(await res.json());
|
|
141
|
+
const phase = body?.phase;
|
|
142
|
+
const switchDate = body?.switchDate;
|
|
143
|
+
if ((phase === "scheduled" || phase === "preview") &&
|
|
144
|
+
typeof switchDate === "string" &&
|
|
145
|
+
Number.isFinite(Date.parse(switchDate))) {
|
|
146
|
+
return { switchDate, phase };
|
|
147
|
+
}
|
|
148
|
+
return null;
|
|
149
|
+
}
|
|
150
|
+
catch {
|
|
151
|
+
return "unreadable";
|
|
152
|
+
}
|
|
153
|
+
}
|
|
65
154
|
function fail(err) {
|
|
66
155
|
console.error(chalk.red("Error:"), err instanceof Error ? err.message : String(err));
|
|
67
156
|
process.exit(1);
|
|
@@ -101,6 +190,18 @@ export function registerBillingCommand(program) {
|
|
|
101
190
|
}
|
|
102
191
|
const sub = summary.subscriptionStatus ?? "none";
|
|
103
192
|
console.log(`${chalk.bold("Subscription")}: ${sub}`);
|
|
193
|
+
const planLabel = describePlan(summary);
|
|
194
|
+
console.log(`${chalk.bold("Plan")}: ${planLabel}`);
|
|
195
|
+
if (!isUsagePricingLabel(planLabel) && planLabel !== PLAN_UNAVAILABLE) {
|
|
196
|
+
const scheduled = await fetchScheduledUsageSwitch(token, companyUid);
|
|
197
|
+
if (scheduled === "unreadable") {
|
|
198
|
+
console.log(" Switch to usage pricing: could not be read right now.");
|
|
199
|
+
}
|
|
200
|
+
else if (scheduled) {
|
|
201
|
+
console.log(` Switch to usage pricing scheduled for ${scheduled.switchDate.slice(0, 10)}` +
|
|
202
|
+
(scheduled.phase === "preview" ? " (preview window open)." : "."));
|
|
203
|
+
}
|
|
204
|
+
}
|
|
104
205
|
const pm = summary.defaultPaymentMethod;
|
|
105
206
|
if (pm?.present) {
|
|
106
207
|
const detail = pm.brand && pm.last4 ? `${pm.brand} ••••${pm.last4}` : "on file";
|
|
@@ -110,6 +211,12 @@ export function registerBillingCommand(program) {
|
|
|
110
211
|
console.log(`${chalk.bold("Card")}: ${chalk.yellow("no card on file")} — ` +
|
|
111
212
|
`run \`hq billing checkout\` to add one before provisioning paid resources.`);
|
|
112
213
|
}
|
|
214
|
+
const state = readBillingState(summary);
|
|
215
|
+
if (state && (state.kind === "usage" || state.kind === "usage-trial")) {
|
|
216
|
+
const { card } = await fetchUsageCard();
|
|
217
|
+
for (const line of accountStatusLines(state, card))
|
|
218
|
+
console.log(line);
|
|
219
|
+
}
|
|
113
220
|
for (const line of formatDatabaseBilling(summary.databases))
|
|
114
221
|
console.log(line);
|
|
115
222
|
}
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
import { Command } from "commander";
|
|
2
|
+
/** Exit code for `refresh` when the domain is not serving yet, so scripts can poll. */
|
|
3
|
+
export declare const REFRESH_NOT_SERVING_EXIT_CODE = 2;
|
|
4
|
+
export declare class DeployDomainsError extends Error {
|
|
5
|
+
readonly code: string;
|
|
6
|
+
readonly status?: number | undefined;
|
|
7
|
+
readonly extra: Record<string, unknown>;
|
|
8
|
+
constructor(message: string, code: string, status?: number | undefined, extra?: Record<string, unknown>);
|
|
9
|
+
}
|
|
10
|
+
/**
|
|
11
|
+
* Terminal hooks for the confirmation prompt. Tests replace these; production
|
|
12
|
+
* reads the real TTY state and asks on stdin.
|
|
13
|
+
*/
|
|
14
|
+
export declare const deployDomainsPrompt: {
|
|
15
|
+
isInteractive: () => boolean;
|
|
16
|
+
ask: (question: string) => Promise<string>;
|
|
17
|
+
};
|
|
18
|
+
/** The sentence `buy` adds when the order's outcome is not known. */
|
|
19
|
+
export declare function resumeOrderSentence(idempotencyKey: string): string;
|
|
20
|
+
/** Shown when hq-pro registered the domain but could not capture the card charge. */
|
|
21
|
+
export declare function retryChargeSentence(idempotencyKey: string): string;
|
|
22
|
+
interface Binding {
|
|
23
|
+
appId: string;
|
|
24
|
+
orgSlug?: string;
|
|
25
|
+
}
|
|
26
|
+
/** Read the app id from the folder's project link (.hq/project.json, or the older hq-deploy.json). */
|
|
27
|
+
export declare function readLinkedApp(dir: string): Binding | null;
|
|
28
|
+
/**
|
|
29
|
+
* Normalize a domain to the ASCII form the server stores, using the same
|
|
30
|
+
* hostname rule hq-deploy's validateAppHostname enforces: at least two
|
|
31
|
+
* labels, each letters/digits/hyphens, 1 to 63 characters, no leading or
|
|
32
|
+
* trailing hyphen. A unicode name is converted to punycode first.
|
|
33
|
+
*
|
|
34
|
+
* The rule also keeps a domain from ever being a URL path segment such as
|
|
35
|
+
* "." or "..": fetch resolves those, so `remove ...` would otherwise send
|
|
36
|
+
* DELETE /api/apps/<id>/, next to the delete-app route (hq-cli#2160 review).
|
|
37
|
+
*/
|
|
38
|
+
export declare function normalizeDomain(raw: string): string;
|
|
39
|
+
/** "This custom domain adds $20/month." from the published add-on price. */
|
|
40
|
+
export declare function addOnPriceLine(pricing: Record<string, unknown> | null): string;
|
|
41
|
+
/** The approval question for `add`, with the published add-on price. */
|
|
42
|
+
export declare function addOnConfirmQuestion(domain: string, environment: string, target: {
|
|
43
|
+
appLabel: string;
|
|
44
|
+
orgSlug: string;
|
|
45
|
+
}, pricing: Record<string, unknown> | null): string;
|
|
46
|
+
/** Shown in the buy prompt when GET /v1/pricing does not answer. */
|
|
47
|
+
export declare const PURCHASE_TERMS_UNAVAILABLE = "The renewal and refund terms could not be loaded. Check them on the HQ pricing page before you buy.";
|
|
48
|
+
/** The purchase terms published as usage.domains.purchase. Null when not published. */
|
|
49
|
+
export declare function purchaseTermsLine(pricing: Record<string, unknown> | null): string | null;
|
|
50
|
+
export declare function parsePrice(raw: string): number;
|
|
51
|
+
export declare function registerDeployDomainsCommand(deploy: Command): void;
|
|
52
|
+
export {};
|
|
53
|
+
//# sourceMappingURL=deploy-domains.d.ts.map
|