apiblaze 0.21.3 → 0.21.5
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 +1240 -773
- package/dist/server/index.js +10 -1
- package/dist/server/index.mjs +10 -1
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -591,6 +591,37 @@ var init_admin = __esm({
|
|
|
591
591
|
}
|
|
592
592
|
});
|
|
593
593
|
|
|
594
|
+
// src/lib/paging.ts
|
|
595
|
+
async function pageAll(fetchPage3, pick2) {
|
|
596
|
+
const items = [];
|
|
597
|
+
let cursor = null;
|
|
598
|
+
let total = 0;
|
|
599
|
+
for (let hop = 0; hop < MAX_HOPS; hop++) {
|
|
600
|
+
const body = await fetchPage3(cursor);
|
|
601
|
+
items.push(...pick2(body) ?? []);
|
|
602
|
+
total = body?.page?.total ?? body?.total ?? items.length;
|
|
603
|
+
cursor = body?.page?.next_cursor ?? null;
|
|
604
|
+
if (!cursor) return { items, total, truncated: false };
|
|
605
|
+
}
|
|
606
|
+
return { items, total, truncated: true };
|
|
607
|
+
}
|
|
608
|
+
function pageQuery(cursor, limit = PAGE_LIMIT) {
|
|
609
|
+
const qs = new URLSearchParams({ limit: String(limit) });
|
|
610
|
+
if (cursor) qs.set("cursor", cursor);
|
|
611
|
+
return qs.toString();
|
|
612
|
+
}
|
|
613
|
+
function withPage(path11, cursor, limit = PAGE_LIMIT) {
|
|
614
|
+
return `${path11}${path11.includes("?") ? "&" : "?"}${pageQuery(cursor, limit)}`;
|
|
615
|
+
}
|
|
616
|
+
var PAGE_LIMIT, MAX_HOPS;
|
|
617
|
+
var init_paging = __esm({
|
|
618
|
+
"src/lib/paging.ts"() {
|
|
619
|
+
"use strict";
|
|
620
|
+
PAGE_LIMIT = 200;
|
|
621
|
+
MAX_HOPS = 50;
|
|
622
|
+
}
|
|
623
|
+
});
|
|
624
|
+
|
|
594
625
|
// src/lib/caller.ts
|
|
595
626
|
function resolveCaller() {
|
|
596
627
|
const creds = loadCredentials();
|
|
@@ -629,12 +660,15 @@ async function producer(caller, call) {
|
|
|
629
660
|
return out;
|
|
630
661
|
}
|
|
631
662
|
async function resolveProjectVia(caller, teamId, nameOrId, version2) {
|
|
632
|
-
const
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
|
|
663
|
+
const walked = await pageAll(
|
|
664
|
+
(cursor) => producer(caller, {
|
|
665
|
+
method: "GET",
|
|
666
|
+
path: withPage(`/projects?team_id=${encodeURIComponent(teamId)}`, cursor),
|
|
667
|
+
summary: `List projects for ${teamId}`
|
|
668
|
+
}),
|
|
669
|
+
(b) => b?.projects
|
|
670
|
+
);
|
|
671
|
+
const rows = walked.items;
|
|
638
672
|
const candidates = rows.filter((r) => r.project_id === nameOrId || r.project_display_name === nameOrId || r.display_name === nameOrId);
|
|
639
673
|
if (!candidates.length) {
|
|
640
674
|
throw new Error(
|
|
@@ -685,6 +719,7 @@ var init_caller = __esm({
|
|
|
685
719
|
init_auth();
|
|
686
720
|
init_anon_cred();
|
|
687
721
|
init_admin();
|
|
722
|
+
init_paging();
|
|
688
723
|
init_trace();
|
|
689
724
|
init_resolve();
|
|
690
725
|
APIKEYS_BASE = process.env.APIBLAZE_APIKEYS_BASE || "https://apikeys.apiblaze.com";
|
|
@@ -705,12 +740,15 @@ async function resolveTenant(caller, teamId, opts) {
|
|
|
705
740
|
if (opts.apiblaze) return opTenantForTeam(teamId);
|
|
706
741
|
let tenants = [];
|
|
707
742
|
try {
|
|
708
|
-
const
|
|
709
|
-
|
|
710
|
-
|
|
711
|
-
|
|
712
|
-
|
|
713
|
-
|
|
743
|
+
const walked = await pageAll(
|
|
744
|
+
(cursor) => producer(caller, {
|
|
745
|
+
method: "GET",
|
|
746
|
+
path: withPage(`/teams/${encodeURIComponent(teamId)}/tenants`, cursor),
|
|
747
|
+
summary: "List the team tenants"
|
|
748
|
+
}),
|
|
749
|
+
(b) => b?.tenants
|
|
750
|
+
);
|
|
751
|
+
tenants = walked.items.map((t) => t.tenant_name ?? t.name ?? "").filter(Boolean);
|
|
714
752
|
} catch {
|
|
715
753
|
}
|
|
716
754
|
if (tenants.length === 1) return tenants[0];
|
|
@@ -784,6 +822,7 @@ var init_allow_oauth_registration = __esm({
|
|
|
784
822
|
"use strict";
|
|
785
823
|
import_chalk11 = __toESM(require("chalk"));
|
|
786
824
|
init_caller();
|
|
825
|
+
init_paging();
|
|
787
826
|
}
|
|
788
827
|
});
|
|
789
828
|
|
|
@@ -1019,15 +1058,18 @@ async function runIdentifiedToggle(project, mode, opts) {
|
|
|
1019
1058
|
require2 ? "Requiring identified traffic..." : "Allowing unattributed traffic..."
|
|
1020
1059
|
).start();
|
|
1021
1060
|
try {
|
|
1022
|
-
const listing = await
|
|
1023
|
-
|
|
1024
|
-
|
|
1025
|
-
|
|
1026
|
-
|
|
1027
|
-
|
|
1061
|
+
const listing = await pageAll(
|
|
1062
|
+
(cursor) => producer(caller, {
|
|
1063
|
+
method: "GET",
|
|
1064
|
+
path: withPage(`/projects?team_id=${encodeURIComponent(teamId)}`, cursor),
|
|
1065
|
+
summary: `Read settings for ${proj2.projectName}`
|
|
1066
|
+
}),
|
|
1067
|
+
(b) => b?.projects
|
|
1068
|
+
);
|
|
1069
|
+
const row = listing.items.find(
|
|
1028
1070
|
(r) => r.project_id === proj2.projectId && r.api_version === proj2.apiVersion
|
|
1029
1071
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
1030
|
-
) ??
|
|
1072
|
+
) ?? listing.items.find((r) => r.project_id === proj2.projectId);
|
|
1031
1073
|
const current = row?.config?.requests_policy ?? {};
|
|
1032
1074
|
const merged = {
|
|
1033
1075
|
mode: current.mode ?? "passthrough",
|
|
@@ -1058,6 +1100,7 @@ var init_iam = __esm({
|
|
|
1058
1100
|
import_chalk15 = __toESM(require("chalk"));
|
|
1059
1101
|
import_ora5 = __toESM(require("ora"));
|
|
1060
1102
|
init_caller();
|
|
1103
|
+
init_paging();
|
|
1061
1104
|
}
|
|
1062
1105
|
});
|
|
1063
1106
|
|
|
@@ -1073,7 +1116,7 @@ async function createTenantRow(teamId) {
|
|
|
1073
1116
|
const { name } = await inquirer3.prompt([{
|
|
1074
1117
|
type: "input",
|
|
1075
1118
|
name: "name",
|
|
1076
|
-
message: `Tenant name ${
|
|
1119
|
+
message: `Tenant name ${import_chalk44.default.dim("(lowercase letters/numbers; globally unique \u2014 becomes {name}.portal.apiblaze.com)")}:`,
|
|
1077
1120
|
validate: (s) => /^[a-z0-9]+$/.test(s.trim()) ? true : "lowercase letters and numbers only"
|
|
1078
1121
|
}]);
|
|
1079
1122
|
const slug = name.trim();
|
|
@@ -1088,7 +1131,7 @@ async function createTenantRow(teamId) {
|
|
|
1088
1131
|
} catch (err) {
|
|
1089
1132
|
const msg = err instanceof Error ? err.message : String(err);
|
|
1090
1133
|
if (/taken|unique|exists|reserved|conflict/i.test(msg)) {
|
|
1091
|
-
console.log(
|
|
1134
|
+
console.log(import_chalk44.default.yellow(` "${slug}" is not available (tenant names are global): ${msg}`));
|
|
1092
1135
|
continue;
|
|
1093
1136
|
}
|
|
1094
1137
|
throw err;
|
|
@@ -1105,11 +1148,11 @@ async function addApiblazeHostedLogin(teamId, tenant2, opts = {}) {
|
|
|
1105
1148
|
pageSize: 10,
|
|
1106
1149
|
choices: [
|
|
1107
1150
|
...PROVIDERS.map((p) => ({ name: p.label, value: p.id })),
|
|
1108
|
-
...opts.allowSkip ? [new inquirer3.Separator(), { name:
|
|
1151
|
+
...opts.allowSkip ? [new inquirer3.Separator(), { name: import_chalk44.default.dim("Skip for now \u2014 set up a login method later"), value: null }] : []
|
|
1109
1152
|
]
|
|
1110
1153
|
}]);
|
|
1111
1154
|
if (!provider) {
|
|
1112
|
-
console.log(
|
|
1155
|
+
console.log(import_chalk44.default.dim(" No login method yet \u2014 add one later under Login methods."));
|
|
1113
1156
|
return false;
|
|
1114
1157
|
}
|
|
1115
1158
|
const opt = PROVIDERS.find((p) => p.id === provider);
|
|
@@ -1155,26 +1198,26 @@ async function addApiblazeHostedLogin(teamId, tenant2, opts = {}) {
|
|
|
1155
1198
|
async function createTenantInteractive(teamId) {
|
|
1156
1199
|
const { default: inquirer3 } = await import("inquirer");
|
|
1157
1200
|
const tenant2 = await createTenantRow(teamId);
|
|
1158
|
-
console.log(
|
|
1201
|
+
console.log(import_chalk44.default.green(` Tenant ${import_chalk44.default.bold(tenant2)} created.`));
|
|
1159
1202
|
const { users } = await inquirer3.prompt([{
|
|
1160
1203
|
type: "confirm",
|
|
1161
1204
|
name: "users",
|
|
1162
1205
|
default: false,
|
|
1163
|
-
message: `Enable Users & groups? ${
|
|
1206
|
+
message: `Enable Users & groups? ${import_chalk44.default.dim(`(identity/key management at iam.apiblaze.com)`)}`
|
|
1164
1207
|
}]);
|
|
1165
1208
|
if (users) {
|
|
1166
1209
|
await admin({ method: "PATCH", path: `/teams/${encodeURIComponent(teamId)}/tenants/${encodeURIComponent(tenant2)}/iam`, body: { enabled: true }, summary: "Enable Users & groups" }).catch(() => {
|
|
1167
1210
|
});
|
|
1168
|
-
console.log(
|
|
1211
|
+
console.log(import_chalk44.default.green(" Users & groups enabled."));
|
|
1169
1212
|
}
|
|
1170
1213
|
await addApiblazeHostedLogin(teamId, tenant2, { allowSkip: true });
|
|
1171
1214
|
return tenant2;
|
|
1172
1215
|
}
|
|
1173
|
-
var
|
|
1216
|
+
var import_chalk44, import_ora17, PROVIDERS, DEFAULT_SCOPES;
|
|
1174
1217
|
var init_tenant_create = __esm({
|
|
1175
1218
|
"src/lib/tenant-create.ts"() {
|
|
1176
1219
|
"use strict";
|
|
1177
|
-
|
|
1220
|
+
import_chalk44 = __toESM(require("chalk"));
|
|
1178
1221
|
import_ora17 = __toESM(require("ora"));
|
|
1179
1222
|
init_admin();
|
|
1180
1223
|
PROVIDERS = [
|
|
@@ -1213,10 +1256,10 @@ async function fetchPage2(teamId, q) {
|
|
|
1213
1256
|
}
|
|
1214
1257
|
function label(t, defaultTenant, active) {
|
|
1215
1258
|
const tags = [
|
|
1216
|
-
t.tenant_name === active ?
|
|
1217
|
-
t.tenant_name === defaultTenant ?
|
|
1259
|
+
t.tenant_name === active ? import_chalk45.default.cyan("active scope") : "",
|
|
1260
|
+
t.tenant_name === defaultTenant ? import_chalk45.default.dim("team default") : ""
|
|
1218
1261
|
].filter(Boolean).join(", ");
|
|
1219
|
-
const disp = t.display_name && t.display_name !== t.tenant_name ?
|
|
1262
|
+
const disp = t.display_name && t.display_name !== t.tenant_name ? import_chalk45.default.dim(` ${t.display_name}`) : "";
|
|
1220
1263
|
return `${t.tenant_name}${disp}${tags ? ` (${tags})` : ""}`;
|
|
1221
1264
|
}
|
|
1222
1265
|
async function pickTenant(teamId, opts = {}) {
|
|
@@ -1231,7 +1274,7 @@ async function pickTenant(teamId, opts = {}) {
|
|
|
1231
1274
|
const { make } = await inquirer3.prompt([{ type: "confirm", name: "make", message: "No tenants yet \u2014 create one?", default: true }]);
|
|
1232
1275
|
if (make) return await createTenantInline(teamId);
|
|
1233
1276
|
}
|
|
1234
|
-
console.error(
|
|
1277
|
+
console.error(import_chalk45.default.red("This team has no tenants. Create one with `apiblaze tenant create`."));
|
|
1235
1278
|
return null;
|
|
1236
1279
|
}
|
|
1237
1280
|
const truncated = page.total > page.rows.length;
|
|
@@ -1240,7 +1283,7 @@ async function pickTenant(teamId, opts = {}) {
|
|
|
1240
1283
|
value: t.tenant_name
|
|
1241
1284
|
}));
|
|
1242
1285
|
if (truncated || q) {
|
|
1243
|
-
choices.push(new inquirer3.Separator(
|
|
1286
|
+
choices.push(new inquirer3.Separator(import_chalk45.default.dim(
|
|
1244
1287
|
truncated ? `showing ${page.rows.length} of ${page.total}${q ? ` matching "${q}"` : ""} \u2014 search to narrow` : `matches for "${q}"`
|
|
1245
1288
|
)));
|
|
1246
1289
|
choices.push({ name: `\u{1F50D} Search${q ? " again" : ""}\u2026`, value: "\0search" });
|
|
@@ -1274,11 +1317,11 @@ async function createTenantInline(teamId) {
|
|
|
1274
1317
|
const { createTenantInteractive: createTenantInteractive2 } = await Promise.resolve().then(() => (init_tenant_create(), tenant_create_exports));
|
|
1275
1318
|
return createTenantInteractive2(teamId);
|
|
1276
1319
|
}
|
|
1277
|
-
var
|
|
1320
|
+
var import_chalk45, import_ora18, PAGE;
|
|
1278
1321
|
var init_tenant_pick = __esm({
|
|
1279
1322
|
"src/lib/tenant-pick.ts"() {
|
|
1280
1323
|
"use strict";
|
|
1281
|
-
|
|
1324
|
+
import_chalk45 = __toESM(require("chalk"));
|
|
1282
1325
|
import_ora18 = __toESM(require("ora"));
|
|
1283
1326
|
init_admin();
|
|
1284
1327
|
init_auth();
|
|
@@ -1289,10 +1332,10 @@ var init_tenant_pick = __esm({
|
|
|
1289
1332
|
// src/index.ts
|
|
1290
1333
|
var import_fs5 = __toESM(require("fs"));
|
|
1291
1334
|
var import_commander = require("commander");
|
|
1292
|
-
var
|
|
1335
|
+
var import_chalk59 = __toESM(require("chalk"));
|
|
1293
1336
|
|
|
1294
1337
|
// package.json
|
|
1295
|
-
var version = "0.21.
|
|
1338
|
+
var version = "0.21.5";
|
|
1296
1339
|
|
|
1297
1340
|
// src/index.ts
|
|
1298
1341
|
init_types();
|
|
@@ -1674,11 +1717,11 @@ function cap(s) {
|
|
|
1674
1717
|
return s.charAt(0).toUpperCase() + s.slice(1);
|
|
1675
1718
|
}
|
|
1676
1719
|
function mcpHost(name, tenant2) {
|
|
1677
|
-
return
|
|
1720
|
+
return `${name}-${tenant2}.mcpblaze.com`;
|
|
1678
1721
|
}
|
|
1679
|
-
function mcpUrl(name, ver, env, opts
|
|
1722
|
+
function mcpUrl(name, ver, env, opts) {
|
|
1680
1723
|
if (BRAND === "mcpblaze") return `https://${mcpHost(name, opts.tenant)}/${ver}/${env}`;
|
|
1681
|
-
return `https://${name}.mcp.${opts.anon ? "tryabz" : "abz"}.run/${ver}/${env}`;
|
|
1724
|
+
return `https://${name}-${opts.tenant}.mcp.${opts.anon ? "tryabz" : "abz"}.run/${ver}/${env}`;
|
|
1682
1725
|
}
|
|
1683
1726
|
var COMMANDS = [
|
|
1684
1727
|
"create",
|
|
@@ -1754,6 +1797,7 @@ function installBrandFilter() {
|
|
|
1754
1797
|
}
|
|
1755
1798
|
|
|
1756
1799
|
// src/lib/tenant-choice.ts
|
|
1800
|
+
init_paging();
|
|
1757
1801
|
function isOpTenant(name) {
|
|
1758
1802
|
return /^opteam[a-z0-9]+$/.test(name);
|
|
1759
1803
|
}
|
|
@@ -1773,12 +1817,15 @@ async function chooseTenantForCreate(teamId, projectSlug, opts) {
|
|
|
1773
1817
|
if (!teamId) return void 0;
|
|
1774
1818
|
let existing = [];
|
|
1775
1819
|
try {
|
|
1776
|
-
const
|
|
1777
|
-
|
|
1778
|
-
|
|
1779
|
-
|
|
1780
|
-
|
|
1781
|
-
|
|
1820
|
+
const walked = await pageAll(
|
|
1821
|
+
(cursor) => admin({
|
|
1822
|
+
method: "GET",
|
|
1823
|
+
path: withPage(`/teams/${encodeURIComponent(teamId)}/tenants`, cursor),
|
|
1824
|
+
summary: "List tenants to place the new proxy"
|
|
1825
|
+
}),
|
|
1826
|
+
(b) => Array.isArray(b) ? b : b?.tenants
|
|
1827
|
+
);
|
|
1828
|
+
const list = walked.items;
|
|
1782
1829
|
existing = list.map((t) => t?.tenant_name ?? t?.name).filter((n) => typeof n === "string" && n.length > 0).filter((n) => !isOpTenant(n));
|
|
1783
1830
|
} catch {
|
|
1784
1831
|
return void 0;
|
|
@@ -2112,6 +2159,7 @@ init_auth();
|
|
|
2112
2159
|
init_admin();
|
|
2113
2160
|
init_resolve();
|
|
2114
2161
|
init_types();
|
|
2162
|
+
init_paging();
|
|
2115
2163
|
var DASHBOARD_BASE4 = process.env.APIBLAZE_DASHBOARD_BASE || "https://dashboard.apiblaze.com";
|
|
2116
2164
|
async function iamCall(tenant2, path11, method = "GET", body) {
|
|
2117
2165
|
const token = getAccessToken();
|
|
@@ -2129,8 +2177,11 @@ async function resolveProducerTenant(tenantOpt) {
|
|
|
2129
2177
|
const tenant2 = (tenantOpt ?? "").trim().toLowerCase();
|
|
2130
2178
|
if (tenant2) return tenant2;
|
|
2131
2179
|
const { teamId } = await resolveTeam(void 0);
|
|
2132
|
-
const
|
|
2133
|
-
|
|
2180
|
+
const walked = await pageAll(
|
|
2181
|
+
(cursor) => admin({ method: "GET", path: withPage(`/teams/${encodeURIComponent(teamId)}/tenants`, cursor), summary: "List tenants" }),
|
|
2182
|
+
(b) => b?.tenants
|
|
2183
|
+
);
|
|
2184
|
+
const tenants = walked.items.map((t) => String(t.tenant_name ?? t.name ?? t)).filter((t) => !isOpTenant(t));
|
|
2134
2185
|
if (tenants.length === 0) throw new Error("No tenants in your team yet. Create a proxy first, or pass --tenant.");
|
|
2135
2186
|
if (tenants.length === 1) return tenants[0];
|
|
2136
2187
|
const { default: inquirer3 } = await import("inquirer");
|
|
@@ -2781,6 +2832,7 @@ async function installAndDemo(cli, spec2, getQuestion, log = console.log) {
|
|
|
2781
2832
|
// src/commands/apichat.ts
|
|
2782
2833
|
init_allow_oauth_registration();
|
|
2783
2834
|
init_types();
|
|
2835
|
+
init_paging();
|
|
2784
2836
|
|
|
2785
2837
|
// src/lib/consumer-auth.ts
|
|
2786
2838
|
var fs5 = __toESM(require("fs"));
|
|
@@ -3291,7 +3343,9 @@ async function provision(spec2, target, opts) {
|
|
|
3291
3343
|
}
|
|
3292
3344
|
}
|
|
3293
3345
|
if (!dpKey) fail3("The proxy was created but no api_key was returned \u2014 cannot authenticate the chat.");
|
|
3294
|
-
const
|
|
3346
|
+
const provisionedTenant = result.tenant ?? tenantFromPortalUrl(result.portal ?? result.devPortal);
|
|
3347
|
+
if (!result.mcpHost && !provisionedTenant) fail3("The server did not report this proxy's tenant \u2014 cannot build its MCP address.");
|
|
3348
|
+
const mcpHost2 = result.mcpHost || `${projectId}-${provisionedTenant}.mcp.${anon ? "tryabz.run" : "abz.run"}`;
|
|
3295
3349
|
const proxyUrl = Array.isArray(result.endpoints) && (result.endpoints.find((e) => e.includes(`/${environment}`)) || result.endpoints[0]) || (anon ? `https://${projectId}.tryabz.run/${version2}/${environment}` : `https://${projectId}.abz.run/${version2}/${environment}`);
|
|
3296
3350
|
const tenant2 = typeof result.tenant === "string" ? result.tenant : void 0;
|
|
3297
3351
|
if (access === "invite" && tenant2) {
|
|
@@ -3791,12 +3845,15 @@ async function mintDurableProxyKey(teamId, tenant2) {
|
|
|
3791
3845
|
}
|
|
3792
3846
|
async function fetchProxyDoor(teamId, projectId, apiVersion) {
|
|
3793
3847
|
try {
|
|
3794
|
-
const listing = await
|
|
3795
|
-
|
|
3796
|
-
|
|
3797
|
-
|
|
3798
|
-
|
|
3799
|
-
|
|
3848
|
+
const listing = await pageAll(
|
|
3849
|
+
(cursor) => admin({
|
|
3850
|
+
method: "GET",
|
|
3851
|
+
path: withPage(`/projects?team_id=${encodeURIComponent(teamId)}`, cursor),
|
|
3852
|
+
summary: `Read the access policy for ${projectId}`
|
|
3853
|
+
}),
|
|
3854
|
+
(b) => b?.projects
|
|
3855
|
+
);
|
|
3856
|
+
const rows = listing.items;
|
|
3800
3857
|
const row = rows.find((r) => r.project_id === projectId && r.api_version === apiVersion) ?? rows.find((r) => r.project_id === projectId);
|
|
3801
3858
|
const tenant2 = row?.config?.default_tenant ?? row?.config?.tenant ?? (typeof row?.urls?.portal === "string" ? row.urls.portal.replace(/^https?:\/\//, "").split(".")[0] : void 0);
|
|
3802
3859
|
const policy = row?.config?.requests_policy;
|
|
@@ -3809,12 +3866,18 @@ async function fetchProxyDoor(teamId, projectId, apiVersion) {
|
|
|
3809
3866
|
return { methods: null };
|
|
3810
3867
|
}
|
|
3811
3868
|
}
|
|
3869
|
+
function tenantFromPortalUrl(url) {
|
|
3870
|
+
if (typeof url !== "string") return void 0;
|
|
3871
|
+
const m = url.match(/^https?:\/\/([a-z0-9-]+)\.portal\./i);
|
|
3872
|
+
return m?.[1];
|
|
3873
|
+
}
|
|
3812
3874
|
async function openDirectProject(projectId, opts) {
|
|
3813
3875
|
const saved = loadApichats().find((a) => a.projectId === projectId);
|
|
3814
3876
|
const version2 = opts.apiversion || saved?.version || "1.0.0";
|
|
3815
3877
|
const environment = opts.environment || saved?.environment || "prod";
|
|
3816
3878
|
const anon = saved?.anon ?? !loadCredentials();
|
|
3817
|
-
|
|
3879
|
+
if (!saved?.mcpHost && !saved?.tenant) fail3(`No MCP address is stored for "${projectId}".`, `Re-run: apiblaze apichat --target <its spec> (or apiblaze create) to refresh it.`);
|
|
3880
|
+
const mcpHost2 = saved?.mcpHost || `${projectId}-${saved?.tenant}.mcp.${anon ? "tryabz" : "abz"}.run`;
|
|
3818
3881
|
const p = {
|
|
3819
3882
|
projectId,
|
|
3820
3883
|
version: version2,
|
|
@@ -4865,12 +4928,17 @@ function printTryIt(block, highlights) {
|
|
|
4865
4928
|
}
|
|
4866
4929
|
}
|
|
4867
4930
|
var VALID_AUTH = ["api_key", "none", "oauth"];
|
|
4868
|
-
function
|
|
4931
|
+
function planLines(args) {
|
|
4869
4932
|
const { name, auth, families, adminEmail } = args;
|
|
4870
|
-
const
|
|
4871
|
-
const
|
|
4872
|
-
const
|
|
4873
|
-
|
|
4933
|
+
const cli = `npx ${B.cli}`;
|
|
4934
|
+
const key = `${import_chalk16.default.bold("API key")} \u2014 ${import_chalk16.default.cyan(`${cli} consumer apikeys`)} (list and create keys)`;
|
|
4935
|
+
const signIn = auth.ownIssuer ? `${import_chalk16.default.bold("Your own login")} \u2014 callers bring a JWT from your issuer` : `${import_chalk16.default.bold("OAuth")} \u2014 ${import_chalk16.default.cyan(`${cli} consumer login`)} (${auth.ownApp ? `your own ${auth.provider} app` : `${B.product} login with GitHub`})`;
|
|
4936
|
+
const doors = auth.doors.length === 2 ? [key, signIn] : auth.doors[0] === "api_key" ? [key] : auth.doors[0] === "oauth" ? [signIn] : [`${import_chalk16.default.bold("Open")} \u2014 no credential required`];
|
|
4937
|
+
const what = `You are about to create a proxy and an MCP to your backend${doors.length > 1 ? ", authenticated by both:" : doors[0].startsWith(import_chalk16.default.bold("Open")) ? ":" : ", authenticated by:"}`;
|
|
4938
|
+
const lines = [` ${what}`, ...doors.map((d) => ` ${d}`)];
|
|
4939
|
+
lines.push(` ${import_chalk16.default.bold("Rules:")} ${families?.length ? `${describeFamilies(families.map((f) => f.key)).replace(/ \+ /, " and ")} \u2014 only their creator, or an admin, can change them` : "none yet \u2014 every caller who gets in can reach every route"}`);
|
|
4940
|
+
if (adminEmail) lines.push(` ${import_chalk16.default.bold("Admin:")} ${adminEmail} \u2014 can change anything`);
|
|
4941
|
+
return lines;
|
|
4874
4942
|
}
|
|
4875
4943
|
var BOTH_DOORS = (() => {
|
|
4876
4944
|
const { auth_type, ...bodyPatch } = bothDoorsBody();
|
|
@@ -5242,6 +5310,16 @@ function printAgentPrompts(lines) {
|
|
|
5242
5310
|
console.log(` ${l.ok ? import_chalk16.default.green("\u2713") : import_chalk16.default.red("\u2717")} ${import_chalk16.default.bold(`"${l.say}"`.padEnd(width))} \u2192 ${import_chalk16.default.dim(l.why)}`);
|
|
5243
5311
|
}
|
|
5244
5312
|
}
|
|
5313
|
+
function printEnvironments(o) {
|
|
5314
|
+
const envs = o.environments ?? {};
|
|
5315
|
+
const names = Object.keys(envs);
|
|
5316
|
+
const rows = names.length ? names.map((e) => [`/${e}`, o.dev && e === "dev" ? `localhost:${o.dev.port} (tunnelled from this machine)` : envs[e]?.target ?? "\u2014"]) : o.dev ? [["/dev", `localhost:${o.dev.port} (tunnelled from this machine)`]] : [];
|
|
5317
|
+
if (!rows.length) return;
|
|
5318
|
+
const w = Math.max(...rows.map(([e]) => e.length));
|
|
5319
|
+
for (const [env, target] of rows) {
|
|
5320
|
+
console.log(` ${import_chalk16.default.cyan(env.padEnd(w))} ${import_chalk16.default.green("\u2192")} ${target}`);
|
|
5321
|
+
}
|
|
5322
|
+
}
|
|
5245
5323
|
async function finishSetup(o) {
|
|
5246
5324
|
if (o.dev) return printDevOutcome(o, o.dev.port);
|
|
5247
5325
|
return printOutcome(o);
|
|
@@ -5322,6 +5400,7 @@ async function printOutcome(o) {
|
|
|
5322
5400
|
const locked = (o.families ?? []).filter((f) => applied.includes(f.key));
|
|
5323
5401
|
console.log();
|
|
5324
5402
|
console.log(` ${import_chalk16.default.green("\u2713")} ${import_chalk16.default.bold(shown)} \u2014 ${o.auth.summary}`);
|
|
5403
|
+
printEnvironments(o);
|
|
5325
5404
|
if (applied.length && s?.enforced) {
|
|
5326
5405
|
const skipped = s.skipped?.length ? ` (${s.skipped.length} skipped)` : "";
|
|
5327
5406
|
console.log(` ${import_chalk16.default.green("\u2713")} ${describeFamilies(applied)} locked to their creator${skipped}`);
|
|
@@ -5501,7 +5580,7 @@ Create an ${B.thing}
|
|
|
5501
5580
|
if (interactive && !opts.yes && !opts.auto) {
|
|
5502
5581
|
const { default: inquirer3 } = await import("inquirer");
|
|
5503
5582
|
console.log();
|
|
5504
|
-
|
|
5583
|
+
for (const line of planLines({ name, auth, families, adminEmail })) console.log(line);
|
|
5505
5584
|
console.log();
|
|
5506
5585
|
const { ok } = await inquirer3.prompt([{
|
|
5507
5586
|
type: "confirm",
|
|
@@ -5543,6 +5622,7 @@ Create an ${B.thing}
|
|
|
5543
5622
|
const proxyUrl = `https://${name}.abz.run/${version2}/dev`;
|
|
5544
5623
|
const devPortal = result.devPortal ? stripTenantFromPortal(result.devPortal) : void 0;
|
|
5545
5624
|
const tenant2 = result.tenant ?? chosenTenant ?? tenantFromPortal(result.devPortal);
|
|
5625
|
+
if (!tenant2) fail4("The server did not report which tenant this proxy belongs to \u2014 cannot build its MCP address. Re-run, or report this.");
|
|
5546
5626
|
rememberForApichat({
|
|
5547
5627
|
projectId: result.project_id ?? name,
|
|
5548
5628
|
target: opts.target || opts.openapi || name,
|
|
@@ -5585,6 +5665,7 @@ Create an ${B.thing}
|
|
|
5585
5665
|
teamId,
|
|
5586
5666
|
auth,
|
|
5587
5667
|
apiKey: adminKey,
|
|
5668
|
+
environments: result.environments,
|
|
5588
5669
|
keys,
|
|
5589
5670
|
secure: result.secure,
|
|
5590
5671
|
families,
|
|
@@ -5611,7 +5692,7 @@ function rememberForApichat(entry) {
|
|
|
5611
5692
|
environment: entry.environment,
|
|
5612
5693
|
// The host `apichat <name> --install-mcp` will connect to: mcpblaze.com under
|
|
5613
5694
|
// that brand, else the same .mcp. convention apichat uses for an opened proxy.
|
|
5614
|
-
mcpHost: BRAND === "mcpblaze" ? mcpHost(entry.projectId, entry.tenant) : `${entry.projectId}.mcp.${entry.anon ? "tryabz" : "abz"}.run`,
|
|
5695
|
+
mcpHost: BRAND === "mcpblaze" ? mcpHost(entry.projectId, entry.tenant) : `${entry.projectId}-${entry.tenant}.mcp.${entry.anon ? "tryabz" : "abz"}.run`,
|
|
5615
5696
|
teamId: entry.teamId,
|
|
5616
5697
|
tenant: entry.tenant,
|
|
5617
5698
|
dpKey: entry.dpKey,
|
|
@@ -5763,6 +5844,7 @@ Create an ${B.thing}`));
|
|
|
5763
5844
|
const apiKey = result.apiKey ?? keys.prod ?? Object.values(keys)[0];
|
|
5764
5845
|
const portal = result.portal ?? (result.devPortal ? stripTenantFromPortal(result.devPortal) : void 0);
|
|
5765
5846
|
const tenant2 = result.tenant ?? tenantFromPortal(result.portal ?? result.devPortal);
|
|
5847
|
+
if (!tenant2) fail4("The server did not report which tenant this proxy belongs to \u2014 cannot build its MCP address. Re-run, or report this.");
|
|
5766
5848
|
const secure = result.secure ?? void 0;
|
|
5767
5849
|
const anonProjectId = result.project_id ?? name ?? "";
|
|
5768
5850
|
if (anonProjectId) {
|
|
@@ -5814,6 +5896,7 @@ Create an ${B.thing}`));
|
|
|
5814
5896
|
openapi: openapiText,
|
|
5815
5897
|
adminEmail,
|
|
5816
5898
|
samplePath: secure?.applied?.[0] ?? firstPlainPath(openapiText),
|
|
5899
|
+
environments: result.environments,
|
|
5817
5900
|
interactive,
|
|
5818
5901
|
claim: { claimCode: result.claim_code || cred?.claim_code, claimUrl: result.claim_url }
|
|
5819
5902
|
});
|
|
@@ -5958,7 +6041,7 @@ async function createDevProxy(args) {
|
|
|
5958
6041
|
return {
|
|
5959
6042
|
projectId: result.project_id ?? name,
|
|
5960
6043
|
version: result.api_version || "1.0.0",
|
|
5961
|
-
tenant: result.tenant ?? tenant2 ?? tenantFromPortal(result.portal ?? result.devPortal),
|
|
6044
|
+
tenant: result.tenant ?? tenant2 ?? tenantFromPortal(result.portal ?? result.devPortal) ?? fail4("The server did not report which tenant this proxy belongs to \u2014 cannot build its MCP address. Re-run, or report this."),
|
|
5962
6045
|
teamId: resolvedTeam,
|
|
5963
6046
|
apiKey: result.apiKey ?? keys.dev ?? Object.values(keys)[0],
|
|
5964
6047
|
keys,
|
|
@@ -6267,63 +6350,193 @@ function skillMarkdown() {
|
|
|
6267
6350
|
const doors = BRAND === "mcpblaze" ? `${P} ships your code to agents (an MCP server); ${other} ships the same code to users (API keys + sign-in).` : `${P} ships your code to users (API keys + sign-in); ${other} ships the same code to agents (an MCP server).`;
|
|
6268
6351
|
return `---
|
|
6269
6352
|
name: ${SKILL_NAME}
|
|
6270
|
-
description: Use when the user asks what ${P} (or ${cli}) does or can do for them; wants to ship, share, expose or make an API public; says "serve my backend behind a proxy and MCP"; wants auth, API keys, sign-in, multi-tenant registration,
|
|
6353
|
+
description: Use when the user asks what ${P} (or ${cli}) does or can do for them; says yes to a demo or asks to see / try / demo it; wants to ship, share, expose or make an API public; says "serve my backend behind a proxy and MCP"; wants auth, API keys, sign-in, multi-tenant registration, users and groups, throttling or quotas; wants agents to use the API through an MCP; or says users can reach each other's records or admin functions. Puts the ${P} gateway in front of the API instead of hand-rolling auth and tenancy in the backend.
|
|
6271
6354
|
---
|
|
6272
6355
|
|
|
6273
6356
|
# Ship safely with ${P}
|
|
6274
6357
|
|
|
6275
6358
|
One gateway, two doors. ${doors}
|
|
6276
|
-
Same rules, one command. Nothing about auth, tenancy or ownership goes into the
|
|
6277
|
-
|
|
6359
|
+
Same rules, one command. Nothing about auth, tenancy or ownership goes into the backend's
|
|
6360
|
+
business logic: the gateway sits in front of the code and enforces it.
|
|
6361
|
+
|
|
6362
|
+
## Which mode \u2014 decide this first
|
|
6363
|
+
|
|
6364
|
+
- They ask what ${P} does or can do \u2192 **Explain**. Glance at the workspace first (see
|
|
6365
|
+
Explain), answer, then offer either the demo or their own app. Run no \`npx ${cli}\`
|
|
6366
|
+
command \u2014 the whole answer is in this file.
|
|
6367
|
+
- They say yes to the demo, or ask to see / try / demo it \u2192 **Demo app**. It lives in its own
|
|
6368
|
+
folder and never touches their code.
|
|
6369
|
+
- They say yes to "can I read the code" \u2192 **Adopt**: scan their app, ask only what the scan
|
|
6370
|
+
could not find, then ship it through the Ship steps.
|
|
6371
|
+
- They have a backend, just wrote one, or ask you to BUILD one and then ship it \u2192 **Ship**.
|
|
6372
|
+
No code yet? First write the smallest plain backend that serves the resources they named \u2014
|
|
6373
|
+
no auth, no tenancy, no ownership checks (the gateway does those); stamp rows with the
|
|
6374
|
+
\`x-abz-user-id\` header so ownership is visible \u2014 plus its \`openapi.yaml\`, start it on
|
|
6375
|
+
their port, then continue at Ship \xA71. Do NOT build the Demo app for this. If they say "use
|
|
6376
|
+
${cli} on this" or "ship it with ${cli}" and point at a backend, skip any pitch and run it.
|
|
6278
6377
|
|
|
6279
|
-
|
|
6378
|
+
Tie-breaks: a question ("what can it do", "what is it") is always **Explain**, whatever else the
|
|
6379
|
+
message names. Mentioning \`npx ${cli} skills\` is NOT a request to run anything \u2014 it is the
|
|
6380
|
+
command that installed this file. If a message fits two modes, the earliest bullet wins.
|
|
6280
6381
|
|
|
6281
|
-
|
|
6282
|
-
"${P} puts a proxy and an MCP in front of your backend in one command, without changing your
|
|
6283
|
-
code. Your tenants get their own API keys, self-served from your site. People and agents sign
|
|
6284
|
-
in \u2014 hosted GitHub, or your own OAuth app. Every caller is throttled and quota'd, and each one
|
|
6285
|
-
can only touch the records they created: the rules are read from your OpenAPI spec, not
|
|
6286
|
-
guessed. Dev tunnels to localhost; prod points at your deploy. Want me to do it? Say:
|
|
6287
|
-
serve my backend (dev: localhost:<port>, prod: <url>) behind an ${P} proxy and MCP."
|
|
6382
|
+
## Explain
|
|
6288
6383
|
|
|
6289
|
-
|
|
6384
|
+
Say this, in these words or close to them, then stop:
|
|
6290
6385
|
|
|
6291
|
-
|
|
6292
|
-
|
|
6293
|
-
|
|
6294
|
-
|
|
6295
|
-
|
|
6296
|
-
|
|
6386
|
+
"${P} puts a gateway in front of your backend in one command: a proxy (every request passes
|
|
6387
|
+
through it first) and an MCP server (the same API, callable by AI agents like Claude). No auth
|
|
6388
|
+
or ownership logic goes into your code \u2014 your backend only has to trust one header. Each of
|
|
6389
|
+
your customers \u2014 a tenant \u2014 gets their own API key, created from a widget you drop into your
|
|
6390
|
+
site. People and agents sign in with hosted GitHub, or your own OAuth app. Each user can only
|
|
6391
|
+
touch the records they created; those rules come from your OpenAPI spec (the file listing your
|
|
6392
|
+
endpoints), and if you have none I draft one from your routes and show it to you first. One
|
|
6393
|
+
more command sets a rate limit and a daily quota per caller. In dev it tunnels to localhost;
|
|
6394
|
+
in prod it points at your deploy."
|
|
6297
6395
|
|
|
6298
|
-
|
|
6396
|
+
Before answering, look at the workspace \u2014 file names and \`package.json\` only, no reading of
|
|
6397
|
+
code yet. It "has an app" if ANY of these is present: an \`openapi.*\` / \`swagger.*\` file; a
|
|
6398
|
+
server framework (express, fastify, hono, koa, nest, fastapi, flask, django, rails, gin, chi,
|
|
6399
|
+
net/http); or a frontend (next, react, vue, svelte, remix, nuxt) whose \`.env*\` file NAMES an
|
|
6400
|
+
API base-URL variable (\`API_URL\`, \`NEXT_PUBLIC_API_URL\`, \`VITE_API_URL\`, or similar) \u2014
|
|
6401
|
+
\`grep -l\` the env files, open nothing else. Then end with exactly one of these two lines:
|
|
6299
6402
|
|
|
6300
|
-
|
|
6403
|
+
- No app \u2192 **"Would you like me to spin up a demo app?"**
|
|
6404
|
+
- An app \u2192 **"I see you have an app already coded. Can I read the code to see how to ready it
|
|
6405
|
+
for human and agent traffic with ${P}?"**
|
|
6406
|
+
|
|
6407
|
+
## Demo app \u2014 the same reservation system, before and after
|
|
6408
|
+
|
|
6409
|
+
One Next.js app on port 3000. Two routes, same UI, same data: \`/before\` is how most apps
|
|
6410
|
+
ship; \`/\` is the same thing behind ${P}. Narrate each step in one line and show every
|
|
6411
|
+
\`npx ${cli}\` command \u2014 the commands are the product. Everything goes in \`${cli}-demo/\`.
|
|
6412
|
+
|
|
6413
|
+
1. **Scaffold.** \`npx create-next-app@latest ${cli}-demo\` (TypeScript, App Router). Then
|
|
6414
|
+
\`npm i hono ${cli}\`.
|
|
6415
|
+
2. **The backend.** Hono mounted at \`app/api/[[...route]]/route.ts\` (\`import { handle } from
|
|
6416
|
+
'hono/vercel'\`). Three resources \u2014 restaurants, tables, reservations \u2014 in an in-memory Map,
|
|
6417
|
+
every row stamped with \`owner\` = the \`x-abz-user-id\` header (${P} sets it on every call it
|
|
6418
|
+
forwards). Every route requires \`X-Api-Secret: demo-secret\` OR an \`x-abz-user-id\` header \u2014
|
|
6419
|
+
the first is how /before gets in, the second is how the gateway gets in. Write
|
|
6420
|
+
\`openapi.yaml\` at the repo root: every path and method, and the \`id\` each POST returns \u2014
|
|
6421
|
+
the ownership rules are read from it.
|
|
6422
|
+
3. **/before.** \`app/before/page.tsx\`: list and create reservations through one server route,
|
|
6423
|
+
\`app/before/api/route.ts\`, which holds \`demo-secret\` and calls \`/api/...\` directly. Put the
|
|
6424
|
+
caption on the page: "Before \u2014 one shared secret, anyone holding it can do anything."
|
|
6425
|
+
4. **Put ${P} in front.** In the BACKGROUND, and leave it running \u2014 it is the tunnel:
|
|
6426
|
+
\`npx ${cli} dev --port 3000 --openapi ./openapi.yaml --name demo --auto\`
|
|
6427
|
+
It prints the API URL, the MCP URL, the tenant, and the bootstrap key ONCE. Write them to
|
|
6428
|
+
\`.env.local\` as \`APIBLAZE_URL\`, \`APIBLAZE_SERVER_KEY\`, \`APIBLAZE_TENANT\`. \`--auto\` locks
|
|
6429
|
+
all three resources to their creator \u2014 say so.
|
|
6430
|
+
5. **Users and groups.** Turn them on and make the group the demo is about:
|
|
6431
|
+
\`npx ${cli} iam demo on\`
|
|
6432
|
+
\`npx ${cli} group create reservationists --tenant <tenant>\`
|
|
6433
|
+
\`npx ${cli} group add-user <the demo user> reservationists --tenant <tenant>\`
|
|
6434
|
+
\`npx ${cli} admins add <the user's email> --tenant <tenant>\` (so the widget has an admin).
|
|
6435
|
+
Optional, billed per turn: \`npx ${cli} rule demo "only members of reservationists can create
|
|
6436
|
+
or change reservations"\`. If the user is logged out or out of credits, keep the free
|
|
6437
|
+
creator-lock rules and say so \u2014 do not pretend the group rule applied.
|
|
6438
|
+
6. **/.** \`app/page.tsx\`: the same UI, plus a **user picker** (ana \xB7 ben \xB7 admin \u2014 a select, no
|
|
6439
|
+
real login; the demo is about authorization, not building sign-in). Its server route calls
|
|
6440
|
+
\`process.env.APIBLAZE_URL\` with \`X-API-Key: APIBLAZE_SERVER_KEY\` and \`X-End-User-Id: <the
|
|
6441
|
+
picked user>\`. Add the three widgets from \`${cli}/react\`, each with its server route:
|
|
6442
|
+
- \`<ApiKeyWidget />\` + \`app/api/apiblaze/keys/route.ts\` (\`createApiblazeKeys\` from \`${cli}/server\`)
|
|
6443
|
+
- \`<UsersGroupsWidget />\` + \`app/api/apiblaze/groups/route.ts\` (\`createApiblazeGroups\` \u2014
|
|
6444
|
+
the keys route with one word changed)
|
|
6445
|
+
- \`<ChatWidget />\` + \`app/api/apiblaze/chat/route.ts\` (\`createApiblazeChat\`)
|
|
6446
|
+
Exact snippets for the keys and chat pair: \`npx ${cli} integration demo --stack nextjs\`.
|
|
6447
|
+
Caption: "After \u2014 a key per tenant, every call names its user, and ${P} decides."
|
|
6448
|
+
7. **Prove it, in the browser.** \`npm run dev\`, open \`http://localhost:3000/before\` then
|
|
6449
|
+
\`http://localhost:3000/\`. On \`/\`: create a reservation as ana, switch to ben, try to cancel
|
|
6450
|
+
it \u2014 show the 403 in the UI. Open the groups widget: reservationists is there. Ask the chat
|
|
6451
|
+
widget to list reservations. Then say what each \`npx ${cli}\` command did, in order.
|
|
6452
|
+
|
|
6453
|
+
## Adopt \u2014 reading their code first
|
|
6454
|
+
|
|
6455
|
+
They said yes. Scan properly now (routes, env files, auth, network calls), classify, then ask
|
|
6456
|
+
ONLY for what the scan could not find \u2014 one short list of questions, not an interview.
|
|
6457
|
+
|
|
6458
|
+
**Classify.**
|
|
6459
|
+
- **Backend** (routes are defined here): the proxy goes in front of it. Find its port or base
|
|
6460
|
+
URL, its spec (or the routes to write one from \u2014 every path and method, and the id each POST
|
|
6461
|
+
returns), and how it is protected today: a header like \`X-Api-Key\` / \`Authorization\`, a
|
|
6462
|
+
session, or nothing.
|
|
6463
|
+
- **Frontend** (calls a backend it does not contain): the proxy replaces its target. Find the
|
|
6464
|
+
base URL it calls (an env var or a fetch/axios wrapper), how a user is signed in (next-auth,
|
|
6465
|
+
clerk, auth0, supabase, a cookie, nothing), and where a signed-in user's page lives \u2014 that
|
|
6466
|
+
is where widgets go. If the backend's secret reaches the browser (a \`NEXT_PUBLIC_*\` /
|
|
6467
|
+
\`VITE_*\` variable, or a fetch wrapper imported by client components), fixing that is part
|
|
6468
|
+
of the job: move the call to a server route, delete the public variable, say so in the report.
|
|
6469
|
+
- **Both** (monorepo): do the backend first, then the frontend.
|
|
6470
|
+
|
|
6471
|
+
**Ask for what is missing \u2014 and nothing else.**
|
|
6472
|
+
- Backend found only on localhost \u2192 "Where is it hosted for production?" (a URL, or "not yet").
|
|
6473
|
+
- Backend protected by a secret whose value is in an env var \u2192 "What is the value of
|
|
6474
|
+
\`<VAR>\`, or should I keep it where it is?" Never read secrets out of \`.env\` aloud.
|
|
6475
|
+
- Frontend with a login \u2192 "Your users sign in with <provider>. Should agents sign in there
|
|
6476
|
+
too?" Yes \u2192 the matching \`--oauth\` shape, with \`--apikey\` so tenants keep a key door.
|
|
6477
|
+
No login found \u2192 say nothing; the default doors apply.
|
|
6478
|
+
- Frontend found \u2192 "Which of these do you want on the site: self-serve API keys, users and
|
|
6479
|
+
groups, chat with the API?" \u2014 one question, three checkboxes.
|
|
6480
|
+
- Always, in the same list: "What email should administer the tenant?" \u2014 Ship \xA76 needs it and
|
|
6481
|
+
you will not get another moment to ask.
|
|
6482
|
+
- Nothing else missing \u2192 say what you found in three lines and proceed.
|
|
6483
|
+
|
|
6484
|
+
**Then ship it** \u2014 continue at Ship \xA71 with the answers. Adopt adds only these to Ship:
|
|
6485
|
+
- Frontend: point its base URL env var at \`APIBLAZE_URL\`; make the server-side call-through
|
|
6486
|
+
send \`X-API-Key: APIBLAZE_SERVER_KEY\` and \`X-End-User-Id: <the signed-in user's id from
|
|
6487
|
+
their session>\`. The browser never holds the key. If the frontend-server already puts the
|
|
6488
|
+
user id in the request body for the backend to stamp its own owner field, leave it \u2014 it is
|
|
6489
|
+
set from the session, not the browser, and the gateway does not read it. next-auth v5 with
|
|
6490
|
+
JWT sessions leaves \`session.user.id\` undefined unless a \`session\` callback copies
|
|
6491
|
+
\`token.sub\`; if \`X-End-User-Id\` would be empty, add that one callback and say so \u2014 it is
|
|
6492
|
+
sign-in plumbing, not business logic. Then the widgets they ticked, each with its server route
|
|
6493
|
+
(\`npx ${cli} integration <name> --stack <their stack>\` for keys and chat; groups is the keys
|
|
6494
|
+
route with \`createApiblazeGroups\`).
|
|
6495
|
+
- Backend behind a secret: the proxy must send it upstream. That is a dashboard setting
|
|
6496
|
+
(Settings \u2192 Transforms); the CLI has no flag for it yet \u2014 say so, do not invent one. For a
|
|
6497
|
+
local run through the tunnel, the backend can instead accept calls carrying
|
|
6498
|
+
\`x-abz-user-id\`, which the gateway stamps on every call it forwards. Say plainly that while
|
|
6499
|
+
the tunnel runs, \`localhost:<port>\` still answers to the old secret and to anyone who sends
|
|
6500
|
+
that header \u2014 fine on a laptop, not on a server; in prod the proxy is the only caller.
|
|
6501
|
+
- Multi-tenant app \u2192 \`npx ${cli} identified <name> require\`, so no call arrives without a user.
|
|
6502
|
+
- Change nothing in their business logic. Report what you changed, file by file.
|
|
6503
|
+
|
|
6504
|
+
## Ship a real backend \u2014 "serve my backend behind a proxy and MCP"
|
|
6301
6505
|
|
|
6302
6506
|
1. **The spec.** Look for \`openapi.yaml\` / \`openapi.json\` / a spec route. None? Say
|
|
6303
6507
|
"Generating an OpenAPI spec from your code" and write \`./openapi.yaml\` from the routes:
|
|
6304
|
-
every path and method, and for each POST the id field it returns
|
|
6305
|
-
|
|
6306
|
-
|
|
6307
|
-
|
|
6508
|
+
every path and method, and for each POST the id field it returns.
|
|
6509
|
+
2. **The sign-in door.** If they said "OAuth" or "sign-in" without naming a provider or an
|
|
6510
|
+
issuer, that IS the default: pass no door flags (both doors, hosted GitHub) and say so in
|
|
6511
|
+
the report. Only ask "where do your users sign in today?" when they mention an existing
|
|
6512
|
+
login. Their own OAuth app \u2192 \`--apikey --oauth '{"provider":"auth0","clientId":"\u2026","clientSecret":"\u2026"}'\`
|
|
6308
6513
|
(both doors: keys for tenants, their login for people and agents). Shapes below.
|
|
6309
|
-
3. **
|
|
6310
|
-
|
|
6311
|
-
|
|
6312
|
-
|
|
6313
|
-
|
|
6314
|
-
|
|
6315
|
-
|
|
6316
|
-
|
|
6317
|
-
|
|
6318
|
-
\
|
|
6319
|
-
|
|
6320
|
-
|
|
6321
|
-
|
|
6322
|
-
|
|
6323
|
-
|
|
6324
|
-
|
|
6325
|
-
|
|
6326
|
-
\u2014
|
|
6514
|
+
3. **Local code** \u2192 \`npx ${cli} dev --port <port> --openapi ./openapi.yaml --name <name> --auto <door flags>\`,
|
|
6515
|
+
in the BACKGROUND, left running \u2014 it is the tunnel. **Deployed code** \u2192
|
|
6516
|
+
\`npx ${cli} create --target <url-or-spec> --name <name> --auto <door flags>\`. Both print the
|
|
6517
|
+
URL, the MCP URL (the "agents:" line), what is locked, and the bootstrap key ONCE \u2014 that key
|
|
6518
|
+
goes in the frontend-server's env, never a browser, a repo or an agent config. The tenant is
|
|
6519
|
+
not labelled: read it from the \`admins add \u2026 --tenant <tenant>\` hint (it is also the middle
|
|
6520
|
+
of the MCP hostname). Save \`APIBLAZE_URL\`, \`APIBLAZE_SERVER_KEY\`, \`APIBLAZE_TENANT\` in
|
|
6521
|
+
the frontend-server's env (\`.env.local\` for Next.js). Then prove it with three curls and
|
|
6522
|
+
say the codes: no key \u2192 401; create as \`X-End-User-Id: ana\` \u2192 201; PATCH that id as
|
|
6523
|
+
\`ben\` \u2192 403.
|
|
6524
|
+
4. **Prod** (local code only): \`npx ${cli} target <name> --env prod --url <prod-url>\`.
|
|
6525
|
+
5. **Limits.** \`npx ${cli} throttle <name> --end-user-rate 10 --quota 10000 --period daily\` \u2014
|
|
6526
|
+
sane defaults; use the user's numbers if they gave any. The CLI echoes \`\u2714 Update throttling
|
|
6527
|
+
{\u2026}\`. Prove it: 15 parallel GETs as one user \u2192 about 10\xD7200 and the rest 429; a different
|
|
6528
|
+
user right after \u2192 200.
|
|
6529
|
+
5b. **Attribution.** \`--auto\` leaves unattributed calls allowed: the bootstrap key with no
|
|
6530
|
+
\`X-End-User-Id\` creates rows nobody owns. For a multi-tenant app run
|
|
6531
|
+
\`npx ${cli} identified <name> require\` \u2014 then every call must carry \`X-End-User-Id\` or a
|
|
6532
|
+
login token. Say plainly that list routes (GET on a collection) return everyone's rows;
|
|
6533
|
+
only item routes are locked to their creator.
|
|
6534
|
+
6. **Admin.** \`npx ${cli} admins add <email> --tenant <tenant>\` \u2014 ask for the email, never guess
|
|
6535
|
+
it. If you cannot ask (a scripted run), skip it and end the report with
|
|
6536
|
+
"Admin \u2014 none yet: what email should administer tenant <tenant>?"
|
|
6537
|
+
7. **Report** in the shape below, then ask: "Would you like me to add the self-serve API key
|
|
6538
|
+
widget to your site?" and "Would you like to chat with your API now via your MCP?"
|
|
6539
|
+
(yes \u2192 \`npx ${cli} apichat <name> --install-mcp claude\`).
|
|
6327
6540
|
|
|
6328
6541
|
## Report like this
|
|
6329
6542
|
|
|
@@ -6333,14 +6546,12 @@ Use the real values the CLI printed. Never invent a URL, a key, a rule or a limi
|
|
|
6333
6546
|
\u26A1\uFE0FAuthorization rules, from the spec: \u25C9 restaurants \u25C9 tables \u25C9 reservations
|
|
6334
6547
|
only their creator, or an admin, can change them
|
|
6335
6548
|
\u25CF Done. Your app is behind a secured API and MCP:
|
|
6336
|
-
API https://<name>.abz.run
|
|
6337
|
-
/dev \u2192 localhost:<port>
|
|
6338
|
-
/prod \u2192 <prod-url>
|
|
6549
|
+
API https://<name>.abz.run/1.0.0/dev your tenants, with API keys
|
|
6550
|
+
/dev \u2192 localhost:<port> tunnelled from this machine \u2014 leave it running
|
|
6551
|
+
/prod \u2192 <prod-url> your deploy
|
|
6339
6552
|
Try it: curl https://<name>.abz.run/1.0.0/dev/<collection> -H "X-API-Key: <key>" -H "X-End-User-Id: you"
|
|
6340
|
-
MCP
|
|
6553
|
+
MCP <the "agents:" URL the CLI printed> agents, signing in with <provider>
|
|
6341
6554
|
Limits 10 requests/s per user \xB7 10,000 a day
|
|
6342
|
-
\u25CF Would you like me to add a self-serve API key widget to the developer section of your website?
|
|
6343
|
-
Would you like to chat with your API now via your MCP?
|
|
6344
6555
|
|
|
6345
6556
|
## Sign-in shapes \u2014 the only ones \`--oauth\` accepts; never invent fields
|
|
6346
6557
|
|
|
@@ -6351,14 +6562,21 @@ Use the real values the CLI printed. Never invent a URL, a key, a rule or a limi
|
|
|
6351
6562
|
- Their own JWT issuer: \`--oauth '{"iss":"https://login.acme.com/","aud":"acme-api","jwks":"https://login.acme.com/.well-known/jwks.json"}'\`
|
|
6352
6563
|
\u2014 all three required; add \`--apikey\` for both doors.
|
|
6353
6564
|
|
|
6354
|
-
##
|
|
6565
|
+
## Wiring a real site
|
|
6355
6566
|
|
|
6356
6567
|
\`npx ${cli} integration <name> --stack nextjs\` (also express | fastapi | other) prints the kit:
|
|
6357
|
-
the key
|
|
6358
|
-
|
|
6359
|
-
|
|
6360
|
-
|
|
6361
|
-
\`npx ${cli} apikeys mint --
|
|
6568
|
+
the key and chat widgets, the call-through (ONE server key + \`X-End-User-Id: <the signed-in
|
|
6569
|
+
person>\` on every call \u2014 that header is how the rules know who is acting), the env vars. The
|
|
6570
|
+
groups widget is the keys route with \`createApiblazeGroups\` in place of \`createApiblazeKeys\`.
|
|
6571
|
+
The keys widget needs a value \`dev\`/\`create\` never print: \`APIBLAZE_CP_KEY\`, a control-plane
|
|
6572
|
+
key. Mint it with \`npx ${cli} apikeys mint --desc "keys widget"\` (no \`--tenant\` \u2014 with
|
|
6573
|
+
\`--tenant\` you get a consumer key, which is the wrong kind).
|
|
6574
|
+
Edit ONLY the frontend and the frontend-server. A key per TENANT comes from that widget \u2014 each
|
|
6575
|
+
of your customers mints their own, in their own tenant; the bootstrap key is only for your
|
|
6576
|
+
frontend-server. A key bound to one person (an agent, a CI job):
|
|
6577
|
+
\`npx ${cli} apikeys mint --tenant <tenant> --for <email>\`. That names a person by email,
|
|
6578
|
+
which is a DIFFERENT identity from a bare \`X-End-User-Id\` string: rows created as "ana" via
|
|
6579
|
+
the header are not owned by ana@example.com's key.
|
|
6362
6580
|
|
|
6363
6581
|
## Never
|
|
6364
6582
|
|
|
@@ -6367,12 +6585,14 @@ and the frontend-server. A key bound to one person (an agent, a CI job):
|
|
|
6367
6585
|
refuses it. Agents get a person-bound key or sign in.
|
|
6368
6586
|
- Put ownership or tenancy checks into business logic. The gateway enforces them.
|
|
6369
6587
|
- Ask for team ids, tenant ids or project ids the CLI already knows.
|
|
6370
|
-
- Delete anything. Deletion stays with the user: \`npx ${cli} delete <name
|
|
6588
|
+
- Delete anything. Deletion stays with the user: \`npx ${cli} delete <name>\` (interactive;
|
|
6589
|
+
\`--yes\` skips the prompt). Its impact banner lists tenant-scoped counts even when the tenant
|
|
6590
|
+
is shared \u2014 those are lost only if this is the tenant's LAST proxy.
|
|
6371
6591
|
|
|
6372
6592
|
## Later
|
|
6373
6593
|
|
|
6374
6594
|
- \`npx ${cli} rule <name>\` reviews or changes the rules; \`npx ${cli} config <name> authorization.enforce_authorization false\` turns enforcement off without losing them.
|
|
6375
|
-
- \`npx ${cli} apichat <name
|
|
6595
|
+
- \`npx ${cli} apichat <name>\` chats with the API again; \`--install-mcp claude\` connects an agent.
|
|
6376
6596
|
|
|
6377
6597
|
## No shell? The control-plane MCP
|
|
6378
6598
|
|
|
@@ -7319,10 +7539,94 @@ async function runMcp(projectArg, apiVersionArg, opts) {
|
|
|
7319
7539
|
}
|
|
7320
7540
|
|
|
7321
7541
|
// src/commands/delete.ts
|
|
7322
|
-
var
|
|
7542
|
+
var import_chalk33 = __toESM(require("chalk"));
|
|
7323
7543
|
var import_ora12 = __toESM(require("ora"));
|
|
7324
7544
|
init_admin();
|
|
7325
7545
|
init_resolve();
|
|
7546
|
+
|
|
7547
|
+
// src/lib/job.ts
|
|
7548
|
+
var import_chalk32 = __toESM(require("chalk"));
|
|
7549
|
+
init_admin();
|
|
7550
|
+
var POLL_MS = 3e3;
|
|
7551
|
+
var MAX_WAIT_MS = 30 * 6e4;
|
|
7552
|
+
function jobPath(jobId, teamId) {
|
|
7553
|
+
return teamId ? `/teams/${encodeURIComponent(teamId)}/jobs/${encodeURIComponent(jobId)}` : `/jobs/${encodeURIComponent(jobId)}`;
|
|
7554
|
+
}
|
|
7555
|
+
function describeJob(job) {
|
|
7556
|
+
switch (job.phase) {
|
|
7557
|
+
case "proxies":
|
|
7558
|
+
case "orphans":
|
|
7559
|
+
return `removing proxies${job.progress?.proxies ? ` (${job.progress.proxies} done)` : ""}`;
|
|
7560
|
+
case "tenants":
|
|
7561
|
+
return `removing tenants${job.progress?.tenants ? ` (${job.progress.tenants} done)` : ""}`;
|
|
7562
|
+
case "cp":
|
|
7563
|
+
case "store":
|
|
7564
|
+
return "removing authorization stores";
|
|
7565
|
+
case "recipes":
|
|
7566
|
+
case "support":
|
|
7567
|
+
return "removing stored content";
|
|
7568
|
+
case "rows":
|
|
7569
|
+
case "kv":
|
|
7570
|
+
case "upstash":
|
|
7571
|
+
case "audit":
|
|
7572
|
+
return "finishing up";
|
|
7573
|
+
default:
|
|
7574
|
+
return "working";
|
|
7575
|
+
}
|
|
7576
|
+
}
|
|
7577
|
+
async function pollJob(jobId, opts = {}) {
|
|
7578
|
+
const started = Date.now();
|
|
7579
|
+
const path11 = jobPath(jobId, opts.teamId);
|
|
7580
|
+
let lastPhase = "";
|
|
7581
|
+
if (!opts.quiet) process.stdout.write(import_chalk32.default.dim(opts.label ?? "Deleting"));
|
|
7582
|
+
for (; ; ) {
|
|
7583
|
+
if (Date.now() - started > MAX_WAIT_MS) {
|
|
7584
|
+
if (!opts.quiet) process.stdout.write("\n");
|
|
7585
|
+
throw new Error(
|
|
7586
|
+
`Still running after 30 minutes. The delete has NOT been cancelled \u2014 check it with:
|
|
7587
|
+
apiblaze job ${jobId}${opts.teamId ? ` --team ${opts.teamId}` : ""}`
|
|
7588
|
+
);
|
|
7589
|
+
}
|
|
7590
|
+
await new Promise((r) => setTimeout(r, POLL_MS));
|
|
7591
|
+
const job = await admin({ method: "GET", path: path11, summary: "poll cascade job" });
|
|
7592
|
+
if (!opts.quiet) {
|
|
7593
|
+
const phase = describeJob(job);
|
|
7594
|
+
if (phase !== lastPhase) {
|
|
7595
|
+
process.stdout.write(import_chalk32.default.dim(`
|
|
7596
|
+
${phase}`));
|
|
7597
|
+
lastPhase = phase;
|
|
7598
|
+
}
|
|
7599
|
+
process.stdout.write(import_chalk32.default.dim("."));
|
|
7600
|
+
}
|
|
7601
|
+
if (job.status === "done" || job.status === "failed") {
|
|
7602
|
+
if (!opts.quiet) process.stdout.write("\n");
|
|
7603
|
+
return job;
|
|
7604
|
+
}
|
|
7605
|
+
}
|
|
7606
|
+
}
|
|
7607
|
+
async function settleDelete(accepted, opts) {
|
|
7608
|
+
const jobId = accepted?.job_id;
|
|
7609
|
+
if (!jobId) return null;
|
|
7610
|
+
if (opts.noWait) {
|
|
7611
|
+
if (!opts.json) {
|
|
7612
|
+
console.log(import_chalk32.default.dim(`Queued as job ${jobId}. Check it with: apiblaze job ${jobId}${opts.teamId ? ` --team ${opts.teamId}` : ""}`));
|
|
7613
|
+
}
|
|
7614
|
+
return null;
|
|
7615
|
+
}
|
|
7616
|
+
const job = await pollJob(jobId, { teamId: opts.teamId, label: opts.label, quiet: opts.json });
|
|
7617
|
+
if (job.status === "failed") {
|
|
7618
|
+
const parked = job.progress?.failed_units ?? [];
|
|
7619
|
+
throw new Error(
|
|
7620
|
+
`Deletion finished with residue \u2014 some resources could NOT be removed.
|
|
7621
|
+
${job.error ?? "unknown reason"}
|
|
7622
|
+
` + (parked.length ? ` Left behind: ${parked.slice(0, 10).join(", ")}
|
|
7623
|
+
` : "") + ` Job: ${jobId}`
|
|
7624
|
+
);
|
|
7625
|
+
}
|
|
7626
|
+
return job;
|
|
7627
|
+
}
|
|
7628
|
+
|
|
7629
|
+
// src/commands/delete.ts
|
|
7326
7630
|
async function runDelete(project, version2, opts) {
|
|
7327
7631
|
const { teamId } = await resolveTeam(opts.team);
|
|
7328
7632
|
const proj2 = await resolveProject(teamId, project, version2);
|
|
@@ -7345,10 +7649,10 @@ async function runDelete(project, version2, opts) {
|
|
|
7345
7649
|
return;
|
|
7346
7650
|
}
|
|
7347
7651
|
} else {
|
|
7348
|
-
console.log(`${
|
|
7652
|
+
console.log(`${import_chalk33.default.red.bold("Delete")} ${import_chalk33.default.bold(proj2.projectName)} ${import_chalk33.default.dim("v" + proj2.apiVersion)} ${import_chalk33.default.dim("(" + proj2.projectId + ")")}`);
|
|
7349
7653
|
if (impact && typeof impact === "object") {
|
|
7350
7654
|
const counts = impact.counts ?? impact.impact ?? impact;
|
|
7351
|
-
console.log(
|
|
7655
|
+
console.log(import_chalk33.default.dim(" This removes (cascade): ") + import_chalk33.default.yellow(JSON.stringify(counts)));
|
|
7352
7656
|
}
|
|
7353
7657
|
if (!opts.yes) {
|
|
7354
7658
|
const { default: inquirer3 } = await import("inquirer");
|
|
@@ -7356,28 +7660,79 @@ async function runDelete(project, version2, opts) {
|
|
|
7356
7660
|
{ type: "confirm", name: "confirm", message: `Permanently delete ${proj2.projectName} v${proj2.apiVersion}? This cannot be undone.`, default: false }
|
|
7357
7661
|
]);
|
|
7358
7662
|
if (!confirm2) {
|
|
7359
|
-
console.log(
|
|
7663
|
+
console.log(import_chalk33.default.dim("Aborted."));
|
|
7360
7664
|
return;
|
|
7361
7665
|
}
|
|
7362
7666
|
}
|
|
7363
7667
|
}
|
|
7364
7668
|
const s2 = (0, import_ora12.default)("Deleting...").start();
|
|
7669
|
+
let accepted;
|
|
7365
7670
|
try {
|
|
7366
|
-
await admin({
|
|
7671
|
+
accepted = await admin({
|
|
7367
7672
|
method: "DELETE",
|
|
7368
7673
|
path: `/projects/${proj2.projectId}/${proj2.apiVersion}`,
|
|
7369
7674
|
summary: `Delete proxy ${proj2.projectName} v${proj2.apiVersion} (full cascade)`
|
|
7370
7675
|
});
|
|
7371
|
-
s2.
|
|
7676
|
+
s2.stop();
|
|
7372
7677
|
} catch (err) {
|
|
7373
7678
|
s2.fail("Delete failed.");
|
|
7374
7679
|
throw err;
|
|
7375
7680
|
}
|
|
7376
|
-
|
|
7681
|
+
const job = await settleDelete(accepted, {
|
|
7682
|
+
teamId,
|
|
7683
|
+
noWait: opts.wait === false,
|
|
7684
|
+
label: `Deleting ${proj2.projectName}`,
|
|
7685
|
+
json: opts.json
|
|
7686
|
+
});
|
|
7687
|
+
if (!opts.json) {
|
|
7688
|
+
if (job || accepted?.job_id == null) {
|
|
7689
|
+
console.log(import_chalk33.default.green(`\u2714 Deleted ${proj2.projectName} v${proj2.apiVersion}.`));
|
|
7690
|
+
}
|
|
7691
|
+
} else {
|
|
7692
|
+
console.log(JSON.stringify({
|
|
7693
|
+
project_id: proj2.projectId,
|
|
7694
|
+
api_version: proj2.apiVersion,
|
|
7695
|
+
deleted: opts.wait === false ? false : true,
|
|
7696
|
+
...accepted?.job_id ? { job_id: accepted.job_id, job_status: job?.status ?? "queued" } : {}
|
|
7697
|
+
}));
|
|
7698
|
+
}
|
|
7699
|
+
}
|
|
7700
|
+
|
|
7701
|
+
// src/commands/job.ts
|
|
7702
|
+
var import_chalk34 = __toESM(require("chalk"));
|
|
7703
|
+
init_admin();
|
|
7704
|
+
init_resolve();
|
|
7705
|
+
async function runJobStatus(jobId, opts) {
|
|
7706
|
+
let teamId;
|
|
7707
|
+
try {
|
|
7708
|
+
({ teamId } = await resolveTeam(opts.team));
|
|
7709
|
+
} catch {
|
|
7710
|
+
teamId = void 0;
|
|
7711
|
+
}
|
|
7712
|
+
const job = opts.watch ? await pollJob(jobId, { teamId, label: "Waiting", quiet: opts.json }) : await admin({ method: "GET", path: jobPath(jobId, teamId), summary: `Read cascade job ${jobId}` });
|
|
7713
|
+
if (opts.json) {
|
|
7714
|
+
console.log(JSON.stringify(job));
|
|
7715
|
+
if (job.status === "failed") process.exitCode = 1;
|
|
7716
|
+
return;
|
|
7717
|
+
}
|
|
7718
|
+
const colour = job.status === "done" ? import_chalk34.default.green : job.status === "failed" ? import_chalk34.default.red : import_chalk34.default.yellow;
|
|
7719
|
+
console.log(`${import_chalk34.default.bold(job.job_id)} ${import_chalk34.default.dim(`(${job.kind})`)} ${colour.bold(job.status)}`);
|
|
7720
|
+
if (job.status === "queued" || job.status === "running") {
|
|
7721
|
+
console.log(import_chalk34.default.dim(` ${describeJob(job)}`));
|
|
7722
|
+
}
|
|
7723
|
+
const counts = [];
|
|
7724
|
+
if (job.progress?.proxies) counts.push(`${job.progress.proxies} prox`);
|
|
7725
|
+
if (job.progress?.tenants) counts.push(`${job.progress.tenants} tenant(s)`);
|
|
7726
|
+
if (counts.length) console.log(import_chalk34.default.dim(` removed: ${counts.join(", ")}`));
|
|
7727
|
+
if (job.attempts > 1) console.log(import_chalk34.default.dim(` attempts: ${job.attempts}`));
|
|
7728
|
+
if (job.error) console.log(import_chalk34.default.red(` ${job.error}`));
|
|
7729
|
+
const parked = job.progress?.failed_units ?? [];
|
|
7730
|
+
if (parked.length) console.log(import_chalk34.default.red(` left behind: ${parked.slice(0, 10).join(", ")}`));
|
|
7731
|
+
if (job.status === "failed") process.exitCode = 1;
|
|
7377
7732
|
}
|
|
7378
7733
|
|
|
7379
7734
|
// src/commands/export.ts
|
|
7380
|
-
var
|
|
7735
|
+
var import_chalk35 = __toESM(require("chalk"));
|
|
7381
7736
|
var import_fs4 = require("fs");
|
|
7382
7737
|
init_admin();
|
|
7383
7738
|
init_auth();
|
|
@@ -7396,7 +7751,7 @@ async function runExport(projectArg, versionArg, opts) {
|
|
|
7396
7751
|
if (opts.noConsumers) q.set("include_consumers", "0");
|
|
7397
7752
|
if (opts.keys) q.set("keys", opts.keys);
|
|
7398
7753
|
const qs = q.toString() ? `?${q.toString()}` : "";
|
|
7399
|
-
console.log(
|
|
7754
|
+
console.log(import_chalk35.default.dim(`Queuing ${target} export of ${projectId}@${version2}\u2026`));
|
|
7400
7755
|
const job = await admin({
|
|
7401
7756
|
path: `/projects/${projectId}/${version2}/export/${target}${qs}`,
|
|
7402
7757
|
method: "GET",
|
|
@@ -7406,11 +7761,11 @@ async function runExport(projectArg, versionArg, opts) {
|
|
|
7406
7761
|
const statusPath = `/projects/${projectId}/${version2}/export/jobs/${job.job_id}`;
|
|
7407
7762
|
let status = "queued";
|
|
7408
7763
|
const started = Date.now();
|
|
7409
|
-
process.stdout.write(
|
|
7764
|
+
process.stdout.write(import_chalk35.default.dim("Building bundle"));
|
|
7410
7765
|
while (status !== "ready" && status !== "failed") {
|
|
7411
7766
|
if (Date.now() - started > 10 * 6e4) throw new Error("Export timed out after 10 minutes.");
|
|
7412
7767
|
await new Promise((r) => setTimeout(r, 3e3));
|
|
7413
|
-
process.stdout.write(
|
|
7768
|
+
process.stdout.write(import_chalk35.default.dim("."));
|
|
7414
7769
|
const st = await admin({ path: statusPath, method: "GET", summary: "poll export" });
|
|
7415
7770
|
status = st?.status ?? "queued";
|
|
7416
7771
|
if (status === "failed") {
|
|
@@ -7428,14 +7783,14 @@ async function runExport(projectArg, versionArg, opts) {
|
|
|
7428
7783
|
if (!res.ok) throw new Error(`Download failed (${res.status}): ${await res.text().catch(() => "")}`);
|
|
7429
7784
|
const buf = Buffer.from(await res.arrayBuffer());
|
|
7430
7785
|
(0, import_fs4.writeFileSync)(outFile, buf);
|
|
7431
|
-
console.log(
|
|
7432
|
-
if (job.decryption_key) console.log(
|
|
7433
|
-
console.log(
|
|
7434
|
-
if (target === "kong") console.log(
|
|
7786
|
+
console.log(import_chalk35.default.green(`\u2713 Saved ${outFile} (${(buf.length / 1024).toFixed(0)} KB)`));
|
|
7787
|
+
if (job.decryption_key) console.log(import_chalk35.default.yellow(`Decryption key (shown once): ${job.decryption_key}`));
|
|
7788
|
+
console.log(import_chalk35.default.dim("Read MIGRATION-REPORT.json inside the zip for the full fidelity ledger."));
|
|
7789
|
+
if (target === "kong") console.log(import_chalk35.default.dim("Kong: unzip, then `cd kong && docker compose up` (see kong/README.md)."));
|
|
7435
7790
|
}
|
|
7436
7791
|
|
|
7437
7792
|
// src/commands/recipe-search.ts
|
|
7438
|
-
var
|
|
7793
|
+
var import_chalk36 = __toESM(require("chalk"));
|
|
7439
7794
|
var import_ora13 = __toESM(require("ora"));
|
|
7440
7795
|
init_caller();
|
|
7441
7796
|
|
|
@@ -7636,20 +7991,20 @@ async function runRecipeSearch(query, opts) {
|
|
|
7636
7991
|
return;
|
|
7637
7992
|
}
|
|
7638
7993
|
if (!hits.length) {
|
|
7639
|
-
console.log(
|
|
7640
|
-
console.log(
|
|
7994
|
+
console.log(import_chalk36.default.yellow(query ? `No public recipes match "${query}".` : "No public recipes yet."));
|
|
7995
|
+
console.log(import_chalk36.default.dim(" Publish one with: apiblaze publish <proxy> --as @yourhandle/name"));
|
|
7641
7996
|
return;
|
|
7642
7997
|
}
|
|
7643
7998
|
console.log();
|
|
7644
7999
|
for (const h of hits) {
|
|
7645
|
-
const pin = h.latest_revision ?
|
|
7646
|
-
const installs = h.install_count ?
|
|
7647
|
-
console.log(` ${
|
|
7648
|
-
if (h.summary) console.log(` ${
|
|
8000
|
+
const pin = h.latest_revision ? import_chalk36.default.dim(`@${h.latest_revision}`) : "";
|
|
8001
|
+
const installs = h.install_count ? import_chalk36.default.dim(` ${h.install_count} install${h.install_count === 1 ? "" : "s"}`) : "";
|
|
8002
|
+
console.log(` ${import_chalk36.default.bold(h.name)}${pin}${h.display_name ? import_chalk36.default.dim(` \u2014 ${h.display_name}`) : ""}${installs}`);
|
|
8003
|
+
if (h.summary) console.log(` ${import_chalk36.default.dim(h.summary)}`);
|
|
7649
8004
|
}
|
|
7650
8005
|
console.log();
|
|
7651
|
-
console.log(
|
|
7652
|
-
console.log(
|
|
8006
|
+
console.log(import_chalk36.default.dim(` Install one with: ${import_chalk36.default.cyan(`npx apiblaze install ${hits[0].name}`)}`));
|
|
8007
|
+
console.log(import_chalk36.default.dim(` Read one first: ${import_chalk36.default.cyan(`npx apiblaze show ${hits[0].name}`)}`));
|
|
7653
8008
|
console.log();
|
|
7654
8009
|
}
|
|
7655
8010
|
async function runRecipeShow(nameArg, opts) {
|
|
@@ -7675,8 +8030,8 @@ async function runRecipeShow(nameArg, opts) {
|
|
|
7675
8030
|
return;
|
|
7676
8031
|
}
|
|
7677
8032
|
console.log();
|
|
7678
|
-
console.log(`${
|
|
7679
|
-
if (recipe.display_name) console.log(` ${
|
|
8033
|
+
console.log(`${import_chalk36.default.bold(`${recipe.name}@${recipe.revision}`)}${import_chalk36.default.dim(` \u2014 by ${ref.handle}`)}`);
|
|
8034
|
+
if (recipe.display_name) console.log(` ${import_chalk36.default.bold(recipe.display_name)}`);
|
|
7680
8035
|
if (recipe.summary) console.log(` ${recipe.summary}`);
|
|
7681
8036
|
const meta = [
|
|
7682
8037
|
head?.install_count !== void 0 ? `${head.install_count} install${head.install_count === 1 ? "" : "s"}` : null,
|
|
@@ -7684,7 +8039,7 @@ async function runRecipeShow(nameArg, opts) {
|
|
|
7684
8039
|
recipe.published_at ? `published ${recipe.published_at.slice(0, 10)}` : null,
|
|
7685
8040
|
`api version ${recipe.api_version}`
|
|
7686
8041
|
].filter(Boolean).join(" \xB7 ");
|
|
7687
|
-
console.log(
|
|
8042
|
+
console.log(import_chalk36.default.dim(` ${meta}`));
|
|
7688
8043
|
console.log();
|
|
7689
8044
|
if (wantsSection) {
|
|
7690
8045
|
if (opts.settings) printSection("settings", recipe.settings);
|
|
@@ -7698,34 +8053,34 @@ async function runRecipeShow(nameArg, opts) {
|
|
|
7698
8053
|
const rules = recipe.transformations?.rules?.length ?? 0;
|
|
7699
8054
|
const tables = recipe.transformations?.mapping_tables?.length ?? 0;
|
|
7700
8055
|
const keyTypes = recipe.auth?.key_types?.length ?? 0;
|
|
7701
|
-
if (hosts.length) console.log(` ${
|
|
7702
|
-
console.log(` ${
|
|
7703
|
-
console.log(` ${
|
|
8056
|
+
if (hosts.length) console.log(` ${import_chalk36.default.dim("sends requests to")} ${hosts.join(", ")}`);
|
|
8057
|
+
console.log(` ${import_chalk36.default.dim("creates ")} ${describeCreates(keyTypes, routes, !!recipe.settings.mcp_enabled)}`);
|
|
8058
|
+
console.log(` ${import_chalk36.default.dim("asks you for ")} ${recipe.questions.length} value${recipe.questions.length === 1 ? "" : "s"}`);
|
|
7704
8059
|
if (rules || tables) {
|
|
7705
|
-
console.log(` ${
|
|
8060
|
+
console.log(` ${import_chalk36.default.dim("transforms ")} ${rules} rule${rules === 1 ? "" : "s"}${tables ? `, ${tables} mapping table${tables === 1 ? "" : "s"}` : ""}`);
|
|
7706
8061
|
}
|
|
7707
8062
|
if (recipe.settings.publish_openapi === true) {
|
|
7708
|
-
console.log(` ${
|
|
8063
|
+
console.log(` ${import_chalk36.default.dim("makes public ")} this API's spec`);
|
|
7709
8064
|
}
|
|
7710
8065
|
if (recipe.questions.length) {
|
|
7711
8066
|
console.log();
|
|
7712
|
-
console.log(
|
|
8067
|
+
console.log(import_chalk36.default.bold(" It will ask you for"));
|
|
7713
8068
|
for (const q of recipe.questions) {
|
|
7714
|
-
console.log(` ${q.prompt}${q.secret ?
|
|
7715
|
-
if (q.example) console.log(
|
|
8069
|
+
console.log(` ${q.prompt}${q.secret ? import_chalk36.default.dim(" (hidden while you type)") : ""}`);
|
|
8070
|
+
if (q.example) console.log(import_chalk36.default.dim(` e.g. ${q.example}`));
|
|
7716
8071
|
}
|
|
7717
8072
|
}
|
|
7718
8073
|
if (recipe.requires_confirmation?.length) {
|
|
7719
8074
|
console.log();
|
|
7720
|
-
console.log(
|
|
8075
|
+
console.log(import_chalk36.default.yellow(" Needs your explicit consent at install time"));
|
|
7721
8076
|
for (const c of recipe.requires_confirmation) {
|
|
7722
|
-
console.log(` ${
|
|
7723
|
-
console.log(
|
|
8077
|
+
console.log(` ${import_chalk36.default.bold(c.kind)} ${c.value}`);
|
|
8078
|
+
console.log(import_chalk36.default.dim(` ${c.why}`));
|
|
7724
8079
|
}
|
|
7725
8080
|
}
|
|
7726
8081
|
console.log();
|
|
7727
|
-
console.log(
|
|
7728
|
-
console.log(
|
|
8082
|
+
console.log(import_chalk36.default.dim(` Install: ${import_chalk36.default.cyan(`npx apiblaze install ${recipe.name}`)}`));
|
|
8083
|
+
console.log(import_chalk36.default.dim(" Sections: --settings --auth --transforms --openapi"));
|
|
7729
8084
|
console.log();
|
|
7730
8085
|
}
|
|
7731
8086
|
function describeCreates(keyTypes, routes, mcp, includeProxy = true) {
|
|
@@ -7735,45 +8090,45 @@ function describeCreates(keyTypes, routes, mcp, includeProxy = true) {
|
|
|
7735
8090
|
return parts.length ? parts.join(", ") : "nothing else";
|
|
7736
8091
|
}
|
|
7737
8092
|
function printSection(title, value) {
|
|
7738
|
-
console.log(
|
|
8093
|
+
console.log(import_chalk36.default.bold(` ${title}`));
|
|
7739
8094
|
console.log(indent(JSON.stringify(value ?? null, null, 2), " "));
|
|
7740
8095
|
console.log();
|
|
7741
8096
|
}
|
|
7742
8097
|
function printAuth(recipe) {
|
|
7743
8098
|
const a = recipe.auth ?? { app_client: null, provider: null, key_types: [] };
|
|
7744
|
-
console.log(
|
|
8099
|
+
console.log(import_chalk36.default.bold(" auth"));
|
|
7745
8100
|
if (a.provider) {
|
|
7746
|
-
console.log(` ${
|
|
7747
|
-
if (a.provider.scopes) console.log(` ${
|
|
7748
|
-
if (a.provider.target_server_token) console.log(` ${
|
|
7749
|
-
console.log(
|
|
8101
|
+
console.log(` ${import_chalk36.default.dim("provider ")} ${a.provider.type ?? "(unknown)"}`);
|
|
8102
|
+
if (a.provider.scopes) console.log(` ${import_chalk36.default.dim("scopes ")} ${a.provider.scopes}`);
|
|
8103
|
+
if (a.provider.target_server_token) console.log(` ${import_chalk36.default.dim("sends to API")} ${a.provider.target_server_token}`);
|
|
8104
|
+
console.log(import_chalk36.default.dim(" (no credentials travel in a recipe \u2014 install asks you for your own)"));
|
|
7750
8105
|
} else {
|
|
7751
|
-
console.log(
|
|
8106
|
+
console.log(import_chalk36.default.dim(" no login provider \u2014 consumers call it with an API key"));
|
|
7752
8107
|
}
|
|
7753
8108
|
if (a.app_client) {
|
|
7754
|
-
console.log(` ${
|
|
8109
|
+
console.log(` ${import_chalk36.default.dim("login app ")} ${a.app_client.name ?? "(unnamed)"}`);
|
|
7755
8110
|
}
|
|
7756
8111
|
if (a.key_types?.length) {
|
|
7757
|
-
console.log(` ${
|
|
8112
|
+
console.log(` ${import_chalk36.default.dim("key types ")} ${a.key_types.map((k) => String(k.name)).join(", ")}`);
|
|
7758
8113
|
}
|
|
7759
8114
|
console.log();
|
|
7760
8115
|
}
|
|
7761
8116
|
function printTransforms(recipe) {
|
|
7762
8117
|
const rules = recipe.transformations?.rules ?? [];
|
|
7763
8118
|
const tables = recipe.transformations?.mapping_tables ?? [];
|
|
7764
|
-
console.log(
|
|
8119
|
+
console.log(import_chalk36.default.bold(` transforms (${rules.length})`));
|
|
7765
8120
|
for (const r of rules) {
|
|
7766
|
-
const off = r.enabled === false ?
|
|
7767
|
-
console.log(` ${
|
|
8121
|
+
const off = r.enabled === false ? import_chalk36.default.dim(" (disabled)") : "";
|
|
8122
|
+
console.log(` ${import_chalk36.default.bold(String(r.name ?? "(unnamed)"))} ${import_chalk36.default.dim(String(r.phase ?? "request"))}${off}`);
|
|
7768
8123
|
console.log(indent(JSON.stringify(r.action ?? null, null, 2), " "));
|
|
7769
8124
|
if (r.condition) console.log(indent(`when ${JSON.stringify(r.condition)}`, " "));
|
|
7770
8125
|
}
|
|
7771
8126
|
if (tables.length) {
|
|
7772
8127
|
console.log();
|
|
7773
|
-
console.log(
|
|
8128
|
+
console.log(import_chalk36.default.bold(` mapping tables (${tables.length})`));
|
|
7774
8129
|
for (const t of tables) {
|
|
7775
8130
|
const entries2 = Array.isArray(t.entries) ? t.entries.length : 0;
|
|
7776
|
-
console.log(` ${
|
|
8131
|
+
console.log(` ${import_chalk36.default.bold(String(t.name ?? "(unnamed)"))} ${import_chalk36.default.dim(`${entries2} entr${entries2 === 1 ? "y" : "ies"}`)}`);
|
|
7777
8132
|
}
|
|
7778
8133
|
}
|
|
7779
8134
|
console.log();
|
|
@@ -7783,10 +8138,11 @@ function indent(text, pad) {
|
|
|
7783
8138
|
}
|
|
7784
8139
|
|
|
7785
8140
|
// src/commands/recipe-install.ts
|
|
7786
|
-
var
|
|
8141
|
+
var import_chalk37 = __toESM(require("chalk"));
|
|
7787
8142
|
init_types();
|
|
7788
8143
|
init_caller();
|
|
7789
8144
|
init_api();
|
|
8145
|
+
init_paging();
|
|
7790
8146
|
async function runRecipeInstall(nameArg, opts) {
|
|
7791
8147
|
const ref = parseRecipeRef(nameArg);
|
|
7792
8148
|
const assumeYes = !!opts.yes;
|
|
@@ -7799,7 +8155,7 @@ async function runRecipeInstall(nameArg, opts) {
|
|
|
7799
8155
|
const chosenTeamId = picked.chosen ? picked.teamId : void 0;
|
|
7800
8156
|
const teamName = picked.teamName;
|
|
7801
8157
|
console.log();
|
|
7802
|
-
console.log(`${
|
|
8158
|
+
console.log(`${import_chalk37.default.bold(pinned)}${import_chalk37.default.dim(` \u2014 by ${ref.handle}`)}`);
|
|
7803
8159
|
if (recipe.summary) console.log(` ${recipe.summary}`);
|
|
7804
8160
|
console.log();
|
|
7805
8161
|
let project;
|
|
@@ -7823,7 +8179,7 @@ async function runRecipeInstall(nameArg, opts) {
|
|
|
7823
8179
|
reuseTenant = chosenTenant.existing;
|
|
7824
8180
|
printSummary(recipe, project, tenant2, reuseTenant);
|
|
7825
8181
|
if (!assumeYes && !await confirmYesNo(" continue?", interactive)) {
|
|
7826
|
-
console.log(
|
|
8182
|
+
console.log(import_chalk37.default.yellow(" Cancelled \u2014 nothing was created."));
|
|
7827
8183
|
return;
|
|
7828
8184
|
}
|
|
7829
8185
|
}
|
|
@@ -7842,18 +8198,18 @@ async function runRecipeInstall(nameArg, opts) {
|
|
|
7842
8198
|
process.stdout.write(` ${step.label.padEnd(33)}`);
|
|
7843
8199
|
try {
|
|
7844
8200
|
const note = await step.run();
|
|
7845
|
-
console.log(`${
|
|
8201
|
+
console.log(`${import_chalk37.default.green("\u2713")}${note ? ` ${import_chalk37.default.dim(note)}` : ""}`);
|
|
7846
8202
|
} catch (err) {
|
|
7847
|
-
console.log(`${
|
|
8203
|
+
console.log(`${import_chalk37.default.red("\u2717")} ${import_chalk37.default.red(describeError(err))}`);
|
|
7848
8204
|
console.log();
|
|
7849
8205
|
if (step.n === 1) {
|
|
7850
|
-
console.log(
|
|
8206
|
+
console.log(import_chalk37.default.red(`stopped at step 1 of ${steps.length}. Nothing was created.`));
|
|
7851
8207
|
console.log("run the same command again once the problem above is fixed:");
|
|
7852
|
-
console.log(
|
|
8208
|
+
console.log(import_chalk37.default.cyan(` npx apiblaze install ${nameArg}`));
|
|
7853
8209
|
} else {
|
|
7854
|
-
console.log(
|
|
8210
|
+
console.log(import_chalk37.default.red(`stopped at step ${step.n} of ${steps.length}. ${project} exists but is not finished.`));
|
|
7855
8211
|
console.log("continue with:");
|
|
7856
|
-
console.log(
|
|
8212
|
+
console.log(import_chalk37.default.cyan(` npx apiblaze install ${nameArg} --into ${project}`));
|
|
7857
8213
|
}
|
|
7858
8214
|
console.log();
|
|
7859
8215
|
process.exit(1);
|
|
@@ -7863,9 +8219,9 @@ async function runRecipeInstall(nameArg, opts) {
|
|
|
7863
8219
|
const urls = installUrls(ctx);
|
|
7864
8220
|
console.log();
|
|
7865
8221
|
console.log(`done in ${secs}s.`);
|
|
7866
|
-
console.log(` ${
|
|
7867
|
-
if (recipe.settings.mcp_enabled === true) console.log(` ${
|
|
7868
|
-
if (recipe.settings.enable_portal === true) console.log(` ${
|
|
8222
|
+
console.log(` ${import_chalk37.default.dim("proxy ")} ${import_chalk37.default.bold(urls.proxy_url)}`);
|
|
8223
|
+
if (recipe.settings.mcp_enabled === true) console.log(` ${import_chalk37.default.dim("mcp ")} ${import_chalk37.default.bold(urls.mcp_url)}`);
|
|
8224
|
+
if (recipe.settings.enable_portal === true) console.log(` ${import_chalk37.default.dim("portal")} ${import_chalk37.default.bold(urls.portal_url)}`);
|
|
7869
8225
|
console.log();
|
|
7870
8226
|
}
|
|
7871
8227
|
function buildSteps(inp) {
|
|
@@ -8118,12 +8474,15 @@ async function teamLabel(caller, teamId) {
|
|
|
8118
8474
|
}
|
|
8119
8475
|
async function owningTeam(caller, project, preferred) {
|
|
8120
8476
|
const has = async (teamId) => {
|
|
8121
|
-
const
|
|
8122
|
-
|
|
8123
|
-
|
|
8124
|
-
|
|
8125
|
-
|
|
8126
|
-
|
|
8477
|
+
const walked = await pageAll(
|
|
8478
|
+
(cursor) => producer(caller, {
|
|
8479
|
+
method: "GET",
|
|
8480
|
+
path: withPage(`/projects?team_id=${encodeURIComponent(teamId)}`, cursor),
|
|
8481
|
+
summary: `Confirm ${project} landed in ${teamId}`
|
|
8482
|
+
}),
|
|
8483
|
+
(b) => b?.projects
|
|
8484
|
+
).catch(() => ({ items: [], total: 0, truncated: false }));
|
|
8485
|
+
return walked.items.some((p) => p.project_id === project);
|
|
8127
8486
|
};
|
|
8128
8487
|
if (await has(preferred)) return preferred;
|
|
8129
8488
|
if (caller.mode === "anon") return preferred;
|
|
@@ -8159,7 +8518,7 @@ async function nameIsFree(name, teamId) {
|
|
|
8159
8518
|
async function chooseProxyName(slug, asOpt, teamId, interactive) {
|
|
8160
8519
|
if (asOpt !== void 0) {
|
|
8161
8520
|
const name = normalizeName3(asOpt);
|
|
8162
|
-
if (name !== asOpt) console.log(
|
|
8521
|
+
if (name !== asOpt) console.log(import_chalk37.default.yellow(` Using "${name}" \u2014 names are lowercase letters and digits only.`));
|
|
8163
8522
|
if (name.length < 3) throw new Error("A proxy name must be at least 3 characters (lowercase letters and digits only).");
|
|
8164
8523
|
const free = await nameIsFree(name, teamId);
|
|
8165
8524
|
if (free === false) throw new Error(`"${name}" is already taken. Pick another with --as.`);
|
|
@@ -8176,7 +8535,7 @@ async function chooseProxyName(slug, asOpt, teamId, interactive) {
|
|
|
8176
8535
|
}
|
|
8177
8536
|
if (!interactive) return suggested;
|
|
8178
8537
|
const { default: inquirer3 } = await import("inquirer");
|
|
8179
|
-
console.log(` ${
|
|
8538
|
+
console.log(` ${import_chalk37.default.dim("proxy")} ${slug} \u2192 ${import_chalk37.default.bold(suggested)} ${import_chalk37.default.dim("[enter to accept, or type a name]")}`);
|
|
8180
8539
|
for (; ; ) {
|
|
8181
8540
|
const { raw } = await inquirer3.prompt([{
|
|
8182
8541
|
type: "input",
|
|
@@ -8187,20 +8546,20 @@ async function chooseProxyName(slug, asOpt, teamId, interactive) {
|
|
|
8187
8546
|
}]);
|
|
8188
8547
|
const name = normalizeName3(raw);
|
|
8189
8548
|
if (name !== String(raw).trim()) {
|
|
8190
|
-
console.log(
|
|
8549
|
+
console.log(import_chalk37.default.yellow(` \u2192 using ${import_chalk37.default.bold(name)} (lowercase letters and digits only)`));
|
|
8191
8550
|
}
|
|
8192
8551
|
if (name.length < 3) {
|
|
8193
|
-
console.log(
|
|
8552
|
+
console.log(import_chalk37.default.yellow(" Name must be at least 3 characters.\n"));
|
|
8194
8553
|
continue;
|
|
8195
8554
|
}
|
|
8196
8555
|
const free = await nameIsFree(name, teamId);
|
|
8197
8556
|
if (free === false) {
|
|
8198
|
-
console.log(
|
|
8557
|
+
console.log(import_chalk37.default.yellow(` "${name}" is taken. Try another.
|
|
8199
8558
|
`));
|
|
8200
8559
|
continue;
|
|
8201
8560
|
}
|
|
8202
|
-
if (free === null) console.log(
|
|
8203
|
-
console.log(` ${
|
|
8561
|
+
if (free === null) console.log(import_chalk37.default.dim(" (could not verify availability; continuing)"));
|
|
8562
|
+
console.log(` ${import_chalk37.default.cyan("\u2192")} ${import_chalk37.default.bold(`https://${name}.abz.run`)}`);
|
|
8204
8563
|
return name;
|
|
8205
8564
|
}
|
|
8206
8565
|
}
|
|
@@ -8218,20 +8577,20 @@ function printSummary(recipe, project, tenant2, reuseTenant = false) {
|
|
|
8218
8577
|
const keyTypes = recipe.auth?.key_types?.length ?? 0;
|
|
8219
8578
|
const questions = reuseTenant ? 0 : recipe.questions?.length ?? 0;
|
|
8220
8579
|
const row = (label3, value, note) => {
|
|
8221
|
-
console.log(` ${
|
|
8580
|
+
console.log(` ${import_chalk37.default.dim(label3.padEnd(21))}${value}${note ? import_chalk37.default.dim(` ${note}`) : ""}`);
|
|
8222
8581
|
};
|
|
8223
8582
|
console.log();
|
|
8224
|
-
console.log(
|
|
8583
|
+
console.log(import_chalk37.default.bold(" This will:"));
|
|
8225
8584
|
console.log();
|
|
8226
|
-
row("create a proxy",
|
|
8227
|
-
row("for its users",
|
|
8585
|
+
row("create a proxy", import_chalk37.default.bold(project), `at https://${project}.abz.run`);
|
|
8586
|
+
row("for its users", import_chalk37.default.bold(tenant2), reuseTenant ? "(one you already have)" : "(a new tenant)");
|
|
8228
8587
|
if (hosts.length) row("sending requests to", hosts.join(", "));
|
|
8229
8588
|
row("and set up", describeCreates(keyTypes, routes, recipe.settings.mcp_enabled === true, false));
|
|
8230
8589
|
row("asking you for", questions ? `${questions} value${questions === 1 ? "" : "s"}` : "nothing");
|
|
8231
8590
|
if (reuseTenant) {
|
|
8232
8591
|
console.log();
|
|
8233
|
-
console.log(
|
|
8234
|
-
console.log(
|
|
8592
|
+
console.log(import_chalk37.default.dim(` ${tenant2} is used exactly as it is \u2014 its existing login stays, and this`));
|
|
8593
|
+
console.log(import_chalk37.default.dim(" recipe's own login setup is not applied."));
|
|
8235
8594
|
}
|
|
8236
8595
|
console.log();
|
|
8237
8596
|
}
|
|
@@ -8240,11 +8599,11 @@ function declineAll(items) {
|
|
|
8240
8599
|
const upstreams = items.filter((i) => i.kind === "upstream");
|
|
8241
8600
|
console.log();
|
|
8242
8601
|
for (const u of upstreams) {
|
|
8243
|
-
console.log(
|
|
8602
|
+
console.log(import_chalk37.default.yellow(` --yes: this proxy sends every request to ${import_chalk37.default.bold(u.value)}.`));
|
|
8244
8603
|
}
|
|
8245
8604
|
if (skippable.length) {
|
|
8246
|
-
console.log(
|
|
8247
|
-
for (const i of skippable) console.log(
|
|
8605
|
+
console.log(import_chalk37.default.yellow(` --yes: installing without ${skippable.length} setting${skippable.length === 1 ? "" : "s"} that need consent.`));
|
|
8606
|
+
for (const i of skippable) console.log(import_chalk37.default.dim(` skipped ${i.kind} ${i.value}`));
|
|
8248
8607
|
}
|
|
8249
8608
|
return new Set(skippable.map((i) => i.kind));
|
|
8250
8609
|
}
|
|
@@ -8262,14 +8621,14 @@ async function askConsent(items, interactive) {
|
|
|
8262
8621
|
const count = items.length;
|
|
8263
8622
|
const word = ["", "it", "both", "all three", "all four", "all five", "all six"][count] ?? `all ${count}`;
|
|
8264
8623
|
console.log();
|
|
8265
|
-
console.log(
|
|
8624
|
+
console.log(import_chalk37.default.yellow("\u26A0 This recipe changes who can reach your proxy, what it trusts, and what it can cost."));
|
|
8266
8625
|
console.log();
|
|
8267
8626
|
for (const item of items) {
|
|
8268
|
-
console.log(` ${
|
|
8269
|
-
for (const line of wrapText(item.why, 62)) console.log(` ${" ".repeat(16)} ${
|
|
8627
|
+
console.log(` ${import_chalk37.default.bold(item.kind.padEnd(16))} ${item.value}`);
|
|
8628
|
+
for (const line of wrapText(item.why, 62)) console.log(` ${" ".repeat(16)} ${import_chalk37.default.dim(line)}`);
|
|
8270
8629
|
}
|
|
8271
8630
|
console.log();
|
|
8272
|
-
console.log(`Type "${
|
|
8631
|
+
console.log(`Type "${import_chalk37.default.bold("yes")}" to accept ${word}.`);
|
|
8273
8632
|
console.log("Press enter to install the recipe without them.");
|
|
8274
8633
|
let typed = "";
|
|
8275
8634
|
if (interactive) {
|
|
@@ -8281,7 +8640,7 @@ async function askConsent(items, interactive) {
|
|
|
8281
8640
|
for (const item of items) {
|
|
8282
8641
|
declined.add(item.kind === "transform" ? `transform:${item.value}` : `kind:${item.kind}`);
|
|
8283
8642
|
}
|
|
8284
|
-
console.log(
|
|
8643
|
+
console.log(import_chalk37.default.dim(` Installing without ${count} setting${count === 1 ? "" : "s"} \u2014 everything else is applied.`));
|
|
8285
8644
|
return declined;
|
|
8286
8645
|
}
|
|
8287
8646
|
async function askQuestions(questions, ctx, interactive) {
|
|
@@ -8294,13 +8653,13 @@ async function askQuestions(questions, ctx, interactive) {
|
|
|
8294
8653
|
const BOX = 64;
|
|
8295
8654
|
const title = "\u2500\u2500 from the publisher ";
|
|
8296
8655
|
console.log();
|
|
8297
|
-
console.log(` ${
|
|
8298
|
-
for (const line of fillContext(q.before, ctx).split("\n")) console.log(` ${
|
|
8299
|
-
console.log(` ${
|
|
8656
|
+
console.log(` ${import_chalk37.default.dim(title + "\u2500".repeat(BOX - title.length))}`);
|
|
8657
|
+
for (const line of fillContext(q.before, ctx).split("\n")) console.log(` ${import_chalk37.default.dim("\u2502")} ${line}`);
|
|
8658
|
+
console.log(` ${import_chalk37.default.dim("\u2500".repeat(BOX))}`);
|
|
8300
8659
|
}
|
|
8301
8660
|
if (fromEnv !== void 0 && fromEnv !== "") {
|
|
8302
8661
|
answers[q.id] = fromEnv;
|
|
8303
|
-
console.log(` ${q.prompt}: ${
|
|
8662
|
+
console.log(` ${q.prompt}: ${import_chalk37.default.dim(`(from ${envName})`)}`);
|
|
8304
8663
|
continue;
|
|
8305
8664
|
}
|
|
8306
8665
|
if (!interactive) {
|
|
@@ -8310,7 +8669,7 @@ async function askQuestions(questions, ctx, interactive) {
|
|
|
8310
8669
|
);
|
|
8311
8670
|
}
|
|
8312
8671
|
const { default: inquirer3 } = await import("inquirer");
|
|
8313
|
-
if (q.example && !q.secret) console.log(
|
|
8672
|
+
if (q.example && !q.secret) console.log(import_chalk37.default.dim(` e.g. ${q.example}`));
|
|
8314
8673
|
const { value } = await inquirer3.prompt([{
|
|
8315
8674
|
// `secret: true` masks the input — the Google client secret is typed once,
|
|
8316
8675
|
// goes to provider creation, and is never echoed or stored.
|
|
@@ -8344,30 +8703,30 @@ async function confirmReinstall(recipe, project, tenant2, interactive) {
|
|
|
8344
8703
|
const tables = recipe.transformations?.mapping_tables?.length ?? 0;
|
|
8345
8704
|
const routes = countRoutes(recipe.openapi);
|
|
8346
8705
|
const keyTypes = recipe.auth?.key_types?.length ?? 0;
|
|
8347
|
-
console.log(`This installs ${
|
|
8706
|
+
console.log(`This installs ${import_chalk37.default.bold(`${recipe.name}@${recipe.revision}`)} over ${import_chalk37.default.bold(project)}.`);
|
|
8348
8707
|
console.log();
|
|
8349
|
-
console.log(` ${
|
|
8708
|
+
console.log(` ${import_chalk37.default.dim("settings ".padEnd(16))}replaced`);
|
|
8350
8709
|
if (rules || tables) {
|
|
8351
|
-
console.log(` ${
|
|
8710
|
+
console.log(` ${import_chalk37.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`);
|
|
8352
8711
|
}
|
|
8353
8712
|
if (routes) {
|
|
8354
|
-
console.log(` ${
|
|
8713
|
+
console.log(` ${import_chalk37.default.dim("api spec ".padEnd(16))}replaced, ${routes} routes regenerated${recipe.settings.mcp_enabled === true ? ", MCP rebuilt" : ""}`);
|
|
8355
8714
|
}
|
|
8356
8715
|
if (keyTypes) {
|
|
8357
|
-
console.log(` ${
|
|
8716
|
+
console.log(` ${import_chalk37.default.dim("key types ".padEnd(16))}${keyTypes} added if missing; existing types left alone`);
|
|
8358
8717
|
}
|
|
8359
8718
|
console.log();
|
|
8360
|
-
console.log(` ${
|
|
8719
|
+
console.log(` ${import_chalk37.default.dim("unchanged ".padEnd(16))}your API keys, your proxy URL, your users,`);
|
|
8361
8720
|
console.log(` ${" ".repeat(16)}and any tenant other than ${tenant2}`);
|
|
8362
8721
|
console.log();
|
|
8363
|
-
console.log(
|
|
8722
|
+
console.log(import_chalk37.default.yellow("This cannot be undone."));
|
|
8364
8723
|
if (!interactive) {
|
|
8365
8724
|
throw new Error("Reinstall needs a typed confirmation and there is no terminal to ask on.");
|
|
8366
8725
|
}
|
|
8367
8726
|
const { default: inquirer3 } = await import("inquirer");
|
|
8368
8727
|
const { typed } = await inquirer3.prompt([{ type: "input", name: "typed", message: `Type the proxy name (${project}) to continue:` }]);
|
|
8369
8728
|
if (String(typed ?? "").trim() !== project) {
|
|
8370
|
-
console.log(
|
|
8729
|
+
console.log(import_chalk37.default.yellow("Cancelled \u2014 nothing was changed."));
|
|
8371
8730
|
process.exit(0);
|
|
8372
8731
|
}
|
|
8373
8732
|
}
|
|
@@ -8375,7 +8734,7 @@ function requireCallerForInstall() {
|
|
|
8375
8734
|
try {
|
|
8376
8735
|
return requireCaller();
|
|
8377
8736
|
} catch {
|
|
8378
|
-
console.error(
|
|
8737
|
+
console.error(import_chalk37.default.red(
|
|
8379
8738
|
"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)."
|
|
8380
8739
|
));
|
|
8381
8740
|
process.exit(1);
|
|
@@ -8411,7 +8770,7 @@ async function chooseTeam(caller, optTeam, interactive) {
|
|
|
8411
8770
|
message: " which team should own this proxy?",
|
|
8412
8771
|
default: active.teamId,
|
|
8413
8772
|
choices: teams.map((t) => ({
|
|
8414
|
-
name: t.teamId === active.teamId ? `${t.name} ${
|
|
8773
|
+
name: t.teamId === active.teamId ? `${t.name} ${import_chalk37.default.dim("(active)")}` : t.name,
|
|
8415
8774
|
value: t.teamId
|
|
8416
8775
|
}))
|
|
8417
8776
|
}]);
|
|
@@ -8439,10 +8798,10 @@ async function chooseTenant(caller, project, teamId, optTenant, interactive) {
|
|
|
8439
8798
|
message: " where should this proxy's users live?",
|
|
8440
8799
|
default: "__new__",
|
|
8441
8800
|
choices: [
|
|
8442
|
-
{ name: `create ${fresh} ${
|
|
8443
|
-
new inquirer3.Separator(
|
|
8801
|
+
{ name: `create ${fresh} ${import_chalk37.default.dim("(a fresh tenant, set up by the recipe)")}`, value: "__new__" },
|
|
8802
|
+
new inquirer3.Separator(import_chalk37.default.dim(" \u2014 or reuse one you already have \u2014")),
|
|
8444
8803
|
...owned.map((t2) => ({
|
|
8445
|
-
name: `${t2} ${
|
|
8804
|
+
name: `${t2} ${import_chalk37.default.dim("(existing \u2014 its own login is kept)")}`,
|
|
8446
8805
|
value: t2
|
|
8447
8806
|
}))
|
|
8448
8807
|
]
|
|
@@ -8460,16 +8819,19 @@ async function chooseTenant(caller, project, teamId, optTenant, interactive) {
|
|
|
8460
8819
|
return { tenant: t, existing: false };
|
|
8461
8820
|
}
|
|
8462
8821
|
async function teamTenants(caller, teamId) {
|
|
8463
|
-
const
|
|
8464
|
-
|
|
8465
|
-
|
|
8466
|
-
|
|
8467
|
-
|
|
8468
|
-
|
|
8822
|
+
const walked = await pageAll(
|
|
8823
|
+
(cursor) => producer(caller, {
|
|
8824
|
+
method: "GET",
|
|
8825
|
+
path: withPage(`/teams/${encodeURIComponent(teamId)}/tenants`, cursor),
|
|
8826
|
+
summary: "List the team's tenants"
|
|
8827
|
+
}),
|
|
8828
|
+
(b) => b?.tenants
|
|
8829
|
+
).catch(() => ({ items: [], total: 0, truncated: false }));
|
|
8830
|
+
return walked.items.map((t) => typeof t === "string" ? t : t.disabled ? "" : t.tenant_name ?? t.name ?? "").filter(Boolean);
|
|
8469
8831
|
}
|
|
8470
8832
|
|
|
8471
8833
|
// src/commands/recipe-publish.ts
|
|
8472
|
-
var
|
|
8834
|
+
var import_chalk38 = __toESM(require("chalk"));
|
|
8473
8835
|
var import_ora14 = __toESM(require("ora"));
|
|
8474
8836
|
init_caller();
|
|
8475
8837
|
init_auth();
|
|
@@ -8479,15 +8841,15 @@ async function runRecipePublish(projectArg, opts) {
|
|
|
8479
8841
|
try {
|
|
8480
8842
|
resolved = resolveRecipeName(opts.as, creds?.githubHandle, normalizeName3(projectArg));
|
|
8481
8843
|
} catch (err) {
|
|
8482
|
-
console.error(
|
|
8844
|
+
console.error(import_chalk38.default.red(err instanceof Error ? err.message : String(err)));
|
|
8483
8845
|
process.exitCode = 1;
|
|
8484
8846
|
return;
|
|
8485
8847
|
}
|
|
8486
8848
|
const ref = resolved.ref;
|
|
8487
8849
|
if (resolved.derivedHandle) {
|
|
8488
|
-
console.log(
|
|
8850
|
+
console.log(import_chalk38.default.dim(` publishing as ${import_chalk38.default.bold(ref.name)}`));
|
|
8489
8851
|
if (resolved.normalizedFrom) {
|
|
8490
|
-
console.log(
|
|
8852
|
+
console.log(import_chalk38.default.dim(` (your GitHub login is ${resolved.normalizedFrom}; apiblaze names are letters and digits only)`));
|
|
8491
8853
|
}
|
|
8492
8854
|
}
|
|
8493
8855
|
const interactive = !!process.stdin.isTTY && !opts.json;
|
|
@@ -8506,7 +8868,7 @@ async function runRecipePublish(projectArg, opts) {
|
|
|
8506
8868
|
let tenant2 = opts.tenant;
|
|
8507
8869
|
if (!prep.tenant_used && prep.tenants.length > 1) {
|
|
8508
8870
|
if (!interactive) {
|
|
8509
|
-
console.error(
|
|
8871
|
+
console.error(import_chalk38.default.red(
|
|
8510
8872
|
`${proj2.projectId} has ${prep.tenants.length} tenants (${prep.tenants.join(", ")}). Pass --tenant <slug> to say which auth setup the recipe carries.`
|
|
8511
8873
|
));
|
|
8512
8874
|
process.exit(1);
|
|
@@ -8522,7 +8884,7 @@ async function runRecipePublish(projectArg, opts) {
|
|
|
8522
8884
|
prep = await prepare(caller, proj2.projectId, proj2.apiVersion, ref.name, tenant2);
|
|
8523
8885
|
}
|
|
8524
8886
|
if (!prep.name_available) {
|
|
8525
|
-
console.error(
|
|
8887
|
+
console.error(import_chalk38.default.red(`${ref.name} belongs to another team, so you cannot publish a revision of it.`));
|
|
8526
8888
|
process.exit(1);
|
|
8527
8889
|
}
|
|
8528
8890
|
if (prep.envelope_violations.length) {
|
|
@@ -8532,13 +8894,13 @@ async function runRecipePublish(projectArg, opts) {
|
|
|
8532
8894
|
if (prep.dropped.length) {
|
|
8533
8895
|
console.log();
|
|
8534
8896
|
console.log(`${prep.dropped.length} setting${prep.dropped.length === 1 ? " was" : "s were"} not included, because they are not on the publish list:`);
|
|
8535
|
-
for (const d of prep.dropped) console.log(
|
|
8897
|
+
for (const d of prep.dropped) console.log(import_chalk38.default.dim(` ${d}`));
|
|
8536
8898
|
if (prep.hidden_tables_excluded) {
|
|
8537
|
-
console.log(
|
|
8899
|
+
console.log(import_chalk38.default.dim(` (${prep.hidden_tables_excluded} hidden mapping table${prep.hidden_tables_excluded === 1 ? "" : "s"} excluded \u2014 those values belong to your consumers.)`));
|
|
8538
8900
|
}
|
|
8539
8901
|
console.log();
|
|
8540
8902
|
if (!opts.yes && !await confirm(" continue without them?", interactive)) {
|
|
8541
|
-
console.log(
|
|
8903
|
+
console.log(import_chalk38.default.yellow("Cancelled."));
|
|
8542
8904
|
return;
|
|
8543
8905
|
}
|
|
8544
8906
|
}
|
|
@@ -8551,12 +8913,12 @@ async function runRecipePublish(projectArg, opts) {
|
|
|
8551
8913
|
console.log();
|
|
8552
8914
|
for (const f of prep.findings) printFinding(f);
|
|
8553
8915
|
console.log();
|
|
8554
|
-
console.error(
|
|
8916
|
+
console.error(import_chalk38.default.red("--yes cannot accept scanner findings. Fix the value in the proxy, or publish interactively and decide."));
|
|
8555
8917
|
process.exit(1);
|
|
8556
8918
|
}
|
|
8557
8919
|
if (!interactive) {
|
|
8558
8920
|
for (const f of prep.findings) printFinding(f);
|
|
8559
|
-
console.error(
|
|
8921
|
+
console.error(import_chalk38.default.red("\nThese need a decision and there is no terminal to ask on."));
|
|
8560
8922
|
process.exit(1);
|
|
8561
8923
|
}
|
|
8562
8924
|
const { default: inquirer3 } = await import("inquirer");
|
|
@@ -8577,10 +8939,10 @@ async function runRecipePublish(projectArg, opts) {
|
|
|
8577
8939
|
if (verdict === "ask") promotions.push(await askPromotion(f.path));
|
|
8578
8940
|
}
|
|
8579
8941
|
}
|
|
8580
|
-
console.log(
|
|
8942
|
+
console.log(import_chalk38.default.dim(`we cannot judge the other ${prep.unjudged_count} values. run \`apiblaze show ${ref.name}\` after publishing to read the whole recipe.`));
|
|
8581
8943
|
const totalQuestions = prep.question_ids.length + promotions.length;
|
|
8582
8944
|
if (totalQuestions > 5) {
|
|
8583
|
-
console.log(
|
|
8945
|
+
console.log(import_chalk38.default.yellow(`
|
|
8584
8946
|
\u26A0 installers will have to supply ${totalQuestions} values before this works.`));
|
|
8585
8947
|
}
|
|
8586
8948
|
const visibility = opts.private ? "private" : "public";
|
|
@@ -8588,11 +8950,11 @@ async function runRecipePublish(projectArg, opts) {
|
|
|
8588
8950
|
if (visibility === "public" && prep.spec_will_become_public) {
|
|
8589
8951
|
console.log();
|
|
8590
8952
|
console.log("Publishing publicly also makes this API's spec readable at");
|
|
8591
|
-
console.log(` ${
|
|
8953
|
+
console.log(` ${import_chalk38.default.bold(`https://${proj2.projectId}.abz.run/${proj2.apiVersion}/openapi.json`)}`);
|
|
8592
8954
|
console.log("It is currently private.");
|
|
8593
8955
|
confirmSpecPublic = opts.yes ? true : await confirm("Continue?", interactive);
|
|
8594
8956
|
if (!confirmSpecPublic) {
|
|
8595
|
-
console.log(
|
|
8957
|
+
console.log(import_chalk38.default.yellow("Cancelled \u2014 nothing was published."));
|
|
8596
8958
|
return;
|
|
8597
8959
|
}
|
|
8598
8960
|
}
|
|
@@ -8618,7 +8980,7 @@ async function runRecipePublish(projectArg, opts) {
|
|
|
8618
8980
|
},
|
|
8619
8981
|
summary: `Publish ${proj2.projectId} as ${ref.name} (${visibility})`
|
|
8620
8982
|
});
|
|
8621
|
-
spin2?.succeed(`Published ${
|
|
8983
|
+
spin2?.succeed(`Published ${import_chalk38.default.bold(`${out.name}@${out.revision}`)} (${out.visibility}).`);
|
|
8622
8984
|
} catch (err) {
|
|
8623
8985
|
spin2?.fail("Publish failed.");
|
|
8624
8986
|
throw err;
|
|
@@ -8627,15 +8989,15 @@ async function runRecipePublish(projectArg, opts) {
|
|
|
8627
8989
|
console.log(JSON.stringify(out));
|
|
8628
8990
|
return;
|
|
8629
8991
|
}
|
|
8630
|
-
console.log(` ${
|
|
8631
|
-
console.log(` ${
|
|
8992
|
+
console.log(` ${import_chalk38.default.dim("size ")} ${(out.size_bytes / 1024).toFixed(1)} KB`);
|
|
8993
|
+
console.log(` ${import_chalk38.default.dim("checksum")} ${out.sha256.slice(0, 16)}\u2026`);
|
|
8632
8994
|
console.log();
|
|
8633
8995
|
if (out.visibility === "public") {
|
|
8634
|
-
console.log(
|
|
8996
|
+
console.log(import_chalk38.default.dim(` Anyone can now run: ${import_chalk38.default.cyan(`npx apiblaze install ${out.name}`)}`));
|
|
8635
8997
|
} else {
|
|
8636
|
-
console.log(
|
|
8998
|
+
console.log(import_chalk38.default.dim(" Private \u2014 only members of this team can see or install it."));
|
|
8637
8999
|
}
|
|
8638
|
-
console.log(
|
|
9000
|
+
console.log(import_chalk38.default.dim(` Pull it back with: ${import_chalk38.default.cyan(`npx apiblaze withdraw ${out.name}@${out.revision}`)}`));
|
|
8639
9001
|
console.log();
|
|
8640
9002
|
}
|
|
8641
9003
|
async function prepare(caller, projectId, apiVersion, name, tenant2) {
|
|
@@ -8648,23 +9010,23 @@ async function prepare(caller, projectId, apiVersion, name, tenant2) {
|
|
|
8648
9010
|
}
|
|
8649
9011
|
function printFinding(f) {
|
|
8650
9012
|
const preview = (f.preview ?? "").replace(/\s+/g, " ").trim();
|
|
8651
|
-
console.log(` ${
|
|
8652
|
-
console.log(` ${
|
|
9013
|
+
console.log(` ${import_chalk38.default.bold(f.path)}`);
|
|
9014
|
+
console.log(` ${import_chalk38.default.yellow(preview.padEnd(38))}${import_chalk38.default.dim(f.why)}`);
|
|
8653
9015
|
}
|
|
8654
9016
|
function printEnvelopeViolations(violations) {
|
|
8655
9017
|
console.log();
|
|
8656
|
-
console.error(
|
|
9018
|
+
console.error(import_chalk38.default.red("This proxy has transform rules a recipe cannot carry."));
|
|
8657
9019
|
console.log();
|
|
8658
9020
|
for (const v of violations) {
|
|
8659
|
-
console.log(` ${
|
|
9021
|
+
console.log(` ${import_chalk38.default.bold(v.rule)} ${import_chalk38.default.dim(`(${v.kind}s ${v.field})`)}`);
|
|
8660
9022
|
console.log(` ${v.why}`);
|
|
8661
9023
|
}
|
|
8662
9024
|
console.log();
|
|
8663
9025
|
console.log("A recipe runs in a stranger's account, after apiblaze has attached their end user's");
|
|
8664
9026
|
console.log("real third-party token, so two rules are absolute:");
|
|
8665
|
-
console.log(` ${
|
|
9027
|
+
console.log(` ${import_chalk38.default.dim("\u2022")} a rule may never READ Authorization, Cookie, or the third-party token headers \u2014`);
|
|
8666
9028
|
console.log(" those hold a live credential that works at the provider without apiblaze;");
|
|
8667
|
-
console.log(` ${
|
|
9029
|
+
console.log(` ${import_chalk38.default.dim("\u2022")} a rule may never WRITE an identity header (x-end-user-id and friends) \u2014`);
|
|
8668
9030
|
console.log(" those tell the installer's own backend who is calling.");
|
|
8669
9031
|
console.log();
|
|
8670
9032
|
console.log("Writing Authorization from a question or a fixed value is fine \u2014 that is the");
|
|
@@ -8682,7 +9044,7 @@ async function askPromotion(path11) {
|
|
|
8682
9044
|
{ type: "input", name: "id", message: " id:", default: suggestedId }
|
|
8683
9045
|
]);
|
|
8684
9046
|
const id = String(answers.id || suggestedId).toLowerCase().replace(/[^a-z0-9_]/g, "_");
|
|
8685
|
-
console.log(
|
|
9047
|
+
console.log(import_chalk38.default.dim(` \u2192 replaced with {{${id}}}`));
|
|
8686
9048
|
return {
|
|
8687
9049
|
path: path11,
|
|
8688
9050
|
id,
|
|
@@ -8701,7 +9063,7 @@ async function confirm(message, interactive) {
|
|
|
8701
9063
|
async function runRecipeWithdraw(nameArg, opts) {
|
|
8702
9064
|
const ref = parseRecipeRef(nameArg);
|
|
8703
9065
|
if (ref.revision === void 0) {
|
|
8704
|
-
console.error(
|
|
9066
|
+
console.error(import_chalk38.default.red(`Name the revision to withdraw, e.g. ${nameArg}@3. Revisions are withdrawn one at a time.`));
|
|
8705
9067
|
process.exit(1);
|
|
8706
9068
|
}
|
|
8707
9069
|
const interactive = !!process.stdin.isTTY && !opts.json;
|
|
@@ -8721,11 +9083,11 @@ async function runRecipeWithdraw(nameArg, opts) {
|
|
|
8721
9083
|
const { default: inquirer3 } = await import("inquirer");
|
|
8722
9084
|
const { typed } = await inquirer3.prompt([{ type: "input", name: "typed", message: `type the recipe name to confirm (${ref.name}):` }]);
|
|
8723
9085
|
if (String(typed ?? "").trim() !== ref.name) {
|
|
8724
|
-
console.log(
|
|
9086
|
+
console.log(import_chalk38.default.yellow("Cancelled \u2014 nothing was withdrawn."));
|
|
8725
9087
|
return;
|
|
8726
9088
|
}
|
|
8727
9089
|
} else {
|
|
8728
|
-
console.error(
|
|
9090
|
+
console.error(import_chalk38.default.red("Withdrawing needs a typed confirmation and there is no terminal to ask on."));
|
|
8729
9091
|
process.exit(1);
|
|
8730
9092
|
}
|
|
8731
9093
|
const spinner = !opts.json ? (0, import_ora14.default)("Withdrawing...").start() : null;
|
|
@@ -8754,33 +9116,33 @@ async function runRecipeWithdraw(nameArg, opts) {
|
|
|
8754
9116
|
}
|
|
8755
9117
|
|
|
8756
9118
|
// src/commands/logs.ts
|
|
8757
|
-
var
|
|
9119
|
+
var import_chalk40 = __toESM(require("chalk"));
|
|
8758
9120
|
init_admin();
|
|
8759
9121
|
init_auth();
|
|
8760
9122
|
init_resolve();
|
|
8761
9123
|
|
|
8762
9124
|
// src/lib/log-view.ts
|
|
8763
|
-
var
|
|
9125
|
+
var import_chalk39 = __toESM(require("chalk"));
|
|
8764
9126
|
var readline2 = __toESM(require("readline"));
|
|
8765
9127
|
function statusColor(status) {
|
|
8766
|
-
if (status < 300) return
|
|
8767
|
-
if (status < 400) return
|
|
8768
|
-
if (status < 500) return
|
|
8769
|
-
return
|
|
9128
|
+
if (status < 300) return import_chalk39.default.green;
|
|
9129
|
+
if (status < 400) return import_chalk39.default.blue;
|
|
9130
|
+
if (status < 500) return import_chalk39.default.yellow;
|
|
9131
|
+
return import_chalk39.default.red;
|
|
8770
9132
|
}
|
|
8771
9133
|
function methodColor(method) {
|
|
8772
9134
|
switch ((method || "").toUpperCase()) {
|
|
8773
9135
|
case "GET":
|
|
8774
|
-
return
|
|
9136
|
+
return import_chalk39.default.green;
|
|
8775
9137
|
case "POST":
|
|
8776
|
-
return
|
|
9138
|
+
return import_chalk39.default.blue;
|
|
8777
9139
|
case "PUT":
|
|
8778
9140
|
case "PATCH":
|
|
8779
|
-
return
|
|
9141
|
+
return import_chalk39.default.yellow;
|
|
8780
9142
|
case "DELETE":
|
|
8781
|
-
return
|
|
9143
|
+
return import_chalk39.default.red;
|
|
8782
9144
|
default:
|
|
8783
|
-
return
|
|
9145
|
+
return import_chalk39.default.white;
|
|
8784
9146
|
}
|
|
8785
9147
|
}
|
|
8786
9148
|
function clock(ts) {
|
|
@@ -8792,12 +9154,12 @@ function routeCell(route) {
|
|
|
8792
9154
|
return (r.length > ROUTE_W ? `\u2026${r.slice(-(ROUTE_W - 1))}` : r).padEnd(ROUTE_W);
|
|
8793
9155
|
}
|
|
8794
9156
|
function formatRow(r) {
|
|
8795
|
-
const time =
|
|
9157
|
+
const time = import_chalk39.default.dim(clock(r.ts));
|
|
8796
9158
|
const method = methodColor(r.method)(String(r.method || "\u2014").padEnd(6));
|
|
8797
9159
|
const route = routeCell(String(r.route || "\u2014"));
|
|
8798
9160
|
const status = statusColor(r.status)(String(r.status ?? "\u2014").padStart(3));
|
|
8799
|
-
const ms =
|
|
8800
|
-
const step = r.error_step ?
|
|
9161
|
+
const ms = import_chalk39.default.dim(`${r.duration_ms}ms`.padStart(8));
|
|
9162
|
+
const step = r.error_step ? import_chalk39.default.dim(` ${r.error_step}`) : "";
|
|
8801
9163
|
return `${time} ${method} ${route} ${status} ${ms}${step}`;
|
|
8802
9164
|
}
|
|
8803
9165
|
function printRow(r, json) {
|
|
@@ -8812,15 +9174,15 @@ var WindowNotices = class {
|
|
|
8812
9174
|
note(res) {
|
|
8813
9175
|
if (res.history_unavailable && !this.saidUnavailable) {
|
|
8814
9176
|
this.saidUnavailable = true;
|
|
8815
|
-
console.error(
|
|
9177
|
+
console.error(import_chalk39.default.yellow("\u26A0 log history is unreachable right now \u2014 showing the live buffer only, older requests exist but cannot be read."));
|
|
8816
9178
|
}
|
|
8817
9179
|
if ((res.history_markers?.length ?? 0) > 0 && !this.saidGap) {
|
|
8818
9180
|
this.saidGap = true;
|
|
8819
|
-
console.error(
|
|
9181
|
+
console.error(import_chalk39.default.yellow("\u26A0 this range includes a volume-ceiling gap \u2014 some rows were dropped at intake and are gone; counts are incomplete."));
|
|
8820
9182
|
}
|
|
8821
9183
|
if ((res.dropped_this_hour ?? 0) > 0 && !this.saidDropped) {
|
|
8822
9184
|
this.saidDropped = true;
|
|
8823
|
-
console.error(
|
|
9185
|
+
console.error(import_chalk39.default.yellow(`\u26A0 volume ceiling \u2014 ${res.dropped_this_hour} rows dropped this hour; errors keep logging.`));
|
|
8824
9186
|
}
|
|
8825
9187
|
}
|
|
8826
9188
|
};
|
|
@@ -8879,13 +9241,13 @@ async function runTailSession(opts) {
|
|
|
8879
9241
|
if (!held.length && !heldRowsDropped && !heldNotesDropped) return;
|
|
8880
9242
|
const rows = held.filter((h) => h.kind === "row").length;
|
|
8881
9243
|
const total = rows + heldRowsDropped;
|
|
8882
|
-
if (total) console.log(
|
|
9244
|
+
if (total) console.log(import_chalk39.default.dim(`\u2500\u2500\u2500 ${total} request${total === 1 ? "" : "s"} arrived while paused \u2500\u2500\u2500`));
|
|
8883
9245
|
for (const h of held) {
|
|
8884
9246
|
if (h.kind === "row") printRow(h.row, opts.json);
|
|
8885
9247
|
else console.error(h.msg);
|
|
8886
9248
|
}
|
|
8887
9249
|
if (heldRowsDropped || heldNotesDropped) {
|
|
8888
|
-
console.log(
|
|
9250
|
+
console.log(import_chalk39.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`));
|
|
8889
9251
|
}
|
|
8890
9252
|
held.length = 0;
|
|
8891
9253
|
heldRowsDropped = 0;
|
|
@@ -8899,7 +9261,7 @@ async function runTailSession(opts) {
|
|
|
8899
9261
|
activeSocket?.close();
|
|
8900
9262
|
} catch {
|
|
8901
9263
|
}
|
|
8902
|
-
if (!opts.json) console.log(
|
|
9264
|
+
if (!opts.json) console.log(import_chalk39.default.dim("\nStopped."));
|
|
8903
9265
|
process.exit(0);
|
|
8904
9266
|
}
|
|
8905
9267
|
function onKeypress(_s, key) {
|
|
@@ -8927,17 +9289,17 @@ async function runTailSession(opts) {
|
|
|
8927
9289
|
async function openInspector() {
|
|
8928
9290
|
if (paused) return;
|
|
8929
9291
|
if (recent.length === 0) {
|
|
8930
|
-
console.log(
|
|
9292
|
+
console.log(import_chalk39.default.dim(" nothing captured yet \u2014 waiting for a request\u2026"));
|
|
8931
9293
|
return;
|
|
8932
9294
|
}
|
|
8933
9295
|
paused = true;
|
|
8934
9296
|
unbindKeys();
|
|
8935
9297
|
try {
|
|
8936
9298
|
const { default: inquirer3 } = await import("inquirer");
|
|
8937
|
-
console.log(
|
|
9299
|
+
console.log(import_chalk39.default.dim("\n\u2500\u2500\u2500 paused \xB7 still capturing in the background \u2500\u2500\u2500"));
|
|
8938
9300
|
for (; ; ) {
|
|
8939
9301
|
const choices = recent.slice(-TAIL_INSPECT_CHOICES).reverse().map((r) => ({ name: formatRow(r), value: r.request_id }));
|
|
8940
|
-
choices.push({ name:
|
|
9302
|
+
choices.push({ name: import_chalk39.default.dim("\u2039resume tail\u203A"), value: "__resume__" });
|
|
8941
9303
|
const { picked } = await inquirer3.prompt([{
|
|
8942
9304
|
type: "list",
|
|
8943
9305
|
name: "picked",
|
|
@@ -8948,12 +9310,12 @@ async function runTailSession(opts) {
|
|
|
8948
9310
|
if (picked === "__resume__") break;
|
|
8949
9311
|
const row = recent.find((r) => r.request_id === picked);
|
|
8950
9312
|
if (row) printDetail(row, opts.detail);
|
|
8951
|
-
else console.log(
|
|
9313
|
+
else console.log(import_chalk39.default.dim(" that request has scrolled out of the tail buffer \u2014 find it with `logs list`.\n"));
|
|
8952
9314
|
}
|
|
8953
9315
|
} finally {
|
|
8954
9316
|
paused = false;
|
|
8955
9317
|
flushHeld();
|
|
8956
|
-
console.log(
|
|
9318
|
+
console.log(import_chalk39.default.dim("\u2500\u2500\u2500 resumed \u2500\u2500\u2500"));
|
|
8957
9319
|
bindKeys();
|
|
8958
9320
|
}
|
|
8959
9321
|
}
|
|
@@ -8963,12 +9325,12 @@ async function runTailSession(opts) {
|
|
|
8963
9325
|
if (reconnects >= MAX_RECONNECTS) {
|
|
8964
9326
|
paused = false;
|
|
8965
9327
|
flushHeld();
|
|
8966
|
-
console.error(
|
|
9328
|
+
console.error(import_chalk39.default.red(`Connection lost \u2014 giving up after repeated reconnects${lastFailure ? ` (last error: ${lastFailure})` : ""}.`));
|
|
8967
9329
|
process.exit(1);
|
|
8968
9330
|
}
|
|
8969
9331
|
const delay = Math.min(1e3 * 2 ** reconnects, 15e3);
|
|
8970
9332
|
reconnects++;
|
|
8971
|
-
diag(
|
|
9333
|
+
diag(import_chalk39.default.dim(`disconnected${lastFailure ? ` (${lastFailure})` : ""} \u2014 reconnecting in ${Math.round(delay / 1e3)}s\u2026`));
|
|
8972
9334
|
setTimeout(() => {
|
|
8973
9335
|
if (!closed) void connect();
|
|
8974
9336
|
}, delay);
|
|
@@ -8979,7 +9341,7 @@ async function runTailSession(opts) {
|
|
|
8979
9341
|
url = await opts.mintTicket();
|
|
8980
9342
|
} catch (err) {
|
|
8981
9343
|
if (!everConnected) {
|
|
8982
|
-
console.error(
|
|
9344
|
+
console.error(import_chalk39.default.red(`Could not start tail: ${err.message}`));
|
|
8983
9345
|
process.exit(1);
|
|
8984
9346
|
}
|
|
8985
9347
|
lastFailure = `ticket mint failed: ${err.message}`;
|
|
@@ -8992,7 +9354,7 @@ async function runTailSession(opts) {
|
|
|
8992
9354
|
} catch (err) {
|
|
8993
9355
|
lastFailure = `bad tail URL: ${err.message}`;
|
|
8994
9356
|
if (!everConnected) {
|
|
8995
|
-
console.error(
|
|
9357
|
+
console.error(import_chalk39.default.red(`Could not start tail: ${lastFailure}`));
|
|
8996
9358
|
process.exit(1);
|
|
8997
9359
|
}
|
|
8998
9360
|
scheduleReconnect();
|
|
@@ -9007,9 +9369,9 @@ async function runTailSession(opts) {
|
|
|
9007
9369
|
lastInbound = Date.now();
|
|
9008
9370
|
lastFailure = "";
|
|
9009
9371
|
if (isReconnect) {
|
|
9010
|
-
diag(
|
|
9372
|
+
diag(import_chalk39.default.dim("reconnected \u2014 requests during the gap were not replayed (browse them with `logs list`)."));
|
|
9011
9373
|
} else {
|
|
9012
|
-
diag(
|
|
9374
|
+
diag(import_chalk39.default.dim(interactive ? "[Enter] inspect a request \xB7 [Ctrl+C] stop\n" : "Ctrl+C to stop."));
|
|
9013
9375
|
}
|
|
9014
9376
|
bindKeys();
|
|
9015
9377
|
pingTimer = setInterval(() => {
|
|
@@ -9036,7 +9398,7 @@ async function runTailSession(opts) {
|
|
|
9036
9398
|
return;
|
|
9037
9399
|
}
|
|
9038
9400
|
if (frame?.type === "ceiling") {
|
|
9039
|
-
diag(
|
|
9401
|
+
diag(import_chalk39.default.yellow(
|
|
9040
9402
|
`\u26A0 volume ceiling \u2014 ${Number(frame.dropped_this_hour) || 0} rows dropped this hour; errors keep streaming.`
|
|
9041
9403
|
));
|
|
9042
9404
|
return;
|
|
@@ -9103,7 +9465,7 @@ function groupIntoDays(rows) {
|
|
|
9103
9465
|
return order;
|
|
9104
9466
|
}
|
|
9105
9467
|
var SCAN_LIMIT = 200;
|
|
9106
|
-
var
|
|
9468
|
+
var PAGE_LIMIT2 = 50;
|
|
9107
9469
|
var JSON_PAGE_LIMIT = 500;
|
|
9108
9470
|
var MAX_EMPTY_HOPS = 100;
|
|
9109
9471
|
async function fetchPage(fetchWindow3, params, notices) {
|
|
@@ -9116,10 +9478,10 @@ async function fetchPage(fetchWindow3, params, notices) {
|
|
|
9116
9478
|
return { rows, nextCursor: res.next_cursor ?? void 0 };
|
|
9117
9479
|
}
|
|
9118
9480
|
if (hop >= MAX_EMPTY_HOPS) {
|
|
9119
|
-
console.error(
|
|
9481
|
+
console.error(import_chalk39.default.yellow("\u26A0 stopped after scanning a long empty span \u2014 older rows may still exist."));
|
|
9120
9482
|
return { rows: [], nextCursor: res.next_cursor, fuseTripped: true };
|
|
9121
9483
|
}
|
|
9122
|
-
if (hop === 0) console.error(
|
|
9484
|
+
if (hop === 0) console.error(import_chalk39.default.dim("searching older history\u2026"));
|
|
9123
9485
|
cursor = res.next_cursor;
|
|
9124
9486
|
}
|
|
9125
9487
|
}
|
|
@@ -9141,7 +9503,7 @@ async function runPlainList(opts, notices) {
|
|
|
9141
9503
|
let fuseTripped = false;
|
|
9142
9504
|
while (rows.length < PLAIN_MAX_ROWS) {
|
|
9143
9505
|
const page = await fetchPage(opts.fetchWindow, {
|
|
9144
|
-
limit: Math.min(
|
|
9506
|
+
limit: Math.min(PAGE_LIMIT2, PLAIN_MAX_ROWS - rows.length),
|
|
9145
9507
|
errorsOnly: opts.errorsOnly,
|
|
9146
9508
|
cursor
|
|
9147
9509
|
}, notices);
|
|
@@ -9159,18 +9521,18 @@ async function runPlainList(opts, notices) {
|
|
|
9159
9521
|
}
|
|
9160
9522
|
}
|
|
9161
9523
|
if (rows.length === 0) {
|
|
9162
|
-
console.log(
|
|
9524
|
+
console.log(import_chalk39.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."));
|
|
9163
9525
|
return;
|
|
9164
9526
|
}
|
|
9165
9527
|
const now = Date.now();
|
|
9166
9528
|
for (const d of groupIntoDays(rows)) {
|
|
9167
|
-
console.log(
|
|
9529
|
+
console.log(import_chalk39.default.bold(dayLabel(d.dayMs, now)));
|
|
9168
9530
|
for (const r of d.rows) console.log(` ${formatRow(r)}`);
|
|
9169
9531
|
console.log("");
|
|
9170
9532
|
}
|
|
9171
9533
|
const errs = rows.filter((r) => r.status >= 400).length;
|
|
9172
9534
|
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" : "";
|
|
9173
|
-
console.log(
|
|
9535
|
+
console.log(import_chalk39.default.dim(
|
|
9174
9536
|
`${rows.length} request${rows.length === 1 ? "" : "s"} \xB7 ${errs} error${errs === 1 ? "" : "s"}${tail}`
|
|
9175
9537
|
));
|
|
9176
9538
|
}
|
|
@@ -9189,7 +9551,7 @@ async function runHistoryBrowser(opts) {
|
|
|
9189
9551
|
for (const r of page.rows) console.log(JSON.stringify(r));
|
|
9190
9552
|
any = any || page.rows.length > 0;
|
|
9191
9553
|
if (page.fuseTripped || !page.nextCursor) {
|
|
9192
|
-
if (!any && !page.fuseTripped) console.error(
|
|
9554
|
+
if (!any && !page.fuseTripped) console.error(import_chalk39.default.dim(opts.errorsOnly ? "No errors logged yet." : "No requests logged yet."));
|
|
9193
9555
|
return;
|
|
9194
9556
|
}
|
|
9195
9557
|
cursor2 = page.nextCursor;
|
|
@@ -9210,7 +9572,7 @@ async function runHistoryBrowser(opts) {
|
|
|
9210
9572
|
scanRows = first.rows;
|
|
9211
9573
|
scanCursor = first.nextCursor;
|
|
9212
9574
|
if (scanRows.length === 0) {
|
|
9213
|
-
console.log(
|
|
9575
|
+
console.log(import_chalk39.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."));
|
|
9214
9576
|
return;
|
|
9215
9577
|
}
|
|
9216
9578
|
}
|
|
@@ -9221,10 +9583,10 @@ async function runHistoryBrowser(opts) {
|
|
|
9221
9583
|
const now2 = Date.now();
|
|
9222
9584
|
const oldestKey = days[days.length - 1]?.key;
|
|
9223
9585
|
const choices = days.map((d) => ({
|
|
9224
|
-
name: `${dayLabel(d.dayMs, now2).padEnd(12)} ${String(d.rows.length).padStart(4)}${(scanCursor !== void 0 || scanFloorUnknown) && d.key === oldestKey ? "+" : ""} requests${d.errors ?
|
|
9586
|
+
name: `${dayLabel(d.dayMs, now2).padEnd(12)} ${String(d.rows.length).padStart(4)}${(scanCursor !== void 0 || scanFloorUnknown) && d.key === oldestKey ? "+" : ""} requests${d.errors ? import_chalk39.default.red(` ${d.errors} errors`) : ""}`,
|
|
9225
9587
|
value: d.key
|
|
9226
9588
|
}));
|
|
9227
|
-
if (scanCursor) choices.push({ name:
|
|
9589
|
+
if (scanCursor) choices.push({ name: import_chalk39.default.dim("\u2026 older days"), value: "__older__" });
|
|
9228
9590
|
const { chosenDay } = await inquirer3.prompt([{
|
|
9229
9591
|
type: "list",
|
|
9230
9592
|
name: "chosenDay",
|
|
@@ -9244,7 +9606,7 @@ async function runHistoryBrowser(opts) {
|
|
|
9244
9606
|
scanCursor = void 0;
|
|
9245
9607
|
} else {
|
|
9246
9608
|
scanCursor = more.nextCursor;
|
|
9247
|
-
if (more.rows.length === 0 && !scanCursor) console.log(
|
|
9609
|
+
if (more.rows.length === 0 && !scanCursor) console.log(import_chalk39.default.dim("That was everything \u2014 no older requests."));
|
|
9248
9610
|
}
|
|
9249
9611
|
continue;
|
|
9250
9612
|
}
|
|
@@ -9259,18 +9621,18 @@ async function runHistoryBrowser(opts) {
|
|
|
9259
9621
|
let moreCursor;
|
|
9260
9622
|
for (; ; ) {
|
|
9261
9623
|
const page = await fetchPage(opts.fetchWindow, {
|
|
9262
|
-
limit:
|
|
9624
|
+
limit: PAGE_LIMIT2,
|
|
9263
9625
|
since,
|
|
9264
9626
|
until,
|
|
9265
9627
|
errorsOnly: opts.errorsOnly,
|
|
9266
9628
|
cursor
|
|
9267
9629
|
}, notices);
|
|
9268
9630
|
if (page.rows.length === 0 && pageRows.length === 0) {
|
|
9269
|
-
console.log(
|
|
9631
|
+
console.log(import_chalk39.default.dim(page.fuseTripped ? "Stopped scanning this day (long empty span) \u2014 older rows in it may exist." : "No requests for this day."));
|
|
9270
9632
|
return;
|
|
9271
9633
|
}
|
|
9272
9634
|
if (page.rows.length === 0) {
|
|
9273
|
-
console.log(
|
|
9635
|
+
console.log(import_chalk39.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."));
|
|
9274
9636
|
moreCursor = void 0;
|
|
9275
9637
|
} else {
|
|
9276
9638
|
pageRows = page.rows;
|
|
@@ -9281,8 +9643,8 @@ async function runHistoryBrowser(opts) {
|
|
|
9281
9643
|
name: formatRow(r),
|
|
9282
9644
|
value: r.request_id
|
|
9283
9645
|
}));
|
|
9284
|
-
if (moreCursor) choices.push({ name:
|
|
9285
|
-
choices.push({ name:
|
|
9646
|
+
if (moreCursor) choices.push({ name: import_chalk39.default.dim("\u2026 load more"), value: "__more__" });
|
|
9647
|
+
choices.push({ name: import_chalk39.default.dim("quit"), value: "__quit__" });
|
|
9286
9648
|
for (; ; ) {
|
|
9287
9649
|
const { picked } = await inquirer3.prompt([{
|
|
9288
9650
|
type: "list",
|
|
@@ -9332,19 +9694,19 @@ function wrapCells(items, width) {
|
|
|
9332
9694
|
${PANE_INDENT}`);
|
|
9333
9695
|
}
|
|
9334
9696
|
function renderHeaders(h) {
|
|
9335
|
-
if (!h) return
|
|
9697
|
+
if (!h) return import_chalk39.default.dim("\u2014");
|
|
9336
9698
|
const width = process.stdout.columns || 100;
|
|
9337
9699
|
if (h.values) {
|
|
9338
9700
|
const e = Object.entries(h.values);
|
|
9339
|
-
if (!e.length) return
|
|
9701
|
+
if (!e.length) return import_chalk39.default.dim("none");
|
|
9340
9702
|
const rank = (k) => {
|
|
9341
9703
|
const i = HEADER_PRIORITY.indexOf(k.toLowerCase());
|
|
9342
9704
|
return i === -1 ? NOISE_HEADER.test(k) ? 999 : 500 : i;
|
|
9343
9705
|
};
|
|
9344
|
-
return e.sort((a, b) => rank(a[0]) - rank(b[0])).map(([k, v]) => `${
|
|
9706
|
+
return e.sort((a, b) => rank(a[0]) - rank(b[0])).map(([k, v]) => `${import_chalk39.default.dim(k + ":")} ${v}`).join(`
|
|
9345
9707
|
${PANE_INDENT}`);
|
|
9346
9708
|
}
|
|
9347
|
-
if (!h.names?.length) return
|
|
9709
|
+
if (!h.names?.length) return import_chalk39.default.dim("none");
|
|
9348
9710
|
const names = [...h.names];
|
|
9349
9711
|
const noise = names.filter((n) => NOISE_HEADER.test(n));
|
|
9350
9712
|
const shown = names.filter((n) => !NOISE_HEADER.test(n)).sort((a, b) => {
|
|
@@ -9352,70 +9714,70 @@ ${PANE_INDENT}`);
|
|
|
9352
9714
|
const ib = HEADER_PRIORITY.indexOf(b.toLowerCase());
|
|
9353
9715
|
return (ia === -1 ? 500 : ia) - (ib === -1 ? 500 : ib) || a.localeCompare(b);
|
|
9354
9716
|
});
|
|
9355
|
-
const body = shown.length ? wrapCells(shown, width) :
|
|
9717
|
+
const body = shown.length ? wrapCells(shown, width) : import_chalk39.default.dim("(all forwarding headers)");
|
|
9356
9718
|
const tail = noise.length ? `
|
|
9357
|
-
${PANE_INDENT}${
|
|
9358
|
-
return `${body}${tail} ${
|
|
9719
|
+
${PANE_INDENT}${import_chalk39.default.dim(`+ ${noise.length} forwarding/client-hint header${noise.length === 1 ? "" : "s"} (cf-*, sec-*)`)}` : "";
|
|
9720
|
+
return `${body}${tail} ${import_chalk39.default.dim("(names only)")}`;
|
|
9359
9721
|
}
|
|
9360
9722
|
function renderBody(b) {
|
|
9361
|
-
if (!b) return
|
|
9723
|
+
if (!b) return import_chalk39.default.dim("\u2014");
|
|
9362
9724
|
const meta = [b.content_type, b.size != null ? `${b.size}B` : null, b.truncated ? "truncated" : null].filter(Boolean).join(" \xB7 ");
|
|
9363
|
-
if (b.not_captured) return
|
|
9725
|
+
if (b.not_captured) return import_chalk39.default.dim(`not captured (${b.not_captured})${meta ? " \xB7 " + meta : ""}`);
|
|
9364
9726
|
const isShape = b.value === void 0 && b.shape !== void 0;
|
|
9365
9727
|
const content = b.value !== void 0 ? b.value : b.shape;
|
|
9366
9728
|
if (content === null || content === void 0 || content === "" || content === "empty") {
|
|
9367
|
-
return
|
|
9729
|
+
return import_chalk39.default.dim(`empty${meta ? " \xB7 " + meta : ""}`);
|
|
9368
9730
|
}
|
|
9369
9731
|
const text = typeof content === "string" ? content : JSON.stringify(content, null, 2);
|
|
9370
|
-
const head = isShape ?
|
|
9732
|
+
const head = isShape ? import_chalk39.default.dim("shape only (values not captured)\n ") : "";
|
|
9371
9733
|
return head + text.split("\n").join("\n ");
|
|
9372
9734
|
}
|
|
9373
9735
|
function pane(title, tone, view, empty) {
|
|
9374
9736
|
console.log(` ${tone(title)}`);
|
|
9375
9737
|
if (empty !== void 0) {
|
|
9376
|
-
console.log(` ${
|
|
9738
|
+
console.log(` ${import_chalk39.default.dim(empty)}`);
|
|
9377
9739
|
return;
|
|
9378
9740
|
}
|
|
9379
9741
|
const v = view ?? {};
|
|
9380
|
-
if (v.url) console.log(` ${
|
|
9742
|
+
if (v.url) console.log(` ${import_chalk39.default.dim("target")} ${v.url}`);
|
|
9381
9743
|
if (v.status != null) {
|
|
9382
|
-
console.log(` ${
|
|
9744
|
+
console.log(` ${import_chalk39.default.dim("status")} ${statusColor(v.status)(String(v.status))}${v.status_text ? import_chalk39.default.dim(" " + v.status_text) : ""}`);
|
|
9383
9745
|
}
|
|
9384
|
-
if (v.raw_path) console.log(` ${
|
|
9746
|
+
if (v.raw_path) console.log(` ${import_chalk39.default.dim("path")} ${v.raw_path}`);
|
|
9385
9747
|
if (v.query && Object.keys(v.query).length) {
|
|
9386
|
-
console.log(` ${
|
|
9748
|
+
console.log(` ${import_chalk39.default.dim("query")} ${Object.entries(v.query).map(([k, q]) => `${k}=${q}`).join(" ")}`);
|
|
9387
9749
|
}
|
|
9388
|
-
if (v.jwt !== void 0) console.log(` ${
|
|
9389
|
-
console.log(` ${
|
|
9390
|
-
console.log(` ${
|
|
9750
|
+
if (v.jwt !== void 0) console.log(` ${import_chalk39.default.dim("jwt")} ${JSON.stringify(v.jwt)}`);
|
|
9751
|
+
console.log(` ${import_chalk39.default.dim("headers")} ${renderHeaders(v.headers)}`);
|
|
9752
|
+
console.log(` ${import_chalk39.default.dim("body")} ${renderBody(v.body)}`);
|
|
9391
9753
|
}
|
|
9392
9754
|
function printDetail(r, mode = "producer") {
|
|
9393
9755
|
const v = r.views;
|
|
9394
9756
|
console.log("");
|
|
9395
|
-
console.log(
|
|
9757
|
+
console.log(import_chalk39.default.bold(`${r.method} ${r.route}`) + " " + statusColor(r.status)(String(r.status)) + import_chalk39.default.dim(` ${r.duration_ms}ms`));
|
|
9396
9758
|
const cost = typeof r.cost_cents === "number" && r.cost_cents > 0 ? ` \xB7 ${r.cost_cents}\xA2` : "";
|
|
9397
|
-
console.log(
|
|
9759
|
+
console.log(import_chalk39.default.dim(` request_id ${r.request_id}${r.environment ? ` \xB7 env ${r.environment}` : ""}${r.consumer_id ? ` \xB7 consumer ${r.consumer_id}` : ""}${cost}`));
|
|
9398
9760
|
console.log("");
|
|
9399
9761
|
if (!v) {
|
|
9400
|
-
console.log(
|
|
9762
|
+
console.log(import_chalk39.default.dim(" No view detail captured for this request.\n"));
|
|
9401
9763
|
return;
|
|
9402
9764
|
}
|
|
9403
|
-
pane("Request (before) \u2014 client \u2192 APIblaze",
|
|
9765
|
+
pane("Request (before) \u2014 client \u2192 APIblaze", import_chalk39.default.cyan, v.request);
|
|
9404
9766
|
if (mode === "producer") {
|
|
9405
9767
|
pane(
|
|
9406
9768
|
"Request (after) \u2014 APIblaze \u2192 upstream",
|
|
9407
|
-
|
|
9769
|
+
import_chalk39.default.cyan,
|
|
9408
9770
|
v.forwarded_request,
|
|
9409
9771
|
v.forwarded_request ? void 0 : "Request did not reach an upstream."
|
|
9410
9772
|
);
|
|
9411
9773
|
pane(
|
|
9412
9774
|
"Response (before) \u2014 upstream \u2192 APIblaze",
|
|
9413
|
-
|
|
9775
|
+
import_chalk39.default.magenta,
|
|
9414
9776
|
v.upstream_response,
|
|
9415
9777
|
v.upstream_response ? void 0 : "No upstream response."
|
|
9416
9778
|
);
|
|
9417
9779
|
}
|
|
9418
|
-
pane("Response (after) \u2014 APIblaze \u2192 client",
|
|
9780
|
+
pane("Response (after) \u2014 APIblaze \u2192 client", import_chalk39.default.magenta, v.response);
|
|
9419
9781
|
console.log("");
|
|
9420
9782
|
}
|
|
9421
9783
|
|
|
@@ -9464,7 +9826,7 @@ async function resolve(projectArg, versionArg, opts) {
|
|
|
9464
9826
|
return { projectId, projectName: match.projectName, version: version2, tenant: tenant2 };
|
|
9465
9827
|
}
|
|
9466
9828
|
function otherPlaneHint() {
|
|
9467
|
-
if (loadConsumer()) console.log(
|
|
9829
|
+
if (loadConsumer()) console.log(import_chalk40.default.dim(" \u2039consumer view: apiblaze consumer logs\u203A"));
|
|
9468
9830
|
}
|
|
9469
9831
|
function fetchWindow(projectId, version2, tenant2) {
|
|
9470
9832
|
return (params) => {
|
|
@@ -9483,8 +9845,8 @@ function fetchWindow(projectId, version2, tenant2) {
|
|
|
9483
9845
|
async function runLogsTail(projectArg, versionArg, opts) {
|
|
9484
9846
|
const { projectId, projectName, version: version2, tenant: tenant2 } = await resolve(projectArg, versionArg, opts);
|
|
9485
9847
|
if (!opts.json) {
|
|
9486
|
-
console.log(
|
|
9487
|
-
console.log(
|
|
9848
|
+
console.log(import_chalk40.default.bold("API Producer logs") + import_chalk40.default.dim(` \xB7 ${projectName}@${version2} \xB7 tenant ${tenant2}${opts.errorsOnly ? " \xB7 errors only" : ""}`));
|
|
9849
|
+
console.log(import_chalk40.default.dim("every caller's requests, with cost and upstream detail"));
|
|
9488
9850
|
otherPlaneHint();
|
|
9489
9851
|
}
|
|
9490
9852
|
await runTailSession({
|
|
@@ -9505,8 +9867,8 @@ async function runLogsTail(projectArg, versionArg, opts) {
|
|
|
9505
9867
|
async function runLogsList(projectArg, versionArg, opts) {
|
|
9506
9868
|
const { projectId, projectName, version: version2, tenant: tenant2 } = await resolve(projectArg, versionArg, opts);
|
|
9507
9869
|
if (!opts.json) {
|
|
9508
|
-
console.log(
|
|
9509
|
-
console.log(
|
|
9870
|
+
console.log(import_chalk40.default.bold("API Producer logs") + import_chalk40.default.dim(` \xB7 ${projectName}@${version2} \xB7 tenant ${tenant2}${opts.errorsOnly ? " \xB7 errors only" : ""}`));
|
|
9871
|
+
console.log(import_chalk40.default.dim("every caller's requests, with cost and upstream detail"));
|
|
9510
9872
|
otherPlaneHint();
|
|
9511
9873
|
console.log("");
|
|
9512
9874
|
}
|
|
@@ -9520,15 +9882,15 @@ async function runLogsList(projectArg, versionArg, opts) {
|
|
|
9520
9882
|
}
|
|
9521
9883
|
|
|
9522
9884
|
// src/commands/consumer-logs.ts
|
|
9523
|
-
var
|
|
9885
|
+
var import_chalk41 = __toESM(require("chalk"));
|
|
9524
9886
|
init_auth();
|
|
9525
9887
|
var CONSUMER_LOGS_BASE = process.env.APIBLAZE_CONSUMER_LOGS_BASE || "https://logs.apiblaze.com/consumer";
|
|
9526
9888
|
function requireConsumer() {
|
|
9527
9889
|
const c = loadConsumer();
|
|
9528
9890
|
if (!c) {
|
|
9529
|
-
console.error(
|
|
9891
|
+
console.error(import_chalk41.default.red("Not logged in as an API Consumer. Run `apiblaze consumer login` first."));
|
|
9530
9892
|
if (loadCredentials()) {
|
|
9531
|
-
console.error(
|
|
9893
|
+
console.error(import_chalk41.default.dim("You're signed in as an API Producer \u2014 for a proxy's full logs run `apiblaze logs <project>`."));
|
|
9532
9894
|
}
|
|
9533
9895
|
process.exit(1);
|
|
9534
9896
|
}
|
|
@@ -9559,7 +9921,7 @@ async function consumerLogsFetch(session, suffix, init) {
|
|
|
9559
9921
|
return data;
|
|
9560
9922
|
}
|
|
9561
9923
|
function otherPlaneHint2() {
|
|
9562
|
-
if (loadCredentials()) console.log(
|
|
9924
|
+
if (loadCredentials()) console.log(import_chalk41.default.dim(" \u2039producer view: apiblaze logs <project>\u203A"));
|
|
9563
9925
|
}
|
|
9564
9926
|
function fetchWindow2(session, project) {
|
|
9565
9927
|
return (params) => {
|
|
@@ -9575,8 +9937,8 @@ function fetchWindow2(session, project) {
|
|
|
9575
9937
|
async function runConsumerLogsTail(opts) {
|
|
9576
9938
|
const session = requireConsumer();
|
|
9577
9939
|
if (!opts.json) {
|
|
9578
|
-
console.log(
|
|
9579
|
-
console.log(
|
|
9940
|
+
console.log(import_chalk41.default.bold("API Consumer logs") + import_chalk41.default.dim(` \xB7 your own requests on ${session.creds.tenant}${opts.project ? ` \xB7 ${opts.project}` : ""}${opts.errorsOnly ? " \xB7 errors only" : ""}`));
|
|
9941
|
+
console.log(import_chalk41.default.dim("cost and upstream details are producer-only and are not sent"));
|
|
9580
9942
|
otherPlaneHint2();
|
|
9581
9943
|
}
|
|
9582
9944
|
await runTailSession({
|
|
@@ -9594,8 +9956,8 @@ async function runConsumerLogsTail(opts) {
|
|
|
9594
9956
|
async function runConsumerLogsList(opts) {
|
|
9595
9957
|
const session = requireConsumer();
|
|
9596
9958
|
if (!opts.json) {
|
|
9597
|
-
console.log(
|
|
9598
|
-
console.log(
|
|
9959
|
+
console.log(import_chalk41.default.bold("API Consumer logs") + import_chalk41.default.dim(` \xB7 your own requests on ${session.creds.tenant}${opts.project ? ` \xB7 ${opts.project}` : ""}${opts.errorsOnly ? " \xB7 errors only" : ""}`));
|
|
9960
|
+
console.log(import_chalk41.default.dim("cost and upstream details are producer-only and are not sent"));
|
|
9599
9961
|
otherPlaneHint2();
|
|
9600
9962
|
console.log("");
|
|
9601
9963
|
}
|
|
@@ -9609,7 +9971,7 @@ async function runConsumerLogsList(opts) {
|
|
|
9609
9971
|
}
|
|
9610
9972
|
|
|
9611
9973
|
// src/commands/config.ts
|
|
9612
|
-
var
|
|
9974
|
+
var import_chalk42 = __toESM(require("chalk"));
|
|
9613
9975
|
var import_ora15 = __toESM(require("ora"));
|
|
9614
9976
|
init_admin();
|
|
9615
9977
|
init_resolve();
|
|
@@ -9633,7 +9995,7 @@ async function patchConfig(project, opts, body, summary) {
|
|
|
9633
9995
|
}
|
|
9634
9996
|
async function runTargetSet(project, opts) {
|
|
9635
9997
|
if (!opts.url) {
|
|
9636
|
-
console.error(
|
|
9998
|
+
console.error(import_chalk42.default.red("--url is required."));
|
|
9637
9999
|
process.exit(1);
|
|
9638
10000
|
}
|
|
9639
10001
|
const body = opts.env ? { environments: { [opts.env]: { target: opts.url } } } : { target_url: opts.url };
|
|
@@ -9646,40 +10008,41 @@ async function runThrottleSet(project, opts) {
|
|
|
9646
10008
|
if (opts.quota !== void 0) throttling.proxyQuota = Number(opts.quota);
|
|
9647
10009
|
if (opts.period !== void 0) {
|
|
9648
10010
|
if (!["daily", "weekly", "monthly"].includes(opts.period)) {
|
|
9649
|
-
console.error(
|
|
10011
|
+
console.error(import_chalk42.default.red("--period must be daily, weekly, or monthly."));
|
|
9650
10012
|
process.exit(1);
|
|
9651
10013
|
}
|
|
9652
10014
|
throttling.quotaPeriod = opts.period;
|
|
9653
10015
|
}
|
|
9654
10016
|
if (Object.keys(throttling).length === 0) {
|
|
9655
|
-
console.error(
|
|
10017
|
+
console.error(import_chalk42.default.red("Nothing to set. Pass at least one of --rate, --end-user-rate, --quota, --period."));
|
|
9656
10018
|
process.exit(1);
|
|
9657
10019
|
}
|
|
9658
10020
|
await patchConfig(project, opts, { throttling }, `Update throttling ${JSON.stringify(throttling)}`);
|
|
9659
10021
|
}
|
|
9660
10022
|
async function runRename(project, opts) {
|
|
9661
10023
|
if (!opts.displayName) {
|
|
9662
|
-
console.error(
|
|
10024
|
+
console.error(import_chalk42.default.red("--display-name is required."));
|
|
9663
10025
|
process.exit(1);
|
|
9664
10026
|
}
|
|
9665
10027
|
await patchConfig(project, opts, { display_name: opts.displayName }, `Rename \u2192 "${opts.displayName}"`);
|
|
9666
10028
|
}
|
|
9667
10029
|
|
|
9668
10030
|
// src/commands/config-browse.ts
|
|
9669
|
-
var
|
|
10031
|
+
var import_chalk51 = __toESM(require("chalk"));
|
|
9670
10032
|
var import_ora23 = __toESM(require("ora"));
|
|
9671
10033
|
init_admin();
|
|
10034
|
+
init_paging();
|
|
9672
10035
|
init_auth();
|
|
9673
10036
|
init_resolve();
|
|
9674
10037
|
|
|
9675
10038
|
// src/commands/domain.ts
|
|
9676
|
-
var
|
|
10039
|
+
var import_chalk43 = __toESM(require("chalk"));
|
|
9677
10040
|
var import_ora16 = __toESM(require("ora"));
|
|
9678
10041
|
init_admin();
|
|
9679
10042
|
init_resolve();
|
|
9680
10043
|
async function runDomainAdd(project, opts) {
|
|
9681
10044
|
if (!opts.domain) {
|
|
9682
|
-
console.error(
|
|
10045
|
+
console.error(import_chalk43.default.red("--domain is required."));
|
|
9683
10046
|
process.exit(1);
|
|
9684
10047
|
}
|
|
9685
10048
|
const { teamId } = await resolveTeam(opts.team);
|
|
@@ -9698,9 +10061,9 @@ async function runDomainAdd(project, opts) {
|
|
|
9698
10061
|
console.log(JSON.stringify(out));
|
|
9699
10062
|
return;
|
|
9700
10063
|
}
|
|
9701
|
-
if (out?.cnameRecord) console.log(` ${
|
|
9702
|
-
if (out?.dcv) console.log(` ${
|
|
9703
|
-
if (out?.id) console.log(
|
|
10064
|
+
if (out?.cnameRecord) console.log(` ${import_chalk43.default.cyan("CNAME")} ${out.cnameRecord.name ?? opts.domain} \u2192 ${out.cnameRecord.value ?? out.cnameRecord.target}`);
|
|
10065
|
+
if (out?.dcv) console.log(` ${import_chalk43.default.cyan("TXT")} ${out.dcv.name} = ${out.dcv.value}`);
|
|
10066
|
+
if (out?.id) console.log(import_chalk43.default.dim(` domain id: ${out.id}`));
|
|
9704
10067
|
} catch (err) {
|
|
9705
10068
|
spinner.fail("Domain registration failed.");
|
|
9706
10069
|
throw err;
|
|
@@ -9720,14 +10083,14 @@ async function runDomainList(project, opts) {
|
|
|
9720
10083
|
return;
|
|
9721
10084
|
}
|
|
9722
10085
|
if (!domains.length) {
|
|
9723
|
-
console.log(
|
|
10086
|
+
console.log(import_chalk43.default.yellow("No custom domains."));
|
|
9724
10087
|
return;
|
|
9725
10088
|
}
|
|
9726
|
-
for (const d of domains) console.log(` ${
|
|
10089
|
+
for (const d of domains) console.log(` ${import_chalk43.default.bold(d.hostname)} ${import_chalk43.default.dim(d.status ?? "")} ${import_chalk43.default.dim(d.id ?? "")}`);
|
|
9727
10090
|
}
|
|
9728
10091
|
async function runDomainStatus(project, opts) {
|
|
9729
10092
|
if (!opts.id) {
|
|
9730
|
-
console.error(
|
|
10093
|
+
console.error(import_chalk43.default.red("--id <domainId> is required (see `apiblaze domain list`)."));
|
|
9731
10094
|
process.exit(1);
|
|
9732
10095
|
}
|
|
9733
10096
|
const { teamId } = await resolveTeam(opts.team);
|
|
@@ -9737,11 +10100,11 @@ async function runDomainStatus(project, opts) {
|
|
|
9737
10100
|
path: `/projects/${proj2.projectId}/domains/${encodeURIComponent(opts.id)}/status`,
|
|
9738
10101
|
summary: `Check custom-domain status`
|
|
9739
10102
|
});
|
|
9740
|
-
console.log(opts.json ? JSON.stringify(out) : ` ${
|
|
10103
|
+
console.log(opts.json ? JSON.stringify(out) : ` ${import_chalk43.default.bold(out?.hostname ?? opts.id)}: ${import_chalk43.default.cyan(out?.status ?? "unknown")}`);
|
|
9741
10104
|
}
|
|
9742
10105
|
async function runDomainRemove(project, opts) {
|
|
9743
10106
|
if (!opts.id) {
|
|
9744
|
-
console.error(
|
|
10107
|
+
console.error(import_chalk43.default.red("--id <domainId> is required (see `apiblaze domain list`)."));
|
|
9745
10108
|
process.exit(1);
|
|
9746
10109
|
}
|
|
9747
10110
|
const { teamId } = await resolveTeam(opts.team);
|
|
@@ -9779,61 +10142,69 @@ async function runDomainSetBase(project, opts) {
|
|
|
9779
10142
|
}
|
|
9780
10143
|
|
|
9781
10144
|
// src/commands/tenant.ts
|
|
9782
|
-
var
|
|
10145
|
+
var import_chalk46 = __toESM(require("chalk"));
|
|
9783
10146
|
var import_ora19 = __toESM(require("ora"));
|
|
9784
10147
|
init_admin();
|
|
9785
10148
|
init_resolve();
|
|
9786
10149
|
init_auth();
|
|
9787
10150
|
init_tenant_pick();
|
|
10151
|
+
init_paging();
|
|
9788
10152
|
async function runTenantUse(query, opts) {
|
|
9789
10153
|
const creds = loadCredentials();
|
|
9790
10154
|
if (!creds) {
|
|
9791
|
-
console.error(
|
|
10155
|
+
console.error(import_chalk46.default.red("Not logged in. Run `apiblaze login` first."));
|
|
9792
10156
|
process.exit(1);
|
|
9793
10157
|
}
|
|
9794
10158
|
if (opts.clear) {
|
|
9795
10159
|
delete creds.activeTenant;
|
|
9796
10160
|
saveCredentials(creds);
|
|
9797
|
-
console.log(
|
|
10161
|
+
console.log(import_chalk46.default.green("Tenant scope cleared."));
|
|
9798
10162
|
return;
|
|
9799
10163
|
}
|
|
9800
10164
|
const { teamId } = await resolveTeam(opts.team);
|
|
9801
10165
|
const slug = await pickTenant(teamId, { message: "Scope future commands to which tenant?", initialQuery: query });
|
|
9802
10166
|
if (!slug) process.exit(1);
|
|
9803
10167
|
saveCredentials({ ...creds, activeTenant: slug });
|
|
9804
|
-
console.log(
|
|
10168
|
+
console.log(import_chalk46.default.green(`Tenant scope set to ${import_chalk46.default.bold(slug)}.`) + import_chalk46.default.dim(" (clear with `apiblaze tenant use --clear`)"));
|
|
9805
10169
|
}
|
|
9806
10170
|
async function runTenantList(opts) {
|
|
9807
10171
|
const { teamId, teamName } = await resolveTeam(opts.team);
|
|
9808
|
-
const
|
|
9809
|
-
const
|
|
9810
|
-
|
|
9811
|
-
|
|
9812
|
-
|
|
9813
|
-
|
|
9814
|
-
|
|
9815
|
-
|
|
10172
|
+
const base2 = `/teams/${encodeURIComponent(teamId)}/tenants?detail=1${opts.q ? `&q=${encodeURIComponent(opts.q)}` : ""}`;
|
|
10173
|
+
const summary = opts.q ? `Search tenants matching "${opts.q}"` : `List tenants for team ${teamName ?? teamId}`;
|
|
10174
|
+
let out;
|
|
10175
|
+
let tenants;
|
|
10176
|
+
if (opts.limit) {
|
|
10177
|
+
out = await admin({ method: "GET", path: `${base2}&limit=${encodeURIComponent(opts.limit)}`, summary });
|
|
10178
|
+
tenants = out?.tenants ?? [];
|
|
10179
|
+
} else {
|
|
10180
|
+
const walked = await pageAll(
|
|
10181
|
+
(cursor) => admin({ method: "GET", path: withPage(base2, cursor), summary }),
|
|
10182
|
+
(b) => b?.tenants
|
|
10183
|
+
);
|
|
10184
|
+
tenants = walked.items;
|
|
10185
|
+
out = { total: walked.total };
|
|
10186
|
+
}
|
|
9816
10187
|
if (opts.json) {
|
|
9817
10188
|
console.log(JSON.stringify(tenants));
|
|
9818
10189
|
return;
|
|
9819
10190
|
}
|
|
9820
10191
|
if (!tenants.length) {
|
|
9821
|
-
console.log(
|
|
10192
|
+
console.log(import_chalk46.default.yellow(opts.q ? `No tenants matching "${opts.q}".` : "No tenants."));
|
|
9822
10193
|
return;
|
|
9823
10194
|
}
|
|
9824
10195
|
for (const t of tenants) {
|
|
9825
10196
|
const name = typeof t === "string" ? t : t.tenant_name;
|
|
9826
|
-
const display = typeof t === "string" ? "" :
|
|
9827
|
-
console.log(` ${
|
|
10197
|
+
const display = typeof t === "string" ? "" : import_chalk46.default.dim(` ${t.display_name ?? ""}`);
|
|
10198
|
+
console.log(` ${import_chalk46.default.bold(name)}${display}`);
|
|
9828
10199
|
}
|
|
9829
10200
|
const total = out?.total ?? tenants.length;
|
|
9830
10201
|
if (total > tenants.length) {
|
|
9831
|
-
console.log(
|
|
10202
|
+
console.log(import_chalk46.default.dim(` \u2026 showing ${tenants.length} of ${total} \u2014 narrow with --q <search>`));
|
|
9832
10203
|
}
|
|
9833
10204
|
}
|
|
9834
10205
|
async function runTenantCreate(opts) {
|
|
9835
10206
|
if (!opts.name) {
|
|
9836
|
-
console.error(
|
|
10207
|
+
console.error(import_chalk46.default.red("--name (display name) is required."));
|
|
9837
10208
|
process.exit(1);
|
|
9838
10209
|
}
|
|
9839
10210
|
const { teamId } = await resolveTeam(opts.team);
|
|
@@ -9845,7 +10216,7 @@ async function runTenantCreate(opts) {
|
|
|
9845
10216
|
body: { display_name: opts.name, ...opts.slug ? { tenant_name: opts.slug } : {} },
|
|
9846
10217
|
summary: `Create tenant "${opts.name}"`
|
|
9847
10218
|
});
|
|
9848
|
-
spinner.succeed(`Created tenant ${
|
|
10219
|
+
spinner.succeed(`Created tenant ${import_chalk46.default.bold(out?.tenant_name ?? opts.name)}.`);
|
|
9849
10220
|
if (opts.json) console.log(JSON.stringify(out));
|
|
9850
10221
|
} catch (err) {
|
|
9851
10222
|
spinner.fail("Tenant create failed.");
|
|
@@ -9854,7 +10225,7 @@ async function runTenantCreate(opts) {
|
|
|
9854
10225
|
}
|
|
9855
10226
|
async function runTenantAttach(project, opts) {
|
|
9856
10227
|
if (!opts.tenant) {
|
|
9857
|
-
console.error(
|
|
10228
|
+
console.error(import_chalk46.default.red("--tenant <slug> is required."));
|
|
9858
10229
|
process.exit(1);
|
|
9859
10230
|
}
|
|
9860
10231
|
const { teamId } = await resolveTeam(opts.team);
|
|
@@ -9876,32 +10247,68 @@ async function runTenantAttach(project, opts) {
|
|
|
9876
10247
|
}
|
|
9877
10248
|
async function runTenantDelete(slug, opts) {
|
|
9878
10249
|
const { teamId } = await resolveTeam(opts.team);
|
|
10250
|
+
const hard = !!opts.purge;
|
|
9879
10251
|
if (!opts.yes && !opts.json) {
|
|
9880
10252
|
const { default: inquirer3 } = await import("inquirer");
|
|
9881
10253
|
const { confirm: confirm2 } = await inquirer3.prompt([
|
|
9882
|
-
{
|
|
10254
|
+
{
|
|
10255
|
+
type: "confirm",
|
|
10256
|
+
name: "confirm",
|
|
10257
|
+
message: hard ? `Permanently delete tenant "${slug}" and everything under it? This CANNOT be undone.` : `Move tenant "${slug}" to the trash? (Reversible \u2014 use \`apiblaze tenant delete ${slug} --purge\` to destroy it.)`,
|
|
10258
|
+
default: false
|
|
10259
|
+
}
|
|
9883
10260
|
]);
|
|
9884
10261
|
if (!confirm2) {
|
|
9885
|
-
console.log(
|
|
10262
|
+
console.log(import_chalk46.default.dim("Aborted."));
|
|
9886
10263
|
return;
|
|
9887
10264
|
}
|
|
9888
10265
|
}
|
|
9889
|
-
const spinner = (0, import_ora19.default)("Deleting tenant...").start();
|
|
10266
|
+
const spinner = (0, import_ora19.default)(hard ? "Trashing tenant..." : "Deleting tenant...").start();
|
|
9890
10267
|
try {
|
|
9891
10268
|
await admin({
|
|
9892
10269
|
method: "DELETE",
|
|
9893
10270
|
path: `/teams/${encodeURIComponent(teamId)}/tenants/${encodeURIComponent(slug)}`,
|
|
9894
|
-
summary: `
|
|
10271
|
+
summary: `Move tenant ${slug} to trash`
|
|
9895
10272
|
});
|
|
9896
|
-
|
|
10273
|
+
if (!hard) {
|
|
10274
|
+
spinner.succeed(`Tenant ${slug} moved to trash.`);
|
|
10275
|
+
return;
|
|
10276
|
+
}
|
|
10277
|
+
spinner.stop();
|
|
9897
10278
|
} catch (err) {
|
|
9898
10279
|
spinner.fail("Tenant delete failed.");
|
|
9899
10280
|
throw err;
|
|
9900
10281
|
}
|
|
10282
|
+
let accepted;
|
|
10283
|
+
try {
|
|
10284
|
+
accepted = await admin({
|
|
10285
|
+
method: "DELETE",
|
|
10286
|
+
path: `/teams/${encodeURIComponent(teamId)}/tenants/${encodeURIComponent(slug)}/empty-trash`,
|
|
10287
|
+
summary: `Purge tenant ${slug} (full cascade)`
|
|
10288
|
+
});
|
|
10289
|
+
} catch (err) {
|
|
10290
|
+
console.log(import_chalk46.default.red("\u2716 Tenant purge failed to start."));
|
|
10291
|
+
throw err;
|
|
10292
|
+
}
|
|
10293
|
+
const job = await settleDelete(accepted, {
|
|
10294
|
+
teamId,
|
|
10295
|
+
noWait: opts.wait === false,
|
|
10296
|
+
label: `Purging ${slug}`,
|
|
10297
|
+
json: opts.json
|
|
10298
|
+
});
|
|
10299
|
+
if (opts.json) {
|
|
10300
|
+
console.log(JSON.stringify({
|
|
10301
|
+
tenant: slug,
|
|
10302
|
+
purged: opts.wait === false ? false : true,
|
|
10303
|
+
...accepted?.job_id ? { job_id: accepted.job_id, job_status: job?.status ?? "queued" } : {}
|
|
10304
|
+
}));
|
|
10305
|
+
} else if (opts.wait !== false) {
|
|
10306
|
+
console.log(import_chalk46.default.green(`\u2714 Deleted tenant ${slug}.`));
|
|
10307
|
+
}
|
|
9901
10308
|
}
|
|
9902
10309
|
async function runTenantCors(opts) {
|
|
9903
10310
|
if (!opts.tenant) {
|
|
9904
|
-
console.error(
|
|
10311
|
+
console.error(import_chalk46.default.red("--tenant <slug> is required."));
|
|
9905
10312
|
process.exit(1);
|
|
9906
10313
|
}
|
|
9907
10314
|
const { teamId } = await resolveTeam(opts.team);
|
|
@@ -9923,7 +10330,7 @@ async function runTenantCors(opts) {
|
|
|
9923
10330
|
}
|
|
9924
10331
|
|
|
9925
10332
|
// src/commands/tenant-drill.ts
|
|
9926
|
-
var
|
|
10333
|
+
var import_chalk47 = __toESM(require("chalk"));
|
|
9927
10334
|
var import_ora20 = __toESM(require("ora"));
|
|
9928
10335
|
var import_crypto = require("crypto");
|
|
9929
10336
|
init_admin();
|
|
@@ -9954,15 +10361,15 @@ async function validScopedTenant(teamId, query) {
|
|
|
9954
10361
|
const next = { ...creds };
|
|
9955
10362
|
delete next.activeTenant;
|
|
9956
10363
|
saveCredentials2(next);
|
|
9957
|
-
console.log(
|
|
10364
|
+
console.log(import_chalk47.default.yellow(`Tenant scope "${scoped}" no longer exists in this team \u2014 cleared.`));
|
|
9958
10365
|
return void 0;
|
|
9959
10366
|
}
|
|
9960
10367
|
async function tenantHome(teamId, tenant2) {
|
|
9961
10368
|
const { default: inquirer3 } = await import("inquirer");
|
|
9962
10369
|
const base2 = `/teams/${encodeURIComponent(teamId)}/tenants/${encodeURIComponent(tenant2)}`;
|
|
9963
|
-
console.log(
|
|
10370
|
+
console.log(import_chalk47.default.bold(`
|
|
9964
10371
|
Tenant ${tenant2}`));
|
|
9965
|
-
console.log(
|
|
10372
|
+
console.log(import_chalk47.default.dim("Tenant auth/settings are SHARED: changes apply to every proxy this tenant serves.\n"));
|
|
9966
10373
|
for (; ; ) {
|
|
9967
10374
|
const spinner = (0, import_ora20.default)("Reading tenant state...").start();
|
|
9968
10375
|
const [iam, cors, emails, issuers, opaque, clients] = await Promise.all([
|
|
@@ -9978,18 +10385,18 @@ Tenant ${tenant2}`));
|
|
|
9978
10385
|
const nClients = Array.isArray(clients) ? clients.length : 0;
|
|
9979
10386
|
const nOpaque = opaque?.opaque?.endpoint ? 1 : 0;
|
|
9980
10387
|
const nLogin = nClients + nIssuers + nOpaque;
|
|
9981
|
-
const onOff = (b) => b ?
|
|
10388
|
+
const onOff = (b) => b ? import_chalk47.default.green("on") : import_chalk47.default.dim("off");
|
|
9982
10389
|
const { pick: pick2 } = await inquirer3.prompt([{
|
|
9983
10390
|
type: "list",
|
|
9984
10391
|
name: "pick",
|
|
9985
10392
|
message: `Tenant ${tenant2}:`,
|
|
9986
10393
|
pageSize: 12,
|
|
9987
10394
|
choices: [
|
|
9988
|
-
{ name: `Login methods (${nLogin}) ${
|
|
9989
|
-
{ name: `Users & groups: ${onOff(iam?.iam_enabled)} ${
|
|
9990
|
-
{ name: `Portal admins (${nEmails}) ${
|
|
9991
|
-
new inquirer3.Separator(
|
|
9992
|
-
{ name: `CORS: ${cors?.cors ?
|
|
10395
|
+
{ name: `Login methods (${nLogin}) ${import_chalk47.default.dim("how your consumers sign in")}`, value: "login" },
|
|
10396
|
+
{ name: `Users & groups: ${onOff(iam?.iam_enabled)} ${import_chalk47.default.dim(`identity & key management (iam.apiblaze.com)`)}`, value: "iam" },
|
|
10397
|
+
{ name: `Portal admins (${nEmails}) ${import_chalk47.default.dim("emails allowed to administer this tenant's portal")}`, value: "emails" },
|
|
10398
|
+
new inquirer3.Separator(import_chalk47.default.dim(" Settings")),
|
|
10399
|
+
{ name: `CORS: ${cors?.cors ? import_chalk47.default.cyan(JSON.stringify(cors.cors)) : import_chalk47.default.dim("(unset)")} ${import_chalk47.default.dim("browser origins allowed to call this tenant")}`, value: "cors" },
|
|
9993
10400
|
{ name: "\u2190 Back", value: "back" }
|
|
9994
10401
|
]
|
|
9995
10402
|
}]);
|
|
@@ -10002,7 +10409,7 @@ Tenant ${tenant2}`));
|
|
|
10002
10409
|
case "iam": {
|
|
10003
10410
|
const { v } = await inquirer3.prompt([{ type: "confirm", name: "v", message: "Enable Users & groups?", default: !!iam?.iam_enabled }]);
|
|
10004
10411
|
await admin({ method: "PATCH", path: `${base2}/iam`, body: { enabled: v }, summary: `IAM enforcement \u2192 ${v ? "on" : "off"}` });
|
|
10005
|
-
console.log(
|
|
10412
|
+
console.log(import_chalk47.default.green(` Users & groups ${v ? "enabled" : "disabled"}.`));
|
|
10006
10413
|
break;
|
|
10007
10414
|
}
|
|
10008
10415
|
case "cors": {
|
|
@@ -10015,11 +10422,11 @@ Tenant ${tenant2}`));
|
|
|
10015
10422
|
if (v === "") break;
|
|
10016
10423
|
const parsed = v === "null" ? null : safeJson(v);
|
|
10017
10424
|
if (parsed === void 0) {
|
|
10018
|
-
console.log(
|
|
10425
|
+
console.log(import_chalk47.default.yellow(" Not valid JSON \u2014 unchanged."));
|
|
10019
10426
|
break;
|
|
10020
10427
|
}
|
|
10021
10428
|
await admin({ method: "PUT", path: `${base2}/cors`, body: { cors: parsed }, summary: "Set tenant CORS" });
|
|
10022
|
-
console.log(
|
|
10429
|
+
console.log(import_chalk47.default.green(" CORS updated."));
|
|
10023
10430
|
break;
|
|
10024
10431
|
}
|
|
10025
10432
|
case "emails":
|
|
@@ -10043,15 +10450,15 @@ async function loginMethodsMenu(teamId, tenant2, base2) {
|
|
|
10043
10450
|
const choices = [];
|
|
10044
10451
|
for (const c of appClients) {
|
|
10045
10452
|
const nP = (c.providers ?? []).length || c.providers_count || 0;
|
|
10046
|
-
choices.push({ name: `${
|
|
10453
|
+
choices.push({ name: `${import_chalk47.default.cyan("APIblaze-hosted")} ${import_chalk47.default.bold(c.name ?? c.clientId)} ${import_chalk47.default.dim(`${c.clientId}${nP ? ` \xB7 ${nP} provider${nP === 1 ? "" : "s"}` : import_chalk47.default.yellow(" \xB7 needs a provider")}`)}`, value: { kind: "client", item: c } });
|
|
10047
10454
|
}
|
|
10048
10455
|
for (const i of issuers) {
|
|
10049
|
-
choices.push({ name: `${
|
|
10456
|
+
choices.push({ name: `${import_chalk47.default.cyan("Your own \u2014 JWT ")} ${import_chalk47.default.bold(i.iss)} ${import_chalk47.default.dim(`aud=${i.aud}`)}`, value: { kind: "issuer", item: i } });
|
|
10050
10457
|
}
|
|
10051
10458
|
if (opaque?.endpoint) {
|
|
10052
|
-
choices.push({ name: `${
|
|
10459
|
+
choices.push({ name: `${import_chalk47.default.cyan("Your own \u2014 opaque")} ${import_chalk47.default.bold(opaque.endpoint)}`, value: { kind: "opaque", item: opaque } });
|
|
10053
10460
|
}
|
|
10054
|
-
if (!choices.length) console.log(
|
|
10461
|
+
if (!choices.length) console.log(import_chalk47.default.dim("\n No login methods yet \u2014 consumers cannot sign in until you add one."));
|
|
10055
10462
|
const { pick: pick2 } = await inquirer3.prompt([{
|
|
10056
10463
|
type: "list",
|
|
10057
10464
|
name: "pick",
|
|
@@ -10091,9 +10498,9 @@ async function addLoginMethod(teamId, tenant2, base2) {
|
|
|
10091
10498
|
message: "How do people log in?",
|
|
10092
10499
|
pageSize: 8,
|
|
10093
10500
|
choices: [
|
|
10094
|
-
{ name: `APIblaze hosted login page ${
|
|
10095
|
-
{ name: `Your own hosted login \u2014 JWT ${
|
|
10096
|
-
{ name: `Your own login \u2014 opaque token ${
|
|
10501
|
+
{ name: `APIblaze hosted login page ${import_chalk47.default.dim("we host login; pick a provider (GitHub/Google/\u2026) \u2014 easiest")}`, value: "apiblaze" },
|
|
10502
|
+
{ name: `Your own hosted login \u2014 JWT ${import_chalk47.default.dim("your identity provider issues JWTs; we trust them")}`, value: "jwt" },
|
|
10503
|
+
{ name: `Your own login \u2014 opaque token ${import_chalk47.default.dim("we validate your opaque tokens via an introspection endpoint")}`, value: "opaque" },
|
|
10097
10504
|
{ name: "\u2190 Back", value: null }
|
|
10098
10505
|
]
|
|
10099
10506
|
}]);
|
|
@@ -10117,8 +10524,8 @@ function safeJson(s) {
|
|
|
10117
10524
|
async function emailsMenu(base2, emails) {
|
|
10118
10525
|
const { default: inquirer3 } = await import("inquirer");
|
|
10119
10526
|
console.log();
|
|
10120
|
-
if (!emails.length) console.log(
|
|
10121
|
-
for (const e of emails) console.log(` ${
|
|
10527
|
+
if (!emails.length) console.log(import_chalk47.default.dim(" No consumer-admin emails."));
|
|
10528
|
+
for (const e of emails) console.log(` ${import_chalk47.default.bold(e.email ?? e)} ${import_chalk47.default.dim(e.status ?? "")}`);
|
|
10122
10529
|
const { act } = await inquirer3.prompt([{
|
|
10123
10530
|
type: "list",
|
|
10124
10531
|
name: "act",
|
|
@@ -10133,7 +10540,7 @@ async function emailsMenu(base2, emails) {
|
|
|
10133
10540
|
if (act === "add") {
|
|
10134
10541
|
const { email } = await inquirer3.prompt([{ type: "input", name: "email", message: "Email:", validate: (s) => /.+@.+\..+/.test(s) || "not an email" }]);
|
|
10135
10542
|
await admin({ method: "POST", path: `${base2}/admin-emails`, body: { email }, summary: `Add consumer-admin ${email}` });
|
|
10136
|
-
console.log(
|
|
10543
|
+
console.log(import_chalk47.default.green(` ${email} added.`));
|
|
10137
10544
|
} else {
|
|
10138
10545
|
const { e } = await inquirer3.prompt([{
|
|
10139
10546
|
type: "list",
|
|
@@ -10143,7 +10550,7 @@ async function emailsMenu(base2, emails) {
|
|
|
10143
10550
|
}]);
|
|
10144
10551
|
if (!e) return;
|
|
10145
10552
|
await admin({ method: "DELETE", path: `${base2}/admin-emails/${encodeURIComponent(e)}`, summary: `Remove consumer-admin ${e}` });
|
|
10146
|
-
console.log(
|
|
10553
|
+
console.log(import_chalk47.default.green(` ${e} removed.`));
|
|
10147
10554
|
}
|
|
10148
10555
|
}
|
|
10149
10556
|
async function addIssuer(base2) {
|
|
@@ -10164,19 +10571,19 @@ async function addIssuer(base2) {
|
|
|
10164
10571
|
body: { iss: a.iss.trim(), aud: a.aud.trim(), jwks_url: a.jwks.trim() || null, sub_semantics: a.sem, ...claim ? { claim_name: claim } : {} },
|
|
10165
10572
|
summary: `Add external issuer ${a.iss.trim()}`
|
|
10166
10573
|
});
|
|
10167
|
-
console.log(
|
|
10574
|
+
console.log(import_chalk47.default.green(" JWT login method saved."));
|
|
10168
10575
|
}
|
|
10169
10576
|
async function issuerHome(base2, issuer) {
|
|
10170
10577
|
const { default: inquirer3 } = await import("inquirer");
|
|
10171
10578
|
console.log(`
|
|
10172
|
-
${
|
|
10579
|
+
${import_chalk47.default.bold(issuer.iss)} ${import_chalk47.default.dim(`aud=${issuer.aud} \xB7 ${issuer.sub_semantics ?? ""}`)}`);
|
|
10173
10580
|
const { act } = await inquirer3.prompt([{
|
|
10174
10581
|
type: "list",
|
|
10175
10582
|
name: "act",
|
|
10176
10583
|
message: "This JWT login method:",
|
|
10177
10584
|
choices: [
|
|
10178
10585
|
{ name: "Replace it (re-enter details)", value: "edit" },
|
|
10179
|
-
{ name:
|
|
10586
|
+
{ name: import_chalk47.default.red("Delete it"), value: "rm" },
|
|
10180
10587
|
{ name: "\u2190 Back", value: "back" }
|
|
10181
10588
|
]
|
|
10182
10589
|
}]);
|
|
@@ -10192,7 +10599,7 @@ async function issuerHome(base2, issuer) {
|
|
|
10192
10599
|
path: `${base2}/external-issuers?iss=${encodeURIComponent(issuer.iss)}&aud=${encodeURIComponent(issuer.aud)}`,
|
|
10193
10600
|
summary: `Delete issuer ${issuer.iss}`
|
|
10194
10601
|
});
|
|
10195
|
-
console.log(
|
|
10602
|
+
console.log(import_chalk47.default.green(" Deleted."));
|
|
10196
10603
|
}
|
|
10197
10604
|
async function setOpaque(base2, cur) {
|
|
10198
10605
|
const { default: inquirer3 } = await import("inquirer");
|
|
@@ -10201,19 +10608,19 @@ async function setOpaque(base2, cur) {
|
|
|
10201
10608
|
{ type: "list", name: "method", message: "HTTP method:", choices: ["GET", "POST"], default: cur?.method ?? "GET" }
|
|
10202
10609
|
]);
|
|
10203
10610
|
await admin({ method: "PUT", path: `${base2}/opaque`, body: { opaque: { endpoint: a.endpoint, method: a.method } }, summary: "Set opaque validator" });
|
|
10204
|
-
console.log(
|
|
10611
|
+
console.log(import_chalk47.default.green(" Opaque login method set."));
|
|
10205
10612
|
}
|
|
10206
10613
|
async function opaqueHome(base2, cur) {
|
|
10207
10614
|
const { default: inquirer3 } = await import("inquirer");
|
|
10208
10615
|
console.log(`
|
|
10209
|
-
${
|
|
10616
|
+
${import_chalk47.default.bold(cur.endpoint)} ${import_chalk47.default.dim(cur.method ?? "GET")}`);
|
|
10210
10617
|
const { act } = await inquirer3.prompt([{
|
|
10211
10618
|
type: "list",
|
|
10212
10619
|
name: "act",
|
|
10213
10620
|
message: "This opaque login method:",
|
|
10214
10621
|
choices: [
|
|
10215
10622
|
{ name: "Replace it", value: "edit" },
|
|
10216
|
-
{ name:
|
|
10623
|
+
{ name: import_chalk47.default.red("Delete it"), value: "rm" },
|
|
10217
10624
|
{ name: "\u2190 Back", value: "back" }
|
|
10218
10625
|
]
|
|
10219
10626
|
}]);
|
|
@@ -10223,7 +10630,7 @@ async function opaqueHome(base2, cur) {
|
|
|
10223
10630
|
return;
|
|
10224
10631
|
}
|
|
10225
10632
|
await admin({ method: "PUT", path: `${base2}/opaque`, body: { opaque: null }, summary: "Clear opaque validator" });
|
|
10226
|
-
console.log(
|
|
10633
|
+
console.log(import_chalk47.default.green(" Deleted."));
|
|
10227
10634
|
}
|
|
10228
10635
|
async function clientHome(base2, summary) {
|
|
10229
10636
|
const { default: inquirer3 } = await import("inquirer");
|
|
@@ -10242,13 +10649,13 @@ async function clientHome(base2, summary) {
|
|
|
10242
10649
|
message: `${c.name ?? id}:`,
|
|
10243
10650
|
pageSize: 12,
|
|
10244
10651
|
choices: [
|
|
10245
|
-
{ name: `Login providers${nProviders ? ` (${nProviders})` : ""} ${
|
|
10246
|
-
{ name: `Callback URLs: ${cb.length ?
|
|
10247
|
-
{ name: `Scopes: ${scopes.length ?
|
|
10652
|
+
{ name: `Login providers${nProviders ? ` (${nProviders})` : ""} ${import_chalk47.default.dim("google/github/microsoft/\u2026 \u2014 how consumers sign in")}`, value: "providers" },
|
|
10653
|
+
{ name: `Callback URLs: ${cb.length ? import_chalk47.default.cyan(cb.join(", ")) : import_chalk47.default.dim("(none)")}`, value: "callbacks" },
|
|
10654
|
+
{ name: `Scopes: ${scopes.length ? import_chalk47.default.cyan(scopes.join(" ")) : import_chalk47.default.dim("(defaults)")}`, value: "scopes" },
|
|
10248
10655
|
{ name: `Token expiries: access ${c.accessTokenExpiry ?? 3600}s \xB7 id ${c.idTokenExpiry ?? 3600}s \xB7 refresh ${c.refreshTokenExpiry ?? 2592e3}s`, value: "expiries" },
|
|
10249
10656
|
{ name: "Reveal client secret", value: "secret" },
|
|
10250
10657
|
{ name: "Rotate client secret", value: "rotate" },
|
|
10251
|
-
{ name:
|
|
10658
|
+
{ name: import_chalk47.default.red("Delete this app client"), value: "delete" },
|
|
10252
10659
|
{ name: "\u2190 Back", value: "back" }
|
|
10253
10660
|
]
|
|
10254
10661
|
}]);
|
|
@@ -10261,13 +10668,13 @@ async function clientHome(base2, summary) {
|
|
|
10261
10668
|
case "callbacks": {
|
|
10262
10669
|
const { v } = await inquirer3.prompt([{ type: "input", name: "v", message: "Callback URLs (comma-separated):", default: cb.join(", ") }]);
|
|
10263
10670
|
await admin({ method: "PATCH", path: cBase, body: { authorizedCallbackUrls: parseList(v) }, summary: "Update callback URLs" });
|
|
10264
|
-
console.log(
|
|
10671
|
+
console.log(import_chalk47.default.green(" Callbacks updated."));
|
|
10265
10672
|
break;
|
|
10266
10673
|
}
|
|
10267
10674
|
case "scopes": {
|
|
10268
10675
|
const { v } = await inquirer3.prompt([{ type: "input", name: "v", message: "Scopes (space/comma-separated):", default: scopes.join(" ") }]);
|
|
10269
10676
|
await admin({ method: "PATCH", path: cBase, body: { scopes: v.split(/[\s,]+/).filter(Boolean) }, summary: "Update scopes" });
|
|
10270
|
-
console.log(
|
|
10677
|
+
console.log(import_chalk47.default.green(" Scopes updated."));
|
|
10271
10678
|
break;
|
|
10272
10679
|
}
|
|
10273
10680
|
case "expiries": {
|
|
@@ -10282,14 +10689,14 @@ async function clientHome(base2, summary) {
|
|
|
10282
10689
|
body: { accessTokenExpiry: Number(a.access), idTokenExpiry: Number(a.id), refreshTokenExpiry: Number(a.refresh) },
|
|
10283
10690
|
summary: "Update token expiries"
|
|
10284
10691
|
});
|
|
10285
|
-
console.log(
|
|
10692
|
+
console.log(import_chalk47.default.green(" Expiries updated."));
|
|
10286
10693
|
break;
|
|
10287
10694
|
}
|
|
10288
10695
|
case "secret": {
|
|
10289
10696
|
const { sure } = await inquirer3.prompt([{ type: "confirm", name: "sure", message: "Print the client secret to this terminal?", default: false }]);
|
|
10290
10697
|
if (!sure) break;
|
|
10291
10698
|
const s = await admin({ method: "GET", path: `${cBase}/secret`, summary: "Reveal client secret" });
|
|
10292
|
-
console.log(` ${
|
|
10699
|
+
console.log(` ${import_chalk47.default.bold("client_secret")}: ${import_chalk47.default.green(s?.clientSecret ?? s?.client_secret ?? JSON.stringify(s))}`);
|
|
10293
10700
|
break;
|
|
10294
10701
|
}
|
|
10295
10702
|
case "rotate": {
|
|
@@ -10297,14 +10704,14 @@ async function clientHome(base2, summary) {
|
|
|
10297
10704
|
if (!sure) break;
|
|
10298
10705
|
const fresh = randomSecret();
|
|
10299
10706
|
await admin({ method: "PATCH", path: cBase, body: { clientSecret: fresh }, summary: "Rotate client secret" });
|
|
10300
|
-
console.log(` New ${
|
|
10707
|
+
console.log(` New ${import_chalk47.default.bold("client_secret")}: ${import_chalk47.default.green(fresh)} ${import_chalk47.default.dim("(store it now)")}`);
|
|
10301
10708
|
break;
|
|
10302
10709
|
}
|
|
10303
10710
|
case "delete": {
|
|
10304
10711
|
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 }]);
|
|
10305
10712
|
if (!sure) break;
|
|
10306
10713
|
await admin({ method: "DELETE", path: cBase, summary: `Delete app client ${id}` });
|
|
10307
|
-
console.log(
|
|
10714
|
+
console.log(import_chalk47.default.green(" App client deleted."));
|
|
10308
10715
|
return;
|
|
10309
10716
|
}
|
|
10310
10717
|
}
|
|
@@ -10331,9 +10738,9 @@ async function providersMenu(cBase, clientLabel) {
|
|
|
10331
10738
|
const providers = Array.isArray(raw) ? raw : [];
|
|
10332
10739
|
console.log();
|
|
10333
10740
|
for (const p of providers) {
|
|
10334
|
-
console.log(` ${
|
|
10741
|
+
console.log(` ${import_chalk47.default.bold(p.type)} ${import_chalk47.default.dim(`${p.clientId || "(managed)"} \xB7 identity=${p.tokenType ?? "apiblaze"} \xB7 to-upstream=${p.targetServerToken ?? "apiblaze"}${p.isApiblazeDefault ? " \xB7 apiblaze-managed" : ""}`)}`);
|
|
10335
10742
|
}
|
|
10336
|
-
if (!providers.length) console.log(
|
|
10743
|
+
if (!providers.length) console.log(import_chalk47.default.dim(" No login providers \u2014 consumers cannot sign in to this client yet."));
|
|
10337
10744
|
const { act } = await inquirer3.prompt([{
|
|
10338
10745
|
type: "list",
|
|
10339
10746
|
name: "act",
|
|
@@ -10391,23 +10798,23 @@ async function providersMenu(cBase, clientLabel) {
|
|
|
10391
10798
|
body.targetServerToken = routing;
|
|
10392
10799
|
}
|
|
10393
10800
|
await admin({ method: "POST", path: `${cBase}/providers`, body, summary: `Add ${type} login provider` });
|
|
10394
|
-
console.log(
|
|
10801
|
+
console.log(import_chalk47.default.green(` ${type} provider added.`));
|
|
10395
10802
|
} else {
|
|
10396
10803
|
const { p } = await inquirer3.prompt([{
|
|
10397
10804
|
type: "list",
|
|
10398
10805
|
name: "p",
|
|
10399
10806
|
message: act === "rm" ? "Remove which provider?" : "Reveal which secret?",
|
|
10400
|
-
choices: [...providers.map((x) => ({ name: `${x.type} ${
|
|
10807
|
+
choices: [...providers.map((x) => ({ name: `${x.type} ${import_chalk47.default.dim(x.clientId || "(managed)")}`, value: x })), { name: "\u2190 Back", value: null }]
|
|
10401
10808
|
}]);
|
|
10402
10809
|
if (!p) continue;
|
|
10403
10810
|
if (act === "rm") {
|
|
10404
10811
|
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 }]);
|
|
10405
10812
|
if (!sure) continue;
|
|
10406
10813
|
await admin({ method: "DELETE", path: `${cBase}/providers/${encodeURIComponent(p.id)}`, summary: `Remove ${p.type} provider` });
|
|
10407
|
-
console.log(
|
|
10814
|
+
console.log(import_chalk47.default.green(` ${p.type} removed.`));
|
|
10408
10815
|
} else {
|
|
10409
10816
|
const s = await admin({ method: "GET", path: `${cBase}/providers/${encodeURIComponent(p.id)}/secret`, summary: `Reveal ${p.type} provider secret` });
|
|
10410
|
-
console.log(` ${
|
|
10817
|
+
console.log(` ${import_chalk47.default.bold("client_secret")}: ${import_chalk47.default.green(s?.clientSecret ?? s?.client_secret ?? JSON.stringify(s))}`);
|
|
10411
10818
|
}
|
|
10412
10819
|
}
|
|
10413
10820
|
}
|
|
@@ -10415,7 +10822,7 @@ async function providersMenu(cBase, clientLabel) {
|
|
|
10415
10822
|
|
|
10416
10823
|
// src/commands/spec.ts
|
|
10417
10824
|
var fs12 = __toESM(require("fs"));
|
|
10418
|
-
var
|
|
10825
|
+
var import_chalk48 = __toESM(require("chalk"));
|
|
10419
10826
|
var import_ora21 = __toESM(require("ora"));
|
|
10420
10827
|
init_admin();
|
|
10421
10828
|
init_api();
|
|
@@ -10432,14 +10839,14 @@ async function runSpecGet(project, opts) {
|
|
|
10432
10839
|
}
|
|
10433
10840
|
async function runSpecSet(project, opts) {
|
|
10434
10841
|
if (!opts.file) {
|
|
10435
|
-
console.error(
|
|
10842
|
+
console.error(import_chalk48.default.red("--file <path> is required (OpenAPI JSON or YAML)."));
|
|
10436
10843
|
process.exit(1);
|
|
10437
10844
|
}
|
|
10438
10845
|
let specContent;
|
|
10439
10846
|
try {
|
|
10440
10847
|
specContent = fs12.readFileSync(opts.file, "utf-8");
|
|
10441
10848
|
} catch {
|
|
10442
|
-
console.error(
|
|
10849
|
+
console.error(import_chalk48.default.red(`Cannot read file: ${opts.file}`));
|
|
10443
10850
|
process.exit(1);
|
|
10444
10851
|
}
|
|
10445
10852
|
const { teamId } = await resolveTeam(opts.team);
|
|
@@ -10468,23 +10875,23 @@ async function runSpecSet(project, opts) {
|
|
|
10468
10875
|
function printSpecChangeReport(report, projectName) {
|
|
10469
10876
|
const r = report.routes;
|
|
10470
10877
|
if (r) {
|
|
10471
|
-
for (const a of r.added) console.log(
|
|
10472
|
-
for (const d of r.removed) console.log(
|
|
10473
|
-
for (const rn of r.renamed ?? []) console.log(
|
|
10474
|
-
for (const m of r.modified ?? []) console.log(
|
|
10878
|
+
for (const a of r.added) console.log(import_chalk48.default.green(` + ${a}`));
|
|
10879
|
+
for (const d of r.removed) console.log(import_chalk48.default.red(` \u2212 ${d}`));
|
|
10880
|
+
for (const rn of r.renamed ?? []) console.log(import_chalk48.default.yellow(` ~ ${rn.from} \u2192 ${rn.to}`));
|
|
10881
|
+
for (const m of r.modified ?? []) console.log(import_chalk48.default.dim(` \xB1 ${m} (params/schema changed)`));
|
|
10475
10882
|
}
|
|
10476
|
-
if (report.applied?.mcp_regenerated) console.log(
|
|
10477
|
-
if (report.applied?.candidates_cleared) console.log(
|
|
10883
|
+
if (report.applied?.mcp_regenerated) console.log(import_chalk48.default.dim(" MCP catalogue regenerated."));
|
|
10884
|
+
if (report.applied?.candidates_cleared) console.log(import_chalk48.default.dim(` ${report.applied.candidates_cleared} route candidate(s) now documented \u2014 cleared.`));
|
|
10478
10885
|
for (const item of report.attention ?? []) {
|
|
10479
|
-
console.log(
|
|
10886
|
+
console.log(import_chalk48.default.yellow(` \u26A0 ${item.detail}`));
|
|
10480
10887
|
if (item.suggestion?.carry_from && item.suggestion.carry_to) {
|
|
10481
|
-
console.log(
|
|
10888
|
+
console.log(import_chalk48.default.dim(` Fix: apiblaze spec carry ${projectName} --method ${item.suggestion.method} --from "${item.suggestion.carry_from}" --to "${item.suggestion.carry_to}"`));
|
|
10482
10889
|
}
|
|
10483
10890
|
}
|
|
10484
10891
|
}
|
|
10485
10892
|
async function runSpecCarry(project, opts) {
|
|
10486
10893
|
if (!opts.method || !opts.from || !opts.to) {
|
|
10487
|
-
console.error(
|
|
10894
|
+
console.error(import_chalk48.default.red("--method, --from and --to are required."));
|
|
10488
10895
|
process.exit(1);
|
|
10489
10896
|
}
|
|
10490
10897
|
const { teamId } = await resolveTeam(opts.team);
|
|
@@ -10506,7 +10913,7 @@ async function runSpecCarry(project, opts) {
|
|
|
10506
10913
|
}
|
|
10507
10914
|
async function runSpecDeleteRule(project, opts) {
|
|
10508
10915
|
if (!opts.method || !opts.route) {
|
|
10509
|
-
console.error(
|
|
10916
|
+
console.error(import_chalk48.default.red("--method and --route are required."));
|
|
10510
10917
|
process.exit(1);
|
|
10511
10918
|
}
|
|
10512
10919
|
const { teamId } = await resolveTeam(opts.team);
|
|
@@ -10526,17 +10933,18 @@ async function runSpecDeleteRule(project, opts) {
|
|
|
10526
10933
|
}
|
|
10527
10934
|
|
|
10528
10935
|
// src/commands/agent.ts
|
|
10529
|
-
var
|
|
10936
|
+
var import_chalk50 = __toESM(require("chalk"));
|
|
10530
10937
|
var import_ora22 = __toESM(require("ora"));
|
|
10531
10938
|
init_auth();
|
|
10532
10939
|
init_admin();
|
|
10533
10940
|
init_resolve();
|
|
10534
10941
|
|
|
10535
10942
|
// src/lib/tools.ts
|
|
10536
|
-
var
|
|
10943
|
+
var import_chalk49 = __toESM(require("chalk"));
|
|
10537
10944
|
init_admin();
|
|
10538
10945
|
init_api();
|
|
10539
10946
|
init_resolve();
|
|
10947
|
+
init_paging();
|
|
10540
10948
|
async function proj(teamId, name, version2) {
|
|
10541
10949
|
return resolveProject(teamId, name, version2);
|
|
10542
10950
|
}
|
|
@@ -10563,15 +10971,15 @@ var TOOLS = [
|
|
|
10563
10971
|
const key = keys.dev ?? Object.values(keys)[0];
|
|
10564
10972
|
const url = `https://${a.name}.abz.run/${version2}/dev`;
|
|
10565
10973
|
const tryIt = buildTryItCurl(url, auth, key, res.tenant);
|
|
10566
|
-
const lines = [` ${
|
|
10567
|
-
if (res.devPortal) lines.push(` ${
|
|
10974
|
+
const lines = [` ${import_chalk49.default.dim("Proxy URL:")} ${import_chalk49.default.bold(url)}`];
|
|
10975
|
+
if (res.devPortal) lines.push(` ${import_chalk49.default.dim("Dev portal:")} ${res.devPortal}`);
|
|
10568
10976
|
const envs = Object.keys(keys);
|
|
10569
10977
|
if (envs.length) {
|
|
10570
|
-
lines.push("", ` ${
|
|
10978
|
+
lines.push("", ` ${import_chalk49.default.bold("API keys")} ${import_chalk49.default.dim("(bootstrapped \u2014 send as the X-API-Key header; shown once):")}`);
|
|
10571
10979
|
const w = Math.max(...envs.map((e) => e.length));
|
|
10572
|
-
for (const env of envs) lines.push(` ${
|
|
10980
|
+
for (const env of envs) lines.push(` ${import_chalk49.default.cyan(env.padEnd(w))} ${import_chalk49.default.green(keys[env])}`);
|
|
10573
10981
|
}
|
|
10574
|
-
if (tryIt) lines.push("", ` ${
|
|
10982
|
+
if (tryIt) lines.push("", ` ${import_chalk49.default.dim("Try it:")}`, ` ${import_chalk49.default.cyan(tryIt)}`);
|
|
10575
10983
|
return { ...res, proxy_url: url, keys, ...tryIt ? { try_it: tryIt } : {}, display: lines.join("\n") };
|
|
10576
10984
|
}
|
|
10577
10985
|
},
|
|
@@ -10626,7 +11034,20 @@ var TOOLS = [
|
|
|
10626
11034
|
name: "list_tenants",
|
|
10627
11035
|
description: "List tenants in the active team.",
|
|
10628
11036
|
params: {},
|
|
10629
|
-
|
|
11037
|
+
// §3b N5: the endpoint pages (50 default, 200 max). An agent asking "what
|
|
11038
|
+
// tenants do I have?" means all of them, and a silently short list would
|
|
11039
|
+
// have it create a tenant that already exists — so walk the cursor.
|
|
11040
|
+
run: async (_a, { teamId }) => {
|
|
11041
|
+
const walked = await pageAll(
|
|
11042
|
+
(cursor) => admin({
|
|
11043
|
+
method: "GET",
|
|
11044
|
+
path: withPage(`/teams/${encodeURIComponent(teamId)}/tenants?detail=1`, cursor),
|
|
11045
|
+
summary: "List tenants"
|
|
11046
|
+
}),
|
|
11047
|
+
(b) => b?.tenants
|
|
11048
|
+
);
|
|
11049
|
+
return { tenants: walked.items, total: walked.total, ...walked.truncated ? { truncated: true } : {} };
|
|
11050
|
+
}
|
|
10630
11051
|
},
|
|
10631
11052
|
{
|
|
10632
11053
|
name: "create_tenant",
|
|
@@ -10727,17 +11148,17 @@ function truncate(value, max = 1500) {
|
|
|
10727
11148
|
}
|
|
10728
11149
|
function printCost(resp) {
|
|
10729
11150
|
const line = formatBilling(resp.billing);
|
|
10730
|
-
if (line) console.log(
|
|
11151
|
+
if (line) console.log(import_chalk50.default.magenta(` ${line}`) + import_chalk50.default.dim(` (${resp.llm.model}, ${resp.llm.total_tokens} tok)`));
|
|
10731
11152
|
}
|
|
10732
11153
|
async function runAgent(opts) {
|
|
10733
11154
|
requireAuth();
|
|
10734
11155
|
const { teamId, teamName } = await resolveTeam(opts.team);
|
|
10735
11156
|
const { default: inquirer3 } = await import("inquirer");
|
|
10736
|
-
console.log(
|
|
10737
|
-
console.log(
|
|
11157
|
+
console.log(import_chalk50.default.bold("APIblaze agent") + import_chalk50.default.dim(` \xB7 team ${teamName ?? teamId}`));
|
|
11158
|
+
console.log(import_chalk50.default.dim('Ask me to create/delete/configure proxies, tenants, keys, domains, specs. Type "exit" to quit.\n'));
|
|
10738
11159
|
const history = [];
|
|
10739
11160
|
while (true) {
|
|
10740
|
-
const { input } = await inquirer3.prompt([{ type: "input", name: "input", message:
|
|
11161
|
+
const { input } = await inquirer3.prompt([{ type: "input", name: "input", message: import_chalk50.default.cyan("you") + " \u203A" }]);
|
|
10741
11162
|
const text = (input ?? "").trim();
|
|
10742
11163
|
if (!text) continue;
|
|
10743
11164
|
if (["exit", "quit", ":q"].includes(text.toLowerCase())) break;
|
|
@@ -10751,14 +11172,14 @@ async function runAgent(opts) {
|
|
|
10751
11172
|
} catch (err) {
|
|
10752
11173
|
spinner.stop();
|
|
10753
11174
|
if (err instanceof ApiError && err.status === 402) {
|
|
10754
|
-
console.log(
|
|
11175
|
+
console.log(import_chalk50.default.yellow(" Insufficient credits \u2014 top up to keep using the agent."));
|
|
10755
11176
|
break;
|
|
10756
11177
|
}
|
|
10757
11178
|
throw err;
|
|
10758
11179
|
}
|
|
10759
11180
|
history.push({ role: "assistant", content: resp.raw });
|
|
10760
11181
|
printCost(resp);
|
|
10761
|
-
if (resp.reply) console.log(
|
|
11182
|
+
if (resp.reply) console.log(import_chalk50.default.green("agent") + " \u203A " + resp.reply);
|
|
10762
11183
|
if (!resp.action) break;
|
|
10763
11184
|
const tool = findTool(resp.action.tool);
|
|
10764
11185
|
if (!tool) {
|
|
@@ -10783,11 +11204,11 @@ async function runAgent(opts) {
|
|
|
10783
11204
|
}
|
|
10784
11205
|
renderTrace();
|
|
10785
11206
|
if (step === MAX_TOOL_STEPS - 1) {
|
|
10786
|
-
console.log(
|
|
11207
|
+
console.log(import_chalk50.default.dim(" (paused after several steps \u2014 tell me how to continue)"));
|
|
10787
11208
|
}
|
|
10788
11209
|
}
|
|
10789
11210
|
}
|
|
10790
|
-
console.log(
|
|
11211
|
+
console.log(import_chalk50.default.dim("\nBye."));
|
|
10791
11212
|
}
|
|
10792
11213
|
|
|
10793
11214
|
// src/commands/config-browse.ts
|
|
@@ -11095,11 +11516,11 @@ function dig(blob, dotted) {
|
|
|
11095
11516
|
}
|
|
11096
11517
|
var readSetting = (s, cfg) => s.read ? s.read(cfg) : dig(cfg, s.key);
|
|
11097
11518
|
function show(v) {
|
|
11098
|
-
if (v === void 0) return
|
|
11099
|
-
if (v === null) return
|
|
11100
|
-
if (typeof v === "object") return
|
|
11101
|
-
if (typeof v === "boolean") return v ?
|
|
11102
|
-
return
|
|
11519
|
+
if (v === void 0) return import_chalk51.default.dim("(unset)");
|
|
11520
|
+
if (v === null) return import_chalk51.default.dim("null");
|
|
11521
|
+
if (typeof v === "object") return import_chalk51.default.cyan(JSON.stringify(v));
|
|
11522
|
+
if (typeof v === "boolean") return v ? import_chalk51.default.green("on") : import_chalk51.default.red("off");
|
|
11523
|
+
return import_chalk51.default.cyan(String(v));
|
|
11103
11524
|
}
|
|
11104
11525
|
function parseValue(raw) {
|
|
11105
11526
|
if (raw === "true") return true;
|
|
@@ -11115,12 +11536,15 @@ function parseValue(raw) {
|
|
|
11115
11536
|
return raw;
|
|
11116
11537
|
}
|
|
11117
11538
|
async function fetchConfigBlob(proj2) {
|
|
11118
|
-
const
|
|
11119
|
-
|
|
11120
|
-
|
|
11121
|
-
|
|
11122
|
-
|
|
11123
|
-
|
|
11539
|
+
const walked = await pageAll(
|
|
11540
|
+
(cursor) => admin({
|
|
11541
|
+
method: "GET",
|
|
11542
|
+
path: withPage(`/projects?team_id=${encodeURIComponent(proj2.teamId)}`, cursor),
|
|
11543
|
+
summary: `Read settings for ${proj2.projectName}`
|
|
11544
|
+
}),
|
|
11545
|
+
(b) => b?.projects
|
|
11546
|
+
);
|
|
11547
|
+
const rows = walked.items;
|
|
11124
11548
|
const row = rows.find((r) => r.project_id === proj2.projectId && r.api_version === proj2.apiVersion) ?? rows.find((r) => r.project_id === proj2.projectId);
|
|
11125
11549
|
return row?.config ?? {};
|
|
11126
11550
|
}
|
|
@@ -11146,10 +11570,10 @@ async function patchSetting(proj2, s, value, cfg) {
|
|
|
11146
11570
|
}
|
|
11147
11571
|
}
|
|
11148
11572
|
var loginFirst = (what) => {
|
|
11149
|
-
console.log(
|
|
11573
|
+
console.log(import_chalk51.default.yellow(`
|
|
11150
11574
|
Log in first to ${what}.`));
|
|
11151
|
-
console.log(
|
|
11152
|
-
console.log(
|
|
11575
|
+
console.log(import_chalk51.default.dim(" Run `npx apiblaze login` \u2014 or `npx apiblaze claim` if you created this proxy"));
|
|
11576
|
+
console.log(import_chalk51.default.dim(" anonymously and want to bring it into your account.\n"));
|
|
11153
11577
|
};
|
|
11154
11578
|
async function runConfig(project, key, value, opts) {
|
|
11155
11579
|
const creds = loadCredentials();
|
|
@@ -11166,9 +11590,9 @@ async function runConfig(project, key, value, opts) {
|
|
|
11166
11590
|
}
|
|
11167
11591
|
const setting = SETTINGS.find((s) => s.key === key);
|
|
11168
11592
|
if (!setting) {
|
|
11169
|
-
console.error(
|
|
11170
|
-
console.error(
|
|
11171
|
-
console.error(
|
|
11593
|
+
console.error(import_chalk51.default.red(`Unknown setting "${key}".`));
|
|
11594
|
+
console.error(import_chalk51.default.dim(" Known: " + SETTINGS.map((s) => s.key).join(", ")));
|
|
11595
|
+
console.error(import_chalk51.default.dim(" (Features like transforms/domains/tenants live in the menu: `apiblaze config <project>`.)"));
|
|
11172
11596
|
process.exit(1);
|
|
11173
11597
|
}
|
|
11174
11598
|
if (value === void 0) {
|
|
@@ -11183,7 +11607,7 @@ async function pickProject(teamId) {
|
|
|
11183
11607
|
const { getProjects: getProjects2 } = await Promise.resolve().then(() => (init_api(), api_exports));
|
|
11184
11608
|
const projects = await getProjects2(teamId).catch(() => []);
|
|
11185
11609
|
if (!projects.length) {
|
|
11186
|
-
console.error(
|
|
11610
|
+
console.error(import_chalk51.default.red("No projects in this team. Create one: `npx apiblaze create`."));
|
|
11187
11611
|
process.exit(1);
|
|
11188
11612
|
}
|
|
11189
11613
|
const { default: inquirer3 } = await import("inquirer");
|
|
@@ -11191,7 +11615,7 @@ async function pickProject(teamId) {
|
|
|
11191
11615
|
type: "list",
|
|
11192
11616
|
name: "picked",
|
|
11193
11617
|
message: "Which project?",
|
|
11194
|
-
choices: projects.map((p) => ({ name: `${p.projectName} ${
|
|
11618
|
+
choices: projects.map((p) => ({ name: `${p.projectName} ${import_chalk51.default.dim("v" + p.apiVersion)}`, value: p }))
|
|
11195
11619
|
}]);
|
|
11196
11620
|
return { projectId: picked.projectId, projectName: picked.projectName, apiVersion: picked.apiVersion, teamId, tenant: picked.tenant };
|
|
11197
11621
|
}
|
|
@@ -11202,25 +11626,25 @@ function printAll(proj2, cfg, json) {
|
|
|
11202
11626
|
console.log(JSON.stringify(out, null, 2));
|
|
11203
11627
|
return;
|
|
11204
11628
|
}
|
|
11205
|
-
console.log(
|
|
11629
|
+
console.log(import_chalk51.default.bold(`
|
|
11206
11630
|
${proj2.projectName} v${proj2.apiVersion} \u2014 settings
|
|
11207
11631
|
`));
|
|
11208
11632
|
for (const group2 of SETTING_GROUPS) {
|
|
11209
|
-
console.log(
|
|
11633
|
+
console.log(import_chalk51.default.bold(group2));
|
|
11210
11634
|
for (const s of SETTINGS.filter((x) => x.group === group2)) {
|
|
11211
|
-
console.log(` ${s.key.padEnd(32)} ${show(readSetting(s, cfg))} ${
|
|
11635
|
+
console.log(` ${s.key.padEnd(32)} ${show(readSetting(s, cfg))} ${import_chalk51.default.dim(s.desc)}`);
|
|
11212
11636
|
}
|
|
11213
11637
|
console.log();
|
|
11214
11638
|
}
|
|
11215
|
-
console.log(
|
|
11639
|
+
console.log(import_chalk51.default.dim("Change one: apiblaze config " + proj2.projectName + " <key> <value> (add --verbose for the API call)"));
|
|
11216
11640
|
}
|
|
11217
11641
|
async function discoveryMenu(project) {
|
|
11218
11642
|
const { default: inquirer3 } = await import("inquirer");
|
|
11219
|
-
console.log(
|
|
11643
|
+
console.log(import_chalk51.default.bold(`
|
|
11220
11644
|
APIblaze proxy configuration${project ? ` \u2014 ${project}` : ""}
|
|
11221
11645
|
`));
|
|
11222
|
-
console.log(
|
|
11223
|
-
console.log(
|
|
11646
|
+
console.log(import_chalk51.default.dim("You are not logged in \u2014 browsing what's configurable. Everything below works"));
|
|
11647
|
+
console.log(import_chalk51.default.dim("from this menu once you log in (`npx apiblaze login`).\n"));
|
|
11224
11648
|
for (; ; ) {
|
|
11225
11649
|
const { pick: pick2 } = await inquirer3.prompt([{
|
|
11226
11650
|
type: "list",
|
|
@@ -11228,13 +11652,13 @@ APIblaze proxy configuration${project ? ` \u2014 ${project}` : ""}
|
|
|
11228
11652
|
message: "Explore:",
|
|
11229
11653
|
pageSize: 20,
|
|
11230
11654
|
choices: [
|
|
11231
|
-
new inquirer3.Separator(
|
|
11655
|
+
new inquirer3.Separator(import_chalk51.default.bold("\u2014 Settings \u2014")),
|
|
11232
11656
|
...SETTING_GROUPS.map((g) => ({
|
|
11233
|
-
name: `${g} ${
|
|
11657
|
+
name: `${g} ${import_chalk51.default.dim(SETTINGS.filter((s) => s.group === g).map((s) => s.label).join(", "))}`,
|
|
11234
11658
|
value: { kind: "settings", g }
|
|
11235
11659
|
})),
|
|
11236
|
-
new inquirer3.Separator(
|
|
11237
|
-
...FEATURES.map((f) => ({ name: `${f.label} ${
|
|
11660
|
+
new inquirer3.Separator(import_chalk51.default.bold("\u2014 Features \u2014")),
|
|
11661
|
+
...FEATURES.map((f) => ({ name: `${f.label} ${import_chalk51.default.dim(f.desc)}`, value: { kind: "feature", f } })),
|
|
11238
11662
|
new inquirer3.Separator(),
|
|
11239
11663
|
{ name: "Exit", value: { kind: "exit" } }
|
|
11240
11664
|
]
|
|
@@ -11243,24 +11667,24 @@ APIblaze proxy configuration${project ? ` \u2014 ${project}` : ""}
|
|
|
11243
11667
|
if (pick2.kind === "settings") {
|
|
11244
11668
|
console.log();
|
|
11245
11669
|
for (const s of SETTINGS.filter((x) => x.group === pick2.g)) {
|
|
11246
|
-
console.log(` ${
|
|
11247
|
-
console.log(` ${
|
|
11670
|
+
console.log(` ${import_chalk51.default.bold(s.label.padEnd(28))} ${import_chalk51.default.dim(s.desc)}`);
|
|
11671
|
+
console.log(` ${import_chalk51.default.dim(" key: " + s.key)}`);
|
|
11248
11672
|
}
|
|
11249
11673
|
loginFirst("view or change these settings");
|
|
11250
11674
|
} else {
|
|
11251
11675
|
const f = pick2.f;
|
|
11252
11676
|
console.log(`
|
|
11253
|
-
${
|
|
11677
|
+
${import_chalk51.default.bold(f.label)} \u2014 ${f.desc}`);
|
|
11254
11678
|
loginFirst(`use ${f.label.toLowerCase()}`);
|
|
11255
11679
|
}
|
|
11256
11680
|
}
|
|
11257
11681
|
}
|
|
11258
11682
|
async function navigator(proj2, cfg, opts) {
|
|
11259
11683
|
const { default: inquirer3 } = await import("inquirer");
|
|
11260
|
-
console.log(
|
|
11684
|
+
console.log(import_chalk51.default.bold(`
|
|
11261
11685
|
${proj2.projectName} v${proj2.apiVersion} \u2014 configuration
|
|
11262
11686
|
`));
|
|
11263
|
-
console.log(
|
|
11687
|
+
console.log(import_chalk51.default.dim("Tip: every change is one API call \u2014 add --verbose to see the curl equivalent.\n"));
|
|
11264
11688
|
let blob = cfg;
|
|
11265
11689
|
for (; ; ) {
|
|
11266
11690
|
const { pick: pick2 } = await inquirer3.prompt([{
|
|
@@ -11269,10 +11693,10 @@ ${proj2.projectName} v${proj2.apiVersion} \u2014 configuration
|
|
|
11269
11693
|
message: "Where to?",
|
|
11270
11694
|
pageSize: 20,
|
|
11271
11695
|
choices: [
|
|
11272
|
-
new inquirer3.Separator(
|
|
11696
|
+
new inquirer3.Separator(import_chalk51.default.bold("\u2014 Settings \u2014")),
|
|
11273
11697
|
...SETTING_GROUPS.map((g) => ({ name: g, value: { kind: "settings", g } })),
|
|
11274
|
-
new inquirer3.Separator(
|
|
11275
|
-
...FEATURES.map((f) => ({ name: `${f.label} ${
|
|
11698
|
+
new inquirer3.Separator(import_chalk51.default.bold("\u2014 Features \u2014")),
|
|
11699
|
+
...FEATURES.map((f) => ({ name: `${f.label} ${import_chalk51.default.dim(f.desc)}`, value: { kind: f.go } })),
|
|
11276
11700
|
new inquirer3.Separator(),
|
|
11277
11701
|
{ name: "Show all settings", value: { kind: "list" } },
|
|
11278
11702
|
{ name: "Exit", value: { kind: "exit" } }
|
|
@@ -11312,7 +11736,7 @@ ${proj2.projectName} v${proj2.apiVersion} \u2014 configuration
|
|
|
11312
11736
|
}
|
|
11313
11737
|
}
|
|
11314
11738
|
} catch (err) {
|
|
11315
|
-
console.error(
|
|
11739
|
+
console.error(import_chalk51.default.red(` ${err instanceof Error ? err.message : String(err)}`));
|
|
11316
11740
|
}
|
|
11317
11741
|
}
|
|
11318
11742
|
}
|
|
@@ -11326,7 +11750,7 @@ async function settingsGroup(proj2, cfg, group2) {
|
|
|
11326
11750
|
message: group2 + ":",
|
|
11327
11751
|
pageSize: 16,
|
|
11328
11752
|
choices: [
|
|
11329
|
-
...items.map((s2) => ({ name: `${s2.label.padEnd(30)} ${show(readSetting(s2, cfg))} ${
|
|
11753
|
+
...items.map((s2) => ({ name: `${s2.label.padEnd(30)} ${show(readSetting(s2, cfg))} ${import_chalk51.default.dim(s2.desc)}`, value: s2 })),
|
|
11330
11754
|
new inquirer3.Separator(),
|
|
11331
11755
|
{ name: "\u2190 Back", value: null }
|
|
11332
11756
|
]
|
|
@@ -11344,7 +11768,7 @@ async function settingsGroup(proj2, cfg, group2) {
|
|
|
11344
11768
|
} else if (s.type === "number") {
|
|
11345
11769
|
const { v } = await inquirer3.prompt([{ type: "input", name: "v", message: `${s.label} (number):`, default: readSetting(s, cfg) }]);
|
|
11346
11770
|
if (v === "" || Number.isNaN(Number(v))) {
|
|
11347
|
-
console.log(
|
|
11771
|
+
console.log(import_chalk51.default.yellow(" Not a number \u2014 unchanged."));
|
|
11348
11772
|
continue;
|
|
11349
11773
|
}
|
|
11350
11774
|
value = Number(v);
|
|
@@ -11425,7 +11849,7 @@ async function buildCondition(phase) {
|
|
|
11425
11849
|
const items = [];
|
|
11426
11850
|
for (; ; ) {
|
|
11427
11851
|
const a = await inquirer3.prompt([
|
|
11428
|
-
{ type: "input", name: "source", message: `Condition field ${
|
|
11852
|
+
{ type: "input", name: "source", message: `Condition field ${import_chalk51.default.dim(srcHint)}:`, validate: (s) => !!s || "required" },
|
|
11429
11853
|
{ type: "list", name: "operator", message: "Operator:", choices: [
|
|
11430
11854
|
"eq",
|
|
11431
11855
|
"neq",
|
|
@@ -11456,7 +11880,7 @@ async function buildCondition(phase) {
|
|
|
11456
11880
|
function showCondition(cond) {
|
|
11457
11881
|
if (!Array.isArray(cond) || !cond.length) return "";
|
|
11458
11882
|
const s = cond.map((c) => `${c.source} ${c.operator}${c.value !== void 0 ? ` "${c.value}"` : ""}${c.logicOp ? ` ${c.logicOp}` : ""}`).join(" ");
|
|
11459
|
-
return
|
|
11883
|
+
return import_chalk51.default.dim(` when ${s}`);
|
|
11460
11884
|
}
|
|
11461
11885
|
async function transformsMenu(proj2) {
|
|
11462
11886
|
const { default: inquirer3 } = await import("inquirer");
|
|
@@ -11465,12 +11889,12 @@ async function transformsMenu(proj2) {
|
|
|
11465
11889
|
const out = await admin({ method: "GET", path: base2, summary: "List transform rules" });
|
|
11466
11890
|
const rules = out?.rules ?? [];
|
|
11467
11891
|
console.log();
|
|
11468
|
-
if (!rules.length) console.log(
|
|
11892
|
+
if (!rules.length) console.log(import_chalk51.default.dim(" No transform rules yet."));
|
|
11469
11893
|
for (const r of rules) {
|
|
11470
11894
|
const a = r.action ?? {};
|
|
11471
11895
|
const fns = [...a.source_fns ?? [], ...a.dest_fns ?? []].map((f) => f.fn);
|
|
11472
|
-
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 ?
|
|
11473
|
-
console.log(` ${r.enabled ?
|
|
11896
|
+
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_chalk51.default.dim(` via ${fns.join("\u2192")}`) : ""}`;
|
|
11897
|
+
console.log(` ${r.enabled ? import_chalk51.default.green("\u25CF") : import_chalk51.default.dim("\u25CB")} ${import_chalk51.default.bold(r.name)} ${import_chalk51.default.dim(`[${r.phase ?? "request"}]`)} ${what}${showCondition(r.condition)}`);
|
|
11474
11898
|
}
|
|
11475
11899
|
const { act } = await inquirer3.prompt([{
|
|
11476
11900
|
type: "list",
|
|
@@ -11482,7 +11906,7 @@ async function transformsMenu(proj2) {
|
|
|
11482
11906
|
{ name: "Enable/disable a rule", value: "toggle" },
|
|
11483
11907
|
{ name: "Delete a rule", value: "delete" }
|
|
11484
11908
|
] : [],
|
|
11485
|
-
{ name:
|
|
11909
|
+
{ name: import_chalk51.default.dim("Add from raw JSON (grouped conditions, lookup tables, \u2026)"), value: "raw" },
|
|
11486
11910
|
{ name: "\u2190 Back", value: "back" }
|
|
11487
11911
|
]
|
|
11488
11912
|
}]);
|
|
@@ -11495,11 +11919,11 @@ async function transformsMenu(proj2) {
|
|
|
11495
11919
|
}]);
|
|
11496
11920
|
const body = parseValue(raw);
|
|
11497
11921
|
if (!body || typeof body !== "object" || Array.isArray(body)) {
|
|
11498
|
-
console.log(
|
|
11922
|
+
console.log(import_chalk51.default.yellow(" Not a JSON object \u2014 skipped."));
|
|
11499
11923
|
continue;
|
|
11500
11924
|
}
|
|
11501
11925
|
await admin({ method: "POST", path: base2, body, summary: "Create transform rule (raw JSON)" });
|
|
11502
|
-
console.log(
|
|
11926
|
+
console.log(import_chalk51.default.green(" Rule created."));
|
|
11503
11927
|
continue;
|
|
11504
11928
|
}
|
|
11505
11929
|
if (act === "add") {
|
|
@@ -11515,7 +11939,7 @@ async function transformsMenu(proj2) {
|
|
|
11515
11939
|
{ name: "Remove a field", value: "remove" }
|
|
11516
11940
|
] }
|
|
11517
11941
|
]);
|
|
11518
|
-
const fieldHint =
|
|
11942
|
+
const fieldHint = import_chalk51.default.dim("(e.g. header:x-api-version, param:limit, bodyvar:user.id)");
|
|
11519
11943
|
let action2;
|
|
11520
11944
|
if (ans.kind === "hardcode") {
|
|
11521
11945
|
const a = await inquirer3.prompt([
|
|
@@ -11562,16 +11986,16 @@ async function transformsMenu(proj2) {
|
|
|
11562
11986
|
type: "list",
|
|
11563
11987
|
name: "rule",
|
|
11564
11988
|
message: act === "toggle" ? "Which rule?" : "Delete which rule?",
|
|
11565
|
-
choices: [...rules.map((r) => ({ name: `${r.name} ${
|
|
11989
|
+
choices: [...rules.map((r) => ({ name: `${r.name} ${import_chalk51.default.dim(`[${r.phase ?? "request"}]`)}`, value: r })), { name: "\u2190 Back", value: null }]
|
|
11566
11990
|
}]);
|
|
11567
11991
|
if (!rule) continue;
|
|
11568
11992
|
if (act === "toggle") {
|
|
11569
11993
|
const flipped = { ...rule, enabled: rule.enabled === false };
|
|
11570
11994
|
await admin({ method: "PUT", path: `${base2}/${rule.id}`, body: flipped, summary: `${flipped.enabled ? "Enable" : "Disable"} transform "${rule.name}"` });
|
|
11571
|
-
console.log(
|
|
11995
|
+
console.log(import_chalk51.default.green(` ${rule.name} \u2192 ${flipped.enabled ? "enabled" : "disabled"}`));
|
|
11572
11996
|
} else {
|
|
11573
11997
|
await admin({ method: "DELETE", path: `${base2}/${rule.id}`, summary: `Delete transform "${rule.name}"` });
|
|
11574
|
-
console.log(
|
|
11998
|
+
console.log(import_chalk51.default.green(` ${rule.name} deleted.`));
|
|
11575
11999
|
}
|
|
11576
12000
|
}
|
|
11577
12001
|
}
|
|
@@ -11583,9 +12007,9 @@ async function mappingsMenu(proj2) {
|
|
|
11583
12007
|
const out = await admin({ method: "GET", path: base2, summary: "List mapping tables" });
|
|
11584
12008
|
const tables = out?.mappings ?? out?.tables ?? [];
|
|
11585
12009
|
console.log();
|
|
11586
|
-
if (!tables.length) console.log(
|
|
12010
|
+
if (!tables.length) console.log(import_chalk51.default.dim(" No mapping tables yet."));
|
|
11587
12011
|
for (const t of tables) {
|
|
11588
|
-
console.log(` ${
|
|
12012
|
+
console.log(` ${import_chalk51.default.bold(t.name)} ${import_chalk51.default.dim(`${t.entries?.length ?? "?"} entries${t.hide_map_values ? ", hidden" : ""}${t.encrypt_values ? ", encrypted" : ""}`)}`);
|
|
11589
12013
|
}
|
|
11590
12014
|
const { act } = await inquirer3.prompt([{
|
|
11591
12015
|
type: "list",
|
|
@@ -11605,11 +12029,11 @@ async function mappingsMenu(proj2) {
|
|
|
11605
12029
|
]);
|
|
11606
12030
|
const entries2 = parseValue(a.entries);
|
|
11607
12031
|
if (!Array.isArray(entries2)) {
|
|
11608
|
-
console.log(
|
|
12032
|
+
console.log(import_chalk51.default.yellow(" Entries must be a JSON array \u2014 not created."));
|
|
11609
12033
|
continue;
|
|
11610
12034
|
}
|
|
11611
12035
|
await admin({ method: "POST", path: base2, body: { name: a.name, entries: entries2 }, summary: `Create mapping table "${a.name}"` });
|
|
11612
|
-
console.log(
|
|
12036
|
+
console.log(import_chalk51.default.green(` Table "${a.name}" created.`));
|
|
11613
12037
|
} else {
|
|
11614
12038
|
const { table } = await inquirer3.prompt([{
|
|
11615
12039
|
type: "list",
|
|
@@ -11619,7 +12043,7 @@ async function mappingsMenu(proj2) {
|
|
|
11619
12043
|
}]);
|
|
11620
12044
|
if (!table) continue;
|
|
11621
12045
|
await admin({ method: "DELETE", path: `${base2}/${table.id}`, summary: `Delete mapping table "${table.name}"` });
|
|
11622
|
-
console.log(
|
|
12046
|
+
console.log(import_chalk51.default.green(` ${table.name} deleted.`));
|
|
11623
12047
|
}
|
|
11624
12048
|
}
|
|
11625
12049
|
}
|
|
@@ -11630,14 +12054,14 @@ async function tenantsMenu(proj2, opts) {
|
|
|
11630
12054
|
const out = await admin({ method: "GET", path: base2, summary: "List attached tenants" });
|
|
11631
12055
|
const tenants = out?.tenants ?? [];
|
|
11632
12056
|
console.log();
|
|
11633
|
-
if (!tenants.length) console.log(
|
|
11634
|
-
for (const t of tenants) console.log(` ${
|
|
12057
|
+
if (!tenants.length) console.log(import_chalk51.default.dim(" No tenants attached (consumers use the default tenant)."));
|
|
12058
|
+
for (const t of tenants) console.log(` ${import_chalk51.default.bold(t.tenant_name ?? t.name)} ${import_chalk51.default.dim(t.display_name ?? "")}`);
|
|
11635
12059
|
const { act } = await inquirer3.prompt([{
|
|
11636
12060
|
type: "list",
|
|
11637
12061
|
name: "act",
|
|
11638
12062
|
message: "Tenants:",
|
|
11639
12063
|
choices: [
|
|
11640
|
-
{ name: `Manage a tenant\u2026 ${
|
|
12064
|
+
{ name: `Manage a tenant\u2026 ${import_chalk51.default.dim("settings, login app clients, providers, issuers \u2014 affects EVERY proxy the tenant serves")}`, value: "manage" },
|
|
11641
12065
|
{ name: "Attach a tenant to this project", value: "attach" },
|
|
11642
12066
|
...tenants.length ? [{ name: "Detach a tenant from this project", value: "detach" }] : [],
|
|
11643
12067
|
{ name: "\u2190 Back", value: "back" }
|
|
@@ -11660,7 +12084,7 @@ async function tenantsMenu(proj2, opts) {
|
|
|
11660
12084
|
}]);
|
|
11661
12085
|
if (!t) continue;
|
|
11662
12086
|
await admin({ method: "DELETE", path: `${base2}/${encodeURIComponent(t.tenant_name ?? t.name)}`, summary: `Detach tenant ${t.tenant_name ?? t.name}` });
|
|
11663
|
-
console.log(
|
|
12087
|
+
console.log(import_chalk51.default.green(` Detached ${t.tenant_name ?? t.name}.`));
|
|
11664
12088
|
}
|
|
11665
12089
|
}
|
|
11666
12090
|
}
|
|
@@ -11703,7 +12127,7 @@ async function specMenu(proj2, opts) {
|
|
|
11703
12127
|
choices: [
|
|
11704
12128
|
{ name: "Print the stored spec", value: "get" },
|
|
11705
12129
|
{ name: "Refresh the spec from its source", value: "refresh" },
|
|
11706
|
-
{ name:
|
|
12130
|
+
{ name: import_chalk51.default.dim("Build the spec by chatting over real traffic \u2192 agent"), value: "agent" },
|
|
11707
12131
|
{ name: "\u2190 Back", value: "back" }
|
|
11708
12132
|
]
|
|
11709
12133
|
}]);
|
|
@@ -11711,7 +12135,7 @@ async function specMenu(proj2, opts) {
|
|
|
11711
12135
|
if (act === "get") await runSpecGet(proj2.projectName, { team: opts.team, apiversion: proj2.apiVersion });
|
|
11712
12136
|
else if (act === "refresh") {
|
|
11713
12137
|
await admin({ method: "POST", path: `/projects/${proj2.projectId}/${proj2.apiVersion}/refresh-spec`, summary: "Refresh spec from source" });
|
|
11714
|
-
console.log(
|
|
12138
|
+
console.log(import_chalk51.default.green(" Spec refresh triggered."));
|
|
11715
12139
|
} else await runOpenapi(proj2.projectName, proj2.apiVersion);
|
|
11716
12140
|
}
|
|
11717
12141
|
async function agentsMenu(proj2, opts) {
|
|
@@ -11736,12 +12160,13 @@ async function agentsMenu(proj2, opts) {
|
|
|
11736
12160
|
}
|
|
11737
12161
|
|
|
11738
12162
|
// src/commands/group.ts
|
|
11739
|
-
var
|
|
12163
|
+
var import_chalk52 = __toESM(require("chalk"));
|
|
11740
12164
|
var import_ora24 = __toESM(require("ora"));
|
|
11741
12165
|
init_auth();
|
|
11742
12166
|
init_admin();
|
|
11743
12167
|
init_resolve();
|
|
11744
12168
|
init_types();
|
|
12169
|
+
init_paging();
|
|
11745
12170
|
var DASHBOARD_BASE7 = process.env.APIBLAZE_DASHBOARD_BASE || "https://dashboard.apiblaze.com";
|
|
11746
12171
|
var IAM_BASE = process.env.APIBLAZE_IAM_BASE || "https://iam.apiblaze.com";
|
|
11747
12172
|
async function resolveTransport(opts) {
|
|
@@ -11750,8 +12175,11 @@ async function resolveTransport(opts) {
|
|
|
11750
12175
|
const { teamId } = await resolveTeam(void 0);
|
|
11751
12176
|
let tenant2 = (opts.tenant ?? "").trim().toLowerCase();
|
|
11752
12177
|
if (!tenant2) {
|
|
11753
|
-
const
|
|
11754
|
-
|
|
12178
|
+
const walkedTenants = await pageAll(
|
|
12179
|
+
(cursor) => admin({ method: "GET", path: withPage(`/teams/${encodeURIComponent(teamId)}/tenants`, cursor), summary: "List tenants" }),
|
|
12180
|
+
(b) => b?.tenants
|
|
12181
|
+
);
|
|
12182
|
+
const tenants = walkedTenants.items.map((t) => String(t.tenant_name ?? t.name ?? t)).filter((t) => !isOpTenant(t));
|
|
11755
12183
|
if (tenants.length === 0) throw new Error("No tenants in your team yet. Create a proxy first, or pass --tenant.");
|
|
11756
12184
|
if (tenants.length === 1) tenant2 = tenants[0];
|
|
11757
12185
|
else {
|
|
@@ -11812,8 +12240,11 @@ async function groupByName(t, name) {
|
|
|
11812
12240
|
return g;
|
|
11813
12241
|
}
|
|
11814
12242
|
async function listUsers(t) {
|
|
11815
|
-
const
|
|
11816
|
-
|
|
12243
|
+
const walked = await pageAll(
|
|
12244
|
+
(cursor) => t.call(withPage("/v1/admin/users", cursor)),
|
|
12245
|
+
(b) => b?.users
|
|
12246
|
+
);
|
|
12247
|
+
return walked.items;
|
|
11817
12248
|
}
|
|
11818
12249
|
async function userByRef(t, ref) {
|
|
11819
12250
|
const users = await listUsers(t);
|
|
@@ -11830,7 +12261,7 @@ async function userByRefOrCreate(t, ref) {
|
|
|
11830
12261
|
if (!ref.includes("@")) throw e;
|
|
11831
12262
|
const created = await t.call("/v1/admin/users", "POST", { email: ref.trim().toLowerCase() });
|
|
11832
12263
|
if (!created?.abz_sub) throw e;
|
|
11833
|
-
console.log(
|
|
12264
|
+
console.log(import_chalk52.default.dim(` ${ref} wasn't a user yet \u2014 created.`));
|
|
11834
12265
|
return { abz_sub: created.abz_sub, email: created.email ?? ref, display_name: null };
|
|
11835
12266
|
}
|
|
11836
12267
|
}
|
|
@@ -11838,11 +12269,11 @@ async function runGroupList(opts) {
|
|
|
11838
12269
|
const t = await resolveTransport(opts);
|
|
11839
12270
|
const groups = await listGroups(t);
|
|
11840
12271
|
if (opts.json) return void console.log(JSON.stringify(groups));
|
|
11841
|
-
if (!groups.length) return void console.log(
|
|
11842
|
-
console.log(
|
|
12272
|
+
if (!groups.length) return void console.log(import_chalk52.default.yellow(`No groups in ${t.tenant}. Create one: apiblaze group create <name>`));
|
|
12273
|
+
console.log(import_chalk52.default.dim(`Tenant ${t.tenant}:`));
|
|
11843
12274
|
for (const g of groups) {
|
|
11844
|
-
const sealed = g.name === "apiblaze_admins" ?
|
|
11845
|
-
console.log(` ${
|
|
12275
|
+
const sealed = g.name === "apiblaze_admins" ? import_chalk52.default.dim(" (admins \u2014 managed by allowlist)") : "";
|
|
12276
|
+
console.log(` ${import_chalk52.default.bold(g.name)} ${import_chalk52.default.dim(`${g.member_count ?? 0} member(s)`)}${sealed}`);
|
|
11846
12277
|
}
|
|
11847
12278
|
}
|
|
11848
12279
|
async function runGroupCreate(name, opts) {
|
|
@@ -11870,7 +12301,7 @@ async function runGroupCreate(name, opts) {
|
|
|
11870
12301
|
const spinner = (0, import_ora24.default)(`Creating group ${name}...`).start();
|
|
11871
12302
|
try {
|
|
11872
12303
|
await t.call("/v1/admin/groups", "POST", { name, admins: [adminSub] });
|
|
11873
|
-
spinner.succeed(`Group ${
|
|
12304
|
+
spinner.succeed(`Group ${import_chalk52.default.bold(name)} created in ${t.tenant}.`);
|
|
11874
12305
|
} catch (e) {
|
|
11875
12306
|
spinner.fail("Create failed.");
|
|
11876
12307
|
throw e;
|
|
@@ -11882,7 +12313,7 @@ async function runGroupDelete(name, opts) {
|
|
|
11882
12313
|
const spinner = (0, import_ora24.default)(`Deleting group ${name}...`).start();
|
|
11883
12314
|
try {
|
|
11884
12315
|
await t.call(`/v1/admin/groups/${encodeURIComponent(g.id)}`, "DELETE");
|
|
11885
|
-
spinner.succeed(`Group ${
|
|
12316
|
+
spinner.succeed(`Group ${import_chalk52.default.bold(name)} deleted.`);
|
|
11886
12317
|
} catch (e) {
|
|
11887
12318
|
spinner.fail("Delete failed.");
|
|
11888
12319
|
throw e;
|
|
@@ -11893,13 +12324,19 @@ async function runGroupMembers(name, opts) {
|
|
|
11893
12324
|
const g = await groupByName(t, name);
|
|
11894
12325
|
const detail = await t.call(`/v1/admin/groups/${encodeURIComponent(g.id)}`);
|
|
11895
12326
|
const subs = await t.call(`/v1/admin/groups/${encodeURIComponent(g.id)}/subgroups`);
|
|
11896
|
-
|
|
11897
|
-
|
|
11898
|
-
|
|
11899
|
-
|
|
11900
|
-
if (
|
|
12327
|
+
const walkedMembers = await pageAll(
|
|
12328
|
+
(cursor) => t.call(withPage(`/v1/admin/groups/${encodeURIComponent(g.id)}`, cursor)),
|
|
12329
|
+
(b) => b?.members
|
|
12330
|
+
);
|
|
12331
|
+
if (opts.json) {
|
|
12332
|
+
return void console.log(JSON.stringify({ ...detail, members: walkedMembers.items, subgroups: subs?.subgroups ?? [] }));
|
|
12333
|
+
}
|
|
12334
|
+
const members = walkedMembers.items;
|
|
12335
|
+
console.log(import_chalk52.default.bold(`${g.name}`) + import_chalk52.default.dim(` (${t.tenant})`));
|
|
12336
|
+
for (const m of members) console.log(` ${label2(m)} ${m.role === "admin" ? import_chalk52.default.cyan("group-admin") : import_chalk52.default.dim("member")}`);
|
|
12337
|
+
if (!members.length) console.log(import_chalk52.default.dim(" no members"));
|
|
11901
12338
|
const sg = subs?.subgroups ?? [];
|
|
11902
|
-
if (sg.length) console.log(
|
|
12339
|
+
if (sg.length) console.log(import_chalk52.default.dim(` subgroups: ${sg.map((s) => s.name).join(", ")}`));
|
|
11903
12340
|
}
|
|
11904
12341
|
async function runGroupAddUser(userRef, groupName, opts) {
|
|
11905
12342
|
const t = await resolveTransport(opts);
|
|
@@ -11909,7 +12346,7 @@ async function runGroupAddUser(userRef, groupName, opts) {
|
|
|
11909
12346
|
const spinner = (0, import_ora24.default)(`Adding ${label2(u)} to ${g.name}...`).start();
|
|
11910
12347
|
try {
|
|
11911
12348
|
await t.call(`/v1/admin/groups/${encodeURIComponent(g.id)}/members`, "POST", { abz_sub: u.abz_sub, role });
|
|
11912
|
-
spinner.succeed(`${
|
|
12349
|
+
spinner.succeed(`${import_chalk52.default.bold(label2(u))} added to ${import_chalk52.default.bold(g.name)} as ${role}.`);
|
|
11913
12350
|
} catch (e) {
|
|
11914
12351
|
spinner.fail("Add failed.");
|
|
11915
12352
|
throw e;
|
|
@@ -11921,7 +12358,7 @@ async function runGroupRemoveUser(userRef, groupName, opts) {
|
|
|
11921
12358
|
const spinner = (0, import_ora24.default)(`Removing ${label2(u)} from ${g.name}...`).start();
|
|
11922
12359
|
try {
|
|
11923
12360
|
await t.call(`/v1/admin/groups/${encodeURIComponent(g.id)}/members/_?abz_sub=${encodeURIComponent(u.abz_sub)}`, "DELETE");
|
|
11924
|
-
spinner.succeed(`${
|
|
12361
|
+
spinner.succeed(`${import_chalk52.default.bold(label2(u))} removed from ${import_chalk52.default.bold(g.name)}.`);
|
|
11925
12362
|
} catch (e) {
|
|
11926
12363
|
spinner.fail("Remove failed.");
|
|
11927
12364
|
throw e;
|
|
@@ -11933,7 +12370,7 @@ async function runGroupAddGroup(childName, parentName, opts) {
|
|
|
11933
12370
|
const spinner = (0, import_ora24.default)(`Nesting ${child.name} under ${parent.name}...`).start();
|
|
11934
12371
|
try {
|
|
11935
12372
|
await t.call(`/v1/admin/groups/${encodeURIComponent(parent.id)}/subgroups`, "POST", { child_group_id: child.id });
|
|
11936
|
-
spinner.succeed(`${
|
|
12373
|
+
spinner.succeed(`${import_chalk52.default.bold(child.name)} is now a subgroup of ${import_chalk52.default.bold(parent.name)}.`);
|
|
11937
12374
|
} catch (e) {
|
|
11938
12375
|
spinner.fail("Nest failed.");
|
|
11939
12376
|
throw e;
|
|
@@ -11945,7 +12382,7 @@ async function runGroupRemoveGroup(childName, parentName, opts) {
|
|
|
11945
12382
|
const spinner = (0, import_ora24.default)(`Un-nesting ${child.name} from ${parent.name}...`).start();
|
|
11946
12383
|
try {
|
|
11947
12384
|
await t.call(`/v1/admin/groups/${encodeURIComponent(parent.id)}/subgroups/${encodeURIComponent(child.id)}`, "DELETE");
|
|
11948
|
-
spinner.succeed(`${
|
|
12385
|
+
spinner.succeed(`${import_chalk52.default.bold(child.name)} un-nested from ${import_chalk52.default.bold(parent.name)}.`);
|
|
11949
12386
|
} catch (e) {
|
|
11950
12387
|
spinner.fail("Un-nest failed.");
|
|
11951
12388
|
throw e;
|
|
@@ -11956,13 +12393,13 @@ async function runGroupUsers(opts) {
|
|
|
11956
12393
|
const users = await listUsers(t);
|
|
11957
12394
|
const observed = await t.call("/v1/admin/observed-identities").catch(() => ({ identities: [] }));
|
|
11958
12395
|
if (opts.json) return void console.log(JSON.stringify({ users, observed: observed?.identities ?? [] }));
|
|
11959
|
-
console.log(
|
|
11960
|
-
for (const u of users) console.log(` ${label2(u)} ${
|
|
11961
|
-
if (!users.length) console.log(
|
|
12396
|
+
console.log(import_chalk52.default.dim(`Tenant ${t.tenant} \u2014 user directory:`));
|
|
12397
|
+
for (const u of users) console.log(` ${label2(u)} ${import_chalk52.default.dim(u.abz_sub)}`);
|
|
12398
|
+
if (!users.length) console.log(import_chalk52.default.dim(" no users yet"));
|
|
11962
12399
|
const fresh = (observed?.identities ?? []).filter((o) => !o.provisioned);
|
|
11963
12400
|
if (fresh.length) {
|
|
11964
|
-
console.log(
|
|
11965
|
-
for (const o of fresh) console.log(` ${o.email || o.abz_sub} ${
|
|
12401
|
+
console.log(import_chalk52.default.dim("Seen in traffic (not yet users):"));
|
|
12402
|
+
for (const o of fresh) console.log(` ${o.email || o.abz_sub} ${import_chalk52.default.dim(o.source_label ?? "")}`);
|
|
11966
12403
|
}
|
|
11967
12404
|
}
|
|
11968
12405
|
|
|
@@ -11971,7 +12408,7 @@ init_iam();
|
|
|
11971
12408
|
init_admins();
|
|
11972
12409
|
|
|
11973
12410
|
// src/commands/preapprove.ts
|
|
11974
|
-
var
|
|
12411
|
+
var import_chalk53 = __toESM(require("chalk"));
|
|
11975
12412
|
var import_ora25 = __toESM(require("ora"));
|
|
11976
12413
|
async function runPreapprove(who, opts) {
|
|
11977
12414
|
const tenant2 = await resolveProducerTenant(opts.tenant);
|
|
@@ -11979,14 +12416,14 @@ async function runPreapprove(who, opts) {
|
|
|
11979
12416
|
const rules = await listPreapprovalRules(tenant2);
|
|
11980
12417
|
if (opts.json) return void console.log(JSON.stringify(rules, null, 2));
|
|
11981
12418
|
if (!rules.length) {
|
|
11982
|
-
console.log(
|
|
11983
|
-
console.log(
|
|
12419
|
+
console.log(import_chalk53.default.dim(`No pre-approval rules in ${tenant2}. Anyone who signs in can access (unless access is restricted).`));
|
|
12420
|
+
console.log(import_chalk53.default.dim(`Add one: apiblaze preapprove someone@acme.com`));
|
|
11984
12421
|
return;
|
|
11985
12422
|
}
|
|
11986
|
-
console.log(
|
|
12423
|
+
console.log(import_chalk53.default.dim(`Pre-approved for ${import_chalk53.default.bold(tenant2)}:`));
|
|
11987
12424
|
for (const r of rules) {
|
|
11988
|
-
const tag = r.kind === "domain" ?
|
|
11989
|
-
const grp = r.groups?.length ?
|
|
12425
|
+
const tag = r.kind === "domain" ? import_chalk53.default.cyan("@" + r.value + " (whole domain)") : r.value;
|
|
12426
|
+
const grp = r.groups?.length ? import_chalk53.default.dim(` \u2192 groups: ${r.groups.join(", ")}`) : "";
|
|
11990
12427
|
console.log(` ${tag}${grp}`);
|
|
11991
12428
|
}
|
|
11992
12429
|
return;
|
|
@@ -11997,17 +12434,17 @@ async function runPreapprove(who, opts) {
|
|
|
11997
12434
|
if (opts.remove) {
|
|
11998
12435
|
const spinner2 = (0, import_ora25.default)(`Removing ${who} from ${tenant2}\u2026`).start();
|
|
11999
12436
|
const { removed, value } = await removePreapprovalRule(tenant2, who);
|
|
12000
|
-
if (removed) spinner2.succeed(`${
|
|
12001
|
-
else spinner2.warn(`No pre-approval rule for ${
|
|
12437
|
+
if (removed) spinner2.succeed(`${import_chalk53.default.bold(value)} is no longer pre-approved for ${tenant2}.`);
|
|
12438
|
+
else spinner2.warn(`No pre-approval rule for ${import_chalk53.default.bold(value)} in ${tenant2} \u2014 nothing to remove.`);
|
|
12002
12439
|
return;
|
|
12003
12440
|
}
|
|
12004
12441
|
const spinner = (0, import_ora25.default)(`Pre-approving ${who} for ${tenant2}\u2026`).start();
|
|
12005
12442
|
try {
|
|
12006
12443
|
const { kind, value } = await addPreapprovalRule(tenant2, who, opts.group);
|
|
12007
12444
|
const what = kind === "domain" ? `Anyone @${value}` : value;
|
|
12008
|
-
spinner.succeed(`${
|
|
12009
|
-
if (opts.group?.length) console.log(
|
|
12010
|
-
console.log(
|
|
12445
|
+
spinner.succeed(`${import_chalk53.default.bold(what)} can now sign in to ${import_chalk53.default.bold(tenant2)}.`);
|
|
12446
|
+
if (opts.group?.length) console.log(import_chalk53.default.dim(` They'll join group(s) ${opts.group.join(", ")} on first sign-in.`));
|
|
12447
|
+
console.log(import_chalk53.default.dim(` See the full list: apiblaze preapprove --list`));
|
|
12011
12448
|
} catch (err) {
|
|
12012
12449
|
spinner.fail("Could not add the rule.");
|
|
12013
12450
|
throw err;
|
|
@@ -12015,11 +12452,12 @@ async function runPreapprove(who, opts) {
|
|
|
12015
12452
|
}
|
|
12016
12453
|
|
|
12017
12454
|
// src/commands/consumer.ts
|
|
12018
|
-
var
|
|
12455
|
+
var import_chalk54 = __toESM(require("chalk"));
|
|
12019
12456
|
var import_ora26 = __toESM(require("ora"));
|
|
12020
12457
|
init_admin();
|
|
12021
12458
|
init_resolve();
|
|
12022
12459
|
init_caller();
|
|
12460
|
+
init_paging();
|
|
12023
12461
|
var DEFAULT_SCOPE = "openid email profile offline_access";
|
|
12024
12462
|
async function consumerFetch(creds, suffix, init) {
|
|
12025
12463
|
const fresh = await validConsumerToken(creds) ?? creds;
|
|
@@ -12037,7 +12475,7 @@ async function consumerFetch(creds, suffix, init) {
|
|
|
12037
12475
|
function requireConsumer2() {
|
|
12038
12476
|
const c = loadConsumer();
|
|
12039
12477
|
if (!c) {
|
|
12040
|
-
console.error(
|
|
12478
|
+
console.error(import_chalk54.default.red("Not logged in as a consumer. Run `apiblaze consumer login` first."));
|
|
12041
12479
|
process.exit(1);
|
|
12042
12480
|
}
|
|
12043
12481
|
return c;
|
|
@@ -12048,7 +12486,7 @@ async function runConsumerLogin(opts) {
|
|
|
12048
12486
|
let clientId = opts.client;
|
|
12049
12487
|
if (clientId) {
|
|
12050
12488
|
if (!tenant2) {
|
|
12051
|
-
console.error(
|
|
12489
|
+
console.error(import_chalk54.default.red("When using --client, also pass --tenant <slug> (it sets which portal/keys host to use)."));
|
|
12052
12490
|
process.exit(1);
|
|
12053
12491
|
}
|
|
12054
12492
|
} else {
|
|
@@ -12066,18 +12504,18 @@ async function runConsumerLogin(opts) {
|
|
|
12066
12504
|
const usable = (Array.isArray(clients) ? clients : []).filter((c) => c && (c.client_id || c.clientId));
|
|
12067
12505
|
const pick2 = usable.find((c) => c.is_default || c.default) ?? usable.find((c) => c.verified !== false) ?? usable[0];
|
|
12068
12506
|
if (!pick2) {
|
|
12069
|
-
console.error(
|
|
12507
|
+
console.error(import_chalk54.default.red(`Tenant "${tenant2}" has no login app configured. Set one up in the dashboard (or \`apiblaze create\` with auth).`));
|
|
12070
12508
|
process.exit(1);
|
|
12071
12509
|
}
|
|
12072
12510
|
clientId = pick2.client_id ?? pick2.clientId;
|
|
12073
12511
|
}
|
|
12074
|
-
console.log(`${
|
|
12512
|
+
console.log(`${import_chalk54.default.cyan("\u2192")} Logging in to ${import_chalk54.default.bold(tenant2)} as a consumer...`);
|
|
12075
12513
|
const result = await deviceLogin(clientId, DEFAULT_SCOPE, ({ verificationUri, userCode }) => {
|
|
12076
12514
|
console.log(`
|
|
12077
|
-
Open: ${
|
|
12078
|
-
console.log(` Code: ${
|
|
12515
|
+
Open: ${import_chalk54.default.underline(verificationUri)}`);
|
|
12516
|
+
console.log(` Code: ${import_chalk54.default.bold(userCode)}
|
|
12079
12517
|
`);
|
|
12080
|
-
console.log(
|
|
12518
|
+
console.log(import_chalk54.default.dim(" (opening your browser\u2026 waiting for you to finish)"));
|
|
12081
12519
|
});
|
|
12082
12520
|
const claims = result.idToken && decodeJwt2(result.idToken) || (decodeJwt2(result.accessToken) ?? {});
|
|
12083
12521
|
const creds = {
|
|
@@ -12092,7 +12530,7 @@ async function runConsumerLogin(opts) {
|
|
|
12092
12530
|
obtainedAt: Date.now()
|
|
12093
12531
|
};
|
|
12094
12532
|
saveConsumer(creds);
|
|
12095
|
-
console.log(
|
|
12533
|
+
console.log(import_chalk54.default.green(`\u2714 Logged in as consumer${creds.email ? ` ${creds.email}` : ""} on ${tenant2}.`));
|
|
12096
12534
|
}
|
|
12097
12535
|
async function runConsumerTokens(opts) {
|
|
12098
12536
|
const creds = requireConsumer2();
|
|
@@ -12105,46 +12543,66 @@ async function runConsumerTokens(opts) {
|
|
|
12105
12543
|
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));
|
|
12106
12544
|
return;
|
|
12107
12545
|
}
|
|
12108
|
-
console.log(`${
|
|
12546
|
+
console.log(`${import_chalk54.default.cyan("Consumer")} ${import_chalk54.default.bold(fresh.email ?? fresh.tenant)} on ${import_chalk54.default.bold(fresh.tenant)}
|
|
12109
12547
|
`);
|
|
12110
|
-
console.log(`${
|
|
12548
|
+
console.log(`${import_chalk54.default.bold("access_token")} ${import_chalk54.default.dim("exp " + (exp(fresh.accessToken) ?? "?"))}
|
|
12111
12549
|
${fresh.accessToken}
|
|
12112
12550
|
`);
|
|
12113
|
-
if (fresh.idToken) console.log(`${
|
|
12551
|
+
if (fresh.idToken) console.log(`${import_chalk54.default.bold("id_token")} ${import_chalk54.default.dim("exp " + (exp(fresh.idToken) ?? "?"))}
|
|
12114
12552
|
${fresh.idToken}
|
|
12115
12553
|
`);
|
|
12116
|
-
if (fresh.refreshToken) console.log(`${
|
|
12554
|
+
if (fresh.refreshToken) console.log(`${import_chalk54.default.bold("refresh_token")}
|
|
12117
12555
|
${fresh.refreshToken}
|
|
12118
12556
|
`);
|
|
12119
|
-
console.log(
|
|
12557
|
+
console.log(import_chalk54.default.dim("These are your own tokens \u2014 keep them secret."));
|
|
12120
12558
|
}
|
|
12121
12559
|
async function runConsumerApikeys(opts) {
|
|
12122
12560
|
const creds = requireConsumer2();
|
|
12123
12561
|
const { default: inquirer3 } = await import("inquirer");
|
|
12124
12562
|
const spinner = (0, import_ora26.default)("Loading your API keys...").start();
|
|
12125
|
-
const list = await consumerFetch(creds, "/apikeys");
|
|
12126
|
-
const revealed = await consumerFetch(list.creds, "/apikeys/reveal").catch(() => ({ status: 0, data: null, creds: list.creds }));
|
|
12127
|
-
spinner.stop();
|
|
12563
|
+
const list = await consumerFetch(creds, withPage("/apikeys", null));
|
|
12128
12564
|
if (list.status >= 400) {
|
|
12129
|
-
|
|
12130
|
-
|
|
12565
|
+
spinner.stop();
|
|
12566
|
+
console.error(import_chalk54.default.red(`Failed to list keys (${list.status}): ${list.data?.error ?? ""}`));
|
|
12567
|
+
if (list.status === 401) console.error(import_chalk54.default.dim("Your consumer session may have expired \u2014 run `apiblaze consumer login` again."));
|
|
12131
12568
|
process.exit(1);
|
|
12132
12569
|
}
|
|
12133
|
-
|
|
12134
|
-
const
|
|
12570
|
+
let walkCreds = list.creds;
|
|
12571
|
+
const keys = [...list.data?.keys ?? []];
|
|
12572
|
+
let keysCursor = list.data?.page?.next_cursor ?? null;
|
|
12573
|
+
for (let hop = 0; hop < MAX_HOPS && keysCursor; hop++) {
|
|
12574
|
+
const more = await consumerFetch(walkCreds, withPage("/apikeys", keysCursor));
|
|
12575
|
+
walkCreds = more.creds;
|
|
12576
|
+
if (more.status >= 400) break;
|
|
12577
|
+
keys.push(...more.data?.keys ?? []);
|
|
12578
|
+
keysCursor = more.data?.page?.next_cursor ?? null;
|
|
12579
|
+
}
|
|
12580
|
+
const revealMap = {};
|
|
12581
|
+
let revealCursor = null;
|
|
12582
|
+
for (let hop = 0; hop < MAX_HOPS; hop++) {
|
|
12583
|
+
const revealed = await consumerFetch(walkCreds, withPage("/apikeys/reveal", revealCursor)).catch(() => ({ status: 0, data: null, creds: walkCreds }));
|
|
12584
|
+
walkCreds = revealed.creds;
|
|
12585
|
+
if (revealed.status >= 400 || !revealed.data) break;
|
|
12586
|
+
for (const [env, v] of Object.entries(revealed.data?.keys ?? {})) {
|
|
12587
|
+
if (!(env in revealMap)) revealMap[env] = v;
|
|
12588
|
+
}
|
|
12589
|
+
revealCursor = revealed.data?.page?.next_cursor ?? null;
|
|
12590
|
+
if (!revealCursor) break;
|
|
12591
|
+
}
|
|
12592
|
+
spinner.stop();
|
|
12135
12593
|
if (opts.json) {
|
|
12136
12594
|
console.log(JSON.stringify({ keys, revealed: revealMap }, null, 2));
|
|
12137
12595
|
} else if (!keys.length) {
|
|
12138
|
-
console.log(
|
|
12596
|
+
console.log(import_chalk54.default.yellow("No API keys yet."));
|
|
12139
12597
|
} else {
|
|
12140
12598
|
for (const k of keys) {
|
|
12141
12599
|
const clear = revealMap[k.environment]?.key;
|
|
12142
|
-
const shown = clear ?
|
|
12143
|
-
const exp = k.expires_at ?
|
|
12144
|
-
console.log(` ${
|
|
12600
|
+
const shown = clear ? import_chalk54.default.green(clear) : import_chalk54.default.dim(`${k.key_prefix ?? ""}\u2026${k.key_suffix ?? ""}`);
|
|
12601
|
+
const exp = k.expires_at ? import_chalk54.default.dim(`exp ${k.expires_at}`) : import_chalk54.default.dim("no expiry");
|
|
12602
|
+
console.log(` ${import_chalk54.default.bold(k.environment ?? "")} ${shown} ${exp} ${import_chalk54.default.dim(k.description ?? "")}`);
|
|
12145
12603
|
}
|
|
12146
12604
|
if (Object.keys(revealMap).length === 0 && keys.some((k) => !k.expires_at)) {
|
|
12147
|
-
console.log(
|
|
12605
|
+
console.log(import_chalk54.default.dim("\n(Only expiring keys can be shown in clear; non-expiring keys show a prefix only.)"));
|
|
12148
12606
|
}
|
|
12149
12607
|
}
|
|
12150
12608
|
if (opts.json) return;
|
|
@@ -12166,12 +12624,12 @@ async function runConsumerApikeys(opts) {
|
|
|
12166
12624
|
}
|
|
12167
12625
|
s2.succeed("Key created.");
|
|
12168
12626
|
const key = created.data?.key ?? created.data?.fullKey;
|
|
12169
|
-
if (key) console.log(` ${
|
|
12170
|
-
else console.log(
|
|
12627
|
+
if (key) console.log(` ${import_chalk54.default.green(key)} ${import_chalk54.default.dim("(shown once \u2014 store it now)")}`);
|
|
12628
|
+
else console.log(import_chalk54.default.dim(" Key created; run `apiblaze consumer apikeys` to reveal it if it expires."));
|
|
12171
12629
|
}
|
|
12172
12630
|
|
|
12173
12631
|
// src/commands/sidecar.ts
|
|
12174
|
-
var
|
|
12632
|
+
var import_chalk55 = __toESM(require("chalk"));
|
|
12175
12633
|
var import_ora27 = __toESM(require("ora"));
|
|
12176
12634
|
var fs13 = __toESM(require("fs"));
|
|
12177
12635
|
var path10 = __toESM(require("path"));
|
|
@@ -12214,7 +12672,7 @@ function upsertEnvLocal(root, token) {
|
|
|
12214
12672
|
}
|
|
12215
12673
|
function installSidecarPackage(root) {
|
|
12216
12674
|
if (fs13.existsSync(path10.join(root, "node_modules", "apiblaze", "package.json"))) {
|
|
12217
|
-
console.log(` ${
|
|
12675
|
+
console.log(` ${import_chalk55.default.green("\u2713")} apiblaze package already installed`);
|
|
12218
12676
|
return;
|
|
12219
12677
|
}
|
|
12220
12678
|
const has = (f) => fs13.existsSync(path10.join(root, f));
|
|
@@ -12225,7 +12683,7 @@ function installSidecarPackage(root) {
|
|
|
12225
12683
|
execSync(`${pm.cmd} ${pm.add} apiblaze`, { cwd: root, stdio: "ignore" });
|
|
12226
12684
|
spinner.succeed("Installed apiblaze (the sidecar runtime).");
|
|
12227
12685
|
} catch {
|
|
12228
|
-
spinner.warn(`Couldn't auto-install \u2014 run ${
|
|
12686
|
+
spinner.warn(`Couldn't auto-install \u2014 run ${import_chalk55.default.cyan(`${pm.cmd} ${pm.add} apiblaze`)} yourself before ${import_chalk55.default.cyan("npm run dev")}.`);
|
|
12229
12687
|
}
|
|
12230
12688
|
}
|
|
12231
12689
|
function readEnvKey(root) {
|
|
@@ -12376,29 +12834,29 @@ async function runAnonymousInit(root, router, opts) {
|
|
|
12376
12834
|
if (out.cp_key && out.team_id) saveAnonCred2(out.cp_key, out.team_id, out.claim_code);
|
|
12377
12835
|
const envState = upsertEnvLocal(root, out.token);
|
|
12378
12836
|
ensureGitignored(root);
|
|
12379
|
-
console.log(` ${
|
|
12380
|
-
console.log(` ${
|
|
12837
|
+
console.log(` ${import_chalk55.default.green("\u2713")} .env.local ${envState} (APIBLAZE_API_KEY) \u2014 gitignored`);
|
|
12838
|
+
console.log(` ${import_chalk55.default.green("\u2713")} instrumentation.ts ${wireInstrumentation(root)}`);
|
|
12381
12839
|
installSidecarPackage(root);
|
|
12382
12840
|
let inspectorPath = null;
|
|
12383
12841
|
if (!opts.noInspector) {
|
|
12384
12842
|
inspectorPath = generateInspector(root, router);
|
|
12385
|
-
if (inspectorPath) console.log(` ${
|
|
12843
|
+
if (inspectorPath) console.log(` ${import_chalk55.default.green("\u2713")} inspector at ${inspectorPath}`);
|
|
12386
12844
|
}
|
|
12387
12845
|
console.log("");
|
|
12388
|
-
console.log(
|
|
12389
|
-
console.log(` 1. ${
|
|
12846
|
+
console.log(import_chalk55.default.bold("Done (no account needed). What happens next:"));
|
|
12847
|
+
console.log(` 1. ${import_chalk55.default.cyan("npm run dev")} and use your app.`);
|
|
12390
12848
|
console.log(` 2. Each external origin your app calls is logged in the console \u2014 approve one with:`);
|
|
12391
|
-
console.log(` ${
|
|
12849
|
+
console.log(` ${import_chalk55.default.cyan("apiblaze sidecar approve api.stripe.com")} (no login needed)`);
|
|
12392
12850
|
console.log("");
|
|
12393
|
-
console.log(
|
|
12394
|
-
console.log(` ${
|
|
12395
|
-
console.log(
|
|
12851
|
+
console.log(import_chalk55.default.bold(" \u{1F511} Keep your setup \u2014 claim it into an account:"));
|
|
12852
|
+
console.log(` ${import_chalk55.default.cyan("apiblaze login")} then ${import_chalk55.default.cyan("apiblaze claim")} ${import_chalk55.default.dim("(no code needed here)")}`);
|
|
12853
|
+
console.log(import_chalk55.default.dim(` From another machine: apiblaze claim ${out.claim_code} \xB7 expires in 30 days`));
|
|
12396
12854
|
}
|
|
12397
12855
|
async function runSidecar(opts) {
|
|
12398
12856
|
const root = path10.resolve(opts.dir ?? process.cwd());
|
|
12399
12857
|
const detected2 = detectNextProject(root);
|
|
12400
12858
|
if (!detected2.found) {
|
|
12401
|
-
console.log(
|
|
12859
|
+
console.log(import_chalk55.default.yellow(`No Next.js project detected in ${root}.`));
|
|
12402
12860
|
console.log("Create one (e.g. `npx create-next-app`) and re-run `apiblaze init` inside it.");
|
|
12403
12861
|
return;
|
|
12404
12862
|
}
|
|
@@ -12409,10 +12867,10 @@ async function runSidecar(opts) {
|
|
|
12409
12867
|
if (!loadCredentials()) {
|
|
12410
12868
|
upsertEnvLocal(root, readEnvKey(root));
|
|
12411
12869
|
ensureGitignored(root);
|
|
12412
|
-
console.log(` ${
|
|
12413
|
-
console.log(` ${
|
|
12870
|
+
console.log(` ${import_chalk55.default.green("\u2713")} .env.local present (APIBLAZE_API_KEY) \u2014 reusing`);
|
|
12871
|
+
console.log(` ${import_chalk55.default.green("\u2713")} instrumentation.ts ${wireInstrumentation(root)}`);
|
|
12414
12872
|
installSidecarPackage(root);
|
|
12415
|
-
console.log(
|
|
12873
|
+
console.log(import_chalk55.default.dim(" Log in and run `apiblaze claim <code>` to keep this setup, or `apiblaze login` to manage it."));
|
|
12416
12874
|
return;
|
|
12417
12875
|
}
|
|
12418
12876
|
const { teamId, teamName } = await resolveTeam(opts.team);
|
|
@@ -12435,38 +12893,38 @@ async function runSidecar(opts) {
|
|
|
12435
12893
|
throw err;
|
|
12436
12894
|
}
|
|
12437
12895
|
} else {
|
|
12438
|
-
console.log(
|
|
12896
|
+
console.log(import_chalk55.default.dim(` Reusing the existing APIBLAZE_API_KEY (run with --rotate to mint a fresh one, or --team <name> to switch teams).`));
|
|
12439
12897
|
}
|
|
12440
12898
|
const envState = upsertEnvLocal(root, token);
|
|
12441
12899
|
ensureGitignored(root);
|
|
12442
|
-
console.log(` ${
|
|
12900
|
+
console.log(` ${import_chalk55.default.green("\u2713")} .env.local ${envState} (APIBLAZE_API_KEY) \u2014 gitignored`);
|
|
12443
12901
|
const wireState = wireInstrumentation(root);
|
|
12444
|
-
console.log(` ${
|
|
12902
|
+
console.log(` ${import_chalk55.default.green("\u2713")} instrumentation.ts ${wireState}`);
|
|
12445
12903
|
installSidecarPackage(root);
|
|
12446
12904
|
let inspectorPath = null;
|
|
12447
12905
|
if (!opts.noInspector) {
|
|
12448
12906
|
inspectorPath = generateInspector(root, detected2.router);
|
|
12449
|
-
if (inspectorPath) console.log(` ${
|
|
12907
|
+
if (inspectorPath) console.log(` ${import_chalk55.default.green("\u2713")} inspector at ${inspectorPath}`);
|
|
12450
12908
|
}
|
|
12451
12909
|
console.log("");
|
|
12452
|
-
console.log(
|
|
12453
|
-
console.log(` 1. ${
|
|
12454
|
-
console.log(` 2. The origins your app calls appear as ${
|
|
12455
|
-
console.log(` 3. Approve the ones to route: ${
|
|
12910
|
+
console.log(import_chalk55.default.bold("Done. What happens next:"));
|
|
12911
|
+
console.log(` 1. ${import_chalk55.default.cyan("npm run dev")} and use your app \u2014 it works exactly as before (all calls go direct).`);
|
|
12912
|
+
console.log(` 2. The origins your app calls appear as ${import_chalk55.default.bold("candidates")} \u2014 list them: ${import_chalk55.default.cyan("apiblaze sidecar")}`);
|
|
12913
|
+
console.log(` 3. Approve the ones to route: ${import_chalk55.default.cyan("apiblaze sidecar approve api.stripe.com")} (or in the dashboard)`);
|
|
12456
12914
|
console.log(` \u2026within ~5 min your app starts routing that origin through APIblaze.`);
|
|
12457
|
-
if (inspectorPath) console.log(` \u2022 Try it now: open ${
|
|
12458
|
-
if (switchingTeam) console.log(
|
|
12915
|
+
if (inspectorPath) console.log(` \u2022 Try it now: open ${import_chalk55.default.underline("http://localhost:3000/abz-inspector")} (dev only; rm ${path10.dirname(inspectorPath)} before shipping)`);
|
|
12916
|
+
if (switchingTeam) console.log(import_chalk55.default.dim(` \u2022 Approved origins are per-team \u2014 re-approve them on ${teamName ?? teamId} with \`apiblaze sidecar approve <origin>\`.`));
|
|
12459
12917
|
console.log("");
|
|
12460
|
-
console.log(
|
|
12461
|
-
console.log(
|
|
12462
|
-
console.log(
|
|
12918
|
+
console.log(import_chalk55.default.dim(" Manage: apiblaze sidecar (list/approve/deny/remove)"));
|
|
12919
|
+
console.log(import_chalk55.default.dim(" Rotate: apiblaze init --rotate \xB7 Switch team: apiblaze init --team <name>"));
|
|
12920
|
+
console.log(import_chalk55.default.dim(" Turn off: set APIBLAZE_SIDECAR=off in .env.local (flip back to on anytime; key stays put)."));
|
|
12463
12921
|
console.log("");
|
|
12464
|
-
console.log(
|
|
12465
|
-
console.log(
|
|
12922
|
+
console.log(import_chalk55.default.yellow(" \u26A0 APIBLAZE_API_KEY is long-lived and lets a holder call your team's proxies. Never commit it."));
|
|
12923
|
+
console.log(import_chalk55.default.dim(" Your control-plane login stays in ~/.apiblaze \u2014 it never entered this project."));
|
|
12466
12924
|
}
|
|
12467
12925
|
|
|
12468
12926
|
// src/commands/origins.ts
|
|
12469
|
-
var
|
|
12927
|
+
var import_chalk56 = __toESM(require("chalk"));
|
|
12470
12928
|
var import_ora28 = __toESM(require("ora"));
|
|
12471
12929
|
init_admin();
|
|
12472
12930
|
init_resolve();
|
|
@@ -12477,7 +12935,7 @@ async function runOriginsList(opts) {
|
|
|
12477
12935
|
if (!loadCredentials()) {
|
|
12478
12936
|
const cred = loadAnonCred();
|
|
12479
12937
|
if (!cred) {
|
|
12480
|
-
console.log(
|
|
12938
|
+
console.log(import_chalk56.default.yellow("No anonymous workspace here. Run `apiblaze init` first."));
|
|
12481
12939
|
return;
|
|
12482
12940
|
}
|
|
12483
12941
|
out = await cpFetch(cred.cp_key, `/teams/${encodeURIComponent(cred.team_id)}/sidecar/candidates`, { method: "GET" });
|
|
@@ -12495,27 +12953,27 @@ async function runOriginsList(opts) {
|
|
|
12495
12953
|
}
|
|
12496
12954
|
const routed = out.routed ?? [];
|
|
12497
12955
|
const candidates = out.candidates ?? [];
|
|
12498
|
-
console.log(
|
|
12956
|
+
console.log(import_chalk56.default.bold(`
|
|
12499
12957
|
Routed through APIblaze (${routed.length})`));
|
|
12500
|
-
if (!routed.length) console.log(
|
|
12501
|
-
for (const r of routed) console.log(` ${
|
|
12502
|
-
console.log(
|
|
12958
|
+
if (!routed.length) console.log(import_chalk56.default.dim(" none yet"));
|
|
12959
|
+
for (const r of routed) console.log(` ${import_chalk56.default.green("\u25CF")} ${r.sidecar_origin} ${import_chalk56.default.dim(`\u2192 ${r.project_id}`)}`);
|
|
12960
|
+
console.log(import_chalk56.default.bold(`
|
|
12503
12961
|
Candidates \u2014 going direct, not yet approved (${candidates.length})`));
|
|
12504
|
-
if (!candidates.length) console.log(
|
|
12962
|
+
if (!candidates.length) console.log(import_chalk56.default.dim(" none \u2014 run your app to discover the origins it calls"));
|
|
12505
12963
|
for (const c of candidates) {
|
|
12506
|
-
console.log(` ${
|
|
12964
|
+
console.log(` ${import_chalk56.default.yellow("\u25CB")} ${c.origin} ${import_chalk56.default.dim(`seen ${c.request_count}\xD7, last ${c.last_seen}`)}`);
|
|
12507
12965
|
}
|
|
12508
12966
|
if (candidates.length) {
|
|
12509
|
-
console.log(
|
|
12967
|
+
console.log(import_chalk56.default.dim(`
|
|
12510
12968
|
Approve: apiblaze sidecar approve ${candidates[0].origin.replace("https://", "")}`));
|
|
12511
|
-
console.log(
|
|
12969
|
+
console.log(import_chalk56.default.dim(` Dismiss: apiblaze sidecar deny ${candidates[0].origin.replace("https://", "")}`));
|
|
12512
12970
|
}
|
|
12513
12971
|
}
|
|
12514
12972
|
async function runOriginsApprove(origin, opts) {
|
|
12515
12973
|
if (!loadCredentials()) {
|
|
12516
12974
|
const cred = loadAnonCred();
|
|
12517
12975
|
if (!cred) {
|
|
12518
|
-
console.error(
|
|
12976
|
+
console.error(import_chalk56.default.red("Not logged in and no anonymous workspace. Run `apiblaze init` first."));
|
|
12519
12977
|
process.exit(1);
|
|
12520
12978
|
}
|
|
12521
12979
|
const spinner2 = (0, import_ora28.default)(`Approving ${origin} (anonymous)...`).start();
|
|
@@ -12568,13 +13026,13 @@ async function runOriginsRemove(origin, opts) {
|
|
|
12568
13026
|
}
|
|
12569
13027
|
|
|
12570
13028
|
// src/commands/op.ts
|
|
12571
|
-
var
|
|
13029
|
+
var import_chalk58 = __toESM(require("chalk"));
|
|
12572
13030
|
init_auth();
|
|
12573
13031
|
init_trace();
|
|
12574
13032
|
init_types();
|
|
12575
13033
|
|
|
12576
13034
|
// src/commands/op-billing.ts
|
|
12577
|
-
var
|
|
13035
|
+
var import_chalk57 = __toESM(require("chalk"));
|
|
12578
13036
|
init_admin();
|
|
12579
13037
|
var SANDBOX = {
|
|
12580
13038
|
teamId: "team_1782844865835_zujrf",
|
|
@@ -12633,7 +13091,7 @@ async function rowsForRays(project, version2, tenant2, rays) {
|
|
|
12633
13091
|
}
|
|
12634
13092
|
function printDoors(data) {
|
|
12635
13093
|
const checks = [];
|
|
12636
|
-
console.log(
|
|
13094
|
+
console.log(import_chalk57.default.bold("\n Doors \u2014 is every way in metered?\n"));
|
|
12637
13095
|
const doors = data?.doors ?? [];
|
|
12638
13096
|
const metered = doors.filter((d) => d.verdict === "metered");
|
|
12639
13097
|
const allowed = doors.filter((d) => d.verdict === "allowed-free");
|
|
@@ -12642,29 +13100,29 @@ function printDoors(data) {
|
|
|
12642
13100
|
const errs = data?.errors ?? [];
|
|
12643
13101
|
const routeAuditBroke = errs.some((e) => e.startsWith("zone "));
|
|
12644
13102
|
const devAuditBroke = errs.some((e) => e.startsWith("workers.dev audit"));
|
|
12645
|
-
console.log(
|
|
12646
|
-
for (const d of metered) console.log(
|
|
12647
|
-
console.log(
|
|
13103
|
+
console.log(import_chalk57.default.dim(` ${metered.length} route(s) behind main-proxy (metered)`));
|
|
13104
|
+
for (const d of metered) console.log(import_chalk57.default.green(` \u2713 ${d.pattern}`));
|
|
13105
|
+
console.log(import_chalk57.default.dim(`
|
|
12648
13106
|
${allowed.length} route(s) free ON PURPOSE`));
|
|
12649
13107
|
for (const d of allowed) {
|
|
12650
|
-
console.log(
|
|
12651
|
-
console.log(
|
|
13108
|
+
console.log(import_chalk57.default.cyan(` \u2022 ${d.pattern}`) + import_chalk57.default.dim(` \u2192 ${d.script}`));
|
|
13109
|
+
console.log(import_chalk57.default.dim(` ${d.why}`));
|
|
12652
13110
|
}
|
|
12653
13111
|
if (known.length) {
|
|
12654
|
-
console.log(
|
|
13112
|
+
console.log(import_chalk57.default.yellow(`
|
|
12655
13113
|
${known.length} route(s) KNOWN OPEN \u2014 unmetered, not yet closed`));
|
|
12656
13114
|
for (const d of known) {
|
|
12657
|
-
console.log(
|
|
12658
|
-
console.log(
|
|
13115
|
+
console.log(import_chalk57.default.yellow(` ! ${d.pattern}`) + import_chalk57.default.dim(` \u2192 ${d.script}`));
|
|
13116
|
+
console.log(import_chalk57.default.dim(` ${d.why}`));
|
|
12659
13117
|
}
|
|
12660
13118
|
checks.push({ name: "no known-open doors", status: "KNOWN", detail: `${known.length} unmetered route(s) still open \u2014 see above` });
|
|
12661
13119
|
}
|
|
12662
13120
|
if (stray.length) {
|
|
12663
|
-
console.log(
|
|
13121
|
+
console.log(import_chalk57.default.red(`
|
|
12664
13122
|
${stray.length} STRAY route(s) \u2014 not main-proxy, not on the allowlist`));
|
|
12665
13123
|
for (const d of stray) {
|
|
12666
|
-
console.log(
|
|
12667
|
-
console.log(
|
|
13124
|
+
console.log(import_chalk57.default.red(` \u2717 ${d.pattern}`) + import_chalk57.default.dim(` \u2192 ${d.script}`));
|
|
13125
|
+
console.log(import_chalk57.default.dim(` ${d.why}`));
|
|
12668
13126
|
}
|
|
12669
13127
|
checks.push({ name: "no stray routes", status: "FAIL", detail: `${stray.length}: ${stray.map((s) => s.pattern).join(", ")}` });
|
|
12670
13128
|
} else if (routeAuditBroke) {
|
|
@@ -12675,22 +13133,22 @@ function printDoors(data) {
|
|
|
12675
13133
|
const wd = data?.workers_dev ?? {};
|
|
12676
13134
|
const open = wd.enabled ?? [];
|
|
12677
13135
|
if (open.length) {
|
|
12678
|
-
console.log(
|
|
13136
|
+
console.log(import_chalk57.default.red(`
|
|
12679
13137
|
${open.length} of ${wd.total} worker(s) reachable on *.workers.dev`));
|
|
12680
|
-
for (const s of open) console.log(
|
|
12681
|
-
console.log(
|
|
13138
|
+
for (const s of open) console.log(import_chalk57.default.red(` \u2717 ${s.script}.workers.dev`) + import_chalk57.default.dim(` (enabled=${s.enabled} previews=${s.previews})`));
|
|
13139
|
+
console.log(import_chalk57.default.dim(" A workers.dev hostname bypasses every CF route, WAF rule and the credit gate."));
|
|
12682
13140
|
checks.push({ name: "no workers.dev doors", status: "FAIL", detail: `${open.length} script(s) publicly reachable: ${open.map((s) => s.script).join(", ")}` });
|
|
12683
13141
|
} else if (devAuditBroke || !wd.total) {
|
|
12684
13142
|
checks.push({ name: "no workers.dev doors", status: "SKIP", detail: "script enumeration failed \u2014 NOT a pass, no subdomain was ever read. Needs a CF token with Workers Scripts:Read." });
|
|
12685
13143
|
} else {
|
|
12686
|
-
console.log(
|
|
13144
|
+
console.log(import_chalk57.default.green(`
|
|
12687
13145
|
\u2713 0 of ${wd.total} workers reachable on *.workers.dev`));
|
|
12688
13146
|
checks.push({ name: "no workers.dev doors", status: "PASS", detail: `all ${wd.total} scripts have workers.dev + previews disabled` });
|
|
12689
13147
|
}
|
|
12690
|
-
if (data?.how_to_fix) console.log(
|
|
12691
|
-
${data.reason}`) +
|
|
13148
|
+
if (data?.how_to_fix) console.log(import_chalk57.default.yellow(`
|
|
13149
|
+
${data.reason}`) + import_chalk57.default.dim(`
|
|
12692
13150
|
${data.how_to_fix}`));
|
|
12693
|
-
else for (const e of errs) console.log(
|
|
13151
|
+
else for (const e of errs) console.log(import_chalk57.default.red(`
|
|
12694
13152
|
audit error: ${e}`));
|
|
12695
13153
|
if (errs.length) {
|
|
12696
13154
|
checks.push({ name: "audit completeness", status: "FAIL", detail: `${errs.length} part(s) of the audit could not run \u2014 coverage is INCOMPLETE, and the checks they would have covered are SKIP above` });
|
|
@@ -12700,9 +13158,9 @@ function printDoors(data) {
|
|
|
12700
13158
|
async function runMeter(readLedger, opts) {
|
|
12701
13159
|
const checks = [];
|
|
12702
13160
|
const N = Math.max(1, Math.min(10, opts.count ?? 3));
|
|
12703
|
-
console.log(
|
|
13161
|
+
console.log(import_chalk57.default.bold("\n Meter \u2014 is 1 request charged exactly 1 request?\n"));
|
|
12704
13162
|
const snap = await readLedger();
|
|
12705
|
-
console.log(
|
|
13163
|
+
console.log(import_chalk57.default.dim(` wallet ${snap.billing_account_id} \xB7 band count ${snap.band_count} \xB7 next request ${snap.next_request_cents}\xA2
|
|
12706
13164
|
`));
|
|
12707
13165
|
const url = `https://${dpHost}/${SANDBOX.version}/${SANDBOX.environment}/`;
|
|
12708
13166
|
const headers = opts.key ? { "X-API-Key": opts.key } : {};
|
|
@@ -12784,22 +13242,22 @@ async function runMeter(readLedger, opts) {
|
|
|
12784
13242
|
return checks;
|
|
12785
13243
|
}
|
|
12786
13244
|
function printChecks(checks) {
|
|
12787
|
-
console.log(
|
|
12788
|
-
const mark = { PASS:
|
|
13245
|
+
console.log(import_chalk57.default.bold("\n Results\n"));
|
|
13246
|
+
const mark = { PASS: import_chalk57.default.green(" PASS"), FAIL: import_chalk57.default.red(" FAIL"), SKIP: import_chalk57.default.dim(" SKIP"), KNOWN: import_chalk57.default.yellow(" KNOWN") };
|
|
12789
13247
|
for (const ch of checks) {
|
|
12790
|
-
console.log(` ${mark[ch.status]} ${
|
|
12791
|
-
console.log(
|
|
13248
|
+
console.log(` ${mark[ch.status]} ${import_chalk57.default.bold(ch.name)}`);
|
|
13249
|
+
console.log(import_chalk57.default.dim(` ${ch.detail}`));
|
|
12792
13250
|
}
|
|
12793
13251
|
const fails = checks.filter((c) => c.status === "FAIL").length;
|
|
12794
13252
|
const skips = checks.filter((c) => c.status === "SKIP").length;
|
|
12795
13253
|
const known = checks.filter((c) => c.status === "KNOWN").length;
|
|
12796
13254
|
console.log("");
|
|
12797
13255
|
const passes = checks.filter((c) => c.status === "PASS").length;
|
|
12798
|
-
if (fails) console.log(
|
|
12799
|
-
else if (passes) console.log(
|
|
12800
|
-
else console.log(
|
|
12801
|
-
if (known) console.log(
|
|
12802
|
-
if (skips) console.log(
|
|
13256
|
+
if (fails) console.log(import_chalk57.default.red(` ${fails} check(s) FAILED.`));
|
|
13257
|
+
else if (passes) console.log(import_chalk57.default.green(` ${passes} check(s) passed, 0 failed.`));
|
|
13258
|
+
else console.log(import_chalk57.default.yellow(" NOTHING WAS VERIFIED \u2014 every check was skipped."));
|
|
13259
|
+
if (known) console.log(import_chalk57.default.yellow(` ${known} known-open issue(s) still outstanding.`));
|
|
13260
|
+
if (skips) console.log(import_chalk57.default.dim(` ${skips} check(s) NOT RUN (see SKIP above) \u2014 those invariants are unverified.`));
|
|
12803
13261
|
console.log("");
|
|
12804
13262
|
}
|
|
12805
13263
|
|
|
@@ -12846,93 +13304,93 @@ var OP_COMMANDS = [
|
|
|
12846
13304
|
function renderOpCommands() {
|
|
12847
13305
|
const width = Math.max(...OP_COMMANDS.map((c) => c.cmd.length)) + 10;
|
|
12848
13306
|
const lines = OP_COMMANDS.map((c) => {
|
|
12849
|
-
const left = ` ${
|
|
13307
|
+
const left = ` ${import_chalk58.default.cyan(`apiblaze ${c.cmd}`)}`;
|
|
12850
13308
|
const pad = " ".repeat(Math.max(1, width - c.cmd.length));
|
|
12851
|
-
return `${left}${pad}${c.blurb}${c.extra ? " " +
|
|
13309
|
+
return `${left}${pad}${c.blurb}${c.extra ? " " + import_chalk58.default.dim(`(${c.extra})`) : ""}`;
|
|
12852
13310
|
});
|
|
12853
13311
|
return [
|
|
12854
|
-
|
|
13312
|
+
import_chalk58.default.bold("Operator commands"),
|
|
12855
13313
|
...lines,
|
|
12856
13314
|
"",
|
|
12857
|
-
|
|
12858
|
-
|
|
12859
|
-
|
|
13315
|
+
import_chalk58.default.dim(" Operators only. The gate is server-side (dashboard /api/cli/op checks the"),
|
|
13316
|
+
import_chalk58.default.dim(" signed-in email, admin-api re-checks with operatorGate) \u2014 a patched CLI just"),
|
|
13317
|
+
import_chalk58.default.dim(" gets 403s. Every op call is read-only except `op sweep`."),
|
|
12860
13318
|
"",
|
|
12861
|
-
|
|
12862
|
-
|
|
12863
|
-
|
|
13319
|
+
import_chalk58.default.dim(" Not a CLI command: to prune all non-CP data run scripts/nuke-but-cp.sh --apply --sweep"),
|
|
13320
|
+
import_chalk58.default.dim(" in the repo. Operator dashboards (dlq, thresholds, throttling, pricing, billing,"),
|
|
13321
|
+
import_chalk58.default.dim(" agent-spend, teams, tests, leak-detection, lifecycle) live at /operator/* in the app.")
|
|
12864
13322
|
].join("\n");
|
|
12865
13323
|
}
|
|
12866
13324
|
function printResidue(report, applied) {
|
|
12867
13325
|
const up = report?.upstash ?? {};
|
|
12868
13326
|
const fga = report?.fga ?? {};
|
|
12869
13327
|
const ghosts = report?.ghosts ?? {};
|
|
12870
|
-
console.log(
|
|
12871
|
-
console.log(
|
|
13328
|
+
console.log(import_chalk58.default.bold(applied ? "\nExternal-residue sweep" : "\nExternal residue (dry-run \u2014 nothing deleted)"));
|
|
13329
|
+
console.log(import_chalk58.default.bold("\n Upstash"));
|
|
12872
13330
|
const orphans = up.orphans ?? [];
|
|
12873
|
-
if (orphans.length === 0) console.log(
|
|
12874
|
-
for (const o of orphans) console.log(` ${
|
|
12875
|
-
console.log(
|
|
13331
|
+
if (orphans.length === 0) console.log(import_chalk58.default.green(" no orphaned keys"));
|
|
13332
|
+
for (const o of orphans) console.log(` ${import_chalk58.default.yellow(o.key)} ${import_chalk58.default.dim(`\u2014 ${o.reason}`)}`);
|
|
13333
|
+
console.log(import_chalk58.default.dim(` kept (live principals): ${up.kept ?? 0} \xB7 anon wallets (untouched): ${up.anon_wallets ?? 0}`));
|
|
12876
13334
|
if (up.anon_wallet_detail) {
|
|
12877
13335
|
const d = up.anon_wallet_detail;
|
|
12878
|
-
console.log(
|
|
13336
|
+
console.log(import_chalk58.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)"}`));
|
|
12879
13337
|
}
|
|
12880
13338
|
if (up.keyspace_census) {
|
|
12881
13339
|
const census = Object.entries(up.keyspace_census).map(([k, v]) => `${k}=${v}`).join(" \xB7 ");
|
|
12882
|
-
console.log(
|
|
13340
|
+
console.log(import_chalk58.default.dim(` keyspace: ${census}`));
|
|
12883
13341
|
}
|
|
12884
|
-
if (up.unknown?.length) console.log(
|
|
12885
|
-
if (applied) console.log(` ${
|
|
12886
|
-
for (const e of up.errors ?? []) console.log(
|
|
12887
|
-
console.log(
|
|
13342
|
+
if (up.unknown?.length) console.log(import_chalk58.default.dim(` unknown (never deleted): ${up.unknown.join(", ")}`));
|
|
13343
|
+
if (applied) console.log(` ${import_chalk58.default.bold(String(up.deleted ?? 0))} key(s) deleted`);
|
|
13344
|
+
for (const e of up.errors ?? []) console.log(import_chalk58.default.red(` error: ${e}`));
|
|
13345
|
+
console.log(import_chalk58.default.bold("\n OpenFGA / Neon \u2014 orphan stores"));
|
|
12888
13346
|
if (applied) {
|
|
12889
13347
|
const swept = fga?.swept ?? [];
|
|
12890
|
-
if (swept.length === 0) console.log(
|
|
13348
|
+
if (swept.length === 0) console.log(import_chalk58.default.green(" no orphaned stores"));
|
|
12891
13349
|
for (const s of swept) {
|
|
12892
13350
|
console.log(
|
|
12893
|
-
` ${
|
|
13351
|
+
` ${import_chalk58.default.yellow(s.store_id)} ${import_chalk58.default.dim(`\u2014 store ${s.openfga_deleted ? "deleted" : "DEFERRED"}, ${s.neon_deleted} Neon tuple(s) purged`)}`
|
|
12894
13352
|
);
|
|
12895
13353
|
}
|
|
12896
|
-
if (fga?.remaining) console.log(
|
|
13354
|
+
if (fga?.remaining) console.log(import_chalk58.default.yellow(` ${fga.remaining} more orphan store(s) \u2014 re-run to drain`));
|
|
12897
13355
|
const st = fga?.side_tables;
|
|
12898
|
-
if (st) console.log(
|
|
13356
|
+
if (st) console.log(import_chalk58.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})` : ""}`));
|
|
12899
13357
|
} else {
|
|
12900
13358
|
const fgaOrphans = fga?.orphans ?? [];
|
|
12901
|
-
if (fgaOrphans.length === 0) console.log(
|
|
13359
|
+
if (fgaOrphans.length === 0) console.log(import_chalk58.default.green(" no orphaned stores"));
|
|
12902
13360
|
for (const s of fgaOrphans) {
|
|
12903
13361
|
const src = s.in_openfga ? "live in OpenFGA" : "Neon tuples only";
|
|
12904
|
-
console.log(` ${
|
|
13362
|
+
console.log(` ${import_chalk58.default.yellow(s.store_id)} ${import_chalk58.default.dim(`\u2014 ${src}${s.name ? ` (${s.name})` : ""}, ${s.neon_tuples} Neon tuple(s)`)}`);
|
|
12905
13363
|
}
|
|
12906
|
-
console.log(
|
|
13364
|
+
console.log(import_chalk58.default.dim(` kept stores: ${(fga?.kept_store_ids ?? []).length}`));
|
|
12907
13365
|
const st = fga?.side_tables;
|
|
12908
|
-
if (st) console.log(
|
|
13366
|
+
if (st) console.log(import_chalk58.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`));
|
|
12909
13367
|
}
|
|
12910
|
-
for (const e of fga?.errors ?? []) console.log(
|
|
12911
|
-
console.log(
|
|
13368
|
+
for (const e of fga?.errors ?? []) console.log(import_chalk58.default.red(` error: ${e}`));
|
|
13369
|
+
console.log(import_chalk58.default.bold("\n OpenFGA \u2014 ghost tuples in surviving stores"));
|
|
12912
13370
|
if (applied) {
|
|
12913
|
-
if ((ghosts?.ghost_count ?? 0) === 0) console.log(
|
|
12914
|
-
else console.log(` ${
|
|
13371
|
+
if ((ghosts?.ghost_count ?? 0) === 0) console.log(import_chalk58.default.green(" no ghost tuples"));
|
|
13372
|
+
else console.log(` ${import_chalk58.default.bold(String(ghosts.deleted ?? 0))} ghost tuple(s) deleted ${import_chalk58.default.dim(`(of ${ghosts.ghost_count} found, ${ghosts.scanned_tuples} scanned across ${ghosts.live_stores} live stores)`)}`);
|
|
12915
13373
|
} else {
|
|
12916
13374
|
const n = ghosts?.ghost_count ?? 0;
|
|
12917
|
-
if (n === 0) console.log(
|
|
13375
|
+
if (n === 0) console.log(import_chalk58.default.green(` no ghost tuples ${import_chalk58.default.dim(`(${ghosts.scanned_tuples ?? 0} scanned across ${ghosts.live_stores ?? 0} live stores)`)}`));
|
|
12918
13376
|
else {
|
|
12919
|
-
console.log(
|
|
13377
|
+
console.log(import_chalk58.default.yellow(` ${n} ghost tuple(s) referencing entities absent from D1:`));
|
|
12920
13378
|
for (const g of (ghosts.ghosts ?? []).slice(0, 20)) {
|
|
12921
|
-
console.log(
|
|
13379
|
+
console.log(import_chalk58.default.dim(` ${g.object_type}:${g.object_id} ${g.relation} ${g._user}`));
|
|
12922
13380
|
}
|
|
12923
|
-
if (n > 20) console.log(
|
|
13381
|
+
if (n > 20) console.log(import_chalk58.default.dim(` \u2026 and ${n - 20} more`));
|
|
12924
13382
|
}
|
|
12925
13383
|
}
|
|
12926
|
-
for (const e of ghosts?.errors ?? []) console.log(
|
|
13384
|
+
for (const e of ghosts?.errors ?? []) console.log(import_chalk58.default.red(` error: ${e}`));
|
|
12927
13385
|
console.log();
|
|
12928
13386
|
}
|
|
12929
13387
|
async function runOp(sub, opts = {}, view) {
|
|
12930
13388
|
if (!loadCredentials()) {
|
|
12931
|
-
console.log(
|
|
13389
|
+
console.log(import_chalk58.default.dim("Not logged in. Run `apiblaze login`."));
|
|
12932
13390
|
return;
|
|
12933
13391
|
}
|
|
12934
13392
|
if (!isOperatorLogin()) {
|
|
12935
|
-
console.log(
|
|
13393
|
+
console.log(import_chalk58.default.dim("`apiblaze op` is only available to platform operators."));
|
|
12936
13394
|
return;
|
|
12937
13395
|
}
|
|
12938
13396
|
switch (sub) {
|
|
@@ -12958,17 +13416,17 @@ async function runOp(sub, opts = {}, view) {
|
|
|
12958
13416
|
const nSide = (st.soft_deleted_stores ?? 0) + (st.orphan_models ?? 0) + (st.orphan_changelog ?? 0);
|
|
12959
13417
|
printResidue(report, false);
|
|
12960
13418
|
if (nUp + nFga + nGhost + nSide === 0) {
|
|
12961
|
-
console.log(
|
|
13419
|
+
console.log(import_chalk58.default.green("Nothing to sweep."));
|
|
12962
13420
|
return;
|
|
12963
13421
|
}
|
|
12964
13422
|
if (!opts.yes) {
|
|
12965
13423
|
const readline3 = await import("readline/promises");
|
|
12966
13424
|
const rl = readline3.createInterface({ input: process.stdin, output: process.stdout });
|
|
12967
13425
|
const answer = await rl.question(
|
|
12968
|
-
|
|
13426
|
+
import_chalk58.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: `)
|
|
12969
13427
|
);
|
|
12970
13428
|
rl.close();
|
|
12971
|
-
if (answer.trim() !== "sweep") return void console.log(
|
|
13429
|
+
if (answer.trim() !== "sweep") return void console.log(import_chalk58.default.dim("Aborted."));
|
|
12972
13430
|
}
|
|
12973
13431
|
const result = await opCall({ method: "POST", path: "/operator/external-residue/sweep", summary: "external residue sweep" });
|
|
12974
13432
|
if (opts.json) return void console.log(JSON.stringify(result, null, 2));
|
|
@@ -12978,25 +13436,25 @@ async function runOp(sub, opts = {}, view) {
|
|
|
12978
13436
|
case "mark": {
|
|
12979
13437
|
const label3 = (view ?? "").trim();
|
|
12980
13438
|
if (!label3) {
|
|
12981
|
-
console.log(
|
|
13439
|
+
console.log(import_chalk58.default.red("Give the change a name:") + import_chalk58.default.cyan(' apiblaze op mark "cached tenant count"'));
|
|
12982
13440
|
return;
|
|
12983
13441
|
}
|
|
12984
13442
|
const res = await opCall({ method: "POST", path: "/operator/latency/mark", body: { label: label3 }, summary: "record change marker" });
|
|
12985
13443
|
const ts = new Date(res?.marker?.ts ?? Date.now()).toISOString();
|
|
12986
|
-
console.log(
|
|
12987
|
-
Marked: `) +
|
|
12988
|
-
console.log(
|
|
12989
|
-
console.log(
|
|
13444
|
+
console.log(import_chalk58.default.green(`
|
|
13445
|
+
Marked: `) + import_chalk58.default.bold(label3));
|
|
13446
|
+
console.log(import_chalk58.default.dim(` ${ts}`));
|
|
13447
|
+
console.log(import_chalk58.default.dim(` Once traffic has run on both sides, compare with: `) + import_chalk58.default.cyan("apiblaze op latency compare") + "\n");
|
|
12990
13448
|
return;
|
|
12991
13449
|
}
|
|
12992
13450
|
case "credits": {
|
|
12993
13451
|
const data = await opCall({ method: "GET", path: "/operator/credits", summary: "list credit wallets" });
|
|
12994
13452
|
if (opts.json) return void console.log(JSON.stringify(data, null, 2));
|
|
12995
13453
|
const accounts = data?.accounts ?? [];
|
|
12996
|
-
if (accounts.length === 0) return void console.log(
|
|
13454
|
+
if (accounts.length === 0) return void console.log(import_chalk58.default.dim("No credit wallets."));
|
|
12997
13455
|
for (const a of accounts) {
|
|
12998
13456
|
const bal = typeof a.balance_cents === "number" ? `$${(a.balance_cents / 100).toFixed(2)}` : "?";
|
|
12999
|
-
console.log(` ${
|
|
13457
|
+
console.log(` ${import_chalk58.default.bold(bal.padStart(9))} ${a.walletId}${a.owner_email ? import_chalk58.default.dim(` \u2014 ${a.owner_email}`) : a.anon ? import_chalk58.default.dim(" \u2014 anon") : ""}`);
|
|
13000
13458
|
}
|
|
13001
13459
|
return;
|
|
13002
13460
|
}
|
|
@@ -13007,7 +13465,7 @@ async function runOp(sub, opts = {}, view) {
|
|
|
13007
13465
|
case "billing": {
|
|
13008
13466
|
const which = (view ?? "").trim().toLowerCase();
|
|
13009
13467
|
if (which && which !== "doors" && which !== "meter") {
|
|
13010
|
-
return void console.log(
|
|
13468
|
+
return void console.log(import_chalk58.default.red(`Unknown: apiblaze op billing ${which}. Use 'doors', 'meter', or neither for both.`));
|
|
13011
13469
|
}
|
|
13012
13470
|
const checks = [];
|
|
13013
13471
|
let doorsData = null;
|
|
@@ -13041,6 +13499,14 @@ async function runOp(sub, opts = {}, view) {
|
|
|
13041
13499
|
// it is the biggest feature on almost every request, so blaming it would
|
|
13042
13500
|
// report "upstream, 94%" forever and tell an operator nothing. The
|
|
13043
13501
|
// summary's "us or them" split is where upstream shows up.
|
|
13502
|
+
//
|
|
13503
|
+
// All of this reads Analytics Engine schema v2 (datasets apiblaze_analytics_v2
|
|
13504
|
+
// and apiblaze_llm_v2, cut over 2026-09-17). The CLI names no column and no
|
|
13505
|
+
// dataset itself — admin-api builds every query from the shared column map in
|
|
13506
|
+
// APIblazeV2/workers/shared/analytics-schema.ts — so the one thing to know here
|
|
13507
|
+
// is that history before the cutover is GONE by design (specs/scale.MD D12).
|
|
13508
|
+
// `op latency compare` against a marker older than that will show an empty
|
|
13509
|
+
// "before" side; that is the cutover, not a regression to zero traffic.
|
|
13044
13510
|
case "latency": {
|
|
13045
13511
|
const qs = new URLSearchParams();
|
|
13046
13512
|
const FILTERS = [
|
|
@@ -13068,29 +13534,29 @@ async function runOp(sub, opts = {}, view) {
|
|
|
13068
13534
|
const data = await opCall({ method: "GET", path: `/operator/latency/grades${q}`, summary: "latency grades" });
|
|
13069
13535
|
if (opts.json) return void console.log(JSON.stringify(data, null, 2));
|
|
13070
13536
|
const t = data.thresholds_ms;
|
|
13071
|
-
console.log(
|
|
13072
|
-
console.log(
|
|
13537
|
+
console.log(import_chalk58.default.bold("\nHow good was apiblaze itself?") + import_chalk58.default.dim(" (our overhead only \u2014 a slow customer API never counts against us)"));
|
|
13538
|
+
console.log(import_chalk58.default.dim(` excellent <${t.excellent.replace("<", "")}ms \xB7 okay ${t.okay}ms \xB7 bad ${t.bad}ms \xB7 terrible ${t.terrible.replace(">=", "")}ms+
|
|
13073
13539
|
`));
|
|
13074
|
-
console.log(
|
|
13540
|
+
console.log(import_chalk58.default.dim(" date reqs excellent okay bad terrible"));
|
|
13075
13541
|
for (const d of data.days ?? []) {
|
|
13076
13542
|
const p = d.pct;
|
|
13077
|
-
const cell = (v, colour) => v > 0 ? colour(`${String(v).padStart(5)}%`) :
|
|
13543
|
+
const cell = (v, colour) => v > 0 ? colour(`${String(v).padStart(5)}%`) : import_chalk58.default.dim(`${String(v).padStart(5)}%`);
|
|
13078
13544
|
console.log(
|
|
13079
|
-
` ${d.date} ${String(d.total).padStart(5)} ${cell(p.excellent,
|
|
13545
|
+
` ${d.date} ${String(d.total).padStart(5)} ${cell(p.excellent, import_chalk58.default.green)} ${cell(p.okay, import_chalk58.default.cyan)} ${cell(p.bad, import_chalk58.default.yellow)} ${cell(p.terrible, import_chalk58.default.red)}`
|
|
13080
13546
|
);
|
|
13081
13547
|
}
|
|
13082
13548
|
const cul = data.culprits ?? [];
|
|
13083
13549
|
if (cul.length) {
|
|
13084
|
-
console.log(
|
|
13085
|
-
console.log(
|
|
13550
|
+
console.log(import_chalk58.default.bold("\n Who caused the bad and terrible ones\n"));
|
|
13551
|
+
console.log(import_chalk58.default.dim(" bad terrible feature \u2192 dependency"));
|
|
13086
13552
|
for (const r of cul.slice(0, 12)) {
|
|
13087
13553
|
if (!r.bad && !r.terrible) continue;
|
|
13088
13554
|
console.log(
|
|
13089
|
-
` ${String(r.bad).padStart(6)} ${
|
|
13555
|
+
` ${String(r.bad).padStart(6)} ${import_chalk58.default.red(String(r.terrible).padStart(8))} ${import_chalk58.default.yellow(r.feature)} ${import_chalk58.default.dim("\u2192")} ${import_chalk58.default.cyan(r.dep)}`
|
|
13090
13556
|
);
|
|
13091
13557
|
}
|
|
13092
13558
|
}
|
|
13093
|
-
if (data.caveat) console.log(
|
|
13559
|
+
if (data.caveat) console.log(import_chalk58.default.dim(`
|
|
13094
13560
|
\u26A0 ${data.caveat}
|
|
13095
13561
|
`));
|
|
13096
13562
|
return;
|
|
@@ -13099,24 +13565,24 @@ async function runOp(sub, opts = {}, view) {
|
|
|
13099
13565
|
const data = await opCall({ method: "GET", path: `/operator/latency/compare${q}`, summary: "latency before/after" });
|
|
13100
13566
|
if (opts.json) return void console.log(JSON.stringify(data, null, 2));
|
|
13101
13567
|
const b = data.before, a = data.after, d = data.delta;
|
|
13102
|
-
console.log(
|
|
13103
|
-
Before vs after: `) +
|
|
13104
|
-
console.log(
|
|
13568
|
+
console.log(import_chalk58.default.bold(`
|
|
13569
|
+
Before vs after: `) + import_chalk58.default.cyan(data.marker.label));
|
|
13570
|
+
console.log(import_chalk58.default.dim(` marked ${new Date(data.marker.ts).toISOString()} \xB7 ${data.window_hours}h either side
|
|
13105
13571
|
`));
|
|
13106
13572
|
const row = (name, before, after, delta) => {
|
|
13107
13573
|
const arrow = delta === 0 ? "=" : delta < 0 ? "\u2193" : "\u2191";
|
|
13108
13574
|
const txt = `${String(before).padStart(6)}ms \u2192${String(after).padStart(7)}ms ${arrow}${Math.abs(delta)}ms`;
|
|
13109
|
-
console.log(` ${name.padEnd(22)}${data.trustworthy ? delta <= 0 ?
|
|
13575
|
+
console.log(` ${name.padEnd(22)}${data.trustworthy ? delta <= 0 ? import_chalk58.default.green(txt) : import_chalk58.default.red(txt) : import_chalk58.default.dim(txt)}`);
|
|
13110
13576
|
};
|
|
13111
|
-
console.log(
|
|
13577
|
+
console.log(import_chalk58.default.dim(" metric before after change"));
|
|
13112
13578
|
row("total p50", b.total_p50, a.total_p50, d.total_p50);
|
|
13113
13579
|
row("total p95", b.total_p95, a.total_p95, d.total_p95);
|
|
13114
13580
|
row("apiblaze overhead p50", b.gw_p50, a.gw_p50, d.gw_p50);
|
|
13115
13581
|
row("apiblaze overhead p95", b.gw_p95, a.gw_p95, d.gw_p95);
|
|
13116
|
-
console.log(
|
|
13582
|
+
console.log(import_chalk58.default.dim(`
|
|
13117
13583
|
requests: ${b.requests} before \xB7 ${a.requests} after`));
|
|
13118
13584
|
for (const w of data.warnings ?? []) {
|
|
13119
|
-
console.log((data.trustworthy ?
|
|
13585
|
+
console.log((data.trustworthy ? import_chalk58.default.dim : import_chalk58.default.yellow)(` ${data.trustworthy ? "\xB7" : "\u26A0"} ${w}`));
|
|
13120
13586
|
}
|
|
13121
13587
|
console.log("");
|
|
13122
13588
|
return;
|
|
@@ -13125,19 +13591,19 @@ Before vs after: `) + import_chalk56.default.cyan(data.marker.label));
|
|
|
13125
13591
|
const data = await opCall({ method: "GET", path: `/operator/latency/slow${q}`, summary: "slowest requests" });
|
|
13126
13592
|
if (opts.json) return void console.log(JSON.stringify(data, null, 2));
|
|
13127
13593
|
const rows2 = data?.rows ?? [];
|
|
13128
|
-
if (!rows2.length) return void console.log(
|
|
13129
|
-
console.log(
|
|
13594
|
+
if (!rows2.length) return void console.log(import_chalk58.default.dim("No requests over the threshold in that window."));
|
|
13595
|
+
console.log(import_chalk58.default.bold(`
|
|
13130
13596
|
Slowest requests \u2014 last ${data.window_hours}h, over ${data.min_ms}ms
|
|
13131
13597
|
`));
|
|
13132
|
-
console.log(
|
|
13598
|
+
console.log(import_chalk58.default.dim(" total ours theirs blame request id"));
|
|
13133
13599
|
for (const r of rows2.slice(0, 30)) {
|
|
13134
13600
|
console.log(
|
|
13135
|
-
` ${String(Math.round(r.duration_ms)).padStart(6)} ${String(Math.round(r.gateway_ms)).padStart(5)} ${String(Math.round(r.upstream_ttfb_ms)).padStart(6)} ${
|
|
13601
|
+
` ${String(Math.round(r.duration_ms)).padStart(6)} ${String(Math.round(r.gateway_ms)).padStart(5)} ${String(Math.round(r.upstream_ttfb_ms)).padStart(6)} ${import_chalk58.default.yellow(`${r.slow_gw_feature || "-"}\u2192${r.slow_dep || "-"}`.padEnd(20))} ${import_chalk58.default.dim(r.request_id || "")}`
|
|
13136
13602
|
);
|
|
13137
13603
|
}
|
|
13138
|
-
console.log(
|
|
13604
|
+
console.log(import_chalk58.default.dim(`
|
|
13139
13605
|
The last column is the request id (Cloudflare calls it a "cf-ray"). Look one up with`));
|
|
13140
|
-
console.log(
|
|
13606
|
+
console.log(import_chalk58.default.dim(` \`apiblaze logs\` for that request's exact per-feature breakdown \u2014 unsampled, unlike the table above.
|
|
13141
13607
|
`));
|
|
13142
13608
|
return;
|
|
13143
13609
|
}
|
|
@@ -13145,18 +13611,18 @@ Slowest requests \u2014 last ${data.window_hours}h, over ${data.min_ms}ms
|
|
|
13145
13611
|
const data = await opCall({ method: "GET", path: `/operator/latency/llm${q}`, summary: "llm latency" });
|
|
13146
13612
|
if (opts.json) return void console.log(JSON.stringify(data, null, 2));
|
|
13147
13613
|
const rows2 = data?.rows ?? [];
|
|
13148
|
-
if (!rows2.length) return void console.log(
|
|
13149
|
-
console.log(
|
|
13614
|
+
if (!rows2.length) return void console.log(import_chalk58.default.dim("No LLM traffic in that window."));
|
|
13615
|
+
console.log(import_chalk58.default.bold(`
|
|
13150
13616
|
LLM timing \u2014 last ${data.window_hours}h
|
|
13151
13617
|
`));
|
|
13152
|
-
console.log(
|
|
13618
|
+
console.log(import_chalk58.default.dim(" turns turn p95 ttfc p95 gen p95 reserve p95 in/out tokens p95 model"));
|
|
13153
13619
|
for (const r of rows2) {
|
|
13154
13620
|
const n = r.turns ?? r.requests ?? 0;
|
|
13155
13621
|
console.log(
|
|
13156
13622
|
` ${String(Math.round(n)).padStart(8)} ${String(Math.round(r.turn_p95_ms ?? 0)).padStart(6)}ms ${String(Math.round(r.ttfc_p95_ms ?? 0)).padStart(6)}ms ${String(Math.round(r.gen_p95_ms ?? 0)).padStart(6)}ms ${String(Math.round(r.reserve_p95_ms ?? 0)).padStart(9)}ms ${String(Math.round(r.input_tokens_p95 ?? 0)).padStart(6)}/${String(Math.round(r.output_tokens_p95 ?? 0)).padEnd(6)} ${r.model || "-"}`
|
|
13157
13623
|
);
|
|
13158
13624
|
}
|
|
13159
|
-
console.log(
|
|
13625
|
+
console.log(import_chalk58.default.dim(`
|
|
13160
13626
|
${data.note}
|
|
13161
13627
|
`));
|
|
13162
13628
|
return;
|
|
@@ -13167,26 +13633,26 @@ LLM timing \u2014 last ${data.window_hours}h
|
|
|
13167
13633
|
]);
|
|
13168
13634
|
if (opts.json) return void console.log(JSON.stringify({ blame, summary }, null, 2));
|
|
13169
13635
|
const ov = summary?.apiblaze_overhead_ms ?? {};
|
|
13170
|
-
console.log(
|
|
13636
|
+
console.log(import_chalk58.default.bold(`
|
|
13171
13637
|
Latency \u2014 last ${summary?.window_hours ?? "?"}h, ${Number(summary?.requests ?? 0).toLocaleString()} requests
|
|
13172
13638
|
`));
|
|
13173
|
-
console.log(` ${
|
|
13174
|
-
console.log(` ${
|
|
13175
|
-
console.log(
|
|
13639
|
+
console.log(` ${import_chalk58.default.bold("apiblaze overhead")} p50 ${String(ov.p50 ?? 0).padStart(5)}ms p95 ${String(ov.p95 ?? 0).padStart(6)}ms p99 ${String(ov.p99 ?? 0).padStart(6)}ms ${import_chalk58.default.dim("\u2190 ours")}`);
|
|
13640
|
+
console.log(` ${import_chalk58.default.bold("upstream ttfb ")} ${" ".repeat(24)}p95 ${String(summary?.upstream_ttfb_p95_ms ?? 0).padStart(6)}ms ${import_chalk58.default.dim("\u2190 theirs")}`);
|
|
13641
|
+
console.log(import_chalk58.default.dim(` (per-request percentiles \u2014 never subtract one from the other)
|
|
13176
13642
|
`));
|
|
13177
13643
|
const rows = blame?.blame ?? [];
|
|
13178
|
-
if (!rows.length) return void console.log(
|
|
13179
|
-
console.log(
|
|
13180
|
-
console.log(
|
|
13644
|
+
if (!rows.length) return void console.log(import_chalk58.default.dim("No latency rows in that window."));
|
|
13645
|
+
console.log(import_chalk58.default.bold(" Which feature ate the time, and what inside it\n"));
|
|
13646
|
+
console.log(import_chalk58.default.dim(" share p95 feature \u2192 dependency"));
|
|
13181
13647
|
for (const r of rows.slice(0, 15)) {
|
|
13182
13648
|
const share = `${(r.share * 100).toFixed(1)}%`;
|
|
13183
|
-
console.log(` ${share.padStart(6)} ${String(r.p95_ms).padStart(6)}ms ${
|
|
13649
|
+
console.log(` ${share.padStart(6)} ${String(r.p95_ms).padStart(6)}ms ${import_chalk58.default.yellow(r.feature)} ${import_chalk58.default.dim("\u2192")} ${import_chalk58.default.cyan(r.dep)}`);
|
|
13184
13650
|
}
|
|
13185
13651
|
console.log("");
|
|
13186
13652
|
return;
|
|
13187
13653
|
}
|
|
13188
13654
|
default:
|
|
13189
|
-
console.log(
|
|
13655
|
+
console.log(import_chalk58.default.red(`Unknown op subcommand '${sub}'. Run \`apiblaze op\` for the menu.`));
|
|
13190
13656
|
}
|
|
13191
13657
|
}
|
|
13192
13658
|
|
|
@@ -13256,7 +13722,7 @@ program.command("dev").description(`Put the code running on this machine behind
|
|
|
13256
13722
|
try {
|
|
13257
13723
|
const resolved = parseInt(port ?? opts.port, 10);
|
|
13258
13724
|
if (Number.isNaN(resolved)) {
|
|
13259
|
-
console.error(
|
|
13725
|
+
console.error(import_chalk59.default.red(`Invalid port: ${port ?? opts.port}`));
|
|
13260
13726
|
process.exit(1);
|
|
13261
13727
|
}
|
|
13262
13728
|
await runDev({ ...opts, port: resolved });
|
|
@@ -13312,7 +13778,8 @@ program.command("show").description("Read a recipe before you install it (upstre
|
|
|
13312
13778
|
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("--tenant <slug>", "Where this proxy's users live. One of yours is reused as it is; a new name is created").option("-y, --yes", "Skip the routine prompts (installs WITHOUT anything needing consent)").action(action((recipe, opts) => runRecipeInstall(recipe, opts)));
|
|
13313
13779
|
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)));
|
|
13314
13780
|
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)));
|
|
13315
|
-
program.command("
|
|
13781
|
+
program.command("job").description("Check a background delete job (from a delete that ran with --no-wait)").argument("<job_id>", "Job id printed by the delete command (cj_\u2026)").option("--team <id|name>", "Team the job belongs to (defaults to active team)").option("--watch", "Wait until the job finishes").option("--json", "Output machine-readable JSON").action(action((jobId, opts) => runJobStatus(jobId, opts)));
|
|
13782
|
+
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").option("--no-wait", "Return as soon as the delete is accepted instead of waiting for it to finish").action(action((project, version2, opts) => runDelete(project, version2, opts)));
|
|
13316
13783
|
program.command("export").description(`Export config and data for migration out of ${B.product} (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 })));
|
|
13317
13784
|
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)));
|
|
13318
13785
|
logs.command("list").description("API Producer view \u2014 browse EVERY caller's past requests by day, then open one to see its 4 views").argument("<project>", "Project name or id").argument("[version]", "API version (defaults to the first match)").option("--tenant <slug>", "Tenant to read (auto when one is attached; prompted otherwise)").option("--errors-only", "Only show 4xx/5xx responses").option("--json", "Emit ALL matching rows as NDJSON, newest first (non-interactive)").option("--plain", "Print day-grouped rows instead of prompting (automatic when piped)").option("--team <id|name>", "Team the project is in (defaults to active team)").action(action((project, version2, _o, cmd) => runLogsList(project, version2, cmd.optsWithGlobals())));
|
|
@@ -13343,7 +13810,7 @@ tenant.command("use").description("Set the sticky tenant scope for future comman
|
|
|
13343
13810
|
tenant.command("list").description("List tenants in your team (--q searches server-side)").option("--q <search>", "Filter by tenant name or display name").option("--limit <n>", "Page size (max 200)").option("--team <id|name>", "Team (defaults to active team)").option("--json", "Output machine-readable JSON").action(action((opts) => runTenantList(opts)));
|
|
13344
13811
|
tenant.command("create").description("Create a tenant in your team (tenant names are lowercase alphanumeric and globally unique)").requiredOption("--name <name>", "Tenant name \u2014 becomes the slug ({name}.portal.apiblaze.com) unless --slug overrides; also the display label").option("--slug <tenant_name>", "Explicit tenant slug when it should differ from --name").option("--team <id|name>", "Team (defaults to active team)").option("--json", "Output machine-readable JSON").action(action((opts) => runTenantCreate(opts)));
|
|
13345
13812
|
tenant.command("attach").description("Attach a tenant to a proxy").argument("<project>", "Project name or id").requiredOption("--tenant <slug>", "Tenant slug to attach").option("--auth-config <id>", "Auth config id to bind").option("--team <id|name>", "Team the project is in").option("--apiversion <version>", "API version").option("--json", "Output machine-readable JSON").action(action((project, opts) => runTenantAttach(project, opts)));
|
|
13346
|
-
tenant.command("delete").description("
|
|
13813
|
+
tenant.command("delete").description("Move a tenant to the trash (reversible); --purge destroys it and everything under it").argument("<slug>", "Tenant slug to delete").option("--team <id|name>", "Team (defaults to active team)").option("-y, --yes", "Skip the confirmation prompt").option("--purge", "Permanently destroy the tenant and all its data (cannot be undone)").option("--no-wait", "With --purge: return as soon as the teardown is accepted instead of waiting for it to finish").option("--json", "Output machine-readable JSON").action(action((slug, opts) => runTenantDelete(slug, opts)));
|
|
13347
13814
|
tenant.command("cors").description("Set the CORS allow-list for a tenant").requiredOption("--tenant <slug>", "Tenant slug").option("--origins <list>", 'Comma-separated origins (or "*"); empty clears').option("--team <id|name>", "Team (defaults to active team)").action(action((opts) => runTenantCors(opts)));
|
|
13348
13815
|
var admins = new import_commander.Command("admins").description("Manage who can administer a tenant's users & groups (the first-admin bootstrap the widget needs)").option("--tenant <slug>", "Tenant slug (e.g. nino)").option("--team <id|name>", "Team (defaults to active team, or your anonymous workspace)").option("--json", "Output machine-readable JSON").action(action((opts) => runAdminsList(opts)));
|
|
13349
13816
|
admins.command("list").description("List a tenant's admins").option("--tenant <slug>", "Tenant slug").option("--team <id|name>", "Team").option("--json", "JSON").action(action((opts, cmd) => runAdminsList({ ...cmd.parent?.opts(), ...opts })));
|
|
@@ -13398,7 +13865,7 @@ function groupedCommandHelp() {
|
|
|
13398
13865
|
const sub = byName.get(e.parent)?.commands.find((s) => s.name() === e.sub);
|
|
13399
13866
|
return sub ? ` ${helpLabel(e).padEnd(width)}${sub.description()}` : "";
|
|
13400
13867
|
}).filter(Boolean).join("\n");
|
|
13401
|
-
return `${
|
|
13868
|
+
return `${import_chalk59.default.bold(g.title)}
|
|
13402
13869
|
${rows}`;
|
|
13403
13870
|
}).join("\n\n");
|
|
13404
13871
|
}
|
|
@@ -13409,7 +13876,7 @@ program.configureHelp({
|
|
|
13409
13876
|
}
|
|
13410
13877
|
});
|
|
13411
13878
|
program.addHelpText("after", () => `
|
|
13412
|
-
${
|
|
13879
|
+
${import_chalk59.default.bold("Don't like reading docs?")} Run ${import_chalk59.default.bgCyan.black.bold(` npx ${B.cli} docs `)} ${import_chalk59.default.cyan(`\u2014 ask an AI chatbot about ${B.product}, and let it act on your account.`)}
|
|
13413
13880
|
|
|
13414
13881
|
${groupedCommandHelp()}
|
|
13415
13882
|
|
|
@@ -13445,14 +13912,14 @@ async function recoverStaleTeam() {
|
|
|
13445
13912
|
const { resolveLinkedTeam: resolveLinkedTeam2 } = await Promise.resolve().then(() => (init_team(), team_exports));
|
|
13446
13913
|
const linked = await resolveLinkedTeam2({ preferredId: creds.teamId, interactive: !!process.stdin.isTTY });
|
|
13447
13914
|
if (!linked) {
|
|
13448
|
-
console.error(
|
|
13915
|
+
console.error(import_chalk59.default.yellow("Your account has no teams anymore (deleted?). Run `apiblaze login` or `apiblaze create` to get a workspace."));
|
|
13449
13916
|
return;
|
|
13450
13917
|
}
|
|
13451
13918
|
if (linked.teamId === creds.teamId) return;
|
|
13452
13919
|
const next = { ...creds, teamId: linked.teamId, teamName: linked.teamName };
|
|
13453
13920
|
delete next.activeTenant;
|
|
13454
13921
|
saveCredentials(next);
|
|
13455
|
-
console.error(
|
|
13922
|
+
console.error(import_chalk59.default.yellow(`Your previous team no longer exists \u2014 relinked to ${import_chalk59.default.bold(linked.teamName ?? linked.teamId)}. Re-run your command.`));
|
|
13456
13923
|
} catch {
|
|
13457
13924
|
}
|
|
13458
13925
|
}
|
|
@@ -13460,16 +13927,16 @@ async function printError(err) {
|
|
|
13460
13927
|
if (err instanceof ApiError) {
|
|
13461
13928
|
const data = err.body;
|
|
13462
13929
|
const extra = [data?.body?.reason, data?.body?.details, data?.details, data?.body?.error].find((x) => typeof x === "string" && x && x !== err.message);
|
|
13463
|
-
console.error(
|
|
13930
|
+
console.error(import_chalk59.default.red(`
|
|
13464
13931
|
API error (${err.status}): ${err.message}${extra ? ` \u2014 ${extra}` : ""}`));
|
|
13465
13932
|
if (err.status === 403 || err.status === 404) {
|
|
13466
13933
|
await recoverStaleTeam();
|
|
13467
13934
|
}
|
|
13468
13935
|
} else if (err instanceof Error) {
|
|
13469
|
-
console.error(
|
|
13936
|
+
console.error(import_chalk59.default.red(`
|
|
13470
13937
|
Error: ${err.message}`));
|
|
13471
13938
|
} else {
|
|
13472
|
-
console.error(
|
|
13939
|
+
console.error(import_chalk59.default.red("\nUnknown error"));
|
|
13473
13940
|
}
|
|
13474
13941
|
}
|
|
13475
13942
|
program.parse(process.argv);
|