@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.
Files changed (35) hide show
  1. package/CHANGELOG.md +33 -0
  2. package/dist/command-catalog.generated.d.ts +341 -1
  3. package/dist/command-catalog.generated.js +422 -1
  4. package/dist/command-registration-plan.d.ts +6 -0
  5. package/dist/command-registration-plan.js +1 -0
  6. package/dist/commands/billing-usage.d.ts +16 -2
  7. package/dist/commands/billing-usage.js +63 -9
  8. package/dist/commands/billing.d.ts +34 -0
  9. package/dist/commands/billing.js +108 -1
  10. package/dist/commands/deploy-domains.d.ts +53 -0
  11. package/dist/commands/deploy-domains.js +802 -0
  12. package/dist/commands/feedback-triage.d.ts +67 -0
  13. package/dist/commands/feedback-triage.js +294 -0
  14. package/dist/commands/feedback.d.ts +13 -1
  15. package/dist/commands/feedback.js +32 -8
  16. package/dist/lib/billing/billing-status-lines.d.ts +14 -0
  17. package/dist/lib/billing/billing-status-lines.js +43 -0
  18. package/dist/lib/billing/usage-only.d.ts +18 -0
  19. package/dist/lib/billing/usage-only.js +18 -1
  20. package/dist/lib/doctor/checks/sync-runtime.d.ts +34 -0
  21. package/dist/lib/doctor/checks/sync-runtime.js +217 -8
  22. package/dist/lib/doctor/fix/apply.js +1 -0
  23. package/dist/lib/doctor/fix/refresh-sync-outcome.d.ts +4 -1
  24. package/dist/lib/doctor/fix/refresh-sync-outcome.js +15 -0
  25. package/dist/lib/doctor/fix/refresh-sync-progress.d.ts +42 -0
  26. package/dist/lib/doctor/fix/refresh-sync-progress.js +100 -0
  27. package/dist/lib/doctor/json-output.d.ts +7 -0
  28. package/dist/lib/doctor/json-output.js +8 -1
  29. package/dist/lib/doctor/types.d.ts +1 -1
  30. package/dist/lib/doctor/types.js +1 -0
  31. package/dist/lib/mesh/live/daemon/work-subscribe.d.ts +1 -1
  32. package/dist/lib/mesh/live/daemon/work-subscribe.js +121 -38
  33. package/dist/lib/mesh/live/paths.d.ts +1 -0
  34. package/dist/lib/mesh/live/paths.js +1 -0
  35. 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
@@ -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