apiblaze 0.11.0 → 0.12.0
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 +1030 -317
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -300,12 +300,199 @@ var init_anon_cred = __esm({
|
|
|
300
300
|
}
|
|
301
301
|
});
|
|
302
302
|
|
|
303
|
+
// src/lib/trace.ts
|
|
304
|
+
function setVerbose(v) {
|
|
305
|
+
verbose = v;
|
|
306
|
+
}
|
|
307
|
+
function recordCall(e) {
|
|
308
|
+
if (verbose) entries.push(e);
|
|
309
|
+
}
|
|
310
|
+
function maskBody(body) {
|
|
311
|
+
if (body === void 0) return void 0;
|
|
312
|
+
return JSON.stringify(body, (k, v) => SECRET_KEY.test(k) && typeof v === "string" ? "***" : v);
|
|
313
|
+
}
|
|
314
|
+
function renderTrace() {
|
|
315
|
+
if (!verbose || entries.length === 0) return;
|
|
316
|
+
console.log(import_chalk15.default.dim("\n" + "\u2500".repeat(64)));
|
|
317
|
+
console.log(import_chalk15.default.bold(`--verbose: ${entries.length} API call${entries.length === 1 ? "" : "s"} this command made`));
|
|
318
|
+
console.log(
|
|
319
|
+
import_chalk15.default.dim("The same thing on the official API \u2014 copy/paste with your control-plane key\n(get one from the Developers section of dashboard.apiblaze.com, then\n`export APIBLAZE_CONTROLPLANE_APIKEY=sk_...`).\nFull API reference: https://api.apiblaze.com/openapi.json\n")
|
|
320
|
+
);
|
|
321
|
+
entries.forEach((e, i) => {
|
|
322
|
+
const n = entries.length > 1 ? import_chalk15.default.bold(`${i + 1}. `) : "";
|
|
323
|
+
if (e.summary) console.log(`${n}${import_chalk15.default.cyan(e.summary)}${e.status ? import_chalk15.default.dim(` (HTTP ${e.status})`) : ""}`);
|
|
324
|
+
const url = `https://api.apiblaze.com/${CONTROL_API_VERSION}/prod${e.path}`;
|
|
325
|
+
const masked = maskBody(e.body);
|
|
326
|
+
const hasBody = e.method !== "GET" && masked !== void 0;
|
|
327
|
+
console.log(import_chalk15.default.green(` curl -sS -X ${e.method} ${url}` + (hasBody ? " \\" : "")));
|
|
328
|
+
console.log(import_chalk15.default.green(' -H "X-API-Key: $APIBLAZE_CONTROLPLANE_APIKEY"' + (hasBody ? " \\" : "")));
|
|
329
|
+
if (hasBody) {
|
|
330
|
+
console.log(import_chalk15.default.green(" -H 'Content-Type: application/json' \\"));
|
|
331
|
+
console.log(import_chalk15.default.green(` -d '${masked}'`));
|
|
332
|
+
}
|
|
333
|
+
if (i < entries.length - 1) console.log();
|
|
334
|
+
});
|
|
335
|
+
entries.length = 0;
|
|
336
|
+
}
|
|
337
|
+
var import_chalk15, CONTROL_API_VERSION, verbose, entries, SECRET_KEY;
|
|
338
|
+
var init_trace = __esm({
|
|
339
|
+
"src/lib/trace.ts"() {
|
|
340
|
+
"use strict";
|
|
341
|
+
import_chalk15 = __toESM(require("chalk"));
|
|
342
|
+
CONTROL_API_VERSION = "1.0.0";
|
|
343
|
+
verbose = false;
|
|
344
|
+
entries = [];
|
|
345
|
+
SECRET_KEY = /secret|token|password|api[_-]?key|client_secret/i;
|
|
346
|
+
}
|
|
347
|
+
});
|
|
348
|
+
|
|
349
|
+
// src/lib/admin.ts
|
|
350
|
+
async function admin(call) {
|
|
351
|
+
const token = getAccessToken();
|
|
352
|
+
const res = await fetch(`${DASHBOARD_BASE3}/api/cli/admin`, {
|
|
353
|
+
method: "POST",
|
|
354
|
+
headers: { "Content-Type": "application/json", Authorization: `Bearer ${token}` },
|
|
355
|
+
body: JSON.stringify({ path: call.path, method: call.method, body: call.body })
|
|
356
|
+
});
|
|
357
|
+
let data = null;
|
|
358
|
+
try {
|
|
359
|
+
data = await res.json();
|
|
360
|
+
} catch {
|
|
361
|
+
}
|
|
362
|
+
recordCall({ method: call.method, path: call.path, body: call.body, status: res.status, summary: call.summary });
|
|
363
|
+
maybePrintBilling(data);
|
|
364
|
+
if (!res.ok) {
|
|
365
|
+
const msg = data?.details ?? data?.error ?? res.statusText;
|
|
366
|
+
throw new ApiError(res.status, typeof msg === "string" ? msg : JSON.stringify(msg), data);
|
|
367
|
+
}
|
|
368
|
+
return data;
|
|
369
|
+
}
|
|
370
|
+
function maybePrintBilling(data) {
|
|
371
|
+
const b = data?.billing;
|
|
372
|
+
if (b && typeof b.charged_cents === "number") {
|
|
373
|
+
const usd = (b.charged_cents / 100).toFixed(2);
|
|
374
|
+
const rem = typeof b.credits_remaining === "number" ? ` \xB7 $${(b.credits_remaining / 100).toFixed(2)} credit left` : "";
|
|
375
|
+
console.log(import_chalk16.default.magenta(` \u{1F4B3} Charged $${usd}${rem}`));
|
|
376
|
+
}
|
|
377
|
+
}
|
|
378
|
+
var import_chalk16, DASHBOARD_BASE3;
|
|
379
|
+
var init_admin = __esm({
|
|
380
|
+
"src/lib/admin.ts"() {
|
|
381
|
+
"use strict";
|
|
382
|
+
import_chalk16 = __toESM(require("chalk"));
|
|
383
|
+
init_auth();
|
|
384
|
+
init_trace();
|
|
385
|
+
init_types();
|
|
386
|
+
DASHBOARD_BASE3 = process.env.APIBLAZE_DASHBOARD_BASE || "https://dashboard.apiblaze.com";
|
|
387
|
+
}
|
|
388
|
+
});
|
|
389
|
+
|
|
390
|
+
// src/lib/tenant-pick.ts
|
|
391
|
+
var tenant_pick_exports = {};
|
|
392
|
+
__export(tenant_pick_exports, {
|
|
393
|
+
pickTenant: () => pickTenant
|
|
394
|
+
});
|
|
395
|
+
async function fetchPage(teamId, q) {
|
|
396
|
+
const out = await admin({
|
|
397
|
+
method: "GET",
|
|
398
|
+
path: `/teams/${encodeURIComponent(teamId)}/tenants?detail=1&limit=${PAGE}${q ? `&q=${encodeURIComponent(q)}` : ""}`,
|
|
399
|
+
summary: q ? `Search tenants matching "${q}"` : "List tenants (first page)"
|
|
400
|
+
});
|
|
401
|
+
const rows = (out?.tenants ?? []).map(
|
|
402
|
+
(t) => typeof t === "string" ? { tenant_name: t } : t
|
|
403
|
+
);
|
|
404
|
+
return { rows, total: out?.total ?? rows.length, defaultTenant: out?.default_tenant ?? null };
|
|
405
|
+
}
|
|
406
|
+
function label(t, defaultTenant, active) {
|
|
407
|
+
const tags = [
|
|
408
|
+
t.tenant_name === active ? import_chalk22.default.cyan("active scope") : "",
|
|
409
|
+
t.tenant_name === defaultTenant ? import_chalk22.default.dim("team default") : ""
|
|
410
|
+
].filter(Boolean).join(", ");
|
|
411
|
+
const disp = t.display_name && t.display_name !== t.tenant_name ? import_chalk22.default.dim(` ${t.display_name}`) : "";
|
|
412
|
+
return `${t.tenant_name}${disp}${tags ? ` (${tags})` : ""}`;
|
|
413
|
+
}
|
|
414
|
+
async function pickTenant(teamId, opts = {}) {
|
|
415
|
+
const { default: inquirer2 } = await import("inquirer");
|
|
416
|
+
const active = loadCredentials()?.activeTenant;
|
|
417
|
+
let q = opts.initialQuery ?? "";
|
|
418
|
+
for (; ; ) {
|
|
419
|
+
const spinner = (0, import_ora8.default)(q ? `Searching tenants for "${q}"...` : "Loading tenants...").start();
|
|
420
|
+
const page = await fetchPage(teamId, q).finally(() => spinner.stop());
|
|
421
|
+
if (!page.total && !q) {
|
|
422
|
+
if (opts.allowCreate) {
|
|
423
|
+
const { make } = await inquirer2.prompt([{ type: "confirm", name: "make", message: "No tenants yet \u2014 create one?", default: true }]);
|
|
424
|
+
if (make) return await createTenantInline(teamId);
|
|
425
|
+
}
|
|
426
|
+
console.error(import_chalk22.default.red("This team has no tenants. Create one with `apiblaze tenant create`."));
|
|
427
|
+
return null;
|
|
428
|
+
}
|
|
429
|
+
const truncated = page.total > page.rows.length;
|
|
430
|
+
const choices = page.rows.map((t) => ({
|
|
431
|
+
name: label(t, page.defaultTenant, active),
|
|
432
|
+
value: t.tenant_name
|
|
433
|
+
}));
|
|
434
|
+
if (truncated || q) {
|
|
435
|
+
choices.push(new inquirer2.Separator(import_chalk22.default.dim(
|
|
436
|
+
truncated ? `showing ${page.rows.length} of ${page.total}${q ? ` matching "${q}"` : ""} \u2014 search to narrow` : `matches for "${q}"`
|
|
437
|
+
)));
|
|
438
|
+
choices.push({ name: `\u{1F50D} Search${q ? " again" : ""}\u2026`, value: "\0search" });
|
|
439
|
+
}
|
|
440
|
+
if (q) choices.push({ name: "\u21BA Show all (clear search)", value: "\0clear" });
|
|
441
|
+
if (opts.allowCreate) choices.push({ name: "\uFF0B Create a new tenant\u2026", value: "\0create" });
|
|
442
|
+
if (opts.allowBack) choices.push({ name: "\u2190 Back", value: "\0back" });
|
|
443
|
+
const { picked } = await inquirer2.prompt([{
|
|
444
|
+
type: "list",
|
|
445
|
+
name: "picked",
|
|
446
|
+
pageSize: PAGE + 5,
|
|
447
|
+
message: opts.message ?? "Which tenant?",
|
|
448
|
+
default: active && page.rows.some((t) => t.tenant_name === active) ? active : void 0,
|
|
449
|
+
choices
|
|
450
|
+
}]);
|
|
451
|
+
if (picked === "\0back") return null;
|
|
452
|
+
if (picked === "\0clear") {
|
|
453
|
+
q = "";
|
|
454
|
+
continue;
|
|
455
|
+
}
|
|
456
|
+
if (picked === "\0create") return await createTenantInline(teamId);
|
|
457
|
+
if (picked === "\0search") {
|
|
458
|
+
const { nq } = await inquirer2.prompt([{ type: "input", name: "nq", message: "Search (name or display name):", default: q }]);
|
|
459
|
+
q = String(nq ?? "").trim();
|
|
460
|
+
continue;
|
|
461
|
+
}
|
|
462
|
+
return picked;
|
|
463
|
+
}
|
|
464
|
+
}
|
|
465
|
+
async function createTenantInline(teamId) {
|
|
466
|
+
const { default: inquirer2 } = await import("inquirer");
|
|
467
|
+
const { display } = await inquirer2.prompt([{ type: "input", name: "display", message: "Display name for the new tenant:", validate: (s) => !!s.trim() || "required" }]);
|
|
468
|
+
const out = await admin({
|
|
469
|
+
method: "POST",
|
|
470
|
+
path: `/teams/${encodeURIComponent(teamId)}/tenants`,
|
|
471
|
+
body: { display_name: display.trim() },
|
|
472
|
+
summary: `Create tenant "${display.trim()}"`
|
|
473
|
+
});
|
|
474
|
+
const slug = out?.tenant_name ?? out?.tenant?.tenant_name;
|
|
475
|
+
console.log(import_chalk22.default.green(` Tenant ${import_chalk22.default.bold(slug)} created.`));
|
|
476
|
+
return slug;
|
|
477
|
+
}
|
|
478
|
+
var import_chalk22, import_ora8, PAGE;
|
|
479
|
+
var init_tenant_pick = __esm({
|
|
480
|
+
"src/lib/tenant-pick.ts"() {
|
|
481
|
+
"use strict";
|
|
482
|
+
import_chalk22 = __toESM(require("chalk"));
|
|
483
|
+
import_ora8 = __toESM(require("ora"));
|
|
484
|
+
init_admin();
|
|
485
|
+
init_auth();
|
|
486
|
+
PAGE = 15;
|
|
487
|
+
}
|
|
488
|
+
});
|
|
489
|
+
|
|
303
490
|
// src/index.ts
|
|
304
491
|
var import_commander = require("commander");
|
|
305
|
-
var
|
|
492
|
+
var import_chalk34 = __toESM(require("chalk"));
|
|
306
493
|
|
|
307
494
|
// package.json
|
|
308
|
-
var version = "0.
|
|
495
|
+
var version = "0.12.0";
|
|
309
496
|
|
|
310
497
|
// src/index.ts
|
|
311
498
|
init_types();
|
|
@@ -1009,9 +1196,9 @@ function printTunnelEndpoints(restore, targets) {
|
|
|
1009
1196
|
if (restore.length === 0) return;
|
|
1010
1197
|
console.log(import_chalk4.default.bold("\nYour proxy is live at:"));
|
|
1011
1198
|
for (const r of restore) {
|
|
1012
|
-
const
|
|
1199
|
+
const label2 = targets.find((t) => t.projectId === r.projectId)?.projectName ?? r.projectId;
|
|
1013
1200
|
console.log(`
|
|
1014
|
-
${import_chalk4.default.bold(
|
|
1201
|
+
${import_chalk4.default.bold(label2)}`);
|
|
1015
1202
|
const internalEnvs = Object.keys(r.environments ?? {}).filter((e) => isInternalTarget(r.environments[e]?.target));
|
|
1016
1203
|
const envs = internalEnvs.includes("dev") ? ["dev"] : internalEnvs.length ? internalEnvs : ["dev"];
|
|
1017
1204
|
for (const env of envs) {
|
|
@@ -1835,6 +2022,7 @@ async function runWhoami(opts = {}) {
|
|
|
1835
2022
|
console.log(` ${import_chalk9.default.cyan("Signed in as")} ${import_chalk9.default.bold(who)}`);
|
|
1836
2023
|
if (creds.email && creds.email !== who) console.log(` Email: ${creds.email}`);
|
|
1837
2024
|
if (creds.teamName || creds.teamId) console.log(` Team: ${import_chalk9.default.bold(creds.teamName ?? creds.teamId)}`);
|
|
2025
|
+
if (creds.activeTenant) console.log(` Tenant scope: ${import_chalk9.default.bold(creds.activeTenant)} ${import_chalk9.default.dim("(clear: `apiblaze tenant use --clear`)")}`);
|
|
1838
2026
|
if (Date.now() >= creds.expiresAt) console.log(import_chalk9.default.yellow(" \u26A0 Session expired \u2014 run `apiblaze login`."));
|
|
1839
2027
|
}
|
|
1840
2028
|
console.log(import_chalk9.default.bold("\nAPI Consumer"));
|
|
@@ -2181,82 +2369,7 @@ async function runMcp(projectArg, apiVersionArg, opts) {
|
|
|
2181
2369
|
// src/commands/delete.ts
|
|
2182
2370
|
var import_chalk18 = __toESM(require("chalk"));
|
|
2183
2371
|
var import_ora5 = __toESM(require("ora"));
|
|
2184
|
-
|
|
2185
|
-
// src/lib/admin.ts
|
|
2186
|
-
var import_chalk16 = __toESM(require("chalk"));
|
|
2187
|
-
init_auth();
|
|
2188
|
-
|
|
2189
|
-
// src/lib/trace.ts
|
|
2190
|
-
var import_chalk15 = __toESM(require("chalk"));
|
|
2191
|
-
var CONTROL_API_VERSION = "1.0.0";
|
|
2192
|
-
var verbose = false;
|
|
2193
|
-
var entries = [];
|
|
2194
|
-
function setVerbose(v) {
|
|
2195
|
-
verbose = v;
|
|
2196
|
-
}
|
|
2197
|
-
function recordCall(e) {
|
|
2198
|
-
if (verbose) entries.push(e);
|
|
2199
|
-
}
|
|
2200
|
-
var SECRET_KEY = /secret|token|password|api[_-]?key|client_secret/i;
|
|
2201
|
-
function maskBody(body) {
|
|
2202
|
-
if (body === void 0) return void 0;
|
|
2203
|
-
return JSON.stringify(body, (k, v) => SECRET_KEY.test(k) && typeof v === "string" ? "***" : v);
|
|
2204
|
-
}
|
|
2205
|
-
function renderTrace() {
|
|
2206
|
-
if (!verbose || entries.length === 0) return;
|
|
2207
|
-
console.log(import_chalk15.default.dim("\n" + "\u2500".repeat(64)));
|
|
2208
|
-
console.log(import_chalk15.default.bold(`--verbose: ${entries.length} API call${entries.length === 1 ? "" : "s"} this command made`));
|
|
2209
|
-
console.log(
|
|
2210
|
-
import_chalk15.default.dim("The same thing on the official API \u2014 copy/paste with your control-plane key\n(get one from the Developers section of dashboard.apiblaze.com, then\n`export APIBLAZE_CONTROLPLANE_APIKEY=sk_...`).\nFull API reference: https://api.apiblaze.com/openapi.json\n")
|
|
2211
|
-
);
|
|
2212
|
-
entries.forEach((e, i) => {
|
|
2213
|
-
const n = entries.length > 1 ? import_chalk15.default.bold(`${i + 1}. `) : "";
|
|
2214
|
-
if (e.summary) console.log(`${n}${import_chalk15.default.cyan(e.summary)}${e.status ? import_chalk15.default.dim(` (HTTP ${e.status})`) : ""}`);
|
|
2215
|
-
const url = `https://api.apiblaze.com/${CONTROL_API_VERSION}/prod${e.path}`;
|
|
2216
|
-
const masked = maskBody(e.body);
|
|
2217
|
-
const hasBody = e.method !== "GET" && masked !== void 0;
|
|
2218
|
-
console.log(import_chalk15.default.green(` curl -sS -X ${e.method} ${url}` + (hasBody ? " \\" : "")));
|
|
2219
|
-
console.log(import_chalk15.default.green(' -H "X-API-Key: $APIBLAZE_CONTROLPLANE_APIKEY"' + (hasBody ? " \\" : "")));
|
|
2220
|
-
if (hasBody) {
|
|
2221
|
-
console.log(import_chalk15.default.green(" -H 'Content-Type: application/json' \\"));
|
|
2222
|
-
console.log(import_chalk15.default.green(` -d '${masked}'`));
|
|
2223
|
-
}
|
|
2224
|
-
if (i < entries.length - 1) console.log();
|
|
2225
|
-
});
|
|
2226
|
-
entries.length = 0;
|
|
2227
|
-
}
|
|
2228
|
-
|
|
2229
|
-
// src/lib/admin.ts
|
|
2230
|
-
init_types();
|
|
2231
|
-
var DASHBOARD_BASE3 = process.env.APIBLAZE_DASHBOARD_BASE || "https://dashboard.apiblaze.com";
|
|
2232
|
-
async function admin(call) {
|
|
2233
|
-
const token = getAccessToken();
|
|
2234
|
-
const res = await fetch(`${DASHBOARD_BASE3}/api/cli/admin`, {
|
|
2235
|
-
method: "POST",
|
|
2236
|
-
headers: { "Content-Type": "application/json", Authorization: `Bearer ${token}` },
|
|
2237
|
-
body: JSON.stringify({ path: call.path, method: call.method, body: call.body })
|
|
2238
|
-
});
|
|
2239
|
-
let data = null;
|
|
2240
|
-
try {
|
|
2241
|
-
data = await res.json();
|
|
2242
|
-
} catch {
|
|
2243
|
-
}
|
|
2244
|
-
recordCall({ method: call.method, path: call.path, body: call.body, status: res.status, summary: call.summary });
|
|
2245
|
-
maybePrintBilling(data);
|
|
2246
|
-
if (!res.ok) {
|
|
2247
|
-
const msg = data?.details ?? data?.error ?? res.statusText;
|
|
2248
|
-
throw new ApiError(res.status, typeof msg === "string" ? msg : JSON.stringify(msg), data);
|
|
2249
|
-
}
|
|
2250
|
-
return data;
|
|
2251
|
-
}
|
|
2252
|
-
function maybePrintBilling(data) {
|
|
2253
|
-
const b = data?.billing;
|
|
2254
|
-
if (b && typeof b.charged_cents === "number") {
|
|
2255
|
-
const usd = (b.charged_cents / 100).toFixed(2);
|
|
2256
|
-
const rem = typeof b.credits_remaining === "number" ? ` \xB7 $${(b.credits_remaining / 100).toFixed(2)} credit left` : "";
|
|
2257
|
-
console.log(import_chalk16.default.magenta(` \u{1F4B3} Charged $${usd}${rem}`));
|
|
2258
|
-
}
|
|
2259
|
-
}
|
|
2372
|
+
init_admin();
|
|
2260
2373
|
|
|
2261
2374
|
// src/lib/resolve.ts
|
|
2262
2375
|
var import_chalk17 = __toESM(require("chalk"));
|
|
@@ -2369,6 +2482,7 @@ async function runDelete(project, version2, opts) {
|
|
|
2369
2482
|
// src/commands/export.ts
|
|
2370
2483
|
var import_chalk19 = __toESM(require("chalk"));
|
|
2371
2484
|
var import_fs3 = require("fs");
|
|
2485
|
+
init_admin();
|
|
2372
2486
|
init_auth();
|
|
2373
2487
|
var DASHBOARD_BASE4 = process.env.APIBLAZE_DASHBOARD_BASE || "https://dashboard.apiblaze.com";
|
|
2374
2488
|
async function runExport(projectArg, versionArg, opts) {
|
|
@@ -2425,6 +2539,7 @@ async function runExport(projectArg, versionArg, opts) {
|
|
|
2425
2539
|
// src/commands/config.ts
|
|
2426
2540
|
var import_chalk20 = __toESM(require("chalk"));
|
|
2427
2541
|
var import_ora6 = __toESM(require("ora"));
|
|
2542
|
+
init_admin();
|
|
2428
2543
|
async function patchConfig(project, opts, body, summary) {
|
|
2429
2544
|
const { teamId } = await resolveTeam(opts.team);
|
|
2430
2545
|
const proj2 = await resolveProject(teamId, project, opts.apiversion);
|
|
@@ -2478,13 +2593,15 @@ async function runRename(project, opts) {
|
|
|
2478
2593
|
}
|
|
2479
2594
|
|
|
2480
2595
|
// src/commands/config-browse.ts
|
|
2481
|
-
var
|
|
2482
|
-
var
|
|
2596
|
+
var import_chalk28 = __toESM(require("chalk"));
|
|
2597
|
+
var import_ora13 = __toESM(require("ora"));
|
|
2598
|
+
init_admin();
|
|
2483
2599
|
init_auth();
|
|
2484
2600
|
|
|
2485
2601
|
// src/commands/domain.ts
|
|
2486
2602
|
var import_chalk21 = __toESM(require("chalk"));
|
|
2487
2603
|
var import_ora7 = __toESM(require("ora"));
|
|
2604
|
+
init_admin();
|
|
2488
2605
|
async function runDomainAdd(project, opts) {
|
|
2489
2606
|
if (!opts.domain) {
|
|
2490
2607
|
console.error(import_chalk21.default.red("--domain is required."));
|
|
@@ -2587,8 +2704,29 @@ async function runDomainSetBase(project, opts) {
|
|
|
2587
2704
|
}
|
|
2588
2705
|
|
|
2589
2706
|
// src/commands/tenant.ts
|
|
2590
|
-
var
|
|
2591
|
-
var
|
|
2707
|
+
var import_chalk23 = __toESM(require("chalk"));
|
|
2708
|
+
var import_ora9 = __toESM(require("ora"));
|
|
2709
|
+
init_admin();
|
|
2710
|
+
init_auth();
|
|
2711
|
+
init_tenant_pick();
|
|
2712
|
+
async function runTenantUse(query, opts) {
|
|
2713
|
+
const creds = loadCredentials();
|
|
2714
|
+
if (!creds) {
|
|
2715
|
+
console.error(import_chalk23.default.red("Not logged in. Run `apiblaze login` first."));
|
|
2716
|
+
process.exit(1);
|
|
2717
|
+
}
|
|
2718
|
+
if (opts.clear) {
|
|
2719
|
+
delete creds.activeTenant;
|
|
2720
|
+
saveCredentials(creds);
|
|
2721
|
+
console.log(import_chalk23.default.green("Tenant scope cleared."));
|
|
2722
|
+
return;
|
|
2723
|
+
}
|
|
2724
|
+
const { teamId } = await resolveTeam(opts.team);
|
|
2725
|
+
const slug = await pickTenant(teamId, { message: "Scope future commands to which tenant?", initialQuery: query });
|
|
2726
|
+
if (!slug) process.exit(1);
|
|
2727
|
+
saveCredentials({ ...creds, activeTenant: slug });
|
|
2728
|
+
console.log(import_chalk23.default.green(`Tenant scope set to ${import_chalk23.default.bold(slug)}.`) + import_chalk23.default.dim(" (clear with `apiblaze tenant use --clear`)"));
|
|
2729
|
+
}
|
|
2592
2730
|
async function runTenantList(opts) {
|
|
2593
2731
|
const { teamId, teamName } = await resolveTeam(opts.team);
|
|
2594
2732
|
const out = await admin({
|
|
@@ -2602,22 +2740,22 @@ async function runTenantList(opts) {
|
|
|
2602
2740
|
return;
|
|
2603
2741
|
}
|
|
2604
2742
|
if (!tenants.length) {
|
|
2605
|
-
console.log(
|
|
2743
|
+
console.log(import_chalk23.default.yellow("No tenants."));
|
|
2606
2744
|
return;
|
|
2607
2745
|
}
|
|
2608
2746
|
for (const t of tenants) {
|
|
2609
2747
|
const name = typeof t === "string" ? t : t.tenant_name;
|
|
2610
|
-
const display = typeof t === "string" ? "" :
|
|
2611
|
-
console.log(` ${
|
|
2748
|
+
const display = typeof t === "string" ? "" : import_chalk23.default.dim(` ${t.display_name ?? ""}`);
|
|
2749
|
+
console.log(` ${import_chalk23.default.bold(name)}${display}`);
|
|
2612
2750
|
}
|
|
2613
2751
|
}
|
|
2614
2752
|
async function runTenantCreate(opts) {
|
|
2615
2753
|
if (!opts.name) {
|
|
2616
|
-
console.error(
|
|
2754
|
+
console.error(import_chalk23.default.red("--name (display name) is required."));
|
|
2617
2755
|
process.exit(1);
|
|
2618
2756
|
}
|
|
2619
2757
|
const { teamId } = await resolveTeam(opts.team);
|
|
2620
|
-
const spinner = (0,
|
|
2758
|
+
const spinner = (0, import_ora9.default)("Creating tenant...").start();
|
|
2621
2759
|
try {
|
|
2622
2760
|
const out = await admin({
|
|
2623
2761
|
method: "POST",
|
|
@@ -2625,7 +2763,7 @@ async function runTenantCreate(opts) {
|
|
|
2625
2763
|
body: { display_name: opts.name, ...opts.slug ? { tenant_name: opts.slug } : {} },
|
|
2626
2764
|
summary: `Create tenant "${opts.name}"`
|
|
2627
2765
|
});
|
|
2628
|
-
spinner.succeed(`Created tenant ${
|
|
2766
|
+
spinner.succeed(`Created tenant ${import_chalk23.default.bold(out?.tenant_name ?? opts.name)}.`);
|
|
2629
2767
|
if (opts.json) console.log(JSON.stringify(out));
|
|
2630
2768
|
} catch (err) {
|
|
2631
2769
|
spinner.fail("Tenant create failed.");
|
|
@@ -2634,12 +2772,12 @@ async function runTenantCreate(opts) {
|
|
|
2634
2772
|
}
|
|
2635
2773
|
async function runTenantAttach(project, opts) {
|
|
2636
2774
|
if (!opts.tenant) {
|
|
2637
|
-
console.error(
|
|
2775
|
+
console.error(import_chalk23.default.red("--tenant <slug> is required."));
|
|
2638
2776
|
process.exit(1);
|
|
2639
2777
|
}
|
|
2640
2778
|
const { teamId } = await resolveTeam(opts.team);
|
|
2641
2779
|
const proj2 = await resolveProject(teamId, project, opts.apiversion);
|
|
2642
|
-
const spinner = (0,
|
|
2780
|
+
const spinner = (0, import_ora9.default)("Attaching tenant...").start();
|
|
2643
2781
|
try {
|
|
2644
2782
|
const out = await admin({
|
|
2645
2783
|
method: "POST",
|
|
@@ -2662,11 +2800,11 @@ async function runTenantDelete(slug, opts) {
|
|
|
2662
2800
|
{ type: "confirm", name: "confirm", message: `Permanently delete tenant "${slug}" and everything under it? This cannot be undone.`, default: false }
|
|
2663
2801
|
]);
|
|
2664
2802
|
if (!confirm) {
|
|
2665
|
-
console.log(
|
|
2803
|
+
console.log(import_chalk23.default.dim("Aborted."));
|
|
2666
2804
|
return;
|
|
2667
2805
|
}
|
|
2668
2806
|
}
|
|
2669
|
-
const spinner = (0,
|
|
2807
|
+
const spinner = (0, import_ora9.default)("Deleting tenant...").start();
|
|
2670
2808
|
try {
|
|
2671
2809
|
await admin({
|
|
2672
2810
|
method: "DELETE",
|
|
@@ -2681,13 +2819,13 @@ async function runTenantDelete(slug, opts) {
|
|
|
2681
2819
|
}
|
|
2682
2820
|
async function runTenantCors(opts) {
|
|
2683
2821
|
if (!opts.tenant) {
|
|
2684
|
-
console.error(
|
|
2822
|
+
console.error(import_chalk23.default.red("--tenant <slug> is required."));
|
|
2685
2823
|
process.exit(1);
|
|
2686
2824
|
}
|
|
2687
2825
|
const { teamId } = await resolveTeam(opts.team);
|
|
2688
2826
|
const origins = (opts.origins ?? "").split(",").map((s) => s.trim()).filter(Boolean);
|
|
2689
2827
|
const cors = origins.length ? { allowed_origins: origins } : null;
|
|
2690
|
-
const spinner = (0,
|
|
2828
|
+
const spinner = (0, import_ora9.default)("Updating CORS...").start();
|
|
2691
2829
|
try {
|
|
2692
2830
|
await admin({
|
|
2693
2831
|
method: "PUT",
|
|
@@ -2702,10 +2840,458 @@ async function runTenantCors(opts) {
|
|
|
2702
2840
|
}
|
|
2703
2841
|
}
|
|
2704
2842
|
|
|
2843
|
+
// src/commands/tenant-drill.ts
|
|
2844
|
+
var import_chalk24 = __toESM(require("chalk"));
|
|
2845
|
+
var import_ora10 = __toESM(require("ora"));
|
|
2846
|
+
var import_crypto = require("crypto");
|
|
2847
|
+
init_admin();
|
|
2848
|
+
init_auth();
|
|
2849
|
+
init_tenant_pick();
|
|
2850
|
+
init_api();
|
|
2851
|
+
var trailingComma = /\s*,\s*/;
|
|
2852
|
+
var parseList = (s) => s.split(trailingComma).map((x) => x.trim()).filter(Boolean);
|
|
2853
|
+
async function runTenantManage(query, opts) {
|
|
2854
|
+
const { teamId } = await resolveTeam(opts.team);
|
|
2855
|
+
const slug = opts.tenant ?? loadCredentialsTenant(query) ?? await pickTenant(teamId, { message: "Manage which tenant?", initialQuery: query, allowCreate: true });
|
|
2856
|
+
if (!slug) return;
|
|
2857
|
+
await tenantHome(teamId, slug);
|
|
2858
|
+
}
|
|
2859
|
+
function loadCredentialsTenant(query) {
|
|
2860
|
+
if (query) return void 0;
|
|
2861
|
+
return loadCredentials()?.activeTenant ?? void 0;
|
|
2862
|
+
}
|
|
2863
|
+
async function tenantHome(teamId, tenant2) {
|
|
2864
|
+
const { default: inquirer2 } = await import("inquirer");
|
|
2865
|
+
const base = `/teams/${encodeURIComponent(teamId)}/tenants/${encodeURIComponent(tenant2)}`;
|
|
2866
|
+
console.log(import_chalk24.default.bold(`
|
|
2867
|
+
Tenant ${tenant2}`));
|
|
2868
|
+
console.log(import_chalk24.default.dim("Tenant auth/settings are SHARED: changes apply to every proxy this tenant serves.\n"));
|
|
2869
|
+
for (; ; ) {
|
|
2870
|
+
const spinner = (0, import_ora10.default)("Reading tenant state...").start();
|
|
2871
|
+
const [iam, cors, emails, issuers, opaque, clients] = await Promise.all([
|
|
2872
|
+
admin({ method: "GET", path: `${base}/iam`, summary: "Read IAM toggle" }).catch(() => null),
|
|
2873
|
+
admin({ method: "GET", path: `${base}/cors`, summary: "Read tenant CORS" }).catch(() => null),
|
|
2874
|
+
admin({ method: "GET", path: `${base}/admin-emails`, summary: "List consumer-admin emails" }).catch(() => null),
|
|
2875
|
+
admin({ method: "GET", path: `${base}/external-issuers`, summary: "List external issuers" }).catch(() => null),
|
|
2876
|
+
admin({ method: "GET", path: `${base}/opaque`, summary: "Read opaque validator" }).catch(() => null),
|
|
2877
|
+
admin({ method: "GET", path: `${base}/app-clients`, summary: "List app clients" }).catch(() => [])
|
|
2878
|
+
]).finally(() => spinner.stop());
|
|
2879
|
+
const nEmails = (emails?.admin_emails ?? []).length;
|
|
2880
|
+
const nIssuers = (issuers?.external_issuers ?? []).length;
|
|
2881
|
+
const nClients = Array.isArray(clients) ? clients.length : 0;
|
|
2882
|
+
const onOff = (b) => b ? import_chalk24.default.green("on") : import_chalk24.default.dim("off");
|
|
2883
|
+
const { pick: pick2 } = await inquirer2.prompt([{
|
|
2884
|
+
type: "list",
|
|
2885
|
+
name: "pick",
|
|
2886
|
+
message: `Tenant ${tenant2}:`,
|
|
2887
|
+
pageSize: 12,
|
|
2888
|
+
choices: [
|
|
2889
|
+
{ name: `App clients (${nClients}) ${import_chalk24.default.dim("OAuth clients your consumers log in through \u2014 providers live inside")}`, value: "clients" },
|
|
2890
|
+
{ name: `IAM enforcement: ${onOff(iam?.iam_enabled)} ${import_chalk24.default.dim("key/identity enforcement for this tenant")}`, value: "iam" },
|
|
2891
|
+
{ name: `CORS override: ${cors?.cors ? import_chalk24.default.cyan(JSON.stringify(cors.cors)) : import_chalk24.default.dim("(unset)")}`, value: "cors" },
|
|
2892
|
+
{ name: `Consumer-admin emails (${nEmails}) ${import_chalk24.default.dim("who may administer the tenant portal")}`, value: "emails" },
|
|
2893
|
+
{ name: `External JWT issuers (${nIssuers}) ${import_chalk24.default.dim("bring-your-own auth: trust tokens you already mint")}`, value: "issuers" },
|
|
2894
|
+
{ name: `Opaque-token validator: ${opaque?.opaque?.endpoint ? import_chalk24.default.cyan(opaque.opaque.endpoint) : import_chalk24.default.dim("(unset)")}`, value: "opaque" },
|
|
2895
|
+
{ name: "\u2190 Back", value: "back" }
|
|
2896
|
+
]
|
|
2897
|
+
}]);
|
|
2898
|
+
switch (pick2) {
|
|
2899
|
+
case "back":
|
|
2900
|
+
return;
|
|
2901
|
+
case "clients":
|
|
2902
|
+
await clientsMenu(teamId, tenant2, base);
|
|
2903
|
+
break;
|
|
2904
|
+
case "iam": {
|
|
2905
|
+
const { v } = await inquirer2.prompt([{ type: "confirm", name: "v", message: "Enable IAM enforcement?", default: !!iam?.iam_enabled }]);
|
|
2906
|
+
await admin({ method: "PATCH", path: `${base}/iam`, body: { enabled: v }, summary: `IAM enforcement \u2192 ${v ? "on" : "off"}` });
|
|
2907
|
+
console.log(import_chalk24.default.green(` IAM enforcement ${v ? "enabled" : "disabled"}.`));
|
|
2908
|
+
break;
|
|
2909
|
+
}
|
|
2910
|
+
case "cors": {
|
|
2911
|
+
const { v } = await inquirer2.prompt([{
|
|
2912
|
+
type: "input",
|
|
2913
|
+
name: "v",
|
|
2914
|
+
message: 'CORS JSON (e.g. {"allow_all_origins":true} \u2014 "null" clears):',
|
|
2915
|
+
default: cors?.cors ? JSON.stringify(cors.cors) : ""
|
|
2916
|
+
}]);
|
|
2917
|
+
if (v === "") break;
|
|
2918
|
+
const parsed = v === "null" ? null : safeJson(v);
|
|
2919
|
+
if (parsed === void 0) {
|
|
2920
|
+
console.log(import_chalk24.default.yellow(" Not valid JSON \u2014 unchanged."));
|
|
2921
|
+
break;
|
|
2922
|
+
}
|
|
2923
|
+
await admin({ method: "PUT", path: `${base}/cors`, body: { cors: parsed }, summary: "Set tenant CORS" });
|
|
2924
|
+
console.log(import_chalk24.default.green(" CORS updated."));
|
|
2925
|
+
break;
|
|
2926
|
+
}
|
|
2927
|
+
case "emails":
|
|
2928
|
+
await emailsMenu(base, emails?.admin_emails ?? []);
|
|
2929
|
+
break;
|
|
2930
|
+
case "issuers":
|
|
2931
|
+
await issuersMenu(base, issuers?.external_issuers ?? []);
|
|
2932
|
+
break;
|
|
2933
|
+
case "opaque": {
|
|
2934
|
+
const cur = opaque?.opaque;
|
|
2935
|
+
const { mode } = await inquirer2.prompt([{
|
|
2936
|
+
type: "list",
|
|
2937
|
+
name: "mode",
|
|
2938
|
+
message: "Opaque-token validator:",
|
|
2939
|
+
choices: [
|
|
2940
|
+
{ name: cur ? "Replace it" : "Set one up", value: "set" },
|
|
2941
|
+
...cur ? [{ name: "Clear it", value: "clear" }] : [],
|
|
2942
|
+
{ name: "\u2190 Back", value: "back" }
|
|
2943
|
+
]
|
|
2944
|
+
}]);
|
|
2945
|
+
if (mode === "back") break;
|
|
2946
|
+
if (mode === "clear") {
|
|
2947
|
+
await admin({ method: "PUT", path: `${base}/opaque`, body: { opaque: null }, summary: "Clear opaque validator" });
|
|
2948
|
+
console.log(import_chalk24.default.green(" Cleared."));
|
|
2949
|
+
break;
|
|
2950
|
+
}
|
|
2951
|
+
const a = await inquirer2.prompt([
|
|
2952
|
+
{ type: "input", name: "endpoint", message: "Introspection endpoint (https):", default: cur?.endpoint, validate: (s) => s.startsWith("https://") || "must be https" },
|
|
2953
|
+
{ type: "list", name: "method", message: "HTTP method:", choices: ["GET", "POST"], default: cur?.method ?? "GET" }
|
|
2954
|
+
]);
|
|
2955
|
+
await admin({ method: "PUT", path: `${base}/opaque`, body: { opaque: { endpoint: a.endpoint, method: a.method } }, summary: "Set opaque validator" });
|
|
2956
|
+
console.log(import_chalk24.default.green(" Opaque validator set."));
|
|
2957
|
+
break;
|
|
2958
|
+
}
|
|
2959
|
+
}
|
|
2960
|
+
}
|
|
2961
|
+
}
|
|
2962
|
+
function safeJson(s) {
|
|
2963
|
+
try {
|
|
2964
|
+
return JSON.parse(s);
|
|
2965
|
+
} catch {
|
|
2966
|
+
return void 0;
|
|
2967
|
+
}
|
|
2968
|
+
}
|
|
2969
|
+
async function emailsMenu(base, emails) {
|
|
2970
|
+
const { default: inquirer2 } = await import("inquirer");
|
|
2971
|
+
console.log();
|
|
2972
|
+
if (!emails.length) console.log(import_chalk24.default.dim(" No consumer-admin emails."));
|
|
2973
|
+
for (const e of emails) console.log(` ${import_chalk24.default.bold(e.email ?? e)} ${import_chalk24.default.dim(e.status ?? "")}`);
|
|
2974
|
+
const { act } = await inquirer2.prompt([{
|
|
2975
|
+
type: "list",
|
|
2976
|
+
name: "act",
|
|
2977
|
+
message: "Consumer-admin emails:",
|
|
2978
|
+
choices: [
|
|
2979
|
+
{ name: "Add an email", value: "add" },
|
|
2980
|
+
...emails.length ? [{ name: "Remove an email", value: "rm" }] : [],
|
|
2981
|
+
{ name: "\u2190 Back", value: "back" }
|
|
2982
|
+
]
|
|
2983
|
+
}]);
|
|
2984
|
+
if (act === "back") return;
|
|
2985
|
+
if (act === "add") {
|
|
2986
|
+
const { email } = await inquirer2.prompt([{ type: "input", name: "email", message: "Email:", validate: (s) => /.+@.+\..+/.test(s) || "not an email" }]);
|
|
2987
|
+
await admin({ method: "POST", path: `${base}/admin-emails`, body: { email }, summary: `Add consumer-admin ${email}` });
|
|
2988
|
+
console.log(import_chalk24.default.green(` ${email} added.`));
|
|
2989
|
+
} else {
|
|
2990
|
+
const { e } = await inquirer2.prompt([{
|
|
2991
|
+
type: "list",
|
|
2992
|
+
name: "e",
|
|
2993
|
+
message: "Remove which?",
|
|
2994
|
+
choices: [...emails.map((x) => ({ name: x.email ?? String(x), value: x.email ?? String(x) })), { name: "\u2190 Back", value: null }]
|
|
2995
|
+
}]);
|
|
2996
|
+
if (!e) return;
|
|
2997
|
+
await admin({ method: "DELETE", path: `${base}/admin-emails/${encodeURIComponent(e)}`, summary: `Remove consumer-admin ${e}` });
|
|
2998
|
+
console.log(import_chalk24.default.green(` ${e} removed.`));
|
|
2999
|
+
}
|
|
3000
|
+
}
|
|
3001
|
+
async function issuersMenu(base, issuers) {
|
|
3002
|
+
const { default: inquirer2 } = await import("inquirer");
|
|
3003
|
+
console.log();
|
|
3004
|
+
if (!issuers.length) console.log(import_chalk24.default.dim(" No external issuers \u2014 consumers use APIblaze-issued tokens."));
|
|
3005
|
+
for (const i of issuers) console.log(` ${import_chalk24.default.bold(i.iss)} aud=${i.aud} ${import_chalk24.default.dim(i.sub_semantics ?? "")}`);
|
|
3006
|
+
const { act } = await inquirer2.prompt([{
|
|
3007
|
+
type: "list",
|
|
3008
|
+
name: "act",
|
|
3009
|
+
message: "External JWT issuers:",
|
|
3010
|
+
choices: [
|
|
3011
|
+
{ name: "Add / replace one issuer", value: "add" },
|
|
3012
|
+
...issuers.length ? [{ name: "Delete an issuer", value: "rm" }] : [],
|
|
3013
|
+
{ name: "\u2190 Back", value: "back" }
|
|
3014
|
+
]
|
|
3015
|
+
}]);
|
|
3016
|
+
if (act === "back") return;
|
|
3017
|
+
if (act === "add") {
|
|
3018
|
+
const a = await inquirer2.prompt([
|
|
3019
|
+
{ type: "input", name: "iss", message: "Issuer URL (iss):", validate: (s) => !!s.trim() || "required" },
|
|
3020
|
+
{ type: "input", name: "aud", message: "Audience (aud):", validate: (s) => !!s.trim() || "required" },
|
|
3021
|
+
{ type: "input", name: "jwks", message: "JWKS URL (empty = derive from issuer):" },
|
|
3022
|
+
{ type: "list", name: "sem", message: "Where is the end-user id?", choices: [
|
|
3023
|
+
{ name: "The token sub IS the end user (tenant-owned)", value: "tenant_owned" },
|
|
3024
|
+
{ name: "Extract it from a claim\u2026", value: "extract_from_claim" }
|
|
3025
|
+
] }
|
|
3026
|
+
]);
|
|
3027
|
+
const claim = a.sem === "extract_from_claim" ? (await inquirer2.prompt([{ type: "input", name: "c", message: "Claim name:", validate: (s) => !!s.trim() || "required" }])).c : void 0;
|
|
3028
|
+
await admin({
|
|
3029
|
+
method: "POST",
|
|
3030
|
+
path: `${base}/external-issuers`,
|
|
3031
|
+
body: { iss: a.iss.trim(), aud: a.aud.trim(), jwks_url: a.jwks.trim() || null, sub_semantics: a.sem, ...claim ? { claim_name: claim } : {} },
|
|
3032
|
+
summary: `Add external issuer ${a.iss.trim()}`
|
|
3033
|
+
});
|
|
3034
|
+
console.log(import_chalk24.default.green(" Issuer saved."));
|
|
3035
|
+
} else {
|
|
3036
|
+
const { i } = await inquirer2.prompt([{
|
|
3037
|
+
type: "list",
|
|
3038
|
+
name: "i",
|
|
3039
|
+
message: "Delete which issuer?",
|
|
3040
|
+
choices: [...issuers.map((x) => ({ name: `${x.iss} (aud=${x.aud})`, value: x })), { name: "\u2190 Back", value: null }]
|
|
3041
|
+
}]);
|
|
3042
|
+
if (!i) return;
|
|
3043
|
+
await admin({
|
|
3044
|
+
method: "DELETE",
|
|
3045
|
+
path: `${base}/external-issuers?iss=${encodeURIComponent(i.iss)}&aud=${encodeURIComponent(i.aud)}`,
|
|
3046
|
+
summary: `Delete issuer ${i.iss}`
|
|
3047
|
+
});
|
|
3048
|
+
console.log(import_chalk24.default.green(" Issuer deleted."));
|
|
3049
|
+
}
|
|
3050
|
+
}
|
|
3051
|
+
async function clientsMenu(teamId, tenant2, base) {
|
|
3052
|
+
const { default: inquirer2 } = await import("inquirer");
|
|
3053
|
+
for (; ; ) {
|
|
3054
|
+
const spinner = (0, import_ora10.default)("Loading app clients...").start();
|
|
3055
|
+
const raw = await admin({ method: "GET", path: `${base}/app-clients`, summary: "List app clients" }).catch(() => []);
|
|
3056
|
+
spinner.stop();
|
|
3057
|
+
const clients = Array.isArray(raw) ? raw : [];
|
|
3058
|
+
const { pick: pick2 } = await inquirer2.prompt([{
|
|
3059
|
+
type: "list",
|
|
3060
|
+
name: "pick",
|
|
3061
|
+
message: `App clients of ${tenant2}:`,
|
|
3062
|
+
pageSize: 15,
|
|
3063
|
+
choices: [
|
|
3064
|
+
...clients.map((c) => ({
|
|
3065
|
+
name: `${import_chalk24.default.bold(c.name ?? c.clientId)} ${import_chalk24.default.dim(`${c.clientId}${c.projectName ? ` \xB7 ${c.projectName}` : ""}`)}`,
|
|
3066
|
+
value: c
|
|
3067
|
+
})),
|
|
3068
|
+
...clients.length ? [] : [new inquirer2.Separator(import_chalk24.default.dim(" no app clients yet"))],
|
|
3069
|
+
{ name: "\uFF0B Create an app client\u2026", value: " create" },
|
|
3070
|
+
{ name: "\u2190 Back", value: " back" }
|
|
3071
|
+
]
|
|
3072
|
+
}]);
|
|
3073
|
+
if (pick2 === " back") return;
|
|
3074
|
+
if (pick2 === " create") {
|
|
3075
|
+
const projects = await getProjects(teamId).catch(() => []);
|
|
3076
|
+
if (!projects.length) {
|
|
3077
|
+
console.log(import_chalk24.default.yellow(" No projects in this team \u2014 create a proxy first."));
|
|
3078
|
+
continue;
|
|
3079
|
+
}
|
|
3080
|
+
const a = await inquirer2.prompt([
|
|
3081
|
+
{ type: "input", name: "name", message: "Client name:", validate: (s) => !!s.trim() || "required" },
|
|
3082
|
+
{ type: "list", name: "proj", message: "For which project?", choices: projects.map((p) => ({ name: `${p.projectName} ${import_chalk24.default.dim("v" + p.apiVersion)}`, value: p })) },
|
|
3083
|
+
{ type: "input", name: "callbacks", message: "Callback URLs (comma-separated, empty = none):" }
|
|
3084
|
+
]);
|
|
3085
|
+
const created = await admin({
|
|
3086
|
+
method: "POST",
|
|
3087
|
+
path: `${base}/app-clients`,
|
|
3088
|
+
body: {
|
|
3089
|
+
name: a.name.trim(),
|
|
3090
|
+
projectName: a.proj.projectName,
|
|
3091
|
+
apiVersion: a.proj.apiVersion,
|
|
3092
|
+
...a.callbacks.trim() ? { authorizedCallbackUrls: parseList(a.callbacks) } : {}
|
|
3093
|
+
},
|
|
3094
|
+
summary: `Create app client "${a.name.trim()}"`
|
|
3095
|
+
});
|
|
3096
|
+
console.log(import_chalk24.default.green(` App client created${created?.clientId ? ` (${created.clientId})` : ""}.`));
|
|
3097
|
+
continue;
|
|
3098
|
+
}
|
|
3099
|
+
await clientHome(base, pick2);
|
|
3100
|
+
}
|
|
3101
|
+
}
|
|
3102
|
+
async function clientHome(base, summary) {
|
|
3103
|
+
const { default: inquirer2 } = await import("inquirer");
|
|
3104
|
+
const id = summary.clientId ?? summary.client_id;
|
|
3105
|
+
const cBase = `${base}/app-clients/${encodeURIComponent(id)}`;
|
|
3106
|
+
for (; ; ) {
|
|
3107
|
+
const spinner = (0, import_ora10.default)("Reading app client...").start();
|
|
3108
|
+
const c = await admin({ method: "GET", path: cBase, summary: `Read app client ${id}` }).catch(() => summary);
|
|
3109
|
+
spinner.stop();
|
|
3110
|
+
const cb = c.authorizedCallbackUrls ?? c.authorized_callback_urls ?? [];
|
|
3111
|
+
const scopes = c.scopes ?? [];
|
|
3112
|
+
const nProviders = (c.providers ?? []).length;
|
|
3113
|
+
const { pick: pick2 } = await inquirer2.prompt([{
|
|
3114
|
+
type: "list",
|
|
3115
|
+
name: "pick",
|
|
3116
|
+
message: `${c.name ?? id}:`,
|
|
3117
|
+
pageSize: 12,
|
|
3118
|
+
choices: [
|
|
3119
|
+
{ name: `Login providers${nProviders ? ` (${nProviders})` : ""} ${import_chalk24.default.dim("google/github/microsoft/\u2026 \u2014 how consumers sign in")}`, value: "providers" },
|
|
3120
|
+
{ name: `Callback URLs: ${cb.length ? import_chalk24.default.cyan(cb.join(", ")) : import_chalk24.default.dim("(none)")}`, value: "callbacks" },
|
|
3121
|
+
{ name: `Scopes: ${scopes.length ? import_chalk24.default.cyan(scopes.join(" ")) : import_chalk24.default.dim("(defaults)")}`, value: "scopes" },
|
|
3122
|
+
{ name: `Token expiries: access ${c.accessTokenExpiry ?? 3600}s \xB7 id ${c.idTokenExpiry ?? 3600}s \xB7 refresh ${c.refreshTokenExpiry ?? 2592e3}s`, value: "expiries" },
|
|
3123
|
+
{ name: "Reveal client secret", value: "secret" },
|
|
3124
|
+
{ name: "Rotate client secret", value: "rotate" },
|
|
3125
|
+
{ name: import_chalk24.default.red("Delete this app client"), value: "delete" },
|
|
3126
|
+
{ name: "\u2190 Back", value: "back" }
|
|
3127
|
+
]
|
|
3128
|
+
}]);
|
|
3129
|
+
switch (pick2) {
|
|
3130
|
+
case "back":
|
|
3131
|
+
return;
|
|
3132
|
+
case "providers":
|
|
3133
|
+
await providersMenu(cBase, c.name ?? id);
|
|
3134
|
+
break;
|
|
3135
|
+
case "callbacks": {
|
|
3136
|
+
const { v } = await inquirer2.prompt([{ type: "input", name: "v", message: "Callback URLs (comma-separated):", default: cb.join(", ") }]);
|
|
3137
|
+
await admin({ method: "PATCH", path: cBase, body: { authorizedCallbackUrls: parseList(v) }, summary: "Update callback URLs" });
|
|
3138
|
+
console.log(import_chalk24.default.green(" Callbacks updated."));
|
|
3139
|
+
break;
|
|
3140
|
+
}
|
|
3141
|
+
case "scopes": {
|
|
3142
|
+
const { v } = await inquirer2.prompt([{ type: "input", name: "v", message: "Scopes (space/comma-separated):", default: scopes.join(" ") }]);
|
|
3143
|
+
await admin({ method: "PATCH", path: cBase, body: { scopes: v.split(/[\s,]+/).filter(Boolean) }, summary: "Update scopes" });
|
|
3144
|
+
console.log(import_chalk24.default.green(" Scopes updated."));
|
|
3145
|
+
break;
|
|
3146
|
+
}
|
|
3147
|
+
case "expiries": {
|
|
3148
|
+
const a = await inquirer2.prompt([
|
|
3149
|
+
{ type: "input", name: "access", message: "Access token expiry (seconds):", default: String(c.accessTokenExpiry ?? 3600) },
|
|
3150
|
+
{ type: "input", name: "id", message: "ID token expiry (seconds):", default: String(c.idTokenExpiry ?? 3600) },
|
|
3151
|
+
{ type: "input", name: "refresh", message: "Refresh token expiry (seconds):", default: String(c.refreshTokenExpiry ?? 2592e3) }
|
|
3152
|
+
]);
|
|
3153
|
+
await admin({
|
|
3154
|
+
method: "PATCH",
|
|
3155
|
+
path: cBase,
|
|
3156
|
+
body: { accessTokenExpiry: Number(a.access), idTokenExpiry: Number(a.id), refreshTokenExpiry: Number(a.refresh) },
|
|
3157
|
+
summary: "Update token expiries"
|
|
3158
|
+
});
|
|
3159
|
+
console.log(import_chalk24.default.green(" Expiries updated."));
|
|
3160
|
+
break;
|
|
3161
|
+
}
|
|
3162
|
+
case "secret": {
|
|
3163
|
+
const { sure } = await inquirer2.prompt([{ type: "confirm", name: "sure", message: "Print the client secret to this terminal?", default: false }]);
|
|
3164
|
+
if (!sure) break;
|
|
3165
|
+
const s = await admin({ method: "GET", path: `${cBase}/secret`, summary: "Reveal client secret" });
|
|
3166
|
+
console.log(` ${import_chalk24.default.bold("client_secret")}: ${import_chalk24.default.green(s?.clientSecret ?? s?.client_secret ?? JSON.stringify(s))}`);
|
|
3167
|
+
break;
|
|
3168
|
+
}
|
|
3169
|
+
case "rotate": {
|
|
3170
|
+
const { sure } = await inquirer2.prompt([{ type: "confirm", name: "sure", message: "Rotate the secret? Existing integrations using it will break.", default: false }]);
|
|
3171
|
+
if (!sure) break;
|
|
3172
|
+
const fresh = randomSecret();
|
|
3173
|
+
await admin({ method: "PATCH", path: cBase, body: { clientSecret: fresh }, summary: "Rotate client secret" });
|
|
3174
|
+
console.log(` New ${import_chalk24.default.bold("client_secret")}: ${import_chalk24.default.green(fresh)} ${import_chalk24.default.dim("(store it now)")}`);
|
|
3175
|
+
break;
|
|
3176
|
+
}
|
|
3177
|
+
case "delete": {
|
|
3178
|
+
const { sure } = await inquirer2.prompt([{ type: "confirm", name: "sure", message: `Delete app client "${c.name ?? id}"? Consumers logged in through it will lose access.`, default: false }]);
|
|
3179
|
+
if (!sure) break;
|
|
3180
|
+
await admin({ method: "DELETE", path: cBase, summary: `Delete app client ${id}` });
|
|
3181
|
+
console.log(import_chalk24.default.green(" App client deleted."));
|
|
3182
|
+
return;
|
|
3183
|
+
}
|
|
3184
|
+
}
|
|
3185
|
+
}
|
|
3186
|
+
}
|
|
3187
|
+
function randomSecret() {
|
|
3188
|
+
const bytes = new Uint8Array(24);
|
|
3189
|
+
(0, import_crypto.randomFillSync)(bytes);
|
|
3190
|
+
return Buffer.from(bytes).toString("base64url");
|
|
3191
|
+
}
|
|
3192
|
+
var PROVIDER_TYPES = ["google", "github", "microsoft", "facebook", "auth0", "other"];
|
|
3193
|
+
var DEFAULT_SCOPES = {
|
|
3194
|
+
google: "openid email profile",
|
|
3195
|
+
microsoft: "openid email profile",
|
|
3196
|
+
github: "read:user user:email",
|
|
3197
|
+
facebook: "public_profile email"
|
|
3198
|
+
};
|
|
3199
|
+
async function providersMenu(cBase, clientLabel) {
|
|
3200
|
+
const { default: inquirer2 } = await import("inquirer");
|
|
3201
|
+
for (; ; ) {
|
|
3202
|
+
const spinner = (0, import_ora10.default)("Loading providers...").start();
|
|
3203
|
+
const raw = await admin({ method: "GET", path: `${cBase}/providers`, summary: "List login providers" }).catch(() => []);
|
|
3204
|
+
spinner.stop();
|
|
3205
|
+
const providers = Array.isArray(raw) ? raw : [];
|
|
3206
|
+
console.log();
|
|
3207
|
+
for (const p of providers) {
|
|
3208
|
+
console.log(` ${import_chalk24.default.bold(p.type)} ${import_chalk24.default.dim(`${p.clientId || "(managed)"} \xB7 identity=${p.tokenType ?? "apiblaze"} \xB7 to-upstream=${p.targetServerToken ?? "apiblaze"}${p.isApiblazeDefault ? " \xB7 apiblaze-managed" : ""}`)}`);
|
|
3209
|
+
}
|
|
3210
|
+
if (!providers.length) console.log(import_chalk24.default.dim(" No login providers \u2014 consumers cannot sign in to this client yet."));
|
|
3211
|
+
const { act } = await inquirer2.prompt([{
|
|
3212
|
+
type: "list",
|
|
3213
|
+
name: "act",
|
|
3214
|
+
message: `Login providers of ${clientLabel}:`,
|
|
3215
|
+
choices: [
|
|
3216
|
+
{ name: "\uFF0B Add a provider", value: "add" },
|
|
3217
|
+
...providers.length ? [
|
|
3218
|
+
{ name: "Reveal a provider secret", value: "secret" },
|
|
3219
|
+
{ name: "Remove a provider", value: "rm" }
|
|
3220
|
+
] : [],
|
|
3221
|
+
{ name: "\u2190 Back", value: "back" }
|
|
3222
|
+
]
|
|
3223
|
+
}]);
|
|
3224
|
+
if (act === "back") return;
|
|
3225
|
+
if (act === "add") {
|
|
3226
|
+
const { type } = await inquirer2.prompt([{ type: "list", name: "type", message: "Provider:", choices: PROVIDER_TYPES }]);
|
|
3227
|
+
let body;
|
|
3228
|
+
if (type === "github") {
|
|
3229
|
+
const { managed } = await inquirer2.prompt([{
|
|
3230
|
+
type: "confirm",
|
|
3231
|
+
name: "managed",
|
|
3232
|
+
default: true,
|
|
3233
|
+
message: "Use the APIblaze-managed GitHub app (no credentials needed)?"
|
|
3234
|
+
}]);
|
|
3235
|
+
if (managed) body = { type, managed: true };
|
|
3236
|
+
}
|
|
3237
|
+
if (!body) {
|
|
3238
|
+
const a = await inquirer2.prompt([
|
|
3239
|
+
{ type: "input", name: "clientId", message: `${type} OAuth client id:`, validate: (s) => !!s.trim() || "required" },
|
|
3240
|
+
{ type: "password", name: "clientSecret", mask: "*", message: `${type} OAuth client secret:`, validate: (s) => s.length >= 6 && s.length <= 200 || "6\u2013200 chars" },
|
|
3241
|
+
...type === "auth0" || type === "other" ? [{ type: "input", name: "domain", message: "Issuer / domain (e.g. your-tenant.auth0.com):" }] : [],
|
|
3242
|
+
{ type: "input", name: "scopes", message: "Scopes:", default: DEFAULT_SCOPES[type] ?? "" }
|
|
3243
|
+
]);
|
|
3244
|
+
body = {
|
|
3245
|
+
type,
|
|
3246
|
+
clientId: a.clientId.trim(),
|
|
3247
|
+
clientSecret: a.clientSecret,
|
|
3248
|
+
...a.domain?.trim() ? { domain: a.domain.trim() } : {},
|
|
3249
|
+
scopes: String(a.scopes).split(/[\s,]+/).filter(Boolean)
|
|
3250
|
+
};
|
|
3251
|
+
}
|
|
3252
|
+
const { routing } = await inquirer2.prompt([{
|
|
3253
|
+
type: "list",
|
|
3254
|
+
name: "routing",
|
|
3255
|
+
message: "What does your upstream receive?",
|
|
3256
|
+
choices: [
|
|
3257
|
+
{ name: "APIblaze token (recommended \u2014 provider stays an identity source)", value: null },
|
|
3258
|
+
{ name: `The ${type} access token`, value: "third_party_access_token" },
|
|
3259
|
+
{ name: `The ${type} id token`, value: "third_party_id_token" },
|
|
3260
|
+
{ name: "Nothing (strip auth)", value: "none" }
|
|
3261
|
+
]
|
|
3262
|
+
}]);
|
|
3263
|
+
if (routing) {
|
|
3264
|
+
body.tokenType = "thirdParty";
|
|
3265
|
+
body.targetServerToken = routing;
|
|
3266
|
+
}
|
|
3267
|
+
await admin({ method: "POST", path: `${cBase}/providers`, body, summary: `Add ${type} login provider` });
|
|
3268
|
+
console.log(import_chalk24.default.green(` ${type} provider added.`));
|
|
3269
|
+
} else {
|
|
3270
|
+
const { p } = await inquirer2.prompt([{
|
|
3271
|
+
type: "list",
|
|
3272
|
+
name: "p",
|
|
3273
|
+
message: act === "rm" ? "Remove which provider?" : "Reveal which secret?",
|
|
3274
|
+
choices: [...providers.map((x) => ({ name: `${x.type} ${import_chalk24.default.dim(x.clientId || "(managed)")}`, value: x })), { name: "\u2190 Back", value: null }]
|
|
3275
|
+
}]);
|
|
3276
|
+
if (!p) continue;
|
|
3277
|
+
if (act === "rm") {
|
|
3278
|
+
const { sure } = await inquirer2.prompt([{ type: "confirm", name: "sure", message: `Remove the ${p.type} provider? Consumers using it can no longer sign in.`, default: false }]);
|
|
3279
|
+
if (!sure) continue;
|
|
3280
|
+
await admin({ method: "DELETE", path: `${cBase}/providers/${encodeURIComponent(p.id)}`, summary: `Remove ${p.type} provider` });
|
|
3281
|
+
console.log(import_chalk24.default.green(` ${p.type} removed.`));
|
|
3282
|
+
} else {
|
|
3283
|
+
const s = await admin({ method: "GET", path: `${cBase}/providers/${encodeURIComponent(p.id)}/secret`, summary: `Reveal ${p.type} provider secret` });
|
|
3284
|
+
console.log(` ${import_chalk24.default.bold("client_secret")}: ${import_chalk24.default.green(s?.clientSecret ?? s?.client_secret ?? JSON.stringify(s))}`);
|
|
3285
|
+
}
|
|
3286
|
+
}
|
|
3287
|
+
}
|
|
3288
|
+
}
|
|
3289
|
+
|
|
2705
3290
|
// src/commands/spec.ts
|
|
2706
3291
|
var fs6 = __toESM(require("fs"));
|
|
2707
|
-
var
|
|
2708
|
-
var
|
|
3292
|
+
var import_chalk25 = __toESM(require("chalk"));
|
|
3293
|
+
var import_ora11 = __toESM(require("ora"));
|
|
3294
|
+
init_admin();
|
|
2709
3295
|
async function runSpecGet(project, opts) {
|
|
2710
3296
|
const { teamId } = await resolveTeam(opts.team);
|
|
2711
3297
|
const proj2 = await resolveProject(teamId, project, opts.apiversion);
|
|
@@ -2718,19 +3304,19 @@ async function runSpecGet(project, opts) {
|
|
|
2718
3304
|
}
|
|
2719
3305
|
async function runSpecSet(project, opts) {
|
|
2720
3306
|
if (!opts.file) {
|
|
2721
|
-
console.error(
|
|
3307
|
+
console.error(import_chalk25.default.red("--file <path> is required (OpenAPI JSON or YAML)."));
|
|
2722
3308
|
process.exit(1);
|
|
2723
3309
|
}
|
|
2724
3310
|
let specContent;
|
|
2725
3311
|
try {
|
|
2726
3312
|
specContent = fs6.readFileSync(opts.file, "utf-8");
|
|
2727
3313
|
} catch {
|
|
2728
|
-
console.error(
|
|
3314
|
+
console.error(import_chalk25.default.red(`Cannot read file: ${opts.file}`));
|
|
2729
3315
|
process.exit(1);
|
|
2730
3316
|
}
|
|
2731
3317
|
const { teamId } = await resolveTeam(opts.team);
|
|
2732
3318
|
const proj2 = await resolveProject(teamId, project, opts.apiversion);
|
|
2733
|
-
const spinner = (0,
|
|
3319
|
+
const spinner = (0, import_ora11.default)("Uploading spec...").start();
|
|
2734
3320
|
try {
|
|
2735
3321
|
const out = await admin({
|
|
2736
3322
|
method: "POST",
|
|
@@ -2747,12 +3333,13 @@ async function runSpecSet(project, opts) {
|
|
|
2747
3333
|
}
|
|
2748
3334
|
|
|
2749
3335
|
// src/commands/agent.ts
|
|
2750
|
-
var
|
|
2751
|
-
var
|
|
3336
|
+
var import_chalk27 = __toESM(require("chalk"));
|
|
3337
|
+
var import_ora12 = __toESM(require("ora"));
|
|
2752
3338
|
init_auth();
|
|
2753
3339
|
|
|
2754
3340
|
// src/lib/tools.ts
|
|
2755
|
-
var
|
|
3341
|
+
var import_chalk26 = __toESM(require("chalk"));
|
|
3342
|
+
init_admin();
|
|
2756
3343
|
init_api();
|
|
2757
3344
|
async function proj(teamId, name, version2) {
|
|
2758
3345
|
return resolveProject(teamId, name, version2);
|
|
@@ -2770,15 +3357,15 @@ var TOOLS = [
|
|
|
2770
3357
|
const key = keys.dev ?? Object.values(keys)[0];
|
|
2771
3358
|
const url = `https://${a.name}.abz.run/${version2}/dev`;
|
|
2772
3359
|
const tryIt = buildTryItCurl(url, auth, key);
|
|
2773
|
-
const lines = [` ${
|
|
2774
|
-
if (res.devPortal) lines.push(` ${
|
|
3360
|
+
const lines = [` ${import_chalk26.default.dim("Proxy URL:")} ${import_chalk26.default.bold(url)}`];
|
|
3361
|
+
if (res.devPortal) lines.push(` ${import_chalk26.default.dim("Dev portal:")} ${res.devPortal}`);
|
|
2775
3362
|
const envs = Object.keys(keys);
|
|
2776
3363
|
if (envs.length) {
|
|
2777
|
-
lines.push("", ` ${
|
|
3364
|
+
lines.push("", ` ${import_chalk26.default.bold("API keys")} ${import_chalk26.default.dim("(bootstrapped \u2014 send as the X-API-Key header; shown once):")}`);
|
|
2778
3365
|
const w = Math.max(...envs.map((e) => e.length));
|
|
2779
|
-
for (const env of envs) lines.push(` ${
|
|
3366
|
+
for (const env of envs) lines.push(` ${import_chalk26.default.cyan(env.padEnd(w))} ${import_chalk26.default.green(keys[env])}`);
|
|
2780
3367
|
}
|
|
2781
|
-
if (tryIt) lines.push("", ` ${
|
|
3368
|
+
if (tryIt) lines.push("", ` ${import_chalk26.default.dim("Try it:")}`, ` ${import_chalk26.default.cyan(tryIt)}`);
|
|
2782
3369
|
return { ...res, proxy_url: url, keys, ...tryIt ? { try_it: tryIt } : {}, display: lines.join("\n") };
|
|
2783
3370
|
}
|
|
2784
3371
|
},
|
|
@@ -2903,6 +3490,7 @@ function findTool(name) {
|
|
|
2903
3490
|
}
|
|
2904
3491
|
|
|
2905
3492
|
// src/commands/agent.ts
|
|
3493
|
+
init_trace();
|
|
2906
3494
|
init_types();
|
|
2907
3495
|
var DASHBOARD_BASE5 = process.env.APIBLAZE_DASHBOARD_BASE || "https://dashboard.apiblaze.com";
|
|
2908
3496
|
var MAX_TOOL_STEPS = 6;
|
|
@@ -2933,23 +3521,23 @@ function truncate(value, max = 1500) {
|
|
|
2933
3521
|
}
|
|
2934
3522
|
function printCost(llm) {
|
|
2935
3523
|
const usd = llm.cost > 0 ? `$${llm.cost.toFixed(4)}` : "<$0.0001";
|
|
2936
|
-
console.log(
|
|
3524
|
+
console.log(import_chalk27.default.magenta(` \u{1F4B3} ${usd}`) + import_chalk27.default.dim(` (${llm.model}, ${llm.total_tokens} tok)`));
|
|
2937
3525
|
}
|
|
2938
3526
|
async function runAgent(opts) {
|
|
2939
3527
|
requireAuth();
|
|
2940
3528
|
const { teamId, teamName } = await resolveTeam(opts.team);
|
|
2941
3529
|
const { default: inquirer2 } = await import("inquirer");
|
|
2942
|
-
console.log(
|
|
2943
|
-
console.log(
|
|
3530
|
+
console.log(import_chalk27.default.bold("APIblaze agent") + import_chalk27.default.dim(` \xB7 team ${teamName ?? teamId}`));
|
|
3531
|
+
console.log(import_chalk27.default.dim('Ask me to create/delete/configure proxies, tenants, keys, domains, specs. Type "exit" to quit.\n'));
|
|
2944
3532
|
const history = [];
|
|
2945
3533
|
while (true) {
|
|
2946
|
-
const { input } = await inquirer2.prompt([{ type: "input", name: "input", message:
|
|
3534
|
+
const { input } = await inquirer2.prompt([{ type: "input", name: "input", message: import_chalk27.default.cyan("you") + " \u203A" }]);
|
|
2947
3535
|
const text = (input ?? "").trim();
|
|
2948
3536
|
if (!text) continue;
|
|
2949
3537
|
if (["exit", "quit", ":q"].includes(text.toLowerCase())) break;
|
|
2950
3538
|
history.push({ role: "user", content: text });
|
|
2951
3539
|
for (let step = 0; step < MAX_TOOL_STEPS; step++) {
|
|
2952
|
-
const spinner = (0,
|
|
3540
|
+
const spinner = (0, import_ora12.default)({ text: "thinking...", color: "magenta" }).start();
|
|
2953
3541
|
let resp;
|
|
2954
3542
|
try {
|
|
2955
3543
|
resp = await callAgent(history, teamId);
|
|
@@ -2957,21 +3545,21 @@ async function runAgent(opts) {
|
|
|
2957
3545
|
} catch (err) {
|
|
2958
3546
|
spinner.stop();
|
|
2959
3547
|
if (err instanceof ApiError && err.status === 402) {
|
|
2960
|
-
console.log(
|
|
3548
|
+
console.log(import_chalk27.default.yellow(" Insufficient credits \u2014 top up to keep using the agent."));
|
|
2961
3549
|
break;
|
|
2962
3550
|
}
|
|
2963
3551
|
throw err;
|
|
2964
3552
|
}
|
|
2965
3553
|
history.push({ role: "assistant", content: resp.raw });
|
|
2966
3554
|
printCost(resp.llm);
|
|
2967
|
-
if (resp.reply) console.log(
|
|
3555
|
+
if (resp.reply) console.log(import_chalk27.default.green("agent") + " \u203A " + resp.reply);
|
|
2968
3556
|
if (!resp.action) break;
|
|
2969
3557
|
const tool = findTool(resp.action.tool);
|
|
2970
3558
|
if (!tool) {
|
|
2971
3559
|
history.push({ role: "user", content: `TOOL_RESULT ${resp.action.tool}: error \u2014 unknown tool` });
|
|
2972
3560
|
continue;
|
|
2973
3561
|
}
|
|
2974
|
-
const runSpinner = (0,
|
|
3562
|
+
const runSpinner = (0, import_ora12.default)({ text: `running ${tool.name}...`, color: "cyan" }).start();
|
|
2975
3563
|
try {
|
|
2976
3564
|
const result = await tool.run(resp.action.args, { teamId });
|
|
2977
3565
|
runSpinner.succeed(`${tool.name} \u2713`);
|
|
@@ -2989,11 +3577,11 @@ async function runAgent(opts) {
|
|
|
2989
3577
|
}
|
|
2990
3578
|
renderTrace();
|
|
2991
3579
|
if (step === MAX_TOOL_STEPS - 1) {
|
|
2992
|
-
console.log(
|
|
3580
|
+
console.log(import_chalk27.default.dim(" (paused after several steps \u2014 tell me how to continue)"));
|
|
2993
3581
|
}
|
|
2994
3582
|
}
|
|
2995
3583
|
}
|
|
2996
|
-
console.log(
|
|
3584
|
+
console.log(import_chalk27.default.dim("\nBye."));
|
|
2997
3585
|
}
|
|
2998
3586
|
|
|
2999
3587
|
// src/commands/config-browse.ts
|
|
@@ -3140,7 +3728,7 @@ var SETTING_GROUPS = ["Basics", "Traffic & limits", "Access & auth", "Portal & M
|
|
|
3140
3728
|
var FEATURES = [
|
|
3141
3729
|
{ go: "transforms", label: "Transforms", desc: "Rewrite requests/responses (headers, body fields) without touching your upstream" },
|
|
3142
3730
|
{ go: "mappings", label: "Mapping tables", desc: "Reusable value-mapping tables used by map transforms (values can be hidden/encrypted)" },
|
|
3143
|
-
{ go: "tenants", label: "Tenants", desc: "
|
|
3731
|
+
{ go: "tenants", label: "Tenants", desc: "Consumer groups \u2014 portal, login app clients & providers, issuers, IAM, CORS" },
|
|
3144
3732
|
{ go: "domains", label: "Custom domains", desc: "Serve the proxy on your own hostname + choose what the bare URL serves" },
|
|
3145
3733
|
{ go: "spec", label: "OpenAPI spec & traffic", desc: "View the stored spec, refresh it from source, or build it from captured traffic" },
|
|
3146
3734
|
{ go: "agents", label: "AI agents", desc: "Chat to build your spec, design access rules, or publish an MCP server (billed per turn)" },
|
|
@@ -3156,11 +3744,11 @@ function dig(blob, dotted) {
|
|
|
3156
3744
|
}
|
|
3157
3745
|
var readSetting = (s, cfg) => s.read ? s.read(cfg) : dig(cfg, s.key);
|
|
3158
3746
|
function show(v) {
|
|
3159
|
-
if (v === void 0) return
|
|
3160
|
-
if (v === null) return
|
|
3161
|
-
if (typeof v === "object") return
|
|
3162
|
-
if (typeof v === "boolean") return v ?
|
|
3163
|
-
return
|
|
3747
|
+
if (v === void 0) return import_chalk28.default.dim("(unset)");
|
|
3748
|
+
if (v === null) return import_chalk28.default.dim("null");
|
|
3749
|
+
if (typeof v === "object") return import_chalk28.default.cyan(JSON.stringify(v));
|
|
3750
|
+
if (typeof v === "boolean") return v ? import_chalk28.default.green("on") : import_chalk28.default.red("off");
|
|
3751
|
+
return import_chalk28.default.cyan(String(v));
|
|
3164
3752
|
}
|
|
3165
3753
|
function parseValue(raw) {
|
|
3166
3754
|
if (raw === "true") return true;
|
|
@@ -3187,7 +3775,7 @@ async function fetchConfigBlob(proj2) {
|
|
|
3187
3775
|
}
|
|
3188
3776
|
async function patchSetting(proj2, s, value, cfg) {
|
|
3189
3777
|
const body = s.toPatch(value, cfg);
|
|
3190
|
-
const spinner = (0,
|
|
3778
|
+
const spinner = (0, import_ora13.default)(`Set ${s.key}...`).start();
|
|
3191
3779
|
try {
|
|
3192
3780
|
await admin({
|
|
3193
3781
|
method: "PATCH",
|
|
@@ -3202,10 +3790,10 @@ async function patchSetting(proj2, s, value, cfg) {
|
|
|
3202
3790
|
}
|
|
3203
3791
|
}
|
|
3204
3792
|
var loginFirst = (what) => {
|
|
3205
|
-
console.log(
|
|
3793
|
+
console.log(import_chalk28.default.yellow(`
|
|
3206
3794
|
Log in first to ${what}.`));
|
|
3207
|
-
console.log(
|
|
3208
|
-
console.log(
|
|
3795
|
+
console.log(import_chalk28.default.dim(" Run `npx apiblaze login` \u2014 or `npx apiblaze claim` if you created this proxy"));
|
|
3796
|
+
console.log(import_chalk28.default.dim(" anonymously and want to bring it into your account.\n"));
|
|
3209
3797
|
};
|
|
3210
3798
|
async function runConfig(project, key, value, opts) {
|
|
3211
3799
|
const creds = loadCredentials();
|
|
@@ -3222,9 +3810,9 @@ async function runConfig(project, key, value, opts) {
|
|
|
3222
3810
|
}
|
|
3223
3811
|
const setting = SETTINGS.find((s) => s.key === key);
|
|
3224
3812
|
if (!setting) {
|
|
3225
|
-
console.error(
|
|
3226
|
-
console.error(
|
|
3227
|
-
console.error(
|
|
3813
|
+
console.error(import_chalk28.default.red(`Unknown setting "${key}".`));
|
|
3814
|
+
console.error(import_chalk28.default.dim(" Known: " + SETTINGS.map((s) => s.key).join(", ")));
|
|
3815
|
+
console.error(import_chalk28.default.dim(" (Features like transforms/domains/tenants live in the menu: `apiblaze config <project>`.)"));
|
|
3228
3816
|
process.exit(1);
|
|
3229
3817
|
}
|
|
3230
3818
|
if (value === void 0) {
|
|
@@ -3239,7 +3827,7 @@ async function pickProject(teamId) {
|
|
|
3239
3827
|
const { getProjects: getProjects2 } = await Promise.resolve().then(() => (init_api(), api_exports));
|
|
3240
3828
|
const projects = await getProjects2(teamId).catch(() => []);
|
|
3241
3829
|
if (!projects.length) {
|
|
3242
|
-
console.error(
|
|
3830
|
+
console.error(import_chalk28.default.red("No projects in this team. Create one: `npx apiblaze create`."));
|
|
3243
3831
|
process.exit(1);
|
|
3244
3832
|
}
|
|
3245
3833
|
const { default: inquirer2 } = await import("inquirer");
|
|
@@ -3247,7 +3835,7 @@ async function pickProject(teamId) {
|
|
|
3247
3835
|
type: "list",
|
|
3248
3836
|
name: "picked",
|
|
3249
3837
|
message: "Which project?",
|
|
3250
|
-
choices: projects.map((p) => ({ name: `${p.projectName} ${
|
|
3838
|
+
choices: projects.map((p) => ({ name: `${p.projectName} ${import_chalk28.default.dim("v" + p.apiVersion)}`, value: p }))
|
|
3251
3839
|
}]);
|
|
3252
3840
|
return { projectId: picked.projectId, projectName: picked.projectName, apiVersion: picked.apiVersion, teamId, tenant: picked.tenant };
|
|
3253
3841
|
}
|
|
@@ -3258,25 +3846,25 @@ function printAll(proj2, cfg, json) {
|
|
|
3258
3846
|
console.log(JSON.stringify(out, null, 2));
|
|
3259
3847
|
return;
|
|
3260
3848
|
}
|
|
3261
|
-
console.log(
|
|
3849
|
+
console.log(import_chalk28.default.bold(`
|
|
3262
3850
|
${proj2.projectName} v${proj2.apiVersion} \u2014 settings
|
|
3263
3851
|
`));
|
|
3264
3852
|
for (const group of SETTING_GROUPS) {
|
|
3265
|
-
console.log(
|
|
3853
|
+
console.log(import_chalk28.default.bold(group));
|
|
3266
3854
|
for (const s of SETTINGS.filter((x) => x.group === group)) {
|
|
3267
|
-
console.log(` ${s.key.padEnd(32)} ${show(readSetting(s, cfg))} ${
|
|
3855
|
+
console.log(` ${s.key.padEnd(32)} ${show(readSetting(s, cfg))} ${import_chalk28.default.dim(s.desc)}`);
|
|
3268
3856
|
}
|
|
3269
3857
|
console.log();
|
|
3270
3858
|
}
|
|
3271
|
-
console.log(
|
|
3859
|
+
console.log(import_chalk28.default.dim("Change one: apiblaze config " + proj2.projectName + " <key> <value> (add --verbose for the API call)"));
|
|
3272
3860
|
}
|
|
3273
3861
|
async function discoveryMenu(project) {
|
|
3274
3862
|
const { default: inquirer2 } = await import("inquirer");
|
|
3275
|
-
console.log(
|
|
3863
|
+
console.log(import_chalk28.default.bold(`
|
|
3276
3864
|
APIblaze proxy configuration${project ? ` \u2014 ${project}` : ""}
|
|
3277
3865
|
`));
|
|
3278
|
-
console.log(
|
|
3279
|
-
console.log(
|
|
3866
|
+
console.log(import_chalk28.default.dim("You are not logged in \u2014 browsing what's configurable. Everything below works"));
|
|
3867
|
+
console.log(import_chalk28.default.dim("from this menu once you log in (`npx apiblaze login`).\n"));
|
|
3280
3868
|
for (; ; ) {
|
|
3281
3869
|
const { pick: pick2 } = await inquirer2.prompt([{
|
|
3282
3870
|
type: "list",
|
|
@@ -3284,13 +3872,13 @@ APIblaze proxy configuration${project ? ` \u2014 ${project}` : ""}
|
|
|
3284
3872
|
message: "Explore:",
|
|
3285
3873
|
pageSize: 20,
|
|
3286
3874
|
choices: [
|
|
3287
|
-
new inquirer2.Separator(
|
|
3875
|
+
new inquirer2.Separator(import_chalk28.default.bold("\u2014 Settings \u2014")),
|
|
3288
3876
|
...SETTING_GROUPS.map((g) => ({
|
|
3289
|
-
name: `${g} ${
|
|
3877
|
+
name: `${g} ${import_chalk28.default.dim(SETTINGS.filter((s) => s.group === g).map((s) => s.label).join(", "))}`,
|
|
3290
3878
|
value: { kind: "settings", g }
|
|
3291
3879
|
})),
|
|
3292
|
-
new inquirer2.Separator(
|
|
3293
|
-
...FEATURES.map((f) => ({ name: `${f.label} ${
|
|
3880
|
+
new inquirer2.Separator(import_chalk28.default.bold("\u2014 Features \u2014")),
|
|
3881
|
+
...FEATURES.map((f) => ({ name: `${f.label} ${import_chalk28.default.dim(f.desc)}`, value: { kind: "feature", f } })),
|
|
3294
3882
|
new inquirer2.Separator(),
|
|
3295
3883
|
{ name: "Exit", value: { kind: "exit" } }
|
|
3296
3884
|
]
|
|
@@ -3299,24 +3887,24 @@ APIblaze proxy configuration${project ? ` \u2014 ${project}` : ""}
|
|
|
3299
3887
|
if (pick2.kind === "settings") {
|
|
3300
3888
|
console.log();
|
|
3301
3889
|
for (const s of SETTINGS.filter((x) => x.group === pick2.g)) {
|
|
3302
|
-
console.log(` ${
|
|
3303
|
-
console.log(` ${
|
|
3890
|
+
console.log(` ${import_chalk28.default.bold(s.label.padEnd(28))} ${import_chalk28.default.dim(s.desc)}`);
|
|
3891
|
+
console.log(` ${import_chalk28.default.dim(" key: " + s.key)}`);
|
|
3304
3892
|
}
|
|
3305
3893
|
loginFirst("view or change these settings");
|
|
3306
3894
|
} else {
|
|
3307
3895
|
const f = pick2.f;
|
|
3308
3896
|
console.log(`
|
|
3309
|
-
${
|
|
3897
|
+
${import_chalk28.default.bold(f.label)} \u2014 ${f.desc}`);
|
|
3310
3898
|
loginFirst(`use ${f.label.toLowerCase()}`);
|
|
3311
3899
|
}
|
|
3312
3900
|
}
|
|
3313
3901
|
}
|
|
3314
3902
|
async function navigator(proj2, cfg, opts) {
|
|
3315
3903
|
const { default: inquirer2 } = await import("inquirer");
|
|
3316
|
-
console.log(
|
|
3904
|
+
console.log(import_chalk28.default.bold(`
|
|
3317
3905
|
${proj2.projectName} v${proj2.apiVersion} \u2014 configuration
|
|
3318
3906
|
`));
|
|
3319
|
-
console.log(
|
|
3907
|
+
console.log(import_chalk28.default.dim("Tip: every change is one API call \u2014 add --verbose to see the curl equivalent.\n"));
|
|
3320
3908
|
let blob = cfg;
|
|
3321
3909
|
for (; ; ) {
|
|
3322
3910
|
const { pick: pick2 } = await inquirer2.prompt([{
|
|
@@ -3325,10 +3913,10 @@ ${proj2.projectName} v${proj2.apiVersion} \u2014 configuration
|
|
|
3325
3913
|
message: "Where to?",
|
|
3326
3914
|
pageSize: 20,
|
|
3327
3915
|
choices: [
|
|
3328
|
-
new inquirer2.Separator(
|
|
3916
|
+
new inquirer2.Separator(import_chalk28.default.bold("\u2014 Settings \u2014")),
|
|
3329
3917
|
...SETTING_GROUPS.map((g) => ({ name: g, value: { kind: "settings", g } })),
|
|
3330
|
-
new inquirer2.Separator(
|
|
3331
|
-
...FEATURES.map((f) => ({ name: `${f.label} ${
|
|
3918
|
+
new inquirer2.Separator(import_chalk28.default.bold("\u2014 Features \u2014")),
|
|
3919
|
+
...FEATURES.map((f) => ({ name: `${f.label} ${import_chalk28.default.dim(f.desc)}`, value: { kind: f.go } })),
|
|
3332
3920
|
new inquirer2.Separator(),
|
|
3333
3921
|
{ name: "Show all settings", value: { kind: "list" } },
|
|
3334
3922
|
{ name: "Exit", value: { kind: "exit" } }
|
|
@@ -3368,7 +3956,7 @@ ${proj2.projectName} v${proj2.apiVersion} \u2014 configuration
|
|
|
3368
3956
|
}
|
|
3369
3957
|
}
|
|
3370
3958
|
} catch (err) {
|
|
3371
|
-
console.error(
|
|
3959
|
+
console.error(import_chalk28.default.red(` ${err instanceof Error ? err.message : String(err)}`));
|
|
3372
3960
|
}
|
|
3373
3961
|
}
|
|
3374
3962
|
}
|
|
@@ -3382,7 +3970,7 @@ async function settingsGroup(proj2, cfg, group) {
|
|
|
3382
3970
|
message: group + ":",
|
|
3383
3971
|
pageSize: 16,
|
|
3384
3972
|
choices: [
|
|
3385
|
-
...items.map((s2) => ({ name: `${s2.label.padEnd(30)} ${show(readSetting(s2, cfg))} ${
|
|
3973
|
+
...items.map((s2) => ({ name: `${s2.label.padEnd(30)} ${show(readSetting(s2, cfg))} ${import_chalk28.default.dim(s2.desc)}`, value: s2 })),
|
|
3386
3974
|
new inquirer2.Separator(),
|
|
3387
3975
|
{ name: "\u2190 Back", value: null }
|
|
3388
3976
|
]
|
|
@@ -3400,7 +3988,7 @@ async function settingsGroup(proj2, cfg, group) {
|
|
|
3400
3988
|
} else if (s.type === "number") {
|
|
3401
3989
|
const { v } = await inquirer2.prompt([{ type: "input", name: "v", message: `${s.label} (number):`, default: readSetting(s, cfg) }]);
|
|
3402
3990
|
if (v === "" || Number.isNaN(Number(v))) {
|
|
3403
|
-
console.log(
|
|
3991
|
+
console.log(import_chalk28.default.yellow(" Not a number \u2014 unchanged."));
|
|
3404
3992
|
continue;
|
|
3405
3993
|
}
|
|
3406
3994
|
value = Number(v);
|
|
@@ -3418,6 +4006,102 @@ async function settingsGroup(proj2, cfg, group) {
|
|
|
3418
4006
|
cfg = await fetchConfigBlob(proj2);
|
|
3419
4007
|
}
|
|
3420
4008
|
}
|
|
4009
|
+
var FN_CHOICES = [
|
|
4010
|
+
{ name: "uppercase", value: "uppercase" },
|
|
4011
|
+
{ name: "lowercase", value: "lowercase" },
|
|
4012
|
+
{ name: "trim", value: "trim" },
|
|
4013
|
+
{ name: "url_encode", value: "url_encode" },
|
|
4014
|
+
{ name: "url_decode", value: "url_decode" },
|
|
4015
|
+
{ name: "base64url_encode", value: "base64url_encode" },
|
|
4016
|
+
{ name: "base64url_decode", value: "base64url_decode" },
|
|
4017
|
+
{ name: "hash \u2014 sha256/md5 digest", value: "hash" },
|
|
4018
|
+
{ name: "regex_extract \u2014 pull out a capture group", value: "regex_extract" },
|
|
4019
|
+
{ name: "concat \u2014 prepend/append text", value: "concat" }
|
|
4020
|
+
];
|
|
4021
|
+
async function buildFns(which) {
|
|
4022
|
+
const { default: inquirer2 } = await import("inquirer");
|
|
4023
|
+
const fns = [];
|
|
4024
|
+
for (; ; ) {
|
|
4025
|
+
const { fn } = await inquirer2.prompt([{
|
|
4026
|
+
type: "list",
|
|
4027
|
+
name: "fn",
|
|
4028
|
+
message: fns.length ? `${which}: ${fns.map((f) => f.fn).join(" \u2192 ")} \u2014 add another?` : `${which} \u2014 add a function?`,
|
|
4029
|
+
choices: [{ name: fns.length ? "\u2713 Done" : "No functions", value: null }, ...FN_CHOICES]
|
|
4030
|
+
}]);
|
|
4031
|
+
if (!fn) return fns;
|
|
4032
|
+
if (fn === "hash") {
|
|
4033
|
+
const { algorithm } = await inquirer2.prompt([{ type: "list", name: "algorithm", message: "Algorithm:", choices: ["sha256", "md5"] }]);
|
|
4034
|
+
fns.push({ fn, algorithm });
|
|
4035
|
+
} else if (fn === "regex_extract") {
|
|
4036
|
+
const a = await inquirer2.prompt([
|
|
4037
|
+
{ type: "input", name: "pattern", message: "Regex pattern:", validate: (s) => {
|
|
4038
|
+
try {
|
|
4039
|
+
new RegExp(s);
|
|
4040
|
+
return true;
|
|
4041
|
+
} catch {
|
|
4042
|
+
return "invalid regex";
|
|
4043
|
+
}
|
|
4044
|
+
} },
|
|
4045
|
+
{ type: "input", name: "group", message: "Capture group (default 1):", default: "1" }
|
|
4046
|
+
]);
|
|
4047
|
+
fns.push({ fn, pattern: a.pattern, ...Number(a.group) !== 1 ? { group: Number(a.group) } : {} });
|
|
4048
|
+
} else if (fn === "concat") {
|
|
4049
|
+
const a = await inquirer2.prompt([
|
|
4050
|
+
{ type: "input", name: "before", message: "Text before (empty = none):" },
|
|
4051
|
+
{ type: "input", name: "after", message: "Text after (empty = none):" }
|
|
4052
|
+
]);
|
|
4053
|
+
fns.push({ fn, ...a.before ? { before: a.before } : {}, ...a.after ? { after: a.after } : {} });
|
|
4054
|
+
} else {
|
|
4055
|
+
fns.push({ fn });
|
|
4056
|
+
}
|
|
4057
|
+
}
|
|
4058
|
+
}
|
|
4059
|
+
async function buildCondition(phase) {
|
|
4060
|
+
const { default: inquirer2 } = await import("inquirer");
|
|
4061
|
+
const { want } = await inquirer2.prompt([{
|
|
4062
|
+
type: "confirm",
|
|
4063
|
+
name: "want",
|
|
4064
|
+
default: false,
|
|
4065
|
+
message: "Only apply when a condition matches?"
|
|
4066
|
+
}]);
|
|
4067
|
+
if (!want) return void 0;
|
|
4068
|
+
const srcHint = phase === "response" ? "(header:x-foo, bodyvar:user.id, status:)" : "(header:x-foo, param:limit, bodyvar:user.id, jwt:sub)";
|
|
4069
|
+
const items = [];
|
|
4070
|
+
for (; ; ) {
|
|
4071
|
+
const a = await inquirer2.prompt([
|
|
4072
|
+
{ type: "input", name: "source", message: `Condition field ${import_chalk28.default.dim(srcHint)}:`, validate: (s) => !!s || "required" },
|
|
4073
|
+
{ type: "list", name: "operator", message: "Operator:", choices: [
|
|
4074
|
+
"eq",
|
|
4075
|
+
"neq",
|
|
4076
|
+
"contains",
|
|
4077
|
+
"starts_with",
|
|
4078
|
+
"ends_with",
|
|
4079
|
+
"exists",
|
|
4080
|
+
"not_exists",
|
|
4081
|
+
"regex"
|
|
4082
|
+
] }
|
|
4083
|
+
]);
|
|
4084
|
+
let value;
|
|
4085
|
+
if (a.operator !== "exists" && a.operator !== "not_exists") {
|
|
4086
|
+
const v = await inquirer2.prompt([{ type: "input", name: "value", message: a.operator === "regex" ? "Pattern:" : "Value:" }]);
|
|
4087
|
+
value = v.value;
|
|
4088
|
+
}
|
|
4089
|
+
items.push({ id: `c${items.length + 1}`, openParen: false, closeParen: false, source: a.source, operator: a.operator, ...value !== void 0 ? { value } : {} });
|
|
4090
|
+
const { more } = await inquirer2.prompt([{
|
|
4091
|
+
type: "list",
|
|
4092
|
+
name: "more",
|
|
4093
|
+
message: "Combine with another condition?",
|
|
4094
|
+
choices: [{ name: "\u2713 Done", value: null }, { name: "AND \u2026", value: "AND" }, { name: "OR \u2026", value: "OR" }]
|
|
4095
|
+
}]);
|
|
4096
|
+
if (!more) return items;
|
|
4097
|
+
items[items.length - 1].logicOp = more;
|
|
4098
|
+
}
|
|
4099
|
+
}
|
|
4100
|
+
function showCondition(cond) {
|
|
4101
|
+
if (!Array.isArray(cond) || !cond.length) return "";
|
|
4102
|
+
const s = cond.map((c) => `${c.source} ${c.operator}${c.value !== void 0 ? ` "${c.value}"` : ""}${c.logicOp ? ` ${c.logicOp}` : ""}`).join(" ");
|
|
4103
|
+
return import_chalk28.default.dim(` when ${s}`);
|
|
4104
|
+
}
|
|
3421
4105
|
async function transformsMenu(proj2) {
|
|
3422
4106
|
const { default: inquirer2 } = await import("inquirer");
|
|
3423
4107
|
const base = `/projects/${proj2.projectId}/${proj2.apiVersion}/transforms`;
|
|
@@ -3425,11 +4109,12 @@ async function transformsMenu(proj2) {
|
|
|
3425
4109
|
const out = await admin({ method: "GET", path: base, summary: "List transform rules" });
|
|
3426
4110
|
const rules = out?.rules ?? [];
|
|
3427
4111
|
console.log();
|
|
3428
|
-
if (!rules.length) console.log(
|
|
4112
|
+
if (!rules.length) console.log(import_chalk28.default.dim(" No transform rules yet."));
|
|
3429
4113
|
for (const r of rules) {
|
|
3430
4114
|
const a = r.action ?? {};
|
|
3431
|
-
const
|
|
3432
|
-
|
|
4115
|
+
const fns = [...a.source_fns ?? [], ...a.dest_fns ?? []].map((f) => f.fn);
|
|
4116
|
+
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_chalk28.default.dim(` via ${fns.join("\u2192")}`) : ""}`;
|
|
4117
|
+
console.log(` ${r.enabled ? import_chalk28.default.green("\u25CF") : import_chalk28.default.dim("\u25CB")} ${import_chalk28.default.bold(r.name)} ${import_chalk28.default.dim(`[${r.phase ?? "request"}]`)} ${what}${showCondition(r.condition)}`);
|
|
3433
4118
|
}
|
|
3434
4119
|
const { act } = await inquirer2.prompt([{
|
|
3435
4120
|
type: "list",
|
|
@@ -3441,10 +4126,26 @@ async function transformsMenu(proj2) {
|
|
|
3441
4126
|
{ name: "Enable/disable a rule", value: "toggle" },
|
|
3442
4127
|
{ name: "Delete a rule", value: "delete" }
|
|
3443
4128
|
] : [],
|
|
4129
|
+
{ name: import_chalk28.default.dim("Add from raw JSON (grouped conditions, lookup tables, \u2026)"), value: "raw" },
|
|
3444
4130
|
{ name: "\u2190 Back", value: "back" }
|
|
3445
4131
|
]
|
|
3446
4132
|
}]);
|
|
3447
4133
|
if (act === "back") return;
|
|
4134
|
+
if (act === "raw") {
|
|
4135
|
+
const { raw } = await inquirer2.prompt([{
|
|
4136
|
+
type: "input",
|
|
4137
|
+
name: "raw",
|
|
4138
|
+
message: "Rule JSON ({name, phase, enabled, action, condition?}):"
|
|
4139
|
+
}]);
|
|
4140
|
+
const body = parseValue(raw);
|
|
4141
|
+
if (!body || typeof body !== "object" || Array.isArray(body)) {
|
|
4142
|
+
console.log(import_chalk28.default.yellow(" Not a JSON object \u2014 skipped."));
|
|
4143
|
+
continue;
|
|
4144
|
+
}
|
|
4145
|
+
await admin({ method: "POST", path: base, body, summary: "Create transform rule (raw JSON)" });
|
|
4146
|
+
console.log(import_chalk28.default.green(" Rule created."));
|
|
4147
|
+
continue;
|
|
4148
|
+
}
|
|
3448
4149
|
if (act === "add") {
|
|
3449
4150
|
const ans = await inquirer2.prompt([
|
|
3450
4151
|
{ type: "input", name: "name", message: "Rule name:", validate: (s) => !!s || "required" },
|
|
@@ -3458,7 +4159,7 @@ async function transformsMenu(proj2) {
|
|
|
3458
4159
|
{ name: "Remove a field", value: "remove" }
|
|
3459
4160
|
] }
|
|
3460
4161
|
]);
|
|
3461
|
-
const fieldHint =
|
|
4162
|
+
const fieldHint = import_chalk28.default.dim("(e.g. header:x-api-version, param:limit, bodyvar:user.id)");
|
|
3462
4163
|
let action2;
|
|
3463
4164
|
if (ans.kind === "hardcode") {
|
|
3464
4165
|
const a = await inquirer2.prompt([
|
|
@@ -3477,11 +4178,26 @@ async function transformsMenu(proj2) {
|
|
|
3477
4178
|
{ type: "input", name: "destination", message: `Destination field ${fieldHint}:`, validate: (s) => !!s || "required" },
|
|
3478
4179
|
{ type: "confirm", name: "strip", message: "Remove the source field after copying?", default: false }
|
|
3479
4180
|
]);
|
|
3480
|
-
|
|
4181
|
+
const source_fns = await buildFns("Transform the value as it is READ (source functions)");
|
|
4182
|
+
const dest_fns = await buildFns("Transform the value as it is WRITTEN (destination functions)");
|
|
4183
|
+
action2 = {
|
|
4184
|
+
type: "copy",
|
|
4185
|
+
source: a.source,
|
|
4186
|
+
destination: a.destination,
|
|
4187
|
+
...a.strip ? { strip_source: true } : {},
|
|
4188
|
+
...source_fns.length ? { source_fns } : {},
|
|
4189
|
+
...dest_fns.length ? { dest_fns } : {}
|
|
4190
|
+
};
|
|
3481
4191
|
}
|
|
3482
|
-
const
|
|
4192
|
+
const condition = await buildCondition(ans.phase);
|
|
4193
|
+
const spinner = (0, import_ora13.default)("Creating rule...").start();
|
|
3483
4194
|
try {
|
|
3484
|
-
await admin({
|
|
4195
|
+
await admin({
|
|
4196
|
+
method: "POST",
|
|
4197
|
+
path: base,
|
|
4198
|
+
body: { name: ans.name, phase: ans.phase, enabled: true, action: action2, ...condition ? { condition } : {} },
|
|
4199
|
+
summary: `Create transform "${ans.name}"`
|
|
4200
|
+
});
|
|
3485
4201
|
spinner.succeed(`Rule "${ans.name}" created.`);
|
|
3486
4202
|
} catch (err) {
|
|
3487
4203
|
spinner.fail("Create failed.");
|
|
@@ -3492,16 +4208,16 @@ async function transformsMenu(proj2) {
|
|
|
3492
4208
|
type: "list",
|
|
3493
4209
|
name: "rule",
|
|
3494
4210
|
message: act === "toggle" ? "Which rule?" : "Delete which rule?",
|
|
3495
|
-
choices: [...rules.map((r) => ({ name: `${r.name} ${
|
|
4211
|
+
choices: [...rules.map((r) => ({ name: `${r.name} ${import_chalk28.default.dim(`[${r.phase ?? "request"}]`)}`, value: r })), { name: "\u2190 Back", value: null }]
|
|
3496
4212
|
}]);
|
|
3497
4213
|
if (!rule) continue;
|
|
3498
4214
|
if (act === "toggle") {
|
|
3499
4215
|
const flipped = { ...rule, enabled: rule.enabled === false };
|
|
3500
4216
|
await admin({ method: "PUT", path: `${base}/${rule.id}`, body: flipped, summary: `${flipped.enabled ? "Enable" : "Disable"} transform "${rule.name}"` });
|
|
3501
|
-
console.log(
|
|
4217
|
+
console.log(import_chalk28.default.green(` ${rule.name} \u2192 ${flipped.enabled ? "enabled" : "disabled"}`));
|
|
3502
4218
|
} else {
|
|
3503
4219
|
await admin({ method: "DELETE", path: `${base}/${rule.id}`, summary: `Delete transform "${rule.name}"` });
|
|
3504
|
-
console.log(
|
|
4220
|
+
console.log(import_chalk28.default.green(` ${rule.name} deleted.`));
|
|
3505
4221
|
}
|
|
3506
4222
|
}
|
|
3507
4223
|
}
|
|
@@ -3513,9 +4229,9 @@ async function mappingsMenu(proj2) {
|
|
|
3513
4229
|
const out = await admin({ method: "GET", path: base, summary: "List mapping tables" });
|
|
3514
4230
|
const tables = out?.mappings ?? out?.tables ?? [];
|
|
3515
4231
|
console.log();
|
|
3516
|
-
if (!tables.length) console.log(
|
|
4232
|
+
if (!tables.length) console.log(import_chalk28.default.dim(" No mapping tables yet."));
|
|
3517
4233
|
for (const t of tables) {
|
|
3518
|
-
console.log(` ${
|
|
4234
|
+
console.log(` ${import_chalk28.default.bold(t.name)} ${import_chalk28.default.dim(`${t.entries?.length ?? "?"} entries${t.hide_map_values ? ", hidden" : ""}${t.encrypt_values ? ", encrypted" : ""}`)}`);
|
|
3519
4235
|
}
|
|
3520
4236
|
const { act } = await inquirer2.prompt([{
|
|
3521
4237
|
type: "list",
|
|
@@ -3535,11 +4251,11 @@ async function mappingsMenu(proj2) {
|
|
|
3535
4251
|
]);
|
|
3536
4252
|
const entries2 = parseValue(a.entries);
|
|
3537
4253
|
if (!Array.isArray(entries2)) {
|
|
3538
|
-
console.log(
|
|
4254
|
+
console.log(import_chalk28.default.yellow(" Entries must be a JSON array \u2014 not created."));
|
|
3539
4255
|
continue;
|
|
3540
4256
|
}
|
|
3541
4257
|
await admin({ method: "POST", path: base, body: { name: a.name, entries: entries2 }, summary: `Create mapping table "${a.name}"` });
|
|
3542
|
-
console.log(
|
|
4258
|
+
console.log(import_chalk28.default.green(` Table "${a.name}" created.`));
|
|
3543
4259
|
} else {
|
|
3544
4260
|
const { table } = await inquirer2.prompt([{
|
|
3545
4261
|
type: "list",
|
|
@@ -3549,7 +4265,7 @@ async function mappingsMenu(proj2) {
|
|
|
3549
4265
|
}]);
|
|
3550
4266
|
if (!table) continue;
|
|
3551
4267
|
await admin({ method: "DELETE", path: `${base}/${table.id}`, summary: `Delete mapping table "${table.name}"` });
|
|
3552
|
-
console.log(
|
|
4268
|
+
console.log(import_chalk28.default.green(` ${table.name} deleted.`));
|
|
3553
4269
|
}
|
|
3554
4270
|
}
|
|
3555
4271
|
}
|
|
@@ -3560,22 +4276,22 @@ async function tenantsMenu(proj2, opts) {
|
|
|
3560
4276
|
const out = await admin({ method: "GET", path: base, summary: "List attached tenants" });
|
|
3561
4277
|
const tenants = out?.tenants ?? [];
|
|
3562
4278
|
console.log();
|
|
3563
|
-
if (!tenants.length) console.log(
|
|
3564
|
-
for (const t of tenants) console.log(` ${
|
|
4279
|
+
if (!tenants.length) console.log(import_chalk28.default.dim(" No tenants attached (consumers use the default tenant)."));
|
|
4280
|
+
for (const t of tenants) console.log(` ${import_chalk28.default.bold(t.tenant_name ?? t.name)} ${import_chalk28.default.dim(t.display_name ?? "")}`);
|
|
3565
4281
|
const { act } = await inquirer2.prompt([{
|
|
3566
4282
|
type: "list",
|
|
3567
4283
|
name: "act",
|
|
3568
4284
|
message: "Tenants:",
|
|
3569
4285
|
choices: [
|
|
3570
|
-
{ name:
|
|
3571
|
-
|
|
3572
|
-
{ name:
|
|
4286
|
+
{ name: `Manage a tenant\u2026 ${import_chalk28.default.dim("settings, login app clients, providers, issuers \u2014 affects EVERY proxy the tenant serves")}`, value: "manage" },
|
|
4287
|
+
{ name: "Attach a tenant to this project", value: "attach" },
|
|
4288
|
+
...tenants.length ? [{ name: "Detach a tenant from this project", value: "detach" }] : [],
|
|
3573
4289
|
{ name: "\u2190 Back", value: "back" }
|
|
3574
4290
|
]
|
|
3575
4291
|
}]);
|
|
3576
4292
|
if (act === "back") return;
|
|
3577
|
-
if (act === "
|
|
3578
|
-
|
|
4293
|
+
if (act === "manage") {
|
|
4294
|
+
await runTenantManage(void 0, { team: opts.team });
|
|
3579
4295
|
continue;
|
|
3580
4296
|
}
|
|
3581
4297
|
if (act === "attach") {
|
|
@@ -3590,7 +4306,7 @@ async function tenantsMenu(proj2, opts) {
|
|
|
3590
4306
|
}]);
|
|
3591
4307
|
if (!t) continue;
|
|
3592
4308
|
await admin({ method: "DELETE", path: `${base}/${encodeURIComponent(t.tenant_name ?? t.name)}`, summary: `Detach tenant ${t.tenant_name ?? t.name}` });
|
|
3593
|
-
console.log(
|
|
4309
|
+
console.log(import_chalk28.default.green(` Detached ${t.tenant_name ?? t.name}.`));
|
|
3594
4310
|
}
|
|
3595
4311
|
}
|
|
3596
4312
|
}
|
|
@@ -3633,7 +4349,7 @@ async function specMenu(proj2, opts) {
|
|
|
3633
4349
|
choices: [
|
|
3634
4350
|
{ name: "Print the stored spec", value: "get" },
|
|
3635
4351
|
{ name: "Refresh the spec from its source", value: "refresh" },
|
|
3636
|
-
{ name:
|
|
4352
|
+
{ name: import_chalk28.default.dim("Build the spec by chatting over real traffic \u2192 agent"), value: "agent" },
|
|
3637
4353
|
{ name: "\u2190 Back", value: "back" }
|
|
3638
4354
|
]
|
|
3639
4355
|
}]);
|
|
@@ -3641,7 +4357,7 @@ async function specMenu(proj2, opts) {
|
|
|
3641
4357
|
if (act === "get") await runSpecGet(proj2.projectName, { team: opts.team, apiversion: proj2.apiVersion });
|
|
3642
4358
|
else if (act === "refresh") {
|
|
3643
4359
|
await admin({ method: "POST", path: `/projects/${proj2.projectId}/${proj2.apiVersion}/refresh-spec`, summary: "Refresh spec from source" });
|
|
3644
|
-
console.log(
|
|
4360
|
+
console.log(import_chalk28.default.green(" Spec refresh triggered."));
|
|
3645
4361
|
} else await runOpenapi(proj2.projectName, proj2.apiVersion);
|
|
3646
4362
|
}
|
|
3647
4363
|
async function agentsMenu(proj2, opts) {
|
|
@@ -3666,8 +4382,9 @@ async function agentsMenu(proj2, opts) {
|
|
|
3666
4382
|
}
|
|
3667
4383
|
|
|
3668
4384
|
// src/commands/key.ts
|
|
3669
|
-
var
|
|
3670
|
-
var
|
|
4385
|
+
var import_chalk29 = __toESM(require("chalk"));
|
|
4386
|
+
var import_ora14 = __toESM(require("ora"));
|
|
4387
|
+
init_admin();
|
|
3671
4388
|
async function runApikeysMenu(opts) {
|
|
3672
4389
|
await runKeyList(opts);
|
|
3673
4390
|
if (opts.json) return;
|
|
@@ -3694,11 +4411,11 @@ async function runKeyList(opts) {
|
|
|
3694
4411
|
return;
|
|
3695
4412
|
}
|
|
3696
4413
|
if (!keys.length) {
|
|
3697
|
-
console.log(
|
|
4414
|
+
console.log(import_chalk29.default.yellow("No developer keys."));
|
|
3698
4415
|
return;
|
|
3699
4416
|
}
|
|
3700
4417
|
for (const k of keys) {
|
|
3701
|
-
console.log(` ${
|
|
4418
|
+
console.log(` ${import_chalk29.default.bold(k.key_id ?? k.id)} ${import_chalk29.default.dim(k.description ?? "")} ${import_chalk29.default.dim(k.expires_at ?? "no expiry")}`);
|
|
3702
4419
|
}
|
|
3703
4420
|
}
|
|
3704
4421
|
async function runKeyMint(opts) {
|
|
@@ -3706,7 +4423,7 @@ async function runKeyMint(opts) {
|
|
|
3706
4423
|
const body = { role: "consumer-admin" };
|
|
3707
4424
|
if (opts.desc) body.description = opts.desc;
|
|
3708
4425
|
if (opts.expiresDays) body.expires_in_seconds = Number(opts.expiresDays) * 24 * 60 * 60;
|
|
3709
|
-
const spinner = (0,
|
|
4426
|
+
const spinner = (0, import_ora14.default)("Minting key...").start();
|
|
3710
4427
|
try {
|
|
3711
4428
|
const out = await admin({
|
|
3712
4429
|
method: "POST",
|
|
@@ -3719,9 +4436,9 @@ async function runKeyMint(opts) {
|
|
|
3719
4436
|
console.log(JSON.stringify(out));
|
|
3720
4437
|
return;
|
|
3721
4438
|
}
|
|
3722
|
-
console.log(` ${
|
|
3723
|
-
console.log(` ${
|
|
3724
|
-
if (out?.expires_at) console.log(` ${
|
|
4439
|
+
console.log(` ${import_chalk29.default.bold("key_id")}: ${out?.key_id}`);
|
|
4440
|
+
console.log(` ${import_chalk29.default.bold("key")}: ${import_chalk29.default.green(out?.key)} ${import_chalk29.default.dim("(shown once \u2014 store it now)")}`);
|
|
4441
|
+
if (out?.expires_at) console.log(` ${import_chalk29.default.dim("expires:")} ${out.expires_at}`);
|
|
3725
4442
|
} catch (err) {
|
|
3726
4443
|
spinner.fail("Mint failed.");
|
|
3727
4444
|
throw err;
|
|
@@ -3729,7 +4446,7 @@ async function runKeyMint(opts) {
|
|
|
3729
4446
|
}
|
|
3730
4447
|
async function runKeyRevoke(keyId, opts) {
|
|
3731
4448
|
const { teamId } = await resolveTeam(opts.team);
|
|
3732
|
-
const spinner = (0,
|
|
4449
|
+
const spinner = (0, import_ora14.default)("Revoking key...").start();
|
|
3733
4450
|
try {
|
|
3734
4451
|
await admin({
|
|
3735
4452
|
method: "DELETE",
|
|
@@ -3744,8 +4461,9 @@ async function runKeyRevoke(keyId, opts) {
|
|
|
3744
4461
|
}
|
|
3745
4462
|
|
|
3746
4463
|
// src/commands/consumer.ts
|
|
3747
|
-
var
|
|
3748
|
-
var
|
|
4464
|
+
var import_chalk30 = __toESM(require("chalk"));
|
|
4465
|
+
var import_ora15 = __toESM(require("ora"));
|
|
4466
|
+
init_admin();
|
|
3749
4467
|
var DEFAULT_SCOPE = "openid email profile offline_access";
|
|
3750
4468
|
var APIKEYS_BASE = process.env.APIBLAZE_APIKEYS_BASE || "https://apikeys.apiblaze.com";
|
|
3751
4469
|
async function consumerFetch(creds, suffix, init) {
|
|
@@ -3764,7 +4482,7 @@ async function consumerFetch(creds, suffix, init) {
|
|
|
3764
4482
|
function requireConsumer() {
|
|
3765
4483
|
const c = loadConsumer();
|
|
3766
4484
|
if (!c) {
|
|
3767
|
-
console.error(
|
|
4485
|
+
console.error(import_chalk30.default.red("Not logged in as a consumer. Run `apiblaze consumer login` first."));
|
|
3768
4486
|
process.exit(1);
|
|
3769
4487
|
}
|
|
3770
4488
|
return c;
|
|
@@ -3775,48 +4493,37 @@ async function runConsumerLogin(opts) {
|
|
|
3775
4493
|
let clientId = opts.client;
|
|
3776
4494
|
if (clientId) {
|
|
3777
4495
|
if (!tenant2) {
|
|
3778
|
-
console.error(
|
|
4496
|
+
console.error(import_chalk30.default.red("When using --client, also pass --tenant <slug> (it sets which portal/keys host to use)."));
|
|
3779
4497
|
process.exit(1);
|
|
3780
4498
|
}
|
|
3781
4499
|
} else {
|
|
3782
4500
|
requireAuth();
|
|
3783
4501
|
const { teamId, teamName } = await resolveTeam(opts.team);
|
|
3784
|
-
const spinner = (0, import_ora13.default)("Loading your tenants...").start();
|
|
3785
|
-
const tdata = await admin({ method: "GET", path: `/teams/${encodeURIComponent(teamId)}/tenants?detail=1`, summary: `List tenants for ${teamName ?? teamId}` });
|
|
3786
|
-
spinner.stop();
|
|
3787
|
-
const tenants = (tdata?.tenants ?? []).map(
|
|
3788
|
-
(t) => typeof t === "string" ? { tenant_name: t } : t
|
|
3789
|
-
);
|
|
3790
|
-
if (!tenants.length) {
|
|
3791
|
-
console.error(import_chalk28.default.red("This team has no tenants. Create one with `apiblaze tenant create`."));
|
|
3792
|
-
process.exit(1);
|
|
3793
|
-
}
|
|
3794
4502
|
if (!tenant2) {
|
|
3795
|
-
|
|
3796
|
-
|
|
3797
|
-
|
|
3798
|
-
|
|
3799
|
-
}
|
|
4503
|
+
const { pickTenant: pickTenant2 } = await Promise.resolve().then(() => (init_tenant_pick(), tenant_pick_exports));
|
|
4504
|
+
const picked = await pickTenant2(teamId, { message: `Which tenant portal${teamName ? ` (team ${teamName})` : ""}?` });
|
|
4505
|
+
if (!picked) process.exit(1);
|
|
4506
|
+
tenant2 = picked;
|
|
3800
4507
|
}
|
|
3801
|
-
const s2 = (0,
|
|
4508
|
+
const s2 = (0, import_ora15.default)("Finding the login app...").start();
|
|
3802
4509
|
const clients = await admin({ method: "GET", path: `/teams/${encodeURIComponent(teamId)}/tenants/${encodeURIComponent(tenant2)}/app-clients`, summary: `List app clients for ${tenant2}` }).catch(() => []);
|
|
3803
4510
|
s2.stop();
|
|
3804
4511
|
const usable = (Array.isArray(clients) ? clients : []).filter((c) => c && (c.client_id || c.clientId));
|
|
3805
4512
|
const pick2 = usable.find((c) => c.is_default || c.default) ?? usable.find((c) => c.verified !== false) ?? usable[0];
|
|
3806
4513
|
if (!pick2) {
|
|
3807
|
-
console.error(
|
|
4514
|
+
console.error(import_chalk30.default.red(`Tenant "${tenant2}" has no login app configured. Set one up in the dashboard (or \`apiblaze create\` with auth).`));
|
|
3808
4515
|
process.exit(1);
|
|
3809
4516
|
}
|
|
3810
4517
|
clientId = pick2.client_id ?? pick2.clientId;
|
|
3811
4518
|
}
|
|
3812
4519
|
const portalResource = `https://${tenant2}.portal.apiblaze.com/1.0.0`;
|
|
3813
|
-
console.log(`${
|
|
4520
|
+
console.log(`${import_chalk30.default.cyan("\u2192")} Logging in to ${import_chalk30.default.bold(tenant2)} as a consumer...`);
|
|
3814
4521
|
const result = await deviceLogin(clientId, DEFAULT_SCOPE, ({ verificationUri, userCode }) => {
|
|
3815
4522
|
console.log(`
|
|
3816
|
-
Open: ${
|
|
3817
|
-
console.log(` Code: ${
|
|
4523
|
+
Open: ${import_chalk30.default.underline(verificationUri)}`);
|
|
4524
|
+
console.log(` Code: ${import_chalk30.default.bold(userCode)}
|
|
3818
4525
|
`);
|
|
3819
|
-
console.log(
|
|
4526
|
+
console.log(import_chalk30.default.dim(" (opening your browser\u2026 waiting for you to finish)"));
|
|
3820
4527
|
}, portalResource);
|
|
3821
4528
|
const claims = result.idToken && decodeJwt2(result.idToken) || (decodeJwt2(result.accessToken) ?? {});
|
|
3822
4529
|
const creds = {
|
|
@@ -3831,7 +4538,7 @@ async function runConsumerLogin(opts) {
|
|
|
3831
4538
|
obtainedAt: Date.now()
|
|
3832
4539
|
};
|
|
3833
4540
|
saveConsumer(creds);
|
|
3834
|
-
console.log(
|
|
4541
|
+
console.log(import_chalk30.default.green(`\u2714 Logged in as consumer${creds.email ? ` ${creds.email}` : ""} on ${tenant2}.`));
|
|
3835
4542
|
}
|
|
3836
4543
|
async function runConsumerTokens(opts) {
|
|
3837
4544
|
const creds = requireConsumer();
|
|
@@ -3844,29 +4551,29 @@ async function runConsumerTokens(opts) {
|
|
|
3844
4551
|
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));
|
|
3845
4552
|
return;
|
|
3846
4553
|
}
|
|
3847
|
-
console.log(`${
|
|
4554
|
+
console.log(`${import_chalk30.default.cyan("Consumer")} ${import_chalk30.default.bold(fresh.email ?? fresh.tenant)} on ${import_chalk30.default.bold(fresh.tenant)}
|
|
3848
4555
|
`);
|
|
3849
|
-
console.log(`${
|
|
4556
|
+
console.log(`${import_chalk30.default.bold("access_token")} ${import_chalk30.default.dim("exp " + (exp(fresh.accessToken) ?? "?"))}
|
|
3850
4557
|
${fresh.accessToken}
|
|
3851
4558
|
`);
|
|
3852
|
-
if (fresh.idToken) console.log(`${
|
|
4559
|
+
if (fresh.idToken) console.log(`${import_chalk30.default.bold("id_token")} ${import_chalk30.default.dim("exp " + (exp(fresh.idToken) ?? "?"))}
|
|
3853
4560
|
${fresh.idToken}
|
|
3854
4561
|
`);
|
|
3855
|
-
if (fresh.refreshToken) console.log(`${
|
|
4562
|
+
if (fresh.refreshToken) console.log(`${import_chalk30.default.bold("refresh_token")}
|
|
3856
4563
|
${fresh.refreshToken}
|
|
3857
4564
|
`);
|
|
3858
|
-
console.log(
|
|
4565
|
+
console.log(import_chalk30.default.dim("These are your own tokens \u2014 keep them secret."));
|
|
3859
4566
|
}
|
|
3860
4567
|
async function runConsumerApikeys(opts) {
|
|
3861
4568
|
const creds = requireConsumer();
|
|
3862
4569
|
const { default: inquirer2 } = await import("inquirer");
|
|
3863
|
-
const spinner = (0,
|
|
4570
|
+
const spinner = (0, import_ora15.default)("Loading your API keys...").start();
|
|
3864
4571
|
const list = await consumerFetch(creds, "/apikeys");
|
|
3865
4572
|
const revealed = await consumerFetch(list.creds, "/apikeys/reveal").catch(() => ({ status: 0, data: null, creds: list.creds }));
|
|
3866
4573
|
spinner.stop();
|
|
3867
4574
|
if (list.status >= 400) {
|
|
3868
|
-
console.error(
|
|
3869
|
-
if (list.status === 401) console.error(
|
|
4575
|
+
console.error(import_chalk30.default.red(`Failed to list keys (${list.status}): ${list.data?.error ?? ""}`));
|
|
4576
|
+
if (list.status === 401) console.error(import_chalk30.default.dim("Your consumer session may have expired \u2014 run `apiblaze consumer login` again."));
|
|
3870
4577
|
process.exit(1);
|
|
3871
4578
|
}
|
|
3872
4579
|
const keys = list.data?.keys ?? [];
|
|
@@ -3874,16 +4581,16 @@ async function runConsumerApikeys(opts) {
|
|
|
3874
4581
|
if (opts.json) {
|
|
3875
4582
|
console.log(JSON.stringify({ keys, revealed: revealMap }, null, 2));
|
|
3876
4583
|
} else if (!keys.length) {
|
|
3877
|
-
console.log(
|
|
4584
|
+
console.log(import_chalk30.default.yellow("No API keys yet."));
|
|
3878
4585
|
} else {
|
|
3879
4586
|
for (const k of keys) {
|
|
3880
4587
|
const clear = revealMap[k.environment]?.key;
|
|
3881
|
-
const shown = clear ?
|
|
3882
|
-
const exp = k.expires_at ?
|
|
3883
|
-
console.log(` ${
|
|
4588
|
+
const shown = clear ? import_chalk30.default.green(clear) : import_chalk30.default.dim(`${k.key_prefix ?? ""}\u2026${k.key_suffix ?? ""}`);
|
|
4589
|
+
const exp = k.expires_at ? import_chalk30.default.dim(`exp ${k.expires_at}`) : import_chalk30.default.dim("no expiry");
|
|
4590
|
+
console.log(` ${import_chalk30.default.bold(k.environment ?? "")} ${shown} ${exp} ${import_chalk30.default.dim(k.description ?? "")}`);
|
|
3884
4591
|
}
|
|
3885
4592
|
if (Object.keys(revealMap).length === 0 && keys.some((k) => !k.expires_at)) {
|
|
3886
|
-
console.log(
|
|
4593
|
+
console.log(import_chalk30.default.dim("\n(Only expiring keys can be shown in clear; non-expiring keys show a prefix only.)"));
|
|
3887
4594
|
}
|
|
3888
4595
|
}
|
|
3889
4596
|
if (opts.json) return;
|
|
@@ -3897,7 +4604,7 @@ async function runConsumerApikeys(opts) {
|
|
|
3897
4604
|
const body = { environment: answers.environment };
|
|
3898
4605
|
if (answers.description) body.description = answers.description;
|
|
3899
4606
|
if (answers.expiresDays) body.expires_in_seconds = Number(answers.expiresDays) * 86400;
|
|
3900
|
-
const s2 = (0,
|
|
4607
|
+
const s2 = (0, import_ora15.default)("Creating key...").start();
|
|
3901
4608
|
const created = await consumerFetch(list.creds, "/apikeys", { method: "POST", body: JSON.stringify(body) });
|
|
3902
4609
|
if (created.status >= 400) {
|
|
3903
4610
|
s2.fail(`Create failed (${created.status}): ${created.data?.error ?? ""}`);
|
|
@@ -3905,15 +4612,16 @@ async function runConsumerApikeys(opts) {
|
|
|
3905
4612
|
}
|
|
3906
4613
|
s2.succeed("Key created.");
|
|
3907
4614
|
const key = created.data?.key ?? created.data?.fullKey;
|
|
3908
|
-
if (key) console.log(` ${
|
|
3909
|
-
else console.log(
|
|
4615
|
+
if (key) console.log(` ${import_chalk30.default.green(key)} ${import_chalk30.default.dim("(shown once \u2014 store it now)")}`);
|
|
4616
|
+
else console.log(import_chalk30.default.dim(" Key created; run `apiblaze consumer apikeys` to reveal it if it expires."));
|
|
3910
4617
|
}
|
|
3911
4618
|
|
|
3912
4619
|
// src/commands/sidecar.ts
|
|
3913
|
-
var
|
|
3914
|
-
var
|
|
4620
|
+
var import_chalk31 = __toESM(require("chalk"));
|
|
4621
|
+
var import_ora16 = __toESM(require("ora"));
|
|
3915
4622
|
var fs7 = __toESM(require("fs"));
|
|
3916
4623
|
var path4 = __toESM(require("path"));
|
|
4624
|
+
init_admin();
|
|
3917
4625
|
init_auth();
|
|
3918
4626
|
function detectNextProject(root) {
|
|
3919
4627
|
const hasConfig = ["next.config.js", "next.config.mjs", "next.config.ts"].some((f) => fs7.existsSync(path4.join(root, f)));
|
|
@@ -3951,18 +4659,18 @@ function upsertEnvLocal(root, token) {
|
|
|
3951
4659
|
}
|
|
3952
4660
|
function installSidecarPackage(root) {
|
|
3953
4661
|
if (fs7.existsSync(path4.join(root, "node_modules", "apiblaze", "package.json"))) {
|
|
3954
|
-
console.log(` ${
|
|
4662
|
+
console.log(` ${import_chalk31.default.green("\u2713")} apiblaze package already installed`);
|
|
3955
4663
|
return;
|
|
3956
4664
|
}
|
|
3957
4665
|
const has = (f) => fs7.existsSync(path4.join(root, f));
|
|
3958
4666
|
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" };
|
|
3959
|
-
const spinner = (0,
|
|
4667
|
+
const spinner = (0, import_ora16.default)(`Installing the apiblaze package (${pm.cmd})\u2026`).start();
|
|
3960
4668
|
try {
|
|
3961
4669
|
const { execSync } = require("child_process");
|
|
3962
4670
|
execSync(`${pm.cmd} ${pm.add} apiblaze`, { cwd: root, stdio: "ignore" });
|
|
3963
4671
|
spinner.succeed("Installed apiblaze (the sidecar runtime).");
|
|
3964
4672
|
} catch {
|
|
3965
|
-
spinner.warn(`Couldn't auto-install \u2014 run ${
|
|
4673
|
+
spinner.warn(`Couldn't auto-install \u2014 run ${import_chalk31.default.cyan(`${pm.cmd} ${pm.add} apiblaze`)} yourself before ${import_chalk31.default.cyan("npm run dev")}.`);
|
|
3966
4674
|
}
|
|
3967
4675
|
}
|
|
3968
4676
|
function readEnvKey(root) {
|
|
@@ -4101,7 +4809,7 @@ async function runAnonymousInit(root, router, opts) {
|
|
|
4101
4809
|
const { sidecarInitAnonymous: sidecarInitAnonymous2 } = await Promise.resolve().then(() => (init_api(), api_exports));
|
|
4102
4810
|
const { saveAnonCred: saveAnonCred2, clearAnonCred: clearAnonCred2 } = await Promise.resolve().then(() => (init_anon_cred(), anon_cred_exports));
|
|
4103
4811
|
if (opts.newSession) clearAnonCred2();
|
|
4104
|
-
const spinner = (0,
|
|
4812
|
+
const spinner = (0, import_ora16.default)("Setting up a sidecar (no login needed)...").start();
|
|
4105
4813
|
let out;
|
|
4106
4814
|
try {
|
|
4107
4815
|
out = await sidecarInitAnonymous2();
|
|
@@ -4113,29 +4821,29 @@ async function runAnonymousInit(root, router, opts) {
|
|
|
4113
4821
|
if (out.cp_key && out.team_id) saveAnonCred2(out.cp_key, out.team_id, out.claim_code);
|
|
4114
4822
|
const envState = upsertEnvLocal(root, out.token);
|
|
4115
4823
|
ensureGitignored(root);
|
|
4116
|
-
console.log(` ${
|
|
4117
|
-
console.log(` ${
|
|
4824
|
+
console.log(` ${import_chalk31.default.green("\u2713")} .env.local ${envState} (APIBLAZE_API_KEY) \u2014 gitignored`);
|
|
4825
|
+
console.log(` ${import_chalk31.default.green("\u2713")} instrumentation.ts ${wireInstrumentation(root)}`);
|
|
4118
4826
|
installSidecarPackage(root);
|
|
4119
4827
|
let inspectorPath = null;
|
|
4120
4828
|
if (!opts.noInspector) {
|
|
4121
4829
|
inspectorPath = generateInspector(root, router);
|
|
4122
|
-
if (inspectorPath) console.log(` ${
|
|
4830
|
+
if (inspectorPath) console.log(` ${import_chalk31.default.green("\u2713")} inspector at ${inspectorPath}`);
|
|
4123
4831
|
}
|
|
4124
4832
|
console.log("");
|
|
4125
|
-
console.log(
|
|
4126
|
-
console.log(` 1. ${
|
|
4833
|
+
console.log(import_chalk31.default.bold("Done (no account needed). What happens next:"));
|
|
4834
|
+
console.log(` 1. ${import_chalk31.default.cyan("npm run dev")} and use your app.`);
|
|
4127
4835
|
console.log(` 2. Each external origin your app calls is logged in the console \u2014 approve one with:`);
|
|
4128
|
-
console.log(` ${
|
|
4836
|
+
console.log(` ${import_chalk31.default.cyan("apiblaze sidecar approve api.stripe.com")} (no login needed)`);
|
|
4129
4837
|
console.log("");
|
|
4130
|
-
console.log(
|
|
4131
|
-
console.log(` ${
|
|
4132
|
-
console.log(
|
|
4838
|
+
console.log(import_chalk31.default.bold(" \u{1F511} Keep your setup \u2014 claim it into an account:"));
|
|
4839
|
+
console.log(` ${import_chalk31.default.cyan("apiblaze login")} then ${import_chalk31.default.cyan("apiblaze claim")} ${import_chalk31.default.dim("(no code needed here)")}`);
|
|
4840
|
+
console.log(import_chalk31.default.dim(` From another machine: apiblaze claim ${out.claim_code} \xB7 expires in 30 days`));
|
|
4133
4841
|
}
|
|
4134
4842
|
async function runSidecar(opts) {
|
|
4135
4843
|
const root = path4.resolve(opts.dir ?? process.cwd());
|
|
4136
4844
|
const detected = detectNextProject(root);
|
|
4137
4845
|
if (!detected.found) {
|
|
4138
|
-
console.log(
|
|
4846
|
+
console.log(import_chalk31.default.yellow(`No Next.js project detected in ${root}.`));
|
|
4139
4847
|
console.log("Create one (e.g. `npx create-next-app`) and re-run `apiblaze init` inside it.");
|
|
4140
4848
|
return;
|
|
4141
4849
|
}
|
|
@@ -4146,10 +4854,10 @@ async function runSidecar(opts) {
|
|
|
4146
4854
|
if (!loadCredentials()) {
|
|
4147
4855
|
upsertEnvLocal(root, readEnvKey(root));
|
|
4148
4856
|
ensureGitignored(root);
|
|
4149
|
-
console.log(` ${
|
|
4150
|
-
console.log(` ${
|
|
4857
|
+
console.log(` ${import_chalk31.default.green("\u2713")} .env.local present (APIBLAZE_API_KEY) \u2014 reusing`);
|
|
4858
|
+
console.log(` ${import_chalk31.default.green("\u2713")} instrumentation.ts ${wireInstrumentation(root)}`);
|
|
4151
4859
|
installSidecarPackage(root);
|
|
4152
|
-
console.log(
|
|
4860
|
+
console.log(import_chalk31.default.dim(" Log in and run `apiblaze claim <code>` to keep this setup, or `apiblaze login` to manage it."));
|
|
4153
4861
|
return;
|
|
4154
4862
|
}
|
|
4155
4863
|
const { teamId, teamName } = await resolveTeam(opts.team);
|
|
@@ -4158,7 +4866,7 @@ async function runSidecar(opts) {
|
|
|
4158
4866
|
const mustMint = !existingKey || opts.rotate || switchingTeam;
|
|
4159
4867
|
let token = existingKey ?? "";
|
|
4160
4868
|
if (mustMint) {
|
|
4161
|
-
const spinner = (0,
|
|
4869
|
+
const spinner = (0, import_ora16.default)(existingKey ? "Re-establishing the sidecar (minting a fresh invoke key)..." : "Setting up the sidecar (tenant + non-expiring invoke key)...").start();
|
|
4162
4870
|
try {
|
|
4163
4871
|
const out = await admin({
|
|
4164
4872
|
method: "POST",
|
|
@@ -4172,39 +4880,40 @@ async function runSidecar(opts) {
|
|
|
4172
4880
|
throw err;
|
|
4173
4881
|
}
|
|
4174
4882
|
} else {
|
|
4175
|
-
console.log(
|
|
4883
|
+
console.log(import_chalk31.default.dim(` Reusing the existing APIBLAZE_API_KEY (run with --rotate to mint a fresh one, or --team <name> to switch teams).`));
|
|
4176
4884
|
}
|
|
4177
4885
|
const envState = upsertEnvLocal(root, token);
|
|
4178
4886
|
ensureGitignored(root);
|
|
4179
|
-
console.log(` ${
|
|
4887
|
+
console.log(` ${import_chalk31.default.green("\u2713")} .env.local ${envState} (APIBLAZE_API_KEY) \u2014 gitignored`);
|
|
4180
4888
|
const wireState = wireInstrumentation(root);
|
|
4181
|
-
console.log(` ${
|
|
4889
|
+
console.log(` ${import_chalk31.default.green("\u2713")} instrumentation.ts ${wireState}`);
|
|
4182
4890
|
installSidecarPackage(root);
|
|
4183
4891
|
let inspectorPath = null;
|
|
4184
4892
|
if (!opts.noInspector) {
|
|
4185
4893
|
inspectorPath = generateInspector(root, detected.router);
|
|
4186
|
-
if (inspectorPath) console.log(` ${
|
|
4894
|
+
if (inspectorPath) console.log(` ${import_chalk31.default.green("\u2713")} inspector at ${inspectorPath}`);
|
|
4187
4895
|
}
|
|
4188
4896
|
console.log("");
|
|
4189
|
-
console.log(
|
|
4190
|
-
console.log(` 1. ${
|
|
4191
|
-
console.log(` 2. The origins your app calls appear as ${
|
|
4192
|
-
console.log(` 3. Approve the ones to route: ${
|
|
4897
|
+
console.log(import_chalk31.default.bold("Done. What happens next:"));
|
|
4898
|
+
console.log(` 1. ${import_chalk31.default.cyan("npm run dev")} and use your app \u2014 it works exactly as before (all calls go direct).`);
|
|
4899
|
+
console.log(` 2. The origins your app calls appear as ${import_chalk31.default.bold("candidates")} \u2014 list them: ${import_chalk31.default.cyan("apiblaze sidecar")}`);
|
|
4900
|
+
console.log(` 3. Approve the ones to route: ${import_chalk31.default.cyan("apiblaze sidecar approve api.stripe.com")} (or in the dashboard)`);
|
|
4193
4901
|
console.log(` \u2026within ~5 min your app starts routing that origin through APIblaze.`);
|
|
4194
|
-
if (inspectorPath) console.log(` \u2022 Try it now: open ${
|
|
4195
|
-
if (switchingTeam) console.log(
|
|
4902
|
+
if (inspectorPath) console.log(` \u2022 Try it now: open ${import_chalk31.default.underline("http://localhost:3000/abz-inspector")} (dev only; rm ${path4.dirname(inspectorPath)} before shipping)`);
|
|
4903
|
+
if (switchingTeam) console.log(import_chalk31.default.dim(` \u2022 Approved origins are per-team \u2014 re-approve them on ${teamName ?? teamId} with \`apiblaze sidecar approve <origin>\`.`));
|
|
4196
4904
|
console.log("");
|
|
4197
|
-
console.log(
|
|
4198
|
-
console.log(
|
|
4199
|
-
console.log(
|
|
4905
|
+
console.log(import_chalk31.default.dim(" Manage: apiblaze sidecar (list/approve/deny/remove)"));
|
|
4906
|
+
console.log(import_chalk31.default.dim(" Rotate: apiblaze init --rotate \xB7 Switch team: apiblaze init --team <name>"));
|
|
4907
|
+
console.log(import_chalk31.default.dim(" Turn off: set APIBLAZE_SIDECAR=off in .env.local (flip back to on anytime; key stays put)."));
|
|
4200
4908
|
console.log("");
|
|
4201
|
-
console.log(
|
|
4202
|
-
console.log(
|
|
4909
|
+
console.log(import_chalk31.default.yellow(" \u26A0 APIBLAZE_API_KEY is long-lived and lets a holder call your team's proxies. Never commit it."));
|
|
4910
|
+
console.log(import_chalk31.default.dim(" Your control-plane login stays in ~/.apiblaze \u2014 it never entered this project."));
|
|
4203
4911
|
}
|
|
4204
4912
|
|
|
4205
4913
|
// src/commands/origins.ts
|
|
4206
|
-
var
|
|
4207
|
-
var
|
|
4914
|
+
var import_chalk32 = __toESM(require("chalk"));
|
|
4915
|
+
var import_ora17 = __toESM(require("ora"));
|
|
4916
|
+
init_admin();
|
|
4208
4917
|
init_auth();
|
|
4209
4918
|
init_anon_cred();
|
|
4210
4919
|
async function runOriginsList(opts) {
|
|
@@ -4212,7 +4921,7 @@ async function runOriginsList(opts) {
|
|
|
4212
4921
|
if (!loadCredentials()) {
|
|
4213
4922
|
const cred = loadAnonCred();
|
|
4214
4923
|
if (!cred) {
|
|
4215
|
-
console.log(
|
|
4924
|
+
console.log(import_chalk32.default.yellow("No anonymous workspace here. Run `apiblaze init` first."));
|
|
4216
4925
|
return;
|
|
4217
4926
|
}
|
|
4218
4927
|
out = await cpFetch(cred.cp_key, `/teams/${encodeURIComponent(cred.team_id)}/sidecar/candidates`, { method: "GET" });
|
|
@@ -4230,30 +4939,30 @@ async function runOriginsList(opts) {
|
|
|
4230
4939
|
}
|
|
4231
4940
|
const routed = out.routed ?? [];
|
|
4232
4941
|
const candidates = out.candidates ?? [];
|
|
4233
|
-
console.log(
|
|
4942
|
+
console.log(import_chalk32.default.bold(`
|
|
4234
4943
|
Routed through APIblaze (${routed.length})`));
|
|
4235
|
-
if (!routed.length) console.log(
|
|
4236
|
-
for (const r of routed) console.log(` ${
|
|
4237
|
-
console.log(
|
|
4944
|
+
if (!routed.length) console.log(import_chalk32.default.dim(" none yet"));
|
|
4945
|
+
for (const r of routed) console.log(` ${import_chalk32.default.green("\u25CF")} ${r.sidecar_origin} ${import_chalk32.default.dim(`\u2192 ${r.project_id}`)}`);
|
|
4946
|
+
console.log(import_chalk32.default.bold(`
|
|
4238
4947
|
Candidates \u2014 going direct, not yet approved (${candidates.length})`));
|
|
4239
|
-
if (!candidates.length) console.log(
|
|
4948
|
+
if (!candidates.length) console.log(import_chalk32.default.dim(" none \u2014 run your app to discover the origins it calls"));
|
|
4240
4949
|
for (const c of candidates) {
|
|
4241
|
-
console.log(` ${
|
|
4950
|
+
console.log(` ${import_chalk32.default.yellow("\u25CB")} ${c.origin} ${import_chalk32.default.dim(`seen ${c.request_count}\xD7, last ${c.last_seen}`)}`);
|
|
4242
4951
|
}
|
|
4243
4952
|
if (candidates.length) {
|
|
4244
|
-
console.log(
|
|
4953
|
+
console.log(import_chalk32.default.dim(`
|
|
4245
4954
|
Approve: apiblaze sidecar approve ${candidates[0].origin.replace("https://", "")}`));
|
|
4246
|
-
console.log(
|
|
4955
|
+
console.log(import_chalk32.default.dim(` Dismiss: apiblaze sidecar deny ${candidates[0].origin.replace("https://", "")}`));
|
|
4247
4956
|
}
|
|
4248
4957
|
}
|
|
4249
4958
|
async function runOriginsApprove(origin, opts) {
|
|
4250
4959
|
if (!loadCredentials()) {
|
|
4251
4960
|
const cred = loadAnonCred();
|
|
4252
4961
|
if (!cred) {
|
|
4253
|
-
console.error(
|
|
4962
|
+
console.error(import_chalk32.default.red("Not logged in and no anonymous workspace. Run `apiblaze init` first."));
|
|
4254
4963
|
process.exit(1);
|
|
4255
4964
|
}
|
|
4256
|
-
const spinner2 = (0,
|
|
4965
|
+
const spinner2 = (0, import_ora17.default)(`Approving ${origin} (anonymous)...`).start();
|
|
4257
4966
|
try {
|
|
4258
4967
|
const out = await cpFetch(cred.cp_key, `/teams/${encodeURIComponent(cred.team_id)}/sidecar/approve`, { method: "POST", body: JSON.stringify({ origin }) });
|
|
4259
4968
|
spinner2.succeed(`Approved ${origin} \u2192 proxy ${out.project_id}. Routing within ~5 min.`);
|
|
@@ -4264,7 +4973,7 @@ async function runOriginsApprove(origin, opts) {
|
|
|
4264
4973
|
return;
|
|
4265
4974
|
}
|
|
4266
4975
|
const { teamId } = await resolveTeam(opts.team);
|
|
4267
|
-
const spinner = (0,
|
|
4976
|
+
const spinner = (0, import_ora17.default)(`Approving ${origin}...`).start();
|
|
4268
4977
|
try {
|
|
4269
4978
|
const out = await admin({
|
|
4270
4979
|
method: "POST",
|
|
@@ -4281,7 +4990,7 @@ async function runOriginsApprove(origin, opts) {
|
|
|
4281
4990
|
}
|
|
4282
4991
|
async function runOriginsDeny(origin, opts) {
|
|
4283
4992
|
const { teamId } = await resolveTeam(opts.team);
|
|
4284
|
-
const spinner = (0,
|
|
4993
|
+
const spinner = (0, import_ora17.default)(`Dismissing ${origin}...`).start();
|
|
4285
4994
|
try {
|
|
4286
4995
|
await admin({ method: "POST", path: `/teams/${encodeURIComponent(teamId)}/sidecar/dismiss`, body: { origin }, summary: `Dismiss sidecar origin ${origin}` });
|
|
4287
4996
|
spinner.succeed(`Dismissed ${origin}. It won't be suggested again.`);
|
|
@@ -4292,7 +5001,7 @@ async function runOriginsDeny(origin, opts) {
|
|
|
4292
5001
|
}
|
|
4293
5002
|
async function runOriginsRemove(origin, opts) {
|
|
4294
5003
|
const { teamId } = await resolveTeam(opts.team);
|
|
4295
|
-
const spinner = (0,
|
|
5004
|
+
const spinner = (0, import_ora17.default)(`Removing the proxy for ${origin}...`).start();
|
|
4296
5005
|
try {
|
|
4297
5006
|
await admin({ method: "POST", path: `/teams/${encodeURIComponent(teamId)}/sidecar/remove`, body: { origin }, summary: `Un-route sidecar origin ${origin}` });
|
|
4298
5007
|
spinner.succeed(`Removed ${origin}. Your app will stop routing it (goes direct) within ~5 min.`);
|
|
@@ -4303,8 +5012,9 @@ async function runOriginsRemove(origin, opts) {
|
|
|
4303
5012
|
}
|
|
4304
5013
|
|
|
4305
5014
|
// src/commands/op.ts
|
|
4306
|
-
var
|
|
5015
|
+
var import_chalk33 = __toESM(require("chalk"));
|
|
4307
5016
|
init_auth();
|
|
5017
|
+
init_trace();
|
|
4308
5018
|
init_types();
|
|
4309
5019
|
var OPERATOR_EMAILS = /* @__PURE__ */ new Set(["julienpmjacquet@gmail.com", "chkev@umich.edu"]);
|
|
4310
5020
|
var DASHBOARD_BASE6 = process.env.APIBLAZE_DASHBOARD_BASE || "https://dashboard.apiblaze.com";
|
|
@@ -4334,53 +5044,53 @@ async function opCall(call) {
|
|
|
4334
5044
|
function printResidue(report, applied) {
|
|
4335
5045
|
const up = report?.upstash ?? {};
|
|
4336
5046
|
const fga = report?.fga ?? {};
|
|
4337
|
-
console.log(
|
|
4338
|
-
console.log(
|
|
5047
|
+
console.log(import_chalk33.default.bold(applied ? "\nExternal-residue sweep" : "\nExternal residue (dry-run \u2014 nothing deleted)"));
|
|
5048
|
+
console.log(import_chalk33.default.bold("\n Upstash"));
|
|
4339
5049
|
const orphans = up.orphans ?? [];
|
|
4340
|
-
if (orphans.length === 0) console.log(
|
|
4341
|
-
for (const o of orphans) console.log(` ${
|
|
4342
|
-
console.log(
|
|
4343
|
-
if (up.unknown?.length) console.log(
|
|
4344
|
-
if (applied) console.log(` ${
|
|
4345
|
-
for (const e of up.errors ?? []) console.log(
|
|
4346
|
-
console.log(
|
|
5050
|
+
if (orphans.length === 0) console.log(import_chalk33.default.green(" no orphaned keys"));
|
|
5051
|
+
for (const o of orphans) console.log(` ${import_chalk33.default.yellow(o.key)} ${import_chalk33.default.dim(`\u2014 ${o.reason}`)}`);
|
|
5052
|
+
console.log(import_chalk33.default.dim(` kept (live principals): ${up.kept ?? 0} \xB7 anon wallets (untouched): ${up.anon_wallets ?? 0}`));
|
|
5053
|
+
if (up.unknown?.length) console.log(import_chalk33.default.dim(` unknown (never deleted): ${up.unknown.join(", ")}`));
|
|
5054
|
+
if (applied) console.log(` ${import_chalk33.default.bold(String(up.deleted ?? 0))} key(s) deleted`);
|
|
5055
|
+
for (const e of up.errors ?? []) console.log(import_chalk33.default.red(` error: ${e}`));
|
|
5056
|
+
console.log(import_chalk33.default.bold("\n OpenFGA / Neon"));
|
|
4347
5057
|
if (applied) {
|
|
4348
5058
|
const swept = fga?.swept ?? [];
|
|
4349
|
-
if (swept.length === 0) console.log(
|
|
5059
|
+
if (swept.length === 0) console.log(import_chalk33.default.green(" no orphaned stores"));
|
|
4350
5060
|
for (const s of swept) {
|
|
4351
5061
|
console.log(
|
|
4352
|
-
` ${
|
|
5062
|
+
` ${import_chalk33.default.yellow(s.store_id)} ${import_chalk33.default.dim(`\u2014 store ${s.openfga_deleted ? "deleted" : "DEFERRED"}, ${s.neon_deleted} Neon tuple(s) purged`)}`
|
|
4353
5063
|
);
|
|
4354
5064
|
}
|
|
4355
|
-
if (fga?.remaining) console.log(
|
|
5065
|
+
if (fga?.remaining) console.log(import_chalk33.default.yellow(` ${fga.remaining} more orphan store(s) \u2014 re-run to drain`));
|
|
4356
5066
|
} else {
|
|
4357
5067
|
const fgaOrphans = fga?.orphans ?? [];
|
|
4358
|
-
if (fgaOrphans.length === 0) console.log(
|
|
5068
|
+
if (fgaOrphans.length === 0) console.log(import_chalk33.default.green(" no orphaned stores"));
|
|
4359
5069
|
for (const s of fgaOrphans) {
|
|
4360
5070
|
const src = s.in_openfga ? "live in OpenFGA" : "Neon tuples only";
|
|
4361
|
-
console.log(` ${
|
|
5071
|
+
console.log(` ${import_chalk33.default.yellow(s.store_id)} ${import_chalk33.default.dim(`\u2014 ${src}${s.name ? ` (${s.name})` : ""}, ${s.neon_tuples} Neon tuple(s)`)}`);
|
|
4362
5072
|
}
|
|
4363
|
-
console.log(
|
|
5073
|
+
console.log(import_chalk33.default.dim(` kept stores: ${(fga?.kept_store_ids ?? []).length}`));
|
|
4364
5074
|
}
|
|
4365
|
-
for (const e of fga?.errors ?? []) console.log(
|
|
5075
|
+
for (const e of fga?.errors ?? []) console.log(import_chalk33.default.red(` error: ${e}`));
|
|
4366
5076
|
console.log();
|
|
4367
5077
|
}
|
|
4368
5078
|
async function runOp(sub, opts = {}) {
|
|
4369
5079
|
if (!loadCredentials()) {
|
|
4370
|
-
console.log(
|
|
5080
|
+
console.log(import_chalk33.default.dim("Not logged in. Run `apiblaze login`."));
|
|
4371
5081
|
return;
|
|
4372
5082
|
}
|
|
4373
5083
|
if (!isOperatorLogin()) {
|
|
4374
|
-
console.log(
|
|
5084
|
+
console.log(import_chalk33.default.dim("`apiblaze op` is only available to platform operators."));
|
|
4375
5085
|
return;
|
|
4376
5086
|
}
|
|
4377
5087
|
switch (sub) {
|
|
4378
5088
|
case void 0:
|
|
4379
5089
|
case "menu": {
|
|
4380
|
-
console.log(
|
|
4381
|
-
console.log(` ${
|
|
4382
|
-
console.log(` ${
|
|
4383
|
-
console.log(` ${
|
|
5090
|
+
console.log(import_chalk33.default.bold("\nOperator menu"));
|
|
5091
|
+
console.log(` ${import_chalk33.default.cyan("apiblaze op residue")} external-store residue report (Upstash + Neon/OpenFGA, dry-run)`);
|
|
5092
|
+
console.log(` ${import_chalk33.default.cyan("apiblaze op sweep")} delete the orphans the report shows (asks first; ${import_chalk33.default.dim("-y to skip")})`);
|
|
5093
|
+
console.log(` ${import_chalk33.default.cyan("apiblaze op credits")} list credit wallets
|
|
4384
5094
|
`);
|
|
4385
5095
|
return;
|
|
4386
5096
|
}
|
|
@@ -4396,15 +5106,15 @@ async function runOp(sub, opts = {}) {
|
|
|
4396
5106
|
const nFga = report?.fga?.orphans?.length ?? 0;
|
|
4397
5107
|
printResidue(report, false);
|
|
4398
5108
|
if (nUp + nFga === 0) {
|
|
4399
|
-
console.log(
|
|
5109
|
+
console.log(import_chalk33.default.green("Nothing to sweep."));
|
|
4400
5110
|
return;
|
|
4401
5111
|
}
|
|
4402
5112
|
if (!opts.yes) {
|
|
4403
5113
|
const readline2 = await import("readline/promises");
|
|
4404
5114
|
const rl = readline2.createInterface({ input: process.stdin, output: process.stdout });
|
|
4405
|
-
const answer = await rl.question(
|
|
5115
|
+
const answer = await rl.question(import_chalk33.default.red(`Delete ${nUp} Upstash key(s) + ${nFga} OpenFGA store(s)? Type 'sweep' to confirm: `));
|
|
4406
5116
|
rl.close();
|
|
4407
|
-
if (answer.trim() !== "sweep") return void console.log(
|
|
5117
|
+
if (answer.trim() !== "sweep") return void console.log(import_chalk33.default.dim("Aborted."));
|
|
4408
5118
|
}
|
|
4409
5119
|
const result = await opCall({ method: "POST", path: "/operator/external-residue/sweep", summary: "external residue sweep" });
|
|
4410
5120
|
if (opts.json) return void console.log(JSON.stringify(result, null, 2));
|
|
@@ -4415,19 +5125,20 @@ async function runOp(sub, opts = {}) {
|
|
|
4415
5125
|
const data = await opCall({ method: "GET", path: "/operator/credits", summary: "list credit wallets" });
|
|
4416
5126
|
if (opts.json) return void console.log(JSON.stringify(data, null, 2));
|
|
4417
5127
|
const accounts = data?.accounts ?? [];
|
|
4418
|
-
if (accounts.length === 0) return void console.log(
|
|
5128
|
+
if (accounts.length === 0) return void console.log(import_chalk33.default.dim("No credit wallets."));
|
|
4419
5129
|
for (const a of accounts) {
|
|
4420
5130
|
const bal = typeof a.balance_cents === "number" ? `$${(a.balance_cents / 100).toFixed(2)}` : "?";
|
|
4421
|
-
console.log(` ${
|
|
5131
|
+
console.log(` ${import_chalk33.default.bold(bal.padStart(9))} ${a.walletId}${a.owner_email ? import_chalk33.default.dim(` \u2014 ${a.owner_email}`) : a.anon ? import_chalk33.default.dim(" \u2014 anon") : ""}`);
|
|
4422
5132
|
}
|
|
4423
5133
|
return;
|
|
4424
5134
|
}
|
|
4425
5135
|
default:
|
|
4426
|
-
console.log(
|
|
5136
|
+
console.log(import_chalk33.default.red(`Unknown op subcommand '${sub}'. Run \`apiblaze op\` for the menu.`));
|
|
4427
5137
|
}
|
|
4428
5138
|
}
|
|
4429
5139
|
|
|
4430
5140
|
// src/index.ts
|
|
5141
|
+
init_trace();
|
|
4431
5142
|
var program = new import_commander.Command();
|
|
4432
5143
|
program.name("apiblaze").description("APIblaze CLI \u2014 create & manage API proxies and run dev tunnels").version(version).option("-v, --verbose", "Print the exact series of API calls each command makes (curl-equivalent you could run yourself)");
|
|
4433
5144
|
program.hook("preAction", () => {
|
|
@@ -4478,7 +5189,7 @@ program.command("dev").description("Put your localhost behind a public URL (dev
|
|
|
4478
5189
|
try {
|
|
4479
5190
|
const resolved = parseInt(port ?? opts.port, 10);
|
|
4480
5191
|
if (Number.isNaN(resolved)) {
|
|
4481
|
-
console.error(
|
|
5192
|
+
console.error(import_chalk34.default.red(`Invalid port: ${port ?? opts.port}`));
|
|
4482
5193
|
process.exit(1);
|
|
4483
5194
|
}
|
|
4484
5195
|
await runDev({ port: resolved, captureFile: opts.captureFile });
|
|
@@ -4537,6 +5248,8 @@ domain.command("status").description("Check a custom domain's validation status"
|
|
|
4537
5248
|
domain.command("rm").description("Remove a custom domain").argument("<project>", "Project name or id").requiredOption("--id <domainId>", "Domain id (see `domain list`)").option("--team <id|name>", "Team the project is in").option("--apiversion <version>", "API version").action(action((project, opts) => runDomainRemove(project, opts)));
|
|
4538
5249
|
domain.command("set-base").description("Choose which version/environment your main URL serves").argument("<project>", "Project name or id").option("--env <env>", "Environment (default: prod)").option("--team <id|name>", "Team the project is in").option("--apiversion <version>", "API version").action(action((project, opts) => runDomainSetBase(project, opts)));
|
|
4539
5250
|
var tenant = program.command("tenant").description("Manage tenants \u2014 separate groups of your API's users");
|
|
5251
|
+
tenant.command("manage").description("Browse & edit one tenant: settings, login app clients, providers, issuers (search-first picker)").argument("[query]", "Search by tenant name/display name (omit to use your tenant scope or pick)").option("--tenant <slug>", "Exact tenant slug (skips the picker)").option("--team <id|name>", "Team (defaults to active team)").action(action((query, opts) => runTenantManage(query, opts)));
|
|
5252
|
+
tenant.command("use").description("Set the sticky tenant scope for future commands (search-first; --clear to unset)").argument("[query]", "Search by tenant name/display name").option("--clear", "Clear the tenant scope").option("--team <id|name>", "Team (defaults to active team)").action(action((query, opts) => runTenantUse(query, opts)));
|
|
4540
5253
|
tenant.command("list").description("List tenants in your team").option("--team <id|name>", "Team (defaults to active team)").option("--json", "Output machine-readable JSON").action(action((opts) => runTenantList(opts)));
|
|
4541
5254
|
tenant.command("create").description("Create a tenant in your team").requiredOption("--name <display>", "Display name").option("--slug <tenant_name>", "Explicit tenant slug (generated if omitted)").option("--team <id|name>", "Team (defaults to active team)").option("--json", "Output machine-readable JSON").action(action((opts) => runTenantCreate(opts)));
|
|
4542
5255
|
tenant.command("attach").description("Attach a tenant to a proxy").argument("<project>", "Project name or id").requiredOption("--tenant <slug>", "Tenant slug to attach").option("--auth-config <id>", "Auth config id to bind").option("--team <id|name>", "Team the project is in").option("--apiversion <version>", "API version").option("--json", "Output machine-readable JSON").action(action((project, opts) => runTenantAttach(project, opts)));
|
|
@@ -4574,7 +5287,7 @@ function groupedCommandHelp() {
|
|
|
4574
5287
|
const sub = byName.get(e.parent)?.commands.find((s) => s.name() === e.sub);
|
|
4575
5288
|
return sub ? ` ${helpLabel(e).padEnd(width)}${sub.description()}` : "";
|
|
4576
5289
|
}).filter(Boolean).join("\n");
|
|
4577
|
-
return `${
|
|
5290
|
+
return `${import_chalk34.default.bold(g.title)}
|
|
4578
5291
|
${rows}`;
|
|
4579
5292
|
}).join("\n\n");
|
|
4580
5293
|
}
|
|
@@ -4602,13 +5315,13 @@ Examples:
|
|
|
4602
5315
|
`);
|
|
4603
5316
|
function printError(err) {
|
|
4604
5317
|
if (err instanceof ApiError) {
|
|
4605
|
-
console.error(
|
|
5318
|
+
console.error(import_chalk34.default.red(`
|
|
4606
5319
|
API error (${err.status}): ${err.message}`));
|
|
4607
5320
|
} else if (err instanceof Error) {
|
|
4608
|
-
console.error(
|
|
5321
|
+
console.error(import_chalk34.default.red(`
|
|
4609
5322
|
Error: ${err.message}`));
|
|
4610
5323
|
} else {
|
|
4611
|
-
console.error(
|
|
5324
|
+
console.error(import_chalk34.default.red("\nUnknown error"));
|
|
4612
5325
|
}
|
|
4613
5326
|
}
|
|
4614
5327
|
program.parse(process.argv);
|