apiblaze 0.21.44 → 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 +671 -525
- 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) {
|
|
@@ -8923,7 +8923,7 @@ async function claimJourney(work, checks, env) {
|
|
|
8923
8923
|
if (res.claimed) {
|
|
8924
8924
|
try {
|
|
8925
8925
|
await admin({ method: "DELETE", path: `/teams/${encodeURIComponent(teamId)}`, summary: "delete the promoted demo team" });
|
|
8926
|
-
for (let i = 0; i <
|
|
8926
|
+
for (let i = 0; i < 180 && !gone; i++) {
|
|
8927
8927
|
try {
|
|
8928
8928
|
await admin({ method: "GET", path: `/teams/${encodeURIComponent(teamId)}` });
|
|
8929
8929
|
} catch (e) {
|
|
@@ -8931,7 +8931,7 @@ async function claimJourney(work, checks, env) {
|
|
|
8931
8931
|
}
|
|
8932
8932
|
if (!gone) await new Promise((r) => setTimeout(r, 1e3));
|
|
8933
8933
|
}
|
|
8934
|
-
detail = gone ? "cascade done" : "still present after
|
|
8934
|
+
detail = gone ? "cascade done" : "still present after 180 s";
|
|
8935
8935
|
} catch (e) {
|
|
8936
8936
|
detail = `delete \u2192 ${fail7(e).status} ${fail7(e).error}`;
|
|
8937
8937
|
}
|
|
@@ -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();
|
|
@@ -9108,13 +9108,26 @@ async function defaultName(openapi, teamId) {
|
|
|
9108
9108
|
}
|
|
9109
9109
|
return randomProxyName();
|
|
9110
9110
|
}
|
|
9111
|
+
function saltedName(name) {
|
|
9112
|
+
return `${name}${Math.random().toString(36).slice(2, 6)}`;
|
|
9113
|
+
}
|
|
9111
9114
|
async function chooseName(opts, openapi, teamId, interactive2) {
|
|
9112
9115
|
if (opts.name !== void 0) {
|
|
9113
9116
|
const name = normalizeName2(opts.name);
|
|
9114
9117
|
if (name.length < 3) throw new Error(`${cap(B.thingShort)} name must be at least 3 characters (letters and digits only).`);
|
|
9115
9118
|
const check = await checkProxyName(name, teamId).catch(() => null);
|
|
9116
9119
|
if (check && (!check.canUseProjectName || !check.canUseApiVersion)) {
|
|
9117
|
-
|
|
9120
|
+
if (opts.auto || !interactive2) {
|
|
9121
|
+
for (let attempt = 0; attempt < 4; attempt++) {
|
|
9122
|
+
const candidate = saltedName(name);
|
|
9123
|
+
const again = await checkProxyName(candidate, teamId).catch(() => null);
|
|
9124
|
+
if (!again || again.canUseProjectName && again.canUseApiVersion) {
|
|
9125
|
+
console.log(import_chalk20.default.dim(` "${name}" is taken (names are global) \u2014 using "${candidate}" instead.`));
|
|
9126
|
+
return candidate;
|
|
9127
|
+
}
|
|
9128
|
+
}
|
|
9129
|
+
}
|
|
9130
|
+
throw new Error(`${cap(B.thingShort)} name "${name}" is not available${check.message ? ` \u2014 ${check.message}` : ""}. Omit --name and a free one is chosen for you.`);
|
|
9118
9131
|
}
|
|
9119
9132
|
return name;
|
|
9120
9133
|
}
|
|
@@ -9972,7 +9985,6 @@ their own app).
|
|
|
9972
9985
|
}
|
|
9973
9986
|
function shipMarkdown() {
|
|
9974
9987
|
const legacy = legacySkillMarkdown();
|
|
9975
|
-
const at = legacy.indexOf("## Adopt");
|
|
9976
9988
|
const backendRules = `## What their backend does \u2014 and nothing else
|
|
9977
9989
|
|
|
9978
9990
|
The gateway does sign-in, keys, tenancy and single-record ownership. The backend does only:
|
|
@@ -9993,7 +10005,7 @@ No code yet? Write the smallest plain backend that does exactly that, plus its \
|
|
|
9993
10005
|
return withLatest(`# Put ${B.product} in front of their app
|
|
9994
10006
|
|
|
9995
10007
|
${backendRules}${blockedMarkdown()}
|
|
9996
|
-
${
|
|
10008
|
+
${legacy}`);
|
|
9997
10009
|
}
|
|
9998
10010
|
function skillMarkdown() {
|
|
9999
10011
|
return `${heroMarkdown()}
|
|
@@ -10003,121 +10015,7 @@ function legacySkillMarkdown() {
|
|
|
10003
10015
|
const P = B.product;
|
|
10004
10016
|
const cli = B.cli;
|
|
10005
10017
|
const LIB = "apiblaze";
|
|
10006
|
-
|
|
10007
|
-
const doors = BRAND === "mcpblaze" ? `${P} ships your code to agents (an MCP server); ${other} ships the same code to users (API keys + sign-in).` : `${P} ships your code to users (API keys + sign-in); ${other} ships the same code to agents (an MCP server).`;
|
|
10008
|
-
return `---
|
|
10009
|
-
name: ${SKILL_NAME}
|
|
10010
|
-
description: Use when the user asks what ${P} (or ${cli}) does or can do for them; says yes to a demo or asks to see / try / demo it; wants to ship, share, expose or make an API public; says "serve my backend behind a proxy and MCP"; wants auth, API keys, sign-in, multi-tenant registration, users and groups, rate limits (throttling), quotas or spend caps; wants agents to use the API through an MCP; or says users can reach each other's records or admin functions. Puts the ${P} gateway in front of the API instead of hand-rolling auth and tenancy in the backend.
|
|
10011
|
-
---
|
|
10012
|
-
|
|
10013
|
-
# Ship safely with ${P}
|
|
10014
|
-
|
|
10015
|
-
One gateway, two doors. ${doors}
|
|
10016
|
-
Same rules, one command. Nothing about auth, tenancy or ownership goes into the backend's
|
|
10017
|
-
business logic: the gateway sits in front of the code and enforces it.
|
|
10018
|
-
|
|
10019
|
-
## Which mode \u2014 decide this first
|
|
10020
|
-
|
|
10021
|
-
- They ask what ${P} does or can do \u2192 **Explain**. Glance at the workspace first (see
|
|
10022
|
-
Explain), answer, then offer either the demo or their own app. Run no \`npx ${cli}\`
|
|
10023
|
-
command \u2014 the whole answer is in this file.
|
|
10024
|
-
- They say yes to the demo, or ask to see / try / demo it \u2192 **Demo app**. It lives in its own
|
|
10025
|
-
folder and never touches their code.
|
|
10026
|
-
- They say yes to "can I read the code" \u2192 **Adopt**: scan their app, ask only what the scan
|
|
10027
|
-
could not find, then ship it through the Ship steps.
|
|
10028
|
-
- They have a backend, just wrote one, or ask you to BUILD one and then ship it \u2192 **Ship**.
|
|
10029
|
-
No code yet? First write the smallest plain backend that serves the resources they named,
|
|
10030
|
-
plus its \`openapi.yaml\`, start it on their port, then continue at Ship \xA71. Do NOT build
|
|
10031
|
-
the Demo app for this. The gateway does auth, tenancy and item ownership; the backend does
|
|
10032
|
-
only these three things:
|
|
10033
|
-
- stamp each new row's \`tenant\` with the \`x-abz-tenant-id\` header and its \`owner\` with
|
|
10034
|
-
the \`x-abz-user-id\` header;
|
|
10035
|
-
- filter every collection GET to rows whose \`tenant\` AND \`owner\` match those headers (the
|
|
10036
|
-
gateway's rules lock single records, never lists \u2014 a list route has no one record to
|
|
10037
|
-
check). Both, always: a user id is not unique across tenants ("ana" at one customer and
|
|
10038
|
-
"ana" at another are two people);
|
|
10039
|
-
- answer 401 to any call whose \`x-target-api-key\` is not \`process.env.TARGET_SERVER_SECRET\`
|
|
10040
|
-
(generate it with \`openssl rand -hex 24\`, put it in \`.env.local\`, start the backend with
|
|
10041
|
-
it; Ship \xA73b makes the proxy send it). If they say "use
|
|
10042
|
-
${cli} on this" or "ship it with ${cli}" and point at a backend, skip any pitch and run it.
|
|
10043
|
-
|
|
10044
|
-
Tie-breaks: a question ("what can it do", "what is it") is always **Explain**, whatever else the
|
|
10045
|
-
message names. Mentioning \`npx ${cli} skills\` is NOT a request to run anything \u2014 it is the
|
|
10046
|
-
command that installed this file. If a message fits two modes, the earliest bullet wins.
|
|
10047
|
-
|
|
10048
|
-
## Explain
|
|
10049
|
-
|
|
10050
|
-
Say this, in these words or close to them, then stop:
|
|
10051
|
-
|
|
10052
|
-
"${P} puts a gateway in front of your backend in one command: a proxy (every request passes
|
|
10053
|
-
through it first) and an MCP server (the same API, callable by AI agents like Claude). No auth
|
|
10054
|
-
or ownership logic goes into your code \u2014 your backend only has to trust one header. Each of
|
|
10055
|
-
your customers \u2014 a tenant \u2014 gets their own API key, created from a widget you drop into your
|
|
10056
|
-
site. People and agents sign in with hosted GitHub, or your own OAuth app. Each user can only
|
|
10057
|
-
touch the records they created; those rules come from your OpenAPI spec (the file listing your
|
|
10058
|
-
endpoints), and if you have none I draft one from your routes and show it to you first. One
|
|
10059
|
-
more command sets a rate limit, a daily quota and a spend cap. In dev it tunnels to localhost;
|
|
10060
|
-
in prod it points at your deploy."
|
|
10061
|
-
|
|
10062
|
-
Before answering, look at the workspace \u2014 file names and \`package.json\` only, no reading of
|
|
10063
|
-
code yet. It "has an app" if ANY of these is present: an \`openapi.*\` / \`swagger.*\` file; a
|
|
10064
|
-
server framework (express, fastify, hono, koa, nest, fastapi, flask, django, rails, gin, chi,
|
|
10065
|
-
net/http); or a frontend (next, react, vue, svelte, remix, nuxt) whose \`.env*\` file NAMES an
|
|
10066
|
-
API base-URL variable (\`API_URL\`, \`NEXT_PUBLIC_API_URL\`, \`VITE_API_URL\`, or similar) \u2014
|
|
10067
|
-
\`grep -l\` the env files, open nothing else. Then end with exactly one of these two lines:
|
|
10068
|
-
|
|
10069
|
-
- No app \u2192 **"Would you like me to spin up a demo app?"**
|
|
10070
|
-
- An app \u2192 **"I see you have an app already coded. Can I read the code to see how to ready it
|
|
10071
|
-
for human and agent traffic with ${P}?"**
|
|
10072
|
-
|
|
10073
|
-
## Demo app \u2014 the same reservation system, before and after
|
|
10074
|
-
|
|
10075
|
-
One Next.js app on port 3000. Two routes, same UI, same data: \`/before\` is how most apps
|
|
10076
|
-
ship; \`/\` is the same thing behind ${P}. Narrate each step in one line and show every
|
|
10077
|
-
\`npx ${cli}\` command \u2014 the commands are the product. Everything goes in \`${cli}-demo/\`.
|
|
10078
|
-
|
|
10079
|
-
1. **Scaffold.** \`npx create-next-app@latest ${cli}-demo\` (TypeScript, App Router). Then
|
|
10080
|
-
\`npm i hono ${LIB}\`.
|
|
10081
|
-
2. **The backend.** Hono mounted at \`app/api/[[...route]]/route.ts\` (\`import { handle } from
|
|
10082
|
-
'hono/vercel'\`). Three resources \u2014 restaurants, tables, reservations \u2014 in an in-memory Map,
|
|
10083
|
-
every row stamped with \`owner\` = the \`x-abz-user-id\` header (${P} sets it on every call it
|
|
10084
|
-
forwards). Every route requires \`X-Api-Secret: demo-secret\` OR an \`x-abz-user-id\` header \u2014
|
|
10085
|
-
the first is how /before gets in, the second is how the gateway gets in. Write
|
|
10086
|
-
\`openapi.yaml\` at the repo root: every path and method, and the \`id\` each POST returns \u2014
|
|
10087
|
-
the ownership rules are read from it.
|
|
10088
|
-
3. **/before.** \`app/before/page.tsx\`: list and create reservations through one server route,
|
|
10089
|
-
\`app/before/api/route.ts\`, which holds \`demo-secret\` and calls \`/api/...\` directly. Put the
|
|
10090
|
-
caption on the page: "Before \u2014 one shared secret, anyone holding it can do anything."
|
|
10091
|
-
4. **Put ${P} in front.** In the BACKGROUND, and leave it running \u2014 it is the tunnel:
|
|
10092
|
-
\`npx ${cli} dev --port 3000 --openapi ./openapi.yaml --name demo --auto\`
|
|
10093
|
-
It prints the API URL, the MCP URL, the tenant, and the bootstrap key ONCE. Write them to
|
|
10094
|
-
\`.env.local\` as \`APIBLAZE_URL\`, \`APIBLAZE_SERVER_KEY\`, \`APIBLAZE_TENANT\`. \`--auto\` locks
|
|
10095
|
-
all three resources to their creator \u2014 say so.
|
|
10096
|
-
5. **Users and groups.** Turn them on and make the group the demo is about:
|
|
10097
|
-
\`npx ${cli} iam demo on\`
|
|
10098
|
-
\`npx ${cli} group create reservationists --tenant <tenant>\`
|
|
10099
|
-
\`npx ${cli} group add-user <the demo user> reservationists --tenant <tenant>\`
|
|
10100
|
-
\`npx ${cli} admins add <the user's email> --tenant <tenant>\` (so the widget has an admin).
|
|
10101
|
-
Optional, billed per turn: \`npx ${cli} rule demo "only members of reservationists can create
|
|
10102
|
-
or change reservations"\`. If the user is logged out or their wallet has nothing available
|
|
10103
|
-
(\`npx ${cli} billing balance\`), keep the free creator-lock rules and say so \u2014 do not pretend
|
|
10104
|
-
the group rule applied.
|
|
10105
|
-
6. **/.** \`app/page.tsx\`: the same UI, plus a **user picker** (ana \xB7 ben \xB7 admin \u2014 a select, no
|
|
10106
|
-
real login; the demo is about authorization, not building sign-in). Its server route calls
|
|
10107
|
-
\`process.env.APIBLAZE_URL\` with \`X-API-Key: APIBLAZE_SERVER_KEY\` and \`X-End-User-Id: <the
|
|
10108
|
-
picked user>\`. Add the three widgets from \`${LIB}/react\`, each with its server route:
|
|
10109
|
-
- \`<ApiKeyWidget />\` + \`app/api/apiblaze/keys/route.ts\` (\`createApiblazeKeys\` from \`${LIB}/server\`)
|
|
10110
|
-
- \`<UsersGroupsWidget />\` + \`app/api/apiblaze/groups/route.ts\` (\`createApiblazeGroups\` \u2014
|
|
10111
|
-
the keys route with one word changed)
|
|
10112
|
-
- \`<ChatWidget />\` + \`app/api/apiblaze/chat/route.ts\` (\`createApiblazeChat\`)
|
|
10113
|
-
Exact snippets for the keys and chat pair: \`npx ${cli} integration demo --stack nextjs\`.
|
|
10114
|
-
Caption: "After \u2014 a key per tenant, every call names its user, and ${P} decides."
|
|
10115
|
-
7. **Prove it, in the browser.** \`npm run dev\`, open \`http://localhost:3000/before\` then
|
|
10116
|
-
\`http://localhost:3000/\`. On \`/\`: create a reservation as ana, switch to ben, try to cancel
|
|
10117
|
-
it \u2014 show the 403 in the UI. Open the groups widget: reservationists is there. Ask the chat
|
|
10118
|
-
widget to list reservations. Then say what each \`npx ${cli}\` command did, in order.
|
|
10119
|
-
|
|
10120
|
-
## Adopt \u2014 reading their code first
|
|
10018
|
+
return `## Adopt \u2014 reading their code first
|
|
10121
10019
|
|
|
10122
10020
|
They said yes. Scan properly now (routes, env files, auth, network calls), classify, then ask
|
|
10123
10021
|
ONLY for what the scan could not find \u2014 one short list of questions, not an interview.
|
|
@@ -10178,12 +10076,14 @@ ONLY for what the scan could not find \u2014 one short list of questions, not an
|
|
|
10178
10076
|
the report. Only ask "where do your users sign in today?" when they mention an existing
|
|
10179
10077
|
login. Their own OAuth app \u2192 \`--apikey --oauth '{"provider":"auth0","clientId":"\u2026","clientSecret":"\u2026"}'\`
|
|
10180
10078
|
(both doors: keys for tenants, their login for people and agents). Shapes below.
|
|
10181
|
-
3. **Local code** \u2192 \`npx ${cli} dev --port <port> --openapi ./openapi.yaml --
|
|
10079
|
+
3. **Local code** \u2192 \`npx ${cli} dev --port <port> --openapi ./openapi.yaml --auto <door flags>\`,
|
|
10182
10080
|
left running \u2014 it is the tunnel. Start it as its own detached process, never \`cmd &\` inside
|
|
10183
10081
|
a shell call that ends: \`nohup npx ${cli} dev \u2026 > ${cli}-dev.log 2>&1 < /dev/null &\`, then
|
|
10184
|
-
read the log.
|
|
10185
|
-
|
|
10186
|
-
|
|
10082
|
+
read the log. Leave \`--name\` out: names are global, so a free one is chosen and printed
|
|
10083
|
+
(\`--name\` only for a name the user asked for; a taken one is salted). If the tunnel stops,
|
|
10084
|
+
run the \`--project <name>\` line it printed: that reattaches and creates nothing. Never start
|
|
10085
|
+
a new name to get it back \u2014 that is a second proxy. **Deployed code** \u2192
|
|
10086
|
+
\`npx ${cli} create --target <url-or-spec> --auto <door flags>\`. Both print the
|
|
10187
10087
|
URL, the MCP URL (the "agents:" line) and what is locked. Run by you (no terminal), \`dev\`
|
|
10188
10088
|
saves the bootstrap key to \`.env.local\` as \`APIBLAZE_SERVER_KEY\` and never prints it;
|
|
10189
10089
|
\`create\` prints it ONCE \u2014 save it there yourself. Either way say only "saved your key to
|
|
@@ -11492,8 +11392,13 @@ function withOverride(cat, tool, flags) {
|
|
|
11492
11392
|
for (const k of ["readOnly", "destructive", "idempotent", "confirm", "alwaysAsync"]) {
|
|
11493
11393
|
if (typeof flags[k] === "boolean") patch[k] = flags[k];
|
|
11494
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
|
+
}
|
|
11495
11400
|
if (Object.keys(patch).length === 0) {
|
|
11496
|
-
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).");
|
|
11497
11402
|
}
|
|
11498
11403
|
return { ...cat, overrides: { ...cat.overrides ?? {}, [tool]: { ...cat.overrides?.[tool] ?? {}, ...patch } } };
|
|
11499
11404
|
}
|
|
@@ -14132,15 +14037,15 @@ async function runDomainAdd(project, opts) {
|
|
|
14132
14037
|
console.error(import_chalk50.default.red(`A ${kind} domain belongs to a proxy: apiblaze domain add <project> --kind ${kind} --domain ${opts.domain}`));
|
|
14133
14038
|
process.exit(1);
|
|
14134
14039
|
}
|
|
14135
|
-
const
|
|
14040
|
+
const scope2 = await scopeFor(project, opts);
|
|
14136
14041
|
const spinner = (0, import_ora20.default)(`Registering ${opts.domain}...`).start();
|
|
14137
14042
|
try {
|
|
14138
|
-
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 };
|
|
14139
14044
|
const out = await admin({
|
|
14140
14045
|
method: "POST",
|
|
14141
|
-
path:
|
|
14046
|
+
path: scope2.base,
|
|
14142
14047
|
body,
|
|
14143
|
-
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})` : ""}`
|
|
14144
14049
|
});
|
|
14145
14050
|
spinner.succeed(`Registered ${out?.hostname ?? opts.domain} (${describeKind(out)}). Add these DNS records, then run \`apiblaze domain status\`:`);
|
|
14146
14051
|
if (opts.json) {
|
|
@@ -14157,11 +14062,11 @@ async function runDomainAdd(project, opts) {
|
|
|
14157
14062
|
}
|
|
14158
14063
|
}
|
|
14159
14064
|
async function runDomainList(project, opts) {
|
|
14160
|
-
const
|
|
14065
|
+
const scope2 = await scopeFor(project, opts);
|
|
14161
14066
|
const out = await admin({
|
|
14162
14067
|
method: "GET",
|
|
14163
|
-
path:
|
|
14164
|
-
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}`
|
|
14165
14070
|
});
|
|
14166
14071
|
const domains = out?.domains ?? [];
|
|
14167
14072
|
if (opts.json) {
|
|
@@ -14173,7 +14078,7 @@ async function runDomainList(project, opts) {
|
|
|
14173
14078
|
return;
|
|
14174
14079
|
}
|
|
14175
14080
|
for (const d of domains) {
|
|
14176
|
-
const pin =
|
|
14081
|
+
const pin = scope2.kind === "project" && d.tenant ? import_chalk50.default.dim(` tenant ${d.tenant}`) : "";
|
|
14177
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 ?? "")}`);
|
|
14178
14083
|
}
|
|
14179
14084
|
}
|
|
@@ -14182,10 +14087,10 @@ async function runDomainStatus(project, opts) {
|
|
|
14182
14087
|
console.error(import_chalk50.default.red("--id <domainId> is required (see `apiblaze domain list`)."));
|
|
14183
14088
|
process.exit(1);
|
|
14184
14089
|
}
|
|
14185
|
-
const
|
|
14090
|
+
const scope2 = await scopeFor(project, opts);
|
|
14186
14091
|
const out = await admin({
|
|
14187
14092
|
method: "GET",
|
|
14188
|
-
path: `${
|
|
14093
|
+
path: `${scope2.base}/${encodeURIComponent(opts.id)}/status`,
|
|
14189
14094
|
summary: `Check custom-domain status`
|
|
14190
14095
|
});
|
|
14191
14096
|
if (opts.json) {
|
|
@@ -14207,12 +14112,12 @@ async function runDomainRemove(project, opts) {
|
|
|
14207
14112
|
console.error(import_chalk50.default.red("--id <domainId> is required (see `apiblaze domain list`)."));
|
|
14208
14113
|
process.exit(1);
|
|
14209
14114
|
}
|
|
14210
|
-
const
|
|
14115
|
+
const scope2 = await scopeFor(project, opts);
|
|
14211
14116
|
const spinner = (0, import_ora20.default)("Removing domain...").start();
|
|
14212
14117
|
try {
|
|
14213
14118
|
await admin({
|
|
14214
14119
|
method: "DELETE",
|
|
14215
|
-
path: `${
|
|
14120
|
+
path: `${scope2.base}/${encodeURIComponent(opts.id)}`,
|
|
14216
14121
|
summary: `Remove custom domain ${opts.id}`
|
|
14217
14122
|
});
|
|
14218
14123
|
spinner.succeed("Removed. Billing for it stops now.");
|
|
@@ -14828,7 +14733,8 @@ function randomSecret() {
|
|
|
14828
14733
|
(0, import_crypto2.randomFillSync)(bytes);
|
|
14829
14734
|
return Buffer.from(bytes).toString("base64url");
|
|
14830
14735
|
}
|
|
14831
|
-
var PROVIDER_TYPES = ["google", "github", "microsoft", "facebook", "auth0", "
|
|
14736
|
+
var PROVIDER_TYPES = ["google", "github", "microsoft", "facebook", "auth0", "oidc"];
|
|
14737
|
+
var PASSWORD_TYPE = "password";
|
|
14832
14738
|
var DEFAULT_SCOPES2 = {
|
|
14833
14739
|
google: "openid email profile",
|
|
14834
14740
|
microsoft: "openid email profile",
|
|
@@ -14862,8 +14768,15 @@ async function providersMenu(cBase, clientLabel) {
|
|
|
14862
14768
|
}]);
|
|
14863
14769
|
if (act === "back") return;
|
|
14864
14770
|
if (act === "add") {
|
|
14865
|
-
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 }]);
|
|
14866
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
|
+
}
|
|
14867
14780
|
if (type === "github") {
|
|
14868
14781
|
const { managed } = await inquirer3.prompt([{
|
|
14869
14782
|
type: "confirm",
|
|
@@ -14877,7 +14790,7 @@ async function providersMenu(cBase, clientLabel) {
|
|
|
14877
14790
|
const a = await inquirer3.prompt([
|
|
14878
14791
|
{ type: "input", name: "clientId", message: `${type} OAuth client id:`, validate: (s) => !!s.trim() || "required" },
|
|
14879
14792
|
{ type: "password", name: "clientSecret", mask: "*", message: `${type} OAuth client secret:`, validate: (s) => s.length >= 6 && s.length <= 200 || "6\u2013200 chars" },
|
|
14880
|
-
...type === "auth0" || type === "
|
|
14793
|
+
...type === "auth0" || type === "oidc" ? [{ type: "input", name: "domain", message: "Issuer / domain (e.g. your-tenant.auth0.com):" }] : [],
|
|
14881
14794
|
{ type: "input", name: "scopes", message: "Scopes:", default: DEFAULT_SCOPES2[type] ?? "" }
|
|
14882
14795
|
]);
|
|
14883
14796
|
body = {
|
|
@@ -16323,9 +16236,230 @@ async function agentsMenu(proj2, opts) {
|
|
|
16323
16236
|
else await runMcp(proj2.projectName, proj2.apiVersion, {});
|
|
16324
16237
|
}
|
|
16325
16238
|
|
|
16326
|
-
// src/commands/
|
|
16239
|
+
// src/commands/tenant-password.ts
|
|
16327
16240
|
var import_chalk59 = __toESM(require("chalk"));
|
|
16328
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"));
|
|
16329
16463
|
init_auth();
|
|
16330
16464
|
init_consumer_auth();
|
|
16331
16465
|
init_admin();
|
|
@@ -16427,7 +16561,7 @@ async function userByRefOrCreate(t, ref) {
|
|
|
16427
16561
|
if (!ref.includes("@")) throw e;
|
|
16428
16562
|
const created = await t.call("/v1/admin/users", "POST", { email: ref.trim().toLowerCase() });
|
|
16429
16563
|
if (!created?.abz_sub) throw e;
|
|
16430
|
-
console.log(
|
|
16564
|
+
console.log(import_chalk60.default.dim(` ${ref} wasn't a user yet \u2014 created.`));
|
|
16431
16565
|
return { abz_sub: created.abz_sub, email: created.email ?? ref, display_name: null };
|
|
16432
16566
|
}
|
|
16433
16567
|
}
|
|
@@ -16435,11 +16569,11 @@ async function runGroupList(opts) {
|
|
|
16435
16569
|
const t = await resolveTransport(opts);
|
|
16436
16570
|
const groups = await listGroups(t);
|
|
16437
16571
|
if (opts.json) return void console.log(JSON.stringify(groups));
|
|
16438
|
-
if (!groups.length) return void console.log(
|
|
16439
|
-
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}:`));
|
|
16440
16574
|
for (const g of groups) {
|
|
16441
|
-
const sealed = g.name === "apiblaze_admins" ?
|
|
16442
|
-
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}`);
|
|
16443
16577
|
}
|
|
16444
16578
|
}
|
|
16445
16579
|
async function runGroupCreate(name, opts) {
|
|
@@ -16464,10 +16598,10 @@ async function runGroupCreate(name, opts) {
|
|
|
16464
16598
|
}
|
|
16465
16599
|
}
|
|
16466
16600
|
}
|
|
16467
|
-
const spinner = (0,
|
|
16601
|
+
const spinner = (0, import_ora29.default)(`Creating group ${name}...`).start();
|
|
16468
16602
|
try {
|
|
16469
16603
|
await t.call("/v1/admin/groups", "POST", { name, admins: [adminSub] });
|
|
16470
|
-
spinner.succeed(`Group ${
|
|
16604
|
+
spinner.succeed(`Group ${import_chalk60.default.bold(name)} created in ${t.tenant}.`);
|
|
16471
16605
|
} catch (e) {
|
|
16472
16606
|
spinner.fail("Create failed.");
|
|
16473
16607
|
throw e;
|
|
@@ -16476,10 +16610,10 @@ async function runGroupCreate(name, opts) {
|
|
|
16476
16610
|
async function runGroupDelete(name, opts) {
|
|
16477
16611
|
const t = await resolveTransport(opts);
|
|
16478
16612
|
const g = await groupByName(t, name);
|
|
16479
|
-
const spinner = (0,
|
|
16613
|
+
const spinner = (0, import_ora29.default)(`Deleting group ${name}...`).start();
|
|
16480
16614
|
try {
|
|
16481
16615
|
await t.call(`/v1/admin/groups/${encodeURIComponent(g.id)}`, "DELETE");
|
|
16482
|
-
spinner.succeed(`Group ${
|
|
16616
|
+
spinner.succeed(`Group ${import_chalk60.default.bold(name)} deleted.`);
|
|
16483
16617
|
} catch (e) {
|
|
16484
16618
|
spinner.fail("Delete failed.");
|
|
16485
16619
|
throw e;
|
|
@@ -16498,21 +16632,21 @@ async function runGroupMembers(name, opts) {
|
|
|
16498
16632
|
return void console.log(JSON.stringify({ ...detail, members: walkedMembers.items, subgroups: subs?.subgroups ?? [] }));
|
|
16499
16633
|
}
|
|
16500
16634
|
const members = walkedMembers.items;
|
|
16501
|
-
console.log(
|
|
16502
|
-
for (const m of members) console.log(` ${label2(m)} ${m.role === "admin" ?
|
|
16503
|
-
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"));
|
|
16504
16638
|
const sg = subs?.subgroups ?? [];
|
|
16505
|
-
if (sg.length) console.log(
|
|
16639
|
+
if (sg.length) console.log(import_chalk60.default.dim(` subgroups: ${sg.map((s) => s.name).join(", ")}`));
|
|
16506
16640
|
}
|
|
16507
16641
|
async function runGroupAddUser(userRef, groupName, opts) {
|
|
16508
16642
|
const t = await resolveTransport(opts);
|
|
16509
16643
|
const g = await groupByName(t, groupName);
|
|
16510
16644
|
const u = await userByRefOrCreate(t, userRef);
|
|
16511
16645
|
const role = opts.role === "admin" ? "admin" : "member";
|
|
16512
|
-
const spinner = (0,
|
|
16646
|
+
const spinner = (0, import_ora29.default)(`Adding ${label2(u)} to ${g.name}...`).start();
|
|
16513
16647
|
try {
|
|
16514
16648
|
await t.call(`/v1/admin/groups/${encodeURIComponent(g.id)}/members`, "POST", { abz_sub: u.abz_sub, role });
|
|
16515
|
-
spinner.succeed(`${
|
|
16649
|
+
spinner.succeed(`${import_chalk60.default.bold(label2(u))} added to ${import_chalk60.default.bold(g.name)} as ${role}.`);
|
|
16516
16650
|
} catch (e) {
|
|
16517
16651
|
spinner.fail("Add failed.");
|
|
16518
16652
|
throw e;
|
|
@@ -16521,10 +16655,10 @@ async function runGroupAddUser(userRef, groupName, opts) {
|
|
|
16521
16655
|
async function runGroupRemoveUser(userRef, groupName, opts) {
|
|
16522
16656
|
const t = await resolveTransport(opts);
|
|
16523
16657
|
const [g, u] = [await groupByName(t, groupName), await userByRef(t, userRef)];
|
|
16524
|
-
const spinner = (0,
|
|
16658
|
+
const spinner = (0, import_ora29.default)(`Removing ${label2(u)} from ${g.name}...`).start();
|
|
16525
16659
|
try {
|
|
16526
16660
|
await t.call(`/v1/admin/groups/${encodeURIComponent(g.id)}/members/_?abz_sub=${encodeURIComponent(u.abz_sub)}`, "DELETE");
|
|
16527
|
-
spinner.succeed(`${
|
|
16661
|
+
spinner.succeed(`${import_chalk60.default.bold(label2(u))} removed from ${import_chalk60.default.bold(g.name)}.`);
|
|
16528
16662
|
} catch (e) {
|
|
16529
16663
|
spinner.fail("Remove failed.");
|
|
16530
16664
|
throw e;
|
|
@@ -16533,10 +16667,10 @@ async function runGroupRemoveUser(userRef, groupName, opts) {
|
|
|
16533
16667
|
async function runGroupAddGroup(childName, parentName, opts) {
|
|
16534
16668
|
const t = await resolveTransport(opts);
|
|
16535
16669
|
const [child, parent] = [await groupByName(t, childName), await groupByName(t, parentName)];
|
|
16536
|
-
const spinner = (0,
|
|
16670
|
+
const spinner = (0, import_ora29.default)(`Nesting ${child.name} under ${parent.name}...`).start();
|
|
16537
16671
|
try {
|
|
16538
16672
|
await t.call(`/v1/admin/groups/${encodeURIComponent(parent.id)}/subgroups`, "POST", { child_group_id: child.id });
|
|
16539
|
-
spinner.succeed(`${
|
|
16673
|
+
spinner.succeed(`${import_chalk60.default.bold(child.name)} is now a subgroup of ${import_chalk60.default.bold(parent.name)}.`);
|
|
16540
16674
|
} catch (e) {
|
|
16541
16675
|
spinner.fail("Nest failed.");
|
|
16542
16676
|
throw e;
|
|
@@ -16545,10 +16679,10 @@ async function runGroupAddGroup(childName, parentName, opts) {
|
|
|
16545
16679
|
async function runGroupRemoveGroup(childName, parentName, opts) {
|
|
16546
16680
|
const t = await resolveTransport(opts);
|
|
16547
16681
|
const [child, parent] = [await groupByName(t, childName), await groupByName(t, parentName)];
|
|
16548
|
-
const spinner = (0,
|
|
16682
|
+
const spinner = (0, import_ora29.default)(`Un-nesting ${child.name} from ${parent.name}...`).start();
|
|
16549
16683
|
try {
|
|
16550
16684
|
await t.call(`/v1/admin/groups/${encodeURIComponent(parent.id)}/subgroups/${encodeURIComponent(child.id)}`, "DELETE");
|
|
16551
|
-
spinner.succeed(`${
|
|
16685
|
+
spinner.succeed(`${import_chalk60.default.bold(child.name)} un-nested from ${import_chalk60.default.bold(parent.name)}.`);
|
|
16552
16686
|
} catch (e) {
|
|
16553
16687
|
spinner.fail("Un-nest failed.");
|
|
16554
16688
|
throw e;
|
|
@@ -16559,13 +16693,13 @@ async function runGroupUsers(opts) {
|
|
|
16559
16693
|
const users = await listUsers(t);
|
|
16560
16694
|
const observed = await t.call("/v1/admin/observed-identities").catch(() => ({ identities: [] }));
|
|
16561
16695
|
if (opts.json) return void console.log(JSON.stringify({ users, observed: observed?.identities ?? [] }));
|
|
16562
|
-
console.log(
|
|
16563
|
-
for (const u of users) console.log(` ${label2(u)} ${
|
|
16564
|
-
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"));
|
|
16565
16699
|
const fresh = (observed?.identities ?? []).filter((o) => !o.provisioned);
|
|
16566
16700
|
if (fresh.length) {
|
|
16567
|
-
console.log(
|
|
16568
|
-
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 ?? "")}`);
|
|
16569
16703
|
}
|
|
16570
16704
|
}
|
|
16571
16705
|
|
|
@@ -16575,8 +16709,8 @@ init_iam();
|
|
|
16575
16709
|
init_admins();
|
|
16576
16710
|
|
|
16577
16711
|
// src/commands/preapprove.ts
|
|
16578
|
-
var
|
|
16579
|
-
var
|
|
16712
|
+
var import_chalk61 = __toESM(require("chalk"));
|
|
16713
|
+
var import_ora30 = __toESM(require("ora"));
|
|
16580
16714
|
init_preapproval();
|
|
16581
16715
|
async function runPreapprove(who, opts) {
|
|
16582
16716
|
const tenant2 = await resolveProducerTenant(opts.tenant);
|
|
@@ -16584,14 +16718,14 @@ async function runPreapprove(who, opts) {
|
|
|
16584
16718
|
const rules = await listPreapprovalRules(tenant2);
|
|
16585
16719
|
if (opts.json) return void console.log(JSON.stringify(rules, null, 2));
|
|
16586
16720
|
if (!rules.length) {
|
|
16587
|
-
console.log(
|
|
16588
|
-
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`));
|
|
16589
16723
|
return;
|
|
16590
16724
|
}
|
|
16591
|
-
console.log(
|
|
16725
|
+
console.log(import_chalk61.default.dim(`Pre-approved for ${import_chalk61.default.bold(tenant2)}:`));
|
|
16592
16726
|
for (const r of rules) {
|
|
16593
|
-
const tag = r.kind === "domain" ?
|
|
16594
|
-
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(", ")}`) : "";
|
|
16595
16729
|
console.log(` ${tag}${grp}`);
|
|
16596
16730
|
}
|
|
16597
16731
|
return;
|
|
@@ -16600,19 +16734,19 @@ async function runPreapprove(who, opts) {
|
|
|
16600
16734
|
throw new Error("Who? Pass an email or a domain: `apiblaze preapprove someone@acme.com` (or `apiblaze preapprove --list`).");
|
|
16601
16735
|
}
|
|
16602
16736
|
if (opts.remove) {
|
|
16603
|
-
const spinner2 = (0,
|
|
16737
|
+
const spinner2 = (0, import_ora30.default)(`Removing ${who} from ${tenant2}\u2026`).start();
|
|
16604
16738
|
const { removed, value } = await removePreapprovalRule(tenant2, who);
|
|
16605
|
-
if (removed) spinner2.succeed(`${
|
|
16606
|
-
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.`);
|
|
16607
16741
|
return;
|
|
16608
16742
|
}
|
|
16609
|
-
const spinner = (0,
|
|
16743
|
+
const spinner = (0, import_ora30.default)(`Pre-approving ${who} for ${tenant2}\u2026`).start();
|
|
16610
16744
|
try {
|
|
16611
16745
|
const { kind, value } = await addPreapprovalRule(tenant2, who, opts.group);
|
|
16612
16746
|
const what = kind === "domain" ? `Anyone @${value}` : value;
|
|
16613
|
-
spinner.succeed(`${
|
|
16614
|
-
if (opts.group?.length) console.log(
|
|
16615
|
-
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`));
|
|
16616
16750
|
} catch (err) {
|
|
16617
16751
|
spinner.fail("Could not add the rule.");
|
|
16618
16752
|
throw err;
|
|
@@ -16624,8 +16758,8 @@ init_apichat();
|
|
|
16624
16758
|
init_llm();
|
|
16625
16759
|
|
|
16626
16760
|
// src/commands/consumer.ts
|
|
16627
|
-
var
|
|
16628
|
-
var
|
|
16761
|
+
var import_chalk62 = __toESM(require("chalk"));
|
|
16762
|
+
var import_ora31 = __toESM(require("ora"));
|
|
16629
16763
|
init_admin();
|
|
16630
16764
|
init_resolve();
|
|
16631
16765
|
init_caller();
|
|
@@ -16648,7 +16782,7 @@ async function consumerFetch(creds, suffix, init) {
|
|
|
16648
16782
|
function requireConsumer2() {
|
|
16649
16783
|
const c = loadConsumer();
|
|
16650
16784
|
if (!c) {
|
|
16651
|
-
console.error(
|
|
16785
|
+
console.error(import_chalk62.default.red("Not logged in as a consumer. Run `apiblaze consumer login` first."));
|
|
16652
16786
|
process.exit(1);
|
|
16653
16787
|
}
|
|
16654
16788
|
return c;
|
|
@@ -16659,7 +16793,7 @@ async function runConsumerLogin(opts) {
|
|
|
16659
16793
|
let clientId = opts.client;
|
|
16660
16794
|
if (clientId) {
|
|
16661
16795
|
if (!tenant2) {
|
|
16662
|
-
console.error(
|
|
16796
|
+
console.error(import_chalk62.default.red("When using --client, also pass --tenant <slug> (it sets which portal/keys host to use)."));
|
|
16663
16797
|
process.exit(1);
|
|
16664
16798
|
}
|
|
16665
16799
|
} else {
|
|
@@ -16671,24 +16805,24 @@ async function runConsumerLogin(opts) {
|
|
|
16671
16805
|
if (!picked) process.exit(1);
|
|
16672
16806
|
tenant2 = picked;
|
|
16673
16807
|
}
|
|
16674
|
-
const s2 = (0,
|
|
16808
|
+
const s2 = (0, import_ora31.default)("Finding the login app...").start();
|
|
16675
16809
|
const clients = await admin({ method: "GET", path: `/teams/${encodeURIComponent(teamId)}/tenants/${encodeURIComponent(tenant2)}/app-clients`, summary: `List app clients for ${tenant2}` }).catch(() => []);
|
|
16676
16810
|
s2.stop();
|
|
16677
16811
|
const usable = (Array.isArray(clients) ? clients : []).filter((c) => c && (c.client_id || c.clientId));
|
|
16678
16812
|
const pick3 = usable.find((c) => c.is_default || c.default) ?? usable.find((c) => c.verified !== false) ?? usable[0];
|
|
16679
16813
|
if (!pick3) {
|
|
16680
|
-
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).`));
|
|
16681
16815
|
process.exit(1);
|
|
16682
16816
|
}
|
|
16683
16817
|
clientId = pick3.client_id ?? pick3.clientId;
|
|
16684
16818
|
}
|
|
16685
|
-
console.log(`${
|
|
16819
|
+
console.log(`${import_chalk62.default.cyan("\u2192")} Logging in to ${import_chalk62.default.bold(tenant2)} as a consumer...`);
|
|
16686
16820
|
const result = await deviceLogin(clientId, DEFAULT_SCOPE, ({ verificationUri, userCode }) => {
|
|
16687
16821
|
console.log(`
|
|
16688
|
-
Open: ${
|
|
16689
|
-
console.log(` Code: ${
|
|
16822
|
+
Open: ${import_chalk62.default.underline(verificationUri)}`);
|
|
16823
|
+
console.log(` Code: ${import_chalk62.default.bold(userCode)}
|
|
16690
16824
|
`);
|
|
16691
|
-
console.log(
|
|
16825
|
+
console.log(import_chalk62.default.dim(" (opening your browser\u2026 waiting for you to finish)"));
|
|
16692
16826
|
});
|
|
16693
16827
|
const claims = result.idToken && decodeJwt2(result.idToken) || (decodeJwt2(result.accessToken) ?? {});
|
|
16694
16828
|
const creds = {
|
|
@@ -16703,7 +16837,7 @@ async function runConsumerLogin(opts) {
|
|
|
16703
16837
|
obtainedAt: Date.now()
|
|
16704
16838
|
};
|
|
16705
16839
|
saveConsumer(creds);
|
|
16706
|
-
console.log(
|
|
16840
|
+
console.log(import_chalk62.default.green(`\u2714 Logged in as consumer${creds.email ? ` ${creds.email}` : ""} on ${tenant2}.`));
|
|
16707
16841
|
}
|
|
16708
16842
|
async function runConsumerTokens(opts) {
|
|
16709
16843
|
const creds = requireConsumer2();
|
|
@@ -16716,28 +16850,28 @@ async function runConsumerTokens(opts) {
|
|
|
16716
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));
|
|
16717
16851
|
return;
|
|
16718
16852
|
}
|
|
16719
|
-
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)}
|
|
16720
16854
|
`);
|
|
16721
|
-
console.log(`${
|
|
16855
|
+
console.log(`${import_chalk62.default.bold("access_token")} ${import_chalk62.default.dim("exp " + (exp(fresh.accessToken) ?? "?"))}
|
|
16722
16856
|
${fresh.accessToken}
|
|
16723
16857
|
`);
|
|
16724
|
-
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) ?? "?"))}
|
|
16725
16859
|
${fresh.idToken}
|
|
16726
16860
|
`);
|
|
16727
|
-
if (fresh.refreshToken) console.log(`${
|
|
16861
|
+
if (fresh.refreshToken) console.log(`${import_chalk62.default.bold("refresh_token")}
|
|
16728
16862
|
${fresh.refreshToken}
|
|
16729
16863
|
`);
|
|
16730
|
-
console.log(
|
|
16864
|
+
console.log(import_chalk62.default.dim("These are your own tokens \u2014 keep them secret."));
|
|
16731
16865
|
}
|
|
16732
16866
|
async function runConsumerApikeys(opts) {
|
|
16733
16867
|
const creds = requireConsumer2();
|
|
16734
16868
|
const { default: inquirer3 } = await import("inquirer");
|
|
16735
|
-
const spinner = (0,
|
|
16869
|
+
const spinner = (0, import_ora31.default)("Loading your API keys...").start();
|
|
16736
16870
|
const list = await consumerFetch(creds, withPage("/apikeys", null));
|
|
16737
16871
|
if (list.status >= 400) {
|
|
16738
16872
|
spinner.stop();
|
|
16739
|
-
console.error(
|
|
16740
|
-
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."));
|
|
16741
16875
|
process.exit(1);
|
|
16742
16876
|
}
|
|
16743
16877
|
let walkCreds = list.creds;
|
|
@@ -16766,16 +16900,16 @@ async function runConsumerApikeys(opts) {
|
|
|
16766
16900
|
if (opts.json) {
|
|
16767
16901
|
console.log(JSON.stringify({ keys, revealed: revealMap }, null, 2));
|
|
16768
16902
|
} else if (!keys.length) {
|
|
16769
|
-
console.log(
|
|
16903
|
+
console.log(import_chalk62.default.yellow("No API keys yet."));
|
|
16770
16904
|
} else {
|
|
16771
16905
|
for (const k of keys) {
|
|
16772
16906
|
const clear = revealMap[k.environment]?.key;
|
|
16773
|
-
const shown = clear ?
|
|
16774
|
-
const exp = k.expires_at ?
|
|
16775
|
-
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 ?? "")}`);
|
|
16776
16910
|
}
|
|
16777
16911
|
if (Object.keys(revealMap).length === 0 && keys.some((k) => !k.expires_at)) {
|
|
16778
|
-
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.)"));
|
|
16779
16913
|
}
|
|
16780
16914
|
}
|
|
16781
16915
|
if (opts.json) return;
|
|
@@ -16789,7 +16923,7 @@ async function runConsumerApikeys(opts) {
|
|
|
16789
16923
|
const body = { environment: answers.environment };
|
|
16790
16924
|
if (answers.description) body.description = answers.description;
|
|
16791
16925
|
if (answers.expiresDays) body.expires_in_seconds = Number(answers.expiresDays) * 86400;
|
|
16792
|
-
const s2 = (0,
|
|
16926
|
+
const s2 = (0, import_ora31.default)("Creating key...").start();
|
|
16793
16927
|
const created = await consumerFetch(list.creds, "/apikeys", { method: "POST", body: JSON.stringify(body) });
|
|
16794
16928
|
if (created.status >= 400) {
|
|
16795
16929
|
s2.fail(`Create failed (${created.status}): ${created.data?.error ?? ""}`);
|
|
@@ -16797,13 +16931,13 @@ async function runConsumerApikeys(opts) {
|
|
|
16797
16931
|
}
|
|
16798
16932
|
s2.succeed("Key created.");
|
|
16799
16933
|
const key = created.data?.key ?? created.data?.fullKey;
|
|
16800
|
-
if (key) console.log(` ${
|
|
16801
|
-
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."));
|
|
16802
16936
|
}
|
|
16803
16937
|
|
|
16804
16938
|
// src/commands/sidecar.ts
|
|
16805
|
-
var
|
|
16806
|
-
var
|
|
16939
|
+
var import_chalk63 = __toESM(require("chalk"));
|
|
16940
|
+
var import_ora32 = __toESM(require("ora"));
|
|
16807
16941
|
var fs18 = __toESM(require("fs"));
|
|
16808
16942
|
var path14 = __toESM(require("path"));
|
|
16809
16943
|
init_admin();
|
|
@@ -16845,18 +16979,18 @@ function upsertEnvLocal(root, token) {
|
|
|
16845
16979
|
}
|
|
16846
16980
|
function installSidecarPackage(root) {
|
|
16847
16981
|
if (fs18.existsSync(path14.join(root, "node_modules", "apiblaze", "package.json"))) {
|
|
16848
|
-
console.log(` ${
|
|
16982
|
+
console.log(` ${import_chalk63.default.green("\u2713")} apiblaze package already installed`);
|
|
16849
16983
|
return;
|
|
16850
16984
|
}
|
|
16851
16985
|
const has = (f) => fs18.existsSync(path14.join(root, f));
|
|
16852
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" };
|
|
16853
|
-
const spinner = (0,
|
|
16987
|
+
const spinner = (0, import_ora32.default)(`Installing the apiblaze package (${pm.cmd})\u2026`).start();
|
|
16854
16988
|
try {
|
|
16855
16989
|
const { execSync } = require("child_process");
|
|
16856
16990
|
execSync(`${pm.cmd} ${pm.add} apiblaze`, { cwd: root, stdio: "ignore" });
|
|
16857
16991
|
spinner.succeed("Installed apiblaze (the sidecar runtime).");
|
|
16858
16992
|
} catch {
|
|
16859
|
-
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")}.`);
|
|
16860
16994
|
}
|
|
16861
16995
|
}
|
|
16862
16996
|
function readEnvKey(root) {
|
|
@@ -16999,7 +17133,7 @@ async function runAnonymousInit(root, router, opts) {
|
|
|
16999
17133
|
const { sidecarInitAnonymous: sidecarInitAnonymous2 } = await Promise.resolve().then(() => (init_api(), api_exports));
|
|
17000
17134
|
const { saveAnonCred: saveAnonCred2, clearAnonCred: clearAnonCred2 } = await Promise.resolve().then(() => (init_anon_cred(), anon_cred_exports));
|
|
17001
17135
|
if (opts.newSession) clearAnonCred2();
|
|
17002
|
-
const spinner = (0,
|
|
17136
|
+
const spinner = (0, import_ora32.default)("Setting up a sidecar (no login needed)...").start();
|
|
17003
17137
|
let out;
|
|
17004
17138
|
try {
|
|
17005
17139
|
out = await sidecarInitAnonymous2();
|
|
@@ -17011,29 +17145,29 @@ async function runAnonymousInit(root, router, opts) {
|
|
|
17011
17145
|
if (out.cp_key && out.team_id) saveAnonCred2(out.cp_key, out.team_id, out.claim_code);
|
|
17012
17146
|
const envState = upsertEnvLocal(root, out.token);
|
|
17013
17147
|
ensureGitignored(root);
|
|
17014
|
-
console.log(` ${
|
|
17015
|
-
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)}`);
|
|
17016
17150
|
installSidecarPackage(root);
|
|
17017
17151
|
let inspectorPath = null;
|
|
17018
17152
|
if (!opts.noInspector) {
|
|
17019
17153
|
inspectorPath = generateInspector(root, router);
|
|
17020
|
-
if (inspectorPath) console.log(` ${
|
|
17154
|
+
if (inspectorPath) console.log(` ${import_chalk63.default.green("\u2713")} inspector at ${inspectorPath}`);
|
|
17021
17155
|
}
|
|
17022
17156
|
console.log("");
|
|
17023
|
-
console.log(
|
|
17024
|
-
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.`);
|
|
17025
17159
|
console.log(` 2. Each external origin your app calls is logged in the console \u2014 approve one with:`);
|
|
17026
|
-
console.log(` ${
|
|
17160
|
+
console.log(` ${import_chalk63.default.cyan("apiblaze sidecar approve api.stripe.com")} (no login needed)`);
|
|
17027
17161
|
console.log("");
|
|
17028
|
-
console.log(
|
|
17029
|
-
console.log(` ${
|
|
17030
|
-
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`));
|
|
17031
17165
|
}
|
|
17032
17166
|
async function runSidecar(opts) {
|
|
17033
17167
|
const root = path14.resolve(opts.dir ?? process.cwd());
|
|
17034
17168
|
const detected2 = detectNextProject(root);
|
|
17035
17169
|
if (!detected2.found) {
|
|
17036
|
-
console.log(
|
|
17170
|
+
console.log(import_chalk63.default.yellow(`No Next.js project detected in ${root}.`));
|
|
17037
17171
|
console.log("Create one (e.g. `npx create-next-app`) and re-run `apiblaze init` inside it.");
|
|
17038
17172
|
return;
|
|
17039
17173
|
}
|
|
@@ -17044,10 +17178,10 @@ async function runSidecar(opts) {
|
|
|
17044
17178
|
if (!loadCredentials()) {
|
|
17045
17179
|
upsertEnvLocal(root, readEnvKey(root));
|
|
17046
17180
|
ensureGitignored(root);
|
|
17047
|
-
console.log(` ${
|
|
17048
|
-
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)}`);
|
|
17049
17183
|
installSidecarPackage(root);
|
|
17050
|
-
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."));
|
|
17051
17185
|
return;
|
|
17052
17186
|
}
|
|
17053
17187
|
const { teamId, teamName } = await resolveTeam(opts.team);
|
|
@@ -17056,7 +17190,7 @@ async function runSidecar(opts) {
|
|
|
17056
17190
|
const mustMint = !existingKey || opts.rotate || switchingTeam;
|
|
17057
17191
|
let token = existingKey ?? "";
|
|
17058
17192
|
if (mustMint) {
|
|
17059
|
-
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();
|
|
17060
17194
|
try {
|
|
17061
17195
|
const out = await admin({
|
|
17062
17196
|
method: "POST",
|
|
@@ -17070,39 +17204,39 @@ async function runSidecar(opts) {
|
|
|
17070
17204
|
throw err;
|
|
17071
17205
|
}
|
|
17072
17206
|
} else {
|
|
17073
|
-
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).`));
|
|
17074
17208
|
}
|
|
17075
17209
|
const envState = upsertEnvLocal(root, token);
|
|
17076
17210
|
ensureGitignored(root);
|
|
17077
|
-
console.log(` ${
|
|
17211
|
+
console.log(` ${import_chalk63.default.green("\u2713")} .env.local ${envState} (APIBLAZE_API_KEY) \u2014 gitignored`);
|
|
17078
17212
|
const wireState = wireInstrumentation(root);
|
|
17079
|
-
console.log(` ${
|
|
17213
|
+
console.log(` ${import_chalk63.default.green("\u2713")} instrumentation.ts ${wireState}`);
|
|
17080
17214
|
installSidecarPackage(root);
|
|
17081
17215
|
let inspectorPath = null;
|
|
17082
17216
|
if (!opts.noInspector) {
|
|
17083
17217
|
inspectorPath = generateInspector(root, detected2.router);
|
|
17084
|
-
if (inspectorPath) console.log(` ${
|
|
17218
|
+
if (inspectorPath) console.log(` ${import_chalk63.default.green("\u2713")} inspector at ${inspectorPath}`);
|
|
17085
17219
|
}
|
|
17086
17220
|
console.log("");
|
|
17087
|
-
console.log(
|
|
17088
|
-
console.log(` 1. ${
|
|
17089
|
-
console.log(` 2. The origins your app calls appear as ${
|
|
17090
|
-
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)`);
|
|
17091
17225
|
console.log(` \u2026within ~5 min your app starts routing that origin through APIblaze.`);
|
|
17092
|
-
if (inspectorPath) console.log(` \u2022 Try it now: open ${
|
|
17093
|
-
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>\`.`));
|
|
17094
17228
|
console.log("");
|
|
17095
|
-
console.log(
|
|
17096
|
-
console.log(
|
|
17097
|
-
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)."));
|
|
17098
17232
|
console.log("");
|
|
17099
|
-
console.log(
|
|
17100
|
-
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."));
|
|
17101
17235
|
}
|
|
17102
17236
|
|
|
17103
17237
|
// src/commands/origins.ts
|
|
17104
|
-
var
|
|
17105
|
-
var
|
|
17238
|
+
var import_chalk64 = __toESM(require("chalk"));
|
|
17239
|
+
var import_ora33 = __toESM(require("ora"));
|
|
17106
17240
|
init_admin();
|
|
17107
17241
|
init_resolve();
|
|
17108
17242
|
init_auth();
|
|
@@ -17112,7 +17246,7 @@ async function runOriginsList(opts) {
|
|
|
17112
17246
|
if (!loadCredentials()) {
|
|
17113
17247
|
const cred = loadAnonCred();
|
|
17114
17248
|
if (!cred) {
|
|
17115
|
-
console.log(
|
|
17249
|
+
console.log(import_chalk64.default.yellow("No anonymous workspace here. Run `apiblaze init` first."));
|
|
17116
17250
|
return;
|
|
17117
17251
|
}
|
|
17118
17252
|
out = await cpFetch(cred.cp_key, `/teams/${encodeURIComponent(cred.team_id)}/sidecar/candidates`, { method: "GET" });
|
|
@@ -17130,30 +17264,30 @@ async function runOriginsList(opts) {
|
|
|
17130
17264
|
}
|
|
17131
17265
|
const routed = out.routed ?? [];
|
|
17132
17266
|
const candidates = out.candidates ?? [];
|
|
17133
|
-
console.log(
|
|
17267
|
+
console.log(import_chalk64.default.bold(`
|
|
17134
17268
|
Routed through APIblaze (${routed.length})`));
|
|
17135
|
-
if (!routed.length) console.log(
|
|
17136
|
-
for (const r of routed) console.log(` ${
|
|
17137
|
-
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(`
|
|
17138
17272
|
Candidates \u2014 going direct, not yet approved (${candidates.length})`));
|
|
17139
|
-
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"));
|
|
17140
17274
|
for (const c of candidates) {
|
|
17141
|
-
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}`)}`);
|
|
17142
17276
|
}
|
|
17143
17277
|
if (candidates.length) {
|
|
17144
|
-
console.log(
|
|
17278
|
+
console.log(import_chalk64.default.dim(`
|
|
17145
17279
|
Approve: apiblaze sidecar approve ${candidates[0].origin.replace("https://", "")}`));
|
|
17146
|
-
console.log(
|
|
17280
|
+
console.log(import_chalk64.default.dim(` Dismiss: apiblaze sidecar deny ${candidates[0].origin.replace("https://", "")}`));
|
|
17147
17281
|
}
|
|
17148
17282
|
}
|
|
17149
17283
|
async function runOriginsApprove(origin, opts) {
|
|
17150
17284
|
if (!loadCredentials()) {
|
|
17151
17285
|
const cred = loadAnonCred();
|
|
17152
17286
|
if (!cred) {
|
|
17153
|
-
console.error(
|
|
17287
|
+
console.error(import_chalk64.default.red("Not logged in and no anonymous workspace. Run `apiblaze init` first."));
|
|
17154
17288
|
process.exit(1);
|
|
17155
17289
|
}
|
|
17156
|
-
const spinner2 = (0,
|
|
17290
|
+
const spinner2 = (0, import_ora33.default)(`Approving ${origin} (anonymous)...`).start();
|
|
17157
17291
|
try {
|
|
17158
17292
|
const out = await cpFetch(cred.cp_key, `/teams/${encodeURIComponent(cred.team_id)}/sidecar/approve`, { method: "POST", body: JSON.stringify({ origin }) });
|
|
17159
17293
|
spinner2.succeed(`Approved ${origin} \u2192 proxy ${out.project_id}. Routing within ~5 min.`);
|
|
@@ -17164,7 +17298,7 @@ async function runOriginsApprove(origin, opts) {
|
|
|
17164
17298
|
return;
|
|
17165
17299
|
}
|
|
17166
17300
|
const { teamId } = await resolveTeam(opts.team);
|
|
17167
|
-
const spinner = (0,
|
|
17301
|
+
const spinner = (0, import_ora33.default)(`Approving ${origin}...`).start();
|
|
17168
17302
|
try {
|
|
17169
17303
|
const out = await admin({
|
|
17170
17304
|
method: "POST",
|
|
@@ -17181,7 +17315,7 @@ async function runOriginsApprove(origin, opts) {
|
|
|
17181
17315
|
}
|
|
17182
17316
|
async function runOriginsDeny(origin, opts) {
|
|
17183
17317
|
const { teamId } = await resolveTeam(opts.team);
|
|
17184
|
-
const spinner = (0,
|
|
17318
|
+
const spinner = (0, import_ora33.default)(`Dismissing ${origin}...`).start();
|
|
17185
17319
|
try {
|
|
17186
17320
|
await admin({ method: "POST", path: `/teams/${encodeURIComponent(teamId)}/sidecar/dismiss`, body: { origin }, summary: `Dismiss sidecar origin ${origin}` });
|
|
17187
17321
|
spinner.succeed(`Dismissed ${origin}. It won't be suggested again.`);
|
|
@@ -17192,7 +17326,7 @@ async function runOriginsDeny(origin, opts) {
|
|
|
17192
17326
|
}
|
|
17193
17327
|
async function runOriginsRemove(origin, opts) {
|
|
17194
17328
|
const { teamId } = await resolveTeam(opts.team);
|
|
17195
|
-
const spinner = (0,
|
|
17329
|
+
const spinner = (0, import_ora33.default)(`Removing the proxy for ${origin}...`).start();
|
|
17196
17330
|
try {
|
|
17197
17331
|
await admin({ method: "POST", path: `/teams/${encodeURIComponent(teamId)}/sidecar/remove`, body: { origin }, summary: `Un-route sidecar origin ${origin}` });
|
|
17198
17332
|
spinner.succeed(`Removed ${origin}. Your app will stop routing it (goes direct) within ~5 min.`);
|
|
@@ -17203,7 +17337,7 @@ async function runOriginsRemove(origin, opts) {
|
|
|
17203
17337
|
}
|
|
17204
17338
|
|
|
17205
17339
|
// src/commands/op.ts
|
|
17206
|
-
var
|
|
17340
|
+
var import_chalk72 = __toESM(require("chalk"));
|
|
17207
17341
|
init_auth();
|
|
17208
17342
|
|
|
17209
17343
|
// src/lib/op-call.ts
|
|
@@ -17232,7 +17366,7 @@ async function opCall(call3) {
|
|
|
17232
17366
|
}
|
|
17233
17367
|
|
|
17234
17368
|
// src/commands/op-money.ts
|
|
17235
|
-
var
|
|
17369
|
+
var import_chalk65 = __toESM(require("chalk"));
|
|
17236
17370
|
init_admin();
|
|
17237
17371
|
init_types();
|
|
17238
17372
|
var money2 = (cents2) => typeof cents2 === "number" ? `$${usdStr(cents2)}` : "\u2014";
|
|
@@ -17254,60 +17388,60 @@ async function opWalletList(opts) {
|
|
|
17254
17388
|
const data = await opCall({ method: "GET", path: "/operator/credits", summary: "list wallets" });
|
|
17255
17389
|
if (opts.json) return void console.log(JSON.stringify(data, null, 2));
|
|
17256
17390
|
const accounts = data?.accounts ?? [];
|
|
17257
|
-
if (accounts.length === 0) return void console.log(
|
|
17258
|
-
console.log(
|
|
17391
|
+
if (accounts.length === 0) return void console.log(import_chalk65.default.dim("No wallets."));
|
|
17392
|
+
console.log(import_chalk65.default.dim(`
|
|
17259
17393
|
${"available".padStart(10)} ${"in use".padStart(9)} ${"grace".padStart(8)} ${"status".padEnd(11)} ${"gen".padStart(3)} wallet`));
|
|
17260
17394
|
for (const a of accounts) {
|
|
17261
|
-
const flags = [a.frozen ?
|
|
17262
|
-
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}`) : "";
|
|
17263
17397
|
console.log(
|
|
17264
|
-
` ${
|
|
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 : ""}`
|
|
17265
17399
|
);
|
|
17266
17400
|
}
|
|
17267
|
-
if (data?.truncated) console.log(
|
|
17401
|
+
if (data?.truncated) console.log(import_chalk65.default.yellow(`
|
|
17268
17402
|
List truncated \u2014 inspect one wallet with: apiblaze op wallet inspect <wallet>`));
|
|
17269
17403
|
console.log("");
|
|
17270
17404
|
}
|
|
17271
17405
|
async function opWalletInspect(ref, opts) {
|
|
17272
|
-
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>"));
|
|
17273
17407
|
const ba = await resolveWalletId(ref);
|
|
17274
17408
|
const w = await opCall({ method: "GET", path: `/operator/wallets/${encodeURIComponent(ba)}`, summary: `inspect wallet ${ba}` });
|
|
17275
17409
|
if (opts.json) return void console.log(JSON.stringify(w, null, 2));
|
|
17276
17410
|
const st = w?.state ?? {};
|
|
17277
|
-
console.log(
|
|
17278
|
-
Wallet ${w?.billing_account_id ?? ba}`) + (ref !== ba ?
|
|
17279
|
-
console.log(` ${"status".padEnd(16)} ${w?.status ?? "?"}${w?.frozen ?
|
|
17280
|
-
console.log(` ${"available".padEnd(16)} ${
|
|
17281
|
-
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")}`);
|
|
17282
17416
|
console.log(` ${"grace".padEnd(16)} ${money2(w?.grace_used_cents)} of ${money2(w?.grace_limit_cents)}`);
|
|
17283
17417
|
console.log(` ${"epoch / gen".padEnd(16)} ${st.epoch ?? "?"} / ${st.generation ?? w?.generation ?? "?"}`);
|
|
17284
17418
|
const unsent = unsentLedgerEntries(st);
|
|
17285
17419
|
const lastSend = lastLedgerSendAtMs(st);
|
|
17286
|
-
console.log(` ${"unsent ledger".padEnd(16)} ${unsent ?? "\u2014"} entries${
|
|
17287
|
-
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")}`);
|
|
17288
17422
|
if (st.counters && typeof st.counters === "object") {
|
|
17289
17423
|
const c = Object.entries(st.counters).map(([k, v]) => `${k}=${v}`).join(" \xB7 ");
|
|
17290
|
-
if (c) console.log(
|
|
17424
|
+
if (c) console.log(import_chalk65.default.dim(` counters ${c}`));
|
|
17291
17425
|
}
|
|
17292
17426
|
const shards = st.shards ?? [];
|
|
17293
17427
|
if (shards.length) {
|
|
17294
|
-
console.log(
|
|
17428
|
+
console.log(import_chalk65.default.bold(`
|
|
17295
17429
|
Shards (${shards.length})`));
|
|
17296
|
-
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`));
|
|
17297
17431
|
for (const s of shards) {
|
|
17298
17432
|
const exp = s.expiresAtMs ? `${Math.max(0, Math.round((s.expiresAtMs - Date.now()) / 1e3))}s` : "\u2014";
|
|
17299
17433
|
console.log(
|
|
17300
|
-
` ${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") : ""}`
|
|
17301
17435
|
);
|
|
17302
17436
|
}
|
|
17303
|
-
console.log(
|
|
17437
|
+
console.log(import_chalk65.default.dim(" float/budget in ledger units (10,000 per cent)"));
|
|
17304
17438
|
} else {
|
|
17305
|
-
console.log(
|
|
17439
|
+
console.log(import_chalk65.default.dim("\n No live shards (no traffic in the last window)."));
|
|
17306
17440
|
}
|
|
17307
17441
|
const teams = w?.teams ?? [];
|
|
17308
|
-
if (teams.length) console.log(
|
|
17442
|
+
if (teams.length) console.log(import_chalk65.default.dim(`
|
|
17309
17443
|
Teams on this wallet: ${teams.map((t) => typeof t === "string" ? t : t.team_id ?? t.teamId ?? JSON.stringify(t)).join(", ")}`));
|
|
17310
|
-
console.log(
|
|
17444
|
+
console.log(import_chalk65.default.dim(`
|
|
17311
17445
|
Act on it (dashboard only): ${walletPage(ba)}
|
|
17312
17446
|
`));
|
|
17313
17447
|
}
|
|
@@ -17336,11 +17470,11 @@ function opWalletMutation(action2, ref, opts) {
|
|
|
17336
17470
|
const target2 = ref ?? "<wallet>";
|
|
17337
17471
|
const url = ref && !isTeamId(ref) ? walletPage(ref) : `${DASHBOARD_BASE10}/operator/wallets`;
|
|
17338
17472
|
if (opts.json) return void console.log(JSON.stringify({ action: action2, wallet: ref ?? null, dashboard_only: true, url, requires: need ?? "a reason" }));
|
|
17339
|
-
console.log(
|
|
17473
|
+
console.log(import_chalk65.default.yellow(`
|
|
17340
17474
|
\`op wallet ${action2}\` is dashboard-only \u2014 it writes an operator_audit row with YOUR reason before touching the root.`));
|
|
17341
|
-
console.log(` ${
|
|
17342
|
-
console.log(
|
|
17343
|
-
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)`));
|
|
17344
17478
|
console.log("");
|
|
17345
17479
|
}
|
|
17346
17480
|
async function opMoneyExposure(opts) {
|
|
@@ -17348,24 +17482,24 @@ async function opMoneyExposure(opts) {
|
|
|
17348
17482
|
const data = await opCall({ method: "GET", path: `/operator/exposure${qs}`, summary: "money exposure" });
|
|
17349
17483
|
if (opts.json) return void console.log(JSON.stringify(data, null, 2));
|
|
17350
17484
|
const m = data?.money;
|
|
17351
|
-
console.log(
|
|
17352
|
-
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}`));
|
|
17353
17487
|
if (m) {
|
|
17354
|
-
console.log(` ${"in use".padEnd(14)} ${
|
|
17355
|
-
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")}`);
|
|
17356
17490
|
const wallets = m.wallets ?? [];
|
|
17357
17491
|
if (wallets.length) {
|
|
17358
|
-
console.log(
|
|
17492
|
+
console.log(import_chalk65.default.dim(`
|
|
17359
17493
|
${"in use".padStart(10)} ${"grace".padStart(8)} wallet`));
|
|
17360
17494
|
for (const w of wallets.slice(0, 40)) {
|
|
17361
|
-
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") : ""}`);
|
|
17362
17496
|
}
|
|
17363
|
-
if (wallets.length > 40) console.log(
|
|
17497
|
+
if (wallets.length > 40) console.log(import_chalk65.default.dim(` \u2026 ${wallets.length - 40} more (--json)`));
|
|
17364
17498
|
}
|
|
17365
17499
|
}
|
|
17366
17500
|
const ae = data?.ae;
|
|
17367
17501
|
if (ae && typeof ae === "object") {
|
|
17368
|
-
console.log(
|
|
17502
|
+
console.log(import_chalk65.default.bold("\n Analytics Engine view") + import_chalk65.default.dim(" admission by gate source, last window"));
|
|
17369
17503
|
for (const [k, v] of Object.entries(ae)) console.log(` ${k.padEnd(18)} ${typeof v === "object" ? JSON.stringify(v) : String(v)}`);
|
|
17370
17504
|
}
|
|
17371
17505
|
console.log("");
|
|
@@ -17379,24 +17513,24 @@ async function opIncidentStatus(opts) {
|
|
|
17379
17513
|
const data = await opCall({ method: "GET", path: "/operator/incident/degraded-mode", summary: "degraded-mode switch" });
|
|
17380
17514
|
if (opts.json) return void console.log(JSON.stringify(data, null, 2));
|
|
17381
17515
|
const mode = String(data?.mode ?? "allow");
|
|
17382
|
-
const colour = mode === "allow" ?
|
|
17383
|
-
console.log(
|
|
17384
|
-
if (data?.reason) console.log(
|
|
17385
|
-
if (data?.actor || data?.at) console.log(
|
|
17386
|
-
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(`
|
|
17387
17521
|
Change it (dashboard only, reason required): ${incidentPage()}
|
|
17388
17522
|
`));
|
|
17389
17523
|
}
|
|
17390
17524
|
function opIncidentMutation(mode, opts) {
|
|
17391
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" }));
|
|
17392
|
-
console.log(
|
|
17526
|
+
console.log(import_chalk65.default.yellow(`
|
|
17393
17527
|
\`op incident ${mode}\` is dashboard-only \u2014 ${MODE_WORDS[mode] ?? mode}.`));
|
|
17394
|
-
console.log(` ${
|
|
17395
|
-
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"));
|
|
17396
17530
|
}
|
|
17397
17531
|
|
|
17398
17532
|
// src/commands/op-ceiling.ts
|
|
17399
|
-
var
|
|
17533
|
+
var import_chalk66 = __toESM(require("chalk"));
|
|
17400
17534
|
init_types();
|
|
17401
17535
|
var STATUSES = ["pending", "decided", "all"];
|
|
17402
17536
|
var rpsStr = (n) => typeof n === "number" && Number.isFinite(n) ? n.toLocaleString("en-US") : "\u2014";
|
|
@@ -17410,7 +17544,7 @@ function ageStr(iso, now = Date.now()) {
|
|
|
17410
17544
|
return `${Math.floor(s / 86400)}d`;
|
|
17411
17545
|
}
|
|
17412
17546
|
var trunc = (s, n) => s.length > n ? `${s.slice(0, n - 1)}\u2026` : s;
|
|
17413
|
-
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 };
|
|
17414
17548
|
function printDecideError(err, id, opts) {
|
|
17415
17549
|
if (!(err instanceof ApiError) || ![400, 404, 409].includes(err.status)) return false;
|
|
17416
17550
|
const body = err.body ?? {};
|
|
@@ -17423,12 +17557,12 @@ function printDecideError(err, id, opts) {
|
|
|
17423
17557
|
if (err.status === 409) {
|
|
17424
17558
|
const d = body.request;
|
|
17425
17559
|
const by = d?.decided_by ? ` by ${d.decided_by}${d.decided_at ? ` at ${d.decided_at}` : ""}` : "";
|
|
17426
|
-
console.log(
|
|
17427
|
-
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"));
|
|
17428
17562
|
} else if (err.status === 404) {
|
|
17429
|
-
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)"));
|
|
17430
17564
|
} else {
|
|
17431
|
-
console.log(
|
|
17565
|
+
console.log(import_chalk66.default.red(` Ceiling request ${id} not decided: ${why}.`));
|
|
17432
17566
|
}
|
|
17433
17567
|
return true;
|
|
17434
17568
|
}
|
|
@@ -17436,31 +17570,31 @@ async function opCeilingList(opts) {
|
|
|
17436
17570
|
const status2 = String(opts.status ?? "pending").trim().toLowerCase();
|
|
17437
17571
|
if (!STATUSES.includes(status2)) {
|
|
17438
17572
|
process.exitCode = 1;
|
|
17439
|
-
return void console.log(
|
|
17573
|
+
return void console.log(import_chalk66.default.red(`--status must be one of ${STATUSES.join(" | ")}.`));
|
|
17440
17574
|
}
|
|
17441
17575
|
const data = await opCall(
|
|
17442
17576
|
{ method: "GET", path: `/operator/ceiling-requests?status=${status2}`, summary: `${status2} ceiling requests` }
|
|
17443
17577
|
);
|
|
17444
17578
|
if (opts.json) return void console.log(JSON.stringify(data, null, 2));
|
|
17445
17579
|
const rows = data?.requests ?? [];
|
|
17446
|
-
console.log(
|
|
17447
|
-
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.
|
|
17448
17582
|
`));
|
|
17449
|
-
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`));
|
|
17450
17584
|
for (const r of rows) {
|
|
17451
17585
|
const rate = `${rpsStr(r.current_ceiling_rps)} \u2192 ${rpsStr(r.requested_rps)}`;
|
|
17452
|
-
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)})`) : "";
|
|
17453
17587
|
const colour = STATUS_COLOUR[r.status] ?? ((s) => s);
|
|
17454
17588
|
console.log(
|
|
17455
|
-
` ${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}`
|
|
17456
17590
|
);
|
|
17457
17591
|
if (r.status !== "pending" && (r.decided_by || r.decision_note)) {
|
|
17458
|
-
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)}` : ""}`));
|
|
17459
17593
|
}
|
|
17460
17594
|
}
|
|
17461
17595
|
if (status2 === "pending") {
|
|
17462
17596
|
const presets = (data?.presets_rps ?? []).map(rpsStr).join(" / ");
|
|
17463
|
-
console.log(
|
|
17597
|
+
console.log(import_chalk66.default.dim(`
|
|
17464
17598
|
Decide: apiblaze op ceiling approve <id> [--rps N${presets ? ` e.g. ${presets}` : ""}] [--note "\u2026"] \xB7 apiblaze op ceiling deny <id> --note "why"`));
|
|
17465
17599
|
}
|
|
17466
17600
|
console.log("");
|
|
@@ -17468,23 +17602,23 @@ async function opCeilingList(opts) {
|
|
|
17468
17602
|
async function opCeilingDecide(decision, id, opts) {
|
|
17469
17603
|
if (!id) {
|
|
17470
17604
|
process.exitCode = 1;
|
|
17471
|
-
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)"));
|
|
17472
17606
|
}
|
|
17473
17607
|
const note = typeof opts.note === "string" ? opts.note.trim() : "";
|
|
17474
17608
|
if (decision === "deny" && !note) {
|
|
17475
17609
|
process.exitCode = 1;
|
|
17476
|
-
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"`));
|
|
17477
17611
|
}
|
|
17478
17612
|
let rps;
|
|
17479
17613
|
if (opts.rps !== void 0 && opts.rps !== null && opts.rps !== "") {
|
|
17480
17614
|
if (decision === "deny") {
|
|
17481
17615
|
process.exitCode = 1;
|
|
17482
|
-
return void console.log(
|
|
17616
|
+
return void console.log(import_chalk66.default.red("--rps only applies to approve."));
|
|
17483
17617
|
}
|
|
17484
17618
|
rps = Number(String(opts.rps).replace(/[,_]/g, ""));
|
|
17485
17619
|
if (!Number.isInteger(rps) || rps <= 0) {
|
|
17486
17620
|
process.exitCode = 1;
|
|
17487
|
-
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)."));
|
|
17488
17622
|
}
|
|
17489
17623
|
}
|
|
17490
17624
|
const body = { decision };
|
|
@@ -17509,19 +17643,19 @@ async function opCeilingDecide(decision, id, opts) {
|
|
|
17509
17643
|
const where = r ? `${r.project_id}/${r.api_version}` : `request ${id}`;
|
|
17510
17644
|
if (decision === "approve") {
|
|
17511
17645
|
const to = res?.limits?.ceilingRps ?? res?.request?.granted_rps ?? rps ?? r?.requested_rps;
|
|
17512
|
-
console.log(
|
|
17646
|
+
console.log(import_chalk66.default.green(`
|
|
17513
17647
|
project ${where}: maximum rate ${rpsStr(from)} \u2192 ${rpsStr(to)} req/s`));
|
|
17514
|
-
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)`));
|
|
17515
17649
|
} else {
|
|
17516
|
-
console.log(
|
|
17517
|
-
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`) : ""));
|
|
17518
17652
|
}
|
|
17519
|
-
if (note) console.log(
|
|
17653
|
+
if (note) console.log(import_chalk66.default.dim(` note: ${note}`));
|
|
17520
17654
|
console.log("");
|
|
17521
17655
|
}
|
|
17522
17656
|
|
|
17523
17657
|
// src/commands/op-ledger.ts
|
|
17524
|
-
var
|
|
17658
|
+
var import_chalk67 = __toESM(require("chalk"));
|
|
17525
17659
|
init_admin();
|
|
17526
17660
|
init_types();
|
|
17527
17661
|
var UNITS_PER_CENT = 1e4;
|
|
@@ -17548,7 +17682,7 @@ function parseLimit(raw, max) {
|
|
|
17548
17682
|
function fail6(opts, msg) {
|
|
17549
17683
|
process.exitCode = 1;
|
|
17550
17684
|
if (opts.json) return void console.log(JSON.stringify({ ok: false, error: msg }));
|
|
17551
|
-
console.log(
|
|
17685
|
+
console.log(import_chalk67.default.red(msg));
|
|
17552
17686
|
}
|
|
17553
17687
|
function printLedgerError(err, what, opts) {
|
|
17554
17688
|
if (!(err instanceof ApiError) || ![400, 403, 404, 500, 502, 503].includes(err.status)) return false;
|
|
@@ -17559,9 +17693,9 @@ function printLedgerError(err, what, opts) {
|
|
|
17559
17693
|
return true;
|
|
17560
17694
|
}
|
|
17561
17695
|
const code = body.error && body.error !== err.message ? ` ${body.error}` : "";
|
|
17562
|
-
console.log(
|
|
17563
|
-
if (err.status === 503 && body.error === "money_unreachable") console.log(
|
|
17564
|
-
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."));
|
|
17565
17699
|
return true;
|
|
17566
17700
|
}
|
|
17567
17701
|
async function call(opts, what, req) {
|
|
@@ -17574,33 +17708,33 @@ async function call(opts, what, req) {
|
|
|
17574
17708
|
}
|
|
17575
17709
|
function renderLedger2(data, opts = {}) {
|
|
17576
17710
|
const out = [];
|
|
17577
|
-
out.push(
|
|
17578
|
-
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}` : ""}`));
|
|
17579
17713
|
const h = data.head;
|
|
17580
17714
|
if (h) {
|
|
17581
|
-
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");
|
|
17582
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}`);
|
|
17583
17717
|
} else {
|
|
17584
|
-
out.push(
|
|
17718
|
+
out.push(import_chalk67.default.dim(" head none \u2014 Neon has no row for this wallet yet"));
|
|
17585
17719
|
}
|
|
17586
17720
|
const rows = data.rows ?? [];
|
|
17587
17721
|
if (!rows.length) {
|
|
17588
|
-
out.push(
|
|
17722
|
+
out.push(import_chalk67.default.dim(" No rows."));
|
|
17589
17723
|
return out;
|
|
17590
17724
|
}
|
|
17591
|
-
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`));
|
|
17592
17726
|
for (const r of rows) {
|
|
17593
17727
|
const c = typeof r.units_cents === "number" ? r.units_cents : cents(r.units);
|
|
17594
17728
|
const amt = signedMoney2(c);
|
|
17595
17729
|
const bal = typeof r.balance_after_cents === "number" ? r.balance_after_cents : cents(r.balance_after);
|
|
17596
|
-
const flag = r.flag ?
|
|
17597
|
-
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}`) : "";
|
|
17598
17732
|
out.push(
|
|
17599
|
-
` ${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}`
|
|
17600
17734
|
);
|
|
17601
17735
|
}
|
|
17602
|
-
out.push(
|
|
17603
|
-
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.`));
|
|
17604
17738
|
return out;
|
|
17605
17739
|
}
|
|
17606
17740
|
async function opLedgerShow(ref, opts) {
|
|
@@ -17625,25 +17759,25 @@ async function opLedgerLag(opts) {
|
|
|
17625
17759
|
if (opts.json) return void console.log(JSON.stringify(data, null, 2));
|
|
17626
17760
|
const newest = data.newest_received_at;
|
|
17627
17761
|
const ageS = newest ? Math.max(0, Math.round((Date.now() - Date.parse(newest)) / 1e3)) : null;
|
|
17628
|
-
console.log(
|
|
17629
|
-
console.log(` ${"newest row".padEnd(16)} ${newest ? `${ts(newest)} (${ageS}s ago)` :
|
|
17630
|
-
console.log(` ${"gaps".padEnd(16)} ${(data.gap_count ?
|
|
17631
|
-
console.log(` ${"flagged 24h".padEnd(16)} ${(data.flagged_24h?.count ?
|
|
17632
|
-
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")}`);
|
|
17633
17767
|
if (data.gaps?.length) {
|
|
17634
|
-
console.log(
|
|
17635
|
-
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`));
|
|
17636
17770
|
for (const g of data.gaps.slice(0, 40)) {
|
|
17637
|
-
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}`);
|
|
17638
17772
|
}
|
|
17639
|
-
if (data.gaps.length > 40) console.log(
|
|
17640
|
-
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)."));
|
|
17641
17775
|
}
|
|
17642
17776
|
const flagged = data.flagged_24h?.rows ?? [];
|
|
17643
17777
|
if (flagged.length) {
|
|
17644
|
-
console.log(
|
|
17645
|
-
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)} ${
|
|
17646
|
-
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)`));
|
|
17647
17781
|
}
|
|
17648
17782
|
console.log("");
|
|
17649
17783
|
}
|
|
@@ -17656,14 +17790,14 @@ async function opLedgerQuarantine(opts) {
|
|
|
17656
17790
|
const data = await call(opts, "Quarantine read", { method: "GET", path: `/operator/ledger/quarantine${qs.toString() ? `?${qs}` : ""}`, summary: "ledger quarantine" });
|
|
17657
17791
|
if (!data) return;
|
|
17658
17792
|
if (opts.json) return void console.log(JSON.stringify(data, null, 2));
|
|
17659
|
-
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`));
|
|
17660
17794
|
const rows = data.rows ?? [];
|
|
17661
|
-
if (!rows.length) return void console.log(
|
|
17662
|
-
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`));
|
|
17663
17797
|
for (const r of rows) {
|
|
17664
|
-
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)") : ""}`);
|
|
17665
17799
|
}
|
|
17666
|
-
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"));
|
|
17667
17801
|
}
|
|
17668
17802
|
async function opLedgerCut(ref, opts) {
|
|
17669
17803
|
if (!ref) return fail6(opts, 'Which wallet? apiblaze op ledger cut <wallet id | team id> [--reason "\u2026"]');
|
|
@@ -17674,9 +17808,9 @@ async function opLedgerCut(ref, opts) {
|
|
|
17674
17808
|
const res = await call(opts, "Ledger cut", { method: "POST", path: "/operator/ledger/cut", body, summary: `cut ledger for ${ba}` });
|
|
17675
17809
|
if (!res) return;
|
|
17676
17810
|
if (opts.json) return void console.log(JSON.stringify(res, null, 2));
|
|
17677
|
-
console.log(
|
|
17811
|
+
console.log(import_chalk67.default.green(`
|
|
17678
17812
|
${res.billing_account_id ?? ba}: cut and sent ${res.rows ?? 0} row${res.rows === 1 ? "" : "s"}.`));
|
|
17679
|
-
console.log(
|
|
17813
|
+
console.log(import_chalk67.default.dim(` They land in Neon within seconds: apiblaze op ledger show ${ba}
|
|
17680
17814
|
`));
|
|
17681
17815
|
}
|
|
17682
17816
|
async function opLedgerBackfill(opts) {
|
|
@@ -17692,33 +17826,33 @@ async function opLedgerBackfill(opts) {
|
|
|
17692
17826
|
const res = await call(opts, "Ledger backfill", { method: "POST", path: "/operator/ledger/backfill", body, summary: `ledger backfill since ${new Date(sinceMs).toISOString()}` });
|
|
17693
17827
|
if (!res) return;
|
|
17694
17828
|
if (opts.json) return void console.log(JSON.stringify(res, null, 2));
|
|
17695
|
-
console.log(
|
|
17696
|
-
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"));
|
|
17697
17831
|
console.log(` ${"wallets checked".padEnd(16)} ${res.wallets_checked}`);
|
|
17698
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"}`);
|
|
17699
17833
|
console.log(` ${"in sync".padEnd(16)} ${res.in_sync}`);
|
|
17700
|
-
console.log(` ${"uncut".padEnd(16)} ${res.uncut}${
|
|
17701
|
-
console.log(` ${"neon ahead".padEnd(16)} ${(res.neon_ahead ?
|
|
17702
|
-
console.log(` ${"unrecoverable".padEnd(16)} ${(res.unrecoverable ?
|
|
17703
|
-
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))}`);
|
|
17704
17838
|
const notable = (res.results ?? []).filter((r) => r.action !== "in_sync");
|
|
17705
17839
|
if (notable.length) {
|
|
17706
|
-
console.log(
|
|
17840
|
+
console.log(import_chalk67.default.dim(`
|
|
17707
17841
|
${"action".padEnd(10)} ${"wallet gen/seq/cut".padEnd(20)} ${"neon gen/head/contig".padEnd(20)} wallet`));
|
|
17708
17842
|
for (const r of notable.slice(0, 60)) {
|
|
17709
17843
|
const w = r.wallet ? `${r.wallet.generation}/${r.wallet.seq}/${r.wallet.cut_to}` : "\u2014";
|
|
17710
17844
|
const n = r.neon ? `${r.neon.generation}/${r.neon.head_seq}/${r.neon.contiguous_to}` : "\u2014";
|
|
17711
|
-
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;
|
|
17712
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 ");
|
|
17713
|
-
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}`) : ""}`);
|
|
17714
17848
|
}
|
|
17715
|
-
if (notable.length > 60) console.log(
|
|
17849
|
+
if (notable.length > 60) console.log(import_chalk67.default.dim(` \u2026 ${notable.length - 60} more (--json)`));
|
|
17716
17850
|
}
|
|
17717
17851
|
console.log("");
|
|
17718
17852
|
}
|
|
17719
17853
|
|
|
17720
17854
|
// src/commands/op-test.ts
|
|
17721
|
-
var
|
|
17855
|
+
var import_chalk68 = __toESM(require("chalk"));
|
|
17722
17856
|
init_auth();
|
|
17723
17857
|
init_types();
|
|
17724
17858
|
var DASHBOARD_BASE11 = process.env.APIBLAZE_DASHBOARD_BASE || "https://dashboard.apiblaze.com";
|
|
@@ -17743,10 +17877,10 @@ async function scrubSandboxKeys(timeoutS = 150) {
|
|
|
17743
17877
|
try {
|
|
17744
17878
|
const r = await runnerCall({ action: "gut", id: TEARDOWN_GUT_ID }, timeoutS);
|
|
17745
17879
|
const first = r.results?.[0]?.detail ?? r.error ?? "";
|
|
17746
|
-
log(
|
|
17880
|
+
log(import_chalk68.default.dim(`teardown: ${first || (r.ok ? "sandbox fixture keys scrubbed" : "scrub reported a problem")}`));
|
|
17747
17881
|
return r.ok;
|
|
17748
17882
|
} catch (e) {
|
|
17749
|
-
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`));
|
|
17750
17884
|
return false;
|
|
17751
17885
|
}
|
|
17752
17886
|
}
|
|
@@ -17768,23 +17902,23 @@ async function runOpTest(o = {}) {
|
|
|
17768
17902
|
const guts = o.skipGuts ? [] : listing.guts.filter((g) => g.id !== TEARDOWN_GUT_ID && matches(g.id, void 0, { ...o, group: void 0 }));
|
|
17769
17903
|
const total = specs.length + guts.length;
|
|
17770
17904
|
if (total === 0) {
|
|
17771
|
-
log(
|
|
17905
|
+
log(import_chalk68.default.yellow("nothing matched"));
|
|
17772
17906
|
process.exitCode = 2;
|
|
17773
17907
|
return;
|
|
17774
17908
|
}
|
|
17775
|
-
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}`));
|
|
17776
17910
|
const results = [];
|
|
17777
17911
|
let failed = 0;
|
|
17778
17912
|
const started = Date.now();
|
|
17779
17913
|
const record = (r) => {
|
|
17780
17914
|
results.push(r);
|
|
17781
17915
|
if (!r.ok) failed += 1;
|
|
17782
|
-
const mark = r.ok ?
|
|
17916
|
+
const mark = r.ok ? import_chalk68.default.green("PASS") : import_chalk68.default.red("FAIL");
|
|
17783
17917
|
log(`[${mark}] ${r.id} \u2014 ${r.label} (${ms(r.duration_ms)})`);
|
|
17784
17918
|
if (!r.ok) {
|
|
17785
|
-
if (r.kind === "spec") for (const c of r.cases.filter((c2) => !c2.ok)) log(
|
|
17786
|
-
else if (r.error) log(
|
|
17787
|
-
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)}`));
|
|
17788
17922
|
}
|
|
17789
17923
|
};
|
|
17790
17924
|
try {
|
|
@@ -17815,13 +17949,13 @@ async function runOpTest(o = {}) {
|
|
|
17815
17949
|
}
|
|
17816
17950
|
const summary = { ran: results.length, passed: results.length - failed, failed, duration_ms: Date.now() - started, sandbox_team: listing.sandbox_team };
|
|
17817
17951
|
log("");
|
|
17818
|
-
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)})`));
|
|
17819
17953
|
if (o.json) process.stdout.write(JSON.stringify({ ...summary, results }, null, 2) + "\n");
|
|
17820
17954
|
if (failed) process.exitCode = 1;
|
|
17821
17955
|
}
|
|
17822
17956
|
|
|
17823
17957
|
// src/commands/op-consumer.ts
|
|
17824
|
-
var
|
|
17958
|
+
var import_chalk69 = __toESM(require("chalk"));
|
|
17825
17959
|
init_consumer_auth();
|
|
17826
17960
|
var SANDBOX_TENANT = "portalteststenant";
|
|
17827
17961
|
var SANDBOX_PROXY = "portaltestproxypreapproved@2.7.0";
|
|
@@ -17864,9 +17998,9 @@ async function runOpConsumer(o = {}) {
|
|
|
17864
17998
|
const creds = loadConsumer();
|
|
17865
17999
|
const done = (r) => {
|
|
17866
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 };
|
|
17867
|
-
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");
|
|
17868
18002
|
log2(`[${mark}] ${out.id} \u2014 ${out.label}${out.tenant ? ` (tenant ${out.tenant})` : ""}${out.url ? ` \u2192 ${out.url}` : ""}`);
|
|
17869
|
-
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}`));
|
|
17870
18004
|
if (o.json) process.stdout.write(JSON.stringify(out, null, 2) + "\n");
|
|
17871
18005
|
if (!out.ok && !out.skipped) process.exitCode = 1;
|
|
17872
18006
|
if (out.skipped) process.exitCode = 2;
|
|
@@ -17906,7 +18040,7 @@ async function runOpConsumer(o = {}) {
|
|
|
17906
18040
|
}
|
|
17907
18041
|
|
|
17908
18042
|
// src/commands/op-perf.ts
|
|
17909
|
-
var
|
|
18043
|
+
var import_chalk70 = __toESM(require("chalk"));
|
|
17910
18044
|
var RPS_DEFAULT = 100;
|
|
17911
18045
|
var RPS_CAP = 300;
|
|
17912
18046
|
var SECONDS_DEFAULT = 60;
|
|
@@ -18090,10 +18224,10 @@ async function readGateway(proxy, sent, signal) {
|
|
|
18090
18224
|
}
|
|
18091
18225
|
const left = deadline - Date.now();
|
|
18092
18226
|
if (left <= 0) {
|
|
18093
|
-
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})` : ""}`));
|
|
18094
18228
|
return null;
|
|
18095
18229
|
}
|
|
18096
|
-
log3(
|
|
18230
|
+
log3(import_chalk70.default.dim(` waiting for the latency ledger\u2026 ${lastSeen}/${sent} rows for ${proxy}${lastErr ? ` (${lastErr})` : ""}`));
|
|
18097
18231
|
await sleep(Math.min(GATEWAY_POLL_MS, left), signal);
|
|
18098
18232
|
}
|
|
18099
18233
|
}
|
|
@@ -18102,7 +18236,7 @@ async function runOpPerf(o = {}) {
|
|
|
18102
18236
|
const thresholds = thresholdsFrom(o);
|
|
18103
18237
|
const ctrl = new AbortController();
|
|
18104
18238
|
const onSigint = () => {
|
|
18105
|
-
log3(
|
|
18239
|
+
log3(import_chalk70.default.yellow("\ninterrupted \u2014 stopping the load and tearing down the fixture"));
|
|
18106
18240
|
ctrl.abort();
|
|
18107
18241
|
};
|
|
18108
18242
|
process.once("SIGINT", onSigint);
|
|
@@ -18111,7 +18245,7 @@ async function runOpPerf(o = {}) {
|
|
|
18111
18245
|
if (!o.key) throw new Error("--url needs --key (the fixture proxy's API key)");
|
|
18112
18246
|
fixture = { url: o.url, key: o.key, proxy: proxyFromUrl(o.url), provisioned: false };
|
|
18113
18247
|
} else {
|
|
18114
|
-
log3(
|
|
18248
|
+
log3(import_chalk70.default.dim("provisioning the perf fixture through the runner\u2026"));
|
|
18115
18249
|
const f = await runnerCall({ action: "perf-fixture" }, 120);
|
|
18116
18250
|
if (!f?.url || !f?.key) throw new Error(`perf-fixture returned no url/key: ${JSON.stringify(f).slice(0, 200)}`);
|
|
18117
18251
|
fixture = { url: f.url, key: f.key, proxy: f.proxy || proxyFromUrl(f.url), team: f.team, provisioned: true };
|
|
@@ -18119,7 +18253,7 @@ async function runOpPerf(o = {}) {
|
|
|
18119
18253
|
try {
|
|
18120
18254
|
const total = Math.round(rps * seconds);
|
|
18121
18255
|
const intervalMs = 1e3 / rps;
|
|
18122
|
-
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})` : ""}`));
|
|
18123
18257
|
const samples = [];
|
|
18124
18258
|
const inflight = /* @__PURE__ */ new Set();
|
|
18125
18259
|
let fired = 0;
|
|
@@ -18127,7 +18261,7 @@ async function runOpPerf(o = {}) {
|
|
|
18127
18261
|
const progress = setInterval(() => {
|
|
18128
18262
|
const m = summarize(samples, Date.now() - t0);
|
|
18129
18263
|
const secs = (Date.now() - t0) / 1e3;
|
|
18130
|
-
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)`));
|
|
18131
18265
|
}, PROGRESS_EVERY_MS);
|
|
18132
18266
|
const { sent, elapsedMs } = await schedule({
|
|
18133
18267
|
total,
|
|
@@ -18141,21 +18275,21 @@ async function runOpPerf(o = {}) {
|
|
|
18141
18275
|
}
|
|
18142
18276
|
});
|
|
18143
18277
|
clearInterval(progress);
|
|
18144
|
-
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`));
|
|
18145
18279
|
await Promise.all([...inflight]);
|
|
18146
18280
|
const client = summarize(samples, elapsedMs);
|
|
18147
18281
|
log3("");
|
|
18148
|
-
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}`);
|
|
18149
18283
|
const gateway = ctrl.signal.aborted ? null : await readGateway(fixture.proxy, sent, ctrl.signal);
|
|
18150
18284
|
if (gateway) {
|
|
18151
18285
|
const extras = [
|
|
18152
18286
|
gateway.kv_per_req !== void 0 ? `kv/req ${gateway.kv_per_req}` : "",
|
|
18153
18287
|
gateway.redis_per_req !== void 0 ? `redis/req ${gateway.redis_per_req}` : ""
|
|
18154
18288
|
].filter(Boolean).join(" ");
|
|
18155
|
-
log3(` ${
|
|
18156
|
-
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}`));
|
|
18157
18291
|
} else {
|
|
18158
|
-
log3(` ${
|
|
18292
|
+
log3(` ${import_chalk70.default.bold("gateway")} ${import_chalk70.default.yellow("not available")} ${import_chalk70.default.dim("(gateway thresholds not evaluated)")}`);
|
|
18159
18293
|
}
|
|
18160
18294
|
const breaches = evaluate(client, gateway, thresholds);
|
|
18161
18295
|
const aborted = ctrl.signal.aborted;
|
|
@@ -18163,7 +18297,7 @@ async function runOpPerf(o = {}) {
|
|
|
18163
18297
|
log3("");
|
|
18164
18298
|
const row = (metric, value, limit, unit) => {
|
|
18165
18299
|
const hit = breaches.find((b) => b.metric === metric);
|
|
18166
|
-
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}`);
|
|
18167
18301
|
};
|
|
18168
18302
|
row("client_p50", client.p50, thresholds.p50_ms, "ms");
|
|
18169
18303
|
row("client_p95", client.p95, thresholds.p95_ms, "ms");
|
|
@@ -18172,12 +18306,12 @@ async function runOpPerf(o = {}) {
|
|
|
18172
18306
|
row("gateway_p50", gateway.p50, thresholds.gateway_p50_ms, "ms");
|
|
18173
18307
|
row("gateway_p95", gateway.p95, thresholds.gateway_p95_ms, "ms");
|
|
18174
18308
|
} else {
|
|
18175
|
-
log3(`[${
|
|
18309
|
+
log3(`[${import_chalk70.default.yellow("SKIP")}] gateway_p50 / gateway_p95 \u2014 ledger rows not available`);
|
|
18176
18310
|
}
|
|
18177
18311
|
log3("");
|
|
18178
|
-
if (aborted) log3(
|
|
18179
|
-
else if (ok) log3(
|
|
18180
|
-
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(", ")}`));
|
|
18181
18315
|
if (o.json) {
|
|
18182
18316
|
const doc = {
|
|
18183
18317
|
kind: "perf",
|
|
@@ -18215,9 +18349,9 @@ async function runOpPerf(o = {}) {
|
|
|
18215
18349
|
if (fixture.provisioned) {
|
|
18216
18350
|
try {
|
|
18217
18351
|
await runnerCall({ action: "perf-teardown", proxy: fixture.proxy }, 120);
|
|
18218
|
-
log3(
|
|
18352
|
+
log3(import_chalk70.default.dim(`fixture ${fixture.proxy} torn down`));
|
|
18219
18353
|
} catch (e) {
|
|
18220
|
-
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`));
|
|
18221
18355
|
process.exitCode = 1;
|
|
18222
18356
|
}
|
|
18223
18357
|
await scrubSandboxKeys(120);
|
|
@@ -18267,79 +18401,79 @@ var OP_COMMANDS = [
|
|
|
18267
18401
|
function renderOpCommands() {
|
|
18268
18402
|
const width = Math.max(...OP_COMMANDS.map((c) => c.cmd.length)) + 10;
|
|
18269
18403
|
const lines = OP_COMMANDS.map((c) => {
|
|
18270
|
-
const left = ` ${
|
|
18404
|
+
const left = ` ${import_chalk72.default.cyan(`apiblaze ${c.cmd}`)}`;
|
|
18271
18405
|
const pad = " ".repeat(Math.max(1, width - c.cmd.length));
|
|
18272
|
-
return `${left}${pad}${c.blurb}${c.extra ? " " +
|
|
18406
|
+
return `${left}${pad}${c.blurb}${c.extra ? " " + import_chalk72.default.dim(`(${c.extra})`) : ""}`;
|
|
18273
18407
|
});
|
|
18274
18408
|
return [
|
|
18275
|
-
|
|
18409
|
+
import_chalk72.default.bold("Operator commands"),
|
|
18276
18410
|
...lines,
|
|
18277
18411
|
"",
|
|
18278
|
-
|
|
18279
|
-
|
|
18280
|
-
|
|
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`."),
|
|
18281
18415
|
"",
|
|
18282
|
-
|
|
18283
|
-
|
|
18284
|
-
|
|
18285
|
-
|
|
18286
|
-
|
|
18287
|
-
|
|
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.")
|
|
18288
18422
|
].join("\n");
|
|
18289
18423
|
}
|
|
18290
18424
|
function printResidue(report, applied) {
|
|
18291
18425
|
const fga = report?.fga ?? {};
|
|
18292
18426
|
const ghosts = report?.ghosts ?? {};
|
|
18293
|
-
console.log(
|
|
18294
|
-
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"));
|
|
18295
18429
|
if (applied) {
|
|
18296
18430
|
const swept = fga?.swept ?? [];
|
|
18297
|
-
if (swept.length === 0) console.log(
|
|
18431
|
+
if (swept.length === 0) console.log(import_chalk72.default.green(" no orphaned stores"));
|
|
18298
18432
|
for (const s of swept) {
|
|
18299
18433
|
console.log(
|
|
18300
|
-
` ${
|
|
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`)}`
|
|
18301
18435
|
);
|
|
18302
18436
|
}
|
|
18303
|
-
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`));
|
|
18304
18438
|
const st = fga?.side_tables;
|
|
18305
|
-
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})` : ""}`));
|
|
18306
18440
|
} else {
|
|
18307
18441
|
const fgaOrphans = fga?.orphans ?? [];
|
|
18308
|
-
if (fgaOrphans.length === 0) console.log(
|
|
18442
|
+
if (fgaOrphans.length === 0) console.log(import_chalk72.default.green(" no orphaned stores"));
|
|
18309
18443
|
for (const s of fgaOrphans) {
|
|
18310
18444
|
const src = s.in_openfga ? "live in OpenFGA" : "Neon tuples only";
|
|
18311
|
-
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)`)}`);
|
|
18312
18446
|
}
|
|
18313
|
-
console.log(
|
|
18447
|
+
console.log(import_chalk72.default.dim(` kept stores: ${(fga?.kept_store_ids ?? []).length}`));
|
|
18314
18448
|
const st = fga?.side_tables;
|
|
18315
|
-
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`));
|
|
18316
18450
|
}
|
|
18317
|
-
for (const e of fga?.errors ?? []) console.log(
|
|
18318
|
-
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"));
|
|
18319
18453
|
if (applied) {
|
|
18320
|
-
if ((ghosts?.ghost_count ?? 0) === 0) console.log(
|
|
18321
|
-
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)`)}`);
|
|
18322
18456
|
} else {
|
|
18323
18457
|
const n = ghosts?.ghost_count ?? 0;
|
|
18324
|
-
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)`)}`));
|
|
18325
18459
|
else {
|
|
18326
|
-
console.log(
|
|
18460
|
+
console.log(import_chalk72.default.yellow(` ${n} ghost tuple(s) referencing entities absent from D1:`));
|
|
18327
18461
|
for (const g of (ghosts.ghosts ?? []).slice(0, 20)) {
|
|
18328
|
-
console.log(
|
|
18462
|
+
console.log(import_chalk72.default.dim(` ${g.object_type}:${g.object_id} ${g.relation} ${g._user}`));
|
|
18329
18463
|
}
|
|
18330
|
-
if (n > 20) console.log(
|
|
18464
|
+
if (n > 20) console.log(import_chalk72.default.dim(` \u2026 and ${n - 20} more`));
|
|
18331
18465
|
}
|
|
18332
18466
|
}
|
|
18333
|
-
for (const e of ghosts?.errors ?? []) console.log(
|
|
18467
|
+
for (const e of ghosts?.errors ?? []) console.log(import_chalk72.default.red(` error: ${e}`));
|
|
18334
18468
|
console.log();
|
|
18335
18469
|
}
|
|
18336
18470
|
async function runOp(sub, opts = {}, view, arg) {
|
|
18337
18471
|
if (!loadCredentials()) {
|
|
18338
|
-
console.log(
|
|
18472
|
+
console.log(import_chalk72.default.dim("Not logged in. Run `apiblaze login`."));
|
|
18339
18473
|
return;
|
|
18340
18474
|
}
|
|
18341
18475
|
if (!isOperatorLogin()) {
|
|
18342
|
-
console.log(
|
|
18476
|
+
console.log(import_chalk72.default.dim("`apiblaze op` is only available to platform operators."));
|
|
18343
18477
|
return;
|
|
18344
18478
|
}
|
|
18345
18479
|
switch (sub) {
|
|
@@ -18408,11 +18542,11 @@ async function runOp(sub, opts = {}, view, arg) {
|
|
|
18408
18542
|
}
|
|
18409
18543
|
if (opts.json) return void console.log(JSON.stringify(out, null, 2));
|
|
18410
18544
|
console.log("");
|
|
18411
|
-
console.log(` Minimum CLI version: ${
|
|
18412
|
-
console.log(` Refuse unversioned: ${out.reject_unversioned ?
|
|
18413
|
-
console.log(` Message: ${out.message ??
|
|
18414
|
-
if (out.updated_at) console.log(
|
|
18415
|
-
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."));
|
|
18416
18550
|
console.log("");
|
|
18417
18551
|
return;
|
|
18418
18552
|
}
|
|
@@ -18430,17 +18564,17 @@ async function runOp(sub, opts = {}, view, arg) {
|
|
|
18430
18564
|
const nSide = (st.soft_deleted_stores ?? 0) + (st.orphan_models ?? 0) + (st.orphan_changelog ?? 0);
|
|
18431
18565
|
printResidue(report, false);
|
|
18432
18566
|
if (nFga + nGhost + nSide === 0) {
|
|
18433
|
-
console.log(
|
|
18567
|
+
console.log(import_chalk72.default.green("Nothing to sweep."));
|
|
18434
18568
|
return;
|
|
18435
18569
|
}
|
|
18436
18570
|
if (!opts.yes) {
|
|
18437
18571
|
const readline3 = await import("readline/promises");
|
|
18438
18572
|
const rl = readline3.createInterface({ input: process.stdin, output: process.stdout });
|
|
18439
18573
|
const answer = await rl.question(
|
|
18440
|
-
|
|
18574
|
+
import_chalk72.default.red(`Delete ${nFga} OpenFGA store(s) + ${nGhost} ghost tuple(s) + ${nSide} Neon side-table row(s)? Type 'sweep' to confirm: `)
|
|
18441
18575
|
);
|
|
18442
18576
|
rl.close();
|
|
18443
|
-
if (answer.trim() !== "sweep") return void console.log(
|
|
18577
|
+
if (answer.trim() !== "sweep") return void console.log(import_chalk72.default.dim("Aborted."));
|
|
18444
18578
|
}
|
|
18445
18579
|
const result = await opCall({ method: "POST", path: "/operator/external-residue/sweep", summary: "external residue sweep" });
|
|
18446
18580
|
if (opts.json) return void console.log(JSON.stringify(result, null, 2));
|
|
@@ -18450,15 +18584,15 @@ async function runOp(sub, opts = {}, view, arg) {
|
|
|
18450
18584
|
case "mark": {
|
|
18451
18585
|
const label3 = (view ?? "").trim();
|
|
18452
18586
|
if (!label3) {
|
|
18453
|
-
console.log(
|
|
18587
|
+
console.log(import_chalk72.default.red("Give the change a name:") + import_chalk72.default.cyan(' apiblaze op mark "cached tenant count"'));
|
|
18454
18588
|
return;
|
|
18455
18589
|
}
|
|
18456
18590
|
const res = await opCall({ method: "POST", path: "/operator/latency/mark", body: { label: label3 }, summary: "record change marker" });
|
|
18457
18591
|
const ts2 = new Date(res?.marker?.ts ?? Date.now()).toISOString();
|
|
18458
|
-
console.log(
|
|
18459
|
-
Marked: `) +
|
|
18460
|
-
console.log(
|
|
18461
|
-
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");
|
|
18462
18596
|
return;
|
|
18463
18597
|
}
|
|
18464
18598
|
// ── MONEY (specs/TQC §11 "Operator console") — commands/op-money.ts ────
|
|
@@ -18469,14 +18603,14 @@ async function runOp(sub, opts = {}, view, arg) {
|
|
|
18469
18603
|
if (which === "list") return void await opWalletList(opts);
|
|
18470
18604
|
if (which === "inspect") return void await opWalletInspect(arg, opts);
|
|
18471
18605
|
if (which in WALLET_MUTATIONS) return void opWalletMutation(which, arg, opts);
|
|
18472
|
-
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(" | ")}.`));
|
|
18473
18607
|
}
|
|
18474
18608
|
case "money": {
|
|
18475
18609
|
const which = (view ?? "").trim().toLowerCase();
|
|
18476
18610
|
if (which === "exposure") return void await opMoneyExposure(opts);
|
|
18477
18611
|
if (which === "journal") return void await opLedgerShow(arg, opts);
|
|
18478
18612
|
process.exitCode = 1;
|
|
18479
|
-
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("."));
|
|
18480
18614
|
}
|
|
18481
18615
|
// ── MONEY LEDGER (specs/noreconciliations.MD §6/§7) — commands/op-ledger.ts ──
|
|
18482
18616
|
case "ledger": {
|
|
@@ -18487,20 +18621,20 @@ async function runOp(sub, opts = {}, view, arg) {
|
|
|
18487
18621
|
if (which === "cut") return void await opLedgerCut(arg, opts);
|
|
18488
18622
|
if (which === "backfill") return void await opLedgerBackfill(opts);
|
|
18489
18623
|
process.exitCode = 1;
|
|
18490
|
-
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>.`));
|
|
18491
18625
|
}
|
|
18492
18626
|
// ── MAXIMUM RATE (project ceiling) requests — commands/op-ceiling.ts ──
|
|
18493
18627
|
case "ceiling": {
|
|
18494
18628
|
const which = (view ?? "list").trim().toLowerCase();
|
|
18495
18629
|
if (which === "list") return void await opCeilingList(opts);
|
|
18496
18630
|
if (which === "approve" || which === "deny") return void await opCeilingDecide(which, arg, opts);
|
|
18497
|
-
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".`));
|
|
18498
18632
|
}
|
|
18499
18633
|
case "incident": {
|
|
18500
18634
|
const which = (view ?? "status").trim().toLowerCase();
|
|
18501
18635
|
if (which === "status" || which === "degraded-mode") return void await opIncidentStatus(opts);
|
|
18502
18636
|
if (which === "allow" || which === "deny" || which === "extend") return void opIncidentMutation(which, opts);
|
|
18503
|
-
return void console.log(
|
|
18637
|
+
return void console.log(import_chalk72.default.red(`Unknown: apiblaze op incident ${which}. Use status | allow | deny | extend.`));
|
|
18504
18638
|
}
|
|
18505
18639
|
// ── LATENCY (specs/latency/ §7.4) ─────────────────────────────────────
|
|
18506
18640
|
// The blame table first: level 1 (which feature) and level 2 (what inside
|
|
@@ -18550,29 +18684,29 @@ async function runOp(sub, opts = {}, view, arg) {
|
|
|
18550
18684
|
const data = await opCall({ method: "GET", path: `/operator/latency/grades${q}`, summary: "latency grades" });
|
|
18551
18685
|
if (opts.json) return void console.log(JSON.stringify(data, null, 2));
|
|
18552
18686
|
const t = data.thresholds_ms;
|
|
18553
|
-
console.log(
|
|
18554
|
-
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+
|
|
18555
18689
|
`));
|
|
18556
|
-
console.log(
|
|
18690
|
+
console.log(import_chalk72.default.dim(" date reqs excellent okay bad terrible"));
|
|
18557
18691
|
for (const d of data.days ?? []) {
|
|
18558
18692
|
const p = d.pct;
|
|
18559
|
-
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)}%`);
|
|
18560
18694
|
console.log(
|
|
18561
|
-
` ${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)}`
|
|
18562
18696
|
);
|
|
18563
18697
|
}
|
|
18564
18698
|
const cul = data.culprits ?? [];
|
|
18565
18699
|
if (cul.length) {
|
|
18566
|
-
console.log(
|
|
18567
|
-
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"));
|
|
18568
18702
|
for (const r of cul.slice(0, 12)) {
|
|
18569
18703
|
if (!r.bad && !r.terrible) continue;
|
|
18570
18704
|
console.log(
|
|
18571
|
-
` ${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)}`
|
|
18572
18706
|
);
|
|
18573
18707
|
}
|
|
18574
18708
|
}
|
|
18575
|
-
if (data.caveat) console.log(
|
|
18709
|
+
if (data.caveat) console.log(import_chalk72.default.dim(`
|
|
18576
18710
|
\u26A0 ${data.caveat}
|
|
18577
18711
|
`));
|
|
18578
18712
|
return;
|
|
@@ -18581,24 +18715,24 @@ async function runOp(sub, opts = {}, view, arg) {
|
|
|
18581
18715
|
const data = await opCall({ method: "GET", path: `/operator/latency/compare${q}`, summary: "latency before/after" });
|
|
18582
18716
|
if (opts.json) return void console.log(JSON.stringify(data, null, 2));
|
|
18583
18717
|
const b = data.before, a = data.after, d = data.delta;
|
|
18584
|
-
console.log(
|
|
18585
|
-
Before vs after: `) +
|
|
18586
|
-
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
|
|
18587
18721
|
`));
|
|
18588
18722
|
const row = (name, before, after, delta) => {
|
|
18589
18723
|
const arrow = delta === 0 ? "=" : delta < 0 ? "\u2193" : "\u2191";
|
|
18590
18724
|
const txt = `${String(before).padStart(6)}ms \u2192${String(after).padStart(7)}ms ${arrow}${Math.abs(delta)}ms`;
|
|
18591
|
-
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)}`);
|
|
18592
18726
|
};
|
|
18593
|
-
console.log(
|
|
18727
|
+
console.log(import_chalk72.default.dim(" metric before after change"));
|
|
18594
18728
|
row("total p50", b.total_p50, a.total_p50, d.total_p50);
|
|
18595
18729
|
row("total p95", b.total_p95, a.total_p95, d.total_p95);
|
|
18596
18730
|
row("apiblaze overhead p50", b.gw_p50, a.gw_p50, d.gw_p50);
|
|
18597
18731
|
row("apiblaze overhead p95", b.gw_p95, a.gw_p95, d.gw_p95);
|
|
18598
|
-
console.log(
|
|
18732
|
+
console.log(import_chalk72.default.dim(`
|
|
18599
18733
|
requests: ${b.requests} before \xB7 ${a.requests} after`));
|
|
18600
18734
|
for (const w of data.warnings ?? []) {
|
|
18601
|
-
console.log((data.trustworthy ?
|
|
18735
|
+
console.log((data.trustworthy ? import_chalk72.default.dim : import_chalk72.default.yellow)(` ${data.trustworthy ? "\xB7" : "\u26A0"} ${w}`));
|
|
18602
18736
|
}
|
|
18603
18737
|
console.log("");
|
|
18604
18738
|
return;
|
|
@@ -18607,19 +18741,19 @@ Before vs after: `) + import_chalk71.default.cyan(data.marker.label));
|
|
|
18607
18741
|
const data = await opCall({ method: "GET", path: `/operator/latency/slow${q}`, summary: "slowest requests" });
|
|
18608
18742
|
if (opts.json) return void console.log(JSON.stringify(data, null, 2));
|
|
18609
18743
|
const rows2 = data?.rows ?? [];
|
|
18610
|
-
if (!rows2.length) return void console.log(
|
|
18611
|
-
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(`
|
|
18612
18746
|
Slowest requests \u2014 last ${data.window_hours}h, over ${data.min_ms}ms
|
|
18613
18747
|
`));
|
|
18614
|
-
console.log(
|
|
18748
|
+
console.log(import_chalk72.default.dim(" total ours theirs blame request id"));
|
|
18615
18749
|
for (const r of rows2.slice(0, 30)) {
|
|
18616
18750
|
console.log(
|
|
18617
|
-
` ${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 || "")}`
|
|
18618
18752
|
);
|
|
18619
18753
|
}
|
|
18620
|
-
console.log(
|
|
18754
|
+
console.log(import_chalk72.default.dim(`
|
|
18621
18755
|
The last column is the request id (Cloudflare calls it a "cf-ray"). Look one up with`));
|
|
18622
|
-
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.
|
|
18623
18757
|
`));
|
|
18624
18758
|
return;
|
|
18625
18759
|
}
|
|
@@ -18627,18 +18761,18 @@ Slowest requests \u2014 last ${data.window_hours}h, over ${data.min_ms}ms
|
|
|
18627
18761
|
const data = await opCall({ method: "GET", path: `/operator/latency/llm${q}`, summary: "llm latency" });
|
|
18628
18762
|
if (opts.json) return void console.log(JSON.stringify(data, null, 2));
|
|
18629
18763
|
const rows2 = data?.rows ?? [];
|
|
18630
|
-
if (!rows2.length) return void console.log(
|
|
18631
|
-
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(`
|
|
18632
18766
|
LLM timing \u2014 last ${data.window_hours}h
|
|
18633
18767
|
`));
|
|
18634
|
-
console.log(
|
|
18768
|
+
console.log(import_chalk72.default.dim(" turns turn p95 ttfc p95 gen p95 reserve p95 in/out tokens p95 model"));
|
|
18635
18769
|
for (const r of rows2) {
|
|
18636
18770
|
const n = r.turns ?? r.requests ?? 0;
|
|
18637
18771
|
console.log(
|
|
18638
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 || "-"}`
|
|
18639
18773
|
);
|
|
18640
18774
|
}
|
|
18641
|
-
console.log(
|
|
18775
|
+
console.log(import_chalk72.default.dim(`
|
|
18642
18776
|
${data.note}
|
|
18643
18777
|
`));
|
|
18644
18778
|
return;
|
|
@@ -18649,26 +18783,26 @@ LLM timing \u2014 last ${data.window_hours}h
|
|
|
18649
18783
|
]);
|
|
18650
18784
|
if (opts.json) return void console.log(JSON.stringify({ blame, summary }, null, 2));
|
|
18651
18785
|
const ov = summary?.apiblaze_overhead_ms ?? {};
|
|
18652
|
-
console.log(
|
|
18786
|
+
console.log(import_chalk72.default.bold(`
|
|
18653
18787
|
Latency \u2014 last ${summary?.window_hours ?? "?"}h, ${Number(summary?.requests ?? 0).toLocaleString()} requests
|
|
18654
18788
|
`));
|
|
18655
|
-
console.log(` ${
|
|
18656
|
-
console.log(` ${
|
|
18657
|
-
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)
|
|
18658
18792
|
`));
|
|
18659
18793
|
const rows = blame?.blame ?? [];
|
|
18660
|
-
if (!rows.length) return void console.log(
|
|
18661
|
-
console.log(
|
|
18662
|
-
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"));
|
|
18663
18797
|
for (const r of rows.slice(0, 15)) {
|
|
18664
18798
|
const share = `${(r.share * 100).toFixed(1)}%`;
|
|
18665
|
-
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)}`);
|
|
18666
18800
|
}
|
|
18667
18801
|
console.log("");
|
|
18668
18802
|
return;
|
|
18669
18803
|
}
|
|
18670
18804
|
default:
|
|
18671
|
-
console.log(
|
|
18805
|
+
console.log(import_chalk72.default.red(`Unknown op subcommand '${sub}'. Run \`apiblaze op\` for the menu.`));
|
|
18672
18806
|
}
|
|
18673
18807
|
}
|
|
18674
18808
|
function printDistance(data) {
|
|
@@ -18676,17 +18810,17 @@ function printDistance(data) {
|
|
|
18676
18810
|
const p = (v) => `${n(v).toFixed(1)}%`;
|
|
18677
18811
|
const msf = (v) => `${Math.round(n(v))}ms`;
|
|
18678
18812
|
const thr = n(data?.threshold_ms) || 50;
|
|
18679
|
-
const
|
|
18680
|
-
console.log(
|
|
18681
|
-
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
|
|
18682
18816
|
`));
|
|
18683
18817
|
console.log(
|
|
18684
|
-
` ${
|
|
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.`
|
|
18685
18819
|
);
|
|
18686
|
-
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)
|
|
18687
18821
|
`));
|
|
18688
18822
|
const nm = data?.network_ms ?? {};
|
|
18689
|
-
console.log(
|
|
18823
|
+
console.log(import_chalk72.default.dim(" network ms calls p50 p95 p99 >thr"));
|
|
18690
18824
|
const line = (label3, r) => {
|
|
18691
18825
|
if (!r) return;
|
|
18692
18826
|
console.log(
|
|
@@ -18700,32 +18834,32 @@ Durable Object distance \u2014 last ${data?.window_hours ?? "?"}h${scope}, ${n(d
|
|
|
18700
18834
|
line("kind: throttle", nm.throttle);
|
|
18701
18835
|
const modes = data?.by_mode ?? [];
|
|
18702
18836
|
if (modes.length) {
|
|
18703
|
-
console.log(
|
|
18837
|
+
console.log(import_chalk72.default.dim("\n mode calls share p95 >thr"));
|
|
18704
18838
|
for (const m of modes) {
|
|
18705
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)}`);
|
|
18706
18840
|
}
|
|
18707
18841
|
}
|
|
18708
18842
|
const routes = data?.routes ?? [];
|
|
18709
18843
|
if (routes.length) {
|
|
18710
|
-
console.log(
|
|
18711
|
-
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"));
|
|
18712
18846
|
for (const r of routes) {
|
|
18713
18847
|
const cross = r.worker_colo !== r.object_colo;
|
|
18714
18848
|
const route = `${r.worker_colo} \u2192 ${r.object_colo}`;
|
|
18715
|
-
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)}`);
|
|
18716
18850
|
}
|
|
18717
18851
|
}
|
|
18718
18852
|
const teams = data?.teams ?? [];
|
|
18719
18853
|
if (teams.length) {
|
|
18720
|
-
console.log(
|
|
18854
|
+
console.log(import_chalk72.default.bold(`
|
|
18721
18855
|
Teams paying the most distance (> ${thr} ms)`));
|
|
18722
|
-
console.log(
|
|
18856
|
+
console.log(import_chalk72.default.dim(" >thr calls of calls object colo worker colos team"));
|
|
18723
18857
|
for (const t of teams) {
|
|
18724
18858
|
const w = (t.top_worker_colos ?? []).map((x) => `${x.colo} ${n(x.problem_calls).toLocaleString()}`).join(", ");
|
|
18725
|
-
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)}`);
|
|
18726
18860
|
}
|
|
18727
18861
|
} else if (!n(data?.do_calls)) {
|
|
18728
|
-
console.log(
|
|
18862
|
+
console.log(import_chalk72.default.dim("\n No Durable Object calls in that window (warm leases write no gate rows)."));
|
|
18729
18863
|
}
|
|
18730
18864
|
console.log("");
|
|
18731
18865
|
}
|
|
@@ -18791,7 +18925,7 @@ agent.command("authz").description("Chat to design and turn on access rules for
|
|
|
18791
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)));
|
|
18792
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)));
|
|
18793
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)));
|
|
18794
|
-
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");
|
|
18795
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");
|
|
18796
18930
|
var mcpPrompts = mcpCmd.command("prompts").description("Prompts the MCP server offers (drafted by the MCP agent; editable here)");
|
|
18797
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)));
|
|
@@ -18800,7 +18934,7 @@ mcpCommon(mcpPrompts.command("set").description("Add or replace a prompt from a
|
|
|
18800
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)));
|
|
18801
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");
|
|
18802
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)));
|
|
18803
|
-
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)));
|
|
18804
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)));
|
|
18805
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)));
|
|
18806
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) => {
|
|
@@ -18824,7 +18958,7 @@ program.command("dev").description(`Put the app running on THIS machine on the i
|
|
|
18824
18958
|
try {
|
|
18825
18959
|
const resolved = parseInt(port ?? opts.port, 10);
|
|
18826
18960
|
if (Number.isNaN(resolved)) {
|
|
18827
|
-
console.error(
|
|
18961
|
+
console.error(import_chalk73.default.red(`Invalid port: ${port ?? opts.port}`));
|
|
18828
18962
|
process.exit(1);
|
|
18829
18963
|
}
|
|
18830
18964
|
await runDev({ ...opts, port: resolved });
|
|
@@ -18936,6 +19070,18 @@ program.command("preapprove").description("Allow an email or company domain to s
|
|
|
18936
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, {})));
|
|
18937
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)));
|
|
18938
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)));
|
|
18939
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)));
|
|
18940
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)));
|
|
18941
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)));
|
|
@@ -18994,7 +19140,7 @@ function groupedCommandHelp() {
|
|
|
18994
19140
|
const sub = byName.get(e.parent)?.commands.find((s) => s.name() === e.sub);
|
|
18995
19141
|
return sub ? ` ${helpLabel(e).padEnd(width)}${sub.description()}` : "";
|
|
18996
19142
|
}).filter(Boolean).join("\n");
|
|
18997
|
-
return `${
|
|
19143
|
+
return `${import_chalk73.default.bold(g.title)}
|
|
18998
19144
|
${rows}`;
|
|
18999
19145
|
}).join("\n\n");
|
|
19000
19146
|
}
|
|
@@ -19007,7 +19153,7 @@ program.configureHelp({
|
|
|
19007
19153
|
program.addHelpText("before", () => isBetaCached() ? `${betaNotice()}
|
|
19008
19154
|
` : "");
|
|
19009
19155
|
program.addHelpText("after", () => `
|
|
19010
|
-
${
|
|
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.`)}
|
|
19011
19157
|
|
|
19012
19158
|
${groupedCommandHelp()}
|
|
19013
19159
|
|
|
@@ -19044,14 +19190,14 @@ async function recoverStaleTeam() {
|
|
|
19044
19190
|
const { resolveLinkedTeam: resolveLinkedTeam2 } = await Promise.resolve().then(() => (init_team(), team_exports));
|
|
19045
19191
|
const linked = await resolveLinkedTeam2({ preferredId: creds.teamId, interactive: !!process.stdin.isTTY });
|
|
19046
19192
|
if (!linked) {
|
|
19047
|
-
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."));
|
|
19048
19194
|
return;
|
|
19049
19195
|
}
|
|
19050
19196
|
if (linked.teamId === creds.teamId) return;
|
|
19051
19197
|
const next = { ...creds, teamId: linked.teamId, teamName: linked.teamName };
|
|
19052
19198
|
delete next.activeTenant;
|
|
19053
19199
|
saveCredentials(next);
|
|
19054
|
-
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.`));
|
|
19055
19201
|
} catch {
|
|
19056
19202
|
}
|
|
19057
19203
|
}
|
|
@@ -19063,28 +19209,28 @@ async function printError(err) {
|
|
|
19063
19209
|
if (err instanceof ApiError) {
|
|
19064
19210
|
const data = err.body;
|
|
19065
19211
|
const extra = [data?.body?.reason, data?.body?.details, data?.details, data?.body?.error].find((x) => typeof x === "string" && x && x !== err.message);
|
|
19066
|
-
console.error(
|
|
19212
|
+
console.error(import_chalk73.default.red(`
|
|
19067
19213
|
API error (${err.status}): ${err.message}${extra ? ` \u2014 ${extra}` : ""}`));
|
|
19068
19214
|
if (err.status === 403 || err.status === 404) {
|
|
19069
19215
|
await recoverStaleTeam();
|
|
19070
19216
|
}
|
|
19071
19217
|
} else if (err instanceof Error) {
|
|
19072
|
-
console.error(
|
|
19218
|
+
console.error(import_chalk73.default.red(`
|
|
19073
19219
|
Error: ${err.message}`));
|
|
19074
19220
|
} else {
|
|
19075
|
-
console.error(
|
|
19221
|
+
console.error(import_chalk73.default.red("\nUnknown error"));
|
|
19076
19222
|
}
|
|
19077
19223
|
}
|
|
19078
19224
|
function hintStaleCacheOnUnknownOption(cmd) {
|
|
19079
19225
|
cmd.exitOverride((err) => {
|
|
19080
19226
|
if (err.code === "commander.unknownOption") {
|
|
19081
|
-
console.error(
|
|
19227
|
+
console.error(import_chalk73.default.yellow(
|
|
19082
19228
|
`
|
|
19083
19229
|
This is ${B.cli} v${version}. If that option is documented but this build
|
|
19084
19230
|
does not have it, your npx cache is serving an older copy \u2014 re-run as:
|
|
19085
19231
|
`
|
|
19086
19232
|
));
|
|
19087
|
-
console.error(
|
|
19233
|
+
console.error(import_chalk73.default.cyan(` npx ${B.cli}@latest ${process.argv.slice(2).join(" ")}
|
|
19088
19234
|
`));
|
|
19089
19235
|
}
|
|
19090
19236
|
process.exit(err.exitCode);
|