apiblaze 0.21.4 → 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 +1216 -767
- 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,
|
|
@@ -5559,6 +5622,7 @@ Create an ${B.thing}
|
|
|
5559
5622
|
const proxyUrl = `https://${name}.abz.run/${version2}/dev`;
|
|
5560
5623
|
const devPortal = result.devPortal ? stripTenantFromPortal(result.devPortal) : void 0;
|
|
5561
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.");
|
|
5562
5626
|
rememberForApichat({
|
|
5563
5627
|
projectId: result.project_id ?? name,
|
|
5564
5628
|
target: opts.target || opts.openapi || name,
|
|
@@ -5628,7 +5692,7 @@ function rememberForApichat(entry) {
|
|
|
5628
5692
|
environment: entry.environment,
|
|
5629
5693
|
// The host `apichat <name> --install-mcp` will connect to: mcpblaze.com under
|
|
5630
5694
|
// that brand, else the same .mcp. convention apichat uses for an opened proxy.
|
|
5631
|
-
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`,
|
|
5632
5696
|
teamId: entry.teamId,
|
|
5633
5697
|
tenant: entry.tenant,
|
|
5634
5698
|
dpKey: entry.dpKey,
|
|
@@ -5780,6 +5844,7 @@ Create an ${B.thing}`));
|
|
|
5780
5844
|
const apiKey = result.apiKey ?? keys.prod ?? Object.values(keys)[0];
|
|
5781
5845
|
const portal = result.portal ?? (result.devPortal ? stripTenantFromPortal(result.devPortal) : void 0);
|
|
5782
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.");
|
|
5783
5848
|
const secure = result.secure ?? void 0;
|
|
5784
5849
|
const anonProjectId = result.project_id ?? name ?? "";
|
|
5785
5850
|
if (anonProjectId) {
|
|
@@ -5976,7 +6041,7 @@ async function createDevProxy(args) {
|
|
|
5976
6041
|
return {
|
|
5977
6042
|
projectId: result.project_id ?? name,
|
|
5978
6043
|
version: result.api_version || "1.0.0",
|
|
5979
|
-
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."),
|
|
5980
6045
|
teamId: resolvedTeam,
|
|
5981
6046
|
apiKey: result.apiKey ?? keys.dev ?? Object.values(keys)[0],
|
|
5982
6047
|
keys,
|
|
@@ -6285,63 +6350,193 @@ function skillMarkdown() {
|
|
|
6285
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).`;
|
|
6286
6351
|
return `---
|
|
6287
6352
|
name: ${SKILL_NAME}
|
|
6288
|
-
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.
|
|
6289
6354
|
---
|
|
6290
6355
|
|
|
6291
6356
|
# Ship safely with ${P}
|
|
6292
6357
|
|
|
6293
6358
|
One gateway, two doors. ${doors}
|
|
6294
|
-
Same rules, one command. Nothing about auth, tenancy or ownership goes into the
|
|
6295
|
-
|
|
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.
|
|
6377
|
+
|
|
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.
|
|
6381
|
+
|
|
6382
|
+
## Explain
|
|
6296
6383
|
|
|
6297
|
-
|
|
6384
|
+
Say this, in these words or close to them, then stop:
|
|
6298
6385
|
|
|
6299
|
-
|
|
6300
|
-
|
|
6301
|
-
|
|
6302
|
-
|
|
6303
|
-
|
|
6304
|
-
|
|
6305
|
-
|
|
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."
|
|
6306
6395
|
|
|
6307
|
-
|
|
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:
|
|
6308
6402
|
|
|
6309
|
-
|
|
6310
|
-
|
|
6311
|
-
|
|
6312
|
-
ChatGPT) the API as an MCP. One line first: "You don't need to write any of that into your
|
|
6313
|
-
backend. Add the ${P} gateway in front of your code \u2014 one command \u2014 and I'll wire the rest."
|
|
6314
|
-
If the user already named the package (\`npx ${cli} skills\`, "use ${cli}"), skip the pitch: run it.
|
|
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}?"**
|
|
6315
6406
|
|
|
6316
|
-
##
|
|
6407
|
+
## Demo app \u2014 the same reservation system, before and after
|
|
6317
6408
|
|
|
6318
|
-
|
|
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"
|
|
6319
6505
|
|
|
6320
6506
|
1. **The spec.** Look for \`openapi.yaml\` / \`openapi.json\` / a spec route. None? Say
|
|
6321
6507
|
"Generating an OpenAPI spec from your code" and write \`./openapi.yaml\` from the routes:
|
|
6322
|
-
every path and method, and for each POST the id field it returns
|
|
6323
|
-
|
|
6324
|
-
|
|
6325
|
-
|
|
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"}'\`
|
|
6326
6513
|
(both doors: keys for tenants, their login for people and agents). Shapes below.
|
|
6327
|
-
3. **
|
|
6328
|
-
|
|
6329
|
-
|
|
6330
|
-
|
|
6331
|
-
|
|
6332
|
-
|
|
6333
|
-
|
|
6334
|
-
|
|
6335
|
-
|
|
6336
|
-
\
|
|
6337
|
-
|
|
6338
|
-
|
|
6339
|
-
|
|
6340
|
-
|
|
6341
|
-
|
|
6342
|
-
|
|
6343
|
-
|
|
6344
|
-
\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\`).
|
|
6345
6540
|
|
|
6346
6541
|
## Report like this
|
|
6347
6542
|
|
|
@@ -6351,14 +6546,12 @@ Use the real values the CLI printed. Never invent a URL, a key, a rule or a limi
|
|
|
6351
6546
|
\u26A1\uFE0FAuthorization rules, from the spec: \u25C9 restaurants \u25C9 tables \u25C9 reservations
|
|
6352
6547
|
only their creator, or an admin, can change them
|
|
6353
6548
|
\u25CF Done. Your app is behind a secured API and MCP:
|
|
6354
|
-
API https://<name>.abz.run
|
|
6355
|
-
/dev \u2192 localhost:<port>
|
|
6356
|
-
/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
|
|
6357
6552
|
Try it: curl https://<name>.abz.run/1.0.0/dev/<collection> -H "X-API-Key: <key>" -H "X-End-User-Id: you"
|
|
6358
|
-
MCP
|
|
6553
|
+
MCP <the "agents:" URL the CLI printed> agents, signing in with <provider>
|
|
6359
6554
|
Limits 10 requests/s per user \xB7 10,000 a day
|
|
6360
|
-
\u25CF Would you like me to add a self-serve API key widget to the developer section of your website?
|
|
6361
|
-
Would you like to chat with your API now via your MCP?
|
|
6362
6555
|
|
|
6363
6556
|
## Sign-in shapes \u2014 the only ones \`--oauth\` accepts; never invent fields
|
|
6364
6557
|
|
|
@@ -6369,14 +6562,21 @@ Use the real values the CLI printed. Never invent a URL, a key, a rule or a limi
|
|
|
6369
6562
|
- Their own JWT issuer: \`--oauth '{"iss":"https://login.acme.com/","aud":"acme-api","jwks":"https://login.acme.com/.well-known/jwks.json"}'\`
|
|
6370
6563
|
\u2014 all three required; add \`--apikey\` for both doors.
|
|
6371
6564
|
|
|
6372
|
-
##
|
|
6565
|
+
## Wiring a real site
|
|
6373
6566
|
|
|
6374
6567
|
\`npx ${cli} integration <name> --stack nextjs\` (also express | fastapi | other) prints the kit:
|
|
6375
|
-
the key
|
|
6376
|
-
|
|
6377
|
-
|
|
6378
|
-
|
|
6379
|
-
\`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.
|
|
6380
6580
|
|
|
6381
6581
|
## Never
|
|
6382
6582
|
|
|
@@ -6385,12 +6585,14 @@ and the frontend-server. A key bound to one person (an agent, a CI job):
|
|
|
6385
6585
|
refuses it. Agents get a person-bound key or sign in.
|
|
6386
6586
|
- Put ownership or tenancy checks into business logic. The gateway enforces them.
|
|
6387
6587
|
- Ask for team ids, tenant ids or project ids the CLI already knows.
|
|
6388
|
-
- 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.
|
|
6389
6591
|
|
|
6390
6592
|
## Later
|
|
6391
6593
|
|
|
6392
6594
|
- \`npx ${cli} rule <name>\` reviews or changes the rules; \`npx ${cli} config <name> authorization.enforce_authorization false\` turns enforcement off without losing them.
|
|
6393
|
-
- \`npx ${cli} apichat <name
|
|
6595
|
+
- \`npx ${cli} apichat <name>\` chats with the API again; \`--install-mcp claude\` connects an agent.
|
|
6394
6596
|
|
|
6395
6597
|
## No shell? The control-plane MCP
|
|
6396
6598
|
|
|
@@ -7337,10 +7539,94 @@ async function runMcp(projectArg, apiVersionArg, opts) {
|
|
|
7337
7539
|
}
|
|
7338
7540
|
|
|
7339
7541
|
// src/commands/delete.ts
|
|
7340
|
-
var
|
|
7542
|
+
var import_chalk33 = __toESM(require("chalk"));
|
|
7341
7543
|
var import_ora12 = __toESM(require("ora"));
|
|
7342
7544
|
init_admin();
|
|
7343
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
|
|
7344
7630
|
async function runDelete(project, version2, opts) {
|
|
7345
7631
|
const { teamId } = await resolveTeam(opts.team);
|
|
7346
7632
|
const proj2 = await resolveProject(teamId, project, version2);
|
|
@@ -7363,10 +7649,10 @@ async function runDelete(project, version2, opts) {
|
|
|
7363
7649
|
return;
|
|
7364
7650
|
}
|
|
7365
7651
|
} else {
|
|
7366
|
-
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 + ")")}`);
|
|
7367
7653
|
if (impact && typeof impact === "object") {
|
|
7368
7654
|
const counts = impact.counts ?? impact.impact ?? impact;
|
|
7369
|
-
console.log(
|
|
7655
|
+
console.log(import_chalk33.default.dim(" This removes (cascade): ") + import_chalk33.default.yellow(JSON.stringify(counts)));
|
|
7370
7656
|
}
|
|
7371
7657
|
if (!opts.yes) {
|
|
7372
7658
|
const { default: inquirer3 } = await import("inquirer");
|
|
@@ -7374,28 +7660,79 @@ async function runDelete(project, version2, opts) {
|
|
|
7374
7660
|
{ type: "confirm", name: "confirm", message: `Permanently delete ${proj2.projectName} v${proj2.apiVersion}? This cannot be undone.`, default: false }
|
|
7375
7661
|
]);
|
|
7376
7662
|
if (!confirm2) {
|
|
7377
|
-
console.log(
|
|
7663
|
+
console.log(import_chalk33.default.dim("Aborted."));
|
|
7378
7664
|
return;
|
|
7379
7665
|
}
|
|
7380
7666
|
}
|
|
7381
7667
|
}
|
|
7382
7668
|
const s2 = (0, import_ora12.default)("Deleting...").start();
|
|
7669
|
+
let accepted;
|
|
7383
7670
|
try {
|
|
7384
|
-
await admin({
|
|
7671
|
+
accepted = await admin({
|
|
7385
7672
|
method: "DELETE",
|
|
7386
7673
|
path: `/projects/${proj2.projectId}/${proj2.apiVersion}`,
|
|
7387
7674
|
summary: `Delete proxy ${proj2.projectName} v${proj2.apiVersion} (full cascade)`
|
|
7388
7675
|
});
|
|
7389
|
-
s2.
|
|
7676
|
+
s2.stop();
|
|
7390
7677
|
} catch (err) {
|
|
7391
7678
|
s2.fail("Delete failed.");
|
|
7392
7679
|
throw err;
|
|
7393
7680
|
}
|
|
7394
|
-
|
|
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;
|
|
7395
7732
|
}
|
|
7396
7733
|
|
|
7397
7734
|
// src/commands/export.ts
|
|
7398
|
-
var
|
|
7735
|
+
var import_chalk35 = __toESM(require("chalk"));
|
|
7399
7736
|
var import_fs4 = require("fs");
|
|
7400
7737
|
init_admin();
|
|
7401
7738
|
init_auth();
|
|
@@ -7414,7 +7751,7 @@ async function runExport(projectArg, versionArg, opts) {
|
|
|
7414
7751
|
if (opts.noConsumers) q.set("include_consumers", "0");
|
|
7415
7752
|
if (opts.keys) q.set("keys", opts.keys);
|
|
7416
7753
|
const qs = q.toString() ? `?${q.toString()}` : "";
|
|
7417
|
-
console.log(
|
|
7754
|
+
console.log(import_chalk35.default.dim(`Queuing ${target} export of ${projectId}@${version2}\u2026`));
|
|
7418
7755
|
const job = await admin({
|
|
7419
7756
|
path: `/projects/${projectId}/${version2}/export/${target}${qs}`,
|
|
7420
7757
|
method: "GET",
|
|
@@ -7424,11 +7761,11 @@ async function runExport(projectArg, versionArg, opts) {
|
|
|
7424
7761
|
const statusPath = `/projects/${projectId}/${version2}/export/jobs/${job.job_id}`;
|
|
7425
7762
|
let status = "queued";
|
|
7426
7763
|
const started = Date.now();
|
|
7427
|
-
process.stdout.write(
|
|
7764
|
+
process.stdout.write(import_chalk35.default.dim("Building bundle"));
|
|
7428
7765
|
while (status !== "ready" && status !== "failed") {
|
|
7429
7766
|
if (Date.now() - started > 10 * 6e4) throw new Error("Export timed out after 10 minutes.");
|
|
7430
7767
|
await new Promise((r) => setTimeout(r, 3e3));
|
|
7431
|
-
process.stdout.write(
|
|
7768
|
+
process.stdout.write(import_chalk35.default.dim("."));
|
|
7432
7769
|
const st = await admin({ path: statusPath, method: "GET", summary: "poll export" });
|
|
7433
7770
|
status = st?.status ?? "queued";
|
|
7434
7771
|
if (status === "failed") {
|
|
@@ -7446,14 +7783,14 @@ async function runExport(projectArg, versionArg, opts) {
|
|
|
7446
7783
|
if (!res.ok) throw new Error(`Download failed (${res.status}): ${await res.text().catch(() => "")}`);
|
|
7447
7784
|
const buf = Buffer.from(await res.arrayBuffer());
|
|
7448
7785
|
(0, import_fs4.writeFileSync)(outFile, buf);
|
|
7449
|
-
console.log(
|
|
7450
|
-
if (job.decryption_key) console.log(
|
|
7451
|
-
console.log(
|
|
7452
|
-
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)."));
|
|
7453
7790
|
}
|
|
7454
7791
|
|
|
7455
7792
|
// src/commands/recipe-search.ts
|
|
7456
|
-
var
|
|
7793
|
+
var import_chalk36 = __toESM(require("chalk"));
|
|
7457
7794
|
var import_ora13 = __toESM(require("ora"));
|
|
7458
7795
|
init_caller();
|
|
7459
7796
|
|
|
@@ -7654,20 +7991,20 @@ async function runRecipeSearch(query, opts) {
|
|
|
7654
7991
|
return;
|
|
7655
7992
|
}
|
|
7656
7993
|
if (!hits.length) {
|
|
7657
|
-
console.log(
|
|
7658
|
-
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"));
|
|
7659
7996
|
return;
|
|
7660
7997
|
}
|
|
7661
7998
|
console.log();
|
|
7662
7999
|
for (const h of hits) {
|
|
7663
|
-
const pin = h.latest_revision ?
|
|
7664
|
-
const installs = h.install_count ?
|
|
7665
|
-
console.log(` ${
|
|
7666
|
-
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)}`);
|
|
7667
8004
|
}
|
|
7668
8005
|
console.log();
|
|
7669
|
-
console.log(
|
|
7670
|
-
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}`)}`));
|
|
7671
8008
|
console.log();
|
|
7672
8009
|
}
|
|
7673
8010
|
async function runRecipeShow(nameArg, opts) {
|
|
@@ -7693,8 +8030,8 @@ async function runRecipeShow(nameArg, opts) {
|
|
|
7693
8030
|
return;
|
|
7694
8031
|
}
|
|
7695
8032
|
console.log();
|
|
7696
|
-
console.log(`${
|
|
7697
|
-
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)}`);
|
|
7698
8035
|
if (recipe.summary) console.log(` ${recipe.summary}`);
|
|
7699
8036
|
const meta = [
|
|
7700
8037
|
head?.install_count !== void 0 ? `${head.install_count} install${head.install_count === 1 ? "" : "s"}` : null,
|
|
@@ -7702,7 +8039,7 @@ async function runRecipeShow(nameArg, opts) {
|
|
|
7702
8039
|
recipe.published_at ? `published ${recipe.published_at.slice(0, 10)}` : null,
|
|
7703
8040
|
`api version ${recipe.api_version}`
|
|
7704
8041
|
].filter(Boolean).join(" \xB7 ");
|
|
7705
|
-
console.log(
|
|
8042
|
+
console.log(import_chalk36.default.dim(` ${meta}`));
|
|
7706
8043
|
console.log();
|
|
7707
8044
|
if (wantsSection) {
|
|
7708
8045
|
if (opts.settings) printSection("settings", recipe.settings);
|
|
@@ -7716,34 +8053,34 @@ async function runRecipeShow(nameArg, opts) {
|
|
|
7716
8053
|
const rules = recipe.transformations?.rules?.length ?? 0;
|
|
7717
8054
|
const tables = recipe.transformations?.mapping_tables?.length ?? 0;
|
|
7718
8055
|
const keyTypes = recipe.auth?.key_types?.length ?? 0;
|
|
7719
|
-
if (hosts.length) console.log(` ${
|
|
7720
|
-
console.log(` ${
|
|
7721
|
-
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"}`);
|
|
7722
8059
|
if (rules || tables) {
|
|
7723
|
-
console.log(` ${
|
|
8060
|
+
console.log(` ${import_chalk36.default.dim("transforms ")} ${rules} rule${rules === 1 ? "" : "s"}${tables ? `, ${tables} mapping table${tables === 1 ? "" : "s"}` : ""}`);
|
|
7724
8061
|
}
|
|
7725
8062
|
if (recipe.settings.publish_openapi === true) {
|
|
7726
|
-
console.log(` ${
|
|
8063
|
+
console.log(` ${import_chalk36.default.dim("makes public ")} this API's spec`);
|
|
7727
8064
|
}
|
|
7728
8065
|
if (recipe.questions.length) {
|
|
7729
8066
|
console.log();
|
|
7730
|
-
console.log(
|
|
8067
|
+
console.log(import_chalk36.default.bold(" It will ask you for"));
|
|
7731
8068
|
for (const q of recipe.questions) {
|
|
7732
|
-
console.log(` ${q.prompt}${q.secret ?
|
|
7733
|
-
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}`));
|
|
7734
8071
|
}
|
|
7735
8072
|
}
|
|
7736
8073
|
if (recipe.requires_confirmation?.length) {
|
|
7737
8074
|
console.log();
|
|
7738
|
-
console.log(
|
|
8075
|
+
console.log(import_chalk36.default.yellow(" Needs your explicit consent at install time"));
|
|
7739
8076
|
for (const c of recipe.requires_confirmation) {
|
|
7740
|
-
console.log(` ${
|
|
7741
|
-
console.log(
|
|
8077
|
+
console.log(` ${import_chalk36.default.bold(c.kind)} ${c.value}`);
|
|
8078
|
+
console.log(import_chalk36.default.dim(` ${c.why}`));
|
|
7742
8079
|
}
|
|
7743
8080
|
}
|
|
7744
8081
|
console.log();
|
|
7745
|
-
console.log(
|
|
7746
|
-
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"));
|
|
7747
8084
|
console.log();
|
|
7748
8085
|
}
|
|
7749
8086
|
function describeCreates(keyTypes, routes, mcp, includeProxy = true) {
|
|
@@ -7753,45 +8090,45 @@ function describeCreates(keyTypes, routes, mcp, includeProxy = true) {
|
|
|
7753
8090
|
return parts.length ? parts.join(", ") : "nothing else";
|
|
7754
8091
|
}
|
|
7755
8092
|
function printSection(title, value) {
|
|
7756
|
-
console.log(
|
|
8093
|
+
console.log(import_chalk36.default.bold(` ${title}`));
|
|
7757
8094
|
console.log(indent(JSON.stringify(value ?? null, null, 2), " "));
|
|
7758
8095
|
console.log();
|
|
7759
8096
|
}
|
|
7760
8097
|
function printAuth(recipe) {
|
|
7761
8098
|
const a = recipe.auth ?? { app_client: null, provider: null, key_types: [] };
|
|
7762
|
-
console.log(
|
|
8099
|
+
console.log(import_chalk36.default.bold(" auth"));
|
|
7763
8100
|
if (a.provider) {
|
|
7764
|
-
console.log(` ${
|
|
7765
|
-
if (a.provider.scopes) console.log(` ${
|
|
7766
|
-
if (a.provider.target_server_token) console.log(` ${
|
|
7767
|
-
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)"));
|
|
7768
8105
|
} else {
|
|
7769
|
-
console.log(
|
|
8106
|
+
console.log(import_chalk36.default.dim(" no login provider \u2014 consumers call it with an API key"));
|
|
7770
8107
|
}
|
|
7771
8108
|
if (a.app_client) {
|
|
7772
|
-
console.log(` ${
|
|
8109
|
+
console.log(` ${import_chalk36.default.dim("login app ")} ${a.app_client.name ?? "(unnamed)"}`);
|
|
7773
8110
|
}
|
|
7774
8111
|
if (a.key_types?.length) {
|
|
7775
|
-
console.log(` ${
|
|
8112
|
+
console.log(` ${import_chalk36.default.dim("key types ")} ${a.key_types.map((k) => String(k.name)).join(", ")}`);
|
|
7776
8113
|
}
|
|
7777
8114
|
console.log();
|
|
7778
8115
|
}
|
|
7779
8116
|
function printTransforms(recipe) {
|
|
7780
8117
|
const rules = recipe.transformations?.rules ?? [];
|
|
7781
8118
|
const tables = recipe.transformations?.mapping_tables ?? [];
|
|
7782
|
-
console.log(
|
|
8119
|
+
console.log(import_chalk36.default.bold(` transforms (${rules.length})`));
|
|
7783
8120
|
for (const r of rules) {
|
|
7784
|
-
const off = r.enabled === false ?
|
|
7785
|
-
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}`);
|
|
7786
8123
|
console.log(indent(JSON.stringify(r.action ?? null, null, 2), " "));
|
|
7787
8124
|
if (r.condition) console.log(indent(`when ${JSON.stringify(r.condition)}`, " "));
|
|
7788
8125
|
}
|
|
7789
8126
|
if (tables.length) {
|
|
7790
8127
|
console.log();
|
|
7791
|
-
console.log(
|
|
8128
|
+
console.log(import_chalk36.default.bold(` mapping tables (${tables.length})`));
|
|
7792
8129
|
for (const t of tables) {
|
|
7793
8130
|
const entries2 = Array.isArray(t.entries) ? t.entries.length : 0;
|
|
7794
|
-
console.log(` ${
|
|
8131
|
+
console.log(` ${import_chalk36.default.bold(String(t.name ?? "(unnamed)"))} ${import_chalk36.default.dim(`${entries2} entr${entries2 === 1 ? "y" : "ies"}`)}`);
|
|
7795
8132
|
}
|
|
7796
8133
|
}
|
|
7797
8134
|
console.log();
|
|
@@ -7801,10 +8138,11 @@ function indent(text, pad) {
|
|
|
7801
8138
|
}
|
|
7802
8139
|
|
|
7803
8140
|
// src/commands/recipe-install.ts
|
|
7804
|
-
var
|
|
8141
|
+
var import_chalk37 = __toESM(require("chalk"));
|
|
7805
8142
|
init_types();
|
|
7806
8143
|
init_caller();
|
|
7807
8144
|
init_api();
|
|
8145
|
+
init_paging();
|
|
7808
8146
|
async function runRecipeInstall(nameArg, opts) {
|
|
7809
8147
|
const ref = parseRecipeRef(nameArg);
|
|
7810
8148
|
const assumeYes = !!opts.yes;
|
|
@@ -7817,7 +8155,7 @@ async function runRecipeInstall(nameArg, opts) {
|
|
|
7817
8155
|
const chosenTeamId = picked.chosen ? picked.teamId : void 0;
|
|
7818
8156
|
const teamName = picked.teamName;
|
|
7819
8157
|
console.log();
|
|
7820
|
-
console.log(`${
|
|
8158
|
+
console.log(`${import_chalk37.default.bold(pinned)}${import_chalk37.default.dim(` \u2014 by ${ref.handle}`)}`);
|
|
7821
8159
|
if (recipe.summary) console.log(` ${recipe.summary}`);
|
|
7822
8160
|
console.log();
|
|
7823
8161
|
let project;
|
|
@@ -7841,7 +8179,7 @@ async function runRecipeInstall(nameArg, opts) {
|
|
|
7841
8179
|
reuseTenant = chosenTenant.existing;
|
|
7842
8180
|
printSummary(recipe, project, tenant2, reuseTenant);
|
|
7843
8181
|
if (!assumeYes && !await confirmYesNo(" continue?", interactive)) {
|
|
7844
|
-
console.log(
|
|
8182
|
+
console.log(import_chalk37.default.yellow(" Cancelled \u2014 nothing was created."));
|
|
7845
8183
|
return;
|
|
7846
8184
|
}
|
|
7847
8185
|
}
|
|
@@ -7860,18 +8198,18 @@ async function runRecipeInstall(nameArg, opts) {
|
|
|
7860
8198
|
process.stdout.write(` ${step.label.padEnd(33)}`);
|
|
7861
8199
|
try {
|
|
7862
8200
|
const note = await step.run();
|
|
7863
|
-
console.log(`${
|
|
8201
|
+
console.log(`${import_chalk37.default.green("\u2713")}${note ? ` ${import_chalk37.default.dim(note)}` : ""}`);
|
|
7864
8202
|
} catch (err) {
|
|
7865
|
-
console.log(`${
|
|
8203
|
+
console.log(`${import_chalk37.default.red("\u2717")} ${import_chalk37.default.red(describeError(err))}`);
|
|
7866
8204
|
console.log();
|
|
7867
8205
|
if (step.n === 1) {
|
|
7868
|
-
console.log(
|
|
8206
|
+
console.log(import_chalk37.default.red(`stopped at step 1 of ${steps.length}. Nothing was created.`));
|
|
7869
8207
|
console.log("run the same command again once the problem above is fixed:");
|
|
7870
|
-
console.log(
|
|
8208
|
+
console.log(import_chalk37.default.cyan(` npx apiblaze install ${nameArg}`));
|
|
7871
8209
|
} else {
|
|
7872
|
-
console.log(
|
|
8210
|
+
console.log(import_chalk37.default.red(`stopped at step ${step.n} of ${steps.length}. ${project} exists but is not finished.`));
|
|
7873
8211
|
console.log("continue with:");
|
|
7874
|
-
console.log(
|
|
8212
|
+
console.log(import_chalk37.default.cyan(` npx apiblaze install ${nameArg} --into ${project}`));
|
|
7875
8213
|
}
|
|
7876
8214
|
console.log();
|
|
7877
8215
|
process.exit(1);
|
|
@@ -7881,9 +8219,9 @@ async function runRecipeInstall(nameArg, opts) {
|
|
|
7881
8219
|
const urls = installUrls(ctx);
|
|
7882
8220
|
console.log();
|
|
7883
8221
|
console.log(`done in ${secs}s.`);
|
|
7884
|
-
console.log(` ${
|
|
7885
|
-
if (recipe.settings.mcp_enabled === true) console.log(` ${
|
|
7886
|
-
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)}`);
|
|
7887
8225
|
console.log();
|
|
7888
8226
|
}
|
|
7889
8227
|
function buildSteps(inp) {
|
|
@@ -8136,12 +8474,15 @@ async function teamLabel(caller, teamId) {
|
|
|
8136
8474
|
}
|
|
8137
8475
|
async function owningTeam(caller, project, preferred) {
|
|
8138
8476
|
const has = async (teamId) => {
|
|
8139
|
-
const
|
|
8140
|
-
|
|
8141
|
-
|
|
8142
|
-
|
|
8143
|
-
|
|
8144
|
-
|
|
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);
|
|
8145
8486
|
};
|
|
8146
8487
|
if (await has(preferred)) return preferred;
|
|
8147
8488
|
if (caller.mode === "anon") return preferred;
|
|
@@ -8177,7 +8518,7 @@ async function nameIsFree(name, teamId) {
|
|
|
8177
8518
|
async function chooseProxyName(slug, asOpt, teamId, interactive) {
|
|
8178
8519
|
if (asOpt !== void 0) {
|
|
8179
8520
|
const name = normalizeName3(asOpt);
|
|
8180
|
-
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.`));
|
|
8181
8522
|
if (name.length < 3) throw new Error("A proxy name must be at least 3 characters (lowercase letters and digits only).");
|
|
8182
8523
|
const free = await nameIsFree(name, teamId);
|
|
8183
8524
|
if (free === false) throw new Error(`"${name}" is already taken. Pick another with --as.`);
|
|
@@ -8194,7 +8535,7 @@ async function chooseProxyName(slug, asOpt, teamId, interactive) {
|
|
|
8194
8535
|
}
|
|
8195
8536
|
if (!interactive) return suggested;
|
|
8196
8537
|
const { default: inquirer3 } = await import("inquirer");
|
|
8197
|
-
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]")}`);
|
|
8198
8539
|
for (; ; ) {
|
|
8199
8540
|
const { raw } = await inquirer3.prompt([{
|
|
8200
8541
|
type: "input",
|
|
@@ -8205,20 +8546,20 @@ async function chooseProxyName(slug, asOpt, teamId, interactive) {
|
|
|
8205
8546
|
}]);
|
|
8206
8547
|
const name = normalizeName3(raw);
|
|
8207
8548
|
if (name !== String(raw).trim()) {
|
|
8208
|
-
console.log(
|
|
8549
|
+
console.log(import_chalk37.default.yellow(` \u2192 using ${import_chalk37.default.bold(name)} (lowercase letters and digits only)`));
|
|
8209
8550
|
}
|
|
8210
8551
|
if (name.length < 3) {
|
|
8211
|
-
console.log(
|
|
8552
|
+
console.log(import_chalk37.default.yellow(" Name must be at least 3 characters.\n"));
|
|
8212
8553
|
continue;
|
|
8213
8554
|
}
|
|
8214
8555
|
const free = await nameIsFree(name, teamId);
|
|
8215
8556
|
if (free === false) {
|
|
8216
|
-
console.log(
|
|
8557
|
+
console.log(import_chalk37.default.yellow(` "${name}" is taken. Try another.
|
|
8217
8558
|
`));
|
|
8218
8559
|
continue;
|
|
8219
8560
|
}
|
|
8220
|
-
if (free === null) console.log(
|
|
8221
|
-
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`)}`);
|
|
8222
8563
|
return name;
|
|
8223
8564
|
}
|
|
8224
8565
|
}
|
|
@@ -8236,20 +8577,20 @@ function printSummary(recipe, project, tenant2, reuseTenant = false) {
|
|
|
8236
8577
|
const keyTypes = recipe.auth?.key_types?.length ?? 0;
|
|
8237
8578
|
const questions = reuseTenant ? 0 : recipe.questions?.length ?? 0;
|
|
8238
8579
|
const row = (label3, value, note) => {
|
|
8239
|
-
console.log(` ${
|
|
8580
|
+
console.log(` ${import_chalk37.default.dim(label3.padEnd(21))}${value}${note ? import_chalk37.default.dim(` ${note}`) : ""}`);
|
|
8240
8581
|
};
|
|
8241
8582
|
console.log();
|
|
8242
|
-
console.log(
|
|
8583
|
+
console.log(import_chalk37.default.bold(" This will:"));
|
|
8243
8584
|
console.log();
|
|
8244
|
-
row("create a proxy",
|
|
8245
|
-
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)");
|
|
8246
8587
|
if (hosts.length) row("sending requests to", hosts.join(", "));
|
|
8247
8588
|
row("and set up", describeCreates(keyTypes, routes, recipe.settings.mcp_enabled === true, false));
|
|
8248
8589
|
row("asking you for", questions ? `${questions} value${questions === 1 ? "" : "s"}` : "nothing");
|
|
8249
8590
|
if (reuseTenant) {
|
|
8250
8591
|
console.log();
|
|
8251
|
-
console.log(
|
|
8252
|
-
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."));
|
|
8253
8594
|
}
|
|
8254
8595
|
console.log();
|
|
8255
8596
|
}
|
|
@@ -8258,11 +8599,11 @@ function declineAll(items) {
|
|
|
8258
8599
|
const upstreams = items.filter((i) => i.kind === "upstream");
|
|
8259
8600
|
console.log();
|
|
8260
8601
|
for (const u of upstreams) {
|
|
8261
|
-
console.log(
|
|
8602
|
+
console.log(import_chalk37.default.yellow(` --yes: this proxy sends every request to ${import_chalk37.default.bold(u.value)}.`));
|
|
8262
8603
|
}
|
|
8263
8604
|
if (skippable.length) {
|
|
8264
|
-
console.log(
|
|
8265
|
-
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}`));
|
|
8266
8607
|
}
|
|
8267
8608
|
return new Set(skippable.map((i) => i.kind));
|
|
8268
8609
|
}
|
|
@@ -8280,14 +8621,14 @@ async function askConsent(items, interactive) {
|
|
|
8280
8621
|
const count = items.length;
|
|
8281
8622
|
const word = ["", "it", "both", "all three", "all four", "all five", "all six"][count] ?? `all ${count}`;
|
|
8282
8623
|
console.log();
|
|
8283
|
-
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."));
|
|
8284
8625
|
console.log();
|
|
8285
8626
|
for (const item of items) {
|
|
8286
|
-
console.log(` ${
|
|
8287
|
-
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)}`);
|
|
8288
8629
|
}
|
|
8289
8630
|
console.log();
|
|
8290
|
-
console.log(`Type "${
|
|
8631
|
+
console.log(`Type "${import_chalk37.default.bold("yes")}" to accept ${word}.`);
|
|
8291
8632
|
console.log("Press enter to install the recipe without them.");
|
|
8292
8633
|
let typed = "";
|
|
8293
8634
|
if (interactive) {
|
|
@@ -8299,7 +8640,7 @@ async function askConsent(items, interactive) {
|
|
|
8299
8640
|
for (const item of items) {
|
|
8300
8641
|
declined.add(item.kind === "transform" ? `transform:${item.value}` : `kind:${item.kind}`);
|
|
8301
8642
|
}
|
|
8302
|
-
console.log(
|
|
8643
|
+
console.log(import_chalk37.default.dim(` Installing without ${count} setting${count === 1 ? "" : "s"} \u2014 everything else is applied.`));
|
|
8303
8644
|
return declined;
|
|
8304
8645
|
}
|
|
8305
8646
|
async function askQuestions(questions, ctx, interactive) {
|
|
@@ -8312,13 +8653,13 @@ async function askQuestions(questions, ctx, interactive) {
|
|
|
8312
8653
|
const BOX = 64;
|
|
8313
8654
|
const title = "\u2500\u2500 from the publisher ";
|
|
8314
8655
|
console.log();
|
|
8315
|
-
console.log(` ${
|
|
8316
|
-
for (const line of fillContext(q.before, ctx).split("\n")) console.log(` ${
|
|
8317
|
-
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))}`);
|
|
8318
8659
|
}
|
|
8319
8660
|
if (fromEnv !== void 0 && fromEnv !== "") {
|
|
8320
8661
|
answers[q.id] = fromEnv;
|
|
8321
|
-
console.log(` ${q.prompt}: ${
|
|
8662
|
+
console.log(` ${q.prompt}: ${import_chalk37.default.dim(`(from ${envName})`)}`);
|
|
8322
8663
|
continue;
|
|
8323
8664
|
}
|
|
8324
8665
|
if (!interactive) {
|
|
@@ -8328,7 +8669,7 @@ async function askQuestions(questions, ctx, interactive) {
|
|
|
8328
8669
|
);
|
|
8329
8670
|
}
|
|
8330
8671
|
const { default: inquirer3 } = await import("inquirer");
|
|
8331
|
-
if (q.example && !q.secret) console.log(
|
|
8672
|
+
if (q.example && !q.secret) console.log(import_chalk37.default.dim(` e.g. ${q.example}`));
|
|
8332
8673
|
const { value } = await inquirer3.prompt([{
|
|
8333
8674
|
// `secret: true` masks the input — the Google client secret is typed once,
|
|
8334
8675
|
// goes to provider creation, and is never echoed or stored.
|
|
@@ -8362,30 +8703,30 @@ async function confirmReinstall(recipe, project, tenant2, interactive) {
|
|
|
8362
8703
|
const tables = recipe.transformations?.mapping_tables?.length ?? 0;
|
|
8363
8704
|
const routes = countRoutes(recipe.openapi);
|
|
8364
8705
|
const keyTypes = recipe.auth?.key_types?.length ?? 0;
|
|
8365
|
-
console.log(`This installs ${
|
|
8706
|
+
console.log(`This installs ${import_chalk37.default.bold(`${recipe.name}@${recipe.revision}`)} over ${import_chalk37.default.bold(project)}.`);
|
|
8366
8707
|
console.log();
|
|
8367
|
-
console.log(` ${
|
|
8708
|
+
console.log(` ${import_chalk37.default.dim("settings ".padEnd(16))}replaced`);
|
|
8368
8709
|
if (rules || tables) {
|
|
8369
|
-
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`);
|
|
8370
8711
|
}
|
|
8371
8712
|
if (routes) {
|
|
8372
|
-
console.log(` ${
|
|
8713
|
+
console.log(` ${import_chalk37.default.dim("api spec ".padEnd(16))}replaced, ${routes} routes regenerated${recipe.settings.mcp_enabled === true ? ", MCP rebuilt" : ""}`);
|
|
8373
8714
|
}
|
|
8374
8715
|
if (keyTypes) {
|
|
8375
|
-
console.log(` ${
|
|
8716
|
+
console.log(` ${import_chalk37.default.dim("key types ".padEnd(16))}${keyTypes} added if missing; existing types left alone`);
|
|
8376
8717
|
}
|
|
8377
8718
|
console.log();
|
|
8378
|
-
console.log(` ${
|
|
8719
|
+
console.log(` ${import_chalk37.default.dim("unchanged ".padEnd(16))}your API keys, your proxy URL, your users,`);
|
|
8379
8720
|
console.log(` ${" ".repeat(16)}and any tenant other than ${tenant2}`);
|
|
8380
8721
|
console.log();
|
|
8381
|
-
console.log(
|
|
8722
|
+
console.log(import_chalk37.default.yellow("This cannot be undone."));
|
|
8382
8723
|
if (!interactive) {
|
|
8383
8724
|
throw new Error("Reinstall needs a typed confirmation and there is no terminal to ask on.");
|
|
8384
8725
|
}
|
|
8385
8726
|
const { default: inquirer3 } = await import("inquirer");
|
|
8386
8727
|
const { typed } = await inquirer3.prompt([{ type: "input", name: "typed", message: `Type the proxy name (${project}) to continue:` }]);
|
|
8387
8728
|
if (String(typed ?? "").trim() !== project) {
|
|
8388
|
-
console.log(
|
|
8729
|
+
console.log(import_chalk37.default.yellow("Cancelled \u2014 nothing was changed."));
|
|
8389
8730
|
process.exit(0);
|
|
8390
8731
|
}
|
|
8391
8732
|
}
|
|
@@ -8393,7 +8734,7 @@ function requireCallerForInstall() {
|
|
|
8393
8734
|
try {
|
|
8394
8735
|
return requireCaller();
|
|
8395
8736
|
} catch {
|
|
8396
|
-
console.error(
|
|
8737
|
+
console.error(import_chalk37.default.red(
|
|
8397
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)."
|
|
8398
8739
|
));
|
|
8399
8740
|
process.exit(1);
|
|
@@ -8429,7 +8770,7 @@ async function chooseTeam(caller, optTeam, interactive) {
|
|
|
8429
8770
|
message: " which team should own this proxy?",
|
|
8430
8771
|
default: active.teamId,
|
|
8431
8772
|
choices: teams.map((t) => ({
|
|
8432
|
-
name: t.teamId === active.teamId ? `${t.name} ${
|
|
8773
|
+
name: t.teamId === active.teamId ? `${t.name} ${import_chalk37.default.dim("(active)")}` : t.name,
|
|
8433
8774
|
value: t.teamId
|
|
8434
8775
|
}))
|
|
8435
8776
|
}]);
|
|
@@ -8457,10 +8798,10 @@ async function chooseTenant(caller, project, teamId, optTenant, interactive) {
|
|
|
8457
8798
|
message: " where should this proxy's users live?",
|
|
8458
8799
|
default: "__new__",
|
|
8459
8800
|
choices: [
|
|
8460
|
-
{ name: `create ${fresh} ${
|
|
8461
|
-
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")),
|
|
8462
8803
|
...owned.map((t2) => ({
|
|
8463
|
-
name: `${t2} ${
|
|
8804
|
+
name: `${t2} ${import_chalk37.default.dim("(existing \u2014 its own login is kept)")}`,
|
|
8464
8805
|
value: t2
|
|
8465
8806
|
}))
|
|
8466
8807
|
]
|
|
@@ -8478,16 +8819,19 @@ async function chooseTenant(caller, project, teamId, optTenant, interactive) {
|
|
|
8478
8819
|
return { tenant: t, existing: false };
|
|
8479
8820
|
}
|
|
8480
8821
|
async function teamTenants(caller, teamId) {
|
|
8481
|
-
const
|
|
8482
|
-
|
|
8483
|
-
|
|
8484
|
-
|
|
8485
|
-
|
|
8486
|
-
|
|
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);
|
|
8487
8831
|
}
|
|
8488
8832
|
|
|
8489
8833
|
// src/commands/recipe-publish.ts
|
|
8490
|
-
var
|
|
8834
|
+
var import_chalk38 = __toESM(require("chalk"));
|
|
8491
8835
|
var import_ora14 = __toESM(require("ora"));
|
|
8492
8836
|
init_caller();
|
|
8493
8837
|
init_auth();
|
|
@@ -8497,15 +8841,15 @@ async function runRecipePublish(projectArg, opts) {
|
|
|
8497
8841
|
try {
|
|
8498
8842
|
resolved = resolveRecipeName(opts.as, creds?.githubHandle, normalizeName3(projectArg));
|
|
8499
8843
|
} catch (err) {
|
|
8500
|
-
console.error(
|
|
8844
|
+
console.error(import_chalk38.default.red(err instanceof Error ? err.message : String(err)));
|
|
8501
8845
|
process.exitCode = 1;
|
|
8502
8846
|
return;
|
|
8503
8847
|
}
|
|
8504
8848
|
const ref = resolved.ref;
|
|
8505
8849
|
if (resolved.derivedHandle) {
|
|
8506
|
-
console.log(
|
|
8850
|
+
console.log(import_chalk38.default.dim(` publishing as ${import_chalk38.default.bold(ref.name)}`));
|
|
8507
8851
|
if (resolved.normalizedFrom) {
|
|
8508
|
-
console.log(
|
|
8852
|
+
console.log(import_chalk38.default.dim(` (your GitHub login is ${resolved.normalizedFrom}; apiblaze names are letters and digits only)`));
|
|
8509
8853
|
}
|
|
8510
8854
|
}
|
|
8511
8855
|
const interactive = !!process.stdin.isTTY && !opts.json;
|
|
@@ -8524,7 +8868,7 @@ async function runRecipePublish(projectArg, opts) {
|
|
|
8524
8868
|
let tenant2 = opts.tenant;
|
|
8525
8869
|
if (!prep.tenant_used && prep.tenants.length > 1) {
|
|
8526
8870
|
if (!interactive) {
|
|
8527
|
-
console.error(
|
|
8871
|
+
console.error(import_chalk38.default.red(
|
|
8528
8872
|
`${proj2.projectId} has ${prep.tenants.length} tenants (${prep.tenants.join(", ")}). Pass --tenant <slug> to say which auth setup the recipe carries.`
|
|
8529
8873
|
));
|
|
8530
8874
|
process.exit(1);
|
|
@@ -8540,7 +8884,7 @@ async function runRecipePublish(projectArg, opts) {
|
|
|
8540
8884
|
prep = await prepare(caller, proj2.projectId, proj2.apiVersion, ref.name, tenant2);
|
|
8541
8885
|
}
|
|
8542
8886
|
if (!prep.name_available) {
|
|
8543
|
-
console.error(
|
|
8887
|
+
console.error(import_chalk38.default.red(`${ref.name} belongs to another team, so you cannot publish a revision of it.`));
|
|
8544
8888
|
process.exit(1);
|
|
8545
8889
|
}
|
|
8546
8890
|
if (prep.envelope_violations.length) {
|
|
@@ -8550,13 +8894,13 @@ async function runRecipePublish(projectArg, opts) {
|
|
|
8550
8894
|
if (prep.dropped.length) {
|
|
8551
8895
|
console.log();
|
|
8552
8896
|
console.log(`${prep.dropped.length} setting${prep.dropped.length === 1 ? " was" : "s were"} not included, because they are not on the publish list:`);
|
|
8553
|
-
for (const d of prep.dropped) console.log(
|
|
8897
|
+
for (const d of prep.dropped) console.log(import_chalk38.default.dim(` ${d}`));
|
|
8554
8898
|
if (prep.hidden_tables_excluded) {
|
|
8555
|
-
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.)`));
|
|
8556
8900
|
}
|
|
8557
8901
|
console.log();
|
|
8558
8902
|
if (!opts.yes && !await confirm(" continue without them?", interactive)) {
|
|
8559
|
-
console.log(
|
|
8903
|
+
console.log(import_chalk38.default.yellow("Cancelled."));
|
|
8560
8904
|
return;
|
|
8561
8905
|
}
|
|
8562
8906
|
}
|
|
@@ -8569,12 +8913,12 @@ async function runRecipePublish(projectArg, opts) {
|
|
|
8569
8913
|
console.log();
|
|
8570
8914
|
for (const f of prep.findings) printFinding(f);
|
|
8571
8915
|
console.log();
|
|
8572
|
-
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."));
|
|
8573
8917
|
process.exit(1);
|
|
8574
8918
|
}
|
|
8575
8919
|
if (!interactive) {
|
|
8576
8920
|
for (const f of prep.findings) printFinding(f);
|
|
8577
|
-
console.error(
|
|
8921
|
+
console.error(import_chalk38.default.red("\nThese need a decision and there is no terminal to ask on."));
|
|
8578
8922
|
process.exit(1);
|
|
8579
8923
|
}
|
|
8580
8924
|
const { default: inquirer3 } = await import("inquirer");
|
|
@@ -8595,10 +8939,10 @@ async function runRecipePublish(projectArg, opts) {
|
|
|
8595
8939
|
if (verdict === "ask") promotions.push(await askPromotion(f.path));
|
|
8596
8940
|
}
|
|
8597
8941
|
}
|
|
8598
|
-
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.`));
|
|
8599
8943
|
const totalQuestions = prep.question_ids.length + promotions.length;
|
|
8600
8944
|
if (totalQuestions > 5) {
|
|
8601
|
-
console.log(
|
|
8945
|
+
console.log(import_chalk38.default.yellow(`
|
|
8602
8946
|
\u26A0 installers will have to supply ${totalQuestions} values before this works.`));
|
|
8603
8947
|
}
|
|
8604
8948
|
const visibility = opts.private ? "private" : "public";
|
|
@@ -8606,11 +8950,11 @@ async function runRecipePublish(projectArg, opts) {
|
|
|
8606
8950
|
if (visibility === "public" && prep.spec_will_become_public) {
|
|
8607
8951
|
console.log();
|
|
8608
8952
|
console.log("Publishing publicly also makes this API's spec readable at");
|
|
8609
|
-
console.log(` ${
|
|
8953
|
+
console.log(` ${import_chalk38.default.bold(`https://${proj2.projectId}.abz.run/${proj2.apiVersion}/openapi.json`)}`);
|
|
8610
8954
|
console.log("It is currently private.");
|
|
8611
8955
|
confirmSpecPublic = opts.yes ? true : await confirm("Continue?", interactive);
|
|
8612
8956
|
if (!confirmSpecPublic) {
|
|
8613
|
-
console.log(
|
|
8957
|
+
console.log(import_chalk38.default.yellow("Cancelled \u2014 nothing was published."));
|
|
8614
8958
|
return;
|
|
8615
8959
|
}
|
|
8616
8960
|
}
|
|
@@ -8636,7 +8980,7 @@ async function runRecipePublish(projectArg, opts) {
|
|
|
8636
8980
|
},
|
|
8637
8981
|
summary: `Publish ${proj2.projectId} as ${ref.name} (${visibility})`
|
|
8638
8982
|
});
|
|
8639
|
-
spin2?.succeed(`Published ${
|
|
8983
|
+
spin2?.succeed(`Published ${import_chalk38.default.bold(`${out.name}@${out.revision}`)} (${out.visibility}).`);
|
|
8640
8984
|
} catch (err) {
|
|
8641
8985
|
spin2?.fail("Publish failed.");
|
|
8642
8986
|
throw err;
|
|
@@ -8645,15 +8989,15 @@ async function runRecipePublish(projectArg, opts) {
|
|
|
8645
8989
|
console.log(JSON.stringify(out));
|
|
8646
8990
|
return;
|
|
8647
8991
|
}
|
|
8648
|
-
console.log(` ${
|
|
8649
|
-
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`);
|
|
8650
8994
|
console.log();
|
|
8651
8995
|
if (out.visibility === "public") {
|
|
8652
|
-
console.log(
|
|
8996
|
+
console.log(import_chalk38.default.dim(` Anyone can now run: ${import_chalk38.default.cyan(`npx apiblaze install ${out.name}`)}`));
|
|
8653
8997
|
} else {
|
|
8654
|
-
console.log(
|
|
8998
|
+
console.log(import_chalk38.default.dim(" Private \u2014 only members of this team can see or install it."));
|
|
8655
8999
|
}
|
|
8656
|
-
console.log(
|
|
9000
|
+
console.log(import_chalk38.default.dim(` Pull it back with: ${import_chalk38.default.cyan(`npx apiblaze withdraw ${out.name}@${out.revision}`)}`));
|
|
8657
9001
|
console.log();
|
|
8658
9002
|
}
|
|
8659
9003
|
async function prepare(caller, projectId, apiVersion, name, tenant2) {
|
|
@@ -8666,23 +9010,23 @@ async function prepare(caller, projectId, apiVersion, name, tenant2) {
|
|
|
8666
9010
|
}
|
|
8667
9011
|
function printFinding(f) {
|
|
8668
9012
|
const preview = (f.preview ?? "").replace(/\s+/g, " ").trim();
|
|
8669
|
-
console.log(` ${
|
|
8670
|
-
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)}`);
|
|
8671
9015
|
}
|
|
8672
9016
|
function printEnvelopeViolations(violations) {
|
|
8673
9017
|
console.log();
|
|
8674
|
-
console.error(
|
|
9018
|
+
console.error(import_chalk38.default.red("This proxy has transform rules a recipe cannot carry."));
|
|
8675
9019
|
console.log();
|
|
8676
9020
|
for (const v of violations) {
|
|
8677
|
-
console.log(` ${
|
|
9021
|
+
console.log(` ${import_chalk38.default.bold(v.rule)} ${import_chalk38.default.dim(`(${v.kind}s ${v.field})`)}`);
|
|
8678
9022
|
console.log(` ${v.why}`);
|
|
8679
9023
|
}
|
|
8680
9024
|
console.log();
|
|
8681
9025
|
console.log("A recipe runs in a stranger's account, after apiblaze has attached their end user's");
|
|
8682
9026
|
console.log("real third-party token, so two rules are absolute:");
|
|
8683
|
-
console.log(` ${
|
|
9027
|
+
console.log(` ${import_chalk38.default.dim("\u2022")} a rule may never READ Authorization, Cookie, or the third-party token headers \u2014`);
|
|
8684
9028
|
console.log(" those hold a live credential that works at the provider without apiblaze;");
|
|
8685
|
-
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`);
|
|
8686
9030
|
console.log(" those tell the installer's own backend who is calling.");
|
|
8687
9031
|
console.log();
|
|
8688
9032
|
console.log("Writing Authorization from a question or a fixed value is fine \u2014 that is the");
|
|
@@ -8700,7 +9044,7 @@ async function askPromotion(path11) {
|
|
|
8700
9044
|
{ type: "input", name: "id", message: " id:", default: suggestedId }
|
|
8701
9045
|
]);
|
|
8702
9046
|
const id = String(answers.id || suggestedId).toLowerCase().replace(/[^a-z0-9_]/g, "_");
|
|
8703
|
-
console.log(
|
|
9047
|
+
console.log(import_chalk38.default.dim(` \u2192 replaced with {{${id}}}`));
|
|
8704
9048
|
return {
|
|
8705
9049
|
path: path11,
|
|
8706
9050
|
id,
|
|
@@ -8719,7 +9063,7 @@ async function confirm(message, interactive) {
|
|
|
8719
9063
|
async function runRecipeWithdraw(nameArg, opts) {
|
|
8720
9064
|
const ref = parseRecipeRef(nameArg);
|
|
8721
9065
|
if (ref.revision === void 0) {
|
|
8722
|
-
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.`));
|
|
8723
9067
|
process.exit(1);
|
|
8724
9068
|
}
|
|
8725
9069
|
const interactive = !!process.stdin.isTTY && !opts.json;
|
|
@@ -8739,11 +9083,11 @@ async function runRecipeWithdraw(nameArg, opts) {
|
|
|
8739
9083
|
const { default: inquirer3 } = await import("inquirer");
|
|
8740
9084
|
const { typed } = await inquirer3.prompt([{ type: "input", name: "typed", message: `type the recipe name to confirm (${ref.name}):` }]);
|
|
8741
9085
|
if (String(typed ?? "").trim() !== ref.name) {
|
|
8742
|
-
console.log(
|
|
9086
|
+
console.log(import_chalk38.default.yellow("Cancelled \u2014 nothing was withdrawn."));
|
|
8743
9087
|
return;
|
|
8744
9088
|
}
|
|
8745
9089
|
} else {
|
|
8746
|
-
console.error(
|
|
9090
|
+
console.error(import_chalk38.default.red("Withdrawing needs a typed confirmation and there is no terminal to ask on."));
|
|
8747
9091
|
process.exit(1);
|
|
8748
9092
|
}
|
|
8749
9093
|
const spinner = !opts.json ? (0, import_ora14.default)("Withdrawing...").start() : null;
|
|
@@ -8772,33 +9116,33 @@ async function runRecipeWithdraw(nameArg, opts) {
|
|
|
8772
9116
|
}
|
|
8773
9117
|
|
|
8774
9118
|
// src/commands/logs.ts
|
|
8775
|
-
var
|
|
9119
|
+
var import_chalk40 = __toESM(require("chalk"));
|
|
8776
9120
|
init_admin();
|
|
8777
9121
|
init_auth();
|
|
8778
9122
|
init_resolve();
|
|
8779
9123
|
|
|
8780
9124
|
// src/lib/log-view.ts
|
|
8781
|
-
var
|
|
9125
|
+
var import_chalk39 = __toESM(require("chalk"));
|
|
8782
9126
|
var readline2 = __toESM(require("readline"));
|
|
8783
9127
|
function statusColor(status) {
|
|
8784
|
-
if (status < 300) return
|
|
8785
|
-
if (status < 400) return
|
|
8786
|
-
if (status < 500) return
|
|
8787
|
-
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;
|
|
8788
9132
|
}
|
|
8789
9133
|
function methodColor(method) {
|
|
8790
9134
|
switch ((method || "").toUpperCase()) {
|
|
8791
9135
|
case "GET":
|
|
8792
|
-
return
|
|
9136
|
+
return import_chalk39.default.green;
|
|
8793
9137
|
case "POST":
|
|
8794
|
-
return
|
|
9138
|
+
return import_chalk39.default.blue;
|
|
8795
9139
|
case "PUT":
|
|
8796
9140
|
case "PATCH":
|
|
8797
|
-
return
|
|
9141
|
+
return import_chalk39.default.yellow;
|
|
8798
9142
|
case "DELETE":
|
|
8799
|
-
return
|
|
9143
|
+
return import_chalk39.default.red;
|
|
8800
9144
|
default:
|
|
8801
|
-
return
|
|
9145
|
+
return import_chalk39.default.white;
|
|
8802
9146
|
}
|
|
8803
9147
|
}
|
|
8804
9148
|
function clock(ts) {
|
|
@@ -8810,12 +9154,12 @@ function routeCell(route) {
|
|
|
8810
9154
|
return (r.length > ROUTE_W ? `\u2026${r.slice(-(ROUTE_W - 1))}` : r).padEnd(ROUTE_W);
|
|
8811
9155
|
}
|
|
8812
9156
|
function formatRow(r) {
|
|
8813
|
-
const time =
|
|
9157
|
+
const time = import_chalk39.default.dim(clock(r.ts));
|
|
8814
9158
|
const method = methodColor(r.method)(String(r.method || "\u2014").padEnd(6));
|
|
8815
9159
|
const route = routeCell(String(r.route || "\u2014"));
|
|
8816
9160
|
const status = statusColor(r.status)(String(r.status ?? "\u2014").padStart(3));
|
|
8817
|
-
const ms =
|
|
8818
|
-
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}`) : "";
|
|
8819
9163
|
return `${time} ${method} ${route} ${status} ${ms}${step}`;
|
|
8820
9164
|
}
|
|
8821
9165
|
function printRow(r, json) {
|
|
@@ -8830,15 +9174,15 @@ var WindowNotices = class {
|
|
|
8830
9174
|
note(res) {
|
|
8831
9175
|
if (res.history_unavailable && !this.saidUnavailable) {
|
|
8832
9176
|
this.saidUnavailable = true;
|
|
8833
|
-
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."));
|
|
8834
9178
|
}
|
|
8835
9179
|
if ((res.history_markers?.length ?? 0) > 0 && !this.saidGap) {
|
|
8836
9180
|
this.saidGap = true;
|
|
8837
|
-
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."));
|
|
8838
9182
|
}
|
|
8839
9183
|
if ((res.dropped_this_hour ?? 0) > 0 && !this.saidDropped) {
|
|
8840
9184
|
this.saidDropped = true;
|
|
8841
|
-
console.error(
|
|
9185
|
+
console.error(import_chalk39.default.yellow(`\u26A0 volume ceiling \u2014 ${res.dropped_this_hour} rows dropped this hour; errors keep logging.`));
|
|
8842
9186
|
}
|
|
8843
9187
|
}
|
|
8844
9188
|
};
|
|
@@ -8897,13 +9241,13 @@ async function runTailSession(opts) {
|
|
|
8897
9241
|
if (!held.length && !heldRowsDropped && !heldNotesDropped) return;
|
|
8898
9242
|
const rows = held.filter((h) => h.kind === "row").length;
|
|
8899
9243
|
const total = rows + heldRowsDropped;
|
|
8900
|
-
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`));
|
|
8901
9245
|
for (const h of held) {
|
|
8902
9246
|
if (h.kind === "row") printRow(h.row, opts.json);
|
|
8903
9247
|
else console.error(h.msg);
|
|
8904
9248
|
}
|
|
8905
9249
|
if (heldRowsDropped || heldNotesDropped) {
|
|
8906
|
-
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`));
|
|
8907
9251
|
}
|
|
8908
9252
|
held.length = 0;
|
|
8909
9253
|
heldRowsDropped = 0;
|
|
@@ -8917,7 +9261,7 @@ async function runTailSession(opts) {
|
|
|
8917
9261
|
activeSocket?.close();
|
|
8918
9262
|
} catch {
|
|
8919
9263
|
}
|
|
8920
|
-
if (!opts.json) console.log(
|
|
9264
|
+
if (!opts.json) console.log(import_chalk39.default.dim("\nStopped."));
|
|
8921
9265
|
process.exit(0);
|
|
8922
9266
|
}
|
|
8923
9267
|
function onKeypress(_s, key) {
|
|
@@ -8945,17 +9289,17 @@ async function runTailSession(opts) {
|
|
|
8945
9289
|
async function openInspector() {
|
|
8946
9290
|
if (paused) return;
|
|
8947
9291
|
if (recent.length === 0) {
|
|
8948
|
-
console.log(
|
|
9292
|
+
console.log(import_chalk39.default.dim(" nothing captured yet \u2014 waiting for a request\u2026"));
|
|
8949
9293
|
return;
|
|
8950
9294
|
}
|
|
8951
9295
|
paused = true;
|
|
8952
9296
|
unbindKeys();
|
|
8953
9297
|
try {
|
|
8954
9298
|
const { default: inquirer3 } = await import("inquirer");
|
|
8955
|
-
console.log(
|
|
9299
|
+
console.log(import_chalk39.default.dim("\n\u2500\u2500\u2500 paused \xB7 still capturing in the background \u2500\u2500\u2500"));
|
|
8956
9300
|
for (; ; ) {
|
|
8957
9301
|
const choices = recent.slice(-TAIL_INSPECT_CHOICES).reverse().map((r) => ({ name: formatRow(r), value: r.request_id }));
|
|
8958
|
-
choices.push({ name:
|
|
9302
|
+
choices.push({ name: import_chalk39.default.dim("\u2039resume tail\u203A"), value: "__resume__" });
|
|
8959
9303
|
const { picked } = await inquirer3.prompt([{
|
|
8960
9304
|
type: "list",
|
|
8961
9305
|
name: "picked",
|
|
@@ -8966,12 +9310,12 @@ async function runTailSession(opts) {
|
|
|
8966
9310
|
if (picked === "__resume__") break;
|
|
8967
9311
|
const row = recent.find((r) => r.request_id === picked);
|
|
8968
9312
|
if (row) printDetail(row, opts.detail);
|
|
8969
|
-
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"));
|
|
8970
9314
|
}
|
|
8971
9315
|
} finally {
|
|
8972
9316
|
paused = false;
|
|
8973
9317
|
flushHeld();
|
|
8974
|
-
console.log(
|
|
9318
|
+
console.log(import_chalk39.default.dim("\u2500\u2500\u2500 resumed \u2500\u2500\u2500"));
|
|
8975
9319
|
bindKeys();
|
|
8976
9320
|
}
|
|
8977
9321
|
}
|
|
@@ -8981,12 +9325,12 @@ async function runTailSession(opts) {
|
|
|
8981
9325
|
if (reconnects >= MAX_RECONNECTS) {
|
|
8982
9326
|
paused = false;
|
|
8983
9327
|
flushHeld();
|
|
8984
|
-
console.error(
|
|
9328
|
+
console.error(import_chalk39.default.red(`Connection lost \u2014 giving up after repeated reconnects${lastFailure ? ` (last error: ${lastFailure})` : ""}.`));
|
|
8985
9329
|
process.exit(1);
|
|
8986
9330
|
}
|
|
8987
9331
|
const delay = Math.min(1e3 * 2 ** reconnects, 15e3);
|
|
8988
9332
|
reconnects++;
|
|
8989
|
-
diag(
|
|
9333
|
+
diag(import_chalk39.default.dim(`disconnected${lastFailure ? ` (${lastFailure})` : ""} \u2014 reconnecting in ${Math.round(delay / 1e3)}s\u2026`));
|
|
8990
9334
|
setTimeout(() => {
|
|
8991
9335
|
if (!closed) void connect();
|
|
8992
9336
|
}, delay);
|
|
@@ -8997,7 +9341,7 @@ async function runTailSession(opts) {
|
|
|
8997
9341
|
url = await opts.mintTicket();
|
|
8998
9342
|
} catch (err) {
|
|
8999
9343
|
if (!everConnected) {
|
|
9000
|
-
console.error(
|
|
9344
|
+
console.error(import_chalk39.default.red(`Could not start tail: ${err.message}`));
|
|
9001
9345
|
process.exit(1);
|
|
9002
9346
|
}
|
|
9003
9347
|
lastFailure = `ticket mint failed: ${err.message}`;
|
|
@@ -9010,7 +9354,7 @@ async function runTailSession(opts) {
|
|
|
9010
9354
|
} catch (err) {
|
|
9011
9355
|
lastFailure = `bad tail URL: ${err.message}`;
|
|
9012
9356
|
if (!everConnected) {
|
|
9013
|
-
console.error(
|
|
9357
|
+
console.error(import_chalk39.default.red(`Could not start tail: ${lastFailure}`));
|
|
9014
9358
|
process.exit(1);
|
|
9015
9359
|
}
|
|
9016
9360
|
scheduleReconnect();
|
|
@@ -9025,9 +9369,9 @@ async function runTailSession(opts) {
|
|
|
9025
9369
|
lastInbound = Date.now();
|
|
9026
9370
|
lastFailure = "";
|
|
9027
9371
|
if (isReconnect) {
|
|
9028
|
-
diag(
|
|
9372
|
+
diag(import_chalk39.default.dim("reconnected \u2014 requests during the gap were not replayed (browse them with `logs list`)."));
|
|
9029
9373
|
} else {
|
|
9030
|
-
diag(
|
|
9374
|
+
diag(import_chalk39.default.dim(interactive ? "[Enter] inspect a request \xB7 [Ctrl+C] stop\n" : "Ctrl+C to stop."));
|
|
9031
9375
|
}
|
|
9032
9376
|
bindKeys();
|
|
9033
9377
|
pingTimer = setInterval(() => {
|
|
@@ -9054,7 +9398,7 @@ async function runTailSession(opts) {
|
|
|
9054
9398
|
return;
|
|
9055
9399
|
}
|
|
9056
9400
|
if (frame?.type === "ceiling") {
|
|
9057
|
-
diag(
|
|
9401
|
+
diag(import_chalk39.default.yellow(
|
|
9058
9402
|
`\u26A0 volume ceiling \u2014 ${Number(frame.dropped_this_hour) || 0} rows dropped this hour; errors keep streaming.`
|
|
9059
9403
|
));
|
|
9060
9404
|
return;
|
|
@@ -9121,7 +9465,7 @@ function groupIntoDays(rows) {
|
|
|
9121
9465
|
return order;
|
|
9122
9466
|
}
|
|
9123
9467
|
var SCAN_LIMIT = 200;
|
|
9124
|
-
var
|
|
9468
|
+
var PAGE_LIMIT2 = 50;
|
|
9125
9469
|
var JSON_PAGE_LIMIT = 500;
|
|
9126
9470
|
var MAX_EMPTY_HOPS = 100;
|
|
9127
9471
|
async function fetchPage(fetchWindow3, params, notices) {
|
|
@@ -9134,10 +9478,10 @@ async function fetchPage(fetchWindow3, params, notices) {
|
|
|
9134
9478
|
return { rows, nextCursor: res.next_cursor ?? void 0 };
|
|
9135
9479
|
}
|
|
9136
9480
|
if (hop >= MAX_EMPTY_HOPS) {
|
|
9137
|
-
console.error(
|
|
9481
|
+
console.error(import_chalk39.default.yellow("\u26A0 stopped after scanning a long empty span \u2014 older rows may still exist."));
|
|
9138
9482
|
return { rows: [], nextCursor: res.next_cursor, fuseTripped: true };
|
|
9139
9483
|
}
|
|
9140
|
-
if (hop === 0) console.error(
|
|
9484
|
+
if (hop === 0) console.error(import_chalk39.default.dim("searching older history\u2026"));
|
|
9141
9485
|
cursor = res.next_cursor;
|
|
9142
9486
|
}
|
|
9143
9487
|
}
|
|
@@ -9159,7 +9503,7 @@ async function runPlainList(opts, notices) {
|
|
|
9159
9503
|
let fuseTripped = false;
|
|
9160
9504
|
while (rows.length < PLAIN_MAX_ROWS) {
|
|
9161
9505
|
const page = await fetchPage(opts.fetchWindow, {
|
|
9162
|
-
limit: Math.min(
|
|
9506
|
+
limit: Math.min(PAGE_LIMIT2, PLAIN_MAX_ROWS - rows.length),
|
|
9163
9507
|
errorsOnly: opts.errorsOnly,
|
|
9164
9508
|
cursor
|
|
9165
9509
|
}, notices);
|
|
@@ -9177,18 +9521,18 @@ async function runPlainList(opts, notices) {
|
|
|
9177
9521
|
}
|
|
9178
9522
|
}
|
|
9179
9523
|
if (rows.length === 0) {
|
|
9180
|
-
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."));
|
|
9181
9525
|
return;
|
|
9182
9526
|
}
|
|
9183
9527
|
const now = Date.now();
|
|
9184
9528
|
for (const d of groupIntoDays(rows)) {
|
|
9185
|
-
console.log(
|
|
9529
|
+
console.log(import_chalk39.default.bold(dayLabel(d.dayMs, now)));
|
|
9186
9530
|
for (const r of d.rows) console.log(` ${formatRow(r)}`);
|
|
9187
9531
|
console.log("");
|
|
9188
9532
|
}
|
|
9189
9533
|
const errs = rows.filter((r) => r.status >= 400).length;
|
|
9190
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" : "";
|
|
9191
|
-
console.log(
|
|
9535
|
+
console.log(import_chalk39.default.dim(
|
|
9192
9536
|
`${rows.length} request${rows.length === 1 ? "" : "s"} \xB7 ${errs} error${errs === 1 ? "" : "s"}${tail}`
|
|
9193
9537
|
));
|
|
9194
9538
|
}
|
|
@@ -9207,7 +9551,7 @@ async function runHistoryBrowser(opts) {
|
|
|
9207
9551
|
for (const r of page.rows) console.log(JSON.stringify(r));
|
|
9208
9552
|
any = any || page.rows.length > 0;
|
|
9209
9553
|
if (page.fuseTripped || !page.nextCursor) {
|
|
9210
|
-
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."));
|
|
9211
9555
|
return;
|
|
9212
9556
|
}
|
|
9213
9557
|
cursor2 = page.nextCursor;
|
|
@@ -9228,7 +9572,7 @@ async function runHistoryBrowser(opts) {
|
|
|
9228
9572
|
scanRows = first.rows;
|
|
9229
9573
|
scanCursor = first.nextCursor;
|
|
9230
9574
|
if (scanRows.length === 0) {
|
|
9231
|
-
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."));
|
|
9232
9576
|
return;
|
|
9233
9577
|
}
|
|
9234
9578
|
}
|
|
@@ -9239,10 +9583,10 @@ async function runHistoryBrowser(opts) {
|
|
|
9239
9583
|
const now2 = Date.now();
|
|
9240
9584
|
const oldestKey = days[days.length - 1]?.key;
|
|
9241
9585
|
const choices = days.map((d) => ({
|
|
9242
|
-
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`) : ""}`,
|
|
9243
9587
|
value: d.key
|
|
9244
9588
|
}));
|
|
9245
|
-
if (scanCursor) choices.push({ name:
|
|
9589
|
+
if (scanCursor) choices.push({ name: import_chalk39.default.dim("\u2026 older days"), value: "__older__" });
|
|
9246
9590
|
const { chosenDay } = await inquirer3.prompt([{
|
|
9247
9591
|
type: "list",
|
|
9248
9592
|
name: "chosenDay",
|
|
@@ -9262,7 +9606,7 @@ async function runHistoryBrowser(opts) {
|
|
|
9262
9606
|
scanCursor = void 0;
|
|
9263
9607
|
} else {
|
|
9264
9608
|
scanCursor = more.nextCursor;
|
|
9265
|
-
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."));
|
|
9266
9610
|
}
|
|
9267
9611
|
continue;
|
|
9268
9612
|
}
|
|
@@ -9277,18 +9621,18 @@ async function runHistoryBrowser(opts) {
|
|
|
9277
9621
|
let moreCursor;
|
|
9278
9622
|
for (; ; ) {
|
|
9279
9623
|
const page = await fetchPage(opts.fetchWindow, {
|
|
9280
|
-
limit:
|
|
9624
|
+
limit: PAGE_LIMIT2,
|
|
9281
9625
|
since,
|
|
9282
9626
|
until,
|
|
9283
9627
|
errorsOnly: opts.errorsOnly,
|
|
9284
9628
|
cursor
|
|
9285
9629
|
}, notices);
|
|
9286
9630
|
if (page.rows.length === 0 && pageRows.length === 0) {
|
|
9287
|
-
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."));
|
|
9288
9632
|
return;
|
|
9289
9633
|
}
|
|
9290
9634
|
if (page.rows.length === 0) {
|
|
9291
|
-
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."));
|
|
9292
9636
|
moreCursor = void 0;
|
|
9293
9637
|
} else {
|
|
9294
9638
|
pageRows = page.rows;
|
|
@@ -9299,8 +9643,8 @@ async function runHistoryBrowser(opts) {
|
|
|
9299
9643
|
name: formatRow(r),
|
|
9300
9644
|
value: r.request_id
|
|
9301
9645
|
}));
|
|
9302
|
-
if (moreCursor) choices.push({ name:
|
|
9303
|
-
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__" });
|
|
9304
9648
|
for (; ; ) {
|
|
9305
9649
|
const { picked } = await inquirer3.prompt([{
|
|
9306
9650
|
type: "list",
|
|
@@ -9350,19 +9694,19 @@ function wrapCells(items, width) {
|
|
|
9350
9694
|
${PANE_INDENT}`);
|
|
9351
9695
|
}
|
|
9352
9696
|
function renderHeaders(h) {
|
|
9353
|
-
if (!h) return
|
|
9697
|
+
if (!h) return import_chalk39.default.dim("\u2014");
|
|
9354
9698
|
const width = process.stdout.columns || 100;
|
|
9355
9699
|
if (h.values) {
|
|
9356
9700
|
const e = Object.entries(h.values);
|
|
9357
|
-
if (!e.length) return
|
|
9701
|
+
if (!e.length) return import_chalk39.default.dim("none");
|
|
9358
9702
|
const rank = (k) => {
|
|
9359
9703
|
const i = HEADER_PRIORITY.indexOf(k.toLowerCase());
|
|
9360
9704
|
return i === -1 ? NOISE_HEADER.test(k) ? 999 : 500 : i;
|
|
9361
9705
|
};
|
|
9362
|
-
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(`
|
|
9363
9707
|
${PANE_INDENT}`);
|
|
9364
9708
|
}
|
|
9365
|
-
if (!h.names?.length) return
|
|
9709
|
+
if (!h.names?.length) return import_chalk39.default.dim("none");
|
|
9366
9710
|
const names = [...h.names];
|
|
9367
9711
|
const noise = names.filter((n) => NOISE_HEADER.test(n));
|
|
9368
9712
|
const shown = names.filter((n) => !NOISE_HEADER.test(n)).sort((a, b) => {
|
|
@@ -9370,70 +9714,70 @@ ${PANE_INDENT}`);
|
|
|
9370
9714
|
const ib = HEADER_PRIORITY.indexOf(b.toLowerCase());
|
|
9371
9715
|
return (ia === -1 ? 500 : ia) - (ib === -1 ? 500 : ib) || a.localeCompare(b);
|
|
9372
9716
|
});
|
|
9373
|
-
const body = shown.length ? wrapCells(shown, width) :
|
|
9717
|
+
const body = shown.length ? wrapCells(shown, width) : import_chalk39.default.dim("(all forwarding headers)");
|
|
9374
9718
|
const tail = noise.length ? `
|
|
9375
|
-
${PANE_INDENT}${
|
|
9376
|
-
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)")}`;
|
|
9377
9721
|
}
|
|
9378
9722
|
function renderBody(b) {
|
|
9379
|
-
if (!b) return
|
|
9723
|
+
if (!b) return import_chalk39.default.dim("\u2014");
|
|
9380
9724
|
const meta = [b.content_type, b.size != null ? `${b.size}B` : null, b.truncated ? "truncated" : null].filter(Boolean).join(" \xB7 ");
|
|
9381
|
-
if (b.not_captured) return
|
|
9725
|
+
if (b.not_captured) return import_chalk39.default.dim(`not captured (${b.not_captured})${meta ? " \xB7 " + meta : ""}`);
|
|
9382
9726
|
const isShape = b.value === void 0 && b.shape !== void 0;
|
|
9383
9727
|
const content = b.value !== void 0 ? b.value : b.shape;
|
|
9384
9728
|
if (content === null || content === void 0 || content === "" || content === "empty") {
|
|
9385
|
-
return
|
|
9729
|
+
return import_chalk39.default.dim(`empty${meta ? " \xB7 " + meta : ""}`);
|
|
9386
9730
|
}
|
|
9387
9731
|
const text = typeof content === "string" ? content : JSON.stringify(content, null, 2);
|
|
9388
|
-
const head = isShape ?
|
|
9732
|
+
const head = isShape ? import_chalk39.default.dim("shape only (values not captured)\n ") : "";
|
|
9389
9733
|
return head + text.split("\n").join("\n ");
|
|
9390
9734
|
}
|
|
9391
9735
|
function pane(title, tone, view, empty) {
|
|
9392
9736
|
console.log(` ${tone(title)}`);
|
|
9393
9737
|
if (empty !== void 0) {
|
|
9394
|
-
console.log(` ${
|
|
9738
|
+
console.log(` ${import_chalk39.default.dim(empty)}`);
|
|
9395
9739
|
return;
|
|
9396
9740
|
}
|
|
9397
9741
|
const v = view ?? {};
|
|
9398
|
-
if (v.url) console.log(` ${
|
|
9742
|
+
if (v.url) console.log(` ${import_chalk39.default.dim("target")} ${v.url}`);
|
|
9399
9743
|
if (v.status != null) {
|
|
9400
|
-
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) : ""}`);
|
|
9401
9745
|
}
|
|
9402
|
-
if (v.raw_path) console.log(` ${
|
|
9746
|
+
if (v.raw_path) console.log(` ${import_chalk39.default.dim("path")} ${v.raw_path}`);
|
|
9403
9747
|
if (v.query && Object.keys(v.query).length) {
|
|
9404
|
-
console.log(` ${
|
|
9748
|
+
console.log(` ${import_chalk39.default.dim("query")} ${Object.entries(v.query).map(([k, q]) => `${k}=${q}`).join(" ")}`);
|
|
9405
9749
|
}
|
|
9406
|
-
if (v.jwt !== void 0) console.log(` ${
|
|
9407
|
-
console.log(` ${
|
|
9408
|
-
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)}`);
|
|
9409
9753
|
}
|
|
9410
9754
|
function printDetail(r, mode = "producer") {
|
|
9411
9755
|
const v = r.views;
|
|
9412
9756
|
console.log("");
|
|
9413
|
-
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`));
|
|
9414
9758
|
const cost = typeof r.cost_cents === "number" && r.cost_cents > 0 ? ` \xB7 ${r.cost_cents}\xA2` : "";
|
|
9415
|
-
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}`));
|
|
9416
9760
|
console.log("");
|
|
9417
9761
|
if (!v) {
|
|
9418
|
-
console.log(
|
|
9762
|
+
console.log(import_chalk39.default.dim(" No view detail captured for this request.\n"));
|
|
9419
9763
|
return;
|
|
9420
9764
|
}
|
|
9421
|
-
pane("Request (before) \u2014 client \u2192 APIblaze",
|
|
9765
|
+
pane("Request (before) \u2014 client \u2192 APIblaze", import_chalk39.default.cyan, v.request);
|
|
9422
9766
|
if (mode === "producer") {
|
|
9423
9767
|
pane(
|
|
9424
9768
|
"Request (after) \u2014 APIblaze \u2192 upstream",
|
|
9425
|
-
|
|
9769
|
+
import_chalk39.default.cyan,
|
|
9426
9770
|
v.forwarded_request,
|
|
9427
9771
|
v.forwarded_request ? void 0 : "Request did not reach an upstream."
|
|
9428
9772
|
);
|
|
9429
9773
|
pane(
|
|
9430
9774
|
"Response (before) \u2014 upstream \u2192 APIblaze",
|
|
9431
|
-
|
|
9775
|
+
import_chalk39.default.magenta,
|
|
9432
9776
|
v.upstream_response,
|
|
9433
9777
|
v.upstream_response ? void 0 : "No upstream response."
|
|
9434
9778
|
);
|
|
9435
9779
|
}
|
|
9436
|
-
pane("Response (after) \u2014 APIblaze \u2192 client",
|
|
9780
|
+
pane("Response (after) \u2014 APIblaze \u2192 client", import_chalk39.default.magenta, v.response);
|
|
9437
9781
|
console.log("");
|
|
9438
9782
|
}
|
|
9439
9783
|
|
|
@@ -9482,7 +9826,7 @@ async function resolve(projectArg, versionArg, opts) {
|
|
|
9482
9826
|
return { projectId, projectName: match.projectName, version: version2, tenant: tenant2 };
|
|
9483
9827
|
}
|
|
9484
9828
|
function otherPlaneHint() {
|
|
9485
|
-
if (loadConsumer()) console.log(
|
|
9829
|
+
if (loadConsumer()) console.log(import_chalk40.default.dim(" \u2039consumer view: apiblaze consumer logs\u203A"));
|
|
9486
9830
|
}
|
|
9487
9831
|
function fetchWindow(projectId, version2, tenant2) {
|
|
9488
9832
|
return (params) => {
|
|
@@ -9501,8 +9845,8 @@ function fetchWindow(projectId, version2, tenant2) {
|
|
|
9501
9845
|
async function runLogsTail(projectArg, versionArg, opts) {
|
|
9502
9846
|
const { projectId, projectName, version: version2, tenant: tenant2 } = await resolve(projectArg, versionArg, opts);
|
|
9503
9847
|
if (!opts.json) {
|
|
9504
|
-
console.log(
|
|
9505
|
-
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"));
|
|
9506
9850
|
otherPlaneHint();
|
|
9507
9851
|
}
|
|
9508
9852
|
await runTailSession({
|
|
@@ -9523,8 +9867,8 @@ async function runLogsTail(projectArg, versionArg, opts) {
|
|
|
9523
9867
|
async function runLogsList(projectArg, versionArg, opts) {
|
|
9524
9868
|
const { projectId, projectName, version: version2, tenant: tenant2 } = await resolve(projectArg, versionArg, opts);
|
|
9525
9869
|
if (!opts.json) {
|
|
9526
|
-
console.log(
|
|
9527
|
-
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"));
|
|
9528
9872
|
otherPlaneHint();
|
|
9529
9873
|
console.log("");
|
|
9530
9874
|
}
|
|
@@ -9538,15 +9882,15 @@ async function runLogsList(projectArg, versionArg, opts) {
|
|
|
9538
9882
|
}
|
|
9539
9883
|
|
|
9540
9884
|
// src/commands/consumer-logs.ts
|
|
9541
|
-
var
|
|
9885
|
+
var import_chalk41 = __toESM(require("chalk"));
|
|
9542
9886
|
init_auth();
|
|
9543
9887
|
var CONSUMER_LOGS_BASE = process.env.APIBLAZE_CONSUMER_LOGS_BASE || "https://logs.apiblaze.com/consumer";
|
|
9544
9888
|
function requireConsumer() {
|
|
9545
9889
|
const c = loadConsumer();
|
|
9546
9890
|
if (!c) {
|
|
9547
|
-
console.error(
|
|
9891
|
+
console.error(import_chalk41.default.red("Not logged in as an API Consumer. Run `apiblaze consumer login` first."));
|
|
9548
9892
|
if (loadCredentials()) {
|
|
9549
|
-
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>`."));
|
|
9550
9894
|
}
|
|
9551
9895
|
process.exit(1);
|
|
9552
9896
|
}
|
|
@@ -9577,7 +9921,7 @@ async function consumerLogsFetch(session, suffix, init) {
|
|
|
9577
9921
|
return data;
|
|
9578
9922
|
}
|
|
9579
9923
|
function otherPlaneHint2() {
|
|
9580
|
-
if (loadCredentials()) console.log(
|
|
9924
|
+
if (loadCredentials()) console.log(import_chalk41.default.dim(" \u2039producer view: apiblaze logs <project>\u203A"));
|
|
9581
9925
|
}
|
|
9582
9926
|
function fetchWindow2(session, project) {
|
|
9583
9927
|
return (params) => {
|
|
@@ -9593,8 +9937,8 @@ function fetchWindow2(session, project) {
|
|
|
9593
9937
|
async function runConsumerLogsTail(opts) {
|
|
9594
9938
|
const session = requireConsumer();
|
|
9595
9939
|
if (!opts.json) {
|
|
9596
|
-
console.log(
|
|
9597
|
-
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"));
|
|
9598
9942
|
otherPlaneHint2();
|
|
9599
9943
|
}
|
|
9600
9944
|
await runTailSession({
|
|
@@ -9612,8 +9956,8 @@ async function runConsumerLogsTail(opts) {
|
|
|
9612
9956
|
async function runConsumerLogsList(opts) {
|
|
9613
9957
|
const session = requireConsumer();
|
|
9614
9958
|
if (!opts.json) {
|
|
9615
|
-
console.log(
|
|
9616
|
-
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"));
|
|
9617
9961
|
otherPlaneHint2();
|
|
9618
9962
|
console.log("");
|
|
9619
9963
|
}
|
|
@@ -9627,7 +9971,7 @@ async function runConsumerLogsList(opts) {
|
|
|
9627
9971
|
}
|
|
9628
9972
|
|
|
9629
9973
|
// src/commands/config.ts
|
|
9630
|
-
var
|
|
9974
|
+
var import_chalk42 = __toESM(require("chalk"));
|
|
9631
9975
|
var import_ora15 = __toESM(require("ora"));
|
|
9632
9976
|
init_admin();
|
|
9633
9977
|
init_resolve();
|
|
@@ -9651,7 +9995,7 @@ async function patchConfig(project, opts, body, summary) {
|
|
|
9651
9995
|
}
|
|
9652
9996
|
async function runTargetSet(project, opts) {
|
|
9653
9997
|
if (!opts.url) {
|
|
9654
|
-
console.error(
|
|
9998
|
+
console.error(import_chalk42.default.red("--url is required."));
|
|
9655
9999
|
process.exit(1);
|
|
9656
10000
|
}
|
|
9657
10001
|
const body = opts.env ? { environments: { [opts.env]: { target: opts.url } } } : { target_url: opts.url };
|
|
@@ -9664,40 +10008,41 @@ async function runThrottleSet(project, opts) {
|
|
|
9664
10008
|
if (opts.quota !== void 0) throttling.proxyQuota = Number(opts.quota);
|
|
9665
10009
|
if (opts.period !== void 0) {
|
|
9666
10010
|
if (!["daily", "weekly", "monthly"].includes(opts.period)) {
|
|
9667
|
-
console.error(
|
|
10011
|
+
console.error(import_chalk42.default.red("--period must be daily, weekly, or monthly."));
|
|
9668
10012
|
process.exit(1);
|
|
9669
10013
|
}
|
|
9670
10014
|
throttling.quotaPeriod = opts.period;
|
|
9671
10015
|
}
|
|
9672
10016
|
if (Object.keys(throttling).length === 0) {
|
|
9673
|
-
console.error(
|
|
10017
|
+
console.error(import_chalk42.default.red("Nothing to set. Pass at least one of --rate, --end-user-rate, --quota, --period."));
|
|
9674
10018
|
process.exit(1);
|
|
9675
10019
|
}
|
|
9676
10020
|
await patchConfig(project, opts, { throttling }, `Update throttling ${JSON.stringify(throttling)}`);
|
|
9677
10021
|
}
|
|
9678
10022
|
async function runRename(project, opts) {
|
|
9679
10023
|
if (!opts.displayName) {
|
|
9680
|
-
console.error(
|
|
10024
|
+
console.error(import_chalk42.default.red("--display-name is required."));
|
|
9681
10025
|
process.exit(1);
|
|
9682
10026
|
}
|
|
9683
10027
|
await patchConfig(project, opts, { display_name: opts.displayName }, `Rename \u2192 "${opts.displayName}"`);
|
|
9684
10028
|
}
|
|
9685
10029
|
|
|
9686
10030
|
// src/commands/config-browse.ts
|
|
9687
|
-
var
|
|
10031
|
+
var import_chalk51 = __toESM(require("chalk"));
|
|
9688
10032
|
var import_ora23 = __toESM(require("ora"));
|
|
9689
10033
|
init_admin();
|
|
10034
|
+
init_paging();
|
|
9690
10035
|
init_auth();
|
|
9691
10036
|
init_resolve();
|
|
9692
10037
|
|
|
9693
10038
|
// src/commands/domain.ts
|
|
9694
|
-
var
|
|
10039
|
+
var import_chalk43 = __toESM(require("chalk"));
|
|
9695
10040
|
var import_ora16 = __toESM(require("ora"));
|
|
9696
10041
|
init_admin();
|
|
9697
10042
|
init_resolve();
|
|
9698
10043
|
async function runDomainAdd(project, opts) {
|
|
9699
10044
|
if (!opts.domain) {
|
|
9700
|
-
console.error(
|
|
10045
|
+
console.error(import_chalk43.default.red("--domain is required."));
|
|
9701
10046
|
process.exit(1);
|
|
9702
10047
|
}
|
|
9703
10048
|
const { teamId } = await resolveTeam(opts.team);
|
|
@@ -9716,9 +10061,9 @@ async function runDomainAdd(project, opts) {
|
|
|
9716
10061
|
console.log(JSON.stringify(out));
|
|
9717
10062
|
return;
|
|
9718
10063
|
}
|
|
9719
|
-
if (out?.cnameRecord) console.log(` ${
|
|
9720
|
-
if (out?.dcv) console.log(` ${
|
|
9721
|
-
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}`));
|
|
9722
10067
|
} catch (err) {
|
|
9723
10068
|
spinner.fail("Domain registration failed.");
|
|
9724
10069
|
throw err;
|
|
@@ -9738,14 +10083,14 @@ async function runDomainList(project, opts) {
|
|
|
9738
10083
|
return;
|
|
9739
10084
|
}
|
|
9740
10085
|
if (!domains.length) {
|
|
9741
|
-
console.log(
|
|
10086
|
+
console.log(import_chalk43.default.yellow("No custom domains."));
|
|
9742
10087
|
return;
|
|
9743
10088
|
}
|
|
9744
|
-
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 ?? "")}`);
|
|
9745
10090
|
}
|
|
9746
10091
|
async function runDomainStatus(project, opts) {
|
|
9747
10092
|
if (!opts.id) {
|
|
9748
|
-
console.error(
|
|
10093
|
+
console.error(import_chalk43.default.red("--id <domainId> is required (see `apiblaze domain list`)."));
|
|
9749
10094
|
process.exit(1);
|
|
9750
10095
|
}
|
|
9751
10096
|
const { teamId } = await resolveTeam(opts.team);
|
|
@@ -9755,11 +10100,11 @@ async function runDomainStatus(project, opts) {
|
|
|
9755
10100
|
path: `/projects/${proj2.projectId}/domains/${encodeURIComponent(opts.id)}/status`,
|
|
9756
10101
|
summary: `Check custom-domain status`
|
|
9757
10102
|
});
|
|
9758
|
-
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")}`);
|
|
9759
10104
|
}
|
|
9760
10105
|
async function runDomainRemove(project, opts) {
|
|
9761
10106
|
if (!opts.id) {
|
|
9762
|
-
console.error(
|
|
10107
|
+
console.error(import_chalk43.default.red("--id <domainId> is required (see `apiblaze domain list`)."));
|
|
9763
10108
|
process.exit(1);
|
|
9764
10109
|
}
|
|
9765
10110
|
const { teamId } = await resolveTeam(opts.team);
|
|
@@ -9797,61 +10142,69 @@ async function runDomainSetBase(project, opts) {
|
|
|
9797
10142
|
}
|
|
9798
10143
|
|
|
9799
10144
|
// src/commands/tenant.ts
|
|
9800
|
-
var
|
|
10145
|
+
var import_chalk46 = __toESM(require("chalk"));
|
|
9801
10146
|
var import_ora19 = __toESM(require("ora"));
|
|
9802
10147
|
init_admin();
|
|
9803
10148
|
init_resolve();
|
|
9804
10149
|
init_auth();
|
|
9805
10150
|
init_tenant_pick();
|
|
10151
|
+
init_paging();
|
|
9806
10152
|
async function runTenantUse(query, opts) {
|
|
9807
10153
|
const creds = loadCredentials();
|
|
9808
10154
|
if (!creds) {
|
|
9809
|
-
console.error(
|
|
10155
|
+
console.error(import_chalk46.default.red("Not logged in. Run `apiblaze login` first."));
|
|
9810
10156
|
process.exit(1);
|
|
9811
10157
|
}
|
|
9812
10158
|
if (opts.clear) {
|
|
9813
10159
|
delete creds.activeTenant;
|
|
9814
10160
|
saveCredentials(creds);
|
|
9815
|
-
console.log(
|
|
10161
|
+
console.log(import_chalk46.default.green("Tenant scope cleared."));
|
|
9816
10162
|
return;
|
|
9817
10163
|
}
|
|
9818
10164
|
const { teamId } = await resolveTeam(opts.team);
|
|
9819
10165
|
const slug = await pickTenant(teamId, { message: "Scope future commands to which tenant?", initialQuery: query });
|
|
9820
10166
|
if (!slug) process.exit(1);
|
|
9821
10167
|
saveCredentials({ ...creds, activeTenant: slug });
|
|
9822
|
-
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`)"));
|
|
9823
10169
|
}
|
|
9824
10170
|
async function runTenantList(opts) {
|
|
9825
10171
|
const { teamId, teamName } = await resolveTeam(opts.team);
|
|
9826
|
-
const
|
|
9827
|
-
const
|
|
9828
|
-
|
|
9829
|
-
|
|
9830
|
-
|
|
9831
|
-
|
|
9832
|
-
|
|
9833
|
-
|
|
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
|
+
}
|
|
9834
10187
|
if (opts.json) {
|
|
9835
10188
|
console.log(JSON.stringify(tenants));
|
|
9836
10189
|
return;
|
|
9837
10190
|
}
|
|
9838
10191
|
if (!tenants.length) {
|
|
9839
|
-
console.log(
|
|
10192
|
+
console.log(import_chalk46.default.yellow(opts.q ? `No tenants matching "${opts.q}".` : "No tenants."));
|
|
9840
10193
|
return;
|
|
9841
10194
|
}
|
|
9842
10195
|
for (const t of tenants) {
|
|
9843
10196
|
const name = typeof t === "string" ? t : t.tenant_name;
|
|
9844
|
-
const display = typeof t === "string" ? "" :
|
|
9845
|
-
console.log(` ${
|
|
10197
|
+
const display = typeof t === "string" ? "" : import_chalk46.default.dim(` ${t.display_name ?? ""}`);
|
|
10198
|
+
console.log(` ${import_chalk46.default.bold(name)}${display}`);
|
|
9846
10199
|
}
|
|
9847
10200
|
const total = out?.total ?? tenants.length;
|
|
9848
10201
|
if (total > tenants.length) {
|
|
9849
|
-
console.log(
|
|
10202
|
+
console.log(import_chalk46.default.dim(` \u2026 showing ${tenants.length} of ${total} \u2014 narrow with --q <search>`));
|
|
9850
10203
|
}
|
|
9851
10204
|
}
|
|
9852
10205
|
async function runTenantCreate(opts) {
|
|
9853
10206
|
if (!opts.name) {
|
|
9854
|
-
console.error(
|
|
10207
|
+
console.error(import_chalk46.default.red("--name (display name) is required."));
|
|
9855
10208
|
process.exit(1);
|
|
9856
10209
|
}
|
|
9857
10210
|
const { teamId } = await resolveTeam(opts.team);
|
|
@@ -9863,7 +10216,7 @@ async function runTenantCreate(opts) {
|
|
|
9863
10216
|
body: { display_name: opts.name, ...opts.slug ? { tenant_name: opts.slug } : {} },
|
|
9864
10217
|
summary: `Create tenant "${opts.name}"`
|
|
9865
10218
|
});
|
|
9866
|
-
spinner.succeed(`Created tenant ${
|
|
10219
|
+
spinner.succeed(`Created tenant ${import_chalk46.default.bold(out?.tenant_name ?? opts.name)}.`);
|
|
9867
10220
|
if (opts.json) console.log(JSON.stringify(out));
|
|
9868
10221
|
} catch (err) {
|
|
9869
10222
|
spinner.fail("Tenant create failed.");
|
|
@@ -9872,7 +10225,7 @@ async function runTenantCreate(opts) {
|
|
|
9872
10225
|
}
|
|
9873
10226
|
async function runTenantAttach(project, opts) {
|
|
9874
10227
|
if (!opts.tenant) {
|
|
9875
|
-
console.error(
|
|
10228
|
+
console.error(import_chalk46.default.red("--tenant <slug> is required."));
|
|
9876
10229
|
process.exit(1);
|
|
9877
10230
|
}
|
|
9878
10231
|
const { teamId } = await resolveTeam(opts.team);
|
|
@@ -9894,32 +10247,68 @@ async function runTenantAttach(project, opts) {
|
|
|
9894
10247
|
}
|
|
9895
10248
|
async function runTenantDelete(slug, opts) {
|
|
9896
10249
|
const { teamId } = await resolveTeam(opts.team);
|
|
10250
|
+
const hard = !!opts.purge;
|
|
9897
10251
|
if (!opts.yes && !opts.json) {
|
|
9898
10252
|
const { default: inquirer3 } = await import("inquirer");
|
|
9899
10253
|
const { confirm: confirm2 } = await inquirer3.prompt([
|
|
9900
|
-
{
|
|
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
|
+
}
|
|
9901
10260
|
]);
|
|
9902
10261
|
if (!confirm2) {
|
|
9903
|
-
console.log(
|
|
10262
|
+
console.log(import_chalk46.default.dim("Aborted."));
|
|
9904
10263
|
return;
|
|
9905
10264
|
}
|
|
9906
10265
|
}
|
|
9907
|
-
const spinner = (0, import_ora19.default)("Deleting tenant...").start();
|
|
10266
|
+
const spinner = (0, import_ora19.default)(hard ? "Trashing tenant..." : "Deleting tenant...").start();
|
|
9908
10267
|
try {
|
|
9909
10268
|
await admin({
|
|
9910
10269
|
method: "DELETE",
|
|
9911
10270
|
path: `/teams/${encodeURIComponent(teamId)}/tenants/${encodeURIComponent(slug)}`,
|
|
9912
|
-
summary: `
|
|
10271
|
+
summary: `Move tenant ${slug} to trash`
|
|
9913
10272
|
});
|
|
9914
|
-
|
|
10273
|
+
if (!hard) {
|
|
10274
|
+
spinner.succeed(`Tenant ${slug} moved to trash.`);
|
|
10275
|
+
return;
|
|
10276
|
+
}
|
|
10277
|
+
spinner.stop();
|
|
9915
10278
|
} catch (err) {
|
|
9916
10279
|
spinner.fail("Tenant delete failed.");
|
|
9917
10280
|
throw err;
|
|
9918
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
|
+
}
|
|
9919
10308
|
}
|
|
9920
10309
|
async function runTenantCors(opts) {
|
|
9921
10310
|
if (!opts.tenant) {
|
|
9922
|
-
console.error(
|
|
10311
|
+
console.error(import_chalk46.default.red("--tenant <slug> is required."));
|
|
9923
10312
|
process.exit(1);
|
|
9924
10313
|
}
|
|
9925
10314
|
const { teamId } = await resolveTeam(opts.team);
|
|
@@ -9941,7 +10330,7 @@ async function runTenantCors(opts) {
|
|
|
9941
10330
|
}
|
|
9942
10331
|
|
|
9943
10332
|
// src/commands/tenant-drill.ts
|
|
9944
|
-
var
|
|
10333
|
+
var import_chalk47 = __toESM(require("chalk"));
|
|
9945
10334
|
var import_ora20 = __toESM(require("ora"));
|
|
9946
10335
|
var import_crypto = require("crypto");
|
|
9947
10336
|
init_admin();
|
|
@@ -9972,15 +10361,15 @@ async function validScopedTenant(teamId, query) {
|
|
|
9972
10361
|
const next = { ...creds };
|
|
9973
10362
|
delete next.activeTenant;
|
|
9974
10363
|
saveCredentials2(next);
|
|
9975
|
-
console.log(
|
|
10364
|
+
console.log(import_chalk47.default.yellow(`Tenant scope "${scoped}" no longer exists in this team \u2014 cleared.`));
|
|
9976
10365
|
return void 0;
|
|
9977
10366
|
}
|
|
9978
10367
|
async function tenantHome(teamId, tenant2) {
|
|
9979
10368
|
const { default: inquirer3 } = await import("inquirer");
|
|
9980
10369
|
const base2 = `/teams/${encodeURIComponent(teamId)}/tenants/${encodeURIComponent(tenant2)}`;
|
|
9981
|
-
console.log(
|
|
10370
|
+
console.log(import_chalk47.default.bold(`
|
|
9982
10371
|
Tenant ${tenant2}`));
|
|
9983
|
-
console.log(
|
|
10372
|
+
console.log(import_chalk47.default.dim("Tenant auth/settings are SHARED: changes apply to every proxy this tenant serves.\n"));
|
|
9984
10373
|
for (; ; ) {
|
|
9985
10374
|
const spinner = (0, import_ora20.default)("Reading tenant state...").start();
|
|
9986
10375
|
const [iam, cors, emails, issuers, opaque, clients] = await Promise.all([
|
|
@@ -9996,18 +10385,18 @@ Tenant ${tenant2}`));
|
|
|
9996
10385
|
const nClients = Array.isArray(clients) ? clients.length : 0;
|
|
9997
10386
|
const nOpaque = opaque?.opaque?.endpoint ? 1 : 0;
|
|
9998
10387
|
const nLogin = nClients + nIssuers + nOpaque;
|
|
9999
|
-
const onOff = (b) => b ?
|
|
10388
|
+
const onOff = (b) => b ? import_chalk47.default.green("on") : import_chalk47.default.dim("off");
|
|
10000
10389
|
const { pick: pick2 } = await inquirer3.prompt([{
|
|
10001
10390
|
type: "list",
|
|
10002
10391
|
name: "pick",
|
|
10003
10392
|
message: `Tenant ${tenant2}:`,
|
|
10004
10393
|
pageSize: 12,
|
|
10005
10394
|
choices: [
|
|
10006
|
-
{ name: `Login methods (${nLogin}) ${
|
|
10007
|
-
{ name: `Users & groups: ${onOff(iam?.iam_enabled)} ${
|
|
10008
|
-
{ name: `Portal admins (${nEmails}) ${
|
|
10009
|
-
new inquirer3.Separator(
|
|
10010
|
-
{ 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" },
|
|
10011
10400
|
{ name: "\u2190 Back", value: "back" }
|
|
10012
10401
|
]
|
|
10013
10402
|
}]);
|
|
@@ -10020,7 +10409,7 @@ Tenant ${tenant2}`));
|
|
|
10020
10409
|
case "iam": {
|
|
10021
10410
|
const { v } = await inquirer3.prompt([{ type: "confirm", name: "v", message: "Enable Users & groups?", default: !!iam?.iam_enabled }]);
|
|
10022
10411
|
await admin({ method: "PATCH", path: `${base2}/iam`, body: { enabled: v }, summary: `IAM enforcement \u2192 ${v ? "on" : "off"}` });
|
|
10023
|
-
console.log(
|
|
10412
|
+
console.log(import_chalk47.default.green(` Users & groups ${v ? "enabled" : "disabled"}.`));
|
|
10024
10413
|
break;
|
|
10025
10414
|
}
|
|
10026
10415
|
case "cors": {
|
|
@@ -10033,11 +10422,11 @@ Tenant ${tenant2}`));
|
|
|
10033
10422
|
if (v === "") break;
|
|
10034
10423
|
const parsed = v === "null" ? null : safeJson(v);
|
|
10035
10424
|
if (parsed === void 0) {
|
|
10036
|
-
console.log(
|
|
10425
|
+
console.log(import_chalk47.default.yellow(" Not valid JSON \u2014 unchanged."));
|
|
10037
10426
|
break;
|
|
10038
10427
|
}
|
|
10039
10428
|
await admin({ method: "PUT", path: `${base2}/cors`, body: { cors: parsed }, summary: "Set tenant CORS" });
|
|
10040
|
-
console.log(
|
|
10429
|
+
console.log(import_chalk47.default.green(" CORS updated."));
|
|
10041
10430
|
break;
|
|
10042
10431
|
}
|
|
10043
10432
|
case "emails":
|
|
@@ -10061,15 +10450,15 @@ async function loginMethodsMenu(teamId, tenant2, base2) {
|
|
|
10061
10450
|
const choices = [];
|
|
10062
10451
|
for (const c of appClients) {
|
|
10063
10452
|
const nP = (c.providers ?? []).length || c.providers_count || 0;
|
|
10064
|
-
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 } });
|
|
10065
10454
|
}
|
|
10066
10455
|
for (const i of issuers) {
|
|
10067
|
-
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 } });
|
|
10068
10457
|
}
|
|
10069
10458
|
if (opaque?.endpoint) {
|
|
10070
|
-
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 } });
|
|
10071
10460
|
}
|
|
10072
|
-
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."));
|
|
10073
10462
|
const { pick: pick2 } = await inquirer3.prompt([{
|
|
10074
10463
|
type: "list",
|
|
10075
10464
|
name: "pick",
|
|
@@ -10109,9 +10498,9 @@ async function addLoginMethod(teamId, tenant2, base2) {
|
|
|
10109
10498
|
message: "How do people log in?",
|
|
10110
10499
|
pageSize: 8,
|
|
10111
10500
|
choices: [
|
|
10112
|
-
{ name: `APIblaze hosted login page ${
|
|
10113
|
-
{ name: `Your own hosted login \u2014 JWT ${
|
|
10114
|
-
{ 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" },
|
|
10115
10504
|
{ name: "\u2190 Back", value: null }
|
|
10116
10505
|
]
|
|
10117
10506
|
}]);
|
|
@@ -10135,8 +10524,8 @@ function safeJson(s) {
|
|
|
10135
10524
|
async function emailsMenu(base2, emails) {
|
|
10136
10525
|
const { default: inquirer3 } = await import("inquirer");
|
|
10137
10526
|
console.log();
|
|
10138
|
-
if (!emails.length) console.log(
|
|
10139
|
-
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 ?? "")}`);
|
|
10140
10529
|
const { act } = await inquirer3.prompt([{
|
|
10141
10530
|
type: "list",
|
|
10142
10531
|
name: "act",
|
|
@@ -10151,7 +10540,7 @@ async function emailsMenu(base2, emails) {
|
|
|
10151
10540
|
if (act === "add") {
|
|
10152
10541
|
const { email } = await inquirer3.prompt([{ type: "input", name: "email", message: "Email:", validate: (s) => /.+@.+\..+/.test(s) || "not an email" }]);
|
|
10153
10542
|
await admin({ method: "POST", path: `${base2}/admin-emails`, body: { email }, summary: `Add consumer-admin ${email}` });
|
|
10154
|
-
console.log(
|
|
10543
|
+
console.log(import_chalk47.default.green(` ${email} added.`));
|
|
10155
10544
|
} else {
|
|
10156
10545
|
const { e } = await inquirer3.prompt([{
|
|
10157
10546
|
type: "list",
|
|
@@ -10161,7 +10550,7 @@ async function emailsMenu(base2, emails) {
|
|
|
10161
10550
|
}]);
|
|
10162
10551
|
if (!e) return;
|
|
10163
10552
|
await admin({ method: "DELETE", path: `${base2}/admin-emails/${encodeURIComponent(e)}`, summary: `Remove consumer-admin ${e}` });
|
|
10164
|
-
console.log(
|
|
10553
|
+
console.log(import_chalk47.default.green(` ${e} removed.`));
|
|
10165
10554
|
}
|
|
10166
10555
|
}
|
|
10167
10556
|
async function addIssuer(base2) {
|
|
@@ -10182,19 +10571,19 @@ async function addIssuer(base2) {
|
|
|
10182
10571
|
body: { iss: a.iss.trim(), aud: a.aud.trim(), jwks_url: a.jwks.trim() || null, sub_semantics: a.sem, ...claim ? { claim_name: claim } : {} },
|
|
10183
10572
|
summary: `Add external issuer ${a.iss.trim()}`
|
|
10184
10573
|
});
|
|
10185
|
-
console.log(
|
|
10574
|
+
console.log(import_chalk47.default.green(" JWT login method saved."));
|
|
10186
10575
|
}
|
|
10187
10576
|
async function issuerHome(base2, issuer) {
|
|
10188
10577
|
const { default: inquirer3 } = await import("inquirer");
|
|
10189
10578
|
console.log(`
|
|
10190
|
-
${
|
|
10579
|
+
${import_chalk47.default.bold(issuer.iss)} ${import_chalk47.default.dim(`aud=${issuer.aud} \xB7 ${issuer.sub_semantics ?? ""}`)}`);
|
|
10191
10580
|
const { act } = await inquirer3.prompt([{
|
|
10192
10581
|
type: "list",
|
|
10193
10582
|
name: "act",
|
|
10194
10583
|
message: "This JWT login method:",
|
|
10195
10584
|
choices: [
|
|
10196
10585
|
{ name: "Replace it (re-enter details)", value: "edit" },
|
|
10197
|
-
{ name:
|
|
10586
|
+
{ name: import_chalk47.default.red("Delete it"), value: "rm" },
|
|
10198
10587
|
{ name: "\u2190 Back", value: "back" }
|
|
10199
10588
|
]
|
|
10200
10589
|
}]);
|
|
@@ -10210,7 +10599,7 @@ async function issuerHome(base2, issuer) {
|
|
|
10210
10599
|
path: `${base2}/external-issuers?iss=${encodeURIComponent(issuer.iss)}&aud=${encodeURIComponent(issuer.aud)}`,
|
|
10211
10600
|
summary: `Delete issuer ${issuer.iss}`
|
|
10212
10601
|
});
|
|
10213
|
-
console.log(
|
|
10602
|
+
console.log(import_chalk47.default.green(" Deleted."));
|
|
10214
10603
|
}
|
|
10215
10604
|
async function setOpaque(base2, cur) {
|
|
10216
10605
|
const { default: inquirer3 } = await import("inquirer");
|
|
@@ -10219,19 +10608,19 @@ async function setOpaque(base2, cur) {
|
|
|
10219
10608
|
{ type: "list", name: "method", message: "HTTP method:", choices: ["GET", "POST"], default: cur?.method ?? "GET" }
|
|
10220
10609
|
]);
|
|
10221
10610
|
await admin({ method: "PUT", path: `${base2}/opaque`, body: { opaque: { endpoint: a.endpoint, method: a.method } }, summary: "Set opaque validator" });
|
|
10222
|
-
console.log(
|
|
10611
|
+
console.log(import_chalk47.default.green(" Opaque login method set."));
|
|
10223
10612
|
}
|
|
10224
10613
|
async function opaqueHome(base2, cur) {
|
|
10225
10614
|
const { default: inquirer3 } = await import("inquirer");
|
|
10226
10615
|
console.log(`
|
|
10227
|
-
${
|
|
10616
|
+
${import_chalk47.default.bold(cur.endpoint)} ${import_chalk47.default.dim(cur.method ?? "GET")}`);
|
|
10228
10617
|
const { act } = await inquirer3.prompt([{
|
|
10229
10618
|
type: "list",
|
|
10230
10619
|
name: "act",
|
|
10231
10620
|
message: "This opaque login method:",
|
|
10232
10621
|
choices: [
|
|
10233
10622
|
{ name: "Replace it", value: "edit" },
|
|
10234
|
-
{ name:
|
|
10623
|
+
{ name: import_chalk47.default.red("Delete it"), value: "rm" },
|
|
10235
10624
|
{ name: "\u2190 Back", value: "back" }
|
|
10236
10625
|
]
|
|
10237
10626
|
}]);
|
|
@@ -10241,7 +10630,7 @@ async function opaqueHome(base2, cur) {
|
|
|
10241
10630
|
return;
|
|
10242
10631
|
}
|
|
10243
10632
|
await admin({ method: "PUT", path: `${base2}/opaque`, body: { opaque: null }, summary: "Clear opaque validator" });
|
|
10244
|
-
console.log(
|
|
10633
|
+
console.log(import_chalk47.default.green(" Deleted."));
|
|
10245
10634
|
}
|
|
10246
10635
|
async function clientHome(base2, summary) {
|
|
10247
10636
|
const { default: inquirer3 } = await import("inquirer");
|
|
@@ -10260,13 +10649,13 @@ async function clientHome(base2, summary) {
|
|
|
10260
10649
|
message: `${c.name ?? id}:`,
|
|
10261
10650
|
pageSize: 12,
|
|
10262
10651
|
choices: [
|
|
10263
|
-
{ name: `Login providers${nProviders ? ` (${nProviders})` : ""} ${
|
|
10264
|
-
{ name: `Callback URLs: ${cb.length ?
|
|
10265
|
-
{ 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" },
|
|
10266
10655
|
{ name: `Token expiries: access ${c.accessTokenExpiry ?? 3600}s \xB7 id ${c.idTokenExpiry ?? 3600}s \xB7 refresh ${c.refreshTokenExpiry ?? 2592e3}s`, value: "expiries" },
|
|
10267
10656
|
{ name: "Reveal client secret", value: "secret" },
|
|
10268
10657
|
{ name: "Rotate client secret", value: "rotate" },
|
|
10269
|
-
{ name:
|
|
10658
|
+
{ name: import_chalk47.default.red("Delete this app client"), value: "delete" },
|
|
10270
10659
|
{ name: "\u2190 Back", value: "back" }
|
|
10271
10660
|
]
|
|
10272
10661
|
}]);
|
|
@@ -10279,13 +10668,13 @@ async function clientHome(base2, summary) {
|
|
|
10279
10668
|
case "callbacks": {
|
|
10280
10669
|
const { v } = await inquirer3.prompt([{ type: "input", name: "v", message: "Callback URLs (comma-separated):", default: cb.join(", ") }]);
|
|
10281
10670
|
await admin({ method: "PATCH", path: cBase, body: { authorizedCallbackUrls: parseList(v) }, summary: "Update callback URLs" });
|
|
10282
|
-
console.log(
|
|
10671
|
+
console.log(import_chalk47.default.green(" Callbacks updated."));
|
|
10283
10672
|
break;
|
|
10284
10673
|
}
|
|
10285
10674
|
case "scopes": {
|
|
10286
10675
|
const { v } = await inquirer3.prompt([{ type: "input", name: "v", message: "Scopes (space/comma-separated):", default: scopes.join(" ") }]);
|
|
10287
10676
|
await admin({ method: "PATCH", path: cBase, body: { scopes: v.split(/[\s,]+/).filter(Boolean) }, summary: "Update scopes" });
|
|
10288
|
-
console.log(
|
|
10677
|
+
console.log(import_chalk47.default.green(" Scopes updated."));
|
|
10289
10678
|
break;
|
|
10290
10679
|
}
|
|
10291
10680
|
case "expiries": {
|
|
@@ -10300,14 +10689,14 @@ async function clientHome(base2, summary) {
|
|
|
10300
10689
|
body: { accessTokenExpiry: Number(a.access), idTokenExpiry: Number(a.id), refreshTokenExpiry: Number(a.refresh) },
|
|
10301
10690
|
summary: "Update token expiries"
|
|
10302
10691
|
});
|
|
10303
|
-
console.log(
|
|
10692
|
+
console.log(import_chalk47.default.green(" Expiries updated."));
|
|
10304
10693
|
break;
|
|
10305
10694
|
}
|
|
10306
10695
|
case "secret": {
|
|
10307
10696
|
const { sure } = await inquirer3.prompt([{ type: "confirm", name: "sure", message: "Print the client secret to this terminal?", default: false }]);
|
|
10308
10697
|
if (!sure) break;
|
|
10309
10698
|
const s = await admin({ method: "GET", path: `${cBase}/secret`, summary: "Reveal client secret" });
|
|
10310
|
-
console.log(` ${
|
|
10699
|
+
console.log(` ${import_chalk47.default.bold("client_secret")}: ${import_chalk47.default.green(s?.clientSecret ?? s?.client_secret ?? JSON.stringify(s))}`);
|
|
10311
10700
|
break;
|
|
10312
10701
|
}
|
|
10313
10702
|
case "rotate": {
|
|
@@ -10315,14 +10704,14 @@ async function clientHome(base2, summary) {
|
|
|
10315
10704
|
if (!sure) break;
|
|
10316
10705
|
const fresh = randomSecret();
|
|
10317
10706
|
await admin({ method: "PATCH", path: cBase, body: { clientSecret: fresh }, summary: "Rotate client secret" });
|
|
10318
|
-
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)")}`);
|
|
10319
10708
|
break;
|
|
10320
10709
|
}
|
|
10321
10710
|
case "delete": {
|
|
10322
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 }]);
|
|
10323
10712
|
if (!sure) break;
|
|
10324
10713
|
await admin({ method: "DELETE", path: cBase, summary: `Delete app client ${id}` });
|
|
10325
|
-
console.log(
|
|
10714
|
+
console.log(import_chalk47.default.green(" App client deleted."));
|
|
10326
10715
|
return;
|
|
10327
10716
|
}
|
|
10328
10717
|
}
|
|
@@ -10349,9 +10738,9 @@ async function providersMenu(cBase, clientLabel) {
|
|
|
10349
10738
|
const providers = Array.isArray(raw) ? raw : [];
|
|
10350
10739
|
console.log();
|
|
10351
10740
|
for (const p of providers) {
|
|
10352
|
-
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" : ""}`)}`);
|
|
10353
10742
|
}
|
|
10354
|
-
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."));
|
|
10355
10744
|
const { act } = await inquirer3.prompt([{
|
|
10356
10745
|
type: "list",
|
|
10357
10746
|
name: "act",
|
|
@@ -10409,23 +10798,23 @@ async function providersMenu(cBase, clientLabel) {
|
|
|
10409
10798
|
body.targetServerToken = routing;
|
|
10410
10799
|
}
|
|
10411
10800
|
await admin({ method: "POST", path: `${cBase}/providers`, body, summary: `Add ${type} login provider` });
|
|
10412
|
-
console.log(
|
|
10801
|
+
console.log(import_chalk47.default.green(` ${type} provider added.`));
|
|
10413
10802
|
} else {
|
|
10414
10803
|
const { p } = await inquirer3.prompt([{
|
|
10415
10804
|
type: "list",
|
|
10416
10805
|
name: "p",
|
|
10417
10806
|
message: act === "rm" ? "Remove which provider?" : "Reveal which secret?",
|
|
10418
|
-
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 }]
|
|
10419
10808
|
}]);
|
|
10420
10809
|
if (!p) continue;
|
|
10421
10810
|
if (act === "rm") {
|
|
10422
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 }]);
|
|
10423
10812
|
if (!sure) continue;
|
|
10424
10813
|
await admin({ method: "DELETE", path: `${cBase}/providers/${encodeURIComponent(p.id)}`, summary: `Remove ${p.type} provider` });
|
|
10425
|
-
console.log(
|
|
10814
|
+
console.log(import_chalk47.default.green(` ${p.type} removed.`));
|
|
10426
10815
|
} else {
|
|
10427
10816
|
const s = await admin({ method: "GET", path: `${cBase}/providers/${encodeURIComponent(p.id)}/secret`, summary: `Reveal ${p.type} provider secret` });
|
|
10428
|
-
console.log(` ${
|
|
10817
|
+
console.log(` ${import_chalk47.default.bold("client_secret")}: ${import_chalk47.default.green(s?.clientSecret ?? s?.client_secret ?? JSON.stringify(s))}`);
|
|
10429
10818
|
}
|
|
10430
10819
|
}
|
|
10431
10820
|
}
|
|
@@ -10433,7 +10822,7 @@ async function providersMenu(cBase, clientLabel) {
|
|
|
10433
10822
|
|
|
10434
10823
|
// src/commands/spec.ts
|
|
10435
10824
|
var fs12 = __toESM(require("fs"));
|
|
10436
|
-
var
|
|
10825
|
+
var import_chalk48 = __toESM(require("chalk"));
|
|
10437
10826
|
var import_ora21 = __toESM(require("ora"));
|
|
10438
10827
|
init_admin();
|
|
10439
10828
|
init_api();
|
|
@@ -10450,14 +10839,14 @@ async function runSpecGet(project, opts) {
|
|
|
10450
10839
|
}
|
|
10451
10840
|
async function runSpecSet(project, opts) {
|
|
10452
10841
|
if (!opts.file) {
|
|
10453
|
-
console.error(
|
|
10842
|
+
console.error(import_chalk48.default.red("--file <path> is required (OpenAPI JSON or YAML)."));
|
|
10454
10843
|
process.exit(1);
|
|
10455
10844
|
}
|
|
10456
10845
|
let specContent;
|
|
10457
10846
|
try {
|
|
10458
10847
|
specContent = fs12.readFileSync(opts.file, "utf-8");
|
|
10459
10848
|
} catch {
|
|
10460
|
-
console.error(
|
|
10849
|
+
console.error(import_chalk48.default.red(`Cannot read file: ${opts.file}`));
|
|
10461
10850
|
process.exit(1);
|
|
10462
10851
|
}
|
|
10463
10852
|
const { teamId } = await resolveTeam(opts.team);
|
|
@@ -10486,23 +10875,23 @@ async function runSpecSet(project, opts) {
|
|
|
10486
10875
|
function printSpecChangeReport(report, projectName) {
|
|
10487
10876
|
const r = report.routes;
|
|
10488
10877
|
if (r) {
|
|
10489
|
-
for (const a of r.added) console.log(
|
|
10490
|
-
for (const d of r.removed) console.log(
|
|
10491
|
-
for (const rn of r.renamed ?? []) console.log(
|
|
10492
|
-
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)`));
|
|
10493
10882
|
}
|
|
10494
|
-
if (report.applied?.mcp_regenerated) console.log(
|
|
10495
|
-
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.`));
|
|
10496
10885
|
for (const item of report.attention ?? []) {
|
|
10497
|
-
console.log(
|
|
10886
|
+
console.log(import_chalk48.default.yellow(` \u26A0 ${item.detail}`));
|
|
10498
10887
|
if (item.suggestion?.carry_from && item.suggestion.carry_to) {
|
|
10499
|
-
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}"`));
|
|
10500
10889
|
}
|
|
10501
10890
|
}
|
|
10502
10891
|
}
|
|
10503
10892
|
async function runSpecCarry(project, opts) {
|
|
10504
10893
|
if (!opts.method || !opts.from || !opts.to) {
|
|
10505
|
-
console.error(
|
|
10894
|
+
console.error(import_chalk48.default.red("--method, --from and --to are required."));
|
|
10506
10895
|
process.exit(1);
|
|
10507
10896
|
}
|
|
10508
10897
|
const { teamId } = await resolveTeam(opts.team);
|
|
@@ -10524,7 +10913,7 @@ async function runSpecCarry(project, opts) {
|
|
|
10524
10913
|
}
|
|
10525
10914
|
async function runSpecDeleteRule(project, opts) {
|
|
10526
10915
|
if (!opts.method || !opts.route) {
|
|
10527
|
-
console.error(
|
|
10916
|
+
console.error(import_chalk48.default.red("--method and --route are required."));
|
|
10528
10917
|
process.exit(1);
|
|
10529
10918
|
}
|
|
10530
10919
|
const { teamId } = await resolveTeam(opts.team);
|
|
@@ -10544,17 +10933,18 @@ async function runSpecDeleteRule(project, opts) {
|
|
|
10544
10933
|
}
|
|
10545
10934
|
|
|
10546
10935
|
// src/commands/agent.ts
|
|
10547
|
-
var
|
|
10936
|
+
var import_chalk50 = __toESM(require("chalk"));
|
|
10548
10937
|
var import_ora22 = __toESM(require("ora"));
|
|
10549
10938
|
init_auth();
|
|
10550
10939
|
init_admin();
|
|
10551
10940
|
init_resolve();
|
|
10552
10941
|
|
|
10553
10942
|
// src/lib/tools.ts
|
|
10554
|
-
var
|
|
10943
|
+
var import_chalk49 = __toESM(require("chalk"));
|
|
10555
10944
|
init_admin();
|
|
10556
10945
|
init_api();
|
|
10557
10946
|
init_resolve();
|
|
10947
|
+
init_paging();
|
|
10558
10948
|
async function proj(teamId, name, version2) {
|
|
10559
10949
|
return resolveProject(teamId, name, version2);
|
|
10560
10950
|
}
|
|
@@ -10581,15 +10971,15 @@ var TOOLS = [
|
|
|
10581
10971
|
const key = keys.dev ?? Object.values(keys)[0];
|
|
10582
10972
|
const url = `https://${a.name}.abz.run/${version2}/dev`;
|
|
10583
10973
|
const tryIt = buildTryItCurl(url, auth, key, res.tenant);
|
|
10584
|
-
const lines = [` ${
|
|
10585
|
-
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}`);
|
|
10586
10976
|
const envs = Object.keys(keys);
|
|
10587
10977
|
if (envs.length) {
|
|
10588
|
-
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):")}`);
|
|
10589
10979
|
const w = Math.max(...envs.map((e) => e.length));
|
|
10590
|
-
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])}`);
|
|
10591
10981
|
}
|
|
10592
|
-
if (tryIt) lines.push("", ` ${
|
|
10982
|
+
if (tryIt) lines.push("", ` ${import_chalk49.default.dim("Try it:")}`, ` ${import_chalk49.default.cyan(tryIt)}`);
|
|
10593
10983
|
return { ...res, proxy_url: url, keys, ...tryIt ? { try_it: tryIt } : {}, display: lines.join("\n") };
|
|
10594
10984
|
}
|
|
10595
10985
|
},
|
|
@@ -10644,7 +11034,20 @@ var TOOLS = [
|
|
|
10644
11034
|
name: "list_tenants",
|
|
10645
11035
|
description: "List tenants in the active team.",
|
|
10646
11036
|
params: {},
|
|
10647
|
-
|
|
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
|
+
}
|
|
10648
11051
|
},
|
|
10649
11052
|
{
|
|
10650
11053
|
name: "create_tenant",
|
|
@@ -10745,17 +11148,17 @@ function truncate(value, max = 1500) {
|
|
|
10745
11148
|
}
|
|
10746
11149
|
function printCost(resp) {
|
|
10747
11150
|
const line = formatBilling(resp.billing);
|
|
10748
|
-
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)`));
|
|
10749
11152
|
}
|
|
10750
11153
|
async function runAgent(opts) {
|
|
10751
11154
|
requireAuth();
|
|
10752
11155
|
const { teamId, teamName } = await resolveTeam(opts.team);
|
|
10753
11156
|
const { default: inquirer3 } = await import("inquirer");
|
|
10754
|
-
console.log(
|
|
10755
|
-
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'));
|
|
10756
11159
|
const history = [];
|
|
10757
11160
|
while (true) {
|
|
10758
|
-
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" }]);
|
|
10759
11162
|
const text = (input ?? "").trim();
|
|
10760
11163
|
if (!text) continue;
|
|
10761
11164
|
if (["exit", "quit", ":q"].includes(text.toLowerCase())) break;
|
|
@@ -10769,14 +11172,14 @@ async function runAgent(opts) {
|
|
|
10769
11172
|
} catch (err) {
|
|
10770
11173
|
spinner.stop();
|
|
10771
11174
|
if (err instanceof ApiError && err.status === 402) {
|
|
10772
|
-
console.log(
|
|
11175
|
+
console.log(import_chalk50.default.yellow(" Insufficient credits \u2014 top up to keep using the agent."));
|
|
10773
11176
|
break;
|
|
10774
11177
|
}
|
|
10775
11178
|
throw err;
|
|
10776
11179
|
}
|
|
10777
11180
|
history.push({ role: "assistant", content: resp.raw });
|
|
10778
11181
|
printCost(resp);
|
|
10779
|
-
if (resp.reply) console.log(
|
|
11182
|
+
if (resp.reply) console.log(import_chalk50.default.green("agent") + " \u203A " + resp.reply);
|
|
10780
11183
|
if (!resp.action) break;
|
|
10781
11184
|
const tool = findTool(resp.action.tool);
|
|
10782
11185
|
if (!tool) {
|
|
@@ -10801,11 +11204,11 @@ async function runAgent(opts) {
|
|
|
10801
11204
|
}
|
|
10802
11205
|
renderTrace();
|
|
10803
11206
|
if (step === MAX_TOOL_STEPS - 1) {
|
|
10804
|
-
console.log(
|
|
11207
|
+
console.log(import_chalk50.default.dim(" (paused after several steps \u2014 tell me how to continue)"));
|
|
10805
11208
|
}
|
|
10806
11209
|
}
|
|
10807
11210
|
}
|
|
10808
|
-
console.log(
|
|
11211
|
+
console.log(import_chalk50.default.dim("\nBye."));
|
|
10809
11212
|
}
|
|
10810
11213
|
|
|
10811
11214
|
// src/commands/config-browse.ts
|
|
@@ -11113,11 +11516,11 @@ function dig(blob, dotted) {
|
|
|
11113
11516
|
}
|
|
11114
11517
|
var readSetting = (s, cfg) => s.read ? s.read(cfg) : dig(cfg, s.key);
|
|
11115
11518
|
function show(v) {
|
|
11116
|
-
if (v === void 0) return
|
|
11117
|
-
if (v === null) return
|
|
11118
|
-
if (typeof v === "object") return
|
|
11119
|
-
if (typeof v === "boolean") return v ?
|
|
11120
|
-
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));
|
|
11121
11524
|
}
|
|
11122
11525
|
function parseValue(raw) {
|
|
11123
11526
|
if (raw === "true") return true;
|
|
@@ -11133,12 +11536,15 @@ function parseValue(raw) {
|
|
|
11133
11536
|
return raw;
|
|
11134
11537
|
}
|
|
11135
11538
|
async function fetchConfigBlob(proj2) {
|
|
11136
|
-
const
|
|
11137
|
-
|
|
11138
|
-
|
|
11139
|
-
|
|
11140
|
-
|
|
11141
|
-
|
|
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;
|
|
11142
11548
|
const row = rows.find((r) => r.project_id === proj2.projectId && r.api_version === proj2.apiVersion) ?? rows.find((r) => r.project_id === proj2.projectId);
|
|
11143
11549
|
return row?.config ?? {};
|
|
11144
11550
|
}
|
|
@@ -11164,10 +11570,10 @@ async function patchSetting(proj2, s, value, cfg) {
|
|
|
11164
11570
|
}
|
|
11165
11571
|
}
|
|
11166
11572
|
var loginFirst = (what) => {
|
|
11167
|
-
console.log(
|
|
11573
|
+
console.log(import_chalk51.default.yellow(`
|
|
11168
11574
|
Log in first to ${what}.`));
|
|
11169
|
-
console.log(
|
|
11170
|
-
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"));
|
|
11171
11577
|
};
|
|
11172
11578
|
async function runConfig(project, key, value, opts) {
|
|
11173
11579
|
const creds = loadCredentials();
|
|
@@ -11184,9 +11590,9 @@ async function runConfig(project, key, value, opts) {
|
|
|
11184
11590
|
}
|
|
11185
11591
|
const setting = SETTINGS.find((s) => s.key === key);
|
|
11186
11592
|
if (!setting) {
|
|
11187
|
-
console.error(
|
|
11188
|
-
console.error(
|
|
11189
|
-
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>`.)"));
|
|
11190
11596
|
process.exit(1);
|
|
11191
11597
|
}
|
|
11192
11598
|
if (value === void 0) {
|
|
@@ -11201,7 +11607,7 @@ async function pickProject(teamId) {
|
|
|
11201
11607
|
const { getProjects: getProjects2 } = await Promise.resolve().then(() => (init_api(), api_exports));
|
|
11202
11608
|
const projects = await getProjects2(teamId).catch(() => []);
|
|
11203
11609
|
if (!projects.length) {
|
|
11204
|
-
console.error(
|
|
11610
|
+
console.error(import_chalk51.default.red("No projects in this team. Create one: `npx apiblaze create`."));
|
|
11205
11611
|
process.exit(1);
|
|
11206
11612
|
}
|
|
11207
11613
|
const { default: inquirer3 } = await import("inquirer");
|
|
@@ -11209,7 +11615,7 @@ async function pickProject(teamId) {
|
|
|
11209
11615
|
type: "list",
|
|
11210
11616
|
name: "picked",
|
|
11211
11617
|
message: "Which project?",
|
|
11212
|
-
choices: projects.map((p) => ({ name: `${p.projectName} ${
|
|
11618
|
+
choices: projects.map((p) => ({ name: `${p.projectName} ${import_chalk51.default.dim("v" + p.apiVersion)}`, value: p }))
|
|
11213
11619
|
}]);
|
|
11214
11620
|
return { projectId: picked.projectId, projectName: picked.projectName, apiVersion: picked.apiVersion, teamId, tenant: picked.tenant };
|
|
11215
11621
|
}
|
|
@@ -11220,25 +11626,25 @@ function printAll(proj2, cfg, json) {
|
|
|
11220
11626
|
console.log(JSON.stringify(out, null, 2));
|
|
11221
11627
|
return;
|
|
11222
11628
|
}
|
|
11223
|
-
console.log(
|
|
11629
|
+
console.log(import_chalk51.default.bold(`
|
|
11224
11630
|
${proj2.projectName} v${proj2.apiVersion} \u2014 settings
|
|
11225
11631
|
`));
|
|
11226
11632
|
for (const group2 of SETTING_GROUPS) {
|
|
11227
|
-
console.log(
|
|
11633
|
+
console.log(import_chalk51.default.bold(group2));
|
|
11228
11634
|
for (const s of SETTINGS.filter((x) => x.group === group2)) {
|
|
11229
|
-
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)}`);
|
|
11230
11636
|
}
|
|
11231
11637
|
console.log();
|
|
11232
11638
|
}
|
|
11233
|
-
console.log(
|
|
11639
|
+
console.log(import_chalk51.default.dim("Change one: apiblaze config " + proj2.projectName + " <key> <value> (add --verbose for the API call)"));
|
|
11234
11640
|
}
|
|
11235
11641
|
async function discoveryMenu(project) {
|
|
11236
11642
|
const { default: inquirer3 } = await import("inquirer");
|
|
11237
|
-
console.log(
|
|
11643
|
+
console.log(import_chalk51.default.bold(`
|
|
11238
11644
|
APIblaze proxy configuration${project ? ` \u2014 ${project}` : ""}
|
|
11239
11645
|
`));
|
|
11240
|
-
console.log(
|
|
11241
|
-
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"));
|
|
11242
11648
|
for (; ; ) {
|
|
11243
11649
|
const { pick: pick2 } = await inquirer3.prompt([{
|
|
11244
11650
|
type: "list",
|
|
@@ -11246,13 +11652,13 @@ APIblaze proxy configuration${project ? ` \u2014 ${project}` : ""}
|
|
|
11246
11652
|
message: "Explore:",
|
|
11247
11653
|
pageSize: 20,
|
|
11248
11654
|
choices: [
|
|
11249
|
-
new inquirer3.Separator(
|
|
11655
|
+
new inquirer3.Separator(import_chalk51.default.bold("\u2014 Settings \u2014")),
|
|
11250
11656
|
...SETTING_GROUPS.map((g) => ({
|
|
11251
|
-
name: `${g} ${
|
|
11657
|
+
name: `${g} ${import_chalk51.default.dim(SETTINGS.filter((s) => s.group === g).map((s) => s.label).join(", "))}`,
|
|
11252
11658
|
value: { kind: "settings", g }
|
|
11253
11659
|
})),
|
|
11254
|
-
new inquirer3.Separator(
|
|
11255
|
-
...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 } })),
|
|
11256
11662
|
new inquirer3.Separator(),
|
|
11257
11663
|
{ name: "Exit", value: { kind: "exit" } }
|
|
11258
11664
|
]
|
|
@@ -11261,24 +11667,24 @@ APIblaze proxy configuration${project ? ` \u2014 ${project}` : ""}
|
|
|
11261
11667
|
if (pick2.kind === "settings") {
|
|
11262
11668
|
console.log();
|
|
11263
11669
|
for (const s of SETTINGS.filter((x) => x.group === pick2.g)) {
|
|
11264
|
-
console.log(` ${
|
|
11265
|
-
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)}`);
|
|
11266
11672
|
}
|
|
11267
11673
|
loginFirst("view or change these settings");
|
|
11268
11674
|
} else {
|
|
11269
11675
|
const f = pick2.f;
|
|
11270
11676
|
console.log(`
|
|
11271
|
-
${
|
|
11677
|
+
${import_chalk51.default.bold(f.label)} \u2014 ${f.desc}`);
|
|
11272
11678
|
loginFirst(`use ${f.label.toLowerCase()}`);
|
|
11273
11679
|
}
|
|
11274
11680
|
}
|
|
11275
11681
|
}
|
|
11276
11682
|
async function navigator(proj2, cfg, opts) {
|
|
11277
11683
|
const { default: inquirer3 } = await import("inquirer");
|
|
11278
|
-
console.log(
|
|
11684
|
+
console.log(import_chalk51.default.bold(`
|
|
11279
11685
|
${proj2.projectName} v${proj2.apiVersion} \u2014 configuration
|
|
11280
11686
|
`));
|
|
11281
|
-
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"));
|
|
11282
11688
|
let blob = cfg;
|
|
11283
11689
|
for (; ; ) {
|
|
11284
11690
|
const { pick: pick2 } = await inquirer3.prompt([{
|
|
@@ -11287,10 +11693,10 @@ ${proj2.projectName} v${proj2.apiVersion} \u2014 configuration
|
|
|
11287
11693
|
message: "Where to?",
|
|
11288
11694
|
pageSize: 20,
|
|
11289
11695
|
choices: [
|
|
11290
|
-
new inquirer3.Separator(
|
|
11696
|
+
new inquirer3.Separator(import_chalk51.default.bold("\u2014 Settings \u2014")),
|
|
11291
11697
|
...SETTING_GROUPS.map((g) => ({ name: g, value: { kind: "settings", g } })),
|
|
11292
|
-
new inquirer3.Separator(
|
|
11293
|
-
...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 } })),
|
|
11294
11700
|
new inquirer3.Separator(),
|
|
11295
11701
|
{ name: "Show all settings", value: { kind: "list" } },
|
|
11296
11702
|
{ name: "Exit", value: { kind: "exit" } }
|
|
@@ -11330,7 +11736,7 @@ ${proj2.projectName} v${proj2.apiVersion} \u2014 configuration
|
|
|
11330
11736
|
}
|
|
11331
11737
|
}
|
|
11332
11738
|
} catch (err) {
|
|
11333
|
-
console.error(
|
|
11739
|
+
console.error(import_chalk51.default.red(` ${err instanceof Error ? err.message : String(err)}`));
|
|
11334
11740
|
}
|
|
11335
11741
|
}
|
|
11336
11742
|
}
|
|
@@ -11344,7 +11750,7 @@ async function settingsGroup(proj2, cfg, group2) {
|
|
|
11344
11750
|
message: group2 + ":",
|
|
11345
11751
|
pageSize: 16,
|
|
11346
11752
|
choices: [
|
|
11347
|
-
...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 })),
|
|
11348
11754
|
new inquirer3.Separator(),
|
|
11349
11755
|
{ name: "\u2190 Back", value: null }
|
|
11350
11756
|
]
|
|
@@ -11362,7 +11768,7 @@ async function settingsGroup(proj2, cfg, group2) {
|
|
|
11362
11768
|
} else if (s.type === "number") {
|
|
11363
11769
|
const { v } = await inquirer3.prompt([{ type: "input", name: "v", message: `${s.label} (number):`, default: readSetting(s, cfg) }]);
|
|
11364
11770
|
if (v === "" || Number.isNaN(Number(v))) {
|
|
11365
|
-
console.log(
|
|
11771
|
+
console.log(import_chalk51.default.yellow(" Not a number \u2014 unchanged."));
|
|
11366
11772
|
continue;
|
|
11367
11773
|
}
|
|
11368
11774
|
value = Number(v);
|
|
@@ -11443,7 +11849,7 @@ async function buildCondition(phase) {
|
|
|
11443
11849
|
const items = [];
|
|
11444
11850
|
for (; ; ) {
|
|
11445
11851
|
const a = await inquirer3.prompt([
|
|
11446
|
-
{ type: "input", name: "source", message: `Condition field ${
|
|
11852
|
+
{ type: "input", name: "source", message: `Condition field ${import_chalk51.default.dim(srcHint)}:`, validate: (s) => !!s || "required" },
|
|
11447
11853
|
{ type: "list", name: "operator", message: "Operator:", choices: [
|
|
11448
11854
|
"eq",
|
|
11449
11855
|
"neq",
|
|
@@ -11474,7 +11880,7 @@ async function buildCondition(phase) {
|
|
|
11474
11880
|
function showCondition(cond) {
|
|
11475
11881
|
if (!Array.isArray(cond) || !cond.length) return "";
|
|
11476
11882
|
const s = cond.map((c) => `${c.source} ${c.operator}${c.value !== void 0 ? ` "${c.value}"` : ""}${c.logicOp ? ` ${c.logicOp}` : ""}`).join(" ");
|
|
11477
|
-
return
|
|
11883
|
+
return import_chalk51.default.dim(` when ${s}`);
|
|
11478
11884
|
}
|
|
11479
11885
|
async function transformsMenu(proj2) {
|
|
11480
11886
|
const { default: inquirer3 } = await import("inquirer");
|
|
@@ -11483,12 +11889,12 @@ async function transformsMenu(proj2) {
|
|
|
11483
11889
|
const out = await admin({ method: "GET", path: base2, summary: "List transform rules" });
|
|
11484
11890
|
const rules = out?.rules ?? [];
|
|
11485
11891
|
console.log();
|
|
11486
|
-
if (!rules.length) console.log(
|
|
11892
|
+
if (!rules.length) console.log(import_chalk51.default.dim(" No transform rules yet."));
|
|
11487
11893
|
for (const r of rules) {
|
|
11488
11894
|
const a = r.action ?? {};
|
|
11489
11895
|
const fns = [...a.source_fns ?? [], ...a.dest_fns ?? []].map((f) => f.fn);
|
|
11490
|
-
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 ?
|
|
11491
|
-
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)}`);
|
|
11492
11898
|
}
|
|
11493
11899
|
const { act } = await inquirer3.prompt([{
|
|
11494
11900
|
type: "list",
|
|
@@ -11500,7 +11906,7 @@ async function transformsMenu(proj2) {
|
|
|
11500
11906
|
{ name: "Enable/disable a rule", value: "toggle" },
|
|
11501
11907
|
{ name: "Delete a rule", value: "delete" }
|
|
11502
11908
|
] : [],
|
|
11503
|
-
{ name:
|
|
11909
|
+
{ name: import_chalk51.default.dim("Add from raw JSON (grouped conditions, lookup tables, \u2026)"), value: "raw" },
|
|
11504
11910
|
{ name: "\u2190 Back", value: "back" }
|
|
11505
11911
|
]
|
|
11506
11912
|
}]);
|
|
@@ -11513,11 +11919,11 @@ async function transformsMenu(proj2) {
|
|
|
11513
11919
|
}]);
|
|
11514
11920
|
const body = parseValue(raw);
|
|
11515
11921
|
if (!body || typeof body !== "object" || Array.isArray(body)) {
|
|
11516
|
-
console.log(
|
|
11922
|
+
console.log(import_chalk51.default.yellow(" Not a JSON object \u2014 skipped."));
|
|
11517
11923
|
continue;
|
|
11518
11924
|
}
|
|
11519
11925
|
await admin({ method: "POST", path: base2, body, summary: "Create transform rule (raw JSON)" });
|
|
11520
|
-
console.log(
|
|
11926
|
+
console.log(import_chalk51.default.green(" Rule created."));
|
|
11521
11927
|
continue;
|
|
11522
11928
|
}
|
|
11523
11929
|
if (act === "add") {
|
|
@@ -11533,7 +11939,7 @@ async function transformsMenu(proj2) {
|
|
|
11533
11939
|
{ name: "Remove a field", value: "remove" }
|
|
11534
11940
|
] }
|
|
11535
11941
|
]);
|
|
11536
|
-
const fieldHint =
|
|
11942
|
+
const fieldHint = import_chalk51.default.dim("(e.g. header:x-api-version, param:limit, bodyvar:user.id)");
|
|
11537
11943
|
let action2;
|
|
11538
11944
|
if (ans.kind === "hardcode") {
|
|
11539
11945
|
const a = await inquirer3.prompt([
|
|
@@ -11580,16 +11986,16 @@ async function transformsMenu(proj2) {
|
|
|
11580
11986
|
type: "list",
|
|
11581
11987
|
name: "rule",
|
|
11582
11988
|
message: act === "toggle" ? "Which rule?" : "Delete which rule?",
|
|
11583
|
-
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 }]
|
|
11584
11990
|
}]);
|
|
11585
11991
|
if (!rule) continue;
|
|
11586
11992
|
if (act === "toggle") {
|
|
11587
11993
|
const flipped = { ...rule, enabled: rule.enabled === false };
|
|
11588
11994
|
await admin({ method: "PUT", path: `${base2}/${rule.id}`, body: flipped, summary: `${flipped.enabled ? "Enable" : "Disable"} transform "${rule.name}"` });
|
|
11589
|
-
console.log(
|
|
11995
|
+
console.log(import_chalk51.default.green(` ${rule.name} \u2192 ${flipped.enabled ? "enabled" : "disabled"}`));
|
|
11590
11996
|
} else {
|
|
11591
11997
|
await admin({ method: "DELETE", path: `${base2}/${rule.id}`, summary: `Delete transform "${rule.name}"` });
|
|
11592
|
-
console.log(
|
|
11998
|
+
console.log(import_chalk51.default.green(` ${rule.name} deleted.`));
|
|
11593
11999
|
}
|
|
11594
12000
|
}
|
|
11595
12001
|
}
|
|
@@ -11601,9 +12007,9 @@ async function mappingsMenu(proj2) {
|
|
|
11601
12007
|
const out = await admin({ method: "GET", path: base2, summary: "List mapping tables" });
|
|
11602
12008
|
const tables = out?.mappings ?? out?.tables ?? [];
|
|
11603
12009
|
console.log();
|
|
11604
|
-
if (!tables.length) console.log(
|
|
12010
|
+
if (!tables.length) console.log(import_chalk51.default.dim(" No mapping tables yet."));
|
|
11605
12011
|
for (const t of tables) {
|
|
11606
|
-
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" : ""}`)}`);
|
|
11607
12013
|
}
|
|
11608
12014
|
const { act } = await inquirer3.prompt([{
|
|
11609
12015
|
type: "list",
|
|
@@ -11623,11 +12029,11 @@ async function mappingsMenu(proj2) {
|
|
|
11623
12029
|
]);
|
|
11624
12030
|
const entries2 = parseValue(a.entries);
|
|
11625
12031
|
if (!Array.isArray(entries2)) {
|
|
11626
|
-
console.log(
|
|
12032
|
+
console.log(import_chalk51.default.yellow(" Entries must be a JSON array \u2014 not created."));
|
|
11627
12033
|
continue;
|
|
11628
12034
|
}
|
|
11629
12035
|
await admin({ method: "POST", path: base2, body: { name: a.name, entries: entries2 }, summary: `Create mapping table "${a.name}"` });
|
|
11630
|
-
console.log(
|
|
12036
|
+
console.log(import_chalk51.default.green(` Table "${a.name}" created.`));
|
|
11631
12037
|
} else {
|
|
11632
12038
|
const { table } = await inquirer3.prompt([{
|
|
11633
12039
|
type: "list",
|
|
@@ -11637,7 +12043,7 @@ async function mappingsMenu(proj2) {
|
|
|
11637
12043
|
}]);
|
|
11638
12044
|
if (!table) continue;
|
|
11639
12045
|
await admin({ method: "DELETE", path: `${base2}/${table.id}`, summary: `Delete mapping table "${table.name}"` });
|
|
11640
|
-
console.log(
|
|
12046
|
+
console.log(import_chalk51.default.green(` ${table.name} deleted.`));
|
|
11641
12047
|
}
|
|
11642
12048
|
}
|
|
11643
12049
|
}
|
|
@@ -11648,14 +12054,14 @@ async function tenantsMenu(proj2, opts) {
|
|
|
11648
12054
|
const out = await admin({ method: "GET", path: base2, summary: "List attached tenants" });
|
|
11649
12055
|
const tenants = out?.tenants ?? [];
|
|
11650
12056
|
console.log();
|
|
11651
|
-
if (!tenants.length) console.log(
|
|
11652
|
-
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 ?? "")}`);
|
|
11653
12059
|
const { act } = await inquirer3.prompt([{
|
|
11654
12060
|
type: "list",
|
|
11655
12061
|
name: "act",
|
|
11656
12062
|
message: "Tenants:",
|
|
11657
12063
|
choices: [
|
|
11658
|
-
{ 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" },
|
|
11659
12065
|
{ name: "Attach a tenant to this project", value: "attach" },
|
|
11660
12066
|
...tenants.length ? [{ name: "Detach a tenant from this project", value: "detach" }] : [],
|
|
11661
12067
|
{ name: "\u2190 Back", value: "back" }
|
|
@@ -11678,7 +12084,7 @@ async function tenantsMenu(proj2, opts) {
|
|
|
11678
12084
|
}]);
|
|
11679
12085
|
if (!t) continue;
|
|
11680
12086
|
await admin({ method: "DELETE", path: `${base2}/${encodeURIComponent(t.tenant_name ?? t.name)}`, summary: `Detach tenant ${t.tenant_name ?? t.name}` });
|
|
11681
|
-
console.log(
|
|
12087
|
+
console.log(import_chalk51.default.green(` Detached ${t.tenant_name ?? t.name}.`));
|
|
11682
12088
|
}
|
|
11683
12089
|
}
|
|
11684
12090
|
}
|
|
@@ -11721,7 +12127,7 @@ async function specMenu(proj2, opts) {
|
|
|
11721
12127
|
choices: [
|
|
11722
12128
|
{ name: "Print the stored spec", value: "get" },
|
|
11723
12129
|
{ name: "Refresh the spec from its source", value: "refresh" },
|
|
11724
|
-
{ name:
|
|
12130
|
+
{ name: import_chalk51.default.dim("Build the spec by chatting over real traffic \u2192 agent"), value: "agent" },
|
|
11725
12131
|
{ name: "\u2190 Back", value: "back" }
|
|
11726
12132
|
]
|
|
11727
12133
|
}]);
|
|
@@ -11729,7 +12135,7 @@ async function specMenu(proj2, opts) {
|
|
|
11729
12135
|
if (act === "get") await runSpecGet(proj2.projectName, { team: opts.team, apiversion: proj2.apiVersion });
|
|
11730
12136
|
else if (act === "refresh") {
|
|
11731
12137
|
await admin({ method: "POST", path: `/projects/${proj2.projectId}/${proj2.apiVersion}/refresh-spec`, summary: "Refresh spec from source" });
|
|
11732
|
-
console.log(
|
|
12138
|
+
console.log(import_chalk51.default.green(" Spec refresh triggered."));
|
|
11733
12139
|
} else await runOpenapi(proj2.projectName, proj2.apiVersion);
|
|
11734
12140
|
}
|
|
11735
12141
|
async function agentsMenu(proj2, opts) {
|
|
@@ -11754,12 +12160,13 @@ async function agentsMenu(proj2, opts) {
|
|
|
11754
12160
|
}
|
|
11755
12161
|
|
|
11756
12162
|
// src/commands/group.ts
|
|
11757
|
-
var
|
|
12163
|
+
var import_chalk52 = __toESM(require("chalk"));
|
|
11758
12164
|
var import_ora24 = __toESM(require("ora"));
|
|
11759
12165
|
init_auth();
|
|
11760
12166
|
init_admin();
|
|
11761
12167
|
init_resolve();
|
|
11762
12168
|
init_types();
|
|
12169
|
+
init_paging();
|
|
11763
12170
|
var DASHBOARD_BASE7 = process.env.APIBLAZE_DASHBOARD_BASE || "https://dashboard.apiblaze.com";
|
|
11764
12171
|
var IAM_BASE = process.env.APIBLAZE_IAM_BASE || "https://iam.apiblaze.com";
|
|
11765
12172
|
async function resolveTransport(opts) {
|
|
@@ -11768,8 +12175,11 @@ async function resolveTransport(opts) {
|
|
|
11768
12175
|
const { teamId } = await resolveTeam(void 0);
|
|
11769
12176
|
let tenant2 = (opts.tenant ?? "").trim().toLowerCase();
|
|
11770
12177
|
if (!tenant2) {
|
|
11771
|
-
const
|
|
11772
|
-
|
|
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));
|
|
11773
12183
|
if (tenants.length === 0) throw new Error("No tenants in your team yet. Create a proxy first, or pass --tenant.");
|
|
11774
12184
|
if (tenants.length === 1) tenant2 = tenants[0];
|
|
11775
12185
|
else {
|
|
@@ -11830,8 +12240,11 @@ async function groupByName(t, name) {
|
|
|
11830
12240
|
return g;
|
|
11831
12241
|
}
|
|
11832
12242
|
async function listUsers(t) {
|
|
11833
|
-
const
|
|
11834
|
-
|
|
12243
|
+
const walked = await pageAll(
|
|
12244
|
+
(cursor) => t.call(withPage("/v1/admin/users", cursor)),
|
|
12245
|
+
(b) => b?.users
|
|
12246
|
+
);
|
|
12247
|
+
return walked.items;
|
|
11835
12248
|
}
|
|
11836
12249
|
async function userByRef(t, ref) {
|
|
11837
12250
|
const users = await listUsers(t);
|
|
@@ -11848,7 +12261,7 @@ async function userByRefOrCreate(t, ref) {
|
|
|
11848
12261
|
if (!ref.includes("@")) throw e;
|
|
11849
12262
|
const created = await t.call("/v1/admin/users", "POST", { email: ref.trim().toLowerCase() });
|
|
11850
12263
|
if (!created?.abz_sub) throw e;
|
|
11851
|
-
console.log(
|
|
12264
|
+
console.log(import_chalk52.default.dim(` ${ref} wasn't a user yet \u2014 created.`));
|
|
11852
12265
|
return { abz_sub: created.abz_sub, email: created.email ?? ref, display_name: null };
|
|
11853
12266
|
}
|
|
11854
12267
|
}
|
|
@@ -11856,11 +12269,11 @@ async function runGroupList(opts) {
|
|
|
11856
12269
|
const t = await resolveTransport(opts);
|
|
11857
12270
|
const groups = await listGroups(t);
|
|
11858
12271
|
if (opts.json) return void console.log(JSON.stringify(groups));
|
|
11859
|
-
if (!groups.length) return void console.log(
|
|
11860
|
-
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}:`));
|
|
11861
12274
|
for (const g of groups) {
|
|
11862
|
-
const sealed = g.name === "apiblaze_admins" ?
|
|
11863
|
-
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}`);
|
|
11864
12277
|
}
|
|
11865
12278
|
}
|
|
11866
12279
|
async function runGroupCreate(name, opts) {
|
|
@@ -11888,7 +12301,7 @@ async function runGroupCreate(name, opts) {
|
|
|
11888
12301
|
const spinner = (0, import_ora24.default)(`Creating group ${name}...`).start();
|
|
11889
12302
|
try {
|
|
11890
12303
|
await t.call("/v1/admin/groups", "POST", { name, admins: [adminSub] });
|
|
11891
|
-
spinner.succeed(`Group ${
|
|
12304
|
+
spinner.succeed(`Group ${import_chalk52.default.bold(name)} created in ${t.tenant}.`);
|
|
11892
12305
|
} catch (e) {
|
|
11893
12306
|
spinner.fail("Create failed.");
|
|
11894
12307
|
throw e;
|
|
@@ -11900,7 +12313,7 @@ async function runGroupDelete(name, opts) {
|
|
|
11900
12313
|
const spinner = (0, import_ora24.default)(`Deleting group ${name}...`).start();
|
|
11901
12314
|
try {
|
|
11902
12315
|
await t.call(`/v1/admin/groups/${encodeURIComponent(g.id)}`, "DELETE");
|
|
11903
|
-
spinner.succeed(`Group ${
|
|
12316
|
+
spinner.succeed(`Group ${import_chalk52.default.bold(name)} deleted.`);
|
|
11904
12317
|
} catch (e) {
|
|
11905
12318
|
spinner.fail("Delete failed.");
|
|
11906
12319
|
throw e;
|
|
@@ -11911,13 +12324,19 @@ async function runGroupMembers(name, opts) {
|
|
|
11911
12324
|
const g = await groupByName(t, name);
|
|
11912
12325
|
const detail = await t.call(`/v1/admin/groups/${encodeURIComponent(g.id)}`);
|
|
11913
12326
|
const subs = await t.call(`/v1/admin/groups/${encodeURIComponent(g.id)}/subgroups`);
|
|
11914
|
-
|
|
11915
|
-
|
|
11916
|
-
|
|
11917
|
-
|
|
11918
|
-
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"));
|
|
11919
12338
|
const sg = subs?.subgroups ?? [];
|
|
11920
|
-
if (sg.length) console.log(
|
|
12339
|
+
if (sg.length) console.log(import_chalk52.default.dim(` subgroups: ${sg.map((s) => s.name).join(", ")}`));
|
|
11921
12340
|
}
|
|
11922
12341
|
async function runGroupAddUser(userRef, groupName, opts) {
|
|
11923
12342
|
const t = await resolveTransport(opts);
|
|
@@ -11927,7 +12346,7 @@ async function runGroupAddUser(userRef, groupName, opts) {
|
|
|
11927
12346
|
const spinner = (0, import_ora24.default)(`Adding ${label2(u)} to ${g.name}...`).start();
|
|
11928
12347
|
try {
|
|
11929
12348
|
await t.call(`/v1/admin/groups/${encodeURIComponent(g.id)}/members`, "POST", { abz_sub: u.abz_sub, role });
|
|
11930
|
-
spinner.succeed(`${
|
|
12349
|
+
spinner.succeed(`${import_chalk52.default.bold(label2(u))} added to ${import_chalk52.default.bold(g.name)} as ${role}.`);
|
|
11931
12350
|
} catch (e) {
|
|
11932
12351
|
spinner.fail("Add failed.");
|
|
11933
12352
|
throw e;
|
|
@@ -11939,7 +12358,7 @@ async function runGroupRemoveUser(userRef, groupName, opts) {
|
|
|
11939
12358
|
const spinner = (0, import_ora24.default)(`Removing ${label2(u)} from ${g.name}...`).start();
|
|
11940
12359
|
try {
|
|
11941
12360
|
await t.call(`/v1/admin/groups/${encodeURIComponent(g.id)}/members/_?abz_sub=${encodeURIComponent(u.abz_sub)}`, "DELETE");
|
|
11942
|
-
spinner.succeed(`${
|
|
12361
|
+
spinner.succeed(`${import_chalk52.default.bold(label2(u))} removed from ${import_chalk52.default.bold(g.name)}.`);
|
|
11943
12362
|
} catch (e) {
|
|
11944
12363
|
spinner.fail("Remove failed.");
|
|
11945
12364
|
throw e;
|
|
@@ -11951,7 +12370,7 @@ async function runGroupAddGroup(childName, parentName, opts) {
|
|
|
11951
12370
|
const spinner = (0, import_ora24.default)(`Nesting ${child.name} under ${parent.name}...`).start();
|
|
11952
12371
|
try {
|
|
11953
12372
|
await t.call(`/v1/admin/groups/${encodeURIComponent(parent.id)}/subgroups`, "POST", { child_group_id: child.id });
|
|
11954
|
-
spinner.succeed(`${
|
|
12373
|
+
spinner.succeed(`${import_chalk52.default.bold(child.name)} is now a subgroup of ${import_chalk52.default.bold(parent.name)}.`);
|
|
11955
12374
|
} catch (e) {
|
|
11956
12375
|
spinner.fail("Nest failed.");
|
|
11957
12376
|
throw e;
|
|
@@ -11963,7 +12382,7 @@ async function runGroupRemoveGroup(childName, parentName, opts) {
|
|
|
11963
12382
|
const spinner = (0, import_ora24.default)(`Un-nesting ${child.name} from ${parent.name}...`).start();
|
|
11964
12383
|
try {
|
|
11965
12384
|
await t.call(`/v1/admin/groups/${encodeURIComponent(parent.id)}/subgroups/${encodeURIComponent(child.id)}`, "DELETE");
|
|
11966
|
-
spinner.succeed(`${
|
|
12385
|
+
spinner.succeed(`${import_chalk52.default.bold(child.name)} un-nested from ${import_chalk52.default.bold(parent.name)}.`);
|
|
11967
12386
|
} catch (e) {
|
|
11968
12387
|
spinner.fail("Un-nest failed.");
|
|
11969
12388
|
throw e;
|
|
@@ -11974,13 +12393,13 @@ async function runGroupUsers(opts) {
|
|
|
11974
12393
|
const users = await listUsers(t);
|
|
11975
12394
|
const observed = await t.call("/v1/admin/observed-identities").catch(() => ({ identities: [] }));
|
|
11976
12395
|
if (opts.json) return void console.log(JSON.stringify({ users, observed: observed?.identities ?? [] }));
|
|
11977
|
-
console.log(
|
|
11978
|
-
for (const u of users) console.log(` ${label2(u)} ${
|
|
11979
|
-
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"));
|
|
11980
12399
|
const fresh = (observed?.identities ?? []).filter((o) => !o.provisioned);
|
|
11981
12400
|
if (fresh.length) {
|
|
11982
|
-
console.log(
|
|
11983
|
-
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 ?? "")}`);
|
|
11984
12403
|
}
|
|
11985
12404
|
}
|
|
11986
12405
|
|
|
@@ -11989,7 +12408,7 @@ init_iam();
|
|
|
11989
12408
|
init_admins();
|
|
11990
12409
|
|
|
11991
12410
|
// src/commands/preapprove.ts
|
|
11992
|
-
var
|
|
12411
|
+
var import_chalk53 = __toESM(require("chalk"));
|
|
11993
12412
|
var import_ora25 = __toESM(require("ora"));
|
|
11994
12413
|
async function runPreapprove(who, opts) {
|
|
11995
12414
|
const tenant2 = await resolveProducerTenant(opts.tenant);
|
|
@@ -11997,14 +12416,14 @@ async function runPreapprove(who, opts) {
|
|
|
11997
12416
|
const rules = await listPreapprovalRules(tenant2);
|
|
11998
12417
|
if (opts.json) return void console.log(JSON.stringify(rules, null, 2));
|
|
11999
12418
|
if (!rules.length) {
|
|
12000
|
-
console.log(
|
|
12001
|
-
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`));
|
|
12002
12421
|
return;
|
|
12003
12422
|
}
|
|
12004
|
-
console.log(
|
|
12423
|
+
console.log(import_chalk53.default.dim(`Pre-approved for ${import_chalk53.default.bold(tenant2)}:`));
|
|
12005
12424
|
for (const r of rules) {
|
|
12006
|
-
const tag = r.kind === "domain" ?
|
|
12007
|
-
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(", ")}`) : "";
|
|
12008
12427
|
console.log(` ${tag}${grp}`);
|
|
12009
12428
|
}
|
|
12010
12429
|
return;
|
|
@@ -12015,17 +12434,17 @@ async function runPreapprove(who, opts) {
|
|
|
12015
12434
|
if (opts.remove) {
|
|
12016
12435
|
const spinner2 = (0, import_ora25.default)(`Removing ${who} from ${tenant2}\u2026`).start();
|
|
12017
12436
|
const { removed, value } = await removePreapprovalRule(tenant2, who);
|
|
12018
|
-
if (removed) spinner2.succeed(`${
|
|
12019
|
-
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.`);
|
|
12020
12439
|
return;
|
|
12021
12440
|
}
|
|
12022
12441
|
const spinner = (0, import_ora25.default)(`Pre-approving ${who} for ${tenant2}\u2026`).start();
|
|
12023
12442
|
try {
|
|
12024
12443
|
const { kind, value } = await addPreapprovalRule(tenant2, who, opts.group);
|
|
12025
12444
|
const what = kind === "domain" ? `Anyone @${value}` : value;
|
|
12026
|
-
spinner.succeed(`${
|
|
12027
|
-
if (opts.group?.length) console.log(
|
|
12028
|
-
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`));
|
|
12029
12448
|
} catch (err) {
|
|
12030
12449
|
spinner.fail("Could not add the rule.");
|
|
12031
12450
|
throw err;
|
|
@@ -12033,11 +12452,12 @@ async function runPreapprove(who, opts) {
|
|
|
12033
12452
|
}
|
|
12034
12453
|
|
|
12035
12454
|
// src/commands/consumer.ts
|
|
12036
|
-
var
|
|
12455
|
+
var import_chalk54 = __toESM(require("chalk"));
|
|
12037
12456
|
var import_ora26 = __toESM(require("ora"));
|
|
12038
12457
|
init_admin();
|
|
12039
12458
|
init_resolve();
|
|
12040
12459
|
init_caller();
|
|
12460
|
+
init_paging();
|
|
12041
12461
|
var DEFAULT_SCOPE = "openid email profile offline_access";
|
|
12042
12462
|
async function consumerFetch(creds, suffix, init) {
|
|
12043
12463
|
const fresh = await validConsumerToken(creds) ?? creds;
|
|
@@ -12055,7 +12475,7 @@ async function consumerFetch(creds, suffix, init) {
|
|
|
12055
12475
|
function requireConsumer2() {
|
|
12056
12476
|
const c = loadConsumer();
|
|
12057
12477
|
if (!c) {
|
|
12058
|
-
console.error(
|
|
12478
|
+
console.error(import_chalk54.default.red("Not logged in as a consumer. Run `apiblaze consumer login` first."));
|
|
12059
12479
|
process.exit(1);
|
|
12060
12480
|
}
|
|
12061
12481
|
return c;
|
|
@@ -12066,7 +12486,7 @@ async function runConsumerLogin(opts) {
|
|
|
12066
12486
|
let clientId = opts.client;
|
|
12067
12487
|
if (clientId) {
|
|
12068
12488
|
if (!tenant2) {
|
|
12069
|
-
console.error(
|
|
12489
|
+
console.error(import_chalk54.default.red("When using --client, also pass --tenant <slug> (it sets which portal/keys host to use)."));
|
|
12070
12490
|
process.exit(1);
|
|
12071
12491
|
}
|
|
12072
12492
|
} else {
|
|
@@ -12084,18 +12504,18 @@ async function runConsumerLogin(opts) {
|
|
|
12084
12504
|
const usable = (Array.isArray(clients) ? clients : []).filter((c) => c && (c.client_id || c.clientId));
|
|
12085
12505
|
const pick2 = usable.find((c) => c.is_default || c.default) ?? usable.find((c) => c.verified !== false) ?? usable[0];
|
|
12086
12506
|
if (!pick2) {
|
|
12087
|
-
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).`));
|
|
12088
12508
|
process.exit(1);
|
|
12089
12509
|
}
|
|
12090
12510
|
clientId = pick2.client_id ?? pick2.clientId;
|
|
12091
12511
|
}
|
|
12092
|
-
console.log(`${
|
|
12512
|
+
console.log(`${import_chalk54.default.cyan("\u2192")} Logging in to ${import_chalk54.default.bold(tenant2)} as a consumer...`);
|
|
12093
12513
|
const result = await deviceLogin(clientId, DEFAULT_SCOPE, ({ verificationUri, userCode }) => {
|
|
12094
12514
|
console.log(`
|
|
12095
|
-
Open: ${
|
|
12096
|
-
console.log(` Code: ${
|
|
12515
|
+
Open: ${import_chalk54.default.underline(verificationUri)}`);
|
|
12516
|
+
console.log(` Code: ${import_chalk54.default.bold(userCode)}
|
|
12097
12517
|
`);
|
|
12098
|
-
console.log(
|
|
12518
|
+
console.log(import_chalk54.default.dim(" (opening your browser\u2026 waiting for you to finish)"));
|
|
12099
12519
|
});
|
|
12100
12520
|
const claims = result.idToken && decodeJwt2(result.idToken) || (decodeJwt2(result.accessToken) ?? {});
|
|
12101
12521
|
const creds = {
|
|
@@ -12110,7 +12530,7 @@ async function runConsumerLogin(opts) {
|
|
|
12110
12530
|
obtainedAt: Date.now()
|
|
12111
12531
|
};
|
|
12112
12532
|
saveConsumer(creds);
|
|
12113
|
-
console.log(
|
|
12533
|
+
console.log(import_chalk54.default.green(`\u2714 Logged in as consumer${creds.email ? ` ${creds.email}` : ""} on ${tenant2}.`));
|
|
12114
12534
|
}
|
|
12115
12535
|
async function runConsumerTokens(opts) {
|
|
12116
12536
|
const creds = requireConsumer2();
|
|
@@ -12123,46 +12543,66 @@ async function runConsumerTokens(opts) {
|
|
|
12123
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));
|
|
12124
12544
|
return;
|
|
12125
12545
|
}
|
|
12126
|
-
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)}
|
|
12127
12547
|
`);
|
|
12128
|
-
console.log(`${
|
|
12548
|
+
console.log(`${import_chalk54.default.bold("access_token")} ${import_chalk54.default.dim("exp " + (exp(fresh.accessToken) ?? "?"))}
|
|
12129
12549
|
${fresh.accessToken}
|
|
12130
12550
|
`);
|
|
12131
|
-
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) ?? "?"))}
|
|
12132
12552
|
${fresh.idToken}
|
|
12133
12553
|
`);
|
|
12134
|
-
if (fresh.refreshToken) console.log(`${
|
|
12554
|
+
if (fresh.refreshToken) console.log(`${import_chalk54.default.bold("refresh_token")}
|
|
12135
12555
|
${fresh.refreshToken}
|
|
12136
12556
|
`);
|
|
12137
|
-
console.log(
|
|
12557
|
+
console.log(import_chalk54.default.dim("These are your own tokens \u2014 keep them secret."));
|
|
12138
12558
|
}
|
|
12139
12559
|
async function runConsumerApikeys(opts) {
|
|
12140
12560
|
const creds = requireConsumer2();
|
|
12141
12561
|
const { default: inquirer3 } = await import("inquirer");
|
|
12142
12562
|
const spinner = (0, import_ora26.default)("Loading your API keys...").start();
|
|
12143
|
-
const list = await consumerFetch(creds, "/apikeys");
|
|
12144
|
-
const revealed = await consumerFetch(list.creds, "/apikeys/reveal").catch(() => ({ status: 0, data: null, creds: list.creds }));
|
|
12145
|
-
spinner.stop();
|
|
12563
|
+
const list = await consumerFetch(creds, withPage("/apikeys", null));
|
|
12146
12564
|
if (list.status >= 400) {
|
|
12147
|
-
|
|
12148
|
-
|
|
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."));
|
|
12149
12568
|
process.exit(1);
|
|
12150
12569
|
}
|
|
12151
|
-
|
|
12152
|
-
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();
|
|
12153
12593
|
if (opts.json) {
|
|
12154
12594
|
console.log(JSON.stringify({ keys, revealed: revealMap }, null, 2));
|
|
12155
12595
|
} else if (!keys.length) {
|
|
12156
|
-
console.log(
|
|
12596
|
+
console.log(import_chalk54.default.yellow("No API keys yet."));
|
|
12157
12597
|
} else {
|
|
12158
12598
|
for (const k of keys) {
|
|
12159
12599
|
const clear = revealMap[k.environment]?.key;
|
|
12160
|
-
const shown = clear ?
|
|
12161
|
-
const exp = k.expires_at ?
|
|
12162
|
-
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 ?? "")}`);
|
|
12163
12603
|
}
|
|
12164
12604
|
if (Object.keys(revealMap).length === 0 && keys.some((k) => !k.expires_at)) {
|
|
12165
|
-
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.)"));
|
|
12166
12606
|
}
|
|
12167
12607
|
}
|
|
12168
12608
|
if (opts.json) return;
|
|
@@ -12184,12 +12624,12 @@ async function runConsumerApikeys(opts) {
|
|
|
12184
12624
|
}
|
|
12185
12625
|
s2.succeed("Key created.");
|
|
12186
12626
|
const key = created.data?.key ?? created.data?.fullKey;
|
|
12187
|
-
if (key) console.log(` ${
|
|
12188
|
-
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."));
|
|
12189
12629
|
}
|
|
12190
12630
|
|
|
12191
12631
|
// src/commands/sidecar.ts
|
|
12192
|
-
var
|
|
12632
|
+
var import_chalk55 = __toESM(require("chalk"));
|
|
12193
12633
|
var import_ora27 = __toESM(require("ora"));
|
|
12194
12634
|
var fs13 = __toESM(require("fs"));
|
|
12195
12635
|
var path10 = __toESM(require("path"));
|
|
@@ -12232,7 +12672,7 @@ function upsertEnvLocal(root, token) {
|
|
|
12232
12672
|
}
|
|
12233
12673
|
function installSidecarPackage(root) {
|
|
12234
12674
|
if (fs13.existsSync(path10.join(root, "node_modules", "apiblaze", "package.json"))) {
|
|
12235
|
-
console.log(` ${
|
|
12675
|
+
console.log(` ${import_chalk55.default.green("\u2713")} apiblaze package already installed`);
|
|
12236
12676
|
return;
|
|
12237
12677
|
}
|
|
12238
12678
|
const has = (f) => fs13.existsSync(path10.join(root, f));
|
|
@@ -12243,7 +12683,7 @@ function installSidecarPackage(root) {
|
|
|
12243
12683
|
execSync(`${pm.cmd} ${pm.add} apiblaze`, { cwd: root, stdio: "ignore" });
|
|
12244
12684
|
spinner.succeed("Installed apiblaze (the sidecar runtime).");
|
|
12245
12685
|
} catch {
|
|
12246
|
-
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")}.`);
|
|
12247
12687
|
}
|
|
12248
12688
|
}
|
|
12249
12689
|
function readEnvKey(root) {
|
|
@@ -12394,29 +12834,29 @@ async function runAnonymousInit(root, router, opts) {
|
|
|
12394
12834
|
if (out.cp_key && out.team_id) saveAnonCred2(out.cp_key, out.team_id, out.claim_code);
|
|
12395
12835
|
const envState = upsertEnvLocal(root, out.token);
|
|
12396
12836
|
ensureGitignored(root);
|
|
12397
|
-
console.log(` ${
|
|
12398
|
-
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)}`);
|
|
12399
12839
|
installSidecarPackage(root);
|
|
12400
12840
|
let inspectorPath = null;
|
|
12401
12841
|
if (!opts.noInspector) {
|
|
12402
12842
|
inspectorPath = generateInspector(root, router);
|
|
12403
|
-
if (inspectorPath) console.log(` ${
|
|
12843
|
+
if (inspectorPath) console.log(` ${import_chalk55.default.green("\u2713")} inspector at ${inspectorPath}`);
|
|
12404
12844
|
}
|
|
12405
12845
|
console.log("");
|
|
12406
|
-
console.log(
|
|
12407
|
-
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.`);
|
|
12408
12848
|
console.log(` 2. Each external origin your app calls is logged in the console \u2014 approve one with:`);
|
|
12409
|
-
console.log(` ${
|
|
12849
|
+
console.log(` ${import_chalk55.default.cyan("apiblaze sidecar approve api.stripe.com")} (no login needed)`);
|
|
12410
12850
|
console.log("");
|
|
12411
|
-
console.log(
|
|
12412
|
-
console.log(` ${
|
|
12413
|
-
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`));
|
|
12414
12854
|
}
|
|
12415
12855
|
async function runSidecar(opts) {
|
|
12416
12856
|
const root = path10.resolve(opts.dir ?? process.cwd());
|
|
12417
12857
|
const detected2 = detectNextProject(root);
|
|
12418
12858
|
if (!detected2.found) {
|
|
12419
|
-
console.log(
|
|
12859
|
+
console.log(import_chalk55.default.yellow(`No Next.js project detected in ${root}.`));
|
|
12420
12860
|
console.log("Create one (e.g. `npx create-next-app`) and re-run `apiblaze init` inside it.");
|
|
12421
12861
|
return;
|
|
12422
12862
|
}
|
|
@@ -12427,10 +12867,10 @@ async function runSidecar(opts) {
|
|
|
12427
12867
|
if (!loadCredentials()) {
|
|
12428
12868
|
upsertEnvLocal(root, readEnvKey(root));
|
|
12429
12869
|
ensureGitignored(root);
|
|
12430
|
-
console.log(` ${
|
|
12431
|
-
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)}`);
|
|
12432
12872
|
installSidecarPackage(root);
|
|
12433
|
-
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."));
|
|
12434
12874
|
return;
|
|
12435
12875
|
}
|
|
12436
12876
|
const { teamId, teamName } = await resolveTeam(opts.team);
|
|
@@ -12453,38 +12893,38 @@ async function runSidecar(opts) {
|
|
|
12453
12893
|
throw err;
|
|
12454
12894
|
}
|
|
12455
12895
|
} else {
|
|
12456
|
-
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).`));
|
|
12457
12897
|
}
|
|
12458
12898
|
const envState = upsertEnvLocal(root, token);
|
|
12459
12899
|
ensureGitignored(root);
|
|
12460
|
-
console.log(` ${
|
|
12900
|
+
console.log(` ${import_chalk55.default.green("\u2713")} .env.local ${envState} (APIBLAZE_API_KEY) \u2014 gitignored`);
|
|
12461
12901
|
const wireState = wireInstrumentation(root);
|
|
12462
|
-
console.log(` ${
|
|
12902
|
+
console.log(` ${import_chalk55.default.green("\u2713")} instrumentation.ts ${wireState}`);
|
|
12463
12903
|
installSidecarPackage(root);
|
|
12464
12904
|
let inspectorPath = null;
|
|
12465
12905
|
if (!opts.noInspector) {
|
|
12466
12906
|
inspectorPath = generateInspector(root, detected2.router);
|
|
12467
|
-
if (inspectorPath) console.log(` ${
|
|
12907
|
+
if (inspectorPath) console.log(` ${import_chalk55.default.green("\u2713")} inspector at ${inspectorPath}`);
|
|
12468
12908
|
}
|
|
12469
12909
|
console.log("");
|
|
12470
|
-
console.log(
|
|
12471
|
-
console.log(` 1. ${
|
|
12472
|
-
console.log(` 2. The origins your app calls appear as ${
|
|
12473
|
-
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)`);
|
|
12474
12914
|
console.log(` \u2026within ~5 min your app starts routing that origin through APIblaze.`);
|
|
12475
|
-
if (inspectorPath) console.log(` \u2022 Try it now: open ${
|
|
12476
|
-
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>\`.`));
|
|
12477
12917
|
console.log("");
|
|
12478
|
-
console.log(
|
|
12479
|
-
console.log(
|
|
12480
|
-
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)."));
|
|
12481
12921
|
console.log("");
|
|
12482
|
-
console.log(
|
|
12483
|
-
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."));
|
|
12484
12924
|
}
|
|
12485
12925
|
|
|
12486
12926
|
// src/commands/origins.ts
|
|
12487
|
-
var
|
|
12927
|
+
var import_chalk56 = __toESM(require("chalk"));
|
|
12488
12928
|
var import_ora28 = __toESM(require("ora"));
|
|
12489
12929
|
init_admin();
|
|
12490
12930
|
init_resolve();
|
|
@@ -12495,7 +12935,7 @@ async function runOriginsList(opts) {
|
|
|
12495
12935
|
if (!loadCredentials()) {
|
|
12496
12936
|
const cred = loadAnonCred();
|
|
12497
12937
|
if (!cred) {
|
|
12498
|
-
console.log(
|
|
12938
|
+
console.log(import_chalk56.default.yellow("No anonymous workspace here. Run `apiblaze init` first."));
|
|
12499
12939
|
return;
|
|
12500
12940
|
}
|
|
12501
12941
|
out = await cpFetch(cred.cp_key, `/teams/${encodeURIComponent(cred.team_id)}/sidecar/candidates`, { method: "GET" });
|
|
@@ -12513,27 +12953,27 @@ async function runOriginsList(opts) {
|
|
|
12513
12953
|
}
|
|
12514
12954
|
const routed = out.routed ?? [];
|
|
12515
12955
|
const candidates = out.candidates ?? [];
|
|
12516
|
-
console.log(
|
|
12956
|
+
console.log(import_chalk56.default.bold(`
|
|
12517
12957
|
Routed through APIblaze (${routed.length})`));
|
|
12518
|
-
if (!routed.length) console.log(
|
|
12519
|
-
for (const r of routed) console.log(` ${
|
|
12520
|
-
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(`
|
|
12521
12961
|
Candidates \u2014 going direct, not yet approved (${candidates.length})`));
|
|
12522
|
-
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"));
|
|
12523
12963
|
for (const c of candidates) {
|
|
12524
|
-
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}`)}`);
|
|
12525
12965
|
}
|
|
12526
12966
|
if (candidates.length) {
|
|
12527
|
-
console.log(
|
|
12967
|
+
console.log(import_chalk56.default.dim(`
|
|
12528
12968
|
Approve: apiblaze sidecar approve ${candidates[0].origin.replace("https://", "")}`));
|
|
12529
|
-
console.log(
|
|
12969
|
+
console.log(import_chalk56.default.dim(` Dismiss: apiblaze sidecar deny ${candidates[0].origin.replace("https://", "")}`));
|
|
12530
12970
|
}
|
|
12531
12971
|
}
|
|
12532
12972
|
async function runOriginsApprove(origin, opts) {
|
|
12533
12973
|
if (!loadCredentials()) {
|
|
12534
12974
|
const cred = loadAnonCred();
|
|
12535
12975
|
if (!cred) {
|
|
12536
|
-
console.error(
|
|
12976
|
+
console.error(import_chalk56.default.red("Not logged in and no anonymous workspace. Run `apiblaze init` first."));
|
|
12537
12977
|
process.exit(1);
|
|
12538
12978
|
}
|
|
12539
12979
|
const spinner2 = (0, import_ora28.default)(`Approving ${origin} (anonymous)...`).start();
|
|
@@ -12586,13 +13026,13 @@ async function runOriginsRemove(origin, opts) {
|
|
|
12586
13026
|
}
|
|
12587
13027
|
|
|
12588
13028
|
// src/commands/op.ts
|
|
12589
|
-
var
|
|
13029
|
+
var import_chalk58 = __toESM(require("chalk"));
|
|
12590
13030
|
init_auth();
|
|
12591
13031
|
init_trace();
|
|
12592
13032
|
init_types();
|
|
12593
13033
|
|
|
12594
13034
|
// src/commands/op-billing.ts
|
|
12595
|
-
var
|
|
13035
|
+
var import_chalk57 = __toESM(require("chalk"));
|
|
12596
13036
|
init_admin();
|
|
12597
13037
|
var SANDBOX = {
|
|
12598
13038
|
teamId: "team_1782844865835_zujrf",
|
|
@@ -12651,7 +13091,7 @@ async function rowsForRays(project, version2, tenant2, rays) {
|
|
|
12651
13091
|
}
|
|
12652
13092
|
function printDoors(data) {
|
|
12653
13093
|
const checks = [];
|
|
12654
|
-
console.log(
|
|
13094
|
+
console.log(import_chalk57.default.bold("\n Doors \u2014 is every way in metered?\n"));
|
|
12655
13095
|
const doors = data?.doors ?? [];
|
|
12656
13096
|
const metered = doors.filter((d) => d.verdict === "metered");
|
|
12657
13097
|
const allowed = doors.filter((d) => d.verdict === "allowed-free");
|
|
@@ -12660,29 +13100,29 @@ function printDoors(data) {
|
|
|
12660
13100
|
const errs = data?.errors ?? [];
|
|
12661
13101
|
const routeAuditBroke = errs.some((e) => e.startsWith("zone "));
|
|
12662
13102
|
const devAuditBroke = errs.some((e) => e.startsWith("workers.dev audit"));
|
|
12663
|
-
console.log(
|
|
12664
|
-
for (const d of metered) console.log(
|
|
12665
|
-
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(`
|
|
12666
13106
|
${allowed.length} route(s) free ON PURPOSE`));
|
|
12667
13107
|
for (const d of allowed) {
|
|
12668
|
-
console.log(
|
|
12669
|
-
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}`));
|
|
12670
13110
|
}
|
|
12671
13111
|
if (known.length) {
|
|
12672
|
-
console.log(
|
|
13112
|
+
console.log(import_chalk57.default.yellow(`
|
|
12673
13113
|
${known.length} route(s) KNOWN OPEN \u2014 unmetered, not yet closed`));
|
|
12674
13114
|
for (const d of known) {
|
|
12675
|
-
console.log(
|
|
12676
|
-
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}`));
|
|
12677
13117
|
}
|
|
12678
13118
|
checks.push({ name: "no known-open doors", status: "KNOWN", detail: `${known.length} unmetered route(s) still open \u2014 see above` });
|
|
12679
13119
|
}
|
|
12680
13120
|
if (stray.length) {
|
|
12681
|
-
console.log(
|
|
13121
|
+
console.log(import_chalk57.default.red(`
|
|
12682
13122
|
${stray.length} STRAY route(s) \u2014 not main-proxy, not on the allowlist`));
|
|
12683
13123
|
for (const d of stray) {
|
|
12684
|
-
console.log(
|
|
12685
|
-
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}`));
|
|
12686
13126
|
}
|
|
12687
13127
|
checks.push({ name: "no stray routes", status: "FAIL", detail: `${stray.length}: ${stray.map((s) => s.pattern).join(", ")}` });
|
|
12688
13128
|
} else if (routeAuditBroke) {
|
|
@@ -12693,22 +13133,22 @@ function printDoors(data) {
|
|
|
12693
13133
|
const wd = data?.workers_dev ?? {};
|
|
12694
13134
|
const open = wd.enabled ?? [];
|
|
12695
13135
|
if (open.length) {
|
|
12696
|
-
console.log(
|
|
13136
|
+
console.log(import_chalk57.default.red(`
|
|
12697
13137
|
${open.length} of ${wd.total} worker(s) reachable on *.workers.dev`));
|
|
12698
|
-
for (const s of open) console.log(
|
|
12699
|
-
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."));
|
|
12700
13140
|
checks.push({ name: "no workers.dev doors", status: "FAIL", detail: `${open.length} script(s) publicly reachable: ${open.map((s) => s.script).join(", ")}` });
|
|
12701
13141
|
} else if (devAuditBroke || !wd.total) {
|
|
12702
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." });
|
|
12703
13143
|
} else {
|
|
12704
|
-
console.log(
|
|
13144
|
+
console.log(import_chalk57.default.green(`
|
|
12705
13145
|
\u2713 0 of ${wd.total} workers reachable on *.workers.dev`));
|
|
12706
13146
|
checks.push({ name: "no workers.dev doors", status: "PASS", detail: `all ${wd.total} scripts have workers.dev + previews disabled` });
|
|
12707
13147
|
}
|
|
12708
|
-
if (data?.how_to_fix) console.log(
|
|
12709
|
-
${data.reason}`) +
|
|
13148
|
+
if (data?.how_to_fix) console.log(import_chalk57.default.yellow(`
|
|
13149
|
+
${data.reason}`) + import_chalk57.default.dim(`
|
|
12710
13150
|
${data.how_to_fix}`));
|
|
12711
|
-
else for (const e of errs) console.log(
|
|
13151
|
+
else for (const e of errs) console.log(import_chalk57.default.red(`
|
|
12712
13152
|
audit error: ${e}`));
|
|
12713
13153
|
if (errs.length) {
|
|
12714
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` });
|
|
@@ -12718,9 +13158,9 @@ function printDoors(data) {
|
|
|
12718
13158
|
async function runMeter(readLedger, opts) {
|
|
12719
13159
|
const checks = [];
|
|
12720
13160
|
const N = Math.max(1, Math.min(10, opts.count ?? 3));
|
|
12721
|
-
console.log(
|
|
13161
|
+
console.log(import_chalk57.default.bold("\n Meter \u2014 is 1 request charged exactly 1 request?\n"));
|
|
12722
13162
|
const snap = await readLedger();
|
|
12723
|
-
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
|
|
12724
13164
|
`));
|
|
12725
13165
|
const url = `https://${dpHost}/${SANDBOX.version}/${SANDBOX.environment}/`;
|
|
12726
13166
|
const headers = opts.key ? { "X-API-Key": opts.key } : {};
|
|
@@ -12802,22 +13242,22 @@ async function runMeter(readLedger, opts) {
|
|
|
12802
13242
|
return checks;
|
|
12803
13243
|
}
|
|
12804
13244
|
function printChecks(checks) {
|
|
12805
|
-
console.log(
|
|
12806
|
-
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") };
|
|
12807
13247
|
for (const ch of checks) {
|
|
12808
|
-
console.log(` ${mark[ch.status]} ${
|
|
12809
|
-
console.log(
|
|
13248
|
+
console.log(` ${mark[ch.status]} ${import_chalk57.default.bold(ch.name)}`);
|
|
13249
|
+
console.log(import_chalk57.default.dim(` ${ch.detail}`));
|
|
12810
13250
|
}
|
|
12811
13251
|
const fails = checks.filter((c) => c.status === "FAIL").length;
|
|
12812
13252
|
const skips = checks.filter((c) => c.status === "SKIP").length;
|
|
12813
13253
|
const known = checks.filter((c) => c.status === "KNOWN").length;
|
|
12814
13254
|
console.log("");
|
|
12815
13255
|
const passes = checks.filter((c) => c.status === "PASS").length;
|
|
12816
|
-
if (fails) console.log(
|
|
12817
|
-
else if (passes) console.log(
|
|
12818
|
-
else console.log(
|
|
12819
|
-
if (known) console.log(
|
|
12820
|
-
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.`));
|
|
12821
13261
|
console.log("");
|
|
12822
13262
|
}
|
|
12823
13263
|
|
|
@@ -12864,93 +13304,93 @@ var OP_COMMANDS = [
|
|
|
12864
13304
|
function renderOpCommands() {
|
|
12865
13305
|
const width = Math.max(...OP_COMMANDS.map((c) => c.cmd.length)) + 10;
|
|
12866
13306
|
const lines = OP_COMMANDS.map((c) => {
|
|
12867
|
-
const left = ` ${
|
|
13307
|
+
const left = ` ${import_chalk58.default.cyan(`apiblaze ${c.cmd}`)}`;
|
|
12868
13308
|
const pad = " ".repeat(Math.max(1, width - c.cmd.length));
|
|
12869
|
-
return `${left}${pad}${c.blurb}${c.extra ? " " +
|
|
13309
|
+
return `${left}${pad}${c.blurb}${c.extra ? " " + import_chalk58.default.dim(`(${c.extra})`) : ""}`;
|
|
12870
13310
|
});
|
|
12871
13311
|
return [
|
|
12872
|
-
|
|
13312
|
+
import_chalk58.default.bold("Operator commands"),
|
|
12873
13313
|
...lines,
|
|
12874
13314
|
"",
|
|
12875
|
-
|
|
12876
|
-
|
|
12877
|
-
|
|
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`."),
|
|
12878
13318
|
"",
|
|
12879
|
-
|
|
12880
|
-
|
|
12881
|
-
|
|
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.")
|
|
12882
13322
|
].join("\n");
|
|
12883
13323
|
}
|
|
12884
13324
|
function printResidue(report, applied) {
|
|
12885
13325
|
const up = report?.upstash ?? {};
|
|
12886
13326
|
const fga = report?.fga ?? {};
|
|
12887
13327
|
const ghosts = report?.ghosts ?? {};
|
|
12888
|
-
console.log(
|
|
12889
|
-
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"));
|
|
12890
13330
|
const orphans = up.orphans ?? [];
|
|
12891
|
-
if (orphans.length === 0) console.log(
|
|
12892
|
-
for (const o of orphans) console.log(` ${
|
|
12893
|
-
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}`));
|
|
12894
13334
|
if (up.anon_wallet_detail) {
|
|
12895
13335
|
const d = up.anon_wallet_detail;
|
|
12896
|
-
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)"}`));
|
|
12897
13337
|
}
|
|
12898
13338
|
if (up.keyspace_census) {
|
|
12899
13339
|
const census = Object.entries(up.keyspace_census).map(([k, v]) => `${k}=${v}`).join(" \xB7 ");
|
|
12900
|
-
console.log(
|
|
13340
|
+
console.log(import_chalk58.default.dim(` keyspace: ${census}`));
|
|
12901
13341
|
}
|
|
12902
|
-
if (up.unknown?.length) console.log(
|
|
12903
|
-
if (applied) console.log(` ${
|
|
12904
|
-
for (const e of up.errors ?? []) console.log(
|
|
12905
|
-
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"));
|
|
12906
13346
|
if (applied) {
|
|
12907
13347
|
const swept = fga?.swept ?? [];
|
|
12908
|
-
if (swept.length === 0) console.log(
|
|
13348
|
+
if (swept.length === 0) console.log(import_chalk58.default.green(" no orphaned stores"));
|
|
12909
13349
|
for (const s of swept) {
|
|
12910
13350
|
console.log(
|
|
12911
|
-
` ${
|
|
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`)}`
|
|
12912
13352
|
);
|
|
12913
13353
|
}
|
|
12914
|
-
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`));
|
|
12915
13355
|
const st = fga?.side_tables;
|
|
12916
|
-
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})` : ""}`));
|
|
12917
13357
|
} else {
|
|
12918
13358
|
const fgaOrphans = fga?.orphans ?? [];
|
|
12919
|
-
if (fgaOrphans.length === 0) console.log(
|
|
13359
|
+
if (fgaOrphans.length === 0) console.log(import_chalk58.default.green(" no orphaned stores"));
|
|
12920
13360
|
for (const s of fgaOrphans) {
|
|
12921
13361
|
const src = s.in_openfga ? "live in OpenFGA" : "Neon tuples only";
|
|
12922
|
-
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)`)}`);
|
|
12923
13363
|
}
|
|
12924
|
-
console.log(
|
|
13364
|
+
console.log(import_chalk58.default.dim(` kept stores: ${(fga?.kept_store_ids ?? []).length}`));
|
|
12925
13365
|
const st = fga?.side_tables;
|
|
12926
|
-
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`));
|
|
12927
13367
|
}
|
|
12928
|
-
for (const e of fga?.errors ?? []) console.log(
|
|
12929
|
-
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"));
|
|
12930
13370
|
if (applied) {
|
|
12931
|
-
if ((ghosts?.ghost_count ?? 0) === 0) console.log(
|
|
12932
|
-
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)`)}`);
|
|
12933
13373
|
} else {
|
|
12934
13374
|
const n = ghosts?.ghost_count ?? 0;
|
|
12935
|
-
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)`)}`));
|
|
12936
13376
|
else {
|
|
12937
|
-
console.log(
|
|
13377
|
+
console.log(import_chalk58.default.yellow(` ${n} ghost tuple(s) referencing entities absent from D1:`));
|
|
12938
13378
|
for (const g of (ghosts.ghosts ?? []).slice(0, 20)) {
|
|
12939
|
-
console.log(
|
|
13379
|
+
console.log(import_chalk58.default.dim(` ${g.object_type}:${g.object_id} ${g.relation} ${g._user}`));
|
|
12940
13380
|
}
|
|
12941
|
-
if (n > 20) console.log(
|
|
13381
|
+
if (n > 20) console.log(import_chalk58.default.dim(` \u2026 and ${n - 20} more`));
|
|
12942
13382
|
}
|
|
12943
13383
|
}
|
|
12944
|
-
for (const e of ghosts?.errors ?? []) console.log(
|
|
13384
|
+
for (const e of ghosts?.errors ?? []) console.log(import_chalk58.default.red(` error: ${e}`));
|
|
12945
13385
|
console.log();
|
|
12946
13386
|
}
|
|
12947
13387
|
async function runOp(sub, opts = {}, view) {
|
|
12948
13388
|
if (!loadCredentials()) {
|
|
12949
|
-
console.log(
|
|
13389
|
+
console.log(import_chalk58.default.dim("Not logged in. Run `apiblaze login`."));
|
|
12950
13390
|
return;
|
|
12951
13391
|
}
|
|
12952
13392
|
if (!isOperatorLogin()) {
|
|
12953
|
-
console.log(
|
|
13393
|
+
console.log(import_chalk58.default.dim("`apiblaze op` is only available to platform operators."));
|
|
12954
13394
|
return;
|
|
12955
13395
|
}
|
|
12956
13396
|
switch (sub) {
|
|
@@ -12976,17 +13416,17 @@ async function runOp(sub, opts = {}, view) {
|
|
|
12976
13416
|
const nSide = (st.soft_deleted_stores ?? 0) + (st.orphan_models ?? 0) + (st.orphan_changelog ?? 0);
|
|
12977
13417
|
printResidue(report, false);
|
|
12978
13418
|
if (nUp + nFga + nGhost + nSide === 0) {
|
|
12979
|
-
console.log(
|
|
13419
|
+
console.log(import_chalk58.default.green("Nothing to sweep."));
|
|
12980
13420
|
return;
|
|
12981
13421
|
}
|
|
12982
13422
|
if (!opts.yes) {
|
|
12983
13423
|
const readline3 = await import("readline/promises");
|
|
12984
13424
|
const rl = readline3.createInterface({ input: process.stdin, output: process.stdout });
|
|
12985
13425
|
const answer = await rl.question(
|
|
12986
|
-
|
|
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: `)
|
|
12987
13427
|
);
|
|
12988
13428
|
rl.close();
|
|
12989
|
-
if (answer.trim() !== "sweep") return void console.log(
|
|
13429
|
+
if (answer.trim() !== "sweep") return void console.log(import_chalk58.default.dim("Aborted."));
|
|
12990
13430
|
}
|
|
12991
13431
|
const result = await opCall({ method: "POST", path: "/operator/external-residue/sweep", summary: "external residue sweep" });
|
|
12992
13432
|
if (opts.json) return void console.log(JSON.stringify(result, null, 2));
|
|
@@ -12996,25 +13436,25 @@ async function runOp(sub, opts = {}, view) {
|
|
|
12996
13436
|
case "mark": {
|
|
12997
13437
|
const label3 = (view ?? "").trim();
|
|
12998
13438
|
if (!label3) {
|
|
12999
|
-
console.log(
|
|
13439
|
+
console.log(import_chalk58.default.red("Give the change a name:") + import_chalk58.default.cyan(' apiblaze op mark "cached tenant count"'));
|
|
13000
13440
|
return;
|
|
13001
13441
|
}
|
|
13002
13442
|
const res = await opCall({ method: "POST", path: "/operator/latency/mark", body: { label: label3 }, summary: "record change marker" });
|
|
13003
13443
|
const ts = new Date(res?.marker?.ts ?? Date.now()).toISOString();
|
|
13004
|
-
console.log(
|
|
13005
|
-
Marked: `) +
|
|
13006
|
-
console.log(
|
|
13007
|
-
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");
|
|
13008
13448
|
return;
|
|
13009
13449
|
}
|
|
13010
13450
|
case "credits": {
|
|
13011
13451
|
const data = await opCall({ method: "GET", path: "/operator/credits", summary: "list credit wallets" });
|
|
13012
13452
|
if (opts.json) return void console.log(JSON.stringify(data, null, 2));
|
|
13013
13453
|
const accounts = data?.accounts ?? [];
|
|
13014
|
-
if (accounts.length === 0) return void console.log(
|
|
13454
|
+
if (accounts.length === 0) return void console.log(import_chalk58.default.dim("No credit wallets."));
|
|
13015
13455
|
for (const a of accounts) {
|
|
13016
13456
|
const bal = typeof a.balance_cents === "number" ? `$${(a.balance_cents / 100).toFixed(2)}` : "?";
|
|
13017
|
-
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") : ""}`);
|
|
13018
13458
|
}
|
|
13019
13459
|
return;
|
|
13020
13460
|
}
|
|
@@ -13025,7 +13465,7 @@ async function runOp(sub, opts = {}, view) {
|
|
|
13025
13465
|
case "billing": {
|
|
13026
13466
|
const which = (view ?? "").trim().toLowerCase();
|
|
13027
13467
|
if (which && which !== "doors" && which !== "meter") {
|
|
13028
|
-
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.`));
|
|
13029
13469
|
}
|
|
13030
13470
|
const checks = [];
|
|
13031
13471
|
let doorsData = null;
|
|
@@ -13059,6 +13499,14 @@ async function runOp(sub, opts = {}, view) {
|
|
|
13059
13499
|
// it is the biggest feature on almost every request, so blaming it would
|
|
13060
13500
|
// report "upstream, 94%" forever and tell an operator nothing. The
|
|
13061
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.
|
|
13062
13510
|
case "latency": {
|
|
13063
13511
|
const qs = new URLSearchParams();
|
|
13064
13512
|
const FILTERS = [
|
|
@@ -13086,29 +13534,29 @@ async function runOp(sub, opts = {}, view) {
|
|
|
13086
13534
|
const data = await opCall({ method: "GET", path: `/operator/latency/grades${q}`, summary: "latency grades" });
|
|
13087
13535
|
if (opts.json) return void console.log(JSON.stringify(data, null, 2));
|
|
13088
13536
|
const t = data.thresholds_ms;
|
|
13089
|
-
console.log(
|
|
13090
|
-
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+
|
|
13091
13539
|
`));
|
|
13092
|
-
console.log(
|
|
13540
|
+
console.log(import_chalk58.default.dim(" date reqs excellent okay bad terrible"));
|
|
13093
13541
|
for (const d of data.days ?? []) {
|
|
13094
13542
|
const p = d.pct;
|
|
13095
|
-
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)}%`);
|
|
13096
13544
|
console.log(
|
|
13097
|
-
` ${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)}`
|
|
13098
13546
|
);
|
|
13099
13547
|
}
|
|
13100
13548
|
const cul = data.culprits ?? [];
|
|
13101
13549
|
if (cul.length) {
|
|
13102
|
-
console.log(
|
|
13103
|
-
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"));
|
|
13104
13552
|
for (const r of cul.slice(0, 12)) {
|
|
13105
13553
|
if (!r.bad && !r.terrible) continue;
|
|
13106
13554
|
console.log(
|
|
13107
|
-
` ${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)}`
|
|
13108
13556
|
);
|
|
13109
13557
|
}
|
|
13110
13558
|
}
|
|
13111
|
-
if (data.caveat) console.log(
|
|
13559
|
+
if (data.caveat) console.log(import_chalk58.default.dim(`
|
|
13112
13560
|
\u26A0 ${data.caveat}
|
|
13113
13561
|
`));
|
|
13114
13562
|
return;
|
|
@@ -13117,24 +13565,24 @@ async function runOp(sub, opts = {}, view) {
|
|
|
13117
13565
|
const data = await opCall({ method: "GET", path: `/operator/latency/compare${q}`, summary: "latency before/after" });
|
|
13118
13566
|
if (opts.json) return void console.log(JSON.stringify(data, null, 2));
|
|
13119
13567
|
const b = data.before, a = data.after, d = data.delta;
|
|
13120
|
-
console.log(
|
|
13121
|
-
Before vs after: `) +
|
|
13122
|
-
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
|
|
13123
13571
|
`));
|
|
13124
13572
|
const row = (name, before, after, delta) => {
|
|
13125
13573
|
const arrow = delta === 0 ? "=" : delta < 0 ? "\u2193" : "\u2191";
|
|
13126
13574
|
const txt = `${String(before).padStart(6)}ms \u2192${String(after).padStart(7)}ms ${arrow}${Math.abs(delta)}ms`;
|
|
13127
|
-
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)}`);
|
|
13128
13576
|
};
|
|
13129
|
-
console.log(
|
|
13577
|
+
console.log(import_chalk58.default.dim(" metric before after change"));
|
|
13130
13578
|
row("total p50", b.total_p50, a.total_p50, d.total_p50);
|
|
13131
13579
|
row("total p95", b.total_p95, a.total_p95, d.total_p95);
|
|
13132
13580
|
row("apiblaze overhead p50", b.gw_p50, a.gw_p50, d.gw_p50);
|
|
13133
13581
|
row("apiblaze overhead p95", b.gw_p95, a.gw_p95, d.gw_p95);
|
|
13134
|
-
console.log(
|
|
13582
|
+
console.log(import_chalk58.default.dim(`
|
|
13135
13583
|
requests: ${b.requests} before \xB7 ${a.requests} after`));
|
|
13136
13584
|
for (const w of data.warnings ?? []) {
|
|
13137
|
-
console.log((data.trustworthy ?
|
|
13585
|
+
console.log((data.trustworthy ? import_chalk58.default.dim : import_chalk58.default.yellow)(` ${data.trustworthy ? "\xB7" : "\u26A0"} ${w}`));
|
|
13138
13586
|
}
|
|
13139
13587
|
console.log("");
|
|
13140
13588
|
return;
|
|
@@ -13143,19 +13591,19 @@ Before vs after: `) + import_chalk56.default.cyan(data.marker.label));
|
|
|
13143
13591
|
const data = await opCall({ method: "GET", path: `/operator/latency/slow${q}`, summary: "slowest requests" });
|
|
13144
13592
|
if (opts.json) return void console.log(JSON.stringify(data, null, 2));
|
|
13145
13593
|
const rows2 = data?.rows ?? [];
|
|
13146
|
-
if (!rows2.length) return void console.log(
|
|
13147
|
-
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(`
|
|
13148
13596
|
Slowest requests \u2014 last ${data.window_hours}h, over ${data.min_ms}ms
|
|
13149
13597
|
`));
|
|
13150
|
-
console.log(
|
|
13598
|
+
console.log(import_chalk58.default.dim(" total ours theirs blame request id"));
|
|
13151
13599
|
for (const r of rows2.slice(0, 30)) {
|
|
13152
13600
|
console.log(
|
|
13153
|
-
` ${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 || "")}`
|
|
13154
13602
|
);
|
|
13155
13603
|
}
|
|
13156
|
-
console.log(
|
|
13604
|
+
console.log(import_chalk58.default.dim(`
|
|
13157
13605
|
The last column is the request id (Cloudflare calls it a "cf-ray"). Look one up with`));
|
|
13158
|
-
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.
|
|
13159
13607
|
`));
|
|
13160
13608
|
return;
|
|
13161
13609
|
}
|
|
@@ -13163,18 +13611,18 @@ Slowest requests \u2014 last ${data.window_hours}h, over ${data.min_ms}ms
|
|
|
13163
13611
|
const data = await opCall({ method: "GET", path: `/operator/latency/llm${q}`, summary: "llm latency" });
|
|
13164
13612
|
if (opts.json) return void console.log(JSON.stringify(data, null, 2));
|
|
13165
13613
|
const rows2 = data?.rows ?? [];
|
|
13166
|
-
if (!rows2.length) return void console.log(
|
|
13167
|
-
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(`
|
|
13168
13616
|
LLM timing \u2014 last ${data.window_hours}h
|
|
13169
13617
|
`));
|
|
13170
|
-
console.log(
|
|
13618
|
+
console.log(import_chalk58.default.dim(" turns turn p95 ttfc p95 gen p95 reserve p95 in/out tokens p95 model"));
|
|
13171
13619
|
for (const r of rows2) {
|
|
13172
13620
|
const n = r.turns ?? r.requests ?? 0;
|
|
13173
13621
|
console.log(
|
|
13174
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 || "-"}`
|
|
13175
13623
|
);
|
|
13176
13624
|
}
|
|
13177
|
-
console.log(
|
|
13625
|
+
console.log(import_chalk58.default.dim(`
|
|
13178
13626
|
${data.note}
|
|
13179
13627
|
`));
|
|
13180
13628
|
return;
|
|
@@ -13185,26 +13633,26 @@ LLM timing \u2014 last ${data.window_hours}h
|
|
|
13185
13633
|
]);
|
|
13186
13634
|
if (opts.json) return void console.log(JSON.stringify({ blame, summary }, null, 2));
|
|
13187
13635
|
const ov = summary?.apiblaze_overhead_ms ?? {};
|
|
13188
|
-
console.log(
|
|
13636
|
+
console.log(import_chalk58.default.bold(`
|
|
13189
13637
|
Latency \u2014 last ${summary?.window_hours ?? "?"}h, ${Number(summary?.requests ?? 0).toLocaleString()} requests
|
|
13190
13638
|
`));
|
|
13191
|
-
console.log(` ${
|
|
13192
|
-
console.log(` ${
|
|
13193
|
-
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)
|
|
13194
13642
|
`));
|
|
13195
13643
|
const rows = blame?.blame ?? [];
|
|
13196
|
-
if (!rows.length) return void console.log(
|
|
13197
|
-
console.log(
|
|
13198
|
-
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"));
|
|
13199
13647
|
for (const r of rows.slice(0, 15)) {
|
|
13200
13648
|
const share = `${(r.share * 100).toFixed(1)}%`;
|
|
13201
|
-
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)}`);
|
|
13202
13650
|
}
|
|
13203
13651
|
console.log("");
|
|
13204
13652
|
return;
|
|
13205
13653
|
}
|
|
13206
13654
|
default:
|
|
13207
|
-
console.log(
|
|
13655
|
+
console.log(import_chalk58.default.red(`Unknown op subcommand '${sub}'. Run \`apiblaze op\` for the menu.`));
|
|
13208
13656
|
}
|
|
13209
13657
|
}
|
|
13210
13658
|
|
|
@@ -13274,7 +13722,7 @@ program.command("dev").description(`Put the code running on this machine behind
|
|
|
13274
13722
|
try {
|
|
13275
13723
|
const resolved = parseInt(port ?? opts.port, 10);
|
|
13276
13724
|
if (Number.isNaN(resolved)) {
|
|
13277
|
-
console.error(
|
|
13725
|
+
console.error(import_chalk59.default.red(`Invalid port: ${port ?? opts.port}`));
|
|
13278
13726
|
process.exit(1);
|
|
13279
13727
|
}
|
|
13280
13728
|
await runDev({ ...opts, port: resolved });
|
|
@@ -13330,7 +13778,8 @@ program.command("show").description("Read a recipe before you install it (upstre
|
|
|
13330
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)));
|
|
13331
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)));
|
|
13332
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)));
|
|
13333
|
-
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)));
|
|
13334
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 })));
|
|
13335
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)));
|
|
13336
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())));
|
|
@@ -13361,7 +13810,7 @@ tenant.command("use").description("Set the sticky tenant scope for future comman
|
|
|
13361
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)));
|
|
13362
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)));
|
|
13363
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)));
|
|
13364
|
-
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)));
|
|
13365
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)));
|
|
13366
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)));
|
|
13367
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 })));
|
|
@@ -13416,7 +13865,7 @@ function groupedCommandHelp() {
|
|
|
13416
13865
|
const sub = byName.get(e.parent)?.commands.find((s) => s.name() === e.sub);
|
|
13417
13866
|
return sub ? ` ${helpLabel(e).padEnd(width)}${sub.description()}` : "";
|
|
13418
13867
|
}).filter(Boolean).join("\n");
|
|
13419
|
-
return `${
|
|
13868
|
+
return `${import_chalk59.default.bold(g.title)}
|
|
13420
13869
|
${rows}`;
|
|
13421
13870
|
}).join("\n\n");
|
|
13422
13871
|
}
|
|
@@ -13427,7 +13876,7 @@ program.configureHelp({
|
|
|
13427
13876
|
}
|
|
13428
13877
|
});
|
|
13429
13878
|
program.addHelpText("after", () => `
|
|
13430
|
-
${
|
|
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.`)}
|
|
13431
13880
|
|
|
13432
13881
|
${groupedCommandHelp()}
|
|
13433
13882
|
|
|
@@ -13463,14 +13912,14 @@ async function recoverStaleTeam() {
|
|
|
13463
13912
|
const { resolveLinkedTeam: resolveLinkedTeam2 } = await Promise.resolve().then(() => (init_team(), team_exports));
|
|
13464
13913
|
const linked = await resolveLinkedTeam2({ preferredId: creds.teamId, interactive: !!process.stdin.isTTY });
|
|
13465
13914
|
if (!linked) {
|
|
13466
|
-
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."));
|
|
13467
13916
|
return;
|
|
13468
13917
|
}
|
|
13469
13918
|
if (linked.teamId === creds.teamId) return;
|
|
13470
13919
|
const next = { ...creds, teamId: linked.teamId, teamName: linked.teamName };
|
|
13471
13920
|
delete next.activeTenant;
|
|
13472
13921
|
saveCredentials(next);
|
|
13473
|
-
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.`));
|
|
13474
13923
|
} catch {
|
|
13475
13924
|
}
|
|
13476
13925
|
}
|
|
@@ -13478,16 +13927,16 @@ async function printError(err) {
|
|
|
13478
13927
|
if (err instanceof ApiError) {
|
|
13479
13928
|
const data = err.body;
|
|
13480
13929
|
const extra = [data?.body?.reason, data?.body?.details, data?.details, data?.body?.error].find((x) => typeof x === "string" && x && x !== err.message);
|
|
13481
|
-
console.error(
|
|
13930
|
+
console.error(import_chalk59.default.red(`
|
|
13482
13931
|
API error (${err.status}): ${err.message}${extra ? ` \u2014 ${extra}` : ""}`));
|
|
13483
13932
|
if (err.status === 403 || err.status === 404) {
|
|
13484
13933
|
await recoverStaleTeam();
|
|
13485
13934
|
}
|
|
13486
13935
|
} else if (err instanceof Error) {
|
|
13487
|
-
console.error(
|
|
13936
|
+
console.error(import_chalk59.default.red(`
|
|
13488
13937
|
Error: ${err.message}`));
|
|
13489
13938
|
} else {
|
|
13490
|
-
console.error(
|
|
13939
|
+
console.error(import_chalk59.default.red("\nUnknown error"));
|
|
13491
13940
|
}
|
|
13492
13941
|
}
|
|
13493
13942
|
program.parse(process.argv);
|