apiblaze 0.19.1 → 0.19.3
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 +422 -318
- package/package.json +2 -2
package/dist/index.js
CHANGED
|
@@ -577,10 +577,10 @@ async function resolveProjectVia(caller, teamId, nameOrId, version2) {
|
|
|
577
577
|
summary: `List projects for ${teamId}`
|
|
578
578
|
});
|
|
579
579
|
const rows = out?.projects ?? [];
|
|
580
|
-
const candidates = rows.filter((r) => r.project_id === nameOrId || r.
|
|
580
|
+
const candidates = rows.filter((r) => r.project_id === nameOrId || r.project_display_name === nameOrId || r.display_name === nameOrId);
|
|
581
581
|
if (!candidates.length) {
|
|
582
582
|
throw new Error(
|
|
583
|
-
`Project "${nameOrId}" not found in this team.` + (rows.length ? ` Known: ${rows.map((r) => r.
|
|
583
|
+
`Project "${nameOrId}" not found in this team.` + (rows.length ? ` Known: ${rows.map((r) => r.project_id).join(", ")}` : "")
|
|
584
584
|
);
|
|
585
585
|
}
|
|
586
586
|
const chosen = version2 ? candidates.find((r) => r.api_version === version2) : candidates[0];
|
|
@@ -590,11 +590,12 @@ async function resolveProjectVia(caller, teamId, nameOrId, version2) {
|
|
|
590
590
|
);
|
|
591
591
|
}
|
|
592
592
|
return {
|
|
593
|
+
// A proxy is addressed by its project_id (that IS its name); display_name is cosmetic.
|
|
593
594
|
projectId: chosen.project_id,
|
|
594
|
-
projectName: chosen.
|
|
595
|
+
projectName: chosen.project_display_name ?? chosen.display_name ?? chosen.project_id,
|
|
595
596
|
apiVersion: chosen.api_version,
|
|
596
597
|
teamId,
|
|
597
|
-
tenant: chosen.tenant ?? chosen.config?.
|
|
598
|
+
tenant: chosen.config?.tenant ?? chosen.config?.default_tenant
|
|
598
599
|
};
|
|
599
600
|
}
|
|
600
601
|
var init_caller = __esm({
|
|
@@ -626,15 +627,16 @@ async function runIamToggle(project, state, opts) {
|
|
|
626
627
|
if (!tenant2) {
|
|
627
628
|
throw new Error("Could not resolve the tenant for this project. Pass --tenant <name>.");
|
|
628
629
|
}
|
|
629
|
-
const spinner = (0, import_ora4.default)(`Turning IAM ${state} for tenant ${tenant2}...`).start();
|
|
630
|
+
const spinner = opts.quiet ? null : (0, import_ora4.default)(`Turning IAM ${state} for tenant ${tenant2}...`).start();
|
|
630
631
|
try {
|
|
631
632
|
const out = await producer(caller, {
|
|
632
633
|
method: "PATCH",
|
|
633
634
|
path: `/teams/${encodeURIComponent(teamId)}/tenants/${encodeURIComponent(tenant2)}/iam`,
|
|
634
|
-
body: {
|
|
635
|
+
body: { enabled },
|
|
635
636
|
summary: `IAM ${state} for tenant ${tenant2}`
|
|
636
637
|
});
|
|
637
|
-
|
|
638
|
+
if (opts.quiet) return;
|
|
639
|
+
spinner?.succeed(`IAM is ${import_chalk9.default.bold(state)} for tenant ${tenant2}.`);
|
|
638
640
|
if (opts.json) console.log(JSON.stringify(out ?? { iam_enabled: enabled }));
|
|
639
641
|
else if (enabled) {
|
|
640
642
|
console.log(import_chalk9.default.dim(" Groups now apply to identified calls. Identify callers with"));
|
|
@@ -642,7 +644,7 @@ async function runIamToggle(project, state, opts) {
|
|
|
642
644
|
console.log(import_chalk9.default.dim(` \`apiblaze identified ${project} require\` to reject unattributed traffic.`));
|
|
643
645
|
}
|
|
644
646
|
} catch (err) {
|
|
645
|
-
spinner
|
|
647
|
+
spinner?.fail(`IAM ${state} failed.`);
|
|
646
648
|
throw err;
|
|
647
649
|
}
|
|
648
650
|
}
|
|
@@ -654,7 +656,7 @@ async function runIdentifiedToggle(project, mode, opts) {
|
|
|
654
656
|
const caller = requireCaller();
|
|
655
657
|
const { teamId } = await resolveActingTeam(caller, opts.team);
|
|
656
658
|
const proj2 = await resolveProjectVia(caller, teamId, project, opts.apiversion);
|
|
657
|
-
const spinner = (0, import_ora4.default)(
|
|
659
|
+
const spinner = opts.quiet ? null : (0, import_ora4.default)(
|
|
658
660
|
require2 ? "Requiring identified traffic..." : "Allowing unattributed traffic..."
|
|
659
661
|
).start();
|
|
660
662
|
try {
|
|
@@ -681,12 +683,13 @@ async function runIdentifiedToggle(project, mode, opts) {
|
|
|
681
683
|
body: { requests_policy: merged },
|
|
682
684
|
summary: `Set identified-traffic policy (${mode}) on ${proj2.projectName}`
|
|
683
685
|
});
|
|
684
|
-
|
|
686
|
+
if (opts.quiet) return;
|
|
687
|
+
spinner?.succeed(
|
|
685
688
|
require2 ? `${proj2.projectName} now rejects calls that don't identify an end user.` : `${proj2.projectName} accepts unattributed calls again (they pass with no groups).`
|
|
686
689
|
);
|
|
687
690
|
if (opts.json) console.log(JSON.stringify(out ?? { identified_traffic_only: require2 }));
|
|
688
691
|
} catch (err) {
|
|
689
|
-
spinner
|
|
692
|
+
spinner?.fail("Policy update failed.");
|
|
690
693
|
throw err;
|
|
691
694
|
}
|
|
692
695
|
}
|
|
@@ -736,7 +739,7 @@ async function createTenantRow(teamId) {
|
|
|
736
739
|
}
|
|
737
740
|
async function addApiblazeHostedLogin(teamId, tenant2, opts = {}) {
|
|
738
741
|
const { default: inquirer3 } = await import("inquirer");
|
|
739
|
-
const
|
|
742
|
+
const base2 = `/teams/${encodeURIComponent(teamId)}/tenants/${encodeURIComponent(tenant2)}`;
|
|
740
743
|
const { provider } = await inquirer3.prompt([{
|
|
741
744
|
type: "list",
|
|
742
745
|
name: "provider",
|
|
@@ -781,9 +784,9 @@ async function addApiblazeHostedLogin(teamId, tenant2, opts = {}) {
|
|
|
781
784
|
}
|
|
782
785
|
const spinner = (0, import_ora10.default)("Setting up login...").start();
|
|
783
786
|
try {
|
|
784
|
-
const client = await admin({ method: "POST", path: `${
|
|
787
|
+
const client = await admin({ method: "POST", path: `${base2}/app-clients`, body: clientBody, summary: `Create login for ${tenant2}` });
|
|
785
788
|
const clientId = client?.clientId ?? client?.client_id;
|
|
786
|
-
await admin({ method: "POST", path: `${
|
|
789
|
+
await admin({ method: "POST", path: `${base2}/app-clients/${encodeURIComponent(clientId)}/providers`, body: providerBody, summary: `Add ${provider} provider` });
|
|
787
790
|
spinner.succeed(opt.own ? `${opt.label} login is ready.` : "APIblaze-hosted GitHub login is ready.");
|
|
788
791
|
return true;
|
|
789
792
|
} catch (err) {
|
|
@@ -927,10 +930,10 @@ var init_tenant_pick = __esm({
|
|
|
927
930
|
|
|
928
931
|
// src/index.ts
|
|
929
932
|
var import_commander = require("commander");
|
|
930
|
-
var
|
|
933
|
+
var import_chalk44 = __toESM(require("chalk"));
|
|
931
934
|
|
|
932
935
|
// package.json
|
|
933
|
-
var version = "0.19.
|
|
936
|
+
var version = "0.19.3";
|
|
934
937
|
|
|
935
938
|
// src/index.ts
|
|
936
939
|
init_types();
|
|
@@ -1183,7 +1186,7 @@ function decodeJwt(token) {
|
|
|
1183
1186
|
function maskPath(path8) {
|
|
1184
1187
|
const q = path8.indexOf("?");
|
|
1185
1188
|
if (q < 0) return path8;
|
|
1186
|
-
const
|
|
1189
|
+
const base2 = path8.slice(0, q);
|
|
1187
1190
|
const query = path8.slice(q + 1);
|
|
1188
1191
|
const masked = query.split("&").map((pair) => {
|
|
1189
1192
|
const eq = pair.indexOf("=");
|
|
@@ -1195,7 +1198,7 @@ function maskPath(path8) {
|
|
|
1195
1198
|
}
|
|
1196
1199
|
return pair;
|
|
1197
1200
|
}).join("&");
|
|
1198
|
-
return `${
|
|
1201
|
+
return `${base2}?${masked}`;
|
|
1199
1202
|
}
|
|
1200
1203
|
function formatHeaderLines(name, value) {
|
|
1201
1204
|
const lower = name.toLowerCase();
|
|
@@ -1665,7 +1668,22 @@ async function runDev(options) {
|
|
|
1665
1668
|
}
|
|
1666
1669
|
}
|
|
1667
1670
|
let selectedTargets;
|
|
1668
|
-
if (
|
|
1671
|
+
if (options.project) {
|
|
1672
|
+
const want = options.project.toLowerCase();
|
|
1673
|
+
const match = targets.find(
|
|
1674
|
+
(t) => t.projectId?.toLowerCase() === want || t.projectName?.toLowerCase() === want
|
|
1675
|
+
);
|
|
1676
|
+
if (!match) {
|
|
1677
|
+
console.error(import_chalk4.default.red(`No localhost project named "${options.project}" found.`));
|
|
1678
|
+
if (targets.length) {
|
|
1679
|
+
console.error(import_chalk4.default.dim(" Projects pointing at this machine: " + targets.map((t) => t.projectName).join(", ")));
|
|
1680
|
+
} else {
|
|
1681
|
+
console.error(import_chalk4.default.dim(" No projects point at localhost yet \u2014 create one with `apiblaze create --target http://localhost:<port>`."));
|
|
1682
|
+
}
|
|
1683
|
+
process.exit(1);
|
|
1684
|
+
}
|
|
1685
|
+
selectedTargets = [match];
|
|
1686
|
+
} else if (targets.length === 0) {
|
|
1669
1687
|
const created = await offerAutoCreate(teamId, options.port);
|
|
1670
1688
|
if (!created) {
|
|
1671
1689
|
console.log("Set a project's upstream target to localhost or a private IP, then try again.");
|
|
@@ -1673,17 +1691,25 @@ async function runDev(options) {
|
|
|
1673
1691
|
}
|
|
1674
1692
|
selectedTargets = [created];
|
|
1675
1693
|
} else if (targets.length === 1) {
|
|
1676
|
-
|
|
1677
|
-
|
|
1678
|
-
|
|
1679
|
-
|
|
1680
|
-
|
|
1681
|
-
|
|
1682
|
-
|
|
1683
|
-
|
|
1684
|
-
|
|
1694
|
+
if (options.yes || !process.stdin.isTTY) {
|
|
1695
|
+
selectedTargets = targets;
|
|
1696
|
+
} else {
|
|
1697
|
+
const { confirmed } = await import_inquirer.default.prompt([{
|
|
1698
|
+
type: "confirm",
|
|
1699
|
+
name: "confirmed",
|
|
1700
|
+
message: `Found 1 project with an internal target \u2014 tunnel "${import_chalk4.default.bold(targets[0].projectName)}" (${targets[0].tenantName})?`,
|
|
1701
|
+
default: true
|
|
1702
|
+
}]);
|
|
1703
|
+
if (!confirmed) {
|
|
1704
|
+
console.log("Aborted.");
|
|
1705
|
+
process.exit(0);
|
|
1706
|
+
}
|
|
1707
|
+
selectedTargets = targets;
|
|
1685
1708
|
}
|
|
1686
|
-
|
|
1709
|
+
} else if (!process.stdin.isTTY) {
|
|
1710
|
+
console.error(import_chalk4.default.red(`Found ${targets.length} projects pointing at localhost \u2014 pass --project <name> to choose one non-interactively.`));
|
|
1711
|
+
console.error(import_chalk4.default.dim(" " + targets.map((t) => t.projectName).join(", ")));
|
|
1712
|
+
process.exit(1);
|
|
1687
1713
|
} else {
|
|
1688
1714
|
const ALL = "__all__";
|
|
1689
1715
|
const { chosen } = await import_inquirer.default.prompt([{
|
|
@@ -2010,6 +2036,7 @@ async function runCreate(opts = {}) {
|
|
|
2010
2036
|
const adminKey = keys.dev ?? Object.values(keys)[0];
|
|
2011
2037
|
const proxyUrl = `https://${name}.abz.run/${version2}/dev`;
|
|
2012
2038
|
const devPortal = result.devPortal ? stripTenantFromPortal(result.devPortal) : void 0;
|
|
2039
|
+
await applyCreateToggles(name, opts);
|
|
2013
2040
|
if (opts.json) {
|
|
2014
2041
|
process.stdout.write(JSON.stringify({
|
|
2015
2042
|
project_id: result.project_id,
|
|
@@ -2037,17 +2064,13 @@ async function runCreate(opts = {}) {
|
|
|
2037
2064
|
}
|
|
2038
2065
|
printCurlExample(proxyUrl, auth, adminKey, devPortal);
|
|
2039
2066
|
console.log();
|
|
2040
|
-
await applyCreateToggles(name, opts);
|
|
2041
2067
|
}
|
|
2042
2068
|
async function applyCreateToggles(name, opts) {
|
|
2043
2069
|
if (!opts.identified && !opts.iam) return;
|
|
2044
2070
|
const { runIamToggle: runIamToggle2, runIdentifiedToggle: runIdentifiedToggle2 } = await Promise.resolve().then(() => (init_iam(), iam_exports));
|
|
2045
|
-
|
|
2046
|
-
|
|
2047
|
-
|
|
2048
|
-
if (opts.iam) {
|
|
2049
|
-
await runIamToggle2(name, "on", { team: opts.team, json: opts.json });
|
|
2050
|
-
}
|
|
2071
|
+
const toggleOpts = { team: opts.team, json: opts.json, quiet: opts.json };
|
|
2072
|
+
if (opts.identified) await runIdentifiedToggle2(name, "require", toggleOpts);
|
|
2073
|
+
if (opts.iam) await runIamToggle2(name, "on", toggleOpts);
|
|
2051
2074
|
}
|
|
2052
2075
|
async function runAnonymousCreate(opts) {
|
|
2053
2076
|
const interactive = !!process.stdin.isTTY && !opts.json;
|
|
@@ -2149,6 +2172,7 @@ async function runAnonymousCreate(opts) {
|
|
|
2149
2172
|
const keys = result.api_keys ?? {};
|
|
2150
2173
|
const apiKey = result.apiKey ?? keys.prod ?? Object.values(keys)[0];
|
|
2151
2174
|
const prodEndpoint = (result.endpoints || []).find((e) => e.endsWith("/prod")) || (result.endpoints || [])[0];
|
|
2175
|
+
if (name) await applyCreateToggles(name, opts);
|
|
2152
2176
|
if (opts.json) {
|
|
2153
2177
|
process.stdout.write(JSON.stringify({
|
|
2154
2178
|
project_id: result.project_id,
|
|
@@ -2182,7 +2206,6 @@ async function runAnonymousCreate(opts) {
|
|
|
2182
2206
|
console.log(` ${import_chalk10.default.bold(result.claim_url)}`);
|
|
2183
2207
|
}
|
|
2184
2208
|
console.log();
|
|
2185
|
-
if (name) await applyCreateToggles(name, opts);
|
|
2186
2209
|
}
|
|
2187
2210
|
|
|
2188
2211
|
// src/commands/claim.ts
|
|
@@ -3422,19 +3445,19 @@ async function validScopedTenant(teamId, query) {
|
|
|
3422
3445
|
}
|
|
3423
3446
|
async function tenantHome(teamId, tenant2) {
|
|
3424
3447
|
const { default: inquirer3 } = await import("inquirer");
|
|
3425
|
-
const
|
|
3448
|
+
const base2 = `/teams/${encodeURIComponent(teamId)}/tenants/${encodeURIComponent(tenant2)}`;
|
|
3426
3449
|
console.log(import_chalk29.default.bold(`
|
|
3427
3450
|
Tenant ${tenant2}`));
|
|
3428
3451
|
console.log(import_chalk29.default.dim("Tenant auth/settings are SHARED: changes apply to every proxy this tenant serves.\n"));
|
|
3429
3452
|
for (; ; ) {
|
|
3430
3453
|
const spinner = (0, import_ora13.default)("Reading tenant state...").start();
|
|
3431
3454
|
const [iam, cors, emails, issuers, opaque, clients] = await Promise.all([
|
|
3432
|
-
admin({ method: "GET", path: `${
|
|
3433
|
-
admin({ method: "GET", path: `${
|
|
3434
|
-
admin({ method: "GET", path: `${
|
|
3435
|
-
admin({ method: "GET", path: `${
|
|
3436
|
-
admin({ method: "GET", path: `${
|
|
3437
|
-
admin({ method: "GET", path: `${
|
|
3455
|
+
admin({ method: "GET", path: `${base2}/iam`, summary: "Read IAM toggle" }).catch(() => null),
|
|
3456
|
+
admin({ method: "GET", path: `${base2}/cors`, summary: "Read tenant CORS" }).catch(() => null),
|
|
3457
|
+
admin({ method: "GET", path: `${base2}/admin-emails`, summary: "List consumer-admin emails" }).catch(() => null),
|
|
3458
|
+
admin({ method: "GET", path: `${base2}/external-issuers`, summary: "List external issuers" }).catch(() => null),
|
|
3459
|
+
admin({ method: "GET", path: `${base2}/opaque`, summary: "Read opaque validator" }).catch(() => null),
|
|
3460
|
+
admin({ method: "GET", path: `${base2}/app-clients`, summary: "List app clients" }).catch(() => [])
|
|
3438
3461
|
]).finally(() => spinner.stop());
|
|
3439
3462
|
const nEmails = (emails?.admin_emails ?? []).length;
|
|
3440
3463
|
const nIssuers = (issuers?.external_issuers ?? []).length;
|
|
@@ -3460,11 +3483,11 @@ Tenant ${tenant2}`));
|
|
|
3460
3483
|
case "back":
|
|
3461
3484
|
return;
|
|
3462
3485
|
case "login":
|
|
3463
|
-
await loginMethodsMenu(teamId, tenant2,
|
|
3486
|
+
await loginMethodsMenu(teamId, tenant2, base2);
|
|
3464
3487
|
break;
|
|
3465
3488
|
case "iam": {
|
|
3466
3489
|
const { v } = await inquirer3.prompt([{ type: "confirm", name: "v", message: "Enable Users & groups?", default: !!iam?.iam_enabled }]);
|
|
3467
|
-
await admin({ method: "PATCH", path: `${
|
|
3490
|
+
await admin({ method: "PATCH", path: `${base2}/iam`, body: { enabled: v }, summary: `IAM enforcement \u2192 ${v ? "on" : "off"}` });
|
|
3468
3491
|
console.log(import_chalk29.default.green(` Users & groups ${v ? "enabled" : "disabled"}.`));
|
|
3469
3492
|
break;
|
|
3470
3493
|
}
|
|
@@ -3481,24 +3504,24 @@ Tenant ${tenant2}`));
|
|
|
3481
3504
|
console.log(import_chalk29.default.yellow(" Not valid JSON \u2014 unchanged."));
|
|
3482
3505
|
break;
|
|
3483
3506
|
}
|
|
3484
|
-
await admin({ method: "PUT", path: `${
|
|
3507
|
+
await admin({ method: "PUT", path: `${base2}/cors`, body: { cors: parsed }, summary: "Set tenant CORS" });
|
|
3485
3508
|
console.log(import_chalk29.default.green(" CORS updated."));
|
|
3486
3509
|
break;
|
|
3487
3510
|
}
|
|
3488
3511
|
case "emails":
|
|
3489
|
-
await emailsMenu(
|
|
3512
|
+
await emailsMenu(base2, emails?.admin_emails ?? []);
|
|
3490
3513
|
break;
|
|
3491
3514
|
}
|
|
3492
3515
|
}
|
|
3493
3516
|
}
|
|
3494
|
-
async function loginMethodsMenu(teamId, tenant2,
|
|
3517
|
+
async function loginMethodsMenu(teamId, tenant2, base2) {
|
|
3495
3518
|
const { default: inquirer3 } = await import("inquirer");
|
|
3496
3519
|
for (; ; ) {
|
|
3497
3520
|
const spinner = (0, import_ora13.default)("Loading login methods...").start();
|
|
3498
3521
|
const [rawClients, rawIssuers, rawOpaque] = await Promise.all([
|
|
3499
|
-
admin({ method: "GET", path: `${
|
|
3500
|
-
admin({ method: "GET", path: `${
|
|
3501
|
-
admin({ method: "GET", path: `${
|
|
3522
|
+
admin({ method: "GET", path: `${base2}/app-clients`, summary: "List APIblaze-hosted logins" }).catch(() => []),
|
|
3523
|
+
admin({ method: "GET", path: `${base2}/external-issuers`, summary: "List your-own-JWT logins" }).catch(() => null),
|
|
3524
|
+
admin({ method: "GET", path: `${base2}/opaque`, summary: "Read opaque login" }).catch(() => null)
|
|
3502
3525
|
]).finally(() => spinner.stop());
|
|
3503
3526
|
const appClients = Array.isArray(rawClients) ? rawClients : [];
|
|
3504
3527
|
const issuers = rawIssuers?.external_issuers ?? [];
|
|
@@ -3529,24 +3552,24 @@ async function loginMethodsMenu(teamId, tenant2, base) {
|
|
|
3529
3552
|
}]);
|
|
3530
3553
|
if (pick2.kind === "back") return;
|
|
3531
3554
|
if (pick2.kind === "add") {
|
|
3532
|
-
await addLoginMethod(teamId, tenant2,
|
|
3555
|
+
await addLoginMethod(teamId, tenant2, base2);
|
|
3533
3556
|
continue;
|
|
3534
3557
|
}
|
|
3535
3558
|
if (pick2.kind === "client") {
|
|
3536
|
-
await clientHome(
|
|
3559
|
+
await clientHome(base2, pick2.item);
|
|
3537
3560
|
continue;
|
|
3538
3561
|
}
|
|
3539
3562
|
if (pick2.kind === "issuer") {
|
|
3540
|
-
await issuerHome(
|
|
3563
|
+
await issuerHome(base2, pick2.item);
|
|
3541
3564
|
continue;
|
|
3542
3565
|
}
|
|
3543
3566
|
if (pick2.kind === "opaque") {
|
|
3544
|
-
await opaqueHome(
|
|
3567
|
+
await opaqueHome(base2, pick2.item);
|
|
3545
3568
|
continue;
|
|
3546
3569
|
}
|
|
3547
3570
|
}
|
|
3548
3571
|
}
|
|
3549
|
-
async function addLoginMethod(teamId, tenant2,
|
|
3572
|
+
async function addLoginMethod(teamId, tenant2, base2) {
|
|
3550
3573
|
const { default: inquirer3 } = await import("inquirer");
|
|
3551
3574
|
const { kind } = await inquirer3.prompt([{
|
|
3552
3575
|
type: "list",
|
|
@@ -3565,9 +3588,9 @@ async function addLoginMethod(teamId, tenant2, base) {
|
|
|
3565
3588
|
const { addApiblazeHostedLogin: addApiblazeHostedLogin2 } = await Promise.resolve().then(() => (init_tenant_create(), tenant_create_exports));
|
|
3566
3589
|
await addApiblazeHostedLogin2(teamId, tenant2);
|
|
3567
3590
|
} else if (kind === "jwt") {
|
|
3568
|
-
await addIssuer(
|
|
3591
|
+
await addIssuer(base2);
|
|
3569
3592
|
} else {
|
|
3570
|
-
await setOpaque(
|
|
3593
|
+
await setOpaque(base2, null);
|
|
3571
3594
|
}
|
|
3572
3595
|
}
|
|
3573
3596
|
function safeJson(s) {
|
|
@@ -3577,7 +3600,7 @@ function safeJson(s) {
|
|
|
3577
3600
|
return void 0;
|
|
3578
3601
|
}
|
|
3579
3602
|
}
|
|
3580
|
-
async function emailsMenu(
|
|
3603
|
+
async function emailsMenu(base2, emails) {
|
|
3581
3604
|
const { default: inquirer3 } = await import("inquirer");
|
|
3582
3605
|
console.log();
|
|
3583
3606
|
if (!emails.length) console.log(import_chalk29.default.dim(" No consumer-admin emails."));
|
|
@@ -3595,7 +3618,7 @@ async function emailsMenu(base, emails) {
|
|
|
3595
3618
|
if (act === "back") return;
|
|
3596
3619
|
if (act === "add") {
|
|
3597
3620
|
const { email } = await inquirer3.prompt([{ type: "input", name: "email", message: "Email:", validate: (s) => /.+@.+\..+/.test(s) || "not an email" }]);
|
|
3598
|
-
await admin({ method: "POST", path: `${
|
|
3621
|
+
await admin({ method: "POST", path: `${base2}/admin-emails`, body: { email }, summary: `Add consumer-admin ${email}` });
|
|
3599
3622
|
console.log(import_chalk29.default.green(` ${email} added.`));
|
|
3600
3623
|
} else {
|
|
3601
3624
|
const { e } = await inquirer3.prompt([{
|
|
@@ -3605,11 +3628,11 @@ async function emailsMenu(base, emails) {
|
|
|
3605
3628
|
choices: [...emails.map((x) => ({ name: x.email ?? String(x), value: x.email ?? String(x) })), { name: "\u2190 Back", value: null }]
|
|
3606
3629
|
}]);
|
|
3607
3630
|
if (!e) return;
|
|
3608
|
-
await admin({ method: "DELETE", path: `${
|
|
3631
|
+
await admin({ method: "DELETE", path: `${base2}/admin-emails/${encodeURIComponent(e)}`, summary: `Remove consumer-admin ${e}` });
|
|
3609
3632
|
console.log(import_chalk29.default.green(` ${e} removed.`));
|
|
3610
3633
|
}
|
|
3611
3634
|
}
|
|
3612
|
-
async function addIssuer(
|
|
3635
|
+
async function addIssuer(base2) {
|
|
3613
3636
|
const { default: inquirer3 } = await import("inquirer");
|
|
3614
3637
|
const a = await inquirer3.prompt([
|
|
3615
3638
|
{ type: "input", name: "iss", message: "Issuer URL (iss):", validate: (s) => !!s.trim() || "required" },
|
|
@@ -3623,13 +3646,13 @@ async function addIssuer(base) {
|
|
|
3623
3646
|
const claim = a.sem === "extract_from_claim" ? (await inquirer3.prompt([{ type: "input", name: "c", message: "Claim name:", validate: (s) => !!s.trim() || "required" }])).c : void 0;
|
|
3624
3647
|
await admin({
|
|
3625
3648
|
method: "POST",
|
|
3626
|
-
path: `${
|
|
3649
|
+
path: `${base2}/external-issuers`,
|
|
3627
3650
|
body: { iss: a.iss.trim(), aud: a.aud.trim(), jwks_url: a.jwks.trim() || null, sub_semantics: a.sem, ...claim ? { claim_name: claim } : {} },
|
|
3628
3651
|
summary: `Add external issuer ${a.iss.trim()}`
|
|
3629
3652
|
});
|
|
3630
3653
|
console.log(import_chalk29.default.green(" JWT login method saved."));
|
|
3631
3654
|
}
|
|
3632
|
-
async function issuerHome(
|
|
3655
|
+
async function issuerHome(base2, issuer) {
|
|
3633
3656
|
const { default: inquirer3 } = await import("inquirer");
|
|
3634
3657
|
console.log(`
|
|
3635
3658
|
${import_chalk29.default.bold(issuer.iss)} ${import_chalk29.default.dim(`aud=${issuer.aud} \xB7 ${issuer.sub_semantics ?? ""}`)}`);
|
|
@@ -3645,28 +3668,28 @@ async function issuerHome(base, issuer) {
|
|
|
3645
3668
|
}]);
|
|
3646
3669
|
if (act === "back") return;
|
|
3647
3670
|
if (act === "edit") {
|
|
3648
|
-
await addIssuer(
|
|
3671
|
+
await addIssuer(base2);
|
|
3649
3672
|
return;
|
|
3650
3673
|
}
|
|
3651
3674
|
const { sure } = await inquirer3.prompt([{ type: "confirm", name: "sure", message: `Delete the JWT login method for ${issuer.iss}? Consumers using it can no longer sign in.`, default: false }]);
|
|
3652
3675
|
if (!sure) return;
|
|
3653
3676
|
await admin({
|
|
3654
3677
|
method: "DELETE",
|
|
3655
|
-
path: `${
|
|
3678
|
+
path: `${base2}/external-issuers?iss=${encodeURIComponent(issuer.iss)}&aud=${encodeURIComponent(issuer.aud)}`,
|
|
3656
3679
|
summary: `Delete issuer ${issuer.iss}`
|
|
3657
3680
|
});
|
|
3658
3681
|
console.log(import_chalk29.default.green(" Deleted."));
|
|
3659
3682
|
}
|
|
3660
|
-
async function setOpaque(
|
|
3683
|
+
async function setOpaque(base2, cur) {
|
|
3661
3684
|
const { default: inquirer3 } = await import("inquirer");
|
|
3662
3685
|
const a = await inquirer3.prompt([
|
|
3663
3686
|
{ type: "input", name: "endpoint", message: "Introspection endpoint (https):", default: cur?.endpoint, validate: (s) => s.startsWith("https://") || "must be https" },
|
|
3664
3687
|
{ type: "list", name: "method", message: "HTTP method:", choices: ["GET", "POST"], default: cur?.method ?? "GET" }
|
|
3665
3688
|
]);
|
|
3666
|
-
await admin({ method: "PUT", path: `${
|
|
3689
|
+
await admin({ method: "PUT", path: `${base2}/opaque`, body: { opaque: { endpoint: a.endpoint, method: a.method } }, summary: "Set opaque validator" });
|
|
3667
3690
|
console.log(import_chalk29.default.green(" Opaque login method set."));
|
|
3668
3691
|
}
|
|
3669
|
-
async function opaqueHome(
|
|
3692
|
+
async function opaqueHome(base2, cur) {
|
|
3670
3693
|
const { default: inquirer3 } = await import("inquirer");
|
|
3671
3694
|
console.log(`
|
|
3672
3695
|
${import_chalk29.default.bold(cur.endpoint)} ${import_chalk29.default.dim(cur.method ?? "GET")}`);
|
|
@@ -3682,16 +3705,16 @@ async function opaqueHome(base, cur) {
|
|
|
3682
3705
|
}]);
|
|
3683
3706
|
if (act === "back") return;
|
|
3684
3707
|
if (act === "edit") {
|
|
3685
|
-
await setOpaque(
|
|
3708
|
+
await setOpaque(base2, cur);
|
|
3686
3709
|
return;
|
|
3687
3710
|
}
|
|
3688
|
-
await admin({ method: "PUT", path: `${
|
|
3711
|
+
await admin({ method: "PUT", path: `${base2}/opaque`, body: { opaque: null }, summary: "Clear opaque validator" });
|
|
3689
3712
|
console.log(import_chalk29.default.green(" Deleted."));
|
|
3690
3713
|
}
|
|
3691
|
-
async function clientHome(
|
|
3714
|
+
async function clientHome(base2, summary) {
|
|
3692
3715
|
const { default: inquirer3 } = await import("inquirer");
|
|
3693
3716
|
const id = summary.clientId ?? summary.client_id;
|
|
3694
|
-
const cBase = `${
|
|
3717
|
+
const cBase = `${base2}/app-clients/${encodeURIComponent(id)}`;
|
|
3695
3718
|
for (; ; ) {
|
|
3696
3719
|
const spinner = (0, import_ora13.default)("Reading app client...").start();
|
|
3697
3720
|
const c = await admin({ method: "GET", path: cBase, summary: `Read app client ${id}` }).catch(() => summary);
|
|
@@ -4839,9 +4862,9 @@ function showCondition(cond) {
|
|
|
4839
4862
|
}
|
|
4840
4863
|
async function transformsMenu(proj2) {
|
|
4841
4864
|
const { default: inquirer3 } = await import("inquirer");
|
|
4842
|
-
const
|
|
4865
|
+
const base2 = `/projects/${proj2.projectId}/${proj2.apiVersion}/transforms`;
|
|
4843
4866
|
for (; ; ) {
|
|
4844
|
-
const out = await admin({ method: "GET", path:
|
|
4867
|
+
const out = await admin({ method: "GET", path: base2, summary: "List transform rules" });
|
|
4845
4868
|
const rules = out?.rules ?? [];
|
|
4846
4869
|
console.log();
|
|
4847
4870
|
if (!rules.length) console.log(import_chalk33.default.dim(" No transform rules yet."));
|
|
@@ -4877,7 +4900,7 @@ async function transformsMenu(proj2) {
|
|
|
4877
4900
|
console.log(import_chalk33.default.yellow(" Not a JSON object \u2014 skipped."));
|
|
4878
4901
|
continue;
|
|
4879
4902
|
}
|
|
4880
|
-
await admin({ method: "POST", path:
|
|
4903
|
+
await admin({ method: "POST", path: base2, body, summary: "Create transform rule (raw JSON)" });
|
|
4881
4904
|
console.log(import_chalk33.default.green(" Rule created."));
|
|
4882
4905
|
continue;
|
|
4883
4906
|
}
|
|
@@ -4927,7 +4950,7 @@ async function transformsMenu(proj2) {
|
|
|
4927
4950
|
try {
|
|
4928
4951
|
await admin({
|
|
4929
4952
|
method: "POST",
|
|
4930
|
-
path:
|
|
4953
|
+
path: base2,
|
|
4931
4954
|
body: { name: ans.name, phase: ans.phase, enabled: true, action: action2, ...condition ? { condition } : {} },
|
|
4932
4955
|
summary: `Create transform "${ans.name}"`
|
|
4933
4956
|
});
|
|
@@ -4946,10 +4969,10 @@ async function transformsMenu(proj2) {
|
|
|
4946
4969
|
if (!rule) continue;
|
|
4947
4970
|
if (act === "toggle") {
|
|
4948
4971
|
const flipped = { ...rule, enabled: rule.enabled === false };
|
|
4949
|
-
await admin({ method: "PUT", path: `${
|
|
4972
|
+
await admin({ method: "PUT", path: `${base2}/${rule.id}`, body: flipped, summary: `${flipped.enabled ? "Enable" : "Disable"} transform "${rule.name}"` });
|
|
4950
4973
|
console.log(import_chalk33.default.green(` ${rule.name} \u2192 ${flipped.enabled ? "enabled" : "disabled"}`));
|
|
4951
4974
|
} else {
|
|
4952
|
-
await admin({ method: "DELETE", path: `${
|
|
4975
|
+
await admin({ method: "DELETE", path: `${base2}/${rule.id}`, summary: `Delete transform "${rule.name}"` });
|
|
4953
4976
|
console.log(import_chalk33.default.green(` ${rule.name} deleted.`));
|
|
4954
4977
|
}
|
|
4955
4978
|
}
|
|
@@ -4957,9 +4980,9 @@ async function transformsMenu(proj2) {
|
|
|
4957
4980
|
}
|
|
4958
4981
|
async function mappingsMenu(proj2) {
|
|
4959
4982
|
const { default: inquirer3 } = await import("inquirer");
|
|
4960
|
-
const
|
|
4983
|
+
const base2 = `/projects/${proj2.projectId}/${proj2.apiVersion}/mappings`;
|
|
4961
4984
|
for (; ; ) {
|
|
4962
|
-
const out = await admin({ method: "GET", path:
|
|
4985
|
+
const out = await admin({ method: "GET", path: base2, summary: "List mapping tables" });
|
|
4963
4986
|
const tables = out?.mappings ?? out?.tables ?? [];
|
|
4964
4987
|
console.log();
|
|
4965
4988
|
if (!tables.length) console.log(import_chalk33.default.dim(" No mapping tables yet."));
|
|
@@ -4987,7 +5010,7 @@ async function mappingsMenu(proj2) {
|
|
|
4987
5010
|
console.log(import_chalk33.default.yellow(" Entries must be a JSON array \u2014 not created."));
|
|
4988
5011
|
continue;
|
|
4989
5012
|
}
|
|
4990
|
-
await admin({ method: "POST", path:
|
|
5013
|
+
await admin({ method: "POST", path: base2, body: { name: a.name, entries: entries2 }, summary: `Create mapping table "${a.name}"` });
|
|
4991
5014
|
console.log(import_chalk33.default.green(` Table "${a.name}" created.`));
|
|
4992
5015
|
} else {
|
|
4993
5016
|
const { table } = await inquirer3.prompt([{
|
|
@@ -4997,16 +5020,16 @@ async function mappingsMenu(proj2) {
|
|
|
4997
5020
|
choices: [...tables.map((t) => ({ name: t.name, value: t })), { name: "\u2190 Back", value: null }]
|
|
4998
5021
|
}]);
|
|
4999
5022
|
if (!table) continue;
|
|
5000
|
-
await admin({ method: "DELETE", path: `${
|
|
5023
|
+
await admin({ method: "DELETE", path: `${base2}/${table.id}`, summary: `Delete mapping table "${table.name}"` });
|
|
5001
5024
|
console.log(import_chalk33.default.green(` ${table.name} deleted.`));
|
|
5002
5025
|
}
|
|
5003
5026
|
}
|
|
5004
5027
|
}
|
|
5005
5028
|
async function tenantsMenu(proj2, opts) {
|
|
5006
5029
|
const { default: inquirer3 } = await import("inquirer");
|
|
5007
|
-
const
|
|
5030
|
+
const base2 = `/projects/${proj2.projectId}/${proj2.apiVersion}/tenants`;
|
|
5008
5031
|
for (; ; ) {
|
|
5009
|
-
const out = await admin({ method: "GET", path:
|
|
5032
|
+
const out = await admin({ method: "GET", path: base2, summary: "List attached tenants" });
|
|
5010
5033
|
const tenants = out?.tenants ?? [];
|
|
5011
5034
|
console.log();
|
|
5012
5035
|
if (!tenants.length) console.log(import_chalk33.default.dim(" No tenants attached (consumers use the default tenant)."));
|
|
@@ -5038,7 +5061,7 @@ async function tenantsMenu(proj2, opts) {
|
|
|
5038
5061
|
choices: [...tenants.map((x) => ({ name: x.tenant_name ?? x.name, value: x })), { name: "\u2190 Back", value: null }]
|
|
5039
5062
|
}]);
|
|
5040
5063
|
if (!t) continue;
|
|
5041
|
-
await admin({ method: "DELETE", path: `${
|
|
5064
|
+
await admin({ method: "DELETE", path: `${base2}/${encodeURIComponent(t.tenant_name ?? t.name)}`, summary: `Detach tenant ${t.tenant_name ?? t.name}` });
|
|
5042
5065
|
console.log(import_chalk33.default.green(` Detached ${t.tenant_name ?? t.name}.`));
|
|
5043
5066
|
}
|
|
5044
5067
|
}
|
|
@@ -5418,9 +5441,85 @@ async function runKeyRevoke(keyId, opts) {
|
|
|
5418
5441
|
// src/index.ts
|
|
5419
5442
|
init_iam();
|
|
5420
5443
|
|
|
5421
|
-
// src/commands/
|
|
5444
|
+
// src/commands/admins.ts
|
|
5422
5445
|
var import_chalk36 = __toESM(require("chalk"));
|
|
5423
5446
|
var import_ora19 = __toESM(require("ora"));
|
|
5447
|
+
init_caller();
|
|
5448
|
+
function requireTenant(opts) {
|
|
5449
|
+
const t = (opts.tenant ?? "").trim();
|
|
5450
|
+
if (!t) {
|
|
5451
|
+
throw new Error("--tenant <slug> is required (e.g. --tenant nino).");
|
|
5452
|
+
}
|
|
5453
|
+
return t;
|
|
5454
|
+
}
|
|
5455
|
+
function base(teamId, tenant2) {
|
|
5456
|
+
return `/teams/${encodeURIComponent(teamId)}/tenants/${encodeURIComponent(tenant2)}/admin-emails`;
|
|
5457
|
+
}
|
|
5458
|
+
async function runAdminsList(opts) {
|
|
5459
|
+
const caller = requireCaller();
|
|
5460
|
+
const { teamId } = await resolveActingTeam(caller, opts.team);
|
|
5461
|
+
const tenant2 = requireTenant(opts);
|
|
5462
|
+
const out = await producer(caller, {
|
|
5463
|
+
method: "GET",
|
|
5464
|
+
path: base(teamId, tenant2),
|
|
5465
|
+
summary: `List admins for tenant ${tenant2}`
|
|
5466
|
+
});
|
|
5467
|
+
const admins2 = out?.admin_emails ?? out?.admins ?? [];
|
|
5468
|
+
if (opts.json) {
|
|
5469
|
+
console.log(JSON.stringify(admins2));
|
|
5470
|
+
return;
|
|
5471
|
+
}
|
|
5472
|
+
if (!admins2.length) {
|
|
5473
|
+
console.log(import_chalk36.default.yellow(`No admins for tenant ${tenant2} yet.`));
|
|
5474
|
+
return;
|
|
5475
|
+
}
|
|
5476
|
+
for (const a of admins2) {
|
|
5477
|
+
const email = typeof a === "string" ? a : a.email;
|
|
5478
|
+
const flags = [a.active === false ? "pending" : null, a.pinned ? "pinned" : null].filter(Boolean).join(", ");
|
|
5479
|
+
console.log(` ${import_chalk36.default.bold(email)}${flags ? import_chalk36.default.dim(` (${flags})`) : ""}`);
|
|
5480
|
+
}
|
|
5481
|
+
}
|
|
5482
|
+
async function runAdminsAdd(email, opts) {
|
|
5483
|
+
const caller = requireCaller();
|
|
5484
|
+
const { teamId } = await resolveActingTeam(caller, opts.team);
|
|
5485
|
+
const tenant2 = requireTenant(opts);
|
|
5486
|
+
const spinner = opts.json ? null : (0, import_ora19.default)(`Adding ${email} as admin of ${tenant2}...`).start();
|
|
5487
|
+
try {
|
|
5488
|
+
const out = await producer(caller, {
|
|
5489
|
+
method: "POST",
|
|
5490
|
+
path: base(teamId, tenant2),
|
|
5491
|
+
body: { email },
|
|
5492
|
+
summary: `Add ${email} to tenant ${tenant2} admins`
|
|
5493
|
+
});
|
|
5494
|
+
spinner?.succeed(`${import_chalk36.default.bold(email)} is now an admin of ${import_chalk36.default.bold(tenant2)}.`);
|
|
5495
|
+
if (opts.json) console.log(JSON.stringify(out ?? { ok: true }));
|
|
5496
|
+
else console.log(import_chalk36.default.dim(" Reload the Users & Groups widget \u2014 access flips from \u201Cpending\u201D to ready."));
|
|
5497
|
+
} catch (err) {
|
|
5498
|
+
spinner?.fail("Add failed.");
|
|
5499
|
+
throw err;
|
|
5500
|
+
}
|
|
5501
|
+
}
|
|
5502
|
+
async function runAdminsRemove(email, opts) {
|
|
5503
|
+
const caller = requireCaller();
|
|
5504
|
+
const { teamId } = await resolveActingTeam(caller, opts.team);
|
|
5505
|
+
const tenant2 = requireTenant(opts);
|
|
5506
|
+
const spinner = opts.json ? null : (0, import_ora19.default)(`Removing ${email} from ${tenant2} admins...`).start();
|
|
5507
|
+
try {
|
|
5508
|
+
await producer(caller, {
|
|
5509
|
+
method: "DELETE",
|
|
5510
|
+
path: `${base(teamId, tenant2)}/${encodeURIComponent(email)}`,
|
|
5511
|
+
summary: `Remove ${email} from tenant ${tenant2} admins`
|
|
5512
|
+
});
|
|
5513
|
+
spinner?.succeed(`Removed ${import_chalk36.default.bold(email)} from ${import_chalk36.default.bold(tenant2)} admins.`);
|
|
5514
|
+
} catch (err) {
|
|
5515
|
+
spinner?.fail("Remove failed.");
|
|
5516
|
+
throw err;
|
|
5517
|
+
}
|
|
5518
|
+
}
|
|
5519
|
+
|
|
5520
|
+
// src/commands/preapprove.ts
|
|
5521
|
+
var import_chalk37 = __toESM(require("chalk"));
|
|
5522
|
+
var import_ora20 = __toESM(require("ora"));
|
|
5424
5523
|
|
|
5425
5524
|
// src/lib/preapproval.ts
|
|
5426
5525
|
init_auth();
|
|
@@ -5484,14 +5583,14 @@ async function runPreapprove(who, opts) {
|
|
|
5484
5583
|
const rules = await listPreapprovalRules(tenant2);
|
|
5485
5584
|
if (opts.json) return void console.log(JSON.stringify(rules, null, 2));
|
|
5486
5585
|
if (!rules.length) {
|
|
5487
|
-
console.log(
|
|
5488
|
-
console.log(
|
|
5586
|
+
console.log(import_chalk37.default.dim(`No pre-approval rules in ${tenant2}. Anyone who signs in can access (unless access is restricted).`));
|
|
5587
|
+
console.log(import_chalk37.default.dim(`Add one: apiblaze preapprove someone@acme.com`));
|
|
5489
5588
|
return;
|
|
5490
5589
|
}
|
|
5491
|
-
console.log(
|
|
5590
|
+
console.log(import_chalk37.default.dim(`Pre-approved for ${import_chalk37.default.bold(tenant2)}:`));
|
|
5492
5591
|
for (const r of rules) {
|
|
5493
|
-
const tag = r.kind === "domain" ?
|
|
5494
|
-
const grp = r.groups?.length ?
|
|
5592
|
+
const tag = r.kind === "domain" ? import_chalk37.default.cyan("@" + r.value + " (whole domain)") : r.value;
|
|
5593
|
+
const grp = r.groups?.length ? import_chalk37.default.dim(` \u2192 groups: ${r.groups.join(", ")}`) : "";
|
|
5495
5594
|
console.log(` ${tag}${grp}`);
|
|
5496
5595
|
}
|
|
5497
5596
|
return;
|
|
@@ -5500,19 +5599,19 @@ async function runPreapprove(who, opts) {
|
|
|
5500
5599
|
throw new Error("Who? Pass an email or a domain: `apiblaze preapprove someone@acme.com` (or `apiblaze preapprove --list`).");
|
|
5501
5600
|
}
|
|
5502
5601
|
if (opts.remove) {
|
|
5503
|
-
const spinner2 = (0,
|
|
5602
|
+
const spinner2 = (0, import_ora20.default)(`Removing ${who} from ${tenant2}\u2026`).start();
|
|
5504
5603
|
const { removed, value } = await removePreapprovalRule(tenant2, who);
|
|
5505
|
-
if (removed) spinner2.succeed(`${
|
|
5506
|
-
else spinner2.warn(`No pre-approval rule for ${
|
|
5604
|
+
if (removed) spinner2.succeed(`${import_chalk37.default.bold(value)} is no longer pre-approved for ${tenant2}.`);
|
|
5605
|
+
else spinner2.warn(`No pre-approval rule for ${import_chalk37.default.bold(value)} in ${tenant2} \u2014 nothing to remove.`);
|
|
5507
5606
|
return;
|
|
5508
5607
|
}
|
|
5509
|
-
const spinner = (0,
|
|
5608
|
+
const spinner = (0, import_ora20.default)(`Pre-approving ${who} for ${tenant2}\u2026`).start();
|
|
5510
5609
|
try {
|
|
5511
5610
|
const { kind, value } = await addPreapprovalRule(tenant2, who, opts.group);
|
|
5512
5611
|
const what = kind === "domain" ? `Anyone @${value}` : value;
|
|
5513
|
-
spinner.succeed(`${
|
|
5514
|
-
if (opts.group?.length) console.log(
|
|
5515
|
-
console.log(
|
|
5612
|
+
spinner.succeed(`${import_chalk37.default.bold(what)} can now sign in to ${import_chalk37.default.bold(tenant2)}.`);
|
|
5613
|
+
if (opts.group?.length) console.log(import_chalk37.default.dim(` They'll join group(s) ${opts.group.join(", ")} on first sign-in.`));
|
|
5614
|
+
console.log(import_chalk37.default.dim(` See the full list: apiblaze preapprove --list`));
|
|
5516
5615
|
} catch (err) {
|
|
5517
5616
|
spinner.fail("Could not add the rule.");
|
|
5518
5617
|
throw err;
|
|
@@ -5523,8 +5622,8 @@ async function runPreapprove(who, opts) {
|
|
|
5523
5622
|
var fs9 = __toESM(require("fs"));
|
|
5524
5623
|
var path6 = __toESM(require("path"));
|
|
5525
5624
|
var crypto2 = __toESM(require("crypto"));
|
|
5526
|
-
var
|
|
5527
|
-
var
|
|
5625
|
+
var import_chalk39 = __toESM(require("chalk"));
|
|
5626
|
+
var import_ora21 = __toESM(require("ora"));
|
|
5528
5627
|
var import_yaml = require("yaml");
|
|
5529
5628
|
init_auth();
|
|
5530
5629
|
init_anon_cred();
|
|
@@ -5534,7 +5633,7 @@ init_admin();
|
|
|
5534
5633
|
// src/commands/llm.ts
|
|
5535
5634
|
var fs8 = __toESM(require("fs"));
|
|
5536
5635
|
var path5 = __toESM(require("path"));
|
|
5537
|
-
var
|
|
5636
|
+
var import_chalk38 = __toESM(require("chalk"));
|
|
5538
5637
|
var import_inquirer2 = __toESM(require("inquirer"));
|
|
5539
5638
|
init_auth();
|
|
5540
5639
|
var LLM_PATH = path5.join(getApiblazeDir(), "llm.json");
|
|
@@ -5578,27 +5677,27 @@ async function runLlmSetKey(keyArg, opts) {
|
|
|
5578
5677
|
}
|
|
5579
5678
|
const existing = loadLlmConfig();
|
|
5580
5679
|
saveLlmConfig({ key, provider, model: opts.model ?? existing?.model });
|
|
5581
|
-
console.log(`${
|
|
5582
|
-
console.log(
|
|
5583
|
-
if (opts.model) console.log(
|
|
5680
|
+
console.log(`${import_chalk38.default.green("\u2713")} Saved ${import_chalk38.default.bold(provider)} key ${maskSecret(key)}`);
|
|
5681
|
+
console.log(import_chalk38.default.gray(` Stored locally at ${LLM_PATH} (0600) \u2014 never sent to APIblaze except per chat turn.`));
|
|
5682
|
+
if (opts.model) console.log(import_chalk38.default.gray(` Model: ${opts.model}`));
|
|
5584
5683
|
}
|
|
5585
5684
|
async function runLlmShow() {
|
|
5586
5685
|
const cfg = loadLlmConfig();
|
|
5587
5686
|
if (!cfg) {
|
|
5588
|
-
console.log(
|
|
5687
|
+
console.log(import_chalk38.default.gray("No LLM key set. `apiblaze llm set-key sk-...` to add one (optional \u2014 chat works without it)."));
|
|
5589
5688
|
return;
|
|
5590
5689
|
}
|
|
5591
|
-
console.log(`Provider: ${
|
|
5690
|
+
console.log(`Provider: ${import_chalk38.default.bold(cfg.provider)}`);
|
|
5592
5691
|
console.log(`Key: ${maskSecret(cfg.key)}`);
|
|
5593
5692
|
if (cfg.model) console.log(`Model: ${cfg.model}`);
|
|
5594
|
-
console.log(
|
|
5693
|
+
console.log(import_chalk38.default.gray(`Stored at ${LLM_PATH}`));
|
|
5595
5694
|
}
|
|
5596
5695
|
async function runLlmClearKey() {
|
|
5597
5696
|
try {
|
|
5598
5697
|
fs8.unlinkSync(LLM_PATH);
|
|
5599
|
-
console.log(`${
|
|
5698
|
+
console.log(`${import_chalk38.default.green("\u2713")} Removed local LLM key.`);
|
|
5600
5699
|
} catch {
|
|
5601
|
-
console.log(
|
|
5700
|
+
console.log(import_chalk38.default.gray("No LLM key was set."));
|
|
5602
5701
|
}
|
|
5603
5702
|
}
|
|
5604
5703
|
|
|
@@ -5606,9 +5705,9 @@ async function runLlmClearKey() {
|
|
|
5606
5705
|
init_trace();
|
|
5607
5706
|
init_types();
|
|
5608
5707
|
function fail4(message, hint) {
|
|
5609
|
-
console.error(
|
|
5708
|
+
console.error(import_chalk39.default.red(`
|
|
5610
5709
|
Error: ${message}`));
|
|
5611
|
-
if (hint) console.error(
|
|
5710
|
+
if (hint) console.error(import_chalk39.default.dim(hint));
|
|
5612
5711
|
process.exit(1);
|
|
5613
5712
|
}
|
|
5614
5713
|
function normalizeName2(raw) {
|
|
@@ -5650,9 +5749,9 @@ async function fetchText(url) {
|
|
|
5650
5749
|
}
|
|
5651
5750
|
}
|
|
5652
5751
|
async function discoverSpec(target) {
|
|
5653
|
-
const
|
|
5752
|
+
const base2 = target.replace(/\/+$/, "");
|
|
5654
5753
|
for (const suffix of ["/openapi.json", "/openapi.yaml", "/swagger.json"]) {
|
|
5655
|
-
const url =
|
|
5754
|
+
const url = base2 + suffix;
|
|
5656
5755
|
const text = await fetchText(url);
|
|
5657
5756
|
if (text) {
|
|
5658
5757
|
try {
|
|
@@ -5756,7 +5855,7 @@ async function resolveTargetAuth(spec2, opts) {
|
|
|
5756
5855
|
"Re-run with --force to provision anyway (configure target auth later with `apiblaze config`),\nor use an api_key / bearer / basic scheme."
|
|
5757
5856
|
);
|
|
5758
5857
|
}
|
|
5759
|
-
if (sawOAuth) console.log(
|
|
5858
|
+
if (sawOAuth) console.log(import_chalk39.default.yellow(" --force: skipping OAuth target auth \u2014 configure it later with `apiblaze config`."));
|
|
5760
5859
|
return null;
|
|
5761
5860
|
}
|
|
5762
5861
|
if (candidates.length === 1 && !noneAllowed) return candidates[0];
|
|
@@ -5821,28 +5920,28 @@ async function provision(spec2, target, opts) {
|
|
|
5821
5920
|
const loggedIn = !!loadCredentials();
|
|
5822
5921
|
const anon = !loggedIn;
|
|
5823
5922
|
const salt = () => Math.random().toString(36).slice(2, 6);
|
|
5824
|
-
let
|
|
5825
|
-
if (!
|
|
5923
|
+
let base2 = opts.name ? normalizeName2(opts.name) : "";
|
|
5924
|
+
if (!base2) {
|
|
5826
5925
|
try {
|
|
5827
5926
|
const host = new URL(target).hostname;
|
|
5828
|
-
|
|
5829
|
-
if (
|
|
5927
|
+
base2 = normalizeName2(host.split(".")[0]);
|
|
5928
|
+
if (base2.length < 3) base2 = normalizeName2(host);
|
|
5830
5929
|
} catch {
|
|
5831
5930
|
}
|
|
5832
5931
|
}
|
|
5833
|
-
if (!
|
|
5834
|
-
if (!
|
|
5835
|
-
let name = opts.name ?
|
|
5932
|
+
if (!base2 && spec2.info && typeof spec2.info.title === "string") base2 = normalizeName2(spec2.info.title);
|
|
5933
|
+
if (!base2 || base2.length < 3) base2 = "apichat";
|
|
5934
|
+
let name = opts.name ? base2 : `${base2}${salt()}`;
|
|
5836
5935
|
const access = anon ? "open" : opts.access === "open" ? "open" : "invite";
|
|
5837
5936
|
if (anon && opts.access === "invite") {
|
|
5838
|
-
console.log(
|
|
5937
|
+
console.log(import_chalk39.default.dim(" Note: --access invite needs an account to pre-approve people. Staying open for this anonymous proxy \u2014 run `apiblaze login`, then `apiblaze apichat --access invite`."));
|
|
5839
5938
|
}
|
|
5840
5939
|
const DUAL_AUTH = {
|
|
5841
5940
|
mode: "authenticate",
|
|
5842
5941
|
methods: ["api_key", "jwt"],
|
|
5843
5942
|
...access === "invite" ? { preapproved_users_only: true } : {}
|
|
5844
5943
|
};
|
|
5845
|
-
const spinner = (0,
|
|
5944
|
+
const spinner = (0, import_ora21.default)("Provisioning an api_key proxy...").start();
|
|
5846
5945
|
let result;
|
|
5847
5946
|
for (let attempt = 0; attempt < 4; attempt++) {
|
|
5848
5947
|
try {
|
|
@@ -5874,13 +5973,13 @@ async function provision(spec2, target, opts) {
|
|
|
5874
5973
|
if (result.cp_key && result.team_id) saveAnonCred(result.cp_key, result.team_id, result.claim_code);
|
|
5875
5974
|
}
|
|
5876
5975
|
}
|
|
5877
|
-
spinner.succeed(`Proxy provisioned${name !==
|
|
5976
|
+
spinner.succeed(`Proxy provisioned${name !== base2 ? ` as "${name}"` : ""}.`);
|
|
5878
5977
|
break;
|
|
5879
5978
|
} catch (err) {
|
|
5880
5979
|
const status = err instanceof ApiError ? err.status : void 0;
|
|
5881
5980
|
const collision = err instanceof ApiError && (err.status === 409 || /exist|taken|available/i.test(err.message));
|
|
5882
5981
|
if (collision && attempt < 3) {
|
|
5883
|
-
name = `${
|
|
5982
|
+
name = `${base2}${salt()}`;
|
|
5884
5983
|
continue;
|
|
5885
5984
|
}
|
|
5886
5985
|
if (status === 401) {
|
|
@@ -5928,14 +6027,14 @@ async function provision(spec2, target, opts) {
|
|
|
5928
6027
|
try {
|
|
5929
6028
|
await addPreapprovalRule(tenant2, email);
|
|
5930
6029
|
} catch {
|
|
5931
|
-
console.log(
|
|
6030
|
+
console.log(import_chalk39.default.dim(` (Could not auto-approve your email for sign-in \u2014 add it later: apiblaze preapprove ${email})`));
|
|
5932
6031
|
}
|
|
5933
6032
|
}
|
|
5934
6033
|
}
|
|
5935
6034
|
return { projectId, version: version2, environment, dpKey, mcpHost, proxyUrl, anon, access, tenant: tenant2 };
|
|
5936
6035
|
}
|
|
5937
6036
|
async function writeTargetAuth(p, auth, secret) {
|
|
5938
|
-
const spinner = (0,
|
|
6037
|
+
const spinner = (0, import_ora21.default)("Storing target credentials (encrypted)...").start();
|
|
5939
6038
|
try {
|
|
5940
6039
|
await cpPost(
|
|
5941
6040
|
p.anon,
|
|
@@ -5957,7 +6056,7 @@ async function writeTargetAuth(p, auth, secret) {
|
|
|
5957
6056
|
}
|
|
5958
6057
|
}
|
|
5959
6058
|
async function uploadSpec(p, specText, opts) {
|
|
5960
|
-
const spinner = (0,
|
|
6059
|
+
const spinner = (0, import_ora21.default)("Uploading the spec...").start();
|
|
5961
6060
|
let out;
|
|
5962
6061
|
try {
|
|
5963
6062
|
out = await cpPost(
|
|
@@ -5972,7 +6071,7 @@ async function uploadSpec(p, specText, opts) {
|
|
|
5972
6071
|
throw err;
|
|
5973
6072
|
}
|
|
5974
6073
|
if (out && out.reused === true) {
|
|
5975
|
-
console.log(
|
|
6074
|
+
console.log(import_chalk39.default.dim(" Spec unchanged since the last provision \u2014 reusing the existing configuration."));
|
|
5976
6075
|
} else if (out && out.changed === true && out.previous_spec_hash) {
|
|
5977
6076
|
const interactive = !!process.stdin.isTTY && !opts.yes;
|
|
5978
6077
|
if (interactive) {
|
|
@@ -5980,12 +6079,12 @@ async function uploadSpec(p, specText, opts) {
|
|
|
5980
6079
|
const { go } = await inquirer3.prompt([
|
|
5981
6080
|
{ type: "confirm", name: "go", message: "The spec changed since the last provision \u2014 re-publish the MCP catalogue?", default: true }
|
|
5982
6081
|
]);
|
|
5983
|
-
if (!go) console.log(
|
|
6082
|
+
if (!go) console.log(import_chalk39.default.dim(" Keeping the existing MCP catalogue."));
|
|
5984
6083
|
}
|
|
5985
6084
|
}
|
|
5986
6085
|
}
|
|
5987
6086
|
async function publishMcp(p, spec2) {
|
|
5988
|
-
const spinner = (0,
|
|
6087
|
+
const spinner = (0, import_ora21.default)("Publishing the MCP catalogue...").start();
|
|
5989
6088
|
try {
|
|
5990
6089
|
const url = `https://${p.mcpHost}/${p.version}/${p.environment}/mcp/generate`;
|
|
5991
6090
|
const res = await fetch(url, {
|
|
@@ -6021,14 +6120,14 @@ var revealAuth = false;
|
|
|
6021
6120
|
function renderToolEvents(events, dpKey) {
|
|
6022
6121
|
for (const e of events ?? []) {
|
|
6023
6122
|
const ok = typeof e.status === "number" ? e.status < 400 : String(e.status).toLowerCase() === "ok";
|
|
6024
|
-
const mark = ok ?
|
|
6025
|
-
console.log(` ${mark} ${
|
|
6123
|
+
const mark = ok ? import_chalk39.default.green("\u2713") : import_chalk39.default.red("\u2717");
|
|
6124
|
+
console.log(` ${mark} ${import_chalk39.default.cyan(e.name)} ${import_chalk39.default.dim(`(${e.status}, ${e.ms}ms)`)}`);
|
|
6026
6125
|
if (isVerbose() && e.method && e.url) {
|
|
6027
|
-
console.log(
|
|
6126
|
+
console.log(import_chalk39.default.dim(` curl -sS -X ${e.method} '${e.url}'${dpKey ? " \\" : ""}`));
|
|
6028
6127
|
if (dpKey) {
|
|
6029
6128
|
const keyLine = ` -H 'X-API-Key: ${revealAuth ? dpKey : maskKey(dpKey)}'`;
|
|
6030
|
-
const hint = revealAuth ? "" :
|
|
6031
|
-
console.log(
|
|
6129
|
+
const hint = revealAuth ? "" : import_chalk39.default.yellow(" \u2190 /showauth will reveal this");
|
|
6130
|
+
console.log(import_chalk39.default.dim(keyLine) + hint);
|
|
6032
6131
|
}
|
|
6033
6132
|
}
|
|
6034
6133
|
}
|
|
@@ -6037,9 +6136,9 @@ function billingLine(billing) {
|
|
|
6037
6136
|
if (!billing || typeof billing.cents !== "number") return null;
|
|
6038
6137
|
if (typeof billing.free_turns_remaining === "number") return null;
|
|
6039
6138
|
const usd = (billing.cents / 100).toFixed(Math.abs(billing.cents - Math.round(billing.cents)) < 1e-9 ? 2 : 4);
|
|
6040
|
-
let line =
|
|
6139
|
+
let line = import_chalk39.default.magenta(` \u{1F4B3} $${usd}`) + import_chalk39.default.dim(billing.model ? ` \xB7 ${billing.model}` : "");
|
|
6041
6140
|
if (typeof billing.credits_remaining === "number") {
|
|
6042
|
-
line +=
|
|
6141
|
+
line += import_chalk39.default.dim(` \xB7 balance $${(billing.credits_remaining / 100).toFixed(2)}`);
|
|
6043
6142
|
}
|
|
6044
6143
|
return line;
|
|
6045
6144
|
}
|
|
@@ -6047,21 +6146,21 @@ function freeBudgetWarning(billing, anon) {
|
|
|
6047
6146
|
if (!anon || !billing) return null;
|
|
6048
6147
|
if (typeof billing.free_turns_remaining === "number") {
|
|
6049
6148
|
const left2 = billing.free_turns_remaining;
|
|
6050
|
-
if (left2 <= 0) return
|
|
6051
|
-
return
|
|
6149
|
+
if (left2 <= 0) return import_chalk39.default.yellow(" Free chats used up \u2014 `npx apiblaze login` (free) to keep going.");
|
|
6150
|
+
return import_chalk39.default.dim(` ${left2} free chat${left2 === 1 ? "" : "s"} left \xB7 /login to get more`);
|
|
6052
6151
|
}
|
|
6053
6152
|
if (typeof billing.free_remaining_cents !== "number") return null;
|
|
6054
6153
|
const perTurn = Math.max(billing.cents || 0, 0.02);
|
|
6055
6154
|
const left = Math.floor(billing.free_remaining_cents / perTurn);
|
|
6056
6155
|
if (left > 8) return null;
|
|
6057
|
-
if (left <= 0) return
|
|
6058
|
-
return
|
|
6156
|
+
if (left <= 0) return import_chalk39.default.yellow(" Free messages used up \u2014 `npx apiblaze login` (free) to keep chatting.");
|
|
6157
|
+
return import_chalk39.default.yellow(` \u26A0 About ${left} free message${left === 1 ? "" : "s"} left \u2014 \`npx apiblaze login\` (free) for more.`);
|
|
6059
6158
|
}
|
|
6060
6159
|
function printAssistant(delta) {
|
|
6061
6160
|
for (let i = delta.length - 1; i >= 0; i--) {
|
|
6062
6161
|
const m = delta[i];
|
|
6063
6162
|
if (m && m.role === "assistant" && typeof m.content === "string" && m.content.trim()) {
|
|
6064
|
-
console.log("\n" +
|
|
6163
|
+
console.log("\n" + import_chalk39.default.green("assistant \u203A ") + m.content + "\n");
|
|
6065
6164
|
return;
|
|
6066
6165
|
}
|
|
6067
6166
|
}
|
|
@@ -6071,7 +6170,7 @@ async function replTurn(p, messages, userText) {
|
|
|
6071
6170
|
const llm2 = loadLlmConfig();
|
|
6072
6171
|
const turnId = crypto2.randomUUID();
|
|
6073
6172
|
for (let round = 0; round < CLIENT_ROUND_CAP; round++) {
|
|
6074
|
-
const spinner = (0,
|
|
6173
|
+
const spinner = (0, import_ora21.default)({ text: round === 0 ? "thinking..." : "working...", color: "magenta" }).start();
|
|
6075
6174
|
const body = {
|
|
6076
6175
|
turn_id: turnId,
|
|
6077
6176
|
messages,
|
|
@@ -6087,7 +6186,7 @@ async function replTurn(p, messages, userText) {
|
|
|
6087
6186
|
});
|
|
6088
6187
|
} catch (err) {
|
|
6089
6188
|
spinner.fail("Network error.");
|
|
6090
|
-
console.log(
|
|
6189
|
+
console.log(import_chalk39.default.red(` Could not reach ${p.mcpHost}: ${err instanceof Error ? err.message : String(err)}`));
|
|
6091
6190
|
return;
|
|
6092
6191
|
}
|
|
6093
6192
|
let data = null;
|
|
@@ -6105,12 +6204,12 @@ async function replTurn(p, messages, userText) {
|
|
|
6105
6204
|
return;
|
|
6106
6205
|
}
|
|
6107
6206
|
if (res.status === 401) {
|
|
6108
|
-
console.log(
|
|
6207
|
+
console.log(import_chalk39.default.red(" The proxy rejected the API key (401). The key may have been revoked; re-run `apiblaze apichat` to re-provision."));
|
|
6109
6208
|
return;
|
|
6110
6209
|
}
|
|
6111
6210
|
if (!res.ok || !data) {
|
|
6112
6211
|
const err = data && data.error || `HTTP ${res.status}`;
|
|
6113
|
-
console.log(
|
|
6212
|
+
console.log(import_chalk39.default.red(` Chat error: ${err}`));
|
|
6114
6213
|
return;
|
|
6115
6214
|
}
|
|
6116
6215
|
if (Array.isArray(data.delta)) {
|
|
@@ -6124,25 +6223,25 @@ async function replTurn(p, messages, userText) {
|
|
|
6124
6223
|
if (warn) console.log(warn);
|
|
6125
6224
|
if (!data.continue) return;
|
|
6126
6225
|
}
|
|
6127
|
-
console.log(
|
|
6226
|
+
console.log(import_chalk39.default.dim(" (stopped after several tool rounds \u2014 ask again to continue)"));
|
|
6128
6227
|
}
|
|
6129
6228
|
function renderUpsell(p, upsell) {
|
|
6130
6229
|
const loggedIn = !!loadCredentials();
|
|
6131
6230
|
if (upsell.reason === "CAPPED" && !loggedIn) {
|
|
6132
|
-
console.log("\n" +
|
|
6133
|
-
console.log(
|
|
6231
|
+
console.log("\n" + import_chalk39.default.yellow(" Type `npx apiblaze login` to claim the rest of your balance."));
|
|
6232
|
+
console.log(import_chalk39.default.dim(" (or `/login` right here \u2014 your chat is preserved \u2014 or `apiblaze llm set-key` for your own model key.)"));
|
|
6134
6233
|
console.log();
|
|
6135
6234
|
return;
|
|
6136
6235
|
}
|
|
6137
|
-
console.log("\n" +
|
|
6236
|
+
console.log("\n" + import_chalk39.default.yellow(` ${upsell.message || "This turn is not available right now."}`));
|
|
6138
6237
|
if (upsell.reason === "INSUFFICIENT" || upsell.reason === "BREAKER" || upsell.reason === "CAPPED" || upsell.reason === "PAUSED") {
|
|
6139
6238
|
if (!loggedIn) {
|
|
6140
|
-
console.log(
|
|
6239
|
+
console.log(import_chalk39.default.dim(" Options: `/login` for more free chats and requests, or `apiblaze llm set-key` to bring your own model key."));
|
|
6141
6240
|
} else {
|
|
6142
|
-
console.log(
|
|
6241
|
+
console.log(import_chalk39.default.dim(" Options: top up your wallet, or `apiblaze llm set-key` to bring your own model key (bypasses platform limits)."));
|
|
6143
6242
|
}
|
|
6144
6243
|
} else if (upsell.reason === "INFLIGHT") {
|
|
6145
|
-
console.log(
|
|
6244
|
+
console.log(import_chalk39.default.dim(" Another turn is still in flight \u2014 wait a moment and try again."));
|
|
6146
6245
|
}
|
|
6147
6246
|
console.log();
|
|
6148
6247
|
}
|
|
@@ -6211,9 +6310,9 @@ async function openServerProxy(project) {
|
|
|
6211
6310
|
const prior = loadApichats().find((a) => a.projectId === project.projectId && a.dpKey);
|
|
6212
6311
|
let dpKey = prior?.dpKey;
|
|
6213
6312
|
if (!dpKey) {
|
|
6214
|
-
console.log(
|
|
6313
|
+
console.log(import_chalk39.default.dim(` Minting an API key for tenant ${import_chalk39.default.bold(tenant2)} to query project ${import_chalk39.default.bold(project.projectName)}\u2026`));
|
|
6215
6314
|
dpKey = await mintDurableProxyKey(project.teamId, tenant2);
|
|
6216
|
-
console.log(` ${
|
|
6315
|
+
console.log(` ${import_chalk39.default.green("\u2714")} API key: ${import_chalk39.default.dim(maskKey(dpKey))}`);
|
|
6217
6316
|
}
|
|
6218
6317
|
const p = {
|
|
6219
6318
|
projectId: project.projectId,
|
|
@@ -6226,7 +6325,7 @@ async function openServerProxy(project) {
|
|
|
6226
6325
|
// Reusing an owned proxy: logged-in apichat doors default to invite-only.
|
|
6227
6326
|
access: "invite"
|
|
6228
6327
|
};
|
|
6229
|
-
const spinner = (0,
|
|
6328
|
+
const spinner = (0, import_ora21.default)("Preparing the chat\u2026").start();
|
|
6230
6329
|
try {
|
|
6231
6330
|
const raw = await admin({
|
|
6232
6331
|
method: "GET",
|
|
@@ -6238,7 +6337,7 @@ async function openServerProxy(project) {
|
|
|
6238
6337
|
if (spec2 && (spec2.paths || spec2.openapi)) {
|
|
6239
6338
|
await publishMcp(p, spec2);
|
|
6240
6339
|
} else {
|
|
6241
|
-
console.log(
|
|
6340
|
+
console.log(import_chalk39.default.yellow(" This proxy has no OpenAPI spec yet \u2014 chat will have no tools. Build one with `apiblaze agent openapi`."));
|
|
6242
6341
|
}
|
|
6243
6342
|
} catch (err) {
|
|
6244
6343
|
spinner.fail("Could not open the proxy.");
|
|
@@ -6291,7 +6390,7 @@ async function noArgsMenu(opts) {
|
|
|
6291
6390
|
const me = loadCredentials()?.apiblazeUserId;
|
|
6292
6391
|
const saved = loadApichats().filter((a) => a.anon ? true : a.ownerUserId !== void 0 && a.ownerUserId === me);
|
|
6293
6392
|
const choices = saved.map((a) => ({
|
|
6294
|
-
name: `Chat with ${
|
|
6393
|
+
name: `Chat with ${import_chalk39.default.bold(a.name)} ${import_chalk39.default.dim(`(${a.target})${a.messages && a.messages.length ? ` \xB7 ${a.messages.length} msgs` : ""}`)}`,
|
|
6295
6394
|
value: { type: "existing", a }
|
|
6296
6395
|
}));
|
|
6297
6396
|
const creds = loadCredentials();
|
|
@@ -6301,14 +6400,14 @@ async function noArgsMenu(opts) {
|
|
|
6301
6400
|
const proxies = (await getProjects(creds.teamId)).filter((pr) => !savedIds.has(pr.projectId));
|
|
6302
6401
|
for (const pr of proxies) {
|
|
6303
6402
|
choices.push({
|
|
6304
|
-
name: `Chat with ${
|
|
6403
|
+
name: `Chat with ${import_chalk39.default.bold(pr.projectName)} ${import_chalk39.default.dim(`(v${pr.apiVersion}) \xB7 your proxy`)}`,
|
|
6305
6404
|
value: { type: "server", project: pr }
|
|
6306
6405
|
});
|
|
6307
6406
|
}
|
|
6308
6407
|
} catch {
|
|
6309
6408
|
}
|
|
6310
6409
|
}
|
|
6311
|
-
choices.push({ name:
|
|
6410
|
+
choices.push({ name: import_chalk39.default.green("\uFF0B Create a new apichat"), value: { type: "new" } });
|
|
6312
6411
|
const { pick: pick2 } = await inquirer3.prompt([
|
|
6313
6412
|
{ type: "list", name: "pick", message: "What would you like to do?", choices }
|
|
6314
6413
|
]);
|
|
@@ -6382,36 +6481,36 @@ async function noArgsMenu(opts) {
|
|
|
6382
6481
|
async function runRepl(p, initialMessages) {
|
|
6383
6482
|
const { default: inquirer3 } = await import("inquirer");
|
|
6384
6483
|
const messages = initialMessages && initialMessages.length ? initialMessages.slice() : [];
|
|
6385
|
-
console.log("\n" +
|
|
6386
|
-
if (messages.length) console.log(
|
|
6484
|
+
console.log("\n" + import_chalk39.default.cyan.bold("Chat with your API") + import_chalk39.default.dim(` \xB7 ${p.mcpHost}`));
|
|
6485
|
+
if (messages.length) console.log(import_chalk39.default.dim(` Resumed \u2014 ${messages.length} prior messages.`));
|
|
6387
6486
|
const llm2 = loadLlmConfig();
|
|
6388
6487
|
console.log(
|
|
6389
|
-
|
|
6488
|
+
import_chalk39.default.dim(
|
|
6390
6489
|
llm2 ? `Using your local ${llm2.provider} key for the model. Type a question, or /exit. /login /claim manage your workspace.` : "Ask a question in plain English. /exit to quit \xB7 /login for more free chats \xB7 /claim to keep this workspace \xB7 `apiblaze llm set-key` for BYO models."
|
|
6391
6490
|
)
|
|
6392
6491
|
);
|
|
6393
6492
|
for (; ; ) {
|
|
6394
|
-
const { input } = await inquirer3.prompt([{ type: "input", name: "input", message:
|
|
6493
|
+
const { input } = await inquirer3.prompt([{ type: "input", name: "input", message: import_chalk39.default.green("you \u203A") }]);
|
|
6395
6494
|
const text = (input ?? "").trim();
|
|
6396
6495
|
if (!text) continue;
|
|
6397
6496
|
if (["/exit", "/quit", "exit", "quit", ":q"].includes(text.toLowerCase())) break;
|
|
6398
6497
|
if (text === "/login") {
|
|
6399
6498
|
try {
|
|
6400
6499
|
await runLogin();
|
|
6401
|
-
console.log(
|
|
6500
|
+
console.log(import_chalk39.default.dim(" Logged in \u2014 history preserved. Keep chatting."));
|
|
6402
6501
|
} catch (err) {
|
|
6403
|
-
console.log(
|
|
6502
|
+
console.log(import_chalk39.default.red(` Login failed: ${err instanceof Error ? err.message : String(err)}`));
|
|
6404
6503
|
}
|
|
6405
6504
|
continue;
|
|
6406
6505
|
}
|
|
6407
6506
|
if (text === "/claim") {
|
|
6408
6507
|
const justLoggedIn = !loadCredentials();
|
|
6409
6508
|
if (justLoggedIn) {
|
|
6410
|
-
console.log(
|
|
6509
|
+
console.log(import_chalk39.default.dim(" Logging in to claim your workspace\u2026"));
|
|
6411
6510
|
try {
|
|
6412
6511
|
await runLogin();
|
|
6413
6512
|
} catch (err) {
|
|
6414
|
-
console.log(
|
|
6513
|
+
console.log(import_chalk39.default.red(` Login failed: ${err instanceof Error ? err.message : String(err)}`));
|
|
6415
6514
|
continue;
|
|
6416
6515
|
}
|
|
6417
6516
|
if (!loadCredentials()) continue;
|
|
@@ -6422,30 +6521,30 @@ async function runRepl(p, initialMessages) {
|
|
|
6422
6521
|
p.mcpHost = p.mcpHost.replace(".mcp.tryabz.run", ".mcp.abz.run");
|
|
6423
6522
|
p.anon = false;
|
|
6424
6523
|
claimApichat(p, loadCredentials()?.apiblazeUserId);
|
|
6425
|
-
console.log(
|
|
6524
|
+
console.log(import_chalk39.default.dim(` Workspace claimed \u2014 chat now routes on ${p.mcpHost}. History preserved.`));
|
|
6426
6525
|
}
|
|
6427
6526
|
} catch (err) {
|
|
6428
|
-
console.log(
|
|
6527
|
+
console.log(import_chalk39.default.red(` Claim failed: ${err instanceof Error ? err.message : String(err)}`));
|
|
6429
6528
|
}
|
|
6430
6529
|
continue;
|
|
6431
6530
|
}
|
|
6432
6531
|
if (text === "/showauth") {
|
|
6433
6532
|
revealAuth = !revealAuth;
|
|
6434
|
-
console.log(
|
|
6533
|
+
console.log(import_chalk39.default.dim(revealAuth ? " The real API key will be shown in the next call's curl. /showauth again to re-mask." : " API key re-masked."));
|
|
6435
6534
|
continue;
|
|
6436
6535
|
}
|
|
6437
6536
|
if (text.startsWith("/")) {
|
|
6438
|
-
console.log(
|
|
6537
|
+
console.log(import_chalk39.default.dim(" Commands: /login /claim /showauth /exit"));
|
|
6439
6538
|
continue;
|
|
6440
6539
|
}
|
|
6441
6540
|
await replTurn(p, messages, text);
|
|
6442
6541
|
saveTranscript(p, messages);
|
|
6443
6542
|
}
|
|
6444
|
-
console.log(
|
|
6543
|
+
console.log(import_chalk39.default.dim("\nBye."));
|
|
6445
6544
|
}
|
|
6446
6545
|
async function runApichat(opts) {
|
|
6447
6546
|
setVerbose(opts.verbose !== false);
|
|
6448
|
-
console.log(
|
|
6547
|
+
console.log(import_chalk39.default.bold("\napichat \u2014 turn any API into a chat\n"));
|
|
6449
6548
|
if (!opts.openapispec && !opts.target) {
|
|
6450
6549
|
if (!process.stdin.isTTY) {
|
|
6451
6550
|
fail4("No spec source. Pass --openapispec <file|url> or --target <url>.", GENERATOR_HINT);
|
|
@@ -6458,7 +6557,7 @@ async function runApichat(opts) {
|
|
|
6458
6557
|
}
|
|
6459
6558
|
const { spec: spec2, sourceUrl } = await loadSpec(opts);
|
|
6460
6559
|
const target = resolveTarget(spec2, opts, sourceUrl);
|
|
6461
|
-
console.log(` ${
|
|
6560
|
+
console.log(` ${import_chalk39.default.dim("Target:")} ${import_chalk39.default.bold(target)}`);
|
|
6462
6561
|
const auth = await resolveTargetAuth(spec2, opts);
|
|
6463
6562
|
if (auth && !process.stdin.isTTY && !opts.targetAuthEnv) {
|
|
6464
6563
|
fail4(
|
|
@@ -6467,7 +6566,7 @@ async function runApichat(opts) {
|
|
|
6467
6566
|
);
|
|
6468
6567
|
}
|
|
6469
6568
|
const p = await provision(spec2, target, opts);
|
|
6470
|
-
console.log(` ${
|
|
6569
|
+
console.log(` ${import_chalk39.default.dim("Proxy: ")} ${import_chalk39.default.bold(p.proxyUrl || `${p.projectId} v${p.version}`)}`);
|
|
6471
6570
|
upsertApichat({
|
|
6472
6571
|
name: p.projectId,
|
|
6473
6572
|
target,
|
|
@@ -6486,31 +6585,31 @@ async function runApichat(opts) {
|
|
|
6486
6585
|
const secret = await captureTargetSecret(auth, opts);
|
|
6487
6586
|
if (secret) await writeTargetAuth(p, auth, secret);
|
|
6488
6587
|
} else {
|
|
6489
|
-
console.log(
|
|
6588
|
+
console.log(import_chalk39.default.dim(" Target auth: none required."));
|
|
6490
6589
|
}
|
|
6491
6590
|
const specText = JSON.stringify(spec2);
|
|
6492
6591
|
await uploadSpec(p, specText, opts);
|
|
6493
6592
|
const mcpUrl = await publishMcp(p, spec2);
|
|
6494
6593
|
console.log();
|
|
6495
|
-
if (p.proxyUrl) console.log(` ${
|
|
6594
|
+
if (p.proxyUrl) console.log(` ${import_chalk39.default.green("\u2713")} proxy ${import_chalk39.default.bold(p.proxyUrl)}`);
|
|
6496
6595
|
if (mcpUrl) {
|
|
6497
|
-
console.log(` ${
|
|
6596
|
+
console.log(` ${import_chalk39.default.green("\u2713")} mcp ${import_chalk39.default.bold(mcpUrl)}`);
|
|
6498
6597
|
if (p.access === "invite") {
|
|
6499
|
-
console.log(
|
|
6500
|
-
console.log(
|
|
6598
|
+
console.log(import_chalk39.default.dim(" Claude/ChatGPT-connectable (GitHub sign-in) \xB7 access: invite \u2014 only you + emails you pre-approve"));
|
|
6599
|
+
console.log(import_chalk39.default.dim(` Let others in: apiblaze preapprove someone@company.com${p.tenant ? ` --tenant ${p.tenant}` : ""} (or re-run with --access open)`));
|
|
6501
6600
|
} else {
|
|
6502
|
-
console.log(
|
|
6601
|
+
console.log(import_chalk39.default.dim(" Claude/ChatGPT-connectable (GitHub sign-in) \xB7 access: open \u2014 anyone who signs in can call this API"));
|
|
6503
6602
|
}
|
|
6504
6603
|
}
|
|
6505
6604
|
if (p.anon) {
|
|
6506
|
-
console.log(
|
|
6605
|
+
console.log(import_chalk39.default.dim("\n Anonymous workspace \u2014 /claim inside the chat to log in and keep it beyond 30 days."));
|
|
6507
6606
|
}
|
|
6508
6607
|
await runRepl(p);
|
|
6509
6608
|
}
|
|
6510
6609
|
|
|
6511
6610
|
// src/commands/consumer.ts
|
|
6512
|
-
var
|
|
6513
|
-
var
|
|
6611
|
+
var import_chalk40 = __toESM(require("chalk"));
|
|
6612
|
+
var import_ora22 = __toESM(require("ora"));
|
|
6514
6613
|
init_admin();
|
|
6515
6614
|
init_resolve();
|
|
6516
6615
|
var DEFAULT_SCOPE = "openid email profile offline_access";
|
|
@@ -6531,7 +6630,7 @@ async function consumerFetch(creds, suffix, init) {
|
|
|
6531
6630
|
function requireConsumer() {
|
|
6532
6631
|
const c = loadConsumer();
|
|
6533
6632
|
if (!c) {
|
|
6534
|
-
console.error(
|
|
6633
|
+
console.error(import_chalk40.default.red("Not logged in as a consumer. Run `apiblaze consumer login` first."));
|
|
6535
6634
|
process.exit(1);
|
|
6536
6635
|
}
|
|
6537
6636
|
return c;
|
|
@@ -6542,7 +6641,7 @@ async function runConsumerLogin(opts) {
|
|
|
6542
6641
|
let clientId = opts.client;
|
|
6543
6642
|
if (clientId) {
|
|
6544
6643
|
if (!tenant2) {
|
|
6545
|
-
console.error(
|
|
6644
|
+
console.error(import_chalk40.default.red("When using --client, also pass --tenant <slug> (it sets which portal/keys host to use)."));
|
|
6546
6645
|
process.exit(1);
|
|
6547
6646
|
}
|
|
6548
6647
|
} else {
|
|
@@ -6554,25 +6653,25 @@ async function runConsumerLogin(opts) {
|
|
|
6554
6653
|
if (!picked) process.exit(1);
|
|
6555
6654
|
tenant2 = picked;
|
|
6556
6655
|
}
|
|
6557
|
-
const s2 = (0,
|
|
6656
|
+
const s2 = (0, import_ora22.default)("Finding the login app...").start();
|
|
6558
6657
|
const clients = await admin({ method: "GET", path: `/teams/${encodeURIComponent(teamId)}/tenants/${encodeURIComponent(tenant2)}/app-clients`, summary: `List app clients for ${tenant2}` }).catch(() => []);
|
|
6559
6658
|
s2.stop();
|
|
6560
6659
|
const usable = (Array.isArray(clients) ? clients : []).filter((c) => c && (c.client_id || c.clientId));
|
|
6561
6660
|
const pick2 = usable.find((c) => c.is_default || c.default) ?? usable.find((c) => c.verified !== false) ?? usable[0];
|
|
6562
6661
|
if (!pick2) {
|
|
6563
|
-
console.error(
|
|
6662
|
+
console.error(import_chalk40.default.red(`Tenant "${tenant2}" has no login app configured. Set one up in the dashboard (or \`apiblaze create\` with auth).`));
|
|
6564
6663
|
process.exit(1);
|
|
6565
6664
|
}
|
|
6566
6665
|
clientId = pick2.client_id ?? pick2.clientId;
|
|
6567
6666
|
}
|
|
6568
6667
|
const portalResource = `https://${tenant2}.portal.apiblaze.com/1.0.0`;
|
|
6569
|
-
console.log(`${
|
|
6668
|
+
console.log(`${import_chalk40.default.cyan("\u2192")} Logging in to ${import_chalk40.default.bold(tenant2)} as a consumer...`);
|
|
6570
6669
|
const result = await deviceLogin(clientId, DEFAULT_SCOPE, ({ verificationUri, userCode }) => {
|
|
6571
6670
|
console.log(`
|
|
6572
|
-
Open: ${
|
|
6573
|
-
console.log(` Code: ${
|
|
6671
|
+
Open: ${import_chalk40.default.underline(verificationUri)}`);
|
|
6672
|
+
console.log(` Code: ${import_chalk40.default.bold(userCode)}
|
|
6574
6673
|
`);
|
|
6575
|
-
console.log(
|
|
6674
|
+
console.log(import_chalk40.default.dim(" (opening your browser\u2026 waiting for you to finish)"));
|
|
6576
6675
|
}, portalResource);
|
|
6577
6676
|
const claims = result.idToken && decodeJwt2(result.idToken) || (decodeJwt2(result.accessToken) ?? {});
|
|
6578
6677
|
const creds = {
|
|
@@ -6587,7 +6686,7 @@ async function runConsumerLogin(opts) {
|
|
|
6587
6686
|
obtainedAt: Date.now()
|
|
6588
6687
|
};
|
|
6589
6688
|
saveConsumer(creds);
|
|
6590
|
-
console.log(
|
|
6689
|
+
console.log(import_chalk40.default.green(`\u2714 Logged in as consumer${creds.email ? ` ${creds.email}` : ""} on ${tenant2}.`));
|
|
6591
6690
|
}
|
|
6592
6691
|
async function runConsumerTokens(opts) {
|
|
6593
6692
|
const creds = requireConsumer();
|
|
@@ -6600,29 +6699,29 @@ async function runConsumerTokens(opts) {
|
|
|
6600
6699
|
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));
|
|
6601
6700
|
return;
|
|
6602
6701
|
}
|
|
6603
|
-
console.log(`${
|
|
6702
|
+
console.log(`${import_chalk40.default.cyan("Consumer")} ${import_chalk40.default.bold(fresh.email ?? fresh.tenant)} on ${import_chalk40.default.bold(fresh.tenant)}
|
|
6604
6703
|
`);
|
|
6605
|
-
console.log(`${
|
|
6704
|
+
console.log(`${import_chalk40.default.bold("access_token")} ${import_chalk40.default.dim("exp " + (exp(fresh.accessToken) ?? "?"))}
|
|
6606
6705
|
${fresh.accessToken}
|
|
6607
6706
|
`);
|
|
6608
|
-
if (fresh.idToken) console.log(`${
|
|
6707
|
+
if (fresh.idToken) console.log(`${import_chalk40.default.bold("id_token")} ${import_chalk40.default.dim("exp " + (exp(fresh.idToken) ?? "?"))}
|
|
6609
6708
|
${fresh.idToken}
|
|
6610
6709
|
`);
|
|
6611
|
-
if (fresh.refreshToken) console.log(`${
|
|
6710
|
+
if (fresh.refreshToken) console.log(`${import_chalk40.default.bold("refresh_token")}
|
|
6612
6711
|
${fresh.refreshToken}
|
|
6613
6712
|
`);
|
|
6614
|
-
console.log(
|
|
6713
|
+
console.log(import_chalk40.default.dim("These are your own tokens \u2014 keep them secret."));
|
|
6615
6714
|
}
|
|
6616
6715
|
async function runConsumerApikeys(opts) {
|
|
6617
6716
|
const creds = requireConsumer();
|
|
6618
6717
|
const { default: inquirer3 } = await import("inquirer");
|
|
6619
|
-
const spinner = (0,
|
|
6718
|
+
const spinner = (0, import_ora22.default)("Loading your API keys...").start();
|
|
6620
6719
|
const list = await consumerFetch(creds, "/apikeys");
|
|
6621
6720
|
const revealed = await consumerFetch(list.creds, "/apikeys/reveal").catch(() => ({ status: 0, data: null, creds: list.creds }));
|
|
6622
6721
|
spinner.stop();
|
|
6623
6722
|
if (list.status >= 400) {
|
|
6624
|
-
console.error(
|
|
6625
|
-
if (list.status === 401) console.error(
|
|
6723
|
+
console.error(import_chalk40.default.red(`Failed to list keys (${list.status}): ${list.data?.error ?? ""}`));
|
|
6724
|
+
if (list.status === 401) console.error(import_chalk40.default.dim("Your consumer session may have expired \u2014 run `apiblaze consumer login` again."));
|
|
6626
6725
|
process.exit(1);
|
|
6627
6726
|
}
|
|
6628
6727
|
const keys = list.data?.keys ?? [];
|
|
@@ -6630,16 +6729,16 @@ async function runConsumerApikeys(opts) {
|
|
|
6630
6729
|
if (opts.json) {
|
|
6631
6730
|
console.log(JSON.stringify({ keys, revealed: revealMap }, null, 2));
|
|
6632
6731
|
} else if (!keys.length) {
|
|
6633
|
-
console.log(
|
|
6732
|
+
console.log(import_chalk40.default.yellow("No API keys yet."));
|
|
6634
6733
|
} else {
|
|
6635
6734
|
for (const k of keys) {
|
|
6636
6735
|
const clear = revealMap[k.environment]?.key;
|
|
6637
|
-
const shown = clear ?
|
|
6638
|
-
const exp = k.expires_at ?
|
|
6639
|
-
console.log(` ${
|
|
6736
|
+
const shown = clear ? import_chalk40.default.green(clear) : import_chalk40.default.dim(`${k.key_prefix ?? ""}\u2026${k.key_suffix ?? ""}`);
|
|
6737
|
+
const exp = k.expires_at ? import_chalk40.default.dim(`exp ${k.expires_at}`) : import_chalk40.default.dim("no expiry");
|
|
6738
|
+
console.log(` ${import_chalk40.default.bold(k.environment ?? "")} ${shown} ${exp} ${import_chalk40.default.dim(k.description ?? "")}`);
|
|
6640
6739
|
}
|
|
6641
6740
|
if (Object.keys(revealMap).length === 0 && keys.some((k) => !k.expires_at)) {
|
|
6642
|
-
console.log(
|
|
6741
|
+
console.log(import_chalk40.default.dim("\n(Only expiring keys can be shown in clear; non-expiring keys show a prefix only.)"));
|
|
6643
6742
|
}
|
|
6644
6743
|
}
|
|
6645
6744
|
if (opts.json) return;
|
|
@@ -6653,7 +6752,7 @@ async function runConsumerApikeys(opts) {
|
|
|
6653
6752
|
const body = { environment: answers.environment };
|
|
6654
6753
|
if (answers.description) body.description = answers.description;
|
|
6655
6754
|
if (answers.expiresDays) body.expires_in_seconds = Number(answers.expiresDays) * 86400;
|
|
6656
|
-
const s2 = (0,
|
|
6755
|
+
const s2 = (0, import_ora22.default)("Creating key...").start();
|
|
6657
6756
|
const created = await consumerFetch(list.creds, "/apikeys", { method: "POST", body: JSON.stringify(body) });
|
|
6658
6757
|
if (created.status >= 400) {
|
|
6659
6758
|
s2.fail(`Create failed (${created.status}): ${created.data?.error ?? ""}`);
|
|
@@ -6661,13 +6760,13 @@ async function runConsumerApikeys(opts) {
|
|
|
6661
6760
|
}
|
|
6662
6761
|
s2.succeed("Key created.");
|
|
6663
6762
|
const key = created.data?.key ?? created.data?.fullKey;
|
|
6664
|
-
if (key) console.log(` ${
|
|
6665
|
-
else console.log(
|
|
6763
|
+
if (key) console.log(` ${import_chalk40.default.green(key)} ${import_chalk40.default.dim("(shown once \u2014 store it now)")}`);
|
|
6764
|
+
else console.log(import_chalk40.default.dim(" Key created; run `apiblaze consumer apikeys` to reveal it if it expires."));
|
|
6666
6765
|
}
|
|
6667
6766
|
|
|
6668
6767
|
// src/commands/sidecar.ts
|
|
6669
|
-
var
|
|
6670
|
-
var
|
|
6768
|
+
var import_chalk41 = __toESM(require("chalk"));
|
|
6769
|
+
var import_ora23 = __toESM(require("ora"));
|
|
6671
6770
|
var fs10 = __toESM(require("fs"));
|
|
6672
6771
|
var path7 = __toESM(require("path"));
|
|
6673
6772
|
init_admin();
|
|
@@ -6709,18 +6808,18 @@ function upsertEnvLocal(root, token) {
|
|
|
6709
6808
|
}
|
|
6710
6809
|
function installSidecarPackage(root) {
|
|
6711
6810
|
if (fs10.existsSync(path7.join(root, "node_modules", "apiblaze", "package.json"))) {
|
|
6712
|
-
console.log(` ${
|
|
6811
|
+
console.log(` ${import_chalk41.default.green("\u2713")} apiblaze package already installed`);
|
|
6713
6812
|
return;
|
|
6714
6813
|
}
|
|
6715
6814
|
const has = (f) => fs10.existsSync(path7.join(root, f));
|
|
6716
6815
|
const pm = has("bun.lockb") || has("bun.lock") ? { cmd: "bun", add: "add" } : has("pnpm-lock.yaml") ? { cmd: "pnpm", add: "add" } : has("yarn.lock") ? { cmd: "yarn", add: "add" } : { cmd: "npm", add: "install" };
|
|
6717
|
-
const spinner = (0,
|
|
6816
|
+
const spinner = (0, import_ora23.default)(`Installing the apiblaze package (${pm.cmd})\u2026`).start();
|
|
6718
6817
|
try {
|
|
6719
6818
|
const { execSync } = require("child_process");
|
|
6720
6819
|
execSync(`${pm.cmd} ${pm.add} apiblaze`, { cwd: root, stdio: "ignore" });
|
|
6721
6820
|
spinner.succeed("Installed apiblaze (the sidecar runtime).");
|
|
6722
6821
|
} catch {
|
|
6723
|
-
spinner.warn(`Couldn't auto-install \u2014 run ${
|
|
6822
|
+
spinner.warn(`Couldn't auto-install \u2014 run ${import_chalk41.default.cyan(`${pm.cmd} ${pm.add} apiblaze`)} yourself before ${import_chalk41.default.cyan("npm run dev")}.`);
|
|
6724
6823
|
}
|
|
6725
6824
|
}
|
|
6726
6825
|
function readEnvKey(root) {
|
|
@@ -6845,8 +6944,8 @@ function generateInspector(root, router) {
|
|
|
6845
6944
|
fs10.writeFileSync(f2, INSPECTOR_PAGE);
|
|
6846
6945
|
return path7.relative(root, f2);
|
|
6847
6946
|
}
|
|
6848
|
-
const
|
|
6849
|
-
const dir = path7.join(
|
|
6947
|
+
const base2 = fs10.existsSync(path7.join(root, "src", "app")) ? path7.join(root, "src", "app") : path7.join(root, "app");
|
|
6948
|
+
const dir = path7.join(base2, "abz-inspector");
|
|
6850
6949
|
fs10.mkdirSync(dir, { recursive: true });
|
|
6851
6950
|
const f = path7.join(dir, "page.tsx");
|
|
6852
6951
|
fs10.writeFileSync(f, INSPECTOR_PAGE);
|
|
@@ -6859,7 +6958,7 @@ async function runAnonymousInit(root, router, opts) {
|
|
|
6859
6958
|
const { sidecarInitAnonymous: sidecarInitAnonymous2 } = await Promise.resolve().then(() => (init_api(), api_exports));
|
|
6860
6959
|
const { saveAnonCred: saveAnonCred2, clearAnonCred: clearAnonCred2 } = await Promise.resolve().then(() => (init_anon_cred(), anon_cred_exports));
|
|
6861
6960
|
if (opts.newSession) clearAnonCred2();
|
|
6862
|
-
const spinner = (0,
|
|
6961
|
+
const spinner = (0, import_ora23.default)("Setting up a sidecar (no login needed)...").start();
|
|
6863
6962
|
let out;
|
|
6864
6963
|
try {
|
|
6865
6964
|
out = await sidecarInitAnonymous2();
|
|
@@ -6871,29 +6970,29 @@ async function runAnonymousInit(root, router, opts) {
|
|
|
6871
6970
|
if (out.cp_key && out.team_id) saveAnonCred2(out.cp_key, out.team_id, out.claim_code);
|
|
6872
6971
|
const envState = upsertEnvLocal(root, out.token);
|
|
6873
6972
|
ensureGitignored(root);
|
|
6874
|
-
console.log(` ${
|
|
6875
|
-
console.log(` ${
|
|
6973
|
+
console.log(` ${import_chalk41.default.green("\u2713")} .env.local ${envState} (APIBLAZE_API_KEY) \u2014 gitignored`);
|
|
6974
|
+
console.log(` ${import_chalk41.default.green("\u2713")} instrumentation.ts ${wireInstrumentation(root)}`);
|
|
6876
6975
|
installSidecarPackage(root);
|
|
6877
6976
|
let inspectorPath = null;
|
|
6878
6977
|
if (!opts.noInspector) {
|
|
6879
6978
|
inspectorPath = generateInspector(root, router);
|
|
6880
|
-
if (inspectorPath) console.log(` ${
|
|
6979
|
+
if (inspectorPath) console.log(` ${import_chalk41.default.green("\u2713")} inspector at ${inspectorPath}`);
|
|
6881
6980
|
}
|
|
6882
6981
|
console.log("");
|
|
6883
|
-
console.log(
|
|
6884
|
-
console.log(` 1. ${
|
|
6982
|
+
console.log(import_chalk41.default.bold("Done (no account needed). What happens next:"));
|
|
6983
|
+
console.log(` 1. ${import_chalk41.default.cyan("npm run dev")} and use your app.`);
|
|
6885
6984
|
console.log(` 2. Each external origin your app calls is logged in the console \u2014 approve one with:`);
|
|
6886
|
-
console.log(` ${
|
|
6985
|
+
console.log(` ${import_chalk41.default.cyan("apiblaze sidecar approve api.stripe.com")} (no login needed)`);
|
|
6887
6986
|
console.log("");
|
|
6888
|
-
console.log(
|
|
6889
|
-
console.log(` ${
|
|
6890
|
-
console.log(
|
|
6987
|
+
console.log(import_chalk41.default.bold(" \u{1F511} Keep your setup \u2014 claim it into an account:"));
|
|
6988
|
+
console.log(` ${import_chalk41.default.cyan("apiblaze login")} then ${import_chalk41.default.cyan("apiblaze claim")} ${import_chalk41.default.dim("(no code needed here)")}`);
|
|
6989
|
+
console.log(import_chalk41.default.dim(` From another machine: apiblaze claim ${out.claim_code} \xB7 expires in 30 days`));
|
|
6891
6990
|
}
|
|
6892
6991
|
async function runSidecar(opts) {
|
|
6893
6992
|
const root = path7.resolve(opts.dir ?? process.cwd());
|
|
6894
6993
|
const detected = detectNextProject(root);
|
|
6895
6994
|
if (!detected.found) {
|
|
6896
|
-
console.log(
|
|
6995
|
+
console.log(import_chalk41.default.yellow(`No Next.js project detected in ${root}.`));
|
|
6897
6996
|
console.log("Create one (e.g. `npx create-next-app`) and re-run `apiblaze init` inside it.");
|
|
6898
6997
|
return;
|
|
6899
6998
|
}
|
|
@@ -6904,10 +7003,10 @@ async function runSidecar(opts) {
|
|
|
6904
7003
|
if (!loadCredentials()) {
|
|
6905
7004
|
upsertEnvLocal(root, readEnvKey(root));
|
|
6906
7005
|
ensureGitignored(root);
|
|
6907
|
-
console.log(` ${
|
|
6908
|
-
console.log(` ${
|
|
7006
|
+
console.log(` ${import_chalk41.default.green("\u2713")} .env.local present (APIBLAZE_API_KEY) \u2014 reusing`);
|
|
7007
|
+
console.log(` ${import_chalk41.default.green("\u2713")} instrumentation.ts ${wireInstrumentation(root)}`);
|
|
6909
7008
|
installSidecarPackage(root);
|
|
6910
|
-
console.log(
|
|
7009
|
+
console.log(import_chalk41.default.dim(" Log in and run `apiblaze claim <code>` to keep this setup, or `apiblaze login` to manage it."));
|
|
6911
7010
|
return;
|
|
6912
7011
|
}
|
|
6913
7012
|
const { teamId, teamName } = await resolveTeam(opts.team);
|
|
@@ -6916,7 +7015,7 @@ async function runSidecar(opts) {
|
|
|
6916
7015
|
const mustMint = !existingKey || opts.rotate || switchingTeam;
|
|
6917
7016
|
let token = existingKey ?? "";
|
|
6918
7017
|
if (mustMint) {
|
|
6919
|
-
const spinner = (0,
|
|
7018
|
+
const spinner = (0, import_ora23.default)(existingKey ? "Re-establishing the sidecar (minting a fresh invoke key)..." : "Setting up the sidecar (tenant + non-expiring invoke key)...").start();
|
|
6920
7019
|
try {
|
|
6921
7020
|
const out = await admin({
|
|
6922
7021
|
method: "POST",
|
|
@@ -6930,39 +7029,39 @@ async function runSidecar(opts) {
|
|
|
6930
7029
|
throw err;
|
|
6931
7030
|
}
|
|
6932
7031
|
} else {
|
|
6933
|
-
console.log(
|
|
7032
|
+
console.log(import_chalk41.default.dim(` Reusing the existing APIBLAZE_API_KEY (run with --rotate to mint a fresh one, or --team <name> to switch teams).`));
|
|
6934
7033
|
}
|
|
6935
7034
|
const envState = upsertEnvLocal(root, token);
|
|
6936
7035
|
ensureGitignored(root);
|
|
6937
|
-
console.log(` ${
|
|
7036
|
+
console.log(` ${import_chalk41.default.green("\u2713")} .env.local ${envState} (APIBLAZE_API_KEY) \u2014 gitignored`);
|
|
6938
7037
|
const wireState = wireInstrumentation(root);
|
|
6939
|
-
console.log(` ${
|
|
7038
|
+
console.log(` ${import_chalk41.default.green("\u2713")} instrumentation.ts ${wireState}`);
|
|
6940
7039
|
installSidecarPackage(root);
|
|
6941
7040
|
let inspectorPath = null;
|
|
6942
7041
|
if (!opts.noInspector) {
|
|
6943
7042
|
inspectorPath = generateInspector(root, detected.router);
|
|
6944
|
-
if (inspectorPath) console.log(` ${
|
|
7043
|
+
if (inspectorPath) console.log(` ${import_chalk41.default.green("\u2713")} inspector at ${inspectorPath}`);
|
|
6945
7044
|
}
|
|
6946
7045
|
console.log("");
|
|
6947
|
-
console.log(
|
|
6948
|
-
console.log(` 1. ${
|
|
6949
|
-
console.log(` 2. The origins your app calls appear as ${
|
|
6950
|
-
console.log(` 3. Approve the ones to route: ${
|
|
7046
|
+
console.log(import_chalk41.default.bold("Done. What happens next:"));
|
|
7047
|
+
console.log(` 1. ${import_chalk41.default.cyan("npm run dev")} and use your app \u2014 it works exactly as before (all calls go direct).`);
|
|
7048
|
+
console.log(` 2. The origins your app calls appear as ${import_chalk41.default.bold("candidates")} \u2014 list them: ${import_chalk41.default.cyan("apiblaze sidecar")}`);
|
|
7049
|
+
console.log(` 3. Approve the ones to route: ${import_chalk41.default.cyan("apiblaze sidecar approve api.stripe.com")} (or in the dashboard)`);
|
|
6951
7050
|
console.log(` \u2026within ~5 min your app starts routing that origin through APIblaze.`);
|
|
6952
|
-
if (inspectorPath) console.log(` \u2022 Try it now: open ${
|
|
6953
|
-
if (switchingTeam) console.log(
|
|
7051
|
+
if (inspectorPath) console.log(` \u2022 Try it now: open ${import_chalk41.default.underline("http://localhost:3000/abz-inspector")} (dev only; rm ${path7.dirname(inspectorPath)} before shipping)`);
|
|
7052
|
+
if (switchingTeam) console.log(import_chalk41.default.dim(` \u2022 Approved origins are per-team \u2014 re-approve them on ${teamName ?? teamId} with \`apiblaze sidecar approve <origin>\`.`));
|
|
6954
7053
|
console.log("");
|
|
6955
|
-
console.log(
|
|
6956
|
-
console.log(
|
|
6957
|
-
console.log(
|
|
7054
|
+
console.log(import_chalk41.default.dim(" Manage: apiblaze sidecar (list/approve/deny/remove)"));
|
|
7055
|
+
console.log(import_chalk41.default.dim(" Rotate: apiblaze init --rotate \xB7 Switch team: apiblaze init --team <name>"));
|
|
7056
|
+
console.log(import_chalk41.default.dim(" Turn off: set APIBLAZE_SIDECAR=off in .env.local (flip back to on anytime; key stays put)."));
|
|
6958
7057
|
console.log("");
|
|
6959
|
-
console.log(
|
|
6960
|
-
console.log(
|
|
7058
|
+
console.log(import_chalk41.default.yellow(" \u26A0 APIBLAZE_API_KEY is long-lived and lets a holder call your team's proxies. Never commit it."));
|
|
7059
|
+
console.log(import_chalk41.default.dim(" Your control-plane login stays in ~/.apiblaze \u2014 it never entered this project."));
|
|
6961
7060
|
}
|
|
6962
7061
|
|
|
6963
7062
|
// src/commands/origins.ts
|
|
6964
|
-
var
|
|
6965
|
-
var
|
|
7063
|
+
var import_chalk42 = __toESM(require("chalk"));
|
|
7064
|
+
var import_ora24 = __toESM(require("ora"));
|
|
6966
7065
|
init_admin();
|
|
6967
7066
|
init_resolve();
|
|
6968
7067
|
init_auth();
|
|
@@ -6972,7 +7071,7 @@ async function runOriginsList(opts) {
|
|
|
6972
7071
|
if (!loadCredentials()) {
|
|
6973
7072
|
const cred = loadAnonCred();
|
|
6974
7073
|
if (!cred) {
|
|
6975
|
-
console.log(
|
|
7074
|
+
console.log(import_chalk42.default.yellow("No anonymous workspace here. Run `apiblaze init` first."));
|
|
6976
7075
|
return;
|
|
6977
7076
|
}
|
|
6978
7077
|
out = await cpFetch(cred.cp_key, `/teams/${encodeURIComponent(cred.team_id)}/sidecar/candidates`, { method: "GET" });
|
|
@@ -6990,30 +7089,30 @@ async function runOriginsList(opts) {
|
|
|
6990
7089
|
}
|
|
6991
7090
|
const routed = out.routed ?? [];
|
|
6992
7091
|
const candidates = out.candidates ?? [];
|
|
6993
|
-
console.log(
|
|
7092
|
+
console.log(import_chalk42.default.bold(`
|
|
6994
7093
|
Routed through APIblaze (${routed.length})`));
|
|
6995
|
-
if (!routed.length) console.log(
|
|
6996
|
-
for (const r of routed) console.log(` ${
|
|
6997
|
-
console.log(
|
|
7094
|
+
if (!routed.length) console.log(import_chalk42.default.dim(" none yet"));
|
|
7095
|
+
for (const r of routed) console.log(` ${import_chalk42.default.green("\u25CF")} ${r.sidecar_origin} ${import_chalk42.default.dim(`\u2192 ${r.project_id}`)}`);
|
|
7096
|
+
console.log(import_chalk42.default.bold(`
|
|
6998
7097
|
Candidates \u2014 going direct, not yet approved (${candidates.length})`));
|
|
6999
|
-
if (!candidates.length) console.log(
|
|
7098
|
+
if (!candidates.length) console.log(import_chalk42.default.dim(" none \u2014 run your app to discover the origins it calls"));
|
|
7000
7099
|
for (const c of candidates) {
|
|
7001
|
-
console.log(` ${
|
|
7100
|
+
console.log(` ${import_chalk42.default.yellow("\u25CB")} ${c.origin} ${import_chalk42.default.dim(`seen ${c.request_count}\xD7, last ${c.last_seen}`)}`);
|
|
7002
7101
|
}
|
|
7003
7102
|
if (candidates.length) {
|
|
7004
|
-
console.log(
|
|
7103
|
+
console.log(import_chalk42.default.dim(`
|
|
7005
7104
|
Approve: apiblaze sidecar approve ${candidates[0].origin.replace("https://", "")}`));
|
|
7006
|
-
console.log(
|
|
7105
|
+
console.log(import_chalk42.default.dim(` Dismiss: apiblaze sidecar deny ${candidates[0].origin.replace("https://", "")}`));
|
|
7007
7106
|
}
|
|
7008
7107
|
}
|
|
7009
7108
|
async function runOriginsApprove(origin, opts) {
|
|
7010
7109
|
if (!loadCredentials()) {
|
|
7011
7110
|
const cred = loadAnonCred();
|
|
7012
7111
|
if (!cred) {
|
|
7013
|
-
console.error(
|
|
7112
|
+
console.error(import_chalk42.default.red("Not logged in and no anonymous workspace. Run `apiblaze init` first."));
|
|
7014
7113
|
process.exit(1);
|
|
7015
7114
|
}
|
|
7016
|
-
const spinner2 = (0,
|
|
7115
|
+
const spinner2 = (0, import_ora24.default)(`Approving ${origin} (anonymous)...`).start();
|
|
7017
7116
|
try {
|
|
7018
7117
|
const out = await cpFetch(cred.cp_key, `/teams/${encodeURIComponent(cred.team_id)}/sidecar/approve`, { method: "POST", body: JSON.stringify({ origin }) });
|
|
7019
7118
|
spinner2.succeed(`Approved ${origin} \u2192 proxy ${out.project_id}. Routing within ~5 min.`);
|
|
@@ -7024,7 +7123,7 @@ async function runOriginsApprove(origin, opts) {
|
|
|
7024
7123
|
return;
|
|
7025
7124
|
}
|
|
7026
7125
|
const { teamId } = await resolveTeam(opts.team);
|
|
7027
|
-
const spinner = (0,
|
|
7126
|
+
const spinner = (0, import_ora24.default)(`Approving ${origin}...`).start();
|
|
7028
7127
|
try {
|
|
7029
7128
|
const out = await admin({
|
|
7030
7129
|
method: "POST",
|
|
@@ -7041,7 +7140,7 @@ async function runOriginsApprove(origin, opts) {
|
|
|
7041
7140
|
}
|
|
7042
7141
|
async function runOriginsDeny(origin, opts) {
|
|
7043
7142
|
const { teamId } = await resolveTeam(opts.team);
|
|
7044
|
-
const spinner = (0,
|
|
7143
|
+
const spinner = (0, import_ora24.default)(`Dismissing ${origin}...`).start();
|
|
7045
7144
|
try {
|
|
7046
7145
|
await admin({ method: "POST", path: `/teams/${encodeURIComponent(teamId)}/sidecar/dismiss`, body: { origin }, summary: `Dismiss sidecar origin ${origin}` });
|
|
7047
7146
|
spinner.succeed(`Dismissed ${origin}. It won't be suggested again.`);
|
|
@@ -7052,7 +7151,7 @@ async function runOriginsDeny(origin, opts) {
|
|
|
7052
7151
|
}
|
|
7053
7152
|
async function runOriginsRemove(origin, opts) {
|
|
7054
7153
|
const { teamId } = await resolveTeam(opts.team);
|
|
7055
|
-
const spinner = (0,
|
|
7154
|
+
const spinner = (0, import_ora24.default)(`Removing the proxy for ${origin}...`).start();
|
|
7056
7155
|
try {
|
|
7057
7156
|
await admin({ method: "POST", path: `/teams/${encodeURIComponent(teamId)}/sidecar/remove`, body: { origin }, summary: `Un-route sidecar origin ${origin}` });
|
|
7058
7157
|
spinner.succeed(`Removed ${origin}. Your app will stop routing it (goes direct) within ~5 min.`);
|
|
@@ -7063,7 +7162,7 @@ async function runOriginsRemove(origin, opts) {
|
|
|
7063
7162
|
}
|
|
7064
7163
|
|
|
7065
7164
|
// src/commands/op.ts
|
|
7066
|
-
var
|
|
7165
|
+
var import_chalk43 = __toESM(require("chalk"));
|
|
7067
7166
|
init_auth();
|
|
7068
7167
|
init_trace();
|
|
7069
7168
|
init_types();
|
|
@@ -7096,82 +7195,82 @@ function printResidue(report, applied) {
|
|
|
7096
7195
|
const up = report?.upstash ?? {};
|
|
7097
7196
|
const fga = report?.fga ?? {};
|
|
7098
7197
|
const ghosts = report?.ghosts ?? {};
|
|
7099
|
-
console.log(
|
|
7100
|
-
console.log(
|
|
7198
|
+
console.log(import_chalk43.default.bold(applied ? "\nExternal-residue sweep" : "\nExternal residue (dry-run \u2014 nothing deleted)"));
|
|
7199
|
+
console.log(import_chalk43.default.bold("\n Upstash"));
|
|
7101
7200
|
const orphans = up.orphans ?? [];
|
|
7102
|
-
if (orphans.length === 0) console.log(
|
|
7103
|
-
for (const o of orphans) console.log(` ${
|
|
7104
|
-
console.log(
|
|
7201
|
+
if (orphans.length === 0) console.log(import_chalk43.default.green(" no orphaned keys"));
|
|
7202
|
+
for (const o of orphans) console.log(` ${import_chalk43.default.yellow(o.key)} ${import_chalk43.default.dim(`\u2014 ${o.reason}`)}`);
|
|
7203
|
+
console.log(import_chalk43.default.dim(` kept (live principals): ${up.kept ?? 0} \xB7 anon wallets (untouched): ${up.anon_wallets ?? 0}`));
|
|
7105
7204
|
if (up.anon_wallet_detail) {
|
|
7106
7205
|
const d = up.anon_wallet_detail;
|
|
7107
|
-
console.log(
|
|
7206
|
+
console.log(import_chalk43.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)"}`));
|
|
7108
7207
|
}
|
|
7109
7208
|
if (up.keyspace_census) {
|
|
7110
7209
|
const census = Object.entries(up.keyspace_census).map(([k, v]) => `${k}=${v}`).join(" \xB7 ");
|
|
7111
|
-
console.log(
|
|
7210
|
+
console.log(import_chalk43.default.dim(` keyspace: ${census}`));
|
|
7112
7211
|
}
|
|
7113
|
-
if (up.unknown?.length) console.log(
|
|
7114
|
-
if (applied) console.log(` ${
|
|
7115
|
-
for (const e of up.errors ?? []) console.log(
|
|
7116
|
-
console.log(
|
|
7212
|
+
if (up.unknown?.length) console.log(import_chalk43.default.dim(` unknown (never deleted): ${up.unknown.join(", ")}`));
|
|
7213
|
+
if (applied) console.log(` ${import_chalk43.default.bold(String(up.deleted ?? 0))} key(s) deleted`);
|
|
7214
|
+
for (const e of up.errors ?? []) console.log(import_chalk43.default.red(` error: ${e}`));
|
|
7215
|
+
console.log(import_chalk43.default.bold("\n OpenFGA / Neon \u2014 orphan stores"));
|
|
7117
7216
|
if (applied) {
|
|
7118
7217
|
const swept = fga?.swept ?? [];
|
|
7119
|
-
if (swept.length === 0) console.log(
|
|
7218
|
+
if (swept.length === 0) console.log(import_chalk43.default.green(" no orphaned stores"));
|
|
7120
7219
|
for (const s of swept) {
|
|
7121
7220
|
console.log(
|
|
7122
|
-
` ${
|
|
7221
|
+
` ${import_chalk43.default.yellow(s.store_id)} ${import_chalk43.default.dim(`\u2014 store ${s.openfga_deleted ? "deleted" : "DEFERRED"}, ${s.neon_deleted} Neon tuple(s) purged`)}`
|
|
7123
7222
|
);
|
|
7124
7223
|
}
|
|
7125
|
-
if (fga?.remaining) console.log(
|
|
7224
|
+
if (fga?.remaining) console.log(import_chalk43.default.yellow(` ${fga.remaining} more orphan store(s) \u2014 re-run to drain`));
|
|
7126
7225
|
const st = fga?.side_tables;
|
|
7127
|
-
if (st) console.log(
|
|
7226
|
+
if (st) console.log(import_chalk43.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})` : ""}`));
|
|
7128
7227
|
} else {
|
|
7129
7228
|
const fgaOrphans = fga?.orphans ?? [];
|
|
7130
|
-
if (fgaOrphans.length === 0) console.log(
|
|
7229
|
+
if (fgaOrphans.length === 0) console.log(import_chalk43.default.green(" no orphaned stores"));
|
|
7131
7230
|
for (const s of fgaOrphans) {
|
|
7132
7231
|
const src = s.in_openfga ? "live in OpenFGA" : "Neon tuples only";
|
|
7133
|
-
console.log(` ${
|
|
7232
|
+
console.log(` ${import_chalk43.default.yellow(s.store_id)} ${import_chalk43.default.dim(`\u2014 ${src}${s.name ? ` (${s.name})` : ""}, ${s.neon_tuples} Neon tuple(s)`)}`);
|
|
7134
7233
|
}
|
|
7135
|
-
console.log(
|
|
7234
|
+
console.log(import_chalk43.default.dim(` kept stores: ${(fga?.kept_store_ids ?? []).length}`));
|
|
7136
7235
|
const st = fga?.side_tables;
|
|
7137
|
-
if (st) console.log(
|
|
7236
|
+
if (st) console.log(import_chalk43.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`));
|
|
7138
7237
|
}
|
|
7139
|
-
for (const e of fga?.errors ?? []) console.log(
|
|
7140
|
-
console.log(
|
|
7238
|
+
for (const e of fga?.errors ?? []) console.log(import_chalk43.default.red(` error: ${e}`));
|
|
7239
|
+
console.log(import_chalk43.default.bold("\n OpenFGA \u2014 ghost tuples in surviving stores"));
|
|
7141
7240
|
if (applied) {
|
|
7142
|
-
if ((ghosts?.ghost_count ?? 0) === 0) console.log(
|
|
7143
|
-
else console.log(` ${
|
|
7241
|
+
if ((ghosts?.ghost_count ?? 0) === 0) console.log(import_chalk43.default.green(" no ghost tuples"));
|
|
7242
|
+
else console.log(` ${import_chalk43.default.bold(String(ghosts.deleted ?? 0))} ghost tuple(s) deleted ${import_chalk43.default.dim(`(of ${ghosts.ghost_count} found, ${ghosts.scanned_tuples} scanned across ${ghosts.live_stores} live stores)`)}`);
|
|
7144
7243
|
} else {
|
|
7145
7244
|
const n = ghosts?.ghost_count ?? 0;
|
|
7146
|
-
if (n === 0) console.log(
|
|
7245
|
+
if (n === 0) console.log(import_chalk43.default.green(` no ghost tuples ${import_chalk43.default.dim(`(${ghosts.scanned_tuples ?? 0} scanned across ${ghosts.live_stores ?? 0} live stores)`)}`));
|
|
7147
7246
|
else {
|
|
7148
|
-
console.log(
|
|
7247
|
+
console.log(import_chalk43.default.yellow(` ${n} ghost tuple(s) referencing entities absent from D1:`));
|
|
7149
7248
|
for (const g of (ghosts.ghosts ?? []).slice(0, 20)) {
|
|
7150
|
-
console.log(
|
|
7249
|
+
console.log(import_chalk43.default.dim(` ${g.object_type}:${g.object_id} ${g.relation} ${g._user}`));
|
|
7151
7250
|
}
|
|
7152
|
-
if (n > 20) console.log(
|
|
7251
|
+
if (n > 20) console.log(import_chalk43.default.dim(` \u2026 and ${n - 20} more`));
|
|
7153
7252
|
}
|
|
7154
7253
|
}
|
|
7155
|
-
for (const e of ghosts?.errors ?? []) console.log(
|
|
7254
|
+
for (const e of ghosts?.errors ?? []) console.log(import_chalk43.default.red(` error: ${e}`));
|
|
7156
7255
|
console.log();
|
|
7157
7256
|
}
|
|
7158
7257
|
async function runOp(sub, opts = {}) {
|
|
7159
7258
|
if (!loadCredentials()) {
|
|
7160
|
-
console.log(
|
|
7259
|
+
console.log(import_chalk43.default.dim("Not logged in. Run `apiblaze login`."));
|
|
7161
7260
|
return;
|
|
7162
7261
|
}
|
|
7163
7262
|
if (!isOperatorLogin()) {
|
|
7164
|
-
console.log(
|
|
7263
|
+
console.log(import_chalk43.default.dim("`apiblaze op` is only available to platform operators."));
|
|
7165
7264
|
return;
|
|
7166
7265
|
}
|
|
7167
7266
|
switch (sub) {
|
|
7168
7267
|
case void 0:
|
|
7169
7268
|
case "menu": {
|
|
7170
|
-
console.log(
|
|
7171
|
-
console.log(` ${
|
|
7172
|
-
console.log(` ${
|
|
7173
|
-
console.log(` ${
|
|
7174
|
-
console.log(
|
|
7269
|
+
console.log(import_chalk43.default.bold("\nOperator menu"));
|
|
7270
|
+
console.log(` ${import_chalk43.default.cyan("apiblaze op residue")} external-store residue report (Upstash + Neon/OpenFGA, dry-run)`);
|
|
7271
|
+
console.log(` ${import_chalk43.default.cyan("apiblaze op sweep")} delete the orphans the report shows (asks first; ${import_chalk43.default.dim("-y to skip")})`);
|
|
7272
|
+
console.log(` ${import_chalk43.default.cyan("apiblaze op credits")} list credit wallets`);
|
|
7273
|
+
console.log(import_chalk43.default.dim(` (to prune all non-CP data: run scripts/nuke-but-cp.sh --apply --sweep in the repo)
|
|
7175
7274
|
`));
|
|
7176
7275
|
return;
|
|
7177
7276
|
}
|
|
@@ -7190,17 +7289,17 @@ async function runOp(sub, opts = {}) {
|
|
|
7190
7289
|
const nSide = (st.soft_deleted_stores ?? 0) + (st.orphan_models ?? 0) + (st.orphan_changelog ?? 0);
|
|
7191
7290
|
printResidue(report, false);
|
|
7192
7291
|
if (nUp + nFga + nGhost + nSide === 0) {
|
|
7193
|
-
console.log(
|
|
7292
|
+
console.log(import_chalk43.default.green("Nothing to sweep."));
|
|
7194
7293
|
return;
|
|
7195
7294
|
}
|
|
7196
7295
|
if (!opts.yes) {
|
|
7197
7296
|
const readline2 = await import("readline/promises");
|
|
7198
7297
|
const rl = readline2.createInterface({ input: process.stdin, output: process.stdout });
|
|
7199
7298
|
const answer = await rl.question(
|
|
7200
|
-
|
|
7299
|
+
import_chalk43.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: `)
|
|
7201
7300
|
);
|
|
7202
7301
|
rl.close();
|
|
7203
|
-
if (answer.trim() !== "sweep") return void console.log(
|
|
7302
|
+
if (answer.trim() !== "sweep") return void console.log(import_chalk43.default.dim("Aborted."));
|
|
7204
7303
|
}
|
|
7205
7304
|
const result = await opCall({ method: "POST", path: "/operator/external-residue/sweep", summary: "external residue sweep" });
|
|
7206
7305
|
if (opts.json) return void console.log(JSON.stringify(result, null, 2));
|
|
@@ -7211,15 +7310,15 @@ async function runOp(sub, opts = {}) {
|
|
|
7211
7310
|
const data = await opCall({ method: "GET", path: "/operator/credits", summary: "list credit wallets" });
|
|
7212
7311
|
if (opts.json) return void console.log(JSON.stringify(data, null, 2));
|
|
7213
7312
|
const accounts = data?.accounts ?? [];
|
|
7214
|
-
if (accounts.length === 0) return void console.log(
|
|
7313
|
+
if (accounts.length === 0) return void console.log(import_chalk43.default.dim("No credit wallets."));
|
|
7215
7314
|
for (const a of accounts) {
|
|
7216
7315
|
const bal = typeof a.balance_cents === "number" ? `$${(a.balance_cents / 100).toFixed(2)}` : "?";
|
|
7217
|
-
console.log(` ${
|
|
7316
|
+
console.log(` ${import_chalk43.default.bold(bal.padStart(9))} ${a.walletId}${a.owner_email ? import_chalk43.default.dim(` \u2014 ${a.owner_email}`) : a.anon ? import_chalk43.default.dim(" \u2014 anon") : ""}`);
|
|
7218
7317
|
}
|
|
7219
7318
|
return;
|
|
7220
7319
|
}
|
|
7221
7320
|
default:
|
|
7222
|
-
console.log(
|
|
7321
|
+
console.log(import_chalk43.default.red(`Unknown op subcommand '${sub}'. Run \`apiblaze op\` for the menu.`));
|
|
7223
7322
|
}
|
|
7224
7323
|
}
|
|
7225
7324
|
|
|
@@ -7278,14 +7377,14 @@ withSetupOptions(sidecar.command("setup").description("Wire a Next.js app to rou
|
|
|
7278
7377
|
sidecar.command("approve").description("Route an origin through APIblaze (creates its proxy)").argument("<origin>", "Origin, e.g. api.stripe.com").option("--team <id|name>", "Team (defaults to active team)").option("--json", "Machine-readable output").action(action((origin, opts) => runOriginsApprove(origin, opts)));
|
|
7279
7378
|
sidecar.command("deny").description("Dismiss a candidate origin so it stops being suggested").argument("<origin>", "Origin, e.g. sentry.io").option("--team <id|name>", "Team (defaults to active team)").action(action((origin, opts) => runOriginsDeny(origin, opts)));
|
|
7280
7379
|
sidecar.command("remove").description("Un-route an approved origin (deletes its proxy; the app goes direct again)").argument("<origin>", "Origin, e.g. api.stripe.com").option("--team <id|name>", "Team (defaults to active team)").action(action((origin, opts) => runOriginsRemove(origin, opts)));
|
|
7281
|
-
program.command("dev").description("Put your localhost behind a public URL (dev tunnel)").argument("[port]", "Local port to tunnel (positional; overrides --port)").option("-p, --port <number>", "Local port to tunnel", "3000").option("-o, --capture-file <path>", "Stream full request/response traffic to a file (JSON lines)").action(async (port, opts) => {
|
|
7380
|
+
program.command("dev").description("Put your localhost behind a public URL (dev tunnel)").argument("[port]", "Local port to tunnel (positional; overrides --port)").option("-p, --port <number>", "Local port to tunnel", "3000").option("--project <nameOrId>", "Tunnel this specific project (skips the picker \u2014 for scripts)").option("-y, --yes", "Skip confirmation prompts (non-interactive)").option("-o, --capture-file <path>", "Stream full request/response traffic to a file (JSON lines)").action(async (port, opts) => {
|
|
7282
7381
|
try {
|
|
7283
7382
|
const resolved = parseInt(port ?? opts.port, 10);
|
|
7284
7383
|
if (Number.isNaN(resolved)) {
|
|
7285
|
-
console.error(
|
|
7384
|
+
console.error(import_chalk44.default.red(`Invalid port: ${port ?? opts.port}`));
|
|
7286
7385
|
process.exit(1);
|
|
7287
7386
|
}
|
|
7288
|
-
await runDev({ port: resolved, captureFile: opts.captureFile });
|
|
7387
|
+
await runDev({ port: resolved, project: opts.project, yes: opts.yes, captureFile: opts.captureFile });
|
|
7289
7388
|
} catch (err) {
|
|
7290
7389
|
await printError(err);
|
|
7291
7390
|
process.exit(1);
|
|
@@ -7360,6 +7459,11 @@ tenant.command("create").description("Create a tenant in your team (tenant names
|
|
|
7360
7459
|
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)));
|
|
7361
7460
|
tenant.command("delete").description("Delete a tenant (full cascade)").argument("<slug>", "Tenant slug to delete").option("--team <id|name>", "Team (defaults to active team)").option("-y, --yes", "Skip the confirmation prompt").action(action((slug, opts) => runTenantDelete(slug, opts)));
|
|
7362
7461
|
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)));
|
|
7462
|
+
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)));
|
|
7463
|
+
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 })));
|
|
7464
|
+
admins.command("add").description("Add an email as a tenant admin (e.g. yourself, to start)").argument("<email>", "Email to grant admin").option("--tenant <slug>", "Tenant slug").option("--team <id|name>", "Team").option("--json", "JSON").action(action((email, opts, cmd) => runAdminsAdd(email, { ...cmd.parent?.opts(), ...opts })));
|
|
7465
|
+
admins.command("remove").description("Remove a tenant admin").argument("<email>", "Email to remove").option("--tenant <slug>", "Tenant slug").option("--team <id|name>", "Team").option("--json", "JSON").action(action((email, opts, cmd) => runAdminsRemove(email, { ...cmd.parent?.opts(), ...opts })));
|
|
7466
|
+
program.addCommand(admins);
|
|
7363
7467
|
program.command("iam").description("Turn users & groups on/off for a proxy's tenant (identified calls get their user's groups applied)").argument("<project>", "Project name or id").argument("<state>", "on | off").option("--tenant <name>", "Tenant to toggle (defaults to the project's tenant)").option("--team <id|name>", "Team (defaults to active team, or your anonymous workspace)").option("--apiversion <version>", "API version (defaults to the first)").option("--json", "Output machine-readable JSON").action(action((project, state, opts) => runIamToggle(project, state, opts)));
|
|
7364
7468
|
program.command("identified").description("Require calls to identify their end user (X-End-User-Id or a login token) \u2014 or allow unattributed calls again").argument("<project>", "Project name or id").argument("<mode>", "require | allow-anon").option("--team <id|name>", "Team (defaults to active team, or your anonymous workspace)").option("--apiversion <version>", "API version (defaults to the first)").option("--json", "Output machine-readable JSON").action(action((project, mode, opts) => runIdentifiedToggle(project, mode, opts)));
|
|
7365
7469
|
var apikeys = new import_commander.Command("apikeys").description("Producer control-plane API keys (list, then offer to create one)").option("--team <id|name>", "Team (defaults to active team)").option("--json", "Output machine-readable JSON").action(action((opts) => runApikeysMenu(opts)));
|
|
@@ -7377,7 +7481,7 @@ spec.command("delete-rule").description("Delete the saved rules for a route (e.g
|
|
|
7377
7481
|
var HELP_GROUPS = [
|
|
7378
7482
|
{ title: "Chat", commands: ["apichat", "agent"] },
|
|
7379
7483
|
{ title: "Setup", commands: ["login", "create", "init", "sidecar", "dev", "claim", "team", "whoami", "logout"] },
|
|
7380
|
-
{ title: "Control plane commands", commands: ["config", "projects", "tenant", "group", "iam", "identified", "preapprove", "rule", "domain", "delete", "target", "throttle", "rename", "spec", "export"] },
|
|
7484
|
+
{ title: "Control plane commands", commands: ["config", "projects", "tenant", "group", "admins", "iam", "identified", "preapprove", "rule", "domain", "delete", "target", "throttle", "rename", "spec", "export"] },
|
|
7381
7485
|
{ title: "Data plane commands", commands: [
|
|
7382
7486
|
{ parent: "consumer", sub: "login" },
|
|
7383
7487
|
{ parent: "consumer", sub: "apikeys" }
|
|
@@ -7396,7 +7500,7 @@ function groupedCommandHelp() {
|
|
|
7396
7500
|
const sub = byName.get(e.parent)?.commands.find((s) => s.name() === e.sub);
|
|
7397
7501
|
return sub ? ` ${helpLabel(e).padEnd(width)}${sub.description()}` : "";
|
|
7398
7502
|
}).filter(Boolean).join("\n");
|
|
7399
|
-
return `${
|
|
7503
|
+
return `${import_chalk44.default.bold(g.title)}
|
|
7400
7504
|
${rows}`;
|
|
7401
7505
|
}).join("\n\n");
|
|
7402
7506
|
}
|
|
@@ -7430,14 +7534,14 @@ async function recoverStaleTeam() {
|
|
|
7430
7534
|
const { resolveLinkedTeam: resolveLinkedTeam2 } = await Promise.resolve().then(() => (init_team(), team_exports));
|
|
7431
7535
|
const linked = await resolveLinkedTeam2({ preferredId: creds.teamId, interactive: !!process.stdin.isTTY });
|
|
7432
7536
|
if (!linked) {
|
|
7433
|
-
console.error(
|
|
7537
|
+
console.error(import_chalk44.default.yellow("Your account has no teams anymore (deleted?). Run `apiblaze login` or `apiblaze create` to get a workspace."));
|
|
7434
7538
|
return;
|
|
7435
7539
|
}
|
|
7436
7540
|
if (linked.teamId === creds.teamId) return;
|
|
7437
7541
|
const next = { ...creds, teamId: linked.teamId, teamName: linked.teamName };
|
|
7438
7542
|
delete next.activeTenant;
|
|
7439
7543
|
saveCredentials(next);
|
|
7440
|
-
console.error(
|
|
7544
|
+
console.error(import_chalk44.default.yellow(`Your previous team no longer exists \u2014 relinked to ${import_chalk44.default.bold(linked.teamName ?? linked.teamId)}. Re-run your command.`));
|
|
7441
7545
|
} catch {
|
|
7442
7546
|
}
|
|
7443
7547
|
}
|
|
@@ -7445,16 +7549,16 @@ async function printError(err) {
|
|
|
7445
7549
|
if (err instanceof ApiError) {
|
|
7446
7550
|
const data = err.body;
|
|
7447
7551
|
const extra = [data?.body?.reason, data?.body?.details, data?.details, data?.body?.error].find((x) => typeof x === "string" && x && x !== err.message);
|
|
7448
|
-
console.error(
|
|
7552
|
+
console.error(import_chalk44.default.red(`
|
|
7449
7553
|
API error (${err.status}): ${err.message}${extra ? ` \u2014 ${extra}` : ""}`));
|
|
7450
7554
|
if (err.status === 403 || err.status === 404) {
|
|
7451
7555
|
await recoverStaleTeam();
|
|
7452
7556
|
}
|
|
7453
7557
|
} else if (err instanceof Error) {
|
|
7454
|
-
console.error(
|
|
7558
|
+
console.error(import_chalk44.default.red(`
|
|
7455
7559
|
Error: ${err.message}`));
|
|
7456
7560
|
} else {
|
|
7457
|
-
console.error(
|
|
7561
|
+
console.error(import_chalk44.default.red("\nUnknown error"));
|
|
7458
7562
|
}
|
|
7459
7563
|
}
|
|
7460
7564
|
program.parse(process.argv);
|