apiblaze 0.21.9 → 0.21.13
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 +968 -619
- 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' \\"));
|
|
@@ -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.13";
|
|
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,
|
|
@@ -4848,10 +4891,30 @@ async function mintConsumerKey(o) {
|
|
|
4848
4891
|
raw: out
|
|
4849
4892
|
};
|
|
4850
4893
|
}
|
|
4894
|
+
async function tenantKeyEndUserMode(caller, teamId, tenant2) {
|
|
4895
|
+
try {
|
|
4896
|
+
const p = await producer(caller, {
|
|
4897
|
+
method: "GET",
|
|
4898
|
+
path: `/teams/${encodeURIComponent(teamId)}/tenants/${encodeURIComponent(tenant2)}/key-policy`,
|
|
4899
|
+
summary: "Read the tenant key policy (default holder)"
|
|
4900
|
+
});
|
|
4901
|
+
return p?.key_end_user_mode === "one" ? "one" : "many";
|
|
4902
|
+
} catch {
|
|
4903
|
+
return "many";
|
|
4904
|
+
}
|
|
4905
|
+
}
|
|
4906
|
+
function defaultHolderRefusal(tenant2) {
|
|
4907
|
+
return `Tenant ${tenant2} issues one key per end user (1 API key = 1 end user): add --for <email|id>. To create a different key anyway, pass --backend (a server key that names each caller) or --client.`;
|
|
4908
|
+
}
|
|
4851
4909
|
async function runConsumerKeyMint(opts) {
|
|
4852
4910
|
const kind = resolveHolder(opts);
|
|
4853
4911
|
const who = (opts.for ?? "").trim();
|
|
4854
4912
|
const tenant2 = (opts.tenant ?? "").trim();
|
|
4913
|
+
if (tenant2 && !who && !opts.backend && !opts.client) {
|
|
4914
|
+
const caller = requireCaller();
|
|
4915
|
+
const { teamId } = await resolveActingTeam(caller, opts.team);
|
|
4916
|
+
if (await tenantKeyEndUserMode(caller, teamId, tenant2) === "one") throw new Error(defaultHolderRefusal(tenant2));
|
|
4917
|
+
}
|
|
4855
4918
|
const spinner = (0, import_ora3.default)(who ? `Minting a key for ${who} in ${tenant2}...` : `Minting a ${kind} key in ${tenant2}...`).start();
|
|
4856
4919
|
let out;
|
|
4857
4920
|
try {
|
|
@@ -5426,8 +5489,8 @@ function printEnvironments(o) {
|
|
|
5426
5489
|
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)`]] : [];
|
|
5427
5490
|
if (!rows.length) return;
|
|
5428
5491
|
const w = Math.max(...rows.map(([e]) => e.length));
|
|
5429
|
-
for (const [env,
|
|
5430
|
-
console.log(` ${import_chalk17.default.cyan(env.padEnd(w))} ${import_chalk17.default.green("\u2192")} ${
|
|
5492
|
+
for (const [env, target2] of rows) {
|
|
5493
|
+
console.log(` ${import_chalk17.default.cyan(env.padEnd(w))} ${import_chalk17.default.green("\u2192")} ${target2}`);
|
|
5431
5494
|
}
|
|
5432
5495
|
}
|
|
5433
5496
|
async function finishSetup(o) {
|
|
@@ -5875,9 +5938,9 @@ Create an ${B.thing}`));
|
|
|
5875
5938
|
body.openapi = await loadOpenapiSource(opts.openapi);
|
|
5876
5939
|
}
|
|
5877
5940
|
if (opts.target && !isHttpUrl3(opts.target)) fail4("--target must be a valid http(s) URL.");
|
|
5878
|
-
let
|
|
5941
|
+
let target2 = opts.target?.trim() || (typeof body.target === "string" ? body.target : "") || (typeof body.target_url === "string" ? body.target_url : "");
|
|
5879
5942
|
const hasOtherSource = !!(body.openapi || body.github);
|
|
5880
|
-
if (!
|
|
5943
|
+
if (!target2 && !hasOtherSource) {
|
|
5881
5944
|
if (interactive) {
|
|
5882
5945
|
const { default: inquirer3 } = await import("inquirer");
|
|
5883
5946
|
if (name === void 0) {
|
|
@@ -5902,7 +5965,7 @@ Create an ${B.thing}`));
|
|
|
5902
5965
|
console.log(import_chalk17.default.yellow(" Enter a valid http(s) URL.\n"));
|
|
5903
5966
|
continue;
|
|
5904
5967
|
}
|
|
5905
|
-
|
|
5968
|
+
target2 = url.trim();
|
|
5906
5969
|
break;
|
|
5907
5970
|
}
|
|
5908
5971
|
} else {
|
|
@@ -5910,12 +5973,12 @@ Create an ${B.thing}`));
|
|
|
5910
5973
|
}
|
|
5911
5974
|
}
|
|
5912
5975
|
if (name === void 0 && opts.auto) {
|
|
5913
|
-
const nameSource =
|
|
5976
|
+
const nameSource = target2 || (typeof opts.openapi === "string" ? opts.openapi : void 0);
|
|
5914
5977
|
name = deriveNameFrom(nameSource) ?? randomProxyName();
|
|
5915
5978
|
}
|
|
5916
|
-
if (
|
|
5917
|
-
body.target =
|
|
5918
|
-
body.target_url =
|
|
5979
|
+
if (target2) {
|
|
5980
|
+
body.target = target2;
|
|
5981
|
+
body.target_url = target2;
|
|
5919
5982
|
}
|
|
5920
5983
|
if (name) {
|
|
5921
5984
|
body.name = name;
|
|
@@ -6126,13 +6189,13 @@ async function chooseName(opts, openapi, teamId, interactive) {
|
|
|
6126
6189
|
}
|
|
6127
6190
|
async function createDevProxy(args) {
|
|
6128
6191
|
const { auth, name, port, openapi, authChoice, families, tenant: tenant2, teamId } = args;
|
|
6129
|
-
const
|
|
6192
|
+
const target2 = `http://localhost:${port}`;
|
|
6130
6193
|
const body = {
|
|
6131
6194
|
name,
|
|
6132
6195
|
subdomain: name,
|
|
6133
6196
|
auth_type: authChoice.auth,
|
|
6134
6197
|
...authChoice.bodyPatch,
|
|
6135
|
-
...openapi ? { openapi, environments: { dev: { target, description: `localhost:${port} through the ${B.cli} dev tunnel` } } } : { target_url:
|
|
6198
|
+
...openapi ? { openapi, environments: { dev: { target: target2, description: `localhost:${port} through the ${B.cli} dev tunnel` } } } : { target_url: target2 },
|
|
6136
6199
|
...tenant2 ? { tenant: tenant2 } : {},
|
|
6137
6200
|
...teamId ? { team_id: teamId } : {},
|
|
6138
6201
|
...families ? { secure: { families } } : {}
|
|
@@ -6142,7 +6205,7 @@ async function createDevProxy(args) {
|
|
|
6142
6205
|
let keyAuth = auth.mode === "key" ? { apiKey: auth.apiKey } : void 0;
|
|
6143
6206
|
let resolvedTeam = teamId;
|
|
6144
6207
|
if (caller) {
|
|
6145
|
-
result = await producer(caller, { method: "POST", path: "/projects", body, summary: `Create ${B.thingShort} ${name} \u2192 ${
|
|
6208
|
+
result = await producer(caller, { method: "POST", path: "/projects", body, summary: `Create ${B.thingShort} ${name} \u2192 ${target2}` });
|
|
6146
6209
|
} else {
|
|
6147
6210
|
result = await createProxyAnonymous(body);
|
|
6148
6211
|
if (result.cp_key && result.team_id) {
|
|
@@ -6198,7 +6261,7 @@ async function runDev(options) {
|
|
|
6198
6261
|
}
|
|
6199
6262
|
let targets = [];
|
|
6200
6263
|
const wantsNew = options.openapi !== void 0 || options.name !== void 0;
|
|
6201
|
-
if (auth.mode !== "none" && !wantsNew) {
|
|
6264
|
+
if (auth.mode !== "none" && (!wantsNew || options.name !== void 0 && !options.project)) {
|
|
6202
6265
|
const spinner = (0, import_ora7.default)("Looking for projects that point at this machine...").start();
|
|
6203
6266
|
try {
|
|
6204
6267
|
targets = await getLocalhostTargets(teamId, keyAuth);
|
|
@@ -6212,7 +6275,15 @@ async function runDev(options) {
|
|
|
6212
6275
|
let selected = [];
|
|
6213
6276
|
let created = null;
|
|
6214
6277
|
let setup = null;
|
|
6215
|
-
|
|
6278
|
+
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;
|
|
6279
|
+
if (rerun) {
|
|
6280
|
+
console.log(import_chalk18.default.dim(` "${rerun.projectName}" already points at this machine \u2014 reattaching the tunnel (spec and keys unchanged).`));
|
|
6281
|
+
selected = [rerun];
|
|
6282
|
+
} else if (wantsNew) {
|
|
6283
|
+
targets = [];
|
|
6284
|
+
}
|
|
6285
|
+
if (selected.length) {
|
|
6286
|
+
} else if (options.project) {
|
|
6216
6287
|
const want = options.project.toLowerCase();
|
|
6217
6288
|
const match = targets.find((t) => t.projectId?.toLowerCase() === want || t.projectName?.toLowerCase() === want);
|
|
6218
6289
|
if (!match) {
|
|
@@ -6318,11 +6389,12 @@ async function runDev(options) {
|
|
|
6318
6389
|
}
|
|
6319
6390
|
let restore = [];
|
|
6320
6391
|
let connect;
|
|
6392
|
+
const registerTargets = { targets: selected.map((t) => ({ projectId: t.projectId, tenantId: t.tenantId })) };
|
|
6321
6393
|
{
|
|
6322
6394
|
const spinner = (0, import_ora7.default)("Registering the tunnel...").start();
|
|
6323
6395
|
try {
|
|
6324
|
-
const result = await putDevTunnel(
|
|
6325
|
-
restore = result.restore ?? [];
|
|
6396
|
+
const result = await putDevTunnel(registerTargets, keyAuth);
|
|
6397
|
+
restore = (result.restore ?? []).map((r) => unmarkRestore(r, options.port));
|
|
6326
6398
|
connect = result.connect;
|
|
6327
6399
|
spinner.stop();
|
|
6328
6400
|
} catch (err) {
|
|
@@ -6340,6 +6412,13 @@ async function runDev(options) {
|
|
|
6340
6412
|
(projectId) => startTunnelClient({
|
|
6341
6413
|
connectUrl: connect.url,
|
|
6342
6414
|
token: connect.token,
|
|
6415
|
+
// Re-register for a fresh token. PUT is idempotent on an already-tunnelled
|
|
6416
|
+
// project; its restore snapshot would be the marker, so it is IGNORED —
|
|
6417
|
+
// teardown keeps replaying the one taken at start.
|
|
6418
|
+
refresh: async () => {
|
|
6419
|
+
const again = await putDevTunnel(registerTargets, keyAuth);
|
|
6420
|
+
return { url: again.connect.url, token: again.connect.token };
|
|
6421
|
+
},
|
|
6343
6422
|
projectId,
|
|
6344
6423
|
localPort: options.port,
|
|
6345
6424
|
onEntry: (entry) => say(formatLogLine(entry)),
|
|
@@ -6359,6 +6438,7 @@ async function runDev(options) {
|
|
|
6359
6438
|
const localUp = await probeLocalServer(options.port);
|
|
6360
6439
|
console.log();
|
|
6361
6440
|
console.log(` ${import_chalk18.default.bold("Tunnel is up \u2014 leave this running. Ctrl-C stops it.")}`);
|
|
6441
|
+
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)`));
|
|
6362
6442
|
if (!localUp) console.log(import_chalk18.default.magenta(` \u26B2 Nothing listening on port ${options.port} yet \u2014 requests are captured until your server starts.`));
|
|
6363
6443
|
if (options.captureFile) console.log(import_chalk18.default.gray(` Streaming full traffic to ${options.captureFile}`));
|
|
6364
6444
|
console.log();
|
|
@@ -6378,9 +6458,30 @@ async function runDev(options) {
|
|
|
6378
6458
|
}
|
|
6379
6459
|
process.on("SIGINT", () => void cleanup());
|
|
6380
6460
|
process.on("SIGTERM", () => void cleanup());
|
|
6461
|
+
process.on("SIGHUP", () => void cleanup());
|
|
6462
|
+
for (const stream of [process.stdout, process.stderr]) {
|
|
6463
|
+
stream.on("error", (err) => {
|
|
6464
|
+
if (err.code !== "EPIPE") throw err;
|
|
6465
|
+
});
|
|
6466
|
+
}
|
|
6381
6467
|
await new Promise(() => {
|
|
6382
6468
|
});
|
|
6383
6469
|
}
|
|
6470
|
+
function unmarkRestore(r, port) {
|
|
6471
|
+
const envs = r.environments;
|
|
6472
|
+
if (!envs || typeof envs !== "object") return r;
|
|
6473
|
+
let changed = false;
|
|
6474
|
+
const next = {};
|
|
6475
|
+
for (const [stage, value] of Object.entries(envs)) {
|
|
6476
|
+
if (typeof value?.target === "string" && value.target.startsWith("devtunnel://")) {
|
|
6477
|
+
next[stage] = { ...value, target: `http://localhost:${port}` };
|
|
6478
|
+
changed = true;
|
|
6479
|
+
} else {
|
|
6480
|
+
next[stage] = value;
|
|
6481
|
+
}
|
|
6482
|
+
}
|
|
6483
|
+
return changed ? { ...r, environments: next } : r;
|
|
6484
|
+
}
|
|
6384
6485
|
|
|
6385
6486
|
// src/commands/projects.ts
|
|
6386
6487
|
var import_chalk19 = __toESM(require("chalk"));
|
|
@@ -6485,10 +6586,16 @@ business logic: the gateway sits in front of the code and enforces it.
|
|
|
6485
6586
|
- They say yes to "can I read the code" \u2192 **Adopt**: scan their app, ask only what the scan
|
|
6486
6587
|
could not find, then ship it through the Ship steps.
|
|
6487
6588
|
- They have a backend, just wrote one, or ask you to BUILD one and then ship it \u2192 **Ship**.
|
|
6488
|
-
No code yet? First write the smallest plain backend that serves the resources they named
|
|
6489
|
-
|
|
6490
|
-
|
|
6491
|
-
|
|
6589
|
+
No code yet? First write the smallest plain backend that serves the resources they named,
|
|
6590
|
+
plus its \`openapi.yaml\`, start it on their port, then continue at Ship \xA71. Do NOT build
|
|
6591
|
+
the Demo app for this. The gateway does auth, tenancy and item ownership; the backend does
|
|
6592
|
+
only these three things:
|
|
6593
|
+
- stamp each new row's \`owner\` with the \`x-abz-user-id\` header;
|
|
6594
|
+
- filter every collection GET to rows whose \`owner\` is that header (the gateway's rules
|
|
6595
|
+
lock single records, never lists \u2014 a list route has no one record to check);
|
|
6596
|
+
- answer 401 to any call whose \`x-target-api-key\` is not \`process.env.TARGET_SERVER_SECRET\`
|
|
6597
|
+
(generate it with \`openssl rand -hex 24\`, put it in \`.env.local\`, start the backend with
|
|
6598
|
+
it; Ship \xA73b makes the proxy send it). If they say "use
|
|
6492
6599
|
${cli} on this" or "ship it with ${cli}" and point at a backend, skip any pitch and run it.
|
|
6493
6600
|
|
|
6494
6601
|
Tie-breaks: a question ("what can it do", "what is it") is always **Explain**, whatever else the
|
|
@@ -6608,12 +6715,11 @@ ONLY for what the scan could not find \u2014 one short list of questions, not an
|
|
|
6608
6715
|
sign-in plumbing, not business logic. Then the widgets they ticked, each with its server route
|
|
6609
6716
|
(\`npx ${cli} integration <name> --stack <their stack>\` for keys and chat; groups is the keys
|
|
6610
6717
|
route with \`createApiblazeGroups\`).
|
|
6611
|
-
- Backend behind a secret: the proxy must send it upstream
|
|
6612
|
-
|
|
6613
|
-
|
|
6614
|
-
|
|
6615
|
-
|
|
6616
|
-
that header \u2014 fine on a laptop, not on a server; in prod the proxy is the only caller.
|
|
6718
|
+
- Backend behind a secret: the proxy must send it upstream \u2014
|
|
6719
|
+
\`npx ${cli} transform set-header <name> <its header> --value-env <VAR> --secret\`. Until then
|
|
6720
|
+
anyone reaching \`localhost:<port>\` skips the gateway.
|
|
6721
|
+
- List routes: check each collection GET filters by owner; if it does not, say so \u2014 the
|
|
6722
|
+
gateway locks single records only.
|
|
6617
6723
|
- Multi-tenant app \u2192 \`npx ${cli} identified <name> require\`, so no call arrives without a user.
|
|
6618
6724
|
- Change nothing in their business logic. Report what you changed, file by file.
|
|
6619
6725
|
|
|
@@ -6628,7 +6734,10 @@ ONLY for what the scan could not find \u2014 one short list of questions, not an
|
|
|
6628
6734
|
login. Their own OAuth app \u2192 \`--apikey --oauth '{"provider":"auth0","clientId":"\u2026","clientSecret":"\u2026"}'\`
|
|
6629
6735
|
(both doors: keys for tenants, their login for people and agents). Shapes below.
|
|
6630
6736
|
3. **Local code** \u2192 \`npx ${cli} dev --port <port> --openapi ./openapi.yaml --name <name> --auto <door flags>\`,
|
|
6631
|
-
|
|
6737
|
+
left running \u2014 it is the tunnel. Start it as its own detached process, never \`cmd &\` inside
|
|
6738
|
+
a shell call that ends: \`nohup npx ${cli} dev \u2026 > ${cli}-dev.log 2>&1 < /dev/null &\`, then
|
|
6739
|
+
read the log. If it stops, re-run the SAME command: the same \`--name\` reattaches and
|
|
6740
|
+
creates nothing. Never pick a new name to get it back \u2014 that is a second proxy. **Deployed code** \u2192
|
|
6632
6741
|
\`npx ${cli} create --target <url-or-spec> --name <name> --auto <door flags>\`. Both print the
|
|
6633
6742
|
URL, the MCP URL (the "agents:" line), what is locked, and the bootstrap key ONCE \u2014 that key
|
|
6634
6743
|
goes in the frontend-server's env, never a browser, a repo or an agent config. The tenant is
|
|
@@ -6637,22 +6746,32 @@ ONLY for what the scan could not find \u2014 one short list of questions, not an
|
|
|
6637
6746
|
the frontend-server's env (\`.env.local\` for Next.js). Then prove it with three curls and
|
|
6638
6747
|
say the codes: no key \u2192 401; create as \`X-End-User-Id: ana\` \u2192 201; PATCH that id as
|
|
6639
6748
|
\`ben\` \u2192 403.
|
|
6749
|
+
3b. **Only the proxy reaches the backend.** If the backend checks \`x-target-api-key\`:
|
|
6750
|
+
\`npx ${cli} transform set-header <name> x-target-api-key --value-env TARGET_SERVER_SECRET --secret\`
|
|
6751
|
+
(export the variable from \`.env.local\` first). Prove it: the same list call through the
|
|
6752
|
+
proxy \u2192 200; \`curl localhost:<port>/<collection>\` with no header \u2192 401.
|
|
6640
6753
|
4. **Prod** (local code only): \`npx ${cli} target <name> --env prod --url <prod-url>\`.
|
|
6641
|
-
5. **Limits.**
|
|
6642
|
-
|
|
6643
|
-
{
|
|
6644
|
-
|
|
6754
|
+
5. **Limits.** Every new proxy already allows 10 requests/s per person and 10,000 a day \u2014
|
|
6755
|
+
say so; that is "throttled". Only for other numbers:
|
|
6756
|
+
\`npx ${cli} throttle <name> --rate <n> --quota <n> --period daily\`. It works logged out;
|
|
6757
|
+
an anonymous workspace is capped at 10/s until claimed. Prove it: 15 parallel GETs as one
|
|
6758
|
+
user \u2192 about 10\xD7200 and the rest 429.
|
|
6645
6759
|
5b. **Attribution.** \`--auto\` leaves unattributed calls allowed: the bootstrap key with no
|
|
6646
6760
|
\`X-End-User-Id\` creates rows nobody owns. For a multi-tenant app run
|
|
6647
6761
|
\`npx ${cli} identified <name> require\` \u2014 then every call must carry \`X-End-User-Id\` or a
|
|
6648
|
-
login token.
|
|
6649
|
-
|
|
6650
|
-
|
|
6651
|
-
|
|
6652
|
-
|
|
6653
|
-
|
|
6654
|
-
|
|
6655
|
-
|
|
6762
|
+
login token. Prove it: the key with no \`X-End-User-Id\` \u2192 403 \`identity_required\`.
|
|
6763
|
+
Lists are the backend's job (it filters by owner); say that the gateway locks records,
|
|
6764
|
+
the backend filters lists.
|
|
6765
|
+
6. **Admin.** \`npx ${cli} admins add <email> --tenant <tenant>\` with the email they named. None
|
|
6766
|
+
named \u2192 \`admin@ninopizzas.com\`, the demo admin (works before that person ever logs in);
|
|
6767
|
+
say so, and that they can \`admins add\` their own email and \`admins remove\` that one.
|
|
6768
|
+
7. **Report** in the shape below, then hand it over \u2014 they try it, you do not:
|
|
6769
|
+
- the full curl with the real key and URL, ready to paste:
|
|
6770
|
+
\`curl <APIBLAZE_URL>/<collection> -H "X-API-Key: <APIBLAZE_SERVER_KEY>" -H "X-End-User-Id: ana"\`
|
|
6771
|
+
(say the key is the server key \u2014 for trying it here, never for a browser or an agent);
|
|
6772
|
+
- the command to chat with their API: \`npx ${cli} apichat <name>\` (add
|
|
6773
|
+
\`--xenduserid ana\` when \xA75b is on).
|
|
6774
|
+
Then ask once: "Would you like me to add the self-serve API key widget to your site?"
|
|
6656
6775
|
|
|
6657
6776
|
## Report like this
|
|
6658
6777
|
|
|
@@ -6665,9 +6784,15 @@ Use the real values the CLI printed. Never invent a URL, a key, a rule or a limi
|
|
|
6665
6784
|
API https://<name>.abz.run/1.0.0/dev your tenants, with API keys
|
|
6666
6785
|
/dev \u2192 localhost:<port> tunnelled from this machine \u2014 leave it running
|
|
6667
6786
|
/prod \u2192 <prod-url> your deploy
|
|
6668
|
-
Try it: curl https://<name>.abz.run/1.0.0/dev/<collection> -H "X-API-Key: <key>" -H "X-End-User-Id: you"
|
|
6669
6787
|
MCP <the "agents:" URL the CLI printed> agents, signing in with <provider>
|
|
6670
|
-
Limits 10 requests/s per
|
|
6788
|
+
Limits 10 requests/s per person \xB7 10,000 a day
|
|
6789
|
+
Backend answers only to the proxy (x-target-api-key) \xB7 lists filtered by owner
|
|
6790
|
+
Admin <email>
|
|
6791
|
+
|
|
6792
|
+
Try it yourself:
|
|
6793
|
+
curl <APIBLAZE_URL>/<collection> -H "X-API-Key: <APIBLAZE_SERVER_KEY>" -H "X-End-User-Id: ana"
|
|
6794
|
+
Chat with it:
|
|
6795
|
+
npx ${cli} apichat <name> --xenduserid ana
|
|
6671
6796
|
|
|
6672
6797
|
## Sign-in shapes \u2014 the only ones \`--oauth\` accepts; never invent fields
|
|
6673
6798
|
|
|
@@ -6689,9 +6814,10 @@ key. Mint it with \`npx ${cli} apikeys mint --desc "keys widget"\` (no \`--tenan
|
|
|
6689
6814
|
\`--tenant\` you get a consumer key, which is the wrong kind).
|
|
6690
6815
|
Edit ONLY the frontend and the frontend-server. A key per TENANT comes from that widget \u2014 each
|
|
6691
6816
|
of your customers mints their own, in their own tenant; the bootstrap key is only for your
|
|
6692
|
-
frontend-server.
|
|
6693
|
-
\`
|
|
6694
|
-
|
|
6817
|
+
frontend-server. Three kinds of key for a tenant, one flag each: \`apikeys mint --tenant <tenant>\` +
|
|
6818
|
+
\`--backend\` (a server that names the user with \`X-End-User-Id\`), \`--client\` (cannot name a
|
|
6819
|
+
user) or \`--for <email>\` (bound to one person \u2014 an agent, a CI job). \`--for\` names a person by
|
|
6820
|
+
email, which is a DIFFERENT identity from a bare \`X-End-User-Id\` string: rows created as "ana" via
|
|
6695
6821
|
the header are not owned by ana@example.com's key.
|
|
6696
6822
|
|
|
6697
6823
|
## Never
|
|
@@ -6702,13 +6828,16 @@ the header are not owned by ana@example.com's key.
|
|
|
6702
6828
|
- Put ownership or tenancy checks into business logic. The gateway enforces them.
|
|
6703
6829
|
- Ask for team ids, tenant ids or project ids the CLI already knows.
|
|
6704
6830
|
- Delete anything. Deletion stays with the user: \`npx ${cli} delete <name>\` (interactive;
|
|
6705
|
-
\`--yes\` skips the prompt). Its impact banner lists tenant-scoped counts even when the tenant
|
|
6831
|
+
\`--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
|
|
6706
6832
|
is shared \u2014 those are lost only if this is the tenant's LAST proxy.
|
|
6707
6833
|
|
|
6708
6834
|
## Later
|
|
6709
6835
|
|
|
6710
6836
|
- \`npx ${cli} rule <name>\` reviews or changes the rules; \`npx ${cli} config <name> authorization.enforce_authorization false\` turns enforcement off without losing them.
|
|
6711
6837
|
- \`npx ${cli} apichat <name>\` chats with the API again; \`--install-mcp claude\` connects an agent.
|
|
6838
|
+
- \`npx ${cli} throttle <name> --rate <n>\` \xB7 \`identified <name> require|allow-anon\` \xB7
|
|
6839
|
+
\`transform <name> list|set-header|remove\` \xB7 \`target <name> --env prod --url <url>\` \u2014 all work
|
|
6840
|
+
logged out. \`npx ${cli} <command> --help\` for the flags; \`--verbose\` prints the API calls.
|
|
6712
6841
|
|
|
6713
6842
|
## No shell? The control-plane MCP
|
|
6714
6843
|
|
|
@@ -6724,14 +6853,14 @@ async function runSkill(opts = {}) {
|
|
|
6724
6853
|
process.stdout.write(md);
|
|
6725
6854
|
return;
|
|
6726
6855
|
}
|
|
6727
|
-
const
|
|
6728
|
-
if (import_fs3.default.existsSync(
|
|
6856
|
+
const target2 = import_path2.default.resolve(process.cwd(), SKILL_RELATIVE_PATH);
|
|
6857
|
+
if (import_fs3.default.existsSync(target2) && !opts.force) {
|
|
6729
6858
|
console.error(import_chalk20.default.yellow(`${SKILL_RELATIVE_PATH} already exists \u2014 re-run with --force to overwrite it.`));
|
|
6730
6859
|
process.exit(1);
|
|
6731
6860
|
}
|
|
6732
|
-
import_fs3.default.mkdirSync(import_path2.default.dirname(
|
|
6733
|
-
import_fs3.default.writeFileSync(
|
|
6734
|
-
console.log(`${import_chalk20.default.green("\u2713")} Installed the ${import_chalk20.default.bold(SKILL_NAME)} skill: ${import_chalk20.default.bold(
|
|
6861
|
+
import_fs3.default.mkdirSync(import_path2.default.dirname(target2), { recursive: true });
|
|
6862
|
+
import_fs3.default.writeFileSync(target2, md, "utf8");
|
|
6863
|
+
console.log(`${import_chalk20.default.green("\u2713")} Installed the ${import_chalk20.default.bold(SKILL_NAME)} skill: ${import_chalk20.default.bold(target2)}`);
|
|
6735
6864
|
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.`));
|
|
6736
6865
|
}
|
|
6737
6866
|
|
|
@@ -6822,12 +6951,12 @@ async function runLogout(opts = {}) {
|
|
|
6822
6951
|
console.log(import_chalk22.default.yellow("You were not logged in."));
|
|
6823
6952
|
return;
|
|
6824
6953
|
}
|
|
6825
|
-
let
|
|
6826
|
-
if (opts.all)
|
|
6827
|
-
else if (opts.producer)
|
|
6828
|
-
else if (opts.consumer)
|
|
6829
|
-
else if (creds && !consumer2)
|
|
6830
|
-
else if (consumer2 && !creds)
|
|
6954
|
+
let target2;
|
|
6955
|
+
if (opts.all) target2 = "all";
|
|
6956
|
+
else if (opts.producer) target2 = "producer";
|
|
6957
|
+
else if (opts.consumer) target2 = "consumer";
|
|
6958
|
+
else if (creds && !consumer2) target2 = "producer";
|
|
6959
|
+
else if (consumer2 && !creds) target2 = "consumer";
|
|
6831
6960
|
else {
|
|
6832
6961
|
const { default: inquirer3 } = await import("inquirer");
|
|
6833
6962
|
const { chosen } = await inquirer3.prompt([
|
|
@@ -6842,13 +6971,13 @@ async function runLogout(opts = {}) {
|
|
|
6842
6971
|
]
|
|
6843
6972
|
}
|
|
6844
6973
|
]);
|
|
6845
|
-
|
|
6974
|
+
target2 = chosen;
|
|
6846
6975
|
}
|
|
6847
|
-
if (
|
|
6976
|
+
if (target2 === "producer" || target2 === "all") {
|
|
6848
6977
|
clearCredentials();
|
|
6849
6978
|
console.log(import_chalk22.default.green(`\u2714 Logged out of API Producer${creds?.githubHandle ? ` (${creds.githubHandle})` : ""}.`));
|
|
6850
6979
|
}
|
|
6851
|
-
if (
|
|
6980
|
+
if (target2 === "consumer" || target2 === "all") {
|
|
6852
6981
|
clearConsumer();
|
|
6853
6982
|
console.log(import_chalk22.default.green(`\u2714 Logged out of API Consumer${consumer2?.tenant ? ` (${consumer2.tenant})` : ""}.`));
|
|
6854
6983
|
}
|
|
@@ -7861,17 +7990,17 @@ async function runExport(projectArg, versionArg, opts) {
|
|
|
7861
7990
|
const match = await resolveProject(teamId, projectArg, versionArg);
|
|
7862
7991
|
const projectId = match.projectId;
|
|
7863
7992
|
const version2 = versionArg || match.apiVersion;
|
|
7864
|
-
const
|
|
7993
|
+
const target2 = opts.kong ? "kong" : "data";
|
|
7865
7994
|
const q = new URLSearchParams();
|
|
7866
7995
|
if (opts.secrets) q.set("include_secrets", "1");
|
|
7867
7996
|
if (opts.noConsumers) q.set("include_consumers", "0");
|
|
7868
7997
|
if (opts.keys) q.set("keys", opts.keys);
|
|
7869
7998
|
const qs = q.toString() ? `?${q.toString()}` : "";
|
|
7870
|
-
console.log(import_chalk36.default.dim(`Queuing ${
|
|
7999
|
+
console.log(import_chalk36.default.dim(`Queuing ${target2} export of ${projectId}@${version2}\u2026`));
|
|
7871
8000
|
const job = await admin({
|
|
7872
|
-
path: `/projects/${projectId}/${version2}/export/${
|
|
8001
|
+
path: `/projects/${projectId}/${version2}/export/${target2}${qs}`,
|
|
7873
8002
|
method: "GET",
|
|
7874
|
-
summary: `export ${projectId} (${
|
|
8003
|
+
summary: `export ${projectId} (${target2})`
|
|
7875
8004
|
});
|
|
7876
8005
|
if (!job?.job_id) throw new Error("Failed to queue export.");
|
|
7877
8006
|
const statusPath = `/projects/${projectId}/${version2}/export/jobs/${job.job_id}`;
|
|
@@ -7890,7 +8019,7 @@ async function runExport(projectArg, versionArg, opts) {
|
|
|
7890
8019
|
}
|
|
7891
8020
|
}
|
|
7892
8021
|
process.stdout.write("\n");
|
|
7893
|
-
const outFile = opts.out || `apiblaze-export-${projectId}-${version2}-${
|
|
8022
|
+
const outFile = opts.out || `apiblaze-export-${projectId}-${version2}-${target2}.zip`;
|
|
7894
8023
|
const res = await fetch(`${DASHBOARD_BASE5}/api/cli/admin/download`, {
|
|
7895
8024
|
method: "POST",
|
|
7896
8025
|
headers: { "Content-Type": "application/json", Authorization: `Bearer ${token}` },
|
|
@@ -7902,7 +8031,7 @@ async function runExport(projectArg, versionArg, opts) {
|
|
|
7902
8031
|
console.log(import_chalk36.default.green(`\u2713 Saved ${outFile} (${(buf.length / 1024).toFixed(0)} KB)`));
|
|
7903
8032
|
if (job.decryption_key) console.log(import_chalk36.default.yellow(`Decryption key (shown once): ${job.decryption_key}`));
|
|
7904
8033
|
console.log(import_chalk36.default.dim("Read MIGRATION-REPORT.json inside the zip for the full fidelity ledger."));
|
|
7905
|
-
if (
|
|
8034
|
+
if (target2 === "kong") console.log(import_chalk36.default.dim("Kong: unzip, then `cd kong && docker compose up` (see kong/README.md)."));
|
|
7906
8035
|
}
|
|
7907
8036
|
|
|
7908
8037
|
// src/commands/recipe-search.ts
|
|
@@ -8281,8 +8410,8 @@ async function runRecipeInstall(nameArg, opts) {
|
|
|
8281
8410
|
let reinstall = false;
|
|
8282
8411
|
if (opts.into) {
|
|
8283
8412
|
reinstall = true;
|
|
8284
|
-
const
|
|
8285
|
-
const proj2 = await resolveProjectVia(caller, teamId,
|
|
8413
|
+
const target2 = normalizeName3(opts.into);
|
|
8414
|
+
const proj2 = await resolveProjectVia(caller, teamId, target2, opts.apiversion);
|
|
8286
8415
|
project = proj2.projectId;
|
|
8287
8416
|
apiVersion = proj2.apiVersion;
|
|
8288
8417
|
tenant2 = await resolveReinstallTenant(caller, project, apiVersion);
|
|
@@ -8365,14 +8494,14 @@ function buildSteps(inp) {
|
|
|
8365
8494
|
label: `creating ${project}`,
|
|
8366
8495
|
skip: reinstall,
|
|
8367
8496
|
run: async () => {
|
|
8368
|
-
const
|
|
8369
|
-
if (!
|
|
8497
|
+
const target2 = firstUpstream(recipe);
|
|
8498
|
+
if (!target2) throw new Error("this recipe carries no upstream URL, so there is nothing to create a proxy against");
|
|
8370
8499
|
const out = await producer(caller, {
|
|
8371
8500
|
method: "POST",
|
|
8372
8501
|
path: "/projects",
|
|
8373
8502
|
body: {
|
|
8374
8503
|
name: project,
|
|
8375
|
-
target,
|
|
8504
|
+
target: target2,
|
|
8376
8505
|
tenant: tenant2,
|
|
8377
8506
|
product_slug: project,
|
|
8378
8507
|
api_version: apiVersion,
|
|
@@ -10089,14 +10218,16 @@ async function runConsumerLogsList(opts) {
|
|
|
10089
10218
|
// src/commands/config.ts
|
|
10090
10219
|
var import_chalk43 = __toESM(require("chalk"));
|
|
10091
10220
|
var import_ora15 = __toESM(require("ora"));
|
|
10092
|
-
|
|
10093
|
-
|
|
10221
|
+
init_caller();
|
|
10222
|
+
init_paging();
|
|
10223
|
+
var resolveCallerMode = () => resolveCaller()?.mode ?? null;
|
|
10094
10224
|
async function patchConfig(project, opts, body, summary) {
|
|
10095
|
-
const
|
|
10096
|
-
const
|
|
10225
|
+
const caller = requireCaller();
|
|
10226
|
+
const { teamId } = await resolveActingTeam(caller, opts.team);
|
|
10227
|
+
const proj2 = await resolveProjectVia(caller, teamId, project, opts.apiversion);
|
|
10097
10228
|
const spinner = (0, import_ora15.default)(summary + "...").start();
|
|
10098
10229
|
try {
|
|
10099
|
-
const out = await
|
|
10230
|
+
const out = await producer(caller, {
|
|
10100
10231
|
method: "PATCH",
|
|
10101
10232
|
path: `/projects/${proj2.projectId}/${proj2.apiVersion}`,
|
|
10102
10233
|
body,
|
|
@@ -10114,13 +10245,47 @@ async function runTargetSet(project, opts) {
|
|
|
10114
10245
|
console.error(import_chalk43.default.red("--url is required."));
|
|
10115
10246
|
process.exit(1);
|
|
10116
10247
|
}
|
|
10117
|
-
|
|
10118
|
-
|
|
10248
|
+
if (!opts.env) {
|
|
10249
|
+
await patchConfig(project, opts, { target_url: opts.url }, `Set target \u2192 ${opts.url}`);
|
|
10250
|
+
return;
|
|
10251
|
+
}
|
|
10252
|
+
const caller = requireCaller();
|
|
10253
|
+
const { teamId } = await resolveActingTeam(caller, opts.team);
|
|
10254
|
+
const proj2 = await resolveProjectVia(caller, teamId, project, opts.apiversion);
|
|
10255
|
+
const listing = await pageAll(
|
|
10256
|
+
(cursor) => producer(caller, {
|
|
10257
|
+
method: "GET",
|
|
10258
|
+
path: withPage(`/projects?team_id=${encodeURIComponent(teamId)}`, cursor),
|
|
10259
|
+
summary: `Read environments for ${proj2.projectName}`
|
|
10260
|
+
}),
|
|
10261
|
+
(b) => b?.projects
|
|
10262
|
+
);
|
|
10263
|
+
const row = listing.items.find((r) => r.project_id === proj2.projectId && r.api_version === proj2.apiVersion);
|
|
10264
|
+
const current = row?.config?.environments;
|
|
10265
|
+
const existing = current && typeof current === "object" && !Array.isArray(current) ? current : {};
|
|
10266
|
+
const prev = existing[opts.env];
|
|
10267
|
+
const environments = {
|
|
10268
|
+
...existing,
|
|
10269
|
+
[opts.env]: { ...prev && typeof prev === "object" ? prev : {}, target: opts.url }
|
|
10270
|
+
};
|
|
10271
|
+
await patchConfig(project, opts, { environments }, `Set target for env ${opts.env} \u2192 ${opts.url}`);
|
|
10119
10272
|
}
|
|
10120
10273
|
async function runThrottleSet(project, opts) {
|
|
10121
10274
|
const throttling = {};
|
|
10122
|
-
|
|
10123
|
-
if (opts.endUserRate !== void 0
|
|
10275
|
+
const rate = opts.rate ?? opts.endUserRate;
|
|
10276
|
+
if (opts.rate !== void 0 && opts.endUserRate !== void 0 && opts.rate !== opts.endUserRate) {
|
|
10277
|
+
console.error(import_chalk43.default.red("--rate and --end-user-rate are the same limit now; pass one of them."));
|
|
10278
|
+
process.exit(1);
|
|
10279
|
+
}
|
|
10280
|
+
if (rate !== void 0) {
|
|
10281
|
+
const n = Number(rate);
|
|
10282
|
+
if (!Number.isFinite(n) || n <= 0) {
|
|
10283
|
+
console.error(import_chalk43.default.red("--rate must be a positive number of requests per second."));
|
|
10284
|
+
process.exit(1);
|
|
10285
|
+
}
|
|
10286
|
+
throttling.userRateLimit = n;
|
|
10287
|
+
throttling.endUserRateLimit = n;
|
|
10288
|
+
}
|
|
10124
10289
|
if (opts.quota !== void 0) throttling.proxyQuota = Number(opts.quota);
|
|
10125
10290
|
if (opts.period !== void 0) {
|
|
10126
10291
|
if (!["daily", "weekly", "monthly"].includes(opts.period)) {
|
|
@@ -10134,6 +10299,11 @@ async function runThrottleSet(project, opts) {
|
|
|
10134
10299
|
process.exit(1);
|
|
10135
10300
|
}
|
|
10136
10301
|
await patchConfig(project, opts, { throttling }, `Update throttling ${JSON.stringify(throttling)}`);
|
|
10302
|
+
if (resolveCallerMode() === "anon" && !opts.json) {
|
|
10303
|
+
console.log(import_chalk43.default.dim(
|
|
10304
|
+
" 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`)."
|
|
10305
|
+
));
|
|
10306
|
+
}
|
|
10137
10307
|
}
|
|
10138
10308
|
async function runRename(project, opts) {
|
|
10139
10309
|
if (!opts.displayName) {
|
|
@@ -10143,55 +10313,192 @@ async function runRename(project, opts) {
|
|
|
10143
10313
|
await patchConfig(project, opts, { display_name: opts.displayName }, `Rename \u2192 "${opts.displayName}"`);
|
|
10144
10314
|
}
|
|
10145
10315
|
|
|
10316
|
+
// src/commands/transform.ts
|
|
10317
|
+
var import_chalk44 = __toESM(require("chalk"));
|
|
10318
|
+
var import_ora16 = __toESM(require("ora"));
|
|
10319
|
+
init_caller();
|
|
10320
|
+
var HEADER_NAME = /^[A-Za-z0-9!#$%&'*+.^_`|~-]+$/;
|
|
10321
|
+
var ruleNameFor = (header) => `set header ${header.toLowerCase()}`;
|
|
10322
|
+
async function target(project, opts) {
|
|
10323
|
+
const caller = requireCaller();
|
|
10324
|
+
const { teamId } = await resolveActingTeam(caller, opts.team);
|
|
10325
|
+
const proj2 = await resolveProjectVia(caller, teamId, project, opts.apiversion);
|
|
10326
|
+
const base2 = `/projects/${encodeURIComponent(proj2.projectId)}/${encodeURIComponent(proj2.apiVersion)}/transforms`;
|
|
10327
|
+
return { caller, proj: proj2, base: base2 };
|
|
10328
|
+
}
|
|
10329
|
+
async function listRules(t) {
|
|
10330
|
+
const out = await producer(t.caller, {
|
|
10331
|
+
method: "GET",
|
|
10332
|
+
path: t.base,
|
|
10333
|
+
summary: `List transforms for ${t.proj.projectName}`
|
|
10334
|
+
});
|
|
10335
|
+
return out?.rules ?? [];
|
|
10336
|
+
}
|
|
10337
|
+
function describe(r) {
|
|
10338
|
+
const a = r.action ?? { type: "?" };
|
|
10339
|
+
if (a.type === "hardcode") {
|
|
10340
|
+
const secret = !a.value || /^abzsec:|^__ABZSEC_|^•+$/.test(a.value);
|
|
10341
|
+
return `set ${a.destination} = ${secret ? "(secret)" : a.value}`;
|
|
10342
|
+
}
|
|
10343
|
+
if (a.type === "remove") return `remove ${a.field}`;
|
|
10344
|
+
return a.type;
|
|
10345
|
+
}
|
|
10346
|
+
async function runTransformList(project, opts) {
|
|
10347
|
+
const t = await target(project, opts);
|
|
10348
|
+
const rules = await listRules(t);
|
|
10349
|
+
if (opts.json) {
|
|
10350
|
+
console.log(JSON.stringify(rules, null, 2));
|
|
10351
|
+
return;
|
|
10352
|
+
}
|
|
10353
|
+
if (!rules.length) {
|
|
10354
|
+
console.log(import_chalk44.default.dim(`No transforms on ${t.proj.projectName} v${t.proj.apiVersion}.`));
|
|
10355
|
+
return;
|
|
10356
|
+
}
|
|
10357
|
+
for (const r of rules) {
|
|
10358
|
+
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}`)}`);
|
|
10359
|
+
console.log(` ${describe(r)}`);
|
|
10360
|
+
}
|
|
10361
|
+
}
|
|
10362
|
+
async function runTransformSetHeader(project, header, opts) {
|
|
10363
|
+
if (!HEADER_NAME.test(header)) throw new Error(`"${header}" is not a valid header name.`);
|
|
10364
|
+
if (opts.value === void 0 === (opts.valueEnv === void 0)) {
|
|
10365
|
+
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).");
|
|
10366
|
+
}
|
|
10367
|
+
const value = opts.value ?? process.env[opts.valueEnv];
|
|
10368
|
+
if (!value) throw new Error(`Environment variable ${opts.valueEnv} is empty or not set.`);
|
|
10369
|
+
const t = await target(project, opts);
|
|
10370
|
+
const name = ruleNameFor(header);
|
|
10371
|
+
const action2 = {
|
|
10372
|
+
type: "hardcode",
|
|
10373
|
+
destination: `header:${header}`,
|
|
10374
|
+
value,
|
|
10375
|
+
// Encrypted at rest and masked on every read; main-proxy decrypts it only
|
|
10376
|
+
// on the way to the upstream.
|
|
10377
|
+
...opts.secret ? { value_secret: true } : {}
|
|
10378
|
+
};
|
|
10379
|
+
const existing = (await listRules(t)).find((r) => r.name === name && r.phase === "request");
|
|
10380
|
+
const summary = `${existing ? "Update" : "Add"} transform: send ${header} to the upstream`;
|
|
10381
|
+
const spinner = opts.json ? null : (0, import_ora16.default)(summary + "...").start();
|
|
10382
|
+
try {
|
|
10383
|
+
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 });
|
|
10384
|
+
spinner?.succeed(`${summary} \u2014 ${t.proj.projectName} v${t.proj.apiVersion}${opts.secret ? " (stored encrypted)" : ""}.`);
|
|
10385
|
+
if (opts.json) console.log(JSON.stringify(out ?? { ok: true }));
|
|
10386
|
+
else console.log(import_chalk44.default.dim(` Every call the proxy forwards now carries ${header}. Your backend can reject calls without it.`));
|
|
10387
|
+
} catch (err) {
|
|
10388
|
+
spinner?.fail(summary + " failed.");
|
|
10389
|
+
throw err;
|
|
10390
|
+
}
|
|
10391
|
+
}
|
|
10392
|
+
async function runTransformRemove(project, nameOrId, opts) {
|
|
10393
|
+
const t = await target(project, opts);
|
|
10394
|
+
const rules = await listRules(t);
|
|
10395
|
+
const want = nameOrId.toLowerCase();
|
|
10396
|
+
const rule = rules.find((r) => r.id === nameOrId || r.name.toLowerCase() === want || r.name === ruleNameFor(nameOrId));
|
|
10397
|
+
if (!rule) {
|
|
10398
|
+
throw new Error(`No transform "${nameOrId}" on ${t.proj.projectName}.` + (rules.length ? ` Transforms: ${rules.map((r) => r.name).join(", ")}` : ""));
|
|
10399
|
+
}
|
|
10400
|
+
await producer(t.caller, {
|
|
10401
|
+
method: "DELETE",
|
|
10402
|
+
path: `${t.base}/${encodeURIComponent(rule.id)}`,
|
|
10403
|
+
summary: `Remove transform "${rule.name}"`
|
|
10404
|
+
});
|
|
10405
|
+
if (opts.json) console.log(JSON.stringify({ ok: true, removed: rule.id }));
|
|
10406
|
+
else console.log(`${import_chalk44.default.green("\u2713")} Removed transform ${import_chalk44.default.bold(rule.name)} from ${t.proj.projectName}.`);
|
|
10407
|
+
}
|
|
10408
|
+
|
|
10146
10409
|
// src/commands/config-browse.ts
|
|
10147
|
-
var
|
|
10148
|
-
var
|
|
10410
|
+
var import_chalk53 = __toESM(require("chalk"));
|
|
10411
|
+
var import_ora24 = __toESM(require("ora"));
|
|
10149
10412
|
init_admin();
|
|
10150
10413
|
init_paging();
|
|
10151
10414
|
init_auth();
|
|
10152
10415
|
init_resolve();
|
|
10153
10416
|
|
|
10154
10417
|
// src/commands/domain.ts
|
|
10155
|
-
var
|
|
10156
|
-
var
|
|
10418
|
+
var import_chalk45 = __toESM(require("chalk"));
|
|
10419
|
+
var import_ora17 = __toESM(require("ora"));
|
|
10157
10420
|
init_admin();
|
|
10158
10421
|
init_resolve();
|
|
10422
|
+
var KINDS = ["api", "mcp", "portal", "auth"];
|
|
10423
|
+
var TENANT_KINDS = ["portal", "auth"];
|
|
10424
|
+
async function scopeFor(project, opts) {
|
|
10425
|
+
const { teamId } = await resolveTeam(opts.team);
|
|
10426
|
+
if (project) {
|
|
10427
|
+
const proj2 = await resolveProject(teamId, project, opts.apiversion);
|
|
10428
|
+
return { kind: "project", base: `/projects/${proj2.projectId}/domains`, label: `${proj2.projectName} v${proj2.apiVersion}`, apiVersion: proj2.apiVersion };
|
|
10429
|
+
}
|
|
10430
|
+
if (!opts.tenant) {
|
|
10431
|
+
console.error(import_chalk45.default.red("Give a <project> (api / mcp domains) or --tenant <slug> (portal / auth domains)."));
|
|
10432
|
+
process.exit(1);
|
|
10433
|
+
}
|
|
10434
|
+
const tenant2 = opts.tenant.toLowerCase();
|
|
10435
|
+
return {
|
|
10436
|
+
kind: "tenant",
|
|
10437
|
+
base: `/teams/${encodeURIComponent(teamId)}/tenants/${encodeURIComponent(tenant2)}/domains`,
|
|
10438
|
+
label: `tenant ${tenant2}`
|
|
10439
|
+
};
|
|
10440
|
+
}
|
|
10441
|
+
function describeKind(d) {
|
|
10442
|
+
return d?.target ? `${d.kind ?? "api"} \u2192 stands in for ${d.target}` : d?.kind ?? "api";
|
|
10443
|
+
}
|
|
10444
|
+
function statusColor2(s) {
|
|
10445
|
+
if (s === "active") return import_chalk45.default.green(s);
|
|
10446
|
+
if (s === "failed" || s === "missing" || s === "suspended") return import_chalk45.default.red(s);
|
|
10447
|
+
return import_chalk45.default.yellow(s ?? "unknown");
|
|
10448
|
+
}
|
|
10159
10449
|
async function runDomainAdd(project, opts) {
|
|
10160
10450
|
if (!opts.domain) {
|
|
10161
|
-
console.error(
|
|
10451
|
+
console.error(import_chalk45.default.red("--domain is required."));
|
|
10162
10452
|
process.exit(1);
|
|
10163
10453
|
}
|
|
10164
|
-
const
|
|
10165
|
-
|
|
10166
|
-
|
|
10454
|
+
const kind = (opts.kind ?? (project ? "api" : "")).toLowerCase();
|
|
10455
|
+
if (!KINDS.includes(kind)) {
|
|
10456
|
+
console.error(import_chalk45.default.red(`--kind must be one of ${KINDS.join(", ")}${project ? "" : " (portal or auth for a --tenant)"}.`));
|
|
10457
|
+
process.exit(1);
|
|
10458
|
+
}
|
|
10459
|
+
const tenantKind = TENANT_KINDS.includes(kind);
|
|
10460
|
+
if (tenantKind && project) {
|
|
10461
|
+
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}`));
|
|
10462
|
+
process.exit(1);
|
|
10463
|
+
}
|
|
10464
|
+
if (tenantKind && !opts.tenant) {
|
|
10465
|
+
console.error(import_chalk45.default.red(`--tenant <slug> is required for a ${kind} domain.`));
|
|
10466
|
+
process.exit(1);
|
|
10467
|
+
}
|
|
10468
|
+
if (!tenantKind && !project) {
|
|
10469
|
+
console.error(import_chalk45.default.red(`A ${kind} domain belongs to a proxy: apiblaze domain add <project> --kind ${kind} --domain ${opts.domain}`));
|
|
10470
|
+
process.exit(1);
|
|
10471
|
+
}
|
|
10472
|
+
const scope = await scopeFor(project, opts);
|
|
10473
|
+
const spinner = (0, import_ora17.default)(`Registering ${opts.domain}...`).start();
|
|
10167
10474
|
try {
|
|
10475
|
+
const body = scope.kind === "project" ? { hostname: opts.domain, api_version: scope.apiVersion, kind, ...opts.tenant ? { tenant_name: opts.tenant.toLowerCase() } : {} } : { hostname: opts.domain, kind };
|
|
10168
10476
|
const out = await admin({
|
|
10169
10477
|
method: "POST",
|
|
10170
|
-
path:
|
|
10171
|
-
|
|
10172
|
-
|
|
10173
|
-
summary: `Add custom domain ${opts.domain} to ${proj2.projectName} v${proj2.apiVersion}${opts.tenant ? ` (tenant ${opts.tenant})` : ""}`
|
|
10478
|
+
path: scope.base,
|
|
10479
|
+
body,
|
|
10480
|
+
summary: `Add custom ${kind} domain ${opts.domain} to ${scope.label}${scope.kind === "project" && opts.tenant ? ` (tenant ${opts.tenant})` : ""}`
|
|
10174
10481
|
});
|
|
10175
|
-
spinner.succeed(`Registered ${opts.domain}. Add these DNS records, then run \`apiblaze domain status\`:`);
|
|
10482
|
+
spinner.succeed(`Registered ${out?.hostname ?? opts.domain} (${describeKind(out)}). Add these DNS records, then run \`apiblaze domain status\`:`);
|
|
10176
10483
|
if (opts.json) {
|
|
10177
10484
|
console.log(JSON.stringify(out));
|
|
10178
10485
|
return;
|
|
10179
10486
|
}
|
|
10180
|
-
if (out?.cnameRecord) console.log(` ${
|
|
10181
|
-
if (out?.dcv) console.log(` ${
|
|
10182
|
-
if (out?.
|
|
10487
|
+
if (out?.cnameRecord) console.log(` ${import_chalk45.default.cyan("CNAME")} ${out.cnameRecord.name ?? opts.domain} \u2192 ${out.cnameRecord.value}`);
|
|
10488
|
+
if (out?.dcv) console.log(` ${import_chalk45.default.cyan("TXT")} ${out.dcv.name} = ${out.dcv.value}`);
|
|
10489
|
+
if (typeof out?.monthlyCents === "number") console.log(import_chalk45.default.dim(` billed $${(out.monthlyCents / 100).toFixed(2)}/month (first month pro rata)`));
|
|
10490
|
+
if (out?.id) console.log(import_chalk45.default.dim(` domain id: ${out.id}`));
|
|
10183
10491
|
} catch (err) {
|
|
10184
10492
|
spinner.fail("Domain registration failed.");
|
|
10185
10493
|
throw err;
|
|
10186
10494
|
}
|
|
10187
10495
|
}
|
|
10188
10496
|
async function runDomainList(project, opts) {
|
|
10189
|
-
const
|
|
10190
|
-
const proj2 = await resolveProject(teamId, project, opts.apiversion);
|
|
10497
|
+
const scope = await scopeFor(project, opts);
|
|
10191
10498
|
const out = await admin({
|
|
10192
10499
|
method: "GET",
|
|
10193
|
-
path:
|
|
10194
|
-
summary: `List custom domains for ${
|
|
10500
|
+
path: scope.kind === "project" ? `${scope.base}?api_version=${encodeURIComponent(scope.apiVersion)}` : scope.base,
|
|
10501
|
+
summary: `List custom domains for ${scope.label}`
|
|
10195
10502
|
});
|
|
10196
10503
|
const domains = out?.domains ?? [];
|
|
10197
10504
|
if (opts.json) {
|
|
@@ -10199,40 +10506,53 @@ async function runDomainList(project, opts) {
|
|
|
10199
10506
|
return;
|
|
10200
10507
|
}
|
|
10201
10508
|
if (!domains.length) {
|
|
10202
|
-
console.log(
|
|
10509
|
+
console.log(import_chalk45.default.yellow("No custom domains."));
|
|
10203
10510
|
return;
|
|
10204
10511
|
}
|
|
10205
|
-
for (const d of domains)
|
|
10512
|
+
for (const d of domains) {
|
|
10513
|
+
const pin = scope.kind === "project" && d.tenant ? import_chalk45.default.dim(` tenant ${d.tenant}`) : "";
|
|
10514
|
+
console.log(` ${import_chalk45.default.bold(d.hostname)} ${statusColor2(d.status)} ${import_chalk45.default.dim(describeKind(d))}${pin} ${import_chalk45.default.dim(d.id ?? "")}`);
|
|
10515
|
+
}
|
|
10206
10516
|
}
|
|
10207
10517
|
async function runDomainStatus(project, opts) {
|
|
10208
10518
|
if (!opts.id) {
|
|
10209
|
-
console.error(
|
|
10519
|
+
console.error(import_chalk45.default.red("--id <domainId> is required (see `apiblaze domain list`)."));
|
|
10210
10520
|
process.exit(1);
|
|
10211
10521
|
}
|
|
10212
|
-
const
|
|
10213
|
-
const proj2 = await resolveProject(teamId, project, opts.apiversion);
|
|
10522
|
+
const scope = await scopeFor(project, opts);
|
|
10214
10523
|
const out = await admin({
|
|
10215
10524
|
method: "GET",
|
|
10216
|
-
path:
|
|
10525
|
+
path: `${scope.base}/${encodeURIComponent(opts.id)}/status`,
|
|
10217
10526
|
summary: `Check custom-domain status`
|
|
10218
10527
|
});
|
|
10219
|
-
|
|
10528
|
+
if (opts.json) {
|
|
10529
|
+
console.log(JSON.stringify(out));
|
|
10530
|
+
return;
|
|
10531
|
+
}
|
|
10532
|
+
console.log(` ${import_chalk45.default.bold(out?.hostname ?? opts.id)}: ${statusColor2(out?.status)} ${import_chalk45.default.dim(describeKind(out))}`);
|
|
10533
|
+
if (out?.status !== "active") {
|
|
10534
|
+
if (out?.status === "suspended") console.log(import_chalk45.default.red(" Suspended for non-payment \u2014 add credit to restore it."));
|
|
10535
|
+
else if (out?.status === "missing") console.log(import_chalk45.default.red(" No longer registered at Cloudflare \u2014 remove it and add it again."));
|
|
10536
|
+
else {
|
|
10537
|
+
if (out?.cnameRecord) console.log(` ${import_chalk45.default.cyan("CNAME")} ${out.cnameRecord.name} \u2192 ${out.cnameRecord.value}`);
|
|
10538
|
+
if (out?.dcv) console.log(` ${import_chalk45.default.cyan("TXT")} ${out.dcv.name} = ${out.dcv.value}`);
|
|
10539
|
+
}
|
|
10540
|
+
}
|
|
10220
10541
|
}
|
|
10221
10542
|
async function runDomainRemove(project, opts) {
|
|
10222
10543
|
if (!opts.id) {
|
|
10223
|
-
console.error(
|
|
10544
|
+
console.error(import_chalk45.default.red("--id <domainId> is required (see `apiblaze domain list`)."));
|
|
10224
10545
|
process.exit(1);
|
|
10225
10546
|
}
|
|
10226
|
-
const
|
|
10227
|
-
const
|
|
10228
|
-
const spinner = (0, import_ora16.default)("Removing domain...").start();
|
|
10547
|
+
const scope = await scopeFor(project, opts);
|
|
10548
|
+
const spinner = (0, import_ora17.default)("Removing domain...").start();
|
|
10229
10549
|
try {
|
|
10230
10550
|
await admin({
|
|
10231
10551
|
method: "DELETE",
|
|
10232
|
-
path:
|
|
10552
|
+
path: `${scope.base}/${encodeURIComponent(opts.id)}`,
|
|
10233
10553
|
summary: `Remove custom domain ${opts.id}`
|
|
10234
10554
|
});
|
|
10235
|
-
spinner.succeed("Removed.");
|
|
10555
|
+
spinner.succeed("Removed. Billing for it stops now.");
|
|
10236
10556
|
} catch (err) {
|
|
10237
10557
|
spinner.fail("Remove failed.");
|
|
10238
10558
|
throw err;
|
|
@@ -10242,7 +10562,7 @@ async function runDomainSetBase(project, opts) {
|
|
|
10242
10562
|
const env = opts.env ?? "prod";
|
|
10243
10563
|
const { teamId } = await resolveTeam(opts.team);
|
|
10244
10564
|
const proj2 = await resolveProject(teamId, project, opts.apiversion);
|
|
10245
|
-
const spinner = (0,
|
|
10565
|
+
const spinner = (0, import_ora17.default)("Setting base domain...").start();
|
|
10246
10566
|
try {
|
|
10247
10567
|
await admin({
|
|
10248
10568
|
method: "PUT",
|
|
@@ -10258,8 +10578,8 @@ async function runDomainSetBase(project, opts) {
|
|
|
10258
10578
|
}
|
|
10259
10579
|
|
|
10260
10580
|
// src/commands/tenant.ts
|
|
10261
|
-
var
|
|
10262
|
-
var
|
|
10581
|
+
var import_chalk48 = __toESM(require("chalk"));
|
|
10582
|
+
var import_ora20 = __toESM(require("ora"));
|
|
10263
10583
|
init_admin();
|
|
10264
10584
|
init_resolve();
|
|
10265
10585
|
init_auth();
|
|
@@ -10268,20 +10588,20 @@ init_paging();
|
|
|
10268
10588
|
async function runTenantUse(query, opts) {
|
|
10269
10589
|
const creds = loadCredentials();
|
|
10270
10590
|
if (!creds) {
|
|
10271
|
-
console.error(
|
|
10591
|
+
console.error(import_chalk48.default.red("Not logged in. Run `apiblaze login` first."));
|
|
10272
10592
|
process.exit(1);
|
|
10273
10593
|
}
|
|
10274
10594
|
if (opts.clear) {
|
|
10275
10595
|
delete creds.activeTenant;
|
|
10276
10596
|
saveCredentials(creds);
|
|
10277
|
-
console.log(
|
|
10597
|
+
console.log(import_chalk48.default.green("Tenant scope cleared."));
|
|
10278
10598
|
return;
|
|
10279
10599
|
}
|
|
10280
10600
|
const { teamId } = await resolveTeam(opts.team);
|
|
10281
10601
|
const slug = await pickTenant(teamId, { message: "Scope future commands to which tenant?", initialQuery: query });
|
|
10282
10602
|
if (!slug) process.exit(1);
|
|
10283
10603
|
saveCredentials({ ...creds, activeTenant: slug });
|
|
10284
|
-
console.log(
|
|
10604
|
+
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`)"));
|
|
10285
10605
|
}
|
|
10286
10606
|
async function runTenantList(opts) {
|
|
10287
10607
|
const { teamId, teamName } = await resolveTeam(opts.team);
|
|
@@ -10305,26 +10625,26 @@ async function runTenantList(opts) {
|
|
|
10305
10625
|
return;
|
|
10306
10626
|
}
|
|
10307
10627
|
if (!tenants.length) {
|
|
10308
|
-
console.log(
|
|
10628
|
+
console.log(import_chalk48.default.yellow(opts.q ? `No tenants matching "${opts.q}".` : "No tenants."));
|
|
10309
10629
|
return;
|
|
10310
10630
|
}
|
|
10311
10631
|
for (const t of tenants) {
|
|
10312
10632
|
const name = typeof t === "string" ? t : t.tenant_name;
|
|
10313
|
-
const display = typeof t === "string" ? "" :
|
|
10314
|
-
console.log(` ${
|
|
10633
|
+
const display = typeof t === "string" ? "" : import_chalk48.default.dim(` ${t.display_name ?? ""}`);
|
|
10634
|
+
console.log(` ${import_chalk48.default.bold(name)}${display}`);
|
|
10315
10635
|
}
|
|
10316
10636
|
const total = out?.total ?? tenants.length;
|
|
10317
10637
|
if (total > tenants.length) {
|
|
10318
|
-
console.log(
|
|
10638
|
+
console.log(import_chalk48.default.dim(` \u2026 showing ${tenants.length} of ${total} \u2014 narrow with --q <search>`));
|
|
10319
10639
|
}
|
|
10320
10640
|
}
|
|
10321
10641
|
async function runTenantCreate(opts) {
|
|
10322
10642
|
if (!opts.name) {
|
|
10323
|
-
console.error(
|
|
10643
|
+
console.error(import_chalk48.default.red("--name (display name) is required."));
|
|
10324
10644
|
process.exit(1);
|
|
10325
10645
|
}
|
|
10326
10646
|
const { teamId } = await resolveTeam(opts.team);
|
|
10327
|
-
const spinner = (0,
|
|
10647
|
+
const spinner = (0, import_ora20.default)("Creating tenant...").start();
|
|
10328
10648
|
try {
|
|
10329
10649
|
const out = await admin({
|
|
10330
10650
|
method: "POST",
|
|
@@ -10332,7 +10652,7 @@ async function runTenantCreate(opts) {
|
|
|
10332
10652
|
body: { display_name: opts.name, ...opts.slug ? { tenant_name: opts.slug } : {} },
|
|
10333
10653
|
summary: `Create tenant "${opts.name}"`
|
|
10334
10654
|
});
|
|
10335
|
-
spinner.succeed(`Created tenant ${
|
|
10655
|
+
spinner.succeed(`Created tenant ${import_chalk48.default.bold(out?.tenant_name ?? opts.name)}.`);
|
|
10336
10656
|
if (opts.json) console.log(JSON.stringify(out));
|
|
10337
10657
|
} catch (err) {
|
|
10338
10658
|
spinner.fail("Tenant create failed.");
|
|
@@ -10341,12 +10661,12 @@ async function runTenantCreate(opts) {
|
|
|
10341
10661
|
}
|
|
10342
10662
|
async function runTenantAttach(project, opts) {
|
|
10343
10663
|
if (!opts.tenant) {
|
|
10344
|
-
console.error(
|
|
10664
|
+
console.error(import_chalk48.default.red("--tenant <slug> is required."));
|
|
10345
10665
|
process.exit(1);
|
|
10346
10666
|
}
|
|
10347
10667
|
const { teamId } = await resolveTeam(opts.team);
|
|
10348
10668
|
const proj2 = await resolveProject(teamId, project, opts.apiversion);
|
|
10349
|
-
const spinner = (0,
|
|
10669
|
+
const spinner = (0, import_ora20.default)("Attaching tenant...").start();
|
|
10350
10670
|
try {
|
|
10351
10671
|
const out = await admin({
|
|
10352
10672
|
method: "POST",
|
|
@@ -10375,11 +10695,11 @@ async function runTenantDelete(slug, opts) {
|
|
|
10375
10695
|
}
|
|
10376
10696
|
]);
|
|
10377
10697
|
if (!confirm2) {
|
|
10378
|
-
console.log(
|
|
10698
|
+
console.log(import_chalk48.default.dim("Aborted."));
|
|
10379
10699
|
return;
|
|
10380
10700
|
}
|
|
10381
10701
|
}
|
|
10382
|
-
const spinner = (0,
|
|
10702
|
+
const spinner = (0, import_ora20.default)(hard ? "Trashing tenant..." : "Deleting tenant...").start();
|
|
10383
10703
|
try {
|
|
10384
10704
|
await admin({
|
|
10385
10705
|
method: "DELETE",
|
|
@@ -10403,7 +10723,7 @@ async function runTenantDelete(slug, opts) {
|
|
|
10403
10723
|
summary: `Purge tenant ${slug} (full cascade)`
|
|
10404
10724
|
});
|
|
10405
10725
|
} catch (err) {
|
|
10406
|
-
console.log(
|
|
10726
|
+
console.log(import_chalk48.default.red("\u2716 Tenant purge failed to start."));
|
|
10407
10727
|
throw err;
|
|
10408
10728
|
}
|
|
10409
10729
|
const job = await settleDelete(accepted, {
|
|
@@ -10419,18 +10739,18 @@ async function runTenantDelete(slug, opts) {
|
|
|
10419
10739
|
...accepted?.job_id ? { job_id: accepted.job_id, job_status: job?.status ?? "queued" } : {}
|
|
10420
10740
|
}));
|
|
10421
10741
|
} else if (opts.wait !== false) {
|
|
10422
|
-
console.log(
|
|
10742
|
+
console.log(import_chalk48.default.green(`\u2714 Deleted tenant ${slug}.`));
|
|
10423
10743
|
}
|
|
10424
10744
|
}
|
|
10425
10745
|
async function runTenantCors(opts) {
|
|
10426
10746
|
if (!opts.tenant) {
|
|
10427
|
-
console.error(
|
|
10747
|
+
console.error(import_chalk48.default.red("--tenant <slug> is required."));
|
|
10428
10748
|
process.exit(1);
|
|
10429
10749
|
}
|
|
10430
10750
|
const { teamId } = await resolveTeam(opts.team);
|
|
10431
10751
|
const origins = (opts.origins ?? "").split(",").map((s) => s.trim()).filter(Boolean);
|
|
10432
10752
|
const cors = origins.length ? { allowed_origins: origins } : null;
|
|
10433
|
-
const spinner = (0,
|
|
10753
|
+
const spinner = (0, import_ora20.default)("Updating CORS...").start();
|
|
10434
10754
|
try {
|
|
10435
10755
|
await admin({
|
|
10436
10756
|
method: "PUT",
|
|
@@ -10446,8 +10766,8 @@ async function runTenantCors(opts) {
|
|
|
10446
10766
|
}
|
|
10447
10767
|
|
|
10448
10768
|
// src/commands/tenant-drill.ts
|
|
10449
|
-
var
|
|
10450
|
-
var
|
|
10769
|
+
var import_chalk49 = __toESM(require("chalk"));
|
|
10770
|
+
var import_ora21 = __toESM(require("ora"));
|
|
10451
10771
|
var import_crypto = require("crypto");
|
|
10452
10772
|
init_admin();
|
|
10453
10773
|
init_auth();
|
|
@@ -10477,17 +10797,17 @@ async function validScopedTenant(teamId, query) {
|
|
|
10477
10797
|
const next = { ...creds };
|
|
10478
10798
|
delete next.activeTenant;
|
|
10479
10799
|
saveCredentials2(next);
|
|
10480
|
-
console.log(
|
|
10800
|
+
console.log(import_chalk49.default.yellow(`Tenant scope "${scoped}" no longer exists in this team \u2014 cleared.`));
|
|
10481
10801
|
return void 0;
|
|
10482
10802
|
}
|
|
10483
10803
|
async function tenantHome(teamId, tenant2) {
|
|
10484
10804
|
const { default: inquirer3 } = await import("inquirer");
|
|
10485
10805
|
const base2 = `/teams/${encodeURIComponent(teamId)}/tenants/${encodeURIComponent(tenant2)}`;
|
|
10486
|
-
console.log(
|
|
10806
|
+
console.log(import_chalk49.default.bold(`
|
|
10487
10807
|
Tenant ${tenant2}`));
|
|
10488
|
-
console.log(
|
|
10808
|
+
console.log(import_chalk49.default.dim("Tenant auth/settings are SHARED: changes apply to every proxy this tenant serves.\n"));
|
|
10489
10809
|
for (; ; ) {
|
|
10490
|
-
const spinner = (0,
|
|
10810
|
+
const spinner = (0, import_ora21.default)("Reading tenant state...").start();
|
|
10491
10811
|
const [iam, cors, emails, issuers, opaque, clients] = await Promise.all([
|
|
10492
10812
|
admin({ method: "GET", path: `${base2}/iam`, summary: "Read IAM toggle" }).catch(() => null),
|
|
10493
10813
|
admin({ method: "GET", path: `${base2}/cors`, summary: "Read tenant CORS" }).catch(() => null),
|
|
@@ -10501,18 +10821,18 @@ Tenant ${tenant2}`));
|
|
|
10501
10821
|
const nClients = Array.isArray(clients) ? clients.length : 0;
|
|
10502
10822
|
const nOpaque = opaque?.opaque?.endpoint ? 1 : 0;
|
|
10503
10823
|
const nLogin = nClients + nIssuers + nOpaque;
|
|
10504
|
-
const onOff = (b) => b ?
|
|
10824
|
+
const onOff = (b) => b ? import_chalk49.default.green("on") : import_chalk49.default.dim("off");
|
|
10505
10825
|
const { pick: pick2 } = await inquirer3.prompt([{
|
|
10506
10826
|
type: "list",
|
|
10507
10827
|
name: "pick",
|
|
10508
10828
|
message: `Tenant ${tenant2}:`,
|
|
10509
10829
|
pageSize: 12,
|
|
10510
10830
|
choices: [
|
|
10511
|
-
{ name: `Login methods (${nLogin}) ${
|
|
10512
|
-
{ name: `Users & groups: ${onOff(iam?.iam_enabled)} ${
|
|
10513
|
-
{ name: `Portal admins (${nEmails}) ${
|
|
10514
|
-
new inquirer3.Separator(
|
|
10515
|
-
{ name: `CORS: ${cors?.cors ?
|
|
10831
|
+
{ name: `Login methods (${nLogin}) ${import_chalk49.default.dim("how your consumers sign in")}`, value: "login" },
|
|
10832
|
+
{ name: `Users & groups: ${onOff(iam?.iam_enabled)} ${import_chalk49.default.dim(`identity & key management (iam.apiblaze.com)`)}`, value: "iam" },
|
|
10833
|
+
{ name: `Portal admins (${nEmails}) ${import_chalk49.default.dim("emails allowed to administer this tenant's portal")}`, value: "emails" },
|
|
10834
|
+
new inquirer3.Separator(import_chalk49.default.dim(" Settings")),
|
|
10835
|
+
{ 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" },
|
|
10516
10836
|
{ name: "\u2190 Back", value: "back" }
|
|
10517
10837
|
]
|
|
10518
10838
|
}]);
|
|
@@ -10525,7 +10845,7 @@ Tenant ${tenant2}`));
|
|
|
10525
10845
|
case "iam": {
|
|
10526
10846
|
const { v } = await inquirer3.prompt([{ type: "confirm", name: "v", message: "Enable Users & groups?", default: !!iam?.iam_enabled }]);
|
|
10527
10847
|
await admin({ method: "PATCH", path: `${base2}/iam`, body: { enabled: v }, summary: `IAM enforcement \u2192 ${v ? "on" : "off"}` });
|
|
10528
|
-
console.log(
|
|
10848
|
+
console.log(import_chalk49.default.green(` Users & groups ${v ? "enabled" : "disabled"}.`));
|
|
10529
10849
|
break;
|
|
10530
10850
|
}
|
|
10531
10851
|
case "cors": {
|
|
@@ -10538,11 +10858,11 @@ Tenant ${tenant2}`));
|
|
|
10538
10858
|
if (v === "") break;
|
|
10539
10859
|
const parsed = v === "null" ? null : safeJson(v);
|
|
10540
10860
|
if (parsed === void 0) {
|
|
10541
|
-
console.log(
|
|
10861
|
+
console.log(import_chalk49.default.yellow(" Not valid JSON \u2014 unchanged."));
|
|
10542
10862
|
break;
|
|
10543
10863
|
}
|
|
10544
10864
|
await admin({ method: "PUT", path: `${base2}/cors`, body: { cors: parsed }, summary: "Set tenant CORS" });
|
|
10545
|
-
console.log(
|
|
10865
|
+
console.log(import_chalk49.default.green(" CORS updated."));
|
|
10546
10866
|
break;
|
|
10547
10867
|
}
|
|
10548
10868
|
case "emails":
|
|
@@ -10554,7 +10874,7 @@ Tenant ${tenant2}`));
|
|
|
10554
10874
|
async function loginMethodsMenu(teamId, tenant2, base2) {
|
|
10555
10875
|
const { default: inquirer3 } = await import("inquirer");
|
|
10556
10876
|
for (; ; ) {
|
|
10557
|
-
const spinner = (0,
|
|
10877
|
+
const spinner = (0, import_ora21.default)("Loading login methods...").start();
|
|
10558
10878
|
const [rawClients, rawIssuers, rawOpaque] = await Promise.all([
|
|
10559
10879
|
admin({ method: "GET", path: `${base2}/app-clients`, summary: "List APIblaze-hosted logins" }).catch(() => []),
|
|
10560
10880
|
admin({ method: "GET", path: `${base2}/external-issuers`, summary: "List your-own-JWT logins" }).catch(() => null),
|
|
@@ -10566,15 +10886,15 @@ async function loginMethodsMenu(teamId, tenant2, base2) {
|
|
|
10566
10886
|
const choices = [];
|
|
10567
10887
|
for (const c of appClients) {
|
|
10568
10888
|
const nP = (c.providers ?? []).length || c.providers_count || 0;
|
|
10569
|
-
choices.push({ name: `${
|
|
10889
|
+
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 } });
|
|
10570
10890
|
}
|
|
10571
10891
|
for (const i of issuers) {
|
|
10572
|
-
choices.push({ name: `${
|
|
10892
|
+
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 } });
|
|
10573
10893
|
}
|
|
10574
10894
|
if (opaque?.endpoint) {
|
|
10575
|
-
choices.push({ name: `${
|
|
10895
|
+
choices.push({ name: `${import_chalk49.default.cyan("Your own \u2014 opaque")} ${import_chalk49.default.bold(opaque.endpoint)}`, value: { kind: "opaque", item: opaque } });
|
|
10576
10896
|
}
|
|
10577
|
-
if (!choices.length) console.log(
|
|
10897
|
+
if (!choices.length) console.log(import_chalk49.default.dim("\n No login methods yet \u2014 consumers cannot sign in until you add one."));
|
|
10578
10898
|
const { pick: pick2 } = await inquirer3.prompt([{
|
|
10579
10899
|
type: "list",
|
|
10580
10900
|
name: "pick",
|
|
@@ -10614,9 +10934,9 @@ async function addLoginMethod(teamId, tenant2, base2) {
|
|
|
10614
10934
|
message: "How do people log in?",
|
|
10615
10935
|
pageSize: 8,
|
|
10616
10936
|
choices: [
|
|
10617
|
-
{ name: `APIblaze hosted login page ${
|
|
10618
|
-
{ name: `Your own hosted login \u2014 JWT ${
|
|
10619
|
-
{ name: `Your own login \u2014 opaque token ${
|
|
10937
|
+
{ name: `APIblaze hosted login page ${import_chalk49.default.dim("we host login; pick a provider (GitHub/Google/\u2026) \u2014 easiest")}`, value: "apiblaze" },
|
|
10938
|
+
{ name: `Your own hosted login \u2014 JWT ${import_chalk49.default.dim("your identity provider issues JWTs; we trust them")}`, value: "jwt" },
|
|
10939
|
+
{ name: `Your own login \u2014 opaque token ${import_chalk49.default.dim("we validate your opaque tokens via an introspection endpoint")}`, value: "opaque" },
|
|
10620
10940
|
{ name: "\u2190 Back", value: null }
|
|
10621
10941
|
]
|
|
10622
10942
|
}]);
|
|
@@ -10640,8 +10960,8 @@ function safeJson(s) {
|
|
|
10640
10960
|
async function emailsMenu(base2, emails) {
|
|
10641
10961
|
const { default: inquirer3 } = await import("inquirer");
|
|
10642
10962
|
console.log();
|
|
10643
|
-
if (!emails.length) console.log(
|
|
10644
|
-
for (const e of emails) console.log(` ${
|
|
10963
|
+
if (!emails.length) console.log(import_chalk49.default.dim(" No consumer-admin emails."));
|
|
10964
|
+
for (const e of emails) console.log(` ${import_chalk49.default.bold(e.email ?? e)} ${import_chalk49.default.dim(e.status ?? "")}`);
|
|
10645
10965
|
const { act } = await inquirer3.prompt([{
|
|
10646
10966
|
type: "list",
|
|
10647
10967
|
name: "act",
|
|
@@ -10656,7 +10976,7 @@ async function emailsMenu(base2, emails) {
|
|
|
10656
10976
|
if (act === "add") {
|
|
10657
10977
|
const { email } = await inquirer3.prompt([{ type: "input", name: "email", message: "Email:", validate: (s) => /.+@.+\..+/.test(s) || "not an email" }]);
|
|
10658
10978
|
await admin({ method: "POST", path: `${base2}/admin-emails`, body: { email }, summary: `Add consumer-admin ${email}` });
|
|
10659
|
-
console.log(
|
|
10979
|
+
console.log(import_chalk49.default.green(` ${email} added.`));
|
|
10660
10980
|
} else {
|
|
10661
10981
|
const { e } = await inquirer3.prompt([{
|
|
10662
10982
|
type: "list",
|
|
@@ -10666,7 +10986,7 @@ async function emailsMenu(base2, emails) {
|
|
|
10666
10986
|
}]);
|
|
10667
10987
|
if (!e) return;
|
|
10668
10988
|
await admin({ method: "DELETE", path: `${base2}/admin-emails/${encodeURIComponent(e)}`, summary: `Remove consumer-admin ${e}` });
|
|
10669
|
-
console.log(
|
|
10989
|
+
console.log(import_chalk49.default.green(` ${e} removed.`));
|
|
10670
10990
|
}
|
|
10671
10991
|
}
|
|
10672
10992
|
async function addIssuer(base2) {
|
|
@@ -10687,19 +11007,19 @@ async function addIssuer(base2) {
|
|
|
10687
11007
|
body: { iss: a.iss.trim(), aud: a.aud.trim(), jwks_url: a.jwks.trim() || null, sub_semantics: a.sem, ...claim ? { claim_name: claim } : {} },
|
|
10688
11008
|
summary: `Add external issuer ${a.iss.trim()}`
|
|
10689
11009
|
});
|
|
10690
|
-
console.log(
|
|
11010
|
+
console.log(import_chalk49.default.green(" JWT login method saved."));
|
|
10691
11011
|
}
|
|
10692
11012
|
async function issuerHome(base2, issuer) {
|
|
10693
11013
|
const { default: inquirer3 } = await import("inquirer");
|
|
10694
11014
|
console.log(`
|
|
10695
|
-
${
|
|
11015
|
+
${import_chalk49.default.bold(issuer.iss)} ${import_chalk49.default.dim(`aud=${issuer.aud} \xB7 ${issuer.sub_semantics ?? ""}`)}`);
|
|
10696
11016
|
const { act } = await inquirer3.prompt([{
|
|
10697
11017
|
type: "list",
|
|
10698
11018
|
name: "act",
|
|
10699
11019
|
message: "This JWT login method:",
|
|
10700
11020
|
choices: [
|
|
10701
11021
|
{ name: "Replace it (re-enter details)", value: "edit" },
|
|
10702
|
-
{ name:
|
|
11022
|
+
{ name: import_chalk49.default.red("Delete it"), value: "rm" },
|
|
10703
11023
|
{ name: "\u2190 Back", value: "back" }
|
|
10704
11024
|
]
|
|
10705
11025
|
}]);
|
|
@@ -10715,7 +11035,7 @@ async function issuerHome(base2, issuer) {
|
|
|
10715
11035
|
path: `${base2}/external-issuers?iss=${encodeURIComponent(issuer.iss)}&aud=${encodeURIComponent(issuer.aud)}`,
|
|
10716
11036
|
summary: `Delete issuer ${issuer.iss}`
|
|
10717
11037
|
});
|
|
10718
|
-
console.log(
|
|
11038
|
+
console.log(import_chalk49.default.green(" Deleted."));
|
|
10719
11039
|
}
|
|
10720
11040
|
async function setOpaque(base2, cur) {
|
|
10721
11041
|
const { default: inquirer3 } = await import("inquirer");
|
|
@@ -10724,19 +11044,19 @@ async function setOpaque(base2, cur) {
|
|
|
10724
11044
|
{ type: "list", name: "method", message: "HTTP method:", choices: ["GET", "POST"], default: cur?.method ?? "GET" }
|
|
10725
11045
|
]);
|
|
10726
11046
|
await admin({ method: "PUT", path: `${base2}/opaque`, body: { opaque: { endpoint: a.endpoint, method: a.method } }, summary: "Set opaque validator" });
|
|
10727
|
-
console.log(
|
|
11047
|
+
console.log(import_chalk49.default.green(" Opaque login method set."));
|
|
10728
11048
|
}
|
|
10729
11049
|
async function opaqueHome(base2, cur) {
|
|
10730
11050
|
const { default: inquirer3 } = await import("inquirer");
|
|
10731
11051
|
console.log(`
|
|
10732
|
-
${
|
|
11052
|
+
${import_chalk49.default.bold(cur.endpoint)} ${import_chalk49.default.dim(cur.method ?? "GET")}`);
|
|
10733
11053
|
const { act } = await inquirer3.prompt([{
|
|
10734
11054
|
type: "list",
|
|
10735
11055
|
name: "act",
|
|
10736
11056
|
message: "This opaque login method:",
|
|
10737
11057
|
choices: [
|
|
10738
11058
|
{ name: "Replace it", value: "edit" },
|
|
10739
|
-
{ name:
|
|
11059
|
+
{ name: import_chalk49.default.red("Delete it"), value: "rm" },
|
|
10740
11060
|
{ name: "\u2190 Back", value: "back" }
|
|
10741
11061
|
]
|
|
10742
11062
|
}]);
|
|
@@ -10746,14 +11066,14 @@ async function opaqueHome(base2, cur) {
|
|
|
10746
11066
|
return;
|
|
10747
11067
|
}
|
|
10748
11068
|
await admin({ method: "PUT", path: `${base2}/opaque`, body: { opaque: null }, summary: "Clear opaque validator" });
|
|
10749
|
-
console.log(
|
|
11069
|
+
console.log(import_chalk49.default.green(" Deleted."));
|
|
10750
11070
|
}
|
|
10751
11071
|
async function clientHome(base2, summary) {
|
|
10752
11072
|
const { default: inquirer3 } = await import("inquirer");
|
|
10753
11073
|
const id = summary.clientId ?? summary.client_id;
|
|
10754
11074
|
const cBase = `${base2}/app-clients/${encodeURIComponent(id)}`;
|
|
10755
11075
|
for (; ; ) {
|
|
10756
|
-
const spinner = (0,
|
|
11076
|
+
const spinner = (0, import_ora21.default)("Reading app client...").start();
|
|
10757
11077
|
const c = await admin({ method: "GET", path: cBase, summary: `Read app client ${id}` }).catch(() => summary);
|
|
10758
11078
|
spinner.stop();
|
|
10759
11079
|
const cb = c.authorizedCallbackUrls ?? c.authorized_callback_urls ?? [];
|
|
@@ -10765,13 +11085,13 @@ async function clientHome(base2, summary) {
|
|
|
10765
11085
|
message: `${c.name ?? id}:`,
|
|
10766
11086
|
pageSize: 12,
|
|
10767
11087
|
choices: [
|
|
10768
|
-
{ name: `Login providers${nProviders ? ` (${nProviders})` : ""} ${
|
|
10769
|
-
{ name: `Callback URLs: ${cb.length ?
|
|
10770
|
-
{ name: `Scopes: ${scopes.length ?
|
|
11088
|
+
{ name: `Login providers${nProviders ? ` (${nProviders})` : ""} ${import_chalk49.default.dim("google/github/microsoft/\u2026 \u2014 how consumers sign in")}`, value: "providers" },
|
|
11089
|
+
{ name: `Callback URLs: ${cb.length ? import_chalk49.default.cyan(cb.join(", ")) : import_chalk49.default.dim("(none)")}`, value: "callbacks" },
|
|
11090
|
+
{ name: `Scopes: ${scopes.length ? import_chalk49.default.cyan(scopes.join(" ")) : import_chalk49.default.dim("(defaults)")}`, value: "scopes" },
|
|
10771
11091
|
{ name: `Token expiries: access ${c.accessTokenExpiry ?? 3600}s \xB7 id ${c.idTokenExpiry ?? 3600}s \xB7 refresh ${c.refreshTokenExpiry ?? 2592e3}s`, value: "expiries" },
|
|
10772
11092
|
{ name: "Reveal client secret", value: "secret" },
|
|
10773
11093
|
{ name: "Rotate client secret", value: "rotate" },
|
|
10774
|
-
{ name:
|
|
11094
|
+
{ name: import_chalk49.default.red("Delete this app client"), value: "delete" },
|
|
10775
11095
|
{ name: "\u2190 Back", value: "back" }
|
|
10776
11096
|
]
|
|
10777
11097
|
}]);
|
|
@@ -10784,13 +11104,13 @@ async function clientHome(base2, summary) {
|
|
|
10784
11104
|
case "callbacks": {
|
|
10785
11105
|
const { v } = await inquirer3.prompt([{ type: "input", name: "v", message: "Callback URLs (comma-separated):", default: cb.join(", ") }]);
|
|
10786
11106
|
await admin({ method: "PATCH", path: cBase, body: { authorizedCallbackUrls: parseList(v) }, summary: "Update callback URLs" });
|
|
10787
|
-
console.log(
|
|
11107
|
+
console.log(import_chalk49.default.green(" Callbacks updated."));
|
|
10788
11108
|
break;
|
|
10789
11109
|
}
|
|
10790
11110
|
case "scopes": {
|
|
10791
11111
|
const { v } = await inquirer3.prompt([{ type: "input", name: "v", message: "Scopes (space/comma-separated):", default: scopes.join(" ") }]);
|
|
10792
11112
|
await admin({ method: "PATCH", path: cBase, body: { scopes: v.split(/[\s,]+/).filter(Boolean) }, summary: "Update scopes" });
|
|
10793
|
-
console.log(
|
|
11113
|
+
console.log(import_chalk49.default.green(" Scopes updated."));
|
|
10794
11114
|
break;
|
|
10795
11115
|
}
|
|
10796
11116
|
case "expiries": {
|
|
@@ -10805,14 +11125,14 @@ async function clientHome(base2, summary) {
|
|
|
10805
11125
|
body: { accessTokenExpiry: Number(a.access), idTokenExpiry: Number(a.id), refreshTokenExpiry: Number(a.refresh) },
|
|
10806
11126
|
summary: "Update token expiries"
|
|
10807
11127
|
});
|
|
10808
|
-
console.log(
|
|
11128
|
+
console.log(import_chalk49.default.green(" Expiries updated."));
|
|
10809
11129
|
break;
|
|
10810
11130
|
}
|
|
10811
11131
|
case "secret": {
|
|
10812
11132
|
const { sure } = await inquirer3.prompt([{ type: "confirm", name: "sure", message: "Print the client secret to this terminal?", default: false }]);
|
|
10813
11133
|
if (!sure) break;
|
|
10814
11134
|
const s = await admin({ method: "GET", path: `${cBase}/secret`, summary: "Reveal client secret" });
|
|
10815
|
-
console.log(` ${
|
|
11135
|
+
console.log(` ${import_chalk49.default.bold("client_secret")}: ${import_chalk49.default.green(s?.clientSecret ?? s?.client_secret ?? JSON.stringify(s))}`);
|
|
10816
11136
|
break;
|
|
10817
11137
|
}
|
|
10818
11138
|
case "rotate": {
|
|
@@ -10820,14 +11140,14 @@ async function clientHome(base2, summary) {
|
|
|
10820
11140
|
if (!sure) break;
|
|
10821
11141
|
const fresh = randomSecret();
|
|
10822
11142
|
await admin({ method: "PATCH", path: cBase, body: { clientSecret: fresh }, summary: "Rotate client secret" });
|
|
10823
|
-
console.log(` New ${
|
|
11143
|
+
console.log(` New ${import_chalk49.default.bold("client_secret")}: ${import_chalk49.default.green(fresh)} ${import_chalk49.default.dim("(store it now)")}`);
|
|
10824
11144
|
break;
|
|
10825
11145
|
}
|
|
10826
11146
|
case "delete": {
|
|
10827
11147
|
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 }]);
|
|
10828
11148
|
if (!sure) break;
|
|
10829
11149
|
await admin({ method: "DELETE", path: cBase, summary: `Delete app client ${id}` });
|
|
10830
|
-
console.log(
|
|
11150
|
+
console.log(import_chalk49.default.green(" App client deleted."));
|
|
10831
11151
|
return;
|
|
10832
11152
|
}
|
|
10833
11153
|
}
|
|
@@ -10848,15 +11168,15 @@ var DEFAULT_SCOPES2 = {
|
|
|
10848
11168
|
async function providersMenu(cBase, clientLabel) {
|
|
10849
11169
|
const { default: inquirer3 } = await import("inquirer");
|
|
10850
11170
|
for (; ; ) {
|
|
10851
|
-
const spinner = (0,
|
|
11171
|
+
const spinner = (0, import_ora21.default)("Loading providers...").start();
|
|
10852
11172
|
const raw = await admin({ method: "GET", path: `${cBase}/providers`, summary: "List login providers" }).catch(() => []);
|
|
10853
11173
|
spinner.stop();
|
|
10854
11174
|
const providers = Array.isArray(raw) ? raw : [];
|
|
10855
11175
|
console.log();
|
|
10856
11176
|
for (const p of providers) {
|
|
10857
|
-
console.log(` ${
|
|
11177
|
+
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" : ""}`)}`);
|
|
10858
11178
|
}
|
|
10859
|
-
if (!providers.length) console.log(
|
|
11179
|
+
if (!providers.length) console.log(import_chalk49.default.dim(" No login providers \u2014 consumers cannot sign in to this client yet."));
|
|
10860
11180
|
const { act } = await inquirer3.prompt([{
|
|
10861
11181
|
type: "list",
|
|
10862
11182
|
name: "act",
|
|
@@ -10914,23 +11234,23 @@ async function providersMenu(cBase, clientLabel) {
|
|
|
10914
11234
|
body.targetServerToken = routing;
|
|
10915
11235
|
}
|
|
10916
11236
|
await admin({ method: "POST", path: `${cBase}/providers`, body, summary: `Add ${type} login provider` });
|
|
10917
|
-
console.log(
|
|
11237
|
+
console.log(import_chalk49.default.green(` ${type} provider added.`));
|
|
10918
11238
|
} else {
|
|
10919
11239
|
const { p } = await inquirer3.prompt([{
|
|
10920
11240
|
type: "list",
|
|
10921
11241
|
name: "p",
|
|
10922
11242
|
message: act === "rm" ? "Remove which provider?" : "Reveal which secret?",
|
|
10923
|
-
choices: [...providers.map((x) => ({ name: `${x.type} ${
|
|
11243
|
+
choices: [...providers.map((x) => ({ name: `${x.type} ${import_chalk49.default.dim(x.clientId || "(managed)")}`, value: x })), { name: "\u2190 Back", value: null }]
|
|
10924
11244
|
}]);
|
|
10925
11245
|
if (!p) continue;
|
|
10926
11246
|
if (act === "rm") {
|
|
10927
11247
|
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 }]);
|
|
10928
11248
|
if (!sure) continue;
|
|
10929
11249
|
await admin({ method: "DELETE", path: `${cBase}/providers/${encodeURIComponent(p.id)}`, summary: `Remove ${p.type} provider` });
|
|
10930
|
-
console.log(
|
|
11250
|
+
console.log(import_chalk49.default.green(` ${p.type} removed.`));
|
|
10931
11251
|
} else {
|
|
10932
11252
|
const s = await admin({ method: "GET", path: `${cBase}/providers/${encodeURIComponent(p.id)}/secret`, summary: `Reveal ${p.type} provider secret` });
|
|
10933
|
-
console.log(` ${
|
|
11253
|
+
console.log(` ${import_chalk49.default.bold("client_secret")}: ${import_chalk49.default.green(s?.clientSecret ?? s?.client_secret ?? JSON.stringify(s))}`);
|
|
10934
11254
|
}
|
|
10935
11255
|
}
|
|
10936
11256
|
}
|
|
@@ -10938,8 +11258,8 @@ async function providersMenu(cBase, clientLabel) {
|
|
|
10938
11258
|
|
|
10939
11259
|
// src/commands/spec.ts
|
|
10940
11260
|
var fs12 = __toESM(require("fs"));
|
|
10941
|
-
var
|
|
10942
|
-
var
|
|
11261
|
+
var import_chalk50 = __toESM(require("chalk"));
|
|
11262
|
+
var import_ora22 = __toESM(require("ora"));
|
|
10943
11263
|
init_admin();
|
|
10944
11264
|
init_api();
|
|
10945
11265
|
init_resolve();
|
|
@@ -10955,19 +11275,19 @@ async function runSpecGet(project, opts) {
|
|
|
10955
11275
|
}
|
|
10956
11276
|
async function runSpecSet(project, opts) {
|
|
10957
11277
|
if (!opts.file) {
|
|
10958
|
-
console.error(
|
|
11278
|
+
console.error(import_chalk50.default.red("--file <path> is required (OpenAPI JSON or YAML)."));
|
|
10959
11279
|
process.exit(1);
|
|
10960
11280
|
}
|
|
10961
11281
|
let specContent;
|
|
10962
11282
|
try {
|
|
10963
11283
|
specContent = fs12.readFileSync(opts.file, "utf-8");
|
|
10964
11284
|
} catch {
|
|
10965
|
-
console.error(
|
|
11285
|
+
console.error(import_chalk50.default.red(`Cannot read file: ${opts.file}`));
|
|
10966
11286
|
process.exit(1);
|
|
10967
11287
|
}
|
|
10968
11288
|
const { teamId } = await resolveTeam(opts.team);
|
|
10969
11289
|
const proj2 = await resolveProject(teamId, project, opts.apiversion);
|
|
10970
|
-
const spinner = (0,
|
|
11290
|
+
const spinner = (0, import_ora22.default)("Uploading spec...").start();
|
|
10971
11291
|
try {
|
|
10972
11292
|
const out = await admin({
|
|
10973
11293
|
method: "POST",
|
|
@@ -10991,28 +11311,28 @@ async function runSpecSet(project, opts) {
|
|
|
10991
11311
|
function printSpecChangeReport(report, projectName) {
|
|
10992
11312
|
const r = report.routes;
|
|
10993
11313
|
if (r) {
|
|
10994
|
-
for (const a of r.added) console.log(
|
|
10995
|
-
for (const d of r.removed) console.log(
|
|
10996
|
-
for (const rn of r.renamed ?? []) console.log(
|
|
10997
|
-
for (const m of r.modified ?? []) console.log(
|
|
11314
|
+
for (const a of r.added) console.log(import_chalk50.default.green(` + ${a}`));
|
|
11315
|
+
for (const d of r.removed) console.log(import_chalk50.default.red(` \u2212 ${d}`));
|
|
11316
|
+
for (const rn of r.renamed ?? []) console.log(import_chalk50.default.yellow(` ~ ${rn.from} \u2192 ${rn.to}`));
|
|
11317
|
+
for (const m of r.modified ?? []) console.log(import_chalk50.default.dim(` \xB1 ${m} (params/schema changed)`));
|
|
10998
11318
|
}
|
|
10999
|
-
if (report.applied?.mcp_regenerated) console.log(
|
|
11000
|
-
if (report.applied?.candidates_cleared) console.log(
|
|
11319
|
+
if (report.applied?.mcp_regenerated) console.log(import_chalk50.default.dim(" MCP catalogue regenerated."));
|
|
11320
|
+
if (report.applied?.candidates_cleared) console.log(import_chalk50.default.dim(` ${report.applied.candidates_cleared} route candidate(s) now documented \u2014 cleared.`));
|
|
11001
11321
|
for (const item of report.attention ?? []) {
|
|
11002
|
-
console.log(
|
|
11322
|
+
console.log(import_chalk50.default.yellow(` \u26A0 ${item.detail}`));
|
|
11003
11323
|
if (item.suggestion?.carry_from && item.suggestion.carry_to) {
|
|
11004
|
-
console.log(
|
|
11324
|
+
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}"`));
|
|
11005
11325
|
}
|
|
11006
11326
|
}
|
|
11007
11327
|
}
|
|
11008
11328
|
async function runSpecCarry(project, opts) {
|
|
11009
11329
|
if (!opts.method || !opts.from || !opts.to) {
|
|
11010
|
-
console.error(
|
|
11330
|
+
console.error(import_chalk50.default.red("--method, --from and --to are required."));
|
|
11011
11331
|
process.exit(1);
|
|
11012
11332
|
}
|
|
11013
11333
|
const { teamId } = await resolveTeam(opts.team);
|
|
11014
11334
|
const proj2 = await resolveProject(teamId, project, opts.apiversion);
|
|
11015
|
-
const spinner = (0,
|
|
11335
|
+
const spinner = (0, import_ora22.default)(`Carrying rules ${opts.from} \u2192 ${opts.to}...`).start();
|
|
11016
11336
|
const res = await agentCall(`/projects/${proj2.projectId}/${proj2.apiVersion}/policies/route/carry`, "POST", {
|
|
11017
11337
|
method: opts.method.toUpperCase(),
|
|
11018
11338
|
from_resource: opts.from,
|
|
@@ -11029,13 +11349,13 @@ async function runSpecCarry(project, opts) {
|
|
|
11029
11349
|
}
|
|
11030
11350
|
async function runSpecDeleteRule(project, opts) {
|
|
11031
11351
|
if (!opts.method || !opts.route) {
|
|
11032
|
-
console.error(
|
|
11352
|
+
console.error(import_chalk50.default.red("--method and --route are required."));
|
|
11033
11353
|
process.exit(1);
|
|
11034
11354
|
}
|
|
11035
11355
|
const { teamId } = await resolveTeam(opts.team);
|
|
11036
11356
|
const proj2 = await resolveProject(teamId, project, opts.apiversion);
|
|
11037
11357
|
const encoded = opts.route.replace(/\{/g, "%7B").replace(/\}/g, "%7D");
|
|
11038
|
-
const spinner = (0,
|
|
11358
|
+
const spinner = (0, import_ora22.default)(`Deleting rules for ${opts.method.toUpperCase()} ${opts.route}...`).start();
|
|
11039
11359
|
const res = await agentCall(
|
|
11040
11360
|
`/projects/${proj2.projectId}/${proj2.apiVersion}/policies/route/${opts.method.toUpperCase()}${encoded.startsWith("/") ? encoded : `/${encoded}`}`,
|
|
11041
11361
|
"DELETE"
|
|
@@ -11049,14 +11369,14 @@ async function runSpecDeleteRule(project, opts) {
|
|
|
11049
11369
|
}
|
|
11050
11370
|
|
|
11051
11371
|
// src/commands/agent.ts
|
|
11052
|
-
var
|
|
11053
|
-
var
|
|
11372
|
+
var import_chalk52 = __toESM(require("chalk"));
|
|
11373
|
+
var import_ora23 = __toESM(require("ora"));
|
|
11054
11374
|
init_auth();
|
|
11055
11375
|
init_admin();
|
|
11056
11376
|
init_resolve();
|
|
11057
11377
|
|
|
11058
11378
|
// src/lib/tools.ts
|
|
11059
|
-
var
|
|
11379
|
+
var import_chalk51 = __toESM(require("chalk"));
|
|
11060
11380
|
init_admin();
|
|
11061
11381
|
init_api();
|
|
11062
11382
|
init_resolve();
|
|
@@ -11087,15 +11407,15 @@ var TOOLS = [
|
|
|
11087
11407
|
const key = keys.dev ?? Object.values(keys)[0];
|
|
11088
11408
|
const url = `https://${a.name}.abz.run/${version2}/dev`;
|
|
11089
11409
|
const tryIt = buildTryItCurl(url, auth, key, res.tenant);
|
|
11090
|
-
const lines = [` ${
|
|
11091
|
-
if (res.devPortal) lines.push(` ${
|
|
11410
|
+
const lines = [` ${import_chalk51.default.dim("Proxy URL:")} ${import_chalk51.default.bold(url)}`];
|
|
11411
|
+
if (res.devPortal) lines.push(` ${import_chalk51.default.dim("Dev portal:")} ${res.devPortal}`);
|
|
11092
11412
|
const envs = Object.keys(keys);
|
|
11093
11413
|
if (envs.length) {
|
|
11094
|
-
lines.push("", ` ${
|
|
11414
|
+
lines.push("", ` ${import_chalk51.default.bold("API keys")} ${import_chalk51.default.dim("(bootstrapped \u2014 send as the X-API-Key header; shown once):")}`);
|
|
11095
11415
|
const w = Math.max(...envs.map((e) => e.length));
|
|
11096
|
-
for (const env of envs) lines.push(` ${
|
|
11416
|
+
for (const env of envs) lines.push(` ${import_chalk51.default.cyan(env.padEnd(w))} ${import_chalk51.default.green(keys[env])}`);
|
|
11097
11417
|
}
|
|
11098
|
-
if (tryIt) lines.push("", ` ${
|
|
11418
|
+
if (tryIt) lines.push("", ` ${import_chalk51.default.dim("Try it:")}`, ` ${import_chalk51.default.cyan(tryIt)}`);
|
|
11099
11419
|
return { ...res, proxy_url: url, keys, ...tryIt ? { try_it: tryIt } : {}, display: lines.join("\n") };
|
|
11100
11420
|
}
|
|
11101
11421
|
},
|
|
@@ -11264,23 +11584,23 @@ function truncate(value, max = 1500) {
|
|
|
11264
11584
|
}
|
|
11265
11585
|
function printCost(resp) {
|
|
11266
11586
|
const line = formatBilling(resp.billing);
|
|
11267
|
-
if (line) console.log(
|
|
11587
|
+
if (line) console.log(import_chalk52.default.magenta(` ${line}`) + import_chalk52.default.dim(` (${resp.llm.model}, ${resp.llm.total_tokens} tok)`));
|
|
11268
11588
|
}
|
|
11269
11589
|
async function runAgent(opts) {
|
|
11270
11590
|
requireAuth();
|
|
11271
11591
|
const { teamId, teamName } = await resolveTeam(opts.team);
|
|
11272
11592
|
const { default: inquirer3 } = await import("inquirer");
|
|
11273
|
-
console.log(
|
|
11274
|
-
console.log(
|
|
11593
|
+
console.log(import_chalk52.default.bold("APIblaze agent") + import_chalk52.default.dim(` \xB7 team ${teamName ?? teamId}`));
|
|
11594
|
+
console.log(import_chalk52.default.dim('Ask me to create/delete/configure proxies, tenants, keys, domains, specs. Type "exit" to quit.\n'));
|
|
11275
11595
|
const history = [];
|
|
11276
11596
|
while (true) {
|
|
11277
|
-
const { input } = await inquirer3.prompt([{ type: "input", name: "input", message:
|
|
11597
|
+
const { input } = await inquirer3.prompt([{ type: "input", name: "input", message: import_chalk52.default.cyan("you") + " \u203A" }]);
|
|
11278
11598
|
const text = (input ?? "").trim();
|
|
11279
11599
|
if (!text) continue;
|
|
11280
11600
|
if (["exit", "quit", ":q"].includes(text.toLowerCase())) break;
|
|
11281
11601
|
history.push({ role: "user", content: text });
|
|
11282
11602
|
for (let step = 0; step < MAX_TOOL_STEPS; step++) {
|
|
11283
|
-
const spinner = (0,
|
|
11603
|
+
const spinner = (0, import_ora23.default)({ text: "thinking...", color: "magenta" }).start();
|
|
11284
11604
|
let resp;
|
|
11285
11605
|
try {
|
|
11286
11606
|
resp = await callAgent(history, teamId);
|
|
@@ -11288,21 +11608,21 @@ async function runAgent(opts) {
|
|
|
11288
11608
|
} catch (err) {
|
|
11289
11609
|
spinner.stop();
|
|
11290
11610
|
if (err instanceof ApiError && err.status === 402) {
|
|
11291
|
-
console.log(
|
|
11611
|
+
console.log(import_chalk52.default.yellow(" Insufficient credits \u2014 top up to keep using the agent."));
|
|
11292
11612
|
break;
|
|
11293
11613
|
}
|
|
11294
11614
|
throw err;
|
|
11295
11615
|
}
|
|
11296
11616
|
history.push({ role: "assistant", content: resp.raw });
|
|
11297
11617
|
printCost(resp);
|
|
11298
|
-
if (resp.reply) console.log(
|
|
11618
|
+
if (resp.reply) console.log(import_chalk52.default.green("agent") + " \u203A " + resp.reply);
|
|
11299
11619
|
if (!resp.action) break;
|
|
11300
11620
|
const tool = findTool(resp.action.tool);
|
|
11301
11621
|
if (!tool) {
|
|
11302
11622
|
history.push({ role: "user", content: `TOOL_RESULT ${resp.action.tool}: error \u2014 unknown tool` });
|
|
11303
11623
|
continue;
|
|
11304
11624
|
}
|
|
11305
|
-
const runSpinner = (0,
|
|
11625
|
+
const runSpinner = (0, import_ora23.default)({ text: `running ${tool.name}...`, color: "cyan" }).start();
|
|
11306
11626
|
try {
|
|
11307
11627
|
const result = await tool.run(resp.action.args, { teamId });
|
|
11308
11628
|
runSpinner.succeed(`${tool.name} \u2713`);
|
|
@@ -11320,11 +11640,11 @@ async function runAgent(opts) {
|
|
|
11320
11640
|
}
|
|
11321
11641
|
renderTrace();
|
|
11322
11642
|
if (step === MAX_TOOL_STEPS - 1) {
|
|
11323
|
-
console.log(
|
|
11643
|
+
console.log(import_chalk52.default.dim(" (paused after several steps \u2014 tell me how to continue)"));
|
|
11324
11644
|
}
|
|
11325
11645
|
}
|
|
11326
11646
|
}
|
|
11327
|
-
console.log(
|
|
11647
|
+
console.log(import_chalk52.default.dim("\nBye."));
|
|
11328
11648
|
}
|
|
11329
11649
|
|
|
11330
11650
|
// src/commands/config-browse.ts
|
|
@@ -11632,11 +11952,11 @@ function dig(blob, dotted) {
|
|
|
11632
11952
|
}
|
|
11633
11953
|
var readSetting = (s, cfg) => s.read ? s.read(cfg) : dig(cfg, s.key);
|
|
11634
11954
|
function show(v) {
|
|
11635
|
-
if (v === void 0) return
|
|
11636
|
-
if (v === null) return
|
|
11637
|
-
if (typeof v === "object") return
|
|
11638
|
-
if (typeof v === "boolean") return v ?
|
|
11639
|
-
return
|
|
11955
|
+
if (v === void 0) return import_chalk53.default.dim("(unset)");
|
|
11956
|
+
if (v === null) return import_chalk53.default.dim("null");
|
|
11957
|
+
if (typeof v === "object") return import_chalk53.default.cyan(JSON.stringify(v));
|
|
11958
|
+
if (typeof v === "boolean") return v ? import_chalk53.default.green("on") : import_chalk53.default.red("off");
|
|
11959
|
+
return import_chalk53.default.cyan(String(v));
|
|
11640
11960
|
}
|
|
11641
11961
|
function parseValue(raw) {
|
|
11642
11962
|
if (raw === "true") return true;
|
|
@@ -11671,7 +11991,7 @@ async function patchSetting(proj2, s, value, cfg) {
|
|
|
11671
11991
|
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.`);
|
|
11672
11992
|
body.tenant = tenant2;
|
|
11673
11993
|
}
|
|
11674
|
-
const spinner = (0,
|
|
11994
|
+
const spinner = (0, import_ora24.default)(`Set ${s.key}...`).start();
|
|
11675
11995
|
try {
|
|
11676
11996
|
await admin({
|
|
11677
11997
|
method: "PATCH",
|
|
@@ -11686,10 +12006,10 @@ async function patchSetting(proj2, s, value, cfg) {
|
|
|
11686
12006
|
}
|
|
11687
12007
|
}
|
|
11688
12008
|
var loginFirst = (what) => {
|
|
11689
|
-
console.log(
|
|
12009
|
+
console.log(import_chalk53.default.yellow(`
|
|
11690
12010
|
Log in first to ${what}.`));
|
|
11691
|
-
console.log(
|
|
11692
|
-
console.log(
|
|
12011
|
+
console.log(import_chalk53.default.dim(" Run `npx apiblaze login` \u2014 or `npx apiblaze claim` if you created this proxy"));
|
|
12012
|
+
console.log(import_chalk53.default.dim(" anonymously and want to bring it into your account.\n"));
|
|
11693
12013
|
};
|
|
11694
12014
|
async function runConfig(project, key, value, opts) {
|
|
11695
12015
|
const creds = loadCredentials();
|
|
@@ -11706,9 +12026,9 @@ async function runConfig(project, key, value, opts) {
|
|
|
11706
12026
|
}
|
|
11707
12027
|
const setting = SETTINGS.find((s) => s.key === key);
|
|
11708
12028
|
if (!setting) {
|
|
11709
|
-
console.error(
|
|
11710
|
-
console.error(
|
|
11711
|
-
console.error(
|
|
12029
|
+
console.error(import_chalk53.default.red(`Unknown setting "${key}".`));
|
|
12030
|
+
console.error(import_chalk53.default.dim(" Known: " + SETTINGS.map((s) => s.key).join(", ")));
|
|
12031
|
+
console.error(import_chalk53.default.dim(" (Features like transforms/domains/tenants live in the menu: `apiblaze config <project>`.)"));
|
|
11712
12032
|
process.exit(1);
|
|
11713
12033
|
}
|
|
11714
12034
|
if (value === void 0) {
|
|
@@ -11723,7 +12043,7 @@ async function pickProject(teamId) {
|
|
|
11723
12043
|
const { getProjects: getProjects2 } = await Promise.resolve().then(() => (init_api(), api_exports));
|
|
11724
12044
|
const projects = await getProjects2(teamId).catch(() => []);
|
|
11725
12045
|
if (!projects.length) {
|
|
11726
|
-
console.error(
|
|
12046
|
+
console.error(import_chalk53.default.red("No projects in this team. Create one: `npx apiblaze create`."));
|
|
11727
12047
|
process.exit(1);
|
|
11728
12048
|
}
|
|
11729
12049
|
const { default: inquirer3 } = await import("inquirer");
|
|
@@ -11731,7 +12051,7 @@ async function pickProject(teamId) {
|
|
|
11731
12051
|
type: "list",
|
|
11732
12052
|
name: "picked",
|
|
11733
12053
|
message: "Which project?",
|
|
11734
|
-
choices: projects.map((p) => ({ name: `${p.projectName} ${
|
|
12054
|
+
choices: projects.map((p) => ({ name: `${p.projectName} ${import_chalk53.default.dim("v" + p.apiVersion)}`, value: p }))
|
|
11735
12055
|
}]);
|
|
11736
12056
|
return { projectId: picked.projectId, projectName: picked.projectName, apiVersion: picked.apiVersion, teamId, tenant: picked.tenant };
|
|
11737
12057
|
}
|
|
@@ -11742,25 +12062,25 @@ function printAll(proj2, cfg, json) {
|
|
|
11742
12062
|
console.log(JSON.stringify(out, null, 2));
|
|
11743
12063
|
return;
|
|
11744
12064
|
}
|
|
11745
|
-
console.log(
|
|
12065
|
+
console.log(import_chalk53.default.bold(`
|
|
11746
12066
|
${proj2.projectName} v${proj2.apiVersion} \u2014 settings
|
|
11747
12067
|
`));
|
|
11748
12068
|
for (const group2 of SETTING_GROUPS) {
|
|
11749
|
-
console.log(
|
|
12069
|
+
console.log(import_chalk53.default.bold(group2));
|
|
11750
12070
|
for (const s of SETTINGS.filter((x) => x.group === group2)) {
|
|
11751
|
-
console.log(` ${s.key.padEnd(32)} ${show(readSetting(s, cfg))} ${
|
|
12071
|
+
console.log(` ${s.key.padEnd(32)} ${show(readSetting(s, cfg))} ${import_chalk53.default.dim(s.desc)}`);
|
|
11752
12072
|
}
|
|
11753
12073
|
console.log();
|
|
11754
12074
|
}
|
|
11755
|
-
console.log(
|
|
12075
|
+
console.log(import_chalk53.default.dim("Change one: apiblaze config " + proj2.projectName + " <key> <value> (add --verbose for the API call)"));
|
|
11756
12076
|
}
|
|
11757
12077
|
async function discoveryMenu(project) {
|
|
11758
12078
|
const { default: inquirer3 } = await import("inquirer");
|
|
11759
|
-
console.log(
|
|
12079
|
+
console.log(import_chalk53.default.bold(`
|
|
11760
12080
|
APIblaze proxy configuration${project ? ` \u2014 ${project}` : ""}
|
|
11761
12081
|
`));
|
|
11762
|
-
console.log(
|
|
11763
|
-
console.log(
|
|
12082
|
+
console.log(import_chalk53.default.dim("You are not logged in \u2014 browsing what's configurable. Everything below works"));
|
|
12083
|
+
console.log(import_chalk53.default.dim("from this menu once you log in (`npx apiblaze login`).\n"));
|
|
11764
12084
|
for (; ; ) {
|
|
11765
12085
|
const { pick: pick2 } = await inquirer3.prompt([{
|
|
11766
12086
|
type: "list",
|
|
@@ -11768,13 +12088,13 @@ APIblaze proxy configuration${project ? ` \u2014 ${project}` : ""}
|
|
|
11768
12088
|
message: "Explore:",
|
|
11769
12089
|
pageSize: 20,
|
|
11770
12090
|
choices: [
|
|
11771
|
-
new inquirer3.Separator(
|
|
12091
|
+
new inquirer3.Separator(import_chalk53.default.bold("\u2014 Settings \u2014")),
|
|
11772
12092
|
...SETTING_GROUPS.map((g) => ({
|
|
11773
|
-
name: `${g} ${
|
|
12093
|
+
name: `${g} ${import_chalk53.default.dim(SETTINGS.filter((s) => s.group === g).map((s) => s.label).join(", "))}`,
|
|
11774
12094
|
value: { kind: "settings", g }
|
|
11775
12095
|
})),
|
|
11776
|
-
new inquirer3.Separator(
|
|
11777
|
-
...FEATURES.map((f) => ({ name: `${f.label} ${
|
|
12096
|
+
new inquirer3.Separator(import_chalk53.default.bold("\u2014 Features \u2014")),
|
|
12097
|
+
...FEATURES.map((f) => ({ name: `${f.label} ${import_chalk53.default.dim(f.desc)}`, value: { kind: "feature", f } })),
|
|
11778
12098
|
new inquirer3.Separator(),
|
|
11779
12099
|
{ name: "Exit", value: { kind: "exit" } }
|
|
11780
12100
|
]
|
|
@@ -11783,24 +12103,24 @@ APIblaze proxy configuration${project ? ` \u2014 ${project}` : ""}
|
|
|
11783
12103
|
if (pick2.kind === "settings") {
|
|
11784
12104
|
console.log();
|
|
11785
12105
|
for (const s of SETTINGS.filter((x) => x.group === pick2.g)) {
|
|
11786
|
-
console.log(` ${
|
|
11787
|
-
console.log(` ${
|
|
12106
|
+
console.log(` ${import_chalk53.default.bold(s.label.padEnd(28))} ${import_chalk53.default.dim(s.desc)}`);
|
|
12107
|
+
console.log(` ${import_chalk53.default.dim(" key: " + s.key)}`);
|
|
11788
12108
|
}
|
|
11789
12109
|
loginFirst("view or change these settings");
|
|
11790
12110
|
} else {
|
|
11791
12111
|
const f = pick2.f;
|
|
11792
12112
|
console.log(`
|
|
11793
|
-
${
|
|
12113
|
+
${import_chalk53.default.bold(f.label)} \u2014 ${f.desc}`);
|
|
11794
12114
|
loginFirst(`use ${f.label.toLowerCase()}`);
|
|
11795
12115
|
}
|
|
11796
12116
|
}
|
|
11797
12117
|
}
|
|
11798
12118
|
async function navigator(proj2, cfg, opts) {
|
|
11799
12119
|
const { default: inquirer3 } = await import("inquirer");
|
|
11800
|
-
console.log(
|
|
12120
|
+
console.log(import_chalk53.default.bold(`
|
|
11801
12121
|
${proj2.projectName} v${proj2.apiVersion} \u2014 configuration
|
|
11802
12122
|
`));
|
|
11803
|
-
console.log(
|
|
12123
|
+
console.log(import_chalk53.default.dim("Tip: every change is one API call \u2014 add --verbose to see the curl equivalent.\n"));
|
|
11804
12124
|
let blob = cfg;
|
|
11805
12125
|
for (; ; ) {
|
|
11806
12126
|
const { pick: pick2 } = await inquirer3.prompt([{
|
|
@@ -11809,10 +12129,10 @@ ${proj2.projectName} v${proj2.apiVersion} \u2014 configuration
|
|
|
11809
12129
|
message: "Where to?",
|
|
11810
12130
|
pageSize: 20,
|
|
11811
12131
|
choices: [
|
|
11812
|
-
new inquirer3.Separator(
|
|
12132
|
+
new inquirer3.Separator(import_chalk53.default.bold("\u2014 Settings \u2014")),
|
|
11813
12133
|
...SETTING_GROUPS.map((g) => ({ name: g, value: { kind: "settings", g } })),
|
|
11814
|
-
new inquirer3.Separator(
|
|
11815
|
-
...FEATURES.map((f) => ({ name: `${f.label} ${
|
|
12134
|
+
new inquirer3.Separator(import_chalk53.default.bold("\u2014 Features \u2014")),
|
|
12135
|
+
...FEATURES.map((f) => ({ name: `${f.label} ${import_chalk53.default.dim(f.desc)}`, value: { kind: f.go } })),
|
|
11816
12136
|
new inquirer3.Separator(),
|
|
11817
12137
|
{ name: "Show all settings", value: { kind: "list" } },
|
|
11818
12138
|
{ name: "Exit", value: { kind: "exit" } }
|
|
@@ -11852,7 +12172,7 @@ ${proj2.projectName} v${proj2.apiVersion} \u2014 configuration
|
|
|
11852
12172
|
}
|
|
11853
12173
|
}
|
|
11854
12174
|
} catch (err) {
|
|
11855
|
-
console.error(
|
|
12175
|
+
console.error(import_chalk53.default.red(` ${err instanceof Error ? err.message : String(err)}`));
|
|
11856
12176
|
}
|
|
11857
12177
|
}
|
|
11858
12178
|
}
|
|
@@ -11866,7 +12186,7 @@ async function settingsGroup(proj2, cfg, group2) {
|
|
|
11866
12186
|
message: group2 + ":",
|
|
11867
12187
|
pageSize: 16,
|
|
11868
12188
|
choices: [
|
|
11869
|
-
...items.map((s2) => ({ name: `${s2.label.padEnd(30)} ${show(readSetting(s2, cfg))} ${
|
|
12189
|
+
...items.map((s2) => ({ name: `${s2.label.padEnd(30)} ${show(readSetting(s2, cfg))} ${import_chalk53.default.dim(s2.desc)}`, value: s2 })),
|
|
11870
12190
|
new inquirer3.Separator(),
|
|
11871
12191
|
{ name: "\u2190 Back", value: null }
|
|
11872
12192
|
]
|
|
@@ -11884,7 +12204,7 @@ async function settingsGroup(proj2, cfg, group2) {
|
|
|
11884
12204
|
} else if (s.type === "number") {
|
|
11885
12205
|
const { v } = await inquirer3.prompt([{ type: "input", name: "v", message: `${s.label} (number):`, default: readSetting(s, cfg) }]);
|
|
11886
12206
|
if (v === "" || Number.isNaN(Number(v))) {
|
|
11887
|
-
console.log(
|
|
12207
|
+
console.log(import_chalk53.default.yellow(" Not a number \u2014 unchanged."));
|
|
11888
12208
|
continue;
|
|
11889
12209
|
}
|
|
11890
12210
|
value = Number(v);
|
|
@@ -11965,7 +12285,7 @@ async function buildCondition(phase) {
|
|
|
11965
12285
|
const items = [];
|
|
11966
12286
|
for (; ; ) {
|
|
11967
12287
|
const a = await inquirer3.prompt([
|
|
11968
|
-
{ type: "input", name: "source", message: `Condition field ${
|
|
12288
|
+
{ type: "input", name: "source", message: `Condition field ${import_chalk53.default.dim(srcHint)}:`, validate: (s) => !!s || "required" },
|
|
11969
12289
|
{ type: "list", name: "operator", message: "Operator:", choices: [
|
|
11970
12290
|
"eq",
|
|
11971
12291
|
"neq",
|
|
@@ -11996,7 +12316,7 @@ async function buildCondition(phase) {
|
|
|
11996
12316
|
function showCondition(cond) {
|
|
11997
12317
|
if (!Array.isArray(cond) || !cond.length) return "";
|
|
11998
12318
|
const s = cond.map((c) => `${c.source} ${c.operator}${c.value !== void 0 ? ` "${c.value}"` : ""}${c.logicOp ? ` ${c.logicOp}` : ""}`).join(" ");
|
|
11999
|
-
return
|
|
12319
|
+
return import_chalk53.default.dim(` when ${s}`);
|
|
12000
12320
|
}
|
|
12001
12321
|
async function transformsMenu(proj2) {
|
|
12002
12322
|
const { default: inquirer3 } = await import("inquirer");
|
|
@@ -12005,12 +12325,12 @@ async function transformsMenu(proj2) {
|
|
|
12005
12325
|
const out = await admin({ method: "GET", path: base2, summary: "List transform rules" });
|
|
12006
12326
|
const rules = out?.rules ?? [];
|
|
12007
12327
|
console.log();
|
|
12008
|
-
if (!rules.length) console.log(
|
|
12328
|
+
if (!rules.length) console.log(import_chalk53.default.dim(" No transform rules yet."));
|
|
12009
12329
|
for (const r of rules) {
|
|
12010
12330
|
const a = r.action ?? {};
|
|
12011
12331
|
const fns = [...a.source_fns ?? [], ...a.dest_fns ?? []].map((f) => f.fn);
|
|
12012
|
-
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 ?
|
|
12013
|
-
console.log(` ${r.enabled ?
|
|
12332
|
+
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")}`) : ""}`;
|
|
12333
|
+
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)}`);
|
|
12014
12334
|
}
|
|
12015
12335
|
const { act } = await inquirer3.prompt([{
|
|
12016
12336
|
type: "list",
|
|
@@ -12022,7 +12342,7 @@ async function transformsMenu(proj2) {
|
|
|
12022
12342
|
{ name: "Enable/disable a rule", value: "toggle" },
|
|
12023
12343
|
{ name: "Delete a rule", value: "delete" }
|
|
12024
12344
|
] : [],
|
|
12025
|
-
{ name:
|
|
12345
|
+
{ name: import_chalk53.default.dim("Add from raw JSON (grouped conditions, lookup tables, \u2026)"), value: "raw" },
|
|
12026
12346
|
{ name: "\u2190 Back", value: "back" }
|
|
12027
12347
|
]
|
|
12028
12348
|
}]);
|
|
@@ -12035,11 +12355,11 @@ async function transformsMenu(proj2) {
|
|
|
12035
12355
|
}]);
|
|
12036
12356
|
const body = parseValue(raw);
|
|
12037
12357
|
if (!body || typeof body !== "object" || Array.isArray(body)) {
|
|
12038
|
-
console.log(
|
|
12358
|
+
console.log(import_chalk53.default.yellow(" Not a JSON object \u2014 skipped."));
|
|
12039
12359
|
continue;
|
|
12040
12360
|
}
|
|
12041
12361
|
await admin({ method: "POST", path: base2, body, summary: "Create transform rule (raw JSON)" });
|
|
12042
|
-
console.log(
|
|
12362
|
+
console.log(import_chalk53.default.green(" Rule created."));
|
|
12043
12363
|
continue;
|
|
12044
12364
|
}
|
|
12045
12365
|
if (act === "add") {
|
|
@@ -12055,7 +12375,7 @@ async function transformsMenu(proj2) {
|
|
|
12055
12375
|
{ name: "Remove a field", value: "remove" }
|
|
12056
12376
|
] }
|
|
12057
12377
|
]);
|
|
12058
|
-
const fieldHint =
|
|
12378
|
+
const fieldHint = import_chalk53.default.dim("(e.g. header:x-api-version, param:limit, bodyvar:user.id)");
|
|
12059
12379
|
let action2;
|
|
12060
12380
|
if (ans.kind === "hardcode") {
|
|
12061
12381
|
const a = await inquirer3.prompt([
|
|
@@ -12084,7 +12404,7 @@ async function transformsMenu(proj2) {
|
|
|
12084
12404
|
};
|
|
12085
12405
|
}
|
|
12086
12406
|
const condition = await buildCondition(ans.phase);
|
|
12087
|
-
const spinner = (0,
|
|
12407
|
+
const spinner = (0, import_ora24.default)("Creating rule...").start();
|
|
12088
12408
|
try {
|
|
12089
12409
|
await admin({
|
|
12090
12410
|
method: "POST",
|
|
@@ -12102,16 +12422,16 @@ async function transformsMenu(proj2) {
|
|
|
12102
12422
|
type: "list",
|
|
12103
12423
|
name: "rule",
|
|
12104
12424
|
message: act === "toggle" ? "Which rule?" : "Delete which rule?",
|
|
12105
|
-
choices: [...rules.map((r) => ({ name: `${r.name} ${
|
|
12425
|
+
choices: [...rules.map((r) => ({ name: `${r.name} ${import_chalk53.default.dim(`[${r.phase ?? "request"}]`)}`, value: r })), { name: "\u2190 Back", value: null }]
|
|
12106
12426
|
}]);
|
|
12107
12427
|
if (!rule) continue;
|
|
12108
12428
|
if (act === "toggle") {
|
|
12109
12429
|
const flipped = { ...rule, enabled: rule.enabled === false };
|
|
12110
12430
|
await admin({ method: "PUT", path: `${base2}/${rule.id}`, body: flipped, summary: `${flipped.enabled ? "Enable" : "Disable"} transform "${rule.name}"` });
|
|
12111
|
-
console.log(
|
|
12431
|
+
console.log(import_chalk53.default.green(` ${rule.name} \u2192 ${flipped.enabled ? "enabled" : "disabled"}`));
|
|
12112
12432
|
} else {
|
|
12113
12433
|
await admin({ method: "DELETE", path: `${base2}/${rule.id}`, summary: `Delete transform "${rule.name}"` });
|
|
12114
|
-
console.log(
|
|
12434
|
+
console.log(import_chalk53.default.green(` ${rule.name} deleted.`));
|
|
12115
12435
|
}
|
|
12116
12436
|
}
|
|
12117
12437
|
}
|
|
@@ -12123,9 +12443,9 @@ async function mappingsMenu(proj2) {
|
|
|
12123
12443
|
const out = await admin({ method: "GET", path: base2, summary: "List mapping tables" });
|
|
12124
12444
|
const tables = out?.mappings ?? out?.tables ?? [];
|
|
12125
12445
|
console.log();
|
|
12126
|
-
if (!tables.length) console.log(
|
|
12446
|
+
if (!tables.length) console.log(import_chalk53.default.dim(" No mapping tables yet."));
|
|
12127
12447
|
for (const t of tables) {
|
|
12128
|
-
console.log(` ${
|
|
12448
|
+
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" : ""}`)}`);
|
|
12129
12449
|
}
|
|
12130
12450
|
const { act } = await inquirer3.prompt([{
|
|
12131
12451
|
type: "list",
|
|
@@ -12145,11 +12465,11 @@ async function mappingsMenu(proj2) {
|
|
|
12145
12465
|
]);
|
|
12146
12466
|
const entries2 = parseValue(a.entries);
|
|
12147
12467
|
if (!Array.isArray(entries2)) {
|
|
12148
|
-
console.log(
|
|
12468
|
+
console.log(import_chalk53.default.yellow(" Entries must be a JSON array \u2014 not created."));
|
|
12149
12469
|
continue;
|
|
12150
12470
|
}
|
|
12151
12471
|
await admin({ method: "POST", path: base2, body: { name: a.name, entries: entries2 }, summary: `Create mapping table "${a.name}"` });
|
|
12152
|
-
console.log(
|
|
12472
|
+
console.log(import_chalk53.default.green(` Table "${a.name}" created.`));
|
|
12153
12473
|
} else {
|
|
12154
12474
|
const { table } = await inquirer3.prompt([{
|
|
12155
12475
|
type: "list",
|
|
@@ -12159,7 +12479,7 @@ async function mappingsMenu(proj2) {
|
|
|
12159
12479
|
}]);
|
|
12160
12480
|
if (!table) continue;
|
|
12161
12481
|
await admin({ method: "DELETE", path: `${base2}/${table.id}`, summary: `Delete mapping table "${table.name}"` });
|
|
12162
|
-
console.log(
|
|
12482
|
+
console.log(import_chalk53.default.green(` ${table.name} deleted.`));
|
|
12163
12483
|
}
|
|
12164
12484
|
}
|
|
12165
12485
|
}
|
|
@@ -12170,14 +12490,14 @@ async function tenantsMenu(proj2, opts) {
|
|
|
12170
12490
|
const out = await admin({ method: "GET", path: base2, summary: "List attached tenants" });
|
|
12171
12491
|
const tenants = out?.tenants ?? [];
|
|
12172
12492
|
console.log();
|
|
12173
|
-
if (!tenants.length) console.log(
|
|
12174
|
-
for (const t of tenants) console.log(` ${
|
|
12493
|
+
if (!tenants.length) console.log(import_chalk53.default.dim(" No tenants attached (consumers use the default tenant)."));
|
|
12494
|
+
for (const t of tenants) console.log(` ${import_chalk53.default.bold(t.tenant_name ?? t.name)} ${import_chalk53.default.dim(t.display_name ?? "")}`);
|
|
12175
12495
|
const { act } = await inquirer3.prompt([{
|
|
12176
12496
|
type: "list",
|
|
12177
12497
|
name: "act",
|
|
12178
12498
|
message: "Tenants:",
|
|
12179
12499
|
choices: [
|
|
12180
|
-
{ name: `Manage a tenant\u2026 ${
|
|
12500
|
+
{ name: `Manage a tenant\u2026 ${import_chalk53.default.dim("settings, login app clients, providers, issuers \u2014 affects EVERY proxy the tenant serves")}`, value: "manage" },
|
|
12181
12501
|
{ name: "Attach a tenant to this project", value: "attach" },
|
|
12182
12502
|
...tenants.length ? [{ name: "Detach a tenant from this project", value: "detach" }] : [],
|
|
12183
12503
|
{ name: "\u2190 Back", value: "back" }
|
|
@@ -12200,7 +12520,7 @@ async function tenantsMenu(proj2, opts) {
|
|
|
12200
12520
|
}]);
|
|
12201
12521
|
if (!t) continue;
|
|
12202
12522
|
await admin({ method: "DELETE", path: `${base2}/${encodeURIComponent(t.tenant_name ?? t.name)}`, summary: `Detach tenant ${t.tenant_name ?? t.name}` });
|
|
12203
|
-
console.log(
|
|
12523
|
+
console.log(import_chalk53.default.green(` Detached ${t.tenant_name ?? t.name}.`));
|
|
12204
12524
|
}
|
|
12205
12525
|
}
|
|
12206
12526
|
}
|
|
@@ -12243,7 +12563,7 @@ async function specMenu(proj2, opts) {
|
|
|
12243
12563
|
choices: [
|
|
12244
12564
|
{ name: "Print the stored spec", value: "get" },
|
|
12245
12565
|
{ name: "Refresh the spec from its source", value: "refresh" },
|
|
12246
|
-
{ name:
|
|
12566
|
+
{ name: import_chalk53.default.dim("Build the spec by chatting over real traffic \u2192 agent"), value: "agent" },
|
|
12247
12567
|
{ name: "\u2190 Back", value: "back" }
|
|
12248
12568
|
]
|
|
12249
12569
|
}]);
|
|
@@ -12251,7 +12571,7 @@ async function specMenu(proj2, opts) {
|
|
|
12251
12571
|
if (act === "get") await runSpecGet(proj2.projectName, { team: opts.team, apiversion: proj2.apiVersion });
|
|
12252
12572
|
else if (act === "refresh") {
|
|
12253
12573
|
await admin({ method: "POST", path: `/projects/${proj2.projectId}/${proj2.apiVersion}/refresh-spec`, summary: "Refresh spec from source" });
|
|
12254
|
-
console.log(
|
|
12574
|
+
console.log(import_chalk53.default.green(" Spec refresh triggered."));
|
|
12255
12575
|
} else await runOpenapi(proj2.projectName, proj2.apiVersion);
|
|
12256
12576
|
}
|
|
12257
12577
|
async function agentsMenu(proj2, opts) {
|
|
@@ -12276,8 +12596,8 @@ async function agentsMenu(proj2, opts) {
|
|
|
12276
12596
|
}
|
|
12277
12597
|
|
|
12278
12598
|
// src/commands/group.ts
|
|
12279
|
-
var
|
|
12280
|
-
var
|
|
12599
|
+
var import_chalk54 = __toESM(require("chalk"));
|
|
12600
|
+
var import_ora25 = __toESM(require("ora"));
|
|
12281
12601
|
init_auth();
|
|
12282
12602
|
init_admin();
|
|
12283
12603
|
init_resolve();
|
|
@@ -12377,7 +12697,7 @@ async function userByRefOrCreate(t, ref) {
|
|
|
12377
12697
|
if (!ref.includes("@")) throw e;
|
|
12378
12698
|
const created = await t.call("/v1/admin/users", "POST", { email: ref.trim().toLowerCase() });
|
|
12379
12699
|
if (!created?.abz_sub) throw e;
|
|
12380
|
-
console.log(
|
|
12700
|
+
console.log(import_chalk54.default.dim(` ${ref} wasn't a user yet \u2014 created.`));
|
|
12381
12701
|
return { abz_sub: created.abz_sub, email: created.email ?? ref, display_name: null };
|
|
12382
12702
|
}
|
|
12383
12703
|
}
|
|
@@ -12385,11 +12705,11 @@ async function runGroupList(opts) {
|
|
|
12385
12705
|
const t = await resolveTransport(opts);
|
|
12386
12706
|
const groups = await listGroups(t);
|
|
12387
12707
|
if (opts.json) return void console.log(JSON.stringify(groups));
|
|
12388
|
-
if (!groups.length) return void console.log(
|
|
12389
|
-
console.log(
|
|
12708
|
+
if (!groups.length) return void console.log(import_chalk54.default.yellow(`No groups in ${t.tenant}. Create one: apiblaze group create <name>`));
|
|
12709
|
+
console.log(import_chalk54.default.dim(`Tenant ${t.tenant}:`));
|
|
12390
12710
|
for (const g of groups) {
|
|
12391
|
-
const sealed = g.name === "apiblaze_admins" ?
|
|
12392
|
-
console.log(` ${
|
|
12711
|
+
const sealed = g.name === "apiblaze_admins" ? import_chalk54.default.dim(" (admins \u2014 managed by allowlist)") : "";
|
|
12712
|
+
console.log(` ${import_chalk54.default.bold(g.name)} ${import_chalk54.default.dim(`${g.member_count ?? 0} member(s)`)}${sealed}`);
|
|
12393
12713
|
}
|
|
12394
12714
|
}
|
|
12395
12715
|
async function runGroupCreate(name, opts) {
|
|
@@ -12414,10 +12734,10 @@ async function runGroupCreate(name, opts) {
|
|
|
12414
12734
|
}
|
|
12415
12735
|
}
|
|
12416
12736
|
}
|
|
12417
|
-
const spinner = (0,
|
|
12737
|
+
const spinner = (0, import_ora25.default)(`Creating group ${name}...`).start();
|
|
12418
12738
|
try {
|
|
12419
12739
|
await t.call("/v1/admin/groups", "POST", { name, admins: [adminSub] });
|
|
12420
|
-
spinner.succeed(`Group ${
|
|
12740
|
+
spinner.succeed(`Group ${import_chalk54.default.bold(name)} created in ${t.tenant}.`);
|
|
12421
12741
|
} catch (e) {
|
|
12422
12742
|
spinner.fail("Create failed.");
|
|
12423
12743
|
throw e;
|
|
@@ -12426,10 +12746,10 @@ async function runGroupCreate(name, opts) {
|
|
|
12426
12746
|
async function runGroupDelete(name, opts) {
|
|
12427
12747
|
const t = await resolveTransport(opts);
|
|
12428
12748
|
const g = await groupByName(t, name);
|
|
12429
|
-
const spinner = (0,
|
|
12749
|
+
const spinner = (0, import_ora25.default)(`Deleting group ${name}...`).start();
|
|
12430
12750
|
try {
|
|
12431
12751
|
await t.call(`/v1/admin/groups/${encodeURIComponent(g.id)}`, "DELETE");
|
|
12432
|
-
spinner.succeed(`Group ${
|
|
12752
|
+
spinner.succeed(`Group ${import_chalk54.default.bold(name)} deleted.`);
|
|
12433
12753
|
} catch (e) {
|
|
12434
12754
|
spinner.fail("Delete failed.");
|
|
12435
12755
|
throw e;
|
|
@@ -12448,21 +12768,21 @@ async function runGroupMembers(name, opts) {
|
|
|
12448
12768
|
return void console.log(JSON.stringify({ ...detail, members: walkedMembers.items, subgroups: subs?.subgroups ?? [] }));
|
|
12449
12769
|
}
|
|
12450
12770
|
const members = walkedMembers.items;
|
|
12451
|
-
console.log(
|
|
12452
|
-
for (const m of members) console.log(` ${label2(m)} ${m.role === "admin" ?
|
|
12453
|
-
if (!members.length) console.log(
|
|
12771
|
+
console.log(import_chalk54.default.bold(`${g.name}`) + import_chalk54.default.dim(` (${t.tenant})`));
|
|
12772
|
+
for (const m of members) console.log(` ${label2(m)} ${m.role === "admin" ? import_chalk54.default.cyan("group-admin") : import_chalk54.default.dim("member")}`);
|
|
12773
|
+
if (!members.length) console.log(import_chalk54.default.dim(" no members"));
|
|
12454
12774
|
const sg = subs?.subgroups ?? [];
|
|
12455
|
-
if (sg.length) console.log(
|
|
12775
|
+
if (sg.length) console.log(import_chalk54.default.dim(` subgroups: ${sg.map((s) => s.name).join(", ")}`));
|
|
12456
12776
|
}
|
|
12457
12777
|
async function runGroupAddUser(userRef, groupName, opts) {
|
|
12458
12778
|
const t = await resolveTransport(opts);
|
|
12459
12779
|
const g = await groupByName(t, groupName);
|
|
12460
12780
|
const u = await userByRefOrCreate(t, userRef);
|
|
12461
12781
|
const role = opts.role === "admin" ? "admin" : "member";
|
|
12462
|
-
const spinner = (0,
|
|
12782
|
+
const spinner = (0, import_ora25.default)(`Adding ${label2(u)} to ${g.name}...`).start();
|
|
12463
12783
|
try {
|
|
12464
12784
|
await t.call(`/v1/admin/groups/${encodeURIComponent(g.id)}/members`, "POST", { abz_sub: u.abz_sub, role });
|
|
12465
|
-
spinner.succeed(`${
|
|
12785
|
+
spinner.succeed(`${import_chalk54.default.bold(label2(u))} added to ${import_chalk54.default.bold(g.name)} as ${role}.`);
|
|
12466
12786
|
} catch (e) {
|
|
12467
12787
|
spinner.fail("Add failed.");
|
|
12468
12788
|
throw e;
|
|
@@ -12471,10 +12791,10 @@ async function runGroupAddUser(userRef, groupName, opts) {
|
|
|
12471
12791
|
async function runGroupRemoveUser(userRef, groupName, opts) {
|
|
12472
12792
|
const t = await resolveTransport(opts);
|
|
12473
12793
|
const [g, u] = [await groupByName(t, groupName), await userByRef(t, userRef)];
|
|
12474
|
-
const spinner = (0,
|
|
12794
|
+
const spinner = (0, import_ora25.default)(`Removing ${label2(u)} from ${g.name}...`).start();
|
|
12475
12795
|
try {
|
|
12476
12796
|
await t.call(`/v1/admin/groups/${encodeURIComponent(g.id)}/members/_?abz_sub=${encodeURIComponent(u.abz_sub)}`, "DELETE");
|
|
12477
|
-
spinner.succeed(`${
|
|
12797
|
+
spinner.succeed(`${import_chalk54.default.bold(label2(u))} removed from ${import_chalk54.default.bold(g.name)}.`);
|
|
12478
12798
|
} catch (e) {
|
|
12479
12799
|
spinner.fail("Remove failed.");
|
|
12480
12800
|
throw e;
|
|
@@ -12483,10 +12803,10 @@ async function runGroupRemoveUser(userRef, groupName, opts) {
|
|
|
12483
12803
|
async function runGroupAddGroup(childName, parentName, opts) {
|
|
12484
12804
|
const t = await resolveTransport(opts);
|
|
12485
12805
|
const [child, parent] = [await groupByName(t, childName), await groupByName(t, parentName)];
|
|
12486
|
-
const spinner = (0,
|
|
12806
|
+
const spinner = (0, import_ora25.default)(`Nesting ${child.name} under ${parent.name}...`).start();
|
|
12487
12807
|
try {
|
|
12488
12808
|
await t.call(`/v1/admin/groups/${encodeURIComponent(parent.id)}/subgroups`, "POST", { child_group_id: child.id });
|
|
12489
|
-
spinner.succeed(`${
|
|
12809
|
+
spinner.succeed(`${import_chalk54.default.bold(child.name)} is now a subgroup of ${import_chalk54.default.bold(parent.name)}.`);
|
|
12490
12810
|
} catch (e) {
|
|
12491
12811
|
spinner.fail("Nest failed.");
|
|
12492
12812
|
throw e;
|
|
@@ -12495,10 +12815,10 @@ async function runGroupAddGroup(childName, parentName, opts) {
|
|
|
12495
12815
|
async function runGroupRemoveGroup(childName, parentName, opts) {
|
|
12496
12816
|
const t = await resolveTransport(opts);
|
|
12497
12817
|
const [child, parent] = [await groupByName(t, childName), await groupByName(t, parentName)];
|
|
12498
|
-
const spinner = (0,
|
|
12818
|
+
const spinner = (0, import_ora25.default)(`Un-nesting ${child.name} from ${parent.name}...`).start();
|
|
12499
12819
|
try {
|
|
12500
12820
|
await t.call(`/v1/admin/groups/${encodeURIComponent(parent.id)}/subgroups/${encodeURIComponent(child.id)}`, "DELETE");
|
|
12501
|
-
spinner.succeed(`${
|
|
12821
|
+
spinner.succeed(`${import_chalk54.default.bold(child.name)} un-nested from ${import_chalk54.default.bold(parent.name)}.`);
|
|
12502
12822
|
} catch (e) {
|
|
12503
12823
|
spinner.fail("Un-nest failed.");
|
|
12504
12824
|
throw e;
|
|
@@ -12509,13 +12829,13 @@ async function runGroupUsers(opts) {
|
|
|
12509
12829
|
const users = await listUsers(t);
|
|
12510
12830
|
const observed = await t.call("/v1/admin/observed-identities").catch(() => ({ identities: [] }));
|
|
12511
12831
|
if (opts.json) return void console.log(JSON.stringify({ users, observed: observed?.identities ?? [] }));
|
|
12512
|
-
console.log(
|
|
12513
|
-
for (const u of users) console.log(` ${label2(u)} ${
|
|
12514
|
-
if (!users.length) console.log(
|
|
12832
|
+
console.log(import_chalk54.default.dim(`Tenant ${t.tenant} \u2014 user directory:`));
|
|
12833
|
+
for (const u of users) console.log(` ${label2(u)} ${import_chalk54.default.dim(u.abz_sub)}`);
|
|
12834
|
+
if (!users.length) console.log(import_chalk54.default.dim(" no users yet"));
|
|
12515
12835
|
const fresh = (observed?.identities ?? []).filter((o) => !o.provisioned);
|
|
12516
12836
|
if (fresh.length) {
|
|
12517
|
-
console.log(
|
|
12518
|
-
for (const o of fresh) console.log(` ${o.email || o.abz_sub} ${
|
|
12837
|
+
console.log(import_chalk54.default.dim("Seen in traffic (not yet users):"));
|
|
12838
|
+
for (const o of fresh) console.log(` ${o.email || o.abz_sub} ${import_chalk54.default.dim(o.source_label ?? "")}`);
|
|
12519
12839
|
}
|
|
12520
12840
|
}
|
|
12521
12841
|
|
|
@@ -12524,22 +12844,22 @@ init_iam();
|
|
|
12524
12844
|
init_admins();
|
|
12525
12845
|
|
|
12526
12846
|
// src/commands/preapprove.ts
|
|
12527
|
-
var
|
|
12528
|
-
var
|
|
12847
|
+
var import_chalk55 = __toESM(require("chalk"));
|
|
12848
|
+
var import_ora26 = __toESM(require("ora"));
|
|
12529
12849
|
async function runPreapprove(who, opts) {
|
|
12530
12850
|
const tenant2 = await resolveProducerTenant(opts.tenant);
|
|
12531
12851
|
if (opts.list) {
|
|
12532
12852
|
const rules = await listPreapprovalRules(tenant2);
|
|
12533
12853
|
if (opts.json) return void console.log(JSON.stringify(rules, null, 2));
|
|
12534
12854
|
if (!rules.length) {
|
|
12535
|
-
console.log(
|
|
12536
|
-
console.log(
|
|
12855
|
+
console.log(import_chalk55.default.dim(`No pre-approval rules in ${tenant2}. Anyone who signs in can access (unless access is restricted).`));
|
|
12856
|
+
console.log(import_chalk55.default.dim(`Add one: apiblaze preapprove someone@acme.com`));
|
|
12537
12857
|
return;
|
|
12538
12858
|
}
|
|
12539
|
-
console.log(
|
|
12859
|
+
console.log(import_chalk55.default.dim(`Pre-approved for ${import_chalk55.default.bold(tenant2)}:`));
|
|
12540
12860
|
for (const r of rules) {
|
|
12541
|
-
const tag = r.kind === "domain" ?
|
|
12542
|
-
const grp = r.groups?.length ?
|
|
12861
|
+
const tag = r.kind === "domain" ? import_chalk55.default.cyan("@" + r.value + " (whole domain)") : r.value;
|
|
12862
|
+
const grp = r.groups?.length ? import_chalk55.default.dim(` \u2192 groups: ${r.groups.join(", ")}`) : "";
|
|
12543
12863
|
console.log(` ${tag}${grp}`);
|
|
12544
12864
|
}
|
|
12545
12865
|
return;
|
|
@@ -12548,19 +12868,19 @@ async function runPreapprove(who, opts) {
|
|
|
12548
12868
|
throw new Error("Who? Pass an email or a domain: `apiblaze preapprove someone@acme.com` (or `apiblaze preapprove --list`).");
|
|
12549
12869
|
}
|
|
12550
12870
|
if (opts.remove) {
|
|
12551
|
-
const spinner2 = (0,
|
|
12871
|
+
const spinner2 = (0, import_ora26.default)(`Removing ${who} from ${tenant2}\u2026`).start();
|
|
12552
12872
|
const { removed, value } = await removePreapprovalRule(tenant2, who);
|
|
12553
|
-
if (removed) spinner2.succeed(`${
|
|
12554
|
-
else spinner2.warn(`No pre-approval rule for ${
|
|
12873
|
+
if (removed) spinner2.succeed(`${import_chalk55.default.bold(value)} is no longer pre-approved for ${tenant2}.`);
|
|
12874
|
+
else spinner2.warn(`No pre-approval rule for ${import_chalk55.default.bold(value)} in ${tenant2} \u2014 nothing to remove.`);
|
|
12555
12875
|
return;
|
|
12556
12876
|
}
|
|
12557
|
-
const spinner = (0,
|
|
12877
|
+
const spinner = (0, import_ora26.default)(`Pre-approving ${who} for ${tenant2}\u2026`).start();
|
|
12558
12878
|
try {
|
|
12559
12879
|
const { kind, value } = await addPreapprovalRule(tenant2, who, opts.group);
|
|
12560
12880
|
const what = kind === "domain" ? `Anyone @${value}` : value;
|
|
12561
|
-
spinner.succeed(`${
|
|
12562
|
-
if (opts.group?.length) console.log(
|
|
12563
|
-
console.log(
|
|
12881
|
+
spinner.succeed(`${import_chalk55.default.bold(what)} can now sign in to ${import_chalk55.default.bold(tenant2)}.`);
|
|
12882
|
+
if (opts.group?.length) console.log(import_chalk55.default.dim(` They'll join group(s) ${opts.group.join(", ")} on first sign-in.`));
|
|
12883
|
+
console.log(import_chalk55.default.dim(` See the full list: apiblaze preapprove --list`));
|
|
12564
12884
|
} catch (err) {
|
|
12565
12885
|
spinner.fail("Could not add the rule.");
|
|
12566
12886
|
throw err;
|
|
@@ -12568,8 +12888,8 @@ async function runPreapprove(who, opts) {
|
|
|
12568
12888
|
}
|
|
12569
12889
|
|
|
12570
12890
|
// src/commands/consumer.ts
|
|
12571
|
-
var
|
|
12572
|
-
var
|
|
12891
|
+
var import_chalk56 = __toESM(require("chalk"));
|
|
12892
|
+
var import_ora27 = __toESM(require("ora"));
|
|
12573
12893
|
init_admin();
|
|
12574
12894
|
init_resolve();
|
|
12575
12895
|
init_caller();
|
|
@@ -12591,7 +12911,7 @@ async function consumerFetch(creds, suffix, init) {
|
|
|
12591
12911
|
function requireConsumer2() {
|
|
12592
12912
|
const c = loadConsumer();
|
|
12593
12913
|
if (!c) {
|
|
12594
|
-
console.error(
|
|
12914
|
+
console.error(import_chalk56.default.red("Not logged in as a consumer. Run `apiblaze consumer login` first."));
|
|
12595
12915
|
process.exit(1);
|
|
12596
12916
|
}
|
|
12597
12917
|
return c;
|
|
@@ -12602,7 +12922,7 @@ async function runConsumerLogin(opts) {
|
|
|
12602
12922
|
let clientId = opts.client;
|
|
12603
12923
|
if (clientId) {
|
|
12604
12924
|
if (!tenant2) {
|
|
12605
|
-
console.error(
|
|
12925
|
+
console.error(import_chalk56.default.red("When using --client, also pass --tenant <slug> (it sets which portal/keys host to use)."));
|
|
12606
12926
|
process.exit(1);
|
|
12607
12927
|
}
|
|
12608
12928
|
} else {
|
|
@@ -12614,24 +12934,24 @@ async function runConsumerLogin(opts) {
|
|
|
12614
12934
|
if (!picked) process.exit(1);
|
|
12615
12935
|
tenant2 = picked;
|
|
12616
12936
|
}
|
|
12617
|
-
const s2 = (0,
|
|
12937
|
+
const s2 = (0, import_ora27.default)("Finding the login app...").start();
|
|
12618
12938
|
const clients = await admin({ method: "GET", path: `/teams/${encodeURIComponent(teamId)}/tenants/${encodeURIComponent(tenant2)}/app-clients`, summary: `List app clients for ${tenant2}` }).catch(() => []);
|
|
12619
12939
|
s2.stop();
|
|
12620
12940
|
const usable = (Array.isArray(clients) ? clients : []).filter((c) => c && (c.client_id || c.clientId));
|
|
12621
12941
|
const pick2 = usable.find((c) => c.is_default || c.default) ?? usable.find((c) => c.verified !== false) ?? usable[0];
|
|
12622
12942
|
if (!pick2) {
|
|
12623
|
-
console.error(
|
|
12943
|
+
console.error(import_chalk56.default.red(`Tenant "${tenant2}" has no login app configured. Set one up in the dashboard (or \`apiblaze create\` with auth).`));
|
|
12624
12944
|
process.exit(1);
|
|
12625
12945
|
}
|
|
12626
12946
|
clientId = pick2.client_id ?? pick2.clientId;
|
|
12627
12947
|
}
|
|
12628
|
-
console.log(`${
|
|
12948
|
+
console.log(`${import_chalk56.default.cyan("\u2192")} Logging in to ${import_chalk56.default.bold(tenant2)} as a consumer...`);
|
|
12629
12949
|
const result = await deviceLogin(clientId, DEFAULT_SCOPE, ({ verificationUri, userCode }) => {
|
|
12630
12950
|
console.log(`
|
|
12631
|
-
Open: ${
|
|
12632
|
-
console.log(` Code: ${
|
|
12951
|
+
Open: ${import_chalk56.default.underline(verificationUri)}`);
|
|
12952
|
+
console.log(` Code: ${import_chalk56.default.bold(userCode)}
|
|
12633
12953
|
`);
|
|
12634
|
-
console.log(
|
|
12954
|
+
console.log(import_chalk56.default.dim(" (opening your browser\u2026 waiting for you to finish)"));
|
|
12635
12955
|
});
|
|
12636
12956
|
const claims = result.idToken && decodeJwt2(result.idToken) || (decodeJwt2(result.accessToken) ?? {});
|
|
12637
12957
|
const creds = {
|
|
@@ -12646,7 +12966,7 @@ async function runConsumerLogin(opts) {
|
|
|
12646
12966
|
obtainedAt: Date.now()
|
|
12647
12967
|
};
|
|
12648
12968
|
saveConsumer(creds);
|
|
12649
|
-
console.log(
|
|
12969
|
+
console.log(import_chalk56.default.green(`\u2714 Logged in as consumer${creds.email ? ` ${creds.email}` : ""} on ${tenant2}.`));
|
|
12650
12970
|
}
|
|
12651
12971
|
async function runConsumerTokens(opts) {
|
|
12652
12972
|
const creds = requireConsumer2();
|
|
@@ -12659,28 +12979,28 @@ async function runConsumerTokens(opts) {
|
|
|
12659
12979
|
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));
|
|
12660
12980
|
return;
|
|
12661
12981
|
}
|
|
12662
|
-
console.log(`${
|
|
12982
|
+
console.log(`${import_chalk56.default.cyan("Consumer")} ${import_chalk56.default.bold(fresh.email ?? fresh.tenant)} on ${import_chalk56.default.bold(fresh.tenant)}
|
|
12663
12983
|
`);
|
|
12664
|
-
console.log(`${
|
|
12984
|
+
console.log(`${import_chalk56.default.bold("access_token")} ${import_chalk56.default.dim("exp " + (exp(fresh.accessToken) ?? "?"))}
|
|
12665
12985
|
${fresh.accessToken}
|
|
12666
12986
|
`);
|
|
12667
|
-
if (fresh.idToken) console.log(`${
|
|
12987
|
+
if (fresh.idToken) console.log(`${import_chalk56.default.bold("id_token")} ${import_chalk56.default.dim("exp " + (exp(fresh.idToken) ?? "?"))}
|
|
12668
12988
|
${fresh.idToken}
|
|
12669
12989
|
`);
|
|
12670
|
-
if (fresh.refreshToken) console.log(`${
|
|
12990
|
+
if (fresh.refreshToken) console.log(`${import_chalk56.default.bold("refresh_token")}
|
|
12671
12991
|
${fresh.refreshToken}
|
|
12672
12992
|
`);
|
|
12673
|
-
console.log(
|
|
12993
|
+
console.log(import_chalk56.default.dim("These are your own tokens \u2014 keep them secret."));
|
|
12674
12994
|
}
|
|
12675
12995
|
async function runConsumerApikeys(opts) {
|
|
12676
12996
|
const creds = requireConsumer2();
|
|
12677
12997
|
const { default: inquirer3 } = await import("inquirer");
|
|
12678
|
-
const spinner = (0,
|
|
12998
|
+
const spinner = (0, import_ora27.default)("Loading your API keys...").start();
|
|
12679
12999
|
const list = await consumerFetch(creds, withPage("/apikeys", null));
|
|
12680
13000
|
if (list.status >= 400) {
|
|
12681
13001
|
spinner.stop();
|
|
12682
|
-
console.error(
|
|
12683
|
-
if (list.status === 401) console.error(
|
|
13002
|
+
console.error(import_chalk56.default.red(`Failed to list keys (${list.status}): ${list.data?.error ?? ""}`));
|
|
13003
|
+
if (list.status === 401) console.error(import_chalk56.default.dim("Your consumer session may have expired \u2014 run `apiblaze consumer login` again."));
|
|
12684
13004
|
process.exit(1);
|
|
12685
13005
|
}
|
|
12686
13006
|
let walkCreds = list.creds;
|
|
@@ -12709,16 +13029,16 @@ async function runConsumerApikeys(opts) {
|
|
|
12709
13029
|
if (opts.json) {
|
|
12710
13030
|
console.log(JSON.stringify({ keys, revealed: revealMap }, null, 2));
|
|
12711
13031
|
} else if (!keys.length) {
|
|
12712
|
-
console.log(
|
|
13032
|
+
console.log(import_chalk56.default.yellow("No API keys yet."));
|
|
12713
13033
|
} else {
|
|
12714
13034
|
for (const k of keys) {
|
|
12715
13035
|
const clear = revealMap[k.environment]?.key;
|
|
12716
|
-
const shown = clear ?
|
|
12717
|
-
const exp = k.expires_at ?
|
|
12718
|
-
console.log(` ${
|
|
13036
|
+
const shown = clear ? import_chalk56.default.green(clear) : import_chalk56.default.dim(`${k.key_prefix ?? ""}\u2026${k.key_suffix ?? ""}`);
|
|
13037
|
+
const exp = k.expires_at ? import_chalk56.default.dim(`exp ${k.expires_at}`) : import_chalk56.default.dim("no expiry");
|
|
13038
|
+
console.log(` ${import_chalk56.default.bold(k.environment ?? "")} ${shown} ${exp} ${import_chalk56.default.dim(k.description ?? "")}`);
|
|
12719
13039
|
}
|
|
12720
13040
|
if (Object.keys(revealMap).length === 0 && keys.some((k) => !k.expires_at)) {
|
|
12721
|
-
console.log(
|
|
13041
|
+
console.log(import_chalk56.default.dim("\n(Only expiring keys can be shown in clear; non-expiring keys show a prefix only.)"));
|
|
12722
13042
|
}
|
|
12723
13043
|
}
|
|
12724
13044
|
if (opts.json) return;
|
|
@@ -12732,7 +13052,7 @@ async function runConsumerApikeys(opts) {
|
|
|
12732
13052
|
const body = { environment: answers.environment };
|
|
12733
13053
|
if (answers.description) body.description = answers.description;
|
|
12734
13054
|
if (answers.expiresDays) body.expires_in_seconds = Number(answers.expiresDays) * 86400;
|
|
12735
|
-
const s2 = (0,
|
|
13055
|
+
const s2 = (0, import_ora27.default)("Creating key...").start();
|
|
12736
13056
|
const created = await consumerFetch(list.creds, "/apikeys", { method: "POST", body: JSON.stringify(body) });
|
|
12737
13057
|
if (created.status >= 400) {
|
|
12738
13058
|
s2.fail(`Create failed (${created.status}): ${created.data?.error ?? ""}`);
|
|
@@ -12740,13 +13060,13 @@ async function runConsumerApikeys(opts) {
|
|
|
12740
13060
|
}
|
|
12741
13061
|
s2.succeed("Key created.");
|
|
12742
13062
|
const key = created.data?.key ?? created.data?.fullKey;
|
|
12743
|
-
if (key) console.log(` ${
|
|
12744
|
-
else console.log(
|
|
13063
|
+
if (key) console.log(` ${import_chalk56.default.green(key)} ${import_chalk56.default.dim("(shown once \u2014 store it now)")}`);
|
|
13064
|
+
else console.log(import_chalk56.default.dim(" Key created; run `apiblaze consumer apikeys` to reveal it if it expires."));
|
|
12745
13065
|
}
|
|
12746
13066
|
|
|
12747
13067
|
// src/commands/sidecar.ts
|
|
12748
|
-
var
|
|
12749
|
-
var
|
|
13068
|
+
var import_chalk57 = __toESM(require("chalk"));
|
|
13069
|
+
var import_ora28 = __toESM(require("ora"));
|
|
12750
13070
|
var fs13 = __toESM(require("fs"));
|
|
12751
13071
|
var path10 = __toESM(require("path"));
|
|
12752
13072
|
init_admin();
|
|
@@ -12788,18 +13108,18 @@ function upsertEnvLocal(root, token) {
|
|
|
12788
13108
|
}
|
|
12789
13109
|
function installSidecarPackage(root) {
|
|
12790
13110
|
if (fs13.existsSync(path10.join(root, "node_modules", "apiblaze", "package.json"))) {
|
|
12791
|
-
console.log(` ${
|
|
13111
|
+
console.log(` ${import_chalk57.default.green("\u2713")} apiblaze package already installed`);
|
|
12792
13112
|
return;
|
|
12793
13113
|
}
|
|
12794
13114
|
const has = (f) => fs13.existsSync(path10.join(root, f));
|
|
12795
13115
|
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" };
|
|
12796
|
-
const spinner = (0,
|
|
13116
|
+
const spinner = (0, import_ora28.default)(`Installing the apiblaze package (${pm.cmd})\u2026`).start();
|
|
12797
13117
|
try {
|
|
12798
13118
|
const { execSync } = require("child_process");
|
|
12799
13119
|
execSync(`${pm.cmd} ${pm.add} apiblaze`, { cwd: root, stdio: "ignore" });
|
|
12800
13120
|
spinner.succeed("Installed apiblaze (the sidecar runtime).");
|
|
12801
13121
|
} catch {
|
|
12802
|
-
spinner.warn(`Couldn't auto-install \u2014 run ${
|
|
13122
|
+
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")}.`);
|
|
12803
13123
|
}
|
|
12804
13124
|
}
|
|
12805
13125
|
function readEnvKey(root) {
|
|
@@ -12938,7 +13258,7 @@ async function runAnonymousInit(root, router, opts) {
|
|
|
12938
13258
|
const { sidecarInitAnonymous: sidecarInitAnonymous2 } = await Promise.resolve().then(() => (init_api(), api_exports));
|
|
12939
13259
|
const { saveAnonCred: saveAnonCred2, clearAnonCred: clearAnonCred2 } = await Promise.resolve().then(() => (init_anon_cred(), anon_cred_exports));
|
|
12940
13260
|
if (opts.newSession) clearAnonCred2();
|
|
12941
|
-
const spinner = (0,
|
|
13261
|
+
const spinner = (0, import_ora28.default)("Setting up a sidecar (no login needed)...").start();
|
|
12942
13262
|
let out;
|
|
12943
13263
|
try {
|
|
12944
13264
|
out = await sidecarInitAnonymous2();
|
|
@@ -12950,29 +13270,29 @@ async function runAnonymousInit(root, router, opts) {
|
|
|
12950
13270
|
if (out.cp_key && out.team_id) saveAnonCred2(out.cp_key, out.team_id, out.claim_code);
|
|
12951
13271
|
const envState = upsertEnvLocal(root, out.token);
|
|
12952
13272
|
ensureGitignored(root);
|
|
12953
|
-
console.log(` ${
|
|
12954
|
-
console.log(` ${
|
|
13273
|
+
console.log(` ${import_chalk57.default.green("\u2713")} .env.local ${envState} (APIBLAZE_API_KEY) \u2014 gitignored`);
|
|
13274
|
+
console.log(` ${import_chalk57.default.green("\u2713")} instrumentation.ts ${wireInstrumentation(root)}`);
|
|
12955
13275
|
installSidecarPackage(root);
|
|
12956
13276
|
let inspectorPath = null;
|
|
12957
13277
|
if (!opts.noInspector) {
|
|
12958
13278
|
inspectorPath = generateInspector(root, router);
|
|
12959
|
-
if (inspectorPath) console.log(` ${
|
|
13279
|
+
if (inspectorPath) console.log(` ${import_chalk57.default.green("\u2713")} inspector at ${inspectorPath}`);
|
|
12960
13280
|
}
|
|
12961
13281
|
console.log("");
|
|
12962
|
-
console.log(
|
|
12963
|
-
console.log(` 1. ${
|
|
13282
|
+
console.log(import_chalk57.default.bold("Done (no account needed). What happens next:"));
|
|
13283
|
+
console.log(` 1. ${import_chalk57.default.cyan("npm run dev")} and use your app.`);
|
|
12964
13284
|
console.log(` 2. Each external origin your app calls is logged in the console \u2014 approve one with:`);
|
|
12965
|
-
console.log(` ${
|
|
13285
|
+
console.log(` ${import_chalk57.default.cyan("apiblaze sidecar approve api.stripe.com")} (no login needed)`);
|
|
12966
13286
|
console.log("");
|
|
12967
|
-
console.log(
|
|
12968
|
-
console.log(` ${
|
|
12969
|
-
console.log(
|
|
13287
|
+
console.log(import_chalk57.default.bold(" \u{1F511} Keep your setup \u2014 claim it into an account:"));
|
|
13288
|
+
console.log(` ${import_chalk57.default.cyan("apiblaze login")} then ${import_chalk57.default.cyan("apiblaze claim")} ${import_chalk57.default.dim("(no code needed here)")}`);
|
|
13289
|
+
console.log(import_chalk57.default.dim(` From another machine: apiblaze claim ${out.claim_code} \xB7 expires in 30 days`));
|
|
12970
13290
|
}
|
|
12971
13291
|
async function runSidecar(opts) {
|
|
12972
13292
|
const root = path10.resolve(opts.dir ?? process.cwd());
|
|
12973
13293
|
const detected2 = detectNextProject(root);
|
|
12974
13294
|
if (!detected2.found) {
|
|
12975
|
-
console.log(
|
|
13295
|
+
console.log(import_chalk57.default.yellow(`No Next.js project detected in ${root}.`));
|
|
12976
13296
|
console.log("Create one (e.g. `npx create-next-app`) and re-run `apiblaze init` inside it.");
|
|
12977
13297
|
return;
|
|
12978
13298
|
}
|
|
@@ -12983,10 +13303,10 @@ async function runSidecar(opts) {
|
|
|
12983
13303
|
if (!loadCredentials()) {
|
|
12984
13304
|
upsertEnvLocal(root, readEnvKey(root));
|
|
12985
13305
|
ensureGitignored(root);
|
|
12986
|
-
console.log(` ${
|
|
12987
|
-
console.log(` ${
|
|
13306
|
+
console.log(` ${import_chalk57.default.green("\u2713")} .env.local present (APIBLAZE_API_KEY) \u2014 reusing`);
|
|
13307
|
+
console.log(` ${import_chalk57.default.green("\u2713")} instrumentation.ts ${wireInstrumentation(root)}`);
|
|
12988
13308
|
installSidecarPackage(root);
|
|
12989
|
-
console.log(
|
|
13309
|
+
console.log(import_chalk57.default.dim(" Log in and run `apiblaze claim <code>` to keep this setup, or `apiblaze login` to manage it."));
|
|
12990
13310
|
return;
|
|
12991
13311
|
}
|
|
12992
13312
|
const { teamId, teamName } = await resolveTeam(opts.team);
|
|
@@ -12995,7 +13315,7 @@ async function runSidecar(opts) {
|
|
|
12995
13315
|
const mustMint = !existingKey || opts.rotate || switchingTeam;
|
|
12996
13316
|
let token = existingKey ?? "";
|
|
12997
13317
|
if (mustMint) {
|
|
12998
|
-
const spinner = (0,
|
|
13318
|
+
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();
|
|
12999
13319
|
try {
|
|
13000
13320
|
const out = await admin({
|
|
13001
13321
|
method: "POST",
|
|
@@ -13009,39 +13329,39 @@ async function runSidecar(opts) {
|
|
|
13009
13329
|
throw err;
|
|
13010
13330
|
}
|
|
13011
13331
|
} else {
|
|
13012
|
-
console.log(
|
|
13332
|
+
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).`));
|
|
13013
13333
|
}
|
|
13014
13334
|
const envState = upsertEnvLocal(root, token);
|
|
13015
13335
|
ensureGitignored(root);
|
|
13016
|
-
console.log(` ${
|
|
13336
|
+
console.log(` ${import_chalk57.default.green("\u2713")} .env.local ${envState} (APIBLAZE_API_KEY) \u2014 gitignored`);
|
|
13017
13337
|
const wireState = wireInstrumentation(root);
|
|
13018
|
-
console.log(` ${
|
|
13338
|
+
console.log(` ${import_chalk57.default.green("\u2713")} instrumentation.ts ${wireState}`);
|
|
13019
13339
|
installSidecarPackage(root);
|
|
13020
13340
|
let inspectorPath = null;
|
|
13021
13341
|
if (!opts.noInspector) {
|
|
13022
13342
|
inspectorPath = generateInspector(root, detected2.router);
|
|
13023
|
-
if (inspectorPath) console.log(` ${
|
|
13343
|
+
if (inspectorPath) console.log(` ${import_chalk57.default.green("\u2713")} inspector at ${inspectorPath}`);
|
|
13024
13344
|
}
|
|
13025
13345
|
console.log("");
|
|
13026
|
-
console.log(
|
|
13027
|
-
console.log(` 1. ${
|
|
13028
|
-
console.log(` 2. The origins your app calls appear as ${
|
|
13029
|
-
console.log(` 3. Approve the ones to route: ${
|
|
13346
|
+
console.log(import_chalk57.default.bold("Done. What happens next:"));
|
|
13347
|
+
console.log(` 1. ${import_chalk57.default.cyan("npm run dev")} and use your app \u2014 it works exactly as before (all calls go direct).`);
|
|
13348
|
+
console.log(` 2. The origins your app calls appear as ${import_chalk57.default.bold("candidates")} \u2014 list them: ${import_chalk57.default.cyan("apiblaze sidecar")}`);
|
|
13349
|
+
console.log(` 3. Approve the ones to route: ${import_chalk57.default.cyan("apiblaze sidecar approve api.stripe.com")} (or in the dashboard)`);
|
|
13030
13350
|
console.log(` \u2026within ~5 min your app starts routing that origin through APIblaze.`);
|
|
13031
|
-
if (inspectorPath) console.log(` \u2022 Try it now: open ${
|
|
13032
|
-
if (switchingTeam) console.log(
|
|
13351
|
+
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)`);
|
|
13352
|
+
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>\`.`));
|
|
13033
13353
|
console.log("");
|
|
13034
|
-
console.log(
|
|
13035
|
-
console.log(
|
|
13036
|
-
console.log(
|
|
13354
|
+
console.log(import_chalk57.default.dim(" Manage: apiblaze sidecar (list/approve/deny/remove)"));
|
|
13355
|
+
console.log(import_chalk57.default.dim(" Rotate: apiblaze init --rotate \xB7 Switch team: apiblaze init --team <name>"));
|
|
13356
|
+
console.log(import_chalk57.default.dim(" Turn off: set APIBLAZE_SIDECAR=off in .env.local (flip back to on anytime; key stays put)."));
|
|
13037
13357
|
console.log("");
|
|
13038
|
-
console.log(
|
|
13039
|
-
console.log(
|
|
13358
|
+
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."));
|
|
13359
|
+
console.log(import_chalk57.default.dim(" Your control-plane login stays in ~/.apiblaze \u2014 it never entered this project."));
|
|
13040
13360
|
}
|
|
13041
13361
|
|
|
13042
13362
|
// src/commands/origins.ts
|
|
13043
|
-
var
|
|
13044
|
-
var
|
|
13363
|
+
var import_chalk58 = __toESM(require("chalk"));
|
|
13364
|
+
var import_ora29 = __toESM(require("ora"));
|
|
13045
13365
|
init_admin();
|
|
13046
13366
|
init_resolve();
|
|
13047
13367
|
init_auth();
|
|
@@ -13051,7 +13371,7 @@ async function runOriginsList(opts) {
|
|
|
13051
13371
|
if (!loadCredentials()) {
|
|
13052
13372
|
const cred = loadAnonCred();
|
|
13053
13373
|
if (!cred) {
|
|
13054
|
-
console.log(
|
|
13374
|
+
console.log(import_chalk58.default.yellow("No anonymous workspace here. Run `apiblaze init` first."));
|
|
13055
13375
|
return;
|
|
13056
13376
|
}
|
|
13057
13377
|
out = await cpFetch(cred.cp_key, `/teams/${encodeURIComponent(cred.team_id)}/sidecar/candidates`, { method: "GET" });
|
|
@@ -13069,30 +13389,30 @@ async function runOriginsList(opts) {
|
|
|
13069
13389
|
}
|
|
13070
13390
|
const routed = out.routed ?? [];
|
|
13071
13391
|
const candidates = out.candidates ?? [];
|
|
13072
|
-
console.log(
|
|
13392
|
+
console.log(import_chalk58.default.bold(`
|
|
13073
13393
|
Routed through APIblaze (${routed.length})`));
|
|
13074
|
-
if (!routed.length) console.log(
|
|
13075
|
-
for (const r of routed) console.log(` ${
|
|
13076
|
-
console.log(
|
|
13394
|
+
if (!routed.length) console.log(import_chalk58.default.dim(" none yet"));
|
|
13395
|
+
for (const r of routed) console.log(` ${import_chalk58.default.green("\u25CF")} ${r.sidecar_origin} ${import_chalk58.default.dim(`\u2192 ${r.project_id}`)}`);
|
|
13396
|
+
console.log(import_chalk58.default.bold(`
|
|
13077
13397
|
Candidates \u2014 going direct, not yet approved (${candidates.length})`));
|
|
13078
|
-
if (!candidates.length) console.log(
|
|
13398
|
+
if (!candidates.length) console.log(import_chalk58.default.dim(" none \u2014 run your app to discover the origins it calls"));
|
|
13079
13399
|
for (const c of candidates) {
|
|
13080
|
-
console.log(` ${
|
|
13400
|
+
console.log(` ${import_chalk58.default.yellow("\u25CB")} ${c.origin} ${import_chalk58.default.dim(`seen ${c.request_count}\xD7, last ${c.last_seen}`)}`);
|
|
13081
13401
|
}
|
|
13082
13402
|
if (candidates.length) {
|
|
13083
|
-
console.log(
|
|
13403
|
+
console.log(import_chalk58.default.dim(`
|
|
13084
13404
|
Approve: apiblaze sidecar approve ${candidates[0].origin.replace("https://", "")}`));
|
|
13085
|
-
console.log(
|
|
13405
|
+
console.log(import_chalk58.default.dim(` Dismiss: apiblaze sidecar deny ${candidates[0].origin.replace("https://", "")}`));
|
|
13086
13406
|
}
|
|
13087
13407
|
}
|
|
13088
13408
|
async function runOriginsApprove(origin, opts) {
|
|
13089
13409
|
if (!loadCredentials()) {
|
|
13090
13410
|
const cred = loadAnonCred();
|
|
13091
13411
|
if (!cred) {
|
|
13092
|
-
console.error(
|
|
13412
|
+
console.error(import_chalk58.default.red("Not logged in and no anonymous workspace. Run `apiblaze init` first."));
|
|
13093
13413
|
process.exit(1);
|
|
13094
13414
|
}
|
|
13095
|
-
const spinner2 = (0,
|
|
13415
|
+
const spinner2 = (0, import_ora29.default)(`Approving ${origin} (anonymous)...`).start();
|
|
13096
13416
|
try {
|
|
13097
13417
|
const out = await cpFetch(cred.cp_key, `/teams/${encodeURIComponent(cred.team_id)}/sidecar/approve`, { method: "POST", body: JSON.stringify({ origin }) });
|
|
13098
13418
|
spinner2.succeed(`Approved ${origin} \u2192 proxy ${out.project_id}. Routing within ~5 min.`);
|
|
@@ -13103,7 +13423,7 @@ async function runOriginsApprove(origin, opts) {
|
|
|
13103
13423
|
return;
|
|
13104
13424
|
}
|
|
13105
13425
|
const { teamId } = await resolveTeam(opts.team);
|
|
13106
|
-
const spinner = (0,
|
|
13426
|
+
const spinner = (0, import_ora29.default)(`Approving ${origin}...`).start();
|
|
13107
13427
|
try {
|
|
13108
13428
|
const out = await admin({
|
|
13109
13429
|
method: "POST",
|
|
@@ -13120,7 +13440,7 @@ async function runOriginsApprove(origin, opts) {
|
|
|
13120
13440
|
}
|
|
13121
13441
|
async function runOriginsDeny(origin, opts) {
|
|
13122
13442
|
const { teamId } = await resolveTeam(opts.team);
|
|
13123
|
-
const spinner = (0,
|
|
13443
|
+
const spinner = (0, import_ora29.default)(`Dismissing ${origin}...`).start();
|
|
13124
13444
|
try {
|
|
13125
13445
|
await admin({ method: "POST", path: `/teams/${encodeURIComponent(teamId)}/sidecar/dismiss`, body: { origin }, summary: `Dismiss sidecar origin ${origin}` });
|
|
13126
13446
|
spinner.succeed(`Dismissed ${origin}. It won't be suggested again.`);
|
|
@@ -13131,7 +13451,7 @@ async function runOriginsDeny(origin, opts) {
|
|
|
13131
13451
|
}
|
|
13132
13452
|
async function runOriginsRemove(origin, opts) {
|
|
13133
13453
|
const { teamId } = await resolveTeam(opts.team);
|
|
13134
|
-
const spinner = (0,
|
|
13454
|
+
const spinner = (0, import_ora29.default)(`Removing the proxy for ${origin}...`).start();
|
|
13135
13455
|
try {
|
|
13136
13456
|
await admin({ method: "POST", path: `/teams/${encodeURIComponent(teamId)}/sidecar/remove`, body: { origin }, summary: `Un-route sidecar origin ${origin}` });
|
|
13137
13457
|
spinner.succeed(`Removed ${origin}. Your app will stop routing it (goes direct) within ~5 min.`);
|
|
@@ -13142,7 +13462,7 @@ async function runOriginsRemove(origin, opts) {
|
|
|
13142
13462
|
}
|
|
13143
13463
|
|
|
13144
13464
|
// src/commands/op.ts
|
|
13145
|
-
var
|
|
13465
|
+
var import_chalk63 = __toESM(require("chalk"));
|
|
13146
13466
|
init_auth();
|
|
13147
13467
|
|
|
13148
13468
|
// src/lib/op-call.ts
|
|
@@ -13171,7 +13491,7 @@ async function opCall(call2) {
|
|
|
13171
13491
|
}
|
|
13172
13492
|
|
|
13173
13493
|
// src/commands/op-billing.ts
|
|
13174
|
-
var
|
|
13494
|
+
var import_chalk59 = __toESM(require("chalk"));
|
|
13175
13495
|
init_admin();
|
|
13176
13496
|
var SANDBOX = {
|
|
13177
13497
|
teamId: "team_1782844865835_zujrf",
|
|
@@ -13230,7 +13550,7 @@ async function rowsForRays(project, version2, tenant2, rays) {
|
|
|
13230
13550
|
}
|
|
13231
13551
|
function printDoors(data) {
|
|
13232
13552
|
const checks = [];
|
|
13233
|
-
console.log(
|
|
13553
|
+
console.log(import_chalk59.default.bold("\n Doors \u2014 is every way in metered?\n"));
|
|
13234
13554
|
const doors = data?.doors ?? [];
|
|
13235
13555
|
const metered = doors.filter((d) => d.verdict === "metered");
|
|
13236
13556
|
const allowed = doors.filter((d) => d.verdict === "allowed-free");
|
|
@@ -13239,29 +13559,29 @@ function printDoors(data) {
|
|
|
13239
13559
|
const errs = data?.errors ?? [];
|
|
13240
13560
|
const routeAuditBroke = errs.some((e) => e.startsWith("zone "));
|
|
13241
13561
|
const devAuditBroke = errs.some((e) => e.startsWith("workers.dev audit"));
|
|
13242
|
-
console.log(
|
|
13243
|
-
for (const d of metered) console.log(
|
|
13244
|
-
console.log(
|
|
13562
|
+
console.log(import_chalk59.default.dim(` ${metered.length} route(s) behind main-proxy (metered)`));
|
|
13563
|
+
for (const d of metered) console.log(import_chalk59.default.green(` \u2713 ${d.pattern}`));
|
|
13564
|
+
console.log(import_chalk59.default.dim(`
|
|
13245
13565
|
${allowed.length} route(s) free ON PURPOSE`));
|
|
13246
13566
|
for (const d of allowed) {
|
|
13247
|
-
console.log(
|
|
13248
|
-
console.log(
|
|
13567
|
+
console.log(import_chalk59.default.cyan(` \u2022 ${d.pattern}`) + import_chalk59.default.dim(` \u2192 ${d.script}`));
|
|
13568
|
+
console.log(import_chalk59.default.dim(` ${d.why}`));
|
|
13249
13569
|
}
|
|
13250
13570
|
if (known.length) {
|
|
13251
|
-
console.log(
|
|
13571
|
+
console.log(import_chalk59.default.yellow(`
|
|
13252
13572
|
${known.length} route(s) KNOWN OPEN \u2014 unmetered, not yet closed`));
|
|
13253
13573
|
for (const d of known) {
|
|
13254
|
-
console.log(
|
|
13255
|
-
console.log(
|
|
13574
|
+
console.log(import_chalk59.default.yellow(` ! ${d.pattern}`) + import_chalk59.default.dim(` \u2192 ${d.script}`));
|
|
13575
|
+
console.log(import_chalk59.default.dim(` ${d.why}`));
|
|
13256
13576
|
}
|
|
13257
13577
|
checks.push({ name: "no known-open doors", status: "KNOWN", detail: `${known.length} unmetered route(s) still open \u2014 see above` });
|
|
13258
13578
|
}
|
|
13259
13579
|
if (stray.length) {
|
|
13260
|
-
console.log(
|
|
13580
|
+
console.log(import_chalk59.default.red(`
|
|
13261
13581
|
${stray.length} STRAY route(s) \u2014 not main-proxy, not on the allowlist`));
|
|
13262
13582
|
for (const d of stray) {
|
|
13263
|
-
console.log(
|
|
13264
|
-
console.log(
|
|
13583
|
+
console.log(import_chalk59.default.red(` \u2717 ${d.pattern}`) + import_chalk59.default.dim(` \u2192 ${d.script}`));
|
|
13584
|
+
console.log(import_chalk59.default.dim(` ${d.why}`));
|
|
13265
13585
|
}
|
|
13266
13586
|
checks.push({ name: "no stray routes", status: "FAIL", detail: `${stray.length}: ${stray.map((s) => s.pattern).join(", ")}` });
|
|
13267
13587
|
} else if (routeAuditBroke) {
|
|
@@ -13272,22 +13592,22 @@ function printDoors(data) {
|
|
|
13272
13592
|
const wd = data?.workers_dev ?? {};
|
|
13273
13593
|
const open = wd.enabled ?? [];
|
|
13274
13594
|
if (open.length) {
|
|
13275
|
-
console.log(
|
|
13595
|
+
console.log(import_chalk59.default.red(`
|
|
13276
13596
|
${open.length} of ${wd.total} worker(s) reachable on *.workers.dev`));
|
|
13277
|
-
for (const s of open) console.log(
|
|
13278
|
-
console.log(
|
|
13597
|
+
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})`));
|
|
13598
|
+
console.log(import_chalk59.default.dim(" A workers.dev hostname bypasses every CF route, WAF rule and the credit gate."));
|
|
13279
13599
|
checks.push({ name: "no workers.dev doors", status: "FAIL", detail: `${open.length} script(s) publicly reachable: ${open.map((s) => s.script).join(", ")}` });
|
|
13280
13600
|
} else if (devAuditBroke || !wd.total) {
|
|
13281
13601
|
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." });
|
|
13282
13602
|
} else {
|
|
13283
|
-
console.log(
|
|
13603
|
+
console.log(import_chalk59.default.green(`
|
|
13284
13604
|
\u2713 0 of ${wd.total} workers reachable on *.workers.dev`));
|
|
13285
13605
|
checks.push({ name: "no workers.dev doors", status: "PASS", detail: `all ${wd.total} scripts have workers.dev + previews disabled` });
|
|
13286
13606
|
}
|
|
13287
|
-
if (data?.how_to_fix) console.log(
|
|
13288
|
-
${data.reason}`) +
|
|
13607
|
+
if (data?.how_to_fix) console.log(import_chalk59.default.yellow(`
|
|
13608
|
+
${data.reason}`) + import_chalk59.default.dim(`
|
|
13289
13609
|
${data.how_to_fix}`));
|
|
13290
|
-
else for (const e of errs) console.log(
|
|
13610
|
+
else for (const e of errs) console.log(import_chalk59.default.red(`
|
|
13291
13611
|
audit error: ${e}`));
|
|
13292
13612
|
if (errs.length) {
|
|
13293
13613
|
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` });
|
|
@@ -13297,9 +13617,9 @@ function printDoors(data) {
|
|
|
13297
13617
|
async function runMeter(readLedger, opts) {
|
|
13298
13618
|
const checks = [];
|
|
13299
13619
|
const N = Math.max(1, Math.min(10, opts.count ?? 3));
|
|
13300
|
-
console.log(
|
|
13620
|
+
console.log(import_chalk59.default.bold("\n Meter \u2014 is 1 request charged exactly 1 request?\n"));
|
|
13301
13621
|
const snap = await readLedger();
|
|
13302
|
-
console.log(
|
|
13622
|
+
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
|
|
13303
13623
|
`));
|
|
13304
13624
|
const url = `https://${dpHost}/${SANDBOX.version}/${SANDBOX.environment}/`;
|
|
13305
13625
|
const headers = opts.key ? { "X-API-Key": opts.key } : {};
|
|
@@ -13381,27 +13701,27 @@ async function runMeter(readLedger, opts) {
|
|
|
13381
13701
|
return checks;
|
|
13382
13702
|
}
|
|
13383
13703
|
function printChecks(checks) {
|
|
13384
|
-
console.log(
|
|
13385
|
-
const mark = { PASS:
|
|
13704
|
+
console.log(import_chalk59.default.bold("\n Results\n"));
|
|
13705
|
+
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") };
|
|
13386
13706
|
for (const ch of checks) {
|
|
13387
|
-
console.log(` ${mark[ch.status]} ${
|
|
13388
|
-
console.log(
|
|
13707
|
+
console.log(` ${mark[ch.status]} ${import_chalk59.default.bold(ch.name)}`);
|
|
13708
|
+
console.log(import_chalk59.default.dim(` ${ch.detail}`));
|
|
13389
13709
|
}
|
|
13390
13710
|
const fails = checks.filter((c) => c.status === "FAIL").length;
|
|
13391
13711
|
const skips = checks.filter((c) => c.status === "SKIP").length;
|
|
13392
13712
|
const known = checks.filter((c) => c.status === "KNOWN").length;
|
|
13393
13713
|
console.log("");
|
|
13394
13714
|
const passes = checks.filter((c) => c.status === "PASS").length;
|
|
13395
|
-
if (fails) console.log(
|
|
13396
|
-
else if (passes) console.log(
|
|
13397
|
-
else console.log(
|
|
13398
|
-
if (known) console.log(
|
|
13399
|
-
if (skips) console.log(
|
|
13715
|
+
if (fails) console.log(import_chalk59.default.red(` ${fails} check(s) FAILED.`));
|
|
13716
|
+
else if (passes) console.log(import_chalk59.default.green(` ${passes} check(s) passed, 0 failed.`));
|
|
13717
|
+
else console.log(import_chalk59.default.yellow(" NOTHING WAS VERIFIED \u2014 every check was skipped."));
|
|
13718
|
+
if (known) console.log(import_chalk59.default.yellow(` ${known} known-open issue(s) still outstanding.`));
|
|
13719
|
+
if (skips) console.log(import_chalk59.default.dim(` ${skips} check(s) NOT RUN (see SKIP above) \u2014 those invariants are unverified.`));
|
|
13400
13720
|
console.log("");
|
|
13401
13721
|
}
|
|
13402
13722
|
|
|
13403
13723
|
// src/commands/op-test.ts
|
|
13404
|
-
var
|
|
13724
|
+
var import_chalk60 = __toESM(require("chalk"));
|
|
13405
13725
|
init_auth();
|
|
13406
13726
|
init_types();
|
|
13407
13727
|
var DASHBOARD_BASE9 = process.env.APIBLAZE_DASHBOARD_BASE || "https://dashboard.apiblaze.com";
|
|
@@ -13426,10 +13746,10 @@ async function scrubSandboxKeys(timeoutS = 150) {
|
|
|
13426
13746
|
try {
|
|
13427
13747
|
const r = await runnerCall({ action: "gut", id: TEARDOWN_GUT_ID }, timeoutS);
|
|
13428
13748
|
const first = r.results?.[0]?.detail ?? r.error ?? "";
|
|
13429
|
-
log(
|
|
13749
|
+
log(import_chalk60.default.dim(`teardown: ${first || (r.ok ? "sandbox fixture keys scrubbed" : "scrub reported a problem")}`));
|
|
13430
13750
|
return r.ok;
|
|
13431
13751
|
} catch (e) {
|
|
13432
|
-
log(
|
|
13752
|
+
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`));
|
|
13433
13753
|
return false;
|
|
13434
13754
|
}
|
|
13435
13755
|
}
|
|
@@ -13451,23 +13771,23 @@ async function runOpTest(o = {}) {
|
|
|
13451
13771
|
const guts = o.skipGuts ? [] : listing.guts.filter((g) => g.id !== TEARDOWN_GUT_ID && matches(g.id, void 0, { ...o, group: void 0 }));
|
|
13452
13772
|
const total = specs.length + guts.length;
|
|
13453
13773
|
if (total === 0) {
|
|
13454
|
-
log(
|
|
13774
|
+
log(import_chalk60.default.yellow("nothing matched"));
|
|
13455
13775
|
process.exitCode = 2;
|
|
13456
13776
|
return;
|
|
13457
13777
|
}
|
|
13458
|
-
log(
|
|
13778
|
+
log(import_chalk60.default.bold(`apiblaze op test \u2014 ${specs.length} spec(s) + ${guts.length} gut check(s) on sandbox ${listing.sandbox_team}`));
|
|
13459
13779
|
const results = [];
|
|
13460
13780
|
let failed = 0;
|
|
13461
13781
|
const started = Date.now();
|
|
13462
13782
|
const record = (r) => {
|
|
13463
13783
|
results.push(r);
|
|
13464
13784
|
if (!r.ok) failed += 1;
|
|
13465
|
-
const mark = r.ok ?
|
|
13785
|
+
const mark = r.ok ? import_chalk60.default.green("PASS") : import_chalk60.default.red("FAIL");
|
|
13466
13786
|
log(`[${mark}] ${r.id} \u2014 ${r.label} (${ms(r.duration_ms)})`);
|
|
13467
13787
|
if (!r.ok) {
|
|
13468
|
-
if (r.kind === "spec") for (const c of r.cases.filter((c2) => !c2.ok)) log(
|
|
13469
|
-
else if (r.error) log(
|
|
13470
|
-
else for (const x of r.results.filter((x2) => x2 && x2.ok === false)) log(
|
|
13788
|
+
if (r.kind === "spec") for (const c of r.cases.filter((c2) => !c2.ok)) log(import_chalk60.default.red(` \u2717 ${c.name}: ${c.detail}`));
|
|
13789
|
+
else if (r.error) log(import_chalk60.default.red(` \u2717 ${r.error}`));
|
|
13790
|
+
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)}`));
|
|
13471
13791
|
}
|
|
13472
13792
|
};
|
|
13473
13793
|
try {
|
|
@@ -13498,13 +13818,13 @@ async function runOpTest(o = {}) {
|
|
|
13498
13818
|
}
|
|
13499
13819
|
const summary = { ran: results.length, passed: results.length - failed, failed, duration_ms: Date.now() - started, sandbox_team: listing.sandbox_team };
|
|
13500
13820
|
log("");
|
|
13501
|
-
log(failed ?
|
|
13821
|
+
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)})`));
|
|
13502
13822
|
if (o.json) process.stdout.write(JSON.stringify({ ...summary, results }, null, 2) + "\n");
|
|
13503
13823
|
if (failed) process.exitCode = 1;
|
|
13504
13824
|
}
|
|
13505
13825
|
|
|
13506
13826
|
// src/commands/op-consumer.ts
|
|
13507
|
-
var
|
|
13827
|
+
var import_chalk61 = __toESM(require("chalk"));
|
|
13508
13828
|
var SANDBOX_TENANT = "portalteststenant";
|
|
13509
13829
|
var SANDBOX_PROXY = "portaltestproxypreapproved@2.7.0";
|
|
13510
13830
|
function proxyUrlFor(spec2, path11 = "/get") {
|
|
@@ -13546,9 +13866,9 @@ async function runOpConsumer(o = {}) {
|
|
|
13546
13866
|
const creds = loadConsumer();
|
|
13547
13867
|
const done = (r) => {
|
|
13548
13868
|
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 };
|
|
13549
|
-
const mark = out.skipped ?
|
|
13869
|
+
const mark = out.skipped ? import_chalk61.default.yellow("SKIP") : out.ok ? import_chalk61.default.green("PASS") : import_chalk61.default.red("FAIL");
|
|
13550
13870
|
log2(`[${mark}] ${out.id} \u2014 ${out.label}${out.tenant ? ` (tenant ${out.tenant})` : ""}${out.url ? ` \u2192 ${out.url}` : ""}`);
|
|
13551
|
-
for (const c of out.cases) if (!c.ok || out.skipped) log2((c.ok ?
|
|
13871
|
+
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}`));
|
|
13552
13872
|
if (o.json) process.stdout.write(JSON.stringify(out, null, 2) + "\n");
|
|
13553
13873
|
if (!out.ok && !out.skipped) process.exitCode = 1;
|
|
13554
13874
|
if (out.skipped) process.exitCode = 2;
|
|
@@ -13588,7 +13908,7 @@ async function runOpConsumer(o = {}) {
|
|
|
13588
13908
|
}
|
|
13589
13909
|
|
|
13590
13910
|
// src/commands/op-perf.ts
|
|
13591
|
-
var
|
|
13911
|
+
var import_chalk62 = __toESM(require("chalk"));
|
|
13592
13912
|
var RPS_DEFAULT = 100;
|
|
13593
13913
|
var RPS_CAP = 300;
|
|
13594
13914
|
var SECONDS_DEFAULT = 60;
|
|
@@ -13772,10 +14092,10 @@ async function readGateway(proxy, sent, signal) {
|
|
|
13772
14092
|
}
|
|
13773
14093
|
const left = deadline - Date.now();
|
|
13774
14094
|
if (left <= 0) {
|
|
13775
|
-
log3(
|
|
14095
|
+
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})` : ""}`));
|
|
13776
14096
|
return null;
|
|
13777
14097
|
}
|
|
13778
|
-
log3(
|
|
14098
|
+
log3(import_chalk62.default.dim(` waiting for the latency ledger\u2026 ${lastSeen}/${sent} rows for ${proxy}${lastErr ? ` (${lastErr})` : ""}`));
|
|
13779
14099
|
await sleep2(Math.min(GATEWAY_POLL_MS, left), signal);
|
|
13780
14100
|
}
|
|
13781
14101
|
}
|
|
@@ -13784,7 +14104,7 @@ async function runOpPerf(o = {}) {
|
|
|
13784
14104
|
const thresholds = thresholdsFrom(o);
|
|
13785
14105
|
const ctrl = new AbortController();
|
|
13786
14106
|
const onSigint = () => {
|
|
13787
|
-
log3(
|
|
14107
|
+
log3(import_chalk62.default.yellow("\ninterrupted \u2014 stopping the load and tearing down the fixture"));
|
|
13788
14108
|
ctrl.abort();
|
|
13789
14109
|
};
|
|
13790
14110
|
process.once("SIGINT", onSigint);
|
|
@@ -13793,7 +14113,7 @@ async function runOpPerf(o = {}) {
|
|
|
13793
14113
|
if (!o.key) throw new Error("--url needs --key (the fixture proxy's API key)");
|
|
13794
14114
|
fixture = { url: o.url, key: o.key, proxy: proxyFromUrl(o.url), provisioned: false };
|
|
13795
14115
|
} else {
|
|
13796
|
-
log3(
|
|
14116
|
+
log3(import_chalk62.default.dim("provisioning the perf fixture through the runner\u2026"));
|
|
13797
14117
|
const f = await runnerCall({ action: "perf-fixture" }, 120);
|
|
13798
14118
|
if (!f?.url || !f?.key) throw new Error(`perf-fixture returned no url/key: ${JSON.stringify(f).slice(0, 200)}`);
|
|
13799
14119
|
fixture = { url: f.url, key: f.key, proxy: f.proxy || proxyFromUrl(f.url), team: f.team, provisioned: true };
|
|
@@ -13801,7 +14121,7 @@ async function runOpPerf(o = {}) {
|
|
|
13801
14121
|
try {
|
|
13802
14122
|
const total = Math.round(rps * seconds);
|
|
13803
14123
|
const intervalMs = 1e3 / rps;
|
|
13804
|
-
log3(
|
|
14124
|
+
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})` : ""}`));
|
|
13805
14125
|
const samples = [];
|
|
13806
14126
|
const inflight = /* @__PURE__ */ new Set();
|
|
13807
14127
|
let fired = 0;
|
|
@@ -13809,7 +14129,7 @@ async function runOpPerf(o = {}) {
|
|
|
13809
14129
|
const progress = setInterval(() => {
|
|
13810
14130
|
const m = summarize(samples, Date.now() - t0);
|
|
13811
14131
|
const secs = (Date.now() - t0) / 1e3;
|
|
13812
|
-
log3(
|
|
14132
|
+
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)`));
|
|
13813
14133
|
}, PROGRESS_EVERY_MS);
|
|
13814
14134
|
const { sent, elapsedMs } = await schedule({
|
|
13815
14135
|
total,
|
|
@@ -13823,21 +14143,21 @@ async function runOpPerf(o = {}) {
|
|
|
13823
14143
|
}
|
|
13824
14144
|
});
|
|
13825
14145
|
clearInterval(progress);
|
|
13826
|
-
if (inflight.size) log3(
|
|
14146
|
+
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`));
|
|
13827
14147
|
await Promise.all([...inflight]);
|
|
13828
14148
|
const client = summarize(samples, elapsedMs);
|
|
13829
14149
|
log3("");
|
|
13830
|
-
log3(` ${
|
|
14150
|
+
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}`);
|
|
13831
14151
|
const gateway = ctrl.signal.aborted ? null : await readGateway(fixture.proxy, sent, ctrl.signal);
|
|
13832
14152
|
if (gateway) {
|
|
13833
14153
|
const extras = [
|
|
13834
14154
|
gateway.kv_per_req !== void 0 ? `kv/req ${gateway.kv_per_req}` : "",
|
|
13835
14155
|
gateway.redis_per_req !== void 0 ? `redis/req ${gateway.redis_per_req}` : ""
|
|
13836
14156
|
].filter(Boolean).join(" ");
|
|
13837
|
-
log3(` ${
|
|
13838
|
-
for (const b of gateway.blame ?? []) log3(
|
|
14157
|
+
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}` : ""})`)}`);
|
|
14158
|
+
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}`));
|
|
13839
14159
|
} else {
|
|
13840
|
-
log3(` ${
|
|
14160
|
+
log3(` ${import_chalk62.default.bold("gateway")} ${import_chalk62.default.yellow("not available")} ${import_chalk62.default.dim("(gateway thresholds not evaluated)")}`);
|
|
13841
14161
|
}
|
|
13842
14162
|
const breaches = evaluate(client, gateway, thresholds);
|
|
13843
14163
|
const aborted = ctrl.signal.aborted;
|
|
@@ -13845,7 +14165,7 @@ async function runOpPerf(o = {}) {
|
|
|
13845
14165
|
log3("");
|
|
13846
14166
|
const row = (metric, value, limit, unit) => {
|
|
13847
14167
|
const hit = breaches.find((b) => b.metric === metric);
|
|
13848
|
-
log3(`[${hit ?
|
|
14168
|
+
log3(`[${hit ? import_chalk62.default.red("FAIL") : import_chalk62.default.green("PASS")}] ${metric.padEnd(11)} ${String(value)}${unit} ${hit ? ">" : "\u2264"} ${limit}${unit}`);
|
|
13849
14169
|
};
|
|
13850
14170
|
row("client_p50", client.p50, thresholds.p50_ms, "ms");
|
|
13851
14171
|
row("client_p95", client.p95, thresholds.p95_ms, "ms");
|
|
@@ -13854,12 +14174,12 @@ async function runOpPerf(o = {}) {
|
|
|
13854
14174
|
row("gateway_p50", gateway.p50, thresholds.gateway_p50_ms, "ms");
|
|
13855
14175
|
row("gateway_p95", gateway.p95, thresholds.gateway_p95_ms, "ms");
|
|
13856
14176
|
} else {
|
|
13857
|
-
log3(`[${
|
|
14177
|
+
log3(`[${import_chalk62.default.yellow("SKIP")}] gateway_p50 / gateway_p95 \u2014 ledger rows not available`);
|
|
13858
14178
|
}
|
|
13859
14179
|
log3("");
|
|
13860
|
-
if (aborted) log3(
|
|
13861
|
-
else if (ok) log3(
|
|
13862
|
-
else log3(
|
|
14180
|
+
if (aborted) log3(import_chalk62.default.red(`FAIL \u2014 interrupted after ${sent}/${total} requests`));
|
|
14181
|
+
else if (ok) log3(import_chalk62.default.green(`PASS \u2014 ${client.sent} requests at ${client.rps_achieved} rps, no threshold breached`));
|
|
14182
|
+
else log3(import_chalk62.default.red(`FAIL \u2014 ${breaches.length} threshold(s) breached: ${breaches.map((b) => `${b.metric} ${b.value} > ${b.limit}`).join(", ")}`));
|
|
13863
14183
|
if (o.json) {
|
|
13864
14184
|
const doc = {
|
|
13865
14185
|
kind: "perf",
|
|
@@ -13897,9 +14217,9 @@ async function runOpPerf(o = {}) {
|
|
|
13897
14217
|
if (fixture.provisioned) {
|
|
13898
14218
|
try {
|
|
13899
14219
|
await runnerCall({ action: "perf-teardown", proxy: fixture.proxy }, 120);
|
|
13900
|
-
log3(
|
|
14220
|
+
log3(import_chalk62.default.dim(`fixture ${fixture.proxy} torn down`));
|
|
13901
14221
|
} catch (e) {
|
|
13902
|
-
log3(
|
|
14222
|
+
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`));
|
|
13903
14223
|
process.exitCode = 1;
|
|
13904
14224
|
}
|
|
13905
14225
|
await scrubSandboxKeys(120);
|
|
@@ -13933,93 +14253,93 @@ var OP_COMMANDS = [
|
|
|
13933
14253
|
function renderOpCommands() {
|
|
13934
14254
|
const width = Math.max(...OP_COMMANDS.map((c) => c.cmd.length)) + 10;
|
|
13935
14255
|
const lines = OP_COMMANDS.map((c) => {
|
|
13936
|
-
const left = ` ${
|
|
14256
|
+
const left = ` ${import_chalk63.default.cyan(`apiblaze ${c.cmd}`)}`;
|
|
13937
14257
|
const pad = " ".repeat(Math.max(1, width - c.cmd.length));
|
|
13938
|
-
return `${left}${pad}${c.blurb}${c.extra ? " " +
|
|
14258
|
+
return `${left}${pad}${c.blurb}${c.extra ? " " + import_chalk63.default.dim(`(${c.extra})`) : ""}`;
|
|
13939
14259
|
});
|
|
13940
14260
|
return [
|
|
13941
|
-
|
|
14261
|
+
import_chalk63.default.bold("Operator commands"),
|
|
13942
14262
|
...lines,
|
|
13943
14263
|
"",
|
|
13944
|
-
|
|
13945
|
-
|
|
13946
|
-
|
|
14264
|
+
import_chalk63.default.dim(" Operators only. The gate is server-side (dashboard /api/cli/op checks the"),
|
|
14265
|
+
import_chalk63.default.dim(" signed-in email, admin-api re-checks with operatorGate) \u2014 a patched CLI just"),
|
|
14266
|
+
import_chalk63.default.dim(" gets 403s. Every op call is read-only except `op sweep`."),
|
|
13947
14267
|
"",
|
|
13948
|
-
|
|
13949
|
-
|
|
13950
|
-
|
|
14268
|
+
import_chalk63.default.dim(" Not a CLI command: to prune all non-CP data run scripts/nuke-but-cp.sh --apply --sweep"),
|
|
14269
|
+
import_chalk63.default.dim(" in the repo. Operator dashboards (dlq, thresholds, throttling, pricing, billing,"),
|
|
14270
|
+
import_chalk63.default.dim(" agent-spend, teams, tests, leak-detection, lifecycle) live at /operator/* in the app.")
|
|
13951
14271
|
].join("\n");
|
|
13952
14272
|
}
|
|
13953
14273
|
function printResidue(report, applied) {
|
|
13954
14274
|
const up = report?.upstash ?? {};
|
|
13955
14275
|
const fga = report?.fga ?? {};
|
|
13956
14276
|
const ghosts = report?.ghosts ?? {};
|
|
13957
|
-
console.log(
|
|
13958
|
-
console.log(
|
|
14277
|
+
console.log(import_chalk63.default.bold(applied ? "\nExternal-residue sweep" : "\nExternal residue (dry-run \u2014 nothing deleted)"));
|
|
14278
|
+
console.log(import_chalk63.default.bold("\n Upstash"));
|
|
13959
14279
|
const orphans = up.orphans ?? [];
|
|
13960
|
-
if (orphans.length === 0) console.log(
|
|
13961
|
-
for (const o of orphans) console.log(` ${
|
|
13962
|
-
console.log(
|
|
14280
|
+
if (orphans.length === 0) console.log(import_chalk63.default.green(" no orphaned keys"));
|
|
14281
|
+
for (const o of orphans) console.log(` ${import_chalk63.default.yellow(o.key)} ${import_chalk63.default.dim(`\u2014 ${o.reason}`)}`);
|
|
14282
|
+
console.log(import_chalk63.default.dim(` kept (live principals): ${up.kept ?? 0} \xB7 anon wallets (untouched): ${up.anon_wallets ?? 0}`));
|
|
13963
14283
|
if (up.anon_wallet_detail) {
|
|
13964
14284
|
const d = up.anon_wallet_detail;
|
|
13965
|
-
console.log(
|
|
14285
|
+
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)"}`));
|
|
13966
14286
|
}
|
|
13967
14287
|
if (up.keyspace_census) {
|
|
13968
14288
|
const census = Object.entries(up.keyspace_census).map(([k, v]) => `${k}=${v}`).join(" \xB7 ");
|
|
13969
|
-
console.log(
|
|
14289
|
+
console.log(import_chalk63.default.dim(` keyspace: ${census}`));
|
|
13970
14290
|
}
|
|
13971
|
-
if (up.unknown?.length) console.log(
|
|
13972
|
-
if (applied) console.log(` ${
|
|
13973
|
-
for (const e of up.errors ?? []) console.log(
|
|
13974
|
-
console.log(
|
|
14291
|
+
if (up.unknown?.length) console.log(import_chalk63.default.dim(` unknown (never deleted): ${up.unknown.join(", ")}`));
|
|
14292
|
+
if (applied) console.log(` ${import_chalk63.default.bold(String(up.deleted ?? 0))} key(s) deleted`);
|
|
14293
|
+
for (const e of up.errors ?? []) console.log(import_chalk63.default.red(` error: ${e}`));
|
|
14294
|
+
console.log(import_chalk63.default.bold("\n OpenFGA / Neon \u2014 orphan stores"));
|
|
13975
14295
|
if (applied) {
|
|
13976
14296
|
const swept = fga?.swept ?? [];
|
|
13977
|
-
if (swept.length === 0) console.log(
|
|
14297
|
+
if (swept.length === 0) console.log(import_chalk63.default.green(" no orphaned stores"));
|
|
13978
14298
|
for (const s of swept) {
|
|
13979
14299
|
console.log(
|
|
13980
|
-
` ${
|
|
14300
|
+
` ${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`)}`
|
|
13981
14301
|
);
|
|
13982
14302
|
}
|
|
13983
|
-
if (fga?.remaining) console.log(
|
|
14303
|
+
if (fga?.remaining) console.log(import_chalk63.default.yellow(` ${fga.remaining} more orphan store(s) \u2014 re-run to drain`));
|
|
13984
14304
|
const st = fga?.side_tables;
|
|
13985
|
-
if (st) console.log(
|
|
14305
|
+
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})` : ""}`));
|
|
13986
14306
|
} else {
|
|
13987
14307
|
const fgaOrphans = fga?.orphans ?? [];
|
|
13988
|
-
if (fgaOrphans.length === 0) console.log(
|
|
14308
|
+
if (fgaOrphans.length === 0) console.log(import_chalk63.default.green(" no orphaned stores"));
|
|
13989
14309
|
for (const s of fgaOrphans) {
|
|
13990
14310
|
const src = s.in_openfga ? "live in OpenFGA" : "Neon tuples only";
|
|
13991
|
-
console.log(` ${
|
|
14311
|
+
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)`)}`);
|
|
13992
14312
|
}
|
|
13993
|
-
console.log(
|
|
14313
|
+
console.log(import_chalk63.default.dim(` kept stores: ${(fga?.kept_store_ids ?? []).length}`));
|
|
13994
14314
|
const st = fga?.side_tables;
|
|
13995
|
-
if (st) console.log(
|
|
14315
|
+
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`));
|
|
13996
14316
|
}
|
|
13997
|
-
for (const e of fga?.errors ?? []) console.log(
|
|
13998
|
-
console.log(
|
|
14317
|
+
for (const e of fga?.errors ?? []) console.log(import_chalk63.default.red(` error: ${e}`));
|
|
14318
|
+
console.log(import_chalk63.default.bold("\n OpenFGA \u2014 ghost tuples in surviving stores"));
|
|
13999
14319
|
if (applied) {
|
|
14000
|
-
if ((ghosts?.ghost_count ?? 0) === 0) console.log(
|
|
14001
|
-
else console.log(` ${
|
|
14320
|
+
if ((ghosts?.ghost_count ?? 0) === 0) console.log(import_chalk63.default.green(" no ghost tuples"));
|
|
14321
|
+
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)`)}`);
|
|
14002
14322
|
} else {
|
|
14003
14323
|
const n = ghosts?.ghost_count ?? 0;
|
|
14004
|
-
if (n === 0) console.log(
|
|
14324
|
+
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)`)}`));
|
|
14005
14325
|
else {
|
|
14006
|
-
console.log(
|
|
14326
|
+
console.log(import_chalk63.default.yellow(` ${n} ghost tuple(s) referencing entities absent from D1:`));
|
|
14007
14327
|
for (const g of (ghosts.ghosts ?? []).slice(0, 20)) {
|
|
14008
|
-
console.log(
|
|
14328
|
+
console.log(import_chalk63.default.dim(` ${g.object_type}:${g.object_id} ${g.relation} ${g._user}`));
|
|
14009
14329
|
}
|
|
14010
|
-
if (n > 20) console.log(
|
|
14330
|
+
if (n > 20) console.log(import_chalk63.default.dim(` \u2026 and ${n - 20} more`));
|
|
14011
14331
|
}
|
|
14012
14332
|
}
|
|
14013
|
-
for (const e of ghosts?.errors ?? []) console.log(
|
|
14333
|
+
for (const e of ghosts?.errors ?? []) console.log(import_chalk63.default.red(` error: ${e}`));
|
|
14014
14334
|
console.log();
|
|
14015
14335
|
}
|
|
14016
14336
|
async function runOp(sub, opts = {}, view) {
|
|
14017
14337
|
if (!loadCredentials()) {
|
|
14018
|
-
console.log(
|
|
14338
|
+
console.log(import_chalk63.default.dim("Not logged in. Run `apiblaze login`."));
|
|
14019
14339
|
return;
|
|
14020
14340
|
}
|
|
14021
14341
|
if (!isOperatorLogin()) {
|
|
14022
|
-
console.log(
|
|
14342
|
+
console.log(import_chalk63.default.dim("`apiblaze op` is only available to platform operators."));
|
|
14023
14343
|
return;
|
|
14024
14344
|
}
|
|
14025
14345
|
switch (sub) {
|
|
@@ -14075,17 +14395,17 @@ async function runOp(sub, opts = {}, view) {
|
|
|
14075
14395
|
const nSide = (st.soft_deleted_stores ?? 0) + (st.orphan_models ?? 0) + (st.orphan_changelog ?? 0);
|
|
14076
14396
|
printResidue(report, false);
|
|
14077
14397
|
if (nUp + nFga + nGhost + nSide === 0) {
|
|
14078
|
-
console.log(
|
|
14398
|
+
console.log(import_chalk63.default.green("Nothing to sweep."));
|
|
14079
14399
|
return;
|
|
14080
14400
|
}
|
|
14081
14401
|
if (!opts.yes) {
|
|
14082
14402
|
const readline3 = await import("readline/promises");
|
|
14083
14403
|
const rl = readline3.createInterface({ input: process.stdin, output: process.stdout });
|
|
14084
14404
|
const answer = await rl.question(
|
|
14085
|
-
|
|
14405
|
+
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: `)
|
|
14086
14406
|
);
|
|
14087
14407
|
rl.close();
|
|
14088
|
-
if (answer.trim() !== "sweep") return void console.log(
|
|
14408
|
+
if (answer.trim() !== "sweep") return void console.log(import_chalk63.default.dim("Aborted."));
|
|
14089
14409
|
}
|
|
14090
14410
|
const result = await opCall({ method: "POST", path: "/operator/external-residue/sweep", summary: "external residue sweep" });
|
|
14091
14411
|
if (opts.json) return void console.log(JSON.stringify(result, null, 2));
|
|
@@ -14095,25 +14415,25 @@ async function runOp(sub, opts = {}, view) {
|
|
|
14095
14415
|
case "mark": {
|
|
14096
14416
|
const label3 = (view ?? "").trim();
|
|
14097
14417
|
if (!label3) {
|
|
14098
|
-
console.log(
|
|
14418
|
+
console.log(import_chalk63.default.red("Give the change a name:") + import_chalk63.default.cyan(' apiblaze op mark "cached tenant count"'));
|
|
14099
14419
|
return;
|
|
14100
14420
|
}
|
|
14101
14421
|
const res = await opCall({ method: "POST", path: "/operator/latency/mark", body: { label: label3 }, summary: "record change marker" });
|
|
14102
14422
|
const ts = new Date(res?.marker?.ts ?? Date.now()).toISOString();
|
|
14103
|
-
console.log(
|
|
14104
|
-
Marked: `) +
|
|
14105
|
-
console.log(
|
|
14106
|
-
console.log(
|
|
14423
|
+
console.log(import_chalk63.default.green(`
|
|
14424
|
+
Marked: `) + import_chalk63.default.bold(label3));
|
|
14425
|
+
console.log(import_chalk63.default.dim(` ${ts}`));
|
|
14426
|
+
console.log(import_chalk63.default.dim(` Once traffic has run on both sides, compare with: `) + import_chalk63.default.cyan("apiblaze op latency compare") + "\n");
|
|
14107
14427
|
return;
|
|
14108
14428
|
}
|
|
14109
14429
|
case "credits": {
|
|
14110
14430
|
const data = await opCall({ method: "GET", path: "/operator/credits", summary: "list credit wallets" });
|
|
14111
14431
|
if (opts.json) return void console.log(JSON.stringify(data, null, 2));
|
|
14112
14432
|
const accounts = data?.accounts ?? [];
|
|
14113
|
-
if (accounts.length === 0) return void console.log(
|
|
14433
|
+
if (accounts.length === 0) return void console.log(import_chalk63.default.dim("No credit wallets."));
|
|
14114
14434
|
for (const a of accounts) {
|
|
14115
14435
|
const bal = typeof a.balance_cents === "number" ? `$${(a.balance_cents / 100).toFixed(2)}` : "?";
|
|
14116
|
-
console.log(` ${
|
|
14436
|
+
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") : ""}`);
|
|
14117
14437
|
}
|
|
14118
14438
|
return;
|
|
14119
14439
|
}
|
|
@@ -14124,7 +14444,7 @@ async function runOp(sub, opts = {}, view) {
|
|
|
14124
14444
|
case "billing": {
|
|
14125
14445
|
const which = (view ?? "").trim().toLowerCase();
|
|
14126
14446
|
if (which && which !== "doors" && which !== "meter") {
|
|
14127
|
-
return void console.log(
|
|
14447
|
+
return void console.log(import_chalk63.default.red(`Unknown: apiblaze op billing ${which}. Use 'doors', 'meter', or neither for both.`));
|
|
14128
14448
|
}
|
|
14129
14449
|
const checks = [];
|
|
14130
14450
|
let doorsData = null;
|
|
@@ -14193,29 +14513,29 @@ async function runOp(sub, opts = {}, view) {
|
|
|
14193
14513
|
const data = await opCall({ method: "GET", path: `/operator/latency/grades${q}`, summary: "latency grades" });
|
|
14194
14514
|
if (opts.json) return void console.log(JSON.stringify(data, null, 2));
|
|
14195
14515
|
const t = data.thresholds_ms;
|
|
14196
|
-
console.log(
|
|
14197
|
-
console.log(
|
|
14516
|
+
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)"));
|
|
14517
|
+
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+
|
|
14198
14518
|
`));
|
|
14199
|
-
console.log(
|
|
14519
|
+
console.log(import_chalk63.default.dim(" date reqs excellent okay bad terrible"));
|
|
14200
14520
|
for (const d of data.days ?? []) {
|
|
14201
14521
|
const p = d.pct;
|
|
14202
|
-
const cell = (v, colour) => v > 0 ? colour(`${String(v).padStart(5)}%`) :
|
|
14522
|
+
const cell = (v, colour) => v > 0 ? colour(`${String(v).padStart(5)}%`) : import_chalk63.default.dim(`${String(v).padStart(5)}%`);
|
|
14203
14523
|
console.log(
|
|
14204
|
-
` ${d.date} ${String(d.total).padStart(5)} ${cell(p.excellent,
|
|
14524
|
+
` ${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)}`
|
|
14205
14525
|
);
|
|
14206
14526
|
}
|
|
14207
14527
|
const cul = data.culprits ?? [];
|
|
14208
14528
|
if (cul.length) {
|
|
14209
|
-
console.log(
|
|
14210
|
-
console.log(
|
|
14529
|
+
console.log(import_chalk63.default.bold("\n Who caused the bad and terrible ones\n"));
|
|
14530
|
+
console.log(import_chalk63.default.dim(" bad terrible feature \u2192 dependency"));
|
|
14211
14531
|
for (const r of cul.slice(0, 12)) {
|
|
14212
14532
|
if (!r.bad && !r.terrible) continue;
|
|
14213
14533
|
console.log(
|
|
14214
|
-
` ${String(r.bad).padStart(6)} ${
|
|
14534
|
+
` ${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)}`
|
|
14215
14535
|
);
|
|
14216
14536
|
}
|
|
14217
14537
|
}
|
|
14218
|
-
if (data.caveat) console.log(
|
|
14538
|
+
if (data.caveat) console.log(import_chalk63.default.dim(`
|
|
14219
14539
|
\u26A0 ${data.caveat}
|
|
14220
14540
|
`));
|
|
14221
14541
|
return;
|
|
@@ -14224,24 +14544,24 @@ async function runOp(sub, opts = {}, view) {
|
|
|
14224
14544
|
const data = await opCall({ method: "GET", path: `/operator/latency/compare${q}`, summary: "latency before/after" });
|
|
14225
14545
|
if (opts.json) return void console.log(JSON.stringify(data, null, 2));
|
|
14226
14546
|
const b = data.before, a = data.after, d = data.delta;
|
|
14227
|
-
console.log(
|
|
14228
|
-
Before vs after: `) +
|
|
14229
|
-
console.log(
|
|
14547
|
+
console.log(import_chalk63.default.bold(`
|
|
14548
|
+
Before vs after: `) + import_chalk63.default.cyan(data.marker.label));
|
|
14549
|
+
console.log(import_chalk63.default.dim(` marked ${new Date(data.marker.ts).toISOString()} \xB7 ${data.window_hours}h either side
|
|
14230
14550
|
`));
|
|
14231
14551
|
const row = (name, before, after, delta) => {
|
|
14232
14552
|
const arrow = delta === 0 ? "=" : delta < 0 ? "\u2193" : "\u2191";
|
|
14233
14553
|
const txt = `${String(before).padStart(6)}ms \u2192${String(after).padStart(7)}ms ${arrow}${Math.abs(delta)}ms`;
|
|
14234
|
-
console.log(` ${name.padEnd(22)}${data.trustworthy ? delta <= 0 ?
|
|
14554
|
+
console.log(` ${name.padEnd(22)}${data.trustworthy ? delta <= 0 ? import_chalk63.default.green(txt) : import_chalk63.default.red(txt) : import_chalk63.default.dim(txt)}`);
|
|
14235
14555
|
};
|
|
14236
|
-
console.log(
|
|
14556
|
+
console.log(import_chalk63.default.dim(" metric before after change"));
|
|
14237
14557
|
row("total p50", b.total_p50, a.total_p50, d.total_p50);
|
|
14238
14558
|
row("total p95", b.total_p95, a.total_p95, d.total_p95);
|
|
14239
14559
|
row("apiblaze overhead p50", b.gw_p50, a.gw_p50, d.gw_p50);
|
|
14240
14560
|
row("apiblaze overhead p95", b.gw_p95, a.gw_p95, d.gw_p95);
|
|
14241
|
-
console.log(
|
|
14561
|
+
console.log(import_chalk63.default.dim(`
|
|
14242
14562
|
requests: ${b.requests} before \xB7 ${a.requests} after`));
|
|
14243
14563
|
for (const w of data.warnings ?? []) {
|
|
14244
|
-
console.log((data.trustworthy ?
|
|
14564
|
+
console.log((data.trustworthy ? import_chalk63.default.dim : import_chalk63.default.yellow)(` ${data.trustworthy ? "\xB7" : "\u26A0"} ${w}`));
|
|
14245
14565
|
}
|
|
14246
14566
|
console.log("");
|
|
14247
14567
|
return;
|
|
@@ -14250,19 +14570,19 @@ Before vs after: `) + import_chalk62.default.cyan(data.marker.label));
|
|
|
14250
14570
|
const data = await opCall({ method: "GET", path: `/operator/latency/slow${q}`, summary: "slowest requests" });
|
|
14251
14571
|
if (opts.json) return void console.log(JSON.stringify(data, null, 2));
|
|
14252
14572
|
const rows2 = data?.rows ?? [];
|
|
14253
|
-
if (!rows2.length) return void console.log(
|
|
14254
|
-
console.log(
|
|
14573
|
+
if (!rows2.length) return void console.log(import_chalk63.default.dim("No requests over the threshold in that window."));
|
|
14574
|
+
console.log(import_chalk63.default.bold(`
|
|
14255
14575
|
Slowest requests \u2014 last ${data.window_hours}h, over ${data.min_ms}ms
|
|
14256
14576
|
`));
|
|
14257
|
-
console.log(
|
|
14577
|
+
console.log(import_chalk63.default.dim(" total ours theirs blame request id"));
|
|
14258
14578
|
for (const r of rows2.slice(0, 30)) {
|
|
14259
14579
|
console.log(
|
|
14260
|
-
` ${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)} ${
|
|
14580
|
+
` ${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 || "")}`
|
|
14261
14581
|
);
|
|
14262
14582
|
}
|
|
14263
|
-
console.log(
|
|
14583
|
+
console.log(import_chalk63.default.dim(`
|
|
14264
14584
|
The last column is the request id (Cloudflare calls it a "cf-ray"). Look one up with`));
|
|
14265
|
-
console.log(
|
|
14585
|
+
console.log(import_chalk63.default.dim(` \`apiblaze logs\` for that request's exact per-feature breakdown \u2014 unsampled, unlike the table above.
|
|
14266
14586
|
`));
|
|
14267
14587
|
return;
|
|
14268
14588
|
}
|
|
@@ -14270,18 +14590,18 @@ Slowest requests \u2014 last ${data.window_hours}h, over ${data.min_ms}ms
|
|
|
14270
14590
|
const data = await opCall({ method: "GET", path: `/operator/latency/llm${q}`, summary: "llm latency" });
|
|
14271
14591
|
if (opts.json) return void console.log(JSON.stringify(data, null, 2));
|
|
14272
14592
|
const rows2 = data?.rows ?? [];
|
|
14273
|
-
if (!rows2.length) return void console.log(
|
|
14274
|
-
console.log(
|
|
14593
|
+
if (!rows2.length) return void console.log(import_chalk63.default.dim("No LLM traffic in that window."));
|
|
14594
|
+
console.log(import_chalk63.default.bold(`
|
|
14275
14595
|
LLM timing \u2014 last ${data.window_hours}h
|
|
14276
14596
|
`));
|
|
14277
|
-
console.log(
|
|
14597
|
+
console.log(import_chalk63.default.dim(" turns turn p95 ttfc p95 gen p95 reserve p95 in/out tokens p95 model"));
|
|
14278
14598
|
for (const r of rows2) {
|
|
14279
14599
|
const n = r.turns ?? r.requests ?? 0;
|
|
14280
14600
|
console.log(
|
|
14281
14601
|
` ${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 || "-"}`
|
|
14282
14602
|
);
|
|
14283
14603
|
}
|
|
14284
|
-
console.log(
|
|
14604
|
+
console.log(import_chalk63.default.dim(`
|
|
14285
14605
|
${data.note}
|
|
14286
14606
|
`));
|
|
14287
14607
|
return;
|
|
@@ -14292,26 +14612,26 @@ LLM timing \u2014 last ${data.window_hours}h
|
|
|
14292
14612
|
]);
|
|
14293
14613
|
if (opts.json) return void console.log(JSON.stringify({ blame, summary }, null, 2));
|
|
14294
14614
|
const ov = summary?.apiblaze_overhead_ms ?? {};
|
|
14295
|
-
console.log(
|
|
14615
|
+
console.log(import_chalk63.default.bold(`
|
|
14296
14616
|
Latency \u2014 last ${summary?.window_hours ?? "?"}h, ${Number(summary?.requests ?? 0).toLocaleString()} requests
|
|
14297
14617
|
`));
|
|
14298
|
-
console.log(` ${
|
|
14299
|
-
console.log(` ${
|
|
14300
|
-
console.log(
|
|
14618
|
+
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")}`);
|
|
14619
|
+
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")}`);
|
|
14620
|
+
console.log(import_chalk63.default.dim(` (per-request percentiles \u2014 never subtract one from the other)
|
|
14301
14621
|
`));
|
|
14302
14622
|
const rows = blame?.blame ?? [];
|
|
14303
|
-
if (!rows.length) return void console.log(
|
|
14304
|
-
console.log(
|
|
14305
|
-
console.log(
|
|
14623
|
+
if (!rows.length) return void console.log(import_chalk63.default.dim("No latency rows in that window."));
|
|
14624
|
+
console.log(import_chalk63.default.bold(" Which feature ate the time, and what inside it\n"));
|
|
14625
|
+
console.log(import_chalk63.default.dim(" share p95 feature \u2192 dependency"));
|
|
14306
14626
|
for (const r of rows.slice(0, 15)) {
|
|
14307
14627
|
const share = `${(r.share * 100).toFixed(1)}%`;
|
|
14308
|
-
console.log(` ${share.padStart(6)} ${String(r.p95_ms).padStart(6)}ms ${
|
|
14628
|
+
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)}`);
|
|
14309
14629
|
}
|
|
14310
14630
|
console.log("");
|
|
14311
14631
|
return;
|
|
14312
14632
|
}
|
|
14313
14633
|
default:
|
|
14314
|
-
console.log(
|
|
14634
|
+
console.log(import_chalk63.default.red(`Unknown op subcommand '${sub}'. Run \`apiblaze op\` for the menu.`));
|
|
14315
14635
|
}
|
|
14316
14636
|
}
|
|
14317
14637
|
|
|
@@ -14362,8 +14682,8 @@ agent.command("openapi").description("Chat to build your API spec from real traf
|
|
|
14362
14682
|
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)));
|
|
14363
14683
|
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) => {
|
|
14364
14684
|
const looksLikeSource = !!project && (/^https?:\/\//i.test(project) || /\.(ya?ml|json)$/i.test(project) || import_fs5.default.existsSync(project));
|
|
14365
|
-
const
|
|
14366
|
-
return runApichat({ ...opts, target, project: looksLikeSource ? void 0 : project, openapispec: opts.openapispec ?? opts.openapi });
|
|
14685
|
+
const target2 = opts.target ?? (looksLikeSource ? project : void 0);
|
|
14686
|
+
return runApichat({ ...opts, target: target2, project: looksLikeSource ? void 0 : project, openapispec: opts.openapispec ?? opts.openapi });
|
|
14367
14687
|
}));
|
|
14368
14688
|
var llm = program.command("llm").description("Manage a local LLM provider key for chat (optional \u2014 lifts model quality, bills your key)");
|
|
14369
14689
|
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)));
|
|
@@ -14381,7 +14701,7 @@ program.command("dev").description(`Put the code running on this machine behind
|
|
|
14381
14701
|
try {
|
|
14382
14702
|
const resolved = parseInt(port ?? opts.port, 10);
|
|
14383
14703
|
if (Number.isNaN(resolved)) {
|
|
14384
|
-
console.error(
|
|
14704
|
+
console.error(import_chalk64.default.red(`Invalid port: ${port ?? opts.port}`));
|
|
14385
14705
|
process.exit(1);
|
|
14386
14706
|
}
|
|
14387
14707
|
await runDev({ ...opts, port: resolved });
|
|
@@ -14444,13 +14764,25 @@ var logs = program.command("logs").description("API Producer view \u2014 stream
|
|
|
14444
14764
|
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())));
|
|
14445
14765
|
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)));
|
|
14446
14766
|
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)));
|
|
14447
|
-
program.command("throttle").description("Set rate limits and quotas for a proxy").argument("<project>", "Project name or id").option("--rate <n>", "
|
|
14767
|
+
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)));
|
|
14768
|
+
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");
|
|
14769
|
+
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)));
|
|
14770
|
+
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", `
|
|
14771
|
+
Example \u2014 lock your backend to the proxy:
|
|
14772
|
+
export TARGET_SERVER_SECRET=$(openssl rand -hex 24) # also give it to your backend
|
|
14773
|
+
apiblaze transform set-header shop x-target-api-key --value-env TARGET_SERVER_SECRET --secret`).action(action((project, header, opts) => runTransformSetHeader(project, header, opts)));
|
|
14774
|
+
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)));
|
|
14448
14775
|
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)));
|
|
14449
|
-
var domain = program.command("domain").description("Use your own domain for
|
|
14450
|
-
domain.command("add").description("Add your own domain (shows the DNS records to set)").argument("
|
|
14451
|
-
|
|
14452
|
-
|
|
14453
|
-
|
|
14776
|
+
var domain = program.command("domain").description("Use your own domain for your API, MCP server, developer portal or login pages (billed monthly)");
|
|
14777
|
+
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", `
|
|
14778
|
+
Examples:
|
|
14779
|
+
apiblaze domain add shop --domain api.acme.com # the API
|
|
14780
|
+
apiblaze domain add shop --domain mcp.acme.com --kind mcp # the MCP server
|
|
14781
|
+
apiblaze domain add --tenant acme --kind portal --domain developers.acme.com
|
|
14782
|
+
apiblaze domain add --tenant acme --kind auth --domain login.acme.com`).action(action((project, opts) => runDomainAdd(project, opts)));
|
|
14783
|
+
domain.command("list").description("List a project's custom domains, or a tenant's portal/login domains").argument("[project]", "Project name or id (omit with --tenant)").option("--tenant <slug>", "List the tenant's portal / auth domains").option("--team <id|name>", "Team the project is in").option("--apiversion <version>", "API version").option("--json", "Output machine-readable JSON").action(action((project, opts) => runDomainList(project, opts)));
|
|
14784
|
+
domain.command("status").description("Check a custom domain's validation status (and show the DNS records still needed)").argument("[project]", "Project name or id (omit with --tenant)").requiredOption("--id <domainId>", "Domain id (see `domain list`)").option("--tenant <slug>", "The tenant, for a portal / auth domain").option("--team <id|name>", "Team the project is in").option("--apiversion <version>", "API version").option("--json", "Output machine-readable JSON").action(action((project, opts) => runDomainStatus(project, opts)));
|
|
14785
|
+
domain.command("rm").description("Remove a custom domain (billing for it stops)").argument("[project]", "Project name or id (omit with --tenant)").requiredOption("--id <domainId>", "Domain id (see `domain list`)").option("--tenant <slug>", "The tenant, for a portal / auth domain").option("--team <id|name>", "Team the project is in").option("--apiversion <version>", "API version").action(action((project, opts) => runDomainRemove(project, opts)));
|
|
14454
14786
|
domain.command("set-base").description("Choose which version/environment your main URL serves").argument("<project>", "Project name or id").option("--env <env>", "Environment (default: prod)").option("--team <id|name>", "Team the project is in").option("--apiversion <version>", "API version").action(action((project, opts) => runDomainSetBase(project, opts)));
|
|
14455
14787
|
var group = program.command("group").description("Manage a tenant's users & groups \u2014 bare command lists groups").action(action(() => runGroupList({})));
|
|
14456
14788
|
group.command("list").description("List groups").option("--tenant <slug>", "Tenant").option("--json", "Machine-readable output").action(action((opts) => runGroupList(opts)));
|
|
@@ -14480,11 +14812,11 @@ admins.command("remove").description("Remove a tenant admin").argument("<email>"
|
|
|
14480
14812
|
program.addCommand(admins);
|
|
14481
14813
|
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)));
|
|
14482
14814
|
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)));
|
|
14483
|
-
var apikeys = new import_commander.Command("apikeys").description("
|
|
14815
|
+
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)));
|
|
14484
14816
|
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 })));
|
|
14485
14817
|
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 })));
|
|
14486
14818
|
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 })));
|
|
14487
|
-
program.addCommand(apikeys
|
|
14819
|
+
program.addCommand(apikeys);
|
|
14488
14820
|
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", () => `
|
|
14489
14821
|
${renderOpCommands()}
|
|
14490
14822
|
`);
|
|
@@ -14495,10 +14827,10 @@ spec.command("set").description("Replace the stored OpenAPI spec from a local fi
|
|
|
14495
14827
|
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)));
|
|
14496
14828
|
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)));
|
|
14497
14829
|
var HELP_GROUPS = [
|
|
14498
|
-
{ title: "Chat", commands: ["docs", "apichat", "agent"] },
|
|
14499
|
-
{ title: "Setup", commands: ["dev", "create", "integration", "skill", "login", "init", "sidecar", "claim", "team", "whoami", "logout"] },
|
|
14830
|
+
{ title: "Chat", commands: ["docs", "apichat", "agent", "llm"] },
|
|
14831
|
+
{ title: "Setup", commands: ["dev", "create", "integration", "skill", "login", "init", "sidecar", "claim", "team", "whoami", "logout", "flush"] },
|
|
14500
14832
|
{ title: "Recipes \u2014 install a working setup, or publish yours", commands: ["search", "show", "install", "publish", "withdraw"] },
|
|
14501
|
-
{ title: "Control plane commands", commands: ["config", "projects", "logs", "tenant", "group", "admins", "iam", "identified", "preapprove", "rule", "domain", "delete", "target", "throttle", "rename", "spec", "export"] },
|
|
14833
|
+
{ 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"] },
|
|
14502
14834
|
{ title: "Data plane commands", commands: [
|
|
14503
14835
|
{ parent: "consumer", sub: "login" },
|
|
14504
14836
|
{ parent: "consumer", sub: "apikeys" },
|
|
@@ -14524,7 +14856,7 @@ function groupedCommandHelp() {
|
|
|
14524
14856
|
const sub = byName.get(e.parent)?.commands.find((s) => s.name() === e.sub);
|
|
14525
14857
|
return sub ? ` ${helpLabel(e).padEnd(width)}${sub.description()}` : "";
|
|
14526
14858
|
}).filter(Boolean).join("\n");
|
|
14527
|
-
return `${
|
|
14859
|
+
return `${import_chalk64.default.bold(g.title)}
|
|
14528
14860
|
${rows}`;
|
|
14529
14861
|
}).join("\n\n");
|
|
14530
14862
|
}
|
|
@@ -14535,7 +14867,7 @@ program.configureHelp({
|
|
|
14535
14867
|
}
|
|
14536
14868
|
});
|
|
14537
14869
|
program.addHelpText("after", () => `
|
|
14538
|
-
${
|
|
14870
|
+
${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.`)}
|
|
14539
14871
|
|
|
14540
14872
|
${groupedCommandHelp()}
|
|
14541
14873
|
|
|
@@ -14571,14 +14903,14 @@ async function recoverStaleTeam() {
|
|
|
14571
14903
|
const { resolveLinkedTeam: resolveLinkedTeam2 } = await Promise.resolve().then(() => (init_team(), team_exports));
|
|
14572
14904
|
const linked = await resolveLinkedTeam2({ preferredId: creds.teamId, interactive: !!process.stdin.isTTY });
|
|
14573
14905
|
if (!linked) {
|
|
14574
|
-
console.error(
|
|
14906
|
+
console.error(import_chalk64.default.yellow("Your account has no teams anymore (deleted?). Run `apiblaze login` or `apiblaze create` to get a workspace."));
|
|
14575
14907
|
return;
|
|
14576
14908
|
}
|
|
14577
14909
|
if (linked.teamId === creds.teamId) return;
|
|
14578
14910
|
const next = { ...creds, teamId: linked.teamId, teamName: linked.teamName };
|
|
14579
14911
|
delete next.activeTenant;
|
|
14580
14912
|
saveCredentials(next);
|
|
14581
|
-
console.error(
|
|
14913
|
+
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.`));
|
|
14582
14914
|
} catch {
|
|
14583
14915
|
}
|
|
14584
14916
|
}
|
|
@@ -14586,16 +14918,33 @@ async function printError(err) {
|
|
|
14586
14918
|
if (err instanceof ApiError) {
|
|
14587
14919
|
const data = err.body;
|
|
14588
14920
|
const extra = [data?.body?.reason, data?.body?.details, data?.details, data?.body?.error].find((x) => typeof x === "string" && x && x !== err.message);
|
|
14589
|
-
console.error(
|
|
14921
|
+
console.error(import_chalk64.default.red(`
|
|
14590
14922
|
API error (${err.status}): ${err.message}${extra ? ` \u2014 ${extra}` : ""}`));
|
|
14591
14923
|
if (err.status === 403 || err.status === 404) {
|
|
14592
14924
|
await recoverStaleTeam();
|
|
14593
14925
|
}
|
|
14594
14926
|
} else if (err instanceof Error) {
|
|
14595
|
-
console.error(
|
|
14927
|
+
console.error(import_chalk64.default.red(`
|
|
14596
14928
|
Error: ${err.message}`));
|
|
14597
14929
|
} else {
|
|
14598
|
-
console.error(
|
|
14930
|
+
console.error(import_chalk64.default.red("\nUnknown error"));
|
|
14599
14931
|
}
|
|
14600
14932
|
}
|
|
14933
|
+
function hintStaleCacheOnUnknownOption(cmd) {
|
|
14934
|
+
cmd.exitOverride((err) => {
|
|
14935
|
+
if (err.code === "commander.unknownOption") {
|
|
14936
|
+
console.error(import_chalk64.default.yellow(
|
|
14937
|
+
`
|
|
14938
|
+
This is ${B.cli} v${version}. If that option is documented but this build
|
|
14939
|
+
does not have it, your npx cache is serving an older copy \u2014 re-run as:
|
|
14940
|
+
`
|
|
14941
|
+
));
|
|
14942
|
+
console.error(import_chalk64.default.cyan(` npx ${B.cli}@latest ${process.argv.slice(2).join(" ")}
|
|
14943
|
+
`));
|
|
14944
|
+
}
|
|
14945
|
+
process.exit(err.exitCode);
|
|
14946
|
+
});
|
|
14947
|
+
for (const sub of cmd.commands) hintStaleCacheOnUnknownOption(sub);
|
|
14948
|
+
}
|
|
14949
|
+
hintStaleCacheOnUnknownOption(program);
|
|
14601
14950
|
program.parse(process.argv);
|