apiblaze 0.21.45 → 0.21.46
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/dist/index.js +647 -401
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -34,7 +34,7 @@ var __toESM = (mod, isNodeMode, target2) => (target2 = mod != null ? __create(__
|
|
|
34
34
|
var version;
|
|
35
35
|
var init_package = __esm({
|
|
36
36
|
"package.json"() {
|
|
37
|
-
version = "0.21.
|
|
37
|
+
version = "0.21.46";
|
|
38
38
|
}
|
|
39
39
|
});
|
|
40
40
|
|
|
@@ -2996,12 +2996,12 @@ function openBrowser2(url) {
|
|
|
2996
2996
|
} catch {
|
|
2997
2997
|
}
|
|
2998
2998
|
}
|
|
2999
|
-
async function deviceLogin(clientId,
|
|
2999
|
+
async function deviceLogin(clientId, scope2, onPrompt, resource) {
|
|
3000
3000
|
const { verifier, challenge } = pkce();
|
|
3001
3001
|
const startRes = await fetch(`${AUTH_BASE}/device_authorization`, {
|
|
3002
3002
|
method: "POST",
|
|
3003
3003
|
headers: { "Content-Type": "application/x-www-form-urlencoded" },
|
|
3004
|
-
body: new URLSearchParams({ client_id: clientId, scope, code_challenge: challenge, code_challenge_method: "S256", ...resource ? { resource } : {} })
|
|
3004
|
+
body: new URLSearchParams({ client_id: clientId, scope: scope2, code_challenge: challenge, code_challenge_method: "S256", ...resource ? { resource } : {} })
|
|
3005
3005
|
});
|
|
3006
3006
|
const start = await startRes.json().catch(() => ({}));
|
|
3007
3007
|
if (!startRes.ok) {
|
|
@@ -8949,7 +8949,7 @@ async function runOpAnon(o = {}) {
|
|
|
8949
8949
|
const log4 = (s) => {
|
|
8950
8950
|
if (!o.json) console.log(s);
|
|
8951
8951
|
};
|
|
8952
|
-
log4(
|
|
8952
|
+
log4(import_chalk71.default.bold(`${B.cli} op anon \u2014 a first-time visitor's demo, against production`));
|
|
8953
8953
|
try {
|
|
8954
8954
|
await journey("first", work, home, checks);
|
|
8955
8955
|
const env = await journey("rerun", work, home, checks);
|
|
@@ -8972,18 +8972,18 @@ async function runOpAnon(o = {}) {
|
|
|
8972
8972
|
checks.push({ id: "stop", ok: stop.code === 0 && portsFree, detail: `exit ${stop.code}${portsFree ? "" : " \xB7 ports still answering"}` });
|
|
8973
8973
|
import_fs10.default.rmSync(root, { recursive: true, force: true });
|
|
8974
8974
|
}
|
|
8975
|
-
for (const c of checks) log4(` ${c.skipped ?
|
|
8975
|
+
for (const c of checks) log4(` ${c.skipped ? import_chalk71.default.yellow("\u2013") : c.ok ? import_chalk71.default.green("\u2713") : import_chalk71.default.red("\u2717")} ${c.id.padEnd(24)} ${import_chalk71.default.dim(c.detail)}`);
|
|
8976
8976
|
const failed = checks.filter((c) => !c.ok).length;
|
|
8977
8977
|
const skipped = checks.filter((c) => c.skipped).length;
|
|
8978
8978
|
const passed = checks.length - failed - skipped;
|
|
8979
8979
|
const secs = Math.round((Date.now() - t0) / 1e3);
|
|
8980
8980
|
const tail = `${skipped ? `, ${skipped} skipped` : ""} (${secs} s)`;
|
|
8981
|
-
log4(failed ?
|
|
8981
|
+
log4(failed ? import_chalk71.default.red(`${passed}/${checks.length - skipped} passed, ${failed} failed${tail}`) : import_chalk71.default.green(`${passed}/${checks.length - skipped} passed${tail}`));
|
|
8982
8982
|
if (o.json) process.stdout.write(JSON.stringify({ ok: !failed, checks }, null, 2) + "\n");
|
|
8983
8983
|
if (failed) process.exitCode = 1;
|
|
8984
8984
|
return { ok: !failed, checks };
|
|
8985
8985
|
}
|
|
8986
|
-
var import_fs10, import_os3, import_path7, import_child_process6,
|
|
8986
|
+
var import_fs10, import_os3, import_path7, import_child_process6, import_chalk71, SECRET, SANDBOX_TEAM;
|
|
8987
8987
|
var init_op_anon = __esm({
|
|
8988
8988
|
"src/commands/op-anon.ts"() {
|
|
8989
8989
|
"use strict";
|
|
@@ -8991,7 +8991,7 @@ var init_op_anon = __esm({
|
|
|
8991
8991
|
import_os3 = __toESM(require("os"));
|
|
8992
8992
|
import_path7 = __toESM(require("path"));
|
|
8993
8993
|
import_child_process6 = require("child_process");
|
|
8994
|
-
|
|
8994
|
+
import_chalk71 = __toESM(require("chalk"));
|
|
8995
8995
|
init_brand();
|
|
8996
8996
|
init_api();
|
|
8997
8997
|
init_admin();
|
|
@@ -9004,7 +9004,7 @@ var init_op_anon = __esm({
|
|
|
9004
9004
|
// src/index.ts
|
|
9005
9005
|
var import_fs11 = __toESM(require("fs"));
|
|
9006
9006
|
var import_commander = require("commander");
|
|
9007
|
-
var
|
|
9007
|
+
var import_chalk73 = __toESM(require("chalk"));
|
|
9008
9008
|
init_package();
|
|
9009
9009
|
init_cli_run_context();
|
|
9010
9010
|
init_version_guard();
|
|
@@ -11392,8 +11392,13 @@ function withOverride(cat, tool, flags) {
|
|
|
11392
11392
|
for (const k of ["readOnly", "destructive", "idempotent", "confirm", "alwaysAsync"]) {
|
|
11393
11393
|
if (typeof flags[k] === "boolean") patch[k] = flags[k];
|
|
11394
11394
|
}
|
|
11395
|
+
if (typeof flags.title === "string") {
|
|
11396
|
+
const t = flags.title.trim().slice(0, 120);
|
|
11397
|
+
if (!t) throw new Error('--title needs some text (a short human label, e.g. "List pets").');
|
|
11398
|
+
patch.title = t;
|
|
11399
|
+
}
|
|
11395
11400
|
if (Object.keys(patch).length === 0) {
|
|
11396
|
-
throw new Error("Nothing to set. Pass --read-only, --destructive, --idempotent, --confirm, --always-async (or their --no-\u2026 forms).");
|
|
11401
|
+
throw new Error("Nothing to set. Pass --title, --read-only, --destructive, --idempotent, --confirm, --always-async (or their --no-\u2026 forms).");
|
|
11397
11402
|
}
|
|
11398
11403
|
return { ...cat, overrides: { ...cat.overrides ?? {}, [tool]: { ...cat.overrides?.[tool] ?? {}, ...patch } } };
|
|
11399
11404
|
}
|
|
@@ -14032,15 +14037,15 @@ async function runDomainAdd(project, opts) {
|
|
|
14032
14037
|
console.error(import_chalk50.default.red(`A ${kind} domain belongs to a proxy: apiblaze domain add <project> --kind ${kind} --domain ${opts.domain}`));
|
|
14033
14038
|
process.exit(1);
|
|
14034
14039
|
}
|
|
14035
|
-
const
|
|
14040
|
+
const scope2 = await scopeFor(project, opts);
|
|
14036
14041
|
const spinner = (0, import_ora20.default)(`Registering ${opts.domain}...`).start();
|
|
14037
14042
|
try {
|
|
14038
|
-
const body =
|
|
14043
|
+
const body = scope2.kind === "project" ? { hostname: opts.domain, api_version: scope2.apiVersion, kind, ...opts.tenant ? { tenant_name: opts.tenant.toLowerCase() } : {} } : { hostname: opts.domain, kind };
|
|
14039
14044
|
const out = await admin({
|
|
14040
14045
|
method: "POST",
|
|
14041
|
-
path:
|
|
14046
|
+
path: scope2.base,
|
|
14042
14047
|
body,
|
|
14043
|
-
summary: `Add custom ${kind} domain ${opts.domain} to ${
|
|
14048
|
+
summary: `Add custom ${kind} domain ${opts.domain} to ${scope2.label}${scope2.kind === "project" && opts.tenant ? ` (tenant ${opts.tenant})` : ""}`
|
|
14044
14049
|
});
|
|
14045
14050
|
spinner.succeed(`Registered ${out?.hostname ?? opts.domain} (${describeKind(out)}). Add these DNS records, then run \`apiblaze domain status\`:`);
|
|
14046
14051
|
if (opts.json) {
|
|
@@ -14057,11 +14062,11 @@ async function runDomainAdd(project, opts) {
|
|
|
14057
14062
|
}
|
|
14058
14063
|
}
|
|
14059
14064
|
async function runDomainList(project, opts) {
|
|
14060
|
-
const
|
|
14065
|
+
const scope2 = await scopeFor(project, opts);
|
|
14061
14066
|
const out = await admin({
|
|
14062
14067
|
method: "GET",
|
|
14063
|
-
path:
|
|
14064
|
-
summary: `List custom domains for ${
|
|
14068
|
+
path: scope2.kind === "project" ? `${scope2.base}?api_version=${encodeURIComponent(scope2.apiVersion)}` : scope2.base,
|
|
14069
|
+
summary: `List custom domains for ${scope2.label}`
|
|
14065
14070
|
});
|
|
14066
14071
|
const domains = out?.domains ?? [];
|
|
14067
14072
|
if (opts.json) {
|
|
@@ -14073,7 +14078,7 @@ async function runDomainList(project, opts) {
|
|
|
14073
14078
|
return;
|
|
14074
14079
|
}
|
|
14075
14080
|
for (const d of domains) {
|
|
14076
|
-
const pin =
|
|
14081
|
+
const pin = scope2.kind === "project" && d.tenant ? import_chalk50.default.dim(` tenant ${d.tenant}`) : "";
|
|
14077
14082
|
console.log(` ${import_chalk50.default.bold(d.hostname)} ${statusColor2(d.status)} ${import_chalk50.default.dim(describeKind(d))}${pin} ${import_chalk50.default.dim(d.id ?? "")}`);
|
|
14078
14083
|
}
|
|
14079
14084
|
}
|
|
@@ -14082,10 +14087,10 @@ async function runDomainStatus(project, opts) {
|
|
|
14082
14087
|
console.error(import_chalk50.default.red("--id <domainId> is required (see `apiblaze domain list`)."));
|
|
14083
14088
|
process.exit(1);
|
|
14084
14089
|
}
|
|
14085
|
-
const
|
|
14090
|
+
const scope2 = await scopeFor(project, opts);
|
|
14086
14091
|
const out = await admin({
|
|
14087
14092
|
method: "GET",
|
|
14088
|
-
path: `${
|
|
14093
|
+
path: `${scope2.base}/${encodeURIComponent(opts.id)}/status`,
|
|
14089
14094
|
summary: `Check custom-domain status`
|
|
14090
14095
|
});
|
|
14091
14096
|
if (opts.json) {
|
|
@@ -14107,12 +14112,12 @@ async function runDomainRemove(project, opts) {
|
|
|
14107
14112
|
console.error(import_chalk50.default.red("--id <domainId> is required (see `apiblaze domain list`)."));
|
|
14108
14113
|
process.exit(1);
|
|
14109
14114
|
}
|
|
14110
|
-
const
|
|
14115
|
+
const scope2 = await scopeFor(project, opts);
|
|
14111
14116
|
const spinner = (0, import_ora20.default)("Removing domain...").start();
|
|
14112
14117
|
try {
|
|
14113
14118
|
await admin({
|
|
14114
14119
|
method: "DELETE",
|
|
14115
|
-
path: `${
|
|
14120
|
+
path: `${scope2.base}/${encodeURIComponent(opts.id)}`,
|
|
14116
14121
|
summary: `Remove custom domain ${opts.id}`
|
|
14117
14122
|
});
|
|
14118
14123
|
spinner.succeed("Removed. Billing for it stops now.");
|
|
@@ -14728,7 +14733,8 @@ function randomSecret() {
|
|
|
14728
14733
|
(0, import_crypto2.randomFillSync)(bytes);
|
|
14729
14734
|
return Buffer.from(bytes).toString("base64url");
|
|
14730
14735
|
}
|
|
14731
|
-
var PROVIDER_TYPES = ["google", "github", "microsoft", "facebook", "auth0", "
|
|
14736
|
+
var PROVIDER_TYPES = ["google", "github", "microsoft", "facebook", "auth0", "oidc"];
|
|
14737
|
+
var PASSWORD_TYPE = "password";
|
|
14732
14738
|
var DEFAULT_SCOPES2 = {
|
|
14733
14739
|
google: "openid email profile",
|
|
14734
14740
|
microsoft: "openid email profile",
|
|
@@ -14762,8 +14768,15 @@ async function providersMenu(cBase, clientLabel) {
|
|
|
14762
14768
|
}]);
|
|
14763
14769
|
if (act === "back") return;
|
|
14764
14770
|
if (act === "add") {
|
|
14765
|
-
const
|
|
14771
|
+
const showPassword = providers.some((p) => String(p.type).toLowerCase() === PASSWORD_TYPE) || process.env.APIBLAZE_ADVANCED === "1";
|
|
14772
|
+
const typeChoices = showPassword ? [...PROVIDER_TYPES, { name: "password (APIblaze-hosted username & password \u2014 reviewer / test accounts)", value: PASSWORD_TYPE }] : PROVIDER_TYPES;
|
|
14773
|
+
const { type } = await inquirer3.prompt([{ type: "list", name: "type", message: "Provider:", choices: typeChoices }]);
|
|
14766
14774
|
let body;
|
|
14775
|
+
if (type === PASSWORD_TYPE) {
|
|
14776
|
+
await admin({ method: "POST", path: `${cBase}/providers`, body: { type: PASSWORD_TYPE }, summary: "Enable username & password sign-in" });
|
|
14777
|
+
console.log(import_chalk54.default.green(" Username & password sign-in enabled. Add people: apiblaze tenant password add <username>"));
|
|
14778
|
+
continue;
|
|
14779
|
+
}
|
|
14767
14780
|
if (type === "github") {
|
|
14768
14781
|
const { managed } = await inquirer3.prompt([{
|
|
14769
14782
|
type: "confirm",
|
|
@@ -14777,7 +14790,7 @@ async function providersMenu(cBase, clientLabel) {
|
|
|
14777
14790
|
const a = await inquirer3.prompt([
|
|
14778
14791
|
{ type: "input", name: "clientId", message: `${type} OAuth client id:`, validate: (s) => !!s.trim() || "required" },
|
|
14779
14792
|
{ type: "password", name: "clientSecret", mask: "*", message: `${type} OAuth client secret:`, validate: (s) => s.length >= 6 && s.length <= 200 || "6\u2013200 chars" },
|
|
14780
|
-
...type === "auth0" || type === "
|
|
14793
|
+
...type === "auth0" || type === "oidc" ? [{ type: "input", name: "domain", message: "Issuer / domain (e.g. your-tenant.auth0.com):" }] : [],
|
|
14781
14794
|
{ type: "input", name: "scopes", message: "Scopes:", default: DEFAULT_SCOPES2[type] ?? "" }
|
|
14782
14795
|
]);
|
|
14783
14796
|
body = {
|
|
@@ -16223,9 +16236,230 @@ async function agentsMenu(proj2, opts) {
|
|
|
16223
16236
|
else await runMcp(proj2.projectName, proj2.apiVersion, {});
|
|
16224
16237
|
}
|
|
16225
16238
|
|
|
16226
|
-
// src/commands/
|
|
16239
|
+
// src/commands/tenant-password.ts
|
|
16227
16240
|
var import_chalk59 = __toESM(require("chalk"));
|
|
16228
16241
|
var import_ora28 = __toESM(require("ora"));
|
|
16242
|
+
init_admin();
|
|
16243
|
+
init_api();
|
|
16244
|
+
init_resolve();
|
|
16245
|
+
init_auth();
|
|
16246
|
+
init_tenant_pick();
|
|
16247
|
+
async function teamHasTenant(teamId, slug) {
|
|
16248
|
+
const out = await admin({
|
|
16249
|
+
method: "GET",
|
|
16250
|
+
path: `/teams/${encodeURIComponent(teamId)}/tenants?limit=200&q=${encodeURIComponent(slug)}`,
|
|
16251
|
+
summary: `Look for tenant ${slug}`
|
|
16252
|
+
}).catch(() => null);
|
|
16253
|
+
const names = (out?.tenants ?? []).map((t) => typeof t === "string" ? t : t.tenant_name ?? "");
|
|
16254
|
+
return names.includes(slug);
|
|
16255
|
+
}
|
|
16256
|
+
async function teamForTenant(slug) {
|
|
16257
|
+
const { teamId: active } = await resolveTeam(void 0);
|
|
16258
|
+
if (await teamHasTenant(active, slug)) return active;
|
|
16259
|
+
const teams = (await getTeams().catch(() => [])).filter((t) => t.teamId !== active);
|
|
16260
|
+
for (const t of teams) {
|
|
16261
|
+
if (await teamHasTenant(t.teamId, slug)) {
|
|
16262
|
+
console.error(import_chalk59.default.dim(` (tenant ${slug} is on team ${t.name})`));
|
|
16263
|
+
return t.teamId;
|
|
16264
|
+
}
|
|
16265
|
+
}
|
|
16266
|
+
console.error(import_chalk59.default.red(`No tenant "${slug}" on any of your teams.`) + import_chalk59.default.dim(" (apiblaze tenant list)"));
|
|
16267
|
+
process.exit(1);
|
|
16268
|
+
}
|
|
16269
|
+
async function scope(opts, message, positional) {
|
|
16270
|
+
const named = (positional ?? opts.tenant)?.trim();
|
|
16271
|
+
let teamId;
|
|
16272
|
+
let tenant2;
|
|
16273
|
+
if (named) {
|
|
16274
|
+
tenant2 = named;
|
|
16275
|
+
teamId = await teamForTenant(named);
|
|
16276
|
+
} else {
|
|
16277
|
+
({ teamId } = await resolveTeam(void 0));
|
|
16278
|
+
tenant2 = loadCredentials()?.activeTenant || null;
|
|
16279
|
+
if (!tenant2) tenant2 = await pickTenant(teamId, { message });
|
|
16280
|
+
}
|
|
16281
|
+
if (!tenant2) process.exit(1);
|
|
16282
|
+
return { teamId, tenant: tenant2, base: `/teams/${encodeURIComponent(teamId)}/tenants/${encodeURIComponent(tenant2)}` };
|
|
16283
|
+
}
|
|
16284
|
+
async function serverSupportsPasswordProvider(s) {
|
|
16285
|
+
try {
|
|
16286
|
+
await admin({ method: "GET", path: `${s.base}/password-accounts`, summary: "Check server capabilities" });
|
|
16287
|
+
return true;
|
|
16288
|
+
} catch (e) {
|
|
16289
|
+
return !/\b404\b|not found/i.test(e instanceof Error ? e.message : String(e));
|
|
16290
|
+
}
|
|
16291
|
+
}
|
|
16292
|
+
async function defaultAppClient(s) {
|
|
16293
|
+
const auth = await admin({ method: "GET", path: `${s.base}/auth`, summary: "Read tenant login config" }).catch(() => null);
|
|
16294
|
+
let defaultId = auth?.default_app_client_id ?? null;
|
|
16295
|
+
if (!defaultId) {
|
|
16296
|
+
const clients = await admin({ method: "GET", path: `${s.base}/app-clients`, summary: "List app clients" }).catch(() => null);
|
|
16297
|
+
const list = Array.isArray(clients) ? clients : Array.isArray(clients?.app_clients) ? clients.app_clients : [];
|
|
16298
|
+
defaultId = list.length === 1 ? list[0].id ?? list[0].client_id ?? list[0].clientId ?? null : null;
|
|
16299
|
+
}
|
|
16300
|
+
if (!defaultId) return null;
|
|
16301
|
+
const providers = await admin({ method: "GET", path: `${s.base}/app-clients/${encodeURIComponent(defaultId)}/providers`, summary: "List login providers" }).catch(() => []);
|
|
16302
|
+
return { clientId: defaultId, providers: Array.isArray(providers) ? providers : [] };
|
|
16303
|
+
}
|
|
16304
|
+
async function runTenantPasswordEnable(tenantArg2, opts) {
|
|
16305
|
+
const s = await scope(opts, "Enable username & password sign-in on which tenant?", tenantArg2);
|
|
16306
|
+
if (!await serverSupportsPasswordProvider(s)) {
|
|
16307
|
+
console.error(import_chalk59.default.red("This APIblaze deployment does not offer username & password sign-in yet."));
|
|
16308
|
+
process.exit(1);
|
|
16309
|
+
}
|
|
16310
|
+
const spinner = (0, import_ora28.default)("Checking the tenant's default app client...").start();
|
|
16311
|
+
const dc = await defaultAppClient(s);
|
|
16312
|
+
if (!dc) {
|
|
16313
|
+
spinner.fail(`Tenant ${s.tenant} has no login yet.`);
|
|
16314
|
+
console.error(import_chalk59.default.dim(` Attach it to a proxy with a sign-in first: apiblaze tenant attach <project> --tenant ${s.tenant}`));
|
|
16315
|
+
process.exit(1);
|
|
16316
|
+
}
|
|
16317
|
+
const existing = dc.providers.find((p) => String(p.type).toLowerCase() === "password");
|
|
16318
|
+
if (existing) {
|
|
16319
|
+
spinner.succeed(`Username & password sign-in is already enabled on ${s.tenant}.`);
|
|
16320
|
+
if (opts.json) console.log(JSON.stringify({ enabled: true, provider_id: existing.id, client_id: dc.clientId }));
|
|
16321
|
+
else console.log(import_chalk59.default.dim(` Add people: apiblaze tenant password add <username> ${s.tenant}`));
|
|
16322
|
+
return;
|
|
16323
|
+
}
|
|
16324
|
+
spinner.text = "Adding the provider...";
|
|
16325
|
+
const created = await admin({ method: "POST", path: `${s.base}/app-clients/${encodeURIComponent(dc.clientId)}/providers`, body: { type: "password" }, summary: "Enable username & password sign-in" });
|
|
16326
|
+
spinner.succeed(`Username & password sign-in enabled on ${import_chalk59.default.bold(s.tenant)}.`);
|
|
16327
|
+
if (opts.json) {
|
|
16328
|
+
console.log(JSON.stringify({ enabled: true, provider_id: created?.id ?? created?.provider?.id ?? null, client_id: dc.clientId }));
|
|
16329
|
+
return;
|
|
16330
|
+
}
|
|
16331
|
+
console.log(import_chalk59.default.dim(` The hosted login page now shows a username & password form next to the other sign-in buttons.
|
|
16332
|
+
Add people: apiblaze tenant password add <username> ${s.tenant}`));
|
|
16333
|
+
}
|
|
16334
|
+
async function runTenantPasswordDisable(tenantArg2, opts) {
|
|
16335
|
+
const s = await scope(opts, "Disable username & password sign-in on which tenant?", tenantArg2);
|
|
16336
|
+
const dc = await defaultAppClient(s);
|
|
16337
|
+
const existing = dc?.providers.find((p) => String(p.type).toLowerCase() === "password");
|
|
16338
|
+
if (!dc || !existing) {
|
|
16339
|
+
console.log(import_chalk59.default.dim(`Username & password sign-in is not enabled on ${s.tenant}.`));
|
|
16340
|
+
return;
|
|
16341
|
+
}
|
|
16342
|
+
if (!opts.yes) {
|
|
16343
|
+
const { default: inquirer3 } = await import("inquirer");
|
|
16344
|
+
const { ok } = await inquirer3.prompt([{ type: "confirm", name: "ok", default: false, message: `Remove username & password sign-in from ${s.tenant}? Every password account on it is deleted too.` }]);
|
|
16345
|
+
if (!ok) return;
|
|
16346
|
+
}
|
|
16347
|
+
await admin({ method: "DELETE", path: `${s.base}/app-clients/${encodeURIComponent(dc.clientId)}/providers/${encodeURIComponent(existing.id)}`, summary: "Disable username & password sign-in" });
|
|
16348
|
+
console.log(import_chalk59.default.green(`Username & password sign-in removed from ${s.tenant}.`));
|
|
16349
|
+
if (opts.json) console.log(JSON.stringify({ enabled: false }));
|
|
16350
|
+
}
|
|
16351
|
+
function printAccount(a) {
|
|
16352
|
+
const flags = [a.disabled ? import_chalk59.default.red("blocked") : import_chalk59.default.green("active"), a.locked_until ? import_chalk59.default.yellow(`locked until ${a.locked_until}`) : "", a.purpose === "reviewer" ? import_chalk59.default.cyan("reviewer") : ""].filter(Boolean).join(" \xB7 ");
|
|
16353
|
+
console.log(` ${import_chalk59.default.bold(a.username)} ${flags}${a.email ? import_chalk59.default.dim(` ${a.email}`) : ""}${a.last_login_at ? import_chalk59.default.dim(` last login ${a.last_login_at}`) : ""}`);
|
|
16354
|
+
}
|
|
16355
|
+
async function runTenantPasswordList(tenantArg2, opts) {
|
|
16356
|
+
const s = await scope(opts, "List password accounts on which tenant?", tenantArg2);
|
|
16357
|
+
const out = await admin({ method: "GET", path: `${s.base}/password-accounts`, summary: "List password accounts" });
|
|
16358
|
+
if (opts.json) {
|
|
16359
|
+
console.log(JSON.stringify(out, null, 2));
|
|
16360
|
+
return;
|
|
16361
|
+
}
|
|
16362
|
+
if (!out?.enabled) {
|
|
16363
|
+
console.log(import_chalk59.default.dim(`Username & password sign-in is not enabled on ${s.tenant}. Enable: apiblaze tenant password enable ${s.tenant}`));
|
|
16364
|
+
return;
|
|
16365
|
+
}
|
|
16366
|
+
const accounts = out.accounts ?? [];
|
|
16367
|
+
if (!accounts.length) {
|
|
16368
|
+
console.log(import_chalk59.default.dim(` No password accounts yet on ${s.tenant}. Add one: apiblaze tenant password add <username> ${s.tenant}`));
|
|
16369
|
+
return;
|
|
16370
|
+
}
|
|
16371
|
+
console.log(import_chalk59.default.dim(` ${s.tenant}`));
|
|
16372
|
+
for (const a of accounts) printAccount(a);
|
|
16373
|
+
}
|
|
16374
|
+
async function readPasswordFromStdin() {
|
|
16375
|
+
const chunks = [];
|
|
16376
|
+
for await (const chunk of process.stdin) chunks.push(Buffer.from(chunk));
|
|
16377
|
+
return Buffer.concat(chunks).toString("utf8").replace(/\r?\n$/, "");
|
|
16378
|
+
}
|
|
16379
|
+
function revealOnce(s, username, password) {
|
|
16380
|
+
console.log();
|
|
16381
|
+
console.log(import_chalk59.default.bold(" Password (shown once, never stored in clear):"));
|
|
16382
|
+
console.log(` ${import_chalk59.default.cyan(username)} ${import_chalk59.default.bgWhite.black(` ${password} `)}`);
|
|
16383
|
+
console.log(import_chalk59.default.dim(` New one any time: apiblaze tenant password rotate ${username} ${s.tenant}`));
|
|
16384
|
+
console.log();
|
|
16385
|
+
}
|
|
16386
|
+
async function runTenantPasswordAdd(username, tenantArg2, opts) {
|
|
16387
|
+
const s = await scope(opts, "Add the account on which tenant?", tenantArg2);
|
|
16388
|
+
const body = { username };
|
|
16389
|
+
if (opts.email) body.email = opts.email;
|
|
16390
|
+
if (opts.displayName) body.displayName = opts.displayName;
|
|
16391
|
+
if (opts.purpose) body.purpose = opts.purpose;
|
|
16392
|
+
if (opts.passwordStdin) body.password = await readPasswordFromStdin();
|
|
16393
|
+
const r = await admin({ method: "POST", path: `${s.base}/password-accounts`, body, summary: `Add password account ${username}` });
|
|
16394
|
+
if (opts.json) {
|
|
16395
|
+
console.log(JSON.stringify(r));
|
|
16396
|
+
return;
|
|
16397
|
+
}
|
|
16398
|
+
console.log(import_chalk59.default.green(`Account ${import_chalk59.default.bold(r.account?.username ?? username)} created on ${s.tenant}.`));
|
|
16399
|
+
if (r.adopts?.abz_sub) console.log(import_chalk59.default.yellow(` This login signs in AS the existing user ${r.adopts.abz_sub} (same email, pre-provisioned on this tenant).`));
|
|
16400
|
+
if (!opts.passwordStdin && r.password) revealOnce(s, r.account?.username ?? username, r.password);
|
|
16401
|
+
}
|
|
16402
|
+
async function findAccount(s, username) {
|
|
16403
|
+
const out = await admin({ method: "GET", path: `${s.base}/password-accounts`, summary: "List password accounts" });
|
|
16404
|
+
const a = (out?.accounts ?? []).find((x) => x.username === username.trim().toLowerCase() || x.id === username);
|
|
16405
|
+
if (!a) {
|
|
16406
|
+
console.error(import_chalk59.default.red(`No password account "${username}" on ${s.tenant}.`));
|
|
16407
|
+
process.exit(1);
|
|
16408
|
+
}
|
|
16409
|
+
return a;
|
|
16410
|
+
}
|
|
16411
|
+
async function runTenantPasswordRotate(username, tenantArg2, opts) {
|
|
16412
|
+
const s = await scope(opts, "Rotate the password on which tenant?", tenantArg2);
|
|
16413
|
+
const a = await findAccount(s, username);
|
|
16414
|
+
const body = {};
|
|
16415
|
+
if (opts.passwordStdin) body.password = await readPasswordFromStdin();
|
|
16416
|
+
const r = await admin({ method: "POST", path: `${s.base}/password-accounts/${encodeURIComponent(a.id)}/rotate`, body, summary: `Rotate password for ${a.username}` });
|
|
16417
|
+
if (opts.json) {
|
|
16418
|
+
console.log(JSON.stringify(r));
|
|
16419
|
+
return;
|
|
16420
|
+
}
|
|
16421
|
+
console.log(import_chalk59.default.green(`Password rotated for ${import_chalk59.default.bold(a.username)}. Existing sessions end at their next refresh.`));
|
|
16422
|
+
if (!opts.passwordStdin && r.password) revealOnce(s, a.username, r.password);
|
|
16423
|
+
}
|
|
16424
|
+
async function runTenantPasswordSetBlocked(username, tenantArg2, blocked, opts) {
|
|
16425
|
+
const s = await scope(opts, `${blocked ? "Block" : "Unblock"} the account on which tenant?`, tenantArg2);
|
|
16426
|
+
const a = await findAccount(s, username);
|
|
16427
|
+
const r = await admin({ method: "PATCH", path: `${s.base}/password-accounts/${encodeURIComponent(a.id)}`, body: { disabled: blocked }, summary: `${blocked ? "Block" : "Unblock"} ${a.username}` });
|
|
16428
|
+
if (opts.json) {
|
|
16429
|
+
console.log(JSON.stringify(r));
|
|
16430
|
+
return;
|
|
16431
|
+
}
|
|
16432
|
+
console.log(import_chalk59.default.green(`${a.username} ${blocked ? "blocked: sign-in refused until unblocked" : "unblocked"}.`));
|
|
16433
|
+
}
|
|
16434
|
+
async function runTenantPasswordUnlock(username, tenantArg2, opts) {
|
|
16435
|
+
const s = await scope(opts, "Unlock the account on which tenant?", tenantArg2);
|
|
16436
|
+
const a = await findAccount(s, username);
|
|
16437
|
+
const r = await admin({ method: "POST", path: `${s.base}/password-accounts/${encodeURIComponent(a.id)}/unlock`, body: {}, summary: `Unlock ${a.username}` });
|
|
16438
|
+
if (opts.json) {
|
|
16439
|
+
console.log(JSON.stringify(r));
|
|
16440
|
+
return;
|
|
16441
|
+
}
|
|
16442
|
+
console.log(import_chalk59.default.green(`${a.username} unlocked.`));
|
|
16443
|
+
}
|
|
16444
|
+
async function runTenantPasswordRemove(username, tenantArg2, opts) {
|
|
16445
|
+
const s = await scope(opts, "Remove the account from which tenant?", tenantArg2);
|
|
16446
|
+
const a = await findAccount(s, username);
|
|
16447
|
+
if (!opts.yes) {
|
|
16448
|
+
const { default: inquirer3 } = await import("inquirer");
|
|
16449
|
+
const { ok } = await inquirer3.prompt([{ type: "confirm", name: "ok", default: false, message: `Remove password account ${a.username}?` }]);
|
|
16450
|
+
if (!ok) return;
|
|
16451
|
+
}
|
|
16452
|
+
const r = await admin({ method: "DELETE", path: `${s.base}/password-accounts/${encodeURIComponent(a.id)}`, summary: `Remove ${a.username}` });
|
|
16453
|
+
if (opts.json) {
|
|
16454
|
+
console.log(JSON.stringify(r));
|
|
16455
|
+
return;
|
|
16456
|
+
}
|
|
16457
|
+
console.log(import_chalk59.default.green(`${a.username} removed.`));
|
|
16458
|
+
}
|
|
16459
|
+
|
|
16460
|
+
// src/commands/group.ts
|
|
16461
|
+
var import_chalk60 = __toESM(require("chalk"));
|
|
16462
|
+
var import_ora29 = __toESM(require("ora"));
|
|
16229
16463
|
init_auth();
|
|
16230
16464
|
init_consumer_auth();
|
|
16231
16465
|
init_admin();
|
|
@@ -16327,7 +16561,7 @@ async function userByRefOrCreate(t, ref) {
|
|
|
16327
16561
|
if (!ref.includes("@")) throw e;
|
|
16328
16562
|
const created = await t.call("/v1/admin/users", "POST", { email: ref.trim().toLowerCase() });
|
|
16329
16563
|
if (!created?.abz_sub) throw e;
|
|
16330
|
-
console.log(
|
|
16564
|
+
console.log(import_chalk60.default.dim(` ${ref} wasn't a user yet \u2014 created.`));
|
|
16331
16565
|
return { abz_sub: created.abz_sub, email: created.email ?? ref, display_name: null };
|
|
16332
16566
|
}
|
|
16333
16567
|
}
|
|
@@ -16335,11 +16569,11 @@ async function runGroupList(opts) {
|
|
|
16335
16569
|
const t = await resolveTransport(opts);
|
|
16336
16570
|
const groups = await listGroups(t);
|
|
16337
16571
|
if (opts.json) return void console.log(JSON.stringify(groups));
|
|
16338
|
-
if (!groups.length) return void console.log(
|
|
16339
|
-
console.log(
|
|
16572
|
+
if (!groups.length) return void console.log(import_chalk60.default.yellow(`No groups in ${t.tenant}. Create one: apiblaze group create <name>`));
|
|
16573
|
+
console.log(import_chalk60.default.dim(`Tenant ${t.tenant}:`));
|
|
16340
16574
|
for (const g of groups) {
|
|
16341
|
-
const sealed = g.name === "apiblaze_admins" ?
|
|
16342
|
-
console.log(` ${
|
|
16575
|
+
const sealed = g.name === "apiblaze_admins" ? import_chalk60.default.dim(" (admins \u2014 managed by allowlist)") : "";
|
|
16576
|
+
console.log(` ${import_chalk60.default.bold(g.name)} ${import_chalk60.default.dim(`${g.member_count ?? 0} member(s)`)}${sealed}`);
|
|
16343
16577
|
}
|
|
16344
16578
|
}
|
|
16345
16579
|
async function runGroupCreate(name, opts) {
|
|
@@ -16364,10 +16598,10 @@ async function runGroupCreate(name, opts) {
|
|
|
16364
16598
|
}
|
|
16365
16599
|
}
|
|
16366
16600
|
}
|
|
16367
|
-
const spinner = (0,
|
|
16601
|
+
const spinner = (0, import_ora29.default)(`Creating group ${name}...`).start();
|
|
16368
16602
|
try {
|
|
16369
16603
|
await t.call("/v1/admin/groups", "POST", { name, admins: [adminSub] });
|
|
16370
|
-
spinner.succeed(`Group ${
|
|
16604
|
+
spinner.succeed(`Group ${import_chalk60.default.bold(name)} created in ${t.tenant}.`);
|
|
16371
16605
|
} catch (e) {
|
|
16372
16606
|
spinner.fail("Create failed.");
|
|
16373
16607
|
throw e;
|
|
@@ -16376,10 +16610,10 @@ async function runGroupCreate(name, opts) {
|
|
|
16376
16610
|
async function runGroupDelete(name, opts) {
|
|
16377
16611
|
const t = await resolveTransport(opts);
|
|
16378
16612
|
const g = await groupByName(t, name);
|
|
16379
|
-
const spinner = (0,
|
|
16613
|
+
const spinner = (0, import_ora29.default)(`Deleting group ${name}...`).start();
|
|
16380
16614
|
try {
|
|
16381
16615
|
await t.call(`/v1/admin/groups/${encodeURIComponent(g.id)}`, "DELETE");
|
|
16382
|
-
spinner.succeed(`Group ${
|
|
16616
|
+
spinner.succeed(`Group ${import_chalk60.default.bold(name)} deleted.`);
|
|
16383
16617
|
} catch (e) {
|
|
16384
16618
|
spinner.fail("Delete failed.");
|
|
16385
16619
|
throw e;
|
|
@@ -16398,21 +16632,21 @@ async function runGroupMembers(name, opts) {
|
|
|
16398
16632
|
return void console.log(JSON.stringify({ ...detail, members: walkedMembers.items, subgroups: subs?.subgroups ?? [] }));
|
|
16399
16633
|
}
|
|
16400
16634
|
const members = walkedMembers.items;
|
|
16401
|
-
console.log(
|
|
16402
|
-
for (const m of members) console.log(` ${label2(m)} ${m.role === "admin" ?
|
|
16403
|
-
if (!members.length) console.log(
|
|
16635
|
+
console.log(import_chalk60.default.bold(`${g.name}`) + import_chalk60.default.dim(` (${t.tenant})`));
|
|
16636
|
+
for (const m of members) console.log(` ${label2(m)} ${m.role === "admin" ? import_chalk60.default.cyan("group-admin") : import_chalk60.default.dim("member")}`);
|
|
16637
|
+
if (!members.length) console.log(import_chalk60.default.dim(" no members"));
|
|
16404
16638
|
const sg = subs?.subgroups ?? [];
|
|
16405
|
-
if (sg.length) console.log(
|
|
16639
|
+
if (sg.length) console.log(import_chalk60.default.dim(` subgroups: ${sg.map((s) => s.name).join(", ")}`));
|
|
16406
16640
|
}
|
|
16407
16641
|
async function runGroupAddUser(userRef, groupName, opts) {
|
|
16408
16642
|
const t = await resolveTransport(opts);
|
|
16409
16643
|
const g = await groupByName(t, groupName);
|
|
16410
16644
|
const u = await userByRefOrCreate(t, userRef);
|
|
16411
16645
|
const role = opts.role === "admin" ? "admin" : "member";
|
|
16412
|
-
const spinner = (0,
|
|
16646
|
+
const spinner = (0, import_ora29.default)(`Adding ${label2(u)} to ${g.name}...`).start();
|
|
16413
16647
|
try {
|
|
16414
16648
|
await t.call(`/v1/admin/groups/${encodeURIComponent(g.id)}/members`, "POST", { abz_sub: u.abz_sub, role });
|
|
16415
|
-
spinner.succeed(`${
|
|
16649
|
+
spinner.succeed(`${import_chalk60.default.bold(label2(u))} added to ${import_chalk60.default.bold(g.name)} as ${role}.`);
|
|
16416
16650
|
} catch (e) {
|
|
16417
16651
|
spinner.fail("Add failed.");
|
|
16418
16652
|
throw e;
|
|
@@ -16421,10 +16655,10 @@ async function runGroupAddUser(userRef, groupName, opts) {
|
|
|
16421
16655
|
async function runGroupRemoveUser(userRef, groupName, opts) {
|
|
16422
16656
|
const t = await resolveTransport(opts);
|
|
16423
16657
|
const [g, u] = [await groupByName(t, groupName), await userByRef(t, userRef)];
|
|
16424
|
-
const spinner = (0,
|
|
16658
|
+
const spinner = (0, import_ora29.default)(`Removing ${label2(u)} from ${g.name}...`).start();
|
|
16425
16659
|
try {
|
|
16426
16660
|
await t.call(`/v1/admin/groups/${encodeURIComponent(g.id)}/members/_?abz_sub=${encodeURIComponent(u.abz_sub)}`, "DELETE");
|
|
16427
|
-
spinner.succeed(`${
|
|
16661
|
+
spinner.succeed(`${import_chalk60.default.bold(label2(u))} removed from ${import_chalk60.default.bold(g.name)}.`);
|
|
16428
16662
|
} catch (e) {
|
|
16429
16663
|
spinner.fail("Remove failed.");
|
|
16430
16664
|
throw e;
|
|
@@ -16433,10 +16667,10 @@ async function runGroupRemoveUser(userRef, groupName, opts) {
|
|
|
16433
16667
|
async function runGroupAddGroup(childName, parentName, opts) {
|
|
16434
16668
|
const t = await resolveTransport(opts);
|
|
16435
16669
|
const [child, parent] = [await groupByName(t, childName), await groupByName(t, parentName)];
|
|
16436
|
-
const spinner = (0,
|
|
16670
|
+
const spinner = (0, import_ora29.default)(`Nesting ${child.name} under ${parent.name}...`).start();
|
|
16437
16671
|
try {
|
|
16438
16672
|
await t.call(`/v1/admin/groups/${encodeURIComponent(parent.id)}/subgroups`, "POST", { child_group_id: child.id });
|
|
16439
|
-
spinner.succeed(`${
|
|
16673
|
+
spinner.succeed(`${import_chalk60.default.bold(child.name)} is now a subgroup of ${import_chalk60.default.bold(parent.name)}.`);
|
|
16440
16674
|
} catch (e) {
|
|
16441
16675
|
spinner.fail("Nest failed.");
|
|
16442
16676
|
throw e;
|
|
@@ -16445,10 +16679,10 @@ async function runGroupAddGroup(childName, parentName, opts) {
|
|
|
16445
16679
|
async function runGroupRemoveGroup(childName, parentName, opts) {
|
|
16446
16680
|
const t = await resolveTransport(opts);
|
|
16447
16681
|
const [child, parent] = [await groupByName(t, childName), await groupByName(t, parentName)];
|
|
16448
|
-
const spinner = (0,
|
|
16682
|
+
const spinner = (0, import_ora29.default)(`Un-nesting ${child.name} from ${parent.name}...`).start();
|
|
16449
16683
|
try {
|
|
16450
16684
|
await t.call(`/v1/admin/groups/${encodeURIComponent(parent.id)}/subgroups/${encodeURIComponent(child.id)}`, "DELETE");
|
|
16451
|
-
spinner.succeed(`${
|
|
16685
|
+
spinner.succeed(`${import_chalk60.default.bold(child.name)} un-nested from ${import_chalk60.default.bold(parent.name)}.`);
|
|
16452
16686
|
} catch (e) {
|
|
16453
16687
|
spinner.fail("Un-nest failed.");
|
|
16454
16688
|
throw e;
|
|
@@ -16459,13 +16693,13 @@ async function runGroupUsers(opts) {
|
|
|
16459
16693
|
const users = await listUsers(t);
|
|
16460
16694
|
const observed = await t.call("/v1/admin/observed-identities").catch(() => ({ identities: [] }));
|
|
16461
16695
|
if (opts.json) return void console.log(JSON.stringify({ users, observed: observed?.identities ?? [] }));
|
|
16462
|
-
console.log(
|
|
16463
|
-
for (const u of users) console.log(` ${label2(u)} ${
|
|
16464
|
-
if (!users.length) console.log(
|
|
16696
|
+
console.log(import_chalk60.default.dim(`Tenant ${t.tenant} \u2014 user directory:`));
|
|
16697
|
+
for (const u of users) console.log(` ${label2(u)} ${import_chalk60.default.dim(u.abz_sub)}`);
|
|
16698
|
+
if (!users.length) console.log(import_chalk60.default.dim(" no users yet"));
|
|
16465
16699
|
const fresh = (observed?.identities ?? []).filter((o) => !o.provisioned);
|
|
16466
16700
|
if (fresh.length) {
|
|
16467
|
-
console.log(
|
|
16468
|
-
for (const o of fresh) console.log(` ${o.email || o.abz_sub} ${
|
|
16701
|
+
console.log(import_chalk60.default.dim("Seen in traffic (not yet users):"));
|
|
16702
|
+
for (const o of fresh) console.log(` ${o.email || o.abz_sub} ${import_chalk60.default.dim(o.source_label ?? "")}`);
|
|
16469
16703
|
}
|
|
16470
16704
|
}
|
|
16471
16705
|
|
|
@@ -16475,8 +16709,8 @@ init_iam();
|
|
|
16475
16709
|
init_admins();
|
|
16476
16710
|
|
|
16477
16711
|
// src/commands/preapprove.ts
|
|
16478
|
-
var
|
|
16479
|
-
var
|
|
16712
|
+
var import_chalk61 = __toESM(require("chalk"));
|
|
16713
|
+
var import_ora30 = __toESM(require("ora"));
|
|
16480
16714
|
init_preapproval();
|
|
16481
16715
|
async function runPreapprove(who, opts) {
|
|
16482
16716
|
const tenant2 = await resolveProducerTenant(opts.tenant);
|
|
@@ -16484,14 +16718,14 @@ async function runPreapprove(who, opts) {
|
|
|
16484
16718
|
const rules = await listPreapprovalRules(tenant2);
|
|
16485
16719
|
if (opts.json) return void console.log(JSON.stringify(rules, null, 2));
|
|
16486
16720
|
if (!rules.length) {
|
|
16487
|
-
console.log(
|
|
16488
|
-
console.log(
|
|
16721
|
+
console.log(import_chalk61.default.dim(`No pre-approval rules in ${tenant2}. Anyone who signs in can access (unless access is restricted).`));
|
|
16722
|
+
console.log(import_chalk61.default.dim(`Add one: apiblaze preapprove someone@acme.com`));
|
|
16489
16723
|
return;
|
|
16490
16724
|
}
|
|
16491
|
-
console.log(
|
|
16725
|
+
console.log(import_chalk61.default.dim(`Pre-approved for ${import_chalk61.default.bold(tenant2)}:`));
|
|
16492
16726
|
for (const r of rules) {
|
|
16493
|
-
const tag = r.kind === "domain" ?
|
|
16494
|
-
const grp = r.groups?.length ?
|
|
16727
|
+
const tag = r.kind === "domain" ? import_chalk61.default.cyan("@" + r.value + " (whole domain)") : r.value;
|
|
16728
|
+
const grp = r.groups?.length ? import_chalk61.default.dim(` \u2192 groups: ${r.groups.join(", ")}`) : "";
|
|
16495
16729
|
console.log(` ${tag}${grp}`);
|
|
16496
16730
|
}
|
|
16497
16731
|
return;
|
|
@@ -16500,19 +16734,19 @@ async function runPreapprove(who, opts) {
|
|
|
16500
16734
|
throw new Error("Who? Pass an email or a domain: `apiblaze preapprove someone@acme.com` (or `apiblaze preapprove --list`).");
|
|
16501
16735
|
}
|
|
16502
16736
|
if (opts.remove) {
|
|
16503
|
-
const spinner2 = (0,
|
|
16737
|
+
const spinner2 = (0, import_ora30.default)(`Removing ${who} from ${tenant2}\u2026`).start();
|
|
16504
16738
|
const { removed, value } = await removePreapprovalRule(tenant2, who);
|
|
16505
|
-
if (removed) spinner2.succeed(`${
|
|
16506
|
-
else spinner2.warn(`No pre-approval rule for ${
|
|
16739
|
+
if (removed) spinner2.succeed(`${import_chalk61.default.bold(value)} is no longer pre-approved for ${tenant2}.`);
|
|
16740
|
+
else spinner2.warn(`No pre-approval rule for ${import_chalk61.default.bold(value)} in ${tenant2} \u2014 nothing to remove.`);
|
|
16507
16741
|
return;
|
|
16508
16742
|
}
|
|
16509
|
-
const spinner = (0,
|
|
16743
|
+
const spinner = (0, import_ora30.default)(`Pre-approving ${who} for ${tenant2}\u2026`).start();
|
|
16510
16744
|
try {
|
|
16511
16745
|
const { kind, value } = await addPreapprovalRule(tenant2, who, opts.group);
|
|
16512
16746
|
const what = kind === "domain" ? `Anyone @${value}` : value;
|
|
16513
|
-
spinner.succeed(`${
|
|
16514
|
-
if (opts.group?.length) console.log(
|
|
16515
|
-
console.log(
|
|
16747
|
+
spinner.succeed(`${import_chalk61.default.bold(what)} can now sign in to ${import_chalk61.default.bold(tenant2)}.`);
|
|
16748
|
+
if (opts.group?.length) console.log(import_chalk61.default.dim(` They'll join group(s) ${opts.group.join(", ")} on first sign-in.`));
|
|
16749
|
+
console.log(import_chalk61.default.dim(` See the full list: apiblaze preapprove --list`));
|
|
16516
16750
|
} catch (err) {
|
|
16517
16751
|
spinner.fail("Could not add the rule.");
|
|
16518
16752
|
throw err;
|
|
@@ -16524,8 +16758,8 @@ init_apichat();
|
|
|
16524
16758
|
init_llm();
|
|
16525
16759
|
|
|
16526
16760
|
// src/commands/consumer.ts
|
|
16527
|
-
var
|
|
16528
|
-
var
|
|
16761
|
+
var import_chalk62 = __toESM(require("chalk"));
|
|
16762
|
+
var import_ora31 = __toESM(require("ora"));
|
|
16529
16763
|
init_admin();
|
|
16530
16764
|
init_resolve();
|
|
16531
16765
|
init_caller();
|
|
@@ -16548,7 +16782,7 @@ async function consumerFetch(creds, suffix, init) {
|
|
|
16548
16782
|
function requireConsumer2() {
|
|
16549
16783
|
const c = loadConsumer();
|
|
16550
16784
|
if (!c) {
|
|
16551
|
-
console.error(
|
|
16785
|
+
console.error(import_chalk62.default.red("Not logged in as a consumer. Run `apiblaze consumer login` first."));
|
|
16552
16786
|
process.exit(1);
|
|
16553
16787
|
}
|
|
16554
16788
|
return c;
|
|
@@ -16559,7 +16793,7 @@ async function runConsumerLogin(opts) {
|
|
|
16559
16793
|
let clientId = opts.client;
|
|
16560
16794
|
if (clientId) {
|
|
16561
16795
|
if (!tenant2) {
|
|
16562
|
-
console.error(
|
|
16796
|
+
console.error(import_chalk62.default.red("When using --client, also pass --tenant <slug> (it sets which portal/keys host to use)."));
|
|
16563
16797
|
process.exit(1);
|
|
16564
16798
|
}
|
|
16565
16799
|
} else {
|
|
@@ -16571,24 +16805,24 @@ async function runConsumerLogin(opts) {
|
|
|
16571
16805
|
if (!picked) process.exit(1);
|
|
16572
16806
|
tenant2 = picked;
|
|
16573
16807
|
}
|
|
16574
|
-
const s2 = (0,
|
|
16808
|
+
const s2 = (0, import_ora31.default)("Finding the login app...").start();
|
|
16575
16809
|
const clients = await admin({ method: "GET", path: `/teams/${encodeURIComponent(teamId)}/tenants/${encodeURIComponent(tenant2)}/app-clients`, summary: `List app clients for ${tenant2}` }).catch(() => []);
|
|
16576
16810
|
s2.stop();
|
|
16577
16811
|
const usable = (Array.isArray(clients) ? clients : []).filter((c) => c && (c.client_id || c.clientId));
|
|
16578
16812
|
const pick3 = usable.find((c) => c.is_default || c.default) ?? usable.find((c) => c.verified !== false) ?? usable[0];
|
|
16579
16813
|
if (!pick3) {
|
|
16580
|
-
console.error(
|
|
16814
|
+
console.error(import_chalk62.default.red(`Tenant "${tenant2}" has no login app configured. Set one up in the dashboard (or \`apiblaze create\` with auth).`));
|
|
16581
16815
|
process.exit(1);
|
|
16582
16816
|
}
|
|
16583
16817
|
clientId = pick3.client_id ?? pick3.clientId;
|
|
16584
16818
|
}
|
|
16585
|
-
console.log(`${
|
|
16819
|
+
console.log(`${import_chalk62.default.cyan("\u2192")} Logging in to ${import_chalk62.default.bold(tenant2)} as a consumer...`);
|
|
16586
16820
|
const result = await deviceLogin(clientId, DEFAULT_SCOPE, ({ verificationUri, userCode }) => {
|
|
16587
16821
|
console.log(`
|
|
16588
|
-
Open: ${
|
|
16589
|
-
console.log(` Code: ${
|
|
16822
|
+
Open: ${import_chalk62.default.underline(verificationUri)}`);
|
|
16823
|
+
console.log(` Code: ${import_chalk62.default.bold(userCode)}
|
|
16590
16824
|
`);
|
|
16591
|
-
console.log(
|
|
16825
|
+
console.log(import_chalk62.default.dim(" (opening your browser\u2026 waiting for you to finish)"));
|
|
16592
16826
|
});
|
|
16593
16827
|
const claims = result.idToken && decodeJwt2(result.idToken) || (decodeJwt2(result.accessToken) ?? {});
|
|
16594
16828
|
const creds = {
|
|
@@ -16603,7 +16837,7 @@ async function runConsumerLogin(opts) {
|
|
|
16603
16837
|
obtainedAt: Date.now()
|
|
16604
16838
|
};
|
|
16605
16839
|
saveConsumer(creds);
|
|
16606
|
-
console.log(
|
|
16840
|
+
console.log(import_chalk62.default.green(`\u2714 Logged in as consumer${creds.email ? ` ${creds.email}` : ""} on ${tenant2}.`));
|
|
16607
16841
|
}
|
|
16608
16842
|
async function runConsumerTokens(opts) {
|
|
16609
16843
|
const creds = requireConsumer2();
|
|
@@ -16616,28 +16850,28 @@ async function runConsumerTokens(opts) {
|
|
|
16616
16850
|
console.log(JSON.stringify({ tenant: fresh.tenant, access_token: fresh.accessToken, refresh_token: fresh.refreshToken, id_token: fresh.idToken, expires_at: new Date(fresh.expiresAt).toISOString() }, null, 2));
|
|
16617
16851
|
return;
|
|
16618
16852
|
}
|
|
16619
|
-
console.log(`${
|
|
16853
|
+
console.log(`${import_chalk62.default.cyan("Consumer")} ${import_chalk62.default.bold(fresh.email ?? fresh.tenant)} on ${import_chalk62.default.bold(fresh.tenant)}
|
|
16620
16854
|
`);
|
|
16621
|
-
console.log(`${
|
|
16855
|
+
console.log(`${import_chalk62.default.bold("access_token")} ${import_chalk62.default.dim("exp " + (exp(fresh.accessToken) ?? "?"))}
|
|
16622
16856
|
${fresh.accessToken}
|
|
16623
16857
|
`);
|
|
16624
|
-
if (fresh.idToken) console.log(`${
|
|
16858
|
+
if (fresh.idToken) console.log(`${import_chalk62.default.bold("id_token")} ${import_chalk62.default.dim("exp " + (exp(fresh.idToken) ?? "?"))}
|
|
16625
16859
|
${fresh.idToken}
|
|
16626
16860
|
`);
|
|
16627
|
-
if (fresh.refreshToken) console.log(`${
|
|
16861
|
+
if (fresh.refreshToken) console.log(`${import_chalk62.default.bold("refresh_token")}
|
|
16628
16862
|
${fresh.refreshToken}
|
|
16629
16863
|
`);
|
|
16630
|
-
console.log(
|
|
16864
|
+
console.log(import_chalk62.default.dim("These are your own tokens \u2014 keep them secret."));
|
|
16631
16865
|
}
|
|
16632
16866
|
async function runConsumerApikeys(opts) {
|
|
16633
16867
|
const creds = requireConsumer2();
|
|
16634
16868
|
const { default: inquirer3 } = await import("inquirer");
|
|
16635
|
-
const spinner = (0,
|
|
16869
|
+
const spinner = (0, import_ora31.default)("Loading your API keys...").start();
|
|
16636
16870
|
const list = await consumerFetch(creds, withPage("/apikeys", null));
|
|
16637
16871
|
if (list.status >= 400) {
|
|
16638
16872
|
spinner.stop();
|
|
16639
|
-
console.error(
|
|
16640
|
-
if (list.status === 401) console.error(
|
|
16873
|
+
console.error(import_chalk62.default.red(`Failed to list keys (${list.status}): ${list.data?.error ?? ""}`));
|
|
16874
|
+
if (list.status === 401) console.error(import_chalk62.default.dim("Your consumer session may have expired \u2014 run `apiblaze consumer login` again."));
|
|
16641
16875
|
process.exit(1);
|
|
16642
16876
|
}
|
|
16643
16877
|
let walkCreds = list.creds;
|
|
@@ -16666,16 +16900,16 @@ async function runConsumerApikeys(opts) {
|
|
|
16666
16900
|
if (opts.json) {
|
|
16667
16901
|
console.log(JSON.stringify({ keys, revealed: revealMap }, null, 2));
|
|
16668
16902
|
} else if (!keys.length) {
|
|
16669
|
-
console.log(
|
|
16903
|
+
console.log(import_chalk62.default.yellow("No API keys yet."));
|
|
16670
16904
|
} else {
|
|
16671
16905
|
for (const k of keys) {
|
|
16672
16906
|
const clear = revealMap[k.environment]?.key;
|
|
16673
|
-
const shown = clear ?
|
|
16674
|
-
const exp = k.expires_at ?
|
|
16675
|
-
console.log(` ${
|
|
16907
|
+
const shown = clear ? import_chalk62.default.green(clear) : import_chalk62.default.dim(`${k.key_prefix ?? ""}\u2026${k.key_suffix ?? ""}`);
|
|
16908
|
+
const exp = k.expires_at ? import_chalk62.default.dim(`exp ${k.expires_at}`) : import_chalk62.default.dim("no expiry");
|
|
16909
|
+
console.log(` ${import_chalk62.default.bold(k.environment ?? "")} ${shown} ${exp} ${import_chalk62.default.dim(k.description ?? "")}`);
|
|
16676
16910
|
}
|
|
16677
16911
|
if (Object.keys(revealMap).length === 0 && keys.some((k) => !k.expires_at)) {
|
|
16678
|
-
console.log(
|
|
16912
|
+
console.log(import_chalk62.default.dim("\n(Only expiring keys can be shown in clear; non-expiring keys show a prefix only.)"));
|
|
16679
16913
|
}
|
|
16680
16914
|
}
|
|
16681
16915
|
if (opts.json) return;
|
|
@@ -16689,7 +16923,7 @@ async function runConsumerApikeys(opts) {
|
|
|
16689
16923
|
const body = { environment: answers.environment };
|
|
16690
16924
|
if (answers.description) body.description = answers.description;
|
|
16691
16925
|
if (answers.expiresDays) body.expires_in_seconds = Number(answers.expiresDays) * 86400;
|
|
16692
|
-
const s2 = (0,
|
|
16926
|
+
const s2 = (0, import_ora31.default)("Creating key...").start();
|
|
16693
16927
|
const created = await consumerFetch(list.creds, "/apikeys", { method: "POST", body: JSON.stringify(body) });
|
|
16694
16928
|
if (created.status >= 400) {
|
|
16695
16929
|
s2.fail(`Create failed (${created.status}): ${created.data?.error ?? ""}`);
|
|
@@ -16697,13 +16931,13 @@ async function runConsumerApikeys(opts) {
|
|
|
16697
16931
|
}
|
|
16698
16932
|
s2.succeed("Key created.");
|
|
16699
16933
|
const key = created.data?.key ?? created.data?.fullKey;
|
|
16700
|
-
if (key) console.log(` ${
|
|
16701
|
-
else console.log(
|
|
16934
|
+
if (key) console.log(` ${import_chalk62.default.green(key)} ${import_chalk62.default.dim("(shown once \u2014 store it now)")}`);
|
|
16935
|
+
else console.log(import_chalk62.default.dim(" Key created; run `apiblaze consumer apikeys` to reveal it if it expires."));
|
|
16702
16936
|
}
|
|
16703
16937
|
|
|
16704
16938
|
// src/commands/sidecar.ts
|
|
16705
|
-
var
|
|
16706
|
-
var
|
|
16939
|
+
var import_chalk63 = __toESM(require("chalk"));
|
|
16940
|
+
var import_ora32 = __toESM(require("ora"));
|
|
16707
16941
|
var fs18 = __toESM(require("fs"));
|
|
16708
16942
|
var path14 = __toESM(require("path"));
|
|
16709
16943
|
init_admin();
|
|
@@ -16745,18 +16979,18 @@ function upsertEnvLocal(root, token) {
|
|
|
16745
16979
|
}
|
|
16746
16980
|
function installSidecarPackage(root) {
|
|
16747
16981
|
if (fs18.existsSync(path14.join(root, "node_modules", "apiblaze", "package.json"))) {
|
|
16748
|
-
console.log(` ${
|
|
16982
|
+
console.log(` ${import_chalk63.default.green("\u2713")} apiblaze package already installed`);
|
|
16749
16983
|
return;
|
|
16750
16984
|
}
|
|
16751
16985
|
const has = (f) => fs18.existsSync(path14.join(root, f));
|
|
16752
16986
|
const pm = has("bun.lockb") || has("bun.lock") ? { cmd: "bun", add: "add" } : has("pnpm-lock.yaml") ? { cmd: "pnpm", add: "add" } : has("yarn.lock") ? { cmd: "yarn", add: "add" } : { cmd: "npm", add: "install" };
|
|
16753
|
-
const spinner = (0,
|
|
16987
|
+
const spinner = (0, import_ora32.default)(`Installing the apiblaze package (${pm.cmd})\u2026`).start();
|
|
16754
16988
|
try {
|
|
16755
16989
|
const { execSync } = require("child_process");
|
|
16756
16990
|
execSync(`${pm.cmd} ${pm.add} apiblaze`, { cwd: root, stdio: "ignore" });
|
|
16757
16991
|
spinner.succeed("Installed apiblaze (the sidecar runtime).");
|
|
16758
16992
|
} catch {
|
|
16759
|
-
spinner.warn(`Couldn't auto-install \u2014 run ${
|
|
16993
|
+
spinner.warn(`Couldn't auto-install \u2014 run ${import_chalk63.default.cyan(`${pm.cmd} ${pm.add} apiblaze`)} yourself before ${import_chalk63.default.cyan("npm run dev")}.`);
|
|
16760
16994
|
}
|
|
16761
16995
|
}
|
|
16762
16996
|
function readEnvKey(root) {
|
|
@@ -16899,7 +17133,7 @@ async function runAnonymousInit(root, router, opts) {
|
|
|
16899
17133
|
const { sidecarInitAnonymous: sidecarInitAnonymous2 } = await Promise.resolve().then(() => (init_api(), api_exports));
|
|
16900
17134
|
const { saveAnonCred: saveAnonCred2, clearAnonCred: clearAnonCred2 } = await Promise.resolve().then(() => (init_anon_cred(), anon_cred_exports));
|
|
16901
17135
|
if (opts.newSession) clearAnonCred2();
|
|
16902
|
-
const spinner = (0,
|
|
17136
|
+
const spinner = (0, import_ora32.default)("Setting up a sidecar (no login needed)...").start();
|
|
16903
17137
|
let out;
|
|
16904
17138
|
try {
|
|
16905
17139
|
out = await sidecarInitAnonymous2();
|
|
@@ -16911,29 +17145,29 @@ async function runAnonymousInit(root, router, opts) {
|
|
|
16911
17145
|
if (out.cp_key && out.team_id) saveAnonCred2(out.cp_key, out.team_id, out.claim_code);
|
|
16912
17146
|
const envState = upsertEnvLocal(root, out.token);
|
|
16913
17147
|
ensureGitignored(root);
|
|
16914
|
-
console.log(` ${
|
|
16915
|
-
console.log(` ${
|
|
17148
|
+
console.log(` ${import_chalk63.default.green("\u2713")} .env.local ${envState} (APIBLAZE_API_KEY) \u2014 gitignored`);
|
|
17149
|
+
console.log(` ${import_chalk63.default.green("\u2713")} instrumentation.ts ${wireInstrumentation(root)}`);
|
|
16916
17150
|
installSidecarPackage(root);
|
|
16917
17151
|
let inspectorPath = null;
|
|
16918
17152
|
if (!opts.noInspector) {
|
|
16919
17153
|
inspectorPath = generateInspector(root, router);
|
|
16920
|
-
if (inspectorPath) console.log(` ${
|
|
17154
|
+
if (inspectorPath) console.log(` ${import_chalk63.default.green("\u2713")} inspector at ${inspectorPath}`);
|
|
16921
17155
|
}
|
|
16922
17156
|
console.log("");
|
|
16923
|
-
console.log(
|
|
16924
|
-
console.log(` 1. ${
|
|
17157
|
+
console.log(import_chalk63.default.bold("Done (no account needed). What happens next:"));
|
|
17158
|
+
console.log(` 1. ${import_chalk63.default.cyan("npm run dev")} and use your app.`);
|
|
16925
17159
|
console.log(` 2. Each external origin your app calls is logged in the console \u2014 approve one with:`);
|
|
16926
|
-
console.log(` ${
|
|
17160
|
+
console.log(` ${import_chalk63.default.cyan("apiblaze sidecar approve api.stripe.com")} (no login needed)`);
|
|
16927
17161
|
console.log("");
|
|
16928
|
-
console.log(
|
|
16929
|
-
console.log(` ${
|
|
16930
|
-
console.log(
|
|
17162
|
+
console.log(import_chalk63.default.bold(" \u{1F511} Keep your setup \u2014 claim it into an account:"));
|
|
17163
|
+
console.log(` ${import_chalk63.default.cyan("apiblaze login")} then ${import_chalk63.default.cyan("apiblaze claim")} ${import_chalk63.default.dim("(no code needed here)")}`);
|
|
17164
|
+
console.log(import_chalk63.default.dim(` From another machine: apiblaze claim ${out.claim_code} \xB7 expires in 30 days`));
|
|
16931
17165
|
}
|
|
16932
17166
|
async function runSidecar(opts) {
|
|
16933
17167
|
const root = path14.resolve(opts.dir ?? process.cwd());
|
|
16934
17168
|
const detected2 = detectNextProject(root);
|
|
16935
17169
|
if (!detected2.found) {
|
|
16936
|
-
console.log(
|
|
17170
|
+
console.log(import_chalk63.default.yellow(`No Next.js project detected in ${root}.`));
|
|
16937
17171
|
console.log("Create one (e.g. `npx create-next-app`) and re-run `apiblaze init` inside it.");
|
|
16938
17172
|
return;
|
|
16939
17173
|
}
|
|
@@ -16944,10 +17178,10 @@ async function runSidecar(opts) {
|
|
|
16944
17178
|
if (!loadCredentials()) {
|
|
16945
17179
|
upsertEnvLocal(root, readEnvKey(root));
|
|
16946
17180
|
ensureGitignored(root);
|
|
16947
|
-
console.log(` ${
|
|
16948
|
-
console.log(` ${
|
|
17181
|
+
console.log(` ${import_chalk63.default.green("\u2713")} .env.local present (APIBLAZE_API_KEY) \u2014 reusing`);
|
|
17182
|
+
console.log(` ${import_chalk63.default.green("\u2713")} instrumentation.ts ${wireInstrumentation(root)}`);
|
|
16949
17183
|
installSidecarPackage(root);
|
|
16950
|
-
console.log(
|
|
17184
|
+
console.log(import_chalk63.default.dim(" Log in and run `apiblaze claim <code>` to keep this setup, or `apiblaze login` to manage it."));
|
|
16951
17185
|
return;
|
|
16952
17186
|
}
|
|
16953
17187
|
const { teamId, teamName } = await resolveTeam(opts.team);
|
|
@@ -16956,7 +17190,7 @@ async function runSidecar(opts) {
|
|
|
16956
17190
|
const mustMint = !existingKey || opts.rotate || switchingTeam;
|
|
16957
17191
|
let token = existingKey ?? "";
|
|
16958
17192
|
if (mustMint) {
|
|
16959
|
-
const spinner = (0,
|
|
17193
|
+
const spinner = (0, import_ora32.default)(existingKey ? "Re-establishing the sidecar (minting a fresh invoke key)..." : "Setting up the sidecar (tenant + non-expiring invoke key)...").start();
|
|
16960
17194
|
try {
|
|
16961
17195
|
const out = await admin({
|
|
16962
17196
|
method: "POST",
|
|
@@ -16970,39 +17204,39 @@ async function runSidecar(opts) {
|
|
|
16970
17204
|
throw err;
|
|
16971
17205
|
}
|
|
16972
17206
|
} else {
|
|
16973
|
-
console.log(
|
|
17207
|
+
console.log(import_chalk63.default.dim(` Reusing the existing APIBLAZE_API_KEY (run with --rotate to mint a fresh one, or --team <name> to switch teams).`));
|
|
16974
17208
|
}
|
|
16975
17209
|
const envState = upsertEnvLocal(root, token);
|
|
16976
17210
|
ensureGitignored(root);
|
|
16977
|
-
console.log(` ${
|
|
17211
|
+
console.log(` ${import_chalk63.default.green("\u2713")} .env.local ${envState} (APIBLAZE_API_KEY) \u2014 gitignored`);
|
|
16978
17212
|
const wireState = wireInstrumentation(root);
|
|
16979
|
-
console.log(` ${
|
|
17213
|
+
console.log(` ${import_chalk63.default.green("\u2713")} instrumentation.ts ${wireState}`);
|
|
16980
17214
|
installSidecarPackage(root);
|
|
16981
17215
|
let inspectorPath = null;
|
|
16982
17216
|
if (!opts.noInspector) {
|
|
16983
17217
|
inspectorPath = generateInspector(root, detected2.router);
|
|
16984
|
-
if (inspectorPath) console.log(` ${
|
|
17218
|
+
if (inspectorPath) console.log(` ${import_chalk63.default.green("\u2713")} inspector at ${inspectorPath}`);
|
|
16985
17219
|
}
|
|
16986
17220
|
console.log("");
|
|
16987
|
-
console.log(
|
|
16988
|
-
console.log(` 1. ${
|
|
16989
|
-
console.log(` 2. The origins your app calls appear as ${
|
|
16990
|
-
console.log(` 3. Approve the ones to route: ${
|
|
17221
|
+
console.log(import_chalk63.default.bold("Done. What happens next:"));
|
|
17222
|
+
console.log(` 1. ${import_chalk63.default.cyan("npm run dev")} and use your app \u2014 it works exactly as before (all calls go direct).`);
|
|
17223
|
+
console.log(` 2. The origins your app calls appear as ${import_chalk63.default.bold("candidates")} \u2014 list them: ${import_chalk63.default.cyan("apiblaze sidecar")}`);
|
|
17224
|
+
console.log(` 3. Approve the ones to route: ${import_chalk63.default.cyan("apiblaze sidecar approve api.stripe.com")} (or in the dashboard)`);
|
|
16991
17225
|
console.log(` \u2026within ~5 min your app starts routing that origin through APIblaze.`);
|
|
16992
|
-
if (inspectorPath) console.log(` \u2022 Try it now: open ${
|
|
16993
|
-
if (switchingTeam) console.log(
|
|
17226
|
+
if (inspectorPath) console.log(` \u2022 Try it now: open ${import_chalk63.default.underline("http://localhost:3000/abz-inspector")} (dev only; rm ${path14.dirname(inspectorPath)} before shipping)`);
|
|
17227
|
+
if (switchingTeam) console.log(import_chalk63.default.dim(` \u2022 Approved origins are per-team \u2014 re-approve them on ${teamName ?? teamId} with \`apiblaze sidecar approve <origin>\`.`));
|
|
16994
17228
|
console.log("");
|
|
16995
|
-
console.log(
|
|
16996
|
-
console.log(
|
|
16997
|
-
console.log(
|
|
17229
|
+
console.log(import_chalk63.default.dim(" Manage: apiblaze sidecar (list/approve/deny/remove)"));
|
|
17230
|
+
console.log(import_chalk63.default.dim(" Rotate: apiblaze init --rotate \xB7 Switch team: apiblaze init --team <name>"));
|
|
17231
|
+
console.log(import_chalk63.default.dim(" Turn off: set APIBLAZE_SIDECAR=off in .env.local (flip back to on anytime; key stays put)."));
|
|
16998
17232
|
console.log("");
|
|
16999
|
-
console.log(
|
|
17000
|
-
console.log(
|
|
17233
|
+
console.log(import_chalk63.default.yellow(" \u26A0 APIBLAZE_API_KEY is long-lived and lets a holder call your team's proxies. Never commit it."));
|
|
17234
|
+
console.log(import_chalk63.default.dim(" Your control-plane login stays in ~/.apiblaze \u2014 it never entered this project."));
|
|
17001
17235
|
}
|
|
17002
17236
|
|
|
17003
17237
|
// src/commands/origins.ts
|
|
17004
|
-
var
|
|
17005
|
-
var
|
|
17238
|
+
var import_chalk64 = __toESM(require("chalk"));
|
|
17239
|
+
var import_ora33 = __toESM(require("ora"));
|
|
17006
17240
|
init_admin();
|
|
17007
17241
|
init_resolve();
|
|
17008
17242
|
init_auth();
|
|
@@ -17012,7 +17246,7 @@ async function runOriginsList(opts) {
|
|
|
17012
17246
|
if (!loadCredentials()) {
|
|
17013
17247
|
const cred = loadAnonCred();
|
|
17014
17248
|
if (!cred) {
|
|
17015
|
-
console.log(
|
|
17249
|
+
console.log(import_chalk64.default.yellow("No anonymous workspace here. Run `apiblaze init` first."));
|
|
17016
17250
|
return;
|
|
17017
17251
|
}
|
|
17018
17252
|
out = await cpFetch(cred.cp_key, `/teams/${encodeURIComponent(cred.team_id)}/sidecar/candidates`, { method: "GET" });
|
|
@@ -17030,30 +17264,30 @@ async function runOriginsList(opts) {
|
|
|
17030
17264
|
}
|
|
17031
17265
|
const routed = out.routed ?? [];
|
|
17032
17266
|
const candidates = out.candidates ?? [];
|
|
17033
|
-
console.log(
|
|
17267
|
+
console.log(import_chalk64.default.bold(`
|
|
17034
17268
|
Routed through APIblaze (${routed.length})`));
|
|
17035
|
-
if (!routed.length) console.log(
|
|
17036
|
-
for (const r of routed) console.log(` ${
|
|
17037
|
-
console.log(
|
|
17269
|
+
if (!routed.length) console.log(import_chalk64.default.dim(" none yet"));
|
|
17270
|
+
for (const r of routed) console.log(` ${import_chalk64.default.green("\u25CF")} ${r.sidecar_origin} ${import_chalk64.default.dim(`\u2192 ${r.project_id}`)}`);
|
|
17271
|
+
console.log(import_chalk64.default.bold(`
|
|
17038
17272
|
Candidates \u2014 going direct, not yet approved (${candidates.length})`));
|
|
17039
|
-
if (!candidates.length) console.log(
|
|
17273
|
+
if (!candidates.length) console.log(import_chalk64.default.dim(" none \u2014 run your app to discover the origins it calls"));
|
|
17040
17274
|
for (const c of candidates) {
|
|
17041
|
-
console.log(` ${
|
|
17275
|
+
console.log(` ${import_chalk64.default.yellow("\u25CB")} ${c.origin} ${import_chalk64.default.dim(`seen ${c.request_count}\xD7, last ${c.last_seen}`)}`);
|
|
17042
17276
|
}
|
|
17043
17277
|
if (candidates.length) {
|
|
17044
|
-
console.log(
|
|
17278
|
+
console.log(import_chalk64.default.dim(`
|
|
17045
17279
|
Approve: apiblaze sidecar approve ${candidates[0].origin.replace("https://", "")}`));
|
|
17046
|
-
console.log(
|
|
17280
|
+
console.log(import_chalk64.default.dim(` Dismiss: apiblaze sidecar deny ${candidates[0].origin.replace("https://", "")}`));
|
|
17047
17281
|
}
|
|
17048
17282
|
}
|
|
17049
17283
|
async function runOriginsApprove(origin, opts) {
|
|
17050
17284
|
if (!loadCredentials()) {
|
|
17051
17285
|
const cred = loadAnonCred();
|
|
17052
17286
|
if (!cred) {
|
|
17053
|
-
console.error(
|
|
17287
|
+
console.error(import_chalk64.default.red("Not logged in and no anonymous workspace. Run `apiblaze init` first."));
|
|
17054
17288
|
process.exit(1);
|
|
17055
17289
|
}
|
|
17056
|
-
const spinner2 = (0,
|
|
17290
|
+
const spinner2 = (0, import_ora33.default)(`Approving ${origin} (anonymous)...`).start();
|
|
17057
17291
|
try {
|
|
17058
17292
|
const out = await cpFetch(cred.cp_key, `/teams/${encodeURIComponent(cred.team_id)}/sidecar/approve`, { method: "POST", body: JSON.stringify({ origin }) });
|
|
17059
17293
|
spinner2.succeed(`Approved ${origin} \u2192 proxy ${out.project_id}. Routing within ~5 min.`);
|
|
@@ -17064,7 +17298,7 @@ async function runOriginsApprove(origin, opts) {
|
|
|
17064
17298
|
return;
|
|
17065
17299
|
}
|
|
17066
17300
|
const { teamId } = await resolveTeam(opts.team);
|
|
17067
|
-
const spinner = (0,
|
|
17301
|
+
const spinner = (0, import_ora33.default)(`Approving ${origin}...`).start();
|
|
17068
17302
|
try {
|
|
17069
17303
|
const out = await admin({
|
|
17070
17304
|
method: "POST",
|
|
@@ -17081,7 +17315,7 @@ async function runOriginsApprove(origin, opts) {
|
|
|
17081
17315
|
}
|
|
17082
17316
|
async function runOriginsDeny(origin, opts) {
|
|
17083
17317
|
const { teamId } = await resolveTeam(opts.team);
|
|
17084
|
-
const spinner = (0,
|
|
17318
|
+
const spinner = (0, import_ora33.default)(`Dismissing ${origin}...`).start();
|
|
17085
17319
|
try {
|
|
17086
17320
|
await admin({ method: "POST", path: `/teams/${encodeURIComponent(teamId)}/sidecar/dismiss`, body: { origin }, summary: `Dismiss sidecar origin ${origin}` });
|
|
17087
17321
|
spinner.succeed(`Dismissed ${origin}. It won't be suggested again.`);
|
|
@@ -17092,7 +17326,7 @@ async function runOriginsDeny(origin, opts) {
|
|
|
17092
17326
|
}
|
|
17093
17327
|
async function runOriginsRemove(origin, opts) {
|
|
17094
17328
|
const { teamId } = await resolveTeam(opts.team);
|
|
17095
|
-
const spinner = (0,
|
|
17329
|
+
const spinner = (0, import_ora33.default)(`Removing the proxy for ${origin}...`).start();
|
|
17096
17330
|
try {
|
|
17097
17331
|
await admin({ method: "POST", path: `/teams/${encodeURIComponent(teamId)}/sidecar/remove`, body: { origin }, summary: `Un-route sidecar origin ${origin}` });
|
|
17098
17332
|
spinner.succeed(`Removed ${origin}. Your app will stop routing it (goes direct) within ~5 min.`);
|
|
@@ -17103,7 +17337,7 @@ async function runOriginsRemove(origin, opts) {
|
|
|
17103
17337
|
}
|
|
17104
17338
|
|
|
17105
17339
|
// src/commands/op.ts
|
|
17106
|
-
var
|
|
17340
|
+
var import_chalk72 = __toESM(require("chalk"));
|
|
17107
17341
|
init_auth();
|
|
17108
17342
|
|
|
17109
17343
|
// src/lib/op-call.ts
|
|
@@ -17132,7 +17366,7 @@ async function opCall(call3) {
|
|
|
17132
17366
|
}
|
|
17133
17367
|
|
|
17134
17368
|
// src/commands/op-money.ts
|
|
17135
|
-
var
|
|
17369
|
+
var import_chalk65 = __toESM(require("chalk"));
|
|
17136
17370
|
init_admin();
|
|
17137
17371
|
init_types();
|
|
17138
17372
|
var money2 = (cents2) => typeof cents2 === "number" ? `$${usdStr(cents2)}` : "\u2014";
|
|
@@ -17154,60 +17388,60 @@ async function opWalletList(opts) {
|
|
|
17154
17388
|
const data = await opCall({ method: "GET", path: "/operator/credits", summary: "list wallets" });
|
|
17155
17389
|
if (opts.json) return void console.log(JSON.stringify(data, null, 2));
|
|
17156
17390
|
const accounts = data?.accounts ?? [];
|
|
17157
|
-
if (accounts.length === 0) return void console.log(
|
|
17158
|
-
console.log(
|
|
17391
|
+
if (accounts.length === 0) return void console.log(import_chalk65.default.dim("No wallets."));
|
|
17392
|
+
console.log(import_chalk65.default.dim(`
|
|
17159
17393
|
${"available".padStart(10)} ${"in use".padStart(9)} ${"grace".padStart(8)} ${"status".padEnd(11)} ${"gen".padStart(3)} wallet`));
|
|
17160
17394
|
for (const a of accounts) {
|
|
17161
|
-
const flags = [a.frozen ?
|
|
17162
|
-
const who = a.owner_email ?
|
|
17395
|
+
const flags = [a.frozen ? import_chalk65.default.red("FROZEN") : "", a.closed ? import_chalk65.default.red("closed") : "", a.terms && a.terms !== "prepaid" ? import_chalk65.default.yellow(a.terms) : ""].filter(Boolean).join(" ");
|
|
17396
|
+
const who = a.owner_email ? import_chalk65.default.dim(` \u2014 ${a.owner_email}`) : a.anon ? import_chalk65.default.dim(" \u2014 anon") : a.name ? import_chalk65.default.dim(` \u2014 ${a.name}`) : "";
|
|
17163
17397
|
console.log(
|
|
17164
|
-
` ${
|
|
17398
|
+
` ${import_chalk65.default.bold(money2(a.available_cents ?? a.balance_cents).padStart(10))} ${money2(a.in_use_cents ?? 0).padStart(9)} ${money2(a.grace_used_cents ?? 0).padStart(8)} ${String(a.status ?? "").padEnd(11)} ${String(a.generation ?? "").padStart(3)} ${a.walletId}${who}${flags ? " " + flags : ""}`
|
|
17165
17399
|
);
|
|
17166
17400
|
}
|
|
17167
|
-
if (data?.truncated) console.log(
|
|
17401
|
+
if (data?.truncated) console.log(import_chalk65.default.yellow(`
|
|
17168
17402
|
List truncated \u2014 inspect one wallet with: apiblaze op wallet inspect <wallet>`));
|
|
17169
17403
|
console.log("");
|
|
17170
17404
|
}
|
|
17171
17405
|
async function opWalletInspect(ref, opts) {
|
|
17172
|
-
if (!ref) return void console.log(
|
|
17406
|
+
if (!ref) return void console.log(import_chalk65.default.red("Which wallet?") + import_chalk65.default.cyan(" apiblaze op wallet inspect <wallet id | team id>"));
|
|
17173
17407
|
const ba = await resolveWalletId(ref);
|
|
17174
17408
|
const w = await opCall({ method: "GET", path: `/operator/wallets/${encodeURIComponent(ba)}`, summary: `inspect wallet ${ba}` });
|
|
17175
17409
|
if (opts.json) return void console.log(JSON.stringify(w, null, 2));
|
|
17176
17410
|
const st = w?.state ?? {};
|
|
17177
|
-
console.log(
|
|
17178
|
-
Wallet ${w?.billing_account_id ?? ba}`) + (ref !== ba ?
|
|
17179
|
-
console.log(` ${"status".padEnd(16)} ${w?.status ?? "?"}${w?.frozen ?
|
|
17180
|
-
console.log(` ${"available".padEnd(16)} ${
|
|
17181
|
-
console.log(` ${"in use".padEnd(16)} ${money2(w?.in_use_cents)}${
|
|
17411
|
+
console.log(import_chalk65.default.bold(`
|
|
17412
|
+
Wallet ${w?.billing_account_id ?? ba}`) + (ref !== ba ? import_chalk65.default.dim(` (team ${ref})`) : ""));
|
|
17413
|
+
console.log(` ${"status".padEnd(16)} ${w?.status ?? "?"}${w?.frozen ? import_chalk65.default.red(" FROZEN") : ""}${w?.closed ? import_chalk65.default.red(" closed") : ""}${w?.terms && w.terms !== "prepaid" ? import_chalk65.default.yellow(` ${w.terms}`) : ""}`);
|
|
17414
|
+
console.log(` ${"available".padEnd(16)} ${import_chalk65.default.bold(money2(w?.available_cents ?? w?.balance_cents ?? w?.balance))}`);
|
|
17415
|
+
console.log(` ${"in use".padEnd(16)} ${money2(w?.in_use_cents)}${import_chalk65.default.dim(" \u03A3 floats + budgets + reserves out at the shards")}`);
|
|
17182
17416
|
console.log(` ${"grace".padEnd(16)} ${money2(w?.grace_used_cents)} of ${money2(w?.grace_limit_cents)}`);
|
|
17183
17417
|
console.log(` ${"epoch / gen".padEnd(16)} ${st.epoch ?? "?"} / ${st.generation ?? w?.generation ?? "?"}`);
|
|
17184
17418
|
const unsent = unsentLedgerEntries(st);
|
|
17185
17419
|
const lastSend = lastLedgerSendAtMs(st);
|
|
17186
|
-
console.log(` ${"unsent ledger".padEnd(16)} ${unsent ?? "\u2014"} entries${
|
|
17187
|
-
console.log(` ${"last ledger send".padEnd(16)} ${lastSend ? new Date(lastSend).toISOString() :
|
|
17420
|
+
console.log(` ${"unsent ledger".padEnd(16)} ${unsent ?? "\u2014"} entries${import_chalk65.default.dim(" made by the wallet, not yet sent to the ledger")}`);
|
|
17421
|
+
console.log(` ${"last ledger send".padEnd(16)} ${lastSend ? new Date(lastSend).toISOString() : import_chalk65.default.dim("never")}`);
|
|
17188
17422
|
if (st.counters && typeof st.counters === "object") {
|
|
17189
17423
|
const c = Object.entries(st.counters).map(([k, v]) => `${k}=${v}`).join(" \xB7 ");
|
|
17190
|
-
if (c) console.log(
|
|
17424
|
+
if (c) console.log(import_chalk65.default.dim(` counters ${c}`));
|
|
17191
17425
|
}
|
|
17192
17426
|
const shards = st.shards ?? [];
|
|
17193
17427
|
if (shards.length) {
|
|
17194
|
-
console.log(
|
|
17428
|
+
console.log(import_chalk65.default.bold(`
|
|
17195
17429
|
Shards (${shards.length})`));
|
|
17196
|
-
console.log(
|
|
17430
|
+
console.log(import_chalk65.default.dim(` ${"shard".padEnd(14)} ${"team".padEnd(26)} ${"float".padStart(9)} ${"budget".padStart(9)} ${"acq/s".padStart(6)} ${"adm/s".padStart(6)} expires`));
|
|
17197
17431
|
for (const s of shards) {
|
|
17198
17432
|
const exp = s.expiresAtMs ? `${Math.max(0, Math.round((s.expiresAtMs - Date.now()) / 1e3))}s` : "\u2014";
|
|
17199
17433
|
console.log(
|
|
17200
|
-
` ${String(s.shardId ?? "").slice(0, 14).padEnd(14)} ${String(s.teamId ?? "").slice(0, 26).padEnd(26)} ${String(s.floatUnits ?? 0).padStart(9)} ${String(s.budgetUnits ?? 0).padStart(9)} ${String(s.acquiresPerS ?? 0).padStart(6)} ${String(s.admittedPerS ?? 0).padStart(6)} ${exp}${s.degraded ?
|
|
17434
|
+
` ${String(s.shardId ?? "").slice(0, 14).padEnd(14)} ${String(s.teamId ?? "").slice(0, 26).padEnd(26)} ${String(s.floatUnits ?? 0).padStart(9)} ${String(s.budgetUnits ?? 0).padStart(9)} ${String(s.acquiresPerS ?? 0).padStart(6)} ${String(s.admittedPerS ?? 0).padStart(6)} ${exp}${s.degraded ? import_chalk65.default.red(" degraded") : ""}`
|
|
17201
17435
|
);
|
|
17202
17436
|
}
|
|
17203
|
-
console.log(
|
|
17437
|
+
console.log(import_chalk65.default.dim(" float/budget in ledger units (10,000 per cent)"));
|
|
17204
17438
|
} else {
|
|
17205
|
-
console.log(
|
|
17439
|
+
console.log(import_chalk65.default.dim("\n No live shards (no traffic in the last window)."));
|
|
17206
17440
|
}
|
|
17207
17441
|
const teams = w?.teams ?? [];
|
|
17208
|
-
if (teams.length) console.log(
|
|
17442
|
+
if (teams.length) console.log(import_chalk65.default.dim(`
|
|
17209
17443
|
Teams on this wallet: ${teams.map((t) => typeof t === "string" ? t : t.team_id ?? t.teamId ?? JSON.stringify(t)).join(", ")}`));
|
|
17210
|
-
console.log(
|
|
17444
|
+
console.log(import_chalk65.default.dim(`
|
|
17211
17445
|
Act on it (dashboard only): ${walletPage(ba)}
|
|
17212
17446
|
`));
|
|
17213
17447
|
}
|
|
@@ -17236,11 +17470,11 @@ function opWalletMutation(action2, ref, opts) {
|
|
|
17236
17470
|
const target2 = ref ?? "<wallet>";
|
|
17237
17471
|
const url = ref && !isTeamId(ref) ? walletPage(ref) : `${DASHBOARD_BASE10}/operator/wallets`;
|
|
17238
17472
|
if (opts.json) return void console.log(JSON.stringify({ action: action2, wallet: ref ?? null, dashboard_only: true, url, requires: need ?? "a reason" }));
|
|
17239
|
-
console.log(
|
|
17473
|
+
console.log(import_chalk65.default.yellow(`
|
|
17240
17474
|
\`op wallet ${action2}\` is dashboard-only \u2014 it writes an operator_audit row with YOUR reason before touching the root.`));
|
|
17241
|
-
console.log(` ${
|
|
17242
|
-
console.log(
|
|
17243
|
-
if (ref && isTeamId(ref)) console.log(
|
|
17475
|
+
console.log(` ${import_chalk65.default.cyan(url)}`);
|
|
17476
|
+
console.log(import_chalk65.default.dim(` ${target2}: needs ${need ?? "a reason (required)"}`));
|
|
17477
|
+
if (ref && isTeamId(ref)) console.log(import_chalk65.default.dim(` (that is a team id \u2014 \`apiblaze op wallet inspect ${ref}\` prints its wallet id and page)`));
|
|
17244
17478
|
console.log("");
|
|
17245
17479
|
}
|
|
17246
17480
|
async function opMoneyExposure(opts) {
|
|
@@ -17248,24 +17482,24 @@ async function opMoneyExposure(opts) {
|
|
|
17248
17482
|
const data = await opCall({ method: "GET", path: `/operator/exposure${qs}`, summary: "money exposure" });
|
|
17249
17483
|
if (opts.json) return void console.log(JSON.stringify(data, null, 2));
|
|
17250
17484
|
const m = data?.money;
|
|
17251
|
-
console.log(
|
|
17252
|
-
if (data?.money_error) console.log(
|
|
17485
|
+
console.log(import_chalk65.default.bold("\n Exposure") + import_chalk65.default.dim(` source: ${data?.source ?? "?"}${data?.source === "ae" ? " (Analytics Engine fallback \u2014 approximate; the root did not answer)" : " (the root \u2014 exact)"}`));
|
|
17486
|
+
if (data?.money_error) console.log(import_chalk65.default.yellow(` root error: ${data.money_error}`));
|
|
17253
17487
|
if (m) {
|
|
17254
|
-
console.log(` ${"in use".padEnd(14)} ${
|
|
17255
|
-
console.log(` ${"grace used".padEnd(14)} ${money2(m.total_grace_used_cents)}${
|
|
17488
|
+
console.log(` ${"in use".padEnd(14)} ${import_chalk65.default.bold(money2(m.total_in_use_cents))}${import_chalk65.default.dim(" customer money out at the shards, pre-paid (\u2264 0.15 \xD7 unallocated)")}`);
|
|
17489
|
+
console.log(` ${"grace used".padEnd(14)} ${money2(m.total_grace_used_cents)}${import_chalk65.default.dim(" platform loss bound: \u03A3 grace + chargebacks")}`);
|
|
17256
17490
|
const wallets = m.wallets ?? [];
|
|
17257
17491
|
if (wallets.length) {
|
|
17258
|
-
console.log(
|
|
17492
|
+
console.log(import_chalk65.default.dim(`
|
|
17259
17493
|
${"in use".padStart(10)} ${"grace".padStart(8)} wallet`));
|
|
17260
17494
|
for (const w of wallets.slice(0, 40)) {
|
|
17261
|
-
console.log(` ${money2(w.in_use_cents).padStart(10)} ${money2(w.grace_used_cents ?? (typeof w.grace_used_units === "number" ? w.grace_used_units / 1e4 : void 0)).padStart(8)} ${w.billing_account_id ?? w.walletId}${w.frozen ?
|
|
17495
|
+
console.log(` ${money2(w.in_use_cents).padStart(10)} ${money2(w.grace_used_cents ?? (typeof w.grace_used_units === "number" ? w.grace_used_units / 1e4 : void 0)).padStart(8)} ${w.billing_account_id ?? w.walletId}${w.frozen ? import_chalk65.default.red(" FROZEN") : ""}`);
|
|
17262
17496
|
}
|
|
17263
|
-
if (wallets.length > 40) console.log(
|
|
17497
|
+
if (wallets.length > 40) console.log(import_chalk65.default.dim(` \u2026 ${wallets.length - 40} more (--json)`));
|
|
17264
17498
|
}
|
|
17265
17499
|
}
|
|
17266
17500
|
const ae = data?.ae;
|
|
17267
17501
|
if (ae && typeof ae === "object") {
|
|
17268
|
-
console.log(
|
|
17502
|
+
console.log(import_chalk65.default.bold("\n Analytics Engine view") + import_chalk65.default.dim(" admission by gate source, last window"));
|
|
17269
17503
|
for (const [k, v] of Object.entries(ae)) console.log(` ${k.padEnd(18)} ${typeof v === "object" ? JSON.stringify(v) : String(v)}`);
|
|
17270
17504
|
}
|
|
17271
17505
|
console.log("");
|
|
@@ -17279,24 +17513,24 @@ async function opIncidentStatus(opts) {
|
|
|
17279
17513
|
const data = await opCall({ method: "GET", path: "/operator/incident/degraded-mode", summary: "degraded-mode switch" });
|
|
17280
17514
|
if (opts.json) return void console.log(JSON.stringify(data, null, 2));
|
|
17281
17515
|
const mode = String(data?.mode ?? "allow");
|
|
17282
|
-
const colour = mode === "allow" ?
|
|
17283
|
-
console.log(
|
|
17284
|
-
if (data?.reason) console.log(
|
|
17285
|
-
if (data?.actor || data?.at) console.log(
|
|
17286
|
-
console.log(
|
|
17516
|
+
const colour = mode === "allow" ? import_chalk65.default.green : mode === "deny" ? import_chalk65.default.red : import_chalk65.default.yellow;
|
|
17517
|
+
console.log(import_chalk65.default.bold("\n Degraded mode: ") + colour(mode) + import_chalk65.default.dim(` \u2014 ${MODE_WORDS[mode] ?? ""}`));
|
|
17518
|
+
if (data?.reason) console.log(import_chalk65.default.dim(` reason: ${data.reason}`));
|
|
17519
|
+
if (data?.actor || data?.at) console.log(import_chalk65.default.dim(` set by ${data?.actor ?? "?"}${data?.at ? ` at ${data.at}` : ""}`));
|
|
17520
|
+
console.log(import_chalk65.default.dim(`
|
|
17287
17521
|
Change it (dashboard only, reason required): ${incidentPage()}
|
|
17288
17522
|
`));
|
|
17289
17523
|
}
|
|
17290
17524
|
function opIncidentMutation(mode, opts) {
|
|
17291
17525
|
if (opts.json) return void console.log(JSON.stringify({ action: `incident ${mode}`, dashboard_only: true, url: incidentPage(), requires: "a reason (required); the switch is written to operator_audit, then KV" }));
|
|
17292
|
-
console.log(
|
|
17526
|
+
console.log(import_chalk65.default.yellow(`
|
|
17293
17527
|
\`op incident ${mode}\` is dashboard-only \u2014 ${MODE_WORDS[mode] ?? mode}.`));
|
|
17294
|
-
console.log(` ${
|
|
17295
|
-
console.log(
|
|
17528
|
+
console.log(` ${import_chalk65.default.cyan(incidentPage())}`);
|
|
17529
|
+
console.log(import_chalk65.default.dim(" Needs a reason (required); it is written to operator_audit before the switch flips. Check with: apiblaze op incident status\n"));
|
|
17296
17530
|
}
|
|
17297
17531
|
|
|
17298
17532
|
// src/commands/op-ceiling.ts
|
|
17299
|
-
var
|
|
17533
|
+
var import_chalk66 = __toESM(require("chalk"));
|
|
17300
17534
|
init_types();
|
|
17301
17535
|
var STATUSES = ["pending", "decided", "all"];
|
|
17302
17536
|
var rpsStr = (n) => typeof n === "number" && Number.isFinite(n) ? n.toLocaleString("en-US") : "\u2014";
|
|
@@ -17310,7 +17544,7 @@ function ageStr(iso, now = Date.now()) {
|
|
|
17310
17544
|
return `${Math.floor(s / 86400)}d`;
|
|
17311
17545
|
}
|
|
17312
17546
|
var trunc = (s, n) => s.length > n ? `${s.slice(0, n - 1)}\u2026` : s;
|
|
17313
|
-
var STATUS_COLOUR = { pending:
|
|
17547
|
+
var STATUS_COLOUR = { pending: import_chalk66.default.yellow, approved: import_chalk66.default.green, denied: import_chalk66.default.red, expired: import_chalk66.default.dim };
|
|
17314
17548
|
function printDecideError(err, id, opts) {
|
|
17315
17549
|
if (!(err instanceof ApiError) || ![400, 404, 409].includes(err.status)) return false;
|
|
17316
17550
|
const body = err.body ?? {};
|
|
@@ -17323,12 +17557,12 @@ function printDecideError(err, id, opts) {
|
|
|
17323
17557
|
if (err.status === 409) {
|
|
17324
17558
|
const d = body.request;
|
|
17325
17559
|
const by = d?.decided_by ? ` by ${d.decided_by}${d.decided_at ? ` at ${d.decided_at}` : ""}` : "";
|
|
17326
|
-
console.log(
|
|
17327
|
-
console.log(
|
|
17560
|
+
console.log(import_chalk66.default.red(` Ceiling request ${id} was not changed: ${why}${by}.`));
|
|
17561
|
+
console.log(import_chalk66.default.dim(" See the decision: apiblaze op ceiling list --status decided"));
|
|
17328
17562
|
} else if (err.status === 404) {
|
|
17329
|
-
console.log(
|
|
17563
|
+
console.log(import_chalk66.default.red(` Not found: ${why.replace(/^Not found:\s*/i, "")}.`) + import_chalk66.default.dim(" (ids: apiblaze op ceiling list)"));
|
|
17330
17564
|
} else {
|
|
17331
|
-
console.log(
|
|
17565
|
+
console.log(import_chalk66.default.red(` Ceiling request ${id} not decided: ${why}.`));
|
|
17332
17566
|
}
|
|
17333
17567
|
return true;
|
|
17334
17568
|
}
|
|
@@ -17336,31 +17570,31 @@ async function opCeilingList(opts) {
|
|
|
17336
17570
|
const status2 = String(opts.status ?? "pending").trim().toLowerCase();
|
|
17337
17571
|
if (!STATUSES.includes(status2)) {
|
|
17338
17572
|
process.exitCode = 1;
|
|
17339
|
-
return void console.log(
|
|
17573
|
+
return void console.log(import_chalk66.default.red(`--status must be one of ${STATUSES.join(" | ")}.`));
|
|
17340
17574
|
}
|
|
17341
17575
|
const data = await opCall(
|
|
17342
17576
|
{ method: "GET", path: `/operator/ceiling-requests?status=${status2}`, summary: `${status2} ceiling requests` }
|
|
17343
17577
|
);
|
|
17344
17578
|
if (opts.json) return void console.log(JSON.stringify(data, null, 2));
|
|
17345
17579
|
const rows = data?.requests ?? [];
|
|
17346
|
-
console.log(
|
|
17347
|
-
if (!rows.length) return void console.log(
|
|
17580
|
+
console.log(import_chalk66.default.bold("\n Maximum-rate requests") + import_chalk66.default.dim(` ${status2} \xB7 all consumers together, per project`));
|
|
17581
|
+
if (!rows.length) return void console.log(import_chalk66.default.dim(` No ${status2 === "all" ? "" : `${status2} `}requests.
|
|
17348
17582
|
`));
|
|
17349
|
-
console.log(
|
|
17583
|
+
console.log(import_chalk66.default.dim(` ${"id".padEnd(10)} ${"project/version".padEnd(26)} ${"team".padEnd(16)} ${"current \u2192 requested req/s".padEnd(25)} ${"age".padStart(4)} ${"requested by".padEnd(24)} ${"status".padEnd(8)} use case`));
|
|
17350
17584
|
for (const r of rows) {
|
|
17351
17585
|
const rate = `${rpsStr(r.current_ceiling_rps)} \u2192 ${rpsStr(r.requested_rps)}`;
|
|
17352
|
-
const granted = r.status === "approved" && typeof r.granted_rps === "number" && r.granted_rps !== r.requested_rps ?
|
|
17586
|
+
const granted = r.status === "approved" && typeof r.granted_rps === "number" && r.granted_rps !== r.requested_rps ? import_chalk66.default.dim(` (granted ${rpsStr(r.granted_rps)})`) : "";
|
|
17353
17587
|
const colour = STATUS_COLOUR[r.status] ?? ((s) => s);
|
|
17354
17588
|
console.log(
|
|
17355
|
-
` ${trunc(String(r.id), 10).padEnd(10)} ${trunc(`${r.project_id}/${r.api_version}`, 26).padEnd(26)} ${trunc(r.team_id ?? "\u2014", 16).padEnd(16)} ${rate.padEnd(25)} ${ageStr(r.created_at).padStart(4)} ${trunc(r.requested_by ?? "\u2014", 24).padEnd(24)} ${colour(r.status.padEnd(8))} ${
|
|
17589
|
+
` ${trunc(String(r.id), 10).padEnd(10)} ${trunc(`${r.project_id}/${r.api_version}`, 26).padEnd(26)} ${trunc(r.team_id ?? "\u2014", 16).padEnd(16)} ${rate.padEnd(25)} ${ageStr(r.created_at).padStart(4)} ${trunc(r.requested_by ?? "\u2014", 24).padEnd(24)} ${colour(r.status.padEnd(8))} ${import_chalk66.default.dim(trunc((r.use_case ?? "").replace(/\s+/g, " "), 48))}${granted}`
|
|
17356
17590
|
);
|
|
17357
17591
|
if (r.status !== "pending" && (r.decided_by || r.decision_note)) {
|
|
17358
|
-
console.log(
|
|
17592
|
+
console.log(import_chalk66.default.dim(` ${"".padEnd(10)} ${r.decided_by ?? "?"}${r.decided_at ? ` \xB7 ${ageStr(r.decided_at)} ago` : ""}${r.decision_note ? ` \xB7 ${trunc(r.decision_note, 80)}` : ""}`));
|
|
17359
17593
|
}
|
|
17360
17594
|
}
|
|
17361
17595
|
if (status2 === "pending") {
|
|
17362
17596
|
const presets = (data?.presets_rps ?? []).map(rpsStr).join(" / ");
|
|
17363
|
-
console.log(
|
|
17597
|
+
console.log(import_chalk66.default.dim(`
|
|
17364
17598
|
Decide: apiblaze op ceiling approve <id> [--rps N${presets ? ` e.g. ${presets}` : ""}] [--note "\u2026"] \xB7 apiblaze op ceiling deny <id> --note "why"`));
|
|
17365
17599
|
}
|
|
17366
17600
|
console.log("");
|
|
@@ -17368,23 +17602,23 @@ async function opCeilingList(opts) {
|
|
|
17368
17602
|
async function opCeilingDecide(decision, id, opts) {
|
|
17369
17603
|
if (!id) {
|
|
17370
17604
|
process.exitCode = 1;
|
|
17371
|
-
return void console.log(
|
|
17605
|
+
return void console.log(import_chalk66.default.red("Which request?") + import_chalk66.default.cyan(` apiblaze op ceiling ${decision} <id>${decision === "deny" ? ' --note "why"' : ' [--rps N] [--note "\u2026"]'}`) + import_chalk66.default.dim(" (ids: apiblaze op ceiling list)"));
|
|
17372
17606
|
}
|
|
17373
17607
|
const note = typeof opts.note === "string" ? opts.note.trim() : "";
|
|
17374
17608
|
if (decision === "deny" && !note) {
|
|
17375
17609
|
process.exitCode = 1;
|
|
17376
|
-
return void console.log(
|
|
17610
|
+
return void console.log(import_chalk66.default.red("A denial needs a reason the producer will read:") + import_chalk66.default.cyan(` apiblaze op ceiling deny ${id} --note "why"`));
|
|
17377
17611
|
}
|
|
17378
17612
|
let rps;
|
|
17379
17613
|
if (opts.rps !== void 0 && opts.rps !== null && opts.rps !== "") {
|
|
17380
17614
|
if (decision === "deny") {
|
|
17381
17615
|
process.exitCode = 1;
|
|
17382
|
-
return void console.log(
|
|
17616
|
+
return void console.log(import_chalk66.default.red("--rps only applies to approve."));
|
|
17383
17617
|
}
|
|
17384
17618
|
rps = Number(String(opts.rps).replace(/[,_]/g, ""));
|
|
17385
17619
|
if (!Number.isInteger(rps) || rps <= 0) {
|
|
17386
17620
|
process.exitCode = 1;
|
|
17387
|
-
return void console.log(
|
|
17621
|
+
return void console.log(import_chalk66.default.red("--rps must be a whole number of requests per second (e.g. 5000)."));
|
|
17388
17622
|
}
|
|
17389
17623
|
}
|
|
17390
17624
|
const body = { decision };
|
|
@@ -17409,19 +17643,19 @@ async function opCeilingDecide(decision, id, opts) {
|
|
|
17409
17643
|
const where = r ? `${r.project_id}/${r.api_version}` : `request ${id}`;
|
|
17410
17644
|
if (decision === "approve") {
|
|
17411
17645
|
const to = res?.limits?.ceilingRps ?? res?.request?.granted_rps ?? rps ?? r?.requested_rps;
|
|
17412
|
-
console.log(
|
|
17646
|
+
console.log(import_chalk66.default.green(`
|
|
17413
17647
|
project ${where}: maximum rate ${rpsStr(from)} \u2192 ${rpsStr(to)} req/s`));
|
|
17414
|
-
if (r?.requested_rps !== void 0 && to !== void 0 && to !== r.requested_rps) console.log(
|
|
17648
|
+
if (r?.requested_rps !== void 0 && to !== void 0 && to !== r.requested_rps) console.log(import_chalk66.default.dim(` (asked for ${rpsStr(r.requested_rps)} req/s)`));
|
|
17415
17649
|
} else {
|
|
17416
|
-
console.log(
|
|
17417
|
-
project ${where}: request for ${rpsStr(r?.requested_rps)} req/s denied`) + (typeof from === "number" ?
|
|
17650
|
+
console.log(import_chalk66.default.yellow(`
|
|
17651
|
+
project ${where}: request for ${rpsStr(r?.requested_rps)} req/s denied`) + (typeof from === "number" ? import_chalk66.default.dim(` \u2014 maximum rate stays ${rpsStr(from)} req/s`) : ""));
|
|
17418
17652
|
}
|
|
17419
|
-
if (note) console.log(
|
|
17653
|
+
if (note) console.log(import_chalk66.default.dim(` note: ${note}`));
|
|
17420
17654
|
console.log("");
|
|
17421
17655
|
}
|
|
17422
17656
|
|
|
17423
17657
|
// src/commands/op-ledger.ts
|
|
17424
|
-
var
|
|
17658
|
+
var import_chalk67 = __toESM(require("chalk"));
|
|
17425
17659
|
init_admin();
|
|
17426
17660
|
init_types();
|
|
17427
17661
|
var UNITS_PER_CENT = 1e4;
|
|
@@ -17448,7 +17682,7 @@ function parseLimit(raw, max) {
|
|
|
17448
17682
|
function fail6(opts, msg) {
|
|
17449
17683
|
process.exitCode = 1;
|
|
17450
17684
|
if (opts.json) return void console.log(JSON.stringify({ ok: false, error: msg }));
|
|
17451
|
-
console.log(
|
|
17685
|
+
console.log(import_chalk67.default.red(msg));
|
|
17452
17686
|
}
|
|
17453
17687
|
function printLedgerError(err, what, opts) {
|
|
17454
17688
|
if (!(err instanceof ApiError) || ![400, 403, 404, 500, 502, 503].includes(err.status)) return false;
|
|
@@ -17459,9 +17693,9 @@ function printLedgerError(err, what, opts) {
|
|
|
17459
17693
|
return true;
|
|
17460
17694
|
}
|
|
17461
17695
|
const code = body.error && body.error !== err.message ? ` ${body.error}` : "";
|
|
17462
|
-
console.log(
|
|
17463
|
-
if (err.status === 503 && body.error === "money_unreachable") console.log(
|
|
17464
|
-
if (err.status === 503 && body.error === "audit_unavailable") console.log(
|
|
17696
|
+
console.log(import_chalk67.default.red(` ${what} failed (${err.status}${code}): ${body.details ?? err.message}`));
|
|
17697
|
+
if (err.status === 503 && body.error === "money_unreachable") console.log(import_chalk67.default.dim(" The money worker did not answer \u2014 retry in a minute; nothing was changed."));
|
|
17698
|
+
if (err.status === 503 && body.error === "audit_unavailable") console.log(import_chalk67.default.dim(" The operator_audit row could not be written, so the action was refused."));
|
|
17465
17699
|
return true;
|
|
17466
17700
|
}
|
|
17467
17701
|
async function call(opts, what, req) {
|
|
@@ -17474,33 +17708,33 @@ async function call(opts, what, req) {
|
|
|
17474
17708
|
}
|
|
17475
17709
|
function renderLedger2(data, opts = {}) {
|
|
17476
17710
|
const out = [];
|
|
17477
|
-
out.push(
|
|
17478
|
-
Ledger \u2014 ${data.billing_account_id}`) +
|
|
17711
|
+
out.push(import_chalk67.default.bold(`
|
|
17712
|
+
Ledger \u2014 ${data.billing_account_id}`) + import_chalk67.default.dim(` money_ledger, oldest first${opts.since ? ` since ${opts.since}` : ""}${opts.kind ? ` \xB7 kind=${opts.kind}` : ""}`));
|
|
17479
17713
|
const h = data.head;
|
|
17480
17714
|
if (h) {
|
|
17481
|
-
const gap = h.contiguous_to < h.head_seq ?
|
|
17715
|
+
const gap = h.contiguous_to < h.head_seq ? import_chalk67.default.yellow(` GAP: ${h.head_seq - h.contiguous_to} seq missing after ${h.contiguous_to}`) : import_chalk67.default.green(" contiguous");
|
|
17482
17716
|
out.push(` head gen ${h.generation} \xB7 seq ${h.head_seq} \xB7 balance ${money3(cents(h.head_balance))} \xB7 at ${ts(h.head_ts)} \xB7 contiguous to ${h.contiguous_to}${gap}`);
|
|
17483
17717
|
} else {
|
|
17484
|
-
out.push(
|
|
17718
|
+
out.push(import_chalk67.default.dim(" head none \u2014 Neon has no row for this wallet yet"));
|
|
17485
17719
|
}
|
|
17486
17720
|
const rows = data.rows ?? [];
|
|
17487
17721
|
if (!rows.length) {
|
|
17488
|
-
out.push(
|
|
17722
|
+
out.push(import_chalk67.default.dim(" No rows."));
|
|
17489
17723
|
return out;
|
|
17490
17724
|
}
|
|
17491
|
-
out.push(
|
|
17725
|
+
out.push(import_chalk67.default.dim(` ${"gen".padStart(3)} ${"seq".padStart(13)} ${"ts".padEnd(19)} ${"class".padEnd(5)} ${"kind".padEnd(18)} ${"amount".padStart(12)} ${"balance after".padStart(13)} flag`));
|
|
17492
17726
|
for (const r of rows) {
|
|
17493
17727
|
const c = typeof r.units_cents === "number" ? r.units_cents : cents(r.units);
|
|
17494
17728
|
const amt = signedMoney2(c);
|
|
17495
17729
|
const bal = typeof r.balance_after_cents === "number" ? r.balance_after_cents : cents(r.balance_after);
|
|
17496
|
-
const flag = r.flag ?
|
|
17497
|
-
const n = r.class === "flow" && (r.count ?? 0) > 1 ?
|
|
17730
|
+
const flag = r.flag ? import_chalk67.default.red(r.flag) : "";
|
|
17731
|
+
const n = r.class === "flow" && (r.count ?? 0) > 1 ? import_chalk67.default.dim(` \xD7${r.count}`) : "";
|
|
17498
17732
|
out.push(
|
|
17499
|
-
` ${String(r.generation).padStart(3)} ${seqRange(r).padStart(13)} ${ts(r.ts_to).padEnd(19)} ${r.class.padEnd(5)} ${trunc2(String(r.kind ?? ""), 18).padEnd(18)} ${(typeof c === "number" && c < 0 ?
|
|
17733
|
+
` ${String(r.generation).padStart(3)} ${seqRange(r).padStart(13)} ${ts(r.ts_to).padEnd(19)} ${r.class.padEnd(5)} ${trunc2(String(r.kind ?? ""), 18).padEnd(18)} ${(typeof c === "number" && c < 0 ? import_chalk67.default.red : import_chalk67.default.green)(amt.padStart(12))} ${money3(bal).padStart(13)} ${flag}${n}`
|
|
17500
17734
|
);
|
|
17501
17735
|
}
|
|
17502
|
-
out.push(
|
|
17503
|
-
if (data.partial) out.push(
|
|
17736
|
+
out.push(import_chalk67.default.bold(` ${"\u03A3".padStart(17)} ${"".padEnd(19)} ${"".padEnd(5)} ${"".padEnd(18)} ${signedMoney2(cents(data.sum_units)).padStart(12)}`));
|
|
17737
|
+
if (data.partial) out.push(import_chalk67.default.yellow(` More than ${data.limit} rows in the window \u2014 narrow --since / --kind or raise --limit (\u2264 1000) before trusting the sum.`));
|
|
17504
17738
|
return out;
|
|
17505
17739
|
}
|
|
17506
17740
|
async function opLedgerShow(ref, opts) {
|
|
@@ -17525,25 +17759,25 @@ async function opLedgerLag(opts) {
|
|
|
17525
17759
|
if (opts.json) return void console.log(JSON.stringify(data, null, 2));
|
|
17526
17760
|
const newest = data.newest_received_at;
|
|
17527
17761
|
const ageS = newest ? Math.max(0, Math.round((Date.now() - Date.parse(newest)) / 1e3)) : null;
|
|
17528
|
-
console.log(
|
|
17529
|
-
console.log(` ${"newest row".padEnd(16)} ${newest ? `${ts(newest)} (${ageS}s ago)` :
|
|
17530
|
-
console.log(` ${"gaps".padEnd(16)} ${(data.gap_count ?
|
|
17531
|
-
console.log(` ${"flagged 24h".padEnd(16)} ${(data.flagged_24h?.count ?
|
|
17532
|
-
console.log(` ${"quarantine 24h".padEnd(16)} ${(data.quarantine_24h ?
|
|
17762
|
+
console.log(import_chalk67.default.bold("\n Ledger lag") + import_chalk67.default.dim(" money_ledger health (gaps, flags, quarantine)"));
|
|
17763
|
+
console.log(` ${"newest row".padEnd(16)} ${newest ? `${ts(newest)} (${ageS}s ago)` : import_chalk67.default.dim("none")}`);
|
|
17764
|
+
console.log(` ${"gaps".padEnd(16)} ${(data.gap_count ? import_chalk67.default.yellow : import_chalk67.default.green)(String(data.gap_count))}${import_chalk67.default.dim(" wallets whose contiguous_to < head_seq")}`);
|
|
17765
|
+
console.log(` ${"flagged 24h".padEnd(16)} ${(data.flagged_24h?.count ? import_chalk67.default.red : import_chalk67.default.green)(String(data.flagged_24h?.count ?? 0))}${import_chalk67.default.dim(" mismatch flags on rows received in the last 24 h")}`);
|
|
17766
|
+
console.log(` ${"quarantine 24h".padEnd(16)} ${(data.quarantine_24h ? import_chalk67.default.red : import_chalk67.default.green)(String(data.quarantine_24h))}${import_chalk67.default.dim(" apiblaze op ledger quarantine")}`);
|
|
17533
17767
|
if (data.gaps?.length) {
|
|
17534
|
-
console.log(
|
|
17535
|
-
console.log(
|
|
17768
|
+
console.log(import_chalk67.default.bold("\n Gaps") + import_chalk67.default.dim(" (biggest first)"));
|
|
17769
|
+
console.log(import_chalk67.default.dim(` ${"missing".padStart(7)} ${"gen".padStart(3)} ${"contig \u2192 head".padEnd(17)} ${"head at".padEnd(19)} wallet`));
|
|
17536
17770
|
for (const g of data.gaps.slice(0, 40)) {
|
|
17537
|
-
console.log(` ${
|
|
17771
|
+
console.log(` ${import_chalk67.default.yellow(String(g.missing).padStart(7))} ${String(g.generation).padStart(3)} ${`${g.contiguous_to} \u2192 ${g.head_seq}`.padEnd(17)} ${ts(g.head_ts).padEnd(19)} ${g.billing_account_id}`);
|
|
17538
17772
|
}
|
|
17539
|
-
if (data.gaps.length > 40) console.log(
|
|
17540
|
-
console.log(
|
|
17773
|
+
if (data.gaps.length > 40) console.log(import_chalk67.default.dim(` \u2026 ${data.gaps.length - 40} more (--json)`));
|
|
17774
|
+
console.log(import_chalk67.default.dim(" A gap the queue is not about to fill (> 30 min): apiblaze op ledger cut <wallet>, or backfill (\xA76.4)."));
|
|
17541
17775
|
}
|
|
17542
17776
|
const flagged = data.flagged_24h?.rows ?? [];
|
|
17543
17777
|
if (flagged.length) {
|
|
17544
|
-
console.log(
|
|
17545
|
-
for (const f of flagged.slice(0, 40)) console.log(` ${ts(f.received_at)} gen ${f.generation} seq ${seqRange(f).padEnd(11)} ${trunc2(f.kind, 14).padEnd(14)} ${
|
|
17546
|
-
if (flagged.length > 40) console.log(
|
|
17778
|
+
console.log(import_chalk67.default.bold("\n Flagged rows (24 h)"));
|
|
17779
|
+
for (const f of flagged.slice(0, 40)) console.log(` ${ts(f.received_at)} gen ${f.generation} seq ${seqRange(f).padEnd(11)} ${trunc2(f.kind, 14).padEnd(14)} ${import_chalk67.default.red(f.flag)} ${f.billing_account_id}`);
|
|
17780
|
+
if (flagged.length > 40) console.log(import_chalk67.default.dim(` \u2026 ${flagged.length - 40} more (--json)`));
|
|
17547
17781
|
}
|
|
17548
17782
|
console.log("");
|
|
17549
17783
|
}
|
|
@@ -17556,14 +17790,14 @@ async function opLedgerQuarantine(opts) {
|
|
|
17556
17790
|
const data = await call(opts, "Quarantine read", { method: "GET", path: `/operator/ledger/quarantine${qs.toString() ? `?${qs}` : ""}`, summary: "ledger quarantine" });
|
|
17557
17791
|
if (!data) return;
|
|
17558
17792
|
if (opts.json) return void console.log(JSON.stringify(data, null, 2));
|
|
17559
|
-
console.log(
|
|
17793
|
+
console.log(import_chalk67.default.bold("\n Ledger quarantine") + import_chalk67.default.dim(` newest first${data.ba ? ` \xB7 ${data.ba}` : ""} \xB7 ${data.count_24h} in the last 24 h`));
|
|
17560
17794
|
const rows = data.rows ?? [];
|
|
17561
|
-
if (!rows.length) return void console.log(
|
|
17562
|
-
console.log(
|
|
17795
|
+
if (!rows.length) return void console.log(import_chalk67.default.green(" Empty.\n"));
|
|
17796
|
+
console.log(import_chalk67.default.dim(` ${"received".padEnd(19)} ${"reason".padEnd(10)} ${"gen".padStart(3)} ${"seq".padStart(13)} wallet`));
|
|
17563
17797
|
for (const r of rows) {
|
|
17564
|
-
console.log(` ${ts(r.received_at).padEnd(19)} ${
|
|
17798
|
+
console.log(` ${ts(r.received_at).padEnd(19)} ${import_chalk67.default.red(trunc2(String(r.reason), 10).padEnd(10))} ${String(r.generation).padStart(3)} ${seqRange(r).padStart(13)} ${r.billing_account_id}${r.existing ? import_chalk67.default.dim(" (differs from the stored row)") : ""}`);
|
|
17565
17799
|
}
|
|
17566
|
-
console.log(
|
|
17800
|
+
console.log(import_chalk67.default.dim("\n Both versions: --json. The fix is always wallet-side (code fix, and an `adjust` if money was wrong) \u2014 never an edit of Neon (\xA76.6).\n"));
|
|
17567
17801
|
}
|
|
17568
17802
|
async function opLedgerCut(ref, opts) {
|
|
17569
17803
|
if (!ref) return fail6(opts, 'Which wallet? apiblaze op ledger cut <wallet id | team id> [--reason "\u2026"]');
|
|
@@ -17574,9 +17808,9 @@ async function opLedgerCut(ref, opts) {
|
|
|
17574
17808
|
const res = await call(opts, "Ledger cut", { method: "POST", path: "/operator/ledger/cut", body, summary: `cut ledger for ${ba}` });
|
|
17575
17809
|
if (!res) return;
|
|
17576
17810
|
if (opts.json) return void console.log(JSON.stringify(res, null, 2));
|
|
17577
|
-
console.log(
|
|
17811
|
+
console.log(import_chalk67.default.green(`
|
|
17578
17812
|
${res.billing_account_id ?? ba}: cut and sent ${res.rows ?? 0} row${res.rows === 1 ? "" : "s"}.`));
|
|
17579
|
-
console.log(
|
|
17813
|
+
console.log(import_chalk67.default.dim(` They land in Neon within seconds: apiblaze op ledger show ${ba}
|
|
17580
17814
|
`));
|
|
17581
17815
|
}
|
|
17582
17816
|
async function opLedgerBackfill(opts) {
|
|
@@ -17592,33 +17826,33 @@ async function opLedgerBackfill(opts) {
|
|
|
17592
17826
|
const res = await call(opts, "Ledger backfill", { method: "POST", path: "/operator/ledger/backfill", body, summary: `ledger backfill since ${new Date(sinceMs).toISOString()}` });
|
|
17593
17827
|
if (!res) return;
|
|
17594
17828
|
if (opts.json) return void console.log(JSON.stringify(res, null, 2));
|
|
17595
|
-
console.log(
|
|
17596
|
-
Ledger backfill since ${new Date(res.since_ms ?? sinceMs).toISOString()}`) +
|
|
17829
|
+
console.log(import_chalk67.default.bold(`
|
|
17830
|
+
Ledger backfill since ${new Date(res.since_ms ?? sinceMs).toISOString()}`) + import_chalk67.default.dim(" verbatim re-send of stored cuts, never a re-cut"));
|
|
17597
17831
|
console.log(` ${"wallets checked".padEnd(16)} ${res.wallets_checked}`);
|
|
17598
17832
|
console.log(` ${"resent".padEnd(16)} ${res.resent_rows} row${res.resent_rows === 1 ? "" : "s"} across ${res.resent_wallets} wallet${res.resent_wallets === 1 ? "" : "s"}`);
|
|
17599
17833
|
console.log(` ${"in sync".padEnd(16)} ${res.in_sync}`);
|
|
17600
|
-
console.log(` ${"uncut".padEnd(16)} ${res.uncut}${
|
|
17601
|
-
console.log(` ${"neon ahead".padEnd(16)} ${(res.neon_ahead ?
|
|
17602
|
-
console.log(` ${"unrecoverable".padEnd(16)} ${(res.unrecoverable ?
|
|
17603
|
-
console.log(` ${"errors".padEnd(16)} ${(res.errors ?
|
|
17834
|
+
console.log(` ${"uncut".padEnd(16)} ${res.uncut}${import_chalk67.default.dim(" entries not cut yet \u2014 reported, not cut (op ledger cut <wallet>)")}`);
|
|
17835
|
+
console.log(` ${"neon ahead".padEnd(16)} ${(res.neon_ahead ? import_chalk67.default.yellow : (s) => s)(String(res.neon_ahead))}`);
|
|
17836
|
+
console.log(` ${"unrecoverable".padEnd(16)} ${(res.unrecoverable ? import_chalk67.default.red : (s) => s)(String(res.unrecoverable))}${import_chalk67.default.dim(" the wallet no longer keeps the cuts after Neon's head (> 30 d)")}`);
|
|
17837
|
+
console.log(` ${"errors".padEnd(16)} ${(res.errors ? import_chalk67.default.red : (s) => s)(String(res.errors))}`);
|
|
17604
17838
|
const notable = (res.results ?? []).filter((r) => r.action !== "in_sync");
|
|
17605
17839
|
if (notable.length) {
|
|
17606
|
-
console.log(
|
|
17840
|
+
console.log(import_chalk67.default.dim(`
|
|
17607
17841
|
${"action".padEnd(10)} ${"wallet gen/seq/cut".padEnd(20)} ${"neon gen/head/contig".padEnd(20)} wallet`));
|
|
17608
17842
|
for (const r of notable.slice(0, 60)) {
|
|
17609
17843
|
const w = r.wallet ? `${r.wallet.generation}/${r.wallet.seq}/${r.wallet.cut_to}` : "\u2014";
|
|
17610
17844
|
const n = r.neon ? `${r.neon.generation}/${r.neon.head_seq}/${r.neon.contiguous_to}` : "\u2014";
|
|
17611
|
-
const colour = r.action === "error" || r.unrecoverable ?
|
|
17845
|
+
const colour = r.action === "error" || r.unrecoverable ? import_chalk67.default.red : r.action === "resent" ? import_chalk67.default.green : import_chalk67.default.yellow;
|
|
17612
17846
|
const note = [r.resent !== void 0 ? `resent ${r.resent}` : "", r.unrecoverable ? `UNRECOVERABLE (oldest kept seq ${r.oldest_kept_seq ?? "?"})` : "", r.error ?? ""].filter(Boolean).join(" \xB7 ");
|
|
17613
|
-
console.log(` ${colour(r.action.padEnd(10))} ${w.padEnd(20)} ${n.padEnd(20)} ${r.billing_account_id}${note ?
|
|
17847
|
+
console.log(` ${colour(r.action.padEnd(10))} ${w.padEnd(20)} ${n.padEnd(20)} ${r.billing_account_id}${note ? import_chalk67.default.dim(` ${note}`) : ""}`);
|
|
17614
17848
|
}
|
|
17615
|
-
if (notable.length > 60) console.log(
|
|
17849
|
+
if (notable.length > 60) console.log(import_chalk67.default.dim(` \u2026 ${notable.length - 60} more (--json)`));
|
|
17616
17850
|
}
|
|
17617
17851
|
console.log("");
|
|
17618
17852
|
}
|
|
17619
17853
|
|
|
17620
17854
|
// src/commands/op-test.ts
|
|
17621
|
-
var
|
|
17855
|
+
var import_chalk68 = __toESM(require("chalk"));
|
|
17622
17856
|
init_auth();
|
|
17623
17857
|
init_types();
|
|
17624
17858
|
var DASHBOARD_BASE11 = process.env.APIBLAZE_DASHBOARD_BASE || "https://dashboard.apiblaze.com";
|
|
@@ -17643,10 +17877,10 @@ async function scrubSandboxKeys(timeoutS = 150) {
|
|
|
17643
17877
|
try {
|
|
17644
17878
|
const r = await runnerCall({ action: "gut", id: TEARDOWN_GUT_ID }, timeoutS);
|
|
17645
17879
|
const first = r.results?.[0]?.detail ?? r.error ?? "";
|
|
17646
|
-
log(
|
|
17880
|
+
log(import_chalk68.default.dim(`teardown: ${first || (r.ok ? "sandbox fixture keys scrubbed" : "scrub reported a problem")}`));
|
|
17647
17881
|
return r.ok;
|
|
17648
17882
|
} catch (e) {
|
|
17649
|
-
log(
|
|
17883
|
+
log(import_chalk68.default.dim(`teardown: sandbox key scrub skipped (${e instanceof Error ? e.message : String(e)}) \u2014 leftovers expire within 14 days; scripts/purge-test-keys.mjs reaps them`));
|
|
17650
17884
|
return false;
|
|
17651
17885
|
}
|
|
17652
17886
|
}
|
|
@@ -17668,23 +17902,23 @@ async function runOpTest(o = {}) {
|
|
|
17668
17902
|
const guts = o.skipGuts ? [] : listing.guts.filter((g) => g.id !== TEARDOWN_GUT_ID && matches(g.id, void 0, { ...o, group: void 0 }));
|
|
17669
17903
|
const total = specs.length + guts.length;
|
|
17670
17904
|
if (total === 0) {
|
|
17671
|
-
log(
|
|
17905
|
+
log(import_chalk68.default.yellow("nothing matched"));
|
|
17672
17906
|
process.exitCode = 2;
|
|
17673
17907
|
return;
|
|
17674
17908
|
}
|
|
17675
|
-
log(
|
|
17909
|
+
log(import_chalk68.default.bold(`apiblaze op test \u2014 ${specs.length} spec(s) + ${guts.length} gut check(s) on sandbox ${listing.sandbox_team}`));
|
|
17676
17910
|
const results = [];
|
|
17677
17911
|
let failed = 0;
|
|
17678
17912
|
const started = Date.now();
|
|
17679
17913
|
const record = (r) => {
|
|
17680
17914
|
results.push(r);
|
|
17681
17915
|
if (!r.ok) failed += 1;
|
|
17682
|
-
const mark = r.ok ?
|
|
17916
|
+
const mark = r.ok ? import_chalk68.default.green("PASS") : import_chalk68.default.red("FAIL");
|
|
17683
17917
|
log(`[${mark}] ${r.id} \u2014 ${r.label} (${ms(r.duration_ms)})`);
|
|
17684
17918
|
if (!r.ok) {
|
|
17685
|
-
if (r.kind === "spec") for (const c of r.cases.filter((c2) => !c2.ok)) log(
|
|
17686
|
-
else if (r.error) log(
|
|
17687
|
-
else for (const x of r.results.filter((x2) => x2 && x2.ok === false)) log(
|
|
17919
|
+
if (r.kind === "spec") for (const c of r.cases.filter((c2) => !c2.ok)) log(import_chalk68.default.red(` \u2717 ${c.name}: ${c.detail}`));
|
|
17920
|
+
else if (r.error) log(import_chalk68.default.red(` \u2717 ${r.error}`));
|
|
17921
|
+
else for (const x of r.results.filter((x2) => x2 && x2.ok === false)) log(import_chalk68.default.red(` \u2717 ${x.name ?? ""}: ${x.detail ?? JSON.stringify(x)}`));
|
|
17688
17922
|
}
|
|
17689
17923
|
};
|
|
17690
17924
|
try {
|
|
@@ -17715,13 +17949,13 @@ async function runOpTest(o = {}) {
|
|
|
17715
17949
|
}
|
|
17716
17950
|
const summary = { ran: results.length, passed: results.length - failed, failed, duration_ms: Date.now() - started, sandbox_team: listing.sandbox_team };
|
|
17717
17951
|
log("");
|
|
17718
|
-
log(failed ?
|
|
17952
|
+
log(failed ? import_chalk68.default.red(`${summary.passed}/${summary.ran} passed, ${failed} failed (${ms(summary.duration_ms)})`) : import_chalk68.default.green(`${summary.passed}/${summary.ran} passed (${ms(summary.duration_ms)})`));
|
|
17719
17953
|
if (o.json) process.stdout.write(JSON.stringify({ ...summary, results }, null, 2) + "\n");
|
|
17720
17954
|
if (failed) process.exitCode = 1;
|
|
17721
17955
|
}
|
|
17722
17956
|
|
|
17723
17957
|
// src/commands/op-consumer.ts
|
|
17724
|
-
var
|
|
17958
|
+
var import_chalk69 = __toESM(require("chalk"));
|
|
17725
17959
|
init_consumer_auth();
|
|
17726
17960
|
var SANDBOX_TENANT = "portalteststenant";
|
|
17727
17961
|
var SANDBOX_PROXY = "portaltestproxypreapproved@2.7.0";
|
|
@@ -17764,9 +17998,9 @@ async function runOpConsumer(o = {}) {
|
|
|
17764
17998
|
const creds = loadConsumer();
|
|
17765
17999
|
const done = (r) => {
|
|
17766
18000
|
const out = { kind: "consumer", id: "consumer-door", label: "Consumer door: the stored consumer token opens its tenant's OAuth-door proxy", duration_ms: Date.now() - t0, ...r };
|
|
17767
|
-
const mark = out.skipped ?
|
|
18001
|
+
const mark = out.skipped ? import_chalk69.default.yellow("SKIP") : out.ok ? import_chalk69.default.green("PASS") : import_chalk69.default.red("FAIL");
|
|
17768
18002
|
log2(`[${mark}] ${out.id} \u2014 ${out.label}${out.tenant ? ` (tenant ${out.tenant})` : ""}${out.url ? ` \u2192 ${out.url}` : ""}`);
|
|
17769
|
-
for (const c of out.cases) if (!c.ok || out.skipped) log2((c.ok ?
|
|
18003
|
+
for (const c of out.cases) if (!c.ok || out.skipped) log2((c.ok ? import_chalk69.default.dim : import_chalk69.default.red)(` ${c.ok ? "ok " : "\u2717"} ${c.name}: ${c.detail}`));
|
|
17770
18004
|
if (o.json) process.stdout.write(JSON.stringify(out, null, 2) + "\n");
|
|
17771
18005
|
if (!out.ok && !out.skipped) process.exitCode = 1;
|
|
17772
18006
|
if (out.skipped) process.exitCode = 2;
|
|
@@ -17806,7 +18040,7 @@ async function runOpConsumer(o = {}) {
|
|
|
17806
18040
|
}
|
|
17807
18041
|
|
|
17808
18042
|
// src/commands/op-perf.ts
|
|
17809
|
-
var
|
|
18043
|
+
var import_chalk70 = __toESM(require("chalk"));
|
|
17810
18044
|
var RPS_DEFAULT = 100;
|
|
17811
18045
|
var RPS_CAP = 300;
|
|
17812
18046
|
var SECONDS_DEFAULT = 60;
|
|
@@ -17990,10 +18224,10 @@ async function readGateway(proxy, sent, signal) {
|
|
|
17990
18224
|
}
|
|
17991
18225
|
const left = deadline - Date.now();
|
|
17992
18226
|
if (left <= 0) {
|
|
17993
|
-
log3(
|
|
18227
|
+
log3(import_chalk70.default.yellow(` gateway: not available \u2014 Analytics Engine had ${lastSeen}/${sent} rows for ${proxy} after ${GATEWAY_WAIT_MS / 1e3}s${lastErr ? ` (last error: ${lastErr})` : ""}`));
|
|
17994
18228
|
return null;
|
|
17995
18229
|
}
|
|
17996
|
-
log3(
|
|
18230
|
+
log3(import_chalk70.default.dim(` waiting for the latency ledger\u2026 ${lastSeen}/${sent} rows for ${proxy}${lastErr ? ` (${lastErr})` : ""}`));
|
|
17997
18231
|
await sleep(Math.min(GATEWAY_POLL_MS, left), signal);
|
|
17998
18232
|
}
|
|
17999
18233
|
}
|
|
@@ -18002,7 +18236,7 @@ async function runOpPerf(o = {}) {
|
|
|
18002
18236
|
const thresholds = thresholdsFrom(o);
|
|
18003
18237
|
const ctrl = new AbortController();
|
|
18004
18238
|
const onSigint = () => {
|
|
18005
|
-
log3(
|
|
18239
|
+
log3(import_chalk70.default.yellow("\ninterrupted \u2014 stopping the load and tearing down the fixture"));
|
|
18006
18240
|
ctrl.abort();
|
|
18007
18241
|
};
|
|
18008
18242
|
process.once("SIGINT", onSigint);
|
|
@@ -18011,7 +18245,7 @@ async function runOpPerf(o = {}) {
|
|
|
18011
18245
|
if (!o.key) throw new Error("--url needs --key (the fixture proxy's API key)");
|
|
18012
18246
|
fixture = { url: o.url, key: o.key, proxy: proxyFromUrl(o.url), provisioned: false };
|
|
18013
18247
|
} else {
|
|
18014
|
-
log3(
|
|
18248
|
+
log3(import_chalk70.default.dim("provisioning the perf fixture through the runner\u2026"));
|
|
18015
18249
|
const f = await runnerCall({ action: "perf-fixture" }, 120);
|
|
18016
18250
|
if (!f?.url || !f?.key) throw new Error(`perf-fixture returned no url/key: ${JSON.stringify(f).slice(0, 200)}`);
|
|
18017
18251
|
fixture = { url: f.url, key: f.key, proxy: f.proxy || proxyFromUrl(f.url), team: f.team, provisioned: true };
|
|
@@ -18019,7 +18253,7 @@ async function runOpPerf(o = {}) {
|
|
|
18019
18253
|
try {
|
|
18020
18254
|
const total = Math.round(rps * seconds);
|
|
18021
18255
|
const intervalMs = 1e3 / rps;
|
|
18022
|
-
log3(
|
|
18256
|
+
log3(import_chalk70.default.bold(`apiblaze op test --perf \u2014 ${rps} rps \xD7 ${seconds}s (${total} requests) against ${fixture.url}${fixture.team ? ` (sandbox ${fixture.team})` : ""}`));
|
|
18023
18257
|
const samples = [];
|
|
18024
18258
|
const inflight = /* @__PURE__ */ new Set();
|
|
18025
18259
|
let fired = 0;
|
|
@@ -18027,7 +18261,7 @@ async function runOpPerf(o = {}) {
|
|
|
18027
18261
|
const progress = setInterval(() => {
|
|
18028
18262
|
const m = summarize(samples, Date.now() - t0);
|
|
18029
18263
|
const secs = (Date.now() - t0) / 1e3;
|
|
18030
|
-
log3(
|
|
18264
|
+
log3(import_chalk70.default.dim(` ${Math.round(secs)}s sent ${fired} ok ${m.ok} err ${m.errors} p50 ${fmtMs(m.p50)} p95 ${fmtMs(m.p95)} (${(fired / secs).toFixed(1)} rps)`));
|
|
18031
18265
|
}, PROGRESS_EVERY_MS);
|
|
18032
18266
|
const { sent, elapsedMs } = await schedule({
|
|
18033
18267
|
total,
|
|
@@ -18041,21 +18275,21 @@ async function runOpPerf(o = {}) {
|
|
|
18041
18275
|
}
|
|
18042
18276
|
});
|
|
18043
18277
|
clearInterval(progress);
|
|
18044
|
-
if (inflight.size) log3(
|
|
18278
|
+
if (inflight.size) log3(import_chalk70.default.dim(` load sent; waiting for ${inflight.size} in-flight response(s) (\u2264 ${REQUEST_TIMEOUT_MS / 1e3}s)\u2026`));
|
|
18045
18279
|
await Promise.all([...inflight]);
|
|
18046
18280
|
const client = summarize(samples, elapsedMs);
|
|
18047
18281
|
log3("");
|
|
18048
|
-
log3(` ${
|
|
18282
|
+
log3(` ${import_chalk70.default.bold("client ")} p50 ${fmtMs(client.p50).padStart(7)} p95 ${fmtMs(client.p95).padStart(7)} p99 ${fmtMs(client.p99).padStart(7)} errors ${client.error_rate_pct.toFixed(2)}% (${client.errors}/${client.sent}: ${client.non2xx} non-2xx, ${client.timeouts} timeouts, ${client.network} network) achieved ${client.rps_achieved} rps of ${rps}`);
|
|
18049
18283
|
const gateway = ctrl.signal.aborted ? null : await readGateway(fixture.proxy, sent, ctrl.signal);
|
|
18050
18284
|
if (gateway) {
|
|
18051
18285
|
const extras = [
|
|
18052
18286
|
gateway.kv_per_req !== void 0 ? `kv/req ${gateway.kv_per_req}` : "",
|
|
18053
18287
|
gateway.redis_per_req !== void 0 ? `redis/req ${gateway.redis_per_req}` : ""
|
|
18054
18288
|
].filter(Boolean).join(" ");
|
|
18055
|
-
log3(` ${
|
|
18056
|
-
for (const b of gateway.blame ?? []) log3(
|
|
18289
|
+
log3(` ${import_chalk70.default.bold("gateway")} p50 ${fmtMs(gateway.p50).padStart(7)} p95 ${fmtMs(gateway.p95).padStart(7)}${gateway.p99 !== void 0 ? ` p99 ${fmtMs(gateway.p99).padStart(7)}` : ""} ${import_chalk70.default.dim(`(${gateway.requests} ledger rows${extras ? `, ${extras}` : ""})`)}`);
|
|
18290
|
+
for (const b of gateway.blame ?? []) log3(import_chalk70.default.dim(` ${(b.share * 100).toFixed(1).padStart(5)}% p95 ${fmtMs(b.p95_ms).padStart(6)} ${b.feature} \u2192 ${b.dep}`));
|
|
18057
18291
|
} else {
|
|
18058
|
-
log3(` ${
|
|
18292
|
+
log3(` ${import_chalk70.default.bold("gateway")} ${import_chalk70.default.yellow("not available")} ${import_chalk70.default.dim("(gateway thresholds not evaluated)")}`);
|
|
18059
18293
|
}
|
|
18060
18294
|
const breaches = evaluate(client, gateway, thresholds);
|
|
18061
18295
|
const aborted = ctrl.signal.aborted;
|
|
@@ -18063,7 +18297,7 @@ async function runOpPerf(o = {}) {
|
|
|
18063
18297
|
log3("");
|
|
18064
18298
|
const row = (metric, value, limit, unit) => {
|
|
18065
18299
|
const hit = breaches.find((b) => b.metric === metric);
|
|
18066
|
-
log3(`[${hit ?
|
|
18300
|
+
log3(`[${hit ? import_chalk70.default.red("FAIL") : import_chalk70.default.green("PASS")}] ${metric.padEnd(11)} ${String(value)}${unit} ${hit ? ">" : "\u2264"} ${limit}${unit}`);
|
|
18067
18301
|
};
|
|
18068
18302
|
row("client_p50", client.p50, thresholds.p50_ms, "ms");
|
|
18069
18303
|
row("client_p95", client.p95, thresholds.p95_ms, "ms");
|
|
@@ -18072,12 +18306,12 @@ async function runOpPerf(o = {}) {
|
|
|
18072
18306
|
row("gateway_p50", gateway.p50, thresholds.gateway_p50_ms, "ms");
|
|
18073
18307
|
row("gateway_p95", gateway.p95, thresholds.gateway_p95_ms, "ms");
|
|
18074
18308
|
} else {
|
|
18075
|
-
log3(`[${
|
|
18309
|
+
log3(`[${import_chalk70.default.yellow("SKIP")}] gateway_p50 / gateway_p95 \u2014 ledger rows not available`);
|
|
18076
18310
|
}
|
|
18077
18311
|
log3("");
|
|
18078
|
-
if (aborted) log3(
|
|
18079
|
-
else if (ok) log3(
|
|
18080
|
-
else log3(
|
|
18312
|
+
if (aborted) log3(import_chalk70.default.red(`FAIL \u2014 interrupted after ${sent}/${total} requests`));
|
|
18313
|
+
else if (ok) log3(import_chalk70.default.green(`PASS \u2014 ${client.sent} requests at ${client.rps_achieved} rps, no threshold breached`));
|
|
18314
|
+
else log3(import_chalk70.default.red(`FAIL \u2014 ${breaches.length} threshold(s) breached: ${breaches.map((b) => `${b.metric} ${b.value} > ${b.limit}`).join(", ")}`));
|
|
18081
18315
|
if (o.json) {
|
|
18082
18316
|
const doc = {
|
|
18083
18317
|
kind: "perf",
|
|
@@ -18115,9 +18349,9 @@ async function runOpPerf(o = {}) {
|
|
|
18115
18349
|
if (fixture.provisioned) {
|
|
18116
18350
|
try {
|
|
18117
18351
|
await runnerCall({ action: "perf-teardown", proxy: fixture.proxy }, 120);
|
|
18118
|
-
log3(
|
|
18352
|
+
log3(import_chalk70.default.dim(`fixture ${fixture.proxy} torn down`));
|
|
18119
18353
|
} catch (e) {
|
|
18120
|
-
log3(
|
|
18354
|
+
log3(import_chalk70.default.red(`teardown of ${fixture.proxy} failed: ${e instanceof Error ? e.message : String(e)} \u2014 run \`apiblaze op residue\` / clean the sandbox`));
|
|
18121
18355
|
process.exitCode = 1;
|
|
18122
18356
|
}
|
|
18123
18357
|
await scrubSandboxKeys(120);
|
|
@@ -18167,79 +18401,79 @@ var OP_COMMANDS = [
|
|
|
18167
18401
|
function renderOpCommands() {
|
|
18168
18402
|
const width = Math.max(...OP_COMMANDS.map((c) => c.cmd.length)) + 10;
|
|
18169
18403
|
const lines = OP_COMMANDS.map((c) => {
|
|
18170
|
-
const left = ` ${
|
|
18404
|
+
const left = ` ${import_chalk72.default.cyan(`apiblaze ${c.cmd}`)}`;
|
|
18171
18405
|
const pad = " ".repeat(Math.max(1, width - c.cmd.length));
|
|
18172
|
-
return `${left}${pad}${c.blurb}${c.extra ? " " +
|
|
18406
|
+
return `${left}${pad}${c.blurb}${c.extra ? " " + import_chalk72.default.dim(`(${c.extra})`) : ""}`;
|
|
18173
18407
|
});
|
|
18174
18408
|
return [
|
|
18175
|
-
|
|
18409
|
+
import_chalk72.default.bold("Operator commands"),
|
|
18176
18410
|
...lines,
|
|
18177
18411
|
"",
|
|
18178
|
-
|
|
18179
|
-
|
|
18180
|
-
|
|
18412
|
+
import_chalk72.default.dim(" Operators only. The gate is server-side (dashboard /api/cli/op checks the"),
|
|
18413
|
+
import_chalk72.default.dim(" signed-in email, admin-api re-checks with operatorGate) \u2014 a patched CLI just"),
|
|
18414
|
+
import_chalk72.default.dim(" gets 403s. Every op call is read-only except `op sweep`."),
|
|
18181
18415
|
"",
|
|
18182
|
-
|
|
18183
|
-
|
|
18184
|
-
|
|
18185
|
-
|
|
18186
|
-
|
|
18187
|
-
|
|
18416
|
+
import_chalk72.default.dim(" Not a CLI command: to prune all non-CP data run scripts/nuke-but-cp.sh --apply --sweep"),
|
|
18417
|
+
import_chalk72.default.dim(" in the repo. Wallet and incident MUTATIONS are dashboard-only (/operator/wallets/<id>,"),
|
|
18418
|
+
import_chalk72.default.dim(" /operator/incident) \u2014 they need a typed reason and an audit row. Billing conformance"),
|
|
18419
|
+
import_chalk72.default.dim(" (every door metered, 1 request = 1 charge) runs inside `op test`. Operator dashboards"),
|
|
18420
|
+
import_chalk72.default.dim(" (dlq, thresholds, limits, pricing, agent-spend, teams, tests, leak-detection,"),
|
|
18421
|
+
import_chalk72.default.dim(" lifecycle) live at /operator/* in the app.")
|
|
18188
18422
|
].join("\n");
|
|
18189
18423
|
}
|
|
18190
18424
|
function printResidue(report, applied) {
|
|
18191
18425
|
const fga = report?.fga ?? {};
|
|
18192
18426
|
const ghosts = report?.ghosts ?? {};
|
|
18193
|
-
console.log(
|
|
18194
|
-
console.log(
|
|
18427
|
+
console.log(import_chalk72.default.bold(applied ? "\nExternal-residue sweep" : "\nExternal residue (dry-run \u2014 nothing deleted)"));
|
|
18428
|
+
console.log(import_chalk72.default.bold("\n OpenFGA / Neon \u2014 orphan stores"));
|
|
18195
18429
|
if (applied) {
|
|
18196
18430
|
const swept = fga?.swept ?? [];
|
|
18197
|
-
if (swept.length === 0) console.log(
|
|
18431
|
+
if (swept.length === 0) console.log(import_chalk72.default.green(" no orphaned stores"));
|
|
18198
18432
|
for (const s of swept) {
|
|
18199
18433
|
console.log(
|
|
18200
|
-
` ${
|
|
18434
|
+
` ${import_chalk72.default.yellow(s.store_id)} ${import_chalk72.default.dim(`\u2014 store ${s.openfga_deleted ? "deleted" : "DEFERRED"}, ${s.neon_deleted} Neon tuple(s) purged`)}`
|
|
18201
18435
|
);
|
|
18202
18436
|
}
|
|
18203
|
-
if (fga?.remaining) console.log(
|
|
18437
|
+
if (fga?.remaining) console.log(import_chalk72.default.yellow(` ${fga.remaining} more orphan store(s) \u2014 re-run to drain`));
|
|
18204
18438
|
const st = fga?.side_tables;
|
|
18205
|
-
if (st) console.log(
|
|
18439
|
+
if (st) console.log(import_chalk72.default.dim(` Neon side-tables purged: ${st.soft_deleted_stores} store records, ${st.orphan_models} models, ${st.orphan_changelog} changelog rows${st.error ? ` (${st.error})` : ""}`));
|
|
18206
18440
|
} else {
|
|
18207
18441
|
const fgaOrphans = fga?.orphans ?? [];
|
|
18208
|
-
if (fgaOrphans.length === 0) console.log(
|
|
18442
|
+
if (fgaOrphans.length === 0) console.log(import_chalk72.default.green(" no orphaned stores"));
|
|
18209
18443
|
for (const s of fgaOrphans) {
|
|
18210
18444
|
const src = s.in_openfga ? "live in OpenFGA" : "Neon tuples only";
|
|
18211
|
-
console.log(` ${
|
|
18445
|
+
console.log(` ${import_chalk72.default.yellow(s.store_id)} ${import_chalk72.default.dim(`\u2014 ${src}${s.name ? ` (${s.name})` : ""}, ${s.neon_tuples} Neon tuple(s)`)}`);
|
|
18212
18446
|
}
|
|
18213
|
-
console.log(
|
|
18447
|
+
console.log(import_chalk72.default.dim(` kept stores: ${(fga?.kept_store_ids ?? []).length}`));
|
|
18214
18448
|
const st = fga?.side_tables;
|
|
18215
|
-
if (st) console.log(
|
|
18449
|
+
if (st) console.log(import_chalk72.default.dim(` Neon side-table residue: ${st.soft_deleted_stores} soft-deleted store records, ${st.orphan_models} orphan models, ${st.orphan_changelog} orphan changelog rows`));
|
|
18216
18450
|
}
|
|
18217
|
-
for (const e of fga?.errors ?? []) console.log(
|
|
18218
|
-
console.log(
|
|
18451
|
+
for (const e of fga?.errors ?? []) console.log(import_chalk72.default.red(` error: ${e}`));
|
|
18452
|
+
console.log(import_chalk72.default.bold("\n OpenFGA \u2014 ghost tuples in surviving stores"));
|
|
18219
18453
|
if (applied) {
|
|
18220
|
-
if ((ghosts?.ghost_count ?? 0) === 0) console.log(
|
|
18221
|
-
else console.log(` ${
|
|
18454
|
+
if ((ghosts?.ghost_count ?? 0) === 0) console.log(import_chalk72.default.green(" no ghost tuples"));
|
|
18455
|
+
else console.log(` ${import_chalk72.default.bold(String(ghosts.deleted ?? 0))} ghost tuple(s) deleted ${import_chalk72.default.dim(`(of ${ghosts.ghost_count} found, ${ghosts.scanned_tuples} scanned across ${ghosts.live_stores} live stores)`)}`);
|
|
18222
18456
|
} else {
|
|
18223
18457
|
const n = ghosts?.ghost_count ?? 0;
|
|
18224
|
-
if (n === 0) console.log(
|
|
18458
|
+
if (n === 0) console.log(import_chalk72.default.green(` no ghost tuples ${import_chalk72.default.dim(`(${ghosts.scanned_tuples ?? 0} scanned across ${ghosts.live_stores ?? 0} live stores)`)}`));
|
|
18225
18459
|
else {
|
|
18226
|
-
console.log(
|
|
18460
|
+
console.log(import_chalk72.default.yellow(` ${n} ghost tuple(s) referencing entities absent from D1:`));
|
|
18227
18461
|
for (const g of (ghosts.ghosts ?? []).slice(0, 20)) {
|
|
18228
|
-
console.log(
|
|
18462
|
+
console.log(import_chalk72.default.dim(` ${g.object_type}:${g.object_id} ${g.relation} ${g._user}`));
|
|
18229
18463
|
}
|
|
18230
|
-
if (n > 20) console.log(
|
|
18464
|
+
if (n > 20) console.log(import_chalk72.default.dim(` \u2026 and ${n - 20} more`));
|
|
18231
18465
|
}
|
|
18232
18466
|
}
|
|
18233
|
-
for (const e of ghosts?.errors ?? []) console.log(
|
|
18467
|
+
for (const e of ghosts?.errors ?? []) console.log(import_chalk72.default.red(` error: ${e}`));
|
|
18234
18468
|
console.log();
|
|
18235
18469
|
}
|
|
18236
18470
|
async function runOp(sub, opts = {}, view, arg) {
|
|
18237
18471
|
if (!loadCredentials()) {
|
|
18238
|
-
console.log(
|
|
18472
|
+
console.log(import_chalk72.default.dim("Not logged in. Run `apiblaze login`."));
|
|
18239
18473
|
return;
|
|
18240
18474
|
}
|
|
18241
18475
|
if (!isOperatorLogin()) {
|
|
18242
|
-
console.log(
|
|
18476
|
+
console.log(import_chalk72.default.dim("`apiblaze op` is only available to platform operators."));
|
|
18243
18477
|
return;
|
|
18244
18478
|
}
|
|
18245
18479
|
switch (sub) {
|
|
@@ -18308,11 +18542,11 @@ async function runOp(sub, opts = {}, view, arg) {
|
|
|
18308
18542
|
}
|
|
18309
18543
|
if (opts.json) return void console.log(JSON.stringify(out, null, 2));
|
|
18310
18544
|
console.log("");
|
|
18311
|
-
console.log(` Minimum CLI version: ${
|
|
18312
|
-
console.log(` Refuse unversioned: ${out.reject_unversioned ?
|
|
18313
|
-
console.log(` Message: ${out.message ??
|
|
18314
|
-
if (out.updated_at) console.log(
|
|
18315
|
-
console.log(
|
|
18545
|
+
console.log(` Minimum CLI version: ${import_chalk72.default.bold(out.min_version)}${out.min_version === "0.0.0" ? import_chalk72.default.dim(" (no minimum)") : ""}`);
|
|
18546
|
+
console.log(` Refuse unversioned: ${out.reject_unversioned ? import_chalk72.default.yellow("yes \u2014 CLIs released before 2026-09-27 get a 426 (cheap refusals; they keep retrying)") : "no"}`);
|
|
18547
|
+
console.log(` Message: ${out.message ?? import_chalk72.default.dim("(default)")}`);
|
|
18548
|
+
if (out.updated_at) console.log(import_chalk72.default.dim(` Updated ${out.updated_at}${out.updated_by ? ` by ${out.updated_by}` : ""}`));
|
|
18549
|
+
console.log(import_chalk72.default.dim(" Takes effect within ~1 minute on the CLI lane; running `dev` sessions re-check every 10 minutes."));
|
|
18316
18550
|
console.log("");
|
|
18317
18551
|
return;
|
|
18318
18552
|
}
|
|
@@ -18330,17 +18564,17 @@ async function runOp(sub, opts = {}, view, arg) {
|
|
|
18330
18564
|
const nSide = (st.soft_deleted_stores ?? 0) + (st.orphan_models ?? 0) + (st.orphan_changelog ?? 0);
|
|
18331
18565
|
printResidue(report, false);
|
|
18332
18566
|
if (nFga + nGhost + nSide === 0) {
|
|
18333
|
-
console.log(
|
|
18567
|
+
console.log(import_chalk72.default.green("Nothing to sweep."));
|
|
18334
18568
|
return;
|
|
18335
18569
|
}
|
|
18336
18570
|
if (!opts.yes) {
|
|
18337
18571
|
const readline3 = await import("readline/promises");
|
|
18338
18572
|
const rl = readline3.createInterface({ input: process.stdin, output: process.stdout });
|
|
18339
18573
|
const answer = await rl.question(
|
|
18340
|
-
|
|
18574
|
+
import_chalk72.default.red(`Delete ${nFga} OpenFGA store(s) + ${nGhost} ghost tuple(s) + ${nSide} Neon side-table row(s)? Type 'sweep' to confirm: `)
|
|
18341
18575
|
);
|
|
18342
18576
|
rl.close();
|
|
18343
|
-
if (answer.trim() !== "sweep") return void console.log(
|
|
18577
|
+
if (answer.trim() !== "sweep") return void console.log(import_chalk72.default.dim("Aborted."));
|
|
18344
18578
|
}
|
|
18345
18579
|
const result = await opCall({ method: "POST", path: "/operator/external-residue/sweep", summary: "external residue sweep" });
|
|
18346
18580
|
if (opts.json) return void console.log(JSON.stringify(result, null, 2));
|
|
@@ -18350,15 +18584,15 @@ async function runOp(sub, opts = {}, view, arg) {
|
|
|
18350
18584
|
case "mark": {
|
|
18351
18585
|
const label3 = (view ?? "").trim();
|
|
18352
18586
|
if (!label3) {
|
|
18353
|
-
console.log(
|
|
18587
|
+
console.log(import_chalk72.default.red("Give the change a name:") + import_chalk72.default.cyan(' apiblaze op mark "cached tenant count"'));
|
|
18354
18588
|
return;
|
|
18355
18589
|
}
|
|
18356
18590
|
const res = await opCall({ method: "POST", path: "/operator/latency/mark", body: { label: label3 }, summary: "record change marker" });
|
|
18357
18591
|
const ts2 = new Date(res?.marker?.ts ?? Date.now()).toISOString();
|
|
18358
|
-
console.log(
|
|
18359
|
-
Marked: `) +
|
|
18360
|
-
console.log(
|
|
18361
|
-
console.log(
|
|
18592
|
+
console.log(import_chalk72.default.green(`
|
|
18593
|
+
Marked: `) + import_chalk72.default.bold(label3));
|
|
18594
|
+
console.log(import_chalk72.default.dim(` ${ts2}`));
|
|
18595
|
+
console.log(import_chalk72.default.dim(` Once traffic has run on both sides, compare with: `) + import_chalk72.default.cyan("apiblaze op latency compare") + "\n");
|
|
18362
18596
|
return;
|
|
18363
18597
|
}
|
|
18364
18598
|
// ── MONEY (specs/TQC §11 "Operator console") — commands/op-money.ts ────
|
|
@@ -18369,14 +18603,14 @@ async function runOp(sub, opts = {}, view, arg) {
|
|
|
18369
18603
|
if (which === "list") return void await opWalletList(opts);
|
|
18370
18604
|
if (which === "inspect") return void await opWalletInspect(arg, opts);
|
|
18371
18605
|
if (which in WALLET_MUTATIONS) return void opWalletMutation(which, arg, opts);
|
|
18372
|
-
return void console.log(
|
|
18606
|
+
return void console.log(import_chalk72.default.red(`Unknown: apiblaze op wallet ${which}. Use list | inspect <wallet> | ${Object.keys(WALLET_MUTATIONS).join(" | ")}.`));
|
|
18373
18607
|
}
|
|
18374
18608
|
case "money": {
|
|
18375
18609
|
const which = (view ?? "").trim().toLowerCase();
|
|
18376
18610
|
if (which === "exposure") return void await opMoneyExposure(opts);
|
|
18377
18611
|
if (which === "journal") return void await opLedgerShow(arg, opts);
|
|
18378
18612
|
process.exitCode = 1;
|
|
18379
|
-
return void console.log(
|
|
18613
|
+
return void console.log(import_chalk72.default.red(`Unknown: apiblaze op money ${which}. Use exposure; the money ledger is`) + import_chalk72.default.cyan(" apiblaze op ledger show <wallet> | lag") + import_chalk72.default.red("."));
|
|
18380
18614
|
}
|
|
18381
18615
|
// ── MONEY LEDGER (specs/noreconciliations.MD §6/§7) — commands/op-ledger.ts ──
|
|
18382
18616
|
case "ledger": {
|
|
@@ -18387,20 +18621,20 @@ async function runOp(sub, opts = {}, view, arg) {
|
|
|
18387
18621
|
if (which === "cut") return void await opLedgerCut(arg, opts);
|
|
18388
18622
|
if (which === "backfill") return void await opLedgerBackfill(opts);
|
|
18389
18623
|
process.exitCode = 1;
|
|
18390
|
-
return void console.log(
|
|
18624
|
+
return void console.log(import_chalk72.default.red(`Unknown: apiblaze op ledger ${which}. Use show <wallet> | lag | quarantine | cut <wallet> | backfill --since <ISO|ms>.`));
|
|
18391
18625
|
}
|
|
18392
18626
|
// ── MAXIMUM RATE (project ceiling) requests — commands/op-ceiling.ts ──
|
|
18393
18627
|
case "ceiling": {
|
|
18394
18628
|
const which = (view ?? "list").trim().toLowerCase();
|
|
18395
18629
|
if (which === "list") return void await opCeilingList(opts);
|
|
18396
18630
|
if (which === "approve" || which === "deny") return void await opCeilingDecide(which, arg, opts);
|
|
18397
|
-
return void console.log(
|
|
18631
|
+
return void console.log(import_chalk72.default.red(`Unknown: apiblaze op ceiling ${which}. Use list | approve <id> | deny <id> --note "why".`));
|
|
18398
18632
|
}
|
|
18399
18633
|
case "incident": {
|
|
18400
18634
|
const which = (view ?? "status").trim().toLowerCase();
|
|
18401
18635
|
if (which === "status" || which === "degraded-mode") return void await opIncidentStatus(opts);
|
|
18402
18636
|
if (which === "allow" || which === "deny" || which === "extend") return void opIncidentMutation(which, opts);
|
|
18403
|
-
return void console.log(
|
|
18637
|
+
return void console.log(import_chalk72.default.red(`Unknown: apiblaze op incident ${which}. Use status | allow | deny | extend.`));
|
|
18404
18638
|
}
|
|
18405
18639
|
// ── LATENCY (specs/latency/ §7.4) ─────────────────────────────────────
|
|
18406
18640
|
// The blame table first: level 1 (which feature) and level 2 (what inside
|
|
@@ -18450,29 +18684,29 @@ async function runOp(sub, opts = {}, view, arg) {
|
|
|
18450
18684
|
const data = await opCall({ method: "GET", path: `/operator/latency/grades${q}`, summary: "latency grades" });
|
|
18451
18685
|
if (opts.json) return void console.log(JSON.stringify(data, null, 2));
|
|
18452
18686
|
const t = data.thresholds_ms;
|
|
18453
|
-
console.log(
|
|
18454
|
-
console.log(
|
|
18687
|
+
console.log(import_chalk72.default.bold("\nHow good was apiblaze itself?") + import_chalk72.default.dim(" (our overhead only \u2014 a slow customer API never counts against us)"));
|
|
18688
|
+
console.log(import_chalk72.default.dim(` excellent <${t.excellent.replace("<", "")}ms \xB7 okay ${t.okay}ms \xB7 bad ${t.bad}ms \xB7 terrible ${t.terrible.replace(">=", "")}ms+
|
|
18455
18689
|
`));
|
|
18456
|
-
console.log(
|
|
18690
|
+
console.log(import_chalk72.default.dim(" date reqs excellent okay bad terrible"));
|
|
18457
18691
|
for (const d of data.days ?? []) {
|
|
18458
18692
|
const p = d.pct;
|
|
18459
|
-
const cell = (v, colour) => v > 0 ? colour(`${String(v).padStart(5)}%`) :
|
|
18693
|
+
const cell = (v, colour) => v > 0 ? colour(`${String(v).padStart(5)}%`) : import_chalk72.default.dim(`${String(v).padStart(5)}%`);
|
|
18460
18694
|
console.log(
|
|
18461
|
-
` ${d.date} ${String(d.total).padStart(5)} ${cell(p.excellent,
|
|
18695
|
+
` ${d.date} ${String(d.total).padStart(5)} ${cell(p.excellent, import_chalk72.default.green)} ${cell(p.okay, import_chalk72.default.cyan)} ${cell(p.bad, import_chalk72.default.yellow)} ${cell(p.terrible, import_chalk72.default.red)}`
|
|
18462
18696
|
);
|
|
18463
18697
|
}
|
|
18464
18698
|
const cul = data.culprits ?? [];
|
|
18465
18699
|
if (cul.length) {
|
|
18466
|
-
console.log(
|
|
18467
|
-
console.log(
|
|
18700
|
+
console.log(import_chalk72.default.bold("\n Who caused the bad and terrible ones\n"));
|
|
18701
|
+
console.log(import_chalk72.default.dim(" bad terrible feature \u2192 dependency"));
|
|
18468
18702
|
for (const r of cul.slice(0, 12)) {
|
|
18469
18703
|
if (!r.bad && !r.terrible) continue;
|
|
18470
18704
|
console.log(
|
|
18471
|
-
` ${String(r.bad).padStart(6)} ${
|
|
18705
|
+
` ${String(r.bad).padStart(6)} ${import_chalk72.default.red(String(r.terrible).padStart(8))} ${import_chalk72.default.yellow(r.feature)} ${import_chalk72.default.dim("\u2192")} ${import_chalk72.default.cyan(r.dep)}`
|
|
18472
18706
|
);
|
|
18473
18707
|
}
|
|
18474
18708
|
}
|
|
18475
|
-
if (data.caveat) console.log(
|
|
18709
|
+
if (data.caveat) console.log(import_chalk72.default.dim(`
|
|
18476
18710
|
\u26A0 ${data.caveat}
|
|
18477
18711
|
`));
|
|
18478
18712
|
return;
|
|
@@ -18481,24 +18715,24 @@ async function runOp(sub, opts = {}, view, arg) {
|
|
|
18481
18715
|
const data = await opCall({ method: "GET", path: `/operator/latency/compare${q}`, summary: "latency before/after" });
|
|
18482
18716
|
if (opts.json) return void console.log(JSON.stringify(data, null, 2));
|
|
18483
18717
|
const b = data.before, a = data.after, d = data.delta;
|
|
18484
|
-
console.log(
|
|
18485
|
-
Before vs after: `) +
|
|
18486
|
-
console.log(
|
|
18718
|
+
console.log(import_chalk72.default.bold(`
|
|
18719
|
+
Before vs after: `) + import_chalk72.default.cyan(data.marker.label));
|
|
18720
|
+
console.log(import_chalk72.default.dim(` marked ${new Date(data.marker.ts).toISOString()} \xB7 ${data.window_hours}h either side
|
|
18487
18721
|
`));
|
|
18488
18722
|
const row = (name, before, after, delta) => {
|
|
18489
18723
|
const arrow = delta === 0 ? "=" : delta < 0 ? "\u2193" : "\u2191";
|
|
18490
18724
|
const txt = `${String(before).padStart(6)}ms \u2192${String(after).padStart(7)}ms ${arrow}${Math.abs(delta)}ms`;
|
|
18491
|
-
console.log(` ${name.padEnd(22)}${data.trustworthy ? delta <= 0 ?
|
|
18725
|
+
console.log(` ${name.padEnd(22)}${data.trustworthy ? delta <= 0 ? import_chalk72.default.green(txt) : import_chalk72.default.red(txt) : import_chalk72.default.dim(txt)}`);
|
|
18492
18726
|
};
|
|
18493
|
-
console.log(
|
|
18727
|
+
console.log(import_chalk72.default.dim(" metric before after change"));
|
|
18494
18728
|
row("total p50", b.total_p50, a.total_p50, d.total_p50);
|
|
18495
18729
|
row("total p95", b.total_p95, a.total_p95, d.total_p95);
|
|
18496
18730
|
row("apiblaze overhead p50", b.gw_p50, a.gw_p50, d.gw_p50);
|
|
18497
18731
|
row("apiblaze overhead p95", b.gw_p95, a.gw_p95, d.gw_p95);
|
|
18498
|
-
console.log(
|
|
18732
|
+
console.log(import_chalk72.default.dim(`
|
|
18499
18733
|
requests: ${b.requests} before \xB7 ${a.requests} after`));
|
|
18500
18734
|
for (const w of data.warnings ?? []) {
|
|
18501
|
-
console.log((data.trustworthy ?
|
|
18735
|
+
console.log((data.trustworthy ? import_chalk72.default.dim : import_chalk72.default.yellow)(` ${data.trustworthy ? "\xB7" : "\u26A0"} ${w}`));
|
|
18502
18736
|
}
|
|
18503
18737
|
console.log("");
|
|
18504
18738
|
return;
|
|
@@ -18507,19 +18741,19 @@ Before vs after: `) + import_chalk71.default.cyan(data.marker.label));
|
|
|
18507
18741
|
const data = await opCall({ method: "GET", path: `/operator/latency/slow${q}`, summary: "slowest requests" });
|
|
18508
18742
|
if (opts.json) return void console.log(JSON.stringify(data, null, 2));
|
|
18509
18743
|
const rows2 = data?.rows ?? [];
|
|
18510
|
-
if (!rows2.length) return void console.log(
|
|
18511
|
-
console.log(
|
|
18744
|
+
if (!rows2.length) return void console.log(import_chalk72.default.dim("No requests over the threshold in that window."));
|
|
18745
|
+
console.log(import_chalk72.default.bold(`
|
|
18512
18746
|
Slowest requests \u2014 last ${data.window_hours}h, over ${data.min_ms}ms
|
|
18513
18747
|
`));
|
|
18514
|
-
console.log(
|
|
18748
|
+
console.log(import_chalk72.default.dim(" total ours theirs blame request id"));
|
|
18515
18749
|
for (const r of rows2.slice(0, 30)) {
|
|
18516
18750
|
console.log(
|
|
18517
|
-
` ${String(Math.round(r.duration_ms)).padStart(6)} ${String(Math.round(r.gateway_ms)).padStart(5)} ${String(Math.round(r.upstream_ttfb_ms)).padStart(6)} ${
|
|
18751
|
+
` ${String(Math.round(r.duration_ms)).padStart(6)} ${String(Math.round(r.gateway_ms)).padStart(5)} ${String(Math.round(r.upstream_ttfb_ms)).padStart(6)} ${import_chalk72.default.yellow(`${r.slow_gw_feature || "-"}\u2192${r.slow_dep || "-"}`.padEnd(20))} ${import_chalk72.default.dim(r.request_id || "")}`
|
|
18518
18752
|
);
|
|
18519
18753
|
}
|
|
18520
|
-
console.log(
|
|
18754
|
+
console.log(import_chalk72.default.dim(`
|
|
18521
18755
|
The last column is the request id (Cloudflare calls it a "cf-ray"). Look one up with`));
|
|
18522
|
-
console.log(
|
|
18756
|
+
console.log(import_chalk72.default.dim(` \`apiblaze logs\` for that request's exact per-feature breakdown \u2014 unsampled, unlike the table above.
|
|
18523
18757
|
`));
|
|
18524
18758
|
return;
|
|
18525
18759
|
}
|
|
@@ -18527,18 +18761,18 @@ Slowest requests \u2014 last ${data.window_hours}h, over ${data.min_ms}ms
|
|
|
18527
18761
|
const data = await opCall({ method: "GET", path: `/operator/latency/llm${q}`, summary: "llm latency" });
|
|
18528
18762
|
if (opts.json) return void console.log(JSON.stringify(data, null, 2));
|
|
18529
18763
|
const rows2 = data?.rows ?? [];
|
|
18530
|
-
if (!rows2.length) return void console.log(
|
|
18531
|
-
console.log(
|
|
18764
|
+
if (!rows2.length) return void console.log(import_chalk72.default.dim("No LLM traffic in that window."));
|
|
18765
|
+
console.log(import_chalk72.default.bold(`
|
|
18532
18766
|
LLM timing \u2014 last ${data.window_hours}h
|
|
18533
18767
|
`));
|
|
18534
|
-
console.log(
|
|
18768
|
+
console.log(import_chalk72.default.dim(" turns turn p95 ttfc p95 gen p95 reserve p95 in/out tokens p95 model"));
|
|
18535
18769
|
for (const r of rows2) {
|
|
18536
18770
|
const n = r.turns ?? r.requests ?? 0;
|
|
18537
18771
|
console.log(
|
|
18538
18772
|
` ${String(Math.round(n)).padStart(8)} ${String(Math.round(r.turn_p95_ms ?? 0)).padStart(6)}ms ${String(Math.round(r.ttfc_p95_ms ?? 0)).padStart(6)}ms ${String(Math.round(r.gen_p95_ms ?? 0)).padStart(6)}ms ${String(Math.round(r.reserve_p95_ms ?? 0)).padStart(9)}ms ${String(Math.round(r.input_tokens_p95 ?? 0)).padStart(6)}/${String(Math.round(r.output_tokens_p95 ?? 0)).padEnd(6)} ${r.model || "-"}`
|
|
18539
18773
|
);
|
|
18540
18774
|
}
|
|
18541
|
-
console.log(
|
|
18775
|
+
console.log(import_chalk72.default.dim(`
|
|
18542
18776
|
${data.note}
|
|
18543
18777
|
`));
|
|
18544
18778
|
return;
|
|
@@ -18549,26 +18783,26 @@ LLM timing \u2014 last ${data.window_hours}h
|
|
|
18549
18783
|
]);
|
|
18550
18784
|
if (opts.json) return void console.log(JSON.stringify({ blame, summary }, null, 2));
|
|
18551
18785
|
const ov = summary?.apiblaze_overhead_ms ?? {};
|
|
18552
|
-
console.log(
|
|
18786
|
+
console.log(import_chalk72.default.bold(`
|
|
18553
18787
|
Latency \u2014 last ${summary?.window_hours ?? "?"}h, ${Number(summary?.requests ?? 0).toLocaleString()} requests
|
|
18554
18788
|
`));
|
|
18555
|
-
console.log(` ${
|
|
18556
|
-
console.log(` ${
|
|
18557
|
-
console.log(
|
|
18789
|
+
console.log(` ${import_chalk72.default.bold("apiblaze overhead")} p50 ${String(ov.p50 ?? 0).padStart(5)}ms p95 ${String(ov.p95 ?? 0).padStart(6)}ms p99 ${String(ov.p99 ?? 0).padStart(6)}ms ${import_chalk72.default.dim("\u2190 ours")}`);
|
|
18790
|
+
console.log(` ${import_chalk72.default.bold("upstream ttfb ")} ${" ".repeat(24)}p95 ${String(summary?.upstream_ttfb_p95_ms ?? 0).padStart(6)}ms ${import_chalk72.default.dim("\u2190 theirs")}`);
|
|
18791
|
+
console.log(import_chalk72.default.dim(` (per-request percentiles \u2014 never subtract one from the other)
|
|
18558
18792
|
`));
|
|
18559
18793
|
const rows = blame?.blame ?? [];
|
|
18560
|
-
if (!rows.length) return void console.log(
|
|
18561
|
-
console.log(
|
|
18562
|
-
console.log(
|
|
18794
|
+
if (!rows.length) return void console.log(import_chalk72.default.dim("No latency rows in that window."));
|
|
18795
|
+
console.log(import_chalk72.default.bold(" Which feature ate the time, and what inside it\n"));
|
|
18796
|
+
console.log(import_chalk72.default.dim(" share p95 feature \u2192 dependency"));
|
|
18563
18797
|
for (const r of rows.slice(0, 15)) {
|
|
18564
18798
|
const share = `${(r.share * 100).toFixed(1)}%`;
|
|
18565
|
-
console.log(` ${share.padStart(6)} ${String(r.p95_ms).padStart(6)}ms ${
|
|
18799
|
+
console.log(` ${share.padStart(6)} ${String(r.p95_ms).padStart(6)}ms ${import_chalk72.default.yellow(r.feature)} ${import_chalk72.default.dim("\u2192")} ${import_chalk72.default.cyan(r.dep)}`);
|
|
18566
18800
|
}
|
|
18567
18801
|
console.log("");
|
|
18568
18802
|
return;
|
|
18569
18803
|
}
|
|
18570
18804
|
default:
|
|
18571
|
-
console.log(
|
|
18805
|
+
console.log(import_chalk72.default.red(`Unknown op subcommand '${sub}'. Run \`apiblaze op\` for the menu.`));
|
|
18572
18806
|
}
|
|
18573
18807
|
}
|
|
18574
18808
|
function printDistance(data) {
|
|
@@ -18576,17 +18810,17 @@ function printDistance(data) {
|
|
|
18576
18810
|
const p = (v) => `${n(v).toFixed(1)}%`;
|
|
18577
18811
|
const msf = (v) => `${Math.round(n(v))}ms`;
|
|
18578
18812
|
const thr = n(data?.threshold_ms) || 50;
|
|
18579
|
-
const
|
|
18580
|
-
console.log(
|
|
18581
|
-
Durable Object distance \u2014 last ${data?.window_hours ?? "?"}h${
|
|
18813
|
+
const scope2 = data?.team ? ` \xB7 team ${data.team}` : "";
|
|
18814
|
+
console.log(import_chalk72.default.bold(`
|
|
18815
|
+
Durable Object distance \u2014 last ${data?.window_hours ?? "?"}h${scope2}, ${n(data?.total_requests).toLocaleString()} requests, ${n(data?.do_calls).toLocaleString()} DO calls
|
|
18582
18816
|
`));
|
|
18583
18817
|
console.log(
|
|
18584
|
-
` ${
|
|
18818
|
+
` ${import_chalk72.default.bold(p(data?.requests_with_do_call_pct))} of requests made a Durable Object call; ${import_chalk72.default.bold(p(data?.far_pct))} of those crossed colos; ${import_chalk72.default.bold((n(data?.problem_pct?.of_all_requests) >= 1 ? import_chalk72.default.red : import_chalk72.default.green)(p(data?.problem_pct?.of_all_requests)))} of all requests paid > ${thr} ms of distance.`
|
|
18585
18819
|
);
|
|
18586
|
-
console.log(
|
|
18820
|
+
console.log(import_chalk72.default.dim(` (${p(data?.problem_pct?.of_do_calls)} of DO calls over ${thr} ms network \xB7 entry or object colo unknown on ${p(data?.unknown_pct)} of calls)
|
|
18587
18821
|
`));
|
|
18588
18822
|
const nm = data?.network_ms ?? {};
|
|
18589
|
-
console.log(
|
|
18823
|
+
console.log(import_chalk72.default.dim(" network ms calls p50 p95 p99 >thr"));
|
|
18590
18824
|
const line = (label3, r) => {
|
|
18591
18825
|
if (!r) return;
|
|
18592
18826
|
console.log(
|
|
@@ -18600,32 +18834,32 @@ Durable Object distance \u2014 last ${data?.window_hours ?? "?"}h${scope}, ${n(d
|
|
|
18600
18834
|
line("kind: throttle", nm.throttle);
|
|
18601
18835
|
const modes = data?.by_mode ?? [];
|
|
18602
18836
|
if (modes.length) {
|
|
18603
|
-
console.log(
|
|
18837
|
+
console.log(import_chalk72.default.dim("\n mode calls share p95 >thr"));
|
|
18604
18838
|
for (const m of modes) {
|
|
18605
18839
|
console.log(` ${String(m.mode).padEnd(14)} ${String(n(m.calls).toLocaleString()).padStart(9)} ${p(m.share_pct).padStart(6)} ${msf(m.p95_ms).padStart(7)} ${String(n(m.problem_calls).toLocaleString()).padStart(5)}`);
|
|
18606
18840
|
}
|
|
18607
18841
|
}
|
|
18608
18842
|
const routes = data?.routes ?? [];
|
|
18609
18843
|
if (routes.length) {
|
|
18610
|
-
console.log(
|
|
18611
|
-
console.log(
|
|
18844
|
+
console.log(import_chalk72.default.bold("\n Routes (worker colo \u2192 object colo)") + import_chalk72.default.dim(` added ms = network above the same-colo p50 (${msf(data?.same_colo_p50_baseline_ms)}), summed`));
|
|
18845
|
+
console.log(import_chalk72.default.dim(" route calls p50 p95 added ms"));
|
|
18612
18846
|
for (const r of routes) {
|
|
18613
18847
|
const cross = r.worker_colo !== r.object_colo;
|
|
18614
18848
|
const route = `${r.worker_colo} \u2192 ${r.object_colo}`;
|
|
18615
|
-
console.log(` ${(cross ?
|
|
18849
|
+
console.log(` ${(cross ? import_chalk72.default.yellow : import_chalk72.default.dim)(route.padEnd(16))} ${String(n(r.calls).toLocaleString()).padStart(8)} ${msf(r.p50_ms).padStart(7)} ${msf(r.p95_ms).padStart(7)} ${String(n(r.added_ms_total).toLocaleString()).padStart(12)}`);
|
|
18616
18850
|
}
|
|
18617
18851
|
}
|
|
18618
18852
|
const teams = data?.teams ?? [];
|
|
18619
18853
|
if (teams.length) {
|
|
18620
|
-
console.log(
|
|
18854
|
+
console.log(import_chalk72.default.bold(`
|
|
18621
18855
|
Teams paying the most distance (> ${thr} ms)`));
|
|
18622
|
-
console.log(
|
|
18856
|
+
console.log(import_chalk72.default.dim(" >thr calls of calls object colo worker colos team"));
|
|
18623
18857
|
for (const t of teams) {
|
|
18624
18858
|
const w = (t.top_worker_colos ?? []).map((x) => `${x.colo} ${n(x.problem_calls).toLocaleString()}`).join(", ");
|
|
18625
|
-
console.log(` ${String(n(t.problem_calls).toLocaleString()).padStart(10)} ${p(t.problem_pct).padStart(8)} ${String(t.dominant_object_colo).padEnd(11)} ${w.padEnd(24)} ${
|
|
18859
|
+
console.log(` ${String(n(t.problem_calls).toLocaleString()).padStart(10)} ${p(t.problem_pct).padStart(8)} ${String(t.dominant_object_colo).padEnd(11)} ${w.padEnd(24)} ${import_chalk72.default.dim(t.team)}`);
|
|
18626
18860
|
}
|
|
18627
18861
|
} else if (!n(data?.do_calls)) {
|
|
18628
|
-
console.log(
|
|
18862
|
+
console.log(import_chalk72.default.dim("\n No Durable Object calls in that window (warm leases write no gate rows)."));
|
|
18629
18863
|
}
|
|
18630
18864
|
console.log("");
|
|
18631
18865
|
}
|
|
@@ -18691,7 +18925,7 @@ agent.command("authz").description("Chat to design and turn on access rules for
|
|
|
18691
18925
|
program.command("rule").description("Lock resources to the person who created them \u2014 or describe any access rule in plain English (that one is billed per turn)").argument("<project>", "Project name or id").argument("[sentence]", 'Optional rule in plain English, e.g. "users see only their own rows". Omit to pick from what the spec can protect.').option("--enforce", "Turn enforcement on right after saving (default: save in shadow mode, then ask)").option("--apiversion <version>", "API version (defaults to the project's)").action(action((project, sentence, opts) => runRule(project, sentence, opts)));
|
|
18692
18926
|
agent.command("openapi").description("Chat to build your API spec from real traffic").argument("<project>", "Project name or id").argument("[apiVersion]", "API version (defaults to the project's)").action(action((project, apiVersion) => runOpenapi(project, apiVersion)));
|
|
18693
18927
|
agent.command("mcp").description("Chat to build an MCP server for an API").argument("<project>", "Project name or id").argument("[apiVersion]", "API version (defaults to the project's)").option("--environment <env>", "Environment to publish (default: prod)").action(action((project, apiVersion, opts) => runMcp(project, apiVersion, opts)));
|
|
18694
|
-
var mcpCmd = program.command("mcp").description("Optional tweaks to a proxy's MCP server: prompts, per-tool behavior (read-only / destructive / confirm / always-async), UI resource");
|
|
18928
|
+
var mcpCmd = program.command("mcp").description("Optional tweaks to a proxy's MCP server: prompts, per-tool behavior (title / read-only / destructive / confirm / always-async), UI resource");
|
|
18695
18929
|
var mcpCommon = (c) => c.option("--apiversion <version>", "API version (defaults to the project's)").option("--environment <env>", "Catalogue environment (default: prod)").option("--json", "Output machine-readable JSON");
|
|
18696
18930
|
var mcpPrompts = mcpCmd.command("prompts").description("Prompts the MCP server offers (drafted by the MCP agent; editable here)");
|
|
18697
18931
|
mcpCommon(mcpPrompts.command("list").description("List the prompts").argument("<project>", "Project name or id")).action(action((project, opts) => runMcpPrompts("list", project, void 0, opts)));
|
|
@@ -18700,7 +18934,7 @@ mcpCommon(mcpPrompts.command("set").description("Add or replace a prompt from a
|
|
|
18700
18934
|
mcpCommon(mcpPrompts.command("rm").description("Remove a prompt").argument("<project>", "Project name or id").argument("<name>", "Prompt name")).action(action((project, name, opts) => runMcpPrompts("rm", project, name, opts)));
|
|
18701
18935
|
var mcpOps = mcpCmd.command("ops").description("Per-tool behavior. Default from the HTTP method: GET/HEAD/OPTIONS read-only, PUT/DELETE idempotent, DELETE destructive + asks to confirm");
|
|
18702
18936
|
mcpCommon(mcpOps.command("list").description("Every tool with its effective behavior (overrides marked)").argument("<project>", "Project name or id")).action(action((project, opts) => runMcpOps("list", project, void 0, opts)));
|
|
18703
|
-
mcpCommon(mcpOps.command("set").description("Override one tool").argument("<project>", "Project name or id").argument("<tool>", "Tool name").option("--read-only", "Safe to call, changes nothing (e.g. a POST search)").option("--no-read-only", "Not read-only").option("--destructive", "Changes or deletes data (e.g. a GET with side effects)").option("--no-destructive", "Not destructive").option("--idempotent", "Repeating it has no extra effect").option("--no-idempotent", "Not idempotent").option("--confirm", "Ask the user to confirm before running").option("--no-confirm", "Never ask to confirm").option("--always-async", "Return a task handle immediately (long jobs)").option("--no-always-async", "Wait for the result (default)")).action(action((project, tool, opts) => runMcpOps("set", project, tool, opts)));
|
|
18937
|
+
mcpCommon(mcpOps.command("set").description("Override one tool").argument("<project>", "Project name or id").argument("<tool>", "Tool name").option("--title <text>", "Human label clients and directories show (default: the OpenAPI summary, else the name spelled out)").option("--read-only", "Safe to call, changes nothing (e.g. a POST search)").option("--no-read-only", "Not read-only").option("--destructive", "Changes or deletes data (e.g. a GET with side effects)").option("--no-destructive", "Not destructive").option("--idempotent", "Repeating it has no extra effect").option("--no-idempotent", "Not idempotent").option("--confirm", "Ask the user to confirm before running").option("--no-confirm", "Never ask to confirm").option("--always-async", "Return a task handle immediately (long jobs)").option("--no-always-async", "Wait for the result (default)")).action(action((project, tool, opts) => runMcpOps("set", project, tool, opts)));
|
|
18704
18938
|
mcpCommon(mcpOps.command("reset").description("Back to the automatic default for one tool").argument("<project>", "Project name or id").argument("<tool>", "Tool name")).action(action((project, tool, opts) => runMcpOps("reset", project, tool, opts)));
|
|
18705
18939
|
mcpCommon(mcpCmd.command("ui").description("Print a tool's MCP Apps UI resource (ui://{project}/{tool}) \u2014 the automatic viewer").argument("<project>", "Project name or id").argument("<tool>", "Tool name")).action(action((project, tool, opts) => runMcpUi(project, tool, opts)));
|
|
18706
18940
|
program.command("apichat [project]").alias("chat").description("Turn any API into a chat: point at an OpenAPI spec \u2014 or chat an EXISTING proxy by name (no login needed)").option("--target <url|file>", "What to chat with \u2014 pass ANY of: a target server base URL (spec auto-discovered at /openapi.json etc.), a local OpenAPI file (./openapi.yaml), or a remote OpenAPI URL (https://acme.com/openapi.yaml)").addOption(new import_commander.Option("--openapi <file|url>", "Deprecated alias \u2014 --target now detects spec files/URLs itself").hideHelp()).addOption(new import_commander.Option("--openapispec <file|url>", "Deprecated alias for --openapi").hideHelp()).option("--name <name>", "Proxy name (defaults to the target host)").option("--apiversion <version>", "API version to create (e.g. 1.0.0)").option("--environment <env>", "Environment to chat against (default: prod anonymous / dev logged-in)").option("--access <mode>", 'Who can call this API once connected (e.g. via Claude): "open" = anyone who signs in, "invite" = only you + emails you pre-approve. Default: invite when logged in, open when anonymous.').option("--target-auth-env <ENV_VAR>", "Read the upstream credential from this env var (CI-safe; required when there is no TTY and the API needs auth)").option("--force", "Proceed even if the API uses oauth2/openIdConnect target auth (you configure target auth yourself later)").option("-y, --yes", "Skip confirmation prompts").option("--tenant <slug>", "Tenant (consumer namespace: portal, login, users) for the new proxy; omit to be asked").option("--apikey <key>", "Use this API key for the proxy's door (api_key proxies). Without it, apichat detects the door and asks \u2014 or runs the consumer login for OAuth doors.").option("--xenduserid <id>", "Assert this end-user id (X-End-User-Id) \u2014 required by proxies with identified/pre-approved enforcement; you are asked for one when the proxy demands it.").option("--verbose", "Show the per-turn proxy curl trace (hidden by default)").option("-p, --prompt <question>", "One-shot question: answered through the external agent CLI after an MCP install, or by apichat itself (exits after answering when there is no TTY)").option("--install-mcp <cli>", "Install this proxy's MCP into an external agent CLI without asking: claude | codex. Also re-offers after an earlier decline.").option("--remove-mcp <cli>", "Disconnect this proxy from an external agent CLI (claude | codex): removes the MCP server and the standing instruction").action(action((project, opts) => {
|
|
@@ -18724,7 +18958,7 @@ program.command("dev").description(`Put the app running on THIS machine on the i
|
|
|
18724
18958
|
try {
|
|
18725
18959
|
const resolved = parseInt(port ?? opts.port, 10);
|
|
18726
18960
|
if (Number.isNaN(resolved)) {
|
|
18727
|
-
console.error(
|
|
18961
|
+
console.error(import_chalk73.default.red(`Invalid port: ${port ?? opts.port}`));
|
|
18728
18962
|
process.exit(1);
|
|
18729
18963
|
}
|
|
18730
18964
|
await runDev({ ...opts, port: resolved });
|
|
@@ -18836,6 +19070,18 @@ program.command("preapprove").description("Allow an email or company domain to s
|
|
|
18836
19070
|
var tenant = program.command("tenant").description("Manage tenants \u2014 bare command opens the interactive picker (settings, OAuth sign-in providers, app clients)").action(action(() => runTenantManage(void 0, {})));
|
|
18837
19071
|
tenant.command("manage").description("Browse & edit one tenant: settings, OAuth sign-in providers (swap or add one \u2014 this is how you change sign-in after create), login app clients, issuers (search-first picker)").argument("[query]", "Search by tenant name/display name (omit to use your tenant scope or pick)").option("--tenant <slug>", "Exact tenant slug (skips the picker)").option("--team <id|name>", "Team (defaults to active team)").action(action((query, opts) => runTenantManage(query, opts)));
|
|
18838
19072
|
tenant.command("use").description("Set the sticky tenant scope for future commands (search-first; --clear to unset)").argument("[query]", "Search by tenant name/display name").option("--clear", "Clear the tenant scope").option("--team <id|name>", "Team (defaults to active team)").action(action((query, opts) => runTenantUse(query, opts)));
|
|
19073
|
+
var tenantPassword = tenant.command("password").description("APIblaze-hosted username & password sign-in on a tenant (reviewer / test accounts; advanced)");
|
|
19074
|
+
var tenantArg = (cmd) => cmd.argument("[tenant]", "Tenant (defaults to your tenant scope, else a picker)").option("--tenant <slug>", "Same as the positional").option("--json", "Output machine-readable JSON");
|
|
19075
|
+
tenantArg(tenantPassword.command("enable").description("Add username & password sign-in to the tenant's login (idempotent)")).action(action((t, opts) => runTenantPasswordEnable(t, opts)));
|
|
19076
|
+
tenantArg(tenantPassword.command("disable").description("Remove username & password sign-in from the tenant (deletes its accounts)")).option("-y, --yes", "Skip the confirmation").action(action((t, opts) => runTenantPasswordDisable(t, opts)));
|
|
19077
|
+
tenantArg(tenantPassword.command("list").description("List the accounts")).action(action((t, opts) => runTenantPasswordList(t, opts)));
|
|
19078
|
+
var accountCmd = (name, desc) => tenantArg(tenantPassword.command(name).description(desc).argument("<username>", "3\u201364 chars: a\u2013z 0\u20139 . _ -"));
|
|
19079
|
+
accountCmd("add", "Create an account; the password is generated and printed once (or read from stdin with --password-stdin)").option("--email <email>", "Optional: lets a pre-provisioned user/admin with this address adopt the login").option("--display-name <name>", "Display name").option("--purpose <user|reviewer>", "`reviewer` marks a directory test account").option("--password-stdin", "Read the password from stdin instead of generating one").action(action((username, t, opts) => runTenantPasswordAdd(username, t, opts)));
|
|
19080
|
+
accountCmd("rotate", "New password (generated, printed once; or --password-stdin); existing sessions end at their next refresh").option("--password-stdin", "Read the new password from stdin").action(action((username, t, opts) => runTenantPasswordRotate(username, t, opts)));
|
|
19081
|
+
accountCmd("block", "Refuse sign-in for an account (keeps it)").action(action((username, t, opts) => runTenantPasswordSetBlocked(username, t, true, opts)));
|
|
19082
|
+
accountCmd("unblock", "Allow sign-in again").action(action((username, t, opts) => runTenantPasswordSetBlocked(username, t, false, opts)));
|
|
19083
|
+
accountCmd("unlock", "Clear the lockout after repeated failed sign-ins").action(action((username, t, opts) => runTenantPasswordUnlock(username, t, opts)));
|
|
19084
|
+
accountCmd("remove", "Delete an account").option("-y, --yes", "Skip the confirmation").action(action((username, t, opts) => runTenantPasswordRemove(username, t, opts)));
|
|
18839
19085
|
tenant.command("list").description("List tenants in your team (--q searches server-side)").option("--q <search>", "Filter by tenant name or display name").option("--limit <n>", "Page size (max 200)").option("--team <id|name>", "Team (defaults to active team)").option("--json", "Output machine-readable JSON").action(action((opts) => runTenantList(opts)));
|
|
18840
19086
|
tenant.command("create").description("Create a tenant in your team (tenant names are lowercase alphanumeric and globally unique)").requiredOption("--name <name>", "Tenant name \u2014 becomes the slug ({name}.portal.apiblaze.com) unless --slug overrides; also the display label").option("--slug <tenant_name>", "Explicit tenant slug when it should differ from --name").option("--team <id|name>", "Team (defaults to active team)").option("--json", "Output machine-readable JSON").action(action((opts) => runTenantCreate(opts)));
|
|
18841
19087
|
tenant.command("attach").description("Attach a tenant to a proxy").argument("<project>", "Project name or id").requiredOption("--tenant <slug>", "Tenant slug to attach").option("--auth-config <id>", "Auth config id to bind").option("--no-login", "Do not give the tenant a dev-portal sign-in (by default it gets the zero-config GitHub one, unless it already has one)").option("--team <id|name>", "Team the project is in").option("--apiversion <version>", "API version").option("--json", "Output machine-readable JSON").action(action((project, opts) => runTenantAttach(project, opts)));
|
|
@@ -18894,7 +19140,7 @@ function groupedCommandHelp() {
|
|
|
18894
19140
|
const sub = byName.get(e.parent)?.commands.find((s) => s.name() === e.sub);
|
|
18895
19141
|
return sub ? ` ${helpLabel(e).padEnd(width)}${sub.description()}` : "";
|
|
18896
19142
|
}).filter(Boolean).join("\n");
|
|
18897
|
-
return `${
|
|
19143
|
+
return `${import_chalk73.default.bold(g.title)}
|
|
18898
19144
|
${rows}`;
|
|
18899
19145
|
}).join("\n\n");
|
|
18900
19146
|
}
|
|
@@ -18907,7 +19153,7 @@ program.configureHelp({
|
|
|
18907
19153
|
program.addHelpText("before", () => isBetaCached() ? `${betaNotice()}
|
|
18908
19154
|
` : "");
|
|
18909
19155
|
program.addHelpText("after", () => `
|
|
18910
|
-
${
|
|
19156
|
+
${import_chalk73.default.bold("Don't like reading docs?")} Run ${import_chalk73.default.bgCyan.black.bold(` npx ${B.cli} docs `)} ${import_chalk73.default.cyan(`\u2014 ask an AI chatbot about ${B.product}, and let it act on your account.`)}
|
|
18911
19157
|
|
|
18912
19158
|
${groupedCommandHelp()}
|
|
18913
19159
|
|
|
@@ -18944,14 +19190,14 @@ async function recoverStaleTeam() {
|
|
|
18944
19190
|
const { resolveLinkedTeam: resolveLinkedTeam2 } = await Promise.resolve().then(() => (init_team(), team_exports));
|
|
18945
19191
|
const linked = await resolveLinkedTeam2({ preferredId: creds.teamId, interactive: !!process.stdin.isTTY });
|
|
18946
19192
|
if (!linked) {
|
|
18947
|
-
console.error(
|
|
19193
|
+
console.error(import_chalk73.default.yellow("Your account has no teams anymore (deleted?). Run `apiblaze login` or `apiblaze create` to get a workspace."));
|
|
18948
19194
|
return;
|
|
18949
19195
|
}
|
|
18950
19196
|
if (linked.teamId === creds.teamId) return;
|
|
18951
19197
|
const next = { ...creds, teamId: linked.teamId, teamName: linked.teamName };
|
|
18952
19198
|
delete next.activeTenant;
|
|
18953
19199
|
saveCredentials(next);
|
|
18954
|
-
console.error(
|
|
19200
|
+
console.error(import_chalk73.default.yellow(`Your previous team no longer exists \u2014 relinked to ${import_chalk73.default.bold(linked.teamName ?? linked.teamId)}. Re-run your command.`));
|
|
18955
19201
|
} catch {
|
|
18956
19202
|
}
|
|
18957
19203
|
}
|
|
@@ -18963,28 +19209,28 @@ async function printError(err) {
|
|
|
18963
19209
|
if (err instanceof ApiError) {
|
|
18964
19210
|
const data = err.body;
|
|
18965
19211
|
const extra = [data?.body?.reason, data?.body?.details, data?.details, data?.body?.error].find((x) => typeof x === "string" && x && x !== err.message);
|
|
18966
|
-
console.error(
|
|
19212
|
+
console.error(import_chalk73.default.red(`
|
|
18967
19213
|
API error (${err.status}): ${err.message}${extra ? ` \u2014 ${extra}` : ""}`));
|
|
18968
19214
|
if (err.status === 403 || err.status === 404) {
|
|
18969
19215
|
await recoverStaleTeam();
|
|
18970
19216
|
}
|
|
18971
19217
|
} else if (err instanceof Error) {
|
|
18972
|
-
console.error(
|
|
19218
|
+
console.error(import_chalk73.default.red(`
|
|
18973
19219
|
Error: ${err.message}`));
|
|
18974
19220
|
} else {
|
|
18975
|
-
console.error(
|
|
19221
|
+
console.error(import_chalk73.default.red("\nUnknown error"));
|
|
18976
19222
|
}
|
|
18977
19223
|
}
|
|
18978
19224
|
function hintStaleCacheOnUnknownOption(cmd) {
|
|
18979
19225
|
cmd.exitOverride((err) => {
|
|
18980
19226
|
if (err.code === "commander.unknownOption") {
|
|
18981
|
-
console.error(
|
|
19227
|
+
console.error(import_chalk73.default.yellow(
|
|
18982
19228
|
`
|
|
18983
19229
|
This is ${B.cli} v${version}. If that option is documented but this build
|
|
18984
19230
|
does not have it, your npx cache is serving an older copy \u2014 re-run as:
|
|
18985
19231
|
`
|
|
18986
19232
|
));
|
|
18987
|
-
console.error(
|
|
19233
|
+
console.error(import_chalk73.default.cyan(` npx ${B.cli}@latest ${process.argv.slice(2).join(" ")}
|
|
18988
19234
|
`));
|
|
18989
19235
|
}
|
|
18990
19236
|
process.exit(err.exitCode);
|