apiblaze 0.21.11 → 0.21.14
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 +951 -615
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -9,9 +9,9 @@ var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
|
9
9
|
var __esm = (fn, res) => function __init() {
|
|
10
10
|
return fn && (res = (0, fn[__getOwnPropNames(fn)[0]])(fn = 0)), res;
|
|
11
11
|
};
|
|
12
|
-
var __export = (
|
|
12
|
+
var __export = (target2, all) => {
|
|
13
13
|
for (var name in all)
|
|
14
|
-
__defProp(
|
|
14
|
+
__defProp(target2, name, { get: all[name], enumerable: true });
|
|
15
15
|
};
|
|
16
16
|
var __copyProps = (to, from, except, desc) => {
|
|
17
17
|
if (from && typeof from === "object" || typeof from === "function") {
|
|
@@ -21,12 +21,12 @@ var __copyProps = (to, from, except, desc) => {
|
|
|
21
21
|
}
|
|
22
22
|
return to;
|
|
23
23
|
};
|
|
24
|
-
var __toESM = (mod, isNodeMode,
|
|
24
|
+
var __toESM = (mod, isNodeMode, target2) => (target2 = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
|
|
25
25
|
// If the importer is in node compatibility mode or this is not an ESM
|
|
26
26
|
// file that has been converted to a CommonJS file using a Babel-
|
|
27
27
|
// compatible transform (i.e. "__esModule" has not been set), then set
|
|
28
28
|
// "default" to the CommonJS "module.exports" for node compatibility.
|
|
29
|
-
isNodeMode || !mod || !mod.__esModule ? __defProp(
|
|
29
|
+
isNodeMode || !mod || !mod.__esModule ? __defProp(target2, "default", { value: mod, enumerable: true }) : target2,
|
|
30
30
|
mod
|
|
31
31
|
));
|
|
32
32
|
|
|
@@ -524,7 +524,7 @@ function renderTrace() {
|
|
|
524
524
|
const url = `https://api.apiblaze.com/${CONTROL_API_VERSION}/prod${e.path}`;
|
|
525
525
|
const masked = maskBody(e.body);
|
|
526
526
|
const hasBody = e.method !== "GET" && masked !== void 0;
|
|
527
|
-
console.error(import_chalk6.default.green(` curl -sS -X ${e.method} ${url}
|
|
527
|
+
console.error(import_chalk6.default.green(` curl -sS -X ${e.method} '${url}' \\`));
|
|
528
528
|
console.error(import_chalk6.default.green(' -H "X-API-Key: $APIBLAZE_CONTROLPLANE_APIKEY"' + (hasBody ? " \\" : "")));
|
|
529
529
|
if (hasBody) {
|
|
530
530
|
console.error(import_chalk6.default.green(" -H 'Content-Type: application/json' \\"));
|
|
@@ -978,7 +978,7 @@ async function runAdminsAdd(email, opts) {
|
|
|
978
978
|
else if (!opts.quiet) {
|
|
979
979
|
console.log(import_chalk15.default.dim(" Reload the Users & Groups widget \u2014 access flips from \u201Cpending\u201D to ready."));
|
|
980
980
|
console.log(import_chalk15.default.dim(` The widget's admin lane takes a personal key, one per admin \u2014 mint it with:`));
|
|
981
|
-
console.log(import_chalk15.default.dim(` npx apiblaze apikeys mint --for ${email} --tenant ${tenant2}`));
|
|
981
|
+
console.log(import_chalk15.default.dim(` npx apiblaze apikeys mint --for ${email} --tenant ${tenant2} --admin`));
|
|
982
982
|
console.log(import_chalk15.default.dim(" (a backend/tenant key with X-End-User-Id is refused there: widget_key_must_be_personal)."));
|
|
983
983
|
}
|
|
984
984
|
return out ?? { ok: true };
|
|
@@ -1123,7 +1123,7 @@ async function createTenantRow(teamId) {
|
|
|
1123
1123
|
const { name } = await inquirer3.prompt([{
|
|
1124
1124
|
type: "input",
|
|
1125
1125
|
name: "name",
|
|
1126
|
-
message: `Tenant name ${
|
|
1126
|
+
message: `Tenant name ${import_chalk46.default.dim("(lowercase letters/numbers; globally unique \u2014 becomes {name}.portal.apiblaze.com)")}:`,
|
|
1127
1127
|
validate: (s) => /^[a-z0-9]+$/.test(s.trim()) ? true : "lowercase letters and numbers only"
|
|
1128
1128
|
}]);
|
|
1129
1129
|
const slug = name.trim();
|
|
@@ -1138,7 +1138,7 @@ async function createTenantRow(teamId) {
|
|
|
1138
1138
|
} catch (err) {
|
|
1139
1139
|
const msg = err instanceof Error ? err.message : String(err);
|
|
1140
1140
|
if (/taken|unique|exists|reserved|conflict/i.test(msg)) {
|
|
1141
|
-
console.log(
|
|
1141
|
+
console.log(import_chalk46.default.yellow(` "${slug}" is not available (tenant names are global): ${msg}`));
|
|
1142
1142
|
continue;
|
|
1143
1143
|
}
|
|
1144
1144
|
throw err;
|
|
@@ -1155,11 +1155,11 @@ async function addApiblazeHostedLogin(teamId, tenant2, opts = {}) {
|
|
|
1155
1155
|
pageSize: 10,
|
|
1156
1156
|
choices: [
|
|
1157
1157
|
...PROVIDERS2.map((p) => ({ name: p.label, value: p.id })),
|
|
1158
|
-
...opts.allowSkip ? [new inquirer3.Separator(), { name:
|
|
1158
|
+
...opts.allowSkip ? [new inquirer3.Separator(), { name: import_chalk46.default.dim("Skip for now \u2014 set up a login method later"), value: null }] : []
|
|
1159
1159
|
]
|
|
1160
1160
|
}]);
|
|
1161
1161
|
if (!provider) {
|
|
1162
|
-
console.log(
|
|
1162
|
+
console.log(import_chalk46.default.dim(" No login method yet \u2014 add one later under Login methods."));
|
|
1163
1163
|
return false;
|
|
1164
1164
|
}
|
|
1165
1165
|
const opt = PROVIDERS2.find((p) => p.id === provider);
|
|
@@ -1190,7 +1190,7 @@ async function addApiblazeHostedLogin(teamId, tenant2, opts = {}) {
|
|
|
1190
1190
|
if (a.callbacks.trim()) clientBody.authorizedCallbackUrls = a.callbacks.split(/\s*,\s*/).map((s) => s.trim()).filter(Boolean);
|
|
1191
1191
|
if (a.scopes.trim() && opt.own) providerBody.scopes = a.scopes.split(/\s+/).filter(Boolean);
|
|
1192
1192
|
}
|
|
1193
|
-
const spinner = (0,
|
|
1193
|
+
const spinner = (0, import_ora18.default)("Setting up login...").start();
|
|
1194
1194
|
try {
|
|
1195
1195
|
const client = await admin({ method: "POST", path: `${base2}/app-clients`, body: clientBody, summary: `Create login for ${tenant2}` });
|
|
1196
1196
|
const clientId = client?.clientId ?? client?.client_id;
|
|
@@ -1205,27 +1205,27 @@ async function addApiblazeHostedLogin(teamId, tenant2, opts = {}) {
|
|
|
1205
1205
|
async function createTenantInteractive(teamId) {
|
|
1206
1206
|
const { default: inquirer3 } = await import("inquirer");
|
|
1207
1207
|
const tenant2 = await createTenantRow(teamId);
|
|
1208
|
-
console.log(
|
|
1208
|
+
console.log(import_chalk46.default.green(` Tenant ${import_chalk46.default.bold(tenant2)} created.`));
|
|
1209
1209
|
const { users } = await inquirer3.prompt([{
|
|
1210
1210
|
type: "confirm",
|
|
1211
1211
|
name: "users",
|
|
1212
1212
|
default: false,
|
|
1213
|
-
message: `Enable Users & groups? ${
|
|
1213
|
+
message: `Enable Users & groups? ${import_chalk46.default.dim(`(identity/key management at iam.apiblaze.com)`)}`
|
|
1214
1214
|
}]);
|
|
1215
1215
|
if (users) {
|
|
1216
1216
|
await admin({ method: "PATCH", path: `/teams/${encodeURIComponent(teamId)}/tenants/${encodeURIComponent(tenant2)}/iam`, body: { enabled: true }, summary: "Enable Users & groups" }).catch(() => {
|
|
1217
1217
|
});
|
|
1218
|
-
console.log(
|
|
1218
|
+
console.log(import_chalk46.default.green(" Users & groups enabled."));
|
|
1219
1219
|
}
|
|
1220
1220
|
await addApiblazeHostedLogin(teamId, tenant2, { allowSkip: true });
|
|
1221
1221
|
return tenant2;
|
|
1222
1222
|
}
|
|
1223
|
-
var
|
|
1223
|
+
var import_chalk46, import_ora18, PROVIDERS2, DEFAULT_SCOPES;
|
|
1224
1224
|
var init_tenant_create = __esm({
|
|
1225
1225
|
"src/lib/tenant-create.ts"() {
|
|
1226
1226
|
"use strict";
|
|
1227
|
-
|
|
1228
|
-
|
|
1227
|
+
import_chalk46 = __toESM(require("chalk"));
|
|
1228
|
+
import_ora18 = __toESM(require("ora"));
|
|
1229
1229
|
init_admin();
|
|
1230
1230
|
PROVIDERS2 = [
|
|
1231
1231
|
{ id: "apiblaze", label: "APIblaze (via GitHub) \u2014 zero setup, recommended", own: false },
|
|
@@ -1263,10 +1263,10 @@ async function fetchPage2(teamId, q) {
|
|
|
1263
1263
|
}
|
|
1264
1264
|
function label(t, defaultTenant, active) {
|
|
1265
1265
|
const tags = [
|
|
1266
|
-
t.tenant_name === active ?
|
|
1267
|
-
t.tenant_name === defaultTenant ?
|
|
1266
|
+
t.tenant_name === active ? import_chalk47.default.cyan("active scope") : "",
|
|
1267
|
+
t.tenant_name === defaultTenant ? import_chalk47.default.dim("team default") : ""
|
|
1268
1268
|
].filter(Boolean).join(", ");
|
|
1269
|
-
const disp = t.display_name && t.display_name !== t.tenant_name ?
|
|
1269
|
+
const disp = t.display_name && t.display_name !== t.tenant_name ? import_chalk47.default.dim(` ${t.display_name}`) : "";
|
|
1270
1270
|
return `${t.tenant_name}${disp}${tags ? ` (${tags})` : ""}`;
|
|
1271
1271
|
}
|
|
1272
1272
|
async function pickTenant(teamId, opts = {}) {
|
|
@@ -1274,14 +1274,14 @@ async function pickTenant(teamId, opts = {}) {
|
|
|
1274
1274
|
const active = loadCredentials()?.activeTenant;
|
|
1275
1275
|
let q = opts.initialQuery ?? "";
|
|
1276
1276
|
for (; ; ) {
|
|
1277
|
-
const spinner = (0,
|
|
1277
|
+
const spinner = (0, import_ora19.default)(q ? `Searching tenants for "${q}"...` : "Loading tenants...").start();
|
|
1278
1278
|
const page = await fetchPage2(teamId, q).finally(() => spinner.stop());
|
|
1279
1279
|
if (!page.total && !q) {
|
|
1280
1280
|
if (opts.allowCreate) {
|
|
1281
1281
|
const { make } = await inquirer3.prompt([{ type: "confirm", name: "make", message: "No tenants yet \u2014 create one?", default: true }]);
|
|
1282
1282
|
if (make) return await createTenantInline(teamId);
|
|
1283
1283
|
}
|
|
1284
|
-
console.error(
|
|
1284
|
+
console.error(import_chalk47.default.red("This team has no tenants. Create one with `apiblaze tenant create`."));
|
|
1285
1285
|
return null;
|
|
1286
1286
|
}
|
|
1287
1287
|
const truncated = page.total > page.rows.length;
|
|
@@ -1290,7 +1290,7 @@ async function pickTenant(teamId, opts = {}) {
|
|
|
1290
1290
|
value: t.tenant_name
|
|
1291
1291
|
}));
|
|
1292
1292
|
if (truncated || q) {
|
|
1293
|
-
choices.push(new inquirer3.Separator(
|
|
1293
|
+
choices.push(new inquirer3.Separator(import_chalk47.default.dim(
|
|
1294
1294
|
truncated ? `showing ${page.rows.length} of ${page.total}${q ? ` matching "${q}"` : ""} \u2014 search to narrow` : `matches for "${q}"`
|
|
1295
1295
|
)));
|
|
1296
1296
|
choices.push({ name: `\u{1F50D} Search${q ? " again" : ""}\u2026`, value: "\0search" });
|
|
@@ -1324,12 +1324,12 @@ async function createTenantInline(teamId) {
|
|
|
1324
1324
|
const { createTenantInteractive: createTenantInteractive2 } = await Promise.resolve().then(() => (init_tenant_create(), tenant_create_exports));
|
|
1325
1325
|
return createTenantInteractive2(teamId);
|
|
1326
1326
|
}
|
|
1327
|
-
var
|
|
1327
|
+
var import_chalk47, import_ora19, PAGE;
|
|
1328
1328
|
var init_tenant_pick = __esm({
|
|
1329
1329
|
"src/lib/tenant-pick.ts"() {
|
|
1330
1330
|
"use strict";
|
|
1331
|
-
|
|
1332
|
-
|
|
1331
|
+
import_chalk47 = __toESM(require("chalk"));
|
|
1332
|
+
import_ora19 = __toESM(require("ora"));
|
|
1333
1333
|
init_admin();
|
|
1334
1334
|
init_auth();
|
|
1335
1335
|
PAGE = 15;
|
|
@@ -1339,10 +1339,10 @@ var init_tenant_pick = __esm({
|
|
|
1339
1339
|
// src/index.ts
|
|
1340
1340
|
var import_fs5 = __toESM(require("fs"));
|
|
1341
1341
|
var import_commander = require("commander");
|
|
1342
|
-
var
|
|
1342
|
+
var import_chalk64 = __toESM(require("chalk"));
|
|
1343
1343
|
|
|
1344
1344
|
// package.json
|
|
1345
|
-
var version = "0.21.
|
|
1345
|
+
var version = "0.21.14";
|
|
1346
1346
|
|
|
1347
1347
|
// src/index.ts
|
|
1348
1348
|
init_types();
|
|
@@ -1972,8 +1972,10 @@ function randomProxyName() {
|
|
|
1972
1972
|
// src/lib/tunnel-client.ts
|
|
1973
1973
|
var import_ws = __toESM(require("ws"));
|
|
1974
1974
|
var CHUNK_BYTES = 512 * 1024;
|
|
1975
|
-
var PING_INTERVAL_MS =
|
|
1975
|
+
var PING_INTERVAL_MS = 3e4;
|
|
1976
|
+
var SILENCE_LIMIT_MS = 75e3;
|
|
1976
1977
|
var MAX_RECONNECT_DELAY_MS = 15e3;
|
|
1978
|
+
var TOKEN_REFRESH_AFTER_MS = 24e4;
|
|
1977
1979
|
var OFFLINE_CODES = /* @__PURE__ */ new Set(["ECONNREFUSED", "ECONNRESET", "ENOTFOUND", "EHOSTUNREACH"]);
|
|
1978
1980
|
var STRIP_HEADERS = /* @__PURE__ */ new Set([
|
|
1979
1981
|
"host",
|
|
@@ -1997,28 +1999,52 @@ function encodeBody(buf) {
|
|
|
1997
1999
|
return { value: buf.toString("utf8"), encoding: "utf8" };
|
|
1998
2000
|
}
|
|
1999
2001
|
function startTunnelClient(opts) {
|
|
2000
|
-
const
|
|
2002
|
+
const target2 = `http://127.0.0.1:${opts.localPort}`;
|
|
2001
2003
|
const inflight = /* @__PURE__ */ new Map();
|
|
2002
2004
|
let ws = null;
|
|
2003
2005
|
let closed = false;
|
|
2004
2006
|
let reconnects = 0;
|
|
2005
2007
|
let pingTimer;
|
|
2008
|
+
let connectUrl = opts.connectUrl;
|
|
2009
|
+
let token = opts.token;
|
|
2010
|
+
let tokenAt = Date.now();
|
|
2011
|
+
let tokenRefused = false;
|
|
2012
|
+
let lastFrameAt = Date.now();
|
|
2006
2013
|
let capturing = false;
|
|
2007
2014
|
function connect() {
|
|
2008
|
-
const url = `${
|
|
2015
|
+
const url = `${connectUrl}?project=${encodeURIComponent(opts.projectId)}&token=${encodeURIComponent(token)}`;
|
|
2009
2016
|
const socket = new import_ws.default(url);
|
|
2010
2017
|
ws = socket;
|
|
2011
2018
|
socket.on("open", () => {
|
|
2012
2019
|
reconnects = 0;
|
|
2020
|
+
tokenRefused = false;
|
|
2021
|
+
lastFrameAt = Date.now();
|
|
2013
2022
|
opts.onStatus?.("connected");
|
|
2014
2023
|
pingTimer = setInterval(() => {
|
|
2024
|
+
if (Date.now() - lastFrameAt > SILENCE_LIMIT_MS) {
|
|
2025
|
+
try {
|
|
2026
|
+
socket.terminate();
|
|
2027
|
+
} catch {
|
|
2028
|
+
}
|
|
2029
|
+
return;
|
|
2030
|
+
}
|
|
2015
2031
|
try {
|
|
2016
2032
|
socket.send("ping");
|
|
2017
2033
|
} catch {
|
|
2018
2034
|
}
|
|
2019
2035
|
}, PING_INTERVAL_MS);
|
|
2020
2036
|
});
|
|
2021
|
-
socket.on("
|
|
2037
|
+
socket.on("unexpected-response", (_req, res) => {
|
|
2038
|
+
if (res.statusCode === 401 || res.statusCode === 403) tokenRefused = true;
|
|
2039
|
+
try {
|
|
2040
|
+
socket.terminate();
|
|
2041
|
+
} catch {
|
|
2042
|
+
}
|
|
2043
|
+
});
|
|
2044
|
+
socket.on("message", (data) => {
|
|
2045
|
+
lastFrameAt = Date.now();
|
|
2046
|
+
handleFrame(socket, data.toString());
|
|
2047
|
+
});
|
|
2022
2048
|
socket.on("close", () => {
|
|
2023
2049
|
if (pingTimer) clearInterval(pingTimer);
|
|
2024
2050
|
if (!closed) scheduleReconnect();
|
|
@@ -2031,9 +2057,26 @@ function startTunnelClient(opts) {
|
|
|
2031
2057
|
reconnects++;
|
|
2032
2058
|
opts.onStatus?.(`disconnected \u2014 reconnecting in ${Math.round(delay / 1e3)}s`);
|
|
2033
2059
|
setTimeout(() => {
|
|
2034
|
-
|
|
2060
|
+
void reconnect();
|
|
2035
2061
|
}, delay);
|
|
2036
2062
|
}
|
|
2063
|
+
async function reconnect() {
|
|
2064
|
+
if (closed) return;
|
|
2065
|
+
if (opts.refresh && (tokenRefused || Date.now() - tokenAt > TOKEN_REFRESH_AFTER_MS)) {
|
|
2066
|
+
try {
|
|
2067
|
+
const fresh = await opts.refresh();
|
|
2068
|
+
connectUrl = fresh.url;
|
|
2069
|
+
token = fresh.token;
|
|
2070
|
+
tokenAt = Date.now();
|
|
2071
|
+
tokenRefused = false;
|
|
2072
|
+
} catch (err) {
|
|
2073
|
+
opts.onStatus?.(`could not refresh the tunnel token (${err?.message ?? err}) \u2014 retrying`);
|
|
2074
|
+
scheduleReconnect();
|
|
2075
|
+
return;
|
|
2076
|
+
}
|
|
2077
|
+
}
|
|
2078
|
+
if (!closed) connect();
|
|
2079
|
+
}
|
|
2037
2080
|
function handleFrame(socket, raw) {
|
|
2038
2081
|
let frame;
|
|
2039
2082
|
try {
|
|
@@ -2099,7 +2142,7 @@ function startTunnelClient(opts) {
|
|
|
2099
2142
|
const init = { method: f.method, headers: stripHeaders(f.headers) };
|
|
2100
2143
|
if (body.length) init.body = body;
|
|
2101
2144
|
try {
|
|
2102
|
-
const resp = await fetch(
|
|
2145
|
+
const resp = await fetch(target2 + f.path, init);
|
|
2103
2146
|
const status2 = resp.status;
|
|
2104
2147
|
const buf = Buffer.from(await resp.arrayBuffer());
|
|
2105
2148
|
const headers = {};
|
|
@@ -2713,7 +2756,7 @@ function writeAgentInstruction(cli, spec2, dir = process.cwd()) {
|
|
|
2713
2756
|
"asking for data the API can provide.",
|
|
2714
2757
|
...spec2.contextNote ? ["", spec2.contextNote] : [],
|
|
2715
2758
|
"",
|
|
2716
|
-
`_Added by \`apiblaze apichat\`. Remove this block, or run \`npx apiblaze apichat ${spec2.name} --remove-mcp ${cli}\`._`,
|
|
2759
|
+
`_Added by \`apiblaze apichat\`. Remove this block, or run \`npx apiblaze@latest apichat ${spec2.name} --remove-mcp ${cli}\`._`,
|
|
2717
2760
|
m.end,
|
|
2718
2761
|
""
|
|
2719
2762
|
].join("\n");
|
|
@@ -2835,7 +2878,7 @@ async function installAndDemo(cli, spec2, getQuestion, log4 = console.log) {
|
|
|
2835
2878
|
${cli.label} exited with ${ans.status ?? "no status"} \u2014 the MCP stays installed.`));
|
|
2836
2879
|
}
|
|
2837
2880
|
const instructionFile = writeAgentInstruction(cli.kind, spec2);
|
|
2838
|
-
const off = `npx apiblaze apichat ${spec2.name} --remove-mcp ${cli.kind}`;
|
|
2881
|
+
const off = `npx apiblaze@latest apichat ${spec2.name} --remove-mcp ${cli.kind}`;
|
|
2839
2882
|
const mdName = cli.kind === "claude" ? "CLAUDE.md" : "AGENTS.md";
|
|
2840
2883
|
log4("");
|
|
2841
2884
|
log4(import_chalk11.default.bgRed.white.bold(` ${cli.label} will launch with your MCP to ${spec2.projectLabel} enabled from now on. `));
|
|
@@ -3028,8 +3071,8 @@ async function fetchText(url) {
|
|
|
3028
3071
|
return null;
|
|
3029
3072
|
}
|
|
3030
3073
|
}
|
|
3031
|
-
async function discoverSpec(
|
|
3032
|
-
const base2 =
|
|
3074
|
+
async function discoverSpec(target2) {
|
|
3075
|
+
const base2 = target2.replace(/\/+$/, "");
|
|
3033
3076
|
for (const suffix of ["/openapi.json", "/openapi.yaml", "/swagger.json"]) {
|
|
3034
3077
|
const url = base2 + suffix;
|
|
3035
3078
|
const text = await fetchText(url);
|
|
@@ -3261,14 +3304,14 @@ async function cpPost(anon, path11, body, summary) {
|
|
|
3261
3304
|
}
|
|
3262
3305
|
return admin({ method: "POST", path: path11, body, summary });
|
|
3263
3306
|
}
|
|
3264
|
-
async function provision(spec2,
|
|
3307
|
+
async function provision(spec2, target2, opts) {
|
|
3265
3308
|
const loggedIn = !!loadCredentials();
|
|
3266
3309
|
const anon = !loggedIn;
|
|
3267
3310
|
const salt = () => Math.random().toString(36).slice(2, 6);
|
|
3268
3311
|
let base2 = opts.name ? normalizeName(opts.name) : "";
|
|
3269
3312
|
if (!base2) {
|
|
3270
3313
|
try {
|
|
3271
|
-
const host = new URL(
|
|
3314
|
+
const host = new URL(target2).hostname;
|
|
3272
3315
|
base2 = normalizeName(host.split(".")[0]);
|
|
3273
3316
|
if (base2.length < 3) base2 = normalizeName(host);
|
|
3274
3317
|
} catch {
|
|
@@ -3295,7 +3338,7 @@ async function provision(spec2, target, opts) {
|
|
|
3295
3338
|
const creds = loadCredentials();
|
|
3296
3339
|
result = await createProxy({
|
|
3297
3340
|
name,
|
|
3298
|
-
target_url:
|
|
3341
|
+
target_url: target2,
|
|
3299
3342
|
auth_type: "api_key",
|
|
3300
3343
|
requests_auth: DUAL_AUTH,
|
|
3301
3344
|
team_id: creds.teamId,
|
|
@@ -3307,8 +3350,8 @@ async function provision(spec2, target, opts) {
|
|
|
3307
3350
|
const body = {
|
|
3308
3351
|
name,
|
|
3309
3352
|
subdomain: name,
|
|
3310
|
-
target,
|
|
3311
|
-
target_url:
|
|
3353
|
+
target: target2,
|
|
3354
|
+
target_url: target2,
|
|
3312
3355
|
auth_type: "api_key",
|
|
3313
3356
|
requests_auth: DUAL_AUTH,
|
|
3314
3357
|
...opts.apiversion ? { api_version: opts.apiversion } : {}
|
|
@@ -4493,9 +4536,9 @@ async function startChat(p, messages, opts) {
|
|
|
4493
4536
|
}
|
|
4494
4537
|
await runRepl(p, messages);
|
|
4495
4538
|
}
|
|
4496
|
-
function isApiblazeControlPlaneTarget(
|
|
4539
|
+
function isApiblazeControlPlaneTarget(target2) {
|
|
4497
4540
|
try {
|
|
4498
|
-
const h = new URL(
|
|
4541
|
+
const h = new URL(target2).hostname.toLowerCase();
|
|
4499
4542
|
return h === "api.mcp.apiblaze.com" || /^externalauth(-[a-z0-9]+)?\.mcp\.apiblaze\.com$/.test(h);
|
|
4500
4543
|
} catch {
|
|
4501
4544
|
return false;
|
|
@@ -4631,8 +4674,8 @@ async function runApichat(opts) {
|
|
|
4631
4674
|
}
|
|
4632
4675
|
}
|
|
4633
4676
|
const { spec: spec2, sourceUrl } = await loadSpec(opts);
|
|
4634
|
-
const
|
|
4635
|
-
console.log(` ${import_chalk13.default.dim("Target:")} ${import_chalk13.default.bold(
|
|
4677
|
+
const target2 = resolveTarget(spec2, opts, sourceUrl);
|
|
4678
|
+
console.log(` ${import_chalk13.default.dim("Target:")} ${import_chalk13.default.bold(target2)}`);
|
|
4636
4679
|
const auth = await resolveTargetAuth(spec2, opts);
|
|
4637
4680
|
if (auth && !process.stdin.isTTY && !opts.targetAuthEnv) {
|
|
4638
4681
|
fail3(
|
|
@@ -4640,11 +4683,11 @@ async function runApichat(opts) {
|
|
|
4640
4683
|
`Re-run with --target-auth-env <ENV_VAR> holding the ${auth.basic ? "base64(user:pass)" : "secret"}.`
|
|
4641
4684
|
);
|
|
4642
4685
|
}
|
|
4643
|
-
const p = await provision(spec2,
|
|
4686
|
+
const p = await provision(spec2, target2, opts);
|
|
4644
4687
|
console.log(` ${import_chalk13.default.dim("Proxy: ")} ${import_chalk13.default.bold(p.proxyUrl || `${p.projectId} v${p.version}`)}`);
|
|
4645
4688
|
upsertApichat({
|
|
4646
4689
|
name: p.projectId,
|
|
4647
|
-
target,
|
|
4690
|
+
target: target2,
|
|
4648
4691
|
projectId: p.projectId,
|
|
4649
4692
|
version: p.version,
|
|
4650
4693
|
environment: p.environment,
|
|
@@ -4804,17 +4847,18 @@ async function mintConsumerKey(o) {
|
|
|
4804
4847
|
if (!tenant2) {
|
|
4805
4848
|
throw new Error(who ? "--for needs a tenant: add --tenant <slug> (the tenant whose person this key is for)." : `a ${o.holder ?? "backend"} key is a consumer key: add --tenant <slug> (the tenant it calls).`);
|
|
4806
4849
|
}
|
|
4807
|
-
|
|
4850
|
+
if (o.admin && !who) throw new Error("--admin needs --for <admin email>: the admin lane only opens for a key issued to that admin.");
|
|
4851
|
+
const environment = (o.env ?? "").trim() || (o.admin ? "prod" : "dev");
|
|
4808
4852
|
const holder = who ? "person" : o.holder ?? "backend";
|
|
4809
|
-
const permissions = holder === "person" ? void 0 : holderPermissions(holder);
|
|
4853
|
+
const permissions = holder === "person" ? o.admin ? ["call", "manage-access"] : void 0 : holderPermissions(holder);
|
|
4810
4854
|
const caller = requireCaller();
|
|
4811
4855
|
const { teamId } = await resolveActingTeam(caller, o.team);
|
|
4812
4856
|
const body = { tenant: tenant2, environment };
|
|
4813
4857
|
if (o.desc) body.description = o.desc;
|
|
4814
4858
|
if (o.expiresDays) body.expires_in_seconds = Number(o.expiresDays) * 24 * 60 * 60;
|
|
4815
4859
|
if (permissions) {
|
|
4816
|
-
body.holder = holder;
|
|
4817
4860
|
body.permissions = permissions;
|
|
4861
|
+
if (holder !== "person") body.holder = holder;
|
|
4818
4862
|
}
|
|
4819
4863
|
const headers = { "Content-Type": "application/json" };
|
|
4820
4864
|
if (who) {
|
|
@@ -4882,6 +4926,7 @@ async function runConsumerKeyMint(opts) {
|
|
|
4882
4926
|
team: opts.team,
|
|
4883
4927
|
desc: opts.desc,
|
|
4884
4928
|
expiresDays: opts.expiresDays,
|
|
4929
|
+
admin: opts.admin,
|
|
4885
4930
|
...kind === "person" ? {} : { holder: kind }
|
|
4886
4931
|
});
|
|
4887
4932
|
spinner.succeed(who ? `Key minted for ${who}.` : `${kind === "client" ? "Client" : "Backend"} key minted.`);
|
|
@@ -4899,6 +4944,7 @@ async function runConsumerKeyMint(opts) {
|
|
|
4899
4944
|
if (out.expiresAt) console.log(` ${import_chalk14.default.dim("expires:")} ${out.expiresAt}`);
|
|
4900
4945
|
if (out.holder === "person") {
|
|
4901
4946
|
console.log(` ${import_chalk14.default.dim("issued to:")} ${out.person} ${import_chalk14.default.dim("\u2014 calls made with this key count as this person; an X-End-User-Id naming anyone else is refused")}`);
|
|
4947
|
+
if (opts.admin) console.log(` ${import_chalk14.default.dim("admin key:")} ${import_chalk14.default.dim("manage-access \u2014 put it in APIBLAZE_CP_KEY for the Users & Groups widget, one key per admin")}`);
|
|
4902
4948
|
} else {
|
|
4903
4949
|
console.log(` ${import_chalk14.default.cyan(holderLine(out.holder))}`);
|
|
4904
4950
|
console.log(import_chalk14.default.dim(out.holder === "backend" ? " (For a key you ship to users, re-mint with --client; to bind one to a person, --for <email>.)" : " (For your server, re-mint with --backend; to bind one to a person, --for <email>.)"));
|
|
@@ -5446,8 +5492,8 @@ function printEnvironments(o) {
|
|
|
5446
5492
|
const rows = names.length ? names.map((e) => [`/${e}`, o.dev && e === "dev" ? `localhost:${o.dev.port} (tunnelled from this machine)` : envs[e]?.target ?? "\u2014"]) : o.dev ? [["/dev", `localhost:${o.dev.port} (tunnelled from this machine)`]] : [];
|
|
5447
5493
|
if (!rows.length) return;
|
|
5448
5494
|
const w = Math.max(...rows.map(([e]) => e.length));
|
|
5449
|
-
for (const [env,
|
|
5450
|
-
console.log(` ${import_chalk17.default.cyan(env.padEnd(w))} ${import_chalk17.default.green("\u2192")} ${
|
|
5495
|
+
for (const [env, target2] of rows) {
|
|
5496
|
+
console.log(` ${import_chalk17.default.cyan(env.padEnd(w))} ${import_chalk17.default.green("\u2192")} ${target2}`);
|
|
5451
5497
|
}
|
|
5452
5498
|
}
|
|
5453
5499
|
async function finishSetup(o) {
|
|
@@ -5895,9 +5941,9 @@ Create an ${B.thing}`));
|
|
|
5895
5941
|
body.openapi = await loadOpenapiSource(opts.openapi);
|
|
5896
5942
|
}
|
|
5897
5943
|
if (opts.target && !isHttpUrl3(opts.target)) fail4("--target must be a valid http(s) URL.");
|
|
5898
|
-
let
|
|
5944
|
+
let target2 = opts.target?.trim() || (typeof body.target === "string" ? body.target : "") || (typeof body.target_url === "string" ? body.target_url : "");
|
|
5899
5945
|
const hasOtherSource = !!(body.openapi || body.github);
|
|
5900
|
-
if (!
|
|
5946
|
+
if (!target2 && !hasOtherSource) {
|
|
5901
5947
|
if (interactive) {
|
|
5902
5948
|
const { default: inquirer3 } = await import("inquirer");
|
|
5903
5949
|
if (name === void 0) {
|
|
@@ -5922,7 +5968,7 @@ Create an ${B.thing}`));
|
|
|
5922
5968
|
console.log(import_chalk17.default.yellow(" Enter a valid http(s) URL.\n"));
|
|
5923
5969
|
continue;
|
|
5924
5970
|
}
|
|
5925
|
-
|
|
5971
|
+
target2 = url.trim();
|
|
5926
5972
|
break;
|
|
5927
5973
|
}
|
|
5928
5974
|
} else {
|
|
@@ -5930,12 +5976,12 @@ Create an ${B.thing}`));
|
|
|
5930
5976
|
}
|
|
5931
5977
|
}
|
|
5932
5978
|
if (name === void 0 && opts.auto) {
|
|
5933
|
-
const nameSource =
|
|
5979
|
+
const nameSource = target2 || (typeof opts.openapi === "string" ? opts.openapi : void 0);
|
|
5934
5980
|
name = deriveNameFrom(nameSource) ?? randomProxyName();
|
|
5935
5981
|
}
|
|
5936
|
-
if (
|
|
5937
|
-
body.target =
|
|
5938
|
-
body.target_url =
|
|
5982
|
+
if (target2) {
|
|
5983
|
+
body.target = target2;
|
|
5984
|
+
body.target_url = target2;
|
|
5939
5985
|
}
|
|
5940
5986
|
if (name) {
|
|
5941
5987
|
body.name = name;
|
|
@@ -6146,13 +6192,13 @@ async function chooseName(opts, openapi, teamId, interactive) {
|
|
|
6146
6192
|
}
|
|
6147
6193
|
async function createDevProxy(args) {
|
|
6148
6194
|
const { auth, name, port, openapi, authChoice, families, tenant: tenant2, teamId } = args;
|
|
6149
|
-
const
|
|
6195
|
+
const target2 = `http://localhost:${port}`;
|
|
6150
6196
|
const body = {
|
|
6151
6197
|
name,
|
|
6152
6198
|
subdomain: name,
|
|
6153
6199
|
auth_type: authChoice.auth,
|
|
6154
6200
|
...authChoice.bodyPatch,
|
|
6155
|
-
...openapi ? { openapi, environments: { dev: { target, description: `localhost:${port} through the ${B.cli} dev tunnel` } } } : { target_url:
|
|
6201
|
+
...openapi ? { openapi, environments: { dev: { target: target2, description: `localhost:${port} through the ${B.cli} dev tunnel` } } } : { target_url: target2 },
|
|
6156
6202
|
...tenant2 ? { tenant: tenant2 } : {},
|
|
6157
6203
|
...teamId ? { team_id: teamId } : {},
|
|
6158
6204
|
...families ? { secure: { families } } : {}
|
|
@@ -6162,7 +6208,7 @@ async function createDevProxy(args) {
|
|
|
6162
6208
|
let keyAuth = auth.mode === "key" ? { apiKey: auth.apiKey } : void 0;
|
|
6163
6209
|
let resolvedTeam = teamId;
|
|
6164
6210
|
if (caller) {
|
|
6165
|
-
result = await producer(caller, { method: "POST", path: "/projects", body, summary: `Create ${B.thingShort} ${name} \u2192 ${
|
|
6211
|
+
result = await producer(caller, { method: "POST", path: "/projects", body, summary: `Create ${B.thingShort} ${name} \u2192 ${target2}` });
|
|
6166
6212
|
} else {
|
|
6167
6213
|
result = await createProxyAnonymous(body);
|
|
6168
6214
|
if (result.cp_key && result.team_id) {
|
|
@@ -6218,7 +6264,7 @@ async function runDev(options) {
|
|
|
6218
6264
|
}
|
|
6219
6265
|
let targets = [];
|
|
6220
6266
|
const wantsNew = options.openapi !== void 0 || options.name !== void 0;
|
|
6221
|
-
if (auth.mode !== "none" && !wantsNew) {
|
|
6267
|
+
if (auth.mode !== "none" && (!wantsNew || options.name !== void 0 && !options.project)) {
|
|
6222
6268
|
const spinner = (0, import_ora7.default)("Looking for projects that point at this machine...").start();
|
|
6223
6269
|
try {
|
|
6224
6270
|
targets = await getLocalhostTargets(teamId, keyAuth);
|
|
@@ -6232,7 +6278,15 @@ async function runDev(options) {
|
|
|
6232
6278
|
let selected = [];
|
|
6233
6279
|
let created = null;
|
|
6234
6280
|
let setup = null;
|
|
6235
|
-
|
|
6281
|
+
const rerun = !options.project && options.name !== void 0 ? targets.find((t) => t.projectId?.toLowerCase() === normalizeName2(options.name) || t.projectName?.toLowerCase() === normalizeName2(options.name)) : void 0;
|
|
6282
|
+
if (rerun) {
|
|
6283
|
+
console.log(import_chalk18.default.dim(` "${rerun.projectName}" already points at this machine \u2014 reattaching the tunnel (spec and keys unchanged).`));
|
|
6284
|
+
selected = [rerun];
|
|
6285
|
+
} else if (wantsNew) {
|
|
6286
|
+
targets = [];
|
|
6287
|
+
}
|
|
6288
|
+
if (selected.length) {
|
|
6289
|
+
} else if (options.project) {
|
|
6236
6290
|
const want = options.project.toLowerCase();
|
|
6237
6291
|
const match = targets.find((t) => t.projectId?.toLowerCase() === want || t.projectName?.toLowerCase() === want);
|
|
6238
6292
|
if (!match) {
|
|
@@ -6338,11 +6392,12 @@ async function runDev(options) {
|
|
|
6338
6392
|
}
|
|
6339
6393
|
let restore = [];
|
|
6340
6394
|
let connect;
|
|
6395
|
+
const registerTargets = { targets: selected.map((t) => ({ projectId: t.projectId, tenantId: t.tenantId })) };
|
|
6341
6396
|
{
|
|
6342
6397
|
const spinner = (0, import_ora7.default)("Registering the tunnel...").start();
|
|
6343
6398
|
try {
|
|
6344
|
-
const result = await putDevTunnel(
|
|
6345
|
-
restore = result.restore ?? [];
|
|
6399
|
+
const result = await putDevTunnel(registerTargets, keyAuth);
|
|
6400
|
+
restore = (result.restore ?? []).map((r) => unmarkRestore(r, options.port));
|
|
6346
6401
|
connect = result.connect;
|
|
6347
6402
|
spinner.stop();
|
|
6348
6403
|
} catch (err) {
|
|
@@ -6360,6 +6415,13 @@ async function runDev(options) {
|
|
|
6360
6415
|
(projectId) => startTunnelClient({
|
|
6361
6416
|
connectUrl: connect.url,
|
|
6362
6417
|
token: connect.token,
|
|
6418
|
+
// Re-register for a fresh token. PUT is idempotent on an already-tunnelled
|
|
6419
|
+
// project; its restore snapshot would be the marker, so it is IGNORED —
|
|
6420
|
+
// teardown keeps replaying the one taken at start.
|
|
6421
|
+
refresh: async () => {
|
|
6422
|
+
const again = await putDevTunnel(registerTargets, keyAuth);
|
|
6423
|
+
return { url: again.connect.url, token: again.connect.token };
|
|
6424
|
+
},
|
|
6363
6425
|
projectId,
|
|
6364
6426
|
localPort: options.port,
|
|
6365
6427
|
onEntry: (entry) => say(formatLogLine(entry)),
|
|
@@ -6379,6 +6441,7 @@ async function runDev(options) {
|
|
|
6379
6441
|
const localUp = await probeLocalServer(options.port);
|
|
6380
6442
|
console.log();
|
|
6381
6443
|
console.log(` ${import_chalk18.default.bold("Tunnel is up \u2014 leave this running. Ctrl-C stops it.")}`);
|
|
6444
|
+
console.log(import_chalk18.default.dim(` If it stops, run: npx ${B.cli} dev --port ${options.port} --project ${selected[0]?.projectName ?? "<name>"} --auto (reattaches; creates nothing)`));
|
|
6382
6445
|
if (!localUp) console.log(import_chalk18.default.magenta(` \u26B2 Nothing listening on port ${options.port} yet \u2014 requests are captured until your server starts.`));
|
|
6383
6446
|
if (options.captureFile) console.log(import_chalk18.default.gray(` Streaming full traffic to ${options.captureFile}`));
|
|
6384
6447
|
console.log();
|
|
@@ -6398,9 +6461,30 @@ async function runDev(options) {
|
|
|
6398
6461
|
}
|
|
6399
6462
|
process.on("SIGINT", () => void cleanup());
|
|
6400
6463
|
process.on("SIGTERM", () => void cleanup());
|
|
6464
|
+
process.on("SIGHUP", () => void cleanup());
|
|
6465
|
+
for (const stream of [process.stdout, process.stderr]) {
|
|
6466
|
+
stream.on("error", (err) => {
|
|
6467
|
+
if (err.code !== "EPIPE") throw err;
|
|
6468
|
+
});
|
|
6469
|
+
}
|
|
6401
6470
|
await new Promise(() => {
|
|
6402
6471
|
});
|
|
6403
6472
|
}
|
|
6473
|
+
function unmarkRestore(r, port) {
|
|
6474
|
+
const envs = r.environments;
|
|
6475
|
+
if (!envs || typeof envs !== "object") return r;
|
|
6476
|
+
let changed = false;
|
|
6477
|
+
const next = {};
|
|
6478
|
+
for (const [stage, value] of Object.entries(envs)) {
|
|
6479
|
+
if (typeof value?.target === "string" && value.target.startsWith("devtunnel://")) {
|
|
6480
|
+
next[stage] = { ...value, target: `http://localhost:${port}` };
|
|
6481
|
+
changed = true;
|
|
6482
|
+
} else {
|
|
6483
|
+
next[stage] = value;
|
|
6484
|
+
}
|
|
6485
|
+
}
|
|
6486
|
+
return changed ? { ...r, environments: next } : r;
|
|
6487
|
+
}
|
|
6404
6488
|
|
|
6405
6489
|
// src/commands/projects.ts
|
|
6406
6490
|
var import_chalk19 = __toESM(require("chalk"));
|
|
@@ -6505,10 +6589,16 @@ business logic: the gateway sits in front of the code and enforces it.
|
|
|
6505
6589
|
- They say yes to "can I read the code" \u2192 **Adopt**: scan their app, ask only what the scan
|
|
6506
6590
|
could not find, then ship it through the Ship steps.
|
|
6507
6591
|
- They have a backend, just wrote one, or ask you to BUILD one and then ship it \u2192 **Ship**.
|
|
6508
|
-
No code yet? First write the smallest plain backend that serves the resources they named
|
|
6509
|
-
|
|
6510
|
-
|
|
6511
|
-
|
|
6592
|
+
No code yet? First write the smallest plain backend that serves the resources they named,
|
|
6593
|
+
plus its \`openapi.yaml\`, start it on their port, then continue at Ship \xA71. Do NOT build
|
|
6594
|
+
the Demo app for this. The gateway does auth, tenancy and item ownership; the backend does
|
|
6595
|
+
only these three things:
|
|
6596
|
+
- stamp each new row's \`owner\` with the \`x-abz-user-id\` header;
|
|
6597
|
+
- filter every collection GET to rows whose \`owner\` is that header (the gateway's rules
|
|
6598
|
+
lock single records, never lists \u2014 a list route has no one record to check);
|
|
6599
|
+
- answer 401 to any call whose \`x-target-api-key\` is not \`process.env.TARGET_SERVER_SECRET\`
|
|
6600
|
+
(generate it with \`openssl rand -hex 24\`, put it in \`.env.local\`, start the backend with
|
|
6601
|
+
it; Ship \xA73b makes the proxy send it). If they say "use
|
|
6512
6602
|
${cli} on this" or "ship it with ${cli}" and point at a backend, skip any pitch and run it.
|
|
6513
6603
|
|
|
6514
6604
|
Tie-breaks: a question ("what can it do", "what is it") is always **Explain**, whatever else the
|
|
@@ -6628,12 +6718,11 @@ ONLY for what the scan could not find \u2014 one short list of questions, not an
|
|
|
6628
6718
|
sign-in plumbing, not business logic. Then the widgets they ticked, each with its server route
|
|
6629
6719
|
(\`npx ${cli} integration <name> --stack <their stack>\` for keys and chat; groups is the keys
|
|
6630
6720
|
route with \`createApiblazeGroups\`).
|
|
6631
|
-
- Backend behind a secret: the proxy must send it upstream
|
|
6632
|
-
|
|
6633
|
-
|
|
6634
|
-
|
|
6635
|
-
|
|
6636
|
-
that header \u2014 fine on a laptop, not on a server; in prod the proxy is the only caller.
|
|
6721
|
+
- Backend behind a secret: the proxy must send it upstream \u2014
|
|
6722
|
+
\`npx ${cli} transform set-header <name> <its header> --value-env <VAR> --secret\`. Until then
|
|
6723
|
+
anyone reaching \`localhost:<port>\` skips the gateway.
|
|
6724
|
+
- List routes: check each collection GET filters by owner; if it does not, say so \u2014 the
|
|
6725
|
+
gateway locks single records only.
|
|
6637
6726
|
- Multi-tenant app \u2192 \`npx ${cli} identified <name> require\`, so no call arrives without a user.
|
|
6638
6727
|
- Change nothing in their business logic. Report what you changed, file by file.
|
|
6639
6728
|
|
|
@@ -6648,7 +6737,10 @@ ONLY for what the scan could not find \u2014 one short list of questions, not an
|
|
|
6648
6737
|
login. Their own OAuth app \u2192 \`--apikey --oauth '{"provider":"auth0","clientId":"\u2026","clientSecret":"\u2026"}'\`
|
|
6649
6738
|
(both doors: keys for tenants, their login for people and agents). Shapes below.
|
|
6650
6739
|
3. **Local code** \u2192 \`npx ${cli} dev --port <port> --openapi ./openapi.yaml --name <name> --auto <door flags>\`,
|
|
6651
|
-
|
|
6740
|
+
left running \u2014 it is the tunnel. Start it as its own detached process, never \`cmd &\` inside
|
|
6741
|
+
a shell call that ends: \`nohup npx ${cli} dev \u2026 > ${cli}-dev.log 2>&1 < /dev/null &\`, then
|
|
6742
|
+
read the log. If it stops, re-run the SAME command: the same \`--name\` reattaches and
|
|
6743
|
+
creates nothing. Never pick a new name to get it back \u2014 that is a second proxy. **Deployed code** \u2192
|
|
6652
6744
|
\`npx ${cli} create --target <url-or-spec> --name <name> --auto <door flags>\`. Both print the
|
|
6653
6745
|
URL, the MCP URL (the "agents:" line), what is locked, and the bootstrap key ONCE \u2014 that key
|
|
6654
6746
|
goes in the frontend-server's env, never a browser, a repo or an agent config. The tenant is
|
|
@@ -6657,22 +6749,32 @@ ONLY for what the scan could not find \u2014 one short list of questions, not an
|
|
|
6657
6749
|
the frontend-server's env (\`.env.local\` for Next.js). Then prove it with three curls and
|
|
6658
6750
|
say the codes: no key \u2192 401; create as \`X-End-User-Id: ana\` \u2192 201; PATCH that id as
|
|
6659
6751
|
\`ben\` \u2192 403.
|
|
6752
|
+
3b. **Only the proxy reaches the backend.** If the backend checks \`x-target-api-key\`:
|
|
6753
|
+
\`npx ${cli} transform set-header <name> x-target-api-key --value-env TARGET_SERVER_SECRET --secret\`
|
|
6754
|
+
(export the variable from \`.env.local\` first). Prove it: the same list call through the
|
|
6755
|
+
proxy \u2192 200; \`curl localhost:<port>/<collection>\` with no header \u2192 401.
|
|
6660
6756
|
4. **Prod** (local code only): \`npx ${cli} target <name> --env prod --url <prod-url>\`.
|
|
6661
|
-
5. **Limits.**
|
|
6662
|
-
|
|
6663
|
-
{
|
|
6664
|
-
|
|
6757
|
+
5. **Limits.** Every new proxy already allows 10 requests/s per person and 10,000 a day \u2014
|
|
6758
|
+
say so; that is "throttled". Only for other numbers:
|
|
6759
|
+
\`npx ${cli} throttle <name> --rate <n> --quota <n> --period daily\`. It works logged out;
|
|
6760
|
+
an anonymous workspace is capped at 10/s until claimed. Prove it: 15 parallel GETs as one
|
|
6761
|
+
user \u2192 about 10\xD7200 and the rest 429.
|
|
6665
6762
|
5b. **Attribution.** \`--auto\` leaves unattributed calls allowed: the bootstrap key with no
|
|
6666
6763
|
\`X-End-User-Id\` creates rows nobody owns. For a multi-tenant app run
|
|
6667
6764
|
\`npx ${cli} identified <name> require\` \u2014 then every call must carry \`X-End-User-Id\` or a
|
|
6668
|
-
login token.
|
|
6669
|
-
|
|
6670
|
-
|
|
6671
|
-
|
|
6672
|
-
|
|
6673
|
-
|
|
6674
|
-
|
|
6675
|
-
|
|
6765
|
+
login token. Prove it: the key with no \`X-End-User-Id\` \u2192 403 \`identity_required\`.
|
|
6766
|
+
Lists are the backend's job (it filters by owner); say that the gateway locks records,
|
|
6767
|
+
the backend filters lists.
|
|
6768
|
+
6. **Admin.** \`npx ${cli} admins add <email> --tenant <tenant>\` with the email they named. None
|
|
6769
|
+
named \u2192 \`admin@ninopizzas.com\`, the demo admin (works before that person ever logs in);
|
|
6770
|
+
say so, and that they can \`admins add\` their own email and \`admins remove\` that one.
|
|
6771
|
+
7. **Report** in the shape below, then hand it over \u2014 they try it, you do not:
|
|
6772
|
+
- the full curl with the real key and URL, ready to paste:
|
|
6773
|
+
\`curl <APIBLAZE_URL>/<collection> -H "X-API-Key: <APIBLAZE_SERVER_KEY>" -H "X-End-User-Id: ana"\`
|
|
6774
|
+
(say the key is the server key \u2014 for trying it here, never for a browser or an agent);
|
|
6775
|
+
- the command to chat with their API: \`npx ${cli} apichat <name>\` (add
|
|
6776
|
+
\`--xenduserid ana\` when \xA75b is on).
|
|
6777
|
+
Then ask once: "Would you like me to add the self-serve API key widget to your site?"
|
|
6676
6778
|
|
|
6677
6779
|
## Report like this
|
|
6678
6780
|
|
|
@@ -6685,9 +6787,15 @@ Use the real values the CLI printed. Never invent a URL, a key, a rule or a limi
|
|
|
6685
6787
|
API https://<name>.abz.run/1.0.0/dev your tenants, with API keys
|
|
6686
6788
|
/dev \u2192 localhost:<port> tunnelled from this machine \u2014 leave it running
|
|
6687
6789
|
/prod \u2192 <prod-url> your deploy
|
|
6688
|
-
Try it: curl https://<name>.abz.run/1.0.0/dev/<collection> -H "X-API-Key: <key>" -H "X-End-User-Id: you"
|
|
6689
6790
|
MCP <the "agents:" URL the CLI printed> agents, signing in with <provider>
|
|
6690
|
-
Limits 10 requests/s per
|
|
6791
|
+
Limits 10 requests/s per person \xB7 10,000 a day
|
|
6792
|
+
Backend answers only to the proxy (x-target-api-key) \xB7 lists filtered by owner
|
|
6793
|
+
Admin <email>
|
|
6794
|
+
|
|
6795
|
+
Try it yourself:
|
|
6796
|
+
curl <APIBLAZE_URL>/<collection> -H "X-API-Key: <APIBLAZE_SERVER_KEY>" -H "X-End-User-Id: ana"
|
|
6797
|
+
Chat with it:
|
|
6798
|
+
npx ${cli} apichat <name> --xenduserid ana
|
|
6691
6799
|
|
|
6692
6800
|
## Sign-in shapes \u2014 the only ones \`--oauth\` accepts; never invent fields
|
|
6693
6801
|
|
|
@@ -6709,9 +6817,10 @@ key. Mint it with \`npx ${cli} apikeys mint --desc "keys widget"\` (no \`--tenan
|
|
|
6709
6817
|
\`--tenant\` you get a consumer key, which is the wrong kind).
|
|
6710
6818
|
Edit ONLY the frontend and the frontend-server. A key per TENANT comes from that widget \u2014 each
|
|
6711
6819
|
of your customers mints their own, in their own tenant; the bootstrap key is only for your
|
|
6712
|
-
frontend-server.
|
|
6713
|
-
\`
|
|
6714
|
-
|
|
6820
|
+
frontend-server. Three kinds of key for a tenant, one flag each: \`apikeys mint --tenant <tenant>\` +
|
|
6821
|
+
\`--backend\` (a server that names the user with \`X-End-User-Id\`), \`--client\` (cannot name a
|
|
6822
|
+
user) or \`--for <email>\` (bound to one person \u2014 an agent, a CI job). \`--for\` names a person by
|
|
6823
|
+
email, which is a DIFFERENT identity from a bare \`X-End-User-Id\` string: rows created as "ana" via
|
|
6715
6824
|
the header are not owned by ana@example.com's key.
|
|
6716
6825
|
|
|
6717
6826
|
## Never
|
|
@@ -6722,13 +6831,16 @@ the header are not owned by ana@example.com's key.
|
|
|
6722
6831
|
- Put ownership or tenancy checks into business logic. The gateway enforces them.
|
|
6723
6832
|
- Ask for team ids, tenant ids or project ids the CLI already knows.
|
|
6724
6833
|
- Delete anything. Deletion stays with the user: \`npx ${cli} delete <name>\` (interactive;
|
|
6725
|
-
\`--yes\` skips the prompt). Its impact banner lists tenant-scoped counts even when the tenant
|
|
6834
|
+
\`--yes\` skips the prompt; logged in only \u2014 an unclaimed workspace is removed after 30 days). Its impact banner lists tenant-scoped counts even when the tenant
|
|
6726
6835
|
is shared \u2014 those are lost only if this is the tenant's LAST proxy.
|
|
6727
6836
|
|
|
6728
6837
|
## Later
|
|
6729
6838
|
|
|
6730
6839
|
- \`npx ${cli} rule <name>\` reviews or changes the rules; \`npx ${cli} config <name> authorization.enforce_authorization false\` turns enforcement off without losing them.
|
|
6731
6840
|
- \`npx ${cli} apichat <name>\` chats with the API again; \`--install-mcp claude\` connects an agent.
|
|
6841
|
+
- \`npx ${cli} throttle <name> --rate <n>\` \xB7 \`identified <name> require|allow-anon\` \xB7
|
|
6842
|
+
\`transform <name> list|set-header|remove\` \xB7 \`target <name> --env prod --url <url>\` \u2014 all work
|
|
6843
|
+
logged out. \`npx ${cli} <command> --help\` for the flags; \`--verbose\` prints the API calls.
|
|
6732
6844
|
|
|
6733
6845
|
## No shell? The control-plane MCP
|
|
6734
6846
|
|
|
@@ -6744,14 +6856,14 @@ async function runSkill(opts = {}) {
|
|
|
6744
6856
|
process.stdout.write(md);
|
|
6745
6857
|
return;
|
|
6746
6858
|
}
|
|
6747
|
-
const
|
|
6748
|
-
if (import_fs3.default.existsSync(
|
|
6859
|
+
const target2 = import_path2.default.resolve(process.cwd(), SKILL_RELATIVE_PATH);
|
|
6860
|
+
if (import_fs3.default.existsSync(target2) && !opts.force) {
|
|
6749
6861
|
console.error(import_chalk20.default.yellow(`${SKILL_RELATIVE_PATH} already exists \u2014 re-run with --force to overwrite it.`));
|
|
6750
6862
|
process.exit(1);
|
|
6751
6863
|
}
|
|
6752
|
-
import_fs3.default.mkdirSync(import_path2.default.dirname(
|
|
6753
|
-
import_fs3.default.writeFileSync(
|
|
6754
|
-
console.log(`${import_chalk20.default.green("\u2713")} Installed the ${import_chalk20.default.bold(SKILL_NAME)} skill: ${import_chalk20.default.bold(
|
|
6864
|
+
import_fs3.default.mkdirSync(import_path2.default.dirname(target2), { recursive: true });
|
|
6865
|
+
import_fs3.default.writeFileSync(target2, md, "utf8");
|
|
6866
|
+
console.log(`${import_chalk20.default.green("\u2713")} Installed the ${import_chalk20.default.bold(SKILL_NAME)} skill: ${import_chalk20.default.bold(target2)}`);
|
|
6755
6867
|
console.log(import_chalk20.default.dim(` Claude Code picks it up in this project. Say "ship this API" and it runs npx ${B.cli} dev / create for you.`));
|
|
6756
6868
|
}
|
|
6757
6869
|
|
|
@@ -6842,12 +6954,12 @@ async function runLogout(opts = {}) {
|
|
|
6842
6954
|
console.log(import_chalk22.default.yellow("You were not logged in."));
|
|
6843
6955
|
return;
|
|
6844
6956
|
}
|
|
6845
|
-
let
|
|
6846
|
-
if (opts.all)
|
|
6847
|
-
else if (opts.producer)
|
|
6848
|
-
else if (opts.consumer)
|
|
6849
|
-
else if (creds && !consumer2)
|
|
6850
|
-
else if (consumer2 && !creds)
|
|
6957
|
+
let target2;
|
|
6958
|
+
if (opts.all) target2 = "all";
|
|
6959
|
+
else if (opts.producer) target2 = "producer";
|
|
6960
|
+
else if (opts.consumer) target2 = "consumer";
|
|
6961
|
+
else if (creds && !consumer2) target2 = "producer";
|
|
6962
|
+
else if (consumer2 && !creds) target2 = "consumer";
|
|
6851
6963
|
else {
|
|
6852
6964
|
const { default: inquirer3 } = await import("inquirer");
|
|
6853
6965
|
const { chosen } = await inquirer3.prompt([
|
|
@@ -6862,13 +6974,13 @@ async function runLogout(opts = {}) {
|
|
|
6862
6974
|
]
|
|
6863
6975
|
}
|
|
6864
6976
|
]);
|
|
6865
|
-
|
|
6977
|
+
target2 = chosen;
|
|
6866
6978
|
}
|
|
6867
|
-
if (
|
|
6979
|
+
if (target2 === "producer" || target2 === "all") {
|
|
6868
6980
|
clearCredentials();
|
|
6869
6981
|
console.log(import_chalk22.default.green(`\u2714 Logged out of API Producer${creds?.githubHandle ? ` (${creds.githubHandle})` : ""}.`));
|
|
6870
6982
|
}
|
|
6871
|
-
if (
|
|
6983
|
+
if (target2 === "consumer" || target2 === "all") {
|
|
6872
6984
|
clearConsumer();
|
|
6873
6985
|
console.log(import_chalk22.default.green(`\u2714 Logged out of API Consumer${consumer2?.tenant ? ` (${consumer2.tenant})` : ""}.`));
|
|
6874
6986
|
}
|
|
@@ -7881,17 +7993,17 @@ async function runExport(projectArg, versionArg, opts) {
|
|
|
7881
7993
|
const match = await resolveProject(teamId, projectArg, versionArg);
|
|
7882
7994
|
const projectId = match.projectId;
|
|
7883
7995
|
const version2 = versionArg || match.apiVersion;
|
|
7884
|
-
const
|
|
7996
|
+
const target2 = opts.kong ? "kong" : "data";
|
|
7885
7997
|
const q = new URLSearchParams();
|
|
7886
7998
|
if (opts.secrets) q.set("include_secrets", "1");
|
|
7887
7999
|
if (opts.noConsumers) q.set("include_consumers", "0");
|
|
7888
8000
|
if (opts.keys) q.set("keys", opts.keys);
|
|
7889
8001
|
const qs = q.toString() ? `?${q.toString()}` : "";
|
|
7890
|
-
console.log(import_chalk36.default.dim(`Queuing ${
|
|
8002
|
+
console.log(import_chalk36.default.dim(`Queuing ${target2} export of ${projectId}@${version2}\u2026`));
|
|
7891
8003
|
const job = await admin({
|
|
7892
|
-
path: `/projects/${projectId}/${version2}/export/${
|
|
8004
|
+
path: `/projects/${projectId}/${version2}/export/${target2}${qs}`,
|
|
7893
8005
|
method: "GET",
|
|
7894
|
-
summary: `export ${projectId} (${
|
|
8006
|
+
summary: `export ${projectId} (${target2})`
|
|
7895
8007
|
});
|
|
7896
8008
|
if (!job?.job_id) throw new Error("Failed to queue export.");
|
|
7897
8009
|
const statusPath = `/projects/${projectId}/${version2}/export/jobs/${job.job_id}`;
|
|
@@ -7910,7 +8022,7 @@ async function runExport(projectArg, versionArg, opts) {
|
|
|
7910
8022
|
}
|
|
7911
8023
|
}
|
|
7912
8024
|
process.stdout.write("\n");
|
|
7913
|
-
const outFile = opts.out || `apiblaze-export-${projectId}-${version2}-${
|
|
8025
|
+
const outFile = opts.out || `apiblaze-export-${projectId}-${version2}-${target2}.zip`;
|
|
7914
8026
|
const res = await fetch(`${DASHBOARD_BASE5}/api/cli/admin/download`, {
|
|
7915
8027
|
method: "POST",
|
|
7916
8028
|
headers: { "Content-Type": "application/json", Authorization: `Bearer ${token}` },
|
|
@@ -7922,7 +8034,7 @@ async function runExport(projectArg, versionArg, opts) {
|
|
|
7922
8034
|
console.log(import_chalk36.default.green(`\u2713 Saved ${outFile} (${(buf.length / 1024).toFixed(0)} KB)`));
|
|
7923
8035
|
if (job.decryption_key) console.log(import_chalk36.default.yellow(`Decryption key (shown once): ${job.decryption_key}`));
|
|
7924
8036
|
console.log(import_chalk36.default.dim("Read MIGRATION-REPORT.json inside the zip for the full fidelity ledger."));
|
|
7925
|
-
if (
|
|
8037
|
+
if (target2 === "kong") console.log(import_chalk36.default.dim("Kong: unzip, then `cd kong && docker compose up` (see kong/README.md)."));
|
|
7926
8038
|
}
|
|
7927
8039
|
|
|
7928
8040
|
// src/commands/recipe-search.ts
|
|
@@ -8301,8 +8413,8 @@ async function runRecipeInstall(nameArg, opts) {
|
|
|
8301
8413
|
let reinstall = false;
|
|
8302
8414
|
if (opts.into) {
|
|
8303
8415
|
reinstall = true;
|
|
8304
|
-
const
|
|
8305
|
-
const proj2 = await resolveProjectVia(caller, teamId,
|
|
8416
|
+
const target2 = normalizeName3(opts.into);
|
|
8417
|
+
const proj2 = await resolveProjectVia(caller, teamId, target2, opts.apiversion);
|
|
8306
8418
|
project = proj2.projectId;
|
|
8307
8419
|
apiVersion = proj2.apiVersion;
|
|
8308
8420
|
tenant2 = await resolveReinstallTenant(caller, project, apiVersion);
|
|
@@ -8385,14 +8497,14 @@ function buildSteps(inp) {
|
|
|
8385
8497
|
label: `creating ${project}`,
|
|
8386
8498
|
skip: reinstall,
|
|
8387
8499
|
run: async () => {
|
|
8388
|
-
const
|
|
8389
|
-
if (!
|
|
8500
|
+
const target2 = firstUpstream(recipe);
|
|
8501
|
+
if (!target2) throw new Error("this recipe carries no upstream URL, so there is nothing to create a proxy against");
|
|
8390
8502
|
const out = await producer(caller, {
|
|
8391
8503
|
method: "POST",
|
|
8392
8504
|
path: "/projects",
|
|
8393
8505
|
body: {
|
|
8394
8506
|
name: project,
|
|
8395
|
-
target,
|
|
8507
|
+
target: target2,
|
|
8396
8508
|
tenant: tenant2,
|
|
8397
8509
|
product_slug: project,
|
|
8398
8510
|
api_version: apiVersion,
|
|
@@ -10109,14 +10221,16 @@ async function runConsumerLogsList(opts) {
|
|
|
10109
10221
|
// src/commands/config.ts
|
|
10110
10222
|
var import_chalk43 = __toESM(require("chalk"));
|
|
10111
10223
|
var import_ora15 = __toESM(require("ora"));
|
|
10112
|
-
|
|
10113
|
-
|
|
10224
|
+
init_caller();
|
|
10225
|
+
init_paging();
|
|
10226
|
+
var resolveCallerMode = () => resolveCaller()?.mode ?? null;
|
|
10114
10227
|
async function patchConfig(project, opts, body, summary) {
|
|
10115
|
-
const
|
|
10116
|
-
const
|
|
10228
|
+
const caller = requireCaller();
|
|
10229
|
+
const { teamId } = await resolveActingTeam(caller, opts.team);
|
|
10230
|
+
const proj2 = await resolveProjectVia(caller, teamId, project, opts.apiversion);
|
|
10117
10231
|
const spinner = (0, import_ora15.default)(summary + "...").start();
|
|
10118
10232
|
try {
|
|
10119
|
-
const out = await
|
|
10233
|
+
const out = await producer(caller, {
|
|
10120
10234
|
method: "PATCH",
|
|
10121
10235
|
path: `/projects/${proj2.projectId}/${proj2.apiVersion}`,
|
|
10122
10236
|
body,
|
|
@@ -10134,13 +10248,47 @@ async function runTargetSet(project, opts) {
|
|
|
10134
10248
|
console.error(import_chalk43.default.red("--url is required."));
|
|
10135
10249
|
process.exit(1);
|
|
10136
10250
|
}
|
|
10137
|
-
|
|
10138
|
-
|
|
10251
|
+
if (!opts.env) {
|
|
10252
|
+
await patchConfig(project, opts, { target_url: opts.url }, `Set target \u2192 ${opts.url}`);
|
|
10253
|
+
return;
|
|
10254
|
+
}
|
|
10255
|
+
const caller = requireCaller();
|
|
10256
|
+
const { teamId } = await resolveActingTeam(caller, opts.team);
|
|
10257
|
+
const proj2 = await resolveProjectVia(caller, teamId, project, opts.apiversion);
|
|
10258
|
+
const listing = await pageAll(
|
|
10259
|
+
(cursor) => producer(caller, {
|
|
10260
|
+
method: "GET",
|
|
10261
|
+
path: withPage(`/projects?team_id=${encodeURIComponent(teamId)}`, cursor),
|
|
10262
|
+
summary: `Read environments for ${proj2.projectName}`
|
|
10263
|
+
}),
|
|
10264
|
+
(b) => b?.projects
|
|
10265
|
+
);
|
|
10266
|
+
const row = listing.items.find((r) => r.project_id === proj2.projectId && r.api_version === proj2.apiVersion);
|
|
10267
|
+
const current = row?.config?.environments;
|
|
10268
|
+
const existing = current && typeof current === "object" && !Array.isArray(current) ? current : {};
|
|
10269
|
+
const prev = existing[opts.env];
|
|
10270
|
+
const environments = {
|
|
10271
|
+
...existing,
|
|
10272
|
+
[opts.env]: { ...prev && typeof prev === "object" ? prev : {}, target: opts.url }
|
|
10273
|
+
};
|
|
10274
|
+
await patchConfig(project, opts, { environments }, `Set target for env ${opts.env} \u2192 ${opts.url}`);
|
|
10139
10275
|
}
|
|
10140
10276
|
async function runThrottleSet(project, opts) {
|
|
10141
10277
|
const throttling = {};
|
|
10142
|
-
|
|
10143
|
-
if (opts.endUserRate !== void 0
|
|
10278
|
+
const rate = opts.rate ?? opts.endUserRate;
|
|
10279
|
+
if (opts.rate !== void 0 && opts.endUserRate !== void 0 && opts.rate !== opts.endUserRate) {
|
|
10280
|
+
console.error(import_chalk43.default.red("--rate and --end-user-rate are the same limit now; pass one of them."));
|
|
10281
|
+
process.exit(1);
|
|
10282
|
+
}
|
|
10283
|
+
if (rate !== void 0) {
|
|
10284
|
+
const n = Number(rate);
|
|
10285
|
+
if (!Number.isFinite(n) || n <= 0) {
|
|
10286
|
+
console.error(import_chalk43.default.red("--rate must be a positive number of requests per second."));
|
|
10287
|
+
process.exit(1);
|
|
10288
|
+
}
|
|
10289
|
+
throttling.userRateLimit = n;
|
|
10290
|
+
throttling.endUserRateLimit = n;
|
|
10291
|
+
}
|
|
10144
10292
|
if (opts.quota !== void 0) throttling.proxyQuota = Number(opts.quota);
|
|
10145
10293
|
if (opts.period !== void 0) {
|
|
10146
10294
|
if (!["daily", "weekly", "monthly"].includes(opts.period)) {
|
|
@@ -10154,6 +10302,11 @@ async function runThrottleSet(project, opts) {
|
|
|
10154
10302
|
process.exit(1);
|
|
10155
10303
|
}
|
|
10156
10304
|
await patchConfig(project, opts, { throttling }, `Update throttling ${JSON.stringify(throttling)}`);
|
|
10305
|
+
if (resolveCallerMode() === "anon" && !opts.json) {
|
|
10306
|
+
console.log(import_chalk43.default.dim(
|
|
10307
|
+
" Anonymous workspace: the rate is capped at 10 requests/s per person and the quota at the default until you claim it (`apiblaze login`, then `apiblaze claim`)."
|
|
10308
|
+
));
|
|
10309
|
+
}
|
|
10157
10310
|
}
|
|
10158
10311
|
async function runRename(project, opts) {
|
|
10159
10312
|
if (!opts.displayName) {
|
|
@@ -10163,17 +10316,171 @@ async function runRename(project, opts) {
|
|
|
10163
10316
|
await patchConfig(project, opts, { display_name: opts.displayName }, `Rename \u2192 "${opts.displayName}"`);
|
|
10164
10317
|
}
|
|
10165
10318
|
|
|
10319
|
+
// src/commands/transform.ts
|
|
10320
|
+
var import_chalk44 = __toESM(require("chalk"));
|
|
10321
|
+
var import_ora16 = __toESM(require("ora"));
|
|
10322
|
+
init_caller();
|
|
10323
|
+
var HEADER_NAME = /^[A-Za-z0-9!#$%&'*+.^_`|~-]+$/;
|
|
10324
|
+
var ruleNameFor = (header) => `set header ${header.toLowerCase()}`;
|
|
10325
|
+
async function target(project, opts) {
|
|
10326
|
+
const caller = requireCaller();
|
|
10327
|
+
const { teamId } = await resolveActingTeam(caller, opts.team);
|
|
10328
|
+
const proj2 = await resolveProjectVia(caller, teamId, project, opts.apiversion);
|
|
10329
|
+
const base2 = `/projects/${encodeURIComponent(proj2.projectId)}/${encodeURIComponent(proj2.apiVersion)}/transforms`;
|
|
10330
|
+
return { caller, proj: proj2, base: base2 };
|
|
10331
|
+
}
|
|
10332
|
+
async function listRules(t) {
|
|
10333
|
+
const out = await producer(t.caller, {
|
|
10334
|
+
method: "GET",
|
|
10335
|
+
path: t.base,
|
|
10336
|
+
summary: `List transforms for ${t.proj.projectName}`
|
|
10337
|
+
});
|
|
10338
|
+
return out?.rules ?? [];
|
|
10339
|
+
}
|
|
10340
|
+
function describe(r) {
|
|
10341
|
+
const a = r.action ?? { type: "?" };
|
|
10342
|
+
if (a.type === "hardcode") {
|
|
10343
|
+
const secret = !a.value || /^abzsec:|^__ABZSEC_|^•+$/.test(a.value);
|
|
10344
|
+
return `set ${a.destination} = ${secret ? "(secret)" : a.value}`;
|
|
10345
|
+
}
|
|
10346
|
+
if (a.type === "remove") return `remove ${a.field}`;
|
|
10347
|
+
if (a.type === "map") return `map ${a.source} \u2192 ${a.destination}${a.lookup?.mapping_id ? " (through a mapping table)" : ""}`;
|
|
10348
|
+
return a.type;
|
|
10349
|
+
}
|
|
10350
|
+
async function runTransformList(project, opts) {
|
|
10351
|
+
const t = await target(project, opts);
|
|
10352
|
+
const rules = await listRules(t);
|
|
10353
|
+
if (opts.json) {
|
|
10354
|
+
console.log(JSON.stringify(rules, null, 2));
|
|
10355
|
+
return;
|
|
10356
|
+
}
|
|
10357
|
+
if (!rules.length) {
|
|
10358
|
+
console.log(import_chalk44.default.dim(`No transforms on ${t.proj.projectName} v${t.proj.apiVersion}.`));
|
|
10359
|
+
return;
|
|
10360
|
+
}
|
|
10361
|
+
for (const r of rules) {
|
|
10362
|
+
console.log(`${r.enabled ? import_chalk44.default.green("\u25CF") : import_chalk44.default.gray("\u25CB")} ${import_chalk44.default.bold(r.name)} ${import_chalk44.default.dim(`[${r.phase}] ${r.id}`)}`);
|
|
10363
|
+
console.log(` ${describe(r)}`);
|
|
10364
|
+
}
|
|
10365
|
+
}
|
|
10366
|
+
async function runTransformSetHeader(project, header, opts) {
|
|
10367
|
+
if (!HEADER_NAME.test(header)) throw new Error(`"${header}" is not a valid header name.`);
|
|
10368
|
+
if (opts.value === void 0 === (opts.valueEnv === void 0)) {
|
|
10369
|
+
throw new Error("Pass exactly one of --value <value> or --value-env <VAR> (reads the value from that environment variable, so it stays out of your shell history).");
|
|
10370
|
+
}
|
|
10371
|
+
const value = opts.value ?? process.env[opts.valueEnv];
|
|
10372
|
+
if (!value) throw new Error(`Environment variable ${opts.valueEnv} is empty or not set.`);
|
|
10373
|
+
const t = await target(project, opts);
|
|
10374
|
+
const name = ruleNameFor(header);
|
|
10375
|
+
const action2 = {
|
|
10376
|
+
type: "hardcode",
|
|
10377
|
+
destination: `header:${header}`,
|
|
10378
|
+
value,
|
|
10379
|
+
// Encrypted at rest and masked on every read; main-proxy decrypts it only
|
|
10380
|
+
// on the way to the upstream.
|
|
10381
|
+
...opts.secret ? { value_secret: true } : {}
|
|
10382
|
+
};
|
|
10383
|
+
const existing = (await listRules(t)).find((r) => r.name === name && r.phase === "request");
|
|
10384
|
+
const summary = `${existing ? "Update" : "Add"} transform: send ${header} to the upstream`;
|
|
10385
|
+
const spinner = opts.json ? null : (0, import_ora16.default)(summary + "...").start();
|
|
10386
|
+
try {
|
|
10387
|
+
const out = await producer(t.caller, existing ? { method: "PUT", path: `${t.base}/${encodeURIComponent(existing.id)}`, body: { action: action2, enabled: true }, summary } : { method: "POST", path: t.base, body: { name, action: action2, phase: "request", enabled: true }, summary });
|
|
10388
|
+
spinner?.succeed(`${summary} \u2014 ${t.proj.projectName} v${t.proj.apiVersion}${opts.secret ? " (stored encrypted)" : ""}.`);
|
|
10389
|
+
if (opts.json) console.log(JSON.stringify(out ?? { ok: true }));
|
|
10390
|
+
else console.log(import_chalk44.default.dim(` Every call the proxy forwards now carries ${header}. Your backend can reject calls without it.`));
|
|
10391
|
+
} catch (err) {
|
|
10392
|
+
spinner?.fail(summary + " failed.");
|
|
10393
|
+
throw err;
|
|
10394
|
+
}
|
|
10395
|
+
}
|
|
10396
|
+
async function runTransformRemove(project, nameOrId, opts) {
|
|
10397
|
+
const t = await target(project, opts);
|
|
10398
|
+
const rules = await listRules(t);
|
|
10399
|
+
const want = nameOrId.toLowerCase();
|
|
10400
|
+
const rule = rules.find((r) => r.id === nameOrId || r.name.toLowerCase() === want || r.name === ruleNameFor(nameOrId));
|
|
10401
|
+
if (!rule) {
|
|
10402
|
+
throw new Error(`No transform "${nameOrId}" on ${t.proj.projectName}.` + (rules.length ? ` Transforms: ${rules.map((r) => r.name).join(", ")}` : ""));
|
|
10403
|
+
}
|
|
10404
|
+
await producer(t.caller, {
|
|
10405
|
+
method: "DELETE",
|
|
10406
|
+
path: `${t.base}/${encodeURIComponent(rule.id)}`,
|
|
10407
|
+
summary: `Remove transform "${rule.name}"`
|
|
10408
|
+
});
|
|
10409
|
+
if (opts.json) console.log(JSON.stringify({ ok: true, removed: rule.id }));
|
|
10410
|
+
else console.log(`${import_chalk44.default.green("\u2713")} Removed transform ${import_chalk44.default.bold(rule.name)} from ${t.proj.projectName}.`);
|
|
10411
|
+
}
|
|
10412
|
+
function tablesBase(t) {
|
|
10413
|
+
return t.base.replace(/\/transforms$/, "/mappings");
|
|
10414
|
+
}
|
|
10415
|
+
async function listTables(t) {
|
|
10416
|
+
const out = await producer(t.caller, {
|
|
10417
|
+
method: "GET",
|
|
10418
|
+
path: tablesBase(t),
|
|
10419
|
+
summary: `List mapping tables for ${t.proj.projectName}`
|
|
10420
|
+
});
|
|
10421
|
+
return out?.tables ?? [];
|
|
10422
|
+
}
|
|
10423
|
+
function parseEntries(pairs) {
|
|
10424
|
+
return pairs.map((p) => {
|
|
10425
|
+
const i = p.indexOf("=");
|
|
10426
|
+
if (i <= 0) throw new Error(`"${p}" is not a from=to pair (e.g. FR=EUR).`);
|
|
10427
|
+
return { from: p.slice(0, i), to: p.slice(i + 1) };
|
|
10428
|
+
});
|
|
10429
|
+
}
|
|
10430
|
+
var FIELD_REF = /^(header|query|param|bodyvar|jwt):.+/;
|
|
10431
|
+
async function runTransformMapping(project, table, pairs, opts) {
|
|
10432
|
+
if (!pairs.length) throw new Error("Give at least one from=to pair, e.g. FR=EUR US=USD.");
|
|
10433
|
+
const entries2 = parseEntries(pairs);
|
|
10434
|
+
const t = await target(project, opts);
|
|
10435
|
+
const existing = (await listTables(t)).find((m) => m.name === table);
|
|
10436
|
+
const body = { name: table, entries: entries2, ...opts.secret ? { encrypt_values: true } : {} };
|
|
10437
|
+
const summary = `${existing ? "Replace" : "Create"} mapping table "${table}" (${entries2.length} entr${entries2.length === 1 ? "y" : "ies"})`;
|
|
10438
|
+
const out = await producer(t.caller, existing ? { method: "PUT", path: `${tablesBase(t)}/${encodeURIComponent(existing.id)}`, body, summary } : { method: "POST", path: tablesBase(t), body, summary });
|
|
10439
|
+
if (opts.json) {
|
|
10440
|
+
console.log(JSON.stringify(out ?? { ok: true }));
|
|
10441
|
+
return;
|
|
10442
|
+
}
|
|
10443
|
+
console.log(`${import_chalk44.default.green("\u2714")} ${summary} \u2014 ${t.proj.projectName} v${t.proj.apiVersion}.`);
|
|
10444
|
+
for (const e of entries2) console.log(import_chalk44.default.dim(` ${e.from} \u2192 ${opts.secret ? "(secret)" : e.to}`));
|
|
10445
|
+
}
|
|
10446
|
+
async function runTransformMap(project, source, destination, opts) {
|
|
10447
|
+
if (!FIELD_REF.test(source)) throw new Error(`Source "${source}" must look like header:X-Country, query:country or bodyvar:order.country.`);
|
|
10448
|
+
if (!/^(header|query|bodyvar):.+/.test(destination)) throw new Error(`Destination "${destination}" must look like header:X-Currency, query:currency or bodyvar:order.currency.`);
|
|
10449
|
+
if (!opts.table) throw new Error("--table <name> is required: the mapping table to look the value up in (create it with `transform mapping`).");
|
|
10450
|
+
const onNoMatch = opts.onNoMatch ?? "skip";
|
|
10451
|
+
if (!["skip", "fail", "passthrough"].includes(onNoMatch)) throw new Error("--on-no-match is skip, fail or passthrough.");
|
|
10452
|
+
const t = await target(project, opts);
|
|
10453
|
+
const table = (await listTables(t)).find((m) => m.name === opts.table);
|
|
10454
|
+
if (!table) throw new Error(`No mapping table "${opts.table}" on ${t.proj.projectName}. Create it: npx apiblaze transform mapping ${project} ${opts.table} FR=EUR \u2026`);
|
|
10455
|
+
const name = `map ${source} to ${destination}`;
|
|
10456
|
+
const action2 = {
|
|
10457
|
+
type: "map",
|
|
10458
|
+
source,
|
|
10459
|
+
destination,
|
|
10460
|
+
lookup: { mapping_id: table.id, on_no_match: onNoMatch }
|
|
10461
|
+
};
|
|
10462
|
+
const existing = (await listRules(t)).find((r) => r.name === name && r.phase === "request");
|
|
10463
|
+
const summary = `${existing ? "Update" : "Add"} transform: ${source} \u2192 ${destination} through "${table.name}"`;
|
|
10464
|
+
const out = await producer(t.caller, existing ? { method: "PUT", path: `${t.base}/${encodeURIComponent(existing.id)}`, body: { action: action2, enabled: true }, summary } : { method: "POST", path: t.base, body: { name, action: action2, phase: "request", enabled: true }, summary });
|
|
10465
|
+
if (opts.json) {
|
|
10466
|
+
console.log(JSON.stringify(out ?? { ok: true }));
|
|
10467
|
+
return;
|
|
10468
|
+
}
|
|
10469
|
+
console.log(`${import_chalk44.default.green("\u2714")} ${summary} \u2014 ${t.proj.projectName} v${t.proj.apiVersion}.`);
|
|
10470
|
+
console.log(import_chalk44.default.dim(` A value not in the table: ${onNoMatch === "skip" ? "nothing is written" : onNoMatch === "fail" ? "the call is refused" : "it is copied as-is"}.`));
|
|
10471
|
+
}
|
|
10472
|
+
|
|
10166
10473
|
// src/commands/config-browse.ts
|
|
10167
|
-
var
|
|
10168
|
-
var
|
|
10474
|
+
var import_chalk53 = __toESM(require("chalk"));
|
|
10475
|
+
var import_ora24 = __toESM(require("ora"));
|
|
10169
10476
|
init_admin();
|
|
10170
10477
|
init_paging();
|
|
10171
10478
|
init_auth();
|
|
10172
10479
|
init_resolve();
|
|
10173
10480
|
|
|
10174
10481
|
// src/commands/domain.ts
|
|
10175
|
-
var
|
|
10176
|
-
var
|
|
10482
|
+
var import_chalk45 = __toESM(require("chalk"));
|
|
10483
|
+
var import_ora17 = __toESM(require("ora"));
|
|
10177
10484
|
init_admin();
|
|
10178
10485
|
init_resolve();
|
|
10179
10486
|
var KINDS = ["api", "mcp", "portal", "auth"];
|
|
@@ -10185,7 +10492,7 @@ async function scopeFor(project, opts) {
|
|
|
10185
10492
|
return { kind: "project", base: `/projects/${proj2.projectId}/domains`, label: `${proj2.projectName} v${proj2.apiVersion}`, apiVersion: proj2.apiVersion };
|
|
10186
10493
|
}
|
|
10187
10494
|
if (!opts.tenant) {
|
|
10188
|
-
console.error(
|
|
10495
|
+
console.error(import_chalk45.default.red("Give a <project> (api / mcp domains) or --tenant <slug> (portal / auth domains)."));
|
|
10189
10496
|
process.exit(1);
|
|
10190
10497
|
}
|
|
10191
10498
|
const tenant2 = opts.tenant.toLowerCase();
|
|
@@ -10199,35 +10506,35 @@ function describeKind(d) {
|
|
|
10199
10506
|
return d?.target ? `${d.kind ?? "api"} \u2192 stands in for ${d.target}` : d?.kind ?? "api";
|
|
10200
10507
|
}
|
|
10201
10508
|
function statusColor2(s) {
|
|
10202
|
-
if (s === "active") return
|
|
10203
|
-
if (s === "failed" || s === "missing" || s === "suspended") return
|
|
10204
|
-
return
|
|
10509
|
+
if (s === "active") return import_chalk45.default.green(s);
|
|
10510
|
+
if (s === "failed" || s === "missing" || s === "suspended") return import_chalk45.default.red(s);
|
|
10511
|
+
return import_chalk45.default.yellow(s ?? "unknown");
|
|
10205
10512
|
}
|
|
10206
10513
|
async function runDomainAdd(project, opts) {
|
|
10207
10514
|
if (!opts.domain) {
|
|
10208
|
-
console.error(
|
|
10515
|
+
console.error(import_chalk45.default.red("--domain is required."));
|
|
10209
10516
|
process.exit(1);
|
|
10210
10517
|
}
|
|
10211
10518
|
const kind = (opts.kind ?? (project ? "api" : "")).toLowerCase();
|
|
10212
10519
|
if (!KINDS.includes(kind)) {
|
|
10213
|
-
console.error(
|
|
10520
|
+
console.error(import_chalk45.default.red(`--kind must be one of ${KINDS.join(", ")}${project ? "" : " (portal or auth for a --tenant)"}.`));
|
|
10214
10521
|
process.exit(1);
|
|
10215
10522
|
}
|
|
10216
10523
|
const tenantKind = TENANT_KINDS.includes(kind);
|
|
10217
10524
|
if (tenantKind && project) {
|
|
10218
|
-
console.error(
|
|
10525
|
+
console.error(import_chalk45.default.red(`A ${kind} domain belongs to a tenant, not a proxy: apiblaze domain add --tenant <slug> --kind ${kind} --domain ${opts.domain}`));
|
|
10219
10526
|
process.exit(1);
|
|
10220
10527
|
}
|
|
10221
10528
|
if (tenantKind && !opts.tenant) {
|
|
10222
|
-
console.error(
|
|
10529
|
+
console.error(import_chalk45.default.red(`--tenant <slug> is required for a ${kind} domain.`));
|
|
10223
10530
|
process.exit(1);
|
|
10224
10531
|
}
|
|
10225
10532
|
if (!tenantKind && !project) {
|
|
10226
|
-
console.error(
|
|
10533
|
+
console.error(import_chalk45.default.red(`A ${kind} domain belongs to a proxy: apiblaze domain add <project> --kind ${kind} --domain ${opts.domain}`));
|
|
10227
10534
|
process.exit(1);
|
|
10228
10535
|
}
|
|
10229
10536
|
const scope = await scopeFor(project, opts);
|
|
10230
|
-
const spinner = (0,
|
|
10537
|
+
const spinner = (0, import_ora17.default)(`Registering ${opts.domain}...`).start();
|
|
10231
10538
|
try {
|
|
10232
10539
|
const body = scope.kind === "project" ? { hostname: opts.domain, api_version: scope.apiVersion, kind, ...opts.tenant ? { tenant_name: opts.tenant.toLowerCase() } : {} } : { hostname: opts.domain, kind };
|
|
10233
10540
|
const out = await admin({
|
|
@@ -10241,10 +10548,10 @@ async function runDomainAdd(project, opts) {
|
|
|
10241
10548
|
console.log(JSON.stringify(out));
|
|
10242
10549
|
return;
|
|
10243
10550
|
}
|
|
10244
|
-
if (out?.cnameRecord) console.log(` ${
|
|
10245
|
-
if (out?.dcv) console.log(` ${
|
|
10246
|
-
if (typeof out?.monthlyCents === "number") console.log(
|
|
10247
|
-
if (out?.id) console.log(
|
|
10551
|
+
if (out?.cnameRecord) console.log(` ${import_chalk45.default.cyan("CNAME")} ${out.cnameRecord.name ?? opts.domain} \u2192 ${out.cnameRecord.value}`);
|
|
10552
|
+
if (out?.dcv) console.log(` ${import_chalk45.default.cyan("TXT")} ${out.dcv.name} = ${out.dcv.value}`);
|
|
10553
|
+
if (typeof out?.monthlyCents === "number") console.log(import_chalk45.default.dim(` billed $${(out.monthlyCents / 100).toFixed(2)}/month (first month pro rata)`));
|
|
10554
|
+
if (out?.id) console.log(import_chalk45.default.dim(` domain id: ${out.id}`));
|
|
10248
10555
|
} catch (err) {
|
|
10249
10556
|
spinner.fail("Domain registration failed.");
|
|
10250
10557
|
throw err;
|
|
@@ -10263,17 +10570,17 @@ async function runDomainList(project, opts) {
|
|
|
10263
10570
|
return;
|
|
10264
10571
|
}
|
|
10265
10572
|
if (!domains.length) {
|
|
10266
|
-
console.log(
|
|
10573
|
+
console.log(import_chalk45.default.yellow("No custom domains."));
|
|
10267
10574
|
return;
|
|
10268
10575
|
}
|
|
10269
10576
|
for (const d of domains) {
|
|
10270
|
-
const pin = scope.kind === "project" && d.tenant ?
|
|
10271
|
-
console.log(` ${
|
|
10577
|
+
const pin = scope.kind === "project" && d.tenant ? import_chalk45.default.dim(` tenant ${d.tenant}`) : "";
|
|
10578
|
+
console.log(` ${import_chalk45.default.bold(d.hostname)} ${statusColor2(d.status)} ${import_chalk45.default.dim(describeKind(d))}${pin} ${import_chalk45.default.dim(d.id ?? "")}`);
|
|
10272
10579
|
}
|
|
10273
10580
|
}
|
|
10274
10581
|
async function runDomainStatus(project, opts) {
|
|
10275
10582
|
if (!opts.id) {
|
|
10276
|
-
console.error(
|
|
10583
|
+
console.error(import_chalk45.default.red("--id <domainId> is required (see `apiblaze domain list`)."));
|
|
10277
10584
|
process.exit(1);
|
|
10278
10585
|
}
|
|
10279
10586
|
const scope = await scopeFor(project, opts);
|
|
@@ -10286,23 +10593,23 @@ async function runDomainStatus(project, opts) {
|
|
|
10286
10593
|
console.log(JSON.stringify(out));
|
|
10287
10594
|
return;
|
|
10288
10595
|
}
|
|
10289
|
-
console.log(` ${
|
|
10596
|
+
console.log(` ${import_chalk45.default.bold(out?.hostname ?? opts.id)}: ${statusColor2(out?.status)} ${import_chalk45.default.dim(describeKind(out))}`);
|
|
10290
10597
|
if (out?.status !== "active") {
|
|
10291
|
-
if (out?.status === "suspended") console.log(
|
|
10292
|
-
else if (out?.status === "missing") console.log(
|
|
10598
|
+
if (out?.status === "suspended") console.log(import_chalk45.default.red(" Suspended for non-payment \u2014 add credit to restore it."));
|
|
10599
|
+
else if (out?.status === "missing") console.log(import_chalk45.default.red(" No longer registered at Cloudflare \u2014 remove it and add it again."));
|
|
10293
10600
|
else {
|
|
10294
|
-
if (out?.cnameRecord) console.log(` ${
|
|
10295
|
-
if (out?.dcv) console.log(` ${
|
|
10601
|
+
if (out?.cnameRecord) console.log(` ${import_chalk45.default.cyan("CNAME")} ${out.cnameRecord.name} \u2192 ${out.cnameRecord.value}`);
|
|
10602
|
+
if (out?.dcv) console.log(` ${import_chalk45.default.cyan("TXT")} ${out.dcv.name} = ${out.dcv.value}`);
|
|
10296
10603
|
}
|
|
10297
10604
|
}
|
|
10298
10605
|
}
|
|
10299
10606
|
async function runDomainRemove(project, opts) {
|
|
10300
10607
|
if (!opts.id) {
|
|
10301
|
-
console.error(
|
|
10608
|
+
console.error(import_chalk45.default.red("--id <domainId> is required (see `apiblaze domain list`)."));
|
|
10302
10609
|
process.exit(1);
|
|
10303
10610
|
}
|
|
10304
10611
|
const scope = await scopeFor(project, opts);
|
|
10305
|
-
const spinner = (0,
|
|
10612
|
+
const spinner = (0, import_ora17.default)("Removing domain...").start();
|
|
10306
10613
|
try {
|
|
10307
10614
|
await admin({
|
|
10308
10615
|
method: "DELETE",
|
|
@@ -10319,7 +10626,7 @@ async function runDomainSetBase(project, opts) {
|
|
|
10319
10626
|
const env = opts.env ?? "prod";
|
|
10320
10627
|
const { teamId } = await resolveTeam(opts.team);
|
|
10321
10628
|
const proj2 = await resolveProject(teamId, project, opts.apiversion);
|
|
10322
|
-
const spinner = (0,
|
|
10629
|
+
const spinner = (0, import_ora17.default)("Setting base domain...").start();
|
|
10323
10630
|
try {
|
|
10324
10631
|
await admin({
|
|
10325
10632
|
method: "PUT",
|
|
@@ -10335,8 +10642,8 @@ async function runDomainSetBase(project, opts) {
|
|
|
10335
10642
|
}
|
|
10336
10643
|
|
|
10337
10644
|
// src/commands/tenant.ts
|
|
10338
|
-
var
|
|
10339
|
-
var
|
|
10645
|
+
var import_chalk48 = __toESM(require("chalk"));
|
|
10646
|
+
var import_ora20 = __toESM(require("ora"));
|
|
10340
10647
|
init_admin();
|
|
10341
10648
|
init_resolve();
|
|
10342
10649
|
init_auth();
|
|
@@ -10345,20 +10652,20 @@ init_paging();
|
|
|
10345
10652
|
async function runTenantUse(query, opts) {
|
|
10346
10653
|
const creds = loadCredentials();
|
|
10347
10654
|
if (!creds) {
|
|
10348
|
-
console.error(
|
|
10655
|
+
console.error(import_chalk48.default.red("Not logged in. Run `apiblaze login` first."));
|
|
10349
10656
|
process.exit(1);
|
|
10350
10657
|
}
|
|
10351
10658
|
if (opts.clear) {
|
|
10352
10659
|
delete creds.activeTenant;
|
|
10353
10660
|
saveCredentials(creds);
|
|
10354
|
-
console.log(
|
|
10661
|
+
console.log(import_chalk48.default.green("Tenant scope cleared."));
|
|
10355
10662
|
return;
|
|
10356
10663
|
}
|
|
10357
10664
|
const { teamId } = await resolveTeam(opts.team);
|
|
10358
10665
|
const slug = await pickTenant(teamId, { message: "Scope future commands to which tenant?", initialQuery: query });
|
|
10359
10666
|
if (!slug) process.exit(1);
|
|
10360
10667
|
saveCredentials({ ...creds, activeTenant: slug });
|
|
10361
|
-
console.log(
|
|
10668
|
+
console.log(import_chalk48.default.green(`Tenant scope set to ${import_chalk48.default.bold(slug)}.`) + import_chalk48.default.dim(" (clear with `apiblaze tenant use --clear`)"));
|
|
10362
10669
|
}
|
|
10363
10670
|
async function runTenantList(opts) {
|
|
10364
10671
|
const { teamId, teamName } = await resolveTeam(opts.team);
|
|
@@ -10382,26 +10689,26 @@ async function runTenantList(opts) {
|
|
|
10382
10689
|
return;
|
|
10383
10690
|
}
|
|
10384
10691
|
if (!tenants.length) {
|
|
10385
|
-
console.log(
|
|
10692
|
+
console.log(import_chalk48.default.yellow(opts.q ? `No tenants matching "${opts.q}".` : "No tenants."));
|
|
10386
10693
|
return;
|
|
10387
10694
|
}
|
|
10388
10695
|
for (const t of tenants) {
|
|
10389
10696
|
const name = typeof t === "string" ? t : t.tenant_name;
|
|
10390
|
-
const display = typeof t === "string" ? "" :
|
|
10391
|
-
console.log(` ${
|
|
10697
|
+
const display = typeof t === "string" ? "" : import_chalk48.default.dim(` ${t.display_name ?? ""}`);
|
|
10698
|
+
console.log(` ${import_chalk48.default.bold(name)}${display}`);
|
|
10392
10699
|
}
|
|
10393
10700
|
const total = out?.total ?? tenants.length;
|
|
10394
10701
|
if (total > tenants.length) {
|
|
10395
|
-
console.log(
|
|
10702
|
+
console.log(import_chalk48.default.dim(` \u2026 showing ${tenants.length} of ${total} \u2014 narrow with --q <search>`));
|
|
10396
10703
|
}
|
|
10397
10704
|
}
|
|
10398
10705
|
async function runTenantCreate(opts) {
|
|
10399
10706
|
if (!opts.name) {
|
|
10400
|
-
console.error(
|
|
10707
|
+
console.error(import_chalk48.default.red("--name (display name) is required."));
|
|
10401
10708
|
process.exit(1);
|
|
10402
10709
|
}
|
|
10403
10710
|
const { teamId } = await resolveTeam(opts.team);
|
|
10404
|
-
const spinner = (0,
|
|
10711
|
+
const spinner = (0, import_ora20.default)("Creating tenant...").start();
|
|
10405
10712
|
try {
|
|
10406
10713
|
const out = await admin({
|
|
10407
10714
|
method: "POST",
|
|
@@ -10409,7 +10716,7 @@ async function runTenantCreate(opts) {
|
|
|
10409
10716
|
body: { display_name: opts.name, ...opts.slug ? { tenant_name: opts.slug } : {} },
|
|
10410
10717
|
summary: `Create tenant "${opts.name}"`
|
|
10411
10718
|
});
|
|
10412
|
-
spinner.succeed(`Created tenant ${
|
|
10719
|
+
spinner.succeed(`Created tenant ${import_chalk48.default.bold(out?.tenant_name ?? opts.name)}.`);
|
|
10413
10720
|
if (opts.json) console.log(JSON.stringify(out));
|
|
10414
10721
|
} catch (err) {
|
|
10415
10722
|
spinner.fail("Tenant create failed.");
|
|
@@ -10418,12 +10725,12 @@ async function runTenantCreate(opts) {
|
|
|
10418
10725
|
}
|
|
10419
10726
|
async function runTenantAttach(project, opts) {
|
|
10420
10727
|
if (!opts.tenant) {
|
|
10421
|
-
console.error(
|
|
10728
|
+
console.error(import_chalk48.default.red("--tenant <slug> is required."));
|
|
10422
10729
|
process.exit(1);
|
|
10423
10730
|
}
|
|
10424
10731
|
const { teamId } = await resolveTeam(opts.team);
|
|
10425
10732
|
const proj2 = await resolveProject(teamId, project, opts.apiversion);
|
|
10426
|
-
const spinner = (0,
|
|
10733
|
+
const spinner = (0, import_ora20.default)("Attaching tenant...").start();
|
|
10427
10734
|
try {
|
|
10428
10735
|
const out = await admin({
|
|
10429
10736
|
method: "POST",
|
|
@@ -10452,11 +10759,11 @@ async function runTenantDelete(slug, opts) {
|
|
|
10452
10759
|
}
|
|
10453
10760
|
]);
|
|
10454
10761
|
if (!confirm2) {
|
|
10455
|
-
console.log(
|
|
10762
|
+
console.log(import_chalk48.default.dim("Aborted."));
|
|
10456
10763
|
return;
|
|
10457
10764
|
}
|
|
10458
10765
|
}
|
|
10459
|
-
const spinner = (0,
|
|
10766
|
+
const spinner = (0, import_ora20.default)(hard ? "Trashing tenant..." : "Deleting tenant...").start();
|
|
10460
10767
|
try {
|
|
10461
10768
|
await admin({
|
|
10462
10769
|
method: "DELETE",
|
|
@@ -10480,7 +10787,7 @@ async function runTenantDelete(slug, opts) {
|
|
|
10480
10787
|
summary: `Purge tenant ${slug} (full cascade)`
|
|
10481
10788
|
});
|
|
10482
10789
|
} catch (err) {
|
|
10483
|
-
console.log(
|
|
10790
|
+
console.log(import_chalk48.default.red("\u2716 Tenant purge failed to start."));
|
|
10484
10791
|
throw err;
|
|
10485
10792
|
}
|
|
10486
10793
|
const job = await settleDelete(accepted, {
|
|
@@ -10496,18 +10803,18 @@ async function runTenantDelete(slug, opts) {
|
|
|
10496
10803
|
...accepted?.job_id ? { job_id: accepted.job_id, job_status: job?.status ?? "queued" } : {}
|
|
10497
10804
|
}));
|
|
10498
10805
|
} else if (opts.wait !== false) {
|
|
10499
|
-
console.log(
|
|
10806
|
+
console.log(import_chalk48.default.green(`\u2714 Deleted tenant ${slug}.`));
|
|
10500
10807
|
}
|
|
10501
10808
|
}
|
|
10502
10809
|
async function runTenantCors(opts) {
|
|
10503
10810
|
if (!opts.tenant) {
|
|
10504
|
-
console.error(
|
|
10811
|
+
console.error(import_chalk48.default.red("--tenant <slug> is required."));
|
|
10505
10812
|
process.exit(1);
|
|
10506
10813
|
}
|
|
10507
10814
|
const { teamId } = await resolveTeam(opts.team);
|
|
10508
10815
|
const origins = (opts.origins ?? "").split(",").map((s) => s.trim()).filter(Boolean);
|
|
10509
10816
|
const cors = origins.length ? { allowed_origins: origins } : null;
|
|
10510
|
-
const spinner = (0,
|
|
10817
|
+
const spinner = (0, import_ora20.default)("Updating CORS...").start();
|
|
10511
10818
|
try {
|
|
10512
10819
|
await admin({
|
|
10513
10820
|
method: "PUT",
|
|
@@ -10523,8 +10830,8 @@ async function runTenantCors(opts) {
|
|
|
10523
10830
|
}
|
|
10524
10831
|
|
|
10525
10832
|
// src/commands/tenant-drill.ts
|
|
10526
|
-
var
|
|
10527
|
-
var
|
|
10833
|
+
var import_chalk49 = __toESM(require("chalk"));
|
|
10834
|
+
var import_ora21 = __toESM(require("ora"));
|
|
10528
10835
|
var import_crypto = require("crypto");
|
|
10529
10836
|
init_admin();
|
|
10530
10837
|
init_auth();
|
|
@@ -10554,17 +10861,17 @@ async function validScopedTenant(teamId, query) {
|
|
|
10554
10861
|
const next = { ...creds };
|
|
10555
10862
|
delete next.activeTenant;
|
|
10556
10863
|
saveCredentials2(next);
|
|
10557
|
-
console.log(
|
|
10864
|
+
console.log(import_chalk49.default.yellow(`Tenant scope "${scoped}" no longer exists in this team \u2014 cleared.`));
|
|
10558
10865
|
return void 0;
|
|
10559
10866
|
}
|
|
10560
10867
|
async function tenantHome(teamId, tenant2) {
|
|
10561
10868
|
const { default: inquirer3 } = await import("inquirer");
|
|
10562
10869
|
const base2 = `/teams/${encodeURIComponent(teamId)}/tenants/${encodeURIComponent(tenant2)}`;
|
|
10563
|
-
console.log(
|
|
10870
|
+
console.log(import_chalk49.default.bold(`
|
|
10564
10871
|
Tenant ${tenant2}`));
|
|
10565
|
-
console.log(
|
|
10872
|
+
console.log(import_chalk49.default.dim("Tenant auth/settings are SHARED: changes apply to every proxy this tenant serves.\n"));
|
|
10566
10873
|
for (; ; ) {
|
|
10567
|
-
const spinner = (0,
|
|
10874
|
+
const spinner = (0, import_ora21.default)("Reading tenant state...").start();
|
|
10568
10875
|
const [iam, cors, emails, issuers, opaque, clients] = await Promise.all([
|
|
10569
10876
|
admin({ method: "GET", path: `${base2}/iam`, summary: "Read IAM toggle" }).catch(() => null),
|
|
10570
10877
|
admin({ method: "GET", path: `${base2}/cors`, summary: "Read tenant CORS" }).catch(() => null),
|
|
@@ -10578,18 +10885,18 @@ Tenant ${tenant2}`));
|
|
|
10578
10885
|
const nClients = Array.isArray(clients) ? clients.length : 0;
|
|
10579
10886
|
const nOpaque = opaque?.opaque?.endpoint ? 1 : 0;
|
|
10580
10887
|
const nLogin = nClients + nIssuers + nOpaque;
|
|
10581
|
-
const onOff = (b) => b ?
|
|
10888
|
+
const onOff = (b) => b ? import_chalk49.default.green("on") : import_chalk49.default.dim("off");
|
|
10582
10889
|
const { pick: pick2 } = await inquirer3.prompt([{
|
|
10583
10890
|
type: "list",
|
|
10584
10891
|
name: "pick",
|
|
10585
10892
|
message: `Tenant ${tenant2}:`,
|
|
10586
10893
|
pageSize: 12,
|
|
10587
10894
|
choices: [
|
|
10588
|
-
{ name: `Login methods (${nLogin}) ${
|
|
10589
|
-
{ name: `Users & groups: ${onOff(iam?.iam_enabled)} ${
|
|
10590
|
-
{ name: `Portal admins (${nEmails}) ${
|
|
10591
|
-
new inquirer3.Separator(
|
|
10592
|
-
{ name: `CORS: ${cors?.cors ?
|
|
10895
|
+
{ name: `Login methods (${nLogin}) ${import_chalk49.default.dim("how your consumers sign in")}`, value: "login" },
|
|
10896
|
+
{ name: `Users & groups: ${onOff(iam?.iam_enabled)} ${import_chalk49.default.dim(`identity & key management (iam.apiblaze.com)`)}`, value: "iam" },
|
|
10897
|
+
{ name: `Portal admins (${nEmails}) ${import_chalk49.default.dim("emails allowed to administer this tenant's portal")}`, value: "emails" },
|
|
10898
|
+
new inquirer3.Separator(import_chalk49.default.dim(" Settings")),
|
|
10899
|
+
{ name: `CORS: ${cors?.cors ? import_chalk49.default.cyan(JSON.stringify(cors.cors)) : import_chalk49.default.dim("(unset)")} ${import_chalk49.default.dim("browser origins allowed to call this tenant")}`, value: "cors" },
|
|
10593
10900
|
{ name: "\u2190 Back", value: "back" }
|
|
10594
10901
|
]
|
|
10595
10902
|
}]);
|
|
@@ -10602,7 +10909,7 @@ Tenant ${tenant2}`));
|
|
|
10602
10909
|
case "iam": {
|
|
10603
10910
|
const { v } = await inquirer3.prompt([{ type: "confirm", name: "v", message: "Enable Users & groups?", default: !!iam?.iam_enabled }]);
|
|
10604
10911
|
await admin({ method: "PATCH", path: `${base2}/iam`, body: { enabled: v }, summary: `IAM enforcement \u2192 ${v ? "on" : "off"}` });
|
|
10605
|
-
console.log(
|
|
10912
|
+
console.log(import_chalk49.default.green(` Users & groups ${v ? "enabled" : "disabled"}.`));
|
|
10606
10913
|
break;
|
|
10607
10914
|
}
|
|
10608
10915
|
case "cors": {
|
|
@@ -10615,11 +10922,11 @@ Tenant ${tenant2}`));
|
|
|
10615
10922
|
if (v === "") break;
|
|
10616
10923
|
const parsed = v === "null" ? null : safeJson(v);
|
|
10617
10924
|
if (parsed === void 0) {
|
|
10618
|
-
console.log(
|
|
10925
|
+
console.log(import_chalk49.default.yellow(" Not valid JSON \u2014 unchanged."));
|
|
10619
10926
|
break;
|
|
10620
10927
|
}
|
|
10621
10928
|
await admin({ method: "PUT", path: `${base2}/cors`, body: { cors: parsed }, summary: "Set tenant CORS" });
|
|
10622
|
-
console.log(
|
|
10929
|
+
console.log(import_chalk49.default.green(" CORS updated."));
|
|
10623
10930
|
break;
|
|
10624
10931
|
}
|
|
10625
10932
|
case "emails":
|
|
@@ -10631,7 +10938,7 @@ Tenant ${tenant2}`));
|
|
|
10631
10938
|
async function loginMethodsMenu(teamId, tenant2, base2) {
|
|
10632
10939
|
const { default: inquirer3 } = await import("inquirer");
|
|
10633
10940
|
for (; ; ) {
|
|
10634
|
-
const spinner = (0,
|
|
10941
|
+
const spinner = (0, import_ora21.default)("Loading login methods...").start();
|
|
10635
10942
|
const [rawClients, rawIssuers, rawOpaque] = await Promise.all([
|
|
10636
10943
|
admin({ method: "GET", path: `${base2}/app-clients`, summary: "List APIblaze-hosted logins" }).catch(() => []),
|
|
10637
10944
|
admin({ method: "GET", path: `${base2}/external-issuers`, summary: "List your-own-JWT logins" }).catch(() => null),
|
|
@@ -10643,15 +10950,15 @@ async function loginMethodsMenu(teamId, tenant2, base2) {
|
|
|
10643
10950
|
const choices = [];
|
|
10644
10951
|
for (const c of appClients) {
|
|
10645
10952
|
const nP = (c.providers ?? []).length || c.providers_count || 0;
|
|
10646
|
-
choices.push({ name: `${
|
|
10953
|
+
choices.push({ name: `${import_chalk49.default.cyan("APIblaze-hosted")} ${import_chalk49.default.bold(c.name ?? c.clientId)} ${import_chalk49.default.dim(`${c.clientId}${nP ? ` \xB7 ${nP} provider${nP === 1 ? "" : "s"}` : import_chalk49.default.yellow(" \xB7 needs a provider")}`)}`, value: { kind: "client", item: c } });
|
|
10647
10954
|
}
|
|
10648
10955
|
for (const i of issuers) {
|
|
10649
|
-
choices.push({ name: `${
|
|
10956
|
+
choices.push({ name: `${import_chalk49.default.cyan("Your own \u2014 JWT ")} ${import_chalk49.default.bold(i.iss)} ${import_chalk49.default.dim(`aud=${i.aud}`)}`, value: { kind: "issuer", item: i } });
|
|
10650
10957
|
}
|
|
10651
10958
|
if (opaque?.endpoint) {
|
|
10652
|
-
choices.push({ name: `${
|
|
10959
|
+
choices.push({ name: `${import_chalk49.default.cyan("Your own \u2014 opaque")} ${import_chalk49.default.bold(opaque.endpoint)}`, value: { kind: "opaque", item: opaque } });
|
|
10653
10960
|
}
|
|
10654
|
-
if (!choices.length) console.log(
|
|
10961
|
+
if (!choices.length) console.log(import_chalk49.default.dim("\n No login methods yet \u2014 consumers cannot sign in until you add one."));
|
|
10655
10962
|
const { pick: pick2 } = await inquirer3.prompt([{
|
|
10656
10963
|
type: "list",
|
|
10657
10964
|
name: "pick",
|
|
@@ -10691,9 +10998,9 @@ async function addLoginMethod(teamId, tenant2, base2) {
|
|
|
10691
10998
|
message: "How do people log in?",
|
|
10692
10999
|
pageSize: 8,
|
|
10693
11000
|
choices: [
|
|
10694
|
-
{ name: `APIblaze hosted login page ${
|
|
10695
|
-
{ name: `Your own hosted login \u2014 JWT ${
|
|
10696
|
-
{ name: `Your own login \u2014 opaque token ${
|
|
11001
|
+
{ name: `APIblaze hosted login page ${import_chalk49.default.dim("we host login; pick a provider (GitHub/Google/\u2026) \u2014 easiest")}`, value: "apiblaze" },
|
|
11002
|
+
{ name: `Your own hosted login \u2014 JWT ${import_chalk49.default.dim("your identity provider issues JWTs; we trust them")}`, value: "jwt" },
|
|
11003
|
+
{ name: `Your own login \u2014 opaque token ${import_chalk49.default.dim("we validate your opaque tokens via an introspection endpoint")}`, value: "opaque" },
|
|
10697
11004
|
{ name: "\u2190 Back", value: null }
|
|
10698
11005
|
]
|
|
10699
11006
|
}]);
|
|
@@ -10717,8 +11024,8 @@ function safeJson(s) {
|
|
|
10717
11024
|
async function emailsMenu(base2, emails) {
|
|
10718
11025
|
const { default: inquirer3 } = await import("inquirer");
|
|
10719
11026
|
console.log();
|
|
10720
|
-
if (!emails.length) console.log(
|
|
10721
|
-
for (const e of emails) console.log(` ${
|
|
11027
|
+
if (!emails.length) console.log(import_chalk49.default.dim(" No consumer-admin emails."));
|
|
11028
|
+
for (const e of emails) console.log(` ${import_chalk49.default.bold(e.email ?? e)} ${import_chalk49.default.dim(e.status ?? "")}`);
|
|
10722
11029
|
const { act } = await inquirer3.prompt([{
|
|
10723
11030
|
type: "list",
|
|
10724
11031
|
name: "act",
|
|
@@ -10733,7 +11040,7 @@ async function emailsMenu(base2, emails) {
|
|
|
10733
11040
|
if (act === "add") {
|
|
10734
11041
|
const { email } = await inquirer3.prompt([{ type: "input", name: "email", message: "Email:", validate: (s) => /.+@.+\..+/.test(s) || "not an email" }]);
|
|
10735
11042
|
await admin({ method: "POST", path: `${base2}/admin-emails`, body: { email }, summary: `Add consumer-admin ${email}` });
|
|
10736
|
-
console.log(
|
|
11043
|
+
console.log(import_chalk49.default.green(` ${email} added.`));
|
|
10737
11044
|
} else {
|
|
10738
11045
|
const { e } = await inquirer3.prompt([{
|
|
10739
11046
|
type: "list",
|
|
@@ -10743,7 +11050,7 @@ async function emailsMenu(base2, emails) {
|
|
|
10743
11050
|
}]);
|
|
10744
11051
|
if (!e) return;
|
|
10745
11052
|
await admin({ method: "DELETE", path: `${base2}/admin-emails/${encodeURIComponent(e)}`, summary: `Remove consumer-admin ${e}` });
|
|
10746
|
-
console.log(
|
|
11053
|
+
console.log(import_chalk49.default.green(` ${e} removed.`));
|
|
10747
11054
|
}
|
|
10748
11055
|
}
|
|
10749
11056
|
async function addIssuer(base2) {
|
|
@@ -10764,19 +11071,19 @@ async function addIssuer(base2) {
|
|
|
10764
11071
|
body: { iss: a.iss.trim(), aud: a.aud.trim(), jwks_url: a.jwks.trim() || null, sub_semantics: a.sem, ...claim ? { claim_name: claim } : {} },
|
|
10765
11072
|
summary: `Add external issuer ${a.iss.trim()}`
|
|
10766
11073
|
});
|
|
10767
|
-
console.log(
|
|
11074
|
+
console.log(import_chalk49.default.green(" JWT login method saved."));
|
|
10768
11075
|
}
|
|
10769
11076
|
async function issuerHome(base2, issuer) {
|
|
10770
11077
|
const { default: inquirer3 } = await import("inquirer");
|
|
10771
11078
|
console.log(`
|
|
10772
|
-
${
|
|
11079
|
+
${import_chalk49.default.bold(issuer.iss)} ${import_chalk49.default.dim(`aud=${issuer.aud} \xB7 ${issuer.sub_semantics ?? ""}`)}`);
|
|
10773
11080
|
const { act } = await inquirer3.prompt([{
|
|
10774
11081
|
type: "list",
|
|
10775
11082
|
name: "act",
|
|
10776
11083
|
message: "This JWT login method:",
|
|
10777
11084
|
choices: [
|
|
10778
11085
|
{ name: "Replace it (re-enter details)", value: "edit" },
|
|
10779
|
-
{ name:
|
|
11086
|
+
{ name: import_chalk49.default.red("Delete it"), value: "rm" },
|
|
10780
11087
|
{ name: "\u2190 Back", value: "back" }
|
|
10781
11088
|
]
|
|
10782
11089
|
}]);
|
|
@@ -10792,7 +11099,7 @@ async function issuerHome(base2, issuer) {
|
|
|
10792
11099
|
path: `${base2}/external-issuers?iss=${encodeURIComponent(issuer.iss)}&aud=${encodeURIComponent(issuer.aud)}`,
|
|
10793
11100
|
summary: `Delete issuer ${issuer.iss}`
|
|
10794
11101
|
});
|
|
10795
|
-
console.log(
|
|
11102
|
+
console.log(import_chalk49.default.green(" Deleted."));
|
|
10796
11103
|
}
|
|
10797
11104
|
async function setOpaque(base2, cur) {
|
|
10798
11105
|
const { default: inquirer3 } = await import("inquirer");
|
|
@@ -10801,19 +11108,19 @@ async function setOpaque(base2, cur) {
|
|
|
10801
11108
|
{ type: "list", name: "method", message: "HTTP method:", choices: ["GET", "POST"], default: cur?.method ?? "GET" }
|
|
10802
11109
|
]);
|
|
10803
11110
|
await admin({ method: "PUT", path: `${base2}/opaque`, body: { opaque: { endpoint: a.endpoint, method: a.method } }, summary: "Set opaque validator" });
|
|
10804
|
-
console.log(
|
|
11111
|
+
console.log(import_chalk49.default.green(" Opaque login method set."));
|
|
10805
11112
|
}
|
|
10806
11113
|
async function opaqueHome(base2, cur) {
|
|
10807
11114
|
const { default: inquirer3 } = await import("inquirer");
|
|
10808
11115
|
console.log(`
|
|
10809
|
-
${
|
|
11116
|
+
${import_chalk49.default.bold(cur.endpoint)} ${import_chalk49.default.dim(cur.method ?? "GET")}`);
|
|
10810
11117
|
const { act } = await inquirer3.prompt([{
|
|
10811
11118
|
type: "list",
|
|
10812
11119
|
name: "act",
|
|
10813
11120
|
message: "This opaque login method:",
|
|
10814
11121
|
choices: [
|
|
10815
11122
|
{ name: "Replace it", value: "edit" },
|
|
10816
|
-
{ name:
|
|
11123
|
+
{ name: import_chalk49.default.red("Delete it"), value: "rm" },
|
|
10817
11124
|
{ name: "\u2190 Back", value: "back" }
|
|
10818
11125
|
]
|
|
10819
11126
|
}]);
|
|
@@ -10823,14 +11130,14 @@ async function opaqueHome(base2, cur) {
|
|
|
10823
11130
|
return;
|
|
10824
11131
|
}
|
|
10825
11132
|
await admin({ method: "PUT", path: `${base2}/opaque`, body: { opaque: null }, summary: "Clear opaque validator" });
|
|
10826
|
-
console.log(
|
|
11133
|
+
console.log(import_chalk49.default.green(" Deleted."));
|
|
10827
11134
|
}
|
|
10828
11135
|
async function clientHome(base2, summary) {
|
|
10829
11136
|
const { default: inquirer3 } = await import("inquirer");
|
|
10830
11137
|
const id = summary.clientId ?? summary.client_id;
|
|
10831
11138
|
const cBase = `${base2}/app-clients/${encodeURIComponent(id)}`;
|
|
10832
11139
|
for (; ; ) {
|
|
10833
|
-
const spinner = (0,
|
|
11140
|
+
const spinner = (0, import_ora21.default)("Reading app client...").start();
|
|
10834
11141
|
const c = await admin({ method: "GET", path: cBase, summary: `Read app client ${id}` }).catch(() => summary);
|
|
10835
11142
|
spinner.stop();
|
|
10836
11143
|
const cb = c.authorizedCallbackUrls ?? c.authorized_callback_urls ?? [];
|
|
@@ -10842,13 +11149,13 @@ async function clientHome(base2, summary) {
|
|
|
10842
11149
|
message: `${c.name ?? id}:`,
|
|
10843
11150
|
pageSize: 12,
|
|
10844
11151
|
choices: [
|
|
10845
|
-
{ name: `Login providers${nProviders ? ` (${nProviders})` : ""} ${
|
|
10846
|
-
{ name: `Callback URLs: ${cb.length ?
|
|
10847
|
-
{ name: `Scopes: ${scopes.length ?
|
|
11152
|
+
{ name: `Login providers${nProviders ? ` (${nProviders})` : ""} ${import_chalk49.default.dim("google/github/microsoft/\u2026 \u2014 how consumers sign in")}`, value: "providers" },
|
|
11153
|
+
{ name: `Callback URLs: ${cb.length ? import_chalk49.default.cyan(cb.join(", ")) : import_chalk49.default.dim("(none)")}`, value: "callbacks" },
|
|
11154
|
+
{ name: `Scopes: ${scopes.length ? import_chalk49.default.cyan(scopes.join(" ")) : import_chalk49.default.dim("(defaults)")}`, value: "scopes" },
|
|
10848
11155
|
{ name: `Token expiries: access ${c.accessTokenExpiry ?? 3600}s \xB7 id ${c.idTokenExpiry ?? 3600}s \xB7 refresh ${c.refreshTokenExpiry ?? 2592e3}s`, value: "expiries" },
|
|
10849
11156
|
{ name: "Reveal client secret", value: "secret" },
|
|
10850
11157
|
{ name: "Rotate client secret", value: "rotate" },
|
|
10851
|
-
{ name:
|
|
11158
|
+
{ name: import_chalk49.default.red("Delete this app client"), value: "delete" },
|
|
10852
11159
|
{ name: "\u2190 Back", value: "back" }
|
|
10853
11160
|
]
|
|
10854
11161
|
}]);
|
|
@@ -10861,13 +11168,13 @@ async function clientHome(base2, summary) {
|
|
|
10861
11168
|
case "callbacks": {
|
|
10862
11169
|
const { v } = await inquirer3.prompt([{ type: "input", name: "v", message: "Callback URLs (comma-separated):", default: cb.join(", ") }]);
|
|
10863
11170
|
await admin({ method: "PATCH", path: cBase, body: { authorizedCallbackUrls: parseList(v) }, summary: "Update callback URLs" });
|
|
10864
|
-
console.log(
|
|
11171
|
+
console.log(import_chalk49.default.green(" Callbacks updated."));
|
|
10865
11172
|
break;
|
|
10866
11173
|
}
|
|
10867
11174
|
case "scopes": {
|
|
10868
11175
|
const { v } = await inquirer3.prompt([{ type: "input", name: "v", message: "Scopes (space/comma-separated):", default: scopes.join(" ") }]);
|
|
10869
11176
|
await admin({ method: "PATCH", path: cBase, body: { scopes: v.split(/[\s,]+/).filter(Boolean) }, summary: "Update scopes" });
|
|
10870
|
-
console.log(
|
|
11177
|
+
console.log(import_chalk49.default.green(" Scopes updated."));
|
|
10871
11178
|
break;
|
|
10872
11179
|
}
|
|
10873
11180
|
case "expiries": {
|
|
@@ -10882,14 +11189,14 @@ async function clientHome(base2, summary) {
|
|
|
10882
11189
|
body: { accessTokenExpiry: Number(a.access), idTokenExpiry: Number(a.id), refreshTokenExpiry: Number(a.refresh) },
|
|
10883
11190
|
summary: "Update token expiries"
|
|
10884
11191
|
});
|
|
10885
|
-
console.log(
|
|
11192
|
+
console.log(import_chalk49.default.green(" Expiries updated."));
|
|
10886
11193
|
break;
|
|
10887
11194
|
}
|
|
10888
11195
|
case "secret": {
|
|
10889
11196
|
const { sure } = await inquirer3.prompt([{ type: "confirm", name: "sure", message: "Print the client secret to this terminal?", default: false }]);
|
|
10890
11197
|
if (!sure) break;
|
|
10891
11198
|
const s = await admin({ method: "GET", path: `${cBase}/secret`, summary: "Reveal client secret" });
|
|
10892
|
-
console.log(` ${
|
|
11199
|
+
console.log(` ${import_chalk49.default.bold("client_secret")}: ${import_chalk49.default.green(s?.clientSecret ?? s?.client_secret ?? JSON.stringify(s))}`);
|
|
10893
11200
|
break;
|
|
10894
11201
|
}
|
|
10895
11202
|
case "rotate": {
|
|
@@ -10897,14 +11204,14 @@ async function clientHome(base2, summary) {
|
|
|
10897
11204
|
if (!sure) break;
|
|
10898
11205
|
const fresh = randomSecret();
|
|
10899
11206
|
await admin({ method: "PATCH", path: cBase, body: { clientSecret: fresh }, summary: "Rotate client secret" });
|
|
10900
|
-
console.log(` New ${
|
|
11207
|
+
console.log(` New ${import_chalk49.default.bold("client_secret")}: ${import_chalk49.default.green(fresh)} ${import_chalk49.default.dim("(store it now)")}`);
|
|
10901
11208
|
break;
|
|
10902
11209
|
}
|
|
10903
11210
|
case "delete": {
|
|
10904
11211
|
const { sure } = await inquirer3.prompt([{ type: "confirm", name: "sure", message: `Delete app client "${c.name ?? id}"? Consumers logged in through it will lose access.`, default: false }]);
|
|
10905
11212
|
if (!sure) break;
|
|
10906
11213
|
await admin({ method: "DELETE", path: cBase, summary: `Delete app client ${id}` });
|
|
10907
|
-
console.log(
|
|
11214
|
+
console.log(import_chalk49.default.green(" App client deleted."));
|
|
10908
11215
|
return;
|
|
10909
11216
|
}
|
|
10910
11217
|
}
|
|
@@ -10925,15 +11232,15 @@ var DEFAULT_SCOPES2 = {
|
|
|
10925
11232
|
async function providersMenu(cBase, clientLabel) {
|
|
10926
11233
|
const { default: inquirer3 } = await import("inquirer");
|
|
10927
11234
|
for (; ; ) {
|
|
10928
|
-
const spinner = (0,
|
|
11235
|
+
const spinner = (0, import_ora21.default)("Loading providers...").start();
|
|
10929
11236
|
const raw = await admin({ method: "GET", path: `${cBase}/providers`, summary: "List login providers" }).catch(() => []);
|
|
10930
11237
|
spinner.stop();
|
|
10931
11238
|
const providers = Array.isArray(raw) ? raw : [];
|
|
10932
11239
|
console.log();
|
|
10933
11240
|
for (const p of providers) {
|
|
10934
|
-
console.log(` ${
|
|
11241
|
+
console.log(` ${import_chalk49.default.bold(p.type)} ${import_chalk49.default.dim(`${p.clientId || "(managed)"} \xB7 identity=${p.tokenType ?? "apiblaze"} \xB7 to-upstream=${p.targetServerToken ?? "apiblaze"}${p.isApiblazeDefault ? " \xB7 apiblaze-managed" : ""}`)}`);
|
|
10935
11242
|
}
|
|
10936
|
-
if (!providers.length) console.log(
|
|
11243
|
+
if (!providers.length) console.log(import_chalk49.default.dim(" No login providers \u2014 consumers cannot sign in to this client yet."));
|
|
10937
11244
|
const { act } = await inquirer3.prompt([{
|
|
10938
11245
|
type: "list",
|
|
10939
11246
|
name: "act",
|
|
@@ -10991,23 +11298,23 @@ async function providersMenu(cBase, clientLabel) {
|
|
|
10991
11298
|
body.targetServerToken = routing;
|
|
10992
11299
|
}
|
|
10993
11300
|
await admin({ method: "POST", path: `${cBase}/providers`, body, summary: `Add ${type} login provider` });
|
|
10994
|
-
console.log(
|
|
11301
|
+
console.log(import_chalk49.default.green(` ${type} provider added.`));
|
|
10995
11302
|
} else {
|
|
10996
11303
|
const { p } = await inquirer3.prompt([{
|
|
10997
11304
|
type: "list",
|
|
10998
11305
|
name: "p",
|
|
10999
11306
|
message: act === "rm" ? "Remove which provider?" : "Reveal which secret?",
|
|
11000
|
-
choices: [...providers.map((x) => ({ name: `${x.type} ${
|
|
11307
|
+
choices: [...providers.map((x) => ({ name: `${x.type} ${import_chalk49.default.dim(x.clientId || "(managed)")}`, value: x })), { name: "\u2190 Back", value: null }]
|
|
11001
11308
|
}]);
|
|
11002
11309
|
if (!p) continue;
|
|
11003
11310
|
if (act === "rm") {
|
|
11004
11311
|
const { sure } = await inquirer3.prompt([{ type: "confirm", name: "sure", message: `Remove the ${p.type} provider? Consumers using it can no longer sign in.`, default: false }]);
|
|
11005
11312
|
if (!sure) continue;
|
|
11006
11313
|
await admin({ method: "DELETE", path: `${cBase}/providers/${encodeURIComponent(p.id)}`, summary: `Remove ${p.type} provider` });
|
|
11007
|
-
console.log(
|
|
11314
|
+
console.log(import_chalk49.default.green(` ${p.type} removed.`));
|
|
11008
11315
|
} else {
|
|
11009
11316
|
const s = await admin({ method: "GET", path: `${cBase}/providers/${encodeURIComponent(p.id)}/secret`, summary: `Reveal ${p.type} provider secret` });
|
|
11010
|
-
console.log(` ${
|
|
11317
|
+
console.log(` ${import_chalk49.default.bold("client_secret")}: ${import_chalk49.default.green(s?.clientSecret ?? s?.client_secret ?? JSON.stringify(s))}`);
|
|
11011
11318
|
}
|
|
11012
11319
|
}
|
|
11013
11320
|
}
|
|
@@ -11015,8 +11322,8 @@ async function providersMenu(cBase, clientLabel) {
|
|
|
11015
11322
|
|
|
11016
11323
|
// src/commands/spec.ts
|
|
11017
11324
|
var fs12 = __toESM(require("fs"));
|
|
11018
|
-
var
|
|
11019
|
-
var
|
|
11325
|
+
var import_chalk50 = __toESM(require("chalk"));
|
|
11326
|
+
var import_ora22 = __toESM(require("ora"));
|
|
11020
11327
|
init_admin();
|
|
11021
11328
|
init_api();
|
|
11022
11329
|
init_resolve();
|
|
@@ -11032,19 +11339,19 @@ async function runSpecGet(project, opts) {
|
|
|
11032
11339
|
}
|
|
11033
11340
|
async function runSpecSet(project, opts) {
|
|
11034
11341
|
if (!opts.file) {
|
|
11035
|
-
console.error(
|
|
11342
|
+
console.error(import_chalk50.default.red("--file <path> is required (OpenAPI JSON or YAML)."));
|
|
11036
11343
|
process.exit(1);
|
|
11037
11344
|
}
|
|
11038
11345
|
let specContent;
|
|
11039
11346
|
try {
|
|
11040
11347
|
specContent = fs12.readFileSync(opts.file, "utf-8");
|
|
11041
11348
|
} catch {
|
|
11042
|
-
console.error(
|
|
11349
|
+
console.error(import_chalk50.default.red(`Cannot read file: ${opts.file}`));
|
|
11043
11350
|
process.exit(1);
|
|
11044
11351
|
}
|
|
11045
11352
|
const { teamId } = await resolveTeam(opts.team);
|
|
11046
11353
|
const proj2 = await resolveProject(teamId, project, opts.apiversion);
|
|
11047
|
-
const spinner = (0,
|
|
11354
|
+
const spinner = (0, import_ora22.default)("Uploading spec...").start();
|
|
11048
11355
|
try {
|
|
11049
11356
|
const out = await admin({
|
|
11050
11357
|
method: "POST",
|
|
@@ -11068,28 +11375,28 @@ async function runSpecSet(project, opts) {
|
|
|
11068
11375
|
function printSpecChangeReport(report, projectName) {
|
|
11069
11376
|
const r = report.routes;
|
|
11070
11377
|
if (r) {
|
|
11071
|
-
for (const a of r.added) console.log(
|
|
11072
|
-
for (const d of r.removed) console.log(
|
|
11073
|
-
for (const rn of r.renamed ?? []) console.log(
|
|
11074
|
-
for (const m of r.modified ?? []) console.log(
|
|
11378
|
+
for (const a of r.added) console.log(import_chalk50.default.green(` + ${a}`));
|
|
11379
|
+
for (const d of r.removed) console.log(import_chalk50.default.red(` \u2212 ${d}`));
|
|
11380
|
+
for (const rn of r.renamed ?? []) console.log(import_chalk50.default.yellow(` ~ ${rn.from} \u2192 ${rn.to}`));
|
|
11381
|
+
for (const m of r.modified ?? []) console.log(import_chalk50.default.dim(` \xB1 ${m} (params/schema changed)`));
|
|
11075
11382
|
}
|
|
11076
|
-
if (report.applied?.mcp_regenerated) console.log(
|
|
11077
|
-
if (report.applied?.candidates_cleared) console.log(
|
|
11383
|
+
if (report.applied?.mcp_regenerated) console.log(import_chalk50.default.dim(" MCP catalogue regenerated."));
|
|
11384
|
+
if (report.applied?.candidates_cleared) console.log(import_chalk50.default.dim(` ${report.applied.candidates_cleared} route candidate(s) now documented \u2014 cleared.`));
|
|
11078
11385
|
for (const item of report.attention ?? []) {
|
|
11079
|
-
console.log(
|
|
11386
|
+
console.log(import_chalk50.default.yellow(` \u26A0 ${item.detail}`));
|
|
11080
11387
|
if (item.suggestion?.carry_from && item.suggestion.carry_to) {
|
|
11081
|
-
console.log(
|
|
11388
|
+
console.log(import_chalk50.default.dim(` Fix: apiblaze spec carry ${projectName} --method ${item.suggestion.method} --from "${item.suggestion.carry_from}" --to "${item.suggestion.carry_to}"`));
|
|
11082
11389
|
}
|
|
11083
11390
|
}
|
|
11084
11391
|
}
|
|
11085
11392
|
async function runSpecCarry(project, opts) {
|
|
11086
11393
|
if (!opts.method || !opts.from || !opts.to) {
|
|
11087
|
-
console.error(
|
|
11394
|
+
console.error(import_chalk50.default.red("--method, --from and --to are required."));
|
|
11088
11395
|
process.exit(1);
|
|
11089
11396
|
}
|
|
11090
11397
|
const { teamId } = await resolveTeam(opts.team);
|
|
11091
11398
|
const proj2 = await resolveProject(teamId, project, opts.apiversion);
|
|
11092
|
-
const spinner = (0,
|
|
11399
|
+
const spinner = (0, import_ora22.default)(`Carrying rules ${opts.from} \u2192 ${opts.to}...`).start();
|
|
11093
11400
|
const res = await agentCall(`/projects/${proj2.projectId}/${proj2.apiVersion}/policies/route/carry`, "POST", {
|
|
11094
11401
|
method: opts.method.toUpperCase(),
|
|
11095
11402
|
from_resource: opts.from,
|
|
@@ -11106,13 +11413,13 @@ async function runSpecCarry(project, opts) {
|
|
|
11106
11413
|
}
|
|
11107
11414
|
async function runSpecDeleteRule(project, opts) {
|
|
11108
11415
|
if (!opts.method || !opts.route) {
|
|
11109
|
-
console.error(
|
|
11416
|
+
console.error(import_chalk50.default.red("--method and --route are required."));
|
|
11110
11417
|
process.exit(1);
|
|
11111
11418
|
}
|
|
11112
11419
|
const { teamId } = await resolveTeam(opts.team);
|
|
11113
11420
|
const proj2 = await resolveProject(teamId, project, opts.apiversion);
|
|
11114
11421
|
const encoded = opts.route.replace(/\{/g, "%7B").replace(/\}/g, "%7D");
|
|
11115
|
-
const spinner = (0,
|
|
11422
|
+
const spinner = (0, import_ora22.default)(`Deleting rules for ${opts.method.toUpperCase()} ${opts.route}...`).start();
|
|
11116
11423
|
const res = await agentCall(
|
|
11117
11424
|
`/projects/${proj2.projectId}/${proj2.apiVersion}/policies/route/${opts.method.toUpperCase()}${encoded.startsWith("/") ? encoded : `/${encoded}`}`,
|
|
11118
11425
|
"DELETE"
|
|
@@ -11126,14 +11433,14 @@ async function runSpecDeleteRule(project, opts) {
|
|
|
11126
11433
|
}
|
|
11127
11434
|
|
|
11128
11435
|
// src/commands/agent.ts
|
|
11129
|
-
var
|
|
11130
|
-
var
|
|
11436
|
+
var import_chalk52 = __toESM(require("chalk"));
|
|
11437
|
+
var import_ora23 = __toESM(require("ora"));
|
|
11131
11438
|
init_auth();
|
|
11132
11439
|
init_admin();
|
|
11133
11440
|
init_resolve();
|
|
11134
11441
|
|
|
11135
11442
|
// src/lib/tools.ts
|
|
11136
|
-
var
|
|
11443
|
+
var import_chalk51 = __toESM(require("chalk"));
|
|
11137
11444
|
init_admin();
|
|
11138
11445
|
init_api();
|
|
11139
11446
|
init_resolve();
|
|
@@ -11164,15 +11471,15 @@ var TOOLS = [
|
|
|
11164
11471
|
const key = keys.dev ?? Object.values(keys)[0];
|
|
11165
11472
|
const url = `https://${a.name}.abz.run/${version2}/dev`;
|
|
11166
11473
|
const tryIt = buildTryItCurl(url, auth, key, res.tenant);
|
|
11167
|
-
const lines = [` ${
|
|
11168
|
-
if (res.devPortal) lines.push(` ${
|
|
11474
|
+
const lines = [` ${import_chalk51.default.dim("Proxy URL:")} ${import_chalk51.default.bold(url)}`];
|
|
11475
|
+
if (res.devPortal) lines.push(` ${import_chalk51.default.dim("Dev portal:")} ${res.devPortal}`);
|
|
11169
11476
|
const envs = Object.keys(keys);
|
|
11170
11477
|
if (envs.length) {
|
|
11171
|
-
lines.push("", ` ${
|
|
11478
|
+
lines.push("", ` ${import_chalk51.default.bold("API keys")} ${import_chalk51.default.dim("(bootstrapped \u2014 send as the X-API-Key header; shown once):")}`);
|
|
11172
11479
|
const w = Math.max(...envs.map((e) => e.length));
|
|
11173
|
-
for (const env of envs) lines.push(` ${
|
|
11480
|
+
for (const env of envs) lines.push(` ${import_chalk51.default.cyan(env.padEnd(w))} ${import_chalk51.default.green(keys[env])}`);
|
|
11174
11481
|
}
|
|
11175
|
-
if (tryIt) lines.push("", ` ${
|
|
11482
|
+
if (tryIt) lines.push("", ` ${import_chalk51.default.dim("Try it:")}`, ` ${import_chalk51.default.cyan(tryIt)}`);
|
|
11176
11483
|
return { ...res, proxy_url: url, keys, ...tryIt ? { try_it: tryIt } : {}, display: lines.join("\n") };
|
|
11177
11484
|
}
|
|
11178
11485
|
},
|
|
@@ -11341,23 +11648,23 @@ function truncate(value, max = 1500) {
|
|
|
11341
11648
|
}
|
|
11342
11649
|
function printCost(resp) {
|
|
11343
11650
|
const line = formatBilling(resp.billing);
|
|
11344
|
-
if (line) console.log(
|
|
11651
|
+
if (line) console.log(import_chalk52.default.magenta(` ${line}`) + import_chalk52.default.dim(` (${resp.llm.model}, ${resp.llm.total_tokens} tok)`));
|
|
11345
11652
|
}
|
|
11346
11653
|
async function runAgent(opts) {
|
|
11347
11654
|
requireAuth();
|
|
11348
11655
|
const { teamId, teamName } = await resolveTeam(opts.team);
|
|
11349
11656
|
const { default: inquirer3 } = await import("inquirer");
|
|
11350
|
-
console.log(
|
|
11351
|
-
console.log(
|
|
11657
|
+
console.log(import_chalk52.default.bold("APIblaze agent") + import_chalk52.default.dim(` \xB7 team ${teamName ?? teamId}`));
|
|
11658
|
+
console.log(import_chalk52.default.dim('Ask me to create/delete/configure proxies, tenants, keys, domains, specs. Type "exit" to quit.\n'));
|
|
11352
11659
|
const history = [];
|
|
11353
11660
|
while (true) {
|
|
11354
|
-
const { input } = await inquirer3.prompt([{ type: "input", name: "input", message:
|
|
11661
|
+
const { input } = await inquirer3.prompt([{ type: "input", name: "input", message: import_chalk52.default.cyan("you") + " \u203A" }]);
|
|
11355
11662
|
const text = (input ?? "").trim();
|
|
11356
11663
|
if (!text) continue;
|
|
11357
11664
|
if (["exit", "quit", ":q"].includes(text.toLowerCase())) break;
|
|
11358
11665
|
history.push({ role: "user", content: text });
|
|
11359
11666
|
for (let step = 0; step < MAX_TOOL_STEPS; step++) {
|
|
11360
|
-
const spinner = (0,
|
|
11667
|
+
const spinner = (0, import_ora23.default)({ text: "thinking...", color: "magenta" }).start();
|
|
11361
11668
|
let resp;
|
|
11362
11669
|
try {
|
|
11363
11670
|
resp = await callAgent(history, teamId);
|
|
@@ -11365,21 +11672,21 @@ async function runAgent(opts) {
|
|
|
11365
11672
|
} catch (err) {
|
|
11366
11673
|
spinner.stop();
|
|
11367
11674
|
if (err instanceof ApiError && err.status === 402) {
|
|
11368
|
-
console.log(
|
|
11675
|
+
console.log(import_chalk52.default.yellow(" Insufficient credits \u2014 top up to keep using the agent."));
|
|
11369
11676
|
break;
|
|
11370
11677
|
}
|
|
11371
11678
|
throw err;
|
|
11372
11679
|
}
|
|
11373
11680
|
history.push({ role: "assistant", content: resp.raw });
|
|
11374
11681
|
printCost(resp);
|
|
11375
|
-
if (resp.reply) console.log(
|
|
11682
|
+
if (resp.reply) console.log(import_chalk52.default.green("agent") + " \u203A " + resp.reply);
|
|
11376
11683
|
if (!resp.action) break;
|
|
11377
11684
|
const tool = findTool(resp.action.tool);
|
|
11378
11685
|
if (!tool) {
|
|
11379
11686
|
history.push({ role: "user", content: `TOOL_RESULT ${resp.action.tool}: error \u2014 unknown tool` });
|
|
11380
11687
|
continue;
|
|
11381
11688
|
}
|
|
11382
|
-
const runSpinner = (0,
|
|
11689
|
+
const runSpinner = (0, import_ora23.default)({ text: `running ${tool.name}...`, color: "cyan" }).start();
|
|
11383
11690
|
try {
|
|
11384
11691
|
const result = await tool.run(resp.action.args, { teamId });
|
|
11385
11692
|
runSpinner.succeed(`${tool.name} \u2713`);
|
|
@@ -11397,11 +11704,11 @@ async function runAgent(opts) {
|
|
|
11397
11704
|
}
|
|
11398
11705
|
renderTrace();
|
|
11399
11706
|
if (step === MAX_TOOL_STEPS - 1) {
|
|
11400
|
-
console.log(
|
|
11707
|
+
console.log(import_chalk52.default.dim(" (paused after several steps \u2014 tell me how to continue)"));
|
|
11401
11708
|
}
|
|
11402
11709
|
}
|
|
11403
11710
|
}
|
|
11404
|
-
console.log(
|
|
11711
|
+
console.log(import_chalk52.default.dim("\nBye."));
|
|
11405
11712
|
}
|
|
11406
11713
|
|
|
11407
11714
|
// src/commands/config-browse.ts
|
|
@@ -11709,11 +12016,11 @@ function dig(blob, dotted) {
|
|
|
11709
12016
|
}
|
|
11710
12017
|
var readSetting = (s, cfg) => s.read ? s.read(cfg) : dig(cfg, s.key);
|
|
11711
12018
|
function show(v) {
|
|
11712
|
-
if (v === void 0) return
|
|
11713
|
-
if (v === null) return
|
|
11714
|
-
if (typeof v === "object") return
|
|
11715
|
-
if (typeof v === "boolean") return v ?
|
|
11716
|
-
return
|
|
12019
|
+
if (v === void 0) return import_chalk53.default.dim("(unset)");
|
|
12020
|
+
if (v === null) return import_chalk53.default.dim("null");
|
|
12021
|
+
if (typeof v === "object") return import_chalk53.default.cyan(JSON.stringify(v));
|
|
12022
|
+
if (typeof v === "boolean") return v ? import_chalk53.default.green("on") : import_chalk53.default.red("off");
|
|
12023
|
+
return import_chalk53.default.cyan(String(v));
|
|
11717
12024
|
}
|
|
11718
12025
|
function parseValue(raw) {
|
|
11719
12026
|
if (raw === "true") return true;
|
|
@@ -11748,7 +12055,7 @@ async function patchSetting(proj2, s, value, cfg) {
|
|
|
11748
12055
|
if (!tenant2) throw new Error(`Could not tell which tenant ${proj2.projectName} serves \u2014 pass it via \`apiblaze config ${proj2.projectName} --tenant <slug>\` is not supported yet; run \`apiblaze projects\` and check the project has a tenant.`);
|
|
11749
12056
|
body.tenant = tenant2;
|
|
11750
12057
|
}
|
|
11751
|
-
const spinner = (0,
|
|
12058
|
+
const spinner = (0, import_ora24.default)(`Set ${s.key}...`).start();
|
|
11752
12059
|
try {
|
|
11753
12060
|
await admin({
|
|
11754
12061
|
method: "PATCH",
|
|
@@ -11763,10 +12070,10 @@ async function patchSetting(proj2, s, value, cfg) {
|
|
|
11763
12070
|
}
|
|
11764
12071
|
}
|
|
11765
12072
|
var loginFirst = (what) => {
|
|
11766
|
-
console.log(
|
|
12073
|
+
console.log(import_chalk53.default.yellow(`
|
|
11767
12074
|
Log in first to ${what}.`));
|
|
11768
|
-
console.log(
|
|
11769
|
-
console.log(
|
|
12075
|
+
console.log(import_chalk53.default.dim(" Run `npx apiblaze login` \u2014 or `npx apiblaze claim` if you created this proxy"));
|
|
12076
|
+
console.log(import_chalk53.default.dim(" anonymously and want to bring it into your account.\n"));
|
|
11770
12077
|
};
|
|
11771
12078
|
async function runConfig(project, key, value, opts) {
|
|
11772
12079
|
const creds = loadCredentials();
|
|
@@ -11783,9 +12090,9 @@ async function runConfig(project, key, value, opts) {
|
|
|
11783
12090
|
}
|
|
11784
12091
|
const setting = SETTINGS.find((s) => s.key === key);
|
|
11785
12092
|
if (!setting) {
|
|
11786
|
-
console.error(
|
|
11787
|
-
console.error(
|
|
11788
|
-
console.error(
|
|
12093
|
+
console.error(import_chalk53.default.red(`Unknown setting "${key}".`));
|
|
12094
|
+
console.error(import_chalk53.default.dim(" Known: " + SETTINGS.map((s) => s.key).join(", ")));
|
|
12095
|
+
console.error(import_chalk53.default.dim(" (Features like transforms/domains/tenants live in the menu: `apiblaze config <project>`.)"));
|
|
11789
12096
|
process.exit(1);
|
|
11790
12097
|
}
|
|
11791
12098
|
if (value === void 0) {
|
|
@@ -11800,7 +12107,7 @@ async function pickProject(teamId) {
|
|
|
11800
12107
|
const { getProjects: getProjects2 } = await Promise.resolve().then(() => (init_api(), api_exports));
|
|
11801
12108
|
const projects = await getProjects2(teamId).catch(() => []);
|
|
11802
12109
|
if (!projects.length) {
|
|
11803
|
-
console.error(
|
|
12110
|
+
console.error(import_chalk53.default.red("No projects in this team. Create one: `npx apiblaze create`."));
|
|
11804
12111
|
process.exit(1);
|
|
11805
12112
|
}
|
|
11806
12113
|
const { default: inquirer3 } = await import("inquirer");
|
|
@@ -11808,7 +12115,7 @@ async function pickProject(teamId) {
|
|
|
11808
12115
|
type: "list",
|
|
11809
12116
|
name: "picked",
|
|
11810
12117
|
message: "Which project?",
|
|
11811
|
-
choices: projects.map((p) => ({ name: `${p.projectName} ${
|
|
12118
|
+
choices: projects.map((p) => ({ name: `${p.projectName} ${import_chalk53.default.dim("v" + p.apiVersion)}`, value: p }))
|
|
11812
12119
|
}]);
|
|
11813
12120
|
return { projectId: picked.projectId, projectName: picked.projectName, apiVersion: picked.apiVersion, teamId, tenant: picked.tenant };
|
|
11814
12121
|
}
|
|
@@ -11819,25 +12126,25 @@ function printAll(proj2, cfg, json) {
|
|
|
11819
12126
|
console.log(JSON.stringify(out, null, 2));
|
|
11820
12127
|
return;
|
|
11821
12128
|
}
|
|
11822
|
-
console.log(
|
|
12129
|
+
console.log(import_chalk53.default.bold(`
|
|
11823
12130
|
${proj2.projectName} v${proj2.apiVersion} \u2014 settings
|
|
11824
12131
|
`));
|
|
11825
12132
|
for (const group2 of SETTING_GROUPS) {
|
|
11826
|
-
console.log(
|
|
12133
|
+
console.log(import_chalk53.default.bold(group2));
|
|
11827
12134
|
for (const s of SETTINGS.filter((x) => x.group === group2)) {
|
|
11828
|
-
console.log(` ${s.key.padEnd(32)} ${show(readSetting(s, cfg))} ${
|
|
12135
|
+
console.log(` ${s.key.padEnd(32)} ${show(readSetting(s, cfg))} ${import_chalk53.default.dim(s.desc)}`);
|
|
11829
12136
|
}
|
|
11830
12137
|
console.log();
|
|
11831
12138
|
}
|
|
11832
|
-
console.log(
|
|
12139
|
+
console.log(import_chalk53.default.dim("Change one: apiblaze config " + proj2.projectName + " <key> <value> (add --verbose for the API call)"));
|
|
11833
12140
|
}
|
|
11834
12141
|
async function discoveryMenu(project) {
|
|
11835
12142
|
const { default: inquirer3 } = await import("inquirer");
|
|
11836
|
-
console.log(
|
|
12143
|
+
console.log(import_chalk53.default.bold(`
|
|
11837
12144
|
APIblaze proxy configuration${project ? ` \u2014 ${project}` : ""}
|
|
11838
12145
|
`));
|
|
11839
|
-
console.log(
|
|
11840
|
-
console.log(
|
|
12146
|
+
console.log(import_chalk53.default.dim("You are not logged in \u2014 browsing what's configurable. Everything below works"));
|
|
12147
|
+
console.log(import_chalk53.default.dim("from this menu once you log in (`npx apiblaze login`).\n"));
|
|
11841
12148
|
for (; ; ) {
|
|
11842
12149
|
const { pick: pick2 } = await inquirer3.prompt([{
|
|
11843
12150
|
type: "list",
|
|
@@ -11845,13 +12152,13 @@ APIblaze proxy configuration${project ? ` \u2014 ${project}` : ""}
|
|
|
11845
12152
|
message: "Explore:",
|
|
11846
12153
|
pageSize: 20,
|
|
11847
12154
|
choices: [
|
|
11848
|
-
new inquirer3.Separator(
|
|
12155
|
+
new inquirer3.Separator(import_chalk53.default.bold("\u2014 Settings \u2014")),
|
|
11849
12156
|
...SETTING_GROUPS.map((g) => ({
|
|
11850
|
-
name: `${g} ${
|
|
12157
|
+
name: `${g} ${import_chalk53.default.dim(SETTINGS.filter((s) => s.group === g).map((s) => s.label).join(", "))}`,
|
|
11851
12158
|
value: { kind: "settings", g }
|
|
11852
12159
|
})),
|
|
11853
|
-
new inquirer3.Separator(
|
|
11854
|
-
...FEATURES.map((f) => ({ name: `${f.label} ${
|
|
12160
|
+
new inquirer3.Separator(import_chalk53.default.bold("\u2014 Features \u2014")),
|
|
12161
|
+
...FEATURES.map((f) => ({ name: `${f.label} ${import_chalk53.default.dim(f.desc)}`, value: { kind: "feature", f } })),
|
|
11855
12162
|
new inquirer3.Separator(),
|
|
11856
12163
|
{ name: "Exit", value: { kind: "exit" } }
|
|
11857
12164
|
]
|
|
@@ -11860,24 +12167,24 @@ APIblaze proxy configuration${project ? ` \u2014 ${project}` : ""}
|
|
|
11860
12167
|
if (pick2.kind === "settings") {
|
|
11861
12168
|
console.log();
|
|
11862
12169
|
for (const s of SETTINGS.filter((x) => x.group === pick2.g)) {
|
|
11863
|
-
console.log(` ${
|
|
11864
|
-
console.log(` ${
|
|
12170
|
+
console.log(` ${import_chalk53.default.bold(s.label.padEnd(28))} ${import_chalk53.default.dim(s.desc)}`);
|
|
12171
|
+
console.log(` ${import_chalk53.default.dim(" key: " + s.key)}`);
|
|
11865
12172
|
}
|
|
11866
12173
|
loginFirst("view or change these settings");
|
|
11867
12174
|
} else {
|
|
11868
12175
|
const f = pick2.f;
|
|
11869
12176
|
console.log(`
|
|
11870
|
-
${
|
|
12177
|
+
${import_chalk53.default.bold(f.label)} \u2014 ${f.desc}`);
|
|
11871
12178
|
loginFirst(`use ${f.label.toLowerCase()}`);
|
|
11872
12179
|
}
|
|
11873
12180
|
}
|
|
11874
12181
|
}
|
|
11875
12182
|
async function navigator(proj2, cfg, opts) {
|
|
11876
12183
|
const { default: inquirer3 } = await import("inquirer");
|
|
11877
|
-
console.log(
|
|
12184
|
+
console.log(import_chalk53.default.bold(`
|
|
11878
12185
|
${proj2.projectName} v${proj2.apiVersion} \u2014 configuration
|
|
11879
12186
|
`));
|
|
11880
|
-
console.log(
|
|
12187
|
+
console.log(import_chalk53.default.dim("Tip: every change is one API call \u2014 add --verbose to see the curl equivalent.\n"));
|
|
11881
12188
|
let blob = cfg;
|
|
11882
12189
|
for (; ; ) {
|
|
11883
12190
|
const { pick: pick2 } = await inquirer3.prompt([{
|
|
@@ -11886,10 +12193,10 @@ ${proj2.projectName} v${proj2.apiVersion} \u2014 configuration
|
|
|
11886
12193
|
message: "Where to?",
|
|
11887
12194
|
pageSize: 20,
|
|
11888
12195
|
choices: [
|
|
11889
|
-
new inquirer3.Separator(
|
|
12196
|
+
new inquirer3.Separator(import_chalk53.default.bold("\u2014 Settings \u2014")),
|
|
11890
12197
|
...SETTING_GROUPS.map((g) => ({ name: g, value: { kind: "settings", g } })),
|
|
11891
|
-
new inquirer3.Separator(
|
|
11892
|
-
...FEATURES.map((f) => ({ name: `${f.label} ${
|
|
12198
|
+
new inquirer3.Separator(import_chalk53.default.bold("\u2014 Features \u2014")),
|
|
12199
|
+
...FEATURES.map((f) => ({ name: `${f.label} ${import_chalk53.default.dim(f.desc)}`, value: { kind: f.go } })),
|
|
11893
12200
|
new inquirer3.Separator(),
|
|
11894
12201
|
{ name: "Show all settings", value: { kind: "list" } },
|
|
11895
12202
|
{ name: "Exit", value: { kind: "exit" } }
|
|
@@ -11929,7 +12236,7 @@ ${proj2.projectName} v${proj2.apiVersion} \u2014 configuration
|
|
|
11929
12236
|
}
|
|
11930
12237
|
}
|
|
11931
12238
|
} catch (err) {
|
|
11932
|
-
console.error(
|
|
12239
|
+
console.error(import_chalk53.default.red(` ${err instanceof Error ? err.message : String(err)}`));
|
|
11933
12240
|
}
|
|
11934
12241
|
}
|
|
11935
12242
|
}
|
|
@@ -11943,7 +12250,7 @@ async function settingsGroup(proj2, cfg, group2) {
|
|
|
11943
12250
|
message: group2 + ":",
|
|
11944
12251
|
pageSize: 16,
|
|
11945
12252
|
choices: [
|
|
11946
|
-
...items.map((s2) => ({ name: `${s2.label.padEnd(30)} ${show(readSetting(s2, cfg))} ${
|
|
12253
|
+
...items.map((s2) => ({ name: `${s2.label.padEnd(30)} ${show(readSetting(s2, cfg))} ${import_chalk53.default.dim(s2.desc)}`, value: s2 })),
|
|
11947
12254
|
new inquirer3.Separator(),
|
|
11948
12255
|
{ name: "\u2190 Back", value: null }
|
|
11949
12256
|
]
|
|
@@ -11961,7 +12268,7 @@ async function settingsGroup(proj2, cfg, group2) {
|
|
|
11961
12268
|
} else if (s.type === "number") {
|
|
11962
12269
|
const { v } = await inquirer3.prompt([{ type: "input", name: "v", message: `${s.label} (number):`, default: readSetting(s, cfg) }]);
|
|
11963
12270
|
if (v === "" || Number.isNaN(Number(v))) {
|
|
11964
|
-
console.log(
|
|
12271
|
+
console.log(import_chalk53.default.yellow(" Not a number \u2014 unchanged."));
|
|
11965
12272
|
continue;
|
|
11966
12273
|
}
|
|
11967
12274
|
value = Number(v);
|
|
@@ -12042,7 +12349,7 @@ async function buildCondition(phase) {
|
|
|
12042
12349
|
const items = [];
|
|
12043
12350
|
for (; ; ) {
|
|
12044
12351
|
const a = await inquirer3.prompt([
|
|
12045
|
-
{ type: "input", name: "source", message: `Condition field ${
|
|
12352
|
+
{ type: "input", name: "source", message: `Condition field ${import_chalk53.default.dim(srcHint)}:`, validate: (s) => !!s || "required" },
|
|
12046
12353
|
{ type: "list", name: "operator", message: "Operator:", choices: [
|
|
12047
12354
|
"eq",
|
|
12048
12355
|
"neq",
|
|
@@ -12073,7 +12380,7 @@ async function buildCondition(phase) {
|
|
|
12073
12380
|
function showCondition(cond) {
|
|
12074
12381
|
if (!Array.isArray(cond) || !cond.length) return "";
|
|
12075
12382
|
const s = cond.map((c) => `${c.source} ${c.operator}${c.value !== void 0 ? ` "${c.value}"` : ""}${c.logicOp ? ` ${c.logicOp}` : ""}`).join(" ");
|
|
12076
|
-
return
|
|
12383
|
+
return import_chalk53.default.dim(` when ${s}`);
|
|
12077
12384
|
}
|
|
12078
12385
|
async function transformsMenu(proj2) {
|
|
12079
12386
|
const { default: inquirer3 } = await import("inquirer");
|
|
@@ -12082,12 +12389,12 @@ async function transformsMenu(proj2) {
|
|
|
12082
12389
|
const out = await admin({ method: "GET", path: base2, summary: "List transform rules" });
|
|
12083
12390
|
const rules = out?.rules ?? [];
|
|
12084
12391
|
console.log();
|
|
12085
|
-
if (!rules.length) console.log(
|
|
12392
|
+
if (!rules.length) console.log(import_chalk53.default.dim(" No transform rules yet."));
|
|
12086
12393
|
for (const r of rules) {
|
|
12087
12394
|
const a = r.action ?? {};
|
|
12088
12395
|
const fns = [...a.source_fns ?? [], ...a.dest_fns ?? []].map((f) => f.fn);
|
|
12089
|
-
const what = a.type === "hardcode" ? `${a.destination} = "${a.value}"` : a.type === "remove" ? `remove ${a.field}` : `${a.source} \u2192 ${a.destination}${a.lookup ? " (mapped)" : ""}${fns.length ?
|
|
12090
|
-
console.log(` ${r.enabled ?
|
|
12396
|
+
const what = a.type === "hardcode" ? `${a.destination} = "${a.value}"` : a.type === "remove" ? `remove ${a.field}` : `${a.source} \u2192 ${a.destination}${a.lookup ? " (mapped)" : ""}${fns.length ? import_chalk53.default.dim(` via ${fns.join("\u2192")}`) : ""}`;
|
|
12397
|
+
console.log(` ${r.enabled ? import_chalk53.default.green("\u25CF") : import_chalk53.default.dim("\u25CB")} ${import_chalk53.default.bold(r.name)} ${import_chalk53.default.dim(`[${r.phase ?? "request"}]`)} ${what}${showCondition(r.condition)}`);
|
|
12091
12398
|
}
|
|
12092
12399
|
const { act } = await inquirer3.prompt([{
|
|
12093
12400
|
type: "list",
|
|
@@ -12099,7 +12406,7 @@ async function transformsMenu(proj2) {
|
|
|
12099
12406
|
{ name: "Enable/disable a rule", value: "toggle" },
|
|
12100
12407
|
{ name: "Delete a rule", value: "delete" }
|
|
12101
12408
|
] : [],
|
|
12102
|
-
{ name:
|
|
12409
|
+
{ name: import_chalk53.default.dim("Add from raw JSON (grouped conditions, lookup tables, \u2026)"), value: "raw" },
|
|
12103
12410
|
{ name: "\u2190 Back", value: "back" }
|
|
12104
12411
|
]
|
|
12105
12412
|
}]);
|
|
@@ -12112,11 +12419,11 @@ async function transformsMenu(proj2) {
|
|
|
12112
12419
|
}]);
|
|
12113
12420
|
const body = parseValue(raw);
|
|
12114
12421
|
if (!body || typeof body !== "object" || Array.isArray(body)) {
|
|
12115
|
-
console.log(
|
|
12422
|
+
console.log(import_chalk53.default.yellow(" Not a JSON object \u2014 skipped."));
|
|
12116
12423
|
continue;
|
|
12117
12424
|
}
|
|
12118
12425
|
await admin({ method: "POST", path: base2, body, summary: "Create transform rule (raw JSON)" });
|
|
12119
|
-
console.log(
|
|
12426
|
+
console.log(import_chalk53.default.green(" Rule created."));
|
|
12120
12427
|
continue;
|
|
12121
12428
|
}
|
|
12122
12429
|
if (act === "add") {
|
|
@@ -12132,7 +12439,7 @@ async function transformsMenu(proj2) {
|
|
|
12132
12439
|
{ name: "Remove a field", value: "remove" }
|
|
12133
12440
|
] }
|
|
12134
12441
|
]);
|
|
12135
|
-
const fieldHint =
|
|
12442
|
+
const fieldHint = import_chalk53.default.dim("(e.g. header:x-api-version, param:limit, bodyvar:user.id)");
|
|
12136
12443
|
let action2;
|
|
12137
12444
|
if (ans.kind === "hardcode") {
|
|
12138
12445
|
const a = await inquirer3.prompt([
|
|
@@ -12161,7 +12468,7 @@ async function transformsMenu(proj2) {
|
|
|
12161
12468
|
};
|
|
12162
12469
|
}
|
|
12163
12470
|
const condition = await buildCondition(ans.phase);
|
|
12164
|
-
const spinner = (0,
|
|
12471
|
+
const spinner = (0, import_ora24.default)("Creating rule...").start();
|
|
12165
12472
|
try {
|
|
12166
12473
|
await admin({
|
|
12167
12474
|
method: "POST",
|
|
@@ -12179,16 +12486,16 @@ async function transformsMenu(proj2) {
|
|
|
12179
12486
|
type: "list",
|
|
12180
12487
|
name: "rule",
|
|
12181
12488
|
message: act === "toggle" ? "Which rule?" : "Delete which rule?",
|
|
12182
|
-
choices: [...rules.map((r) => ({ name: `${r.name} ${
|
|
12489
|
+
choices: [...rules.map((r) => ({ name: `${r.name} ${import_chalk53.default.dim(`[${r.phase ?? "request"}]`)}`, value: r })), { name: "\u2190 Back", value: null }]
|
|
12183
12490
|
}]);
|
|
12184
12491
|
if (!rule) continue;
|
|
12185
12492
|
if (act === "toggle") {
|
|
12186
12493
|
const flipped = { ...rule, enabled: rule.enabled === false };
|
|
12187
12494
|
await admin({ method: "PUT", path: `${base2}/${rule.id}`, body: flipped, summary: `${flipped.enabled ? "Enable" : "Disable"} transform "${rule.name}"` });
|
|
12188
|
-
console.log(
|
|
12495
|
+
console.log(import_chalk53.default.green(` ${rule.name} \u2192 ${flipped.enabled ? "enabled" : "disabled"}`));
|
|
12189
12496
|
} else {
|
|
12190
12497
|
await admin({ method: "DELETE", path: `${base2}/${rule.id}`, summary: `Delete transform "${rule.name}"` });
|
|
12191
|
-
console.log(
|
|
12498
|
+
console.log(import_chalk53.default.green(` ${rule.name} deleted.`));
|
|
12192
12499
|
}
|
|
12193
12500
|
}
|
|
12194
12501
|
}
|
|
@@ -12200,9 +12507,9 @@ async function mappingsMenu(proj2) {
|
|
|
12200
12507
|
const out = await admin({ method: "GET", path: base2, summary: "List mapping tables" });
|
|
12201
12508
|
const tables = out?.mappings ?? out?.tables ?? [];
|
|
12202
12509
|
console.log();
|
|
12203
|
-
if (!tables.length) console.log(
|
|
12510
|
+
if (!tables.length) console.log(import_chalk53.default.dim(" No mapping tables yet."));
|
|
12204
12511
|
for (const t of tables) {
|
|
12205
|
-
console.log(` ${
|
|
12512
|
+
console.log(` ${import_chalk53.default.bold(t.name)} ${import_chalk53.default.dim(`${t.entries?.length ?? "?"} entries${t.hide_map_values ? ", hidden" : ""}${t.encrypt_values ? ", encrypted" : ""}`)}`);
|
|
12206
12513
|
}
|
|
12207
12514
|
const { act } = await inquirer3.prompt([{
|
|
12208
12515
|
type: "list",
|
|
@@ -12222,11 +12529,11 @@ async function mappingsMenu(proj2) {
|
|
|
12222
12529
|
]);
|
|
12223
12530
|
const entries2 = parseValue(a.entries);
|
|
12224
12531
|
if (!Array.isArray(entries2)) {
|
|
12225
|
-
console.log(
|
|
12532
|
+
console.log(import_chalk53.default.yellow(" Entries must be a JSON array \u2014 not created."));
|
|
12226
12533
|
continue;
|
|
12227
12534
|
}
|
|
12228
12535
|
await admin({ method: "POST", path: base2, body: { name: a.name, entries: entries2 }, summary: `Create mapping table "${a.name}"` });
|
|
12229
|
-
console.log(
|
|
12536
|
+
console.log(import_chalk53.default.green(` Table "${a.name}" created.`));
|
|
12230
12537
|
} else {
|
|
12231
12538
|
const { table } = await inquirer3.prompt([{
|
|
12232
12539
|
type: "list",
|
|
@@ -12236,7 +12543,7 @@ async function mappingsMenu(proj2) {
|
|
|
12236
12543
|
}]);
|
|
12237
12544
|
if (!table) continue;
|
|
12238
12545
|
await admin({ method: "DELETE", path: `${base2}/${table.id}`, summary: `Delete mapping table "${table.name}"` });
|
|
12239
|
-
console.log(
|
|
12546
|
+
console.log(import_chalk53.default.green(` ${table.name} deleted.`));
|
|
12240
12547
|
}
|
|
12241
12548
|
}
|
|
12242
12549
|
}
|
|
@@ -12247,14 +12554,14 @@ async function tenantsMenu(proj2, opts) {
|
|
|
12247
12554
|
const out = await admin({ method: "GET", path: base2, summary: "List attached tenants" });
|
|
12248
12555
|
const tenants = out?.tenants ?? [];
|
|
12249
12556
|
console.log();
|
|
12250
|
-
if (!tenants.length) console.log(
|
|
12251
|
-
for (const t of tenants) console.log(` ${
|
|
12557
|
+
if (!tenants.length) console.log(import_chalk53.default.dim(" No tenants attached (consumers use the default tenant)."));
|
|
12558
|
+
for (const t of tenants) console.log(` ${import_chalk53.default.bold(t.tenant_name ?? t.name)} ${import_chalk53.default.dim(t.display_name ?? "")}`);
|
|
12252
12559
|
const { act } = await inquirer3.prompt([{
|
|
12253
12560
|
type: "list",
|
|
12254
12561
|
name: "act",
|
|
12255
12562
|
message: "Tenants:",
|
|
12256
12563
|
choices: [
|
|
12257
|
-
{ name: `Manage a tenant\u2026 ${
|
|
12564
|
+
{ name: `Manage a tenant\u2026 ${import_chalk53.default.dim("settings, login app clients, providers, issuers \u2014 affects EVERY proxy the tenant serves")}`, value: "manage" },
|
|
12258
12565
|
{ name: "Attach a tenant to this project", value: "attach" },
|
|
12259
12566
|
...tenants.length ? [{ name: "Detach a tenant from this project", value: "detach" }] : [],
|
|
12260
12567
|
{ name: "\u2190 Back", value: "back" }
|
|
@@ -12277,7 +12584,7 @@ async function tenantsMenu(proj2, opts) {
|
|
|
12277
12584
|
}]);
|
|
12278
12585
|
if (!t) continue;
|
|
12279
12586
|
await admin({ method: "DELETE", path: `${base2}/${encodeURIComponent(t.tenant_name ?? t.name)}`, summary: `Detach tenant ${t.tenant_name ?? t.name}` });
|
|
12280
|
-
console.log(
|
|
12587
|
+
console.log(import_chalk53.default.green(` Detached ${t.tenant_name ?? t.name}.`));
|
|
12281
12588
|
}
|
|
12282
12589
|
}
|
|
12283
12590
|
}
|
|
@@ -12320,7 +12627,7 @@ async function specMenu(proj2, opts) {
|
|
|
12320
12627
|
choices: [
|
|
12321
12628
|
{ name: "Print the stored spec", value: "get" },
|
|
12322
12629
|
{ name: "Refresh the spec from its source", value: "refresh" },
|
|
12323
|
-
{ name:
|
|
12630
|
+
{ name: import_chalk53.default.dim("Build the spec by chatting over real traffic \u2192 agent"), value: "agent" },
|
|
12324
12631
|
{ name: "\u2190 Back", value: "back" }
|
|
12325
12632
|
]
|
|
12326
12633
|
}]);
|
|
@@ -12328,7 +12635,7 @@ async function specMenu(proj2, opts) {
|
|
|
12328
12635
|
if (act === "get") await runSpecGet(proj2.projectName, { team: opts.team, apiversion: proj2.apiVersion });
|
|
12329
12636
|
else if (act === "refresh") {
|
|
12330
12637
|
await admin({ method: "POST", path: `/projects/${proj2.projectId}/${proj2.apiVersion}/refresh-spec`, summary: "Refresh spec from source" });
|
|
12331
|
-
console.log(
|
|
12638
|
+
console.log(import_chalk53.default.green(" Spec refresh triggered."));
|
|
12332
12639
|
} else await runOpenapi(proj2.projectName, proj2.apiVersion);
|
|
12333
12640
|
}
|
|
12334
12641
|
async function agentsMenu(proj2, opts) {
|
|
@@ -12353,8 +12660,8 @@ async function agentsMenu(proj2, opts) {
|
|
|
12353
12660
|
}
|
|
12354
12661
|
|
|
12355
12662
|
// src/commands/group.ts
|
|
12356
|
-
var
|
|
12357
|
-
var
|
|
12663
|
+
var import_chalk54 = __toESM(require("chalk"));
|
|
12664
|
+
var import_ora25 = __toESM(require("ora"));
|
|
12358
12665
|
init_auth();
|
|
12359
12666
|
init_admin();
|
|
12360
12667
|
init_resolve();
|
|
@@ -12454,7 +12761,7 @@ async function userByRefOrCreate(t, ref) {
|
|
|
12454
12761
|
if (!ref.includes("@")) throw e;
|
|
12455
12762
|
const created = await t.call("/v1/admin/users", "POST", { email: ref.trim().toLowerCase() });
|
|
12456
12763
|
if (!created?.abz_sub) throw e;
|
|
12457
|
-
console.log(
|
|
12764
|
+
console.log(import_chalk54.default.dim(` ${ref} wasn't a user yet \u2014 created.`));
|
|
12458
12765
|
return { abz_sub: created.abz_sub, email: created.email ?? ref, display_name: null };
|
|
12459
12766
|
}
|
|
12460
12767
|
}
|
|
@@ -12462,11 +12769,11 @@ async function runGroupList(opts) {
|
|
|
12462
12769
|
const t = await resolveTransport(opts);
|
|
12463
12770
|
const groups = await listGroups(t);
|
|
12464
12771
|
if (opts.json) return void console.log(JSON.stringify(groups));
|
|
12465
|
-
if (!groups.length) return void console.log(
|
|
12466
|
-
console.log(
|
|
12772
|
+
if (!groups.length) return void console.log(import_chalk54.default.yellow(`No groups in ${t.tenant}. Create one: apiblaze group create <name>`));
|
|
12773
|
+
console.log(import_chalk54.default.dim(`Tenant ${t.tenant}:`));
|
|
12467
12774
|
for (const g of groups) {
|
|
12468
|
-
const sealed = g.name === "apiblaze_admins" ?
|
|
12469
|
-
console.log(` ${
|
|
12775
|
+
const sealed = g.name === "apiblaze_admins" ? import_chalk54.default.dim(" (admins \u2014 managed by allowlist)") : "";
|
|
12776
|
+
console.log(` ${import_chalk54.default.bold(g.name)} ${import_chalk54.default.dim(`${g.member_count ?? 0} member(s)`)}${sealed}`);
|
|
12470
12777
|
}
|
|
12471
12778
|
}
|
|
12472
12779
|
async function runGroupCreate(name, opts) {
|
|
@@ -12491,10 +12798,10 @@ async function runGroupCreate(name, opts) {
|
|
|
12491
12798
|
}
|
|
12492
12799
|
}
|
|
12493
12800
|
}
|
|
12494
|
-
const spinner = (0,
|
|
12801
|
+
const spinner = (0, import_ora25.default)(`Creating group ${name}...`).start();
|
|
12495
12802
|
try {
|
|
12496
12803
|
await t.call("/v1/admin/groups", "POST", { name, admins: [adminSub] });
|
|
12497
|
-
spinner.succeed(`Group ${
|
|
12804
|
+
spinner.succeed(`Group ${import_chalk54.default.bold(name)} created in ${t.tenant}.`);
|
|
12498
12805
|
} catch (e) {
|
|
12499
12806
|
spinner.fail("Create failed.");
|
|
12500
12807
|
throw e;
|
|
@@ -12503,10 +12810,10 @@ async function runGroupCreate(name, opts) {
|
|
|
12503
12810
|
async function runGroupDelete(name, opts) {
|
|
12504
12811
|
const t = await resolveTransport(opts);
|
|
12505
12812
|
const g = await groupByName(t, name);
|
|
12506
|
-
const spinner = (0,
|
|
12813
|
+
const spinner = (0, import_ora25.default)(`Deleting group ${name}...`).start();
|
|
12507
12814
|
try {
|
|
12508
12815
|
await t.call(`/v1/admin/groups/${encodeURIComponent(g.id)}`, "DELETE");
|
|
12509
|
-
spinner.succeed(`Group ${
|
|
12816
|
+
spinner.succeed(`Group ${import_chalk54.default.bold(name)} deleted.`);
|
|
12510
12817
|
} catch (e) {
|
|
12511
12818
|
spinner.fail("Delete failed.");
|
|
12512
12819
|
throw e;
|
|
@@ -12525,21 +12832,21 @@ async function runGroupMembers(name, opts) {
|
|
|
12525
12832
|
return void console.log(JSON.stringify({ ...detail, members: walkedMembers.items, subgroups: subs?.subgroups ?? [] }));
|
|
12526
12833
|
}
|
|
12527
12834
|
const members = walkedMembers.items;
|
|
12528
|
-
console.log(
|
|
12529
|
-
for (const m of members) console.log(` ${label2(m)} ${m.role === "admin" ?
|
|
12530
|
-
if (!members.length) console.log(
|
|
12835
|
+
console.log(import_chalk54.default.bold(`${g.name}`) + import_chalk54.default.dim(` (${t.tenant})`));
|
|
12836
|
+
for (const m of members) console.log(` ${label2(m)} ${m.role === "admin" ? import_chalk54.default.cyan("group-admin") : import_chalk54.default.dim("member")}`);
|
|
12837
|
+
if (!members.length) console.log(import_chalk54.default.dim(" no members"));
|
|
12531
12838
|
const sg = subs?.subgroups ?? [];
|
|
12532
|
-
if (sg.length) console.log(
|
|
12839
|
+
if (sg.length) console.log(import_chalk54.default.dim(` subgroups: ${sg.map((s) => s.name).join(", ")}`));
|
|
12533
12840
|
}
|
|
12534
12841
|
async function runGroupAddUser(userRef, groupName, opts) {
|
|
12535
12842
|
const t = await resolveTransport(opts);
|
|
12536
12843
|
const g = await groupByName(t, groupName);
|
|
12537
12844
|
const u = await userByRefOrCreate(t, userRef);
|
|
12538
12845
|
const role = opts.role === "admin" ? "admin" : "member";
|
|
12539
|
-
const spinner = (0,
|
|
12846
|
+
const spinner = (0, import_ora25.default)(`Adding ${label2(u)} to ${g.name}...`).start();
|
|
12540
12847
|
try {
|
|
12541
12848
|
await t.call(`/v1/admin/groups/${encodeURIComponent(g.id)}/members`, "POST", { abz_sub: u.abz_sub, role });
|
|
12542
|
-
spinner.succeed(`${
|
|
12849
|
+
spinner.succeed(`${import_chalk54.default.bold(label2(u))} added to ${import_chalk54.default.bold(g.name)} as ${role}.`);
|
|
12543
12850
|
} catch (e) {
|
|
12544
12851
|
spinner.fail("Add failed.");
|
|
12545
12852
|
throw e;
|
|
@@ -12548,10 +12855,10 @@ async function runGroupAddUser(userRef, groupName, opts) {
|
|
|
12548
12855
|
async function runGroupRemoveUser(userRef, groupName, opts) {
|
|
12549
12856
|
const t = await resolveTransport(opts);
|
|
12550
12857
|
const [g, u] = [await groupByName(t, groupName), await userByRef(t, userRef)];
|
|
12551
|
-
const spinner = (0,
|
|
12858
|
+
const spinner = (0, import_ora25.default)(`Removing ${label2(u)} from ${g.name}...`).start();
|
|
12552
12859
|
try {
|
|
12553
12860
|
await t.call(`/v1/admin/groups/${encodeURIComponent(g.id)}/members/_?abz_sub=${encodeURIComponent(u.abz_sub)}`, "DELETE");
|
|
12554
|
-
spinner.succeed(`${
|
|
12861
|
+
spinner.succeed(`${import_chalk54.default.bold(label2(u))} removed from ${import_chalk54.default.bold(g.name)}.`);
|
|
12555
12862
|
} catch (e) {
|
|
12556
12863
|
spinner.fail("Remove failed.");
|
|
12557
12864
|
throw e;
|
|
@@ -12560,10 +12867,10 @@ async function runGroupRemoveUser(userRef, groupName, opts) {
|
|
|
12560
12867
|
async function runGroupAddGroup(childName, parentName, opts) {
|
|
12561
12868
|
const t = await resolveTransport(opts);
|
|
12562
12869
|
const [child, parent] = [await groupByName(t, childName), await groupByName(t, parentName)];
|
|
12563
|
-
const spinner = (0,
|
|
12870
|
+
const spinner = (0, import_ora25.default)(`Nesting ${child.name} under ${parent.name}...`).start();
|
|
12564
12871
|
try {
|
|
12565
12872
|
await t.call(`/v1/admin/groups/${encodeURIComponent(parent.id)}/subgroups`, "POST", { child_group_id: child.id });
|
|
12566
|
-
spinner.succeed(`${
|
|
12873
|
+
spinner.succeed(`${import_chalk54.default.bold(child.name)} is now a subgroup of ${import_chalk54.default.bold(parent.name)}.`);
|
|
12567
12874
|
} catch (e) {
|
|
12568
12875
|
spinner.fail("Nest failed.");
|
|
12569
12876
|
throw e;
|
|
@@ -12572,10 +12879,10 @@ async function runGroupAddGroup(childName, parentName, opts) {
|
|
|
12572
12879
|
async function runGroupRemoveGroup(childName, parentName, opts) {
|
|
12573
12880
|
const t = await resolveTransport(opts);
|
|
12574
12881
|
const [child, parent] = [await groupByName(t, childName), await groupByName(t, parentName)];
|
|
12575
|
-
const spinner = (0,
|
|
12882
|
+
const spinner = (0, import_ora25.default)(`Un-nesting ${child.name} from ${parent.name}...`).start();
|
|
12576
12883
|
try {
|
|
12577
12884
|
await t.call(`/v1/admin/groups/${encodeURIComponent(parent.id)}/subgroups/${encodeURIComponent(child.id)}`, "DELETE");
|
|
12578
|
-
spinner.succeed(`${
|
|
12885
|
+
spinner.succeed(`${import_chalk54.default.bold(child.name)} un-nested from ${import_chalk54.default.bold(parent.name)}.`);
|
|
12579
12886
|
} catch (e) {
|
|
12580
12887
|
spinner.fail("Un-nest failed.");
|
|
12581
12888
|
throw e;
|
|
@@ -12586,13 +12893,13 @@ async function runGroupUsers(opts) {
|
|
|
12586
12893
|
const users = await listUsers(t);
|
|
12587
12894
|
const observed = await t.call("/v1/admin/observed-identities").catch(() => ({ identities: [] }));
|
|
12588
12895
|
if (opts.json) return void console.log(JSON.stringify({ users, observed: observed?.identities ?? [] }));
|
|
12589
|
-
console.log(
|
|
12590
|
-
for (const u of users) console.log(` ${label2(u)} ${
|
|
12591
|
-
if (!users.length) console.log(
|
|
12896
|
+
console.log(import_chalk54.default.dim(`Tenant ${t.tenant} \u2014 user directory:`));
|
|
12897
|
+
for (const u of users) console.log(` ${label2(u)} ${import_chalk54.default.dim(u.abz_sub)}`);
|
|
12898
|
+
if (!users.length) console.log(import_chalk54.default.dim(" no users yet"));
|
|
12592
12899
|
const fresh = (observed?.identities ?? []).filter((o) => !o.provisioned);
|
|
12593
12900
|
if (fresh.length) {
|
|
12594
|
-
console.log(
|
|
12595
|
-
for (const o of fresh) console.log(` ${o.email || o.abz_sub} ${
|
|
12901
|
+
console.log(import_chalk54.default.dim("Seen in traffic (not yet users):"));
|
|
12902
|
+
for (const o of fresh) console.log(` ${o.email || o.abz_sub} ${import_chalk54.default.dim(o.source_label ?? "")}`);
|
|
12596
12903
|
}
|
|
12597
12904
|
}
|
|
12598
12905
|
|
|
@@ -12601,22 +12908,22 @@ init_iam();
|
|
|
12601
12908
|
init_admins();
|
|
12602
12909
|
|
|
12603
12910
|
// src/commands/preapprove.ts
|
|
12604
|
-
var
|
|
12605
|
-
var
|
|
12911
|
+
var import_chalk55 = __toESM(require("chalk"));
|
|
12912
|
+
var import_ora26 = __toESM(require("ora"));
|
|
12606
12913
|
async function runPreapprove(who, opts) {
|
|
12607
12914
|
const tenant2 = await resolveProducerTenant(opts.tenant);
|
|
12608
12915
|
if (opts.list) {
|
|
12609
12916
|
const rules = await listPreapprovalRules(tenant2);
|
|
12610
12917
|
if (opts.json) return void console.log(JSON.stringify(rules, null, 2));
|
|
12611
12918
|
if (!rules.length) {
|
|
12612
|
-
console.log(
|
|
12613
|
-
console.log(
|
|
12919
|
+
console.log(import_chalk55.default.dim(`No pre-approval rules in ${tenant2}. Anyone who signs in can access (unless access is restricted).`));
|
|
12920
|
+
console.log(import_chalk55.default.dim(`Add one: apiblaze preapprove someone@acme.com`));
|
|
12614
12921
|
return;
|
|
12615
12922
|
}
|
|
12616
|
-
console.log(
|
|
12923
|
+
console.log(import_chalk55.default.dim(`Pre-approved for ${import_chalk55.default.bold(tenant2)}:`));
|
|
12617
12924
|
for (const r of rules) {
|
|
12618
|
-
const tag = r.kind === "domain" ?
|
|
12619
|
-
const grp = r.groups?.length ?
|
|
12925
|
+
const tag = r.kind === "domain" ? import_chalk55.default.cyan("@" + r.value + " (whole domain)") : r.value;
|
|
12926
|
+
const grp = r.groups?.length ? import_chalk55.default.dim(` \u2192 groups: ${r.groups.join(", ")}`) : "";
|
|
12620
12927
|
console.log(` ${tag}${grp}`);
|
|
12621
12928
|
}
|
|
12622
12929
|
return;
|
|
@@ -12625,19 +12932,19 @@ async function runPreapprove(who, opts) {
|
|
|
12625
12932
|
throw new Error("Who? Pass an email or a domain: `apiblaze preapprove someone@acme.com` (or `apiblaze preapprove --list`).");
|
|
12626
12933
|
}
|
|
12627
12934
|
if (opts.remove) {
|
|
12628
|
-
const spinner2 = (0,
|
|
12935
|
+
const spinner2 = (0, import_ora26.default)(`Removing ${who} from ${tenant2}\u2026`).start();
|
|
12629
12936
|
const { removed, value } = await removePreapprovalRule(tenant2, who);
|
|
12630
|
-
if (removed) spinner2.succeed(`${
|
|
12631
|
-
else spinner2.warn(`No pre-approval rule for ${
|
|
12937
|
+
if (removed) spinner2.succeed(`${import_chalk55.default.bold(value)} is no longer pre-approved for ${tenant2}.`);
|
|
12938
|
+
else spinner2.warn(`No pre-approval rule for ${import_chalk55.default.bold(value)} in ${tenant2} \u2014 nothing to remove.`);
|
|
12632
12939
|
return;
|
|
12633
12940
|
}
|
|
12634
|
-
const spinner = (0,
|
|
12941
|
+
const spinner = (0, import_ora26.default)(`Pre-approving ${who} for ${tenant2}\u2026`).start();
|
|
12635
12942
|
try {
|
|
12636
12943
|
const { kind, value } = await addPreapprovalRule(tenant2, who, opts.group);
|
|
12637
12944
|
const what = kind === "domain" ? `Anyone @${value}` : value;
|
|
12638
|
-
spinner.succeed(`${
|
|
12639
|
-
if (opts.group?.length) console.log(
|
|
12640
|
-
console.log(
|
|
12945
|
+
spinner.succeed(`${import_chalk55.default.bold(what)} can now sign in to ${import_chalk55.default.bold(tenant2)}.`);
|
|
12946
|
+
if (opts.group?.length) console.log(import_chalk55.default.dim(` They'll join group(s) ${opts.group.join(", ")} on first sign-in.`));
|
|
12947
|
+
console.log(import_chalk55.default.dim(` See the full list: apiblaze preapprove --list`));
|
|
12641
12948
|
} catch (err) {
|
|
12642
12949
|
spinner.fail("Could not add the rule.");
|
|
12643
12950
|
throw err;
|
|
@@ -12645,8 +12952,8 @@ async function runPreapprove(who, opts) {
|
|
|
12645
12952
|
}
|
|
12646
12953
|
|
|
12647
12954
|
// src/commands/consumer.ts
|
|
12648
|
-
var
|
|
12649
|
-
var
|
|
12955
|
+
var import_chalk56 = __toESM(require("chalk"));
|
|
12956
|
+
var import_ora27 = __toESM(require("ora"));
|
|
12650
12957
|
init_admin();
|
|
12651
12958
|
init_resolve();
|
|
12652
12959
|
init_caller();
|
|
@@ -12668,7 +12975,7 @@ async function consumerFetch(creds, suffix, init) {
|
|
|
12668
12975
|
function requireConsumer2() {
|
|
12669
12976
|
const c = loadConsumer();
|
|
12670
12977
|
if (!c) {
|
|
12671
|
-
console.error(
|
|
12978
|
+
console.error(import_chalk56.default.red("Not logged in as a consumer. Run `apiblaze consumer login` first."));
|
|
12672
12979
|
process.exit(1);
|
|
12673
12980
|
}
|
|
12674
12981
|
return c;
|
|
@@ -12679,7 +12986,7 @@ async function runConsumerLogin(opts) {
|
|
|
12679
12986
|
let clientId = opts.client;
|
|
12680
12987
|
if (clientId) {
|
|
12681
12988
|
if (!tenant2) {
|
|
12682
|
-
console.error(
|
|
12989
|
+
console.error(import_chalk56.default.red("When using --client, also pass --tenant <slug> (it sets which portal/keys host to use)."));
|
|
12683
12990
|
process.exit(1);
|
|
12684
12991
|
}
|
|
12685
12992
|
} else {
|
|
@@ -12691,24 +12998,24 @@ async function runConsumerLogin(opts) {
|
|
|
12691
12998
|
if (!picked) process.exit(1);
|
|
12692
12999
|
tenant2 = picked;
|
|
12693
13000
|
}
|
|
12694
|
-
const s2 = (0,
|
|
13001
|
+
const s2 = (0, import_ora27.default)("Finding the login app...").start();
|
|
12695
13002
|
const clients = await admin({ method: "GET", path: `/teams/${encodeURIComponent(teamId)}/tenants/${encodeURIComponent(tenant2)}/app-clients`, summary: `List app clients for ${tenant2}` }).catch(() => []);
|
|
12696
13003
|
s2.stop();
|
|
12697
13004
|
const usable = (Array.isArray(clients) ? clients : []).filter((c) => c && (c.client_id || c.clientId));
|
|
12698
13005
|
const pick2 = usable.find((c) => c.is_default || c.default) ?? usable.find((c) => c.verified !== false) ?? usable[0];
|
|
12699
13006
|
if (!pick2) {
|
|
12700
|
-
console.error(
|
|
13007
|
+
console.error(import_chalk56.default.red(`Tenant "${tenant2}" has no login app configured. Set one up in the dashboard (or \`apiblaze create\` with auth).`));
|
|
12701
13008
|
process.exit(1);
|
|
12702
13009
|
}
|
|
12703
13010
|
clientId = pick2.client_id ?? pick2.clientId;
|
|
12704
13011
|
}
|
|
12705
|
-
console.log(`${
|
|
13012
|
+
console.log(`${import_chalk56.default.cyan("\u2192")} Logging in to ${import_chalk56.default.bold(tenant2)} as a consumer...`);
|
|
12706
13013
|
const result = await deviceLogin(clientId, DEFAULT_SCOPE, ({ verificationUri, userCode }) => {
|
|
12707
13014
|
console.log(`
|
|
12708
|
-
Open: ${
|
|
12709
|
-
console.log(` Code: ${
|
|
13015
|
+
Open: ${import_chalk56.default.underline(verificationUri)}`);
|
|
13016
|
+
console.log(` Code: ${import_chalk56.default.bold(userCode)}
|
|
12710
13017
|
`);
|
|
12711
|
-
console.log(
|
|
13018
|
+
console.log(import_chalk56.default.dim(" (opening your browser\u2026 waiting for you to finish)"));
|
|
12712
13019
|
});
|
|
12713
13020
|
const claims = result.idToken && decodeJwt2(result.idToken) || (decodeJwt2(result.accessToken) ?? {});
|
|
12714
13021
|
const creds = {
|
|
@@ -12723,7 +13030,7 @@ async function runConsumerLogin(opts) {
|
|
|
12723
13030
|
obtainedAt: Date.now()
|
|
12724
13031
|
};
|
|
12725
13032
|
saveConsumer(creds);
|
|
12726
|
-
console.log(
|
|
13033
|
+
console.log(import_chalk56.default.green(`\u2714 Logged in as consumer${creds.email ? ` ${creds.email}` : ""} on ${tenant2}.`));
|
|
12727
13034
|
}
|
|
12728
13035
|
async function runConsumerTokens(opts) {
|
|
12729
13036
|
const creds = requireConsumer2();
|
|
@@ -12736,28 +13043,28 @@ async function runConsumerTokens(opts) {
|
|
|
12736
13043
|
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));
|
|
12737
13044
|
return;
|
|
12738
13045
|
}
|
|
12739
|
-
console.log(`${
|
|
13046
|
+
console.log(`${import_chalk56.default.cyan("Consumer")} ${import_chalk56.default.bold(fresh.email ?? fresh.tenant)} on ${import_chalk56.default.bold(fresh.tenant)}
|
|
12740
13047
|
`);
|
|
12741
|
-
console.log(`${
|
|
13048
|
+
console.log(`${import_chalk56.default.bold("access_token")} ${import_chalk56.default.dim("exp " + (exp(fresh.accessToken) ?? "?"))}
|
|
12742
13049
|
${fresh.accessToken}
|
|
12743
13050
|
`);
|
|
12744
|
-
if (fresh.idToken) console.log(`${
|
|
13051
|
+
if (fresh.idToken) console.log(`${import_chalk56.default.bold("id_token")} ${import_chalk56.default.dim("exp " + (exp(fresh.idToken) ?? "?"))}
|
|
12745
13052
|
${fresh.idToken}
|
|
12746
13053
|
`);
|
|
12747
|
-
if (fresh.refreshToken) console.log(`${
|
|
13054
|
+
if (fresh.refreshToken) console.log(`${import_chalk56.default.bold("refresh_token")}
|
|
12748
13055
|
${fresh.refreshToken}
|
|
12749
13056
|
`);
|
|
12750
|
-
console.log(
|
|
13057
|
+
console.log(import_chalk56.default.dim("These are your own tokens \u2014 keep them secret."));
|
|
12751
13058
|
}
|
|
12752
13059
|
async function runConsumerApikeys(opts) {
|
|
12753
13060
|
const creds = requireConsumer2();
|
|
12754
13061
|
const { default: inquirer3 } = await import("inquirer");
|
|
12755
|
-
const spinner = (0,
|
|
13062
|
+
const spinner = (0, import_ora27.default)("Loading your API keys...").start();
|
|
12756
13063
|
const list = await consumerFetch(creds, withPage("/apikeys", null));
|
|
12757
13064
|
if (list.status >= 400) {
|
|
12758
13065
|
spinner.stop();
|
|
12759
|
-
console.error(
|
|
12760
|
-
if (list.status === 401) console.error(
|
|
13066
|
+
console.error(import_chalk56.default.red(`Failed to list keys (${list.status}): ${list.data?.error ?? ""}`));
|
|
13067
|
+
if (list.status === 401) console.error(import_chalk56.default.dim("Your consumer session may have expired \u2014 run `apiblaze consumer login` again."));
|
|
12761
13068
|
process.exit(1);
|
|
12762
13069
|
}
|
|
12763
13070
|
let walkCreds = list.creds;
|
|
@@ -12786,16 +13093,16 @@ async function runConsumerApikeys(opts) {
|
|
|
12786
13093
|
if (opts.json) {
|
|
12787
13094
|
console.log(JSON.stringify({ keys, revealed: revealMap }, null, 2));
|
|
12788
13095
|
} else if (!keys.length) {
|
|
12789
|
-
console.log(
|
|
13096
|
+
console.log(import_chalk56.default.yellow("No API keys yet."));
|
|
12790
13097
|
} else {
|
|
12791
13098
|
for (const k of keys) {
|
|
12792
13099
|
const clear = revealMap[k.environment]?.key;
|
|
12793
|
-
const shown = clear ?
|
|
12794
|
-
const exp = k.expires_at ?
|
|
12795
|
-
console.log(` ${
|
|
13100
|
+
const shown = clear ? import_chalk56.default.green(clear) : import_chalk56.default.dim(`${k.key_prefix ?? ""}\u2026${k.key_suffix ?? ""}`);
|
|
13101
|
+
const exp = k.expires_at ? import_chalk56.default.dim(`exp ${k.expires_at}`) : import_chalk56.default.dim("no expiry");
|
|
13102
|
+
console.log(` ${import_chalk56.default.bold(k.environment ?? "")} ${shown} ${exp} ${import_chalk56.default.dim(k.description ?? "")}`);
|
|
12796
13103
|
}
|
|
12797
13104
|
if (Object.keys(revealMap).length === 0 && keys.some((k) => !k.expires_at)) {
|
|
12798
|
-
console.log(
|
|
13105
|
+
console.log(import_chalk56.default.dim("\n(Only expiring keys can be shown in clear; non-expiring keys show a prefix only.)"));
|
|
12799
13106
|
}
|
|
12800
13107
|
}
|
|
12801
13108
|
if (opts.json) return;
|
|
@@ -12809,7 +13116,7 @@ async function runConsumerApikeys(opts) {
|
|
|
12809
13116
|
const body = { environment: answers.environment };
|
|
12810
13117
|
if (answers.description) body.description = answers.description;
|
|
12811
13118
|
if (answers.expiresDays) body.expires_in_seconds = Number(answers.expiresDays) * 86400;
|
|
12812
|
-
const s2 = (0,
|
|
13119
|
+
const s2 = (0, import_ora27.default)("Creating key...").start();
|
|
12813
13120
|
const created = await consumerFetch(list.creds, "/apikeys", { method: "POST", body: JSON.stringify(body) });
|
|
12814
13121
|
if (created.status >= 400) {
|
|
12815
13122
|
s2.fail(`Create failed (${created.status}): ${created.data?.error ?? ""}`);
|
|
@@ -12817,13 +13124,13 @@ async function runConsumerApikeys(opts) {
|
|
|
12817
13124
|
}
|
|
12818
13125
|
s2.succeed("Key created.");
|
|
12819
13126
|
const key = created.data?.key ?? created.data?.fullKey;
|
|
12820
|
-
if (key) console.log(` ${
|
|
12821
|
-
else console.log(
|
|
13127
|
+
if (key) console.log(` ${import_chalk56.default.green(key)} ${import_chalk56.default.dim("(shown once \u2014 store it now)")}`);
|
|
13128
|
+
else console.log(import_chalk56.default.dim(" Key created; run `apiblaze consumer apikeys` to reveal it if it expires."));
|
|
12822
13129
|
}
|
|
12823
13130
|
|
|
12824
13131
|
// src/commands/sidecar.ts
|
|
12825
|
-
var
|
|
12826
|
-
var
|
|
13132
|
+
var import_chalk57 = __toESM(require("chalk"));
|
|
13133
|
+
var import_ora28 = __toESM(require("ora"));
|
|
12827
13134
|
var fs13 = __toESM(require("fs"));
|
|
12828
13135
|
var path10 = __toESM(require("path"));
|
|
12829
13136
|
init_admin();
|
|
@@ -12865,18 +13172,18 @@ function upsertEnvLocal(root, token) {
|
|
|
12865
13172
|
}
|
|
12866
13173
|
function installSidecarPackage(root) {
|
|
12867
13174
|
if (fs13.existsSync(path10.join(root, "node_modules", "apiblaze", "package.json"))) {
|
|
12868
|
-
console.log(` ${
|
|
13175
|
+
console.log(` ${import_chalk57.default.green("\u2713")} apiblaze package already installed`);
|
|
12869
13176
|
return;
|
|
12870
13177
|
}
|
|
12871
13178
|
const has = (f) => fs13.existsSync(path10.join(root, f));
|
|
12872
13179
|
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" };
|
|
12873
|
-
const spinner = (0,
|
|
13180
|
+
const spinner = (0, import_ora28.default)(`Installing the apiblaze package (${pm.cmd})\u2026`).start();
|
|
12874
13181
|
try {
|
|
12875
13182
|
const { execSync } = require("child_process");
|
|
12876
13183
|
execSync(`${pm.cmd} ${pm.add} apiblaze`, { cwd: root, stdio: "ignore" });
|
|
12877
13184
|
spinner.succeed("Installed apiblaze (the sidecar runtime).");
|
|
12878
13185
|
} catch {
|
|
12879
|
-
spinner.warn(`Couldn't auto-install \u2014 run ${
|
|
13186
|
+
spinner.warn(`Couldn't auto-install \u2014 run ${import_chalk57.default.cyan(`${pm.cmd} ${pm.add} apiblaze`)} yourself before ${import_chalk57.default.cyan("npm run dev")}.`);
|
|
12880
13187
|
}
|
|
12881
13188
|
}
|
|
12882
13189
|
function readEnvKey(root) {
|
|
@@ -13015,7 +13322,7 @@ async function runAnonymousInit(root, router, opts) {
|
|
|
13015
13322
|
const { sidecarInitAnonymous: sidecarInitAnonymous2 } = await Promise.resolve().then(() => (init_api(), api_exports));
|
|
13016
13323
|
const { saveAnonCred: saveAnonCred2, clearAnonCred: clearAnonCred2 } = await Promise.resolve().then(() => (init_anon_cred(), anon_cred_exports));
|
|
13017
13324
|
if (opts.newSession) clearAnonCred2();
|
|
13018
|
-
const spinner = (0,
|
|
13325
|
+
const spinner = (0, import_ora28.default)("Setting up a sidecar (no login needed)...").start();
|
|
13019
13326
|
let out;
|
|
13020
13327
|
try {
|
|
13021
13328
|
out = await sidecarInitAnonymous2();
|
|
@@ -13027,29 +13334,29 @@ async function runAnonymousInit(root, router, opts) {
|
|
|
13027
13334
|
if (out.cp_key && out.team_id) saveAnonCred2(out.cp_key, out.team_id, out.claim_code);
|
|
13028
13335
|
const envState = upsertEnvLocal(root, out.token);
|
|
13029
13336
|
ensureGitignored(root);
|
|
13030
|
-
console.log(` ${
|
|
13031
|
-
console.log(` ${
|
|
13337
|
+
console.log(` ${import_chalk57.default.green("\u2713")} .env.local ${envState} (APIBLAZE_API_KEY) \u2014 gitignored`);
|
|
13338
|
+
console.log(` ${import_chalk57.default.green("\u2713")} instrumentation.ts ${wireInstrumentation(root)}`);
|
|
13032
13339
|
installSidecarPackage(root);
|
|
13033
13340
|
let inspectorPath = null;
|
|
13034
13341
|
if (!opts.noInspector) {
|
|
13035
13342
|
inspectorPath = generateInspector(root, router);
|
|
13036
|
-
if (inspectorPath) console.log(` ${
|
|
13343
|
+
if (inspectorPath) console.log(` ${import_chalk57.default.green("\u2713")} inspector at ${inspectorPath}`);
|
|
13037
13344
|
}
|
|
13038
13345
|
console.log("");
|
|
13039
|
-
console.log(
|
|
13040
|
-
console.log(` 1. ${
|
|
13346
|
+
console.log(import_chalk57.default.bold("Done (no account needed). What happens next:"));
|
|
13347
|
+
console.log(` 1. ${import_chalk57.default.cyan("npm run dev")} and use your app.`);
|
|
13041
13348
|
console.log(` 2. Each external origin your app calls is logged in the console \u2014 approve one with:`);
|
|
13042
|
-
console.log(` ${
|
|
13349
|
+
console.log(` ${import_chalk57.default.cyan("apiblaze sidecar approve api.stripe.com")} (no login needed)`);
|
|
13043
13350
|
console.log("");
|
|
13044
|
-
console.log(
|
|
13045
|
-
console.log(` ${
|
|
13046
|
-
console.log(
|
|
13351
|
+
console.log(import_chalk57.default.bold(" \u{1F511} Keep your setup \u2014 claim it into an account:"));
|
|
13352
|
+
console.log(` ${import_chalk57.default.cyan("apiblaze login")} then ${import_chalk57.default.cyan("apiblaze claim")} ${import_chalk57.default.dim("(no code needed here)")}`);
|
|
13353
|
+
console.log(import_chalk57.default.dim(` From another machine: apiblaze claim ${out.claim_code} \xB7 expires in 30 days`));
|
|
13047
13354
|
}
|
|
13048
13355
|
async function runSidecar(opts) {
|
|
13049
13356
|
const root = path10.resolve(opts.dir ?? process.cwd());
|
|
13050
13357
|
const detected2 = detectNextProject(root);
|
|
13051
13358
|
if (!detected2.found) {
|
|
13052
|
-
console.log(
|
|
13359
|
+
console.log(import_chalk57.default.yellow(`No Next.js project detected in ${root}.`));
|
|
13053
13360
|
console.log("Create one (e.g. `npx create-next-app`) and re-run `apiblaze init` inside it.");
|
|
13054
13361
|
return;
|
|
13055
13362
|
}
|
|
@@ -13060,10 +13367,10 @@ async function runSidecar(opts) {
|
|
|
13060
13367
|
if (!loadCredentials()) {
|
|
13061
13368
|
upsertEnvLocal(root, readEnvKey(root));
|
|
13062
13369
|
ensureGitignored(root);
|
|
13063
|
-
console.log(` ${
|
|
13064
|
-
console.log(` ${
|
|
13370
|
+
console.log(` ${import_chalk57.default.green("\u2713")} .env.local present (APIBLAZE_API_KEY) \u2014 reusing`);
|
|
13371
|
+
console.log(` ${import_chalk57.default.green("\u2713")} instrumentation.ts ${wireInstrumentation(root)}`);
|
|
13065
13372
|
installSidecarPackage(root);
|
|
13066
|
-
console.log(
|
|
13373
|
+
console.log(import_chalk57.default.dim(" Log in and run `apiblaze claim <code>` to keep this setup, or `apiblaze login` to manage it."));
|
|
13067
13374
|
return;
|
|
13068
13375
|
}
|
|
13069
13376
|
const { teamId, teamName } = await resolveTeam(opts.team);
|
|
@@ -13072,7 +13379,7 @@ async function runSidecar(opts) {
|
|
|
13072
13379
|
const mustMint = !existingKey || opts.rotate || switchingTeam;
|
|
13073
13380
|
let token = existingKey ?? "";
|
|
13074
13381
|
if (mustMint) {
|
|
13075
|
-
const spinner = (0,
|
|
13382
|
+
const spinner = (0, import_ora28.default)(existingKey ? "Re-establishing the sidecar (minting a fresh invoke key)..." : "Setting up the sidecar (tenant + non-expiring invoke key)...").start();
|
|
13076
13383
|
try {
|
|
13077
13384
|
const out = await admin({
|
|
13078
13385
|
method: "POST",
|
|
@@ -13086,39 +13393,39 @@ async function runSidecar(opts) {
|
|
|
13086
13393
|
throw err;
|
|
13087
13394
|
}
|
|
13088
13395
|
} else {
|
|
13089
|
-
console.log(
|
|
13396
|
+
console.log(import_chalk57.default.dim(` Reusing the existing APIBLAZE_API_KEY (run with --rotate to mint a fresh one, or --team <name> to switch teams).`));
|
|
13090
13397
|
}
|
|
13091
13398
|
const envState = upsertEnvLocal(root, token);
|
|
13092
13399
|
ensureGitignored(root);
|
|
13093
|
-
console.log(` ${
|
|
13400
|
+
console.log(` ${import_chalk57.default.green("\u2713")} .env.local ${envState} (APIBLAZE_API_KEY) \u2014 gitignored`);
|
|
13094
13401
|
const wireState = wireInstrumentation(root);
|
|
13095
|
-
console.log(` ${
|
|
13402
|
+
console.log(` ${import_chalk57.default.green("\u2713")} instrumentation.ts ${wireState}`);
|
|
13096
13403
|
installSidecarPackage(root);
|
|
13097
13404
|
let inspectorPath = null;
|
|
13098
13405
|
if (!opts.noInspector) {
|
|
13099
13406
|
inspectorPath = generateInspector(root, detected2.router);
|
|
13100
|
-
if (inspectorPath) console.log(` ${
|
|
13407
|
+
if (inspectorPath) console.log(` ${import_chalk57.default.green("\u2713")} inspector at ${inspectorPath}`);
|
|
13101
13408
|
}
|
|
13102
13409
|
console.log("");
|
|
13103
|
-
console.log(
|
|
13104
|
-
console.log(` 1. ${
|
|
13105
|
-
console.log(` 2. The origins your app calls appear as ${
|
|
13106
|
-
console.log(` 3. Approve the ones to route: ${
|
|
13410
|
+
console.log(import_chalk57.default.bold("Done. What happens next:"));
|
|
13411
|
+
console.log(` 1. ${import_chalk57.default.cyan("npm run dev")} and use your app \u2014 it works exactly as before (all calls go direct).`);
|
|
13412
|
+
console.log(` 2. The origins your app calls appear as ${import_chalk57.default.bold("candidates")} \u2014 list them: ${import_chalk57.default.cyan("apiblaze sidecar")}`);
|
|
13413
|
+
console.log(` 3. Approve the ones to route: ${import_chalk57.default.cyan("apiblaze sidecar approve api.stripe.com")} (or in the dashboard)`);
|
|
13107
13414
|
console.log(` \u2026within ~5 min your app starts routing that origin through APIblaze.`);
|
|
13108
|
-
if (inspectorPath) console.log(` \u2022 Try it now: open ${
|
|
13109
|
-
if (switchingTeam) console.log(
|
|
13415
|
+
if (inspectorPath) console.log(` \u2022 Try it now: open ${import_chalk57.default.underline("http://localhost:3000/abz-inspector")} (dev only; rm ${path10.dirname(inspectorPath)} before shipping)`);
|
|
13416
|
+
if (switchingTeam) console.log(import_chalk57.default.dim(` \u2022 Approved origins are per-team \u2014 re-approve them on ${teamName ?? teamId} with \`apiblaze sidecar approve <origin>\`.`));
|
|
13110
13417
|
console.log("");
|
|
13111
|
-
console.log(
|
|
13112
|
-
console.log(
|
|
13113
|
-
console.log(
|
|
13418
|
+
console.log(import_chalk57.default.dim(" Manage: apiblaze sidecar (list/approve/deny/remove)"));
|
|
13419
|
+
console.log(import_chalk57.default.dim(" Rotate: apiblaze init --rotate \xB7 Switch team: apiblaze init --team <name>"));
|
|
13420
|
+
console.log(import_chalk57.default.dim(" Turn off: set APIBLAZE_SIDECAR=off in .env.local (flip back to on anytime; key stays put)."));
|
|
13114
13421
|
console.log("");
|
|
13115
|
-
console.log(
|
|
13116
|
-
console.log(
|
|
13422
|
+
console.log(import_chalk57.default.yellow(" \u26A0 APIBLAZE_API_KEY is long-lived and lets a holder call your team's proxies. Never commit it."));
|
|
13423
|
+
console.log(import_chalk57.default.dim(" Your control-plane login stays in ~/.apiblaze \u2014 it never entered this project."));
|
|
13117
13424
|
}
|
|
13118
13425
|
|
|
13119
13426
|
// src/commands/origins.ts
|
|
13120
|
-
var
|
|
13121
|
-
var
|
|
13427
|
+
var import_chalk58 = __toESM(require("chalk"));
|
|
13428
|
+
var import_ora29 = __toESM(require("ora"));
|
|
13122
13429
|
init_admin();
|
|
13123
13430
|
init_resolve();
|
|
13124
13431
|
init_auth();
|
|
@@ -13128,7 +13435,7 @@ async function runOriginsList(opts) {
|
|
|
13128
13435
|
if (!loadCredentials()) {
|
|
13129
13436
|
const cred = loadAnonCred();
|
|
13130
13437
|
if (!cred) {
|
|
13131
|
-
console.log(
|
|
13438
|
+
console.log(import_chalk58.default.yellow("No anonymous workspace here. Run `apiblaze init` first."));
|
|
13132
13439
|
return;
|
|
13133
13440
|
}
|
|
13134
13441
|
out = await cpFetch(cred.cp_key, `/teams/${encodeURIComponent(cred.team_id)}/sidecar/candidates`, { method: "GET" });
|
|
@@ -13146,30 +13453,30 @@ async function runOriginsList(opts) {
|
|
|
13146
13453
|
}
|
|
13147
13454
|
const routed = out.routed ?? [];
|
|
13148
13455
|
const candidates = out.candidates ?? [];
|
|
13149
|
-
console.log(
|
|
13456
|
+
console.log(import_chalk58.default.bold(`
|
|
13150
13457
|
Routed through APIblaze (${routed.length})`));
|
|
13151
|
-
if (!routed.length) console.log(
|
|
13152
|
-
for (const r of routed) console.log(` ${
|
|
13153
|
-
console.log(
|
|
13458
|
+
if (!routed.length) console.log(import_chalk58.default.dim(" none yet"));
|
|
13459
|
+
for (const r of routed) console.log(` ${import_chalk58.default.green("\u25CF")} ${r.sidecar_origin} ${import_chalk58.default.dim(`\u2192 ${r.project_id}`)}`);
|
|
13460
|
+
console.log(import_chalk58.default.bold(`
|
|
13154
13461
|
Candidates \u2014 going direct, not yet approved (${candidates.length})`));
|
|
13155
|
-
if (!candidates.length) console.log(
|
|
13462
|
+
if (!candidates.length) console.log(import_chalk58.default.dim(" none \u2014 run your app to discover the origins it calls"));
|
|
13156
13463
|
for (const c of candidates) {
|
|
13157
|
-
console.log(` ${
|
|
13464
|
+
console.log(` ${import_chalk58.default.yellow("\u25CB")} ${c.origin} ${import_chalk58.default.dim(`seen ${c.request_count}\xD7, last ${c.last_seen}`)}`);
|
|
13158
13465
|
}
|
|
13159
13466
|
if (candidates.length) {
|
|
13160
|
-
console.log(
|
|
13467
|
+
console.log(import_chalk58.default.dim(`
|
|
13161
13468
|
Approve: apiblaze sidecar approve ${candidates[0].origin.replace("https://", "")}`));
|
|
13162
|
-
console.log(
|
|
13469
|
+
console.log(import_chalk58.default.dim(` Dismiss: apiblaze sidecar deny ${candidates[0].origin.replace("https://", "")}`));
|
|
13163
13470
|
}
|
|
13164
13471
|
}
|
|
13165
13472
|
async function runOriginsApprove(origin, opts) {
|
|
13166
13473
|
if (!loadCredentials()) {
|
|
13167
13474
|
const cred = loadAnonCred();
|
|
13168
13475
|
if (!cred) {
|
|
13169
|
-
console.error(
|
|
13476
|
+
console.error(import_chalk58.default.red("Not logged in and no anonymous workspace. Run `apiblaze init` first."));
|
|
13170
13477
|
process.exit(1);
|
|
13171
13478
|
}
|
|
13172
|
-
const spinner2 = (0,
|
|
13479
|
+
const spinner2 = (0, import_ora29.default)(`Approving ${origin} (anonymous)...`).start();
|
|
13173
13480
|
try {
|
|
13174
13481
|
const out = await cpFetch(cred.cp_key, `/teams/${encodeURIComponent(cred.team_id)}/sidecar/approve`, { method: "POST", body: JSON.stringify({ origin }) });
|
|
13175
13482
|
spinner2.succeed(`Approved ${origin} \u2192 proxy ${out.project_id}. Routing within ~5 min.`);
|
|
@@ -13180,7 +13487,7 @@ async function runOriginsApprove(origin, opts) {
|
|
|
13180
13487
|
return;
|
|
13181
13488
|
}
|
|
13182
13489
|
const { teamId } = await resolveTeam(opts.team);
|
|
13183
|
-
const spinner = (0,
|
|
13490
|
+
const spinner = (0, import_ora29.default)(`Approving ${origin}...`).start();
|
|
13184
13491
|
try {
|
|
13185
13492
|
const out = await admin({
|
|
13186
13493
|
method: "POST",
|
|
@@ -13197,7 +13504,7 @@ async function runOriginsApprove(origin, opts) {
|
|
|
13197
13504
|
}
|
|
13198
13505
|
async function runOriginsDeny(origin, opts) {
|
|
13199
13506
|
const { teamId } = await resolveTeam(opts.team);
|
|
13200
|
-
const spinner = (0,
|
|
13507
|
+
const spinner = (0, import_ora29.default)(`Dismissing ${origin}...`).start();
|
|
13201
13508
|
try {
|
|
13202
13509
|
await admin({ method: "POST", path: `/teams/${encodeURIComponent(teamId)}/sidecar/dismiss`, body: { origin }, summary: `Dismiss sidecar origin ${origin}` });
|
|
13203
13510
|
spinner.succeed(`Dismissed ${origin}. It won't be suggested again.`);
|
|
@@ -13208,7 +13515,7 @@ async function runOriginsDeny(origin, opts) {
|
|
|
13208
13515
|
}
|
|
13209
13516
|
async function runOriginsRemove(origin, opts) {
|
|
13210
13517
|
const { teamId } = await resolveTeam(opts.team);
|
|
13211
|
-
const spinner = (0,
|
|
13518
|
+
const spinner = (0, import_ora29.default)(`Removing the proxy for ${origin}...`).start();
|
|
13212
13519
|
try {
|
|
13213
13520
|
await admin({ method: "POST", path: `/teams/${encodeURIComponent(teamId)}/sidecar/remove`, body: { origin }, summary: `Un-route sidecar origin ${origin}` });
|
|
13214
13521
|
spinner.succeed(`Removed ${origin}. Your app will stop routing it (goes direct) within ~5 min.`);
|
|
@@ -13219,7 +13526,7 @@ async function runOriginsRemove(origin, opts) {
|
|
|
13219
13526
|
}
|
|
13220
13527
|
|
|
13221
13528
|
// src/commands/op.ts
|
|
13222
|
-
var
|
|
13529
|
+
var import_chalk63 = __toESM(require("chalk"));
|
|
13223
13530
|
init_auth();
|
|
13224
13531
|
|
|
13225
13532
|
// src/lib/op-call.ts
|
|
@@ -13248,7 +13555,7 @@ async function opCall(call2) {
|
|
|
13248
13555
|
}
|
|
13249
13556
|
|
|
13250
13557
|
// src/commands/op-billing.ts
|
|
13251
|
-
var
|
|
13558
|
+
var import_chalk59 = __toESM(require("chalk"));
|
|
13252
13559
|
init_admin();
|
|
13253
13560
|
var SANDBOX = {
|
|
13254
13561
|
teamId: "team_1782844865835_zujrf",
|
|
@@ -13307,7 +13614,7 @@ async function rowsForRays(project, version2, tenant2, rays) {
|
|
|
13307
13614
|
}
|
|
13308
13615
|
function printDoors(data) {
|
|
13309
13616
|
const checks = [];
|
|
13310
|
-
console.log(
|
|
13617
|
+
console.log(import_chalk59.default.bold("\n Doors \u2014 is every way in metered?\n"));
|
|
13311
13618
|
const doors = data?.doors ?? [];
|
|
13312
13619
|
const metered = doors.filter((d) => d.verdict === "metered");
|
|
13313
13620
|
const allowed = doors.filter((d) => d.verdict === "allowed-free");
|
|
@@ -13316,29 +13623,29 @@ function printDoors(data) {
|
|
|
13316
13623
|
const errs = data?.errors ?? [];
|
|
13317
13624
|
const routeAuditBroke = errs.some((e) => e.startsWith("zone "));
|
|
13318
13625
|
const devAuditBroke = errs.some((e) => e.startsWith("workers.dev audit"));
|
|
13319
|
-
console.log(
|
|
13320
|
-
for (const d of metered) console.log(
|
|
13321
|
-
console.log(
|
|
13626
|
+
console.log(import_chalk59.default.dim(` ${metered.length} route(s) behind main-proxy (metered)`));
|
|
13627
|
+
for (const d of metered) console.log(import_chalk59.default.green(` \u2713 ${d.pattern}`));
|
|
13628
|
+
console.log(import_chalk59.default.dim(`
|
|
13322
13629
|
${allowed.length} route(s) free ON PURPOSE`));
|
|
13323
13630
|
for (const d of allowed) {
|
|
13324
|
-
console.log(
|
|
13325
|
-
console.log(
|
|
13631
|
+
console.log(import_chalk59.default.cyan(` \u2022 ${d.pattern}`) + import_chalk59.default.dim(` \u2192 ${d.script}`));
|
|
13632
|
+
console.log(import_chalk59.default.dim(` ${d.why}`));
|
|
13326
13633
|
}
|
|
13327
13634
|
if (known.length) {
|
|
13328
|
-
console.log(
|
|
13635
|
+
console.log(import_chalk59.default.yellow(`
|
|
13329
13636
|
${known.length} route(s) KNOWN OPEN \u2014 unmetered, not yet closed`));
|
|
13330
13637
|
for (const d of known) {
|
|
13331
|
-
console.log(
|
|
13332
|
-
console.log(
|
|
13638
|
+
console.log(import_chalk59.default.yellow(` ! ${d.pattern}`) + import_chalk59.default.dim(` \u2192 ${d.script}`));
|
|
13639
|
+
console.log(import_chalk59.default.dim(` ${d.why}`));
|
|
13333
13640
|
}
|
|
13334
13641
|
checks.push({ name: "no known-open doors", status: "KNOWN", detail: `${known.length} unmetered route(s) still open \u2014 see above` });
|
|
13335
13642
|
}
|
|
13336
13643
|
if (stray.length) {
|
|
13337
|
-
console.log(
|
|
13644
|
+
console.log(import_chalk59.default.red(`
|
|
13338
13645
|
${stray.length} STRAY route(s) \u2014 not main-proxy, not on the allowlist`));
|
|
13339
13646
|
for (const d of stray) {
|
|
13340
|
-
console.log(
|
|
13341
|
-
console.log(
|
|
13647
|
+
console.log(import_chalk59.default.red(` \u2717 ${d.pattern}`) + import_chalk59.default.dim(` \u2192 ${d.script}`));
|
|
13648
|
+
console.log(import_chalk59.default.dim(` ${d.why}`));
|
|
13342
13649
|
}
|
|
13343
13650
|
checks.push({ name: "no stray routes", status: "FAIL", detail: `${stray.length}: ${stray.map((s) => s.pattern).join(", ")}` });
|
|
13344
13651
|
} else if (routeAuditBroke) {
|
|
@@ -13349,22 +13656,22 @@ function printDoors(data) {
|
|
|
13349
13656
|
const wd = data?.workers_dev ?? {};
|
|
13350
13657
|
const open = wd.enabled ?? [];
|
|
13351
13658
|
if (open.length) {
|
|
13352
|
-
console.log(
|
|
13659
|
+
console.log(import_chalk59.default.red(`
|
|
13353
13660
|
${open.length} of ${wd.total} worker(s) reachable on *.workers.dev`));
|
|
13354
|
-
for (const s of open) console.log(
|
|
13355
|
-
console.log(
|
|
13661
|
+
for (const s of open) console.log(import_chalk59.default.red(` \u2717 ${s.script}.workers.dev`) + import_chalk59.default.dim(` (enabled=${s.enabled} previews=${s.previews})`));
|
|
13662
|
+
console.log(import_chalk59.default.dim(" A workers.dev hostname bypasses every CF route, WAF rule and the credit gate."));
|
|
13356
13663
|
checks.push({ name: "no workers.dev doors", status: "FAIL", detail: `${open.length} script(s) publicly reachable: ${open.map((s) => s.script).join(", ")}` });
|
|
13357
13664
|
} else if (devAuditBroke || !wd.total) {
|
|
13358
13665
|
checks.push({ name: "no workers.dev doors", status: "SKIP", detail: "script enumeration failed \u2014 NOT a pass, no subdomain was ever read. Needs a CF token with Workers Scripts:Read." });
|
|
13359
13666
|
} else {
|
|
13360
|
-
console.log(
|
|
13667
|
+
console.log(import_chalk59.default.green(`
|
|
13361
13668
|
\u2713 0 of ${wd.total} workers reachable on *.workers.dev`));
|
|
13362
13669
|
checks.push({ name: "no workers.dev doors", status: "PASS", detail: `all ${wd.total} scripts have workers.dev + previews disabled` });
|
|
13363
13670
|
}
|
|
13364
|
-
if (data?.how_to_fix) console.log(
|
|
13365
|
-
${data.reason}`) +
|
|
13671
|
+
if (data?.how_to_fix) console.log(import_chalk59.default.yellow(`
|
|
13672
|
+
${data.reason}`) + import_chalk59.default.dim(`
|
|
13366
13673
|
${data.how_to_fix}`));
|
|
13367
|
-
else for (const e of errs) console.log(
|
|
13674
|
+
else for (const e of errs) console.log(import_chalk59.default.red(`
|
|
13368
13675
|
audit error: ${e}`));
|
|
13369
13676
|
if (errs.length) {
|
|
13370
13677
|
checks.push({ name: "audit completeness", status: "FAIL", detail: `${errs.length} part(s) of the audit could not run \u2014 coverage is INCOMPLETE, and the checks they would have covered are SKIP above` });
|
|
@@ -13374,9 +13681,9 @@ function printDoors(data) {
|
|
|
13374
13681
|
async function runMeter(readLedger, opts) {
|
|
13375
13682
|
const checks = [];
|
|
13376
13683
|
const N = Math.max(1, Math.min(10, opts.count ?? 3));
|
|
13377
|
-
console.log(
|
|
13684
|
+
console.log(import_chalk59.default.bold("\n Meter \u2014 is 1 request charged exactly 1 request?\n"));
|
|
13378
13685
|
const snap = await readLedger();
|
|
13379
|
-
console.log(
|
|
13686
|
+
console.log(import_chalk59.default.dim(` wallet ${snap.billing_account_id} \xB7 band count ${snap.band_count} \xB7 next request ${snap.next_request_cents}\xA2
|
|
13380
13687
|
`));
|
|
13381
13688
|
const url = `https://${dpHost}/${SANDBOX.version}/${SANDBOX.environment}/`;
|
|
13382
13689
|
const headers = opts.key ? { "X-API-Key": opts.key } : {};
|
|
@@ -13458,27 +13765,27 @@ async function runMeter(readLedger, opts) {
|
|
|
13458
13765
|
return checks;
|
|
13459
13766
|
}
|
|
13460
13767
|
function printChecks(checks) {
|
|
13461
|
-
console.log(
|
|
13462
|
-
const mark = { PASS:
|
|
13768
|
+
console.log(import_chalk59.default.bold("\n Results\n"));
|
|
13769
|
+
const mark = { PASS: import_chalk59.default.green(" PASS"), FAIL: import_chalk59.default.red(" FAIL"), SKIP: import_chalk59.default.dim(" SKIP"), KNOWN: import_chalk59.default.yellow(" KNOWN") };
|
|
13463
13770
|
for (const ch of checks) {
|
|
13464
|
-
console.log(` ${mark[ch.status]} ${
|
|
13465
|
-
console.log(
|
|
13771
|
+
console.log(` ${mark[ch.status]} ${import_chalk59.default.bold(ch.name)}`);
|
|
13772
|
+
console.log(import_chalk59.default.dim(` ${ch.detail}`));
|
|
13466
13773
|
}
|
|
13467
13774
|
const fails = checks.filter((c) => c.status === "FAIL").length;
|
|
13468
13775
|
const skips = checks.filter((c) => c.status === "SKIP").length;
|
|
13469
13776
|
const known = checks.filter((c) => c.status === "KNOWN").length;
|
|
13470
13777
|
console.log("");
|
|
13471
13778
|
const passes = checks.filter((c) => c.status === "PASS").length;
|
|
13472
|
-
if (fails) console.log(
|
|
13473
|
-
else if (passes) console.log(
|
|
13474
|
-
else console.log(
|
|
13475
|
-
if (known) console.log(
|
|
13476
|
-
if (skips) console.log(
|
|
13779
|
+
if (fails) console.log(import_chalk59.default.red(` ${fails} check(s) FAILED.`));
|
|
13780
|
+
else if (passes) console.log(import_chalk59.default.green(` ${passes} check(s) passed, 0 failed.`));
|
|
13781
|
+
else console.log(import_chalk59.default.yellow(" NOTHING WAS VERIFIED \u2014 every check was skipped."));
|
|
13782
|
+
if (known) console.log(import_chalk59.default.yellow(` ${known} known-open issue(s) still outstanding.`));
|
|
13783
|
+
if (skips) console.log(import_chalk59.default.dim(` ${skips} check(s) NOT RUN (see SKIP above) \u2014 those invariants are unverified.`));
|
|
13477
13784
|
console.log("");
|
|
13478
13785
|
}
|
|
13479
13786
|
|
|
13480
13787
|
// src/commands/op-test.ts
|
|
13481
|
-
var
|
|
13788
|
+
var import_chalk60 = __toESM(require("chalk"));
|
|
13482
13789
|
init_auth();
|
|
13483
13790
|
init_types();
|
|
13484
13791
|
var DASHBOARD_BASE9 = process.env.APIBLAZE_DASHBOARD_BASE || "https://dashboard.apiblaze.com";
|
|
@@ -13503,10 +13810,10 @@ async function scrubSandboxKeys(timeoutS = 150) {
|
|
|
13503
13810
|
try {
|
|
13504
13811
|
const r = await runnerCall({ action: "gut", id: TEARDOWN_GUT_ID }, timeoutS);
|
|
13505
13812
|
const first = r.results?.[0]?.detail ?? r.error ?? "";
|
|
13506
|
-
log(
|
|
13813
|
+
log(import_chalk60.default.dim(`teardown: ${first || (r.ok ? "sandbox fixture keys scrubbed" : "scrub reported a problem")}`));
|
|
13507
13814
|
return r.ok;
|
|
13508
13815
|
} catch (e) {
|
|
13509
|
-
log(
|
|
13816
|
+
log(import_chalk60.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`));
|
|
13510
13817
|
return false;
|
|
13511
13818
|
}
|
|
13512
13819
|
}
|
|
@@ -13528,23 +13835,23 @@ async function runOpTest(o = {}) {
|
|
|
13528
13835
|
const guts = o.skipGuts ? [] : listing.guts.filter((g) => g.id !== TEARDOWN_GUT_ID && matches(g.id, void 0, { ...o, group: void 0 }));
|
|
13529
13836
|
const total = specs.length + guts.length;
|
|
13530
13837
|
if (total === 0) {
|
|
13531
|
-
log(
|
|
13838
|
+
log(import_chalk60.default.yellow("nothing matched"));
|
|
13532
13839
|
process.exitCode = 2;
|
|
13533
13840
|
return;
|
|
13534
13841
|
}
|
|
13535
|
-
log(
|
|
13842
|
+
log(import_chalk60.default.bold(`apiblaze op test \u2014 ${specs.length} spec(s) + ${guts.length} gut check(s) on sandbox ${listing.sandbox_team}`));
|
|
13536
13843
|
const results = [];
|
|
13537
13844
|
let failed = 0;
|
|
13538
13845
|
const started = Date.now();
|
|
13539
13846
|
const record = (r) => {
|
|
13540
13847
|
results.push(r);
|
|
13541
13848
|
if (!r.ok) failed += 1;
|
|
13542
|
-
const mark = r.ok ?
|
|
13849
|
+
const mark = r.ok ? import_chalk60.default.green("PASS") : import_chalk60.default.red("FAIL");
|
|
13543
13850
|
log(`[${mark}] ${r.id} \u2014 ${r.label} (${ms(r.duration_ms)})`);
|
|
13544
13851
|
if (!r.ok) {
|
|
13545
|
-
if (r.kind === "spec") for (const c of r.cases.filter((c2) => !c2.ok)) log(
|
|
13546
|
-
else if (r.error) log(
|
|
13547
|
-
else for (const x of r.results.filter((x2) => x2 && x2.ok === false)) log(
|
|
13852
|
+
if (r.kind === "spec") for (const c of r.cases.filter((c2) => !c2.ok)) log(import_chalk60.default.red(` \u2717 ${c.name}: ${c.detail}`));
|
|
13853
|
+
else if (r.error) log(import_chalk60.default.red(` \u2717 ${r.error}`));
|
|
13854
|
+
else for (const x of r.results.filter((x2) => x2 && x2.ok === false)) log(import_chalk60.default.red(` \u2717 ${x.name ?? ""}: ${x.detail ?? JSON.stringify(x)}`));
|
|
13548
13855
|
}
|
|
13549
13856
|
};
|
|
13550
13857
|
try {
|
|
@@ -13575,13 +13882,13 @@ async function runOpTest(o = {}) {
|
|
|
13575
13882
|
}
|
|
13576
13883
|
const summary = { ran: results.length, passed: results.length - failed, failed, duration_ms: Date.now() - started, sandbox_team: listing.sandbox_team };
|
|
13577
13884
|
log("");
|
|
13578
|
-
log(failed ?
|
|
13885
|
+
log(failed ? import_chalk60.default.red(`${summary.passed}/${summary.ran} passed, ${failed} failed (${ms(summary.duration_ms)})`) : import_chalk60.default.green(`${summary.passed}/${summary.ran} passed (${ms(summary.duration_ms)})`));
|
|
13579
13886
|
if (o.json) process.stdout.write(JSON.stringify({ ...summary, results }, null, 2) + "\n");
|
|
13580
13887
|
if (failed) process.exitCode = 1;
|
|
13581
13888
|
}
|
|
13582
13889
|
|
|
13583
13890
|
// src/commands/op-consumer.ts
|
|
13584
|
-
var
|
|
13891
|
+
var import_chalk61 = __toESM(require("chalk"));
|
|
13585
13892
|
var SANDBOX_TENANT = "portalteststenant";
|
|
13586
13893
|
var SANDBOX_PROXY = "portaltestproxypreapproved@2.7.0";
|
|
13587
13894
|
function proxyUrlFor(spec2, path11 = "/get") {
|
|
@@ -13623,9 +13930,9 @@ async function runOpConsumer(o = {}) {
|
|
|
13623
13930
|
const creds = loadConsumer();
|
|
13624
13931
|
const done = (r) => {
|
|
13625
13932
|
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 };
|
|
13626
|
-
const mark = out.skipped ?
|
|
13933
|
+
const mark = out.skipped ? import_chalk61.default.yellow("SKIP") : out.ok ? import_chalk61.default.green("PASS") : import_chalk61.default.red("FAIL");
|
|
13627
13934
|
log2(`[${mark}] ${out.id} \u2014 ${out.label}${out.tenant ? ` (tenant ${out.tenant})` : ""}${out.url ? ` \u2192 ${out.url}` : ""}`);
|
|
13628
|
-
for (const c of out.cases) if (!c.ok || out.skipped) log2((c.ok ?
|
|
13935
|
+
for (const c of out.cases) if (!c.ok || out.skipped) log2((c.ok ? import_chalk61.default.dim : import_chalk61.default.red)(` ${c.ok ? "ok " : "\u2717"} ${c.name}: ${c.detail}`));
|
|
13629
13936
|
if (o.json) process.stdout.write(JSON.stringify(out, null, 2) + "\n");
|
|
13630
13937
|
if (!out.ok && !out.skipped) process.exitCode = 1;
|
|
13631
13938
|
if (out.skipped) process.exitCode = 2;
|
|
@@ -13665,7 +13972,7 @@ async function runOpConsumer(o = {}) {
|
|
|
13665
13972
|
}
|
|
13666
13973
|
|
|
13667
13974
|
// src/commands/op-perf.ts
|
|
13668
|
-
var
|
|
13975
|
+
var import_chalk62 = __toESM(require("chalk"));
|
|
13669
13976
|
var RPS_DEFAULT = 100;
|
|
13670
13977
|
var RPS_CAP = 300;
|
|
13671
13978
|
var SECONDS_DEFAULT = 60;
|
|
@@ -13849,10 +14156,10 @@ async function readGateway(proxy, sent, signal) {
|
|
|
13849
14156
|
}
|
|
13850
14157
|
const left = deadline - Date.now();
|
|
13851
14158
|
if (left <= 0) {
|
|
13852
|
-
log3(
|
|
14159
|
+
log3(import_chalk62.default.yellow(` gateway: not available \u2014 Analytics Engine had ${lastSeen}/${sent} rows for ${proxy} after ${GATEWAY_WAIT_MS / 1e3}s${lastErr ? ` (last error: ${lastErr})` : ""}`));
|
|
13853
14160
|
return null;
|
|
13854
14161
|
}
|
|
13855
|
-
log3(
|
|
14162
|
+
log3(import_chalk62.default.dim(` waiting for the latency ledger\u2026 ${lastSeen}/${sent} rows for ${proxy}${lastErr ? ` (${lastErr})` : ""}`));
|
|
13856
14163
|
await sleep2(Math.min(GATEWAY_POLL_MS, left), signal);
|
|
13857
14164
|
}
|
|
13858
14165
|
}
|
|
@@ -13861,7 +14168,7 @@ async function runOpPerf(o = {}) {
|
|
|
13861
14168
|
const thresholds = thresholdsFrom(o);
|
|
13862
14169
|
const ctrl = new AbortController();
|
|
13863
14170
|
const onSigint = () => {
|
|
13864
|
-
log3(
|
|
14171
|
+
log3(import_chalk62.default.yellow("\ninterrupted \u2014 stopping the load and tearing down the fixture"));
|
|
13865
14172
|
ctrl.abort();
|
|
13866
14173
|
};
|
|
13867
14174
|
process.once("SIGINT", onSigint);
|
|
@@ -13870,7 +14177,7 @@ async function runOpPerf(o = {}) {
|
|
|
13870
14177
|
if (!o.key) throw new Error("--url needs --key (the fixture proxy's API key)");
|
|
13871
14178
|
fixture = { url: o.url, key: o.key, proxy: proxyFromUrl(o.url), provisioned: false };
|
|
13872
14179
|
} else {
|
|
13873
|
-
log3(
|
|
14180
|
+
log3(import_chalk62.default.dim("provisioning the perf fixture through the runner\u2026"));
|
|
13874
14181
|
const f = await runnerCall({ action: "perf-fixture" }, 120);
|
|
13875
14182
|
if (!f?.url || !f?.key) throw new Error(`perf-fixture returned no url/key: ${JSON.stringify(f).slice(0, 200)}`);
|
|
13876
14183
|
fixture = { url: f.url, key: f.key, proxy: f.proxy || proxyFromUrl(f.url), team: f.team, provisioned: true };
|
|
@@ -13878,7 +14185,7 @@ async function runOpPerf(o = {}) {
|
|
|
13878
14185
|
try {
|
|
13879
14186
|
const total = Math.round(rps * seconds);
|
|
13880
14187
|
const intervalMs = 1e3 / rps;
|
|
13881
|
-
log3(
|
|
14188
|
+
log3(import_chalk62.default.bold(`apiblaze op test --perf \u2014 ${rps} rps \xD7 ${seconds}s (${total} requests) against ${fixture.url}${fixture.team ? ` (sandbox ${fixture.team})` : ""}`));
|
|
13882
14189
|
const samples = [];
|
|
13883
14190
|
const inflight = /* @__PURE__ */ new Set();
|
|
13884
14191
|
let fired = 0;
|
|
@@ -13886,7 +14193,7 @@ async function runOpPerf(o = {}) {
|
|
|
13886
14193
|
const progress = setInterval(() => {
|
|
13887
14194
|
const m = summarize(samples, Date.now() - t0);
|
|
13888
14195
|
const secs = (Date.now() - t0) / 1e3;
|
|
13889
|
-
log3(
|
|
14196
|
+
log3(import_chalk62.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)`));
|
|
13890
14197
|
}, PROGRESS_EVERY_MS);
|
|
13891
14198
|
const { sent, elapsedMs } = await schedule({
|
|
13892
14199
|
total,
|
|
@@ -13900,21 +14207,21 @@ async function runOpPerf(o = {}) {
|
|
|
13900
14207
|
}
|
|
13901
14208
|
});
|
|
13902
14209
|
clearInterval(progress);
|
|
13903
|
-
if (inflight.size) log3(
|
|
14210
|
+
if (inflight.size) log3(import_chalk62.default.dim(` load sent; waiting for ${inflight.size} in-flight response(s) (\u2264 ${REQUEST_TIMEOUT_MS / 1e3}s)\u2026`));
|
|
13904
14211
|
await Promise.all([...inflight]);
|
|
13905
14212
|
const client = summarize(samples, elapsedMs);
|
|
13906
14213
|
log3("");
|
|
13907
|
-
log3(` ${
|
|
14214
|
+
log3(` ${import_chalk62.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}`);
|
|
13908
14215
|
const gateway = ctrl.signal.aborted ? null : await readGateway(fixture.proxy, sent, ctrl.signal);
|
|
13909
14216
|
if (gateway) {
|
|
13910
14217
|
const extras = [
|
|
13911
14218
|
gateway.kv_per_req !== void 0 ? `kv/req ${gateway.kv_per_req}` : "",
|
|
13912
14219
|
gateway.redis_per_req !== void 0 ? `redis/req ${gateway.redis_per_req}` : ""
|
|
13913
14220
|
].filter(Boolean).join(" ");
|
|
13914
|
-
log3(` ${
|
|
13915
|
-
for (const b of gateway.blame ?? []) log3(
|
|
14221
|
+
log3(` ${import_chalk62.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_chalk62.default.dim(`(${gateway.requests} ledger rows${extras ? `, ${extras}` : ""})`)}`);
|
|
14222
|
+
for (const b of gateway.blame ?? []) log3(import_chalk62.default.dim(` ${(b.share * 100).toFixed(1).padStart(5)}% p95 ${fmtMs(b.p95_ms).padStart(6)} ${b.feature} \u2192 ${b.dep}`));
|
|
13916
14223
|
} else {
|
|
13917
|
-
log3(` ${
|
|
14224
|
+
log3(` ${import_chalk62.default.bold("gateway")} ${import_chalk62.default.yellow("not available")} ${import_chalk62.default.dim("(gateway thresholds not evaluated)")}`);
|
|
13918
14225
|
}
|
|
13919
14226
|
const breaches = evaluate(client, gateway, thresholds);
|
|
13920
14227
|
const aborted = ctrl.signal.aborted;
|
|
@@ -13922,7 +14229,7 @@ async function runOpPerf(o = {}) {
|
|
|
13922
14229
|
log3("");
|
|
13923
14230
|
const row = (metric, value, limit, unit) => {
|
|
13924
14231
|
const hit = breaches.find((b) => b.metric === metric);
|
|
13925
|
-
log3(`[${hit ?
|
|
14232
|
+
log3(`[${hit ? import_chalk62.default.red("FAIL") : import_chalk62.default.green("PASS")}] ${metric.padEnd(11)} ${String(value)}${unit} ${hit ? ">" : "\u2264"} ${limit}${unit}`);
|
|
13926
14233
|
};
|
|
13927
14234
|
row("client_p50", client.p50, thresholds.p50_ms, "ms");
|
|
13928
14235
|
row("client_p95", client.p95, thresholds.p95_ms, "ms");
|
|
@@ -13931,12 +14238,12 @@ async function runOpPerf(o = {}) {
|
|
|
13931
14238
|
row("gateway_p50", gateway.p50, thresholds.gateway_p50_ms, "ms");
|
|
13932
14239
|
row("gateway_p95", gateway.p95, thresholds.gateway_p95_ms, "ms");
|
|
13933
14240
|
} else {
|
|
13934
|
-
log3(`[${
|
|
14241
|
+
log3(`[${import_chalk62.default.yellow("SKIP")}] gateway_p50 / gateway_p95 \u2014 ledger rows not available`);
|
|
13935
14242
|
}
|
|
13936
14243
|
log3("");
|
|
13937
|
-
if (aborted) log3(
|
|
13938
|
-
else if (ok) log3(
|
|
13939
|
-
else log3(
|
|
14244
|
+
if (aborted) log3(import_chalk62.default.red(`FAIL \u2014 interrupted after ${sent}/${total} requests`));
|
|
14245
|
+
else if (ok) log3(import_chalk62.default.green(`PASS \u2014 ${client.sent} requests at ${client.rps_achieved} rps, no threshold breached`));
|
|
14246
|
+
else log3(import_chalk62.default.red(`FAIL \u2014 ${breaches.length} threshold(s) breached: ${breaches.map((b) => `${b.metric} ${b.value} > ${b.limit}`).join(", ")}`));
|
|
13940
14247
|
if (o.json) {
|
|
13941
14248
|
const doc = {
|
|
13942
14249
|
kind: "perf",
|
|
@@ -13974,9 +14281,9 @@ async function runOpPerf(o = {}) {
|
|
|
13974
14281
|
if (fixture.provisioned) {
|
|
13975
14282
|
try {
|
|
13976
14283
|
await runnerCall({ action: "perf-teardown", proxy: fixture.proxy }, 120);
|
|
13977
|
-
log3(
|
|
14284
|
+
log3(import_chalk62.default.dim(`fixture ${fixture.proxy} torn down`));
|
|
13978
14285
|
} catch (e) {
|
|
13979
|
-
log3(
|
|
14286
|
+
log3(import_chalk62.default.red(`teardown of ${fixture.proxy} failed: ${e instanceof Error ? e.message : String(e)} \u2014 run \`apiblaze op residue\` / clean the sandbox`));
|
|
13980
14287
|
process.exitCode = 1;
|
|
13981
14288
|
}
|
|
13982
14289
|
await scrubSandboxKeys(120);
|
|
@@ -14010,93 +14317,93 @@ var OP_COMMANDS = [
|
|
|
14010
14317
|
function renderOpCommands() {
|
|
14011
14318
|
const width = Math.max(...OP_COMMANDS.map((c) => c.cmd.length)) + 10;
|
|
14012
14319
|
const lines = OP_COMMANDS.map((c) => {
|
|
14013
|
-
const left = ` ${
|
|
14320
|
+
const left = ` ${import_chalk63.default.cyan(`apiblaze ${c.cmd}`)}`;
|
|
14014
14321
|
const pad = " ".repeat(Math.max(1, width - c.cmd.length));
|
|
14015
|
-
return `${left}${pad}${c.blurb}${c.extra ? " " +
|
|
14322
|
+
return `${left}${pad}${c.blurb}${c.extra ? " " + import_chalk63.default.dim(`(${c.extra})`) : ""}`;
|
|
14016
14323
|
});
|
|
14017
14324
|
return [
|
|
14018
|
-
|
|
14325
|
+
import_chalk63.default.bold("Operator commands"),
|
|
14019
14326
|
...lines,
|
|
14020
14327
|
"",
|
|
14021
|
-
|
|
14022
|
-
|
|
14023
|
-
|
|
14328
|
+
import_chalk63.default.dim(" Operators only. The gate is server-side (dashboard /api/cli/op checks the"),
|
|
14329
|
+
import_chalk63.default.dim(" signed-in email, admin-api re-checks with operatorGate) \u2014 a patched CLI just"),
|
|
14330
|
+
import_chalk63.default.dim(" gets 403s. Every op call is read-only except `op sweep`."),
|
|
14024
14331
|
"",
|
|
14025
|
-
|
|
14026
|
-
|
|
14027
|
-
|
|
14332
|
+
import_chalk63.default.dim(" Not a CLI command: to prune all non-CP data run scripts/nuke-but-cp.sh --apply --sweep"),
|
|
14333
|
+
import_chalk63.default.dim(" in the repo. Operator dashboards (dlq, thresholds, throttling, pricing, billing,"),
|
|
14334
|
+
import_chalk63.default.dim(" agent-spend, teams, tests, leak-detection, lifecycle) live at /operator/* in the app.")
|
|
14028
14335
|
].join("\n");
|
|
14029
14336
|
}
|
|
14030
14337
|
function printResidue(report, applied) {
|
|
14031
14338
|
const up = report?.upstash ?? {};
|
|
14032
14339
|
const fga = report?.fga ?? {};
|
|
14033
14340
|
const ghosts = report?.ghosts ?? {};
|
|
14034
|
-
console.log(
|
|
14035
|
-
console.log(
|
|
14341
|
+
console.log(import_chalk63.default.bold(applied ? "\nExternal-residue sweep" : "\nExternal residue (dry-run \u2014 nothing deleted)"));
|
|
14342
|
+
console.log(import_chalk63.default.bold("\n Upstash"));
|
|
14036
14343
|
const orphans = up.orphans ?? [];
|
|
14037
|
-
if (orphans.length === 0) console.log(
|
|
14038
|
-
for (const o of orphans) console.log(` ${
|
|
14039
|
-
console.log(
|
|
14344
|
+
if (orphans.length === 0) console.log(import_chalk63.default.green(" no orphaned keys"));
|
|
14345
|
+
for (const o of orphans) console.log(` ${import_chalk63.default.yellow(o.key)} ${import_chalk63.default.dim(`\u2014 ${o.reason}`)}`);
|
|
14346
|
+
console.log(import_chalk63.default.dim(` kept (live principals): ${up.kept ?? 0} \xB7 anon wallets (untouched): ${up.anon_wallets ?? 0}`));
|
|
14040
14347
|
if (up.anon_wallet_detail) {
|
|
14041
14348
|
const d = up.anon_wallet_detail;
|
|
14042
|
-
console.log(
|
|
14349
|
+
console.log(import_chalk63.default.dim(` anon wallets: ${d.count} ($${(d.total_cents / 100).toFixed(2)}), ${d.no_ttl} with NO TTL${d.no_ttl ? " \u26A0" : " (all self-expire)"}`));
|
|
14043
14350
|
}
|
|
14044
14351
|
if (up.keyspace_census) {
|
|
14045
14352
|
const census = Object.entries(up.keyspace_census).map(([k, v]) => `${k}=${v}`).join(" \xB7 ");
|
|
14046
|
-
console.log(
|
|
14353
|
+
console.log(import_chalk63.default.dim(` keyspace: ${census}`));
|
|
14047
14354
|
}
|
|
14048
|
-
if (up.unknown?.length) console.log(
|
|
14049
|
-
if (applied) console.log(` ${
|
|
14050
|
-
for (const e of up.errors ?? []) console.log(
|
|
14051
|
-
console.log(
|
|
14355
|
+
if (up.unknown?.length) console.log(import_chalk63.default.dim(` unknown (never deleted): ${up.unknown.join(", ")}`));
|
|
14356
|
+
if (applied) console.log(` ${import_chalk63.default.bold(String(up.deleted ?? 0))} key(s) deleted`);
|
|
14357
|
+
for (const e of up.errors ?? []) console.log(import_chalk63.default.red(` error: ${e}`));
|
|
14358
|
+
console.log(import_chalk63.default.bold("\n OpenFGA / Neon \u2014 orphan stores"));
|
|
14052
14359
|
if (applied) {
|
|
14053
14360
|
const swept = fga?.swept ?? [];
|
|
14054
|
-
if (swept.length === 0) console.log(
|
|
14361
|
+
if (swept.length === 0) console.log(import_chalk63.default.green(" no orphaned stores"));
|
|
14055
14362
|
for (const s of swept) {
|
|
14056
14363
|
console.log(
|
|
14057
|
-
` ${
|
|
14364
|
+
` ${import_chalk63.default.yellow(s.store_id)} ${import_chalk63.default.dim(`\u2014 store ${s.openfga_deleted ? "deleted" : "DEFERRED"}, ${s.neon_deleted} Neon tuple(s) purged`)}`
|
|
14058
14365
|
);
|
|
14059
14366
|
}
|
|
14060
|
-
if (fga?.remaining) console.log(
|
|
14367
|
+
if (fga?.remaining) console.log(import_chalk63.default.yellow(` ${fga.remaining} more orphan store(s) \u2014 re-run to drain`));
|
|
14061
14368
|
const st = fga?.side_tables;
|
|
14062
|
-
if (st) console.log(
|
|
14369
|
+
if (st) console.log(import_chalk63.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})` : ""}`));
|
|
14063
14370
|
} else {
|
|
14064
14371
|
const fgaOrphans = fga?.orphans ?? [];
|
|
14065
|
-
if (fgaOrphans.length === 0) console.log(
|
|
14372
|
+
if (fgaOrphans.length === 0) console.log(import_chalk63.default.green(" no orphaned stores"));
|
|
14066
14373
|
for (const s of fgaOrphans) {
|
|
14067
14374
|
const src = s.in_openfga ? "live in OpenFGA" : "Neon tuples only";
|
|
14068
|
-
console.log(` ${
|
|
14375
|
+
console.log(` ${import_chalk63.default.yellow(s.store_id)} ${import_chalk63.default.dim(`\u2014 ${src}${s.name ? ` (${s.name})` : ""}, ${s.neon_tuples} Neon tuple(s)`)}`);
|
|
14069
14376
|
}
|
|
14070
|
-
console.log(
|
|
14377
|
+
console.log(import_chalk63.default.dim(` kept stores: ${(fga?.kept_store_ids ?? []).length}`));
|
|
14071
14378
|
const st = fga?.side_tables;
|
|
14072
|
-
if (st) console.log(
|
|
14379
|
+
if (st) console.log(import_chalk63.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`));
|
|
14073
14380
|
}
|
|
14074
|
-
for (const e of fga?.errors ?? []) console.log(
|
|
14075
|
-
console.log(
|
|
14381
|
+
for (const e of fga?.errors ?? []) console.log(import_chalk63.default.red(` error: ${e}`));
|
|
14382
|
+
console.log(import_chalk63.default.bold("\n OpenFGA \u2014 ghost tuples in surviving stores"));
|
|
14076
14383
|
if (applied) {
|
|
14077
|
-
if ((ghosts?.ghost_count ?? 0) === 0) console.log(
|
|
14078
|
-
else console.log(` ${
|
|
14384
|
+
if ((ghosts?.ghost_count ?? 0) === 0) console.log(import_chalk63.default.green(" no ghost tuples"));
|
|
14385
|
+
else console.log(` ${import_chalk63.default.bold(String(ghosts.deleted ?? 0))} ghost tuple(s) deleted ${import_chalk63.default.dim(`(of ${ghosts.ghost_count} found, ${ghosts.scanned_tuples} scanned across ${ghosts.live_stores} live stores)`)}`);
|
|
14079
14386
|
} else {
|
|
14080
14387
|
const n = ghosts?.ghost_count ?? 0;
|
|
14081
|
-
if (n === 0) console.log(
|
|
14388
|
+
if (n === 0) console.log(import_chalk63.default.green(` no ghost tuples ${import_chalk63.default.dim(`(${ghosts.scanned_tuples ?? 0} scanned across ${ghosts.live_stores ?? 0} live stores)`)}`));
|
|
14082
14389
|
else {
|
|
14083
|
-
console.log(
|
|
14390
|
+
console.log(import_chalk63.default.yellow(` ${n} ghost tuple(s) referencing entities absent from D1:`));
|
|
14084
14391
|
for (const g of (ghosts.ghosts ?? []).slice(0, 20)) {
|
|
14085
|
-
console.log(
|
|
14392
|
+
console.log(import_chalk63.default.dim(` ${g.object_type}:${g.object_id} ${g.relation} ${g._user}`));
|
|
14086
14393
|
}
|
|
14087
|
-
if (n > 20) console.log(
|
|
14394
|
+
if (n > 20) console.log(import_chalk63.default.dim(` \u2026 and ${n - 20} more`));
|
|
14088
14395
|
}
|
|
14089
14396
|
}
|
|
14090
|
-
for (const e of ghosts?.errors ?? []) console.log(
|
|
14397
|
+
for (const e of ghosts?.errors ?? []) console.log(import_chalk63.default.red(` error: ${e}`));
|
|
14091
14398
|
console.log();
|
|
14092
14399
|
}
|
|
14093
14400
|
async function runOp(sub, opts = {}, view) {
|
|
14094
14401
|
if (!loadCredentials()) {
|
|
14095
|
-
console.log(
|
|
14402
|
+
console.log(import_chalk63.default.dim("Not logged in. Run `apiblaze login`."));
|
|
14096
14403
|
return;
|
|
14097
14404
|
}
|
|
14098
14405
|
if (!isOperatorLogin()) {
|
|
14099
|
-
console.log(
|
|
14406
|
+
console.log(import_chalk63.default.dim("`apiblaze op` is only available to platform operators."));
|
|
14100
14407
|
return;
|
|
14101
14408
|
}
|
|
14102
14409
|
switch (sub) {
|
|
@@ -14152,17 +14459,17 @@ async function runOp(sub, opts = {}, view) {
|
|
|
14152
14459
|
const nSide = (st.soft_deleted_stores ?? 0) + (st.orphan_models ?? 0) + (st.orphan_changelog ?? 0);
|
|
14153
14460
|
printResidue(report, false);
|
|
14154
14461
|
if (nUp + nFga + nGhost + nSide === 0) {
|
|
14155
|
-
console.log(
|
|
14462
|
+
console.log(import_chalk63.default.green("Nothing to sweep."));
|
|
14156
14463
|
return;
|
|
14157
14464
|
}
|
|
14158
14465
|
if (!opts.yes) {
|
|
14159
14466
|
const readline3 = await import("readline/promises");
|
|
14160
14467
|
const rl = readline3.createInterface({ input: process.stdin, output: process.stdout });
|
|
14161
14468
|
const answer = await rl.question(
|
|
14162
|
-
|
|
14469
|
+
import_chalk63.default.red(`Delete ${nUp} Upstash key(s) + ${nFga} OpenFGA store(s) + ${nGhost} ghost tuple(s) + ${nSide} Neon side-table row(s)? Type 'sweep' to confirm: `)
|
|
14163
14470
|
);
|
|
14164
14471
|
rl.close();
|
|
14165
|
-
if (answer.trim() !== "sweep") return void console.log(
|
|
14472
|
+
if (answer.trim() !== "sweep") return void console.log(import_chalk63.default.dim("Aborted."));
|
|
14166
14473
|
}
|
|
14167
14474
|
const result = await opCall({ method: "POST", path: "/operator/external-residue/sweep", summary: "external residue sweep" });
|
|
14168
14475
|
if (opts.json) return void console.log(JSON.stringify(result, null, 2));
|
|
@@ -14172,25 +14479,25 @@ async function runOp(sub, opts = {}, view) {
|
|
|
14172
14479
|
case "mark": {
|
|
14173
14480
|
const label3 = (view ?? "").trim();
|
|
14174
14481
|
if (!label3) {
|
|
14175
|
-
console.log(
|
|
14482
|
+
console.log(import_chalk63.default.red("Give the change a name:") + import_chalk63.default.cyan(' apiblaze op mark "cached tenant count"'));
|
|
14176
14483
|
return;
|
|
14177
14484
|
}
|
|
14178
14485
|
const res = await opCall({ method: "POST", path: "/operator/latency/mark", body: { label: label3 }, summary: "record change marker" });
|
|
14179
14486
|
const ts = new Date(res?.marker?.ts ?? Date.now()).toISOString();
|
|
14180
|
-
console.log(
|
|
14181
|
-
Marked: `) +
|
|
14182
|
-
console.log(
|
|
14183
|
-
console.log(
|
|
14487
|
+
console.log(import_chalk63.default.green(`
|
|
14488
|
+
Marked: `) + import_chalk63.default.bold(label3));
|
|
14489
|
+
console.log(import_chalk63.default.dim(` ${ts}`));
|
|
14490
|
+
console.log(import_chalk63.default.dim(` Once traffic has run on both sides, compare with: `) + import_chalk63.default.cyan("apiblaze op latency compare") + "\n");
|
|
14184
14491
|
return;
|
|
14185
14492
|
}
|
|
14186
14493
|
case "credits": {
|
|
14187
14494
|
const data = await opCall({ method: "GET", path: "/operator/credits", summary: "list credit wallets" });
|
|
14188
14495
|
if (opts.json) return void console.log(JSON.stringify(data, null, 2));
|
|
14189
14496
|
const accounts = data?.accounts ?? [];
|
|
14190
|
-
if (accounts.length === 0) return void console.log(
|
|
14497
|
+
if (accounts.length === 0) return void console.log(import_chalk63.default.dim("No credit wallets."));
|
|
14191
14498
|
for (const a of accounts) {
|
|
14192
14499
|
const bal = typeof a.balance_cents === "number" ? `$${(a.balance_cents / 100).toFixed(2)}` : "?";
|
|
14193
|
-
console.log(` ${
|
|
14500
|
+
console.log(` ${import_chalk63.default.bold(bal.padStart(9))} ${a.walletId}${a.owner_email ? import_chalk63.default.dim(` \u2014 ${a.owner_email}`) : a.anon ? import_chalk63.default.dim(" \u2014 anon") : ""}`);
|
|
14194
14501
|
}
|
|
14195
14502
|
return;
|
|
14196
14503
|
}
|
|
@@ -14201,7 +14508,7 @@ async function runOp(sub, opts = {}, view) {
|
|
|
14201
14508
|
case "billing": {
|
|
14202
14509
|
const which = (view ?? "").trim().toLowerCase();
|
|
14203
14510
|
if (which && which !== "doors" && which !== "meter") {
|
|
14204
|
-
return void console.log(
|
|
14511
|
+
return void console.log(import_chalk63.default.red(`Unknown: apiblaze op billing ${which}. Use 'doors', 'meter', or neither for both.`));
|
|
14205
14512
|
}
|
|
14206
14513
|
const checks = [];
|
|
14207
14514
|
let doorsData = null;
|
|
@@ -14270,29 +14577,29 @@ async function runOp(sub, opts = {}, view) {
|
|
|
14270
14577
|
const data = await opCall({ method: "GET", path: `/operator/latency/grades${q}`, summary: "latency grades" });
|
|
14271
14578
|
if (opts.json) return void console.log(JSON.stringify(data, null, 2));
|
|
14272
14579
|
const t = data.thresholds_ms;
|
|
14273
|
-
console.log(
|
|
14274
|
-
console.log(
|
|
14580
|
+
console.log(import_chalk63.default.bold("\nHow good was apiblaze itself?") + import_chalk63.default.dim(" (our overhead only \u2014 a slow customer API never counts against us)"));
|
|
14581
|
+
console.log(import_chalk63.default.dim(` excellent <${t.excellent.replace("<", "")}ms \xB7 okay ${t.okay}ms \xB7 bad ${t.bad}ms \xB7 terrible ${t.terrible.replace(">=", "")}ms+
|
|
14275
14582
|
`));
|
|
14276
|
-
console.log(
|
|
14583
|
+
console.log(import_chalk63.default.dim(" date reqs excellent okay bad terrible"));
|
|
14277
14584
|
for (const d of data.days ?? []) {
|
|
14278
14585
|
const p = d.pct;
|
|
14279
|
-
const cell = (v, colour) => v > 0 ? colour(`${String(v).padStart(5)}%`) :
|
|
14586
|
+
const cell = (v, colour) => v > 0 ? colour(`${String(v).padStart(5)}%`) : import_chalk63.default.dim(`${String(v).padStart(5)}%`);
|
|
14280
14587
|
console.log(
|
|
14281
|
-
` ${d.date} ${String(d.total).padStart(5)} ${cell(p.excellent,
|
|
14588
|
+
` ${d.date} ${String(d.total).padStart(5)} ${cell(p.excellent, import_chalk63.default.green)} ${cell(p.okay, import_chalk63.default.cyan)} ${cell(p.bad, import_chalk63.default.yellow)} ${cell(p.terrible, import_chalk63.default.red)}`
|
|
14282
14589
|
);
|
|
14283
14590
|
}
|
|
14284
14591
|
const cul = data.culprits ?? [];
|
|
14285
14592
|
if (cul.length) {
|
|
14286
|
-
console.log(
|
|
14287
|
-
console.log(
|
|
14593
|
+
console.log(import_chalk63.default.bold("\n Who caused the bad and terrible ones\n"));
|
|
14594
|
+
console.log(import_chalk63.default.dim(" bad terrible feature \u2192 dependency"));
|
|
14288
14595
|
for (const r of cul.slice(0, 12)) {
|
|
14289
14596
|
if (!r.bad && !r.terrible) continue;
|
|
14290
14597
|
console.log(
|
|
14291
|
-
` ${String(r.bad).padStart(6)} ${
|
|
14598
|
+
` ${String(r.bad).padStart(6)} ${import_chalk63.default.red(String(r.terrible).padStart(8))} ${import_chalk63.default.yellow(r.feature)} ${import_chalk63.default.dim("\u2192")} ${import_chalk63.default.cyan(r.dep)}`
|
|
14292
14599
|
);
|
|
14293
14600
|
}
|
|
14294
14601
|
}
|
|
14295
|
-
if (data.caveat) console.log(
|
|
14602
|
+
if (data.caveat) console.log(import_chalk63.default.dim(`
|
|
14296
14603
|
\u26A0 ${data.caveat}
|
|
14297
14604
|
`));
|
|
14298
14605
|
return;
|
|
@@ -14301,24 +14608,24 @@ async function runOp(sub, opts = {}, view) {
|
|
|
14301
14608
|
const data = await opCall({ method: "GET", path: `/operator/latency/compare${q}`, summary: "latency before/after" });
|
|
14302
14609
|
if (opts.json) return void console.log(JSON.stringify(data, null, 2));
|
|
14303
14610
|
const b = data.before, a = data.after, d = data.delta;
|
|
14304
|
-
console.log(
|
|
14305
|
-
Before vs after: `) +
|
|
14306
|
-
console.log(
|
|
14611
|
+
console.log(import_chalk63.default.bold(`
|
|
14612
|
+
Before vs after: `) + import_chalk63.default.cyan(data.marker.label));
|
|
14613
|
+
console.log(import_chalk63.default.dim(` marked ${new Date(data.marker.ts).toISOString()} \xB7 ${data.window_hours}h either side
|
|
14307
14614
|
`));
|
|
14308
14615
|
const row = (name, before, after, delta) => {
|
|
14309
14616
|
const arrow = delta === 0 ? "=" : delta < 0 ? "\u2193" : "\u2191";
|
|
14310
14617
|
const txt = `${String(before).padStart(6)}ms \u2192${String(after).padStart(7)}ms ${arrow}${Math.abs(delta)}ms`;
|
|
14311
|
-
console.log(` ${name.padEnd(22)}${data.trustworthy ? delta <= 0 ?
|
|
14618
|
+
console.log(` ${name.padEnd(22)}${data.trustworthy ? delta <= 0 ? import_chalk63.default.green(txt) : import_chalk63.default.red(txt) : import_chalk63.default.dim(txt)}`);
|
|
14312
14619
|
};
|
|
14313
|
-
console.log(
|
|
14620
|
+
console.log(import_chalk63.default.dim(" metric before after change"));
|
|
14314
14621
|
row("total p50", b.total_p50, a.total_p50, d.total_p50);
|
|
14315
14622
|
row("total p95", b.total_p95, a.total_p95, d.total_p95);
|
|
14316
14623
|
row("apiblaze overhead p50", b.gw_p50, a.gw_p50, d.gw_p50);
|
|
14317
14624
|
row("apiblaze overhead p95", b.gw_p95, a.gw_p95, d.gw_p95);
|
|
14318
|
-
console.log(
|
|
14625
|
+
console.log(import_chalk63.default.dim(`
|
|
14319
14626
|
requests: ${b.requests} before \xB7 ${a.requests} after`));
|
|
14320
14627
|
for (const w of data.warnings ?? []) {
|
|
14321
|
-
console.log((data.trustworthy ?
|
|
14628
|
+
console.log((data.trustworthy ? import_chalk63.default.dim : import_chalk63.default.yellow)(` ${data.trustworthy ? "\xB7" : "\u26A0"} ${w}`));
|
|
14322
14629
|
}
|
|
14323
14630
|
console.log("");
|
|
14324
14631
|
return;
|
|
@@ -14327,19 +14634,19 @@ Before vs after: `) + import_chalk62.default.cyan(data.marker.label));
|
|
|
14327
14634
|
const data = await opCall({ method: "GET", path: `/operator/latency/slow${q}`, summary: "slowest requests" });
|
|
14328
14635
|
if (opts.json) return void console.log(JSON.stringify(data, null, 2));
|
|
14329
14636
|
const rows2 = data?.rows ?? [];
|
|
14330
|
-
if (!rows2.length) return void console.log(
|
|
14331
|
-
console.log(
|
|
14637
|
+
if (!rows2.length) return void console.log(import_chalk63.default.dim("No requests over the threshold in that window."));
|
|
14638
|
+
console.log(import_chalk63.default.bold(`
|
|
14332
14639
|
Slowest requests \u2014 last ${data.window_hours}h, over ${data.min_ms}ms
|
|
14333
14640
|
`));
|
|
14334
|
-
console.log(
|
|
14641
|
+
console.log(import_chalk63.default.dim(" total ours theirs blame request id"));
|
|
14335
14642
|
for (const r of rows2.slice(0, 30)) {
|
|
14336
14643
|
console.log(
|
|
14337
|
-
` ${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)} ${
|
|
14644
|
+
` ${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_chalk63.default.yellow(`${r.slow_gw_feature || "-"}\u2192${r.slow_dep || "-"}`.padEnd(20))} ${import_chalk63.default.dim(r.request_id || "")}`
|
|
14338
14645
|
);
|
|
14339
14646
|
}
|
|
14340
|
-
console.log(
|
|
14647
|
+
console.log(import_chalk63.default.dim(`
|
|
14341
14648
|
The last column is the request id (Cloudflare calls it a "cf-ray"). Look one up with`));
|
|
14342
|
-
console.log(
|
|
14649
|
+
console.log(import_chalk63.default.dim(` \`apiblaze logs\` for that request's exact per-feature breakdown \u2014 unsampled, unlike the table above.
|
|
14343
14650
|
`));
|
|
14344
14651
|
return;
|
|
14345
14652
|
}
|
|
@@ -14347,18 +14654,18 @@ Slowest requests \u2014 last ${data.window_hours}h, over ${data.min_ms}ms
|
|
|
14347
14654
|
const data = await opCall({ method: "GET", path: `/operator/latency/llm${q}`, summary: "llm latency" });
|
|
14348
14655
|
if (opts.json) return void console.log(JSON.stringify(data, null, 2));
|
|
14349
14656
|
const rows2 = data?.rows ?? [];
|
|
14350
|
-
if (!rows2.length) return void console.log(
|
|
14351
|
-
console.log(
|
|
14657
|
+
if (!rows2.length) return void console.log(import_chalk63.default.dim("No LLM traffic in that window."));
|
|
14658
|
+
console.log(import_chalk63.default.bold(`
|
|
14352
14659
|
LLM timing \u2014 last ${data.window_hours}h
|
|
14353
14660
|
`));
|
|
14354
|
-
console.log(
|
|
14661
|
+
console.log(import_chalk63.default.dim(" turns turn p95 ttfc p95 gen p95 reserve p95 in/out tokens p95 model"));
|
|
14355
14662
|
for (const r of rows2) {
|
|
14356
14663
|
const n = r.turns ?? r.requests ?? 0;
|
|
14357
14664
|
console.log(
|
|
14358
14665
|
` ${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 || "-"}`
|
|
14359
14666
|
);
|
|
14360
14667
|
}
|
|
14361
|
-
console.log(
|
|
14668
|
+
console.log(import_chalk63.default.dim(`
|
|
14362
14669
|
${data.note}
|
|
14363
14670
|
`));
|
|
14364
14671
|
return;
|
|
@@ -14369,26 +14676,26 @@ LLM timing \u2014 last ${data.window_hours}h
|
|
|
14369
14676
|
]);
|
|
14370
14677
|
if (opts.json) return void console.log(JSON.stringify({ blame, summary }, null, 2));
|
|
14371
14678
|
const ov = summary?.apiblaze_overhead_ms ?? {};
|
|
14372
|
-
console.log(
|
|
14679
|
+
console.log(import_chalk63.default.bold(`
|
|
14373
14680
|
Latency \u2014 last ${summary?.window_hours ?? "?"}h, ${Number(summary?.requests ?? 0).toLocaleString()} requests
|
|
14374
14681
|
`));
|
|
14375
|
-
console.log(` ${
|
|
14376
|
-
console.log(` ${
|
|
14377
|
-
console.log(
|
|
14682
|
+
console.log(` ${import_chalk63.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_chalk63.default.dim("\u2190 ours")}`);
|
|
14683
|
+
console.log(` ${import_chalk63.default.bold("upstream ttfb ")} ${" ".repeat(24)}p95 ${String(summary?.upstream_ttfb_p95_ms ?? 0).padStart(6)}ms ${import_chalk63.default.dim("\u2190 theirs")}`);
|
|
14684
|
+
console.log(import_chalk63.default.dim(` (per-request percentiles \u2014 never subtract one from the other)
|
|
14378
14685
|
`));
|
|
14379
14686
|
const rows = blame?.blame ?? [];
|
|
14380
|
-
if (!rows.length) return void console.log(
|
|
14381
|
-
console.log(
|
|
14382
|
-
console.log(
|
|
14687
|
+
if (!rows.length) return void console.log(import_chalk63.default.dim("No latency rows in that window."));
|
|
14688
|
+
console.log(import_chalk63.default.bold(" Which feature ate the time, and what inside it\n"));
|
|
14689
|
+
console.log(import_chalk63.default.dim(" share p95 feature \u2192 dependency"));
|
|
14383
14690
|
for (const r of rows.slice(0, 15)) {
|
|
14384
14691
|
const share = `${(r.share * 100).toFixed(1)}%`;
|
|
14385
|
-
console.log(` ${share.padStart(6)} ${String(r.p95_ms).padStart(6)}ms ${
|
|
14692
|
+
console.log(` ${share.padStart(6)} ${String(r.p95_ms).padStart(6)}ms ${import_chalk63.default.yellow(r.feature)} ${import_chalk63.default.dim("\u2192")} ${import_chalk63.default.cyan(r.dep)}`);
|
|
14386
14693
|
}
|
|
14387
14694
|
console.log("");
|
|
14388
14695
|
return;
|
|
14389
14696
|
}
|
|
14390
14697
|
default:
|
|
14391
|
-
console.log(
|
|
14698
|
+
console.log(import_chalk63.default.red(`Unknown op subcommand '${sub}'. Run \`apiblaze op\` for the menu.`));
|
|
14392
14699
|
}
|
|
14393
14700
|
}
|
|
14394
14701
|
|
|
@@ -14439,8 +14746,8 @@ agent.command("openapi").description("Chat to build your API spec from real traf
|
|
|
14439
14746
|
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)));
|
|
14440
14747
|
program.command("apichat [project]").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) => {
|
|
14441
14748
|
const looksLikeSource = !!project && (/^https?:\/\//i.test(project) || /\.(ya?ml|json)$/i.test(project) || import_fs5.default.existsSync(project));
|
|
14442
|
-
const
|
|
14443
|
-
return runApichat({ ...opts, target, project: looksLikeSource ? void 0 : project, openapispec: opts.openapispec ?? opts.openapi });
|
|
14749
|
+
const target2 = opts.target ?? (looksLikeSource ? project : void 0);
|
|
14750
|
+
return runApichat({ ...opts, target: target2, project: looksLikeSource ? void 0 : project, openapispec: opts.openapispec ?? opts.openapi });
|
|
14444
14751
|
}));
|
|
14445
14752
|
var llm = program.command("llm").description("Manage a local LLM provider key for chat (optional \u2014 lifts model quality, bills your key)");
|
|
14446
14753
|
llm.command("set-key").description("Store an LLM provider key locally (OpenRouter/Anthropic/DeepSeek/OpenAI)").argument("[key]", "The API key (omit to enter it hidden at a prompt)").option("--model <id>", "Model id to use with this key (e.g. anthropic/claude-haiku-4.5)").action(action((key, opts) => runLlmSetKey(key, opts)));
|
|
@@ -14458,7 +14765,7 @@ program.command("dev").description(`Put the code running on this machine behind
|
|
|
14458
14765
|
try {
|
|
14459
14766
|
const resolved = parseInt(port ?? opts.port, 10);
|
|
14460
14767
|
if (Number.isNaN(resolved)) {
|
|
14461
|
-
console.error(
|
|
14768
|
+
console.error(import_chalk64.default.red(`Invalid port: ${port ?? opts.port}`));
|
|
14462
14769
|
process.exit(1);
|
|
14463
14770
|
}
|
|
14464
14771
|
await runDev({ ...opts, port: resolved });
|
|
@@ -14521,7 +14828,19 @@ var logs = program.command("logs").description("API Producer view \u2014 stream
|
|
|
14521
14828
|
logs.command("list").description("API Producer view \u2014 browse EVERY caller's past requests by day, then open one to see its 4 views").argument("<project>", "Project name or id").argument("[version]", "API version (defaults to the first match)").option("--tenant <slug>", "Tenant to read (auto when one is attached; prompted otherwise)").option("--errors-only", "Only show 4xx/5xx responses").option("--json", "Emit ALL matching rows as NDJSON, newest first (non-interactive)").option("--plain", "Print day-grouped rows instead of prompting (automatic when piped)").option("--team <id|name>", "Team the project is in (defaults to active team)").action(action((project, version2, _o, cmd) => runLogsList(project, version2, cmd.optsWithGlobals())));
|
|
14522
14829
|
program.command("config").description("Browse and change every proxy setting & feature (interactive; git-config-style get/set)").argument("[project]", "Project name or id (omit to pick interactively)").argument("[key]", "Setting key, e.g. throttling.proxyQuota (omit for the menu)").argument("[value]", "New value (omit to read the current value)").option("--list", "Print every setting and its current value").option("--team <id|name>", "Team the project is in").option("--apiversion <version>", "API version (defaults to the first match)").option("--json", "Machine-readable output (with --list or a key read)").action(action((project, key, value, opts) => runConfig(project, key, value, opts)));
|
|
14523
14830
|
program.command("target").description("Change where a proxy forwards requests").argument("<project>", "Project name or id").requiredOption("--url <url>", "Target URL to forward to").option("--env <env>", "Environment to scope the target to (e.g. prod, dev)").option("--team <id|name>", "Team the project is in").option("--apiversion <version>", "API version (defaults to the first match)").option("--json", "Output machine-readable JSON").action(action((project, opts) => runTargetSet(project, opts)));
|
|
14524
|
-
program.command("throttle").description("Set rate limits and quotas for a proxy").argument("<project>", "Project name or id").option("--rate <n>", "
|
|
14831
|
+
program.command("throttle").description("Set rate limits and quotas for a proxy (works in an anonymous workspace too; new proxies start at 10/s per person and 10,000/day)").argument("<project>", "Project name or id").option("--rate <n>", "Requests/sec per person (or per key when a call names no person). Anonymous workspaces: at most 10").option("--end-user-rate <n>", "Same as --rate (kept for older scripts)").option("--quota <n>", "Proxy quota (requests/period)").option("--period <p>", "Quota period: daily | weekly | monthly").option("--team <id|name>", "Team the project is in").option("--apiversion <version>", "API version").option("--json", "Output machine-readable JSON").action(action((project, opts) => runThrottleSet(project, opts)));
|
|
14832
|
+
var transform = program.command("transform").description("Change requests on their way to your backend \u2014 e.g. send it a secret header so it only answers to the proxy");
|
|
14833
|
+
transform.command("list").description("List a proxy's transforms").argument("<project>", "Project name or id").option("--team <id|name>", "Team the project is in").option("--apiversion <version>", "API version").option("--json", "Output machine-readable JSON").action(action((project, opts) => runTransformList(project, opts)));
|
|
14834
|
+
transform.command("set-header").description("Send a header with a fixed value to the upstream on every call (replaces the value if already set)").argument("<project>", "Project name or id").argument("<header>", "Header name, e.g. x-target-api-key").option("--value <value>", "The header value").option("--value-env <VAR>", "Read the value from this environment variable (keeps secrets out of shell history)").option("--secret", "Store the value encrypted and never show it again").option("--team <id|name>", "Team the project is in").option("--apiversion <version>", "API version").option("--json", "Output machine-readable JSON").addHelpText("after", `
|
|
14835
|
+
Example \u2014 lock your backend to the proxy:
|
|
14836
|
+
export TARGET_SERVER_SECRET=$(openssl rand -hex 24) # also give it to your backend
|
|
14837
|
+
apiblaze transform set-header shop x-target-api-key --value-env TARGET_SERVER_SECRET --secret`).action(action((project, header, opts) => runTransformSetHeader(project, header, opts)));
|
|
14838
|
+
transform.command("mapping").description("Create or replace a lookup table for map transforms, e.g. country code \u2192 currency").argument("<project>", "Project name or id").argument("<table>", "Table name, e.g. currencies").argument("<pairs...>", "from=to pairs, e.g. FR=EUR US=USD GB=GBP").option("--secret", "Store the values encrypted and never show them again").option("--team <id|name>", "Team the project is in").option("--apiversion <version>", "API version").option("--json", "Output machine-readable JSON").action(action((project, table, pairs, opts) => runTransformMapping(project, table, pairs, opts)));
|
|
14839
|
+
transform.command("map").description("Rewrite a value through a mapping table on its way to the backend, e.g. header:X-Country \u2192 header:X-Currency").argument("<project>", "Project name or id").argument("<source>", "Where to read: header:X-Country \xB7 query:country \xB7 bodyvar:order.country").argument("<destination>", "Where to write: header:X-Currency \xB7 query:currency \xB7 bodyvar:order.currency").requiredOption("--table <name>", "The mapping table to look the value up in").option("--on-no-match <mode>", "skip (default) \xB7 fail \xB7 passthrough").option("--team <id|name>", "Team the project is in").option("--apiversion <version>", "API version").option("--json", "Output machine-readable JSON").addHelpText("after", `
|
|
14840
|
+
Example \u2014 the backend wants a currency, callers send a country:
|
|
14841
|
+
apiblaze transform mapping shop currencies FR=EUR US=USD GB=GBP
|
|
14842
|
+
apiblaze transform map shop header:X-Country header:X-Currency --table currencies`).action(action((project, source, destination, opts) => runTransformMap(project, source, destination, opts)));
|
|
14843
|
+
transform.command("remove").description("Remove a transform by name, id, or header name").argument("<project>", "Project name or id").argument("<rule>", "Rule name, id, or the header a set-header rule sets").option("--team <id|name>", "Team the project is in").option("--apiversion <version>", "API version").option("--json", "Output machine-readable JSON").action(action((project, rule, opts) => runTransformRemove(project, rule, opts)));
|
|
14525
14844
|
program.command("rename").description("Change a proxy's display name").argument("<project>", "Project name or id").requiredOption("--display-name <name>", "New human-friendly display name").option("--team <id|name>", "Team the project is in").option("--apiversion <version>", "API version").option("--json", "Output machine-readable JSON").action(action((project, opts) => runRename(project, opts)));
|
|
14526
14845
|
var domain = program.command("domain").description("Use your own domain for your API, MCP server, developer portal or login pages (billed monthly)");
|
|
14527
14846
|
domain.command("add").description("Add your own domain (shows the DNS records to set)").argument("[project]", "Project name or id (for api / mcp domains; omit with --tenant for portal / auth)").requiredOption("--domain <host>", "Custom hostname to add, e.g. api.example.com").option("--kind <kind>", "api (default) | mcp \u2014 on a project; portal | auth \u2014 on a --tenant").option("--tenant <slug>", "portal/auth: the tenant the domain belongs to. api/mcp: pin the domain to one of the project's tenants").option("--team <id|name>", "Team the project is in").option("--apiversion <version>", "API version").option("--json", "Output machine-readable JSON").addHelpText("after", `
|
|
@@ -14562,11 +14881,11 @@ admins.command("remove").description("Remove a tenant admin").argument("<email>"
|
|
|
14562
14881
|
program.addCommand(admins);
|
|
14563
14882
|
program.command("iam").description("Turn users & groups on/off for a proxy's tenant (identified calls get their user's groups applied)").argument("<project>", "Project name or id").argument("<state>", "on | off").option("--tenant <name>", "Tenant to toggle (defaults to the project's tenant)").option("--team <id|name>", "Team (defaults to active team, or your anonymous workspace)").option("--apiversion <version>", "API version (defaults to the first)").option("--json", "Output machine-readable JSON").action(action((project, state, opts) => runIamToggle(project, state, opts)));
|
|
14564
14883
|
program.command("identified").description("Require calls to identify their end user (X-End-User-Id from a backend key, or a login token \u2014 a client key alone cannot) \u2014 or allow unattributed calls again").argument("<project>", "Project name or id").argument("<mode>", "require | allow-anon").option("--team <id|name>", "Team (defaults to active team, or your anonymous workspace)").option("--apiversion <version>", "API version (defaults to the first)").option("--json", "Output machine-readable JSON").action(action((project, mode, opts) => runIdentifiedToggle(project, mode, opts)));
|
|
14565
|
-
var apikeys = new import_commander.Command("apikeys").description("
|
|
14884
|
+
var apikeys = new import_commander.Command("apikeys").description("API keys \u2014 for your team (control plane), or with `mint --tenant` for your API's callers: a backend key, a client key (--client) or a key bound to one person (--for)").option("--team <id|name>", "Team (defaults to active team)").option("--json", "Output machine-readable JSON").action(action((opts) => runApikeysMenu(opts)));
|
|
14566
14885
|
apikeys.command("list").description("List control-plane API keys in your team").option("--team <id|name>", "Team (defaults to active team)").option("--json", "Output machine-readable JSON").action(action((opts, cmd) => runKeyList({ ...cmd.parent?.opts(), ...opts })));
|
|
14567
|
-
apikeys.command("mint").description("Create a control-plane API key (secret shown once) \u2014 or, with --tenant, a consumer key in that tenant: a backend key (default), a client key (--client) or a person-bound key (--for)").option("--desc <text>", "Description").option("--expires-days <n>", "Expiry in days (default 90 server-side)").option("--tenant <slug>", "Mint a CONSUMER key in this tenant instead of a control-plane key").option("--backend", `Backend key (default). ${HOLDER_HELP.backend}`).option("--client", `Client key. ${HOLDER_HELP.client} Needs --tenant.`).option("--for <email|id>", "Issue this key to a named person \u2014 calls made with it count as that person, and ownership rules trust it. The Users & Groups widget's admin lane accepts only such a key, one per admin. Needs --tenant.").option("--env <name>", "Environment the consumer key is valid for (with --tenant; default dev)").option("--team <id|name>", "Team (defaults to active team)").option("--json", "Output machine-readable JSON").action(action((opts, cmd) => runKeyMint({ ...cmd.parent?.opts(), ...opts })));
|
|
14886
|
+
apikeys.command("mint").description("Create a control-plane API key (secret shown once) \u2014 or, with --tenant, a consumer key in that tenant: a backend key (default), a client key (--client) or a person-bound key (--for)").option("--desc <text>", "Description").option("--expires-days <n>", "Expiry in days (default 90 server-side)").option("--tenant <slug>", "Mint a CONSUMER key in this tenant instead of a control-plane key").option("--backend", `Backend key (default). ${HOLDER_HELP.backend}`).option("--client", `Client key. ${HOLDER_HELP.client} Needs --tenant.`).option("--for <email|id>", "Issue this key to a named person \u2014 calls made with it count as that person, and ownership rules trust it. The Users & Groups widget's admin lane accepts only such a key, one per admin. Needs --tenant.").option("--admin", "With --for: the key a tenant admin uses in the Users & Groups widget (adds manage-access; env defaults to prod)").option("--env <name>", "Environment the consumer key is valid for (with --tenant; default dev, prod with --admin)").option("--team <id|name>", "Team (defaults to active team)").option("--json", "Output machine-readable JSON").action(action((opts, cmd) => runKeyMint({ ...cmd.parent?.opts(), ...opts })));
|
|
14568
14887
|
apikeys.command("revoke").description("Revoke a control-plane API key").argument("<keyId>", "Key id (see `apikeys list`)").option("--team <id|name>", "Team (defaults to active team)").action(action((keyId, opts, cmd) => runKeyRevoke(keyId, { ...cmd.parent?.opts(), ...opts })));
|
|
14569
|
-
program.addCommand(apikeys
|
|
14888
|
+
program.addCommand(apikeys);
|
|
14570
14889
|
var op = new import_commander.Command("op").description("Operator menu (platform operators only)").argument("[action]", "latency | billing | mark | credits | residue | sweep | test | perf (omit for the menu)").argument("[view]", "for `latency`: slow | llm | compare | grades \xB7 for `billing`: doors | meter \xB7 for `mark`: the change name, quoted").option("-y, --yes", "Skip the sweep confirmation prompt").option("--json", "Output machine-readable JSON").option("--hours <n>", "Look-back window in hours (default 1)").option("--team <id>", "Filter to one team").option("--only <ids>", "for `test`: comma-separated spec/gut ids or prefixes").option("--group <name>", "for `test`: only specs whose group matches").option("--skip-guts", "for `test`: contract specs only").option("--guts-only", "for `test`: server-side gut checks only").option("--fail-fast", "for `test`: stop at the first failure").option("--timeout <s>", "for `test`: seconds to wait per check (default 150)").option("--perf", "for `test`: run the performance gate instead of the suite (fixed-rate load on a sandbox proxy)").option("--consumer", "for `test`: probe the data plane with the stored `consumer login` token instead of the suite").option("--path <p>", "for `test --consumer`: request path on the proxy (default /get)").option("--proxy <name@version>", "for `test --consumer`: the OAuth-door proxy to knock on (default: the sandbox proxy for a sandbox-tenant session)").option("--rps <n>", "for `test --perf`: requests per second (default 100, max 300)").option("--seconds <n>", "for `test --perf`: how long to send for (default 60, max 300)").option("--url <url>", "for `test --perf`: load this fixture URL instead of provisioning one (with --key; nothing is torn down)").option("--max-p50 <ms>", "for `test --perf`: client p50 threshold (default 250)").option("--max-p95 <ms>", "for `test --perf`: client p95 threshold (default 800)").option("--max-error-rate <pct>", "for `test --perf`: error-rate threshold in percent (default 0.5)").option("--max-gateway-p50 <ms>", "for `test --perf`: apiblaze-overhead p50 threshold (default 60)").option("--max-gateway-p95 <ms>", "for `test --perf`: apiblaze-overhead p95 threshold (default 250)").option("--project <name>", "Filter to one proxy").option("--api-version <v>", "Filter to one API version").option("--tenant <name>", "Filter to one tenant").option("--route <path>", "Filter to one route").option("--environment <env>", "Filter to one environment").option("--status-class <c>", "Filter to a status class (2xx/4xx/5xx)").option("--min-ms <n>", "For `latency slow`: minimum duration (default 1000)").option("--days <n>", "For `latency grades`: how many days back (default 7)").option("--excellent <ms>", "Grade threshold: under this is excellent (default 250)").option("--okay <ms>", "Grade threshold: under this is okay (default 500)").option("--bad <ms>", "Grade threshold: under this is bad, at/over is terrible (default 1000)").option("--key <key>", "For `billing meter`: a data-plane API key for the sandbox proxy (without it the served-request checks are SKIPPED) \xB7 for `test --perf --url`: the fixture's API key").option("--count <n>", "For `billing meter`: requests per check (default 3, max 10)").option("--mcp-host <host>", "For `billing meter`: an MCP host to check tools/call is charged once").action(action((sub, view, opts) => runOp(sub, opts, view))).addHelpText("after", () => `
|
|
14571
14890
|
${renderOpCommands()}
|
|
14572
14891
|
`);
|
|
@@ -14577,10 +14896,10 @@ spec.command("set").description("Replace the stored OpenAPI spec from a local fi
|
|
|
14577
14896
|
spec.command("carry").description("Copy a route's saved rules to the path a spec rename moved traffic to").argument("<project>", "Project name or id").requiredOption("--method <method>", "HTTP method of the route (GET, POST, ...)").requiredOption("--from <path>", "Old route path, e.g. /owners/{ownerId}").requiredOption("--to <path>", "New route path, e.g. /customers/{customerId}").option("--delete-source", "Also delete the old rules after carrying").option("--team <id|name>", "Team the project is in").option("--apiversion <version>", "API version").option("--json", "Output machine-readable JSON").action(action((project, opts) => runSpecCarry(project, opts)));
|
|
14578
14897
|
spec.command("delete-rule").description("Delete the saved rules for a route (e.g. an orphan left by a spec change)").argument("<project>", "Project name or id").requiredOption("--method <method>", "HTTP method of the route").requiredOption("--route <path>", "Route path, e.g. /owners/{ownerId}").option("--team <id|name>", "Team the project is in").option("--apiversion <version>", "API version").action(action((project, opts) => runSpecDeleteRule(project, opts)));
|
|
14579
14898
|
var HELP_GROUPS = [
|
|
14580
|
-
{ title: "Chat", commands: ["docs", "apichat", "agent"] },
|
|
14581
|
-
{ title: "Setup", commands: ["dev", "create", "integration", "skill", "login", "init", "sidecar", "claim", "team", "whoami", "logout"] },
|
|
14899
|
+
{ title: "Chat", commands: ["docs", "apichat", "agent", "llm"] },
|
|
14900
|
+
{ title: "Setup", commands: ["dev", "create", "integration", "skill", "login", "init", "sidecar", "claim", "team", "whoami", "logout", "flush"] },
|
|
14582
14901
|
{ title: "Recipes \u2014 install a working setup, or publish yours", commands: ["search", "show", "install", "publish", "withdraw"] },
|
|
14583
|
-
{ title: "Control plane commands", commands: ["config", "projects", "logs", "tenant", "group", "admins", "iam", "identified", "preapprove", "rule", "domain", "delete", "target", "throttle", "rename", "spec", "export"] },
|
|
14902
|
+
{ title: "Control plane commands", commands: ["config", "projects", "logs", "tenant", "group", "admins", "apikeys", "iam", "identified", "preapprove", "allowoauthregistration", "rule", "domain", "delete", "job", "target", "throttle", "transform", "rename", "spec", "export"] },
|
|
14584
14903
|
{ title: "Data plane commands", commands: [
|
|
14585
14904
|
{ parent: "consumer", sub: "login" },
|
|
14586
14905
|
{ parent: "consumer", sub: "apikeys" },
|
|
@@ -14606,7 +14925,7 @@ function groupedCommandHelp() {
|
|
|
14606
14925
|
const sub = byName.get(e.parent)?.commands.find((s) => s.name() === e.sub);
|
|
14607
14926
|
return sub ? ` ${helpLabel(e).padEnd(width)}${sub.description()}` : "";
|
|
14608
14927
|
}).filter(Boolean).join("\n");
|
|
14609
|
-
return `${
|
|
14928
|
+
return `${import_chalk64.default.bold(g.title)}
|
|
14610
14929
|
${rows}`;
|
|
14611
14930
|
}).join("\n\n");
|
|
14612
14931
|
}
|
|
@@ -14617,7 +14936,7 @@ program.configureHelp({
|
|
|
14617
14936
|
}
|
|
14618
14937
|
});
|
|
14619
14938
|
program.addHelpText("after", () => `
|
|
14620
|
-
${
|
|
14939
|
+
${import_chalk64.default.bold("Don't like reading docs?")} Run ${import_chalk64.default.bgCyan.black.bold(` npx ${B.cli} docs `)} ${import_chalk64.default.cyan(`\u2014 ask an AI chatbot about ${B.product}, and let it act on your account.`)}
|
|
14621
14940
|
|
|
14622
14941
|
${groupedCommandHelp()}
|
|
14623
14942
|
|
|
@@ -14653,14 +14972,14 @@ async function recoverStaleTeam() {
|
|
|
14653
14972
|
const { resolveLinkedTeam: resolveLinkedTeam2 } = await Promise.resolve().then(() => (init_team(), team_exports));
|
|
14654
14973
|
const linked = await resolveLinkedTeam2({ preferredId: creds.teamId, interactive: !!process.stdin.isTTY });
|
|
14655
14974
|
if (!linked) {
|
|
14656
|
-
console.error(
|
|
14975
|
+
console.error(import_chalk64.default.yellow("Your account has no teams anymore (deleted?). Run `apiblaze login` or `apiblaze create` to get a workspace."));
|
|
14657
14976
|
return;
|
|
14658
14977
|
}
|
|
14659
14978
|
if (linked.teamId === creds.teamId) return;
|
|
14660
14979
|
const next = { ...creds, teamId: linked.teamId, teamName: linked.teamName };
|
|
14661
14980
|
delete next.activeTenant;
|
|
14662
14981
|
saveCredentials(next);
|
|
14663
|
-
console.error(
|
|
14982
|
+
console.error(import_chalk64.default.yellow(`Your previous team no longer exists \u2014 relinked to ${import_chalk64.default.bold(linked.teamName ?? linked.teamId)}. Re-run your command.`));
|
|
14664
14983
|
} catch {
|
|
14665
14984
|
}
|
|
14666
14985
|
}
|
|
@@ -14668,16 +14987,33 @@ async function printError(err) {
|
|
|
14668
14987
|
if (err instanceof ApiError) {
|
|
14669
14988
|
const data = err.body;
|
|
14670
14989
|
const extra = [data?.body?.reason, data?.body?.details, data?.details, data?.body?.error].find((x) => typeof x === "string" && x && x !== err.message);
|
|
14671
|
-
console.error(
|
|
14990
|
+
console.error(import_chalk64.default.red(`
|
|
14672
14991
|
API error (${err.status}): ${err.message}${extra ? ` \u2014 ${extra}` : ""}`));
|
|
14673
14992
|
if (err.status === 403 || err.status === 404) {
|
|
14674
14993
|
await recoverStaleTeam();
|
|
14675
14994
|
}
|
|
14676
14995
|
} else if (err instanceof Error) {
|
|
14677
|
-
console.error(
|
|
14996
|
+
console.error(import_chalk64.default.red(`
|
|
14678
14997
|
Error: ${err.message}`));
|
|
14679
14998
|
} else {
|
|
14680
|
-
console.error(
|
|
14999
|
+
console.error(import_chalk64.default.red("\nUnknown error"));
|
|
14681
15000
|
}
|
|
14682
15001
|
}
|
|
15002
|
+
function hintStaleCacheOnUnknownOption(cmd) {
|
|
15003
|
+
cmd.exitOverride((err) => {
|
|
15004
|
+
if (err.code === "commander.unknownOption") {
|
|
15005
|
+
console.error(import_chalk64.default.yellow(
|
|
15006
|
+
`
|
|
15007
|
+
This is ${B.cli} v${version}. If that option is documented but this build
|
|
15008
|
+
does not have it, your npx cache is serving an older copy \u2014 re-run as:
|
|
15009
|
+
`
|
|
15010
|
+
));
|
|
15011
|
+
console.error(import_chalk64.default.cyan(` npx ${B.cli}@latest ${process.argv.slice(2).join(" ")}
|
|
15012
|
+
`));
|
|
15013
|
+
}
|
|
15014
|
+
process.exit(err.exitCode);
|
|
15015
|
+
});
|
|
15016
|
+
for (const sub of cmd.commands) hintStaleCacheOnUnknownOption(sub);
|
|
15017
|
+
}
|
|
15018
|
+
hintStaleCacheOnUnknownOption(program);
|
|
14683
15019
|
program.parse(process.argv);
|