apiblaze 0.21.11 → 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 +877 -610
- 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,
|
|
@@ -5446,8 +5489,8 @@ function printEnvironments(o) {
|
|
|
5446
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)`]] : [];
|
|
5447
5490
|
if (!rows.length) return;
|
|
5448
5491
|
const w = Math.max(...rows.map(([e]) => e.length));
|
|
5449
|
-
for (const [env,
|
|
5450
|
-
console.log(` ${import_chalk17.default.cyan(env.padEnd(w))} ${import_chalk17.default.green("\u2192")} ${
|
|
5492
|
+
for (const [env, target2] of rows) {
|
|
5493
|
+
console.log(` ${import_chalk17.default.cyan(env.padEnd(w))} ${import_chalk17.default.green("\u2192")} ${target2}`);
|
|
5451
5494
|
}
|
|
5452
5495
|
}
|
|
5453
5496
|
async function finishSetup(o) {
|
|
@@ -5895,9 +5938,9 @@ Create an ${B.thing}`));
|
|
|
5895
5938
|
body.openapi = await loadOpenapiSource(opts.openapi);
|
|
5896
5939
|
}
|
|
5897
5940
|
if (opts.target && !isHttpUrl3(opts.target)) fail4("--target must be a valid http(s) URL.");
|
|
5898
|
-
let
|
|
5941
|
+
let target2 = opts.target?.trim() || (typeof body.target === "string" ? body.target : "") || (typeof body.target_url === "string" ? body.target_url : "");
|
|
5899
5942
|
const hasOtherSource = !!(body.openapi || body.github);
|
|
5900
|
-
if (!
|
|
5943
|
+
if (!target2 && !hasOtherSource) {
|
|
5901
5944
|
if (interactive) {
|
|
5902
5945
|
const { default: inquirer3 } = await import("inquirer");
|
|
5903
5946
|
if (name === void 0) {
|
|
@@ -5922,7 +5965,7 @@ Create an ${B.thing}`));
|
|
|
5922
5965
|
console.log(import_chalk17.default.yellow(" Enter a valid http(s) URL.\n"));
|
|
5923
5966
|
continue;
|
|
5924
5967
|
}
|
|
5925
|
-
|
|
5968
|
+
target2 = url.trim();
|
|
5926
5969
|
break;
|
|
5927
5970
|
}
|
|
5928
5971
|
} else {
|
|
@@ -5930,12 +5973,12 @@ Create an ${B.thing}`));
|
|
|
5930
5973
|
}
|
|
5931
5974
|
}
|
|
5932
5975
|
if (name === void 0 && opts.auto) {
|
|
5933
|
-
const nameSource =
|
|
5976
|
+
const nameSource = target2 || (typeof opts.openapi === "string" ? opts.openapi : void 0);
|
|
5934
5977
|
name = deriveNameFrom(nameSource) ?? randomProxyName();
|
|
5935
5978
|
}
|
|
5936
|
-
if (
|
|
5937
|
-
body.target =
|
|
5938
|
-
body.target_url =
|
|
5979
|
+
if (target2) {
|
|
5980
|
+
body.target = target2;
|
|
5981
|
+
body.target_url = target2;
|
|
5939
5982
|
}
|
|
5940
5983
|
if (name) {
|
|
5941
5984
|
body.name = name;
|
|
@@ -6146,13 +6189,13 @@ async function chooseName(opts, openapi, teamId, interactive) {
|
|
|
6146
6189
|
}
|
|
6147
6190
|
async function createDevProxy(args) {
|
|
6148
6191
|
const { auth, name, port, openapi, authChoice, families, tenant: tenant2, teamId } = args;
|
|
6149
|
-
const
|
|
6192
|
+
const target2 = `http://localhost:${port}`;
|
|
6150
6193
|
const body = {
|
|
6151
6194
|
name,
|
|
6152
6195
|
subdomain: name,
|
|
6153
6196
|
auth_type: authChoice.auth,
|
|
6154
6197
|
...authChoice.bodyPatch,
|
|
6155
|
-
...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 },
|
|
6156
6199
|
...tenant2 ? { tenant: tenant2 } : {},
|
|
6157
6200
|
...teamId ? { team_id: teamId } : {},
|
|
6158
6201
|
...families ? { secure: { families } } : {}
|
|
@@ -6162,7 +6205,7 @@ async function createDevProxy(args) {
|
|
|
6162
6205
|
let keyAuth = auth.mode === "key" ? { apiKey: auth.apiKey } : void 0;
|
|
6163
6206
|
let resolvedTeam = teamId;
|
|
6164
6207
|
if (caller) {
|
|
6165
|
-
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}` });
|
|
6166
6209
|
} else {
|
|
6167
6210
|
result = await createProxyAnonymous(body);
|
|
6168
6211
|
if (result.cp_key && result.team_id) {
|
|
@@ -6218,7 +6261,7 @@ async function runDev(options) {
|
|
|
6218
6261
|
}
|
|
6219
6262
|
let targets = [];
|
|
6220
6263
|
const wantsNew = options.openapi !== void 0 || options.name !== void 0;
|
|
6221
|
-
if (auth.mode !== "none" && !wantsNew) {
|
|
6264
|
+
if (auth.mode !== "none" && (!wantsNew || options.name !== void 0 && !options.project)) {
|
|
6222
6265
|
const spinner = (0, import_ora7.default)("Looking for projects that point at this machine...").start();
|
|
6223
6266
|
try {
|
|
6224
6267
|
targets = await getLocalhostTargets(teamId, keyAuth);
|
|
@@ -6232,7 +6275,15 @@ async function runDev(options) {
|
|
|
6232
6275
|
let selected = [];
|
|
6233
6276
|
let created = null;
|
|
6234
6277
|
let setup = null;
|
|
6235
|
-
|
|
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) {
|
|
6236
6287
|
const want = options.project.toLowerCase();
|
|
6237
6288
|
const match = targets.find((t) => t.projectId?.toLowerCase() === want || t.projectName?.toLowerCase() === want);
|
|
6238
6289
|
if (!match) {
|
|
@@ -6338,11 +6389,12 @@ async function runDev(options) {
|
|
|
6338
6389
|
}
|
|
6339
6390
|
let restore = [];
|
|
6340
6391
|
let connect;
|
|
6392
|
+
const registerTargets = { targets: selected.map((t) => ({ projectId: t.projectId, tenantId: t.tenantId })) };
|
|
6341
6393
|
{
|
|
6342
6394
|
const spinner = (0, import_ora7.default)("Registering the tunnel...").start();
|
|
6343
6395
|
try {
|
|
6344
|
-
const result = await putDevTunnel(
|
|
6345
|
-
restore = result.restore ?? [];
|
|
6396
|
+
const result = await putDevTunnel(registerTargets, keyAuth);
|
|
6397
|
+
restore = (result.restore ?? []).map((r) => unmarkRestore(r, options.port));
|
|
6346
6398
|
connect = result.connect;
|
|
6347
6399
|
spinner.stop();
|
|
6348
6400
|
} catch (err) {
|
|
@@ -6360,6 +6412,13 @@ async function runDev(options) {
|
|
|
6360
6412
|
(projectId) => startTunnelClient({
|
|
6361
6413
|
connectUrl: connect.url,
|
|
6362
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
|
+
},
|
|
6363
6422
|
projectId,
|
|
6364
6423
|
localPort: options.port,
|
|
6365
6424
|
onEntry: (entry) => say(formatLogLine(entry)),
|
|
@@ -6379,6 +6438,7 @@ async function runDev(options) {
|
|
|
6379
6438
|
const localUp = await probeLocalServer(options.port);
|
|
6380
6439
|
console.log();
|
|
6381
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)`));
|
|
6382
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.`));
|
|
6383
6443
|
if (options.captureFile) console.log(import_chalk18.default.gray(` Streaming full traffic to ${options.captureFile}`));
|
|
6384
6444
|
console.log();
|
|
@@ -6398,9 +6458,30 @@ async function runDev(options) {
|
|
|
6398
6458
|
}
|
|
6399
6459
|
process.on("SIGINT", () => void cleanup());
|
|
6400
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
|
+
}
|
|
6401
6467
|
await new Promise(() => {
|
|
6402
6468
|
});
|
|
6403
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
|
+
}
|
|
6404
6485
|
|
|
6405
6486
|
// src/commands/projects.ts
|
|
6406
6487
|
var import_chalk19 = __toESM(require("chalk"));
|
|
@@ -6505,10 +6586,16 @@ business logic: the gateway sits in front of the code and enforces it.
|
|
|
6505
6586
|
- They say yes to "can I read the code" \u2192 **Adopt**: scan their app, ask only what the scan
|
|
6506
6587
|
could not find, then ship it through the Ship steps.
|
|
6507
6588
|
- They have a backend, just wrote one, or ask you to BUILD one and then ship it \u2192 **Ship**.
|
|
6508
|
-
No code yet? First write the smallest plain backend that serves the resources they named
|
|
6509
|
-
|
|
6510
|
-
|
|
6511
|
-
|
|
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
|
|
6512
6599
|
${cli} on this" or "ship it with ${cli}" and point at a backend, skip any pitch and run it.
|
|
6513
6600
|
|
|
6514
6601
|
Tie-breaks: a question ("what can it do", "what is it") is always **Explain**, whatever else the
|
|
@@ -6628,12 +6715,11 @@ ONLY for what the scan could not find \u2014 one short list of questions, not an
|
|
|
6628
6715
|
sign-in plumbing, not business logic. Then the widgets they ticked, each with its server route
|
|
6629
6716
|
(\`npx ${cli} integration <name> --stack <their stack>\` for keys and chat; groups is the keys
|
|
6630
6717
|
route with \`createApiblazeGroups\`).
|
|
6631
|
-
- Backend behind a secret: the proxy must send it upstream
|
|
6632
|
-
|
|
6633
|
-
|
|
6634
|
-
|
|
6635
|
-
|
|
6636
|
-
that header \u2014 fine on a laptop, not on a server; in prod the proxy is the only caller.
|
|
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.
|
|
6637
6723
|
- Multi-tenant app \u2192 \`npx ${cli} identified <name> require\`, so no call arrives without a user.
|
|
6638
6724
|
- Change nothing in their business logic. Report what you changed, file by file.
|
|
6639
6725
|
|
|
@@ -6648,7 +6734,10 @@ ONLY for what the scan could not find \u2014 one short list of questions, not an
|
|
|
6648
6734
|
login. Their own OAuth app \u2192 \`--apikey --oauth '{"provider":"auth0","clientId":"\u2026","clientSecret":"\u2026"}'\`
|
|
6649
6735
|
(both doors: keys for tenants, their login for people and agents). Shapes below.
|
|
6650
6736
|
3. **Local code** \u2192 \`npx ${cli} dev --port <port> --openapi ./openapi.yaml --name <name> --auto <door flags>\`,
|
|
6651
|
-
|
|
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
|
|
6652
6741
|
\`npx ${cli} create --target <url-or-spec> --name <name> --auto <door flags>\`. Both print the
|
|
6653
6742
|
URL, the MCP URL (the "agents:" line), what is locked, and the bootstrap key ONCE \u2014 that key
|
|
6654
6743
|
goes in the frontend-server's env, never a browser, a repo or an agent config. The tenant is
|
|
@@ -6657,22 +6746,32 @@ ONLY for what the scan could not find \u2014 one short list of questions, not an
|
|
|
6657
6746
|
the frontend-server's env (\`.env.local\` for Next.js). Then prove it with three curls and
|
|
6658
6747
|
say the codes: no key \u2192 401; create as \`X-End-User-Id: ana\` \u2192 201; PATCH that id as
|
|
6659
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.
|
|
6660
6753
|
4. **Prod** (local code only): \`npx ${cli} target <name> --env prod --url <prod-url>\`.
|
|
6661
|
-
5. **Limits.**
|
|
6662
|
-
|
|
6663
|
-
{
|
|
6664
|
-
|
|
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.
|
|
6665
6759
|
5b. **Attribution.** \`--auto\` leaves unattributed calls allowed: the bootstrap key with no
|
|
6666
6760
|
\`X-End-User-Id\` creates rows nobody owns. For a multi-tenant app run
|
|
6667
6761
|
\`npx ${cli} identified <name> require\` \u2014 then every call must carry \`X-End-User-Id\` or a
|
|
6668
|
-
login token.
|
|
6669
|
-
|
|
6670
|
-
|
|
6671
|
-
|
|
6672
|
-
|
|
6673
|
-
|
|
6674
|
-
|
|
6675
|
-
|
|
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?"
|
|
6676
6775
|
|
|
6677
6776
|
## Report like this
|
|
6678
6777
|
|
|
@@ -6685,9 +6784,15 @@ Use the real values the CLI printed. Never invent a URL, a key, a rule or a limi
|
|
|
6685
6784
|
API https://<name>.abz.run/1.0.0/dev your tenants, with API keys
|
|
6686
6785
|
/dev \u2192 localhost:<port> tunnelled from this machine \u2014 leave it running
|
|
6687
6786
|
/prod \u2192 <prod-url> your deploy
|
|
6688
|
-
Try it: curl https://<name>.abz.run/1.0.0/dev/<collection> -H "X-API-Key: <key>" -H "X-End-User-Id: you"
|
|
6689
6787
|
MCP <the "agents:" URL the CLI printed> agents, signing in with <provider>
|
|
6690
|
-
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
|
|
6691
6796
|
|
|
6692
6797
|
## Sign-in shapes \u2014 the only ones \`--oauth\` accepts; never invent fields
|
|
6693
6798
|
|
|
@@ -6709,9 +6814,10 @@ key. Mint it with \`npx ${cli} apikeys mint --desc "keys widget"\` (no \`--tenan
|
|
|
6709
6814
|
\`--tenant\` you get a consumer key, which is the wrong kind).
|
|
6710
6815
|
Edit ONLY the frontend and the frontend-server. A key per TENANT comes from that widget \u2014 each
|
|
6711
6816
|
of your customers mints their own, in their own tenant; the bootstrap key is only for your
|
|
6712
|
-
frontend-server.
|
|
6713
|
-
\`
|
|
6714
|
-
|
|
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
|
|
6715
6821
|
the header are not owned by ana@example.com's key.
|
|
6716
6822
|
|
|
6717
6823
|
## Never
|
|
@@ -6722,13 +6828,16 @@ the header are not owned by ana@example.com's key.
|
|
|
6722
6828
|
- Put ownership or tenancy checks into business logic. The gateway enforces them.
|
|
6723
6829
|
- Ask for team ids, tenant ids or project ids the CLI already knows.
|
|
6724
6830
|
- Delete anything. Deletion stays with the user: \`npx ${cli} delete <name>\` (interactive;
|
|
6725
|
-
\`--yes\` skips the prompt). Its impact banner lists tenant-scoped counts even when the tenant
|
|
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
|
|
6726
6832
|
is shared \u2014 those are lost only if this is the tenant's LAST proxy.
|
|
6727
6833
|
|
|
6728
6834
|
## Later
|
|
6729
6835
|
|
|
6730
6836
|
- \`npx ${cli} rule <name>\` reviews or changes the rules; \`npx ${cli} config <name> authorization.enforce_authorization false\` turns enforcement off without losing them.
|
|
6731
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.
|
|
6732
6841
|
|
|
6733
6842
|
## No shell? The control-plane MCP
|
|
6734
6843
|
|
|
@@ -6744,14 +6853,14 @@ async function runSkill(opts = {}) {
|
|
|
6744
6853
|
process.stdout.write(md);
|
|
6745
6854
|
return;
|
|
6746
6855
|
}
|
|
6747
|
-
const
|
|
6748
|
-
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) {
|
|
6749
6858
|
console.error(import_chalk20.default.yellow(`${SKILL_RELATIVE_PATH} already exists \u2014 re-run with --force to overwrite it.`));
|
|
6750
6859
|
process.exit(1);
|
|
6751
6860
|
}
|
|
6752
|
-
import_fs3.default.mkdirSync(import_path2.default.dirname(
|
|
6753
|
-
import_fs3.default.writeFileSync(
|
|
6754
|
-
console.log(`${import_chalk20.default.green("\u2713")} Installed the ${import_chalk20.default.bold(SKILL_NAME)} skill: ${import_chalk20.default.bold(
|
|
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)}`);
|
|
6755
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.`));
|
|
6756
6865
|
}
|
|
6757
6866
|
|
|
@@ -6842,12 +6951,12 @@ async function runLogout(opts = {}) {
|
|
|
6842
6951
|
console.log(import_chalk22.default.yellow("You were not logged in."));
|
|
6843
6952
|
return;
|
|
6844
6953
|
}
|
|
6845
|
-
let
|
|
6846
|
-
if (opts.all)
|
|
6847
|
-
else if (opts.producer)
|
|
6848
|
-
else if (opts.consumer)
|
|
6849
|
-
else if (creds && !consumer2)
|
|
6850
|
-
else if (consumer2 && !creds)
|
|
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";
|
|
6851
6960
|
else {
|
|
6852
6961
|
const { default: inquirer3 } = await import("inquirer");
|
|
6853
6962
|
const { chosen } = await inquirer3.prompt([
|
|
@@ -6862,13 +6971,13 @@ async function runLogout(opts = {}) {
|
|
|
6862
6971
|
]
|
|
6863
6972
|
}
|
|
6864
6973
|
]);
|
|
6865
|
-
|
|
6974
|
+
target2 = chosen;
|
|
6866
6975
|
}
|
|
6867
|
-
if (
|
|
6976
|
+
if (target2 === "producer" || target2 === "all") {
|
|
6868
6977
|
clearCredentials();
|
|
6869
6978
|
console.log(import_chalk22.default.green(`\u2714 Logged out of API Producer${creds?.githubHandle ? ` (${creds.githubHandle})` : ""}.`));
|
|
6870
6979
|
}
|
|
6871
|
-
if (
|
|
6980
|
+
if (target2 === "consumer" || target2 === "all") {
|
|
6872
6981
|
clearConsumer();
|
|
6873
6982
|
console.log(import_chalk22.default.green(`\u2714 Logged out of API Consumer${consumer2?.tenant ? ` (${consumer2.tenant})` : ""}.`));
|
|
6874
6983
|
}
|
|
@@ -7881,17 +7990,17 @@ async function runExport(projectArg, versionArg, opts) {
|
|
|
7881
7990
|
const match = await resolveProject(teamId, projectArg, versionArg);
|
|
7882
7991
|
const projectId = match.projectId;
|
|
7883
7992
|
const version2 = versionArg || match.apiVersion;
|
|
7884
|
-
const
|
|
7993
|
+
const target2 = opts.kong ? "kong" : "data";
|
|
7885
7994
|
const q = new URLSearchParams();
|
|
7886
7995
|
if (opts.secrets) q.set("include_secrets", "1");
|
|
7887
7996
|
if (opts.noConsumers) q.set("include_consumers", "0");
|
|
7888
7997
|
if (opts.keys) q.set("keys", opts.keys);
|
|
7889
7998
|
const qs = q.toString() ? `?${q.toString()}` : "";
|
|
7890
|
-
console.log(import_chalk36.default.dim(`Queuing ${
|
|
7999
|
+
console.log(import_chalk36.default.dim(`Queuing ${target2} export of ${projectId}@${version2}\u2026`));
|
|
7891
8000
|
const job = await admin({
|
|
7892
|
-
path: `/projects/${projectId}/${version2}/export/${
|
|
8001
|
+
path: `/projects/${projectId}/${version2}/export/${target2}${qs}`,
|
|
7893
8002
|
method: "GET",
|
|
7894
|
-
summary: `export ${projectId} (${
|
|
8003
|
+
summary: `export ${projectId} (${target2})`
|
|
7895
8004
|
});
|
|
7896
8005
|
if (!job?.job_id) throw new Error("Failed to queue export.");
|
|
7897
8006
|
const statusPath = `/projects/${projectId}/${version2}/export/jobs/${job.job_id}`;
|
|
@@ -7910,7 +8019,7 @@ async function runExport(projectArg, versionArg, opts) {
|
|
|
7910
8019
|
}
|
|
7911
8020
|
}
|
|
7912
8021
|
process.stdout.write("\n");
|
|
7913
|
-
const outFile = opts.out || `apiblaze-export-${projectId}-${version2}-${
|
|
8022
|
+
const outFile = opts.out || `apiblaze-export-${projectId}-${version2}-${target2}.zip`;
|
|
7914
8023
|
const res = await fetch(`${DASHBOARD_BASE5}/api/cli/admin/download`, {
|
|
7915
8024
|
method: "POST",
|
|
7916
8025
|
headers: { "Content-Type": "application/json", Authorization: `Bearer ${token}` },
|
|
@@ -7922,7 +8031,7 @@ async function runExport(projectArg, versionArg, opts) {
|
|
|
7922
8031
|
console.log(import_chalk36.default.green(`\u2713 Saved ${outFile} (${(buf.length / 1024).toFixed(0)} KB)`));
|
|
7923
8032
|
if (job.decryption_key) console.log(import_chalk36.default.yellow(`Decryption key (shown once): ${job.decryption_key}`));
|
|
7924
8033
|
console.log(import_chalk36.default.dim("Read MIGRATION-REPORT.json inside the zip for the full fidelity ledger."));
|
|
7925
|
-
if (
|
|
8034
|
+
if (target2 === "kong") console.log(import_chalk36.default.dim("Kong: unzip, then `cd kong && docker compose up` (see kong/README.md)."));
|
|
7926
8035
|
}
|
|
7927
8036
|
|
|
7928
8037
|
// src/commands/recipe-search.ts
|
|
@@ -8301,8 +8410,8 @@ async function runRecipeInstall(nameArg, opts) {
|
|
|
8301
8410
|
let reinstall = false;
|
|
8302
8411
|
if (opts.into) {
|
|
8303
8412
|
reinstall = true;
|
|
8304
|
-
const
|
|
8305
|
-
const proj2 = await resolveProjectVia(caller, teamId,
|
|
8413
|
+
const target2 = normalizeName3(opts.into);
|
|
8414
|
+
const proj2 = await resolveProjectVia(caller, teamId, target2, opts.apiversion);
|
|
8306
8415
|
project = proj2.projectId;
|
|
8307
8416
|
apiVersion = proj2.apiVersion;
|
|
8308
8417
|
tenant2 = await resolveReinstallTenant(caller, project, apiVersion);
|
|
@@ -8385,14 +8494,14 @@ function buildSteps(inp) {
|
|
|
8385
8494
|
label: `creating ${project}`,
|
|
8386
8495
|
skip: reinstall,
|
|
8387
8496
|
run: async () => {
|
|
8388
|
-
const
|
|
8389
|
-
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");
|
|
8390
8499
|
const out = await producer(caller, {
|
|
8391
8500
|
method: "POST",
|
|
8392
8501
|
path: "/projects",
|
|
8393
8502
|
body: {
|
|
8394
8503
|
name: project,
|
|
8395
|
-
target,
|
|
8504
|
+
target: target2,
|
|
8396
8505
|
tenant: tenant2,
|
|
8397
8506
|
product_slug: project,
|
|
8398
8507
|
api_version: apiVersion,
|
|
@@ -10109,14 +10218,16 @@ async function runConsumerLogsList(opts) {
|
|
|
10109
10218
|
// src/commands/config.ts
|
|
10110
10219
|
var import_chalk43 = __toESM(require("chalk"));
|
|
10111
10220
|
var import_ora15 = __toESM(require("ora"));
|
|
10112
|
-
|
|
10113
|
-
|
|
10221
|
+
init_caller();
|
|
10222
|
+
init_paging();
|
|
10223
|
+
var resolveCallerMode = () => resolveCaller()?.mode ?? null;
|
|
10114
10224
|
async function patchConfig(project, opts, body, summary) {
|
|
10115
|
-
const
|
|
10116
|
-
const
|
|
10225
|
+
const caller = requireCaller();
|
|
10226
|
+
const { teamId } = await resolveActingTeam(caller, opts.team);
|
|
10227
|
+
const proj2 = await resolveProjectVia(caller, teamId, project, opts.apiversion);
|
|
10117
10228
|
const spinner = (0, import_ora15.default)(summary + "...").start();
|
|
10118
10229
|
try {
|
|
10119
|
-
const out = await
|
|
10230
|
+
const out = await producer(caller, {
|
|
10120
10231
|
method: "PATCH",
|
|
10121
10232
|
path: `/projects/${proj2.projectId}/${proj2.apiVersion}`,
|
|
10122
10233
|
body,
|
|
@@ -10134,13 +10245,47 @@ async function runTargetSet(project, opts) {
|
|
|
10134
10245
|
console.error(import_chalk43.default.red("--url is required."));
|
|
10135
10246
|
process.exit(1);
|
|
10136
10247
|
}
|
|
10137
|
-
|
|
10138
|
-
|
|
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}`);
|
|
10139
10272
|
}
|
|
10140
10273
|
async function runThrottleSet(project, opts) {
|
|
10141
10274
|
const throttling = {};
|
|
10142
|
-
|
|
10143
|
-
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
|
+
}
|
|
10144
10289
|
if (opts.quota !== void 0) throttling.proxyQuota = Number(opts.quota);
|
|
10145
10290
|
if (opts.period !== void 0) {
|
|
10146
10291
|
if (!["daily", "weekly", "monthly"].includes(opts.period)) {
|
|
@@ -10154,6 +10299,11 @@ async function runThrottleSet(project, opts) {
|
|
|
10154
10299
|
process.exit(1);
|
|
10155
10300
|
}
|
|
10156
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
|
+
}
|
|
10157
10307
|
}
|
|
10158
10308
|
async function runRename(project, opts) {
|
|
10159
10309
|
if (!opts.displayName) {
|
|
@@ -10163,17 +10313,110 @@ async function runRename(project, opts) {
|
|
|
10163
10313
|
await patchConfig(project, opts, { display_name: opts.displayName }, `Rename \u2192 "${opts.displayName}"`);
|
|
10164
10314
|
}
|
|
10165
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
|
+
|
|
10166
10409
|
// src/commands/config-browse.ts
|
|
10167
|
-
var
|
|
10168
|
-
var
|
|
10410
|
+
var import_chalk53 = __toESM(require("chalk"));
|
|
10411
|
+
var import_ora24 = __toESM(require("ora"));
|
|
10169
10412
|
init_admin();
|
|
10170
10413
|
init_paging();
|
|
10171
10414
|
init_auth();
|
|
10172
10415
|
init_resolve();
|
|
10173
10416
|
|
|
10174
10417
|
// src/commands/domain.ts
|
|
10175
|
-
var
|
|
10176
|
-
var
|
|
10418
|
+
var import_chalk45 = __toESM(require("chalk"));
|
|
10419
|
+
var import_ora17 = __toESM(require("ora"));
|
|
10177
10420
|
init_admin();
|
|
10178
10421
|
init_resolve();
|
|
10179
10422
|
var KINDS = ["api", "mcp", "portal", "auth"];
|
|
@@ -10185,7 +10428,7 @@ async function scopeFor(project, opts) {
|
|
|
10185
10428
|
return { kind: "project", base: `/projects/${proj2.projectId}/domains`, label: `${proj2.projectName} v${proj2.apiVersion}`, apiVersion: proj2.apiVersion };
|
|
10186
10429
|
}
|
|
10187
10430
|
if (!opts.tenant) {
|
|
10188
|
-
console.error(
|
|
10431
|
+
console.error(import_chalk45.default.red("Give a <project> (api / mcp domains) or --tenant <slug> (portal / auth domains)."));
|
|
10189
10432
|
process.exit(1);
|
|
10190
10433
|
}
|
|
10191
10434
|
const tenant2 = opts.tenant.toLowerCase();
|
|
@@ -10199,35 +10442,35 @@ function describeKind(d) {
|
|
|
10199
10442
|
return d?.target ? `${d.kind ?? "api"} \u2192 stands in for ${d.target}` : d?.kind ?? "api";
|
|
10200
10443
|
}
|
|
10201
10444
|
function statusColor2(s) {
|
|
10202
|
-
if (s === "active") return
|
|
10203
|
-
if (s === "failed" || s === "missing" || s === "suspended") return
|
|
10204
|
-
return
|
|
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");
|
|
10205
10448
|
}
|
|
10206
10449
|
async function runDomainAdd(project, opts) {
|
|
10207
10450
|
if (!opts.domain) {
|
|
10208
|
-
console.error(
|
|
10451
|
+
console.error(import_chalk45.default.red("--domain is required."));
|
|
10209
10452
|
process.exit(1);
|
|
10210
10453
|
}
|
|
10211
10454
|
const kind = (opts.kind ?? (project ? "api" : "")).toLowerCase();
|
|
10212
10455
|
if (!KINDS.includes(kind)) {
|
|
10213
|
-
console.error(
|
|
10456
|
+
console.error(import_chalk45.default.red(`--kind must be one of ${KINDS.join(", ")}${project ? "" : " (portal or auth for a --tenant)"}.`));
|
|
10214
10457
|
process.exit(1);
|
|
10215
10458
|
}
|
|
10216
10459
|
const tenantKind = TENANT_KINDS.includes(kind);
|
|
10217
10460
|
if (tenantKind && project) {
|
|
10218
|
-
console.error(
|
|
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}`));
|
|
10219
10462
|
process.exit(1);
|
|
10220
10463
|
}
|
|
10221
10464
|
if (tenantKind && !opts.tenant) {
|
|
10222
|
-
console.error(
|
|
10465
|
+
console.error(import_chalk45.default.red(`--tenant <slug> is required for a ${kind} domain.`));
|
|
10223
10466
|
process.exit(1);
|
|
10224
10467
|
}
|
|
10225
10468
|
if (!tenantKind && !project) {
|
|
10226
|
-
console.error(
|
|
10469
|
+
console.error(import_chalk45.default.red(`A ${kind} domain belongs to a proxy: apiblaze domain add <project> --kind ${kind} --domain ${opts.domain}`));
|
|
10227
10470
|
process.exit(1);
|
|
10228
10471
|
}
|
|
10229
10472
|
const scope = await scopeFor(project, opts);
|
|
10230
|
-
const spinner = (0,
|
|
10473
|
+
const spinner = (0, import_ora17.default)(`Registering ${opts.domain}...`).start();
|
|
10231
10474
|
try {
|
|
10232
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 };
|
|
10233
10476
|
const out = await admin({
|
|
@@ -10241,10 +10484,10 @@ async function runDomainAdd(project, opts) {
|
|
|
10241
10484
|
console.log(JSON.stringify(out));
|
|
10242
10485
|
return;
|
|
10243
10486
|
}
|
|
10244
|
-
if (out?.cnameRecord) console.log(` ${
|
|
10245
|
-
if (out?.dcv) console.log(` ${
|
|
10246
|
-
if (typeof out?.monthlyCents === "number") console.log(
|
|
10247
|
-
if (out?.id) console.log(
|
|
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}`));
|
|
10248
10491
|
} catch (err) {
|
|
10249
10492
|
spinner.fail("Domain registration failed.");
|
|
10250
10493
|
throw err;
|
|
@@ -10263,17 +10506,17 @@ async function runDomainList(project, opts) {
|
|
|
10263
10506
|
return;
|
|
10264
10507
|
}
|
|
10265
10508
|
if (!domains.length) {
|
|
10266
|
-
console.log(
|
|
10509
|
+
console.log(import_chalk45.default.yellow("No custom domains."));
|
|
10267
10510
|
return;
|
|
10268
10511
|
}
|
|
10269
10512
|
for (const d of domains) {
|
|
10270
|
-
const pin = scope.kind === "project" && d.tenant ?
|
|
10271
|
-
console.log(` ${
|
|
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 ?? "")}`);
|
|
10272
10515
|
}
|
|
10273
10516
|
}
|
|
10274
10517
|
async function runDomainStatus(project, opts) {
|
|
10275
10518
|
if (!opts.id) {
|
|
10276
|
-
console.error(
|
|
10519
|
+
console.error(import_chalk45.default.red("--id <domainId> is required (see `apiblaze domain list`)."));
|
|
10277
10520
|
process.exit(1);
|
|
10278
10521
|
}
|
|
10279
10522
|
const scope = await scopeFor(project, opts);
|
|
@@ -10286,23 +10529,23 @@ async function runDomainStatus(project, opts) {
|
|
|
10286
10529
|
console.log(JSON.stringify(out));
|
|
10287
10530
|
return;
|
|
10288
10531
|
}
|
|
10289
|
-
console.log(` ${
|
|
10532
|
+
console.log(` ${import_chalk45.default.bold(out?.hostname ?? opts.id)}: ${statusColor2(out?.status)} ${import_chalk45.default.dim(describeKind(out))}`);
|
|
10290
10533
|
if (out?.status !== "active") {
|
|
10291
|
-
if (out?.status === "suspended") console.log(
|
|
10292
|
-
else if (out?.status === "missing") console.log(
|
|
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."));
|
|
10293
10536
|
else {
|
|
10294
|
-
if (out?.cnameRecord) console.log(` ${
|
|
10295
|
-
if (out?.dcv) console.log(` ${
|
|
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}`);
|
|
10296
10539
|
}
|
|
10297
10540
|
}
|
|
10298
10541
|
}
|
|
10299
10542
|
async function runDomainRemove(project, opts) {
|
|
10300
10543
|
if (!opts.id) {
|
|
10301
|
-
console.error(
|
|
10544
|
+
console.error(import_chalk45.default.red("--id <domainId> is required (see `apiblaze domain list`)."));
|
|
10302
10545
|
process.exit(1);
|
|
10303
10546
|
}
|
|
10304
10547
|
const scope = await scopeFor(project, opts);
|
|
10305
|
-
const spinner = (0,
|
|
10548
|
+
const spinner = (0, import_ora17.default)("Removing domain...").start();
|
|
10306
10549
|
try {
|
|
10307
10550
|
await admin({
|
|
10308
10551
|
method: "DELETE",
|
|
@@ -10319,7 +10562,7 @@ async function runDomainSetBase(project, opts) {
|
|
|
10319
10562
|
const env = opts.env ?? "prod";
|
|
10320
10563
|
const { teamId } = await resolveTeam(opts.team);
|
|
10321
10564
|
const proj2 = await resolveProject(teamId, project, opts.apiversion);
|
|
10322
|
-
const spinner = (0,
|
|
10565
|
+
const spinner = (0, import_ora17.default)("Setting base domain...").start();
|
|
10323
10566
|
try {
|
|
10324
10567
|
await admin({
|
|
10325
10568
|
method: "PUT",
|
|
@@ -10335,8 +10578,8 @@ async function runDomainSetBase(project, opts) {
|
|
|
10335
10578
|
}
|
|
10336
10579
|
|
|
10337
10580
|
// src/commands/tenant.ts
|
|
10338
|
-
var
|
|
10339
|
-
var
|
|
10581
|
+
var import_chalk48 = __toESM(require("chalk"));
|
|
10582
|
+
var import_ora20 = __toESM(require("ora"));
|
|
10340
10583
|
init_admin();
|
|
10341
10584
|
init_resolve();
|
|
10342
10585
|
init_auth();
|
|
@@ -10345,20 +10588,20 @@ init_paging();
|
|
|
10345
10588
|
async function runTenantUse(query, opts) {
|
|
10346
10589
|
const creds = loadCredentials();
|
|
10347
10590
|
if (!creds) {
|
|
10348
|
-
console.error(
|
|
10591
|
+
console.error(import_chalk48.default.red("Not logged in. Run `apiblaze login` first."));
|
|
10349
10592
|
process.exit(1);
|
|
10350
10593
|
}
|
|
10351
10594
|
if (opts.clear) {
|
|
10352
10595
|
delete creds.activeTenant;
|
|
10353
10596
|
saveCredentials(creds);
|
|
10354
|
-
console.log(
|
|
10597
|
+
console.log(import_chalk48.default.green("Tenant scope cleared."));
|
|
10355
10598
|
return;
|
|
10356
10599
|
}
|
|
10357
10600
|
const { teamId } = await resolveTeam(opts.team);
|
|
10358
10601
|
const slug = await pickTenant(teamId, { message: "Scope future commands to which tenant?", initialQuery: query });
|
|
10359
10602
|
if (!slug) process.exit(1);
|
|
10360
10603
|
saveCredentials({ ...creds, activeTenant: slug });
|
|
10361
|
-
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`)"));
|
|
10362
10605
|
}
|
|
10363
10606
|
async function runTenantList(opts) {
|
|
10364
10607
|
const { teamId, teamName } = await resolveTeam(opts.team);
|
|
@@ -10382,26 +10625,26 @@ async function runTenantList(opts) {
|
|
|
10382
10625
|
return;
|
|
10383
10626
|
}
|
|
10384
10627
|
if (!tenants.length) {
|
|
10385
|
-
console.log(
|
|
10628
|
+
console.log(import_chalk48.default.yellow(opts.q ? `No tenants matching "${opts.q}".` : "No tenants."));
|
|
10386
10629
|
return;
|
|
10387
10630
|
}
|
|
10388
10631
|
for (const t of tenants) {
|
|
10389
10632
|
const name = typeof t === "string" ? t : t.tenant_name;
|
|
10390
|
-
const display = typeof t === "string" ? "" :
|
|
10391
|
-
console.log(` ${
|
|
10633
|
+
const display = typeof t === "string" ? "" : import_chalk48.default.dim(` ${t.display_name ?? ""}`);
|
|
10634
|
+
console.log(` ${import_chalk48.default.bold(name)}${display}`);
|
|
10392
10635
|
}
|
|
10393
10636
|
const total = out?.total ?? tenants.length;
|
|
10394
10637
|
if (total > tenants.length) {
|
|
10395
|
-
console.log(
|
|
10638
|
+
console.log(import_chalk48.default.dim(` \u2026 showing ${tenants.length} of ${total} \u2014 narrow with --q <search>`));
|
|
10396
10639
|
}
|
|
10397
10640
|
}
|
|
10398
10641
|
async function runTenantCreate(opts) {
|
|
10399
10642
|
if (!opts.name) {
|
|
10400
|
-
console.error(
|
|
10643
|
+
console.error(import_chalk48.default.red("--name (display name) is required."));
|
|
10401
10644
|
process.exit(1);
|
|
10402
10645
|
}
|
|
10403
10646
|
const { teamId } = await resolveTeam(opts.team);
|
|
10404
|
-
const spinner = (0,
|
|
10647
|
+
const spinner = (0, import_ora20.default)("Creating tenant...").start();
|
|
10405
10648
|
try {
|
|
10406
10649
|
const out = await admin({
|
|
10407
10650
|
method: "POST",
|
|
@@ -10409,7 +10652,7 @@ async function runTenantCreate(opts) {
|
|
|
10409
10652
|
body: { display_name: opts.name, ...opts.slug ? { tenant_name: opts.slug } : {} },
|
|
10410
10653
|
summary: `Create tenant "${opts.name}"`
|
|
10411
10654
|
});
|
|
10412
|
-
spinner.succeed(`Created tenant ${
|
|
10655
|
+
spinner.succeed(`Created tenant ${import_chalk48.default.bold(out?.tenant_name ?? opts.name)}.`);
|
|
10413
10656
|
if (opts.json) console.log(JSON.stringify(out));
|
|
10414
10657
|
} catch (err) {
|
|
10415
10658
|
spinner.fail("Tenant create failed.");
|
|
@@ -10418,12 +10661,12 @@ async function runTenantCreate(opts) {
|
|
|
10418
10661
|
}
|
|
10419
10662
|
async function runTenantAttach(project, opts) {
|
|
10420
10663
|
if (!opts.tenant) {
|
|
10421
|
-
console.error(
|
|
10664
|
+
console.error(import_chalk48.default.red("--tenant <slug> is required."));
|
|
10422
10665
|
process.exit(1);
|
|
10423
10666
|
}
|
|
10424
10667
|
const { teamId } = await resolveTeam(opts.team);
|
|
10425
10668
|
const proj2 = await resolveProject(teamId, project, opts.apiversion);
|
|
10426
|
-
const spinner = (0,
|
|
10669
|
+
const spinner = (0, import_ora20.default)("Attaching tenant...").start();
|
|
10427
10670
|
try {
|
|
10428
10671
|
const out = await admin({
|
|
10429
10672
|
method: "POST",
|
|
@@ -10452,11 +10695,11 @@ async function runTenantDelete(slug, opts) {
|
|
|
10452
10695
|
}
|
|
10453
10696
|
]);
|
|
10454
10697
|
if (!confirm2) {
|
|
10455
|
-
console.log(
|
|
10698
|
+
console.log(import_chalk48.default.dim("Aborted."));
|
|
10456
10699
|
return;
|
|
10457
10700
|
}
|
|
10458
10701
|
}
|
|
10459
|
-
const spinner = (0,
|
|
10702
|
+
const spinner = (0, import_ora20.default)(hard ? "Trashing tenant..." : "Deleting tenant...").start();
|
|
10460
10703
|
try {
|
|
10461
10704
|
await admin({
|
|
10462
10705
|
method: "DELETE",
|
|
@@ -10480,7 +10723,7 @@ async function runTenantDelete(slug, opts) {
|
|
|
10480
10723
|
summary: `Purge tenant ${slug} (full cascade)`
|
|
10481
10724
|
});
|
|
10482
10725
|
} catch (err) {
|
|
10483
|
-
console.log(
|
|
10726
|
+
console.log(import_chalk48.default.red("\u2716 Tenant purge failed to start."));
|
|
10484
10727
|
throw err;
|
|
10485
10728
|
}
|
|
10486
10729
|
const job = await settleDelete(accepted, {
|
|
@@ -10496,18 +10739,18 @@ async function runTenantDelete(slug, opts) {
|
|
|
10496
10739
|
...accepted?.job_id ? { job_id: accepted.job_id, job_status: job?.status ?? "queued" } : {}
|
|
10497
10740
|
}));
|
|
10498
10741
|
} else if (opts.wait !== false) {
|
|
10499
|
-
console.log(
|
|
10742
|
+
console.log(import_chalk48.default.green(`\u2714 Deleted tenant ${slug}.`));
|
|
10500
10743
|
}
|
|
10501
10744
|
}
|
|
10502
10745
|
async function runTenantCors(opts) {
|
|
10503
10746
|
if (!opts.tenant) {
|
|
10504
|
-
console.error(
|
|
10747
|
+
console.error(import_chalk48.default.red("--tenant <slug> is required."));
|
|
10505
10748
|
process.exit(1);
|
|
10506
10749
|
}
|
|
10507
10750
|
const { teamId } = await resolveTeam(opts.team);
|
|
10508
10751
|
const origins = (opts.origins ?? "").split(",").map((s) => s.trim()).filter(Boolean);
|
|
10509
10752
|
const cors = origins.length ? { allowed_origins: origins } : null;
|
|
10510
|
-
const spinner = (0,
|
|
10753
|
+
const spinner = (0, import_ora20.default)("Updating CORS...").start();
|
|
10511
10754
|
try {
|
|
10512
10755
|
await admin({
|
|
10513
10756
|
method: "PUT",
|
|
@@ -10523,8 +10766,8 @@ async function runTenantCors(opts) {
|
|
|
10523
10766
|
}
|
|
10524
10767
|
|
|
10525
10768
|
// src/commands/tenant-drill.ts
|
|
10526
|
-
var
|
|
10527
|
-
var
|
|
10769
|
+
var import_chalk49 = __toESM(require("chalk"));
|
|
10770
|
+
var import_ora21 = __toESM(require("ora"));
|
|
10528
10771
|
var import_crypto = require("crypto");
|
|
10529
10772
|
init_admin();
|
|
10530
10773
|
init_auth();
|
|
@@ -10554,17 +10797,17 @@ async function validScopedTenant(teamId, query) {
|
|
|
10554
10797
|
const next = { ...creds };
|
|
10555
10798
|
delete next.activeTenant;
|
|
10556
10799
|
saveCredentials2(next);
|
|
10557
|
-
console.log(
|
|
10800
|
+
console.log(import_chalk49.default.yellow(`Tenant scope "${scoped}" no longer exists in this team \u2014 cleared.`));
|
|
10558
10801
|
return void 0;
|
|
10559
10802
|
}
|
|
10560
10803
|
async function tenantHome(teamId, tenant2) {
|
|
10561
10804
|
const { default: inquirer3 } = await import("inquirer");
|
|
10562
10805
|
const base2 = `/teams/${encodeURIComponent(teamId)}/tenants/${encodeURIComponent(tenant2)}`;
|
|
10563
|
-
console.log(
|
|
10806
|
+
console.log(import_chalk49.default.bold(`
|
|
10564
10807
|
Tenant ${tenant2}`));
|
|
10565
|
-
console.log(
|
|
10808
|
+
console.log(import_chalk49.default.dim("Tenant auth/settings are SHARED: changes apply to every proxy this tenant serves.\n"));
|
|
10566
10809
|
for (; ; ) {
|
|
10567
|
-
const spinner = (0,
|
|
10810
|
+
const spinner = (0, import_ora21.default)("Reading tenant state...").start();
|
|
10568
10811
|
const [iam, cors, emails, issuers, opaque, clients] = await Promise.all([
|
|
10569
10812
|
admin({ method: "GET", path: `${base2}/iam`, summary: "Read IAM toggle" }).catch(() => null),
|
|
10570
10813
|
admin({ method: "GET", path: `${base2}/cors`, summary: "Read tenant CORS" }).catch(() => null),
|
|
@@ -10578,18 +10821,18 @@ Tenant ${tenant2}`));
|
|
|
10578
10821
|
const nClients = Array.isArray(clients) ? clients.length : 0;
|
|
10579
10822
|
const nOpaque = opaque?.opaque?.endpoint ? 1 : 0;
|
|
10580
10823
|
const nLogin = nClients + nIssuers + nOpaque;
|
|
10581
|
-
const onOff = (b) => b ?
|
|
10824
|
+
const onOff = (b) => b ? import_chalk49.default.green("on") : import_chalk49.default.dim("off");
|
|
10582
10825
|
const { pick: pick2 } = await inquirer3.prompt([{
|
|
10583
10826
|
type: "list",
|
|
10584
10827
|
name: "pick",
|
|
10585
10828
|
message: `Tenant ${tenant2}:`,
|
|
10586
10829
|
pageSize: 12,
|
|
10587
10830
|
choices: [
|
|
10588
|
-
{ name: `Login methods (${nLogin}) ${
|
|
10589
|
-
{ name: `Users & groups: ${onOff(iam?.iam_enabled)} ${
|
|
10590
|
-
{ name: `Portal admins (${nEmails}) ${
|
|
10591
|
-
new inquirer3.Separator(
|
|
10592
|
-
{ name: `CORS: ${cors?.cors ?
|
|
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" },
|
|
10593
10836
|
{ name: "\u2190 Back", value: "back" }
|
|
10594
10837
|
]
|
|
10595
10838
|
}]);
|
|
@@ -10602,7 +10845,7 @@ Tenant ${tenant2}`));
|
|
|
10602
10845
|
case "iam": {
|
|
10603
10846
|
const { v } = await inquirer3.prompt([{ type: "confirm", name: "v", message: "Enable Users & groups?", default: !!iam?.iam_enabled }]);
|
|
10604
10847
|
await admin({ method: "PATCH", path: `${base2}/iam`, body: { enabled: v }, summary: `IAM enforcement \u2192 ${v ? "on" : "off"}` });
|
|
10605
|
-
console.log(
|
|
10848
|
+
console.log(import_chalk49.default.green(` Users & groups ${v ? "enabled" : "disabled"}.`));
|
|
10606
10849
|
break;
|
|
10607
10850
|
}
|
|
10608
10851
|
case "cors": {
|
|
@@ -10615,11 +10858,11 @@ Tenant ${tenant2}`));
|
|
|
10615
10858
|
if (v === "") break;
|
|
10616
10859
|
const parsed = v === "null" ? null : safeJson(v);
|
|
10617
10860
|
if (parsed === void 0) {
|
|
10618
|
-
console.log(
|
|
10861
|
+
console.log(import_chalk49.default.yellow(" Not valid JSON \u2014 unchanged."));
|
|
10619
10862
|
break;
|
|
10620
10863
|
}
|
|
10621
10864
|
await admin({ method: "PUT", path: `${base2}/cors`, body: { cors: parsed }, summary: "Set tenant CORS" });
|
|
10622
|
-
console.log(
|
|
10865
|
+
console.log(import_chalk49.default.green(" CORS updated."));
|
|
10623
10866
|
break;
|
|
10624
10867
|
}
|
|
10625
10868
|
case "emails":
|
|
@@ -10631,7 +10874,7 @@ Tenant ${tenant2}`));
|
|
|
10631
10874
|
async function loginMethodsMenu(teamId, tenant2, base2) {
|
|
10632
10875
|
const { default: inquirer3 } = await import("inquirer");
|
|
10633
10876
|
for (; ; ) {
|
|
10634
|
-
const spinner = (0,
|
|
10877
|
+
const spinner = (0, import_ora21.default)("Loading login methods...").start();
|
|
10635
10878
|
const [rawClients, rawIssuers, rawOpaque] = await Promise.all([
|
|
10636
10879
|
admin({ method: "GET", path: `${base2}/app-clients`, summary: "List APIblaze-hosted logins" }).catch(() => []),
|
|
10637
10880
|
admin({ method: "GET", path: `${base2}/external-issuers`, summary: "List your-own-JWT logins" }).catch(() => null),
|
|
@@ -10643,15 +10886,15 @@ async function loginMethodsMenu(teamId, tenant2, base2) {
|
|
|
10643
10886
|
const choices = [];
|
|
10644
10887
|
for (const c of appClients) {
|
|
10645
10888
|
const nP = (c.providers ?? []).length || c.providers_count || 0;
|
|
10646
|
-
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 } });
|
|
10647
10890
|
}
|
|
10648
10891
|
for (const i of issuers) {
|
|
10649
|
-
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 } });
|
|
10650
10893
|
}
|
|
10651
10894
|
if (opaque?.endpoint) {
|
|
10652
|
-
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 } });
|
|
10653
10896
|
}
|
|
10654
|
-
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."));
|
|
10655
10898
|
const { pick: pick2 } = await inquirer3.prompt([{
|
|
10656
10899
|
type: "list",
|
|
10657
10900
|
name: "pick",
|
|
@@ -10691,9 +10934,9 @@ async function addLoginMethod(teamId, tenant2, base2) {
|
|
|
10691
10934
|
message: "How do people log in?",
|
|
10692
10935
|
pageSize: 8,
|
|
10693
10936
|
choices: [
|
|
10694
|
-
{ name: `APIblaze hosted login page ${
|
|
10695
|
-
{ name: `Your own hosted login \u2014 JWT ${
|
|
10696
|
-
{ 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" },
|
|
10697
10940
|
{ name: "\u2190 Back", value: null }
|
|
10698
10941
|
]
|
|
10699
10942
|
}]);
|
|
@@ -10717,8 +10960,8 @@ function safeJson(s) {
|
|
|
10717
10960
|
async function emailsMenu(base2, emails) {
|
|
10718
10961
|
const { default: inquirer3 } = await import("inquirer");
|
|
10719
10962
|
console.log();
|
|
10720
|
-
if (!emails.length) console.log(
|
|
10721
|
-
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 ?? "")}`);
|
|
10722
10965
|
const { act } = await inquirer3.prompt([{
|
|
10723
10966
|
type: "list",
|
|
10724
10967
|
name: "act",
|
|
@@ -10733,7 +10976,7 @@ async function emailsMenu(base2, emails) {
|
|
|
10733
10976
|
if (act === "add") {
|
|
10734
10977
|
const { email } = await inquirer3.prompt([{ type: "input", name: "email", message: "Email:", validate: (s) => /.+@.+\..+/.test(s) || "not an email" }]);
|
|
10735
10978
|
await admin({ method: "POST", path: `${base2}/admin-emails`, body: { email }, summary: `Add consumer-admin ${email}` });
|
|
10736
|
-
console.log(
|
|
10979
|
+
console.log(import_chalk49.default.green(` ${email} added.`));
|
|
10737
10980
|
} else {
|
|
10738
10981
|
const { e } = await inquirer3.prompt([{
|
|
10739
10982
|
type: "list",
|
|
@@ -10743,7 +10986,7 @@ async function emailsMenu(base2, emails) {
|
|
|
10743
10986
|
}]);
|
|
10744
10987
|
if (!e) return;
|
|
10745
10988
|
await admin({ method: "DELETE", path: `${base2}/admin-emails/${encodeURIComponent(e)}`, summary: `Remove consumer-admin ${e}` });
|
|
10746
|
-
console.log(
|
|
10989
|
+
console.log(import_chalk49.default.green(` ${e} removed.`));
|
|
10747
10990
|
}
|
|
10748
10991
|
}
|
|
10749
10992
|
async function addIssuer(base2) {
|
|
@@ -10764,19 +11007,19 @@ async function addIssuer(base2) {
|
|
|
10764
11007
|
body: { iss: a.iss.trim(), aud: a.aud.trim(), jwks_url: a.jwks.trim() || null, sub_semantics: a.sem, ...claim ? { claim_name: claim } : {} },
|
|
10765
11008
|
summary: `Add external issuer ${a.iss.trim()}`
|
|
10766
11009
|
});
|
|
10767
|
-
console.log(
|
|
11010
|
+
console.log(import_chalk49.default.green(" JWT login method saved."));
|
|
10768
11011
|
}
|
|
10769
11012
|
async function issuerHome(base2, issuer) {
|
|
10770
11013
|
const { default: inquirer3 } = await import("inquirer");
|
|
10771
11014
|
console.log(`
|
|
10772
|
-
${
|
|
11015
|
+
${import_chalk49.default.bold(issuer.iss)} ${import_chalk49.default.dim(`aud=${issuer.aud} \xB7 ${issuer.sub_semantics ?? ""}`)}`);
|
|
10773
11016
|
const { act } = await inquirer3.prompt([{
|
|
10774
11017
|
type: "list",
|
|
10775
11018
|
name: "act",
|
|
10776
11019
|
message: "This JWT login method:",
|
|
10777
11020
|
choices: [
|
|
10778
11021
|
{ name: "Replace it (re-enter details)", value: "edit" },
|
|
10779
|
-
{ name:
|
|
11022
|
+
{ name: import_chalk49.default.red("Delete it"), value: "rm" },
|
|
10780
11023
|
{ name: "\u2190 Back", value: "back" }
|
|
10781
11024
|
]
|
|
10782
11025
|
}]);
|
|
@@ -10792,7 +11035,7 @@ async function issuerHome(base2, issuer) {
|
|
|
10792
11035
|
path: `${base2}/external-issuers?iss=${encodeURIComponent(issuer.iss)}&aud=${encodeURIComponent(issuer.aud)}`,
|
|
10793
11036
|
summary: `Delete issuer ${issuer.iss}`
|
|
10794
11037
|
});
|
|
10795
|
-
console.log(
|
|
11038
|
+
console.log(import_chalk49.default.green(" Deleted."));
|
|
10796
11039
|
}
|
|
10797
11040
|
async function setOpaque(base2, cur) {
|
|
10798
11041
|
const { default: inquirer3 } = await import("inquirer");
|
|
@@ -10801,19 +11044,19 @@ async function setOpaque(base2, cur) {
|
|
|
10801
11044
|
{ type: "list", name: "method", message: "HTTP method:", choices: ["GET", "POST"], default: cur?.method ?? "GET" }
|
|
10802
11045
|
]);
|
|
10803
11046
|
await admin({ method: "PUT", path: `${base2}/opaque`, body: { opaque: { endpoint: a.endpoint, method: a.method } }, summary: "Set opaque validator" });
|
|
10804
|
-
console.log(
|
|
11047
|
+
console.log(import_chalk49.default.green(" Opaque login method set."));
|
|
10805
11048
|
}
|
|
10806
11049
|
async function opaqueHome(base2, cur) {
|
|
10807
11050
|
const { default: inquirer3 } = await import("inquirer");
|
|
10808
11051
|
console.log(`
|
|
10809
|
-
${
|
|
11052
|
+
${import_chalk49.default.bold(cur.endpoint)} ${import_chalk49.default.dim(cur.method ?? "GET")}`);
|
|
10810
11053
|
const { act } = await inquirer3.prompt([{
|
|
10811
11054
|
type: "list",
|
|
10812
11055
|
name: "act",
|
|
10813
11056
|
message: "This opaque login method:",
|
|
10814
11057
|
choices: [
|
|
10815
11058
|
{ name: "Replace it", value: "edit" },
|
|
10816
|
-
{ name:
|
|
11059
|
+
{ name: import_chalk49.default.red("Delete it"), value: "rm" },
|
|
10817
11060
|
{ name: "\u2190 Back", value: "back" }
|
|
10818
11061
|
]
|
|
10819
11062
|
}]);
|
|
@@ -10823,14 +11066,14 @@ async function opaqueHome(base2, cur) {
|
|
|
10823
11066
|
return;
|
|
10824
11067
|
}
|
|
10825
11068
|
await admin({ method: "PUT", path: `${base2}/opaque`, body: { opaque: null }, summary: "Clear opaque validator" });
|
|
10826
|
-
console.log(
|
|
11069
|
+
console.log(import_chalk49.default.green(" Deleted."));
|
|
10827
11070
|
}
|
|
10828
11071
|
async function clientHome(base2, summary) {
|
|
10829
11072
|
const { default: inquirer3 } = await import("inquirer");
|
|
10830
11073
|
const id = summary.clientId ?? summary.client_id;
|
|
10831
11074
|
const cBase = `${base2}/app-clients/${encodeURIComponent(id)}`;
|
|
10832
11075
|
for (; ; ) {
|
|
10833
|
-
const spinner = (0,
|
|
11076
|
+
const spinner = (0, import_ora21.default)("Reading app client...").start();
|
|
10834
11077
|
const c = await admin({ method: "GET", path: cBase, summary: `Read app client ${id}` }).catch(() => summary);
|
|
10835
11078
|
spinner.stop();
|
|
10836
11079
|
const cb = c.authorizedCallbackUrls ?? c.authorized_callback_urls ?? [];
|
|
@@ -10842,13 +11085,13 @@ async function clientHome(base2, summary) {
|
|
|
10842
11085
|
message: `${c.name ?? id}:`,
|
|
10843
11086
|
pageSize: 12,
|
|
10844
11087
|
choices: [
|
|
10845
|
-
{ name: `Login providers${nProviders ? ` (${nProviders})` : ""} ${
|
|
10846
|
-
{ name: `Callback URLs: ${cb.length ?
|
|
10847
|
-
{ 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" },
|
|
10848
11091
|
{ name: `Token expiries: access ${c.accessTokenExpiry ?? 3600}s \xB7 id ${c.idTokenExpiry ?? 3600}s \xB7 refresh ${c.refreshTokenExpiry ?? 2592e3}s`, value: "expiries" },
|
|
10849
11092
|
{ name: "Reveal client secret", value: "secret" },
|
|
10850
11093
|
{ name: "Rotate client secret", value: "rotate" },
|
|
10851
|
-
{ name:
|
|
11094
|
+
{ name: import_chalk49.default.red("Delete this app client"), value: "delete" },
|
|
10852
11095
|
{ name: "\u2190 Back", value: "back" }
|
|
10853
11096
|
]
|
|
10854
11097
|
}]);
|
|
@@ -10861,13 +11104,13 @@ async function clientHome(base2, summary) {
|
|
|
10861
11104
|
case "callbacks": {
|
|
10862
11105
|
const { v } = await inquirer3.prompt([{ type: "input", name: "v", message: "Callback URLs (comma-separated):", default: cb.join(", ") }]);
|
|
10863
11106
|
await admin({ method: "PATCH", path: cBase, body: { authorizedCallbackUrls: parseList(v) }, summary: "Update callback URLs" });
|
|
10864
|
-
console.log(
|
|
11107
|
+
console.log(import_chalk49.default.green(" Callbacks updated."));
|
|
10865
11108
|
break;
|
|
10866
11109
|
}
|
|
10867
11110
|
case "scopes": {
|
|
10868
11111
|
const { v } = await inquirer3.prompt([{ type: "input", name: "v", message: "Scopes (space/comma-separated):", default: scopes.join(" ") }]);
|
|
10869
11112
|
await admin({ method: "PATCH", path: cBase, body: { scopes: v.split(/[\s,]+/).filter(Boolean) }, summary: "Update scopes" });
|
|
10870
|
-
console.log(
|
|
11113
|
+
console.log(import_chalk49.default.green(" Scopes updated."));
|
|
10871
11114
|
break;
|
|
10872
11115
|
}
|
|
10873
11116
|
case "expiries": {
|
|
@@ -10882,14 +11125,14 @@ async function clientHome(base2, summary) {
|
|
|
10882
11125
|
body: { accessTokenExpiry: Number(a.access), idTokenExpiry: Number(a.id), refreshTokenExpiry: Number(a.refresh) },
|
|
10883
11126
|
summary: "Update token expiries"
|
|
10884
11127
|
});
|
|
10885
|
-
console.log(
|
|
11128
|
+
console.log(import_chalk49.default.green(" Expiries updated."));
|
|
10886
11129
|
break;
|
|
10887
11130
|
}
|
|
10888
11131
|
case "secret": {
|
|
10889
11132
|
const { sure } = await inquirer3.prompt([{ type: "confirm", name: "sure", message: "Print the client secret to this terminal?", default: false }]);
|
|
10890
11133
|
if (!sure) break;
|
|
10891
11134
|
const s = await admin({ method: "GET", path: `${cBase}/secret`, summary: "Reveal client secret" });
|
|
10892
|
-
console.log(` ${
|
|
11135
|
+
console.log(` ${import_chalk49.default.bold("client_secret")}: ${import_chalk49.default.green(s?.clientSecret ?? s?.client_secret ?? JSON.stringify(s))}`);
|
|
10893
11136
|
break;
|
|
10894
11137
|
}
|
|
10895
11138
|
case "rotate": {
|
|
@@ -10897,14 +11140,14 @@ async function clientHome(base2, summary) {
|
|
|
10897
11140
|
if (!sure) break;
|
|
10898
11141
|
const fresh = randomSecret();
|
|
10899
11142
|
await admin({ method: "PATCH", path: cBase, body: { clientSecret: fresh }, summary: "Rotate client secret" });
|
|
10900
|
-
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)")}`);
|
|
10901
11144
|
break;
|
|
10902
11145
|
}
|
|
10903
11146
|
case "delete": {
|
|
10904
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 }]);
|
|
10905
11148
|
if (!sure) break;
|
|
10906
11149
|
await admin({ method: "DELETE", path: cBase, summary: `Delete app client ${id}` });
|
|
10907
|
-
console.log(
|
|
11150
|
+
console.log(import_chalk49.default.green(" App client deleted."));
|
|
10908
11151
|
return;
|
|
10909
11152
|
}
|
|
10910
11153
|
}
|
|
@@ -10925,15 +11168,15 @@ var DEFAULT_SCOPES2 = {
|
|
|
10925
11168
|
async function providersMenu(cBase, clientLabel) {
|
|
10926
11169
|
const { default: inquirer3 } = await import("inquirer");
|
|
10927
11170
|
for (; ; ) {
|
|
10928
|
-
const spinner = (0,
|
|
11171
|
+
const spinner = (0, import_ora21.default)("Loading providers...").start();
|
|
10929
11172
|
const raw = await admin({ method: "GET", path: `${cBase}/providers`, summary: "List login providers" }).catch(() => []);
|
|
10930
11173
|
spinner.stop();
|
|
10931
11174
|
const providers = Array.isArray(raw) ? raw : [];
|
|
10932
11175
|
console.log();
|
|
10933
11176
|
for (const p of providers) {
|
|
10934
|
-
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" : ""}`)}`);
|
|
10935
11178
|
}
|
|
10936
|
-
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."));
|
|
10937
11180
|
const { act } = await inquirer3.prompt([{
|
|
10938
11181
|
type: "list",
|
|
10939
11182
|
name: "act",
|
|
@@ -10991,23 +11234,23 @@ async function providersMenu(cBase, clientLabel) {
|
|
|
10991
11234
|
body.targetServerToken = routing;
|
|
10992
11235
|
}
|
|
10993
11236
|
await admin({ method: "POST", path: `${cBase}/providers`, body, summary: `Add ${type} login provider` });
|
|
10994
|
-
console.log(
|
|
11237
|
+
console.log(import_chalk49.default.green(` ${type} provider added.`));
|
|
10995
11238
|
} else {
|
|
10996
11239
|
const { p } = await inquirer3.prompt([{
|
|
10997
11240
|
type: "list",
|
|
10998
11241
|
name: "p",
|
|
10999
11242
|
message: act === "rm" ? "Remove which provider?" : "Reveal which secret?",
|
|
11000
|
-
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 }]
|
|
11001
11244
|
}]);
|
|
11002
11245
|
if (!p) continue;
|
|
11003
11246
|
if (act === "rm") {
|
|
11004
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 }]);
|
|
11005
11248
|
if (!sure) continue;
|
|
11006
11249
|
await admin({ method: "DELETE", path: `${cBase}/providers/${encodeURIComponent(p.id)}`, summary: `Remove ${p.type} provider` });
|
|
11007
|
-
console.log(
|
|
11250
|
+
console.log(import_chalk49.default.green(` ${p.type} removed.`));
|
|
11008
11251
|
} else {
|
|
11009
11252
|
const s = await admin({ method: "GET", path: `${cBase}/providers/${encodeURIComponent(p.id)}/secret`, summary: `Reveal ${p.type} provider secret` });
|
|
11010
|
-
console.log(` ${
|
|
11253
|
+
console.log(` ${import_chalk49.default.bold("client_secret")}: ${import_chalk49.default.green(s?.clientSecret ?? s?.client_secret ?? JSON.stringify(s))}`);
|
|
11011
11254
|
}
|
|
11012
11255
|
}
|
|
11013
11256
|
}
|
|
@@ -11015,8 +11258,8 @@ async function providersMenu(cBase, clientLabel) {
|
|
|
11015
11258
|
|
|
11016
11259
|
// src/commands/spec.ts
|
|
11017
11260
|
var fs12 = __toESM(require("fs"));
|
|
11018
|
-
var
|
|
11019
|
-
var
|
|
11261
|
+
var import_chalk50 = __toESM(require("chalk"));
|
|
11262
|
+
var import_ora22 = __toESM(require("ora"));
|
|
11020
11263
|
init_admin();
|
|
11021
11264
|
init_api();
|
|
11022
11265
|
init_resolve();
|
|
@@ -11032,19 +11275,19 @@ async function runSpecGet(project, opts) {
|
|
|
11032
11275
|
}
|
|
11033
11276
|
async function runSpecSet(project, opts) {
|
|
11034
11277
|
if (!opts.file) {
|
|
11035
|
-
console.error(
|
|
11278
|
+
console.error(import_chalk50.default.red("--file <path> is required (OpenAPI JSON or YAML)."));
|
|
11036
11279
|
process.exit(1);
|
|
11037
11280
|
}
|
|
11038
11281
|
let specContent;
|
|
11039
11282
|
try {
|
|
11040
11283
|
specContent = fs12.readFileSync(opts.file, "utf-8");
|
|
11041
11284
|
} catch {
|
|
11042
|
-
console.error(
|
|
11285
|
+
console.error(import_chalk50.default.red(`Cannot read file: ${opts.file}`));
|
|
11043
11286
|
process.exit(1);
|
|
11044
11287
|
}
|
|
11045
11288
|
const { teamId } = await resolveTeam(opts.team);
|
|
11046
11289
|
const proj2 = await resolveProject(teamId, project, opts.apiversion);
|
|
11047
|
-
const spinner = (0,
|
|
11290
|
+
const spinner = (0, import_ora22.default)("Uploading spec...").start();
|
|
11048
11291
|
try {
|
|
11049
11292
|
const out = await admin({
|
|
11050
11293
|
method: "POST",
|
|
@@ -11068,28 +11311,28 @@ async function runSpecSet(project, opts) {
|
|
|
11068
11311
|
function printSpecChangeReport(report, projectName) {
|
|
11069
11312
|
const r = report.routes;
|
|
11070
11313
|
if (r) {
|
|
11071
|
-
for (const a of r.added) console.log(
|
|
11072
|
-
for (const d of r.removed) console.log(
|
|
11073
|
-
for (const rn of r.renamed ?? []) console.log(
|
|
11074
|
-
for (const m of r.modified ?? []) console.log(
|
|
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)`));
|
|
11075
11318
|
}
|
|
11076
|
-
if (report.applied?.mcp_regenerated) console.log(
|
|
11077
|
-
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.`));
|
|
11078
11321
|
for (const item of report.attention ?? []) {
|
|
11079
|
-
console.log(
|
|
11322
|
+
console.log(import_chalk50.default.yellow(` \u26A0 ${item.detail}`));
|
|
11080
11323
|
if (item.suggestion?.carry_from && item.suggestion.carry_to) {
|
|
11081
|
-
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}"`));
|
|
11082
11325
|
}
|
|
11083
11326
|
}
|
|
11084
11327
|
}
|
|
11085
11328
|
async function runSpecCarry(project, opts) {
|
|
11086
11329
|
if (!opts.method || !opts.from || !opts.to) {
|
|
11087
|
-
console.error(
|
|
11330
|
+
console.error(import_chalk50.default.red("--method, --from and --to are required."));
|
|
11088
11331
|
process.exit(1);
|
|
11089
11332
|
}
|
|
11090
11333
|
const { teamId } = await resolveTeam(opts.team);
|
|
11091
11334
|
const proj2 = await resolveProject(teamId, project, opts.apiversion);
|
|
11092
|
-
const spinner = (0,
|
|
11335
|
+
const spinner = (0, import_ora22.default)(`Carrying rules ${opts.from} \u2192 ${opts.to}...`).start();
|
|
11093
11336
|
const res = await agentCall(`/projects/${proj2.projectId}/${proj2.apiVersion}/policies/route/carry`, "POST", {
|
|
11094
11337
|
method: opts.method.toUpperCase(),
|
|
11095
11338
|
from_resource: opts.from,
|
|
@@ -11106,13 +11349,13 @@ async function runSpecCarry(project, opts) {
|
|
|
11106
11349
|
}
|
|
11107
11350
|
async function runSpecDeleteRule(project, opts) {
|
|
11108
11351
|
if (!opts.method || !opts.route) {
|
|
11109
|
-
console.error(
|
|
11352
|
+
console.error(import_chalk50.default.red("--method and --route are required."));
|
|
11110
11353
|
process.exit(1);
|
|
11111
11354
|
}
|
|
11112
11355
|
const { teamId } = await resolveTeam(opts.team);
|
|
11113
11356
|
const proj2 = await resolveProject(teamId, project, opts.apiversion);
|
|
11114
11357
|
const encoded = opts.route.replace(/\{/g, "%7B").replace(/\}/g, "%7D");
|
|
11115
|
-
const spinner = (0,
|
|
11358
|
+
const spinner = (0, import_ora22.default)(`Deleting rules for ${opts.method.toUpperCase()} ${opts.route}...`).start();
|
|
11116
11359
|
const res = await agentCall(
|
|
11117
11360
|
`/projects/${proj2.projectId}/${proj2.apiVersion}/policies/route/${opts.method.toUpperCase()}${encoded.startsWith("/") ? encoded : `/${encoded}`}`,
|
|
11118
11361
|
"DELETE"
|
|
@@ -11126,14 +11369,14 @@ async function runSpecDeleteRule(project, opts) {
|
|
|
11126
11369
|
}
|
|
11127
11370
|
|
|
11128
11371
|
// src/commands/agent.ts
|
|
11129
|
-
var
|
|
11130
|
-
var
|
|
11372
|
+
var import_chalk52 = __toESM(require("chalk"));
|
|
11373
|
+
var import_ora23 = __toESM(require("ora"));
|
|
11131
11374
|
init_auth();
|
|
11132
11375
|
init_admin();
|
|
11133
11376
|
init_resolve();
|
|
11134
11377
|
|
|
11135
11378
|
// src/lib/tools.ts
|
|
11136
|
-
var
|
|
11379
|
+
var import_chalk51 = __toESM(require("chalk"));
|
|
11137
11380
|
init_admin();
|
|
11138
11381
|
init_api();
|
|
11139
11382
|
init_resolve();
|
|
@@ -11164,15 +11407,15 @@ var TOOLS = [
|
|
|
11164
11407
|
const key = keys.dev ?? Object.values(keys)[0];
|
|
11165
11408
|
const url = `https://${a.name}.abz.run/${version2}/dev`;
|
|
11166
11409
|
const tryIt = buildTryItCurl(url, auth, key, res.tenant);
|
|
11167
|
-
const lines = [` ${
|
|
11168
|
-
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}`);
|
|
11169
11412
|
const envs = Object.keys(keys);
|
|
11170
11413
|
if (envs.length) {
|
|
11171
|
-
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):")}`);
|
|
11172
11415
|
const w = Math.max(...envs.map((e) => e.length));
|
|
11173
|
-
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])}`);
|
|
11174
11417
|
}
|
|
11175
|
-
if (tryIt) lines.push("", ` ${
|
|
11418
|
+
if (tryIt) lines.push("", ` ${import_chalk51.default.dim("Try it:")}`, ` ${import_chalk51.default.cyan(tryIt)}`);
|
|
11176
11419
|
return { ...res, proxy_url: url, keys, ...tryIt ? { try_it: tryIt } : {}, display: lines.join("\n") };
|
|
11177
11420
|
}
|
|
11178
11421
|
},
|
|
@@ -11341,23 +11584,23 @@ function truncate(value, max = 1500) {
|
|
|
11341
11584
|
}
|
|
11342
11585
|
function printCost(resp) {
|
|
11343
11586
|
const line = formatBilling(resp.billing);
|
|
11344
|
-
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)`));
|
|
11345
11588
|
}
|
|
11346
11589
|
async function runAgent(opts) {
|
|
11347
11590
|
requireAuth();
|
|
11348
11591
|
const { teamId, teamName } = await resolveTeam(opts.team);
|
|
11349
11592
|
const { default: inquirer3 } = await import("inquirer");
|
|
11350
|
-
console.log(
|
|
11351
|
-
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'));
|
|
11352
11595
|
const history = [];
|
|
11353
11596
|
while (true) {
|
|
11354
|
-
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" }]);
|
|
11355
11598
|
const text = (input ?? "").trim();
|
|
11356
11599
|
if (!text) continue;
|
|
11357
11600
|
if (["exit", "quit", ":q"].includes(text.toLowerCase())) break;
|
|
11358
11601
|
history.push({ role: "user", content: text });
|
|
11359
11602
|
for (let step = 0; step < MAX_TOOL_STEPS; step++) {
|
|
11360
|
-
const spinner = (0,
|
|
11603
|
+
const spinner = (0, import_ora23.default)({ text: "thinking...", color: "magenta" }).start();
|
|
11361
11604
|
let resp;
|
|
11362
11605
|
try {
|
|
11363
11606
|
resp = await callAgent(history, teamId);
|
|
@@ -11365,21 +11608,21 @@ async function runAgent(opts) {
|
|
|
11365
11608
|
} catch (err) {
|
|
11366
11609
|
spinner.stop();
|
|
11367
11610
|
if (err instanceof ApiError && err.status === 402) {
|
|
11368
|
-
console.log(
|
|
11611
|
+
console.log(import_chalk52.default.yellow(" Insufficient credits \u2014 top up to keep using the agent."));
|
|
11369
11612
|
break;
|
|
11370
11613
|
}
|
|
11371
11614
|
throw err;
|
|
11372
11615
|
}
|
|
11373
11616
|
history.push({ role: "assistant", content: resp.raw });
|
|
11374
11617
|
printCost(resp);
|
|
11375
|
-
if (resp.reply) console.log(
|
|
11618
|
+
if (resp.reply) console.log(import_chalk52.default.green("agent") + " \u203A " + resp.reply);
|
|
11376
11619
|
if (!resp.action) break;
|
|
11377
11620
|
const tool = findTool(resp.action.tool);
|
|
11378
11621
|
if (!tool) {
|
|
11379
11622
|
history.push({ role: "user", content: `TOOL_RESULT ${resp.action.tool}: error \u2014 unknown tool` });
|
|
11380
11623
|
continue;
|
|
11381
11624
|
}
|
|
11382
|
-
const runSpinner = (0,
|
|
11625
|
+
const runSpinner = (0, import_ora23.default)({ text: `running ${tool.name}...`, color: "cyan" }).start();
|
|
11383
11626
|
try {
|
|
11384
11627
|
const result = await tool.run(resp.action.args, { teamId });
|
|
11385
11628
|
runSpinner.succeed(`${tool.name} \u2713`);
|
|
@@ -11397,11 +11640,11 @@ async function runAgent(opts) {
|
|
|
11397
11640
|
}
|
|
11398
11641
|
renderTrace();
|
|
11399
11642
|
if (step === MAX_TOOL_STEPS - 1) {
|
|
11400
|
-
console.log(
|
|
11643
|
+
console.log(import_chalk52.default.dim(" (paused after several steps \u2014 tell me how to continue)"));
|
|
11401
11644
|
}
|
|
11402
11645
|
}
|
|
11403
11646
|
}
|
|
11404
|
-
console.log(
|
|
11647
|
+
console.log(import_chalk52.default.dim("\nBye."));
|
|
11405
11648
|
}
|
|
11406
11649
|
|
|
11407
11650
|
// src/commands/config-browse.ts
|
|
@@ -11709,11 +11952,11 @@ function dig(blob, dotted) {
|
|
|
11709
11952
|
}
|
|
11710
11953
|
var readSetting = (s, cfg) => s.read ? s.read(cfg) : dig(cfg, s.key);
|
|
11711
11954
|
function show(v) {
|
|
11712
|
-
if (v === void 0) return
|
|
11713
|
-
if (v === null) return
|
|
11714
|
-
if (typeof v === "object") return
|
|
11715
|
-
if (typeof v === "boolean") return v ?
|
|
11716
|
-
return
|
|
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));
|
|
11717
11960
|
}
|
|
11718
11961
|
function parseValue(raw) {
|
|
11719
11962
|
if (raw === "true") return true;
|
|
@@ -11748,7 +11991,7 @@ async function patchSetting(proj2, s, value, cfg) {
|
|
|
11748
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.`);
|
|
11749
11992
|
body.tenant = tenant2;
|
|
11750
11993
|
}
|
|
11751
|
-
const spinner = (0,
|
|
11994
|
+
const spinner = (0, import_ora24.default)(`Set ${s.key}...`).start();
|
|
11752
11995
|
try {
|
|
11753
11996
|
await admin({
|
|
11754
11997
|
method: "PATCH",
|
|
@@ -11763,10 +12006,10 @@ async function patchSetting(proj2, s, value, cfg) {
|
|
|
11763
12006
|
}
|
|
11764
12007
|
}
|
|
11765
12008
|
var loginFirst = (what) => {
|
|
11766
|
-
console.log(
|
|
12009
|
+
console.log(import_chalk53.default.yellow(`
|
|
11767
12010
|
Log in first to ${what}.`));
|
|
11768
|
-
console.log(
|
|
11769
|
-
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"));
|
|
11770
12013
|
};
|
|
11771
12014
|
async function runConfig(project, key, value, opts) {
|
|
11772
12015
|
const creds = loadCredentials();
|
|
@@ -11783,9 +12026,9 @@ async function runConfig(project, key, value, opts) {
|
|
|
11783
12026
|
}
|
|
11784
12027
|
const setting = SETTINGS.find((s) => s.key === key);
|
|
11785
12028
|
if (!setting) {
|
|
11786
|
-
console.error(
|
|
11787
|
-
console.error(
|
|
11788
|
-
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>`.)"));
|
|
11789
12032
|
process.exit(1);
|
|
11790
12033
|
}
|
|
11791
12034
|
if (value === void 0) {
|
|
@@ -11800,7 +12043,7 @@ async function pickProject(teamId) {
|
|
|
11800
12043
|
const { getProjects: getProjects2 } = await Promise.resolve().then(() => (init_api(), api_exports));
|
|
11801
12044
|
const projects = await getProjects2(teamId).catch(() => []);
|
|
11802
12045
|
if (!projects.length) {
|
|
11803
|
-
console.error(
|
|
12046
|
+
console.error(import_chalk53.default.red("No projects in this team. Create one: `npx apiblaze create`."));
|
|
11804
12047
|
process.exit(1);
|
|
11805
12048
|
}
|
|
11806
12049
|
const { default: inquirer3 } = await import("inquirer");
|
|
@@ -11808,7 +12051,7 @@ async function pickProject(teamId) {
|
|
|
11808
12051
|
type: "list",
|
|
11809
12052
|
name: "picked",
|
|
11810
12053
|
message: "Which project?",
|
|
11811
|
-
choices: projects.map((p) => ({ name: `${p.projectName} ${
|
|
12054
|
+
choices: projects.map((p) => ({ name: `${p.projectName} ${import_chalk53.default.dim("v" + p.apiVersion)}`, value: p }))
|
|
11812
12055
|
}]);
|
|
11813
12056
|
return { projectId: picked.projectId, projectName: picked.projectName, apiVersion: picked.apiVersion, teamId, tenant: picked.tenant };
|
|
11814
12057
|
}
|
|
@@ -11819,25 +12062,25 @@ function printAll(proj2, cfg, json) {
|
|
|
11819
12062
|
console.log(JSON.stringify(out, null, 2));
|
|
11820
12063
|
return;
|
|
11821
12064
|
}
|
|
11822
|
-
console.log(
|
|
12065
|
+
console.log(import_chalk53.default.bold(`
|
|
11823
12066
|
${proj2.projectName} v${proj2.apiVersion} \u2014 settings
|
|
11824
12067
|
`));
|
|
11825
12068
|
for (const group2 of SETTING_GROUPS) {
|
|
11826
|
-
console.log(
|
|
12069
|
+
console.log(import_chalk53.default.bold(group2));
|
|
11827
12070
|
for (const s of SETTINGS.filter((x) => x.group === group2)) {
|
|
11828
|
-
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)}`);
|
|
11829
12072
|
}
|
|
11830
12073
|
console.log();
|
|
11831
12074
|
}
|
|
11832
|
-
console.log(
|
|
12075
|
+
console.log(import_chalk53.default.dim("Change one: apiblaze config " + proj2.projectName + " <key> <value> (add --verbose for the API call)"));
|
|
11833
12076
|
}
|
|
11834
12077
|
async function discoveryMenu(project) {
|
|
11835
12078
|
const { default: inquirer3 } = await import("inquirer");
|
|
11836
|
-
console.log(
|
|
12079
|
+
console.log(import_chalk53.default.bold(`
|
|
11837
12080
|
APIblaze proxy configuration${project ? ` \u2014 ${project}` : ""}
|
|
11838
12081
|
`));
|
|
11839
|
-
console.log(
|
|
11840
|
-
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"));
|
|
11841
12084
|
for (; ; ) {
|
|
11842
12085
|
const { pick: pick2 } = await inquirer3.prompt([{
|
|
11843
12086
|
type: "list",
|
|
@@ -11845,13 +12088,13 @@ APIblaze proxy configuration${project ? ` \u2014 ${project}` : ""}
|
|
|
11845
12088
|
message: "Explore:",
|
|
11846
12089
|
pageSize: 20,
|
|
11847
12090
|
choices: [
|
|
11848
|
-
new inquirer3.Separator(
|
|
12091
|
+
new inquirer3.Separator(import_chalk53.default.bold("\u2014 Settings \u2014")),
|
|
11849
12092
|
...SETTING_GROUPS.map((g) => ({
|
|
11850
|
-
name: `${g} ${
|
|
12093
|
+
name: `${g} ${import_chalk53.default.dim(SETTINGS.filter((s) => s.group === g).map((s) => s.label).join(", "))}`,
|
|
11851
12094
|
value: { kind: "settings", g }
|
|
11852
12095
|
})),
|
|
11853
|
-
new inquirer3.Separator(
|
|
11854
|
-
...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 } })),
|
|
11855
12098
|
new inquirer3.Separator(),
|
|
11856
12099
|
{ name: "Exit", value: { kind: "exit" } }
|
|
11857
12100
|
]
|
|
@@ -11860,24 +12103,24 @@ APIblaze proxy configuration${project ? ` \u2014 ${project}` : ""}
|
|
|
11860
12103
|
if (pick2.kind === "settings") {
|
|
11861
12104
|
console.log();
|
|
11862
12105
|
for (const s of SETTINGS.filter((x) => x.group === pick2.g)) {
|
|
11863
|
-
console.log(` ${
|
|
11864
|
-
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)}`);
|
|
11865
12108
|
}
|
|
11866
12109
|
loginFirst("view or change these settings");
|
|
11867
12110
|
} else {
|
|
11868
12111
|
const f = pick2.f;
|
|
11869
12112
|
console.log(`
|
|
11870
|
-
${
|
|
12113
|
+
${import_chalk53.default.bold(f.label)} \u2014 ${f.desc}`);
|
|
11871
12114
|
loginFirst(`use ${f.label.toLowerCase()}`);
|
|
11872
12115
|
}
|
|
11873
12116
|
}
|
|
11874
12117
|
}
|
|
11875
12118
|
async function navigator(proj2, cfg, opts) {
|
|
11876
12119
|
const { default: inquirer3 } = await import("inquirer");
|
|
11877
|
-
console.log(
|
|
12120
|
+
console.log(import_chalk53.default.bold(`
|
|
11878
12121
|
${proj2.projectName} v${proj2.apiVersion} \u2014 configuration
|
|
11879
12122
|
`));
|
|
11880
|
-
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"));
|
|
11881
12124
|
let blob = cfg;
|
|
11882
12125
|
for (; ; ) {
|
|
11883
12126
|
const { pick: pick2 } = await inquirer3.prompt([{
|
|
@@ -11886,10 +12129,10 @@ ${proj2.projectName} v${proj2.apiVersion} \u2014 configuration
|
|
|
11886
12129
|
message: "Where to?",
|
|
11887
12130
|
pageSize: 20,
|
|
11888
12131
|
choices: [
|
|
11889
|
-
new inquirer3.Separator(
|
|
12132
|
+
new inquirer3.Separator(import_chalk53.default.bold("\u2014 Settings \u2014")),
|
|
11890
12133
|
...SETTING_GROUPS.map((g) => ({ name: g, value: { kind: "settings", g } })),
|
|
11891
|
-
new inquirer3.Separator(
|
|
11892
|
-
...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 } })),
|
|
11893
12136
|
new inquirer3.Separator(),
|
|
11894
12137
|
{ name: "Show all settings", value: { kind: "list" } },
|
|
11895
12138
|
{ name: "Exit", value: { kind: "exit" } }
|
|
@@ -11929,7 +12172,7 @@ ${proj2.projectName} v${proj2.apiVersion} \u2014 configuration
|
|
|
11929
12172
|
}
|
|
11930
12173
|
}
|
|
11931
12174
|
} catch (err) {
|
|
11932
|
-
console.error(
|
|
12175
|
+
console.error(import_chalk53.default.red(` ${err instanceof Error ? err.message : String(err)}`));
|
|
11933
12176
|
}
|
|
11934
12177
|
}
|
|
11935
12178
|
}
|
|
@@ -11943,7 +12186,7 @@ async function settingsGroup(proj2, cfg, group2) {
|
|
|
11943
12186
|
message: group2 + ":",
|
|
11944
12187
|
pageSize: 16,
|
|
11945
12188
|
choices: [
|
|
11946
|
-
...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 })),
|
|
11947
12190
|
new inquirer3.Separator(),
|
|
11948
12191
|
{ name: "\u2190 Back", value: null }
|
|
11949
12192
|
]
|
|
@@ -11961,7 +12204,7 @@ async function settingsGroup(proj2, cfg, group2) {
|
|
|
11961
12204
|
} else if (s.type === "number") {
|
|
11962
12205
|
const { v } = await inquirer3.prompt([{ type: "input", name: "v", message: `${s.label} (number):`, default: readSetting(s, cfg) }]);
|
|
11963
12206
|
if (v === "" || Number.isNaN(Number(v))) {
|
|
11964
|
-
console.log(
|
|
12207
|
+
console.log(import_chalk53.default.yellow(" Not a number \u2014 unchanged."));
|
|
11965
12208
|
continue;
|
|
11966
12209
|
}
|
|
11967
12210
|
value = Number(v);
|
|
@@ -12042,7 +12285,7 @@ async function buildCondition(phase) {
|
|
|
12042
12285
|
const items = [];
|
|
12043
12286
|
for (; ; ) {
|
|
12044
12287
|
const a = await inquirer3.prompt([
|
|
12045
|
-
{ type: "input", name: "source", message: `Condition field ${
|
|
12288
|
+
{ type: "input", name: "source", message: `Condition field ${import_chalk53.default.dim(srcHint)}:`, validate: (s) => !!s || "required" },
|
|
12046
12289
|
{ type: "list", name: "operator", message: "Operator:", choices: [
|
|
12047
12290
|
"eq",
|
|
12048
12291
|
"neq",
|
|
@@ -12073,7 +12316,7 @@ async function buildCondition(phase) {
|
|
|
12073
12316
|
function showCondition(cond) {
|
|
12074
12317
|
if (!Array.isArray(cond) || !cond.length) return "";
|
|
12075
12318
|
const s = cond.map((c) => `${c.source} ${c.operator}${c.value !== void 0 ? ` "${c.value}"` : ""}${c.logicOp ? ` ${c.logicOp}` : ""}`).join(" ");
|
|
12076
|
-
return
|
|
12319
|
+
return import_chalk53.default.dim(` when ${s}`);
|
|
12077
12320
|
}
|
|
12078
12321
|
async function transformsMenu(proj2) {
|
|
12079
12322
|
const { default: inquirer3 } = await import("inquirer");
|
|
@@ -12082,12 +12325,12 @@ async function transformsMenu(proj2) {
|
|
|
12082
12325
|
const out = await admin({ method: "GET", path: base2, summary: "List transform rules" });
|
|
12083
12326
|
const rules = out?.rules ?? [];
|
|
12084
12327
|
console.log();
|
|
12085
|
-
if (!rules.length) console.log(
|
|
12328
|
+
if (!rules.length) console.log(import_chalk53.default.dim(" No transform rules yet."));
|
|
12086
12329
|
for (const r of rules) {
|
|
12087
12330
|
const a = r.action ?? {};
|
|
12088
12331
|
const fns = [...a.source_fns ?? [], ...a.dest_fns ?? []].map((f) => f.fn);
|
|
12089
|
-
const what = a.type === "hardcode" ? `${a.destination} = "${a.value}"` : a.type === "remove" ? `remove ${a.field}` : `${a.source} \u2192 ${a.destination}${a.lookup ? " (mapped)" : ""}${fns.length ?
|
|
12090
|
-
console.log(` ${r.enabled ?
|
|
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)}`);
|
|
12091
12334
|
}
|
|
12092
12335
|
const { act } = await inquirer3.prompt([{
|
|
12093
12336
|
type: "list",
|
|
@@ -12099,7 +12342,7 @@ async function transformsMenu(proj2) {
|
|
|
12099
12342
|
{ name: "Enable/disable a rule", value: "toggle" },
|
|
12100
12343
|
{ name: "Delete a rule", value: "delete" }
|
|
12101
12344
|
] : [],
|
|
12102
|
-
{ name:
|
|
12345
|
+
{ name: import_chalk53.default.dim("Add from raw JSON (grouped conditions, lookup tables, \u2026)"), value: "raw" },
|
|
12103
12346
|
{ name: "\u2190 Back", value: "back" }
|
|
12104
12347
|
]
|
|
12105
12348
|
}]);
|
|
@@ -12112,11 +12355,11 @@ async function transformsMenu(proj2) {
|
|
|
12112
12355
|
}]);
|
|
12113
12356
|
const body = parseValue(raw);
|
|
12114
12357
|
if (!body || typeof body !== "object" || Array.isArray(body)) {
|
|
12115
|
-
console.log(
|
|
12358
|
+
console.log(import_chalk53.default.yellow(" Not a JSON object \u2014 skipped."));
|
|
12116
12359
|
continue;
|
|
12117
12360
|
}
|
|
12118
12361
|
await admin({ method: "POST", path: base2, body, summary: "Create transform rule (raw JSON)" });
|
|
12119
|
-
console.log(
|
|
12362
|
+
console.log(import_chalk53.default.green(" Rule created."));
|
|
12120
12363
|
continue;
|
|
12121
12364
|
}
|
|
12122
12365
|
if (act === "add") {
|
|
@@ -12132,7 +12375,7 @@ async function transformsMenu(proj2) {
|
|
|
12132
12375
|
{ name: "Remove a field", value: "remove" }
|
|
12133
12376
|
] }
|
|
12134
12377
|
]);
|
|
12135
|
-
const fieldHint =
|
|
12378
|
+
const fieldHint = import_chalk53.default.dim("(e.g. header:x-api-version, param:limit, bodyvar:user.id)");
|
|
12136
12379
|
let action2;
|
|
12137
12380
|
if (ans.kind === "hardcode") {
|
|
12138
12381
|
const a = await inquirer3.prompt([
|
|
@@ -12161,7 +12404,7 @@ async function transformsMenu(proj2) {
|
|
|
12161
12404
|
};
|
|
12162
12405
|
}
|
|
12163
12406
|
const condition = await buildCondition(ans.phase);
|
|
12164
|
-
const spinner = (0,
|
|
12407
|
+
const spinner = (0, import_ora24.default)("Creating rule...").start();
|
|
12165
12408
|
try {
|
|
12166
12409
|
await admin({
|
|
12167
12410
|
method: "POST",
|
|
@@ -12179,16 +12422,16 @@ async function transformsMenu(proj2) {
|
|
|
12179
12422
|
type: "list",
|
|
12180
12423
|
name: "rule",
|
|
12181
12424
|
message: act === "toggle" ? "Which rule?" : "Delete which rule?",
|
|
12182
|
-
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 }]
|
|
12183
12426
|
}]);
|
|
12184
12427
|
if (!rule) continue;
|
|
12185
12428
|
if (act === "toggle") {
|
|
12186
12429
|
const flipped = { ...rule, enabled: rule.enabled === false };
|
|
12187
12430
|
await admin({ method: "PUT", path: `${base2}/${rule.id}`, body: flipped, summary: `${flipped.enabled ? "Enable" : "Disable"} transform "${rule.name}"` });
|
|
12188
|
-
console.log(
|
|
12431
|
+
console.log(import_chalk53.default.green(` ${rule.name} \u2192 ${flipped.enabled ? "enabled" : "disabled"}`));
|
|
12189
12432
|
} else {
|
|
12190
12433
|
await admin({ method: "DELETE", path: `${base2}/${rule.id}`, summary: `Delete transform "${rule.name}"` });
|
|
12191
|
-
console.log(
|
|
12434
|
+
console.log(import_chalk53.default.green(` ${rule.name} deleted.`));
|
|
12192
12435
|
}
|
|
12193
12436
|
}
|
|
12194
12437
|
}
|
|
@@ -12200,9 +12443,9 @@ async function mappingsMenu(proj2) {
|
|
|
12200
12443
|
const out = await admin({ method: "GET", path: base2, summary: "List mapping tables" });
|
|
12201
12444
|
const tables = out?.mappings ?? out?.tables ?? [];
|
|
12202
12445
|
console.log();
|
|
12203
|
-
if (!tables.length) console.log(
|
|
12446
|
+
if (!tables.length) console.log(import_chalk53.default.dim(" No mapping tables yet."));
|
|
12204
12447
|
for (const t of tables) {
|
|
12205
|
-
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" : ""}`)}`);
|
|
12206
12449
|
}
|
|
12207
12450
|
const { act } = await inquirer3.prompt([{
|
|
12208
12451
|
type: "list",
|
|
@@ -12222,11 +12465,11 @@ async function mappingsMenu(proj2) {
|
|
|
12222
12465
|
]);
|
|
12223
12466
|
const entries2 = parseValue(a.entries);
|
|
12224
12467
|
if (!Array.isArray(entries2)) {
|
|
12225
|
-
console.log(
|
|
12468
|
+
console.log(import_chalk53.default.yellow(" Entries must be a JSON array \u2014 not created."));
|
|
12226
12469
|
continue;
|
|
12227
12470
|
}
|
|
12228
12471
|
await admin({ method: "POST", path: base2, body: { name: a.name, entries: entries2 }, summary: `Create mapping table "${a.name}"` });
|
|
12229
|
-
console.log(
|
|
12472
|
+
console.log(import_chalk53.default.green(` Table "${a.name}" created.`));
|
|
12230
12473
|
} else {
|
|
12231
12474
|
const { table } = await inquirer3.prompt([{
|
|
12232
12475
|
type: "list",
|
|
@@ -12236,7 +12479,7 @@ async function mappingsMenu(proj2) {
|
|
|
12236
12479
|
}]);
|
|
12237
12480
|
if (!table) continue;
|
|
12238
12481
|
await admin({ method: "DELETE", path: `${base2}/${table.id}`, summary: `Delete mapping table "${table.name}"` });
|
|
12239
|
-
console.log(
|
|
12482
|
+
console.log(import_chalk53.default.green(` ${table.name} deleted.`));
|
|
12240
12483
|
}
|
|
12241
12484
|
}
|
|
12242
12485
|
}
|
|
@@ -12247,14 +12490,14 @@ async function tenantsMenu(proj2, opts) {
|
|
|
12247
12490
|
const out = await admin({ method: "GET", path: base2, summary: "List attached tenants" });
|
|
12248
12491
|
const tenants = out?.tenants ?? [];
|
|
12249
12492
|
console.log();
|
|
12250
|
-
if (!tenants.length) console.log(
|
|
12251
|
-
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 ?? "")}`);
|
|
12252
12495
|
const { act } = await inquirer3.prompt([{
|
|
12253
12496
|
type: "list",
|
|
12254
12497
|
name: "act",
|
|
12255
12498
|
message: "Tenants:",
|
|
12256
12499
|
choices: [
|
|
12257
|
-
{ 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" },
|
|
12258
12501
|
{ name: "Attach a tenant to this project", value: "attach" },
|
|
12259
12502
|
...tenants.length ? [{ name: "Detach a tenant from this project", value: "detach" }] : [],
|
|
12260
12503
|
{ name: "\u2190 Back", value: "back" }
|
|
@@ -12277,7 +12520,7 @@ async function tenantsMenu(proj2, opts) {
|
|
|
12277
12520
|
}]);
|
|
12278
12521
|
if (!t) continue;
|
|
12279
12522
|
await admin({ method: "DELETE", path: `${base2}/${encodeURIComponent(t.tenant_name ?? t.name)}`, summary: `Detach tenant ${t.tenant_name ?? t.name}` });
|
|
12280
|
-
console.log(
|
|
12523
|
+
console.log(import_chalk53.default.green(` Detached ${t.tenant_name ?? t.name}.`));
|
|
12281
12524
|
}
|
|
12282
12525
|
}
|
|
12283
12526
|
}
|
|
@@ -12320,7 +12563,7 @@ async function specMenu(proj2, opts) {
|
|
|
12320
12563
|
choices: [
|
|
12321
12564
|
{ name: "Print the stored spec", value: "get" },
|
|
12322
12565
|
{ name: "Refresh the spec from its source", value: "refresh" },
|
|
12323
|
-
{ name:
|
|
12566
|
+
{ name: import_chalk53.default.dim("Build the spec by chatting over real traffic \u2192 agent"), value: "agent" },
|
|
12324
12567
|
{ name: "\u2190 Back", value: "back" }
|
|
12325
12568
|
]
|
|
12326
12569
|
}]);
|
|
@@ -12328,7 +12571,7 @@ async function specMenu(proj2, opts) {
|
|
|
12328
12571
|
if (act === "get") await runSpecGet(proj2.projectName, { team: opts.team, apiversion: proj2.apiVersion });
|
|
12329
12572
|
else if (act === "refresh") {
|
|
12330
12573
|
await admin({ method: "POST", path: `/projects/${proj2.projectId}/${proj2.apiVersion}/refresh-spec`, summary: "Refresh spec from source" });
|
|
12331
|
-
console.log(
|
|
12574
|
+
console.log(import_chalk53.default.green(" Spec refresh triggered."));
|
|
12332
12575
|
} else await runOpenapi(proj2.projectName, proj2.apiVersion);
|
|
12333
12576
|
}
|
|
12334
12577
|
async function agentsMenu(proj2, opts) {
|
|
@@ -12353,8 +12596,8 @@ async function agentsMenu(proj2, opts) {
|
|
|
12353
12596
|
}
|
|
12354
12597
|
|
|
12355
12598
|
// src/commands/group.ts
|
|
12356
|
-
var
|
|
12357
|
-
var
|
|
12599
|
+
var import_chalk54 = __toESM(require("chalk"));
|
|
12600
|
+
var import_ora25 = __toESM(require("ora"));
|
|
12358
12601
|
init_auth();
|
|
12359
12602
|
init_admin();
|
|
12360
12603
|
init_resolve();
|
|
@@ -12454,7 +12697,7 @@ async function userByRefOrCreate(t, ref) {
|
|
|
12454
12697
|
if (!ref.includes("@")) throw e;
|
|
12455
12698
|
const created = await t.call("/v1/admin/users", "POST", { email: ref.trim().toLowerCase() });
|
|
12456
12699
|
if (!created?.abz_sub) throw e;
|
|
12457
|
-
console.log(
|
|
12700
|
+
console.log(import_chalk54.default.dim(` ${ref} wasn't a user yet \u2014 created.`));
|
|
12458
12701
|
return { abz_sub: created.abz_sub, email: created.email ?? ref, display_name: null };
|
|
12459
12702
|
}
|
|
12460
12703
|
}
|
|
@@ -12462,11 +12705,11 @@ async function runGroupList(opts) {
|
|
|
12462
12705
|
const t = await resolveTransport(opts);
|
|
12463
12706
|
const groups = await listGroups(t);
|
|
12464
12707
|
if (opts.json) return void console.log(JSON.stringify(groups));
|
|
12465
|
-
if (!groups.length) return void console.log(
|
|
12466
|
-
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}:`));
|
|
12467
12710
|
for (const g of groups) {
|
|
12468
|
-
const sealed = g.name === "apiblaze_admins" ?
|
|
12469
|
-
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}`);
|
|
12470
12713
|
}
|
|
12471
12714
|
}
|
|
12472
12715
|
async function runGroupCreate(name, opts) {
|
|
@@ -12491,10 +12734,10 @@ async function runGroupCreate(name, opts) {
|
|
|
12491
12734
|
}
|
|
12492
12735
|
}
|
|
12493
12736
|
}
|
|
12494
|
-
const spinner = (0,
|
|
12737
|
+
const spinner = (0, import_ora25.default)(`Creating group ${name}...`).start();
|
|
12495
12738
|
try {
|
|
12496
12739
|
await t.call("/v1/admin/groups", "POST", { name, admins: [adminSub] });
|
|
12497
|
-
spinner.succeed(`Group ${
|
|
12740
|
+
spinner.succeed(`Group ${import_chalk54.default.bold(name)} created in ${t.tenant}.`);
|
|
12498
12741
|
} catch (e) {
|
|
12499
12742
|
spinner.fail("Create failed.");
|
|
12500
12743
|
throw e;
|
|
@@ -12503,10 +12746,10 @@ async function runGroupCreate(name, opts) {
|
|
|
12503
12746
|
async function runGroupDelete(name, opts) {
|
|
12504
12747
|
const t = await resolveTransport(opts);
|
|
12505
12748
|
const g = await groupByName(t, name);
|
|
12506
|
-
const spinner = (0,
|
|
12749
|
+
const spinner = (0, import_ora25.default)(`Deleting group ${name}...`).start();
|
|
12507
12750
|
try {
|
|
12508
12751
|
await t.call(`/v1/admin/groups/${encodeURIComponent(g.id)}`, "DELETE");
|
|
12509
|
-
spinner.succeed(`Group ${
|
|
12752
|
+
spinner.succeed(`Group ${import_chalk54.default.bold(name)} deleted.`);
|
|
12510
12753
|
} catch (e) {
|
|
12511
12754
|
spinner.fail("Delete failed.");
|
|
12512
12755
|
throw e;
|
|
@@ -12525,21 +12768,21 @@ async function runGroupMembers(name, opts) {
|
|
|
12525
12768
|
return void console.log(JSON.stringify({ ...detail, members: walkedMembers.items, subgroups: subs?.subgroups ?? [] }));
|
|
12526
12769
|
}
|
|
12527
12770
|
const members = walkedMembers.items;
|
|
12528
|
-
console.log(
|
|
12529
|
-
for (const m of members) console.log(` ${label2(m)} ${m.role === "admin" ?
|
|
12530
|
-
if (!members.length) console.log(
|
|
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"));
|
|
12531
12774
|
const sg = subs?.subgroups ?? [];
|
|
12532
|
-
if (sg.length) console.log(
|
|
12775
|
+
if (sg.length) console.log(import_chalk54.default.dim(` subgroups: ${sg.map((s) => s.name).join(", ")}`));
|
|
12533
12776
|
}
|
|
12534
12777
|
async function runGroupAddUser(userRef, groupName, opts) {
|
|
12535
12778
|
const t = await resolveTransport(opts);
|
|
12536
12779
|
const g = await groupByName(t, groupName);
|
|
12537
12780
|
const u = await userByRefOrCreate(t, userRef);
|
|
12538
12781
|
const role = opts.role === "admin" ? "admin" : "member";
|
|
12539
|
-
const spinner = (0,
|
|
12782
|
+
const spinner = (0, import_ora25.default)(`Adding ${label2(u)} to ${g.name}...`).start();
|
|
12540
12783
|
try {
|
|
12541
12784
|
await t.call(`/v1/admin/groups/${encodeURIComponent(g.id)}/members`, "POST", { abz_sub: u.abz_sub, role });
|
|
12542
|
-
spinner.succeed(`${
|
|
12785
|
+
spinner.succeed(`${import_chalk54.default.bold(label2(u))} added to ${import_chalk54.default.bold(g.name)} as ${role}.`);
|
|
12543
12786
|
} catch (e) {
|
|
12544
12787
|
spinner.fail("Add failed.");
|
|
12545
12788
|
throw e;
|
|
@@ -12548,10 +12791,10 @@ async function runGroupAddUser(userRef, groupName, opts) {
|
|
|
12548
12791
|
async function runGroupRemoveUser(userRef, groupName, opts) {
|
|
12549
12792
|
const t = await resolveTransport(opts);
|
|
12550
12793
|
const [g, u] = [await groupByName(t, groupName), await userByRef(t, userRef)];
|
|
12551
|
-
const spinner = (0,
|
|
12794
|
+
const spinner = (0, import_ora25.default)(`Removing ${label2(u)} from ${g.name}...`).start();
|
|
12552
12795
|
try {
|
|
12553
12796
|
await t.call(`/v1/admin/groups/${encodeURIComponent(g.id)}/members/_?abz_sub=${encodeURIComponent(u.abz_sub)}`, "DELETE");
|
|
12554
|
-
spinner.succeed(`${
|
|
12797
|
+
spinner.succeed(`${import_chalk54.default.bold(label2(u))} removed from ${import_chalk54.default.bold(g.name)}.`);
|
|
12555
12798
|
} catch (e) {
|
|
12556
12799
|
spinner.fail("Remove failed.");
|
|
12557
12800
|
throw e;
|
|
@@ -12560,10 +12803,10 @@ async function runGroupRemoveUser(userRef, groupName, opts) {
|
|
|
12560
12803
|
async function runGroupAddGroup(childName, parentName, opts) {
|
|
12561
12804
|
const t = await resolveTransport(opts);
|
|
12562
12805
|
const [child, parent] = [await groupByName(t, childName), await groupByName(t, parentName)];
|
|
12563
|
-
const spinner = (0,
|
|
12806
|
+
const spinner = (0, import_ora25.default)(`Nesting ${child.name} under ${parent.name}...`).start();
|
|
12564
12807
|
try {
|
|
12565
12808
|
await t.call(`/v1/admin/groups/${encodeURIComponent(parent.id)}/subgroups`, "POST", { child_group_id: child.id });
|
|
12566
|
-
spinner.succeed(`${
|
|
12809
|
+
spinner.succeed(`${import_chalk54.default.bold(child.name)} is now a subgroup of ${import_chalk54.default.bold(parent.name)}.`);
|
|
12567
12810
|
} catch (e) {
|
|
12568
12811
|
spinner.fail("Nest failed.");
|
|
12569
12812
|
throw e;
|
|
@@ -12572,10 +12815,10 @@ async function runGroupAddGroup(childName, parentName, opts) {
|
|
|
12572
12815
|
async function runGroupRemoveGroup(childName, parentName, opts) {
|
|
12573
12816
|
const t = await resolveTransport(opts);
|
|
12574
12817
|
const [child, parent] = [await groupByName(t, childName), await groupByName(t, parentName)];
|
|
12575
|
-
const spinner = (0,
|
|
12818
|
+
const spinner = (0, import_ora25.default)(`Un-nesting ${child.name} from ${parent.name}...`).start();
|
|
12576
12819
|
try {
|
|
12577
12820
|
await t.call(`/v1/admin/groups/${encodeURIComponent(parent.id)}/subgroups/${encodeURIComponent(child.id)}`, "DELETE");
|
|
12578
|
-
spinner.succeed(`${
|
|
12821
|
+
spinner.succeed(`${import_chalk54.default.bold(child.name)} un-nested from ${import_chalk54.default.bold(parent.name)}.`);
|
|
12579
12822
|
} catch (e) {
|
|
12580
12823
|
spinner.fail("Un-nest failed.");
|
|
12581
12824
|
throw e;
|
|
@@ -12586,13 +12829,13 @@ async function runGroupUsers(opts) {
|
|
|
12586
12829
|
const users = await listUsers(t);
|
|
12587
12830
|
const observed = await t.call("/v1/admin/observed-identities").catch(() => ({ identities: [] }));
|
|
12588
12831
|
if (opts.json) return void console.log(JSON.stringify({ users, observed: observed?.identities ?? [] }));
|
|
12589
|
-
console.log(
|
|
12590
|
-
for (const u of users) console.log(` ${label2(u)} ${
|
|
12591
|
-
if (!users.length) console.log(
|
|
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"));
|
|
12592
12835
|
const fresh = (observed?.identities ?? []).filter((o) => !o.provisioned);
|
|
12593
12836
|
if (fresh.length) {
|
|
12594
|
-
console.log(
|
|
12595
|
-
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 ?? "")}`);
|
|
12596
12839
|
}
|
|
12597
12840
|
}
|
|
12598
12841
|
|
|
@@ -12601,22 +12844,22 @@ init_iam();
|
|
|
12601
12844
|
init_admins();
|
|
12602
12845
|
|
|
12603
12846
|
// src/commands/preapprove.ts
|
|
12604
|
-
var
|
|
12605
|
-
var
|
|
12847
|
+
var import_chalk55 = __toESM(require("chalk"));
|
|
12848
|
+
var import_ora26 = __toESM(require("ora"));
|
|
12606
12849
|
async function runPreapprove(who, opts) {
|
|
12607
12850
|
const tenant2 = await resolveProducerTenant(opts.tenant);
|
|
12608
12851
|
if (opts.list) {
|
|
12609
12852
|
const rules = await listPreapprovalRules(tenant2);
|
|
12610
12853
|
if (opts.json) return void console.log(JSON.stringify(rules, null, 2));
|
|
12611
12854
|
if (!rules.length) {
|
|
12612
|
-
console.log(
|
|
12613
|
-
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`));
|
|
12614
12857
|
return;
|
|
12615
12858
|
}
|
|
12616
|
-
console.log(
|
|
12859
|
+
console.log(import_chalk55.default.dim(`Pre-approved for ${import_chalk55.default.bold(tenant2)}:`));
|
|
12617
12860
|
for (const r of rules) {
|
|
12618
|
-
const tag = r.kind === "domain" ?
|
|
12619
|
-
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(", ")}`) : "";
|
|
12620
12863
|
console.log(` ${tag}${grp}`);
|
|
12621
12864
|
}
|
|
12622
12865
|
return;
|
|
@@ -12625,19 +12868,19 @@ async function runPreapprove(who, opts) {
|
|
|
12625
12868
|
throw new Error("Who? Pass an email or a domain: `apiblaze preapprove someone@acme.com` (or `apiblaze preapprove --list`).");
|
|
12626
12869
|
}
|
|
12627
12870
|
if (opts.remove) {
|
|
12628
|
-
const spinner2 = (0,
|
|
12871
|
+
const spinner2 = (0, import_ora26.default)(`Removing ${who} from ${tenant2}\u2026`).start();
|
|
12629
12872
|
const { removed, value } = await removePreapprovalRule(tenant2, who);
|
|
12630
|
-
if (removed) spinner2.succeed(`${
|
|
12631
|
-
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.`);
|
|
12632
12875
|
return;
|
|
12633
12876
|
}
|
|
12634
|
-
const spinner = (0,
|
|
12877
|
+
const spinner = (0, import_ora26.default)(`Pre-approving ${who} for ${tenant2}\u2026`).start();
|
|
12635
12878
|
try {
|
|
12636
12879
|
const { kind, value } = await addPreapprovalRule(tenant2, who, opts.group);
|
|
12637
12880
|
const what = kind === "domain" ? `Anyone @${value}` : value;
|
|
12638
|
-
spinner.succeed(`${
|
|
12639
|
-
if (opts.group?.length) console.log(
|
|
12640
|
-
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`));
|
|
12641
12884
|
} catch (err) {
|
|
12642
12885
|
spinner.fail("Could not add the rule.");
|
|
12643
12886
|
throw err;
|
|
@@ -12645,8 +12888,8 @@ async function runPreapprove(who, opts) {
|
|
|
12645
12888
|
}
|
|
12646
12889
|
|
|
12647
12890
|
// src/commands/consumer.ts
|
|
12648
|
-
var
|
|
12649
|
-
var
|
|
12891
|
+
var import_chalk56 = __toESM(require("chalk"));
|
|
12892
|
+
var import_ora27 = __toESM(require("ora"));
|
|
12650
12893
|
init_admin();
|
|
12651
12894
|
init_resolve();
|
|
12652
12895
|
init_caller();
|
|
@@ -12668,7 +12911,7 @@ async function consumerFetch(creds, suffix, init) {
|
|
|
12668
12911
|
function requireConsumer2() {
|
|
12669
12912
|
const c = loadConsumer();
|
|
12670
12913
|
if (!c) {
|
|
12671
|
-
console.error(
|
|
12914
|
+
console.error(import_chalk56.default.red("Not logged in as a consumer. Run `apiblaze consumer login` first."));
|
|
12672
12915
|
process.exit(1);
|
|
12673
12916
|
}
|
|
12674
12917
|
return c;
|
|
@@ -12679,7 +12922,7 @@ async function runConsumerLogin(opts) {
|
|
|
12679
12922
|
let clientId = opts.client;
|
|
12680
12923
|
if (clientId) {
|
|
12681
12924
|
if (!tenant2) {
|
|
12682
|
-
console.error(
|
|
12925
|
+
console.error(import_chalk56.default.red("When using --client, also pass --tenant <slug> (it sets which portal/keys host to use)."));
|
|
12683
12926
|
process.exit(1);
|
|
12684
12927
|
}
|
|
12685
12928
|
} else {
|
|
@@ -12691,24 +12934,24 @@ async function runConsumerLogin(opts) {
|
|
|
12691
12934
|
if (!picked) process.exit(1);
|
|
12692
12935
|
tenant2 = picked;
|
|
12693
12936
|
}
|
|
12694
|
-
const s2 = (0,
|
|
12937
|
+
const s2 = (0, import_ora27.default)("Finding the login app...").start();
|
|
12695
12938
|
const clients = await admin({ method: "GET", path: `/teams/${encodeURIComponent(teamId)}/tenants/${encodeURIComponent(tenant2)}/app-clients`, summary: `List app clients for ${tenant2}` }).catch(() => []);
|
|
12696
12939
|
s2.stop();
|
|
12697
12940
|
const usable = (Array.isArray(clients) ? clients : []).filter((c) => c && (c.client_id || c.clientId));
|
|
12698
12941
|
const pick2 = usable.find((c) => c.is_default || c.default) ?? usable.find((c) => c.verified !== false) ?? usable[0];
|
|
12699
12942
|
if (!pick2) {
|
|
12700
|
-
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).`));
|
|
12701
12944
|
process.exit(1);
|
|
12702
12945
|
}
|
|
12703
12946
|
clientId = pick2.client_id ?? pick2.clientId;
|
|
12704
12947
|
}
|
|
12705
|
-
console.log(`${
|
|
12948
|
+
console.log(`${import_chalk56.default.cyan("\u2192")} Logging in to ${import_chalk56.default.bold(tenant2)} as a consumer...`);
|
|
12706
12949
|
const result = await deviceLogin(clientId, DEFAULT_SCOPE, ({ verificationUri, userCode }) => {
|
|
12707
12950
|
console.log(`
|
|
12708
|
-
Open: ${
|
|
12709
|
-
console.log(` Code: ${
|
|
12951
|
+
Open: ${import_chalk56.default.underline(verificationUri)}`);
|
|
12952
|
+
console.log(` Code: ${import_chalk56.default.bold(userCode)}
|
|
12710
12953
|
`);
|
|
12711
|
-
console.log(
|
|
12954
|
+
console.log(import_chalk56.default.dim(" (opening your browser\u2026 waiting for you to finish)"));
|
|
12712
12955
|
});
|
|
12713
12956
|
const claims = result.idToken && decodeJwt2(result.idToken) || (decodeJwt2(result.accessToken) ?? {});
|
|
12714
12957
|
const creds = {
|
|
@@ -12723,7 +12966,7 @@ async function runConsumerLogin(opts) {
|
|
|
12723
12966
|
obtainedAt: Date.now()
|
|
12724
12967
|
};
|
|
12725
12968
|
saveConsumer(creds);
|
|
12726
|
-
console.log(
|
|
12969
|
+
console.log(import_chalk56.default.green(`\u2714 Logged in as consumer${creds.email ? ` ${creds.email}` : ""} on ${tenant2}.`));
|
|
12727
12970
|
}
|
|
12728
12971
|
async function runConsumerTokens(opts) {
|
|
12729
12972
|
const creds = requireConsumer2();
|
|
@@ -12736,28 +12979,28 @@ async function runConsumerTokens(opts) {
|
|
|
12736
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));
|
|
12737
12980
|
return;
|
|
12738
12981
|
}
|
|
12739
|
-
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)}
|
|
12740
12983
|
`);
|
|
12741
|
-
console.log(`${
|
|
12984
|
+
console.log(`${import_chalk56.default.bold("access_token")} ${import_chalk56.default.dim("exp " + (exp(fresh.accessToken) ?? "?"))}
|
|
12742
12985
|
${fresh.accessToken}
|
|
12743
12986
|
`);
|
|
12744
|
-
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) ?? "?"))}
|
|
12745
12988
|
${fresh.idToken}
|
|
12746
12989
|
`);
|
|
12747
|
-
if (fresh.refreshToken) console.log(`${
|
|
12990
|
+
if (fresh.refreshToken) console.log(`${import_chalk56.default.bold("refresh_token")}
|
|
12748
12991
|
${fresh.refreshToken}
|
|
12749
12992
|
`);
|
|
12750
|
-
console.log(
|
|
12993
|
+
console.log(import_chalk56.default.dim("These are your own tokens \u2014 keep them secret."));
|
|
12751
12994
|
}
|
|
12752
12995
|
async function runConsumerApikeys(opts) {
|
|
12753
12996
|
const creds = requireConsumer2();
|
|
12754
12997
|
const { default: inquirer3 } = await import("inquirer");
|
|
12755
|
-
const spinner = (0,
|
|
12998
|
+
const spinner = (0, import_ora27.default)("Loading your API keys...").start();
|
|
12756
12999
|
const list = await consumerFetch(creds, withPage("/apikeys", null));
|
|
12757
13000
|
if (list.status >= 400) {
|
|
12758
13001
|
spinner.stop();
|
|
12759
|
-
console.error(
|
|
12760
|
-
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."));
|
|
12761
13004
|
process.exit(1);
|
|
12762
13005
|
}
|
|
12763
13006
|
let walkCreds = list.creds;
|
|
@@ -12786,16 +13029,16 @@ async function runConsumerApikeys(opts) {
|
|
|
12786
13029
|
if (opts.json) {
|
|
12787
13030
|
console.log(JSON.stringify({ keys, revealed: revealMap }, null, 2));
|
|
12788
13031
|
} else if (!keys.length) {
|
|
12789
|
-
console.log(
|
|
13032
|
+
console.log(import_chalk56.default.yellow("No API keys yet."));
|
|
12790
13033
|
} else {
|
|
12791
13034
|
for (const k of keys) {
|
|
12792
13035
|
const clear = revealMap[k.environment]?.key;
|
|
12793
|
-
const shown = clear ?
|
|
12794
|
-
const exp = k.expires_at ?
|
|
12795
|
-
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 ?? "")}`);
|
|
12796
13039
|
}
|
|
12797
13040
|
if (Object.keys(revealMap).length === 0 && keys.some((k) => !k.expires_at)) {
|
|
12798
|
-
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.)"));
|
|
12799
13042
|
}
|
|
12800
13043
|
}
|
|
12801
13044
|
if (opts.json) return;
|
|
@@ -12809,7 +13052,7 @@ async function runConsumerApikeys(opts) {
|
|
|
12809
13052
|
const body = { environment: answers.environment };
|
|
12810
13053
|
if (answers.description) body.description = answers.description;
|
|
12811
13054
|
if (answers.expiresDays) body.expires_in_seconds = Number(answers.expiresDays) * 86400;
|
|
12812
|
-
const s2 = (0,
|
|
13055
|
+
const s2 = (0, import_ora27.default)("Creating key...").start();
|
|
12813
13056
|
const created = await consumerFetch(list.creds, "/apikeys", { method: "POST", body: JSON.stringify(body) });
|
|
12814
13057
|
if (created.status >= 400) {
|
|
12815
13058
|
s2.fail(`Create failed (${created.status}): ${created.data?.error ?? ""}`);
|
|
@@ -12817,13 +13060,13 @@ async function runConsumerApikeys(opts) {
|
|
|
12817
13060
|
}
|
|
12818
13061
|
s2.succeed("Key created.");
|
|
12819
13062
|
const key = created.data?.key ?? created.data?.fullKey;
|
|
12820
|
-
if (key) console.log(` ${
|
|
12821
|
-
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."));
|
|
12822
13065
|
}
|
|
12823
13066
|
|
|
12824
13067
|
// src/commands/sidecar.ts
|
|
12825
|
-
var
|
|
12826
|
-
var
|
|
13068
|
+
var import_chalk57 = __toESM(require("chalk"));
|
|
13069
|
+
var import_ora28 = __toESM(require("ora"));
|
|
12827
13070
|
var fs13 = __toESM(require("fs"));
|
|
12828
13071
|
var path10 = __toESM(require("path"));
|
|
12829
13072
|
init_admin();
|
|
@@ -12865,18 +13108,18 @@ function upsertEnvLocal(root, token) {
|
|
|
12865
13108
|
}
|
|
12866
13109
|
function installSidecarPackage(root) {
|
|
12867
13110
|
if (fs13.existsSync(path10.join(root, "node_modules", "apiblaze", "package.json"))) {
|
|
12868
|
-
console.log(` ${
|
|
13111
|
+
console.log(` ${import_chalk57.default.green("\u2713")} apiblaze package already installed`);
|
|
12869
13112
|
return;
|
|
12870
13113
|
}
|
|
12871
13114
|
const has = (f) => fs13.existsSync(path10.join(root, f));
|
|
12872
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" };
|
|
12873
|
-
const spinner = (0,
|
|
13116
|
+
const spinner = (0, import_ora28.default)(`Installing the apiblaze package (${pm.cmd})\u2026`).start();
|
|
12874
13117
|
try {
|
|
12875
13118
|
const { execSync } = require("child_process");
|
|
12876
13119
|
execSync(`${pm.cmd} ${pm.add} apiblaze`, { cwd: root, stdio: "ignore" });
|
|
12877
13120
|
spinner.succeed("Installed apiblaze (the sidecar runtime).");
|
|
12878
13121
|
} catch {
|
|
12879
|
-
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")}.`);
|
|
12880
13123
|
}
|
|
12881
13124
|
}
|
|
12882
13125
|
function readEnvKey(root) {
|
|
@@ -13015,7 +13258,7 @@ async function runAnonymousInit(root, router, opts) {
|
|
|
13015
13258
|
const { sidecarInitAnonymous: sidecarInitAnonymous2 } = await Promise.resolve().then(() => (init_api(), api_exports));
|
|
13016
13259
|
const { saveAnonCred: saveAnonCred2, clearAnonCred: clearAnonCred2 } = await Promise.resolve().then(() => (init_anon_cred(), anon_cred_exports));
|
|
13017
13260
|
if (opts.newSession) clearAnonCred2();
|
|
13018
|
-
const spinner = (0,
|
|
13261
|
+
const spinner = (0, import_ora28.default)("Setting up a sidecar (no login needed)...").start();
|
|
13019
13262
|
let out;
|
|
13020
13263
|
try {
|
|
13021
13264
|
out = await sidecarInitAnonymous2();
|
|
@@ -13027,29 +13270,29 @@ async function runAnonymousInit(root, router, opts) {
|
|
|
13027
13270
|
if (out.cp_key && out.team_id) saveAnonCred2(out.cp_key, out.team_id, out.claim_code);
|
|
13028
13271
|
const envState = upsertEnvLocal(root, out.token);
|
|
13029
13272
|
ensureGitignored(root);
|
|
13030
|
-
console.log(` ${
|
|
13031
|
-
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)}`);
|
|
13032
13275
|
installSidecarPackage(root);
|
|
13033
13276
|
let inspectorPath = null;
|
|
13034
13277
|
if (!opts.noInspector) {
|
|
13035
13278
|
inspectorPath = generateInspector(root, router);
|
|
13036
|
-
if (inspectorPath) console.log(` ${
|
|
13279
|
+
if (inspectorPath) console.log(` ${import_chalk57.default.green("\u2713")} inspector at ${inspectorPath}`);
|
|
13037
13280
|
}
|
|
13038
13281
|
console.log("");
|
|
13039
|
-
console.log(
|
|
13040
|
-
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.`);
|
|
13041
13284
|
console.log(` 2. Each external origin your app calls is logged in the console \u2014 approve one with:`);
|
|
13042
|
-
console.log(` ${
|
|
13285
|
+
console.log(` ${import_chalk57.default.cyan("apiblaze sidecar approve api.stripe.com")} (no login needed)`);
|
|
13043
13286
|
console.log("");
|
|
13044
|
-
console.log(
|
|
13045
|
-
console.log(` ${
|
|
13046
|
-
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`));
|
|
13047
13290
|
}
|
|
13048
13291
|
async function runSidecar(opts) {
|
|
13049
13292
|
const root = path10.resolve(opts.dir ?? process.cwd());
|
|
13050
13293
|
const detected2 = detectNextProject(root);
|
|
13051
13294
|
if (!detected2.found) {
|
|
13052
|
-
console.log(
|
|
13295
|
+
console.log(import_chalk57.default.yellow(`No Next.js project detected in ${root}.`));
|
|
13053
13296
|
console.log("Create one (e.g. `npx create-next-app`) and re-run `apiblaze init` inside it.");
|
|
13054
13297
|
return;
|
|
13055
13298
|
}
|
|
@@ -13060,10 +13303,10 @@ async function runSidecar(opts) {
|
|
|
13060
13303
|
if (!loadCredentials()) {
|
|
13061
13304
|
upsertEnvLocal(root, readEnvKey(root));
|
|
13062
13305
|
ensureGitignored(root);
|
|
13063
|
-
console.log(` ${
|
|
13064
|
-
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)}`);
|
|
13065
13308
|
installSidecarPackage(root);
|
|
13066
|
-
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."));
|
|
13067
13310
|
return;
|
|
13068
13311
|
}
|
|
13069
13312
|
const { teamId, teamName } = await resolveTeam(opts.team);
|
|
@@ -13072,7 +13315,7 @@ async function runSidecar(opts) {
|
|
|
13072
13315
|
const mustMint = !existingKey || opts.rotate || switchingTeam;
|
|
13073
13316
|
let token = existingKey ?? "";
|
|
13074
13317
|
if (mustMint) {
|
|
13075
|
-
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();
|
|
13076
13319
|
try {
|
|
13077
13320
|
const out = await admin({
|
|
13078
13321
|
method: "POST",
|
|
@@ -13086,39 +13329,39 @@ async function runSidecar(opts) {
|
|
|
13086
13329
|
throw err;
|
|
13087
13330
|
}
|
|
13088
13331
|
} else {
|
|
13089
|
-
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).`));
|
|
13090
13333
|
}
|
|
13091
13334
|
const envState = upsertEnvLocal(root, token);
|
|
13092
13335
|
ensureGitignored(root);
|
|
13093
|
-
console.log(` ${
|
|
13336
|
+
console.log(` ${import_chalk57.default.green("\u2713")} .env.local ${envState} (APIBLAZE_API_KEY) \u2014 gitignored`);
|
|
13094
13337
|
const wireState = wireInstrumentation(root);
|
|
13095
|
-
console.log(` ${
|
|
13338
|
+
console.log(` ${import_chalk57.default.green("\u2713")} instrumentation.ts ${wireState}`);
|
|
13096
13339
|
installSidecarPackage(root);
|
|
13097
13340
|
let inspectorPath = null;
|
|
13098
13341
|
if (!opts.noInspector) {
|
|
13099
13342
|
inspectorPath = generateInspector(root, detected2.router);
|
|
13100
|
-
if (inspectorPath) console.log(` ${
|
|
13343
|
+
if (inspectorPath) console.log(` ${import_chalk57.default.green("\u2713")} inspector at ${inspectorPath}`);
|
|
13101
13344
|
}
|
|
13102
13345
|
console.log("");
|
|
13103
|
-
console.log(
|
|
13104
|
-
console.log(` 1. ${
|
|
13105
|
-
console.log(` 2. The origins your app calls appear as ${
|
|
13106
|
-
console.log(` 3. Approve the ones to route: ${
|
|
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)`);
|
|
13107
13350
|
console.log(` \u2026within ~5 min your app starts routing that origin through APIblaze.`);
|
|
13108
|
-
if (inspectorPath) console.log(` \u2022 Try it now: open ${
|
|
13109
|
-
if (switchingTeam) console.log(
|
|
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>\`.`));
|
|
13110
13353
|
console.log("");
|
|
13111
|
-
console.log(
|
|
13112
|
-
console.log(
|
|
13113
|
-
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)."));
|
|
13114
13357
|
console.log("");
|
|
13115
|
-
console.log(
|
|
13116
|
-
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."));
|
|
13117
13360
|
}
|
|
13118
13361
|
|
|
13119
13362
|
// src/commands/origins.ts
|
|
13120
|
-
var
|
|
13121
|
-
var
|
|
13363
|
+
var import_chalk58 = __toESM(require("chalk"));
|
|
13364
|
+
var import_ora29 = __toESM(require("ora"));
|
|
13122
13365
|
init_admin();
|
|
13123
13366
|
init_resolve();
|
|
13124
13367
|
init_auth();
|
|
@@ -13128,7 +13371,7 @@ async function runOriginsList(opts) {
|
|
|
13128
13371
|
if (!loadCredentials()) {
|
|
13129
13372
|
const cred = loadAnonCred();
|
|
13130
13373
|
if (!cred) {
|
|
13131
|
-
console.log(
|
|
13374
|
+
console.log(import_chalk58.default.yellow("No anonymous workspace here. Run `apiblaze init` first."));
|
|
13132
13375
|
return;
|
|
13133
13376
|
}
|
|
13134
13377
|
out = await cpFetch(cred.cp_key, `/teams/${encodeURIComponent(cred.team_id)}/sidecar/candidates`, { method: "GET" });
|
|
@@ -13146,30 +13389,30 @@ async function runOriginsList(opts) {
|
|
|
13146
13389
|
}
|
|
13147
13390
|
const routed = out.routed ?? [];
|
|
13148
13391
|
const candidates = out.candidates ?? [];
|
|
13149
|
-
console.log(
|
|
13392
|
+
console.log(import_chalk58.default.bold(`
|
|
13150
13393
|
Routed through APIblaze (${routed.length})`));
|
|
13151
|
-
if (!routed.length) console.log(
|
|
13152
|
-
for (const r of routed) console.log(` ${
|
|
13153
|
-
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(`
|
|
13154
13397
|
Candidates \u2014 going direct, not yet approved (${candidates.length})`));
|
|
13155
|
-
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"));
|
|
13156
13399
|
for (const c of candidates) {
|
|
13157
|
-
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}`)}`);
|
|
13158
13401
|
}
|
|
13159
13402
|
if (candidates.length) {
|
|
13160
|
-
console.log(
|
|
13403
|
+
console.log(import_chalk58.default.dim(`
|
|
13161
13404
|
Approve: apiblaze sidecar approve ${candidates[0].origin.replace("https://", "")}`));
|
|
13162
|
-
console.log(
|
|
13405
|
+
console.log(import_chalk58.default.dim(` Dismiss: apiblaze sidecar deny ${candidates[0].origin.replace("https://", "")}`));
|
|
13163
13406
|
}
|
|
13164
13407
|
}
|
|
13165
13408
|
async function runOriginsApprove(origin, opts) {
|
|
13166
13409
|
if (!loadCredentials()) {
|
|
13167
13410
|
const cred = loadAnonCred();
|
|
13168
13411
|
if (!cred) {
|
|
13169
|
-
console.error(
|
|
13412
|
+
console.error(import_chalk58.default.red("Not logged in and no anonymous workspace. Run `apiblaze init` first."));
|
|
13170
13413
|
process.exit(1);
|
|
13171
13414
|
}
|
|
13172
|
-
const spinner2 = (0,
|
|
13415
|
+
const spinner2 = (0, import_ora29.default)(`Approving ${origin} (anonymous)...`).start();
|
|
13173
13416
|
try {
|
|
13174
13417
|
const out = await cpFetch(cred.cp_key, `/teams/${encodeURIComponent(cred.team_id)}/sidecar/approve`, { method: "POST", body: JSON.stringify({ origin }) });
|
|
13175
13418
|
spinner2.succeed(`Approved ${origin} \u2192 proxy ${out.project_id}. Routing within ~5 min.`);
|
|
@@ -13180,7 +13423,7 @@ async function runOriginsApprove(origin, opts) {
|
|
|
13180
13423
|
return;
|
|
13181
13424
|
}
|
|
13182
13425
|
const { teamId } = await resolveTeam(opts.team);
|
|
13183
|
-
const spinner = (0,
|
|
13426
|
+
const spinner = (0, import_ora29.default)(`Approving ${origin}...`).start();
|
|
13184
13427
|
try {
|
|
13185
13428
|
const out = await admin({
|
|
13186
13429
|
method: "POST",
|
|
@@ -13197,7 +13440,7 @@ async function runOriginsApprove(origin, opts) {
|
|
|
13197
13440
|
}
|
|
13198
13441
|
async function runOriginsDeny(origin, opts) {
|
|
13199
13442
|
const { teamId } = await resolveTeam(opts.team);
|
|
13200
|
-
const spinner = (0,
|
|
13443
|
+
const spinner = (0, import_ora29.default)(`Dismissing ${origin}...`).start();
|
|
13201
13444
|
try {
|
|
13202
13445
|
await admin({ method: "POST", path: `/teams/${encodeURIComponent(teamId)}/sidecar/dismiss`, body: { origin }, summary: `Dismiss sidecar origin ${origin}` });
|
|
13203
13446
|
spinner.succeed(`Dismissed ${origin}. It won't be suggested again.`);
|
|
@@ -13208,7 +13451,7 @@ async function runOriginsDeny(origin, opts) {
|
|
|
13208
13451
|
}
|
|
13209
13452
|
async function runOriginsRemove(origin, opts) {
|
|
13210
13453
|
const { teamId } = await resolveTeam(opts.team);
|
|
13211
|
-
const spinner = (0,
|
|
13454
|
+
const spinner = (0, import_ora29.default)(`Removing the proxy for ${origin}...`).start();
|
|
13212
13455
|
try {
|
|
13213
13456
|
await admin({ method: "POST", path: `/teams/${encodeURIComponent(teamId)}/sidecar/remove`, body: { origin }, summary: `Un-route sidecar origin ${origin}` });
|
|
13214
13457
|
spinner.succeed(`Removed ${origin}. Your app will stop routing it (goes direct) within ~5 min.`);
|
|
@@ -13219,7 +13462,7 @@ async function runOriginsRemove(origin, opts) {
|
|
|
13219
13462
|
}
|
|
13220
13463
|
|
|
13221
13464
|
// src/commands/op.ts
|
|
13222
|
-
var
|
|
13465
|
+
var import_chalk63 = __toESM(require("chalk"));
|
|
13223
13466
|
init_auth();
|
|
13224
13467
|
|
|
13225
13468
|
// src/lib/op-call.ts
|
|
@@ -13248,7 +13491,7 @@ async function opCall(call2) {
|
|
|
13248
13491
|
}
|
|
13249
13492
|
|
|
13250
13493
|
// src/commands/op-billing.ts
|
|
13251
|
-
var
|
|
13494
|
+
var import_chalk59 = __toESM(require("chalk"));
|
|
13252
13495
|
init_admin();
|
|
13253
13496
|
var SANDBOX = {
|
|
13254
13497
|
teamId: "team_1782844865835_zujrf",
|
|
@@ -13307,7 +13550,7 @@ async function rowsForRays(project, version2, tenant2, rays) {
|
|
|
13307
13550
|
}
|
|
13308
13551
|
function printDoors(data) {
|
|
13309
13552
|
const checks = [];
|
|
13310
|
-
console.log(
|
|
13553
|
+
console.log(import_chalk59.default.bold("\n Doors \u2014 is every way in metered?\n"));
|
|
13311
13554
|
const doors = data?.doors ?? [];
|
|
13312
13555
|
const metered = doors.filter((d) => d.verdict === "metered");
|
|
13313
13556
|
const allowed = doors.filter((d) => d.verdict === "allowed-free");
|
|
@@ -13316,29 +13559,29 @@ function printDoors(data) {
|
|
|
13316
13559
|
const errs = data?.errors ?? [];
|
|
13317
13560
|
const routeAuditBroke = errs.some((e) => e.startsWith("zone "));
|
|
13318
13561
|
const devAuditBroke = errs.some((e) => e.startsWith("workers.dev audit"));
|
|
13319
|
-
console.log(
|
|
13320
|
-
for (const d of metered) console.log(
|
|
13321
|
-
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(`
|
|
13322
13565
|
${allowed.length} route(s) free ON PURPOSE`));
|
|
13323
13566
|
for (const d of allowed) {
|
|
13324
|
-
console.log(
|
|
13325
|
-
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}`));
|
|
13326
13569
|
}
|
|
13327
13570
|
if (known.length) {
|
|
13328
|
-
console.log(
|
|
13571
|
+
console.log(import_chalk59.default.yellow(`
|
|
13329
13572
|
${known.length} route(s) KNOWN OPEN \u2014 unmetered, not yet closed`));
|
|
13330
13573
|
for (const d of known) {
|
|
13331
|
-
console.log(
|
|
13332
|
-
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}`));
|
|
13333
13576
|
}
|
|
13334
13577
|
checks.push({ name: "no known-open doors", status: "KNOWN", detail: `${known.length} unmetered route(s) still open \u2014 see above` });
|
|
13335
13578
|
}
|
|
13336
13579
|
if (stray.length) {
|
|
13337
|
-
console.log(
|
|
13580
|
+
console.log(import_chalk59.default.red(`
|
|
13338
13581
|
${stray.length} STRAY route(s) \u2014 not main-proxy, not on the allowlist`));
|
|
13339
13582
|
for (const d of stray) {
|
|
13340
|
-
console.log(
|
|
13341
|
-
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}`));
|
|
13342
13585
|
}
|
|
13343
13586
|
checks.push({ name: "no stray routes", status: "FAIL", detail: `${stray.length}: ${stray.map((s) => s.pattern).join(", ")}` });
|
|
13344
13587
|
} else if (routeAuditBroke) {
|
|
@@ -13349,22 +13592,22 @@ function printDoors(data) {
|
|
|
13349
13592
|
const wd = data?.workers_dev ?? {};
|
|
13350
13593
|
const open = wd.enabled ?? [];
|
|
13351
13594
|
if (open.length) {
|
|
13352
|
-
console.log(
|
|
13595
|
+
console.log(import_chalk59.default.red(`
|
|
13353
13596
|
${open.length} of ${wd.total} worker(s) reachable on *.workers.dev`));
|
|
13354
|
-
for (const s of open) console.log(
|
|
13355
|
-
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."));
|
|
13356
13599
|
checks.push({ name: "no workers.dev doors", status: "FAIL", detail: `${open.length} script(s) publicly reachable: ${open.map((s) => s.script).join(", ")}` });
|
|
13357
13600
|
} else if (devAuditBroke || !wd.total) {
|
|
13358
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." });
|
|
13359
13602
|
} else {
|
|
13360
|
-
console.log(
|
|
13603
|
+
console.log(import_chalk59.default.green(`
|
|
13361
13604
|
\u2713 0 of ${wd.total} workers reachable on *.workers.dev`));
|
|
13362
13605
|
checks.push({ name: "no workers.dev doors", status: "PASS", detail: `all ${wd.total} scripts have workers.dev + previews disabled` });
|
|
13363
13606
|
}
|
|
13364
|
-
if (data?.how_to_fix) console.log(
|
|
13365
|
-
${data.reason}`) +
|
|
13607
|
+
if (data?.how_to_fix) console.log(import_chalk59.default.yellow(`
|
|
13608
|
+
${data.reason}`) + import_chalk59.default.dim(`
|
|
13366
13609
|
${data.how_to_fix}`));
|
|
13367
|
-
else for (const e of errs) console.log(
|
|
13610
|
+
else for (const e of errs) console.log(import_chalk59.default.red(`
|
|
13368
13611
|
audit error: ${e}`));
|
|
13369
13612
|
if (errs.length) {
|
|
13370
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` });
|
|
@@ -13374,9 +13617,9 @@ function printDoors(data) {
|
|
|
13374
13617
|
async function runMeter(readLedger, opts) {
|
|
13375
13618
|
const checks = [];
|
|
13376
13619
|
const N = Math.max(1, Math.min(10, opts.count ?? 3));
|
|
13377
|
-
console.log(
|
|
13620
|
+
console.log(import_chalk59.default.bold("\n Meter \u2014 is 1 request charged exactly 1 request?\n"));
|
|
13378
13621
|
const snap = await readLedger();
|
|
13379
|
-
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
|
|
13380
13623
|
`));
|
|
13381
13624
|
const url = `https://${dpHost}/${SANDBOX.version}/${SANDBOX.environment}/`;
|
|
13382
13625
|
const headers = opts.key ? { "X-API-Key": opts.key } : {};
|
|
@@ -13458,27 +13701,27 @@ async function runMeter(readLedger, opts) {
|
|
|
13458
13701
|
return checks;
|
|
13459
13702
|
}
|
|
13460
13703
|
function printChecks(checks) {
|
|
13461
|
-
console.log(
|
|
13462
|
-
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") };
|
|
13463
13706
|
for (const ch of checks) {
|
|
13464
|
-
console.log(` ${mark[ch.status]} ${
|
|
13465
|
-
console.log(
|
|
13707
|
+
console.log(` ${mark[ch.status]} ${import_chalk59.default.bold(ch.name)}`);
|
|
13708
|
+
console.log(import_chalk59.default.dim(` ${ch.detail}`));
|
|
13466
13709
|
}
|
|
13467
13710
|
const fails = checks.filter((c) => c.status === "FAIL").length;
|
|
13468
13711
|
const skips = checks.filter((c) => c.status === "SKIP").length;
|
|
13469
13712
|
const known = checks.filter((c) => c.status === "KNOWN").length;
|
|
13470
13713
|
console.log("");
|
|
13471
13714
|
const passes = checks.filter((c) => c.status === "PASS").length;
|
|
13472
|
-
if (fails) console.log(
|
|
13473
|
-
else if (passes) console.log(
|
|
13474
|
-
else console.log(
|
|
13475
|
-
if (known) console.log(
|
|
13476
|
-
if (skips) console.log(
|
|
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.`));
|
|
13477
13720
|
console.log("");
|
|
13478
13721
|
}
|
|
13479
13722
|
|
|
13480
13723
|
// src/commands/op-test.ts
|
|
13481
|
-
var
|
|
13724
|
+
var import_chalk60 = __toESM(require("chalk"));
|
|
13482
13725
|
init_auth();
|
|
13483
13726
|
init_types();
|
|
13484
13727
|
var DASHBOARD_BASE9 = process.env.APIBLAZE_DASHBOARD_BASE || "https://dashboard.apiblaze.com";
|
|
@@ -13503,10 +13746,10 @@ async function scrubSandboxKeys(timeoutS = 150) {
|
|
|
13503
13746
|
try {
|
|
13504
13747
|
const r = await runnerCall({ action: "gut", id: TEARDOWN_GUT_ID }, timeoutS);
|
|
13505
13748
|
const first = r.results?.[0]?.detail ?? r.error ?? "";
|
|
13506
|
-
log(
|
|
13749
|
+
log(import_chalk60.default.dim(`teardown: ${first || (r.ok ? "sandbox fixture keys scrubbed" : "scrub reported a problem")}`));
|
|
13507
13750
|
return r.ok;
|
|
13508
13751
|
} catch (e) {
|
|
13509
|
-
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`));
|
|
13510
13753
|
return false;
|
|
13511
13754
|
}
|
|
13512
13755
|
}
|
|
@@ -13528,23 +13771,23 @@ async function runOpTest(o = {}) {
|
|
|
13528
13771
|
const guts = o.skipGuts ? [] : listing.guts.filter((g) => g.id !== TEARDOWN_GUT_ID && matches(g.id, void 0, { ...o, group: void 0 }));
|
|
13529
13772
|
const total = specs.length + guts.length;
|
|
13530
13773
|
if (total === 0) {
|
|
13531
|
-
log(
|
|
13774
|
+
log(import_chalk60.default.yellow("nothing matched"));
|
|
13532
13775
|
process.exitCode = 2;
|
|
13533
13776
|
return;
|
|
13534
13777
|
}
|
|
13535
|
-
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}`));
|
|
13536
13779
|
const results = [];
|
|
13537
13780
|
let failed = 0;
|
|
13538
13781
|
const started = Date.now();
|
|
13539
13782
|
const record = (r) => {
|
|
13540
13783
|
results.push(r);
|
|
13541
13784
|
if (!r.ok) failed += 1;
|
|
13542
|
-
const mark = r.ok ?
|
|
13785
|
+
const mark = r.ok ? import_chalk60.default.green("PASS") : import_chalk60.default.red("FAIL");
|
|
13543
13786
|
log(`[${mark}] ${r.id} \u2014 ${r.label} (${ms(r.duration_ms)})`);
|
|
13544
13787
|
if (!r.ok) {
|
|
13545
|
-
if (r.kind === "spec") for (const c of r.cases.filter((c2) => !c2.ok)) log(
|
|
13546
|
-
else if (r.error) log(
|
|
13547
|
-
else for (const x of r.results.filter((x2) => x2 && x2.ok === false)) log(
|
|
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)}`));
|
|
13548
13791
|
}
|
|
13549
13792
|
};
|
|
13550
13793
|
try {
|
|
@@ -13575,13 +13818,13 @@ async function runOpTest(o = {}) {
|
|
|
13575
13818
|
}
|
|
13576
13819
|
const summary = { ran: results.length, passed: results.length - failed, failed, duration_ms: Date.now() - started, sandbox_team: listing.sandbox_team };
|
|
13577
13820
|
log("");
|
|
13578
|
-
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)})`));
|
|
13579
13822
|
if (o.json) process.stdout.write(JSON.stringify({ ...summary, results }, null, 2) + "\n");
|
|
13580
13823
|
if (failed) process.exitCode = 1;
|
|
13581
13824
|
}
|
|
13582
13825
|
|
|
13583
13826
|
// src/commands/op-consumer.ts
|
|
13584
|
-
var
|
|
13827
|
+
var import_chalk61 = __toESM(require("chalk"));
|
|
13585
13828
|
var SANDBOX_TENANT = "portalteststenant";
|
|
13586
13829
|
var SANDBOX_PROXY = "portaltestproxypreapproved@2.7.0";
|
|
13587
13830
|
function proxyUrlFor(spec2, path11 = "/get") {
|
|
@@ -13623,9 +13866,9 @@ async function runOpConsumer(o = {}) {
|
|
|
13623
13866
|
const creds = loadConsumer();
|
|
13624
13867
|
const done = (r) => {
|
|
13625
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 };
|
|
13626
|
-
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");
|
|
13627
13870
|
log2(`[${mark}] ${out.id} \u2014 ${out.label}${out.tenant ? ` (tenant ${out.tenant})` : ""}${out.url ? ` \u2192 ${out.url}` : ""}`);
|
|
13628
|
-
for (const c of out.cases) if (!c.ok || out.skipped) log2((c.ok ?
|
|
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}`));
|
|
13629
13872
|
if (o.json) process.stdout.write(JSON.stringify(out, null, 2) + "\n");
|
|
13630
13873
|
if (!out.ok && !out.skipped) process.exitCode = 1;
|
|
13631
13874
|
if (out.skipped) process.exitCode = 2;
|
|
@@ -13665,7 +13908,7 @@ async function runOpConsumer(o = {}) {
|
|
|
13665
13908
|
}
|
|
13666
13909
|
|
|
13667
13910
|
// src/commands/op-perf.ts
|
|
13668
|
-
var
|
|
13911
|
+
var import_chalk62 = __toESM(require("chalk"));
|
|
13669
13912
|
var RPS_DEFAULT = 100;
|
|
13670
13913
|
var RPS_CAP = 300;
|
|
13671
13914
|
var SECONDS_DEFAULT = 60;
|
|
@@ -13849,10 +14092,10 @@ async function readGateway(proxy, sent, signal) {
|
|
|
13849
14092
|
}
|
|
13850
14093
|
const left = deadline - Date.now();
|
|
13851
14094
|
if (left <= 0) {
|
|
13852
|
-
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})` : ""}`));
|
|
13853
14096
|
return null;
|
|
13854
14097
|
}
|
|
13855
|
-
log3(
|
|
14098
|
+
log3(import_chalk62.default.dim(` waiting for the latency ledger\u2026 ${lastSeen}/${sent} rows for ${proxy}${lastErr ? ` (${lastErr})` : ""}`));
|
|
13856
14099
|
await sleep2(Math.min(GATEWAY_POLL_MS, left), signal);
|
|
13857
14100
|
}
|
|
13858
14101
|
}
|
|
@@ -13861,7 +14104,7 @@ async function runOpPerf(o = {}) {
|
|
|
13861
14104
|
const thresholds = thresholdsFrom(o);
|
|
13862
14105
|
const ctrl = new AbortController();
|
|
13863
14106
|
const onSigint = () => {
|
|
13864
|
-
log3(
|
|
14107
|
+
log3(import_chalk62.default.yellow("\ninterrupted \u2014 stopping the load and tearing down the fixture"));
|
|
13865
14108
|
ctrl.abort();
|
|
13866
14109
|
};
|
|
13867
14110
|
process.once("SIGINT", onSigint);
|
|
@@ -13870,7 +14113,7 @@ async function runOpPerf(o = {}) {
|
|
|
13870
14113
|
if (!o.key) throw new Error("--url needs --key (the fixture proxy's API key)");
|
|
13871
14114
|
fixture = { url: o.url, key: o.key, proxy: proxyFromUrl(o.url), provisioned: false };
|
|
13872
14115
|
} else {
|
|
13873
|
-
log3(
|
|
14116
|
+
log3(import_chalk62.default.dim("provisioning the perf fixture through the runner\u2026"));
|
|
13874
14117
|
const f = await runnerCall({ action: "perf-fixture" }, 120);
|
|
13875
14118
|
if (!f?.url || !f?.key) throw new Error(`perf-fixture returned no url/key: ${JSON.stringify(f).slice(0, 200)}`);
|
|
13876
14119
|
fixture = { url: f.url, key: f.key, proxy: f.proxy || proxyFromUrl(f.url), team: f.team, provisioned: true };
|
|
@@ -13878,7 +14121,7 @@ async function runOpPerf(o = {}) {
|
|
|
13878
14121
|
try {
|
|
13879
14122
|
const total = Math.round(rps * seconds);
|
|
13880
14123
|
const intervalMs = 1e3 / rps;
|
|
13881
|
-
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})` : ""}`));
|
|
13882
14125
|
const samples = [];
|
|
13883
14126
|
const inflight = /* @__PURE__ */ new Set();
|
|
13884
14127
|
let fired = 0;
|
|
@@ -13886,7 +14129,7 @@ async function runOpPerf(o = {}) {
|
|
|
13886
14129
|
const progress = setInterval(() => {
|
|
13887
14130
|
const m = summarize(samples, Date.now() - t0);
|
|
13888
14131
|
const secs = (Date.now() - t0) / 1e3;
|
|
13889
|
-
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)`));
|
|
13890
14133
|
}, PROGRESS_EVERY_MS);
|
|
13891
14134
|
const { sent, elapsedMs } = await schedule({
|
|
13892
14135
|
total,
|
|
@@ -13900,21 +14143,21 @@ async function runOpPerf(o = {}) {
|
|
|
13900
14143
|
}
|
|
13901
14144
|
});
|
|
13902
14145
|
clearInterval(progress);
|
|
13903
|
-
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`));
|
|
13904
14147
|
await Promise.all([...inflight]);
|
|
13905
14148
|
const client = summarize(samples, elapsedMs);
|
|
13906
14149
|
log3("");
|
|
13907
|
-
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}`);
|
|
13908
14151
|
const gateway = ctrl.signal.aborted ? null : await readGateway(fixture.proxy, sent, ctrl.signal);
|
|
13909
14152
|
if (gateway) {
|
|
13910
14153
|
const extras = [
|
|
13911
14154
|
gateway.kv_per_req !== void 0 ? `kv/req ${gateway.kv_per_req}` : "",
|
|
13912
14155
|
gateway.redis_per_req !== void 0 ? `redis/req ${gateway.redis_per_req}` : ""
|
|
13913
14156
|
].filter(Boolean).join(" ");
|
|
13914
|
-
log3(` ${
|
|
13915
|
-
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}`));
|
|
13916
14159
|
} else {
|
|
13917
|
-
log3(` ${
|
|
14160
|
+
log3(` ${import_chalk62.default.bold("gateway")} ${import_chalk62.default.yellow("not available")} ${import_chalk62.default.dim("(gateway thresholds not evaluated)")}`);
|
|
13918
14161
|
}
|
|
13919
14162
|
const breaches = evaluate(client, gateway, thresholds);
|
|
13920
14163
|
const aborted = ctrl.signal.aborted;
|
|
@@ -13922,7 +14165,7 @@ async function runOpPerf(o = {}) {
|
|
|
13922
14165
|
log3("");
|
|
13923
14166
|
const row = (metric, value, limit, unit) => {
|
|
13924
14167
|
const hit = breaches.find((b) => b.metric === metric);
|
|
13925
|
-
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}`);
|
|
13926
14169
|
};
|
|
13927
14170
|
row("client_p50", client.p50, thresholds.p50_ms, "ms");
|
|
13928
14171
|
row("client_p95", client.p95, thresholds.p95_ms, "ms");
|
|
@@ -13931,12 +14174,12 @@ async function runOpPerf(o = {}) {
|
|
|
13931
14174
|
row("gateway_p50", gateway.p50, thresholds.gateway_p50_ms, "ms");
|
|
13932
14175
|
row("gateway_p95", gateway.p95, thresholds.gateway_p95_ms, "ms");
|
|
13933
14176
|
} else {
|
|
13934
|
-
log3(`[${
|
|
14177
|
+
log3(`[${import_chalk62.default.yellow("SKIP")}] gateway_p50 / gateway_p95 \u2014 ledger rows not available`);
|
|
13935
14178
|
}
|
|
13936
14179
|
log3("");
|
|
13937
|
-
if (aborted) log3(
|
|
13938
|
-
else if (ok) log3(
|
|
13939
|
-
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(", ")}`));
|
|
13940
14183
|
if (o.json) {
|
|
13941
14184
|
const doc = {
|
|
13942
14185
|
kind: "perf",
|
|
@@ -13974,9 +14217,9 @@ async function runOpPerf(o = {}) {
|
|
|
13974
14217
|
if (fixture.provisioned) {
|
|
13975
14218
|
try {
|
|
13976
14219
|
await runnerCall({ action: "perf-teardown", proxy: fixture.proxy }, 120);
|
|
13977
|
-
log3(
|
|
14220
|
+
log3(import_chalk62.default.dim(`fixture ${fixture.proxy} torn down`));
|
|
13978
14221
|
} catch (e) {
|
|
13979
|
-
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`));
|
|
13980
14223
|
process.exitCode = 1;
|
|
13981
14224
|
}
|
|
13982
14225
|
await scrubSandboxKeys(120);
|
|
@@ -14010,93 +14253,93 @@ var OP_COMMANDS = [
|
|
|
14010
14253
|
function renderOpCommands() {
|
|
14011
14254
|
const width = Math.max(...OP_COMMANDS.map((c) => c.cmd.length)) + 10;
|
|
14012
14255
|
const lines = OP_COMMANDS.map((c) => {
|
|
14013
|
-
const left = ` ${
|
|
14256
|
+
const left = ` ${import_chalk63.default.cyan(`apiblaze ${c.cmd}`)}`;
|
|
14014
14257
|
const pad = " ".repeat(Math.max(1, width - c.cmd.length));
|
|
14015
|
-
return `${left}${pad}${c.blurb}${c.extra ? " " +
|
|
14258
|
+
return `${left}${pad}${c.blurb}${c.extra ? " " + import_chalk63.default.dim(`(${c.extra})`) : ""}`;
|
|
14016
14259
|
});
|
|
14017
14260
|
return [
|
|
14018
|
-
|
|
14261
|
+
import_chalk63.default.bold("Operator commands"),
|
|
14019
14262
|
...lines,
|
|
14020
14263
|
"",
|
|
14021
|
-
|
|
14022
|
-
|
|
14023
|
-
|
|
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`."),
|
|
14024
14267
|
"",
|
|
14025
|
-
|
|
14026
|
-
|
|
14027
|
-
|
|
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.")
|
|
14028
14271
|
].join("\n");
|
|
14029
14272
|
}
|
|
14030
14273
|
function printResidue(report, applied) {
|
|
14031
14274
|
const up = report?.upstash ?? {};
|
|
14032
14275
|
const fga = report?.fga ?? {};
|
|
14033
14276
|
const ghosts = report?.ghosts ?? {};
|
|
14034
|
-
console.log(
|
|
14035
|
-
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"));
|
|
14036
14279
|
const orphans = up.orphans ?? [];
|
|
14037
|
-
if (orphans.length === 0) console.log(
|
|
14038
|
-
for (const o of orphans) console.log(` ${
|
|
14039
|
-
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}`));
|
|
14040
14283
|
if (up.anon_wallet_detail) {
|
|
14041
14284
|
const d = up.anon_wallet_detail;
|
|
14042
|
-
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)"}`));
|
|
14043
14286
|
}
|
|
14044
14287
|
if (up.keyspace_census) {
|
|
14045
14288
|
const census = Object.entries(up.keyspace_census).map(([k, v]) => `${k}=${v}`).join(" \xB7 ");
|
|
14046
|
-
console.log(
|
|
14289
|
+
console.log(import_chalk63.default.dim(` keyspace: ${census}`));
|
|
14047
14290
|
}
|
|
14048
|
-
if (up.unknown?.length) console.log(
|
|
14049
|
-
if (applied) console.log(` ${
|
|
14050
|
-
for (const e of up.errors ?? []) console.log(
|
|
14051
|
-
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"));
|
|
14052
14295
|
if (applied) {
|
|
14053
14296
|
const swept = fga?.swept ?? [];
|
|
14054
|
-
if (swept.length === 0) console.log(
|
|
14297
|
+
if (swept.length === 0) console.log(import_chalk63.default.green(" no orphaned stores"));
|
|
14055
14298
|
for (const s of swept) {
|
|
14056
14299
|
console.log(
|
|
14057
|
-
` ${
|
|
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`)}`
|
|
14058
14301
|
);
|
|
14059
14302
|
}
|
|
14060
|
-
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`));
|
|
14061
14304
|
const st = fga?.side_tables;
|
|
14062
|
-
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})` : ""}`));
|
|
14063
14306
|
} else {
|
|
14064
14307
|
const fgaOrphans = fga?.orphans ?? [];
|
|
14065
|
-
if (fgaOrphans.length === 0) console.log(
|
|
14308
|
+
if (fgaOrphans.length === 0) console.log(import_chalk63.default.green(" no orphaned stores"));
|
|
14066
14309
|
for (const s of fgaOrphans) {
|
|
14067
14310
|
const src = s.in_openfga ? "live in OpenFGA" : "Neon tuples only";
|
|
14068
|
-
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)`)}`);
|
|
14069
14312
|
}
|
|
14070
|
-
console.log(
|
|
14313
|
+
console.log(import_chalk63.default.dim(` kept stores: ${(fga?.kept_store_ids ?? []).length}`));
|
|
14071
14314
|
const st = fga?.side_tables;
|
|
14072
|
-
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`));
|
|
14073
14316
|
}
|
|
14074
|
-
for (const e of fga?.errors ?? []) console.log(
|
|
14075
|
-
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"));
|
|
14076
14319
|
if (applied) {
|
|
14077
|
-
if ((ghosts?.ghost_count ?? 0) === 0) console.log(
|
|
14078
|
-
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)`)}`);
|
|
14079
14322
|
} else {
|
|
14080
14323
|
const n = ghosts?.ghost_count ?? 0;
|
|
14081
|
-
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)`)}`));
|
|
14082
14325
|
else {
|
|
14083
|
-
console.log(
|
|
14326
|
+
console.log(import_chalk63.default.yellow(` ${n} ghost tuple(s) referencing entities absent from D1:`));
|
|
14084
14327
|
for (const g of (ghosts.ghosts ?? []).slice(0, 20)) {
|
|
14085
|
-
console.log(
|
|
14328
|
+
console.log(import_chalk63.default.dim(` ${g.object_type}:${g.object_id} ${g.relation} ${g._user}`));
|
|
14086
14329
|
}
|
|
14087
|
-
if (n > 20) console.log(
|
|
14330
|
+
if (n > 20) console.log(import_chalk63.default.dim(` \u2026 and ${n - 20} more`));
|
|
14088
14331
|
}
|
|
14089
14332
|
}
|
|
14090
|
-
for (const e of ghosts?.errors ?? []) console.log(
|
|
14333
|
+
for (const e of ghosts?.errors ?? []) console.log(import_chalk63.default.red(` error: ${e}`));
|
|
14091
14334
|
console.log();
|
|
14092
14335
|
}
|
|
14093
14336
|
async function runOp(sub, opts = {}, view) {
|
|
14094
14337
|
if (!loadCredentials()) {
|
|
14095
|
-
console.log(
|
|
14338
|
+
console.log(import_chalk63.default.dim("Not logged in. Run `apiblaze login`."));
|
|
14096
14339
|
return;
|
|
14097
14340
|
}
|
|
14098
14341
|
if (!isOperatorLogin()) {
|
|
14099
|
-
console.log(
|
|
14342
|
+
console.log(import_chalk63.default.dim("`apiblaze op` is only available to platform operators."));
|
|
14100
14343
|
return;
|
|
14101
14344
|
}
|
|
14102
14345
|
switch (sub) {
|
|
@@ -14152,17 +14395,17 @@ async function runOp(sub, opts = {}, view) {
|
|
|
14152
14395
|
const nSide = (st.soft_deleted_stores ?? 0) + (st.orphan_models ?? 0) + (st.orphan_changelog ?? 0);
|
|
14153
14396
|
printResidue(report, false);
|
|
14154
14397
|
if (nUp + nFga + nGhost + nSide === 0) {
|
|
14155
|
-
console.log(
|
|
14398
|
+
console.log(import_chalk63.default.green("Nothing to sweep."));
|
|
14156
14399
|
return;
|
|
14157
14400
|
}
|
|
14158
14401
|
if (!opts.yes) {
|
|
14159
14402
|
const readline3 = await import("readline/promises");
|
|
14160
14403
|
const rl = readline3.createInterface({ input: process.stdin, output: process.stdout });
|
|
14161
14404
|
const answer = await rl.question(
|
|
14162
|
-
|
|
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: `)
|
|
14163
14406
|
);
|
|
14164
14407
|
rl.close();
|
|
14165
|
-
if (answer.trim() !== "sweep") return void console.log(
|
|
14408
|
+
if (answer.trim() !== "sweep") return void console.log(import_chalk63.default.dim("Aborted."));
|
|
14166
14409
|
}
|
|
14167
14410
|
const result = await opCall({ method: "POST", path: "/operator/external-residue/sweep", summary: "external residue sweep" });
|
|
14168
14411
|
if (opts.json) return void console.log(JSON.stringify(result, null, 2));
|
|
@@ -14172,25 +14415,25 @@ async function runOp(sub, opts = {}, view) {
|
|
|
14172
14415
|
case "mark": {
|
|
14173
14416
|
const label3 = (view ?? "").trim();
|
|
14174
14417
|
if (!label3) {
|
|
14175
|
-
console.log(
|
|
14418
|
+
console.log(import_chalk63.default.red("Give the change a name:") + import_chalk63.default.cyan(' apiblaze op mark "cached tenant count"'));
|
|
14176
14419
|
return;
|
|
14177
14420
|
}
|
|
14178
14421
|
const res = await opCall({ method: "POST", path: "/operator/latency/mark", body: { label: label3 }, summary: "record change marker" });
|
|
14179
14422
|
const ts = new Date(res?.marker?.ts ?? Date.now()).toISOString();
|
|
14180
|
-
console.log(
|
|
14181
|
-
Marked: `) +
|
|
14182
|
-
console.log(
|
|
14183
|
-
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");
|
|
14184
14427
|
return;
|
|
14185
14428
|
}
|
|
14186
14429
|
case "credits": {
|
|
14187
14430
|
const data = await opCall({ method: "GET", path: "/operator/credits", summary: "list credit wallets" });
|
|
14188
14431
|
if (opts.json) return void console.log(JSON.stringify(data, null, 2));
|
|
14189
14432
|
const accounts = data?.accounts ?? [];
|
|
14190
|
-
if (accounts.length === 0) return void console.log(
|
|
14433
|
+
if (accounts.length === 0) return void console.log(import_chalk63.default.dim("No credit wallets."));
|
|
14191
14434
|
for (const a of accounts) {
|
|
14192
14435
|
const bal = typeof a.balance_cents === "number" ? `$${(a.balance_cents / 100).toFixed(2)}` : "?";
|
|
14193
|
-
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") : ""}`);
|
|
14194
14437
|
}
|
|
14195
14438
|
return;
|
|
14196
14439
|
}
|
|
@@ -14201,7 +14444,7 @@ async function runOp(sub, opts = {}, view) {
|
|
|
14201
14444
|
case "billing": {
|
|
14202
14445
|
const which = (view ?? "").trim().toLowerCase();
|
|
14203
14446
|
if (which && which !== "doors" && which !== "meter") {
|
|
14204
|
-
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.`));
|
|
14205
14448
|
}
|
|
14206
14449
|
const checks = [];
|
|
14207
14450
|
let doorsData = null;
|
|
@@ -14270,29 +14513,29 @@ async function runOp(sub, opts = {}, view) {
|
|
|
14270
14513
|
const data = await opCall({ method: "GET", path: `/operator/latency/grades${q}`, summary: "latency grades" });
|
|
14271
14514
|
if (opts.json) return void console.log(JSON.stringify(data, null, 2));
|
|
14272
14515
|
const t = data.thresholds_ms;
|
|
14273
|
-
console.log(
|
|
14274
|
-
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+
|
|
14275
14518
|
`));
|
|
14276
|
-
console.log(
|
|
14519
|
+
console.log(import_chalk63.default.dim(" date reqs excellent okay bad terrible"));
|
|
14277
14520
|
for (const d of data.days ?? []) {
|
|
14278
14521
|
const p = d.pct;
|
|
14279
|
-
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)}%`);
|
|
14280
14523
|
console.log(
|
|
14281
|
-
` ${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)}`
|
|
14282
14525
|
);
|
|
14283
14526
|
}
|
|
14284
14527
|
const cul = data.culprits ?? [];
|
|
14285
14528
|
if (cul.length) {
|
|
14286
|
-
console.log(
|
|
14287
|
-
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"));
|
|
14288
14531
|
for (const r of cul.slice(0, 12)) {
|
|
14289
14532
|
if (!r.bad && !r.terrible) continue;
|
|
14290
14533
|
console.log(
|
|
14291
|
-
` ${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)}`
|
|
14292
14535
|
);
|
|
14293
14536
|
}
|
|
14294
14537
|
}
|
|
14295
|
-
if (data.caveat) console.log(
|
|
14538
|
+
if (data.caveat) console.log(import_chalk63.default.dim(`
|
|
14296
14539
|
\u26A0 ${data.caveat}
|
|
14297
14540
|
`));
|
|
14298
14541
|
return;
|
|
@@ -14301,24 +14544,24 @@ async function runOp(sub, opts = {}, view) {
|
|
|
14301
14544
|
const data = await opCall({ method: "GET", path: `/operator/latency/compare${q}`, summary: "latency before/after" });
|
|
14302
14545
|
if (opts.json) return void console.log(JSON.stringify(data, null, 2));
|
|
14303
14546
|
const b = data.before, a = data.after, d = data.delta;
|
|
14304
|
-
console.log(
|
|
14305
|
-
Before vs after: `) +
|
|
14306
|
-
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
|
|
14307
14550
|
`));
|
|
14308
14551
|
const row = (name, before, after, delta) => {
|
|
14309
14552
|
const arrow = delta === 0 ? "=" : delta < 0 ? "\u2193" : "\u2191";
|
|
14310
14553
|
const txt = `${String(before).padStart(6)}ms \u2192${String(after).padStart(7)}ms ${arrow}${Math.abs(delta)}ms`;
|
|
14311
|
-
console.log(` ${name.padEnd(22)}${data.trustworthy ? delta <= 0 ?
|
|
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)}`);
|
|
14312
14555
|
};
|
|
14313
|
-
console.log(
|
|
14556
|
+
console.log(import_chalk63.default.dim(" metric before after change"));
|
|
14314
14557
|
row("total p50", b.total_p50, a.total_p50, d.total_p50);
|
|
14315
14558
|
row("total p95", b.total_p95, a.total_p95, d.total_p95);
|
|
14316
14559
|
row("apiblaze overhead p50", b.gw_p50, a.gw_p50, d.gw_p50);
|
|
14317
14560
|
row("apiblaze overhead p95", b.gw_p95, a.gw_p95, d.gw_p95);
|
|
14318
|
-
console.log(
|
|
14561
|
+
console.log(import_chalk63.default.dim(`
|
|
14319
14562
|
requests: ${b.requests} before \xB7 ${a.requests} after`));
|
|
14320
14563
|
for (const w of data.warnings ?? []) {
|
|
14321
|
-
console.log((data.trustworthy ?
|
|
14564
|
+
console.log((data.trustworthy ? import_chalk63.default.dim : import_chalk63.default.yellow)(` ${data.trustworthy ? "\xB7" : "\u26A0"} ${w}`));
|
|
14322
14565
|
}
|
|
14323
14566
|
console.log("");
|
|
14324
14567
|
return;
|
|
@@ -14327,19 +14570,19 @@ Before vs after: `) + import_chalk62.default.cyan(data.marker.label));
|
|
|
14327
14570
|
const data = await opCall({ method: "GET", path: `/operator/latency/slow${q}`, summary: "slowest requests" });
|
|
14328
14571
|
if (opts.json) return void console.log(JSON.stringify(data, null, 2));
|
|
14329
14572
|
const rows2 = data?.rows ?? [];
|
|
14330
|
-
if (!rows2.length) return void console.log(
|
|
14331
|
-
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(`
|
|
14332
14575
|
Slowest requests \u2014 last ${data.window_hours}h, over ${data.min_ms}ms
|
|
14333
14576
|
`));
|
|
14334
|
-
console.log(
|
|
14577
|
+
console.log(import_chalk63.default.dim(" total ours theirs blame request id"));
|
|
14335
14578
|
for (const r of rows2.slice(0, 30)) {
|
|
14336
14579
|
console.log(
|
|
14337
|
-
` ${String(Math.round(r.duration_ms)).padStart(6)} ${String(Math.round(r.gateway_ms)).padStart(5)} ${String(Math.round(r.upstream_ttfb_ms)).padStart(6)} ${
|
|
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 || "")}`
|
|
14338
14581
|
);
|
|
14339
14582
|
}
|
|
14340
|
-
console.log(
|
|
14583
|
+
console.log(import_chalk63.default.dim(`
|
|
14341
14584
|
The last column is the request id (Cloudflare calls it a "cf-ray"). Look one up with`));
|
|
14342
|
-
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.
|
|
14343
14586
|
`));
|
|
14344
14587
|
return;
|
|
14345
14588
|
}
|
|
@@ -14347,18 +14590,18 @@ Slowest requests \u2014 last ${data.window_hours}h, over ${data.min_ms}ms
|
|
|
14347
14590
|
const data = await opCall({ method: "GET", path: `/operator/latency/llm${q}`, summary: "llm latency" });
|
|
14348
14591
|
if (opts.json) return void console.log(JSON.stringify(data, null, 2));
|
|
14349
14592
|
const rows2 = data?.rows ?? [];
|
|
14350
|
-
if (!rows2.length) return void console.log(
|
|
14351
|
-
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(`
|
|
14352
14595
|
LLM timing \u2014 last ${data.window_hours}h
|
|
14353
14596
|
`));
|
|
14354
|
-
console.log(
|
|
14597
|
+
console.log(import_chalk63.default.dim(" turns turn p95 ttfc p95 gen p95 reserve p95 in/out tokens p95 model"));
|
|
14355
14598
|
for (const r of rows2) {
|
|
14356
14599
|
const n = r.turns ?? r.requests ?? 0;
|
|
14357
14600
|
console.log(
|
|
14358
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 || "-"}`
|
|
14359
14602
|
);
|
|
14360
14603
|
}
|
|
14361
|
-
console.log(
|
|
14604
|
+
console.log(import_chalk63.default.dim(`
|
|
14362
14605
|
${data.note}
|
|
14363
14606
|
`));
|
|
14364
14607
|
return;
|
|
@@ -14369,26 +14612,26 @@ LLM timing \u2014 last ${data.window_hours}h
|
|
|
14369
14612
|
]);
|
|
14370
14613
|
if (opts.json) return void console.log(JSON.stringify({ blame, summary }, null, 2));
|
|
14371
14614
|
const ov = summary?.apiblaze_overhead_ms ?? {};
|
|
14372
|
-
console.log(
|
|
14615
|
+
console.log(import_chalk63.default.bold(`
|
|
14373
14616
|
Latency \u2014 last ${summary?.window_hours ?? "?"}h, ${Number(summary?.requests ?? 0).toLocaleString()} requests
|
|
14374
14617
|
`));
|
|
14375
|
-
console.log(` ${
|
|
14376
|
-
console.log(` ${
|
|
14377
|
-
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)
|
|
14378
14621
|
`));
|
|
14379
14622
|
const rows = blame?.blame ?? [];
|
|
14380
|
-
if (!rows.length) return void console.log(
|
|
14381
|
-
console.log(
|
|
14382
|
-
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"));
|
|
14383
14626
|
for (const r of rows.slice(0, 15)) {
|
|
14384
14627
|
const share = `${(r.share * 100).toFixed(1)}%`;
|
|
14385
|
-
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)}`);
|
|
14386
14629
|
}
|
|
14387
14630
|
console.log("");
|
|
14388
14631
|
return;
|
|
14389
14632
|
}
|
|
14390
14633
|
default:
|
|
14391
|
-
console.log(
|
|
14634
|
+
console.log(import_chalk63.default.red(`Unknown op subcommand '${sub}'. Run \`apiblaze op\` for the menu.`));
|
|
14392
14635
|
}
|
|
14393
14636
|
}
|
|
14394
14637
|
|
|
@@ -14439,8 +14682,8 @@ agent.command("openapi").description("Chat to build your API spec from real traf
|
|
|
14439
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)));
|
|
14440
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) => {
|
|
14441
14684
|
const looksLikeSource = !!project && (/^https?:\/\//i.test(project) || /\.(ya?ml|json)$/i.test(project) || import_fs5.default.existsSync(project));
|
|
14442
|
-
const
|
|
14443
|
-
return runApichat({ ...opts, target, project: looksLikeSource ? void 0 : project, openapispec: opts.openapispec ?? opts.openapi });
|
|
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 });
|
|
14444
14687
|
}));
|
|
14445
14688
|
var llm = program.command("llm").description("Manage a local LLM provider key for chat (optional \u2014 lifts model quality, bills your key)");
|
|
14446
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)));
|
|
@@ -14458,7 +14701,7 @@ program.command("dev").description(`Put the code running on this machine behind
|
|
|
14458
14701
|
try {
|
|
14459
14702
|
const resolved = parseInt(port ?? opts.port, 10);
|
|
14460
14703
|
if (Number.isNaN(resolved)) {
|
|
14461
|
-
console.error(
|
|
14704
|
+
console.error(import_chalk64.default.red(`Invalid port: ${port ?? opts.port}`));
|
|
14462
14705
|
process.exit(1);
|
|
14463
14706
|
}
|
|
14464
14707
|
await runDev({ ...opts, port: resolved });
|
|
@@ -14521,7 +14764,14 @@ var logs = program.command("logs").description("API Producer view \u2014 stream
|
|
|
14521
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())));
|
|
14522
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)));
|
|
14523
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)));
|
|
14524
|
-
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)));
|
|
14525
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)));
|
|
14526
14776
|
var domain = program.command("domain").description("Use your own domain for your API, MCP server, developer portal or login pages (billed monthly)");
|
|
14527
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", `
|
|
@@ -14562,11 +14812,11 @@ admins.command("remove").description("Remove a tenant admin").argument("<email>"
|
|
|
14562
14812
|
program.addCommand(admins);
|
|
14563
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)));
|
|
14564
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)));
|
|
14565
|
-
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)));
|
|
14566
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 })));
|
|
14567
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 })));
|
|
14568
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 })));
|
|
14569
|
-
program.addCommand(apikeys
|
|
14819
|
+
program.addCommand(apikeys);
|
|
14570
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", () => `
|
|
14571
14821
|
${renderOpCommands()}
|
|
14572
14822
|
`);
|
|
@@ -14577,10 +14827,10 @@ spec.command("set").description("Replace the stored OpenAPI spec from a local fi
|
|
|
14577
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)));
|
|
14578
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)));
|
|
14579
14829
|
var HELP_GROUPS = [
|
|
14580
|
-
{ title: "Chat", commands: ["docs", "apichat", "agent"] },
|
|
14581
|
-
{ title: "Setup", commands: ["dev", "create", "integration", "skill", "login", "init", "sidecar", "claim", "team", "whoami", "logout"] },
|
|
14830
|
+
{ title: "Chat", commands: ["docs", "apichat", "agent", "llm"] },
|
|
14831
|
+
{ title: "Setup", commands: ["dev", "create", "integration", "skill", "login", "init", "sidecar", "claim", "team", "whoami", "logout", "flush"] },
|
|
14582
14832
|
{ title: "Recipes \u2014 install a working setup, or publish yours", commands: ["search", "show", "install", "publish", "withdraw"] },
|
|
14583
|
-
{ title: "Control plane commands", commands: ["config", "projects", "logs", "tenant", "group", "admins", "iam", "identified", "preapprove", "rule", "domain", "delete", "target", "throttle", "rename", "spec", "export"] },
|
|
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"] },
|
|
14584
14834
|
{ title: "Data plane commands", commands: [
|
|
14585
14835
|
{ parent: "consumer", sub: "login" },
|
|
14586
14836
|
{ parent: "consumer", sub: "apikeys" },
|
|
@@ -14606,7 +14856,7 @@ function groupedCommandHelp() {
|
|
|
14606
14856
|
const sub = byName.get(e.parent)?.commands.find((s) => s.name() === e.sub);
|
|
14607
14857
|
return sub ? ` ${helpLabel(e).padEnd(width)}${sub.description()}` : "";
|
|
14608
14858
|
}).filter(Boolean).join("\n");
|
|
14609
|
-
return `${
|
|
14859
|
+
return `${import_chalk64.default.bold(g.title)}
|
|
14610
14860
|
${rows}`;
|
|
14611
14861
|
}).join("\n\n");
|
|
14612
14862
|
}
|
|
@@ -14617,7 +14867,7 @@ program.configureHelp({
|
|
|
14617
14867
|
}
|
|
14618
14868
|
});
|
|
14619
14869
|
program.addHelpText("after", () => `
|
|
14620
|
-
${
|
|
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.`)}
|
|
14621
14871
|
|
|
14622
14872
|
${groupedCommandHelp()}
|
|
14623
14873
|
|
|
@@ -14653,14 +14903,14 @@ async function recoverStaleTeam() {
|
|
|
14653
14903
|
const { resolveLinkedTeam: resolveLinkedTeam2 } = await Promise.resolve().then(() => (init_team(), team_exports));
|
|
14654
14904
|
const linked = await resolveLinkedTeam2({ preferredId: creds.teamId, interactive: !!process.stdin.isTTY });
|
|
14655
14905
|
if (!linked) {
|
|
14656
|
-
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."));
|
|
14657
14907
|
return;
|
|
14658
14908
|
}
|
|
14659
14909
|
if (linked.teamId === creds.teamId) return;
|
|
14660
14910
|
const next = { ...creds, teamId: linked.teamId, teamName: linked.teamName };
|
|
14661
14911
|
delete next.activeTenant;
|
|
14662
14912
|
saveCredentials(next);
|
|
14663
|
-
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.`));
|
|
14664
14914
|
} catch {
|
|
14665
14915
|
}
|
|
14666
14916
|
}
|
|
@@ -14668,16 +14918,33 @@ async function printError(err) {
|
|
|
14668
14918
|
if (err instanceof ApiError) {
|
|
14669
14919
|
const data = err.body;
|
|
14670
14920
|
const extra = [data?.body?.reason, data?.body?.details, data?.details, data?.body?.error].find((x) => typeof x === "string" && x && x !== err.message);
|
|
14671
|
-
console.error(
|
|
14921
|
+
console.error(import_chalk64.default.red(`
|
|
14672
14922
|
API error (${err.status}): ${err.message}${extra ? ` \u2014 ${extra}` : ""}`));
|
|
14673
14923
|
if (err.status === 403 || err.status === 404) {
|
|
14674
14924
|
await recoverStaleTeam();
|
|
14675
14925
|
}
|
|
14676
14926
|
} else if (err instanceof Error) {
|
|
14677
|
-
console.error(
|
|
14927
|
+
console.error(import_chalk64.default.red(`
|
|
14678
14928
|
Error: ${err.message}`));
|
|
14679
14929
|
} else {
|
|
14680
|
-
console.error(
|
|
14930
|
+
console.error(import_chalk64.default.red("\nUnknown error"));
|
|
14681
14931
|
}
|
|
14682
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);
|
|
14683
14950
|
program.parse(process.argv);
|