@indigoai-us/hq-cli 5.345.163 → 5.345.165
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 +36 -0
- package/dist/command-catalog.generated.d.ts +66 -0
- package/dist/command-catalog.generated.js +82 -0
- package/dist/commands/billing-sponsor.d.ts +20 -0
- package/dist/commands/billing-sponsor.js +116 -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 +2 -0
- package/dist/commands/deploy-domains.js +18 -3
- package/dist/commands/scan.d.ts +6 -0
- package/dist/commands/scan.js +11 -1
- 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/dist/lib/secret-scan/detect.js +2 -0
- package/dist/lib/secret-scan/probes.d.ts +13 -0
- package/dist/lib/secret-scan/probes.js +69 -0
- package/dist/lib/secret-scan/repo-config.d.ts +24 -0
- package/dist/lib/secret-scan/repo-config.js +159 -29
- package/dist/lib/secret-scan/rules.d.ts +2 -0
- package/dist/lib/secret-scan/staged.d.ts +15 -1
- package/dist/lib/secret-scan/staged.js +46 -4
- package/package.json +7 -7
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
|
}
|
|
@@ -17,6 +17,8 @@ export declare const deployDomainsPrompt: {
|
|
|
17
17
|
};
|
|
18
18
|
/** The sentence `buy` adds when the order's outcome is not known. */
|
|
19
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;
|
|
20
22
|
interface Binding {
|
|
21
23
|
appId: string;
|
|
22
24
|
orgSlug?: string;
|
|
@@ -117,6 +117,10 @@ const SETTLED_PURCHASE_CODES = new Set([
|
|
|
117
117
|
export function resumeOrderSentence(idempotencyKey) {
|
|
118
118
|
return `The order may still be in progress. Run the same command again with --idempotency-key ${idempotencyKey} to finish or check it. Do not start a new order.`;
|
|
119
119
|
}
|
|
120
|
+
/** Shown when hq-pro registered the domain but could not capture the card charge. */
|
|
121
|
+
export function retryChargeSentence(idempotencyKey) {
|
|
122
|
+
return `Run the same command again with --idempotency-key ${idempotencyKey} to retry the charge. Do not start a new order.`;
|
|
123
|
+
}
|
|
120
124
|
/** The registrant fields hq-pro accepts; nothing else from the file is sent. */
|
|
121
125
|
const REGISTRANT_FIELDS = [
|
|
122
126
|
"firstName", "lastName", "companyName", "address1", "address2",
|
|
@@ -312,16 +316,18 @@ async function resolveTarget(token, options) {
|
|
|
312
316
|
if (!binding.orgSlug && options.company === undefined) {
|
|
313
317
|
throw new DeployDomainsError("The folder's link does not name a company. Pass --company <slug>, or re-link the folder.", "COMPANY_REQUIRED");
|
|
314
318
|
}
|
|
315
|
-
if (binding.orgSlug && options.company !== undefined && options.company !== binding.orgSlug) {
|
|
319
|
+
if (binding.orgSlug && options.company !== undefined && options.company.toLowerCase() !== binding.orgSlug) {
|
|
316
320
|
throw new DeployDomainsError(`This folder is linked to an app in '${binding.orgSlug}', not '${displayText(options.company)}'. Drop --company, or pass --app <name or id>.`, "COMPANY_MISMATCH");
|
|
317
321
|
}
|
|
318
322
|
return {
|
|
319
323
|
appId: binding.appId,
|
|
320
|
-
|
|
324
|
+
// A matching --company may differ only in case; the link's slug is canonical.
|
|
325
|
+
orgSlug: binding.orgSlug ?? resolveSlug(options.company, undefined),
|
|
321
326
|
appLabel: binding.appId,
|
|
322
327
|
};
|
|
323
328
|
}
|
|
324
329
|
const HOSTNAME_LABEL = /^[a-z0-9](?:[a-z0-9-]*[a-z0-9])?$/;
|
|
330
|
+
const ALL_DIGITS = /^\d+$/;
|
|
325
331
|
/**
|
|
326
332
|
* Normalize a domain to the ASCII form the server stores, using the same
|
|
327
333
|
* hostname rule hq-deploy's validateAppHostname enforces: at least two
|
|
@@ -344,6 +350,13 @@ export function normalizeDomain(raw) {
|
|
|
344
350
|
if (labels.length < 2 || labels.some((label) => label.length > 63 || !HOSTNAME_LABEL.test(label))) {
|
|
345
351
|
throw invalid();
|
|
346
352
|
}
|
|
353
|
+
// domainToASCII applies WHATWG IPv4 parsing ("1.2" becomes "1.0.0.2",
|
|
354
|
+
// "0x7f.1" becomes "127.0.0.1"), and a name it cannot parse as one comes
|
|
355
|
+
// back empty. No real top-level domain is numeric, so a numeric last label
|
|
356
|
+
// after the conversion is an address.
|
|
357
|
+
if (ALL_DIGITS.test(labels[labels.length - 1])) {
|
|
358
|
+
throw invalid();
|
|
359
|
+
}
|
|
347
360
|
return domain;
|
|
348
361
|
}
|
|
349
362
|
function domainRoute(appId, domain, suffix = "") {
|
|
@@ -727,8 +740,10 @@ async function buyAction(domainArg, options) {
|
|
|
727
740
|
}
|
|
728
741
|
else if (body.paymentCaptured === false) {
|
|
729
742
|
// hq-pro registered the name, but capturing the card authorization failed.
|
|
743
|
+
// A retry with the same key retrieves the same authorization, replays the
|
|
744
|
+
// registration for this order and captures again (hq-pro domain-purchase.ts).
|
|
730
745
|
console.log(`Registered ${displayText(domain)}.${expires} The card was authorized for ${usd(expectedPriceUsd)}, ` +
|
|
731
|
-
`but the charge
|
|
746
|
+
`but the charge did not complete. ${retryChargeSentence(idempotencyKey)}`);
|
|
732
747
|
}
|
|
733
748
|
else {
|
|
734
749
|
console.log(`Bought ${displayText(domain)} for ${usd(expectedPriceUsd)}.${expires}`);
|
package/dist/commands/scan.d.ts
CHANGED
|
@@ -4,6 +4,9 @@
|
|
|
4
4
|
* hq scan secrets scan the staged changes of the current repo
|
|
5
5
|
* hq scan secrets <file...> scan whole files instead
|
|
6
6
|
* hq scan secrets --json findings as JSON
|
|
7
|
+
* hq scan secrets --repo <dir> [--snapshot-root <dir>] [--allowlist-rev <rev>] <file...>
|
|
8
|
+
* scan files with that repo's committed
|
|
9
|
+
* .gitleaks.toml allowlists (used by pr-check)
|
|
7
10
|
*
|
|
8
11
|
* Rules: the vendored gitleaks default rule set (MIT, vendor/gitleaks/) plus
|
|
9
12
|
* HQ's own token patterns. No network, no account, no quota. A staged scan also
|
|
@@ -18,6 +21,9 @@ import { Command } from "commander";
|
|
|
18
21
|
interface ScanSecretsOptions {
|
|
19
22
|
json?: boolean;
|
|
20
23
|
staged?: boolean;
|
|
24
|
+
repo?: string;
|
|
25
|
+
snapshotRoot?: string;
|
|
26
|
+
allowlistRev?: string;
|
|
21
27
|
}
|
|
22
28
|
export declare function runScanSecrets(paths: string[], opts: ScanSecretsOptions, cwd?: string): number;
|
|
23
29
|
export declare function registerScanCommand(program: Command): void;
|
package/dist/commands/scan.js
CHANGED
|
@@ -4,6 +4,9 @@
|
|
|
4
4
|
* hq scan secrets scan the staged changes of the current repo
|
|
5
5
|
* hq scan secrets <file...> scan whole files instead
|
|
6
6
|
* hq scan secrets --json findings as JSON
|
|
7
|
+
* hq scan secrets --repo <dir> [--snapshot-root <dir>] [--allowlist-rev <rev>] <file...>
|
|
8
|
+
* scan files with that repo's committed
|
|
9
|
+
* .gitleaks.toml allowlists (used by pr-check)
|
|
7
10
|
*
|
|
8
11
|
* Rules: the vendored gitleaks default rule set (MIT, vendor/gitleaks/) plus
|
|
9
12
|
* HQ's own token patterns. No network, no account, no quota. A staged scan also
|
|
@@ -31,7 +34,11 @@ function report(findings, scope) {
|
|
|
31
34
|
export function runScanSecrets(paths, opts, cwd = process.cwd()) {
|
|
32
35
|
let findings;
|
|
33
36
|
try {
|
|
34
|
-
|
|
37
|
+
if (opts.snapshotRoot !== undefined && opts.repo === undefined)
|
|
38
|
+
throw new ScanInputError("--snapshot-root needs --repo");
|
|
39
|
+
if (opts.allowlistRev !== undefined && opts.repo === undefined)
|
|
40
|
+
throw new ScanInputError("--allowlist-rev needs --repo");
|
|
41
|
+
findings = paths.length && !opts.staged ? scanFiles(paths, { repo: opts.repo, snapshotRoot: opts.snapshotRoot, allowlistRev: opts.allowlistRev }) : scanStaged(cwd);
|
|
35
42
|
}
|
|
36
43
|
catch (err) {
|
|
37
44
|
const msg = err instanceof ScanInputError || err instanceof ScanConfigError ? err.message : `scan failed: ${err instanceof Error ? err.message : String(err)}`;
|
|
@@ -56,6 +63,9 @@ export function registerScanCommand(program) {
|
|
|
56
63
|
.argument("[files...]", "Scan these files instead of the staged changes")
|
|
57
64
|
.option("--staged", "Scan the staged changes (default when no files are given)")
|
|
58
65
|
.option("--json", "Print findings as JSON")
|
|
66
|
+
.option("--repo <dir>", "With files: apply this repo's committed .gitleaks.toml allowlists, matching repo-relative paths")
|
|
67
|
+
.option("--snapshot-root <dir>", "With --repo: files under <dir>/<commit>/<path> are matched as <path>")
|
|
68
|
+
.option("--allowlist-rev <rev>", "With --repo: read .gitleaks.toml from this commit instead of HEAD")
|
|
59
69
|
.action((files, opts) => {
|
|
60
70
|
process.exitCode = runScanSecrets(files ?? [], opts);
|
|
61
71
|
});
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The account lines `hq billing status`, `hq billing usage` and
|
|
3
|
+
* `hq billing usage --card` print from hq-pro's shared `billingState` (US-025).
|
|
4
|
+
* One helper so every surface shows the same trial usage, included usage,
|
|
5
|
+
* spend limit and card prompt for the same state.
|
|
6
|
+
*/
|
|
7
|
+
import { type UsageCard } from "./usage-card.js";
|
|
8
|
+
import type { SharedBillingState } from "./usage-only.js";
|
|
9
|
+
/**
|
|
10
|
+
* Lines for one billing state. Legacy, enterprise and comped accounts get no
|
|
11
|
+
* lines. The member view carries no money figures, so it gets none either.
|
|
12
|
+
*/
|
|
13
|
+
export declare function accountStatusLines(state: SharedBillingState | null, card?: UsageCard): string[];
|
|
14
|
+
//# sourceMappingURL=billing-status-lines.d.ts.map
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The account lines `hq billing status`, `hq billing usage` and
|
|
3
|
+
* `hq billing usage --card` print from hq-pro's shared `billingState` (US-025).
|
|
4
|
+
* One helper so every surface shows the same trial usage, included usage,
|
|
5
|
+
* spend limit and card prompt for the same state.
|
|
6
|
+
*/
|
|
7
|
+
import { BUNDLED_USAGE_CARD, formatCardUsd } from "./usage-card.js";
|
|
8
|
+
function usd(cents) {
|
|
9
|
+
return `$${(cents / 100).toFixed(2)}`;
|
|
10
|
+
}
|
|
11
|
+
function onUsagePricing(state) {
|
|
12
|
+
return state.kind === "usage" || state.kind === "usage-trial";
|
|
13
|
+
}
|
|
14
|
+
/**
|
|
15
|
+
* Lines for one billing state. Legacy, enterprise and comped accounts get no
|
|
16
|
+
* lines. The member view carries no money figures, so it gets none either.
|
|
17
|
+
*/
|
|
18
|
+
export function accountStatusLines(state, card = BUNDLED_USAGE_CARD) {
|
|
19
|
+
if (!state || !onUsagePricing(state))
|
|
20
|
+
return [];
|
|
21
|
+
const lines = [];
|
|
22
|
+
if (state.kind === "usage-trial" && state.trial) {
|
|
23
|
+
lines.push(`Trial: ${state.trial.daysLeft} days left (ends ${state.trial.endsAt.slice(0, 10)})`);
|
|
24
|
+
if (state.view === "full" && state.trial.usedCents !== null) {
|
|
25
|
+
const capCents = state.trial.capCents ?? card.trial.capUsd * 100;
|
|
26
|
+
lines.push(`Trial usage: ${usd(state.trial.usedCents)} of the ${usd(capCents)} trial cap`);
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
if (state.view !== "full")
|
|
30
|
+
return lines;
|
|
31
|
+
lines.push(`Included usage: ${formatCardUsd(card.includedUsd)} each month`);
|
|
32
|
+
if (state.spendLimit) {
|
|
33
|
+
lines.push(state.spendLimit.cents === null
|
|
34
|
+
? "Spend limit: none set"
|
|
35
|
+
: `Spend limit: ${usd(state.spendLimit.cents)} a month`);
|
|
36
|
+
}
|
|
37
|
+
if (state.cardPrompt) {
|
|
38
|
+
lines.push(`Add a card: usage is past the ${formatCardUsd(card.includedUsd)} included this month. ` +
|
|
39
|
+
"Run `hq billing checkout` to keep working past it.");
|
|
40
|
+
}
|
|
41
|
+
return lines;
|
|
42
|
+
}
|
|
43
|
+
//# sourceMappingURL=billing-status-lines.js.map
|
|
@@ -35,10 +35,28 @@ export interface SharedBillingState {
|
|
|
35
35
|
kind: BillingStateKind;
|
|
36
36
|
scope: "company" | "person";
|
|
37
37
|
view: "full" | "member";
|
|
38
|
+
/**
|
|
39
|
+
* The usage trial, or null. `capCents` is the trial's usage cap and
|
|
40
|
+
* `usedCents` what the trial has used so far; both are null when the server
|
|
41
|
+
* does not say (member view, older server).
|
|
42
|
+
*/
|
|
38
43
|
trial: {
|
|
39
44
|
daysLeft: number;
|
|
40
45
|
endsAt: string;
|
|
46
|
+
capCents: number | null;
|
|
47
|
+
usedCents: number | null;
|
|
48
|
+
} | null;
|
|
49
|
+
/**
|
|
50
|
+
* The monthly spend limit. `cents: null` means no limit is set. The whole
|
|
51
|
+
* field is null off usage pricing and in the member view.
|
|
52
|
+
*/
|
|
53
|
+
spendLimit: {
|
|
54
|
+
cents: number | null;
|
|
41
55
|
} | null;
|
|
56
|
+
/** Whether a card is on file. Null when the server does not say. */
|
|
57
|
+
cardOnFile: boolean | null;
|
|
58
|
+
/** Usage passed the included allowance with no card on file. */
|
|
59
|
+
cardPrompt: boolean;
|
|
42
60
|
/** A pause the server is applying now, or null. */
|
|
43
61
|
paused: {
|
|
44
62
|
reason: string;
|
|
@@ -65,7 +65,21 @@ function readTrial(value) {
|
|
|
65
65
|
!Number.isFinite(Date.parse(trial.endsAt))) {
|
|
66
66
|
return undefined;
|
|
67
67
|
}
|
|
68
|
-
return {
|
|
68
|
+
return {
|
|
69
|
+
daysLeft: trial.daysLeft,
|
|
70
|
+
endsAt: trial.endsAt,
|
|
71
|
+
capCents: centsOrNull(trial.capCents),
|
|
72
|
+
usedCents: centsOrNull(trial.usedCents),
|
|
73
|
+
};
|
|
74
|
+
}
|
|
75
|
+
function centsOrNull(value) {
|
|
76
|
+
return typeof value === "number" && Number.isFinite(value) && value >= 0 ? value : null;
|
|
77
|
+
}
|
|
78
|
+
function readSpendLimit(value) {
|
|
79
|
+
const limit = asRecord(value);
|
|
80
|
+
if (!limit)
|
|
81
|
+
return null;
|
|
82
|
+
return { cents: centsOrNull(limit.cents) };
|
|
69
83
|
}
|
|
70
84
|
function readPaused(value) {
|
|
71
85
|
const paused = asRecord(value);
|
|
@@ -102,6 +116,9 @@ export function readBillingState(body) {
|
|
|
102
116
|
scope: state.scope,
|
|
103
117
|
view: state.view,
|
|
104
118
|
trial,
|
|
119
|
+
spendLimit: readSpendLimit(state.spendLimit),
|
|
120
|
+
cardOnFile: typeof state.cardOnFile === "boolean" ? state.cardOnFile : null,
|
|
121
|
+
cardPrompt: state.cardPrompt === true,
|
|
105
122
|
paused: readPaused(state.paused),
|
|
106
123
|
actions: {
|
|
107
124
|
addCard: actions?.addCard === true,
|
|
@@ -1,13 +1,47 @@
|
|
|
1
|
+
import { readSessionCompanyLock } from "../../session-company-lock.js";
|
|
1
2
|
import type { CheckResult } from "../types.js";
|
|
2
3
|
export declare const SYNC_RUNTIME_WINDOW_MS: number;
|
|
3
4
|
/** Ignore timing and restart signals until a newly started receiver has settled. */
|
|
4
5
|
export declare const SYNC_RUNTIME_RESTART_WARMUP_MS: number;
|
|
6
|
+
/** Most refused vault keys named by `sync.runtime.pull-access-denied`. */
|
|
7
|
+
export declare const PULL_DENIED_KEY_CAP = 10;
|
|
8
|
+
/** A key refused on at least this many pulls in the hour is a standing condition, not a one-off. */
|
|
9
|
+
export declare const PULL_DENIED_MIN_REPEATS = 2;
|
|
5
10
|
export interface SyncRuntimeOptions {
|
|
6
11
|
now: number;
|
|
7
12
|
conflictCount?: number;
|
|
13
|
+
/**
|
|
14
|
+
* Company scopes whose refused vault keys may be named. `undefined` means
|
|
15
|
+
* the session holds no company lock, so every scope may be named. Keys in
|
|
16
|
+
* any other scope (including `personal` when it is not listed) are counted
|
|
17
|
+
* but never named, so `hq doctor` in a session locked to company A cannot
|
|
18
|
+
* print company B's file names from the machine-wide sync log.
|
|
19
|
+
*/
|
|
20
|
+
nameableCompanies?: readonly string[];
|
|
8
21
|
}
|
|
22
|
+
/**
|
|
23
|
+
* Which cloud call a receiver permission refusal came from. Closed vocabulary
|
|
24
|
+
* derived from the hq-cloud receiver's event names, so the breakdown can be
|
|
25
|
+
* reported without the error prose.
|
|
26
|
+
*
|
|
27
|
+
* - `subscribe`: `POST /v1/sync/subscribe` itself answered 403 while the
|
|
28
|
+
* receiver re-vended its queue credentials.
|
|
29
|
+
* - `sqs-receive` / `sqs-visibility` / `sqs-delete`: the per-device queue
|
|
30
|
+
* credentials vended by subscribe were refused by SQS. One bad vend
|
|
31
|
+
* repeats on every poll and lease heartbeat until the runner restarts.
|
|
32
|
+
* - `cloudwatch-metric`: the latency metric publisher was refused.
|
|
33
|
+
* - `targeted-pull`: the object pull after a change event failed; the
|
|
34
|
+
* refused keys are named by `sync.runtime.pull-access-denied`.
|
|
35
|
+
* - `runner-error`: a runner `error` record outside the receiver.
|
|
36
|
+
*/
|
|
37
|
+
export type ReceiverRefusalCall = "subscribe" | "sqs-receive" | "sqs-visibility" | "sqs-delete" | "cloudwatch-metric" | "targeted-pull" | "runner-error" | "other";
|
|
9
38
|
/** Classify the last hour of sync log lines into privacy-safe results. */
|
|
10
39
|
export declare function analyzeSyncRuntimeLog(lines: readonly string[], options: SyncRuntimeOptions): CheckResult[];
|
|
40
|
+
/**
|
|
41
|
+
* Company scopes this session may see named: `undefined` (all) when there is
|
|
42
|
+
* no session or no company lock; nothing when the lock cannot be verified.
|
|
43
|
+
*/
|
|
44
|
+
export declare function sessionNameableCompanies(env: NodeJS.ProcessEnv, readLock?: typeof readSessionCompanyLock): readonly string[] | undefined;
|
|
11
45
|
/** Read the machine's sync logs and client health file. */
|
|
12
46
|
export declare function defaultSyncRuntimeResults(now: Date, hqHome?: string, env?: NodeJS.ProcessEnv): CheckResult[];
|
|
13
47
|
//# sourceMappingURL=sync-runtime.d.ts.map
|