apiblaze 0.19.32 → 0.20.2
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 +1869 -581
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -714,7 +714,7 @@ async function createTenantRow(teamId) {
|
|
|
714
714
|
const { name } = await inquirer3.prompt([{
|
|
715
715
|
type: "input",
|
|
716
716
|
name: "name",
|
|
717
|
-
message: `Tenant name ${
|
|
717
|
+
message: `Tenant name ${import_chalk33.default.dim("(lowercase letters/numbers; globally unique \u2014 becomes {name}.portal.apiblaze.com)")}:`,
|
|
718
718
|
validate: (s) => /^[a-z0-9]+$/.test(s.trim()) ? true : "lowercase letters and numbers only"
|
|
719
719
|
}]);
|
|
720
720
|
const slug = name.trim();
|
|
@@ -729,7 +729,7 @@ async function createTenantRow(teamId) {
|
|
|
729
729
|
} catch (err) {
|
|
730
730
|
const msg = err instanceof Error ? err.message : String(err);
|
|
731
731
|
if (/taken|unique|exists|reserved|conflict/i.test(msg)) {
|
|
732
|
-
console.log(
|
|
732
|
+
console.log(import_chalk33.default.yellow(` "${slug}" is not available (tenant names are global): ${msg}`));
|
|
733
733
|
continue;
|
|
734
734
|
}
|
|
735
735
|
throw err;
|
|
@@ -746,11 +746,11 @@ async function addApiblazeHostedLogin(teamId, tenant2, opts = {}) {
|
|
|
746
746
|
pageSize: 10,
|
|
747
747
|
choices: [
|
|
748
748
|
...PROVIDERS.map((p) => ({ name: p.label, value: p.id })),
|
|
749
|
-
...opts.allowSkip ? [new inquirer3.Separator(), { name:
|
|
749
|
+
...opts.allowSkip ? [new inquirer3.Separator(), { name: import_chalk33.default.dim("Skip for now \u2014 set up a login method later"), value: null }] : []
|
|
750
750
|
]
|
|
751
751
|
}]);
|
|
752
752
|
if (!provider) {
|
|
753
|
-
console.log(
|
|
753
|
+
console.log(import_chalk33.default.dim(" No login method yet \u2014 add one later under Login methods."));
|
|
754
754
|
return false;
|
|
755
755
|
}
|
|
756
756
|
const opt = PROVIDERS.find((p) => p.id === provider);
|
|
@@ -781,7 +781,7 @@ async function addApiblazeHostedLogin(teamId, tenant2, opts = {}) {
|
|
|
781
781
|
if (a.callbacks.trim()) clientBody.authorizedCallbackUrls = a.callbacks.split(/\s*,\s*/).map((s) => s.trim()).filter(Boolean);
|
|
782
782
|
if (a.scopes.trim() && opt.own) providerBody.scopes = a.scopes.split(/\s+/).filter(Boolean);
|
|
783
783
|
}
|
|
784
|
-
const spinner = (0,
|
|
784
|
+
const spinner = (0, import_ora12.default)("Setting up login...").start();
|
|
785
785
|
try {
|
|
786
786
|
const client = await admin({ method: "POST", path: `${base2}/app-clients`, body: clientBody, summary: `Create login for ${tenant2}` });
|
|
787
787
|
const clientId = client?.clientId ?? client?.client_id;
|
|
@@ -796,27 +796,27 @@ async function addApiblazeHostedLogin(teamId, tenant2, opts = {}) {
|
|
|
796
796
|
async function createTenantInteractive(teamId) {
|
|
797
797
|
const { default: inquirer3 } = await import("inquirer");
|
|
798
798
|
const tenant2 = await createTenantRow(teamId);
|
|
799
|
-
console.log(
|
|
799
|
+
console.log(import_chalk33.default.green(` Tenant ${import_chalk33.default.bold(tenant2)} created.`));
|
|
800
800
|
const { users } = await inquirer3.prompt([{
|
|
801
801
|
type: "confirm",
|
|
802
802
|
name: "users",
|
|
803
803
|
default: false,
|
|
804
|
-
message: `Enable Users & groups? ${
|
|
804
|
+
message: `Enable Users & groups? ${import_chalk33.default.dim(`(identity/key management at iam.apiblaze.com)`)}`
|
|
805
805
|
}]);
|
|
806
806
|
if (users) {
|
|
807
807
|
await admin({ method: "PATCH", path: `/teams/${encodeURIComponent(teamId)}/tenants/${encodeURIComponent(tenant2)}/iam`, body: { enabled: true }, summary: "Enable Users & groups" }).catch(() => {
|
|
808
808
|
});
|
|
809
|
-
console.log(
|
|
809
|
+
console.log(import_chalk33.default.green(" Users & groups enabled."));
|
|
810
810
|
}
|
|
811
811
|
await addApiblazeHostedLogin(teamId, tenant2, { allowSkip: true });
|
|
812
812
|
return tenant2;
|
|
813
813
|
}
|
|
814
|
-
var
|
|
814
|
+
var import_chalk33, import_ora12, PROVIDERS, DEFAULT_SCOPES;
|
|
815
815
|
var init_tenant_create = __esm({
|
|
816
816
|
"src/lib/tenant-create.ts"() {
|
|
817
817
|
"use strict";
|
|
818
|
-
|
|
819
|
-
|
|
818
|
+
import_chalk33 = __toESM(require("chalk"));
|
|
819
|
+
import_ora12 = __toESM(require("ora"));
|
|
820
820
|
init_admin();
|
|
821
821
|
PROVIDERS = [
|
|
822
822
|
{ id: "apiblaze", label: "APIblaze (via GitHub) \u2014 zero setup, recommended", own: false },
|
|
@@ -854,10 +854,10 @@ async function fetchPage2(teamId, q) {
|
|
|
854
854
|
}
|
|
855
855
|
function label(t, defaultTenant, active) {
|
|
856
856
|
const tags = [
|
|
857
|
-
t.tenant_name === active ?
|
|
858
|
-
t.tenant_name === defaultTenant ?
|
|
857
|
+
t.tenant_name === active ? import_chalk34.default.cyan("active scope") : "",
|
|
858
|
+
t.tenant_name === defaultTenant ? import_chalk34.default.dim("team default") : ""
|
|
859
859
|
].filter(Boolean).join(", ");
|
|
860
|
-
const disp = t.display_name && t.display_name !== t.tenant_name ?
|
|
860
|
+
const disp = t.display_name && t.display_name !== t.tenant_name ? import_chalk34.default.dim(` ${t.display_name}`) : "";
|
|
861
861
|
return `${t.tenant_name}${disp}${tags ? ` (${tags})` : ""}`;
|
|
862
862
|
}
|
|
863
863
|
async function pickTenant(teamId, opts = {}) {
|
|
@@ -865,14 +865,14 @@ async function pickTenant(teamId, opts = {}) {
|
|
|
865
865
|
const active = loadCredentials()?.activeTenant;
|
|
866
866
|
let q = opts.initialQuery ?? "";
|
|
867
867
|
for (; ; ) {
|
|
868
|
-
const spinner = (0,
|
|
868
|
+
const spinner = (0, import_ora13.default)(q ? `Searching tenants for "${q}"...` : "Loading tenants...").start();
|
|
869
869
|
const page = await fetchPage2(teamId, q).finally(() => spinner.stop());
|
|
870
870
|
if (!page.total && !q) {
|
|
871
871
|
if (opts.allowCreate) {
|
|
872
872
|
const { make } = await inquirer3.prompt([{ type: "confirm", name: "make", message: "No tenants yet \u2014 create one?", default: true }]);
|
|
873
873
|
if (make) return await createTenantInline(teamId);
|
|
874
874
|
}
|
|
875
|
-
console.error(
|
|
875
|
+
console.error(import_chalk34.default.red("This team has no tenants. Create one with `apiblaze tenant create`."));
|
|
876
876
|
return null;
|
|
877
877
|
}
|
|
878
878
|
const truncated = page.total > page.rows.length;
|
|
@@ -881,7 +881,7 @@ async function pickTenant(teamId, opts = {}) {
|
|
|
881
881
|
value: t.tenant_name
|
|
882
882
|
}));
|
|
883
883
|
if (truncated || q) {
|
|
884
|
-
choices.push(new inquirer3.Separator(
|
|
884
|
+
choices.push(new inquirer3.Separator(import_chalk34.default.dim(
|
|
885
885
|
truncated ? `showing ${page.rows.length} of ${page.total}${q ? ` matching "${q}"` : ""} \u2014 search to narrow` : `matches for "${q}"`
|
|
886
886
|
)));
|
|
887
887
|
choices.push({ name: `\u{1F50D} Search${q ? " again" : ""}\u2026`, value: "\0search" });
|
|
@@ -915,12 +915,12 @@ async function createTenantInline(teamId) {
|
|
|
915
915
|
const { createTenantInteractive: createTenantInteractive2 } = await Promise.resolve().then(() => (init_tenant_create(), tenant_create_exports));
|
|
916
916
|
return createTenantInteractive2(teamId);
|
|
917
917
|
}
|
|
918
|
-
var
|
|
918
|
+
var import_chalk34, import_ora13, PAGE;
|
|
919
919
|
var init_tenant_pick = __esm({
|
|
920
920
|
"src/lib/tenant-pick.ts"() {
|
|
921
921
|
"use strict";
|
|
922
|
-
|
|
923
|
-
|
|
922
|
+
import_chalk34 = __toESM(require("chalk"));
|
|
923
|
+
import_ora13 = __toESM(require("ora"));
|
|
924
924
|
init_admin();
|
|
925
925
|
init_auth();
|
|
926
926
|
PAGE = 15;
|
|
@@ -929,10 +929,10 @@ var init_tenant_pick = __esm({
|
|
|
929
929
|
|
|
930
930
|
// src/index.ts
|
|
931
931
|
var import_commander = require("commander");
|
|
932
|
-
var
|
|
932
|
+
var import_chalk51 = __toESM(require("chalk"));
|
|
933
933
|
|
|
934
934
|
// package.json
|
|
935
|
-
var version = "0.
|
|
935
|
+
var version = "0.20.2";
|
|
936
936
|
|
|
937
937
|
// src/index.ts
|
|
938
938
|
init_types();
|
|
@@ -3225,10 +3225,10 @@ async function runDelete(project, version2, opts) {
|
|
|
3225
3225
|
}
|
|
3226
3226
|
if (!opts.yes) {
|
|
3227
3227
|
const { default: inquirer3 } = await import("inquirer");
|
|
3228
|
-
const { confirm } = await inquirer3.prompt([
|
|
3228
|
+
const { confirm: confirm2 } = await inquirer3.prompt([
|
|
3229
3229
|
{ type: "confirm", name: "confirm", message: `Permanently delete ${proj2.projectName} v${proj2.apiVersion}? This cannot be undone.`, default: false }
|
|
3230
3230
|
]);
|
|
3231
|
-
if (!
|
|
3231
|
+
if (!confirm2) {
|
|
3232
3232
|
console.log(import_chalk23.default.dim("Aborted."));
|
|
3233
3233
|
return;
|
|
3234
3234
|
}
|
|
@@ -3307,34 +3307,1313 @@ async function runExport(projectArg, versionArg, opts) {
|
|
|
3307
3307
|
if (target === "kong") console.log(import_chalk24.default.dim("Kong: unzip, then `cd kong && docker compose up` (see kong/README.md)."));
|
|
3308
3308
|
}
|
|
3309
3309
|
|
|
3310
|
-
// src/commands/
|
|
3310
|
+
// src/commands/recipe-search.ts
|
|
3311
|
+
var import_chalk25 = __toESM(require("chalk"));
|
|
3312
|
+
var import_ora8 = __toESM(require("ora"));
|
|
3313
|
+
init_caller();
|
|
3314
|
+
|
|
3315
|
+
// src/lib/recipes.ts
|
|
3316
|
+
init_types();
|
|
3317
|
+
init_caller();
|
|
3318
|
+
var RECIPES_BASE = process.env.APIBLAZE_RECIPES_BASE || "https://recipes.apiblaze.com";
|
|
3319
|
+
function parseRecipeRef(raw) {
|
|
3320
|
+
const m = /^@([a-z0-9]+)\/([a-z0-9]+)(?:@(\d+))?$/.exec((raw || "").trim().toLowerCase());
|
|
3321
|
+
if (!m || m[1].length < 3 || m[2].length < 3) {
|
|
3322
|
+
throw new Error(
|
|
3323
|
+
`"${raw}" is not a recipe name. A recipe looks like @julien/gmail (or @julien/gmail@2 to pin a revision) \u2014 lowercase letters and digits only, at least 3 of each.`
|
|
3324
|
+
);
|
|
3325
|
+
}
|
|
3326
|
+
return {
|
|
3327
|
+
handle: m[1],
|
|
3328
|
+
slug: m[2],
|
|
3329
|
+
revision: m[3] ? Number(m[3]) : void 0,
|
|
3330
|
+
name: `@${m[1]}/${m[2]}`
|
|
3331
|
+
};
|
|
3332
|
+
}
|
|
3333
|
+
function resolveRecipeName(raw, githubHandle, proxyName) {
|
|
3334
|
+
const given = (raw ?? "").trim();
|
|
3335
|
+
if (given.startsWith("@")) {
|
|
3336
|
+
return { ref: parseRecipeRef(given), derivedHandle: false, normalizedFrom: null };
|
|
3337
|
+
}
|
|
3338
|
+
const slug = normalizeName2(given || proxyName);
|
|
3339
|
+
const handleRaw = (githubHandle ?? "").trim();
|
|
3340
|
+
const handle = normalizeName2(handleRaw);
|
|
3341
|
+
if (!handle || handle.length < 3) {
|
|
3342
|
+
throw new Error(
|
|
3343
|
+
"Could not work out a handle for you, so pass one: --as @yourhandle/name\n (a handle is lowercase letters and digits, at least 3.)"
|
|
3344
|
+
);
|
|
3345
|
+
}
|
|
3346
|
+
if (slug.length < 3) {
|
|
3347
|
+
throw new Error(`"${given || proxyName}" is too short for a recipe name \u2014 at least 3 letters or digits.`);
|
|
3348
|
+
}
|
|
3349
|
+
return {
|
|
3350
|
+
ref: parseRecipeRef(`@${handle}/${slug}`),
|
|
3351
|
+
derivedHandle: true,
|
|
3352
|
+
// GitHub allows hyphens; apiblaze names do not. Say so rather than silently
|
|
3353
|
+
// publishing under a name they did not type.
|
|
3354
|
+
normalizedFrom: handleRaw && handleRaw.toLowerCase() !== handle ? handleRaw : null
|
|
3355
|
+
};
|
|
3356
|
+
}
|
|
3357
|
+
function normalizeName2(raw) {
|
|
3358
|
+
return (raw || "").toLowerCase().replace(/[^a-z0-9]/g, "");
|
|
3359
|
+
}
|
|
3360
|
+
async function publicFetch(path8) {
|
|
3361
|
+
const res = await fetch(`${RECIPES_BASE}/api/recipes${path8}`, {
|
|
3362
|
+
headers: { accept: "application/json" }
|
|
3363
|
+
}).catch((err) => {
|
|
3364
|
+
throw new Error(`Could not reach the recipe registry at ${RECIPES_BASE} \u2014 ${err.message}`);
|
|
3365
|
+
});
|
|
3366
|
+
let data = null;
|
|
3367
|
+
let json = false;
|
|
3368
|
+
try {
|
|
3369
|
+
data = await res.json();
|
|
3370
|
+
json = true;
|
|
3371
|
+
} catch {
|
|
3372
|
+
}
|
|
3373
|
+
return { status: res.status, data, json };
|
|
3374
|
+
}
|
|
3375
|
+
function registryDown(status) {
|
|
3376
|
+
return new Error(
|
|
3377
|
+
`The recipe registry at ${RECIPES_BASE}/api/recipes did not answer with JSON (HTTP ${status}).
|
|
3378
|
+
Set APIBLAZE_RECIPES_BASE if you are pointing at a different deployment.`
|
|
3379
|
+
);
|
|
3380
|
+
}
|
|
3381
|
+
async function searchRecipes(query, limit) {
|
|
3382
|
+
const q = `?q=${encodeURIComponent(query ?? "")}${limit ? `&limit=${limit}` : ""}`;
|
|
3383
|
+
const { status, data, json } = await publicFetch(`/search${q}`);
|
|
3384
|
+
if (!json) throw registryDown(status);
|
|
3385
|
+
if (status >= 400) throw new ApiError(status, data?.error ?? "Search failed.");
|
|
3386
|
+
return data?.recipes ?? [];
|
|
3387
|
+
}
|
|
3388
|
+
async function getRecipeHead(ref) {
|
|
3389
|
+
const { status, data, json } = await publicFetch(`/${ref.handle}/${ref.slug}`);
|
|
3390
|
+
if (!json) throw registryDown(status);
|
|
3391
|
+
if (status === 404) return null;
|
|
3392
|
+
if (status >= 400) throw new ApiError(status, data?.error ?? "Lookup failed.");
|
|
3393
|
+
return data;
|
|
3394
|
+
}
|
|
3395
|
+
async function fetchRecipeFile(ref, caller) {
|
|
3396
|
+
const head = ref.revision !== void 0 ? null : await getRecipeHead(ref);
|
|
3397
|
+
const revision = ref.revision ?? head?.revision;
|
|
3398
|
+
if (revision !== void 0) {
|
|
3399
|
+
const { status, data, json } = await publicFetch(
|
|
3400
|
+
`/${ref.handle}/${ref.slug}/${revision}`
|
|
3401
|
+
);
|
|
3402
|
+
if (!json) throw registryDown(status);
|
|
3403
|
+
if (status === 410) {
|
|
3404
|
+
throw new Error(`${ref.name}@${revision} was withdrawn by its publisher. Try a different revision.`);
|
|
3405
|
+
}
|
|
3406
|
+
if (status < 400 && data) return data;
|
|
3407
|
+
}
|
|
3408
|
+
if (!caller) {
|
|
3409
|
+
throw new Error(
|
|
3410
|
+
`${ref.name} does not exist, or it is private.
|
|
3411
|
+
Private recipes are visible only to the publisher's team \u2014 run \`apiblaze login\` if you are on it.`
|
|
3412
|
+
);
|
|
3413
|
+
}
|
|
3414
|
+
const path8 = revision !== void 0 ? `/recipes/${ref.handle}/${ref.slug}/${revision}` : `/recipes/${ref.handle}/${ref.slug}/file`;
|
|
3415
|
+
try {
|
|
3416
|
+
return await producer(caller, {
|
|
3417
|
+
method: "GET",
|
|
3418
|
+
path: path8,
|
|
3419
|
+
summary: `Read private recipe ${ref.name}${revision !== void 0 ? `@${revision}` : ""}`
|
|
3420
|
+
});
|
|
3421
|
+
} catch (err) {
|
|
3422
|
+
const status = err instanceof ApiError ? err.status : 0;
|
|
3423
|
+
if (status === 404 || status === 400) {
|
|
3424
|
+
throw new Error(
|
|
3425
|
+
`${ref.name} does not exist, or it is private and your team does not own it.
|
|
3426
|
+
(admin-api exposes no authorized read for a private recipe file yet \u2014 only GET /recipes/public/* is registered, so private install cannot resolve.)`
|
|
3427
|
+
);
|
|
3428
|
+
}
|
|
3429
|
+
throw err;
|
|
3430
|
+
}
|
|
3431
|
+
}
|
|
3432
|
+
function installUrls(ctx) {
|
|
3433
|
+
return {
|
|
3434
|
+
proxy_url: `https://${ctx.project}.abz.run`,
|
|
3435
|
+
redirect_uri: `https://${ctx.tenant}.auth.apiblaze.com/callback`,
|
|
3436
|
+
mcp_url: `https://${ctx.project}-${ctx.tenant}.mcp.apiblaze.com`,
|
|
3437
|
+
portal_url: `https://${ctx.tenant}.portal.apiblaze.com`
|
|
3438
|
+
};
|
|
3439
|
+
}
|
|
3440
|
+
function fillContext(text, ctx) {
|
|
3441
|
+
const u = installUrls(ctx);
|
|
3442
|
+
const table = {
|
|
3443
|
+
redirect_uri: u.redirect_uri,
|
|
3444
|
+
proxy_url: u.proxy_url,
|
|
3445
|
+
mcp_url: u.mcp_url,
|
|
3446
|
+
portal_url: u.portal_url,
|
|
3447
|
+
project: ctx.project,
|
|
3448
|
+
tenant: ctx.tenant
|
|
3449
|
+
};
|
|
3450
|
+
return (text || "").replace(/\{(redirect_uri|proxy_url|mcp_url|portal_url|project|tenant)\}/g, (m, k) => table[k] ?? m);
|
|
3451
|
+
}
|
|
3452
|
+
function fillAnswers(value, answers) {
|
|
3453
|
+
if (typeof value === "string") {
|
|
3454
|
+
return value.replace(/\{\{([A-Za-z0-9_]+)\}\}/g, (m, id) => Object.prototype.hasOwnProperty.call(answers, id) ? answers[id] : m);
|
|
3455
|
+
}
|
|
3456
|
+
if (Array.isArray(value)) return value.map((v) => fillAnswers(v, answers));
|
|
3457
|
+
if (value && typeof value === "object") {
|
|
3458
|
+
const out = {};
|
|
3459
|
+
for (const [k, v] of Object.entries(value)) out[k] = fillAnswers(v, answers);
|
|
3460
|
+
return out;
|
|
3461
|
+
}
|
|
3462
|
+
return value;
|
|
3463
|
+
}
|
|
3464
|
+
function countRoutes(spec2) {
|
|
3465
|
+
const paths = spec2?.paths;
|
|
3466
|
+
if (!paths || typeof paths !== "object") return 0;
|
|
3467
|
+
const VERBS = ["get", "post", "put", "patch", "delete", "head", "options"];
|
|
3468
|
+
let n = 0;
|
|
3469
|
+
for (const item of Object.values(paths)) {
|
|
3470
|
+
if (!item || typeof item !== "object") continue;
|
|
3471
|
+
for (const k of Object.keys(item)) if (VERBS.includes(k.toLowerCase())) n++;
|
|
3472
|
+
}
|
|
3473
|
+
return n;
|
|
3474
|
+
}
|
|
3475
|
+
function firstUpstream(recipe) {
|
|
3476
|
+
const envs = recipe.settings?.environments ?? {};
|
|
3477
|
+
for (const e of Object.values(envs)) {
|
|
3478
|
+
const t = typeof e?.target === "string" ? e.target.trim() : "";
|
|
3479
|
+
if (t) return t;
|
|
3480
|
+
}
|
|
3481
|
+
return null;
|
|
3482
|
+
}
|
|
3483
|
+
function upstreamHosts(recipe) {
|
|
3484
|
+
const envs = recipe.settings?.environments ?? {};
|
|
3485
|
+
const out = [];
|
|
3486
|
+
for (const e of Object.values(envs)) {
|
|
3487
|
+
try {
|
|
3488
|
+
const h = new URL(String(e?.target ?? "")).host;
|
|
3489
|
+
if (h && !out.includes(h)) out.push(h);
|
|
3490
|
+
} catch {
|
|
3491
|
+
}
|
|
3492
|
+
}
|
|
3493
|
+
return out;
|
|
3494
|
+
}
|
|
3495
|
+
|
|
3496
|
+
// src/commands/recipe-search.ts
|
|
3497
|
+
async function runRecipeSearch(query, opts) {
|
|
3498
|
+
const spinner = !opts.json ? (0, import_ora8.default)("Searching recipes...").start() : null;
|
|
3499
|
+
let hits;
|
|
3500
|
+
try {
|
|
3501
|
+
hits = await searchRecipes(query ?? "", opts.limit ? Number(opts.limit) : void 0);
|
|
3502
|
+
spinner?.stop();
|
|
3503
|
+
} catch (err) {
|
|
3504
|
+
spinner?.fail("Search failed.");
|
|
3505
|
+
throw err;
|
|
3506
|
+
}
|
|
3507
|
+
if (opts.json) {
|
|
3508
|
+
console.log(JSON.stringify(hits));
|
|
3509
|
+
return;
|
|
3510
|
+
}
|
|
3511
|
+
if (!hits.length) {
|
|
3512
|
+
console.log(import_chalk25.default.yellow(query ? `No public recipes match "${query}".` : "No public recipes yet."));
|
|
3513
|
+
console.log(import_chalk25.default.dim(" Publish one with: apiblaze publish <proxy> --as @yourhandle/name"));
|
|
3514
|
+
return;
|
|
3515
|
+
}
|
|
3516
|
+
console.log();
|
|
3517
|
+
for (const h of hits) {
|
|
3518
|
+
const pin = h.latest_revision ? import_chalk25.default.dim(`@${h.latest_revision}`) : "";
|
|
3519
|
+
const installs = h.install_count ? import_chalk25.default.dim(` ${h.install_count} install${h.install_count === 1 ? "" : "s"}`) : "";
|
|
3520
|
+
console.log(` ${import_chalk25.default.bold(h.name)}${pin}${h.display_name ? import_chalk25.default.dim(` \u2014 ${h.display_name}`) : ""}${installs}`);
|
|
3521
|
+
if (h.summary) console.log(` ${import_chalk25.default.dim(h.summary)}`);
|
|
3522
|
+
}
|
|
3523
|
+
console.log();
|
|
3524
|
+
console.log(import_chalk25.default.dim(` Install one with: ${import_chalk25.default.cyan(`npx apiblaze install ${hits[0].name}`)}`));
|
|
3525
|
+
console.log(import_chalk25.default.dim(` Read one first: ${import_chalk25.default.cyan(`npx apiblaze show ${hits[0].name}`)}`));
|
|
3526
|
+
console.log();
|
|
3527
|
+
}
|
|
3528
|
+
async function runRecipeShow(nameArg, opts) {
|
|
3529
|
+
const ref = parseRecipeRef(nameArg);
|
|
3530
|
+
const wantsSection = !!(opts.transforms || opts.settings || opts.auth || opts.openapi);
|
|
3531
|
+
const caller = resolveCaller();
|
|
3532
|
+
const head = ref.revision === void 0 ? await getRecipeHead(ref).catch(() => null) : null;
|
|
3533
|
+
const spinner = !opts.json ? (0, import_ora8.default)(`Reading ${ref.name}...`).start() : null;
|
|
3534
|
+
let recipe;
|
|
3535
|
+
try {
|
|
3536
|
+
recipe = await fetchRecipeFile(ref, caller);
|
|
3537
|
+
spinner?.stop();
|
|
3538
|
+
} catch (err) {
|
|
3539
|
+
spinner?.stop();
|
|
3540
|
+
throw err;
|
|
3541
|
+
}
|
|
3542
|
+
if (opts.json) {
|
|
3543
|
+
if (opts.transforms) console.log(JSON.stringify(recipe.transformations));
|
|
3544
|
+
else if (opts.settings) console.log(JSON.stringify(recipe.settings));
|
|
3545
|
+
else if (opts.auth) console.log(JSON.stringify(recipe.auth));
|
|
3546
|
+
else if (opts.openapi) console.log(JSON.stringify(recipe.openapi));
|
|
3547
|
+
else console.log(JSON.stringify(recipe));
|
|
3548
|
+
return;
|
|
3549
|
+
}
|
|
3550
|
+
console.log();
|
|
3551
|
+
console.log(`${import_chalk25.default.bold(`${recipe.name}@${recipe.revision}`)}${import_chalk25.default.dim(` \u2014 by ${ref.handle}`)}`);
|
|
3552
|
+
if (recipe.display_name) console.log(` ${import_chalk25.default.bold(recipe.display_name)}`);
|
|
3553
|
+
if (recipe.summary) console.log(` ${recipe.summary}`);
|
|
3554
|
+
const meta = [
|
|
3555
|
+
head?.install_count !== void 0 ? `${head.install_count} install${head.install_count === 1 ? "" : "s"}` : null,
|
|
3556
|
+
recipe.license ? `licence ${recipe.license}` : null,
|
|
3557
|
+
recipe.published_at ? `published ${recipe.published_at.slice(0, 10)}` : null,
|
|
3558
|
+
`api version ${recipe.api_version}`
|
|
3559
|
+
].filter(Boolean).join(" \xB7 ");
|
|
3560
|
+
console.log(import_chalk25.default.dim(` ${meta}`));
|
|
3561
|
+
console.log();
|
|
3562
|
+
if (wantsSection) {
|
|
3563
|
+
if (opts.settings) printSection("settings", recipe.settings);
|
|
3564
|
+
if (opts.auth) printAuth(recipe);
|
|
3565
|
+
if (opts.transforms) printTransforms(recipe);
|
|
3566
|
+
if (opts.openapi) console.log(JSON.stringify(recipe.openapi, null, 2));
|
|
3567
|
+
return;
|
|
3568
|
+
}
|
|
3569
|
+
const hosts = upstreamHosts(recipe);
|
|
3570
|
+
const routes = countRoutes(recipe.openapi);
|
|
3571
|
+
const rules = recipe.transformations?.rules?.length ?? 0;
|
|
3572
|
+
const tables = recipe.transformations?.mapping_tables?.length ?? 0;
|
|
3573
|
+
const keyTypes = recipe.auth?.key_types?.length ?? 0;
|
|
3574
|
+
if (hosts.length) console.log(` ${import_chalk25.default.dim("sends requests to")} ${hosts.join(", ")}`);
|
|
3575
|
+
console.log(` ${import_chalk25.default.dim("creates ")} ${describeCreates(keyTypes, routes, !!recipe.settings.mcp_enabled)}`);
|
|
3576
|
+
console.log(` ${import_chalk25.default.dim("asks you for ")} ${recipe.questions.length} value${recipe.questions.length === 1 ? "" : "s"}`);
|
|
3577
|
+
if (rules || tables) {
|
|
3578
|
+
console.log(` ${import_chalk25.default.dim("transforms ")} ${rules} rule${rules === 1 ? "" : "s"}${tables ? `, ${tables} mapping table${tables === 1 ? "" : "s"}` : ""}`);
|
|
3579
|
+
}
|
|
3580
|
+
if (recipe.settings.publish_openapi === true) {
|
|
3581
|
+
console.log(` ${import_chalk25.default.dim("makes public ")} this API's spec`);
|
|
3582
|
+
}
|
|
3583
|
+
if (recipe.questions.length) {
|
|
3584
|
+
console.log();
|
|
3585
|
+
console.log(import_chalk25.default.bold(" It will ask you for"));
|
|
3586
|
+
for (const q of recipe.questions) {
|
|
3587
|
+
console.log(` ${q.prompt}${q.secret ? import_chalk25.default.dim(" (hidden while you type)") : ""}`);
|
|
3588
|
+
if (q.example) console.log(import_chalk25.default.dim(` e.g. ${q.example}`));
|
|
3589
|
+
}
|
|
3590
|
+
}
|
|
3591
|
+
if (recipe.requires_confirmation?.length) {
|
|
3592
|
+
console.log();
|
|
3593
|
+
console.log(import_chalk25.default.yellow(" Needs your explicit consent at install time"));
|
|
3594
|
+
for (const c of recipe.requires_confirmation) {
|
|
3595
|
+
console.log(` ${import_chalk25.default.bold(c.kind)} ${c.value}`);
|
|
3596
|
+
console.log(import_chalk25.default.dim(` ${c.why}`));
|
|
3597
|
+
}
|
|
3598
|
+
}
|
|
3599
|
+
console.log();
|
|
3600
|
+
console.log(import_chalk25.default.dim(` Install: ${import_chalk25.default.cyan(`npx apiblaze install ${recipe.name}`)}`));
|
|
3601
|
+
console.log(import_chalk25.default.dim(" Sections: --settings --auth --transforms --openapi"));
|
|
3602
|
+
console.log();
|
|
3603
|
+
}
|
|
3604
|
+
function describeCreates(keyTypes, routes, mcp) {
|
|
3605
|
+
const parts = ["1 proxy", "1 tenant"];
|
|
3606
|
+
if (keyTypes) parts.push(`${keyTypes} key type${keyTypes === 1 ? "" : "s"}`);
|
|
3607
|
+
if (routes) parts.push(mcp ? `${routes} MCP tools` : `${routes} route${routes === 1 ? "" : "s"}`);
|
|
3608
|
+
return parts.join(", ");
|
|
3609
|
+
}
|
|
3610
|
+
function printSection(title, value) {
|
|
3611
|
+
console.log(import_chalk25.default.bold(` ${title}`));
|
|
3612
|
+
console.log(indent(JSON.stringify(value ?? null, null, 2), " "));
|
|
3613
|
+
console.log();
|
|
3614
|
+
}
|
|
3615
|
+
function printAuth(recipe) {
|
|
3616
|
+
const a = recipe.auth ?? { app_client: null, provider: null, key_types: [] };
|
|
3617
|
+
console.log(import_chalk25.default.bold(" auth"));
|
|
3618
|
+
if (a.provider) {
|
|
3619
|
+
console.log(` ${import_chalk25.default.dim("provider ")} ${a.provider.type ?? "(unknown)"}`);
|
|
3620
|
+
if (a.provider.scopes) console.log(` ${import_chalk25.default.dim("scopes ")} ${a.provider.scopes}`);
|
|
3621
|
+
if (a.provider.target_server_token) console.log(` ${import_chalk25.default.dim("sends to API")} ${a.provider.target_server_token}`);
|
|
3622
|
+
console.log(import_chalk25.default.dim(" (no credentials travel in a recipe \u2014 install asks you for your own)"));
|
|
3623
|
+
} else {
|
|
3624
|
+
console.log(import_chalk25.default.dim(" no login provider \u2014 consumers call it with an API key"));
|
|
3625
|
+
}
|
|
3626
|
+
if (a.app_client) {
|
|
3627
|
+
console.log(` ${import_chalk25.default.dim("login app ")} ${a.app_client.name ?? "(unnamed)"}`);
|
|
3628
|
+
}
|
|
3629
|
+
if (a.key_types?.length) {
|
|
3630
|
+
console.log(` ${import_chalk25.default.dim("key types ")} ${a.key_types.map((k) => String(k.name)).join(", ")}`);
|
|
3631
|
+
}
|
|
3632
|
+
console.log();
|
|
3633
|
+
}
|
|
3634
|
+
function printTransforms(recipe) {
|
|
3635
|
+
const rules = recipe.transformations?.rules ?? [];
|
|
3636
|
+
const tables = recipe.transformations?.mapping_tables ?? [];
|
|
3637
|
+
console.log(import_chalk25.default.bold(` transforms (${rules.length})`));
|
|
3638
|
+
for (const r of rules) {
|
|
3639
|
+
const off = r.enabled === false ? import_chalk25.default.dim(" (disabled)") : "";
|
|
3640
|
+
console.log(` ${import_chalk25.default.bold(String(r.name ?? "(unnamed)"))} ${import_chalk25.default.dim(String(r.phase ?? "request"))}${off}`);
|
|
3641
|
+
console.log(indent(JSON.stringify(r.action ?? null, null, 2), " "));
|
|
3642
|
+
if (r.condition) console.log(indent(`when ${JSON.stringify(r.condition)}`, " "));
|
|
3643
|
+
}
|
|
3644
|
+
if (tables.length) {
|
|
3645
|
+
console.log();
|
|
3646
|
+
console.log(import_chalk25.default.bold(` mapping tables (${tables.length})`));
|
|
3647
|
+
for (const t of tables) {
|
|
3648
|
+
const entries2 = Array.isArray(t.entries) ? t.entries.length : 0;
|
|
3649
|
+
console.log(` ${import_chalk25.default.bold(String(t.name ?? "(unnamed)"))} ${import_chalk25.default.dim(`${entries2} entr${entries2 === 1 ? "y" : "ies"}`)}`);
|
|
3650
|
+
}
|
|
3651
|
+
}
|
|
3652
|
+
console.log();
|
|
3653
|
+
}
|
|
3654
|
+
function indent(text, pad) {
|
|
3655
|
+
return text.split("\n").map((l) => `${pad}${l}`).join("\n");
|
|
3656
|
+
}
|
|
3657
|
+
|
|
3658
|
+
// src/commands/recipe-install.ts
|
|
3311
3659
|
var import_chalk26 = __toESM(require("chalk"));
|
|
3660
|
+
init_types();
|
|
3661
|
+
init_caller();
|
|
3662
|
+
init_api();
|
|
3663
|
+
var APIKEYS_BASE = process.env.APIBLAZE_APIKEYS_BASE || "https://apikeys.apiblaze.com";
|
|
3664
|
+
async function runRecipeInstall(nameArg, opts) {
|
|
3665
|
+
const ref = parseRecipeRef(nameArg);
|
|
3666
|
+
const assumeYes = !!opts.yes;
|
|
3667
|
+
const interactive = !!process.stdin.isTTY && !assumeYes;
|
|
3668
|
+
const caller = requireCallerForInstall();
|
|
3669
|
+
const recipe = await fetchRecipeFile(ref, caller);
|
|
3670
|
+
const pinned = `${recipe.name}@${recipe.revision}`;
|
|
3671
|
+
const picked = await chooseTeam(caller, opts.team, interactive);
|
|
3672
|
+
const teamId = picked.teamId;
|
|
3673
|
+
const chosenTeamId = picked.chosen ? picked.teamId : void 0;
|
|
3674
|
+
const teamName = picked.teamName;
|
|
3675
|
+
console.log();
|
|
3676
|
+
console.log(`${import_chalk26.default.bold(pinned)}${import_chalk26.default.dim(` \u2014 by ${ref.handle}`)}`);
|
|
3677
|
+
if (recipe.summary) console.log(` ${recipe.summary}`);
|
|
3678
|
+
console.log();
|
|
3679
|
+
let project;
|
|
3680
|
+
let apiVersion;
|
|
3681
|
+
let tenant2;
|
|
3682
|
+
let reinstall = false;
|
|
3683
|
+
if (opts.into) {
|
|
3684
|
+
reinstall = true;
|
|
3685
|
+
const target = normalizeName2(opts.into);
|
|
3686
|
+
const proj2 = await resolveProjectVia(caller, teamId, target, opts.apiversion);
|
|
3687
|
+
project = proj2.projectId;
|
|
3688
|
+
apiVersion = proj2.apiVersion;
|
|
3689
|
+
tenant2 = await resolveReinstallTenant(caller, project, apiVersion);
|
|
3690
|
+
await confirmReinstall(recipe, project, tenant2, interactive);
|
|
3691
|
+
} else {
|
|
3692
|
+
project = await chooseProxyName(ref.slug, opts.as, teamId, interactive);
|
|
3693
|
+
apiVersion = recipe.api_version || "1.0.0";
|
|
3694
|
+
tenant2 = `${project}users`;
|
|
3695
|
+
await assertTenantFree(tenant2, teamId);
|
|
3696
|
+
printSummary(recipe, project, tenant2);
|
|
3697
|
+
if (!assumeYes && !await confirmYesNo(" continue?", interactive)) {
|
|
3698
|
+
console.log(import_chalk26.default.yellow(" Cancelled \u2014 nothing was created."));
|
|
3699
|
+
return;
|
|
3700
|
+
}
|
|
3701
|
+
}
|
|
3702
|
+
const ctx = { project, tenant: tenant2 };
|
|
3703
|
+
const declined = assumeYes ? declineAll(recipe.requires_confirmation ?? []) : await askConsent(recipe.requires_confirmation ?? [], interactive);
|
|
3704
|
+
const answers = await askQuestions(recipe.questions ?? [], ctx, interactive);
|
|
3705
|
+
const started = Date.now();
|
|
3706
|
+
const steps = buildSteps({ recipe, caller, teamId, chosenTeamId, teamName, ctx, answers, declined, apiVersion, reinstall, pinned });
|
|
3707
|
+
console.log();
|
|
3708
|
+
for (const step of steps) {
|
|
3709
|
+
if (step.skip) continue;
|
|
3710
|
+
process.stdout.write(` ${step.label.padEnd(33)}`);
|
|
3711
|
+
try {
|
|
3712
|
+
const note = await step.run();
|
|
3713
|
+
console.log(`${import_chalk26.default.green("\u2713")}${note ? ` ${import_chalk26.default.dim(note)}` : ""}`);
|
|
3714
|
+
} catch (err) {
|
|
3715
|
+
console.log(`${import_chalk26.default.red("\u2717")} ${import_chalk26.default.red(describeError(err))}`);
|
|
3716
|
+
console.log();
|
|
3717
|
+
if (step.n === 1) {
|
|
3718
|
+
console.log(import_chalk26.default.red(`stopped at step 1 of ${steps.length}. Nothing was created.`));
|
|
3719
|
+
console.log("run the same command again once the problem above is fixed:");
|
|
3720
|
+
console.log(import_chalk26.default.cyan(` npx apiblaze install ${nameArg}`));
|
|
3721
|
+
} else {
|
|
3722
|
+
console.log(import_chalk26.default.red(`stopped at step ${step.n} of ${steps.length}. ${project} exists but is not finished.`));
|
|
3723
|
+
console.log("continue with:");
|
|
3724
|
+
console.log(import_chalk26.default.cyan(` npx apiblaze install ${nameArg} --into ${project}`));
|
|
3725
|
+
}
|
|
3726
|
+
console.log();
|
|
3727
|
+
process.exit(1);
|
|
3728
|
+
}
|
|
3729
|
+
}
|
|
3730
|
+
const secs = ((Date.now() - started) / 1e3).toFixed(0);
|
|
3731
|
+
const urls = installUrls(ctx);
|
|
3732
|
+
console.log();
|
|
3733
|
+
console.log(`done in ${secs}s.`);
|
|
3734
|
+
console.log(` ${import_chalk26.default.dim("proxy ")} ${import_chalk26.default.bold(urls.proxy_url)}`);
|
|
3735
|
+
if (recipe.settings.mcp_enabled === true) console.log(` ${import_chalk26.default.dim("mcp ")} ${import_chalk26.default.bold(urls.mcp_url)}`);
|
|
3736
|
+
if (recipe.settings.enable_portal === true) console.log(` ${import_chalk26.default.dim("portal")} ${import_chalk26.default.bold(urls.portal_url)}`);
|
|
3737
|
+
console.log();
|
|
3738
|
+
}
|
|
3739
|
+
function buildSteps(inp) {
|
|
3740
|
+
const { recipe, caller, ctx, answers, declined, reinstall, pinned } = inp;
|
|
3741
|
+
const { project, tenant: tenant2 } = ctx;
|
|
3742
|
+
let teamId = inp.teamId;
|
|
3743
|
+
const { chosenTeamId, teamName } = inp;
|
|
3744
|
+
const filled = fillAnswers(
|
|
3745
|
+
{
|
|
3746
|
+
settings: recipe.settings ?? {},
|
|
3747
|
+
auth: recipe.auth ?? { app_client: null, provider: null, key_types: [] },
|
|
3748
|
+
transformations: recipe.transformations ?? { rules: [], mapping_tables: [] },
|
|
3749
|
+
openapi: recipe.openapi ?? null
|
|
3750
|
+
},
|
|
3751
|
+
answers
|
|
3752
|
+
);
|
|
3753
|
+
let apiVersion = inp.apiVersion;
|
|
3754
|
+
const rules = (filled.transformations.rules ?? []).filter((r) => !declined.has(`transform:${String(r.name ?? "")}`));
|
|
3755
|
+
const tables = filled.transformations.mapping_tables ?? [];
|
|
3756
|
+
const keyTypes = filled.auth.key_types ?? [];
|
|
3757
|
+
const routes = countRoutes(filled.openapi);
|
|
3758
|
+
return [
|
|
3759
|
+
{
|
|
3760
|
+
n: 1,
|
|
3761
|
+
label: `creating ${project}`,
|
|
3762
|
+
skip: reinstall,
|
|
3763
|
+
run: async () => {
|
|
3764
|
+
const target = firstUpstream(recipe);
|
|
3765
|
+
if (!target) throw new Error("this recipe carries no upstream URL, so there is nothing to create a proxy against");
|
|
3766
|
+
const out = await producer(caller, {
|
|
3767
|
+
method: "POST",
|
|
3768
|
+
path: "/projects",
|
|
3769
|
+
body: {
|
|
3770
|
+
name: project,
|
|
3771
|
+
target,
|
|
3772
|
+
tenant: tenant2,
|
|
3773
|
+
product_slug: project,
|
|
3774
|
+
api_version: apiVersion,
|
|
3775
|
+
auth_type: "api_key",
|
|
3776
|
+
// Sent so a chosen team is honoured. create-project.ts:919 IGNORES an
|
|
3777
|
+
// unauthorized team_id and silently substitutes the caller's default,
|
|
3778
|
+
// returning 201 — so the landing team is verified below rather than
|
|
3779
|
+
// assumed. Without that check "install into team B" could quietly
|
|
3780
|
+
// create the proxy in team A.
|
|
3781
|
+
team_id: chosenTeamId
|
|
3782
|
+
},
|
|
3783
|
+
summary: `Create proxy ${project} (tenant ${tenant2}) from ${pinned}`
|
|
3784
|
+
});
|
|
3785
|
+
if (out?.api_version) apiVersion = out.api_version;
|
|
3786
|
+
const landed = await owningTeam(caller, project, teamId);
|
|
3787
|
+
if (chosenTeamId && landed !== chosenTeamId) {
|
|
3788
|
+
throw new Error(
|
|
3789
|
+
`${project} was created in a different team than you chose. You may not have permission to create proxies there \u2014 delete it and re-run with a team you belong to.`
|
|
3790
|
+
);
|
|
3791
|
+
}
|
|
3792
|
+
teamId = landed;
|
|
3793
|
+
const landedName = teamName ?? await teamLabel(caller, landed);
|
|
3794
|
+
return landedName ? `proxy + tenant in ${landedName}` : "proxy + tenant";
|
|
3795
|
+
}
|
|
3796
|
+
},
|
|
3797
|
+
{
|
|
3798
|
+
n: 2,
|
|
3799
|
+
label: `recording ${pinned}`,
|
|
3800
|
+
run: async () => {
|
|
3801
|
+
await producer(caller, {
|
|
3802
|
+
method: "PATCH",
|
|
3803
|
+
path: `/projects/${encodeURIComponent(project)}/${encodeURIComponent(apiVersion)}/installed`,
|
|
3804
|
+
body: { installed_from: pinned, installed_tenant: tenant2 },
|
|
3805
|
+
summary: `Stamp provenance ${pinned} on ${project}`
|
|
3806
|
+
});
|
|
3807
|
+
return "provenance";
|
|
3808
|
+
}
|
|
3809
|
+
},
|
|
3810
|
+
{
|
|
3811
|
+
n: 3,
|
|
3812
|
+
label: "settings and environments",
|
|
3813
|
+
run: async () => {
|
|
3814
|
+
const s = filled.settings;
|
|
3815
|
+
const body = {};
|
|
3816
|
+
if (!declined.has("kind:upstream") && s.environments) body.environments = s.environments;
|
|
3817
|
+
if (!declined.has("kind:quota") && s.throttling) body.throttling = s.throttling;
|
|
3818
|
+
if (!declined.has("kind:cors") && s.cors !== void 0) body.cors = s.cors;
|
|
3819
|
+
if (!declined.has("kind:request policy") && s.requests_policy) body.requests_policy = s.requests_policy;
|
|
3820
|
+
if (!declined.has("kind:dev portal") && typeof s.enable_portal === "boolean") body.enable_portal = s.enable_portal;
|
|
3821
|
+
if (!declined.has("kind:mcp server") && typeof s.mcp_enabled === "boolean") body.mcp_enabled = s.mcp_enabled;
|
|
3822
|
+
if (typeof s.project_display_name === "string") body.display_name = s.project_display_name;
|
|
3823
|
+
if (typeof s.publish_openapi === "boolean") body.publish_openapi = s.publish_openapi;
|
|
3824
|
+
if (!Object.keys(body).length) return "nothing to apply";
|
|
3825
|
+
await producer(caller, {
|
|
3826
|
+
method: "PATCH",
|
|
3827
|
+
path: `/projects/${encodeURIComponent(project)}/${encodeURIComponent(apiVersion)}`,
|
|
3828
|
+
body,
|
|
3829
|
+
summary: `Apply recipe settings to ${project}`
|
|
3830
|
+
});
|
|
3831
|
+
return Object.keys(body).join(", ");
|
|
3832
|
+
}
|
|
3833
|
+
},
|
|
3834
|
+
{
|
|
3835
|
+
n: 4,
|
|
3836
|
+
label: providerLabel(filled.auth.provider),
|
|
3837
|
+
skip: !filled.auth.provider,
|
|
3838
|
+
run: async () => {
|
|
3839
|
+
const p = filled.auth.provider;
|
|
3840
|
+
const clientId = answers.oauth_client_id;
|
|
3841
|
+
const clientSecret = answers.oauth_client_secret;
|
|
3842
|
+
if (!clientId || !clientSecret) {
|
|
3843
|
+
throw new Error("the OAuth client id and secret were not supplied, so the login provider cannot be created");
|
|
3844
|
+
}
|
|
3845
|
+
const appClientId = await defaultAppClientId(caller, teamId, tenant2);
|
|
3846
|
+
await producer(caller, {
|
|
3847
|
+
method: "POST",
|
|
3848
|
+
path: `/teams/${encodeURIComponent(teamId)}/tenants/${encodeURIComponent(tenant2)}/app-clients/${encodeURIComponent(appClientId)}/providers`,
|
|
3849
|
+
body: {
|
|
3850
|
+
type: p.type,
|
|
3851
|
+
clientId,
|
|
3852
|
+
clientSecret,
|
|
3853
|
+
scopes: p.scopes,
|
|
3854
|
+
tokenType: p.token_type,
|
|
3855
|
+
targetServerToken: p.target_server_token,
|
|
3856
|
+
includeApiblazeAccessTokenHeader: p.include_apiblaze_access_token_header === true,
|
|
3857
|
+
includeApiblazeIdTokenHeader: p.include_apiblaze_id_token_header === true
|
|
3858
|
+
},
|
|
3859
|
+
summary: `Create the ${String(p.type)} login provider on ${tenant2}`
|
|
3860
|
+
});
|
|
3861
|
+
return String(p.type);
|
|
3862
|
+
}
|
|
3863
|
+
},
|
|
3864
|
+
{
|
|
3865
|
+
n: 5,
|
|
3866
|
+
label: keyTypes.length ? `${keyTypes.length} key type${keyTypes.length === 1 ? "" : "s"}` : "key types",
|
|
3867
|
+
skip: !keyTypes.length,
|
|
3868
|
+
run: async () => {
|
|
3869
|
+
let added = 0;
|
|
3870
|
+
let existed = 0;
|
|
3871
|
+
await withCpKey(caller, teamId, async (cpKey) => {
|
|
3872
|
+
for (const kt of keyTypes) {
|
|
3873
|
+
const scopes = parseMaybeJson(kt.scopes_json);
|
|
3874
|
+
const res = await fetch(`${APIKEYS_BASE}/key-types?product=apiblaze&tenant=${encodeURIComponent(tenant2)}`, {
|
|
3875
|
+
method: "POST",
|
|
3876
|
+
headers: { "Content-Type": "application/json", "X-API-Key": cpKey },
|
|
3877
|
+
body: JSON.stringify({
|
|
3878
|
+
tenant: tenant2,
|
|
3879
|
+
name: kt.name,
|
|
3880
|
+
scopes: Array.isArray(scopes) ? scopes : [],
|
|
3881
|
+
...kt.default_expires_at_seconds != null ? { default_expires_at_seconds: kt.default_expires_at_seconds } : {}
|
|
3882
|
+
})
|
|
3883
|
+
});
|
|
3884
|
+
if (res.status === 409) {
|
|
3885
|
+
existed++;
|
|
3886
|
+
continue;
|
|
3887
|
+
}
|
|
3888
|
+
if (!res.ok) {
|
|
3889
|
+
const body = await res.json().catch(() => ({}));
|
|
3890
|
+
const msg = typeof body.error === "string" ? body.error : body.error?.message;
|
|
3891
|
+
throw new ApiError(res.status, `key type "${String(kt.name)}": ${msg ?? res.statusText}`);
|
|
3892
|
+
}
|
|
3893
|
+
added++;
|
|
3894
|
+
}
|
|
3895
|
+
});
|
|
3896
|
+
return existed ? `${added} added, ${existed} already there` : `${added} added`;
|
|
3897
|
+
}
|
|
3898
|
+
},
|
|
3899
|
+
{
|
|
3900
|
+
n: 6,
|
|
3901
|
+
label: transformsLabel(rules.length, tables.length),
|
|
3902
|
+
skip: !rules.length && !tables.length,
|
|
3903
|
+
run: async () => {
|
|
3904
|
+
for (const t of tables) {
|
|
3905
|
+
await producer(caller, {
|
|
3906
|
+
method: "POST",
|
|
3907
|
+
path: `/projects/${encodeURIComponent(project)}/${encodeURIComponent(apiVersion)}/mappings`,
|
|
3908
|
+
body: { name: t.name, entries: t.entries ?? [], encrypt_values: t.encrypt_values === true },
|
|
3909
|
+
summary: `Create mapping table "${String(t.name)}"`
|
|
3910
|
+
}).catch((err) => {
|
|
3911
|
+
throw wrap(err, `mapping table "${String(t.name)}"`);
|
|
3912
|
+
});
|
|
3913
|
+
}
|
|
3914
|
+
for (const r of rules) {
|
|
3915
|
+
await producer(caller, {
|
|
3916
|
+
method: "POST",
|
|
3917
|
+
path: `/projects/${encodeURIComponent(project)}/${encodeURIComponent(apiVersion)}/transforms`,
|
|
3918
|
+
// The recipe carries `ord`; the create route reads `order`.
|
|
3919
|
+
body: {
|
|
3920
|
+
name: r.name,
|
|
3921
|
+
action: r.action,
|
|
3922
|
+
phase: r.phase ?? "request",
|
|
3923
|
+
enabled: r.enabled !== false,
|
|
3924
|
+
...r.ord != null ? { order: r.ord } : {},
|
|
3925
|
+
...r.condition ? { condition: r.condition } : {}
|
|
3926
|
+
},
|
|
3927
|
+
summary: `Create transform rule "${String(r.name)}"`
|
|
3928
|
+
}).catch((err) => {
|
|
3929
|
+
throw wrap(err, `rule "${String(r.name)}"`);
|
|
3930
|
+
});
|
|
3931
|
+
}
|
|
3932
|
+
return void 0;
|
|
3933
|
+
}
|
|
3934
|
+
},
|
|
3935
|
+
{
|
|
3936
|
+
n: 7,
|
|
3937
|
+
label: routes ? `API spec \u2192 ${routes} routes${filled.settings.mcp_enabled === true ? ", MCP tools" : ""}` : "API spec",
|
|
3938
|
+
skip: !filled.openapi,
|
|
3939
|
+
run: async () => {
|
|
3940
|
+
const out = await producer(caller, {
|
|
3941
|
+
method: "POST",
|
|
3942
|
+
path: `/projects/${encodeURIComponent(project)}/${encodeURIComponent(apiVersion)}/refresh-spec`,
|
|
3943
|
+
body: { specContent: JSON.stringify(filled.openapi) },
|
|
3944
|
+
summary: `Install the recipe's OpenAPI spec on ${project}`
|
|
3945
|
+
});
|
|
3946
|
+
const added = out?.report?.routes?.added?.length ?? routes;
|
|
3947
|
+
return out?.report?.applied?.mcp_regenerated ? `${added} routes, MCP rebuilt` : `${added} routes`;
|
|
3948
|
+
}
|
|
3949
|
+
}
|
|
3950
|
+
];
|
|
3951
|
+
}
|
|
3952
|
+
function providerLabel(provider) {
|
|
3953
|
+
if (!provider) return "login provider";
|
|
3954
|
+
const t = String(provider.type ?? "oauth");
|
|
3955
|
+
return `${t.charAt(0).toUpperCase()}${t.slice(1)} provider`;
|
|
3956
|
+
}
|
|
3957
|
+
function transformsLabel(rules, tables) {
|
|
3958
|
+
const parts = [];
|
|
3959
|
+
if (rules) parts.push(`${rules} transform${rules === 1 ? "" : "s"}`);
|
|
3960
|
+
if (tables) parts.push(`${tables} mapping table${tables === 1 ? "" : "s"}`);
|
|
3961
|
+
return parts.join(", ") || "transforms";
|
|
3962
|
+
}
|
|
3963
|
+
function parseMaybeJson(v) {
|
|
3964
|
+
if (typeof v !== "string") return v;
|
|
3965
|
+
try {
|
|
3966
|
+
return JSON.parse(v);
|
|
3967
|
+
} catch {
|
|
3968
|
+
return v;
|
|
3969
|
+
}
|
|
3970
|
+
}
|
|
3971
|
+
function wrap(err, what) {
|
|
3972
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
3973
|
+
return new Error(`${what}: ${msg}`);
|
|
3974
|
+
}
|
|
3975
|
+
function describeError(err) {
|
|
3976
|
+
if (err instanceof ApiError) {
|
|
3977
|
+
const body = err.body;
|
|
3978
|
+
return `${err.message}${body?.details && body.details !== err.message ? ` \u2014 ${body.details}` : ""}`;
|
|
3979
|
+
}
|
|
3980
|
+
return err instanceof Error ? err.message : String(err);
|
|
3981
|
+
}
|
|
3982
|
+
async function withCpKey(caller, teamId, fn) {
|
|
3983
|
+
if (caller.mode === "anon" && caller.cpKey) return fn(caller.cpKey);
|
|
3984
|
+
const minted = await producer(caller, {
|
|
3985
|
+
method: "POST",
|
|
3986
|
+
path: `/teams/${encodeURIComponent(teamId)}/developer-keys`,
|
|
3987
|
+
body: { description: "apiblaze install (temporary)", expires_in_seconds: 300 },
|
|
3988
|
+
summary: "Mint a 5-minute control-plane key for the key-types step"
|
|
3989
|
+
});
|
|
3990
|
+
if (!minted?.key) throw new Error("could not mint a control-plane key for the key-types step");
|
|
3991
|
+
try {
|
|
3992
|
+
return await fn(minted.key);
|
|
3993
|
+
} finally {
|
|
3994
|
+
if (minted.key_id) {
|
|
3995
|
+
await producer(caller, {
|
|
3996
|
+
method: "DELETE",
|
|
3997
|
+
path: `/teams/${encodeURIComponent(teamId)}/developer-keys/${encodeURIComponent(minted.key_id)}`,
|
|
3998
|
+
summary: "Revoke the temporary control-plane key"
|
|
3999
|
+
}).catch(() => {
|
|
4000
|
+
});
|
|
4001
|
+
}
|
|
4002
|
+
}
|
|
4003
|
+
}
|
|
4004
|
+
async function teamLabel(caller, teamId) {
|
|
4005
|
+
if (caller.mode === "anon") return null;
|
|
4006
|
+
const teams = await getTeams().catch(() => []);
|
|
4007
|
+
return teams.find((t) => t.teamId === teamId)?.name ?? null;
|
|
4008
|
+
}
|
|
4009
|
+
async function owningTeam(caller, project, preferred) {
|
|
4010
|
+
const has = async (teamId) => {
|
|
4011
|
+
const out = await producer(caller, {
|
|
4012
|
+
method: "GET",
|
|
4013
|
+
path: `/projects?team_id=${encodeURIComponent(teamId)}`,
|
|
4014
|
+
summary: `Confirm ${project} landed in ${teamId}`
|
|
4015
|
+
}).catch(() => ({ projects: [] }));
|
|
4016
|
+
return (out?.projects ?? []).some((p) => p.project_id === project);
|
|
4017
|
+
};
|
|
4018
|
+
if (await has(preferred)) return preferred;
|
|
4019
|
+
if (caller.mode === "anon") return preferred;
|
|
4020
|
+
const { getTeams: getTeams2 } = await Promise.resolve().then(() => (init_api(), api_exports));
|
|
4021
|
+
const teams = await getTeams2().catch(() => []);
|
|
4022
|
+
for (const t of teams) {
|
|
4023
|
+
if (t.teamId !== preferred && await has(t.teamId)) return t.teamId;
|
|
4024
|
+
}
|
|
4025
|
+
return preferred;
|
|
4026
|
+
}
|
|
4027
|
+
async function defaultAppClientId(caller, teamId, tenant2) {
|
|
4028
|
+
const out = await producer(caller, {
|
|
4029
|
+
method: "GET",
|
|
4030
|
+
path: `/teams/${encodeURIComponent(teamId)}/tenants/${encodeURIComponent(tenant2)}/app-clients`,
|
|
4031
|
+
summary: `Find the login app for ${tenant2}`
|
|
4032
|
+
});
|
|
4033
|
+
const list = Array.isArray(out) ? out : out?.app_clients ?? out?.clients ?? [];
|
|
4034
|
+
const chosen = list.find((c) => c.is_default === true || c.default === true) ?? list[0];
|
|
4035
|
+
const id = chosen?.client_id ?? chosen?.id;
|
|
4036
|
+
if (typeof id !== "string" || !id) {
|
|
4037
|
+
throw new Error(`tenant ${tenant2} has no login app to attach the provider to`);
|
|
4038
|
+
}
|
|
4039
|
+
return id;
|
|
4040
|
+
}
|
|
4041
|
+
async function nameIsFree(name, teamId) {
|
|
4042
|
+
try {
|
|
4043
|
+
const check = await checkProxyName(name, teamId);
|
|
4044
|
+
return check.canUseProjectName && check.canUseApiVersion;
|
|
4045
|
+
} catch {
|
|
4046
|
+
return null;
|
|
4047
|
+
}
|
|
4048
|
+
}
|
|
4049
|
+
async function chooseProxyName(slug, asOpt, teamId, interactive) {
|
|
4050
|
+
if (asOpt !== void 0) {
|
|
4051
|
+
const name = normalizeName2(asOpt);
|
|
4052
|
+
if (name !== asOpt) console.log(import_chalk26.default.yellow(` Using "${name}" \u2014 names are lowercase letters and digits only.`));
|
|
4053
|
+
if (name.length < 3) throw new Error("A proxy name must be at least 3 characters (lowercase letters and digits only).");
|
|
4054
|
+
const free = await nameIsFree(name, teamId);
|
|
4055
|
+
if (free === false) throw new Error(`"${name}" is already taken. Pick another with --as.`);
|
|
4056
|
+
return name;
|
|
4057
|
+
}
|
|
4058
|
+
const base2 = normalizeName2(`my${slug}`) || normalizeName2(slug);
|
|
4059
|
+
let suggested = base2;
|
|
4060
|
+
for (let i = 1; i <= 20; i++) {
|
|
4061
|
+
const candidate = i === 1 ? base2 : `${base2}${i}`;
|
|
4062
|
+
if (await nameIsFree(candidate, teamId) !== false) {
|
|
4063
|
+
suggested = candidate;
|
|
4064
|
+
break;
|
|
4065
|
+
}
|
|
4066
|
+
}
|
|
4067
|
+
if (!interactive) return suggested;
|
|
4068
|
+
const { default: inquirer3 } = await import("inquirer");
|
|
4069
|
+
console.log(` ${import_chalk26.default.dim("proxy")} ${slug} \u2192 ${import_chalk26.default.bold(suggested)} ${import_chalk26.default.dim("[enter to accept, or type a name]")}`);
|
|
4070
|
+
for (; ; ) {
|
|
4071
|
+
const { raw } = await inquirer3.prompt([{
|
|
4072
|
+
type: "input",
|
|
4073
|
+
name: "raw",
|
|
4074
|
+
message: ">",
|
|
4075
|
+
default: suggested,
|
|
4076
|
+
transformer: (v) => normalizeName2(v)
|
|
4077
|
+
}]);
|
|
4078
|
+
const name = normalizeName2(raw);
|
|
4079
|
+
if (name !== String(raw).trim()) {
|
|
4080
|
+
console.log(import_chalk26.default.yellow(` \u2192 using ${import_chalk26.default.bold(name)} (lowercase letters and digits only)`));
|
|
4081
|
+
}
|
|
4082
|
+
if (name.length < 3) {
|
|
4083
|
+
console.log(import_chalk26.default.yellow(" Name must be at least 3 characters.\n"));
|
|
4084
|
+
continue;
|
|
4085
|
+
}
|
|
4086
|
+
const free = await nameIsFree(name, teamId);
|
|
4087
|
+
if (free === false) {
|
|
4088
|
+
console.log(import_chalk26.default.yellow(` "${name}" is taken. Try another.
|
|
4089
|
+
`));
|
|
4090
|
+
continue;
|
|
4091
|
+
}
|
|
4092
|
+
if (free === null) console.log(import_chalk26.default.dim(" (could not verify availability; continuing)"));
|
|
4093
|
+
console.log(` ${import_chalk26.default.cyan("\u2192")} ${import_chalk26.default.bold(`https://${name}.abz.run`)} ${import_chalk26.default.dim(`users live in the tenant ${name}users`)}`);
|
|
4094
|
+
return name;
|
|
4095
|
+
}
|
|
4096
|
+
}
|
|
4097
|
+
async function assertTenantFree(tenant2, teamId) {
|
|
4098
|
+
if (await nameIsFree(tenant2, teamId) === false) {
|
|
4099
|
+
throw new Error(
|
|
4100
|
+
`The tenant name "${tenant2}" is already taken, so this proxy name cannot be used.
|
|
4101
|
+
Pick a different proxy name (the tenant is always <proxy>users).`
|
|
4102
|
+
);
|
|
4103
|
+
}
|
|
4104
|
+
}
|
|
4105
|
+
function printSummary(recipe, project, tenant2) {
|
|
4106
|
+
const hosts = upstreamHosts(recipe);
|
|
4107
|
+
const routes = countRoutes(recipe.openapi);
|
|
4108
|
+
const keyTypes = recipe.auth?.key_types?.length ?? 0;
|
|
4109
|
+
const questions = recipe.questions?.length ?? 0;
|
|
4110
|
+
console.log();
|
|
4111
|
+
if (hosts.length) console.log(` ${import_chalk26.default.dim("sends requests to")} ${hosts.join(", ")}`);
|
|
4112
|
+
console.log(` ${import_chalk26.default.dim("creates ")} ${describeCreates(keyTypes, routes, recipe.settings.mcp_enabled === true)}`);
|
|
4113
|
+
console.log(` ${import_chalk26.default.dim("asks you for ")} ${questions} value${questions === 1 ? "" : "s"}`);
|
|
4114
|
+
if (recipe.settings.publish_openapi === true) {
|
|
4115
|
+
console.log(` ${import_chalk26.default.dim("makes public ")} this API's spec, at ${project}.abz.run/${recipe.api_version}/openapi.json`);
|
|
4116
|
+
}
|
|
4117
|
+
console.log(import_chalk26.default.dim(` ${"users live in".padEnd(17)} ${tenant2}`));
|
|
4118
|
+
console.log();
|
|
4119
|
+
}
|
|
4120
|
+
function declineAll(items) {
|
|
4121
|
+
const skippable = items.filter((i) => i.kind !== "upstream");
|
|
4122
|
+
const upstreams = items.filter((i) => i.kind === "upstream");
|
|
4123
|
+
console.log();
|
|
4124
|
+
for (const u of upstreams) {
|
|
4125
|
+
console.log(import_chalk26.default.yellow(` --yes: this proxy sends every request to ${import_chalk26.default.bold(u.value)}.`));
|
|
4126
|
+
}
|
|
4127
|
+
if (skippable.length) {
|
|
4128
|
+
console.log(import_chalk26.default.yellow(` --yes: installing without ${skippable.length} setting${skippable.length === 1 ? "" : "s"} that need consent.`));
|
|
4129
|
+
for (const i of skippable) console.log(import_chalk26.default.dim(` skipped ${i.kind} ${i.value}`));
|
|
4130
|
+
}
|
|
4131
|
+
return new Set(skippable.map((i) => i.kind));
|
|
4132
|
+
}
|
|
4133
|
+
async function confirmYesNo(message, interactive) {
|
|
4134
|
+
if (!interactive) {
|
|
4135
|
+
throw new Error(`${message.trim()} needs an answer, but there is no terminal to ask on. Run this from an interactive shell.`);
|
|
4136
|
+
}
|
|
4137
|
+
const { default: inquirer3 } = await import("inquirer");
|
|
4138
|
+
const { ok } = await inquirer3.prompt([{ type: "confirm", name: "ok", message, default: false }]);
|
|
4139
|
+
return !!ok;
|
|
4140
|
+
}
|
|
4141
|
+
async function askConsent(items, interactive) {
|
|
4142
|
+
const declined = /* @__PURE__ */ new Set();
|
|
4143
|
+
if (!items.length) return declined;
|
|
4144
|
+
const count = items.length;
|
|
4145
|
+
const word = ["", "it", "both", "all three", "all four", "all five", "all six"][count] ?? `all ${count}`;
|
|
4146
|
+
console.log();
|
|
4147
|
+
console.log(import_chalk26.default.yellow("\u26A0 This recipe changes who can reach your proxy, what it trusts, and what it can cost."));
|
|
4148
|
+
console.log();
|
|
4149
|
+
for (const item of items) {
|
|
4150
|
+
console.log(` ${import_chalk26.default.bold(item.kind.padEnd(16))} ${item.value}`);
|
|
4151
|
+
for (const line of wrapText(item.why, 62)) console.log(` ${" ".repeat(16)} ${import_chalk26.default.dim(line)}`);
|
|
4152
|
+
}
|
|
4153
|
+
console.log();
|
|
4154
|
+
console.log(`Type "${import_chalk26.default.bold("yes")}" to accept ${word}.`);
|
|
4155
|
+
console.log("Press enter to install the recipe without them.");
|
|
4156
|
+
let typed = "";
|
|
4157
|
+
if (interactive) {
|
|
4158
|
+
const { default: inquirer3 } = await import("inquirer");
|
|
4159
|
+
const { answer } = await inquirer3.prompt([{ type: "input", name: "answer", message: ">" }]);
|
|
4160
|
+
typed = String(answer ?? "").trim();
|
|
4161
|
+
}
|
|
4162
|
+
if (typed.toLowerCase() === "yes") return declined;
|
|
4163
|
+
for (const item of items) {
|
|
4164
|
+
declined.add(item.kind === "transform" ? `transform:${item.value}` : `kind:${item.kind}`);
|
|
4165
|
+
}
|
|
4166
|
+
console.log(import_chalk26.default.dim(` Installing without ${count} setting${count === 1 ? "" : "s"} \u2014 everything else is applied.`));
|
|
4167
|
+
return declined;
|
|
4168
|
+
}
|
|
4169
|
+
async function askQuestions(questions, ctx, interactive) {
|
|
4170
|
+
const answers = {};
|
|
4171
|
+
if (!questions.length) return answers;
|
|
4172
|
+
for (const q of questions) {
|
|
4173
|
+
const envName = `ABZ_ANSWER_${q.id.toUpperCase()}`;
|
|
4174
|
+
const fromEnv = process.env[envName];
|
|
4175
|
+
if (q.before) {
|
|
4176
|
+
const BOX = 64;
|
|
4177
|
+
const title = "\u2500\u2500 from the publisher ";
|
|
4178
|
+
console.log();
|
|
4179
|
+
console.log(` ${import_chalk26.default.dim(title + "\u2500".repeat(BOX - title.length))}`);
|
|
4180
|
+
for (const line of fillContext(q.before, ctx).split("\n")) console.log(` ${import_chalk26.default.dim("\u2502")} ${line}`);
|
|
4181
|
+
console.log(` ${import_chalk26.default.dim("\u2500".repeat(BOX))}`);
|
|
4182
|
+
}
|
|
4183
|
+
if (fromEnv !== void 0 && fromEnv !== "") {
|
|
4184
|
+
answers[q.id] = fromEnv;
|
|
4185
|
+
console.log(` ${q.prompt}: ${import_chalk26.default.dim(`(from ${envName})`)}`);
|
|
4186
|
+
continue;
|
|
4187
|
+
}
|
|
4188
|
+
if (!interactive) {
|
|
4189
|
+
throw new Error(
|
|
4190
|
+
`This recipe needs "${q.prompt}" and there is no terminal to ask on.
|
|
4191
|
+
Set ${envName} in the environment (never as a flag \u2014 a flag lands in shell history and CI logs).`
|
|
4192
|
+
);
|
|
4193
|
+
}
|
|
4194
|
+
const { default: inquirer3 } = await import("inquirer");
|
|
4195
|
+
if (q.example && !q.secret) console.log(import_chalk26.default.dim(` e.g. ${q.example}`));
|
|
4196
|
+
const { value } = await inquirer3.prompt([{
|
|
4197
|
+
// `secret: true` masks the input — the Google client secret is typed once,
|
|
4198
|
+
// goes to provider creation, and is never echoed or stored.
|
|
4199
|
+
type: q.secret ? "password" : "input",
|
|
4200
|
+
name: "value",
|
|
4201
|
+
mask: "\u2022",
|
|
4202
|
+
message: ` ${q.prompt}:`,
|
|
4203
|
+
validate: (v) => String(v ?? "").trim() ? true : "Required."
|
|
4204
|
+
}]);
|
|
4205
|
+
answers[q.id] = String(value).trim();
|
|
4206
|
+
}
|
|
4207
|
+
return answers;
|
|
4208
|
+
}
|
|
4209
|
+
async function resolveReinstallTenant(caller, project, apiVersion) {
|
|
4210
|
+
const out = await producer(caller, {
|
|
4211
|
+
method: "GET",
|
|
4212
|
+
path: `/projects/${encodeURIComponent(project)}/${encodeURIComponent(apiVersion)}/tenants`,
|
|
4213
|
+
summary: `List tenants attached to ${project}`
|
|
4214
|
+
});
|
|
4215
|
+
const names = (out?.tenants ?? []).map((t) => typeof t === "string" ? t : t?.tenant_name).filter((t) => typeof t === "string" && !!t);
|
|
4216
|
+
const derived = `${project}users`;
|
|
4217
|
+
if (names.includes(derived)) return derived;
|
|
4218
|
+
if (names.length === 1) return names[0];
|
|
4219
|
+
if (!names.length) throw new Error(`${project} has no tenant attached, so there is nothing to reinstall into.`);
|
|
4220
|
+
throw new Error(
|
|
4221
|
+
`${project} has ${names.length} tenants (${names.join(", ")}) and none of them is ${derived}, so this proxy was not created by an install. Refusing to guess which one the recipe should overwrite.`
|
|
4222
|
+
);
|
|
4223
|
+
}
|
|
4224
|
+
async function confirmReinstall(recipe, project, tenant2, interactive) {
|
|
4225
|
+
const rules = recipe.transformations?.rules?.length ?? 0;
|
|
4226
|
+
const tables = recipe.transformations?.mapping_tables?.length ?? 0;
|
|
4227
|
+
const routes = countRoutes(recipe.openapi);
|
|
4228
|
+
const keyTypes = recipe.auth?.key_types?.length ?? 0;
|
|
4229
|
+
console.log(`This installs ${import_chalk26.default.bold(`${recipe.name}@${recipe.revision}`)} over ${import_chalk26.default.bold(project)}.`);
|
|
4230
|
+
console.log();
|
|
4231
|
+
console.log(` ${import_chalk26.default.dim("settings ".padEnd(16))}replaced`);
|
|
4232
|
+
if (rules || tables) {
|
|
4233
|
+
console.log(` ${import_chalk26.default.dim("transforms".padEnd(16))}${rules} rule${rules === 1 ? "" : "s"}${tables ? ` and ${tables} mapping table${tables === 1 ? "" : "s"}` : ""} replaced \u2014 your edits to them are lost`);
|
|
4234
|
+
}
|
|
4235
|
+
if (routes) {
|
|
4236
|
+
console.log(` ${import_chalk26.default.dim("api spec ".padEnd(16))}replaced, ${routes} routes regenerated${recipe.settings.mcp_enabled === true ? ", MCP rebuilt" : ""}`);
|
|
4237
|
+
}
|
|
4238
|
+
if (keyTypes) {
|
|
4239
|
+
console.log(` ${import_chalk26.default.dim("key types ".padEnd(16))}${keyTypes} added if missing; existing types left alone`);
|
|
4240
|
+
}
|
|
4241
|
+
console.log();
|
|
4242
|
+
console.log(` ${import_chalk26.default.dim("unchanged ".padEnd(16))}your API keys, your proxy URL, your users,`);
|
|
4243
|
+
console.log(` ${" ".repeat(16)}and any tenant other than ${tenant2}`);
|
|
4244
|
+
console.log();
|
|
4245
|
+
console.log(import_chalk26.default.yellow("This cannot be undone."));
|
|
4246
|
+
if (!interactive) {
|
|
4247
|
+
throw new Error("Reinstall needs a typed confirmation and there is no terminal to ask on.");
|
|
4248
|
+
}
|
|
4249
|
+
const { default: inquirer3 } = await import("inquirer");
|
|
4250
|
+
const { typed } = await inquirer3.prompt([{ type: "input", name: "typed", message: `Type the proxy name (${project}) to continue:` }]);
|
|
4251
|
+
if (String(typed ?? "").trim() !== project) {
|
|
4252
|
+
console.log(import_chalk26.default.yellow("Cancelled \u2014 nothing was changed."));
|
|
4253
|
+
process.exit(0);
|
|
4254
|
+
}
|
|
4255
|
+
}
|
|
4256
|
+
function requireCallerForInstall() {
|
|
4257
|
+
try {
|
|
4258
|
+
return requireCaller();
|
|
4259
|
+
} catch {
|
|
4260
|
+
console.error(import_chalk26.default.red(
|
|
4261
|
+
"Installing writes a proxy into an account, so it needs a credential.\n Run `apiblaze login`, or `apiblaze create` first (an anonymous workspace works too)."
|
|
4262
|
+
));
|
|
4263
|
+
process.exit(1);
|
|
4264
|
+
}
|
|
4265
|
+
}
|
|
4266
|
+
function wrapText(text, width) {
|
|
4267
|
+
const words = (text || "").split(/\s+/).filter(Boolean);
|
|
4268
|
+
const lines = [];
|
|
4269
|
+
let cur = "";
|
|
4270
|
+
for (const w of words) {
|
|
4271
|
+
if (cur && `${cur} ${w}`.length > width) {
|
|
4272
|
+
lines.push(cur);
|
|
4273
|
+
cur = w;
|
|
4274
|
+
} else {
|
|
4275
|
+
cur = cur ? `${cur} ${w}` : w;
|
|
4276
|
+
}
|
|
4277
|
+
}
|
|
4278
|
+
if (cur) lines.push(cur);
|
|
4279
|
+
return lines;
|
|
4280
|
+
}
|
|
4281
|
+
async function chooseTeam(caller, optTeam, interactive) {
|
|
4282
|
+
if (optTeam) return { ...await resolveActingTeam(caller, optTeam), chosen: true };
|
|
4283
|
+
if (caller.mode === "anon" || !interactive) {
|
|
4284
|
+
return { ...await resolveActingTeam(caller, void 0), chosen: false };
|
|
4285
|
+
}
|
|
4286
|
+
const active = await resolveActingTeam(caller, void 0);
|
|
4287
|
+
const teams = await getTeams().catch(() => []);
|
|
4288
|
+
if (teams.length < 2) return { ...active, chosen: false };
|
|
4289
|
+
const { default: inquirer3 } = await import("inquirer");
|
|
4290
|
+
const { picked } = await inquirer3.prompt([{
|
|
4291
|
+
type: "list",
|
|
4292
|
+
name: "picked",
|
|
4293
|
+
message: " which team should own this proxy?",
|
|
4294
|
+
default: active.teamId,
|
|
4295
|
+
choices: teams.map((t) => ({
|
|
4296
|
+
name: t.teamId === active.teamId ? `${t.name} ${import_chalk26.default.dim("(active)")}` : t.name,
|
|
4297
|
+
value: t.teamId
|
|
4298
|
+
}))
|
|
4299
|
+
}]);
|
|
4300
|
+
const match = teams.find((t) => t.teamId === picked);
|
|
4301
|
+
return { teamId: picked, teamName: match?.name, chosen: true };
|
|
4302
|
+
}
|
|
4303
|
+
|
|
4304
|
+
// src/commands/recipe-publish.ts
|
|
4305
|
+
var import_chalk27 = __toESM(require("chalk"));
|
|
4306
|
+
var import_ora9 = __toESM(require("ora"));
|
|
4307
|
+
init_caller();
|
|
4308
|
+
init_auth();
|
|
4309
|
+
async function runRecipePublish(projectArg, opts) {
|
|
4310
|
+
const creds = loadCredentials();
|
|
4311
|
+
let resolved;
|
|
4312
|
+
try {
|
|
4313
|
+
resolved = resolveRecipeName(opts.as, creds?.githubHandle, normalizeName2(projectArg));
|
|
4314
|
+
} catch (err) {
|
|
4315
|
+
console.error(import_chalk27.default.red(err instanceof Error ? err.message : String(err)));
|
|
4316
|
+
process.exitCode = 1;
|
|
4317
|
+
return;
|
|
4318
|
+
}
|
|
4319
|
+
const ref = resolved.ref;
|
|
4320
|
+
if (resolved.derivedHandle) {
|
|
4321
|
+
console.log(import_chalk27.default.dim(` publishing as ${import_chalk27.default.bold(ref.name)}`));
|
|
4322
|
+
if (resolved.normalizedFrom) {
|
|
4323
|
+
console.log(import_chalk27.default.dim(` (your GitHub login is ${resolved.normalizedFrom}; apiblaze names are letters and digits only)`));
|
|
4324
|
+
}
|
|
4325
|
+
}
|
|
4326
|
+
const interactive = !!process.stdin.isTTY && !opts.json;
|
|
4327
|
+
const caller = requireCaller();
|
|
4328
|
+
const { teamId } = await resolveActingTeam(caller, opts.team);
|
|
4329
|
+
const proj2 = await resolveProjectVia(caller, teamId, projectArg, opts.apiversion);
|
|
4330
|
+
const spinner = !opts.json ? (0, import_ora9.default)(`Reading ${proj2.projectId} v${proj2.apiVersion}...`).start() : null;
|
|
4331
|
+
let prep;
|
|
4332
|
+
try {
|
|
4333
|
+
prep = await prepare(caller, proj2.projectId, proj2.apiVersion, ref.name, opts.tenant);
|
|
4334
|
+
spinner?.stop();
|
|
4335
|
+
} catch (err) {
|
|
4336
|
+
spinner?.fail("Could not read the proxy.");
|
|
4337
|
+
throw err;
|
|
4338
|
+
}
|
|
4339
|
+
let tenant2 = opts.tenant;
|
|
4340
|
+
if (!prep.tenant_used && prep.tenants.length > 1) {
|
|
4341
|
+
if (!interactive) {
|
|
4342
|
+
console.error(import_chalk27.default.red(
|
|
4343
|
+
`${proj2.projectId} has ${prep.tenants.length} tenants (${prep.tenants.join(", ")}). Pass --tenant <slug> to say which auth setup the recipe carries.`
|
|
4344
|
+
));
|
|
4345
|
+
process.exit(1);
|
|
4346
|
+
}
|
|
4347
|
+
const { default: inquirer3 } = await import("inquirer");
|
|
4348
|
+
const { picked } = await inquirer3.prompt([{
|
|
4349
|
+
type: "list",
|
|
4350
|
+
name: "picked",
|
|
4351
|
+
choices: prep.tenants,
|
|
4352
|
+
message: "This proxy has several tenants. Which auth setup should the recipe carry?"
|
|
4353
|
+
}]);
|
|
4354
|
+
tenant2 = picked;
|
|
4355
|
+
prep = await prepare(caller, proj2.projectId, proj2.apiVersion, ref.name, tenant2);
|
|
4356
|
+
}
|
|
4357
|
+
if (!prep.name_available) {
|
|
4358
|
+
console.error(import_chalk27.default.red(`${ref.name} belongs to another team, so you cannot publish a revision of it.`));
|
|
4359
|
+
process.exit(1);
|
|
4360
|
+
}
|
|
4361
|
+
if (prep.envelope_violations.length) {
|
|
4362
|
+
printEnvelopeViolations(prep.envelope_violations);
|
|
4363
|
+
process.exit(1);
|
|
4364
|
+
}
|
|
4365
|
+
if (prep.dropped.length) {
|
|
4366
|
+
console.log();
|
|
4367
|
+
console.log(`${prep.dropped.length} setting${prep.dropped.length === 1 ? " was" : "s were"} not included, because they are not on the publish list:`);
|
|
4368
|
+
for (const d of prep.dropped) console.log(import_chalk27.default.dim(` ${d}`));
|
|
4369
|
+
if (prep.hidden_tables_excluded) {
|
|
4370
|
+
console.log(import_chalk27.default.dim(` (${prep.hidden_tables_excluded} hidden mapping table${prep.hidden_tables_excluded === 1 ? "" : "s"} excluded \u2014 those values belong to your consumers.)`));
|
|
4371
|
+
}
|
|
4372
|
+
console.log();
|
|
4373
|
+
if (!opts.yes && !await confirm(" continue without them?", interactive)) {
|
|
4374
|
+
console.log(import_chalk27.default.yellow("Cancelled."));
|
|
4375
|
+
return;
|
|
4376
|
+
}
|
|
4377
|
+
}
|
|
4378
|
+
const decisions = {};
|
|
4379
|
+
const promotions = [];
|
|
4380
|
+
console.log();
|
|
4381
|
+
console.log(`checked ${prep.checked_count} values against 12 known patterns. ${prep.findings.length || "none"} need${prep.findings.length === 1 ? "s" : ""} your decision${prep.findings.length ? ":" : "."}`);
|
|
4382
|
+
if (prep.findings.length) {
|
|
4383
|
+
if (opts.yes) {
|
|
4384
|
+
console.log();
|
|
4385
|
+
for (const f of prep.findings) printFinding(f);
|
|
4386
|
+
console.log();
|
|
4387
|
+
console.error(import_chalk27.default.red("--yes cannot accept scanner findings. Fix the value in the proxy, or publish interactively and decide."));
|
|
4388
|
+
process.exit(1);
|
|
4389
|
+
}
|
|
4390
|
+
if (!interactive) {
|
|
4391
|
+
for (const f of prep.findings) printFinding(f);
|
|
4392
|
+
console.error(import_chalk27.default.red("\nThese need a decision and there is no terminal to ask on."));
|
|
4393
|
+
process.exit(1);
|
|
4394
|
+
}
|
|
4395
|
+
const { default: inquirer3 } = await import("inquirer");
|
|
4396
|
+
for (const f of prep.findings) {
|
|
4397
|
+
console.log();
|
|
4398
|
+
printFinding(f);
|
|
4399
|
+
const { verdict } = await inquirer3.prompt([{
|
|
4400
|
+
type: "list",
|
|
4401
|
+
name: "verdict",
|
|
4402
|
+
message: " ",
|
|
4403
|
+
choices: [
|
|
4404
|
+
{ name: "keep it as it is", value: "keep" },
|
|
4405
|
+
{ name: "ask the installer for their own value", value: "ask" },
|
|
4406
|
+
{ name: "remove it from the recipe", value: "remove" }
|
|
4407
|
+
]
|
|
4408
|
+
}]);
|
|
4409
|
+
decisions[f.path] = verdict;
|
|
4410
|
+
if (verdict === "ask") promotions.push(await askPromotion(f.path));
|
|
4411
|
+
}
|
|
4412
|
+
}
|
|
4413
|
+
console.log(import_chalk27.default.dim(`we cannot judge the other ${prep.unjudged_count} values. run \`apiblaze show ${ref.name}\` after publishing to read the whole recipe.`));
|
|
4414
|
+
const totalQuestions = prep.question_ids.length + promotions.length;
|
|
4415
|
+
if (totalQuestions > 5) {
|
|
4416
|
+
console.log(import_chalk27.default.yellow(`
|
|
4417
|
+
\u26A0 installers will have to supply ${totalQuestions} values before this works.`));
|
|
4418
|
+
}
|
|
4419
|
+
const visibility = opts.private ? "private" : "public";
|
|
4420
|
+
let confirmSpecPublic = false;
|
|
4421
|
+
if (visibility === "public" && prep.spec_will_become_public) {
|
|
4422
|
+
console.log();
|
|
4423
|
+
console.log("Publishing publicly also makes this API's spec readable at");
|
|
4424
|
+
console.log(` ${import_chalk27.default.bold(`https://${proj2.projectId}.abz.run/${proj2.apiVersion}/openapi.json`)}`);
|
|
4425
|
+
console.log("It is currently private.");
|
|
4426
|
+
confirmSpecPublic = opts.yes ? true : await confirm("Continue?", interactive);
|
|
4427
|
+
if (!confirmSpecPublic) {
|
|
4428
|
+
console.log(import_chalk27.default.yellow("Cancelled \u2014 nothing was published."));
|
|
4429
|
+
return;
|
|
4430
|
+
}
|
|
4431
|
+
}
|
|
4432
|
+
const spin2 = !opts.json ? (0, import_ora9.default)(`Publishing ${ref.name}...`).start() : null;
|
|
4433
|
+
let out;
|
|
4434
|
+
try {
|
|
4435
|
+
out = await producer(caller, {
|
|
4436
|
+
method: "POST",
|
|
4437
|
+
path: "/recipes/publish",
|
|
4438
|
+
body: {
|
|
4439
|
+
project_id: proj2.projectId,
|
|
4440
|
+
api_version: proj2.apiVersion,
|
|
4441
|
+
name: ref.name,
|
|
4442
|
+
visibility,
|
|
4443
|
+
candidate_sha256: prep.candidate_sha256,
|
|
4444
|
+
decisions,
|
|
4445
|
+
promotions,
|
|
4446
|
+
...opts.displayName ? { display_name: opts.displayName } : {},
|
|
4447
|
+
...opts.summary ? { summary: opts.summary } : {},
|
|
4448
|
+
...opts.license ? { license: opts.license } : {},
|
|
4449
|
+
...tenant2 ? { tenant: tenant2 } : {},
|
|
4450
|
+
...confirmSpecPublic ? { confirm_spec_public: true } : {}
|
|
4451
|
+
},
|
|
4452
|
+
summary: `Publish ${proj2.projectId} as ${ref.name} (${visibility})`
|
|
4453
|
+
});
|
|
4454
|
+
spin2?.succeed(`Published ${import_chalk27.default.bold(`${out.name}@${out.revision}`)} (${out.visibility}).`);
|
|
4455
|
+
} catch (err) {
|
|
4456
|
+
spin2?.fail("Publish failed.");
|
|
4457
|
+
throw err;
|
|
4458
|
+
}
|
|
4459
|
+
if (opts.json) {
|
|
4460
|
+
console.log(JSON.stringify(out));
|
|
4461
|
+
return;
|
|
4462
|
+
}
|
|
4463
|
+
console.log(` ${import_chalk27.default.dim("size ")} ${(out.size_bytes / 1024).toFixed(1)} KB`);
|
|
4464
|
+
console.log(` ${import_chalk27.default.dim("checksum")} ${out.sha256.slice(0, 16)}\u2026`);
|
|
4465
|
+
console.log();
|
|
4466
|
+
if (out.visibility === "public") {
|
|
4467
|
+
console.log(import_chalk27.default.dim(` Anyone can now run: ${import_chalk27.default.cyan(`npx apiblaze install ${out.name}`)}`));
|
|
4468
|
+
} else {
|
|
4469
|
+
console.log(import_chalk27.default.dim(" Private \u2014 only members of this team can see or install it."));
|
|
4470
|
+
}
|
|
4471
|
+
console.log(import_chalk27.default.dim(` Pull it back with: ${import_chalk27.default.cyan(`npx apiblaze withdraw ${out.name}@${out.revision}`)}`));
|
|
4472
|
+
console.log();
|
|
4473
|
+
}
|
|
4474
|
+
async function prepare(caller, projectId, apiVersion, name, tenant2) {
|
|
4475
|
+
return producer(caller, {
|
|
4476
|
+
method: "POST",
|
|
4477
|
+
path: "/recipes/prepare",
|
|
4478
|
+
body: { project_id: projectId, api_version: apiVersion, name, ...tenant2 ? { tenant: tenant2 } : {} },
|
|
4479
|
+
summary: `Prepare ${projectId} v${apiVersion} for publishing as ${name}`
|
|
4480
|
+
});
|
|
4481
|
+
}
|
|
4482
|
+
function printFinding(f) {
|
|
4483
|
+
const preview = (f.preview ?? "").replace(/\s+/g, " ").trim();
|
|
4484
|
+
console.log(` ${import_chalk27.default.bold(f.path)}`);
|
|
4485
|
+
console.log(` ${import_chalk27.default.yellow(preview.padEnd(38))}${import_chalk27.default.dim(f.why)}`);
|
|
4486
|
+
}
|
|
4487
|
+
function printEnvelopeViolations(violations) {
|
|
4488
|
+
console.log();
|
|
4489
|
+
console.error(import_chalk27.default.red("This proxy has transform rules a recipe cannot carry."));
|
|
4490
|
+
console.log();
|
|
4491
|
+
for (const v of violations) {
|
|
4492
|
+
console.log(` ${import_chalk27.default.bold(v.rule)} ${import_chalk27.default.dim(`(${v.kind}s ${v.field})`)}`);
|
|
4493
|
+
console.log(` ${v.why}`);
|
|
4494
|
+
}
|
|
4495
|
+
console.log();
|
|
4496
|
+
console.log("A recipe runs in a stranger's account, after apiblaze has attached their end user's");
|
|
4497
|
+
console.log("real third-party token, so two rules are absolute:");
|
|
4498
|
+
console.log(` ${import_chalk27.default.dim("\u2022")} a rule may never READ Authorization, Cookie, or the third-party token headers \u2014`);
|
|
4499
|
+
console.log(" those hold a live credential that works at the provider without apiblaze;");
|
|
4500
|
+
console.log(` ${import_chalk27.default.dim("\u2022")} a rule may never WRITE an identity header (x-end-user-id and friends) \u2014`);
|
|
4501
|
+
console.log(" those tell the installer's own backend who is calling.");
|
|
4502
|
+
console.log();
|
|
4503
|
+
console.log("Writing Authorization from a question or a fixed value is fine \u2014 that is the");
|
|
4504
|
+
console.log("bring-your-own-key case. Change the rules above, then publish again.");
|
|
4505
|
+
console.log();
|
|
4506
|
+
}
|
|
4507
|
+
async function askPromotion(path8) {
|
|
4508
|
+
const { default: inquirer3 } = await import("inquirer");
|
|
4509
|
+
const suggestedId = path8.split(/[.[\]]/).filter(Boolean).slice(-2).join("_").toLowerCase().replace(/[^a-z0-9_]/g, "_").replace(/_+/g, "_").replace(/^_|_$/g, "") || "value";
|
|
4510
|
+
const answers = await inquirer3.prompt([
|
|
4511
|
+
{ type: "input", name: "prompt", message: " prompt them with:", validate: (v) => v.trim() ? true : "Required." },
|
|
4512
|
+
{ type: "input", name: "example", message: " example:" },
|
|
4513
|
+
{ type: "input", name: "before", message: " before (optional):" },
|
|
4514
|
+
{ type: "confirm", name: "secret", message: " is the answer a secret (hide it while they type)?", default: false },
|
|
4515
|
+
{ type: "input", name: "id", message: " id:", default: suggestedId }
|
|
4516
|
+
]);
|
|
4517
|
+
const id = String(answers.id || suggestedId).toLowerCase().replace(/[^a-z0-9_]/g, "_");
|
|
4518
|
+
console.log(import_chalk27.default.dim(` \u2192 replaced with {{${id}}}`));
|
|
4519
|
+
return {
|
|
4520
|
+
path: path8,
|
|
4521
|
+
id,
|
|
4522
|
+
prompt: String(answers.prompt).trim(),
|
|
4523
|
+
...answers.example ? { example: String(answers.example).trim() } : {},
|
|
4524
|
+
...answers.before ? { before: String(answers.before).trim() } : {},
|
|
4525
|
+
secret: answers.secret === true
|
|
4526
|
+
};
|
|
4527
|
+
}
|
|
4528
|
+
async function confirm(message, interactive) {
|
|
4529
|
+
if (!interactive) return false;
|
|
4530
|
+
const { default: inquirer3 } = await import("inquirer");
|
|
4531
|
+
const { ok } = await inquirer3.prompt([{ type: "confirm", name: "ok", message, default: false }]);
|
|
4532
|
+
return !!ok;
|
|
4533
|
+
}
|
|
4534
|
+
async function runRecipeWithdraw(nameArg, opts) {
|
|
4535
|
+
const ref = parseRecipeRef(nameArg);
|
|
4536
|
+
if (ref.revision === void 0) {
|
|
4537
|
+
console.error(import_chalk27.default.red(`Name the revision to withdraw, e.g. ${nameArg}@3. Revisions are withdrawn one at a time.`));
|
|
4538
|
+
process.exit(1);
|
|
4539
|
+
}
|
|
4540
|
+
const interactive = !!process.stdin.isTTY && !opts.json;
|
|
4541
|
+
const caller = requireCaller();
|
|
4542
|
+
const { teamId } = await resolveActingTeam(caller, opts.team);
|
|
4543
|
+
const mine = await producer(caller, {
|
|
4544
|
+
method: "GET",
|
|
4545
|
+
path: `/recipes/mine?team_id=${encodeURIComponent(teamId)}`,
|
|
4546
|
+
summary: `List recipes published by ${teamId}`
|
|
4547
|
+
}).catch(() => ({ recipes: [] }));
|
|
4548
|
+
const row = (mine?.recipes ?? []).find((r) => r.name === ref.name);
|
|
4549
|
+
const installs = row?.install_count ?? 0;
|
|
4550
|
+
console.log();
|
|
4551
|
+
console.log(`this permanently deletes revision ${ref.revision}. installs of it will fail.`);
|
|
4552
|
+
if (installs) console.log(`${installs} account${installs === 1 ? "" : "s"} installed it \u2014 rotate any secrets it contained.`);
|
|
4553
|
+
if (interactive) {
|
|
4554
|
+
const { default: inquirer3 } = await import("inquirer");
|
|
4555
|
+
const { typed } = await inquirer3.prompt([{ type: "input", name: "typed", message: `type the recipe name to confirm (${ref.name}):` }]);
|
|
4556
|
+
if (String(typed ?? "").trim() !== ref.name) {
|
|
4557
|
+
console.log(import_chalk27.default.yellow("Cancelled \u2014 nothing was withdrawn."));
|
|
4558
|
+
return;
|
|
4559
|
+
}
|
|
4560
|
+
} else {
|
|
4561
|
+
console.error(import_chalk27.default.red("Withdrawing needs a typed confirmation and there is no terminal to ask on."));
|
|
4562
|
+
process.exit(1);
|
|
4563
|
+
}
|
|
4564
|
+
const spinner = !opts.json ? (0, import_ora9.default)("Withdrawing...").start() : null;
|
|
4565
|
+
let out;
|
|
4566
|
+
try {
|
|
4567
|
+
out = await producer(caller, {
|
|
4568
|
+
method: "DELETE",
|
|
4569
|
+
path: `/recipes/${ref.handle}/${ref.slug}/${ref.revision}`,
|
|
4570
|
+
summary: `Withdraw ${ref.name}@${ref.revision}`
|
|
4571
|
+
});
|
|
4572
|
+
spinner?.stop();
|
|
4573
|
+
} catch (err) {
|
|
4574
|
+
spinner?.fail("Withdraw failed.");
|
|
4575
|
+
throw err;
|
|
4576
|
+
}
|
|
4577
|
+
if (opts.json) {
|
|
4578
|
+
console.log(JSON.stringify(out));
|
|
4579
|
+
return;
|
|
4580
|
+
}
|
|
4581
|
+
console.log();
|
|
4582
|
+
console.log(
|
|
4583
|
+
out.cache_purged === true ? "withdrawn. cached copies were purged." : `withdrawn. ${out.cache_note ?? "cached copies may be served for a few more minutes"}.`
|
|
4584
|
+
);
|
|
4585
|
+
console.log("rotate any secrets it contained now, not later.");
|
|
4586
|
+
console.log();
|
|
4587
|
+
}
|
|
4588
|
+
|
|
4589
|
+
// src/commands/logs.ts
|
|
4590
|
+
var import_chalk29 = __toESM(require("chalk"));
|
|
3312
4591
|
init_admin();
|
|
3313
4592
|
init_auth();
|
|
3314
4593
|
init_resolve();
|
|
3315
4594
|
|
|
3316
4595
|
// src/lib/log-view.ts
|
|
3317
|
-
var
|
|
4596
|
+
var import_chalk28 = __toESM(require("chalk"));
|
|
3318
4597
|
var readline2 = __toESM(require("readline"));
|
|
3319
4598
|
function statusColor(status) {
|
|
3320
|
-
if (status < 300) return
|
|
3321
|
-
if (status < 400) return
|
|
3322
|
-
if (status < 500) return
|
|
3323
|
-
return
|
|
4599
|
+
if (status < 300) return import_chalk28.default.green;
|
|
4600
|
+
if (status < 400) return import_chalk28.default.blue;
|
|
4601
|
+
if (status < 500) return import_chalk28.default.yellow;
|
|
4602
|
+
return import_chalk28.default.red;
|
|
3324
4603
|
}
|
|
3325
4604
|
function methodColor(method) {
|
|
3326
4605
|
switch ((method || "").toUpperCase()) {
|
|
3327
4606
|
case "GET":
|
|
3328
|
-
return
|
|
4607
|
+
return import_chalk28.default.green;
|
|
3329
4608
|
case "POST":
|
|
3330
|
-
return
|
|
4609
|
+
return import_chalk28.default.blue;
|
|
3331
4610
|
case "PUT":
|
|
3332
4611
|
case "PATCH":
|
|
3333
|
-
return
|
|
4612
|
+
return import_chalk28.default.yellow;
|
|
3334
4613
|
case "DELETE":
|
|
3335
|
-
return
|
|
4614
|
+
return import_chalk28.default.red;
|
|
3336
4615
|
default:
|
|
3337
|
-
return
|
|
4616
|
+
return import_chalk28.default.white;
|
|
3338
4617
|
}
|
|
3339
4618
|
}
|
|
3340
4619
|
function clock(ts) {
|
|
@@ -3346,12 +4625,12 @@ function routeCell(route) {
|
|
|
3346
4625
|
return (r.length > ROUTE_W ? `\u2026${r.slice(-(ROUTE_W - 1))}` : r).padEnd(ROUTE_W);
|
|
3347
4626
|
}
|
|
3348
4627
|
function formatRow(r) {
|
|
3349
|
-
const time =
|
|
4628
|
+
const time = import_chalk28.default.dim(clock(r.ts));
|
|
3350
4629
|
const method = methodColor(r.method)(String(r.method || "\u2014").padEnd(6));
|
|
3351
4630
|
const route = routeCell(String(r.route || "\u2014"));
|
|
3352
4631
|
const status = statusColor(r.status)(String(r.status ?? "\u2014").padStart(3));
|
|
3353
|
-
const ms =
|
|
3354
|
-
const step = r.error_step ?
|
|
4632
|
+
const ms = import_chalk28.default.dim(`${r.duration_ms}ms`.padStart(8));
|
|
4633
|
+
const step = r.error_step ? import_chalk28.default.dim(` ${r.error_step}`) : "";
|
|
3355
4634
|
return `${time} ${method} ${route} ${status} ${ms}${step}`;
|
|
3356
4635
|
}
|
|
3357
4636
|
function printRow(r, json) {
|
|
@@ -3366,15 +4645,15 @@ var WindowNotices = class {
|
|
|
3366
4645
|
note(res) {
|
|
3367
4646
|
if (res.history_unavailable && !this.saidUnavailable) {
|
|
3368
4647
|
this.saidUnavailable = true;
|
|
3369
|
-
console.error(
|
|
4648
|
+
console.error(import_chalk28.default.yellow("\u26A0 log history is unreachable right now \u2014 showing the live buffer only, older requests exist but cannot be read."));
|
|
3370
4649
|
}
|
|
3371
4650
|
if ((res.history_markers?.length ?? 0) > 0 && !this.saidGap) {
|
|
3372
4651
|
this.saidGap = true;
|
|
3373
|
-
console.error(
|
|
4652
|
+
console.error(import_chalk28.default.yellow("\u26A0 this range includes a volume-ceiling gap \u2014 some rows were dropped at intake and are gone; counts are incomplete."));
|
|
3374
4653
|
}
|
|
3375
4654
|
if ((res.dropped_this_hour ?? 0) > 0 && !this.saidDropped) {
|
|
3376
4655
|
this.saidDropped = true;
|
|
3377
|
-
console.error(
|
|
4656
|
+
console.error(import_chalk28.default.yellow(`\u26A0 volume ceiling \u2014 ${res.dropped_this_hour} rows dropped this hour; errors keep logging.`));
|
|
3378
4657
|
}
|
|
3379
4658
|
}
|
|
3380
4659
|
};
|
|
@@ -3433,13 +4712,13 @@ async function runTailSession(opts) {
|
|
|
3433
4712
|
if (!held.length && !heldRowsDropped && !heldNotesDropped) return;
|
|
3434
4713
|
const rows = held.filter((h) => h.kind === "row").length;
|
|
3435
4714
|
const total = rows + heldRowsDropped;
|
|
3436
|
-
if (total) console.log(
|
|
4715
|
+
if (total) console.log(import_chalk28.default.dim(`\u2500\u2500\u2500 ${total} request${total === 1 ? "" : "s"} arrived while paused \u2500\u2500\u2500`));
|
|
3437
4716
|
for (const h of held) {
|
|
3438
4717
|
if (h.kind === "row") printRow(h.row, opts.json);
|
|
3439
4718
|
else console.error(h.msg);
|
|
3440
4719
|
}
|
|
3441
4720
|
if (heldRowsDropped || heldNotesDropped) {
|
|
3442
|
-
console.log(
|
|
4721
|
+
console.log(import_chalk28.default.dim(`\u2500\u2500\u2500 ${heldRowsDropped} older one${heldRowsDropped === 1 ? "" : "s"} not shown (pause buffer full) \u2014 browse them with \`logs list\` \u2500\u2500\u2500`));
|
|
3443
4722
|
}
|
|
3444
4723
|
held.length = 0;
|
|
3445
4724
|
heldRowsDropped = 0;
|
|
@@ -3453,7 +4732,7 @@ async function runTailSession(opts) {
|
|
|
3453
4732
|
activeSocket?.close();
|
|
3454
4733
|
} catch {
|
|
3455
4734
|
}
|
|
3456
|
-
if (!opts.json) console.log(
|
|
4735
|
+
if (!opts.json) console.log(import_chalk28.default.dim("\nStopped."));
|
|
3457
4736
|
process.exit(0);
|
|
3458
4737
|
}
|
|
3459
4738
|
function onKeypress(_s, key) {
|
|
@@ -3481,17 +4760,17 @@ async function runTailSession(opts) {
|
|
|
3481
4760
|
async function openInspector() {
|
|
3482
4761
|
if (paused) return;
|
|
3483
4762
|
if (recent.length === 0) {
|
|
3484
|
-
console.log(
|
|
4763
|
+
console.log(import_chalk28.default.dim(" nothing captured yet \u2014 waiting for a request\u2026"));
|
|
3485
4764
|
return;
|
|
3486
4765
|
}
|
|
3487
4766
|
paused = true;
|
|
3488
4767
|
unbindKeys();
|
|
3489
4768
|
try {
|
|
3490
4769
|
const { default: inquirer3 } = await import("inquirer");
|
|
3491
|
-
console.log(
|
|
4770
|
+
console.log(import_chalk28.default.dim("\n\u2500\u2500\u2500 paused \xB7 still capturing in the background \u2500\u2500\u2500"));
|
|
3492
4771
|
for (; ; ) {
|
|
3493
4772
|
const choices = recent.slice(-TAIL_INSPECT_CHOICES).reverse().map((r) => ({ name: formatRow(r), value: r.request_id }));
|
|
3494
|
-
choices.push({ name:
|
|
4773
|
+
choices.push({ name: import_chalk28.default.dim("\u2039resume tail\u203A"), value: "__resume__" });
|
|
3495
4774
|
const { picked } = await inquirer3.prompt([{
|
|
3496
4775
|
type: "list",
|
|
3497
4776
|
name: "picked",
|
|
@@ -3502,12 +4781,12 @@ async function runTailSession(opts) {
|
|
|
3502
4781
|
if (picked === "__resume__") break;
|
|
3503
4782
|
const row = recent.find((r) => r.request_id === picked);
|
|
3504
4783
|
if (row) printDetail(row, opts.detail);
|
|
3505
|
-
else console.log(
|
|
4784
|
+
else console.log(import_chalk28.default.dim(" that request has scrolled out of the tail buffer \u2014 find it with `logs list`.\n"));
|
|
3506
4785
|
}
|
|
3507
4786
|
} finally {
|
|
3508
4787
|
paused = false;
|
|
3509
4788
|
flushHeld();
|
|
3510
|
-
console.log(
|
|
4789
|
+
console.log(import_chalk28.default.dim("\u2500\u2500\u2500 resumed \u2500\u2500\u2500"));
|
|
3511
4790
|
bindKeys();
|
|
3512
4791
|
}
|
|
3513
4792
|
}
|
|
@@ -3517,12 +4796,12 @@ async function runTailSession(opts) {
|
|
|
3517
4796
|
if (reconnects >= MAX_RECONNECTS) {
|
|
3518
4797
|
paused = false;
|
|
3519
4798
|
flushHeld();
|
|
3520
|
-
console.error(
|
|
4799
|
+
console.error(import_chalk28.default.red(`Connection lost \u2014 giving up after repeated reconnects${lastFailure ? ` (last error: ${lastFailure})` : ""}.`));
|
|
3521
4800
|
process.exit(1);
|
|
3522
4801
|
}
|
|
3523
4802
|
const delay = Math.min(1e3 * 2 ** reconnects, 15e3);
|
|
3524
4803
|
reconnects++;
|
|
3525
|
-
diag(
|
|
4804
|
+
diag(import_chalk28.default.dim(`disconnected${lastFailure ? ` (${lastFailure})` : ""} \u2014 reconnecting in ${Math.round(delay / 1e3)}s\u2026`));
|
|
3526
4805
|
setTimeout(() => {
|
|
3527
4806
|
if (!closed) void connect();
|
|
3528
4807
|
}, delay);
|
|
@@ -3533,7 +4812,7 @@ async function runTailSession(opts) {
|
|
|
3533
4812
|
url = await opts.mintTicket();
|
|
3534
4813
|
} catch (err) {
|
|
3535
4814
|
if (!everConnected) {
|
|
3536
|
-
console.error(
|
|
4815
|
+
console.error(import_chalk28.default.red(`Could not start tail: ${err.message}`));
|
|
3537
4816
|
process.exit(1);
|
|
3538
4817
|
}
|
|
3539
4818
|
lastFailure = `ticket mint failed: ${err.message}`;
|
|
@@ -3546,7 +4825,7 @@ async function runTailSession(opts) {
|
|
|
3546
4825
|
} catch (err) {
|
|
3547
4826
|
lastFailure = `bad tail URL: ${err.message}`;
|
|
3548
4827
|
if (!everConnected) {
|
|
3549
|
-
console.error(
|
|
4828
|
+
console.error(import_chalk28.default.red(`Could not start tail: ${lastFailure}`));
|
|
3550
4829
|
process.exit(1);
|
|
3551
4830
|
}
|
|
3552
4831
|
scheduleReconnect();
|
|
@@ -3561,9 +4840,9 @@ async function runTailSession(opts) {
|
|
|
3561
4840
|
lastInbound = Date.now();
|
|
3562
4841
|
lastFailure = "";
|
|
3563
4842
|
if (isReconnect) {
|
|
3564
|
-
diag(
|
|
4843
|
+
diag(import_chalk28.default.dim("reconnected \u2014 requests during the gap were not replayed (browse them with `logs list`)."));
|
|
3565
4844
|
} else {
|
|
3566
|
-
diag(
|
|
4845
|
+
diag(import_chalk28.default.dim(interactive ? "[Enter] inspect a request \xB7 [Ctrl+C] stop\n" : "Ctrl+C to stop."));
|
|
3567
4846
|
}
|
|
3568
4847
|
bindKeys();
|
|
3569
4848
|
pingTimer = setInterval(() => {
|
|
@@ -3590,7 +4869,7 @@ async function runTailSession(opts) {
|
|
|
3590
4869
|
return;
|
|
3591
4870
|
}
|
|
3592
4871
|
if (frame?.type === "ceiling") {
|
|
3593
|
-
diag(
|
|
4872
|
+
diag(import_chalk28.default.yellow(
|
|
3594
4873
|
`\u26A0 volume ceiling \u2014 ${Number(frame.dropped_this_hour) || 0} rows dropped this hour; errors keep streaming.`
|
|
3595
4874
|
));
|
|
3596
4875
|
return;
|
|
@@ -3670,10 +4949,10 @@ async function fetchPage(fetchWindow3, params, notices) {
|
|
|
3670
4949
|
return { rows, nextCursor: res.next_cursor ?? void 0 };
|
|
3671
4950
|
}
|
|
3672
4951
|
if (hop >= MAX_EMPTY_HOPS) {
|
|
3673
|
-
console.error(
|
|
4952
|
+
console.error(import_chalk28.default.yellow("\u26A0 stopped after scanning a long empty span \u2014 older rows may still exist."));
|
|
3674
4953
|
return { rows: [], nextCursor: res.next_cursor, fuseTripped: true };
|
|
3675
4954
|
}
|
|
3676
|
-
if (hop === 0) console.error(
|
|
4955
|
+
if (hop === 0) console.error(import_chalk28.default.dim("searching older history\u2026"));
|
|
3677
4956
|
cursor = res.next_cursor;
|
|
3678
4957
|
}
|
|
3679
4958
|
}
|
|
@@ -3713,18 +4992,18 @@ async function runPlainList(opts, notices) {
|
|
|
3713
4992
|
}
|
|
3714
4993
|
}
|
|
3715
4994
|
if (rows.length === 0) {
|
|
3716
|
-
console.log(
|
|
4995
|
+
console.log(import_chalk28.default.dim(fuseTripped ? "Nothing found in the scanned span \u2014 older requests may still exist." : opts.errorsOnly ? "No errors logged yet." : "No requests logged yet."));
|
|
3717
4996
|
return;
|
|
3718
4997
|
}
|
|
3719
4998
|
const now = Date.now();
|
|
3720
4999
|
for (const d of groupIntoDays(rows)) {
|
|
3721
|
-
console.log(
|
|
5000
|
+
console.log(import_chalk28.default.bold(dayLabel(d.dayMs, now)));
|
|
3722
5001
|
for (const r of d.rows) console.log(` ${formatRow(r)}`);
|
|
3723
5002
|
console.log("");
|
|
3724
5003
|
}
|
|
3725
5004
|
const errs = rows.filter((r) => r.status >= 400).length;
|
|
3726
5005
|
const tail = fuseTripped ? " \xB7 stopped early (long empty span) \u2014 older requests may still exist" : more ? " \xB7 showing the most recent \u2014 `--json` streams the rest" : "";
|
|
3727
|
-
console.log(
|
|
5006
|
+
console.log(import_chalk28.default.dim(
|
|
3728
5007
|
`${rows.length} request${rows.length === 1 ? "" : "s"} \xB7 ${errs} error${errs === 1 ? "" : "s"}${tail}`
|
|
3729
5008
|
));
|
|
3730
5009
|
}
|
|
@@ -3743,7 +5022,7 @@ async function runHistoryBrowser(opts) {
|
|
|
3743
5022
|
for (const r of page.rows) console.log(JSON.stringify(r));
|
|
3744
5023
|
any = any || page.rows.length > 0;
|
|
3745
5024
|
if (page.fuseTripped || !page.nextCursor) {
|
|
3746
|
-
if (!any && !page.fuseTripped) console.error(
|
|
5025
|
+
if (!any && !page.fuseTripped) console.error(import_chalk28.default.dim(opts.errorsOnly ? "No errors logged yet." : "No requests logged yet."));
|
|
3747
5026
|
return;
|
|
3748
5027
|
}
|
|
3749
5028
|
cursor2 = page.nextCursor;
|
|
@@ -3764,7 +5043,7 @@ async function runHistoryBrowser(opts) {
|
|
|
3764
5043
|
scanRows = first.rows;
|
|
3765
5044
|
scanCursor = first.nextCursor;
|
|
3766
5045
|
if (scanRows.length === 0) {
|
|
3767
|
-
console.log(
|
|
5046
|
+
console.log(import_chalk28.default.dim(first.fuseTripped ? "Nothing found in the scanned span \u2014 older rows may exist." : opts.errorsOnly ? "No errors logged yet." : "No requests logged yet."));
|
|
3768
5047
|
return;
|
|
3769
5048
|
}
|
|
3770
5049
|
}
|
|
@@ -3775,10 +5054,10 @@ async function runHistoryBrowser(opts) {
|
|
|
3775
5054
|
const now2 = Date.now();
|
|
3776
5055
|
const oldestKey = days[days.length - 1]?.key;
|
|
3777
5056
|
const choices = days.map((d) => ({
|
|
3778
|
-
name: `${dayLabel(d.dayMs, now2).padEnd(12)} ${String(d.rows.length).padStart(4)}${(scanCursor !== void 0 || scanFloorUnknown) && d.key === oldestKey ? "+" : ""} requests${d.errors ?
|
|
5057
|
+
name: `${dayLabel(d.dayMs, now2).padEnd(12)} ${String(d.rows.length).padStart(4)}${(scanCursor !== void 0 || scanFloorUnknown) && d.key === oldestKey ? "+" : ""} requests${d.errors ? import_chalk28.default.red(` ${d.errors} errors`) : ""}`,
|
|
3779
5058
|
value: d.key
|
|
3780
5059
|
}));
|
|
3781
|
-
if (scanCursor) choices.push({ name:
|
|
5060
|
+
if (scanCursor) choices.push({ name: import_chalk28.default.dim("\u2026 older days"), value: "__older__" });
|
|
3782
5061
|
const { chosenDay } = await inquirer3.prompt([{
|
|
3783
5062
|
type: "list",
|
|
3784
5063
|
name: "chosenDay",
|
|
@@ -3798,7 +5077,7 @@ async function runHistoryBrowser(opts) {
|
|
|
3798
5077
|
scanCursor = void 0;
|
|
3799
5078
|
} else {
|
|
3800
5079
|
scanCursor = more.nextCursor;
|
|
3801
|
-
if (more.rows.length === 0 && !scanCursor) console.log(
|
|
5080
|
+
if (more.rows.length === 0 && !scanCursor) console.log(import_chalk28.default.dim("That was everything \u2014 no older requests."));
|
|
3802
5081
|
}
|
|
3803
5082
|
continue;
|
|
3804
5083
|
}
|
|
@@ -3820,11 +5099,11 @@ async function runHistoryBrowser(opts) {
|
|
|
3820
5099
|
cursor
|
|
3821
5100
|
}, notices);
|
|
3822
5101
|
if (page.rows.length === 0 && pageRows.length === 0) {
|
|
3823
|
-
console.log(
|
|
5102
|
+
console.log(import_chalk28.default.dim(page.fuseTripped ? "Stopped scanning this day (long empty span) \u2014 older rows in it may exist." : "No requests for this day."));
|
|
3824
5103
|
return;
|
|
3825
5104
|
}
|
|
3826
5105
|
if (page.rows.length === 0) {
|
|
3827
|
-
console.log(
|
|
5106
|
+
console.log(import_chalk28.default.dim(page.fuseTripped ? "Stopped scanning further into this day (long empty span) \u2014 older rows in it may exist." : "That was everything for this day."));
|
|
3828
5107
|
moreCursor = void 0;
|
|
3829
5108
|
} else {
|
|
3830
5109
|
pageRows = page.rows;
|
|
@@ -3835,8 +5114,8 @@ async function runHistoryBrowser(opts) {
|
|
|
3835
5114
|
name: formatRow(r),
|
|
3836
5115
|
value: r.request_id
|
|
3837
5116
|
}));
|
|
3838
|
-
if (moreCursor) choices.push({ name:
|
|
3839
|
-
choices.push({ name:
|
|
5117
|
+
if (moreCursor) choices.push({ name: import_chalk28.default.dim("\u2026 load more"), value: "__more__" });
|
|
5118
|
+
choices.push({ name: import_chalk28.default.dim("quit"), value: "__quit__" });
|
|
3840
5119
|
for (; ; ) {
|
|
3841
5120
|
const { picked } = await inquirer3.prompt([{
|
|
3842
5121
|
type: "list",
|
|
@@ -3886,19 +5165,19 @@ function wrapCells(items, width) {
|
|
|
3886
5165
|
${PANE_INDENT}`);
|
|
3887
5166
|
}
|
|
3888
5167
|
function renderHeaders(h) {
|
|
3889
|
-
if (!h) return
|
|
5168
|
+
if (!h) return import_chalk28.default.dim("\u2014");
|
|
3890
5169
|
const width = process.stdout.columns || 100;
|
|
3891
5170
|
if (h.values) {
|
|
3892
5171
|
const e = Object.entries(h.values);
|
|
3893
|
-
if (!e.length) return
|
|
5172
|
+
if (!e.length) return import_chalk28.default.dim("none");
|
|
3894
5173
|
const rank = (k) => {
|
|
3895
5174
|
const i = HEADER_PRIORITY.indexOf(k.toLowerCase());
|
|
3896
5175
|
return i === -1 ? NOISE_HEADER.test(k) ? 999 : 500 : i;
|
|
3897
5176
|
};
|
|
3898
|
-
return e.sort((a, b) => rank(a[0]) - rank(b[0])).map(([k, v]) => `${
|
|
5177
|
+
return e.sort((a, b) => rank(a[0]) - rank(b[0])).map(([k, v]) => `${import_chalk28.default.dim(k + ":")} ${v}`).join(`
|
|
3899
5178
|
${PANE_INDENT}`);
|
|
3900
5179
|
}
|
|
3901
|
-
if (!h.names?.length) return
|
|
5180
|
+
if (!h.names?.length) return import_chalk28.default.dim("none");
|
|
3902
5181
|
const names = [...h.names];
|
|
3903
5182
|
const noise = names.filter((n) => NOISE_HEADER.test(n));
|
|
3904
5183
|
const shown = names.filter((n) => !NOISE_HEADER.test(n)).sort((a, b) => {
|
|
@@ -3906,70 +5185,70 @@ ${PANE_INDENT}`);
|
|
|
3906
5185
|
const ib = HEADER_PRIORITY.indexOf(b.toLowerCase());
|
|
3907
5186
|
return (ia === -1 ? 500 : ia) - (ib === -1 ? 500 : ib) || a.localeCompare(b);
|
|
3908
5187
|
});
|
|
3909
|
-
const body = shown.length ? wrapCells(shown, width) :
|
|
5188
|
+
const body = shown.length ? wrapCells(shown, width) : import_chalk28.default.dim("(all forwarding headers)");
|
|
3910
5189
|
const tail = noise.length ? `
|
|
3911
|
-
${PANE_INDENT}${
|
|
3912
|
-
return `${body}${tail} ${
|
|
5190
|
+
${PANE_INDENT}${import_chalk28.default.dim(`+ ${noise.length} forwarding/client-hint header${noise.length === 1 ? "" : "s"} (cf-*, sec-*)`)}` : "";
|
|
5191
|
+
return `${body}${tail} ${import_chalk28.default.dim("(names only)")}`;
|
|
3913
5192
|
}
|
|
3914
5193
|
function renderBody(b) {
|
|
3915
|
-
if (!b) return
|
|
5194
|
+
if (!b) return import_chalk28.default.dim("\u2014");
|
|
3916
5195
|
const meta = [b.content_type, b.size != null ? `${b.size}B` : null, b.truncated ? "truncated" : null].filter(Boolean).join(" \xB7 ");
|
|
3917
|
-
if (b.not_captured) return
|
|
5196
|
+
if (b.not_captured) return import_chalk28.default.dim(`not captured (${b.not_captured})${meta ? " \xB7 " + meta : ""}`);
|
|
3918
5197
|
const isShape = b.value === void 0 && b.shape !== void 0;
|
|
3919
5198
|
const content = b.value !== void 0 ? b.value : b.shape;
|
|
3920
5199
|
if (content === null || content === void 0 || content === "" || content === "empty") {
|
|
3921
|
-
return
|
|
5200
|
+
return import_chalk28.default.dim(`empty${meta ? " \xB7 " + meta : ""}`);
|
|
3922
5201
|
}
|
|
3923
5202
|
const text = typeof content === "string" ? content : JSON.stringify(content, null, 2);
|
|
3924
|
-
const head = isShape ?
|
|
5203
|
+
const head = isShape ? import_chalk28.default.dim("shape only (values not captured)\n ") : "";
|
|
3925
5204
|
return head + text.split("\n").join("\n ");
|
|
3926
5205
|
}
|
|
3927
5206
|
function pane(title, tone, view, empty) {
|
|
3928
5207
|
console.log(` ${tone(title)}`);
|
|
3929
5208
|
if (empty !== void 0) {
|
|
3930
|
-
console.log(` ${
|
|
5209
|
+
console.log(` ${import_chalk28.default.dim(empty)}`);
|
|
3931
5210
|
return;
|
|
3932
5211
|
}
|
|
3933
5212
|
const v = view ?? {};
|
|
3934
|
-
if (v.url) console.log(` ${
|
|
5213
|
+
if (v.url) console.log(` ${import_chalk28.default.dim("target")} ${v.url}`);
|
|
3935
5214
|
if (v.status != null) {
|
|
3936
|
-
console.log(` ${
|
|
5215
|
+
console.log(` ${import_chalk28.default.dim("status")} ${statusColor(v.status)(String(v.status))}${v.status_text ? import_chalk28.default.dim(" " + v.status_text) : ""}`);
|
|
3937
5216
|
}
|
|
3938
|
-
if (v.raw_path) console.log(` ${
|
|
5217
|
+
if (v.raw_path) console.log(` ${import_chalk28.default.dim("path")} ${v.raw_path}`);
|
|
3939
5218
|
if (v.query && Object.keys(v.query).length) {
|
|
3940
|
-
console.log(` ${
|
|
5219
|
+
console.log(` ${import_chalk28.default.dim("query")} ${Object.entries(v.query).map(([k, q]) => `${k}=${q}`).join(" ")}`);
|
|
3941
5220
|
}
|
|
3942
|
-
if (v.jwt !== void 0) console.log(` ${
|
|
3943
|
-
console.log(` ${
|
|
3944
|
-
console.log(` ${
|
|
5221
|
+
if (v.jwt !== void 0) console.log(` ${import_chalk28.default.dim("jwt")} ${JSON.stringify(v.jwt)}`);
|
|
5222
|
+
console.log(` ${import_chalk28.default.dim("headers")} ${renderHeaders(v.headers)}`);
|
|
5223
|
+
console.log(` ${import_chalk28.default.dim("body")} ${renderBody(v.body)}`);
|
|
3945
5224
|
}
|
|
3946
5225
|
function printDetail(r, mode = "producer") {
|
|
3947
5226
|
const v = r.views;
|
|
3948
5227
|
console.log("");
|
|
3949
|
-
console.log(
|
|
5228
|
+
console.log(import_chalk28.default.bold(`${r.method} ${r.route}`) + " " + statusColor(r.status)(String(r.status)) + import_chalk28.default.dim(` ${r.duration_ms}ms`));
|
|
3950
5229
|
const cost = typeof r.cost_cents === "number" && r.cost_cents > 0 ? ` \xB7 ${r.cost_cents}\xA2` : "";
|
|
3951
|
-
console.log(
|
|
5230
|
+
console.log(import_chalk28.default.dim(` request_id ${r.request_id}${r.environment ? ` \xB7 env ${r.environment}` : ""}${r.consumer_id ? ` \xB7 consumer ${r.consumer_id}` : ""}${cost}`));
|
|
3952
5231
|
console.log("");
|
|
3953
5232
|
if (!v) {
|
|
3954
|
-
console.log(
|
|
5233
|
+
console.log(import_chalk28.default.dim(" No view detail captured for this request.\n"));
|
|
3955
5234
|
return;
|
|
3956
5235
|
}
|
|
3957
|
-
pane("Request (before) \u2014 client \u2192 APIblaze",
|
|
5236
|
+
pane("Request (before) \u2014 client \u2192 APIblaze", import_chalk28.default.cyan, v.request);
|
|
3958
5237
|
if (mode === "producer") {
|
|
3959
5238
|
pane(
|
|
3960
5239
|
"Request (after) \u2014 APIblaze \u2192 upstream",
|
|
3961
|
-
|
|
5240
|
+
import_chalk28.default.cyan,
|
|
3962
5241
|
v.forwarded_request,
|
|
3963
5242
|
v.forwarded_request ? void 0 : "Request did not reach an upstream."
|
|
3964
5243
|
);
|
|
3965
5244
|
pane(
|
|
3966
5245
|
"Response (before) \u2014 upstream \u2192 APIblaze",
|
|
3967
|
-
|
|
5246
|
+
import_chalk28.default.magenta,
|
|
3968
5247
|
v.upstream_response,
|
|
3969
5248
|
v.upstream_response ? void 0 : "No upstream response."
|
|
3970
5249
|
);
|
|
3971
5250
|
}
|
|
3972
|
-
pane("Response (after) \u2014 APIblaze \u2192 client",
|
|
5251
|
+
pane("Response (after) \u2014 APIblaze \u2192 client", import_chalk28.default.magenta, v.response);
|
|
3973
5252
|
console.log("");
|
|
3974
5253
|
}
|
|
3975
5254
|
|
|
@@ -4018,7 +5297,7 @@ async function resolve(projectArg, versionArg, opts) {
|
|
|
4018
5297
|
return { projectId, projectName: match.projectName, version: version2, tenant: tenant2 };
|
|
4019
5298
|
}
|
|
4020
5299
|
function otherPlaneHint() {
|
|
4021
|
-
if (loadConsumer()) console.log(
|
|
5300
|
+
if (loadConsumer()) console.log(import_chalk29.default.dim(" \u2039consumer view: apiblaze consumer logs\u203A"));
|
|
4022
5301
|
}
|
|
4023
5302
|
function fetchWindow(projectId, version2, tenant2) {
|
|
4024
5303
|
return (params) => {
|
|
@@ -4037,8 +5316,8 @@ function fetchWindow(projectId, version2, tenant2) {
|
|
|
4037
5316
|
async function runLogsTail(projectArg, versionArg, opts) {
|
|
4038
5317
|
const { projectId, projectName, version: version2, tenant: tenant2 } = await resolve(projectArg, versionArg, opts);
|
|
4039
5318
|
if (!opts.json) {
|
|
4040
|
-
console.log(
|
|
4041
|
-
console.log(
|
|
5319
|
+
console.log(import_chalk29.default.bold("API Producer logs") + import_chalk29.default.dim(` \xB7 ${projectName}@${version2} \xB7 tenant ${tenant2}${opts.errorsOnly ? " \xB7 errors only" : ""}`));
|
|
5320
|
+
console.log(import_chalk29.default.dim("every caller's requests, with cost and upstream detail"));
|
|
4042
5321
|
otherPlaneHint();
|
|
4043
5322
|
}
|
|
4044
5323
|
await runTailSession({
|
|
@@ -4059,8 +5338,8 @@ async function runLogsTail(projectArg, versionArg, opts) {
|
|
|
4059
5338
|
async function runLogsList(projectArg, versionArg, opts) {
|
|
4060
5339
|
const { projectId, projectName, version: version2, tenant: tenant2 } = await resolve(projectArg, versionArg, opts);
|
|
4061
5340
|
if (!opts.json) {
|
|
4062
|
-
console.log(
|
|
4063
|
-
console.log(
|
|
5341
|
+
console.log(import_chalk29.default.bold("API Producer logs") + import_chalk29.default.dim(` \xB7 ${projectName}@${version2} \xB7 tenant ${tenant2}${opts.errorsOnly ? " \xB7 errors only" : ""}`));
|
|
5342
|
+
console.log(import_chalk29.default.dim("every caller's requests, with cost and upstream detail"));
|
|
4064
5343
|
otherPlaneHint();
|
|
4065
5344
|
console.log("");
|
|
4066
5345
|
}
|
|
@@ -4074,15 +5353,15 @@ async function runLogsList(projectArg, versionArg, opts) {
|
|
|
4074
5353
|
}
|
|
4075
5354
|
|
|
4076
5355
|
// src/commands/consumer-logs.ts
|
|
4077
|
-
var
|
|
5356
|
+
var import_chalk30 = __toESM(require("chalk"));
|
|
4078
5357
|
init_auth();
|
|
4079
5358
|
var CONSUMER_LOGS_BASE = process.env.APIBLAZE_CONSUMER_LOGS_BASE || "https://logs.apiblaze.com/consumer";
|
|
4080
5359
|
function requireConsumer() {
|
|
4081
5360
|
const c = loadConsumer();
|
|
4082
5361
|
if (!c) {
|
|
4083
|
-
console.error(
|
|
5362
|
+
console.error(import_chalk30.default.red("Not logged in as an API Consumer. Run `apiblaze consumer login` first."));
|
|
4084
5363
|
if (loadCredentials()) {
|
|
4085
|
-
console.error(
|
|
5364
|
+
console.error(import_chalk30.default.dim("You're signed in as an API Producer \u2014 for a proxy's full logs run `apiblaze logs <project>`."));
|
|
4086
5365
|
}
|
|
4087
5366
|
process.exit(1);
|
|
4088
5367
|
}
|
|
@@ -4113,7 +5392,7 @@ async function consumerLogsFetch(session, suffix, init) {
|
|
|
4113
5392
|
return data;
|
|
4114
5393
|
}
|
|
4115
5394
|
function otherPlaneHint2() {
|
|
4116
|
-
if (loadCredentials()) console.log(
|
|
5395
|
+
if (loadCredentials()) console.log(import_chalk30.default.dim(" \u2039producer view: apiblaze logs <project>\u203A"));
|
|
4117
5396
|
}
|
|
4118
5397
|
function fetchWindow2(session, project) {
|
|
4119
5398
|
return (params) => {
|
|
@@ -4129,8 +5408,8 @@ function fetchWindow2(session, project) {
|
|
|
4129
5408
|
async function runConsumerLogsTail(opts) {
|
|
4130
5409
|
const session = requireConsumer();
|
|
4131
5410
|
if (!opts.json) {
|
|
4132
|
-
console.log(
|
|
4133
|
-
console.log(
|
|
5411
|
+
console.log(import_chalk30.default.bold("API Consumer logs") + import_chalk30.default.dim(` \xB7 your own requests on ${session.creds.tenant}${opts.project ? ` \xB7 ${opts.project}` : ""}${opts.errorsOnly ? " \xB7 errors only" : ""}`));
|
|
5412
|
+
console.log(import_chalk30.default.dim("cost and upstream details are producer-only and are not sent"));
|
|
4134
5413
|
otherPlaneHint2();
|
|
4135
5414
|
}
|
|
4136
5415
|
await runTailSession({
|
|
@@ -4148,8 +5427,8 @@ async function runConsumerLogsTail(opts) {
|
|
|
4148
5427
|
async function runConsumerLogsList(opts) {
|
|
4149
5428
|
const session = requireConsumer();
|
|
4150
5429
|
if (!opts.json) {
|
|
4151
|
-
console.log(
|
|
4152
|
-
console.log(
|
|
5430
|
+
console.log(import_chalk30.default.bold("API Consumer logs") + import_chalk30.default.dim(` \xB7 your own requests on ${session.creds.tenant}${opts.project ? ` \xB7 ${opts.project}` : ""}${opts.errorsOnly ? " \xB7 errors only" : ""}`));
|
|
5431
|
+
console.log(import_chalk30.default.dim("cost and upstream details are producer-only and are not sent"));
|
|
4153
5432
|
otherPlaneHint2();
|
|
4154
5433
|
console.log("");
|
|
4155
5434
|
}
|
|
@@ -4163,14 +5442,14 @@ async function runConsumerLogsList(opts) {
|
|
|
4163
5442
|
}
|
|
4164
5443
|
|
|
4165
5444
|
// src/commands/config.ts
|
|
4166
|
-
var
|
|
4167
|
-
var
|
|
5445
|
+
var import_chalk31 = __toESM(require("chalk"));
|
|
5446
|
+
var import_ora10 = __toESM(require("ora"));
|
|
4168
5447
|
init_admin();
|
|
4169
5448
|
init_resolve();
|
|
4170
5449
|
async function patchConfig(project, opts, body, summary) {
|
|
4171
5450
|
const { teamId } = await resolveTeam(opts.team);
|
|
4172
5451
|
const proj2 = await resolveProject(teamId, project, opts.apiversion);
|
|
4173
|
-
const spinner = (0,
|
|
5452
|
+
const spinner = (0, import_ora10.default)(summary + "...").start();
|
|
4174
5453
|
try {
|
|
4175
5454
|
const out = await admin({
|
|
4176
5455
|
method: "PATCH",
|
|
@@ -4187,7 +5466,7 @@ async function patchConfig(project, opts, body, summary) {
|
|
|
4187
5466
|
}
|
|
4188
5467
|
async function runTargetSet(project, opts) {
|
|
4189
5468
|
if (!opts.url) {
|
|
4190
|
-
console.error(
|
|
5469
|
+
console.error(import_chalk31.default.red("--url is required."));
|
|
4191
5470
|
process.exit(1);
|
|
4192
5471
|
}
|
|
4193
5472
|
const body = opts.env ? { environments: { [opts.env]: { target: opts.url } } } : { target_url: opts.url };
|
|
@@ -4200,45 +5479,45 @@ async function runThrottleSet(project, opts) {
|
|
|
4200
5479
|
if (opts.quota !== void 0) throttling.proxyQuota = Number(opts.quota);
|
|
4201
5480
|
if (opts.period !== void 0) {
|
|
4202
5481
|
if (!["daily", "weekly", "monthly"].includes(opts.period)) {
|
|
4203
|
-
console.error(
|
|
5482
|
+
console.error(import_chalk31.default.red("--period must be daily, weekly, or monthly."));
|
|
4204
5483
|
process.exit(1);
|
|
4205
5484
|
}
|
|
4206
5485
|
throttling.quotaPeriod = opts.period;
|
|
4207
5486
|
}
|
|
4208
5487
|
if (Object.keys(throttling).length === 0) {
|
|
4209
|
-
console.error(
|
|
5488
|
+
console.error(import_chalk31.default.red("Nothing to set. Pass at least one of --rate, --end-user-rate, --quota, --period."));
|
|
4210
5489
|
process.exit(1);
|
|
4211
5490
|
}
|
|
4212
5491
|
await patchConfig(project, opts, { throttling }, `Update throttling ${JSON.stringify(throttling)}`);
|
|
4213
5492
|
}
|
|
4214
5493
|
async function runRename(project, opts) {
|
|
4215
5494
|
if (!opts.displayName) {
|
|
4216
|
-
console.error(
|
|
5495
|
+
console.error(import_chalk31.default.red("--display-name is required."));
|
|
4217
5496
|
process.exit(1);
|
|
4218
5497
|
}
|
|
4219
5498
|
await patchConfig(project, opts, { display_name: opts.displayName }, `Rename \u2192 "${opts.displayName}"`);
|
|
4220
5499
|
}
|
|
4221
5500
|
|
|
4222
5501
|
// src/commands/config-browse.ts
|
|
4223
|
-
var
|
|
4224
|
-
var
|
|
5502
|
+
var import_chalk40 = __toESM(require("chalk"));
|
|
5503
|
+
var import_ora18 = __toESM(require("ora"));
|
|
4225
5504
|
init_admin();
|
|
4226
5505
|
init_auth();
|
|
4227
5506
|
init_resolve();
|
|
4228
5507
|
|
|
4229
5508
|
// src/commands/domain.ts
|
|
4230
|
-
var
|
|
4231
|
-
var
|
|
5509
|
+
var import_chalk32 = __toESM(require("chalk"));
|
|
5510
|
+
var import_ora11 = __toESM(require("ora"));
|
|
4232
5511
|
init_admin();
|
|
4233
5512
|
init_resolve();
|
|
4234
5513
|
async function runDomainAdd(project, opts) {
|
|
4235
5514
|
if (!opts.domain) {
|
|
4236
|
-
console.error(
|
|
5515
|
+
console.error(import_chalk32.default.red("--domain is required."));
|
|
4237
5516
|
process.exit(1);
|
|
4238
5517
|
}
|
|
4239
5518
|
const { teamId } = await resolveTeam(opts.team);
|
|
4240
5519
|
const proj2 = await resolveProject(teamId, project, opts.apiversion);
|
|
4241
|
-
const spinner = (0,
|
|
5520
|
+
const spinner = (0, import_ora11.default)(`Registering ${opts.domain}...`).start();
|
|
4242
5521
|
try {
|
|
4243
5522
|
const out = await admin({
|
|
4244
5523
|
method: "POST",
|
|
@@ -4252,9 +5531,9 @@ async function runDomainAdd(project, opts) {
|
|
|
4252
5531
|
console.log(JSON.stringify(out));
|
|
4253
5532
|
return;
|
|
4254
5533
|
}
|
|
4255
|
-
if (out?.cnameRecord) console.log(` ${
|
|
4256
|
-
if (out?.dcv) console.log(` ${
|
|
4257
|
-
if (out?.id) console.log(
|
|
5534
|
+
if (out?.cnameRecord) console.log(` ${import_chalk32.default.cyan("CNAME")} ${out.cnameRecord.name ?? opts.domain} \u2192 ${out.cnameRecord.value ?? out.cnameRecord.target}`);
|
|
5535
|
+
if (out?.dcv) console.log(` ${import_chalk32.default.cyan("TXT")} ${out.dcv.name} = ${out.dcv.value}`);
|
|
5536
|
+
if (out?.id) console.log(import_chalk32.default.dim(` domain id: ${out.id}`));
|
|
4258
5537
|
} catch (err) {
|
|
4259
5538
|
spinner.fail("Domain registration failed.");
|
|
4260
5539
|
throw err;
|
|
@@ -4274,14 +5553,14 @@ async function runDomainList(project, opts) {
|
|
|
4274
5553
|
return;
|
|
4275
5554
|
}
|
|
4276
5555
|
if (!domains.length) {
|
|
4277
|
-
console.log(
|
|
5556
|
+
console.log(import_chalk32.default.yellow("No custom domains."));
|
|
4278
5557
|
return;
|
|
4279
5558
|
}
|
|
4280
|
-
for (const d of domains) console.log(` ${
|
|
5559
|
+
for (const d of domains) console.log(` ${import_chalk32.default.bold(d.hostname)} ${import_chalk32.default.dim(d.status ?? "")} ${import_chalk32.default.dim(d.id ?? "")}`);
|
|
4281
5560
|
}
|
|
4282
5561
|
async function runDomainStatus(project, opts) {
|
|
4283
5562
|
if (!opts.id) {
|
|
4284
|
-
console.error(
|
|
5563
|
+
console.error(import_chalk32.default.red("--id <domainId> is required (see `apiblaze domain list`)."));
|
|
4285
5564
|
process.exit(1);
|
|
4286
5565
|
}
|
|
4287
5566
|
const { teamId } = await resolveTeam(opts.team);
|
|
@@ -4291,16 +5570,16 @@ async function runDomainStatus(project, opts) {
|
|
|
4291
5570
|
path: `/projects/${proj2.projectId}/domains/${encodeURIComponent(opts.id)}/status`,
|
|
4292
5571
|
summary: `Check custom-domain status`
|
|
4293
5572
|
});
|
|
4294
|
-
console.log(opts.json ? JSON.stringify(out) : ` ${
|
|
5573
|
+
console.log(opts.json ? JSON.stringify(out) : ` ${import_chalk32.default.bold(out?.hostname ?? opts.id)}: ${import_chalk32.default.cyan(out?.status ?? "unknown")}`);
|
|
4295
5574
|
}
|
|
4296
5575
|
async function runDomainRemove(project, opts) {
|
|
4297
5576
|
if (!opts.id) {
|
|
4298
|
-
console.error(
|
|
5577
|
+
console.error(import_chalk32.default.red("--id <domainId> is required (see `apiblaze domain list`)."));
|
|
4299
5578
|
process.exit(1);
|
|
4300
5579
|
}
|
|
4301
5580
|
const { teamId } = await resolveTeam(opts.team);
|
|
4302
5581
|
const proj2 = await resolveProject(teamId, project, opts.apiversion);
|
|
4303
|
-
const spinner = (0,
|
|
5582
|
+
const spinner = (0, import_ora11.default)("Removing domain...").start();
|
|
4304
5583
|
try {
|
|
4305
5584
|
await admin({
|
|
4306
5585
|
method: "DELETE",
|
|
@@ -4317,7 +5596,7 @@ async function runDomainSetBase(project, opts) {
|
|
|
4317
5596
|
const env = opts.env ?? "prod";
|
|
4318
5597
|
const { teamId } = await resolveTeam(opts.team);
|
|
4319
5598
|
const proj2 = await resolveProject(teamId, project, opts.apiversion);
|
|
4320
|
-
const spinner = (0,
|
|
5599
|
+
const spinner = (0, import_ora11.default)("Setting base domain...").start();
|
|
4321
5600
|
try {
|
|
4322
5601
|
await admin({
|
|
4323
5602
|
method: "PUT",
|
|
@@ -4333,8 +5612,8 @@ async function runDomainSetBase(project, opts) {
|
|
|
4333
5612
|
}
|
|
4334
5613
|
|
|
4335
5614
|
// src/commands/tenant.ts
|
|
4336
|
-
var
|
|
4337
|
-
var
|
|
5615
|
+
var import_chalk35 = __toESM(require("chalk"));
|
|
5616
|
+
var import_ora14 = __toESM(require("ora"));
|
|
4338
5617
|
init_admin();
|
|
4339
5618
|
init_resolve();
|
|
4340
5619
|
init_auth();
|
|
@@ -4342,20 +5621,20 @@ init_tenant_pick();
|
|
|
4342
5621
|
async function runTenantUse(query, opts) {
|
|
4343
5622
|
const creds = loadCredentials();
|
|
4344
5623
|
if (!creds) {
|
|
4345
|
-
console.error(
|
|
5624
|
+
console.error(import_chalk35.default.red("Not logged in. Run `apiblaze login` first."));
|
|
4346
5625
|
process.exit(1);
|
|
4347
5626
|
}
|
|
4348
5627
|
if (opts.clear) {
|
|
4349
5628
|
delete creds.activeTenant;
|
|
4350
5629
|
saveCredentials(creds);
|
|
4351
|
-
console.log(
|
|
5630
|
+
console.log(import_chalk35.default.green("Tenant scope cleared."));
|
|
4352
5631
|
return;
|
|
4353
5632
|
}
|
|
4354
5633
|
const { teamId } = await resolveTeam(opts.team);
|
|
4355
5634
|
const slug = await pickTenant(teamId, { message: "Scope future commands to which tenant?", initialQuery: query });
|
|
4356
5635
|
if (!slug) process.exit(1);
|
|
4357
5636
|
saveCredentials({ ...creds, activeTenant: slug });
|
|
4358
|
-
console.log(
|
|
5637
|
+
console.log(import_chalk35.default.green(`Tenant scope set to ${import_chalk35.default.bold(slug)}.`) + import_chalk35.default.dim(" (clear with `apiblaze tenant use --clear`)"));
|
|
4359
5638
|
}
|
|
4360
5639
|
async function runTenantList(opts) {
|
|
4361
5640
|
const { teamId, teamName } = await resolveTeam(opts.team);
|
|
@@ -4372,26 +5651,26 @@ async function runTenantList(opts) {
|
|
|
4372
5651
|
return;
|
|
4373
5652
|
}
|
|
4374
5653
|
if (!tenants.length) {
|
|
4375
|
-
console.log(
|
|
5654
|
+
console.log(import_chalk35.default.yellow(opts.q ? `No tenants matching "${opts.q}".` : "No tenants."));
|
|
4376
5655
|
return;
|
|
4377
5656
|
}
|
|
4378
5657
|
for (const t of tenants) {
|
|
4379
5658
|
const name = typeof t === "string" ? t : t.tenant_name;
|
|
4380
|
-
const display = typeof t === "string" ? "" :
|
|
4381
|
-
console.log(` ${
|
|
5659
|
+
const display = typeof t === "string" ? "" : import_chalk35.default.dim(` ${t.display_name ?? ""}`);
|
|
5660
|
+
console.log(` ${import_chalk35.default.bold(name)}${display}`);
|
|
4382
5661
|
}
|
|
4383
5662
|
const total = out?.total ?? tenants.length;
|
|
4384
5663
|
if (total > tenants.length) {
|
|
4385
|
-
console.log(
|
|
5664
|
+
console.log(import_chalk35.default.dim(` \u2026 showing ${tenants.length} of ${total} \u2014 narrow with --q <search>`));
|
|
4386
5665
|
}
|
|
4387
5666
|
}
|
|
4388
5667
|
async function runTenantCreate(opts) {
|
|
4389
5668
|
if (!opts.name) {
|
|
4390
|
-
console.error(
|
|
5669
|
+
console.error(import_chalk35.default.red("--name (display name) is required."));
|
|
4391
5670
|
process.exit(1);
|
|
4392
5671
|
}
|
|
4393
5672
|
const { teamId } = await resolveTeam(opts.team);
|
|
4394
|
-
const spinner = (0,
|
|
5673
|
+
const spinner = (0, import_ora14.default)("Creating tenant...").start();
|
|
4395
5674
|
try {
|
|
4396
5675
|
const out = await admin({
|
|
4397
5676
|
method: "POST",
|
|
@@ -4399,7 +5678,7 @@ async function runTenantCreate(opts) {
|
|
|
4399
5678
|
body: { display_name: opts.name, ...opts.slug ? { tenant_name: opts.slug } : {} },
|
|
4400
5679
|
summary: `Create tenant "${opts.name}"`
|
|
4401
5680
|
});
|
|
4402
|
-
spinner.succeed(`Created tenant ${
|
|
5681
|
+
spinner.succeed(`Created tenant ${import_chalk35.default.bold(out?.tenant_name ?? opts.name)}.`);
|
|
4403
5682
|
if (opts.json) console.log(JSON.stringify(out));
|
|
4404
5683
|
} catch (err) {
|
|
4405
5684
|
spinner.fail("Tenant create failed.");
|
|
@@ -4408,12 +5687,12 @@ async function runTenantCreate(opts) {
|
|
|
4408
5687
|
}
|
|
4409
5688
|
async function runTenantAttach(project, opts) {
|
|
4410
5689
|
if (!opts.tenant) {
|
|
4411
|
-
console.error(
|
|
5690
|
+
console.error(import_chalk35.default.red("--tenant <slug> is required."));
|
|
4412
5691
|
process.exit(1);
|
|
4413
5692
|
}
|
|
4414
5693
|
const { teamId } = await resolveTeam(opts.team);
|
|
4415
5694
|
const proj2 = await resolveProject(teamId, project, opts.apiversion);
|
|
4416
|
-
const spinner = (0,
|
|
5695
|
+
const spinner = (0, import_ora14.default)("Attaching tenant...").start();
|
|
4417
5696
|
try {
|
|
4418
5697
|
const out = await admin({
|
|
4419
5698
|
method: "POST",
|
|
@@ -4432,15 +5711,15 @@ async function runTenantDelete(slug, opts) {
|
|
|
4432
5711
|
const { teamId } = await resolveTeam(opts.team);
|
|
4433
5712
|
if (!opts.yes && !opts.json) {
|
|
4434
5713
|
const { default: inquirer3 } = await import("inquirer");
|
|
4435
|
-
const { confirm } = await inquirer3.prompt([
|
|
5714
|
+
const { confirm: confirm2 } = await inquirer3.prompt([
|
|
4436
5715
|
{ type: "confirm", name: "confirm", message: `Permanently delete tenant "${slug}" and everything under it? This cannot be undone.`, default: false }
|
|
4437
5716
|
]);
|
|
4438
|
-
if (!
|
|
4439
|
-
console.log(
|
|
5717
|
+
if (!confirm2) {
|
|
5718
|
+
console.log(import_chalk35.default.dim("Aborted."));
|
|
4440
5719
|
return;
|
|
4441
5720
|
}
|
|
4442
5721
|
}
|
|
4443
|
-
const spinner = (0,
|
|
5722
|
+
const spinner = (0, import_ora14.default)("Deleting tenant...").start();
|
|
4444
5723
|
try {
|
|
4445
5724
|
await admin({
|
|
4446
5725
|
method: "DELETE",
|
|
@@ -4455,13 +5734,13 @@ async function runTenantDelete(slug, opts) {
|
|
|
4455
5734
|
}
|
|
4456
5735
|
async function runTenantCors(opts) {
|
|
4457
5736
|
if (!opts.tenant) {
|
|
4458
|
-
console.error(
|
|
5737
|
+
console.error(import_chalk35.default.red("--tenant <slug> is required."));
|
|
4459
5738
|
process.exit(1);
|
|
4460
5739
|
}
|
|
4461
5740
|
const { teamId } = await resolveTeam(opts.team);
|
|
4462
5741
|
const origins = (opts.origins ?? "").split(",").map((s) => s.trim()).filter(Boolean);
|
|
4463
5742
|
const cors = origins.length ? { allowed_origins: origins } : null;
|
|
4464
|
-
const spinner = (0,
|
|
5743
|
+
const spinner = (0, import_ora14.default)("Updating CORS...").start();
|
|
4465
5744
|
try {
|
|
4466
5745
|
await admin({
|
|
4467
5746
|
method: "PUT",
|
|
@@ -4477,8 +5756,8 @@ async function runTenantCors(opts) {
|
|
|
4477
5756
|
}
|
|
4478
5757
|
|
|
4479
5758
|
// src/commands/tenant-drill.ts
|
|
4480
|
-
var
|
|
4481
|
-
var
|
|
5759
|
+
var import_chalk36 = __toESM(require("chalk"));
|
|
5760
|
+
var import_ora15 = __toESM(require("ora"));
|
|
4482
5761
|
var import_crypto = require("crypto");
|
|
4483
5762
|
init_admin();
|
|
4484
5763
|
init_auth();
|
|
@@ -4508,17 +5787,17 @@ async function validScopedTenant(teamId, query) {
|
|
|
4508
5787
|
const next = { ...creds };
|
|
4509
5788
|
delete next.activeTenant;
|
|
4510
5789
|
saveCredentials2(next);
|
|
4511
|
-
console.log(
|
|
5790
|
+
console.log(import_chalk36.default.yellow(`Tenant scope "${scoped}" no longer exists in this team \u2014 cleared.`));
|
|
4512
5791
|
return void 0;
|
|
4513
5792
|
}
|
|
4514
5793
|
async function tenantHome(teamId, tenant2) {
|
|
4515
5794
|
const { default: inquirer3 } = await import("inquirer");
|
|
4516
5795
|
const base2 = `/teams/${encodeURIComponent(teamId)}/tenants/${encodeURIComponent(tenant2)}`;
|
|
4517
|
-
console.log(
|
|
5796
|
+
console.log(import_chalk36.default.bold(`
|
|
4518
5797
|
Tenant ${tenant2}`));
|
|
4519
|
-
console.log(
|
|
5798
|
+
console.log(import_chalk36.default.dim("Tenant auth/settings are SHARED: changes apply to every proxy this tenant serves.\n"));
|
|
4520
5799
|
for (; ; ) {
|
|
4521
|
-
const spinner = (0,
|
|
5800
|
+
const spinner = (0, import_ora15.default)("Reading tenant state...").start();
|
|
4522
5801
|
const [iam, cors, emails, issuers, opaque, clients] = await Promise.all([
|
|
4523
5802
|
admin({ method: "GET", path: `${base2}/iam`, summary: "Read IAM toggle" }).catch(() => null),
|
|
4524
5803
|
admin({ method: "GET", path: `${base2}/cors`, summary: "Read tenant CORS" }).catch(() => null),
|
|
@@ -4532,18 +5811,18 @@ Tenant ${tenant2}`));
|
|
|
4532
5811
|
const nClients = Array.isArray(clients) ? clients.length : 0;
|
|
4533
5812
|
const nOpaque = opaque?.opaque?.endpoint ? 1 : 0;
|
|
4534
5813
|
const nLogin = nClients + nIssuers + nOpaque;
|
|
4535
|
-
const onOff = (b) => b ?
|
|
5814
|
+
const onOff = (b) => b ? import_chalk36.default.green("on") : import_chalk36.default.dim("off");
|
|
4536
5815
|
const { pick: pick2 } = await inquirer3.prompt([{
|
|
4537
5816
|
type: "list",
|
|
4538
5817
|
name: "pick",
|
|
4539
5818
|
message: `Tenant ${tenant2}:`,
|
|
4540
5819
|
pageSize: 12,
|
|
4541
5820
|
choices: [
|
|
4542
|
-
{ name: `Login methods (${nLogin}) ${
|
|
4543
|
-
{ name: `Users & groups: ${onOff(iam?.iam_enabled)} ${
|
|
4544
|
-
{ name: `Portal admins (${nEmails}) ${
|
|
4545
|
-
new inquirer3.Separator(
|
|
4546
|
-
{ name: `CORS: ${cors?.cors ?
|
|
5821
|
+
{ name: `Login methods (${nLogin}) ${import_chalk36.default.dim("how your consumers sign in")}`, value: "login" },
|
|
5822
|
+
{ name: `Users & groups: ${onOff(iam?.iam_enabled)} ${import_chalk36.default.dim(`identity & key management (iam.apiblaze.com)`)}`, value: "iam" },
|
|
5823
|
+
{ name: `Portal admins (${nEmails}) ${import_chalk36.default.dim("emails allowed to administer this tenant's portal")}`, value: "emails" },
|
|
5824
|
+
new inquirer3.Separator(import_chalk36.default.dim(" Settings")),
|
|
5825
|
+
{ name: `CORS: ${cors?.cors ? import_chalk36.default.cyan(JSON.stringify(cors.cors)) : import_chalk36.default.dim("(unset)")} ${import_chalk36.default.dim("browser origins allowed to call this tenant")}`, value: "cors" },
|
|
4547
5826
|
{ name: "\u2190 Back", value: "back" }
|
|
4548
5827
|
]
|
|
4549
5828
|
}]);
|
|
@@ -4556,7 +5835,7 @@ Tenant ${tenant2}`));
|
|
|
4556
5835
|
case "iam": {
|
|
4557
5836
|
const { v } = await inquirer3.prompt([{ type: "confirm", name: "v", message: "Enable Users & groups?", default: !!iam?.iam_enabled }]);
|
|
4558
5837
|
await admin({ method: "PATCH", path: `${base2}/iam`, body: { enabled: v }, summary: `IAM enforcement \u2192 ${v ? "on" : "off"}` });
|
|
4559
|
-
console.log(
|
|
5838
|
+
console.log(import_chalk36.default.green(` Users & groups ${v ? "enabled" : "disabled"}.`));
|
|
4560
5839
|
break;
|
|
4561
5840
|
}
|
|
4562
5841
|
case "cors": {
|
|
@@ -4569,11 +5848,11 @@ Tenant ${tenant2}`));
|
|
|
4569
5848
|
if (v === "") break;
|
|
4570
5849
|
const parsed = v === "null" ? null : safeJson(v);
|
|
4571
5850
|
if (parsed === void 0) {
|
|
4572
|
-
console.log(
|
|
5851
|
+
console.log(import_chalk36.default.yellow(" Not valid JSON \u2014 unchanged."));
|
|
4573
5852
|
break;
|
|
4574
5853
|
}
|
|
4575
5854
|
await admin({ method: "PUT", path: `${base2}/cors`, body: { cors: parsed }, summary: "Set tenant CORS" });
|
|
4576
|
-
console.log(
|
|
5855
|
+
console.log(import_chalk36.default.green(" CORS updated."));
|
|
4577
5856
|
break;
|
|
4578
5857
|
}
|
|
4579
5858
|
case "emails":
|
|
@@ -4585,7 +5864,7 @@ Tenant ${tenant2}`));
|
|
|
4585
5864
|
async function loginMethodsMenu(teamId, tenant2, base2) {
|
|
4586
5865
|
const { default: inquirer3 } = await import("inquirer");
|
|
4587
5866
|
for (; ; ) {
|
|
4588
|
-
const spinner = (0,
|
|
5867
|
+
const spinner = (0, import_ora15.default)("Loading login methods...").start();
|
|
4589
5868
|
const [rawClients, rawIssuers, rawOpaque] = await Promise.all([
|
|
4590
5869
|
admin({ method: "GET", path: `${base2}/app-clients`, summary: "List APIblaze-hosted logins" }).catch(() => []),
|
|
4591
5870
|
admin({ method: "GET", path: `${base2}/external-issuers`, summary: "List your-own-JWT logins" }).catch(() => null),
|
|
@@ -4597,15 +5876,15 @@ async function loginMethodsMenu(teamId, tenant2, base2) {
|
|
|
4597
5876
|
const choices = [];
|
|
4598
5877
|
for (const c of appClients) {
|
|
4599
5878
|
const nP = (c.providers ?? []).length || c.providers_count || 0;
|
|
4600
|
-
choices.push({ name: `${
|
|
5879
|
+
choices.push({ name: `${import_chalk36.default.cyan("APIblaze-hosted")} ${import_chalk36.default.bold(c.name ?? c.clientId)} ${import_chalk36.default.dim(`${c.clientId}${nP ? ` \xB7 ${nP} provider${nP === 1 ? "" : "s"}` : import_chalk36.default.yellow(" \xB7 needs a provider")}`)}`, value: { kind: "client", item: c } });
|
|
4601
5880
|
}
|
|
4602
5881
|
for (const i of issuers) {
|
|
4603
|
-
choices.push({ name: `${
|
|
5882
|
+
choices.push({ name: `${import_chalk36.default.cyan("Your own \u2014 JWT ")} ${import_chalk36.default.bold(i.iss)} ${import_chalk36.default.dim(`aud=${i.aud}`)}`, value: { kind: "issuer", item: i } });
|
|
4604
5883
|
}
|
|
4605
5884
|
if (opaque?.endpoint) {
|
|
4606
|
-
choices.push({ name: `${
|
|
5885
|
+
choices.push({ name: `${import_chalk36.default.cyan("Your own \u2014 opaque")} ${import_chalk36.default.bold(opaque.endpoint)}`, value: { kind: "opaque", item: opaque } });
|
|
4607
5886
|
}
|
|
4608
|
-
if (!choices.length) console.log(
|
|
5887
|
+
if (!choices.length) console.log(import_chalk36.default.dim("\n No login methods yet \u2014 consumers cannot sign in until you add one."));
|
|
4609
5888
|
const { pick: pick2 } = await inquirer3.prompt([{
|
|
4610
5889
|
type: "list",
|
|
4611
5890
|
name: "pick",
|
|
@@ -4645,9 +5924,9 @@ async function addLoginMethod(teamId, tenant2, base2) {
|
|
|
4645
5924
|
message: "How do people log in?",
|
|
4646
5925
|
pageSize: 8,
|
|
4647
5926
|
choices: [
|
|
4648
|
-
{ name: `APIblaze hosted login page ${
|
|
4649
|
-
{ name: `Your own hosted login \u2014 JWT ${
|
|
4650
|
-
{ name: `Your own login \u2014 opaque token ${
|
|
5927
|
+
{ name: `APIblaze hosted login page ${import_chalk36.default.dim("we host login; pick a provider (GitHub/Google/\u2026) \u2014 easiest")}`, value: "apiblaze" },
|
|
5928
|
+
{ name: `Your own hosted login \u2014 JWT ${import_chalk36.default.dim("your identity provider issues JWTs; we trust them")}`, value: "jwt" },
|
|
5929
|
+
{ name: `Your own login \u2014 opaque token ${import_chalk36.default.dim("we validate your opaque tokens via an introspection endpoint")}`, value: "opaque" },
|
|
4651
5930
|
{ name: "\u2190 Back", value: null }
|
|
4652
5931
|
]
|
|
4653
5932
|
}]);
|
|
@@ -4671,8 +5950,8 @@ function safeJson(s) {
|
|
|
4671
5950
|
async function emailsMenu(base2, emails) {
|
|
4672
5951
|
const { default: inquirer3 } = await import("inquirer");
|
|
4673
5952
|
console.log();
|
|
4674
|
-
if (!emails.length) console.log(
|
|
4675
|
-
for (const e of emails) console.log(` ${
|
|
5953
|
+
if (!emails.length) console.log(import_chalk36.default.dim(" No consumer-admin emails."));
|
|
5954
|
+
for (const e of emails) console.log(` ${import_chalk36.default.bold(e.email ?? e)} ${import_chalk36.default.dim(e.status ?? "")}`);
|
|
4676
5955
|
const { act } = await inquirer3.prompt([{
|
|
4677
5956
|
type: "list",
|
|
4678
5957
|
name: "act",
|
|
@@ -4687,7 +5966,7 @@ async function emailsMenu(base2, emails) {
|
|
|
4687
5966
|
if (act === "add") {
|
|
4688
5967
|
const { email } = await inquirer3.prompt([{ type: "input", name: "email", message: "Email:", validate: (s) => /.+@.+\..+/.test(s) || "not an email" }]);
|
|
4689
5968
|
await admin({ method: "POST", path: `${base2}/admin-emails`, body: { email }, summary: `Add consumer-admin ${email}` });
|
|
4690
|
-
console.log(
|
|
5969
|
+
console.log(import_chalk36.default.green(` ${email} added.`));
|
|
4691
5970
|
} else {
|
|
4692
5971
|
const { e } = await inquirer3.prompt([{
|
|
4693
5972
|
type: "list",
|
|
@@ -4697,7 +5976,7 @@ async function emailsMenu(base2, emails) {
|
|
|
4697
5976
|
}]);
|
|
4698
5977
|
if (!e) return;
|
|
4699
5978
|
await admin({ method: "DELETE", path: `${base2}/admin-emails/${encodeURIComponent(e)}`, summary: `Remove consumer-admin ${e}` });
|
|
4700
|
-
console.log(
|
|
5979
|
+
console.log(import_chalk36.default.green(` ${e} removed.`));
|
|
4701
5980
|
}
|
|
4702
5981
|
}
|
|
4703
5982
|
async function addIssuer(base2) {
|
|
@@ -4718,19 +5997,19 @@ async function addIssuer(base2) {
|
|
|
4718
5997
|
body: { iss: a.iss.trim(), aud: a.aud.trim(), jwks_url: a.jwks.trim() || null, sub_semantics: a.sem, ...claim ? { claim_name: claim } : {} },
|
|
4719
5998
|
summary: `Add external issuer ${a.iss.trim()}`
|
|
4720
5999
|
});
|
|
4721
|
-
console.log(
|
|
6000
|
+
console.log(import_chalk36.default.green(" JWT login method saved."));
|
|
4722
6001
|
}
|
|
4723
6002
|
async function issuerHome(base2, issuer) {
|
|
4724
6003
|
const { default: inquirer3 } = await import("inquirer");
|
|
4725
6004
|
console.log(`
|
|
4726
|
-
${
|
|
6005
|
+
${import_chalk36.default.bold(issuer.iss)} ${import_chalk36.default.dim(`aud=${issuer.aud} \xB7 ${issuer.sub_semantics ?? ""}`)}`);
|
|
4727
6006
|
const { act } = await inquirer3.prompt([{
|
|
4728
6007
|
type: "list",
|
|
4729
6008
|
name: "act",
|
|
4730
6009
|
message: "This JWT login method:",
|
|
4731
6010
|
choices: [
|
|
4732
6011
|
{ name: "Replace it (re-enter details)", value: "edit" },
|
|
4733
|
-
{ name:
|
|
6012
|
+
{ name: import_chalk36.default.red("Delete it"), value: "rm" },
|
|
4734
6013
|
{ name: "\u2190 Back", value: "back" }
|
|
4735
6014
|
]
|
|
4736
6015
|
}]);
|
|
@@ -4746,7 +6025,7 @@ async function issuerHome(base2, issuer) {
|
|
|
4746
6025
|
path: `${base2}/external-issuers?iss=${encodeURIComponent(issuer.iss)}&aud=${encodeURIComponent(issuer.aud)}`,
|
|
4747
6026
|
summary: `Delete issuer ${issuer.iss}`
|
|
4748
6027
|
});
|
|
4749
|
-
console.log(
|
|
6028
|
+
console.log(import_chalk36.default.green(" Deleted."));
|
|
4750
6029
|
}
|
|
4751
6030
|
async function setOpaque(base2, cur) {
|
|
4752
6031
|
const { default: inquirer3 } = await import("inquirer");
|
|
@@ -4755,19 +6034,19 @@ async function setOpaque(base2, cur) {
|
|
|
4755
6034
|
{ type: "list", name: "method", message: "HTTP method:", choices: ["GET", "POST"], default: cur?.method ?? "GET" }
|
|
4756
6035
|
]);
|
|
4757
6036
|
await admin({ method: "PUT", path: `${base2}/opaque`, body: { opaque: { endpoint: a.endpoint, method: a.method } }, summary: "Set opaque validator" });
|
|
4758
|
-
console.log(
|
|
6037
|
+
console.log(import_chalk36.default.green(" Opaque login method set."));
|
|
4759
6038
|
}
|
|
4760
6039
|
async function opaqueHome(base2, cur) {
|
|
4761
6040
|
const { default: inquirer3 } = await import("inquirer");
|
|
4762
6041
|
console.log(`
|
|
4763
|
-
${
|
|
6042
|
+
${import_chalk36.default.bold(cur.endpoint)} ${import_chalk36.default.dim(cur.method ?? "GET")}`);
|
|
4764
6043
|
const { act } = await inquirer3.prompt([{
|
|
4765
6044
|
type: "list",
|
|
4766
6045
|
name: "act",
|
|
4767
6046
|
message: "This opaque login method:",
|
|
4768
6047
|
choices: [
|
|
4769
6048
|
{ name: "Replace it", value: "edit" },
|
|
4770
|
-
{ name:
|
|
6049
|
+
{ name: import_chalk36.default.red("Delete it"), value: "rm" },
|
|
4771
6050
|
{ name: "\u2190 Back", value: "back" }
|
|
4772
6051
|
]
|
|
4773
6052
|
}]);
|
|
@@ -4777,14 +6056,14 @@ async function opaqueHome(base2, cur) {
|
|
|
4777
6056
|
return;
|
|
4778
6057
|
}
|
|
4779
6058
|
await admin({ method: "PUT", path: `${base2}/opaque`, body: { opaque: null }, summary: "Clear opaque validator" });
|
|
4780
|
-
console.log(
|
|
6059
|
+
console.log(import_chalk36.default.green(" Deleted."));
|
|
4781
6060
|
}
|
|
4782
6061
|
async function clientHome(base2, summary) {
|
|
4783
6062
|
const { default: inquirer3 } = await import("inquirer");
|
|
4784
6063
|
const id = summary.clientId ?? summary.client_id;
|
|
4785
6064
|
const cBase = `${base2}/app-clients/${encodeURIComponent(id)}`;
|
|
4786
6065
|
for (; ; ) {
|
|
4787
|
-
const spinner = (0,
|
|
6066
|
+
const spinner = (0, import_ora15.default)("Reading app client...").start();
|
|
4788
6067
|
const c = await admin({ method: "GET", path: cBase, summary: `Read app client ${id}` }).catch(() => summary);
|
|
4789
6068
|
spinner.stop();
|
|
4790
6069
|
const cb = c.authorizedCallbackUrls ?? c.authorized_callback_urls ?? [];
|
|
@@ -4796,13 +6075,13 @@ async function clientHome(base2, summary) {
|
|
|
4796
6075
|
message: `${c.name ?? id}:`,
|
|
4797
6076
|
pageSize: 12,
|
|
4798
6077
|
choices: [
|
|
4799
|
-
{ name: `Login providers${nProviders ? ` (${nProviders})` : ""} ${
|
|
4800
|
-
{ name: `Callback URLs: ${cb.length ?
|
|
4801
|
-
{ name: `Scopes: ${scopes.length ?
|
|
6078
|
+
{ name: `Login providers${nProviders ? ` (${nProviders})` : ""} ${import_chalk36.default.dim("google/github/microsoft/\u2026 \u2014 how consumers sign in")}`, value: "providers" },
|
|
6079
|
+
{ name: `Callback URLs: ${cb.length ? import_chalk36.default.cyan(cb.join(", ")) : import_chalk36.default.dim("(none)")}`, value: "callbacks" },
|
|
6080
|
+
{ name: `Scopes: ${scopes.length ? import_chalk36.default.cyan(scopes.join(" ")) : import_chalk36.default.dim("(defaults)")}`, value: "scopes" },
|
|
4802
6081
|
{ name: `Token expiries: access ${c.accessTokenExpiry ?? 3600}s \xB7 id ${c.idTokenExpiry ?? 3600}s \xB7 refresh ${c.refreshTokenExpiry ?? 2592e3}s`, value: "expiries" },
|
|
4803
6082
|
{ name: "Reveal client secret", value: "secret" },
|
|
4804
6083
|
{ name: "Rotate client secret", value: "rotate" },
|
|
4805
|
-
{ name:
|
|
6084
|
+
{ name: import_chalk36.default.red("Delete this app client"), value: "delete" },
|
|
4806
6085
|
{ name: "\u2190 Back", value: "back" }
|
|
4807
6086
|
]
|
|
4808
6087
|
}]);
|
|
@@ -4815,13 +6094,13 @@ async function clientHome(base2, summary) {
|
|
|
4815
6094
|
case "callbacks": {
|
|
4816
6095
|
const { v } = await inquirer3.prompt([{ type: "input", name: "v", message: "Callback URLs (comma-separated):", default: cb.join(", ") }]);
|
|
4817
6096
|
await admin({ method: "PATCH", path: cBase, body: { authorizedCallbackUrls: parseList(v) }, summary: "Update callback URLs" });
|
|
4818
|
-
console.log(
|
|
6097
|
+
console.log(import_chalk36.default.green(" Callbacks updated."));
|
|
4819
6098
|
break;
|
|
4820
6099
|
}
|
|
4821
6100
|
case "scopes": {
|
|
4822
6101
|
const { v } = await inquirer3.prompt([{ type: "input", name: "v", message: "Scopes (space/comma-separated):", default: scopes.join(" ") }]);
|
|
4823
6102
|
await admin({ method: "PATCH", path: cBase, body: { scopes: v.split(/[\s,]+/).filter(Boolean) }, summary: "Update scopes" });
|
|
4824
|
-
console.log(
|
|
6103
|
+
console.log(import_chalk36.default.green(" Scopes updated."));
|
|
4825
6104
|
break;
|
|
4826
6105
|
}
|
|
4827
6106
|
case "expiries": {
|
|
@@ -4836,14 +6115,14 @@ async function clientHome(base2, summary) {
|
|
|
4836
6115
|
body: { accessTokenExpiry: Number(a.access), idTokenExpiry: Number(a.id), refreshTokenExpiry: Number(a.refresh) },
|
|
4837
6116
|
summary: "Update token expiries"
|
|
4838
6117
|
});
|
|
4839
|
-
console.log(
|
|
6118
|
+
console.log(import_chalk36.default.green(" Expiries updated."));
|
|
4840
6119
|
break;
|
|
4841
6120
|
}
|
|
4842
6121
|
case "secret": {
|
|
4843
6122
|
const { sure } = await inquirer3.prompt([{ type: "confirm", name: "sure", message: "Print the client secret to this terminal?", default: false }]);
|
|
4844
6123
|
if (!sure) break;
|
|
4845
6124
|
const s = await admin({ method: "GET", path: `${cBase}/secret`, summary: "Reveal client secret" });
|
|
4846
|
-
console.log(` ${
|
|
6125
|
+
console.log(` ${import_chalk36.default.bold("client_secret")}: ${import_chalk36.default.green(s?.clientSecret ?? s?.client_secret ?? JSON.stringify(s))}`);
|
|
4847
6126
|
break;
|
|
4848
6127
|
}
|
|
4849
6128
|
case "rotate": {
|
|
@@ -4851,14 +6130,14 @@ async function clientHome(base2, summary) {
|
|
|
4851
6130
|
if (!sure) break;
|
|
4852
6131
|
const fresh = randomSecret();
|
|
4853
6132
|
await admin({ method: "PATCH", path: cBase, body: { clientSecret: fresh }, summary: "Rotate client secret" });
|
|
4854
|
-
console.log(` New ${
|
|
6133
|
+
console.log(` New ${import_chalk36.default.bold("client_secret")}: ${import_chalk36.default.green(fresh)} ${import_chalk36.default.dim("(store it now)")}`);
|
|
4855
6134
|
break;
|
|
4856
6135
|
}
|
|
4857
6136
|
case "delete": {
|
|
4858
6137
|
const { sure } = await inquirer3.prompt([{ type: "confirm", name: "sure", message: `Delete app client "${c.name ?? id}"? Consumers logged in through it will lose access.`, default: false }]);
|
|
4859
6138
|
if (!sure) break;
|
|
4860
6139
|
await admin({ method: "DELETE", path: cBase, summary: `Delete app client ${id}` });
|
|
4861
|
-
console.log(
|
|
6140
|
+
console.log(import_chalk36.default.green(" App client deleted."));
|
|
4862
6141
|
return;
|
|
4863
6142
|
}
|
|
4864
6143
|
}
|
|
@@ -4879,15 +6158,15 @@ var DEFAULT_SCOPES2 = {
|
|
|
4879
6158
|
async function providersMenu(cBase, clientLabel) {
|
|
4880
6159
|
const { default: inquirer3 } = await import("inquirer");
|
|
4881
6160
|
for (; ; ) {
|
|
4882
|
-
const spinner = (0,
|
|
6161
|
+
const spinner = (0, import_ora15.default)("Loading providers...").start();
|
|
4883
6162
|
const raw = await admin({ method: "GET", path: `${cBase}/providers`, summary: "List login providers" }).catch(() => []);
|
|
4884
6163
|
spinner.stop();
|
|
4885
6164
|
const providers = Array.isArray(raw) ? raw : [];
|
|
4886
6165
|
console.log();
|
|
4887
6166
|
for (const p of providers) {
|
|
4888
|
-
console.log(` ${
|
|
6167
|
+
console.log(` ${import_chalk36.default.bold(p.type)} ${import_chalk36.default.dim(`${p.clientId || "(managed)"} \xB7 identity=${p.tokenType ?? "apiblaze"} \xB7 to-upstream=${p.targetServerToken ?? "apiblaze"}${p.isApiblazeDefault ? " \xB7 apiblaze-managed" : ""}`)}`);
|
|
4889
6168
|
}
|
|
4890
|
-
if (!providers.length) console.log(
|
|
6169
|
+
if (!providers.length) console.log(import_chalk36.default.dim(" No login providers \u2014 consumers cannot sign in to this client yet."));
|
|
4891
6170
|
const { act } = await inquirer3.prompt([{
|
|
4892
6171
|
type: "list",
|
|
4893
6172
|
name: "act",
|
|
@@ -4945,23 +6224,23 @@ async function providersMenu(cBase, clientLabel) {
|
|
|
4945
6224
|
body.targetServerToken = routing;
|
|
4946
6225
|
}
|
|
4947
6226
|
await admin({ method: "POST", path: `${cBase}/providers`, body, summary: `Add ${type} login provider` });
|
|
4948
|
-
console.log(
|
|
6227
|
+
console.log(import_chalk36.default.green(` ${type} provider added.`));
|
|
4949
6228
|
} else {
|
|
4950
6229
|
const { p } = await inquirer3.prompt([{
|
|
4951
6230
|
type: "list",
|
|
4952
6231
|
name: "p",
|
|
4953
6232
|
message: act === "rm" ? "Remove which provider?" : "Reveal which secret?",
|
|
4954
|
-
choices: [...providers.map((x) => ({ name: `${x.type} ${
|
|
6233
|
+
choices: [...providers.map((x) => ({ name: `${x.type} ${import_chalk36.default.dim(x.clientId || "(managed)")}`, value: x })), { name: "\u2190 Back", value: null }]
|
|
4955
6234
|
}]);
|
|
4956
6235
|
if (!p) continue;
|
|
4957
6236
|
if (act === "rm") {
|
|
4958
6237
|
const { sure } = await inquirer3.prompt([{ type: "confirm", name: "sure", message: `Remove the ${p.type} provider? Consumers using it can no longer sign in.`, default: false }]);
|
|
4959
6238
|
if (!sure) continue;
|
|
4960
6239
|
await admin({ method: "DELETE", path: `${cBase}/providers/${encodeURIComponent(p.id)}`, summary: `Remove ${p.type} provider` });
|
|
4961
|
-
console.log(
|
|
6240
|
+
console.log(import_chalk36.default.green(` ${p.type} removed.`));
|
|
4962
6241
|
} else {
|
|
4963
6242
|
const s = await admin({ method: "GET", path: `${cBase}/providers/${encodeURIComponent(p.id)}/secret`, summary: `Reveal ${p.type} provider secret` });
|
|
4964
|
-
console.log(` ${
|
|
6243
|
+
console.log(` ${import_chalk36.default.bold("client_secret")}: ${import_chalk36.default.green(s?.clientSecret ?? s?.client_secret ?? JSON.stringify(s))}`);
|
|
4965
6244
|
}
|
|
4966
6245
|
}
|
|
4967
6246
|
}
|
|
@@ -4969,8 +6248,8 @@ async function providersMenu(cBase, clientLabel) {
|
|
|
4969
6248
|
|
|
4970
6249
|
// src/commands/spec.ts
|
|
4971
6250
|
var fs7 = __toESM(require("fs"));
|
|
4972
|
-
var
|
|
4973
|
-
var
|
|
6251
|
+
var import_chalk37 = __toESM(require("chalk"));
|
|
6252
|
+
var import_ora16 = __toESM(require("ora"));
|
|
4974
6253
|
init_admin();
|
|
4975
6254
|
init_api();
|
|
4976
6255
|
init_resolve();
|
|
@@ -4986,19 +6265,19 @@ async function runSpecGet(project, opts) {
|
|
|
4986
6265
|
}
|
|
4987
6266
|
async function runSpecSet(project, opts) {
|
|
4988
6267
|
if (!opts.file) {
|
|
4989
|
-
console.error(
|
|
6268
|
+
console.error(import_chalk37.default.red("--file <path> is required (OpenAPI JSON or YAML)."));
|
|
4990
6269
|
process.exit(1);
|
|
4991
6270
|
}
|
|
4992
6271
|
let specContent;
|
|
4993
6272
|
try {
|
|
4994
6273
|
specContent = fs7.readFileSync(opts.file, "utf-8");
|
|
4995
6274
|
} catch {
|
|
4996
|
-
console.error(
|
|
6275
|
+
console.error(import_chalk37.default.red(`Cannot read file: ${opts.file}`));
|
|
4997
6276
|
process.exit(1);
|
|
4998
6277
|
}
|
|
4999
6278
|
const { teamId } = await resolveTeam(opts.team);
|
|
5000
6279
|
const proj2 = await resolveProject(teamId, project, opts.apiversion);
|
|
5001
|
-
const spinner = (0,
|
|
6280
|
+
const spinner = (0, import_ora16.default)("Uploading spec...").start();
|
|
5002
6281
|
try {
|
|
5003
6282
|
const out = await admin({
|
|
5004
6283
|
method: "POST",
|
|
@@ -5022,28 +6301,28 @@ async function runSpecSet(project, opts) {
|
|
|
5022
6301
|
function printSpecChangeReport(report, projectName) {
|
|
5023
6302
|
const r = report.routes;
|
|
5024
6303
|
if (r) {
|
|
5025
|
-
for (const a of r.added) console.log(
|
|
5026
|
-
for (const d of r.removed) console.log(
|
|
5027
|
-
for (const rn of r.renamed ?? []) console.log(
|
|
5028
|
-
for (const m of r.modified ?? []) console.log(
|
|
6304
|
+
for (const a of r.added) console.log(import_chalk37.default.green(` + ${a}`));
|
|
6305
|
+
for (const d of r.removed) console.log(import_chalk37.default.red(` \u2212 ${d}`));
|
|
6306
|
+
for (const rn of r.renamed ?? []) console.log(import_chalk37.default.yellow(` ~ ${rn.from} \u2192 ${rn.to}`));
|
|
6307
|
+
for (const m of r.modified ?? []) console.log(import_chalk37.default.dim(` \xB1 ${m} (params/schema changed)`));
|
|
5029
6308
|
}
|
|
5030
|
-
if (report.applied?.mcp_regenerated) console.log(
|
|
5031
|
-
if (report.applied?.candidates_cleared) console.log(
|
|
6309
|
+
if (report.applied?.mcp_regenerated) console.log(import_chalk37.default.dim(" MCP catalogue regenerated."));
|
|
6310
|
+
if (report.applied?.candidates_cleared) console.log(import_chalk37.default.dim(` ${report.applied.candidates_cleared} route candidate(s) now documented \u2014 cleared.`));
|
|
5032
6311
|
for (const item of report.attention ?? []) {
|
|
5033
|
-
console.log(
|
|
6312
|
+
console.log(import_chalk37.default.yellow(` \u26A0 ${item.detail}`));
|
|
5034
6313
|
if (item.suggestion?.carry_from && item.suggestion.carry_to) {
|
|
5035
|
-
console.log(
|
|
6314
|
+
console.log(import_chalk37.default.dim(` Fix: apiblaze spec carry ${projectName} --method ${item.suggestion.method} --from "${item.suggestion.carry_from}" --to "${item.suggestion.carry_to}"`));
|
|
5036
6315
|
}
|
|
5037
6316
|
}
|
|
5038
6317
|
}
|
|
5039
6318
|
async function runSpecCarry(project, opts) {
|
|
5040
6319
|
if (!opts.method || !opts.from || !opts.to) {
|
|
5041
|
-
console.error(
|
|
6320
|
+
console.error(import_chalk37.default.red("--method, --from and --to are required."));
|
|
5042
6321
|
process.exit(1);
|
|
5043
6322
|
}
|
|
5044
6323
|
const { teamId } = await resolveTeam(opts.team);
|
|
5045
6324
|
const proj2 = await resolveProject(teamId, project, opts.apiversion);
|
|
5046
|
-
const spinner = (0,
|
|
6325
|
+
const spinner = (0, import_ora16.default)(`Carrying rules ${opts.from} \u2192 ${opts.to}...`).start();
|
|
5047
6326
|
const res = await agentCall(`/projects/${proj2.projectId}/${proj2.apiVersion}/policies/route/carry`, "POST", {
|
|
5048
6327
|
method: opts.method.toUpperCase(),
|
|
5049
6328
|
from_resource: opts.from,
|
|
@@ -5060,13 +6339,13 @@ async function runSpecCarry(project, opts) {
|
|
|
5060
6339
|
}
|
|
5061
6340
|
async function runSpecDeleteRule(project, opts) {
|
|
5062
6341
|
if (!opts.method || !opts.route) {
|
|
5063
|
-
console.error(
|
|
6342
|
+
console.error(import_chalk37.default.red("--method and --route are required."));
|
|
5064
6343
|
process.exit(1);
|
|
5065
6344
|
}
|
|
5066
6345
|
const { teamId } = await resolveTeam(opts.team);
|
|
5067
6346
|
const proj2 = await resolveProject(teamId, project, opts.apiversion);
|
|
5068
6347
|
const encoded = opts.route.replace(/\{/g, "%7B").replace(/\}/g, "%7D");
|
|
5069
|
-
const spinner = (0,
|
|
6348
|
+
const spinner = (0, import_ora16.default)(`Deleting rules for ${opts.method.toUpperCase()} ${opts.route}...`).start();
|
|
5070
6349
|
const res = await agentCall(
|
|
5071
6350
|
`/projects/${proj2.projectId}/${proj2.apiVersion}/policies/route/${opts.method.toUpperCase()}${encoded.startsWith("/") ? encoded : `/${encoded}`}`,
|
|
5072
6351
|
"DELETE"
|
|
@@ -5080,14 +6359,14 @@ async function runSpecDeleteRule(project, opts) {
|
|
|
5080
6359
|
}
|
|
5081
6360
|
|
|
5082
6361
|
// src/commands/agent.ts
|
|
5083
|
-
var
|
|
5084
|
-
var
|
|
6362
|
+
var import_chalk39 = __toESM(require("chalk"));
|
|
6363
|
+
var import_ora17 = __toESM(require("ora"));
|
|
5085
6364
|
init_auth();
|
|
5086
6365
|
init_admin();
|
|
5087
6366
|
init_resolve();
|
|
5088
6367
|
|
|
5089
6368
|
// src/lib/tools.ts
|
|
5090
|
-
var
|
|
6369
|
+
var import_chalk38 = __toESM(require("chalk"));
|
|
5091
6370
|
init_admin();
|
|
5092
6371
|
init_api();
|
|
5093
6372
|
init_resolve();
|
|
@@ -5107,15 +6386,15 @@ var TOOLS = [
|
|
|
5107
6386
|
const key = keys.dev ?? Object.values(keys)[0];
|
|
5108
6387
|
const url = `https://${a.name}.abz.run/${version2}/dev`;
|
|
5109
6388
|
const tryIt = buildTryItCurl(url, auth, key);
|
|
5110
|
-
const lines = [` ${
|
|
5111
|
-
if (res.devPortal) lines.push(` ${
|
|
6389
|
+
const lines = [` ${import_chalk38.default.dim("Proxy URL:")} ${import_chalk38.default.bold(url)}`];
|
|
6390
|
+
if (res.devPortal) lines.push(` ${import_chalk38.default.dim("Dev portal:")} ${res.devPortal}`);
|
|
5112
6391
|
const envs = Object.keys(keys);
|
|
5113
6392
|
if (envs.length) {
|
|
5114
|
-
lines.push("", ` ${
|
|
6393
|
+
lines.push("", ` ${import_chalk38.default.bold("API keys")} ${import_chalk38.default.dim("(bootstrapped \u2014 send as the X-API-Key header; shown once):")}`);
|
|
5115
6394
|
const w = Math.max(...envs.map((e) => e.length));
|
|
5116
|
-
for (const env of envs) lines.push(` ${
|
|
6395
|
+
for (const env of envs) lines.push(` ${import_chalk38.default.cyan(env.padEnd(w))} ${import_chalk38.default.green(keys[env])}`);
|
|
5117
6396
|
}
|
|
5118
|
-
if (tryIt) lines.push("", ` ${
|
|
6397
|
+
if (tryIt) lines.push("", ` ${import_chalk38.default.dim("Try it:")}`, ` ${import_chalk38.default.cyan(tryIt)}`);
|
|
5119
6398
|
return { ...res, proxy_url: url, keys, ...tryIt ? { try_it: tryIt } : {}, display: lines.join("\n") };
|
|
5120
6399
|
}
|
|
5121
6400
|
},
|
|
@@ -5271,23 +6550,23 @@ function truncate(value, max = 1500) {
|
|
|
5271
6550
|
}
|
|
5272
6551
|
function printCost(resp) {
|
|
5273
6552
|
const line = formatBilling(resp.billing);
|
|
5274
|
-
if (line) console.log(
|
|
6553
|
+
if (line) console.log(import_chalk39.default.magenta(` ${line}`) + import_chalk39.default.dim(` (${resp.llm.model}, ${resp.llm.total_tokens} tok)`));
|
|
5275
6554
|
}
|
|
5276
6555
|
async function runAgent(opts) {
|
|
5277
6556
|
requireAuth();
|
|
5278
6557
|
const { teamId, teamName } = await resolveTeam(opts.team);
|
|
5279
6558
|
const { default: inquirer3 } = await import("inquirer");
|
|
5280
|
-
console.log(
|
|
5281
|
-
console.log(
|
|
6559
|
+
console.log(import_chalk39.default.bold("APIblaze agent") + import_chalk39.default.dim(` \xB7 team ${teamName ?? teamId}`));
|
|
6560
|
+
console.log(import_chalk39.default.dim('Ask me to create/delete/configure proxies, tenants, keys, domains, specs. Type "exit" to quit.\n'));
|
|
5282
6561
|
const history = [];
|
|
5283
6562
|
while (true) {
|
|
5284
|
-
const { input } = await inquirer3.prompt([{ type: "input", name: "input", message:
|
|
6563
|
+
const { input } = await inquirer3.prompt([{ type: "input", name: "input", message: import_chalk39.default.cyan("you") + " \u203A" }]);
|
|
5285
6564
|
const text = (input ?? "").trim();
|
|
5286
6565
|
if (!text) continue;
|
|
5287
6566
|
if (["exit", "quit", ":q"].includes(text.toLowerCase())) break;
|
|
5288
6567
|
history.push({ role: "user", content: text });
|
|
5289
6568
|
for (let step = 0; step < MAX_TOOL_STEPS; step++) {
|
|
5290
|
-
const spinner = (0,
|
|
6569
|
+
const spinner = (0, import_ora17.default)({ text: "thinking...", color: "magenta" }).start();
|
|
5291
6570
|
let resp;
|
|
5292
6571
|
try {
|
|
5293
6572
|
resp = await callAgent(history, teamId);
|
|
@@ -5295,21 +6574,21 @@ async function runAgent(opts) {
|
|
|
5295
6574
|
} catch (err) {
|
|
5296
6575
|
spinner.stop();
|
|
5297
6576
|
if (err instanceof ApiError && err.status === 402) {
|
|
5298
|
-
console.log(
|
|
6577
|
+
console.log(import_chalk39.default.yellow(" Insufficient credits \u2014 top up to keep using the agent."));
|
|
5299
6578
|
break;
|
|
5300
6579
|
}
|
|
5301
6580
|
throw err;
|
|
5302
6581
|
}
|
|
5303
6582
|
history.push({ role: "assistant", content: resp.raw });
|
|
5304
6583
|
printCost(resp);
|
|
5305
|
-
if (resp.reply) console.log(
|
|
6584
|
+
if (resp.reply) console.log(import_chalk39.default.green("agent") + " \u203A " + resp.reply);
|
|
5306
6585
|
if (!resp.action) break;
|
|
5307
6586
|
const tool = findTool(resp.action.tool);
|
|
5308
6587
|
if (!tool) {
|
|
5309
6588
|
history.push({ role: "user", content: `TOOL_RESULT ${resp.action.tool}: error \u2014 unknown tool` });
|
|
5310
6589
|
continue;
|
|
5311
6590
|
}
|
|
5312
|
-
const runSpinner = (0,
|
|
6591
|
+
const runSpinner = (0, import_ora17.default)({ text: `running ${tool.name}...`, color: "cyan" }).start();
|
|
5313
6592
|
try {
|
|
5314
6593
|
const result = await tool.run(resp.action.args, { teamId });
|
|
5315
6594
|
runSpinner.succeed(`${tool.name} \u2713`);
|
|
@@ -5327,11 +6606,11 @@ async function runAgent(opts) {
|
|
|
5327
6606
|
}
|
|
5328
6607
|
renderTrace();
|
|
5329
6608
|
if (step === MAX_TOOL_STEPS - 1) {
|
|
5330
|
-
console.log(
|
|
6609
|
+
console.log(import_chalk39.default.dim(" (paused after several steps \u2014 tell me how to continue)"));
|
|
5331
6610
|
}
|
|
5332
6611
|
}
|
|
5333
6612
|
}
|
|
5334
|
-
console.log(
|
|
6613
|
+
console.log(import_chalk39.default.dim("\nBye."));
|
|
5335
6614
|
}
|
|
5336
6615
|
|
|
5337
6616
|
// src/commands/config-browse.ts
|
|
@@ -5617,11 +6896,11 @@ function dig(blob, dotted) {
|
|
|
5617
6896
|
}
|
|
5618
6897
|
var readSetting = (s, cfg) => s.read ? s.read(cfg) : dig(cfg, s.key);
|
|
5619
6898
|
function show(v) {
|
|
5620
|
-
if (v === void 0) return
|
|
5621
|
-
if (v === null) return
|
|
5622
|
-
if (typeof v === "object") return
|
|
5623
|
-
if (typeof v === "boolean") return v ?
|
|
5624
|
-
return
|
|
6899
|
+
if (v === void 0) return import_chalk40.default.dim("(unset)");
|
|
6900
|
+
if (v === null) return import_chalk40.default.dim("null");
|
|
6901
|
+
if (typeof v === "object") return import_chalk40.default.cyan(JSON.stringify(v));
|
|
6902
|
+
if (typeof v === "boolean") return v ? import_chalk40.default.green("on") : import_chalk40.default.red("off");
|
|
6903
|
+
return import_chalk40.default.cyan(String(v));
|
|
5625
6904
|
}
|
|
5626
6905
|
function parseValue(raw) {
|
|
5627
6906
|
if (raw === "true") return true;
|
|
@@ -5648,7 +6927,7 @@ async function fetchConfigBlob(proj2) {
|
|
|
5648
6927
|
}
|
|
5649
6928
|
async function patchSetting(proj2, s, value, cfg) {
|
|
5650
6929
|
const body = s.toPatch(value, cfg);
|
|
5651
|
-
const spinner = (0,
|
|
6930
|
+
const spinner = (0, import_ora18.default)(`Set ${s.key}...`).start();
|
|
5652
6931
|
try {
|
|
5653
6932
|
await admin({
|
|
5654
6933
|
method: "PATCH",
|
|
@@ -5663,10 +6942,10 @@ async function patchSetting(proj2, s, value, cfg) {
|
|
|
5663
6942
|
}
|
|
5664
6943
|
}
|
|
5665
6944
|
var loginFirst = (what) => {
|
|
5666
|
-
console.log(
|
|
6945
|
+
console.log(import_chalk40.default.yellow(`
|
|
5667
6946
|
Log in first to ${what}.`));
|
|
5668
|
-
console.log(
|
|
5669
|
-
console.log(
|
|
6947
|
+
console.log(import_chalk40.default.dim(" Run `npx apiblaze login` \u2014 or `npx apiblaze claim` if you created this proxy"));
|
|
6948
|
+
console.log(import_chalk40.default.dim(" anonymously and want to bring it into your account.\n"));
|
|
5670
6949
|
};
|
|
5671
6950
|
async function runConfig(project, key, value, opts) {
|
|
5672
6951
|
const creds = loadCredentials();
|
|
@@ -5683,9 +6962,9 @@ async function runConfig(project, key, value, opts) {
|
|
|
5683
6962
|
}
|
|
5684
6963
|
const setting = SETTINGS.find((s) => s.key === key);
|
|
5685
6964
|
if (!setting) {
|
|
5686
|
-
console.error(
|
|
5687
|
-
console.error(
|
|
5688
|
-
console.error(
|
|
6965
|
+
console.error(import_chalk40.default.red(`Unknown setting "${key}".`));
|
|
6966
|
+
console.error(import_chalk40.default.dim(" Known: " + SETTINGS.map((s) => s.key).join(", ")));
|
|
6967
|
+
console.error(import_chalk40.default.dim(" (Features like transforms/domains/tenants live in the menu: `apiblaze config <project>`.)"));
|
|
5689
6968
|
process.exit(1);
|
|
5690
6969
|
}
|
|
5691
6970
|
if (value === void 0) {
|
|
@@ -5700,7 +6979,7 @@ async function pickProject(teamId) {
|
|
|
5700
6979
|
const { getProjects: getProjects2 } = await Promise.resolve().then(() => (init_api(), api_exports));
|
|
5701
6980
|
const projects = await getProjects2(teamId).catch(() => []);
|
|
5702
6981
|
if (!projects.length) {
|
|
5703
|
-
console.error(
|
|
6982
|
+
console.error(import_chalk40.default.red("No projects in this team. Create one: `npx apiblaze create`."));
|
|
5704
6983
|
process.exit(1);
|
|
5705
6984
|
}
|
|
5706
6985
|
const { default: inquirer3 } = await import("inquirer");
|
|
@@ -5708,7 +6987,7 @@ async function pickProject(teamId) {
|
|
|
5708
6987
|
type: "list",
|
|
5709
6988
|
name: "picked",
|
|
5710
6989
|
message: "Which project?",
|
|
5711
|
-
choices: projects.map((p) => ({ name: `${p.projectName} ${
|
|
6990
|
+
choices: projects.map((p) => ({ name: `${p.projectName} ${import_chalk40.default.dim("v" + p.apiVersion)}`, value: p }))
|
|
5712
6991
|
}]);
|
|
5713
6992
|
return { projectId: picked.projectId, projectName: picked.projectName, apiVersion: picked.apiVersion, teamId, tenant: picked.tenant };
|
|
5714
6993
|
}
|
|
@@ -5719,25 +6998,25 @@ function printAll(proj2, cfg, json) {
|
|
|
5719
6998
|
console.log(JSON.stringify(out, null, 2));
|
|
5720
6999
|
return;
|
|
5721
7000
|
}
|
|
5722
|
-
console.log(
|
|
7001
|
+
console.log(import_chalk40.default.bold(`
|
|
5723
7002
|
${proj2.projectName} v${proj2.apiVersion} \u2014 settings
|
|
5724
7003
|
`));
|
|
5725
7004
|
for (const group2 of SETTING_GROUPS) {
|
|
5726
|
-
console.log(
|
|
7005
|
+
console.log(import_chalk40.default.bold(group2));
|
|
5727
7006
|
for (const s of SETTINGS.filter((x) => x.group === group2)) {
|
|
5728
|
-
console.log(` ${s.key.padEnd(32)} ${show(readSetting(s, cfg))} ${
|
|
7007
|
+
console.log(` ${s.key.padEnd(32)} ${show(readSetting(s, cfg))} ${import_chalk40.default.dim(s.desc)}`);
|
|
5729
7008
|
}
|
|
5730
7009
|
console.log();
|
|
5731
7010
|
}
|
|
5732
|
-
console.log(
|
|
7011
|
+
console.log(import_chalk40.default.dim("Change one: apiblaze config " + proj2.projectName + " <key> <value> (add --verbose for the API call)"));
|
|
5733
7012
|
}
|
|
5734
7013
|
async function discoveryMenu(project) {
|
|
5735
7014
|
const { default: inquirer3 } = await import("inquirer");
|
|
5736
|
-
console.log(
|
|
7015
|
+
console.log(import_chalk40.default.bold(`
|
|
5737
7016
|
APIblaze proxy configuration${project ? ` \u2014 ${project}` : ""}
|
|
5738
7017
|
`));
|
|
5739
|
-
console.log(
|
|
5740
|
-
console.log(
|
|
7018
|
+
console.log(import_chalk40.default.dim("You are not logged in \u2014 browsing what's configurable. Everything below works"));
|
|
7019
|
+
console.log(import_chalk40.default.dim("from this menu once you log in (`npx apiblaze login`).\n"));
|
|
5741
7020
|
for (; ; ) {
|
|
5742
7021
|
const { pick: pick2 } = await inquirer3.prompt([{
|
|
5743
7022
|
type: "list",
|
|
@@ -5745,13 +7024,13 @@ APIblaze proxy configuration${project ? ` \u2014 ${project}` : ""}
|
|
|
5745
7024
|
message: "Explore:",
|
|
5746
7025
|
pageSize: 20,
|
|
5747
7026
|
choices: [
|
|
5748
|
-
new inquirer3.Separator(
|
|
7027
|
+
new inquirer3.Separator(import_chalk40.default.bold("\u2014 Settings \u2014")),
|
|
5749
7028
|
...SETTING_GROUPS.map((g) => ({
|
|
5750
|
-
name: `${g} ${
|
|
7029
|
+
name: `${g} ${import_chalk40.default.dim(SETTINGS.filter((s) => s.group === g).map((s) => s.label).join(", "))}`,
|
|
5751
7030
|
value: { kind: "settings", g }
|
|
5752
7031
|
})),
|
|
5753
|
-
new inquirer3.Separator(
|
|
5754
|
-
...FEATURES.map((f) => ({ name: `${f.label} ${
|
|
7032
|
+
new inquirer3.Separator(import_chalk40.default.bold("\u2014 Features \u2014")),
|
|
7033
|
+
...FEATURES.map((f) => ({ name: `${f.label} ${import_chalk40.default.dim(f.desc)}`, value: { kind: "feature", f } })),
|
|
5755
7034
|
new inquirer3.Separator(),
|
|
5756
7035
|
{ name: "Exit", value: { kind: "exit" } }
|
|
5757
7036
|
]
|
|
@@ -5760,24 +7039,24 @@ APIblaze proxy configuration${project ? ` \u2014 ${project}` : ""}
|
|
|
5760
7039
|
if (pick2.kind === "settings") {
|
|
5761
7040
|
console.log();
|
|
5762
7041
|
for (const s of SETTINGS.filter((x) => x.group === pick2.g)) {
|
|
5763
|
-
console.log(` ${
|
|
5764
|
-
console.log(` ${
|
|
7042
|
+
console.log(` ${import_chalk40.default.bold(s.label.padEnd(28))} ${import_chalk40.default.dim(s.desc)}`);
|
|
7043
|
+
console.log(` ${import_chalk40.default.dim(" key: " + s.key)}`);
|
|
5765
7044
|
}
|
|
5766
7045
|
loginFirst("view or change these settings");
|
|
5767
7046
|
} else {
|
|
5768
7047
|
const f = pick2.f;
|
|
5769
7048
|
console.log(`
|
|
5770
|
-
${
|
|
7049
|
+
${import_chalk40.default.bold(f.label)} \u2014 ${f.desc}`);
|
|
5771
7050
|
loginFirst(`use ${f.label.toLowerCase()}`);
|
|
5772
7051
|
}
|
|
5773
7052
|
}
|
|
5774
7053
|
}
|
|
5775
7054
|
async function navigator(proj2, cfg, opts) {
|
|
5776
7055
|
const { default: inquirer3 } = await import("inquirer");
|
|
5777
|
-
console.log(
|
|
7056
|
+
console.log(import_chalk40.default.bold(`
|
|
5778
7057
|
${proj2.projectName} v${proj2.apiVersion} \u2014 configuration
|
|
5779
7058
|
`));
|
|
5780
|
-
console.log(
|
|
7059
|
+
console.log(import_chalk40.default.dim("Tip: every change is one API call \u2014 add --verbose to see the curl equivalent.\n"));
|
|
5781
7060
|
let blob = cfg;
|
|
5782
7061
|
for (; ; ) {
|
|
5783
7062
|
const { pick: pick2 } = await inquirer3.prompt([{
|
|
@@ -5786,10 +7065,10 @@ ${proj2.projectName} v${proj2.apiVersion} \u2014 configuration
|
|
|
5786
7065
|
message: "Where to?",
|
|
5787
7066
|
pageSize: 20,
|
|
5788
7067
|
choices: [
|
|
5789
|
-
new inquirer3.Separator(
|
|
7068
|
+
new inquirer3.Separator(import_chalk40.default.bold("\u2014 Settings \u2014")),
|
|
5790
7069
|
...SETTING_GROUPS.map((g) => ({ name: g, value: { kind: "settings", g } })),
|
|
5791
|
-
new inquirer3.Separator(
|
|
5792
|
-
...FEATURES.map((f) => ({ name: `${f.label} ${
|
|
7070
|
+
new inquirer3.Separator(import_chalk40.default.bold("\u2014 Features \u2014")),
|
|
7071
|
+
...FEATURES.map((f) => ({ name: `${f.label} ${import_chalk40.default.dim(f.desc)}`, value: { kind: f.go } })),
|
|
5793
7072
|
new inquirer3.Separator(),
|
|
5794
7073
|
{ name: "Show all settings", value: { kind: "list" } },
|
|
5795
7074
|
{ name: "Exit", value: { kind: "exit" } }
|
|
@@ -5829,7 +7108,7 @@ ${proj2.projectName} v${proj2.apiVersion} \u2014 configuration
|
|
|
5829
7108
|
}
|
|
5830
7109
|
}
|
|
5831
7110
|
} catch (err) {
|
|
5832
|
-
console.error(
|
|
7111
|
+
console.error(import_chalk40.default.red(` ${err instanceof Error ? err.message : String(err)}`));
|
|
5833
7112
|
}
|
|
5834
7113
|
}
|
|
5835
7114
|
}
|
|
@@ -5843,7 +7122,7 @@ async function settingsGroup(proj2, cfg, group2) {
|
|
|
5843
7122
|
message: group2 + ":",
|
|
5844
7123
|
pageSize: 16,
|
|
5845
7124
|
choices: [
|
|
5846
|
-
...items.map((s2) => ({ name: `${s2.label.padEnd(30)} ${show(readSetting(s2, cfg))} ${
|
|
7125
|
+
...items.map((s2) => ({ name: `${s2.label.padEnd(30)} ${show(readSetting(s2, cfg))} ${import_chalk40.default.dim(s2.desc)}`, value: s2 })),
|
|
5847
7126
|
new inquirer3.Separator(),
|
|
5848
7127
|
{ name: "\u2190 Back", value: null }
|
|
5849
7128
|
]
|
|
@@ -5861,7 +7140,7 @@ async function settingsGroup(proj2, cfg, group2) {
|
|
|
5861
7140
|
} else if (s.type === "number") {
|
|
5862
7141
|
const { v } = await inquirer3.prompt([{ type: "input", name: "v", message: `${s.label} (number):`, default: readSetting(s, cfg) }]);
|
|
5863
7142
|
if (v === "" || Number.isNaN(Number(v))) {
|
|
5864
|
-
console.log(
|
|
7143
|
+
console.log(import_chalk40.default.yellow(" Not a number \u2014 unchanged."));
|
|
5865
7144
|
continue;
|
|
5866
7145
|
}
|
|
5867
7146
|
value = Number(v);
|
|
@@ -5942,7 +7221,7 @@ async function buildCondition(phase) {
|
|
|
5942
7221
|
const items = [];
|
|
5943
7222
|
for (; ; ) {
|
|
5944
7223
|
const a = await inquirer3.prompt([
|
|
5945
|
-
{ type: "input", name: "source", message: `Condition field ${
|
|
7224
|
+
{ type: "input", name: "source", message: `Condition field ${import_chalk40.default.dim(srcHint)}:`, validate: (s) => !!s || "required" },
|
|
5946
7225
|
{ type: "list", name: "operator", message: "Operator:", choices: [
|
|
5947
7226
|
"eq",
|
|
5948
7227
|
"neq",
|
|
@@ -5973,7 +7252,7 @@ async function buildCondition(phase) {
|
|
|
5973
7252
|
function showCondition(cond) {
|
|
5974
7253
|
if (!Array.isArray(cond) || !cond.length) return "";
|
|
5975
7254
|
const s = cond.map((c) => `${c.source} ${c.operator}${c.value !== void 0 ? ` "${c.value}"` : ""}${c.logicOp ? ` ${c.logicOp}` : ""}`).join(" ");
|
|
5976
|
-
return
|
|
7255
|
+
return import_chalk40.default.dim(` when ${s}`);
|
|
5977
7256
|
}
|
|
5978
7257
|
async function transformsMenu(proj2) {
|
|
5979
7258
|
const { default: inquirer3 } = await import("inquirer");
|
|
@@ -5982,12 +7261,12 @@ async function transformsMenu(proj2) {
|
|
|
5982
7261
|
const out = await admin({ method: "GET", path: base2, summary: "List transform rules" });
|
|
5983
7262
|
const rules = out?.rules ?? [];
|
|
5984
7263
|
console.log();
|
|
5985
|
-
if (!rules.length) console.log(
|
|
7264
|
+
if (!rules.length) console.log(import_chalk40.default.dim(" No transform rules yet."));
|
|
5986
7265
|
for (const r of rules) {
|
|
5987
7266
|
const a = r.action ?? {};
|
|
5988
7267
|
const fns = [...a.source_fns ?? [], ...a.dest_fns ?? []].map((f) => f.fn);
|
|
5989
|
-
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 ?
|
|
5990
|
-
console.log(` ${r.enabled ?
|
|
7268
|
+
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_chalk40.default.dim(` via ${fns.join("\u2192")}`) : ""}`;
|
|
7269
|
+
console.log(` ${r.enabled ? import_chalk40.default.green("\u25CF") : import_chalk40.default.dim("\u25CB")} ${import_chalk40.default.bold(r.name)} ${import_chalk40.default.dim(`[${r.phase ?? "request"}]`)} ${what}${showCondition(r.condition)}`);
|
|
5991
7270
|
}
|
|
5992
7271
|
const { act } = await inquirer3.prompt([{
|
|
5993
7272
|
type: "list",
|
|
@@ -5999,7 +7278,7 @@ async function transformsMenu(proj2) {
|
|
|
5999
7278
|
{ name: "Enable/disable a rule", value: "toggle" },
|
|
6000
7279
|
{ name: "Delete a rule", value: "delete" }
|
|
6001
7280
|
] : [],
|
|
6002
|
-
{ name:
|
|
7281
|
+
{ name: import_chalk40.default.dim("Add from raw JSON (grouped conditions, lookup tables, \u2026)"), value: "raw" },
|
|
6003
7282
|
{ name: "\u2190 Back", value: "back" }
|
|
6004
7283
|
]
|
|
6005
7284
|
}]);
|
|
@@ -6012,11 +7291,11 @@ async function transformsMenu(proj2) {
|
|
|
6012
7291
|
}]);
|
|
6013
7292
|
const body = parseValue(raw);
|
|
6014
7293
|
if (!body || typeof body !== "object" || Array.isArray(body)) {
|
|
6015
|
-
console.log(
|
|
7294
|
+
console.log(import_chalk40.default.yellow(" Not a JSON object \u2014 skipped."));
|
|
6016
7295
|
continue;
|
|
6017
7296
|
}
|
|
6018
7297
|
await admin({ method: "POST", path: base2, body, summary: "Create transform rule (raw JSON)" });
|
|
6019
|
-
console.log(
|
|
7298
|
+
console.log(import_chalk40.default.green(" Rule created."));
|
|
6020
7299
|
continue;
|
|
6021
7300
|
}
|
|
6022
7301
|
if (act === "add") {
|
|
@@ -6032,7 +7311,7 @@ async function transformsMenu(proj2) {
|
|
|
6032
7311
|
{ name: "Remove a field", value: "remove" }
|
|
6033
7312
|
] }
|
|
6034
7313
|
]);
|
|
6035
|
-
const fieldHint =
|
|
7314
|
+
const fieldHint = import_chalk40.default.dim("(e.g. header:x-api-version, param:limit, bodyvar:user.id)");
|
|
6036
7315
|
let action2;
|
|
6037
7316
|
if (ans.kind === "hardcode") {
|
|
6038
7317
|
const a = await inquirer3.prompt([
|
|
@@ -6061,7 +7340,7 @@ async function transformsMenu(proj2) {
|
|
|
6061
7340
|
};
|
|
6062
7341
|
}
|
|
6063
7342
|
const condition = await buildCondition(ans.phase);
|
|
6064
|
-
const spinner = (0,
|
|
7343
|
+
const spinner = (0, import_ora18.default)("Creating rule...").start();
|
|
6065
7344
|
try {
|
|
6066
7345
|
await admin({
|
|
6067
7346
|
method: "POST",
|
|
@@ -6079,16 +7358,16 @@ async function transformsMenu(proj2) {
|
|
|
6079
7358
|
type: "list",
|
|
6080
7359
|
name: "rule",
|
|
6081
7360
|
message: act === "toggle" ? "Which rule?" : "Delete which rule?",
|
|
6082
|
-
choices: [...rules.map((r) => ({ name: `${r.name} ${
|
|
7361
|
+
choices: [...rules.map((r) => ({ name: `${r.name} ${import_chalk40.default.dim(`[${r.phase ?? "request"}]`)}`, value: r })), { name: "\u2190 Back", value: null }]
|
|
6083
7362
|
}]);
|
|
6084
7363
|
if (!rule) continue;
|
|
6085
7364
|
if (act === "toggle") {
|
|
6086
7365
|
const flipped = { ...rule, enabled: rule.enabled === false };
|
|
6087
7366
|
await admin({ method: "PUT", path: `${base2}/${rule.id}`, body: flipped, summary: `${flipped.enabled ? "Enable" : "Disable"} transform "${rule.name}"` });
|
|
6088
|
-
console.log(
|
|
7367
|
+
console.log(import_chalk40.default.green(` ${rule.name} \u2192 ${flipped.enabled ? "enabled" : "disabled"}`));
|
|
6089
7368
|
} else {
|
|
6090
7369
|
await admin({ method: "DELETE", path: `${base2}/${rule.id}`, summary: `Delete transform "${rule.name}"` });
|
|
6091
|
-
console.log(
|
|
7370
|
+
console.log(import_chalk40.default.green(` ${rule.name} deleted.`));
|
|
6092
7371
|
}
|
|
6093
7372
|
}
|
|
6094
7373
|
}
|
|
@@ -6100,9 +7379,9 @@ async function mappingsMenu(proj2) {
|
|
|
6100
7379
|
const out = await admin({ method: "GET", path: base2, summary: "List mapping tables" });
|
|
6101
7380
|
const tables = out?.mappings ?? out?.tables ?? [];
|
|
6102
7381
|
console.log();
|
|
6103
|
-
if (!tables.length) console.log(
|
|
7382
|
+
if (!tables.length) console.log(import_chalk40.default.dim(" No mapping tables yet."));
|
|
6104
7383
|
for (const t of tables) {
|
|
6105
|
-
console.log(` ${
|
|
7384
|
+
console.log(` ${import_chalk40.default.bold(t.name)} ${import_chalk40.default.dim(`${t.entries?.length ?? "?"} entries${t.hide_map_values ? ", hidden" : ""}${t.encrypt_values ? ", encrypted" : ""}`)}`);
|
|
6106
7385
|
}
|
|
6107
7386
|
const { act } = await inquirer3.prompt([{
|
|
6108
7387
|
type: "list",
|
|
@@ -6122,11 +7401,11 @@ async function mappingsMenu(proj2) {
|
|
|
6122
7401
|
]);
|
|
6123
7402
|
const entries2 = parseValue(a.entries);
|
|
6124
7403
|
if (!Array.isArray(entries2)) {
|
|
6125
|
-
console.log(
|
|
7404
|
+
console.log(import_chalk40.default.yellow(" Entries must be a JSON array \u2014 not created."));
|
|
6126
7405
|
continue;
|
|
6127
7406
|
}
|
|
6128
7407
|
await admin({ method: "POST", path: base2, body: { name: a.name, entries: entries2 }, summary: `Create mapping table "${a.name}"` });
|
|
6129
|
-
console.log(
|
|
7408
|
+
console.log(import_chalk40.default.green(` Table "${a.name}" created.`));
|
|
6130
7409
|
} else {
|
|
6131
7410
|
const { table } = await inquirer3.prompt([{
|
|
6132
7411
|
type: "list",
|
|
@@ -6136,7 +7415,7 @@ async function mappingsMenu(proj2) {
|
|
|
6136
7415
|
}]);
|
|
6137
7416
|
if (!table) continue;
|
|
6138
7417
|
await admin({ method: "DELETE", path: `${base2}/${table.id}`, summary: `Delete mapping table "${table.name}"` });
|
|
6139
|
-
console.log(
|
|
7418
|
+
console.log(import_chalk40.default.green(` ${table.name} deleted.`));
|
|
6140
7419
|
}
|
|
6141
7420
|
}
|
|
6142
7421
|
}
|
|
@@ -6147,14 +7426,14 @@ async function tenantsMenu(proj2, opts) {
|
|
|
6147
7426
|
const out = await admin({ method: "GET", path: base2, summary: "List attached tenants" });
|
|
6148
7427
|
const tenants = out?.tenants ?? [];
|
|
6149
7428
|
console.log();
|
|
6150
|
-
if (!tenants.length) console.log(
|
|
6151
|
-
for (const t of tenants) console.log(` ${
|
|
7429
|
+
if (!tenants.length) console.log(import_chalk40.default.dim(" No tenants attached (consumers use the default tenant)."));
|
|
7430
|
+
for (const t of tenants) console.log(` ${import_chalk40.default.bold(t.tenant_name ?? t.name)} ${import_chalk40.default.dim(t.display_name ?? "")}`);
|
|
6152
7431
|
const { act } = await inquirer3.prompt([{
|
|
6153
7432
|
type: "list",
|
|
6154
7433
|
name: "act",
|
|
6155
7434
|
message: "Tenants:",
|
|
6156
7435
|
choices: [
|
|
6157
|
-
{ name: `Manage a tenant\u2026 ${
|
|
7436
|
+
{ name: `Manage a tenant\u2026 ${import_chalk40.default.dim("settings, login app clients, providers, issuers \u2014 affects EVERY proxy the tenant serves")}`, value: "manage" },
|
|
6158
7437
|
{ name: "Attach a tenant to this project", value: "attach" },
|
|
6159
7438
|
...tenants.length ? [{ name: "Detach a tenant from this project", value: "detach" }] : [],
|
|
6160
7439
|
{ name: "\u2190 Back", value: "back" }
|
|
@@ -6177,7 +7456,7 @@ async function tenantsMenu(proj2, opts) {
|
|
|
6177
7456
|
}]);
|
|
6178
7457
|
if (!t) continue;
|
|
6179
7458
|
await admin({ method: "DELETE", path: `${base2}/${encodeURIComponent(t.tenant_name ?? t.name)}`, summary: `Detach tenant ${t.tenant_name ?? t.name}` });
|
|
6180
|
-
console.log(
|
|
7459
|
+
console.log(import_chalk40.default.green(` Detached ${t.tenant_name ?? t.name}.`));
|
|
6181
7460
|
}
|
|
6182
7461
|
}
|
|
6183
7462
|
}
|
|
@@ -6220,7 +7499,7 @@ async function specMenu(proj2, opts) {
|
|
|
6220
7499
|
choices: [
|
|
6221
7500
|
{ name: "Print the stored spec", value: "get" },
|
|
6222
7501
|
{ name: "Refresh the spec from its source", value: "refresh" },
|
|
6223
|
-
{ name:
|
|
7502
|
+
{ name: import_chalk40.default.dim("Build the spec by chatting over real traffic \u2192 agent"), value: "agent" },
|
|
6224
7503
|
{ name: "\u2190 Back", value: "back" }
|
|
6225
7504
|
]
|
|
6226
7505
|
}]);
|
|
@@ -6228,7 +7507,7 @@ async function specMenu(proj2, opts) {
|
|
|
6228
7507
|
if (act === "get") await runSpecGet(proj2.projectName, { team: opts.team, apiversion: proj2.apiVersion });
|
|
6229
7508
|
else if (act === "refresh") {
|
|
6230
7509
|
await admin({ method: "POST", path: `/projects/${proj2.projectId}/${proj2.apiVersion}/refresh-spec`, summary: "Refresh spec from source" });
|
|
6231
|
-
console.log(
|
|
7510
|
+
console.log(import_chalk40.default.green(" Spec refresh triggered."));
|
|
6232
7511
|
} else await runOpenapi(proj2.projectName, proj2.apiVersion);
|
|
6233
7512
|
}
|
|
6234
7513
|
async function agentsMenu(proj2, opts) {
|
|
@@ -6253,8 +7532,8 @@ async function agentsMenu(proj2, opts) {
|
|
|
6253
7532
|
}
|
|
6254
7533
|
|
|
6255
7534
|
// src/commands/group.ts
|
|
6256
|
-
var
|
|
6257
|
-
var
|
|
7535
|
+
var import_chalk41 = __toESM(require("chalk"));
|
|
7536
|
+
var import_ora19 = __toESM(require("ora"));
|
|
6258
7537
|
init_auth();
|
|
6259
7538
|
init_admin();
|
|
6260
7539
|
init_resolve();
|
|
@@ -6347,7 +7626,7 @@ async function userByRefOrCreate(t, ref) {
|
|
|
6347
7626
|
if (!ref.includes("@")) throw e;
|
|
6348
7627
|
const created = await t.call("/v1/admin/users", "POST", { email: ref.trim().toLowerCase() });
|
|
6349
7628
|
if (!created?.abz_sub) throw e;
|
|
6350
|
-
console.log(
|
|
7629
|
+
console.log(import_chalk41.default.dim(` ${ref} wasn't a user yet \u2014 created.`));
|
|
6351
7630
|
return { abz_sub: created.abz_sub, email: created.email ?? ref, display_name: null };
|
|
6352
7631
|
}
|
|
6353
7632
|
}
|
|
@@ -6355,11 +7634,11 @@ async function runGroupList(opts) {
|
|
|
6355
7634
|
const t = await resolveTransport(opts);
|
|
6356
7635
|
const groups = await listGroups(t);
|
|
6357
7636
|
if (opts.json) return void console.log(JSON.stringify(groups));
|
|
6358
|
-
if (!groups.length) return void console.log(
|
|
6359
|
-
console.log(
|
|
7637
|
+
if (!groups.length) return void console.log(import_chalk41.default.yellow(`No groups in ${t.tenant}. Create one: apiblaze group create <name>`));
|
|
7638
|
+
console.log(import_chalk41.default.dim(`Tenant ${t.tenant}:`));
|
|
6360
7639
|
for (const g of groups) {
|
|
6361
|
-
const sealed = g.name === "apiblaze_admins" ?
|
|
6362
|
-
console.log(` ${
|
|
7640
|
+
const sealed = g.name === "apiblaze_admins" ? import_chalk41.default.dim(" (admins \u2014 managed by allowlist)") : "";
|
|
7641
|
+
console.log(` ${import_chalk41.default.bold(g.name)} ${import_chalk41.default.dim(`${g.member_count ?? 0} member(s)`)}${sealed}`);
|
|
6363
7642
|
}
|
|
6364
7643
|
}
|
|
6365
7644
|
async function runGroupCreate(name, opts) {
|
|
@@ -6384,10 +7663,10 @@ async function runGroupCreate(name, opts) {
|
|
|
6384
7663
|
}
|
|
6385
7664
|
}
|
|
6386
7665
|
}
|
|
6387
|
-
const spinner = (0,
|
|
7666
|
+
const spinner = (0, import_ora19.default)(`Creating group ${name}...`).start();
|
|
6388
7667
|
try {
|
|
6389
7668
|
await t.call("/v1/admin/groups", "POST", { name, admins: [adminSub] });
|
|
6390
|
-
spinner.succeed(`Group ${
|
|
7669
|
+
spinner.succeed(`Group ${import_chalk41.default.bold(name)} created in ${t.tenant}.`);
|
|
6391
7670
|
} catch (e) {
|
|
6392
7671
|
spinner.fail("Create failed.");
|
|
6393
7672
|
throw e;
|
|
@@ -6396,10 +7675,10 @@ async function runGroupCreate(name, opts) {
|
|
|
6396
7675
|
async function runGroupDelete(name, opts) {
|
|
6397
7676
|
const t = await resolveTransport(opts);
|
|
6398
7677
|
const g = await groupByName(t, name);
|
|
6399
|
-
const spinner = (0,
|
|
7678
|
+
const spinner = (0, import_ora19.default)(`Deleting group ${name}...`).start();
|
|
6400
7679
|
try {
|
|
6401
7680
|
await t.call(`/v1/admin/groups/${encodeURIComponent(g.id)}`, "DELETE");
|
|
6402
|
-
spinner.succeed(`Group ${
|
|
7681
|
+
spinner.succeed(`Group ${import_chalk41.default.bold(name)} deleted.`);
|
|
6403
7682
|
} catch (e) {
|
|
6404
7683
|
spinner.fail("Delete failed.");
|
|
6405
7684
|
throw e;
|
|
@@ -6412,21 +7691,21 @@ async function runGroupMembers(name, opts) {
|
|
|
6412
7691
|
const subs = await t.call(`/v1/admin/groups/${encodeURIComponent(g.id)}/subgroups`);
|
|
6413
7692
|
if (opts.json) return void console.log(JSON.stringify({ ...detail, subgroups: subs?.subgroups ?? [] }));
|
|
6414
7693
|
const members = detail?.members ?? [];
|
|
6415
|
-
console.log(
|
|
6416
|
-
for (const m of members) console.log(` ${label2(m)} ${m.role === "admin" ?
|
|
6417
|
-
if (!members.length) console.log(
|
|
7694
|
+
console.log(import_chalk41.default.bold(`${g.name}`) + import_chalk41.default.dim(` (${t.tenant})`));
|
|
7695
|
+
for (const m of members) console.log(` ${label2(m)} ${m.role === "admin" ? import_chalk41.default.cyan("group-admin") : import_chalk41.default.dim("member")}`);
|
|
7696
|
+
if (!members.length) console.log(import_chalk41.default.dim(" no members"));
|
|
6418
7697
|
const sg = subs?.subgroups ?? [];
|
|
6419
|
-
if (sg.length) console.log(
|
|
7698
|
+
if (sg.length) console.log(import_chalk41.default.dim(` subgroups: ${sg.map((s) => s.name).join(", ")}`));
|
|
6420
7699
|
}
|
|
6421
7700
|
async function runGroupAddUser(userRef, groupName, opts) {
|
|
6422
7701
|
const t = await resolveTransport(opts);
|
|
6423
7702
|
const g = await groupByName(t, groupName);
|
|
6424
7703
|
const u = await userByRefOrCreate(t, userRef);
|
|
6425
7704
|
const role = opts.role === "admin" ? "admin" : "member";
|
|
6426
|
-
const spinner = (0,
|
|
7705
|
+
const spinner = (0, import_ora19.default)(`Adding ${label2(u)} to ${g.name}...`).start();
|
|
6427
7706
|
try {
|
|
6428
7707
|
await t.call(`/v1/admin/groups/${encodeURIComponent(g.id)}/members`, "POST", { abz_sub: u.abz_sub, role });
|
|
6429
|
-
spinner.succeed(`${
|
|
7708
|
+
spinner.succeed(`${import_chalk41.default.bold(label2(u))} added to ${import_chalk41.default.bold(g.name)} as ${role}.`);
|
|
6430
7709
|
} catch (e) {
|
|
6431
7710
|
spinner.fail("Add failed.");
|
|
6432
7711
|
throw e;
|
|
@@ -6435,10 +7714,10 @@ async function runGroupAddUser(userRef, groupName, opts) {
|
|
|
6435
7714
|
async function runGroupRemoveUser(userRef, groupName, opts) {
|
|
6436
7715
|
const t = await resolveTransport(opts);
|
|
6437
7716
|
const [g, u] = [await groupByName(t, groupName), await userByRef(t, userRef)];
|
|
6438
|
-
const spinner = (0,
|
|
7717
|
+
const spinner = (0, import_ora19.default)(`Removing ${label2(u)} from ${g.name}...`).start();
|
|
6439
7718
|
try {
|
|
6440
7719
|
await t.call(`/v1/admin/groups/${encodeURIComponent(g.id)}/members/_?abz_sub=${encodeURIComponent(u.abz_sub)}`, "DELETE");
|
|
6441
|
-
spinner.succeed(`${
|
|
7720
|
+
spinner.succeed(`${import_chalk41.default.bold(label2(u))} removed from ${import_chalk41.default.bold(g.name)}.`);
|
|
6442
7721
|
} catch (e) {
|
|
6443
7722
|
spinner.fail("Remove failed.");
|
|
6444
7723
|
throw e;
|
|
@@ -6447,10 +7726,10 @@ async function runGroupRemoveUser(userRef, groupName, opts) {
|
|
|
6447
7726
|
async function runGroupAddGroup(childName, parentName, opts) {
|
|
6448
7727
|
const t = await resolveTransport(opts);
|
|
6449
7728
|
const [child, parent] = [await groupByName(t, childName), await groupByName(t, parentName)];
|
|
6450
|
-
const spinner = (0,
|
|
7729
|
+
const spinner = (0, import_ora19.default)(`Nesting ${child.name} under ${parent.name}...`).start();
|
|
6451
7730
|
try {
|
|
6452
7731
|
await t.call(`/v1/admin/groups/${encodeURIComponent(parent.id)}/subgroups`, "POST", { child_group_id: child.id });
|
|
6453
|
-
spinner.succeed(`${
|
|
7732
|
+
spinner.succeed(`${import_chalk41.default.bold(child.name)} is now a subgroup of ${import_chalk41.default.bold(parent.name)}.`);
|
|
6454
7733
|
} catch (e) {
|
|
6455
7734
|
spinner.fail("Nest failed.");
|
|
6456
7735
|
throw e;
|
|
@@ -6459,10 +7738,10 @@ async function runGroupAddGroup(childName, parentName, opts) {
|
|
|
6459
7738
|
async function runGroupRemoveGroup(childName, parentName, opts) {
|
|
6460
7739
|
const t = await resolveTransport(opts);
|
|
6461
7740
|
const [child, parent] = [await groupByName(t, childName), await groupByName(t, parentName)];
|
|
6462
|
-
const spinner = (0,
|
|
7741
|
+
const spinner = (0, import_ora19.default)(`Un-nesting ${child.name} from ${parent.name}...`).start();
|
|
6463
7742
|
try {
|
|
6464
7743
|
await t.call(`/v1/admin/groups/${encodeURIComponent(parent.id)}/subgroups/${encodeURIComponent(child.id)}`, "DELETE");
|
|
6465
|
-
spinner.succeed(`${
|
|
7744
|
+
spinner.succeed(`${import_chalk41.default.bold(child.name)} un-nested from ${import_chalk41.default.bold(parent.name)}.`);
|
|
6466
7745
|
} catch (e) {
|
|
6467
7746
|
spinner.fail("Un-nest failed.");
|
|
6468
7747
|
throw e;
|
|
@@ -6473,19 +7752,19 @@ async function runGroupUsers(opts) {
|
|
|
6473
7752
|
const users = await listUsers(t);
|
|
6474
7753
|
const observed = await t.call("/v1/admin/observed-identities").catch(() => ({ identities: [] }));
|
|
6475
7754
|
if (opts.json) return void console.log(JSON.stringify({ users, observed: observed?.identities ?? [] }));
|
|
6476
|
-
console.log(
|
|
6477
|
-
for (const u of users) console.log(` ${label2(u)} ${
|
|
6478
|
-
if (!users.length) console.log(
|
|
7755
|
+
console.log(import_chalk41.default.dim(`Tenant ${t.tenant} \u2014 user directory:`));
|
|
7756
|
+
for (const u of users) console.log(` ${label2(u)} ${import_chalk41.default.dim(u.abz_sub)}`);
|
|
7757
|
+
if (!users.length) console.log(import_chalk41.default.dim(" no users yet"));
|
|
6479
7758
|
const fresh = (observed?.identities ?? []).filter((o) => !o.provisioned);
|
|
6480
7759
|
if (fresh.length) {
|
|
6481
|
-
console.log(
|
|
6482
|
-
for (const o of fresh) console.log(` ${o.email || o.abz_sub} ${
|
|
7760
|
+
console.log(import_chalk41.default.dim("Seen in traffic (not yet users):"));
|
|
7761
|
+
for (const o of fresh) console.log(` ${o.email || o.abz_sub} ${import_chalk41.default.dim(o.source_label ?? "")}`);
|
|
6483
7762
|
}
|
|
6484
7763
|
}
|
|
6485
7764
|
|
|
6486
7765
|
// src/commands/key.ts
|
|
6487
|
-
var
|
|
6488
|
-
var
|
|
7766
|
+
var import_chalk42 = __toESM(require("chalk"));
|
|
7767
|
+
var import_ora20 = __toESM(require("ora"));
|
|
6489
7768
|
init_caller();
|
|
6490
7769
|
async function runApikeysMenu(opts) {
|
|
6491
7770
|
await runKeyList(opts);
|
|
@@ -6514,11 +7793,11 @@ async function runKeyList(opts) {
|
|
|
6514
7793
|
return;
|
|
6515
7794
|
}
|
|
6516
7795
|
if (!keys.length) {
|
|
6517
|
-
console.log(
|
|
7796
|
+
console.log(import_chalk42.default.yellow("No developer keys."));
|
|
6518
7797
|
return;
|
|
6519
7798
|
}
|
|
6520
7799
|
for (const k of keys) {
|
|
6521
|
-
console.log(` ${
|
|
7800
|
+
console.log(` ${import_chalk42.default.bold(k.key_id ?? k.id)} ${import_chalk42.default.dim(k.description ?? "")} ${import_chalk42.default.dim(k.expires_at ?? "no expiry")}`);
|
|
6522
7801
|
}
|
|
6523
7802
|
}
|
|
6524
7803
|
async function runKeyMint(opts) {
|
|
@@ -6527,7 +7806,7 @@ async function runKeyMint(opts) {
|
|
|
6527
7806
|
const body = { role: "consumer-admin" };
|
|
6528
7807
|
if (opts.desc) body.description = opts.desc;
|
|
6529
7808
|
if (opts.expiresDays) body.expires_in_seconds = Number(opts.expiresDays) * 24 * 60 * 60;
|
|
6530
|
-
const spinner = (0,
|
|
7809
|
+
const spinner = (0, import_ora20.default)("Minting key...").start();
|
|
6531
7810
|
try {
|
|
6532
7811
|
const out = await producer(caller, {
|
|
6533
7812
|
method: "POST",
|
|
@@ -6540,9 +7819,9 @@ async function runKeyMint(opts) {
|
|
|
6540
7819
|
console.log(JSON.stringify(out));
|
|
6541
7820
|
return;
|
|
6542
7821
|
}
|
|
6543
|
-
console.log(` ${
|
|
6544
|
-
console.log(` ${
|
|
6545
|
-
if (out?.expires_at) console.log(` ${
|
|
7822
|
+
console.log(` ${import_chalk42.default.bold("key_id")}: ${out?.key_id}`);
|
|
7823
|
+
console.log(` ${import_chalk42.default.bold("key")}: ${import_chalk42.default.green(out?.key)} ${import_chalk42.default.dim("(shown once \u2014 store it now)")}`);
|
|
7824
|
+
if (out?.expires_at) console.log(` ${import_chalk42.default.dim("expires:")} ${out.expires_at}`);
|
|
6546
7825
|
} catch (err) {
|
|
6547
7826
|
spinner.fail("Mint failed.");
|
|
6548
7827
|
throw err;
|
|
@@ -6551,7 +7830,7 @@ async function runKeyMint(opts) {
|
|
|
6551
7830
|
async function runKeyRevoke(keyId, opts) {
|
|
6552
7831
|
const caller = requireCaller();
|
|
6553
7832
|
const { teamId } = await resolveActingTeam(caller, opts.team);
|
|
6554
|
-
const spinner = (0,
|
|
7833
|
+
const spinner = (0, import_ora20.default)("Revoking key...").start();
|
|
6555
7834
|
try {
|
|
6556
7835
|
await producer(caller, {
|
|
6557
7836
|
method: "DELETE",
|
|
@@ -6569,8 +7848,8 @@ async function runKeyRevoke(keyId, opts) {
|
|
|
6569
7848
|
init_iam();
|
|
6570
7849
|
|
|
6571
7850
|
// src/commands/admins.ts
|
|
6572
|
-
var
|
|
6573
|
-
var
|
|
7851
|
+
var import_chalk43 = __toESM(require("chalk"));
|
|
7852
|
+
var import_ora21 = __toESM(require("ora"));
|
|
6574
7853
|
init_caller();
|
|
6575
7854
|
function requireTenant(opts) {
|
|
6576
7855
|
const t = (opts.tenant ?? "").trim();
|
|
@@ -6597,20 +7876,20 @@ async function runAdminsList(opts) {
|
|
|
6597
7876
|
return;
|
|
6598
7877
|
}
|
|
6599
7878
|
if (!admins2.length) {
|
|
6600
|
-
console.log(
|
|
7879
|
+
console.log(import_chalk43.default.yellow(`No tenant admins for ${tenant2} yet.`));
|
|
6601
7880
|
return;
|
|
6602
7881
|
}
|
|
6603
7882
|
for (const a of admins2) {
|
|
6604
7883
|
const email = typeof a === "string" ? a : a.email;
|
|
6605
7884
|
const flags = [a.active === false ? "pending" : null, a.pinned ? "pinned" : null].filter(Boolean).join(", ");
|
|
6606
|
-
console.log(` ${
|
|
7885
|
+
console.log(` ${import_chalk43.default.bold(email)}${flags ? import_chalk43.default.dim(` (${flags})`) : ""}`);
|
|
6607
7886
|
}
|
|
6608
7887
|
}
|
|
6609
7888
|
async function runAdminsAdd(email, opts) {
|
|
6610
7889
|
const caller = requireCaller();
|
|
6611
7890
|
const { teamId } = await resolveActingTeam(caller, opts.team);
|
|
6612
7891
|
const tenant2 = requireTenant(opts);
|
|
6613
|
-
const spinner = opts.json ? null : (0,
|
|
7892
|
+
const spinner = opts.json ? null : (0, import_ora21.default)(`Adding ${email} as a tenant admin of ${tenant2}...`).start();
|
|
6614
7893
|
try {
|
|
6615
7894
|
const out = await producer(caller, {
|
|
6616
7895
|
method: "POST",
|
|
@@ -6618,9 +7897,9 @@ async function runAdminsAdd(email, opts) {
|
|
|
6618
7897
|
body: { email },
|
|
6619
7898
|
summary: `Add ${email} to tenant ${tenant2} admins`
|
|
6620
7899
|
});
|
|
6621
|
-
spinner?.succeed(`${
|
|
7900
|
+
spinner?.succeed(`${import_chalk43.default.bold(email)} is now a tenant admin of ${import_chalk43.default.bold(tenant2)}.`);
|
|
6622
7901
|
if (opts.json) console.log(JSON.stringify(out ?? { ok: true }));
|
|
6623
|
-
else console.log(
|
|
7902
|
+
else console.log(import_chalk43.default.dim(" Reload the Users & Groups widget \u2014 access flips from \u201Cpending\u201D to ready."));
|
|
6624
7903
|
} catch (err) {
|
|
6625
7904
|
spinner?.fail("Add failed.");
|
|
6626
7905
|
throw err;
|
|
@@ -6630,14 +7909,14 @@ async function runAdminsRemove(email, opts) {
|
|
|
6630
7909
|
const caller = requireCaller();
|
|
6631
7910
|
const { teamId } = await resolveActingTeam(caller, opts.team);
|
|
6632
7911
|
const tenant2 = requireTenant(opts);
|
|
6633
|
-
const spinner = opts.json ? null : (0,
|
|
7912
|
+
const spinner = opts.json ? null : (0, import_ora21.default)(`Removing ${email} from the tenant admins of ${tenant2}...`).start();
|
|
6634
7913
|
try {
|
|
6635
7914
|
await producer(caller, {
|
|
6636
7915
|
method: "DELETE",
|
|
6637
7916
|
path: `${base(teamId, tenant2)}/${encodeURIComponent(email)}`,
|
|
6638
7917
|
summary: `Remove ${email} from tenant ${tenant2} admins`
|
|
6639
7918
|
});
|
|
6640
|
-
spinner?.succeed(`Removed ${
|
|
7919
|
+
spinner?.succeed(`Removed ${import_chalk43.default.bold(email)} from the tenant admins of ${import_chalk43.default.bold(tenant2)}.`);
|
|
6641
7920
|
} catch (err) {
|
|
6642
7921
|
spinner?.fail("Remove failed.");
|
|
6643
7922
|
throw err;
|
|
@@ -6645,8 +7924,8 @@ async function runAdminsRemove(email, opts) {
|
|
|
6645
7924
|
}
|
|
6646
7925
|
|
|
6647
7926
|
// src/commands/preapprove.ts
|
|
6648
|
-
var
|
|
6649
|
-
var
|
|
7927
|
+
var import_chalk44 = __toESM(require("chalk"));
|
|
7928
|
+
var import_ora22 = __toESM(require("ora"));
|
|
6650
7929
|
|
|
6651
7930
|
// src/lib/preapproval.ts
|
|
6652
7931
|
init_auth();
|
|
@@ -6710,14 +7989,14 @@ async function runPreapprove(who, opts) {
|
|
|
6710
7989
|
const rules = await listPreapprovalRules(tenant2);
|
|
6711
7990
|
if (opts.json) return void console.log(JSON.stringify(rules, null, 2));
|
|
6712
7991
|
if (!rules.length) {
|
|
6713
|
-
console.log(
|
|
6714
|
-
console.log(
|
|
7992
|
+
console.log(import_chalk44.default.dim(`No pre-approval rules in ${tenant2}. Anyone who signs in can access (unless access is restricted).`));
|
|
7993
|
+
console.log(import_chalk44.default.dim(`Add one: apiblaze preapprove someone@acme.com`));
|
|
6715
7994
|
return;
|
|
6716
7995
|
}
|
|
6717
|
-
console.log(
|
|
7996
|
+
console.log(import_chalk44.default.dim(`Pre-approved for ${import_chalk44.default.bold(tenant2)}:`));
|
|
6718
7997
|
for (const r of rules) {
|
|
6719
|
-
const tag = r.kind === "domain" ?
|
|
6720
|
-
const grp = r.groups?.length ?
|
|
7998
|
+
const tag = r.kind === "domain" ? import_chalk44.default.cyan("@" + r.value + " (whole domain)") : r.value;
|
|
7999
|
+
const grp = r.groups?.length ? import_chalk44.default.dim(` \u2192 groups: ${r.groups.join(", ")}`) : "";
|
|
6721
8000
|
console.log(` ${tag}${grp}`);
|
|
6722
8001
|
}
|
|
6723
8002
|
return;
|
|
@@ -6726,19 +8005,19 @@ async function runPreapprove(who, opts) {
|
|
|
6726
8005
|
throw new Error("Who? Pass an email or a domain: `apiblaze preapprove someone@acme.com` (or `apiblaze preapprove --list`).");
|
|
6727
8006
|
}
|
|
6728
8007
|
if (opts.remove) {
|
|
6729
|
-
const spinner2 = (0,
|
|
8008
|
+
const spinner2 = (0, import_ora22.default)(`Removing ${who} from ${tenant2}\u2026`).start();
|
|
6730
8009
|
const { removed, value } = await removePreapprovalRule(tenant2, who);
|
|
6731
|
-
if (removed) spinner2.succeed(`${
|
|
6732
|
-
else spinner2.warn(`No pre-approval rule for ${
|
|
8010
|
+
if (removed) spinner2.succeed(`${import_chalk44.default.bold(value)} is no longer pre-approved for ${tenant2}.`);
|
|
8011
|
+
else spinner2.warn(`No pre-approval rule for ${import_chalk44.default.bold(value)} in ${tenant2} \u2014 nothing to remove.`);
|
|
6733
8012
|
return;
|
|
6734
8013
|
}
|
|
6735
|
-
const spinner = (0,
|
|
8014
|
+
const spinner = (0, import_ora22.default)(`Pre-approving ${who} for ${tenant2}\u2026`).start();
|
|
6736
8015
|
try {
|
|
6737
8016
|
const { kind, value } = await addPreapprovalRule(tenant2, who, opts.group);
|
|
6738
8017
|
const what = kind === "domain" ? `Anyone @${value}` : value;
|
|
6739
|
-
spinner.succeed(`${
|
|
6740
|
-
if (opts.group?.length) console.log(
|
|
6741
|
-
console.log(
|
|
8018
|
+
spinner.succeed(`${import_chalk44.default.bold(what)} can now sign in to ${import_chalk44.default.bold(tenant2)}.`);
|
|
8019
|
+
if (opts.group?.length) console.log(import_chalk44.default.dim(` They'll join group(s) ${opts.group.join(", ")} on first sign-in.`));
|
|
8020
|
+
console.log(import_chalk44.default.dim(` See the full list: apiblaze preapprove --list`));
|
|
6742
8021
|
} catch (err) {
|
|
6743
8022
|
spinner.fail("Could not add the rule.");
|
|
6744
8023
|
throw err;
|
|
@@ -6749,8 +8028,8 @@ async function runPreapprove(who, opts) {
|
|
|
6749
8028
|
var fs9 = __toESM(require("fs"));
|
|
6750
8029
|
var path6 = __toESM(require("path"));
|
|
6751
8030
|
var crypto2 = __toESM(require("crypto"));
|
|
6752
|
-
var
|
|
6753
|
-
var
|
|
8031
|
+
var import_chalk46 = __toESM(require("chalk"));
|
|
8032
|
+
var import_ora23 = __toESM(require("ora"));
|
|
6754
8033
|
var import_yaml2 = require("yaml");
|
|
6755
8034
|
init_auth();
|
|
6756
8035
|
init_anon_cred();
|
|
@@ -6760,7 +8039,7 @@ init_admin();
|
|
|
6760
8039
|
// src/commands/llm.ts
|
|
6761
8040
|
var fs8 = __toESM(require("fs"));
|
|
6762
8041
|
var path5 = __toESM(require("path"));
|
|
6763
|
-
var
|
|
8042
|
+
var import_chalk45 = __toESM(require("chalk"));
|
|
6764
8043
|
var import_inquirer2 = __toESM(require("inquirer"));
|
|
6765
8044
|
init_auth();
|
|
6766
8045
|
var LLM_PATH = path5.join(getApiblazeDir(), "llm.json");
|
|
@@ -6804,27 +8083,27 @@ async function runLlmSetKey(keyArg, opts) {
|
|
|
6804
8083
|
}
|
|
6805
8084
|
const existing = loadLlmConfig();
|
|
6806
8085
|
saveLlmConfig({ key, provider, model: opts.model ?? existing?.model });
|
|
6807
|
-
console.log(`${
|
|
6808
|
-
console.log(
|
|
6809
|
-
if (opts.model) console.log(
|
|
8086
|
+
console.log(`${import_chalk45.default.green("\u2713")} Saved ${import_chalk45.default.bold(provider)} key ${maskSecret(key)}`);
|
|
8087
|
+
console.log(import_chalk45.default.gray(` Stored locally at ${LLM_PATH} (0600) \u2014 never sent to APIblaze except per chat turn.`));
|
|
8088
|
+
if (opts.model) console.log(import_chalk45.default.gray(` Model: ${opts.model}`));
|
|
6810
8089
|
}
|
|
6811
8090
|
async function runLlmShow() {
|
|
6812
8091
|
const cfg = loadLlmConfig();
|
|
6813
8092
|
if (!cfg) {
|
|
6814
|
-
console.log(
|
|
8093
|
+
console.log(import_chalk45.default.gray("No LLM key set. `apiblaze llm set-key sk-...` to add one (optional \u2014 chat works without it)."));
|
|
6815
8094
|
return;
|
|
6816
8095
|
}
|
|
6817
|
-
console.log(`Provider: ${
|
|
8096
|
+
console.log(`Provider: ${import_chalk45.default.bold(cfg.provider)}`);
|
|
6818
8097
|
console.log(`Key: ${maskSecret(cfg.key)}`);
|
|
6819
8098
|
if (cfg.model) console.log(`Model: ${cfg.model}`);
|
|
6820
|
-
console.log(
|
|
8099
|
+
console.log(import_chalk45.default.gray(`Stored at ${LLM_PATH}`));
|
|
6821
8100
|
}
|
|
6822
8101
|
async function runLlmClearKey() {
|
|
6823
8102
|
try {
|
|
6824
8103
|
fs8.unlinkSync(LLM_PATH);
|
|
6825
|
-
console.log(`${
|
|
8104
|
+
console.log(`${import_chalk45.default.green("\u2713")} Removed local LLM key.`);
|
|
6826
8105
|
} catch {
|
|
6827
|
-
console.log(
|
|
8106
|
+
console.log(import_chalk45.default.gray("No LLM key was set."));
|
|
6828
8107
|
}
|
|
6829
8108
|
}
|
|
6830
8109
|
|
|
@@ -6832,12 +8111,12 @@ async function runLlmClearKey() {
|
|
|
6832
8111
|
init_trace();
|
|
6833
8112
|
init_types();
|
|
6834
8113
|
function fail4(message, hint) {
|
|
6835
|
-
console.error(
|
|
8114
|
+
console.error(import_chalk46.default.red(`
|
|
6836
8115
|
Error: ${message}`));
|
|
6837
|
-
if (hint) console.error(
|
|
8116
|
+
if (hint) console.error(import_chalk46.default.dim(hint));
|
|
6838
8117
|
process.exit(1);
|
|
6839
8118
|
}
|
|
6840
|
-
function
|
|
8119
|
+
function normalizeName3(raw) {
|
|
6841
8120
|
return (raw || "").toLowerCase().replace(/[^a-z0-9]/g, "");
|
|
6842
8121
|
}
|
|
6843
8122
|
function isHttpUrl2(s) {
|
|
@@ -6982,7 +8261,7 @@ async function resolveTargetAuth(spec2, opts) {
|
|
|
6982
8261
|
"Re-run with --force to provision anyway (configure target auth later with `apiblaze config`),\nor use an api_key / bearer / basic scheme."
|
|
6983
8262
|
);
|
|
6984
8263
|
}
|
|
6985
|
-
if (sawOAuth) console.log(
|
|
8264
|
+
if (sawOAuth) console.log(import_chalk46.default.yellow(" --force: skipping OAuth target auth \u2014 configure it later with `apiblaze config`."));
|
|
6986
8265
|
return null;
|
|
6987
8266
|
}
|
|
6988
8267
|
if (candidates.length === 1 && !noneAllowed) return candidates[0];
|
|
@@ -7055,11 +8334,11 @@ async function ensureConsumerLogin(teamId, tenant2, version2) {
|
|
|
7055
8334
|
const fresh = await validConsumerToken(existing);
|
|
7056
8335
|
if (fresh) {
|
|
7057
8336
|
if (fresh.accessToken !== existing.accessToken) saveConsumer({ ...fresh, resource });
|
|
7058
|
-
console.log(
|
|
8337
|
+
console.log(import_chalk46.default.dim(` Using your consumer session on ${import_chalk46.default.bold(tenant2)}${fresh.email ? ` (${fresh.email})` : ""}.`));
|
|
7059
8338
|
return fresh;
|
|
7060
8339
|
}
|
|
7061
8340
|
}
|
|
7062
|
-
const spinner = (0,
|
|
8341
|
+
const spinner = (0, import_ora23.default)(`Finding the login app for ${tenant2}...`).start();
|
|
7063
8342
|
const clients = await admin({
|
|
7064
8343
|
method: "GET",
|
|
7065
8344
|
path: `/teams/${encodeURIComponent(teamId)}/tenants/${encodeURIComponent(tenant2)}/app-clients`,
|
|
@@ -7074,13 +8353,13 @@ async function ensureConsumerLogin(teamId, tenant2, version2) {
|
|
|
7074
8353
|
);
|
|
7075
8354
|
}
|
|
7076
8355
|
const clientId = pick2.client_id ?? pick2.clientId;
|
|
7077
|
-
console.log(`${
|
|
8356
|
+
console.log(`${import_chalk46.default.cyan("\u2192")} This proxy signs consumers in with OAuth \u2014 logging you in to ${import_chalk46.default.bold(tenant2)}...`);
|
|
7078
8357
|
const result = await deviceLogin(clientId, "openid email profile offline_access", ({ verificationUri, userCode }) => {
|
|
7079
8358
|
console.log(`
|
|
7080
|
-
Open: ${
|
|
7081
|
-
console.log(` Code: ${
|
|
8359
|
+
Open: ${import_chalk46.default.underline(verificationUri)}`);
|
|
8360
|
+
console.log(` Code: ${import_chalk46.default.bold(userCode)}
|
|
7082
8361
|
`);
|
|
7083
|
-
console.log(
|
|
8362
|
+
console.log(import_chalk46.default.dim(" (opening your browser\u2026 waiting for you to finish)"));
|
|
7084
8363
|
}, resource);
|
|
7085
8364
|
const claims = result.idToken && decodeJwt2(result.idToken) || (decodeJwt2(result.accessToken) ?? {});
|
|
7086
8365
|
const creds = {
|
|
@@ -7096,7 +8375,7 @@ async function ensureConsumerLogin(teamId, tenant2, version2) {
|
|
|
7096
8375
|
resource
|
|
7097
8376
|
};
|
|
7098
8377
|
saveConsumer(creds);
|
|
7099
|
-
console.log(` ${
|
|
8378
|
+
console.log(` ${import_chalk46.default.green("\u2714")} Signed in as${creds.email ? ` ${import_chalk46.default.bold(creds.email)}` : " a consumer"} on ${tenant2}.`);
|
|
7100
8379
|
return creds;
|
|
7101
8380
|
}
|
|
7102
8381
|
async function cpPost(anon, path8, body, summary) {
|
|
@@ -7111,21 +8390,21 @@ async function provision(spec2, target, opts) {
|
|
|
7111
8390
|
const loggedIn = !!loadCredentials();
|
|
7112
8391
|
const anon = !loggedIn;
|
|
7113
8392
|
const salt = () => Math.random().toString(36).slice(2, 6);
|
|
7114
|
-
let base2 = opts.name ?
|
|
8393
|
+
let base2 = opts.name ? normalizeName3(opts.name) : "";
|
|
7115
8394
|
if (!base2) {
|
|
7116
8395
|
try {
|
|
7117
8396
|
const host = new URL(target).hostname;
|
|
7118
|
-
base2 =
|
|
7119
|
-
if (base2.length < 3) base2 =
|
|
8397
|
+
base2 = normalizeName3(host.split(".")[0]);
|
|
8398
|
+
if (base2.length < 3) base2 = normalizeName3(host);
|
|
7120
8399
|
} catch {
|
|
7121
8400
|
}
|
|
7122
8401
|
}
|
|
7123
|
-
if (!base2 && spec2.info && typeof spec2.info.title === "string") base2 =
|
|
8402
|
+
if (!base2 && spec2.info && typeof spec2.info.title === "string") base2 = normalizeName3(spec2.info.title);
|
|
7124
8403
|
if (!base2 || base2.length < 3) base2 = "apichat";
|
|
7125
8404
|
let name = opts.name ? base2 : `${base2}${salt()}`;
|
|
7126
8405
|
const access = anon ? "open" : opts.access === "open" ? "open" : "invite";
|
|
7127
8406
|
if (anon && opts.access === "invite") {
|
|
7128
|
-
console.log(
|
|
8407
|
+
console.log(import_chalk46.default.dim(" Note: --access invite needs an account to pre-approve people. Staying open for this anonymous proxy \u2014 run `apiblaze login`, then `apiblaze apichat --access invite`."));
|
|
7129
8408
|
}
|
|
7130
8409
|
const DUAL_AUTH = {
|
|
7131
8410
|
mode: "authenticate",
|
|
@@ -7137,7 +8416,7 @@ async function provision(spec2, target, opts) {
|
|
|
7137
8416
|
yes: opts.yes,
|
|
7138
8417
|
json: opts.json
|
|
7139
8418
|
}) : void 0;
|
|
7140
|
-
const spinner = (0,
|
|
8419
|
+
const spinner = (0, import_ora23.default)("Provisioning an api_key proxy...").start();
|
|
7141
8420
|
let result;
|
|
7142
8421
|
for (let attempt = 0; attempt < 4; attempt++) {
|
|
7143
8422
|
try {
|
|
@@ -7224,14 +8503,14 @@ async function provision(spec2, target, opts) {
|
|
|
7224
8503
|
try {
|
|
7225
8504
|
await addPreapprovalRule(tenant2, email);
|
|
7226
8505
|
} catch {
|
|
7227
|
-
console.log(
|
|
8506
|
+
console.log(import_chalk46.default.dim(` (Could not auto-approve your email for sign-in \u2014 add it later: apiblaze preapprove ${email})`));
|
|
7228
8507
|
}
|
|
7229
8508
|
}
|
|
7230
8509
|
}
|
|
7231
8510
|
return { projectId, version: version2, environment, dpKey, mcpHost, proxyUrl, anon, access, tenant: tenant2 };
|
|
7232
8511
|
}
|
|
7233
8512
|
async function writeTargetAuth(p, auth, secret) {
|
|
7234
|
-
const spinner = (0,
|
|
8513
|
+
const spinner = (0, import_ora23.default)("Storing target credentials (encrypted)...").start();
|
|
7235
8514
|
try {
|
|
7236
8515
|
await cpPost(
|
|
7237
8516
|
p.anon,
|
|
@@ -7253,7 +8532,7 @@ async function writeTargetAuth(p, auth, secret) {
|
|
|
7253
8532
|
}
|
|
7254
8533
|
}
|
|
7255
8534
|
async function uploadSpec(p, specText, opts) {
|
|
7256
|
-
const spinner = (0,
|
|
8535
|
+
const spinner = (0, import_ora23.default)("Uploading the spec...").start();
|
|
7257
8536
|
let out;
|
|
7258
8537
|
try {
|
|
7259
8538
|
out = await cpPost(
|
|
@@ -7268,7 +8547,7 @@ async function uploadSpec(p, specText, opts) {
|
|
|
7268
8547
|
throw err;
|
|
7269
8548
|
}
|
|
7270
8549
|
if (out && out.reused === true) {
|
|
7271
|
-
console.log(
|
|
8550
|
+
console.log(import_chalk46.default.dim(" Spec unchanged since the last provision \u2014 reusing the existing configuration."));
|
|
7272
8551
|
} else if (out && out.changed === true && out.previous_spec_hash) {
|
|
7273
8552
|
const interactive = !!process.stdin.isTTY && !opts.yes;
|
|
7274
8553
|
if (interactive) {
|
|
@@ -7276,12 +8555,12 @@ async function uploadSpec(p, specText, opts) {
|
|
|
7276
8555
|
const { go } = await inquirer3.prompt([
|
|
7277
8556
|
{ type: "confirm", name: "go", message: "The spec changed since the last provision \u2014 re-publish the MCP catalogue?", default: true }
|
|
7278
8557
|
]);
|
|
7279
|
-
if (!go) console.log(
|
|
8558
|
+
if (!go) console.log(import_chalk46.default.dim(" Keeping the existing MCP catalogue."));
|
|
7280
8559
|
}
|
|
7281
8560
|
}
|
|
7282
8561
|
}
|
|
7283
8562
|
async function publishMcp(p, spec2) {
|
|
7284
|
-
const spinner = (0,
|
|
8563
|
+
const spinner = (0, import_ora23.default)("Publishing the MCP catalogue...").start();
|
|
7285
8564
|
try {
|
|
7286
8565
|
const url = `https://${p.mcpHost}/${p.version}/${p.environment}/mcp/generate`;
|
|
7287
8566
|
const res = await fetch(url, {
|
|
@@ -7317,15 +8596,15 @@ var revealAuth = false;
|
|
|
7317
8596
|
function renderToolEvents(events, cred) {
|
|
7318
8597
|
for (const e of events ?? []) {
|
|
7319
8598
|
const ok = typeof e.status === "number" ? e.status < 400 : String(e.status).toLowerCase() === "ok";
|
|
7320
|
-
const mark = ok ?
|
|
7321
|
-
console.log(` ${mark} ${
|
|
8599
|
+
const mark = ok ? import_chalk46.default.green("\u2713") : import_chalk46.default.red("\u2717");
|
|
8600
|
+
console.log(` ${mark} ${import_chalk46.default.cyan(e.name)} ${import_chalk46.default.dim(`(${e.status}, ${e.ms}ms)`)}`);
|
|
7322
8601
|
if (isVerbose() && e.method && e.url) {
|
|
7323
|
-
console.log(
|
|
8602
|
+
console.log(import_chalk46.default.dim(` curl -sS -X ${e.method} '${e.url}'${cred ? " \\" : ""}`));
|
|
7324
8603
|
if (cred) {
|
|
7325
8604
|
const shown = revealAuth ? cred.value : maskKey(cred.value);
|
|
7326
8605
|
const keyLine = ` -H '${cred.header}: ${shown}'`;
|
|
7327
|
-
const hint = revealAuth ? "" :
|
|
7328
|
-
console.log(
|
|
8606
|
+
const hint = revealAuth ? "" : import_chalk46.default.yellow(" \u2190 /showauth will reveal this");
|
|
8607
|
+
console.log(import_chalk46.default.dim(keyLine) + hint);
|
|
7329
8608
|
}
|
|
7330
8609
|
}
|
|
7331
8610
|
}
|
|
@@ -7346,20 +8625,20 @@ function renderToolResults(delta, events) {
|
|
|
7346
8625
|
}
|
|
7347
8626
|
})();
|
|
7348
8627
|
const lines = pretty.split("\n");
|
|
7349
|
-
console.log(
|
|
8628
|
+
console.log(import_chalk46.default.dim(" response:"));
|
|
7350
8629
|
for (const line of lines.slice(0, cap)) {
|
|
7351
|
-
console.log(
|
|
8630
|
+
console.log(import_chalk46.default.dim(` ${line}`));
|
|
7352
8631
|
}
|
|
7353
|
-
if (lines.length > cap) console.log(
|
|
8632
|
+
if (lines.length > cap) console.log(import_chalk46.default.dim(` \u2026${lines.length - cap} more lines`));
|
|
7354
8633
|
}
|
|
7355
8634
|
}
|
|
7356
8635
|
function billingLine(billing) {
|
|
7357
8636
|
if (!billing || typeof billing.cents !== "number") return null;
|
|
7358
8637
|
if (typeof billing.free_turns_remaining === "number") return null;
|
|
7359
8638
|
const usd = (billing.cents / 100).toFixed(Math.abs(billing.cents - Math.round(billing.cents)) < 1e-9 ? 2 : 4);
|
|
7360
|
-
let line =
|
|
8639
|
+
let line = import_chalk46.default.magenta(` \u{1F4B3} $${usd}`) + import_chalk46.default.dim(billing.model ? ` \xB7 ${billing.model}` : "");
|
|
7361
8640
|
if (typeof billing.credits_remaining === "number") {
|
|
7362
|
-
line +=
|
|
8641
|
+
line += import_chalk46.default.dim(` \xB7 balance $${(billing.credits_remaining / 100).toFixed(2)}`);
|
|
7363
8642
|
}
|
|
7364
8643
|
return line;
|
|
7365
8644
|
}
|
|
@@ -7367,21 +8646,21 @@ function freeBudgetWarning(billing, anon) {
|
|
|
7367
8646
|
if (!anon || !billing) return null;
|
|
7368
8647
|
if (typeof billing.free_turns_remaining === "number") {
|
|
7369
8648
|
const left2 = billing.free_turns_remaining;
|
|
7370
|
-
if (left2 <= 0) return
|
|
7371
|
-
return
|
|
8649
|
+
if (left2 <= 0) return import_chalk46.default.yellow(" Free chats used up \u2014 `npx apiblaze login` (free) to keep going.");
|
|
8650
|
+
return import_chalk46.default.dim(` ${left2} free chat${left2 === 1 ? "" : "s"} left \xB7 /login to get more`);
|
|
7372
8651
|
}
|
|
7373
8652
|
if (typeof billing.free_remaining_cents !== "number") return null;
|
|
7374
8653
|
const perTurn = Math.max(billing.cents || 0, 0.02);
|
|
7375
8654
|
const left = Math.floor(billing.free_remaining_cents / perTurn);
|
|
7376
8655
|
if (left > 8) return null;
|
|
7377
|
-
if (left <= 0) return
|
|
7378
|
-
return
|
|
8656
|
+
if (left <= 0) return import_chalk46.default.yellow(" Free messages used up \u2014 `npx apiblaze login` (free) to keep chatting.");
|
|
8657
|
+
return import_chalk46.default.yellow(` \u26A0 About ${left} free message${left === 1 ? "" : "s"} left \u2014 \`npx apiblaze login\` (free) for more.`);
|
|
7379
8658
|
}
|
|
7380
8659
|
function printAssistant(delta) {
|
|
7381
8660
|
for (let i = delta.length - 1; i >= 0; i--) {
|
|
7382
8661
|
const m = delta[i];
|
|
7383
8662
|
if (m && m.role === "assistant" && typeof m.content === "string" && m.content.trim()) {
|
|
7384
|
-
console.log("\n" +
|
|
8663
|
+
console.log("\n" + import_chalk46.default.green("assistant \u203A ") + m.content + "\n");
|
|
7385
8664
|
return;
|
|
7386
8665
|
}
|
|
7387
8666
|
}
|
|
@@ -7391,7 +8670,7 @@ async function replTurn(p, messages, userText) {
|
|
|
7391
8670
|
const llm2 = loadLlmConfig();
|
|
7392
8671
|
const turnId = crypto2.randomUUID();
|
|
7393
8672
|
for (let round = 0; round < CLIENT_ROUND_CAP; round++) {
|
|
7394
|
-
const spinner = (0,
|
|
8673
|
+
const spinner = (0, import_ora23.default)({ text: round === 0 ? "thinking..." : "working...", color: "magenta" }).start();
|
|
7395
8674
|
const body = {
|
|
7396
8675
|
turn_id: turnId,
|
|
7397
8676
|
messages,
|
|
@@ -7407,7 +8686,7 @@ async function replTurn(p, messages, userText) {
|
|
|
7407
8686
|
});
|
|
7408
8687
|
} catch (err) {
|
|
7409
8688
|
spinner.fail("Network error.");
|
|
7410
|
-
console.log(
|
|
8689
|
+
console.log(import_chalk46.default.red(` Could not reach ${p.mcpHost}: ${err instanceof Error ? err.message : String(err)}`));
|
|
7411
8690
|
return;
|
|
7412
8691
|
}
|
|
7413
8692
|
let data = null;
|
|
@@ -7427,9 +8706,9 @@ async function replTurn(p, messages, userText) {
|
|
|
7427
8706
|
if (res.status === 401) {
|
|
7428
8707
|
const reason = data && (data.error || data.message) || null;
|
|
7429
8708
|
const usingKey = !p.consumerAuth;
|
|
7430
|
-
console.log(
|
|
8709
|
+
console.log(import_chalk46.default.red(` The proxy rejected the request (401)${reason ? `: ${reason}` : "."}`));
|
|
7431
8710
|
console.log(
|
|
7432
|
-
|
|
8711
|
+
import_chalk46.default.dim(
|
|
7433
8712
|
usingKey ? " Sent an API key. If the key was revoked, re-run `apiblaze apichat` to re-provision." : " Sent your consumer OAuth token. Re-run `apiblaze apichat` to sign in again."
|
|
7434
8713
|
)
|
|
7435
8714
|
);
|
|
@@ -7437,7 +8716,7 @@ async function replTurn(p, messages, userText) {
|
|
|
7437
8716
|
}
|
|
7438
8717
|
if (!res.ok || !data) {
|
|
7439
8718
|
const err = data && data.error || `HTTP ${res.status}`;
|
|
7440
|
-
console.log(
|
|
8719
|
+
console.log(import_chalk46.default.red(` Chat error: ${err}`));
|
|
7441
8720
|
return;
|
|
7442
8721
|
}
|
|
7443
8722
|
if (Array.isArray(data.delta)) {
|
|
@@ -7458,25 +8737,25 @@ async function replTurn(p, messages, userText) {
|
|
|
7458
8737
|
if (warn) console.log(warn);
|
|
7459
8738
|
if (!data.continue) return;
|
|
7460
8739
|
}
|
|
7461
|
-
console.log(
|
|
8740
|
+
console.log(import_chalk46.default.dim(" (stopped after several tool rounds \u2014 ask again to continue)"));
|
|
7462
8741
|
}
|
|
7463
8742
|
function renderUpsell(p, upsell) {
|
|
7464
8743
|
const loggedIn = !!loadCredentials();
|
|
7465
8744
|
if (upsell.reason === "CAPPED" && !loggedIn) {
|
|
7466
|
-
console.log("\n" +
|
|
7467
|
-
console.log(
|
|
8745
|
+
console.log("\n" + import_chalk46.default.yellow(" Type `npx apiblaze login` to claim the rest of your balance."));
|
|
8746
|
+
console.log(import_chalk46.default.dim(" (or `/login` right here \u2014 your chat is preserved \u2014 or `apiblaze llm set-key` for your own model key.)"));
|
|
7468
8747
|
console.log();
|
|
7469
8748
|
return;
|
|
7470
8749
|
}
|
|
7471
|
-
console.log("\n" +
|
|
8750
|
+
console.log("\n" + import_chalk46.default.yellow(` ${upsell.message || "This turn is not available right now."}`));
|
|
7472
8751
|
if (upsell.reason === "INSUFFICIENT" || upsell.reason === "BREAKER" || upsell.reason === "CAPPED" || upsell.reason === "QUOTA" || upsell.reason === "PROXY_CAP" || upsell.reason === "PAUSED" || upsell.reason === "BYO_REQUIRED") {
|
|
7473
8752
|
if (!loggedIn) {
|
|
7474
|
-
console.log(
|
|
8753
|
+
console.log(import_chalk46.default.dim(" Options: `/login` for more free chats and requests, or `apiblaze llm set-key` to bring your own model key."));
|
|
7475
8754
|
} else {
|
|
7476
|
-
console.log(
|
|
8755
|
+
console.log(import_chalk46.default.dim(" Options: top up your wallet, or `apiblaze llm set-key` to bring your own model key (bypasses platform limits)."));
|
|
7477
8756
|
}
|
|
7478
8757
|
} else if (upsell.reason === "INFLIGHT") {
|
|
7479
|
-
console.log(
|
|
8758
|
+
console.log(import_chalk46.default.dim(" Another turn is still in flight \u2014 wait a moment and try again."));
|
|
7480
8759
|
}
|
|
7481
8760
|
console.log();
|
|
7482
8761
|
}
|
|
@@ -7569,9 +8848,9 @@ async function openServerProxy(project) {
|
|
|
7569
8848
|
let consumerAuth = false;
|
|
7570
8849
|
if (acceptsApiKey) {
|
|
7571
8850
|
if (!dpKey) {
|
|
7572
|
-
console.log(
|
|
8851
|
+
console.log(import_chalk46.default.dim(` Minting an API key for tenant ${import_chalk46.default.bold(tenant2)} to query project ${import_chalk46.default.bold(project.projectName)}\u2026`));
|
|
7573
8852
|
dpKey = await mintDurableProxyKey(project.teamId, tenant2);
|
|
7574
|
-
console.log(` ${
|
|
8853
|
+
console.log(` ${import_chalk46.default.green("\u2714")} API key: ${import_chalk46.default.dim(maskKey(dpKey))}`);
|
|
7575
8854
|
}
|
|
7576
8855
|
} else {
|
|
7577
8856
|
consumerAuth = true;
|
|
@@ -7590,7 +8869,7 @@ async function openServerProxy(project) {
|
|
|
7590
8869
|
// Reusing an owned proxy: logged-in apichat doors default to invite-only.
|
|
7591
8870
|
access: "invite"
|
|
7592
8871
|
};
|
|
7593
|
-
const spinner = (0,
|
|
8872
|
+
const spinner = (0, import_ora23.default)("Preparing the chat\u2026").start();
|
|
7594
8873
|
try {
|
|
7595
8874
|
const raw = await admin({
|
|
7596
8875
|
method: "GET",
|
|
@@ -7600,9 +8879,9 @@ async function openServerProxy(project) {
|
|
|
7600
8879
|
const spec2 = raw && (raw.spec ?? raw);
|
|
7601
8880
|
spinner.stop();
|
|
7602
8881
|
if (!spec2 || !(spec2.paths || spec2.openapi)) {
|
|
7603
|
-
console.log(
|
|
8882
|
+
console.log(import_chalk46.default.yellow(" This proxy has no OpenAPI spec yet \u2014 chat will have no tools. Build one with `apiblaze agent openapi`."));
|
|
7604
8883
|
} else {
|
|
7605
|
-
console.log(
|
|
8884
|
+
console.log(import_chalk46.default.dim(" Using the proxy's existing MCP catalogue (`apiblaze mcp` to rebuild it)."));
|
|
7606
8885
|
}
|
|
7607
8886
|
} catch (err) {
|
|
7608
8887
|
spinner.fail("Could not open the proxy.");
|
|
@@ -7658,7 +8937,7 @@ async function noArgsMenu(opts) {
|
|
|
7658
8937
|
const me = loadCredentials()?.apiblazeUserId;
|
|
7659
8938
|
const saved = loadApichats().filter((a) => a.anon ? true : a.ownerUserId !== void 0 && a.ownerUserId === me);
|
|
7660
8939
|
const choices = saved.map((a) => ({
|
|
7661
|
-
name: `Chat with ${
|
|
8940
|
+
name: `Chat with ${import_chalk46.default.bold(a.name)} ${import_chalk46.default.dim(`(${a.target})${a.messages && a.messages.length ? ` \xB7 ${a.messages.length} msgs` : ""}`)}`,
|
|
7662
8941
|
value: { type: "existing", a }
|
|
7663
8942
|
}));
|
|
7664
8943
|
const creds = loadCredentials();
|
|
@@ -7668,14 +8947,14 @@ async function noArgsMenu(opts) {
|
|
|
7668
8947
|
const proxies = (await getProjects(creds.teamId)).filter((pr) => !savedIds.has(pr.projectId));
|
|
7669
8948
|
for (const pr of proxies) {
|
|
7670
8949
|
choices.push({
|
|
7671
|
-
name: `Chat with ${
|
|
8950
|
+
name: `Chat with ${import_chalk46.default.bold(pr.projectName)} ${import_chalk46.default.dim(`(v${pr.apiVersion}) \xB7 your proxy`)}`,
|
|
7672
8951
|
value: { type: "server", project: pr }
|
|
7673
8952
|
});
|
|
7674
8953
|
}
|
|
7675
8954
|
} catch {
|
|
7676
8955
|
}
|
|
7677
8956
|
}
|
|
7678
|
-
choices.push({ name:
|
|
8957
|
+
choices.push({ name: import_chalk46.default.green("\uFF0B Create a new apichat"), value: { type: "new" } });
|
|
7679
8958
|
const { pick: pick2 } = await inquirer3.prompt([
|
|
7680
8959
|
{ type: "list", name: "pick", message: "What would you like to do?", choices }
|
|
7681
8960
|
]);
|
|
@@ -7778,36 +9057,36 @@ async function noArgsMenu(opts) {
|
|
|
7778
9057
|
async function runRepl(p, initialMessages) {
|
|
7779
9058
|
const { default: inquirer3 } = await import("inquirer");
|
|
7780
9059
|
const messages = initialMessages && initialMessages.length ? initialMessages.slice() : [];
|
|
7781
|
-
console.log("\n" +
|
|
7782
|
-
if (messages.length) console.log(
|
|
9060
|
+
console.log("\n" + import_chalk46.default.cyan.bold("Chat with your API") + import_chalk46.default.dim(` \xB7 ${p.mcpHost}`));
|
|
9061
|
+
if (messages.length) console.log(import_chalk46.default.dim(` Resumed \u2014 ${messages.length} prior messages.`));
|
|
7783
9062
|
const llm2 = loadLlmConfig();
|
|
7784
9063
|
console.log(
|
|
7785
|
-
|
|
9064
|
+
import_chalk46.default.dim(
|
|
7786
9065
|
llm2 ? `Using your local ${llm2.provider} key for the model. Type a question, or /exit. /login /claim manage your workspace.` : "Ask a question in plain English. /exit to quit \xB7 /login for more free chats \xB7 /claim to keep this workspace \xB7 `apiblaze llm set-key` for BYO models."
|
|
7787
9066
|
)
|
|
7788
9067
|
);
|
|
7789
9068
|
for (; ; ) {
|
|
7790
|
-
const { input } = await inquirer3.prompt([{ type: "input", name: "input", message:
|
|
9069
|
+
const { input } = await inquirer3.prompt([{ type: "input", name: "input", message: import_chalk46.default.green("you \u203A") }]);
|
|
7791
9070
|
const text = (input ?? "").trim();
|
|
7792
9071
|
if (!text) continue;
|
|
7793
9072
|
if (["/exit", "/quit", "exit", "quit", ":q"].includes(text.toLowerCase())) break;
|
|
7794
9073
|
if (text === "/login") {
|
|
7795
9074
|
try {
|
|
7796
9075
|
await runLogin();
|
|
7797
|
-
console.log(
|
|
9076
|
+
console.log(import_chalk46.default.dim(" Logged in \u2014 history preserved. Keep chatting."));
|
|
7798
9077
|
} catch (err) {
|
|
7799
|
-
console.log(
|
|
9078
|
+
console.log(import_chalk46.default.red(` Login failed: ${err instanceof Error ? err.message : String(err)}`));
|
|
7800
9079
|
}
|
|
7801
9080
|
continue;
|
|
7802
9081
|
}
|
|
7803
9082
|
if (text === "/claim") {
|
|
7804
9083
|
const justLoggedIn = !loadCredentials();
|
|
7805
9084
|
if (justLoggedIn) {
|
|
7806
|
-
console.log(
|
|
9085
|
+
console.log(import_chalk46.default.dim(" Logging in to claim your workspace\u2026"));
|
|
7807
9086
|
try {
|
|
7808
9087
|
await runLogin();
|
|
7809
9088
|
} catch (err) {
|
|
7810
|
-
console.log(
|
|
9089
|
+
console.log(import_chalk46.default.red(` Login failed: ${err instanceof Error ? err.message : String(err)}`));
|
|
7811
9090
|
continue;
|
|
7812
9091
|
}
|
|
7813
9092
|
if (!loadCredentials()) continue;
|
|
@@ -7818,30 +9097,30 @@ async function runRepl(p, initialMessages) {
|
|
|
7818
9097
|
p.mcpHost = p.mcpHost.replace(".mcp.tryabz.run", ".mcp.abz.run");
|
|
7819
9098
|
p.anon = false;
|
|
7820
9099
|
claimApichat(p, loadCredentials()?.apiblazeUserId);
|
|
7821
|
-
console.log(
|
|
9100
|
+
console.log(import_chalk46.default.dim(` Workspace claimed \u2014 chat now routes on ${p.mcpHost}. History preserved.`));
|
|
7822
9101
|
}
|
|
7823
9102
|
} catch (err) {
|
|
7824
|
-
console.log(
|
|
9103
|
+
console.log(import_chalk46.default.red(` Claim failed: ${err instanceof Error ? err.message : String(err)}`));
|
|
7825
9104
|
}
|
|
7826
9105
|
continue;
|
|
7827
9106
|
}
|
|
7828
9107
|
if (text === "/showauth") {
|
|
7829
9108
|
revealAuth = !revealAuth;
|
|
7830
|
-
console.log(
|
|
9109
|
+
console.log(import_chalk46.default.dim(revealAuth ? " The real API key will be shown in the next call's curl. /showauth again to re-mask." : " API key re-masked."));
|
|
7831
9110
|
continue;
|
|
7832
9111
|
}
|
|
7833
9112
|
if (text.startsWith("/")) {
|
|
7834
|
-
console.log(
|
|
9113
|
+
console.log(import_chalk46.default.dim(" Commands: /login /claim /showauth /exit"));
|
|
7835
9114
|
continue;
|
|
7836
9115
|
}
|
|
7837
9116
|
await replTurn(p, messages, text);
|
|
7838
9117
|
saveTranscript(p, messages);
|
|
7839
9118
|
}
|
|
7840
|
-
console.log(
|
|
9119
|
+
console.log(import_chalk46.default.dim("\nBye."));
|
|
7841
9120
|
}
|
|
7842
9121
|
async function runApichat(opts) {
|
|
7843
9122
|
setVerbose(opts.verbose !== false);
|
|
7844
|
-
console.log(
|
|
9123
|
+
console.log(import_chalk46.default.bold("\napichat \u2014 turn any API into a chat\n"));
|
|
7845
9124
|
if (!opts.openapispec && !opts.target) {
|
|
7846
9125
|
if (!process.stdin.isTTY) {
|
|
7847
9126
|
fail4("No spec source. Pass --openapispec <file|url> or --target <url>.", GENERATOR_HINT);
|
|
@@ -7854,7 +9133,7 @@ async function runApichat(opts) {
|
|
|
7854
9133
|
}
|
|
7855
9134
|
const { spec: spec2, sourceUrl } = await loadSpec(opts);
|
|
7856
9135
|
const target = resolveTarget(spec2, opts, sourceUrl);
|
|
7857
|
-
console.log(` ${
|
|
9136
|
+
console.log(` ${import_chalk46.default.dim("Target:")} ${import_chalk46.default.bold(target)}`);
|
|
7858
9137
|
const auth = await resolveTargetAuth(spec2, opts);
|
|
7859
9138
|
if (auth && !process.stdin.isTTY && !opts.targetAuthEnv) {
|
|
7860
9139
|
fail4(
|
|
@@ -7863,7 +9142,7 @@ async function runApichat(opts) {
|
|
|
7863
9142
|
);
|
|
7864
9143
|
}
|
|
7865
9144
|
const p = await provision(spec2, target, opts);
|
|
7866
|
-
console.log(` ${
|
|
9145
|
+
console.log(` ${import_chalk46.default.dim("Proxy: ")} ${import_chalk46.default.bold(p.proxyUrl || `${p.projectId} v${p.version}`)}`);
|
|
7867
9146
|
upsertApichat({
|
|
7868
9147
|
name: p.projectId,
|
|
7869
9148
|
target,
|
|
@@ -7883,38 +9162,38 @@ async function runApichat(opts) {
|
|
|
7883
9162
|
const secret = await captureTargetSecret(auth, opts);
|
|
7884
9163
|
if (secret) await writeTargetAuth(p, auth, secret);
|
|
7885
9164
|
} else {
|
|
7886
|
-
console.log(
|
|
9165
|
+
console.log(import_chalk46.default.dim(" Target auth: none required."));
|
|
7887
9166
|
}
|
|
7888
9167
|
const specText = JSON.stringify(spec2);
|
|
7889
9168
|
await uploadSpec(p, specText, opts);
|
|
7890
9169
|
const mcpUrl = await publishMcp(p, spec2);
|
|
7891
9170
|
console.log();
|
|
7892
|
-
if (p.proxyUrl) console.log(` ${
|
|
9171
|
+
if (p.proxyUrl) console.log(` ${import_chalk46.default.green("\u2713")} proxy ${import_chalk46.default.bold(p.proxyUrl)}`);
|
|
7893
9172
|
if (mcpUrl) {
|
|
7894
|
-
console.log(` ${
|
|
9173
|
+
console.log(` ${import_chalk46.default.green("\u2713")} mcp ${import_chalk46.default.bold(mcpUrl)}`);
|
|
7895
9174
|
if (p.access === "invite") {
|
|
7896
|
-
console.log(
|
|
7897
|
-
console.log(
|
|
9175
|
+
console.log(import_chalk46.default.dim(" Claude/ChatGPT-connectable (GitHub sign-in) \xB7 access: invite \u2014 only you + emails you pre-approve"));
|
|
9176
|
+
console.log(import_chalk46.default.dim(` Let others in: apiblaze preapprove someone@company.com${p.tenant ? ` --tenant ${p.tenant}` : ""} (or re-run with --access open)`));
|
|
7898
9177
|
} else {
|
|
7899
|
-
console.log(
|
|
9178
|
+
console.log(import_chalk46.default.dim(" Claude/ChatGPT-connectable (GitHub sign-in) \xB7 access: open \u2014 anyone who signs in can call this API"));
|
|
7900
9179
|
}
|
|
7901
9180
|
}
|
|
7902
9181
|
if (p.anon) {
|
|
7903
|
-
console.log(
|
|
9182
|
+
console.log(import_chalk46.default.dim("\n Anonymous workspace \u2014 /claim inside the chat to log in and keep it beyond 30 days."));
|
|
7904
9183
|
}
|
|
7905
9184
|
await runRepl(p);
|
|
7906
9185
|
}
|
|
7907
9186
|
|
|
7908
9187
|
// src/commands/consumer.ts
|
|
7909
|
-
var
|
|
7910
|
-
var
|
|
9188
|
+
var import_chalk47 = __toESM(require("chalk"));
|
|
9189
|
+
var import_ora24 = __toESM(require("ora"));
|
|
7911
9190
|
init_admin();
|
|
7912
9191
|
init_resolve();
|
|
7913
9192
|
var DEFAULT_SCOPE = "openid email profile offline_access";
|
|
7914
|
-
var
|
|
9193
|
+
var APIKEYS_BASE2 = process.env.APIBLAZE_APIKEYS_BASE || "https://apikeys.apiblaze.com";
|
|
7915
9194
|
async function consumerFetch(creds, suffix, init) {
|
|
7916
9195
|
const fresh = await validConsumerToken(creds) ?? creds;
|
|
7917
|
-
const res = await fetch(`${
|
|
9196
|
+
const res = await fetch(`${APIKEYS_BASE2}${suffix}`, {
|
|
7918
9197
|
...init,
|
|
7919
9198
|
headers: { "Content-Type": "application/json", Authorization: `Bearer ${fresh.accessToken}`, ...init?.headers ?? {} }
|
|
7920
9199
|
});
|
|
@@ -7928,7 +9207,7 @@ async function consumerFetch(creds, suffix, init) {
|
|
|
7928
9207
|
function requireConsumer2() {
|
|
7929
9208
|
const c = loadConsumer();
|
|
7930
9209
|
if (!c) {
|
|
7931
|
-
console.error(
|
|
9210
|
+
console.error(import_chalk47.default.red("Not logged in as a consumer. Run `apiblaze consumer login` first."));
|
|
7932
9211
|
process.exit(1);
|
|
7933
9212
|
}
|
|
7934
9213
|
return c;
|
|
@@ -7939,7 +9218,7 @@ async function runConsumerLogin(opts) {
|
|
|
7939
9218
|
let clientId = opts.client;
|
|
7940
9219
|
if (clientId) {
|
|
7941
9220
|
if (!tenant2) {
|
|
7942
|
-
console.error(
|
|
9221
|
+
console.error(import_chalk47.default.red("When using --client, also pass --tenant <slug> (it sets which portal/keys host to use)."));
|
|
7943
9222
|
process.exit(1);
|
|
7944
9223
|
}
|
|
7945
9224
|
} else {
|
|
@@ -7951,24 +9230,24 @@ async function runConsumerLogin(opts) {
|
|
|
7951
9230
|
if (!picked) process.exit(1);
|
|
7952
9231
|
tenant2 = picked;
|
|
7953
9232
|
}
|
|
7954
|
-
const s2 = (0,
|
|
9233
|
+
const s2 = (0, import_ora24.default)("Finding the login app...").start();
|
|
7955
9234
|
const clients = await admin({ method: "GET", path: `/teams/${encodeURIComponent(teamId)}/tenants/${encodeURIComponent(tenant2)}/app-clients`, summary: `List app clients for ${tenant2}` }).catch(() => []);
|
|
7956
9235
|
s2.stop();
|
|
7957
9236
|
const usable = (Array.isArray(clients) ? clients : []).filter((c) => c && (c.client_id || c.clientId));
|
|
7958
9237
|
const pick2 = usable.find((c) => c.is_default || c.default) ?? usable.find((c) => c.verified !== false) ?? usable[0];
|
|
7959
9238
|
if (!pick2) {
|
|
7960
|
-
console.error(
|
|
9239
|
+
console.error(import_chalk47.default.red(`Tenant "${tenant2}" has no login app configured. Set one up in the dashboard (or \`apiblaze create\` with auth).`));
|
|
7961
9240
|
process.exit(1);
|
|
7962
9241
|
}
|
|
7963
9242
|
clientId = pick2.client_id ?? pick2.clientId;
|
|
7964
9243
|
}
|
|
7965
|
-
console.log(`${
|
|
9244
|
+
console.log(`${import_chalk47.default.cyan("\u2192")} Logging in to ${import_chalk47.default.bold(tenant2)} as a consumer...`);
|
|
7966
9245
|
const result = await deviceLogin(clientId, DEFAULT_SCOPE, ({ verificationUri, userCode }) => {
|
|
7967
9246
|
console.log(`
|
|
7968
|
-
Open: ${
|
|
7969
|
-
console.log(` Code: ${
|
|
9247
|
+
Open: ${import_chalk47.default.underline(verificationUri)}`);
|
|
9248
|
+
console.log(` Code: ${import_chalk47.default.bold(userCode)}
|
|
7970
9249
|
`);
|
|
7971
|
-
console.log(
|
|
9250
|
+
console.log(import_chalk47.default.dim(" (opening your browser\u2026 waiting for you to finish)"));
|
|
7972
9251
|
});
|
|
7973
9252
|
const claims = result.idToken && decodeJwt2(result.idToken) || (decodeJwt2(result.accessToken) ?? {});
|
|
7974
9253
|
const creds = {
|
|
@@ -7983,7 +9262,7 @@ async function runConsumerLogin(opts) {
|
|
|
7983
9262
|
obtainedAt: Date.now()
|
|
7984
9263
|
};
|
|
7985
9264
|
saveConsumer(creds);
|
|
7986
|
-
console.log(
|
|
9265
|
+
console.log(import_chalk47.default.green(`\u2714 Logged in as consumer${creds.email ? ` ${creds.email}` : ""} on ${tenant2}.`));
|
|
7987
9266
|
}
|
|
7988
9267
|
async function runConsumerTokens(opts) {
|
|
7989
9268
|
const creds = requireConsumer2();
|
|
@@ -7996,29 +9275,29 @@ async function runConsumerTokens(opts) {
|
|
|
7996
9275
|
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));
|
|
7997
9276
|
return;
|
|
7998
9277
|
}
|
|
7999
|
-
console.log(`${
|
|
9278
|
+
console.log(`${import_chalk47.default.cyan("Consumer")} ${import_chalk47.default.bold(fresh.email ?? fresh.tenant)} on ${import_chalk47.default.bold(fresh.tenant)}
|
|
8000
9279
|
`);
|
|
8001
|
-
console.log(`${
|
|
9280
|
+
console.log(`${import_chalk47.default.bold("access_token")} ${import_chalk47.default.dim("exp " + (exp(fresh.accessToken) ?? "?"))}
|
|
8002
9281
|
${fresh.accessToken}
|
|
8003
9282
|
`);
|
|
8004
|
-
if (fresh.idToken) console.log(`${
|
|
9283
|
+
if (fresh.idToken) console.log(`${import_chalk47.default.bold("id_token")} ${import_chalk47.default.dim("exp " + (exp(fresh.idToken) ?? "?"))}
|
|
8005
9284
|
${fresh.idToken}
|
|
8006
9285
|
`);
|
|
8007
|
-
if (fresh.refreshToken) console.log(`${
|
|
9286
|
+
if (fresh.refreshToken) console.log(`${import_chalk47.default.bold("refresh_token")}
|
|
8008
9287
|
${fresh.refreshToken}
|
|
8009
9288
|
`);
|
|
8010
|
-
console.log(
|
|
9289
|
+
console.log(import_chalk47.default.dim("These are your own tokens \u2014 keep them secret."));
|
|
8011
9290
|
}
|
|
8012
9291
|
async function runConsumerApikeys(opts) {
|
|
8013
9292
|
const creds = requireConsumer2();
|
|
8014
9293
|
const { default: inquirer3 } = await import("inquirer");
|
|
8015
|
-
const spinner = (0,
|
|
9294
|
+
const spinner = (0, import_ora24.default)("Loading your API keys...").start();
|
|
8016
9295
|
const list = await consumerFetch(creds, "/apikeys");
|
|
8017
9296
|
const revealed = await consumerFetch(list.creds, "/apikeys/reveal").catch(() => ({ status: 0, data: null, creds: list.creds }));
|
|
8018
9297
|
spinner.stop();
|
|
8019
9298
|
if (list.status >= 400) {
|
|
8020
|
-
console.error(
|
|
8021
|
-
if (list.status === 401) console.error(
|
|
9299
|
+
console.error(import_chalk47.default.red(`Failed to list keys (${list.status}): ${list.data?.error ?? ""}`));
|
|
9300
|
+
if (list.status === 401) console.error(import_chalk47.default.dim("Your consumer session may have expired \u2014 run `apiblaze consumer login` again."));
|
|
8022
9301
|
process.exit(1);
|
|
8023
9302
|
}
|
|
8024
9303
|
const keys = list.data?.keys ?? [];
|
|
@@ -8026,16 +9305,16 @@ async function runConsumerApikeys(opts) {
|
|
|
8026
9305
|
if (opts.json) {
|
|
8027
9306
|
console.log(JSON.stringify({ keys, revealed: revealMap }, null, 2));
|
|
8028
9307
|
} else if (!keys.length) {
|
|
8029
|
-
console.log(
|
|
9308
|
+
console.log(import_chalk47.default.yellow("No API keys yet."));
|
|
8030
9309
|
} else {
|
|
8031
9310
|
for (const k of keys) {
|
|
8032
9311
|
const clear = revealMap[k.environment]?.key;
|
|
8033
|
-
const shown = clear ?
|
|
8034
|
-
const exp = k.expires_at ?
|
|
8035
|
-
console.log(` ${
|
|
9312
|
+
const shown = clear ? import_chalk47.default.green(clear) : import_chalk47.default.dim(`${k.key_prefix ?? ""}\u2026${k.key_suffix ?? ""}`);
|
|
9313
|
+
const exp = k.expires_at ? import_chalk47.default.dim(`exp ${k.expires_at}`) : import_chalk47.default.dim("no expiry");
|
|
9314
|
+
console.log(` ${import_chalk47.default.bold(k.environment ?? "")} ${shown} ${exp} ${import_chalk47.default.dim(k.description ?? "")}`);
|
|
8036
9315
|
}
|
|
8037
9316
|
if (Object.keys(revealMap).length === 0 && keys.some((k) => !k.expires_at)) {
|
|
8038
|
-
console.log(
|
|
9317
|
+
console.log(import_chalk47.default.dim("\n(Only expiring keys can be shown in clear; non-expiring keys show a prefix only.)"));
|
|
8039
9318
|
}
|
|
8040
9319
|
}
|
|
8041
9320
|
if (opts.json) return;
|
|
@@ -8049,7 +9328,7 @@ async function runConsumerApikeys(opts) {
|
|
|
8049
9328
|
const body = { environment: answers.environment };
|
|
8050
9329
|
if (answers.description) body.description = answers.description;
|
|
8051
9330
|
if (answers.expiresDays) body.expires_in_seconds = Number(answers.expiresDays) * 86400;
|
|
8052
|
-
const s2 = (0,
|
|
9331
|
+
const s2 = (0, import_ora24.default)("Creating key...").start();
|
|
8053
9332
|
const created = await consumerFetch(list.creds, "/apikeys", { method: "POST", body: JSON.stringify(body) });
|
|
8054
9333
|
if (created.status >= 400) {
|
|
8055
9334
|
s2.fail(`Create failed (${created.status}): ${created.data?.error ?? ""}`);
|
|
@@ -8057,13 +9336,13 @@ async function runConsumerApikeys(opts) {
|
|
|
8057
9336
|
}
|
|
8058
9337
|
s2.succeed("Key created.");
|
|
8059
9338
|
const key = created.data?.key ?? created.data?.fullKey;
|
|
8060
|
-
if (key) console.log(` ${
|
|
8061
|
-
else console.log(
|
|
9339
|
+
if (key) console.log(` ${import_chalk47.default.green(key)} ${import_chalk47.default.dim("(shown once \u2014 store it now)")}`);
|
|
9340
|
+
else console.log(import_chalk47.default.dim(" Key created; run `apiblaze consumer apikeys` to reveal it if it expires."));
|
|
8062
9341
|
}
|
|
8063
9342
|
|
|
8064
9343
|
// src/commands/sidecar.ts
|
|
8065
|
-
var
|
|
8066
|
-
var
|
|
9344
|
+
var import_chalk48 = __toESM(require("chalk"));
|
|
9345
|
+
var import_ora25 = __toESM(require("ora"));
|
|
8067
9346
|
var fs10 = __toESM(require("fs"));
|
|
8068
9347
|
var path7 = __toESM(require("path"));
|
|
8069
9348
|
init_admin();
|
|
@@ -8105,18 +9384,18 @@ function upsertEnvLocal(root, token) {
|
|
|
8105
9384
|
}
|
|
8106
9385
|
function installSidecarPackage(root) {
|
|
8107
9386
|
if (fs10.existsSync(path7.join(root, "node_modules", "apiblaze", "package.json"))) {
|
|
8108
|
-
console.log(` ${
|
|
9387
|
+
console.log(` ${import_chalk48.default.green("\u2713")} apiblaze package already installed`);
|
|
8109
9388
|
return;
|
|
8110
9389
|
}
|
|
8111
9390
|
const has = (f) => fs10.existsSync(path7.join(root, f));
|
|
8112
9391
|
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" };
|
|
8113
|
-
const spinner = (0,
|
|
9392
|
+
const spinner = (0, import_ora25.default)(`Installing the apiblaze package (${pm.cmd})\u2026`).start();
|
|
8114
9393
|
try {
|
|
8115
9394
|
const { execSync } = require("child_process");
|
|
8116
9395
|
execSync(`${pm.cmd} ${pm.add} apiblaze`, { cwd: root, stdio: "ignore" });
|
|
8117
9396
|
spinner.succeed("Installed apiblaze (the sidecar runtime).");
|
|
8118
9397
|
} catch {
|
|
8119
|
-
spinner.warn(`Couldn't auto-install \u2014 run ${
|
|
9398
|
+
spinner.warn(`Couldn't auto-install \u2014 run ${import_chalk48.default.cyan(`${pm.cmd} ${pm.add} apiblaze`)} yourself before ${import_chalk48.default.cyan("npm run dev")}.`);
|
|
8120
9399
|
}
|
|
8121
9400
|
}
|
|
8122
9401
|
function readEnvKey(root) {
|
|
@@ -8255,7 +9534,7 @@ async function runAnonymousInit(root, router, opts) {
|
|
|
8255
9534
|
const { sidecarInitAnonymous: sidecarInitAnonymous2 } = await Promise.resolve().then(() => (init_api(), api_exports));
|
|
8256
9535
|
const { saveAnonCred: saveAnonCred2, clearAnonCred: clearAnonCred2 } = await Promise.resolve().then(() => (init_anon_cred(), anon_cred_exports));
|
|
8257
9536
|
if (opts.newSession) clearAnonCred2();
|
|
8258
|
-
const spinner = (0,
|
|
9537
|
+
const spinner = (0, import_ora25.default)("Setting up a sidecar (no login needed)...").start();
|
|
8259
9538
|
let out;
|
|
8260
9539
|
try {
|
|
8261
9540
|
out = await sidecarInitAnonymous2();
|
|
@@ -8267,29 +9546,29 @@ async function runAnonymousInit(root, router, opts) {
|
|
|
8267
9546
|
if (out.cp_key && out.team_id) saveAnonCred2(out.cp_key, out.team_id, out.claim_code);
|
|
8268
9547
|
const envState = upsertEnvLocal(root, out.token);
|
|
8269
9548
|
ensureGitignored(root);
|
|
8270
|
-
console.log(` ${
|
|
8271
|
-
console.log(` ${
|
|
9549
|
+
console.log(` ${import_chalk48.default.green("\u2713")} .env.local ${envState} (APIBLAZE_API_KEY) \u2014 gitignored`);
|
|
9550
|
+
console.log(` ${import_chalk48.default.green("\u2713")} instrumentation.ts ${wireInstrumentation(root)}`);
|
|
8272
9551
|
installSidecarPackage(root);
|
|
8273
9552
|
let inspectorPath = null;
|
|
8274
9553
|
if (!opts.noInspector) {
|
|
8275
9554
|
inspectorPath = generateInspector(root, router);
|
|
8276
|
-
if (inspectorPath) console.log(` ${
|
|
9555
|
+
if (inspectorPath) console.log(` ${import_chalk48.default.green("\u2713")} inspector at ${inspectorPath}`);
|
|
8277
9556
|
}
|
|
8278
9557
|
console.log("");
|
|
8279
|
-
console.log(
|
|
8280
|
-
console.log(` 1. ${
|
|
9558
|
+
console.log(import_chalk48.default.bold("Done (no account needed). What happens next:"));
|
|
9559
|
+
console.log(` 1. ${import_chalk48.default.cyan("npm run dev")} and use your app.`);
|
|
8281
9560
|
console.log(` 2. Each external origin your app calls is logged in the console \u2014 approve one with:`);
|
|
8282
|
-
console.log(` ${
|
|
9561
|
+
console.log(` ${import_chalk48.default.cyan("apiblaze sidecar approve api.stripe.com")} (no login needed)`);
|
|
8283
9562
|
console.log("");
|
|
8284
|
-
console.log(
|
|
8285
|
-
console.log(` ${
|
|
8286
|
-
console.log(
|
|
9563
|
+
console.log(import_chalk48.default.bold(" \u{1F511} Keep your setup \u2014 claim it into an account:"));
|
|
9564
|
+
console.log(` ${import_chalk48.default.cyan("apiblaze login")} then ${import_chalk48.default.cyan("apiblaze claim")} ${import_chalk48.default.dim("(no code needed here)")}`);
|
|
9565
|
+
console.log(import_chalk48.default.dim(` From another machine: apiblaze claim ${out.claim_code} \xB7 expires in 30 days`));
|
|
8287
9566
|
}
|
|
8288
9567
|
async function runSidecar(opts) {
|
|
8289
9568
|
const root = path7.resolve(opts.dir ?? process.cwd());
|
|
8290
9569
|
const detected = detectNextProject(root);
|
|
8291
9570
|
if (!detected.found) {
|
|
8292
|
-
console.log(
|
|
9571
|
+
console.log(import_chalk48.default.yellow(`No Next.js project detected in ${root}.`));
|
|
8293
9572
|
console.log("Create one (e.g. `npx create-next-app`) and re-run `apiblaze init` inside it.");
|
|
8294
9573
|
return;
|
|
8295
9574
|
}
|
|
@@ -8300,10 +9579,10 @@ async function runSidecar(opts) {
|
|
|
8300
9579
|
if (!loadCredentials()) {
|
|
8301
9580
|
upsertEnvLocal(root, readEnvKey(root));
|
|
8302
9581
|
ensureGitignored(root);
|
|
8303
|
-
console.log(` ${
|
|
8304
|
-
console.log(` ${
|
|
9582
|
+
console.log(` ${import_chalk48.default.green("\u2713")} .env.local present (APIBLAZE_API_KEY) \u2014 reusing`);
|
|
9583
|
+
console.log(` ${import_chalk48.default.green("\u2713")} instrumentation.ts ${wireInstrumentation(root)}`);
|
|
8305
9584
|
installSidecarPackage(root);
|
|
8306
|
-
console.log(
|
|
9585
|
+
console.log(import_chalk48.default.dim(" Log in and run `apiblaze claim <code>` to keep this setup, or `apiblaze login` to manage it."));
|
|
8307
9586
|
return;
|
|
8308
9587
|
}
|
|
8309
9588
|
const { teamId, teamName } = await resolveTeam(opts.team);
|
|
@@ -8312,7 +9591,7 @@ async function runSidecar(opts) {
|
|
|
8312
9591
|
const mustMint = !existingKey || opts.rotate || switchingTeam;
|
|
8313
9592
|
let token = existingKey ?? "";
|
|
8314
9593
|
if (mustMint) {
|
|
8315
|
-
const spinner = (0,
|
|
9594
|
+
const spinner = (0, import_ora25.default)(existingKey ? "Re-establishing the sidecar (minting a fresh invoke key)..." : "Setting up the sidecar (tenant + non-expiring invoke key)...").start();
|
|
8316
9595
|
try {
|
|
8317
9596
|
const out = await admin({
|
|
8318
9597
|
method: "POST",
|
|
@@ -8326,39 +9605,39 @@ async function runSidecar(opts) {
|
|
|
8326
9605
|
throw err;
|
|
8327
9606
|
}
|
|
8328
9607
|
} else {
|
|
8329
|
-
console.log(
|
|
9608
|
+
console.log(import_chalk48.default.dim(` Reusing the existing APIBLAZE_API_KEY (run with --rotate to mint a fresh one, or --team <name> to switch teams).`));
|
|
8330
9609
|
}
|
|
8331
9610
|
const envState = upsertEnvLocal(root, token);
|
|
8332
9611
|
ensureGitignored(root);
|
|
8333
|
-
console.log(` ${
|
|
9612
|
+
console.log(` ${import_chalk48.default.green("\u2713")} .env.local ${envState} (APIBLAZE_API_KEY) \u2014 gitignored`);
|
|
8334
9613
|
const wireState = wireInstrumentation(root);
|
|
8335
|
-
console.log(` ${
|
|
9614
|
+
console.log(` ${import_chalk48.default.green("\u2713")} instrumentation.ts ${wireState}`);
|
|
8336
9615
|
installSidecarPackage(root);
|
|
8337
9616
|
let inspectorPath = null;
|
|
8338
9617
|
if (!opts.noInspector) {
|
|
8339
9618
|
inspectorPath = generateInspector(root, detected.router);
|
|
8340
|
-
if (inspectorPath) console.log(` ${
|
|
9619
|
+
if (inspectorPath) console.log(` ${import_chalk48.default.green("\u2713")} inspector at ${inspectorPath}`);
|
|
8341
9620
|
}
|
|
8342
9621
|
console.log("");
|
|
8343
|
-
console.log(
|
|
8344
|
-
console.log(` 1. ${
|
|
8345
|
-
console.log(` 2. The origins your app calls appear as ${
|
|
8346
|
-
console.log(` 3. Approve the ones to route: ${
|
|
9622
|
+
console.log(import_chalk48.default.bold("Done. What happens next:"));
|
|
9623
|
+
console.log(` 1. ${import_chalk48.default.cyan("npm run dev")} and use your app \u2014 it works exactly as before (all calls go direct).`);
|
|
9624
|
+
console.log(` 2. The origins your app calls appear as ${import_chalk48.default.bold("candidates")} \u2014 list them: ${import_chalk48.default.cyan("apiblaze sidecar")}`);
|
|
9625
|
+
console.log(` 3. Approve the ones to route: ${import_chalk48.default.cyan("apiblaze sidecar approve api.stripe.com")} (or in the dashboard)`);
|
|
8347
9626
|
console.log(` \u2026within ~5 min your app starts routing that origin through APIblaze.`);
|
|
8348
|
-
if (inspectorPath) console.log(` \u2022 Try it now: open ${
|
|
8349
|
-
if (switchingTeam) console.log(
|
|
9627
|
+
if (inspectorPath) console.log(` \u2022 Try it now: open ${import_chalk48.default.underline("http://localhost:3000/abz-inspector")} (dev only; rm ${path7.dirname(inspectorPath)} before shipping)`);
|
|
9628
|
+
if (switchingTeam) console.log(import_chalk48.default.dim(` \u2022 Approved origins are per-team \u2014 re-approve them on ${teamName ?? teamId} with \`apiblaze sidecar approve <origin>\`.`));
|
|
8350
9629
|
console.log("");
|
|
8351
|
-
console.log(
|
|
8352
|
-
console.log(
|
|
8353
|
-
console.log(
|
|
9630
|
+
console.log(import_chalk48.default.dim(" Manage: apiblaze sidecar (list/approve/deny/remove)"));
|
|
9631
|
+
console.log(import_chalk48.default.dim(" Rotate: apiblaze init --rotate \xB7 Switch team: apiblaze init --team <name>"));
|
|
9632
|
+
console.log(import_chalk48.default.dim(" Turn off: set APIBLAZE_SIDECAR=off in .env.local (flip back to on anytime; key stays put)."));
|
|
8354
9633
|
console.log("");
|
|
8355
|
-
console.log(
|
|
8356
|
-
console.log(
|
|
9634
|
+
console.log(import_chalk48.default.yellow(" \u26A0 APIBLAZE_API_KEY is long-lived and lets a holder call your team's proxies. Never commit it."));
|
|
9635
|
+
console.log(import_chalk48.default.dim(" Your control-plane login stays in ~/.apiblaze \u2014 it never entered this project."));
|
|
8357
9636
|
}
|
|
8358
9637
|
|
|
8359
9638
|
// src/commands/origins.ts
|
|
8360
|
-
var
|
|
8361
|
-
var
|
|
9639
|
+
var import_chalk49 = __toESM(require("chalk"));
|
|
9640
|
+
var import_ora26 = __toESM(require("ora"));
|
|
8362
9641
|
init_admin();
|
|
8363
9642
|
init_resolve();
|
|
8364
9643
|
init_auth();
|
|
@@ -8368,7 +9647,7 @@ async function runOriginsList(opts) {
|
|
|
8368
9647
|
if (!loadCredentials()) {
|
|
8369
9648
|
const cred = loadAnonCred();
|
|
8370
9649
|
if (!cred) {
|
|
8371
|
-
console.log(
|
|
9650
|
+
console.log(import_chalk49.default.yellow("No anonymous workspace here. Run `apiblaze init` first."));
|
|
8372
9651
|
return;
|
|
8373
9652
|
}
|
|
8374
9653
|
out = await cpFetch(cred.cp_key, `/teams/${encodeURIComponent(cred.team_id)}/sidecar/candidates`, { method: "GET" });
|
|
@@ -8386,30 +9665,30 @@ async function runOriginsList(opts) {
|
|
|
8386
9665
|
}
|
|
8387
9666
|
const routed = out.routed ?? [];
|
|
8388
9667
|
const candidates = out.candidates ?? [];
|
|
8389
|
-
console.log(
|
|
9668
|
+
console.log(import_chalk49.default.bold(`
|
|
8390
9669
|
Routed through APIblaze (${routed.length})`));
|
|
8391
|
-
if (!routed.length) console.log(
|
|
8392
|
-
for (const r of routed) console.log(` ${
|
|
8393
|
-
console.log(
|
|
9670
|
+
if (!routed.length) console.log(import_chalk49.default.dim(" none yet"));
|
|
9671
|
+
for (const r of routed) console.log(` ${import_chalk49.default.green("\u25CF")} ${r.sidecar_origin} ${import_chalk49.default.dim(`\u2192 ${r.project_id}`)}`);
|
|
9672
|
+
console.log(import_chalk49.default.bold(`
|
|
8394
9673
|
Candidates \u2014 going direct, not yet approved (${candidates.length})`));
|
|
8395
|
-
if (!candidates.length) console.log(
|
|
9674
|
+
if (!candidates.length) console.log(import_chalk49.default.dim(" none \u2014 run your app to discover the origins it calls"));
|
|
8396
9675
|
for (const c of candidates) {
|
|
8397
|
-
console.log(` ${
|
|
9676
|
+
console.log(` ${import_chalk49.default.yellow("\u25CB")} ${c.origin} ${import_chalk49.default.dim(`seen ${c.request_count}\xD7, last ${c.last_seen}`)}`);
|
|
8398
9677
|
}
|
|
8399
9678
|
if (candidates.length) {
|
|
8400
|
-
console.log(
|
|
9679
|
+
console.log(import_chalk49.default.dim(`
|
|
8401
9680
|
Approve: apiblaze sidecar approve ${candidates[0].origin.replace("https://", "")}`));
|
|
8402
|
-
console.log(
|
|
9681
|
+
console.log(import_chalk49.default.dim(` Dismiss: apiblaze sidecar deny ${candidates[0].origin.replace("https://", "")}`));
|
|
8403
9682
|
}
|
|
8404
9683
|
}
|
|
8405
9684
|
async function runOriginsApprove(origin, opts) {
|
|
8406
9685
|
if (!loadCredentials()) {
|
|
8407
9686
|
const cred = loadAnonCred();
|
|
8408
9687
|
if (!cred) {
|
|
8409
|
-
console.error(
|
|
9688
|
+
console.error(import_chalk49.default.red("Not logged in and no anonymous workspace. Run `apiblaze init` first."));
|
|
8410
9689
|
process.exit(1);
|
|
8411
9690
|
}
|
|
8412
|
-
const spinner2 = (0,
|
|
9691
|
+
const spinner2 = (0, import_ora26.default)(`Approving ${origin} (anonymous)...`).start();
|
|
8413
9692
|
try {
|
|
8414
9693
|
const out = await cpFetch(cred.cp_key, `/teams/${encodeURIComponent(cred.team_id)}/sidecar/approve`, { method: "POST", body: JSON.stringify({ origin }) });
|
|
8415
9694
|
spinner2.succeed(`Approved ${origin} \u2192 proxy ${out.project_id}. Routing within ~5 min.`);
|
|
@@ -8420,7 +9699,7 @@ async function runOriginsApprove(origin, opts) {
|
|
|
8420
9699
|
return;
|
|
8421
9700
|
}
|
|
8422
9701
|
const { teamId } = await resolveTeam(opts.team);
|
|
8423
|
-
const spinner = (0,
|
|
9702
|
+
const spinner = (0, import_ora26.default)(`Approving ${origin}...`).start();
|
|
8424
9703
|
try {
|
|
8425
9704
|
const out = await admin({
|
|
8426
9705
|
method: "POST",
|
|
@@ -8437,7 +9716,7 @@ async function runOriginsApprove(origin, opts) {
|
|
|
8437
9716
|
}
|
|
8438
9717
|
async function runOriginsDeny(origin, opts) {
|
|
8439
9718
|
const { teamId } = await resolveTeam(opts.team);
|
|
8440
|
-
const spinner = (0,
|
|
9719
|
+
const spinner = (0, import_ora26.default)(`Dismissing ${origin}...`).start();
|
|
8441
9720
|
try {
|
|
8442
9721
|
await admin({ method: "POST", path: `/teams/${encodeURIComponent(teamId)}/sidecar/dismiss`, body: { origin }, summary: `Dismiss sidecar origin ${origin}` });
|
|
8443
9722
|
spinner.succeed(`Dismissed ${origin}. It won't be suggested again.`);
|
|
@@ -8448,7 +9727,7 @@ async function runOriginsDeny(origin, opts) {
|
|
|
8448
9727
|
}
|
|
8449
9728
|
async function runOriginsRemove(origin, opts) {
|
|
8450
9729
|
const { teamId } = await resolveTeam(opts.team);
|
|
8451
|
-
const spinner = (0,
|
|
9730
|
+
const spinner = (0, import_ora26.default)(`Removing the proxy for ${origin}...`).start();
|
|
8452
9731
|
try {
|
|
8453
9732
|
await admin({ method: "POST", path: `/teams/${encodeURIComponent(teamId)}/sidecar/remove`, body: { origin }, summary: `Un-route sidecar origin ${origin}` });
|
|
8454
9733
|
spinner.succeed(`Removed ${origin}. Your app will stop routing it (goes direct) within ~5 min.`);
|
|
@@ -8459,7 +9738,7 @@ async function runOriginsRemove(origin, opts) {
|
|
|
8459
9738
|
}
|
|
8460
9739
|
|
|
8461
9740
|
// src/commands/op.ts
|
|
8462
|
-
var
|
|
9741
|
+
var import_chalk50 = __toESM(require("chalk"));
|
|
8463
9742
|
init_auth();
|
|
8464
9743
|
init_trace();
|
|
8465
9744
|
init_types();
|
|
@@ -8492,82 +9771,82 @@ function printResidue(report, applied) {
|
|
|
8492
9771
|
const up = report?.upstash ?? {};
|
|
8493
9772
|
const fga = report?.fga ?? {};
|
|
8494
9773
|
const ghosts = report?.ghosts ?? {};
|
|
8495
|
-
console.log(
|
|
8496
|
-
console.log(
|
|
9774
|
+
console.log(import_chalk50.default.bold(applied ? "\nExternal-residue sweep" : "\nExternal residue (dry-run \u2014 nothing deleted)"));
|
|
9775
|
+
console.log(import_chalk50.default.bold("\n Upstash"));
|
|
8497
9776
|
const orphans = up.orphans ?? [];
|
|
8498
|
-
if (orphans.length === 0) console.log(
|
|
8499
|
-
for (const o of orphans) console.log(` ${
|
|
8500
|
-
console.log(
|
|
9777
|
+
if (orphans.length === 0) console.log(import_chalk50.default.green(" no orphaned keys"));
|
|
9778
|
+
for (const o of orphans) console.log(` ${import_chalk50.default.yellow(o.key)} ${import_chalk50.default.dim(`\u2014 ${o.reason}`)}`);
|
|
9779
|
+
console.log(import_chalk50.default.dim(` kept (live principals): ${up.kept ?? 0} \xB7 anon wallets (untouched): ${up.anon_wallets ?? 0}`));
|
|
8501
9780
|
if (up.anon_wallet_detail) {
|
|
8502
9781
|
const d = up.anon_wallet_detail;
|
|
8503
|
-
console.log(
|
|
9782
|
+
console.log(import_chalk50.default.dim(` anon wallets: ${d.count} ($${(d.total_cents / 100).toFixed(2)}), ${d.no_ttl} with NO TTL${d.no_ttl ? " \u26A0" : " (all self-expire)"}`));
|
|
8504
9783
|
}
|
|
8505
9784
|
if (up.keyspace_census) {
|
|
8506
9785
|
const census = Object.entries(up.keyspace_census).map(([k, v]) => `${k}=${v}`).join(" \xB7 ");
|
|
8507
|
-
console.log(
|
|
9786
|
+
console.log(import_chalk50.default.dim(` keyspace: ${census}`));
|
|
8508
9787
|
}
|
|
8509
|
-
if (up.unknown?.length) console.log(
|
|
8510
|
-
if (applied) console.log(` ${
|
|
8511
|
-
for (const e of up.errors ?? []) console.log(
|
|
8512
|
-
console.log(
|
|
9788
|
+
if (up.unknown?.length) console.log(import_chalk50.default.dim(` unknown (never deleted): ${up.unknown.join(", ")}`));
|
|
9789
|
+
if (applied) console.log(` ${import_chalk50.default.bold(String(up.deleted ?? 0))} key(s) deleted`);
|
|
9790
|
+
for (const e of up.errors ?? []) console.log(import_chalk50.default.red(` error: ${e}`));
|
|
9791
|
+
console.log(import_chalk50.default.bold("\n OpenFGA / Neon \u2014 orphan stores"));
|
|
8513
9792
|
if (applied) {
|
|
8514
9793
|
const swept = fga?.swept ?? [];
|
|
8515
|
-
if (swept.length === 0) console.log(
|
|
9794
|
+
if (swept.length === 0) console.log(import_chalk50.default.green(" no orphaned stores"));
|
|
8516
9795
|
for (const s of swept) {
|
|
8517
9796
|
console.log(
|
|
8518
|
-
` ${
|
|
9797
|
+
` ${import_chalk50.default.yellow(s.store_id)} ${import_chalk50.default.dim(`\u2014 store ${s.openfga_deleted ? "deleted" : "DEFERRED"}, ${s.neon_deleted} Neon tuple(s) purged`)}`
|
|
8519
9798
|
);
|
|
8520
9799
|
}
|
|
8521
|
-
if (fga?.remaining) console.log(
|
|
9800
|
+
if (fga?.remaining) console.log(import_chalk50.default.yellow(` ${fga.remaining} more orphan store(s) \u2014 re-run to drain`));
|
|
8522
9801
|
const st = fga?.side_tables;
|
|
8523
|
-
if (st) console.log(
|
|
9802
|
+
if (st) console.log(import_chalk50.default.dim(` Neon side-tables purged: ${st.soft_deleted_stores} store records, ${st.orphan_models} models, ${st.orphan_changelog} changelog rows${st.error ? ` (${st.error})` : ""}`));
|
|
8524
9803
|
} else {
|
|
8525
9804
|
const fgaOrphans = fga?.orphans ?? [];
|
|
8526
|
-
if (fgaOrphans.length === 0) console.log(
|
|
9805
|
+
if (fgaOrphans.length === 0) console.log(import_chalk50.default.green(" no orphaned stores"));
|
|
8527
9806
|
for (const s of fgaOrphans) {
|
|
8528
9807
|
const src = s.in_openfga ? "live in OpenFGA" : "Neon tuples only";
|
|
8529
|
-
console.log(` ${
|
|
9808
|
+
console.log(` ${import_chalk50.default.yellow(s.store_id)} ${import_chalk50.default.dim(`\u2014 ${src}${s.name ? ` (${s.name})` : ""}, ${s.neon_tuples} Neon tuple(s)`)}`);
|
|
8530
9809
|
}
|
|
8531
|
-
console.log(
|
|
9810
|
+
console.log(import_chalk50.default.dim(` kept stores: ${(fga?.kept_store_ids ?? []).length}`));
|
|
8532
9811
|
const st = fga?.side_tables;
|
|
8533
|
-
if (st) console.log(
|
|
9812
|
+
if (st) console.log(import_chalk50.default.dim(` Neon side-table residue: ${st.soft_deleted_stores} soft-deleted store records, ${st.orphan_models} orphan models, ${st.orphan_changelog} orphan changelog rows`));
|
|
8534
9813
|
}
|
|
8535
|
-
for (const e of fga?.errors ?? []) console.log(
|
|
8536
|
-
console.log(
|
|
9814
|
+
for (const e of fga?.errors ?? []) console.log(import_chalk50.default.red(` error: ${e}`));
|
|
9815
|
+
console.log(import_chalk50.default.bold("\n OpenFGA \u2014 ghost tuples in surviving stores"));
|
|
8537
9816
|
if (applied) {
|
|
8538
|
-
if ((ghosts?.ghost_count ?? 0) === 0) console.log(
|
|
8539
|
-
else console.log(` ${
|
|
9817
|
+
if ((ghosts?.ghost_count ?? 0) === 0) console.log(import_chalk50.default.green(" no ghost tuples"));
|
|
9818
|
+
else console.log(` ${import_chalk50.default.bold(String(ghosts.deleted ?? 0))} ghost tuple(s) deleted ${import_chalk50.default.dim(`(of ${ghosts.ghost_count} found, ${ghosts.scanned_tuples} scanned across ${ghosts.live_stores} live stores)`)}`);
|
|
8540
9819
|
} else {
|
|
8541
9820
|
const n = ghosts?.ghost_count ?? 0;
|
|
8542
|
-
if (n === 0) console.log(
|
|
9821
|
+
if (n === 0) console.log(import_chalk50.default.green(` no ghost tuples ${import_chalk50.default.dim(`(${ghosts.scanned_tuples ?? 0} scanned across ${ghosts.live_stores ?? 0} live stores)`)}`));
|
|
8543
9822
|
else {
|
|
8544
|
-
console.log(
|
|
9823
|
+
console.log(import_chalk50.default.yellow(` ${n} ghost tuple(s) referencing entities absent from D1:`));
|
|
8545
9824
|
for (const g of (ghosts.ghosts ?? []).slice(0, 20)) {
|
|
8546
|
-
console.log(
|
|
9825
|
+
console.log(import_chalk50.default.dim(` ${g.object_type}:${g.object_id} ${g.relation} ${g._user}`));
|
|
8547
9826
|
}
|
|
8548
|
-
if (n > 20) console.log(
|
|
9827
|
+
if (n > 20) console.log(import_chalk50.default.dim(` \u2026 and ${n - 20} more`));
|
|
8549
9828
|
}
|
|
8550
9829
|
}
|
|
8551
|
-
for (const e of ghosts?.errors ?? []) console.log(
|
|
9830
|
+
for (const e of ghosts?.errors ?? []) console.log(import_chalk50.default.red(` error: ${e}`));
|
|
8552
9831
|
console.log();
|
|
8553
9832
|
}
|
|
8554
9833
|
async function runOp(sub, opts = {}) {
|
|
8555
9834
|
if (!loadCredentials()) {
|
|
8556
|
-
console.log(
|
|
9835
|
+
console.log(import_chalk50.default.dim("Not logged in. Run `apiblaze login`."));
|
|
8557
9836
|
return;
|
|
8558
9837
|
}
|
|
8559
9838
|
if (!isOperatorLogin()) {
|
|
8560
|
-
console.log(
|
|
9839
|
+
console.log(import_chalk50.default.dim("`apiblaze op` is only available to platform operators."));
|
|
8561
9840
|
return;
|
|
8562
9841
|
}
|
|
8563
9842
|
switch (sub) {
|
|
8564
9843
|
case void 0:
|
|
8565
9844
|
case "menu": {
|
|
8566
|
-
console.log(
|
|
8567
|
-
console.log(` ${
|
|
8568
|
-
console.log(` ${
|
|
8569
|
-
console.log(` ${
|
|
8570
|
-
console.log(
|
|
9845
|
+
console.log(import_chalk50.default.bold("\nOperator menu"));
|
|
9846
|
+
console.log(` ${import_chalk50.default.cyan("apiblaze op residue")} external-store residue report (Upstash + Neon/OpenFGA, dry-run)`);
|
|
9847
|
+
console.log(` ${import_chalk50.default.cyan("apiblaze op sweep")} delete the orphans the report shows (asks first; ${import_chalk50.default.dim("-y to skip")})`);
|
|
9848
|
+
console.log(` ${import_chalk50.default.cyan("apiblaze op credits")} list credit wallets`);
|
|
9849
|
+
console.log(import_chalk50.default.dim(` (to prune all non-CP data: run scripts/nuke-but-cp.sh --apply --sweep in the repo)
|
|
8571
9850
|
`));
|
|
8572
9851
|
return;
|
|
8573
9852
|
}
|
|
@@ -8586,17 +9865,17 @@ async function runOp(sub, opts = {}) {
|
|
|
8586
9865
|
const nSide = (st.soft_deleted_stores ?? 0) + (st.orphan_models ?? 0) + (st.orphan_changelog ?? 0);
|
|
8587
9866
|
printResidue(report, false);
|
|
8588
9867
|
if (nUp + nFga + nGhost + nSide === 0) {
|
|
8589
|
-
console.log(
|
|
9868
|
+
console.log(import_chalk50.default.green("Nothing to sweep."));
|
|
8590
9869
|
return;
|
|
8591
9870
|
}
|
|
8592
9871
|
if (!opts.yes) {
|
|
8593
9872
|
const readline3 = await import("readline/promises");
|
|
8594
9873
|
const rl = readline3.createInterface({ input: process.stdin, output: process.stdout });
|
|
8595
9874
|
const answer = await rl.question(
|
|
8596
|
-
|
|
9875
|
+
import_chalk50.default.red(`Delete ${nUp} Upstash key(s) + ${nFga} OpenFGA store(s) + ${nGhost} ghost tuple(s) + ${nSide} Neon side-table row(s)? Type 'sweep' to confirm: `)
|
|
8597
9876
|
);
|
|
8598
9877
|
rl.close();
|
|
8599
|
-
if (answer.trim() !== "sweep") return void console.log(
|
|
9878
|
+
if (answer.trim() !== "sweep") return void console.log(import_chalk50.default.dim("Aborted."));
|
|
8600
9879
|
}
|
|
8601
9880
|
const result = await opCall({ method: "POST", path: "/operator/external-residue/sweep", summary: "external residue sweep" });
|
|
8602
9881
|
if (opts.json) return void console.log(JSON.stringify(result, null, 2));
|
|
@@ -8607,15 +9886,15 @@ async function runOp(sub, opts = {}) {
|
|
|
8607
9886
|
const data = await opCall({ method: "GET", path: "/operator/credits", summary: "list credit wallets" });
|
|
8608
9887
|
if (opts.json) return void console.log(JSON.stringify(data, null, 2));
|
|
8609
9888
|
const accounts = data?.accounts ?? [];
|
|
8610
|
-
if (accounts.length === 0) return void console.log(
|
|
9889
|
+
if (accounts.length === 0) return void console.log(import_chalk50.default.dim("No credit wallets."));
|
|
8611
9890
|
for (const a of accounts) {
|
|
8612
9891
|
const bal = typeof a.balance_cents === "number" ? `$${(a.balance_cents / 100).toFixed(2)}` : "?";
|
|
8613
|
-
console.log(` ${
|
|
9892
|
+
console.log(` ${import_chalk50.default.bold(bal.padStart(9))} ${a.walletId}${a.owner_email ? import_chalk50.default.dim(` \u2014 ${a.owner_email}`) : a.anon ? import_chalk50.default.dim(" \u2014 anon") : ""}`);
|
|
8614
9893
|
}
|
|
8615
9894
|
return;
|
|
8616
9895
|
}
|
|
8617
9896
|
default:
|
|
8618
|
-
console.log(
|
|
9897
|
+
console.log(import_chalk50.default.red(`Unknown op subcommand '${sub}'. Run \`apiblaze op\` for the menu.`));
|
|
8619
9898
|
}
|
|
8620
9899
|
}
|
|
8621
9900
|
|
|
@@ -8678,7 +9957,7 @@ program.command("dev").description("Put your localhost behind a public URL (dev
|
|
|
8678
9957
|
try {
|
|
8679
9958
|
const resolved = parseInt(port ?? opts.port, 10);
|
|
8680
9959
|
if (Number.isNaN(resolved)) {
|
|
8681
|
-
console.error(
|
|
9960
|
+
console.error(import_chalk51.default.red(`Invalid port: ${port ?? opts.port}`));
|
|
8682
9961
|
process.exit(1);
|
|
8683
9962
|
}
|
|
8684
9963
|
await runDev({ port: resolved, project: opts.project, yes: opts.yes, captureFile: opts.captureFile, newSession: opts.newSession });
|
|
@@ -8727,6 +10006,11 @@ program.command("projects").description("List the projects in your team").action
|
|
|
8727
10006
|
process.exit(1);
|
|
8728
10007
|
}
|
|
8729
10008
|
});
|
|
10009
|
+
program.command("search").description("Find a published recipe \u2014 a whole working proxy someone else set up").argument("[query]", "Words to match against a recipe name or summary").option("--limit <n>", "Maximum results (default 25)").option("--json", "Output machine-readable JSON").action(action((query, opts) => runRecipeSearch(query, opts)));
|
|
10010
|
+
program.command("show").description("Read a recipe before you install it (upstreams, questions, transforms, spec)").argument("<recipe>", "Recipe name, e.g. @julien/gmail or @julien/gmail@2").option("--settings", "Print the settings section").option("--auth", "Print the auth section (login provider, key types)").option("--transforms", "Print the transform rules and mapping tables").option("--openapi", "Print the OpenAPI document").option("--json", "Output machine-readable JSON").action(action((recipe, opts) => runRecipeShow(recipe, opts)));
|
|
10011
|
+
program.command("install").description("Create a proxy from a recipe, with your own credentials").argument("<recipe>", "Recipe name, e.g. @julien/gmail or @julien/gmail@2 to pin a revision").option("--as <name>", "Proxy name (skips the name prompt)").option("--into <proxy>", "Reinstall over an existing proxy \u2014 replaces its settings, transforms and spec").option("--team <id|name>", "Team the proxy is created in (you are asked when you belong to several)").option("--apiversion <v>", "API version of the proxy named by --into").option("-y, --yes", "Skip the routine prompts (installs WITHOUT anything needing consent)").action(action((recipe, opts) => runRecipeInstall(recipe, opts)));
|
|
10012
|
+
program.command("publish").description("Publish one of your proxies as a recipe others can install").argument("<project>", "Project name or id (see `apiblaze projects`)").option("--as <name>", "Recipe name. Defaults to your GitHub handle + the proxy name; pass `pokedex` for just the name, or `@acme/pokedex` to set both").option("--private", "Only your team can see or install it (default is public)").option("--tenant <slug>", "Which tenant's auth setup the recipe carries (asked when a proxy has several)").option("--display-name <text>", "Human-readable title").option("--summary <text>", "One-line description shown in search").option("--license <id>", "Licence identifier, e.g. MIT").option("--team <id|name>", "Team the project is in (defaults to active team)").option("--apiversion <v>", "API version to publish (defaults to the first match)").option("-y, --yes", "Skip routine confirmations (never accepts scanner findings)").option("--json", "Output machine-readable JSON").action(action((project, opts) => runRecipePublish(project, opts)));
|
|
10013
|
+
program.command("withdraw").description("Permanently delete one published recipe revision").argument("<recipe>", "Recipe revision, e.g. @julien/gmail@3").option("--team <id|name>", "Team that published it (defaults to active team)").option("--json", "Output machine-readable JSON").action(action((recipe, opts) => runRecipeWithdraw(recipe, opts)));
|
|
8730
10014
|
program.command("delete").description("Delete a proxy and everything under it (asks first)").argument("<project>", "Project name or id (see `apiblaze projects`)").argument("[version]", "API version (defaults to the first match)").option("--team <id|name>", "Team the project is in (defaults to active team)").option("-y, --yes", "Skip the confirmation prompt").option("--json", "Output machine-readable JSON").action(action((project, version2, opts) => runDelete(project, version2, opts)));
|
|
8731
10015
|
program.command("export").description("Export config and data for migration out of APIblaze (Kong, ...)").argument("<project>", "Project name or id (see `apiblaze projects`)").argument("[version]", "API version (defaults to the first match)").option("--kong", "Produce a runnable Kong OSS bundle (decK config + plugins + docker-compose)").option("--data", "Plain data export (default)").option("--secrets", "Include decrypted producer-supplied secrets (member role; audit-logged)").option("--keys <mode>", "API-key export: hashes (default, consumers keep keys) | mint | none").option("--no-consumers", "Skip the end-user lane (users, groups, keys)").option("-o, --out <file>", "Output zip path").option("--team <id|name>", "Team the project is in (defaults to active team)").action(action((project, version2, opts) => runExport(project, version2, { ...opts, noConsumers: opts.consumers === false })));
|
|
8732
10016
|
var logs = program.command("logs").description("API Producer view \u2014 stream this proxy's requests from EVERY caller live (see `apiblaze logs list` to browse past days)").argument("<project>", "Project name or id (see `apiblaze projects`)").argument("[version]", "API version (defaults to the first match)").option("--tenant <slug>", "Tenant to tail (auto when one is attached; prompted otherwise)").option("--errors-only", "Only show 4xx/5xx responses").option("--json", "Emit raw NDJSON rows (pipe into jq)").option("--team <id|name>", "Team the project is in (defaults to active team)").action(action((project, version2, opts) => runLogsTail(project, version2, opts)));
|
|
@@ -8782,6 +10066,7 @@ spec.command("delete-rule").description("Delete the saved rules for a route (e.g
|
|
|
8782
10066
|
var HELP_GROUPS = [
|
|
8783
10067
|
{ title: "Chat", commands: ["apichat", "agent"] },
|
|
8784
10068
|
{ title: "Setup", commands: ["login", "create", "init", "sidecar", "dev", "claim", "team", "whoami", "logout"] },
|
|
10069
|
+
{ title: "Recipes \u2014 install a working setup, or publish yours", commands: ["search", "show", "install", "publish", "withdraw"] },
|
|
8785
10070
|
{ title: "Control plane commands", commands: ["config", "projects", "logs", "tenant", "group", "admins", "iam", "identified", "preapprove", "rule", "domain", "delete", "target", "throttle", "rename", "spec", "export"] },
|
|
8786
10071
|
{ title: "Data plane commands", commands: [
|
|
8787
10072
|
{ parent: "consumer", sub: "login" },
|
|
@@ -8802,7 +10087,7 @@ function groupedCommandHelp() {
|
|
|
8802
10087
|
const sub = byName.get(e.parent)?.commands.find((s) => s.name() === e.sub);
|
|
8803
10088
|
return sub ? ` ${helpLabel(e).padEnd(width)}${sub.description()}` : "";
|
|
8804
10089
|
}).filter(Boolean).join("\n");
|
|
8805
|
-
return `${
|
|
10090
|
+
return `${import_chalk51.default.bold(g.title)}
|
|
8806
10091
|
${rows}`;
|
|
8807
10092
|
}).join("\n\n");
|
|
8808
10093
|
}
|
|
@@ -8826,6 +10111,9 @@ Examples:
|
|
|
8826
10111
|
$ npx apiblaze agent # just chat
|
|
8827
10112
|
$ npx apiblaze create --target https://api.example.com # one-line API
|
|
8828
10113
|
$ npx apiblaze create --openapi https://pokeapi.co/openapi.yaml # build it from a spec URL
|
|
10114
|
+
$ npx apiblaze search gmail # find a recipe (no login needed)
|
|
10115
|
+
$ npx apiblaze install @julien/gmail # someone's whole working setup, your credentials
|
|
10116
|
+
$ npx apiblaze publish mygmail # share yours back, as @yourgithubhandle/mygmail
|
|
8829
10117
|
$ npx apiblaze dev 3000 # localhost \u2192 public URL
|
|
8830
10118
|
$ npx apiblaze throttle myapi --rate 50 --verbose # configure + show the API call
|
|
8831
10119
|
$ npx apiblaze consumer login # act as a consumer of your API
|
|
@@ -8837,14 +10125,14 @@ async function recoverStaleTeam() {
|
|
|
8837
10125
|
const { resolveLinkedTeam: resolveLinkedTeam2 } = await Promise.resolve().then(() => (init_team(), team_exports));
|
|
8838
10126
|
const linked = await resolveLinkedTeam2({ preferredId: creds.teamId, interactive: !!process.stdin.isTTY });
|
|
8839
10127
|
if (!linked) {
|
|
8840
|
-
console.error(
|
|
10128
|
+
console.error(import_chalk51.default.yellow("Your account has no teams anymore (deleted?). Run `apiblaze login` or `apiblaze create` to get a workspace."));
|
|
8841
10129
|
return;
|
|
8842
10130
|
}
|
|
8843
10131
|
if (linked.teamId === creds.teamId) return;
|
|
8844
10132
|
const next = { ...creds, teamId: linked.teamId, teamName: linked.teamName };
|
|
8845
10133
|
delete next.activeTenant;
|
|
8846
10134
|
saveCredentials(next);
|
|
8847
|
-
console.error(
|
|
10135
|
+
console.error(import_chalk51.default.yellow(`Your previous team no longer exists \u2014 relinked to ${import_chalk51.default.bold(linked.teamName ?? linked.teamId)}. Re-run your command.`));
|
|
8848
10136
|
} catch {
|
|
8849
10137
|
}
|
|
8850
10138
|
}
|
|
@@ -8852,16 +10140,16 @@ async function printError(err) {
|
|
|
8852
10140
|
if (err instanceof ApiError) {
|
|
8853
10141
|
const data = err.body;
|
|
8854
10142
|
const extra = [data?.body?.reason, data?.body?.details, data?.details, data?.body?.error].find((x) => typeof x === "string" && x && x !== err.message);
|
|
8855
|
-
console.error(
|
|
10143
|
+
console.error(import_chalk51.default.red(`
|
|
8856
10144
|
API error (${err.status}): ${err.message}${extra ? ` \u2014 ${extra}` : ""}`));
|
|
8857
10145
|
if (err.status === 403 || err.status === 404) {
|
|
8858
10146
|
await recoverStaleTeam();
|
|
8859
10147
|
}
|
|
8860
10148
|
} else if (err instanceof Error) {
|
|
8861
|
-
console.error(
|
|
10149
|
+
console.error(import_chalk51.default.red(`
|
|
8862
10150
|
Error: ${err.message}`));
|
|
8863
10151
|
} else {
|
|
8864
|
-
console.error(
|
|
10152
|
+
console.error(import_chalk51.default.red("\nUnknown error"));
|
|
8865
10153
|
}
|
|
8866
10154
|
}
|
|
8867
10155
|
program.parse(process.argv);
|