apiblaze 0.10.0 → 0.11.1
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 +1153 -205
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -302,10 +302,10 @@ var init_anon_cred = __esm({
|
|
|
302
302
|
|
|
303
303
|
// src/index.ts
|
|
304
304
|
var import_commander = require("commander");
|
|
305
|
-
var
|
|
305
|
+
var import_chalk32 = __toESM(require("chalk"));
|
|
306
306
|
|
|
307
307
|
// package.json
|
|
308
|
-
var version = "0.
|
|
308
|
+
var version = "0.11.1";
|
|
309
309
|
|
|
310
310
|
// src/index.ts
|
|
311
311
|
init_types();
|
|
@@ -2207,7 +2207,7 @@ function renderTrace() {
|
|
|
2207
2207
|
console.log(import_chalk15.default.dim("\n" + "\u2500".repeat(64)));
|
|
2208
2208
|
console.log(import_chalk15.default.bold(`--verbose: ${entries.length} API call${entries.length === 1 ? "" : "s"} this command made`));
|
|
2209
2209
|
console.log(
|
|
2210
|
-
import_chalk15.default.dim("The same thing on the official API \u2014 copy/paste with your control-plane key\n(get one from the Developers section of dashboard.apiblaze.com, then\n`export APIBLAZE_CONTROLPLANE_APIKEY=sk_...`)
|
|
2210
|
+
import_chalk15.default.dim("The same thing on the official API \u2014 copy/paste with your control-plane key\n(get one from the Developers section of dashboard.apiblaze.com, then\n`export APIBLAZE_CONTROLPLANE_APIKEY=sk_...`).\nFull API reference: https://api.apiblaze.com/openapi.json\n")
|
|
2211
2211
|
);
|
|
2212
2212
|
entries.forEach((e, i) => {
|
|
2213
2213
|
const n = entries.length > 1 ? import_chalk15.default.bold(`${i + 1}. `) : "";
|
|
@@ -2477,6 +2477,11 @@ async function runRename(project, opts) {
|
|
|
2477
2477
|
await patchConfig(project, opts, { display_name: opts.displayName }, `Rename \u2192 "${opts.displayName}"`);
|
|
2478
2478
|
}
|
|
2479
2479
|
|
|
2480
|
+
// src/commands/config-browse.ts
|
|
2481
|
+
var import_chalk26 = __toESM(require("chalk"));
|
|
2482
|
+
var import_ora11 = __toESM(require("ora"));
|
|
2483
|
+
init_auth();
|
|
2484
|
+
|
|
2480
2485
|
// src/commands/domain.ts
|
|
2481
2486
|
var import_chalk21 = __toESM(require("chalk"));
|
|
2482
2487
|
var import_ora7 = __toESM(require("ora"));
|
|
@@ -2680,15 +2685,15 @@ async function runTenantCors(opts) {
|
|
|
2680
2685
|
process.exit(1);
|
|
2681
2686
|
}
|
|
2682
2687
|
const { teamId } = await resolveTeam(opts.team);
|
|
2683
|
-
const
|
|
2684
|
-
const cors =
|
|
2688
|
+
const origins = (opts.origins ?? "").split(",").map((s) => s.trim()).filter(Boolean);
|
|
2689
|
+
const cors = origins.length ? { allowed_origins: origins } : null;
|
|
2685
2690
|
const spinner = (0, import_ora8.default)("Updating CORS...").start();
|
|
2686
2691
|
try {
|
|
2687
2692
|
await admin({
|
|
2688
2693
|
method: "PUT",
|
|
2689
2694
|
path: `/teams/${encodeURIComponent(teamId)}/tenants/${encodeURIComponent(opts.tenant)}/cors`,
|
|
2690
2695
|
body: { cors },
|
|
2691
|
-
summary: `Set CORS for tenant ${opts.tenant} \u2192 ${
|
|
2696
|
+
summary: `Set CORS for tenant ${opts.tenant} \u2192 ${origins.length ? origins.join(", ") : "(cleared)"}`
|
|
2692
2697
|
});
|
|
2693
2698
|
spinner.succeed(`CORS updated for ${opts.tenant}.`);
|
|
2694
2699
|
} catch (err) {
|
|
@@ -2697,88 +2702,10 @@ async function runTenantCors(opts) {
|
|
|
2697
2702
|
}
|
|
2698
2703
|
}
|
|
2699
2704
|
|
|
2700
|
-
// src/commands/key.ts
|
|
2701
|
-
var import_chalk23 = __toESM(require("chalk"));
|
|
2702
|
-
var import_ora9 = __toESM(require("ora"));
|
|
2703
|
-
async function runApikeysMenu(opts) {
|
|
2704
|
-
await runKeyList(opts);
|
|
2705
|
-
if (opts.json) return;
|
|
2706
|
-
const { default: inquirer2 } = await import("inquirer");
|
|
2707
|
-
const { make } = await inquirer2.prompt([
|
|
2708
|
-
{ type: "confirm", name: "make", message: "Generate a new control-plane API key?", default: false }
|
|
2709
|
-
]);
|
|
2710
|
-
if (!make) return;
|
|
2711
|
-
const { desc } = await inquirer2.prompt([
|
|
2712
|
-
{ type: "input", name: "desc", message: "Description (optional):" }
|
|
2713
|
-
]);
|
|
2714
|
-
await runKeyMint({ ...opts, desc: desc || void 0 });
|
|
2715
|
-
}
|
|
2716
|
-
async function runKeyList(opts) {
|
|
2717
|
-
const { teamId, teamName } = await resolveTeam(opts.team);
|
|
2718
|
-
const out = await admin({
|
|
2719
|
-
method: "GET",
|
|
2720
|
-
path: `/teams/${encodeURIComponent(teamId)}/developer-keys`,
|
|
2721
|
-
summary: `List developer keys for team ${teamName ?? teamId}`
|
|
2722
|
-
});
|
|
2723
|
-
const keys = out?.keys ?? [];
|
|
2724
|
-
if (opts.json) {
|
|
2725
|
-
console.log(JSON.stringify(keys));
|
|
2726
|
-
return;
|
|
2727
|
-
}
|
|
2728
|
-
if (!keys.length) {
|
|
2729
|
-
console.log(import_chalk23.default.yellow("No developer keys."));
|
|
2730
|
-
return;
|
|
2731
|
-
}
|
|
2732
|
-
for (const k of keys) {
|
|
2733
|
-
console.log(` ${import_chalk23.default.bold(k.key_id ?? k.id)} ${import_chalk23.default.dim(k.description ?? "")} ${import_chalk23.default.dim(k.expires_at ?? "no expiry")}`);
|
|
2734
|
-
}
|
|
2735
|
-
}
|
|
2736
|
-
async function runKeyMint(opts) {
|
|
2737
|
-
const { teamId } = await resolveTeam(opts.team);
|
|
2738
|
-
const body = { role: "consumer-admin" };
|
|
2739
|
-
if (opts.desc) body.description = opts.desc;
|
|
2740
|
-
if (opts.expiresDays) body.expires_in_seconds = Number(opts.expiresDays) * 24 * 60 * 60;
|
|
2741
|
-
const spinner = (0, import_ora9.default)("Minting key...").start();
|
|
2742
|
-
try {
|
|
2743
|
-
const out = await admin({
|
|
2744
|
-
method: "POST",
|
|
2745
|
-
path: `/teams/${encodeURIComponent(teamId)}/developer-keys`,
|
|
2746
|
-
body,
|
|
2747
|
-
summary: `Mint a consumer-admin developer key`
|
|
2748
|
-
});
|
|
2749
|
-
spinner.succeed("Key minted.");
|
|
2750
|
-
if (opts.json) {
|
|
2751
|
-
console.log(JSON.stringify(out));
|
|
2752
|
-
return;
|
|
2753
|
-
}
|
|
2754
|
-
console.log(` ${import_chalk23.default.bold("key_id")}: ${out?.key_id}`);
|
|
2755
|
-
console.log(` ${import_chalk23.default.bold("key")}: ${import_chalk23.default.green(out?.key)} ${import_chalk23.default.dim("(shown once \u2014 store it now)")}`);
|
|
2756
|
-
if (out?.expires_at) console.log(` ${import_chalk23.default.dim("expires:")} ${out.expires_at}`);
|
|
2757
|
-
} catch (err) {
|
|
2758
|
-
spinner.fail("Mint failed.");
|
|
2759
|
-
throw err;
|
|
2760
|
-
}
|
|
2761
|
-
}
|
|
2762
|
-
async function runKeyRevoke(keyId, opts) {
|
|
2763
|
-
const { teamId } = await resolveTeam(opts.team);
|
|
2764
|
-
const spinner = (0, import_ora9.default)("Revoking key...").start();
|
|
2765
|
-
try {
|
|
2766
|
-
await admin({
|
|
2767
|
-
method: "DELETE",
|
|
2768
|
-
path: `/teams/${encodeURIComponent(teamId)}/developer-keys/${encodeURIComponent(keyId)}`,
|
|
2769
|
-
summary: `Revoke developer key ${keyId}`
|
|
2770
|
-
});
|
|
2771
|
-
spinner.succeed(`Revoked ${keyId}.`);
|
|
2772
|
-
} catch (err) {
|
|
2773
|
-
spinner.fail("Revoke failed.");
|
|
2774
|
-
throw err;
|
|
2775
|
-
}
|
|
2776
|
-
}
|
|
2777
|
-
|
|
2778
2705
|
// src/commands/spec.ts
|
|
2779
2706
|
var fs6 = __toESM(require("fs"));
|
|
2780
|
-
var
|
|
2781
|
-
var
|
|
2707
|
+
var import_chalk23 = __toESM(require("chalk"));
|
|
2708
|
+
var import_ora9 = __toESM(require("ora"));
|
|
2782
2709
|
async function runSpecGet(project, opts) {
|
|
2783
2710
|
const { teamId } = await resolveTeam(opts.team);
|
|
2784
2711
|
const proj2 = await resolveProject(teamId, project, opts.apiversion);
|
|
@@ -2791,19 +2718,19 @@ async function runSpecGet(project, opts) {
|
|
|
2791
2718
|
}
|
|
2792
2719
|
async function runSpecSet(project, opts) {
|
|
2793
2720
|
if (!opts.file) {
|
|
2794
|
-
console.error(
|
|
2721
|
+
console.error(import_chalk23.default.red("--file <path> is required (OpenAPI JSON or YAML)."));
|
|
2795
2722
|
process.exit(1);
|
|
2796
2723
|
}
|
|
2797
2724
|
let specContent;
|
|
2798
2725
|
try {
|
|
2799
2726
|
specContent = fs6.readFileSync(opts.file, "utf-8");
|
|
2800
2727
|
} catch {
|
|
2801
|
-
console.error(
|
|
2728
|
+
console.error(import_chalk23.default.red(`Cannot read file: ${opts.file}`));
|
|
2802
2729
|
process.exit(1);
|
|
2803
2730
|
}
|
|
2804
2731
|
const { teamId } = await resolveTeam(opts.team);
|
|
2805
2732
|
const proj2 = await resolveProject(teamId, project, opts.apiversion);
|
|
2806
|
-
const spinner = (0,
|
|
2733
|
+
const spinner = (0, import_ora9.default)("Uploading spec...").start();
|
|
2807
2734
|
try {
|
|
2808
2735
|
const out = await admin({
|
|
2809
2736
|
method: "POST",
|
|
@@ -2820,12 +2747,12 @@ async function runSpecSet(project, opts) {
|
|
|
2820
2747
|
}
|
|
2821
2748
|
|
|
2822
2749
|
// src/commands/agent.ts
|
|
2823
|
-
var
|
|
2824
|
-
var
|
|
2750
|
+
var import_chalk25 = __toESM(require("chalk"));
|
|
2751
|
+
var import_ora10 = __toESM(require("ora"));
|
|
2825
2752
|
init_auth();
|
|
2826
2753
|
|
|
2827
2754
|
// src/lib/tools.ts
|
|
2828
|
-
var
|
|
2755
|
+
var import_chalk24 = __toESM(require("chalk"));
|
|
2829
2756
|
init_api();
|
|
2830
2757
|
async function proj(teamId, name, version2) {
|
|
2831
2758
|
return resolveProject(teamId, name, version2);
|
|
@@ -2843,15 +2770,15 @@ var TOOLS = [
|
|
|
2843
2770
|
const key = keys.dev ?? Object.values(keys)[0];
|
|
2844
2771
|
const url = `https://${a.name}.abz.run/${version2}/dev`;
|
|
2845
2772
|
const tryIt = buildTryItCurl(url, auth, key);
|
|
2846
|
-
const lines = [` ${
|
|
2847
|
-
if (res.devPortal) lines.push(` ${
|
|
2773
|
+
const lines = [` ${import_chalk24.default.dim("Proxy URL:")} ${import_chalk24.default.bold(url)}`];
|
|
2774
|
+
if (res.devPortal) lines.push(` ${import_chalk24.default.dim("Dev portal:")} ${res.devPortal}`);
|
|
2848
2775
|
const envs = Object.keys(keys);
|
|
2849
2776
|
if (envs.length) {
|
|
2850
|
-
lines.push("", ` ${
|
|
2777
|
+
lines.push("", ` ${import_chalk24.default.bold("API keys")} ${import_chalk24.default.dim("(bootstrapped \u2014 send as the X-API-Key header; shown once):")}`);
|
|
2851
2778
|
const w = Math.max(...envs.map((e) => e.length));
|
|
2852
|
-
for (const env of envs) lines.push(` ${
|
|
2779
|
+
for (const env of envs) lines.push(` ${import_chalk24.default.cyan(env.padEnd(w))} ${import_chalk24.default.green(keys[env])}`);
|
|
2853
2780
|
}
|
|
2854
|
-
if (tryIt) lines.push("", ` ${
|
|
2781
|
+
if (tryIt) lines.push("", ` ${import_chalk24.default.dim("Try it:")}`, ` ${import_chalk24.default.cyan(tryIt)}`);
|
|
2855
2782
|
return { ...res, proxy_url: url, keys, ...tryIt ? { try_it: tryIt } : {}, display: lines.join("\n") };
|
|
2856
2783
|
}
|
|
2857
2784
|
},
|
|
@@ -3006,23 +2933,23 @@ function truncate(value, max = 1500) {
|
|
|
3006
2933
|
}
|
|
3007
2934
|
function printCost(llm) {
|
|
3008
2935
|
const usd = llm.cost > 0 ? `$${llm.cost.toFixed(4)}` : "<$0.0001";
|
|
3009
|
-
console.log(
|
|
2936
|
+
console.log(import_chalk25.default.magenta(` \u{1F4B3} ${usd}`) + import_chalk25.default.dim(` (${llm.model}, ${llm.total_tokens} tok)`));
|
|
3010
2937
|
}
|
|
3011
2938
|
async function runAgent(opts) {
|
|
3012
2939
|
requireAuth();
|
|
3013
2940
|
const { teamId, teamName } = await resolveTeam(opts.team);
|
|
3014
2941
|
const { default: inquirer2 } = await import("inquirer");
|
|
3015
|
-
console.log(
|
|
3016
|
-
console.log(
|
|
2942
|
+
console.log(import_chalk25.default.bold("APIblaze agent") + import_chalk25.default.dim(` \xB7 team ${teamName ?? teamId}`));
|
|
2943
|
+
console.log(import_chalk25.default.dim('Ask me to create/delete/configure proxies, tenants, keys, domains, specs. Type "exit" to quit.\n'));
|
|
3017
2944
|
const history = [];
|
|
3018
2945
|
while (true) {
|
|
3019
|
-
const { input } = await inquirer2.prompt([{ type: "input", name: "input", message:
|
|
2946
|
+
const { input } = await inquirer2.prompt([{ type: "input", name: "input", message: import_chalk25.default.cyan("you") + " \u203A" }]);
|
|
3020
2947
|
const text = (input ?? "").trim();
|
|
3021
2948
|
if (!text) continue;
|
|
3022
2949
|
if (["exit", "quit", ":q"].includes(text.toLowerCase())) break;
|
|
3023
2950
|
history.push({ role: "user", content: text });
|
|
3024
2951
|
for (let step = 0; step < MAX_TOOL_STEPS; step++) {
|
|
3025
|
-
const spinner = (0,
|
|
2952
|
+
const spinner = (0, import_ora10.default)({ text: "thinking...", color: "magenta" }).start();
|
|
3026
2953
|
let resp;
|
|
3027
2954
|
try {
|
|
3028
2955
|
resp = await callAgent(history, teamId);
|
|
@@ -3030,21 +2957,21 @@ async function runAgent(opts) {
|
|
|
3030
2957
|
} catch (err) {
|
|
3031
2958
|
spinner.stop();
|
|
3032
2959
|
if (err instanceof ApiError && err.status === 402) {
|
|
3033
|
-
console.log(
|
|
2960
|
+
console.log(import_chalk25.default.yellow(" Insufficient credits \u2014 top up to keep using the agent."));
|
|
3034
2961
|
break;
|
|
3035
2962
|
}
|
|
3036
2963
|
throw err;
|
|
3037
2964
|
}
|
|
3038
2965
|
history.push({ role: "assistant", content: resp.raw });
|
|
3039
2966
|
printCost(resp.llm);
|
|
3040
|
-
if (resp.reply) console.log(
|
|
2967
|
+
if (resp.reply) console.log(import_chalk25.default.green("agent") + " \u203A " + resp.reply);
|
|
3041
2968
|
if (!resp.action) break;
|
|
3042
2969
|
const tool = findTool(resp.action.tool);
|
|
3043
2970
|
if (!tool) {
|
|
3044
2971
|
history.push({ role: "user", content: `TOOL_RESULT ${resp.action.tool}: error \u2014 unknown tool` });
|
|
3045
2972
|
continue;
|
|
3046
2973
|
}
|
|
3047
|
-
const runSpinner = (0,
|
|
2974
|
+
const runSpinner = (0, import_ora10.default)({ text: `running ${tool.name}...`, color: "cyan" }).start();
|
|
3048
2975
|
try {
|
|
3049
2976
|
const result = await tool.run(resp.action.args, { teamId });
|
|
3050
2977
|
runSpinner.succeed(`${tool.name} \u2713`);
|
|
@@ -3062,16 +2989,891 @@ async function runAgent(opts) {
|
|
|
3062
2989
|
}
|
|
3063
2990
|
renderTrace();
|
|
3064
2991
|
if (step === MAX_TOOL_STEPS - 1) {
|
|
3065
|
-
console.log(
|
|
2992
|
+
console.log(import_chalk25.default.dim(" (paused after several steps \u2014 tell me how to continue)"));
|
|
3066
2993
|
}
|
|
3067
2994
|
}
|
|
3068
2995
|
}
|
|
3069
|
-
console.log(
|
|
2996
|
+
console.log(import_chalk25.default.dim("\nBye."));
|
|
3070
2997
|
}
|
|
3071
2998
|
|
|
3072
|
-
// src/commands/
|
|
2999
|
+
// src/commands/config-browse.ts
|
|
3000
|
+
var fullThrottle = (cfg, patch) => ({
|
|
3001
|
+
throttling: { ...cfg.throttling ?? {}, ...patch }
|
|
3002
|
+
});
|
|
3003
|
+
var fullPolicy = (cfg, patch) => {
|
|
3004
|
+
const merged = { mode: "passthrough", ...cfg.requests_policy ?? {}, ...patch };
|
|
3005
|
+
return { requests_policy: merged };
|
|
3006
|
+
};
|
|
3007
|
+
var SETTINGS = [
|
|
3008
|
+
{
|
|
3009
|
+
key: "display_name",
|
|
3010
|
+
label: "Display name",
|
|
3011
|
+
group: "Basics",
|
|
3012
|
+
type: "string",
|
|
3013
|
+
desc: "Human-friendly label shown in the dashboard and portal",
|
|
3014
|
+
read: (cfg) => cfg.display_name ?? cfg.project_display_name,
|
|
3015
|
+
toPatch: (v) => ({ display_name: v })
|
|
3016
|
+
},
|
|
3017
|
+
{
|
|
3018
|
+
key: "target_url",
|
|
3019
|
+
label: "Upstream target URL",
|
|
3020
|
+
group: "Basics",
|
|
3021
|
+
type: "string",
|
|
3022
|
+
desc: "Where the proxy forwards requests (cascades to every environment)",
|
|
3023
|
+
read: (cfg) => cfg.target_url ?? dig(cfg, "environments.prod.target") ?? Object.values(cfg.environments ?? {})[0]?.target,
|
|
3024
|
+
toPatch: (v) => ({ target_url: v })
|
|
3025
|
+
},
|
|
3026
|
+
{
|
|
3027
|
+
key: "enabled",
|
|
3028
|
+
label: "Proxy serving",
|
|
3029
|
+
group: "Traffic & limits",
|
|
3030
|
+
type: "boolean",
|
|
3031
|
+
desc: "Master on/off switch \u2014 off stops serving traffic",
|
|
3032
|
+
read: (cfg) => cfg.enabled !== false,
|
|
3033
|
+
toPatch: (v) => ({ enabled: v })
|
|
3034
|
+
},
|
|
3035
|
+
{
|
|
3036
|
+
key: "listenToTraffic",
|
|
3037
|
+
label: "Traffic capture",
|
|
3038
|
+
group: "Traffic & limits",
|
|
3039
|
+
type: "boolean",
|
|
3040
|
+
desc: "Capture request samples to build your OpenAPI spec from real traffic",
|
|
3041
|
+
read: (cfg) => cfg.listenToTraffic !== false,
|
|
3042
|
+
toPatch: (v) => ({ listenToTraffic: v })
|
|
3043
|
+
},
|
|
3044
|
+
{
|
|
3045
|
+
key: "throttling.userRateLimit",
|
|
3046
|
+
label: "Caller rate limit (req/s)",
|
|
3047
|
+
group: "Traffic & limits",
|
|
3048
|
+
type: "number",
|
|
3049
|
+
desc: "Requests/second per authenticated caller",
|
|
3050
|
+
toPatch: (v, cfg) => fullThrottle(cfg, { userRateLimit: v })
|
|
3051
|
+
},
|
|
3052
|
+
{
|
|
3053
|
+
key: "throttling.endUserRateLimit",
|
|
3054
|
+
label: "End-user rate limit (req/s)",
|
|
3055
|
+
group: "Traffic & limits",
|
|
3056
|
+
type: "number",
|
|
3057
|
+
desc: "Requests/second per end user (x-end-user-id)",
|
|
3058
|
+
toPatch: (v, cfg) => fullThrottle(cfg, { endUserRateLimit: v })
|
|
3059
|
+
},
|
|
3060
|
+
{
|
|
3061
|
+
key: "throttling.proxyQuota",
|
|
3062
|
+
label: "Quota (requests/period)",
|
|
3063
|
+
group: "Traffic & limits",
|
|
3064
|
+
type: "number",
|
|
3065
|
+
desc: "Total requests per quota period for the whole proxy",
|
|
3066
|
+
toPatch: (v, cfg) => fullThrottle(cfg, { proxyQuota: v })
|
|
3067
|
+
},
|
|
3068
|
+
{
|
|
3069
|
+
key: "throttling.quotaPeriod",
|
|
3070
|
+
label: "Quota period",
|
|
3071
|
+
group: "Traffic & limits",
|
|
3072
|
+
type: "enum",
|
|
3073
|
+
enum: ["daily", "weekly", "monthly"],
|
|
3074
|
+
desc: "Window the quota applies to",
|
|
3075
|
+
toPatch: (v, cfg) => fullThrottle(cfg, { quotaPeriod: v })
|
|
3076
|
+
},
|
|
3077
|
+
{
|
|
3078
|
+
key: "requests_policy.mode",
|
|
3079
|
+
label: "Credential policy",
|
|
3080
|
+
group: "Access & auth",
|
|
3081
|
+
type: "enum",
|
|
3082
|
+
enum: ["passthrough", "authenticate"],
|
|
3083
|
+
desc: "passthrough = forward anything; authenticate = require a credential",
|
|
3084
|
+
toPatch: (v, cfg) => fullPolicy(cfg, v === "authenticate" ? { mode: v, methods: cfg.requests_policy?.methods?.length ? cfg.requests_policy.methods : ["api_key"] } : { mode: v })
|
|
3085
|
+
},
|
|
3086
|
+
{
|
|
3087
|
+
key: "requests_policy.methods",
|
|
3088
|
+
label: "Accepted credentials",
|
|
3089
|
+
group: "Access & auth",
|
|
3090
|
+
type: "json",
|
|
3091
|
+
desc: "Array from api_key | jwt | opaque (used when policy = authenticate)",
|
|
3092
|
+
toPatch: (v, cfg) => fullPolicy(cfg, { mode: cfg.requests_policy?.mode ?? "authenticate", methods: v })
|
|
3093
|
+
},
|
|
3094
|
+
{
|
|
3095
|
+
key: "auth_type",
|
|
3096
|
+
label: "Consumer login",
|
|
3097
|
+
group: "Access & auth",
|
|
3098
|
+
type: "enum",
|
|
3099
|
+
enum: ["oauth", "none"],
|
|
3100
|
+
desc: "OAuth login for your API consumers (portal sign-in)",
|
|
3101
|
+
read: (cfg) => cfg.auth_type ?? "none",
|
|
3102
|
+
toPatch: (v) => ({ auth_type: v })
|
|
3103
|
+
},
|
|
3104
|
+
{
|
|
3105
|
+
key: "cors",
|
|
3106
|
+
label: "CORS",
|
|
3107
|
+
group: "Access & auth",
|
|
3108
|
+
type: "json",
|
|
3109
|
+
desc: "Per-proxy CORS config object; null clears the override",
|
|
3110
|
+
toPatch: (v) => ({ cors: v })
|
|
3111
|
+
},
|
|
3112
|
+
{
|
|
3113
|
+
key: "enable_portal",
|
|
3114
|
+
label: "Developer portal",
|
|
3115
|
+
group: "Portal & MCP",
|
|
3116
|
+
type: "boolean",
|
|
3117
|
+
desc: "The hosted docs + key-management portal for your consumers",
|
|
3118
|
+
read: (cfg) => cfg.enable_portal !== false,
|
|
3119
|
+
toPatch: (v) => ({ enable_portal: v })
|
|
3120
|
+
},
|
|
3121
|
+
{
|
|
3122
|
+
key: "mcp_enabled",
|
|
3123
|
+
label: "MCP server",
|
|
3124
|
+
group: "Portal & MCP",
|
|
3125
|
+
type: "boolean",
|
|
3126
|
+
desc: "Serve this API as an MCP server for AI agents",
|
|
3127
|
+
read: (cfg) => cfg.mcp_enabled !== false,
|
|
3128
|
+
toPatch: (v) => ({ mcp_enabled: v })
|
|
3129
|
+
},
|
|
3130
|
+
{
|
|
3131
|
+
key: "product_slug",
|
|
3132
|
+
label: "Product tag",
|
|
3133
|
+
group: "Portal & MCP",
|
|
3134
|
+
type: "string",
|
|
3135
|
+
desc: "Team-scoped tag grouping projects in the consumer portal",
|
|
3136
|
+
toPatch: (v) => ({ product_slug: v })
|
|
3137
|
+
}
|
|
3138
|
+
];
|
|
3139
|
+
var SETTING_GROUPS = ["Basics", "Traffic & limits", "Access & auth", "Portal & MCP"];
|
|
3140
|
+
var FEATURES = [
|
|
3141
|
+
{ go: "transforms", label: "Transforms", desc: "Rewrite requests/responses (headers, body fields) without touching your upstream" },
|
|
3142
|
+
{ go: "mappings", label: "Mapping tables", desc: "Reusable value-mapping tables used by map transforms (values can be hidden/encrypted)" },
|
|
3143
|
+
{ go: "tenants", label: "Tenants", desc: "Separate groups of your API consumers \u2014 each with its own portal, login, and keys" },
|
|
3144
|
+
{ go: "domains", label: "Custom domains", desc: "Serve the proxy on your own hostname + choose what the bare URL serves" },
|
|
3145
|
+
{ go: "spec", label: "OpenAPI spec & traffic", desc: "View the stored spec, refresh it from source, or build it from captured traffic" },
|
|
3146
|
+
{ go: "agents", label: "AI agents", desc: "Chat to build your spec, design access rules, or publish an MCP server (billed per turn)" },
|
|
3147
|
+
{ go: "danger", label: "Danger zone", desc: "Delete this proxy and everything under it" }
|
|
3148
|
+
];
|
|
3149
|
+
function dig(blob, dotted) {
|
|
3150
|
+
let cur = blob;
|
|
3151
|
+
for (const part of dotted.split(".")) {
|
|
3152
|
+
if (cur == null || typeof cur !== "object") return void 0;
|
|
3153
|
+
cur = cur[part];
|
|
3154
|
+
}
|
|
3155
|
+
return cur;
|
|
3156
|
+
}
|
|
3157
|
+
var readSetting = (s, cfg) => s.read ? s.read(cfg) : dig(cfg, s.key);
|
|
3158
|
+
function show(v) {
|
|
3159
|
+
if (v === void 0) return import_chalk26.default.dim("(unset)");
|
|
3160
|
+
if (v === null) return import_chalk26.default.dim("null");
|
|
3161
|
+
if (typeof v === "object") return import_chalk26.default.cyan(JSON.stringify(v));
|
|
3162
|
+
if (typeof v === "boolean") return v ? import_chalk26.default.green("on") : import_chalk26.default.red("off");
|
|
3163
|
+
return import_chalk26.default.cyan(String(v));
|
|
3164
|
+
}
|
|
3165
|
+
function parseValue(raw) {
|
|
3166
|
+
if (raw === "true") return true;
|
|
3167
|
+
if (raw === "false") return false;
|
|
3168
|
+
if (raw === "null") return null;
|
|
3169
|
+
if (raw !== "" && !Number.isNaN(Number(raw))) return Number(raw);
|
|
3170
|
+
if (/^[[{]/.test(raw.trim())) {
|
|
3171
|
+
try {
|
|
3172
|
+
return JSON.parse(raw);
|
|
3173
|
+
} catch {
|
|
3174
|
+
}
|
|
3175
|
+
}
|
|
3176
|
+
return raw;
|
|
3177
|
+
}
|
|
3178
|
+
async function fetchConfigBlob(proj2) {
|
|
3179
|
+
const out = await admin({
|
|
3180
|
+
method: "GET",
|
|
3181
|
+
path: `/projects?team_id=${encodeURIComponent(proj2.teamId)}`,
|
|
3182
|
+
summary: `Read settings for ${proj2.projectName}`
|
|
3183
|
+
});
|
|
3184
|
+
const rows = out?.projects ?? [];
|
|
3185
|
+
const row = rows.find((r) => r.project_id === proj2.projectId && r.api_version === proj2.apiVersion) ?? rows.find((r) => r.project_id === proj2.projectId);
|
|
3186
|
+
return row?.config ?? {};
|
|
3187
|
+
}
|
|
3188
|
+
async function patchSetting(proj2, s, value, cfg) {
|
|
3189
|
+
const body = s.toPatch(value, cfg);
|
|
3190
|
+
const spinner = (0, import_ora11.default)(`Set ${s.key}...`).start();
|
|
3191
|
+
try {
|
|
3192
|
+
await admin({
|
|
3193
|
+
method: "PATCH",
|
|
3194
|
+
path: `/projects/${proj2.projectId}/${proj2.apiVersion}`,
|
|
3195
|
+
body,
|
|
3196
|
+
summary: `Set ${s.key} = ${JSON.stringify(value)}`
|
|
3197
|
+
});
|
|
3198
|
+
spinner.succeed(`${s.key} = ${JSON.stringify(value)}`);
|
|
3199
|
+
} catch (err) {
|
|
3200
|
+
spinner.fail(`Set ${s.key} failed.`);
|
|
3201
|
+
throw err;
|
|
3202
|
+
}
|
|
3203
|
+
}
|
|
3204
|
+
var loginFirst = (what) => {
|
|
3205
|
+
console.log(import_chalk26.default.yellow(`
|
|
3206
|
+
Log in first to ${what}.`));
|
|
3207
|
+
console.log(import_chalk26.default.dim(" Run `npx apiblaze login` \u2014 or `npx apiblaze claim` if you created this proxy"));
|
|
3208
|
+
console.log(import_chalk26.default.dim(" anonymously and want to bring it into your account.\n"));
|
|
3209
|
+
};
|
|
3210
|
+
async function runConfig(project, key, value, opts) {
|
|
3211
|
+
const creds = loadCredentials();
|
|
3212
|
+
if (!creds) {
|
|
3213
|
+
await discoveryMenu(project);
|
|
3214
|
+
return;
|
|
3215
|
+
}
|
|
3216
|
+
const { teamId } = await resolveTeam(opts.team);
|
|
3217
|
+
const proj2 = project ? await resolveProject(teamId, project, opts.apiversion) : await pickProject(teamId);
|
|
3218
|
+
const cfg = await fetchConfigBlob(proj2);
|
|
3219
|
+
if (opts.list || key === void 0) {
|
|
3220
|
+
if (opts.list) return printAll(proj2, cfg, opts.json);
|
|
3221
|
+
return navigator(proj2, cfg, opts);
|
|
3222
|
+
}
|
|
3223
|
+
const setting = SETTINGS.find((s) => s.key === key);
|
|
3224
|
+
if (!setting) {
|
|
3225
|
+
console.error(import_chalk26.default.red(`Unknown setting "${key}".`));
|
|
3226
|
+
console.error(import_chalk26.default.dim(" Known: " + SETTINGS.map((s) => s.key).join(", ")));
|
|
3227
|
+
console.error(import_chalk26.default.dim(" (Features like transforms/domains/tenants live in the menu: `apiblaze config <project>`.)"));
|
|
3228
|
+
process.exit(1);
|
|
3229
|
+
}
|
|
3230
|
+
if (value === void 0) {
|
|
3231
|
+
const v = readSetting(setting, cfg);
|
|
3232
|
+
if (opts.json) console.log(JSON.stringify(v ?? null));
|
|
3233
|
+
else console.log(`${setting.key} = ${show(v)}`);
|
|
3234
|
+
return;
|
|
3235
|
+
}
|
|
3236
|
+
await patchSetting(proj2, setting, parseValue(value), cfg);
|
|
3237
|
+
}
|
|
3238
|
+
async function pickProject(teamId) {
|
|
3239
|
+
const { getProjects: getProjects2 } = await Promise.resolve().then(() => (init_api(), api_exports));
|
|
3240
|
+
const projects = await getProjects2(teamId).catch(() => []);
|
|
3241
|
+
if (!projects.length) {
|
|
3242
|
+
console.error(import_chalk26.default.red("No projects in this team. Create one: `npx apiblaze create`."));
|
|
3243
|
+
process.exit(1);
|
|
3244
|
+
}
|
|
3245
|
+
const { default: inquirer2 } = await import("inquirer");
|
|
3246
|
+
const { picked } = await inquirer2.prompt([{
|
|
3247
|
+
type: "list",
|
|
3248
|
+
name: "picked",
|
|
3249
|
+
message: "Which project?",
|
|
3250
|
+
choices: projects.map((p) => ({ name: `${p.projectName} ${import_chalk26.default.dim("v" + p.apiVersion)}`, value: p }))
|
|
3251
|
+
}]);
|
|
3252
|
+
return { projectId: picked.projectId, projectName: picked.projectName, apiVersion: picked.apiVersion, teamId, tenant: picked.tenant };
|
|
3253
|
+
}
|
|
3254
|
+
function printAll(proj2, cfg, json) {
|
|
3255
|
+
if (json) {
|
|
3256
|
+
const out = {};
|
|
3257
|
+
for (const s of SETTINGS) out[s.key] = readSetting(s, cfg) ?? null;
|
|
3258
|
+
console.log(JSON.stringify(out, null, 2));
|
|
3259
|
+
return;
|
|
3260
|
+
}
|
|
3261
|
+
console.log(import_chalk26.default.bold(`
|
|
3262
|
+
${proj2.projectName} v${proj2.apiVersion} \u2014 settings
|
|
3263
|
+
`));
|
|
3264
|
+
for (const group of SETTING_GROUPS) {
|
|
3265
|
+
console.log(import_chalk26.default.bold(group));
|
|
3266
|
+
for (const s of SETTINGS.filter((x) => x.group === group)) {
|
|
3267
|
+
console.log(` ${s.key.padEnd(32)} ${show(readSetting(s, cfg))} ${import_chalk26.default.dim(s.desc)}`);
|
|
3268
|
+
}
|
|
3269
|
+
console.log();
|
|
3270
|
+
}
|
|
3271
|
+
console.log(import_chalk26.default.dim("Change one: apiblaze config " + proj2.projectName + " <key> <value> (add --verbose for the API call)"));
|
|
3272
|
+
}
|
|
3273
|
+
async function discoveryMenu(project) {
|
|
3274
|
+
const { default: inquirer2 } = await import("inquirer");
|
|
3275
|
+
console.log(import_chalk26.default.bold(`
|
|
3276
|
+
APIblaze proxy configuration${project ? ` \u2014 ${project}` : ""}
|
|
3277
|
+
`));
|
|
3278
|
+
console.log(import_chalk26.default.dim("You are not logged in \u2014 browsing what's configurable. Everything below works"));
|
|
3279
|
+
console.log(import_chalk26.default.dim("from this menu once you log in (`npx apiblaze login`).\n"));
|
|
3280
|
+
for (; ; ) {
|
|
3281
|
+
const { pick: pick2 } = await inquirer2.prompt([{
|
|
3282
|
+
type: "list",
|
|
3283
|
+
name: "pick",
|
|
3284
|
+
message: "Explore:",
|
|
3285
|
+
pageSize: 20,
|
|
3286
|
+
choices: [
|
|
3287
|
+
new inquirer2.Separator(import_chalk26.default.bold("\u2014 Settings \u2014")),
|
|
3288
|
+
...SETTING_GROUPS.map((g) => ({
|
|
3289
|
+
name: `${g} ${import_chalk26.default.dim(SETTINGS.filter((s) => s.group === g).map((s) => s.label).join(", "))}`,
|
|
3290
|
+
value: { kind: "settings", g }
|
|
3291
|
+
})),
|
|
3292
|
+
new inquirer2.Separator(import_chalk26.default.bold("\u2014 Features \u2014")),
|
|
3293
|
+
...FEATURES.map((f) => ({ name: `${f.label} ${import_chalk26.default.dim(f.desc)}`, value: { kind: "feature", f } })),
|
|
3294
|
+
new inquirer2.Separator(),
|
|
3295
|
+
{ name: "Exit", value: { kind: "exit" } }
|
|
3296
|
+
]
|
|
3297
|
+
}]);
|
|
3298
|
+
if (pick2.kind === "exit") return;
|
|
3299
|
+
if (pick2.kind === "settings") {
|
|
3300
|
+
console.log();
|
|
3301
|
+
for (const s of SETTINGS.filter((x) => x.group === pick2.g)) {
|
|
3302
|
+
console.log(` ${import_chalk26.default.bold(s.label.padEnd(28))} ${import_chalk26.default.dim(s.desc)}`);
|
|
3303
|
+
console.log(` ${import_chalk26.default.dim(" key: " + s.key)}`);
|
|
3304
|
+
}
|
|
3305
|
+
loginFirst("view or change these settings");
|
|
3306
|
+
} else {
|
|
3307
|
+
const f = pick2.f;
|
|
3308
|
+
console.log(`
|
|
3309
|
+
${import_chalk26.default.bold(f.label)} \u2014 ${f.desc}`);
|
|
3310
|
+
loginFirst(`use ${f.label.toLowerCase()}`);
|
|
3311
|
+
}
|
|
3312
|
+
}
|
|
3313
|
+
}
|
|
3314
|
+
async function navigator(proj2, cfg, opts) {
|
|
3315
|
+
const { default: inquirer2 } = await import("inquirer");
|
|
3316
|
+
console.log(import_chalk26.default.bold(`
|
|
3317
|
+
${proj2.projectName} v${proj2.apiVersion} \u2014 configuration
|
|
3318
|
+
`));
|
|
3319
|
+
console.log(import_chalk26.default.dim("Tip: every change is one API call \u2014 add --verbose to see the curl equivalent.\n"));
|
|
3320
|
+
let blob = cfg;
|
|
3321
|
+
for (; ; ) {
|
|
3322
|
+
const { pick: pick2 } = await inquirer2.prompt([{
|
|
3323
|
+
type: "list",
|
|
3324
|
+
name: "pick",
|
|
3325
|
+
message: "Where to?",
|
|
3326
|
+
pageSize: 20,
|
|
3327
|
+
choices: [
|
|
3328
|
+
new inquirer2.Separator(import_chalk26.default.bold("\u2014 Settings \u2014")),
|
|
3329
|
+
...SETTING_GROUPS.map((g) => ({ name: g, value: { kind: "settings", g } })),
|
|
3330
|
+
new inquirer2.Separator(import_chalk26.default.bold("\u2014 Features \u2014")),
|
|
3331
|
+
...FEATURES.map((f) => ({ name: `${f.label} ${import_chalk26.default.dim(f.desc)}`, value: { kind: f.go } })),
|
|
3332
|
+
new inquirer2.Separator(),
|
|
3333
|
+
{ name: "Show all settings", value: { kind: "list" } },
|
|
3334
|
+
{ name: "Exit", value: { kind: "exit" } }
|
|
3335
|
+
]
|
|
3336
|
+
}]);
|
|
3337
|
+
try {
|
|
3338
|
+
switch (pick2.kind) {
|
|
3339
|
+
case "exit":
|
|
3340
|
+
return;
|
|
3341
|
+
case "list":
|
|
3342
|
+
printAll(proj2, blob);
|
|
3343
|
+
break;
|
|
3344
|
+
case "settings":
|
|
3345
|
+
blob = await settingsGroup(proj2, blob, pick2.g);
|
|
3346
|
+
break;
|
|
3347
|
+
case "transforms":
|
|
3348
|
+
await transformsMenu(proj2);
|
|
3349
|
+
break;
|
|
3350
|
+
case "mappings":
|
|
3351
|
+
await mappingsMenu(proj2);
|
|
3352
|
+
break;
|
|
3353
|
+
case "tenants":
|
|
3354
|
+
await tenantsMenu(proj2, opts);
|
|
3355
|
+
break;
|
|
3356
|
+
case "domains":
|
|
3357
|
+
await domainsMenu(proj2, opts);
|
|
3358
|
+
break;
|
|
3359
|
+
case "spec":
|
|
3360
|
+
await specMenu(proj2, opts);
|
|
3361
|
+
break;
|
|
3362
|
+
case "agents":
|
|
3363
|
+
await agentsMenu(proj2, opts);
|
|
3364
|
+
break;
|
|
3365
|
+
case "danger": {
|
|
3366
|
+
await runDelete(proj2.projectName, proj2.apiVersion, { team: opts.team });
|
|
3367
|
+
return;
|
|
3368
|
+
}
|
|
3369
|
+
}
|
|
3370
|
+
} catch (err) {
|
|
3371
|
+
console.error(import_chalk26.default.red(` ${err instanceof Error ? err.message : String(err)}`));
|
|
3372
|
+
}
|
|
3373
|
+
}
|
|
3374
|
+
}
|
|
3375
|
+
async function settingsGroup(proj2, cfg, group) {
|
|
3376
|
+
const { default: inquirer2 } = await import("inquirer");
|
|
3377
|
+
for (; ; ) {
|
|
3378
|
+
const items = SETTINGS.filter((s2) => s2.group === group);
|
|
3379
|
+
const { pick: pick2 } = await inquirer2.prompt([{
|
|
3380
|
+
type: "list",
|
|
3381
|
+
name: "pick",
|
|
3382
|
+
message: group + ":",
|
|
3383
|
+
pageSize: 16,
|
|
3384
|
+
choices: [
|
|
3385
|
+
...items.map((s2) => ({ name: `${s2.label.padEnd(30)} ${show(readSetting(s2, cfg))} ${import_chalk26.default.dim(s2.desc)}`, value: s2 })),
|
|
3386
|
+
new inquirer2.Separator(),
|
|
3387
|
+
{ name: "\u2190 Back", value: null }
|
|
3388
|
+
]
|
|
3389
|
+
}]);
|
|
3390
|
+
if (!pick2) return cfg;
|
|
3391
|
+
const s = pick2;
|
|
3392
|
+
let value;
|
|
3393
|
+
if (s.type === "boolean") {
|
|
3394
|
+
const cur = readSetting(s, cfg);
|
|
3395
|
+
const { v } = await inquirer2.prompt([{ type: "confirm", name: "v", message: `${s.label} on?`, default: cur !== false }]);
|
|
3396
|
+
value = v;
|
|
3397
|
+
} else if (s.type === "enum") {
|
|
3398
|
+
const { v } = await inquirer2.prompt([{ type: "list", name: "v", message: s.label + ":", choices: s.enum, default: readSetting(s, cfg) }]);
|
|
3399
|
+
value = v;
|
|
3400
|
+
} else if (s.type === "number") {
|
|
3401
|
+
const { v } = await inquirer2.prompt([{ type: "input", name: "v", message: `${s.label} (number):`, default: readSetting(s, cfg) }]);
|
|
3402
|
+
if (v === "" || Number.isNaN(Number(v))) {
|
|
3403
|
+
console.log(import_chalk26.default.yellow(" Not a number \u2014 unchanged."));
|
|
3404
|
+
continue;
|
|
3405
|
+
}
|
|
3406
|
+
value = Number(v);
|
|
3407
|
+
} else if (s.type === "json") {
|
|
3408
|
+
const cur = readSetting(s, cfg);
|
|
3409
|
+
const { v } = await inquirer2.prompt([{ type: "input", name: "v", message: `${s.label} (JSON, or "null" to clear):`, default: cur === void 0 ? "" : JSON.stringify(cur) }]);
|
|
3410
|
+
if (v === "") continue;
|
|
3411
|
+
value = parseValue(v);
|
|
3412
|
+
} else {
|
|
3413
|
+
const { v } = await inquirer2.prompt([{ type: "input", name: "v", message: s.label + ":", default: readSetting(s, cfg) }]);
|
|
3414
|
+
if (v === "") continue;
|
|
3415
|
+
value = v;
|
|
3416
|
+
}
|
|
3417
|
+
await patchSetting(proj2, s, value, cfg);
|
|
3418
|
+
cfg = await fetchConfigBlob(proj2);
|
|
3419
|
+
}
|
|
3420
|
+
}
|
|
3421
|
+
var FN_CHOICES = [
|
|
3422
|
+
{ name: "uppercase", value: "uppercase" },
|
|
3423
|
+
{ name: "lowercase", value: "lowercase" },
|
|
3424
|
+
{ name: "trim", value: "trim" },
|
|
3425
|
+
{ name: "url_encode", value: "url_encode" },
|
|
3426
|
+
{ name: "url_decode", value: "url_decode" },
|
|
3427
|
+
{ name: "base64url_encode", value: "base64url_encode" },
|
|
3428
|
+
{ name: "base64url_decode", value: "base64url_decode" },
|
|
3429
|
+
{ name: "hash \u2014 sha256/md5 digest", value: "hash" },
|
|
3430
|
+
{ name: "regex_extract \u2014 pull out a capture group", value: "regex_extract" },
|
|
3431
|
+
{ name: "concat \u2014 prepend/append text", value: "concat" }
|
|
3432
|
+
];
|
|
3433
|
+
async function buildFns(which) {
|
|
3434
|
+
const { default: inquirer2 } = await import("inquirer");
|
|
3435
|
+
const fns = [];
|
|
3436
|
+
for (; ; ) {
|
|
3437
|
+
const { fn } = await inquirer2.prompt([{
|
|
3438
|
+
type: "list",
|
|
3439
|
+
name: "fn",
|
|
3440
|
+
message: fns.length ? `${which}: ${fns.map((f) => f.fn).join(" \u2192 ")} \u2014 add another?` : `${which} \u2014 add a function?`,
|
|
3441
|
+
choices: [{ name: fns.length ? "\u2713 Done" : "No functions", value: null }, ...FN_CHOICES]
|
|
3442
|
+
}]);
|
|
3443
|
+
if (!fn) return fns;
|
|
3444
|
+
if (fn === "hash") {
|
|
3445
|
+
const { algorithm } = await inquirer2.prompt([{ type: "list", name: "algorithm", message: "Algorithm:", choices: ["sha256", "md5"] }]);
|
|
3446
|
+
fns.push({ fn, algorithm });
|
|
3447
|
+
} else if (fn === "regex_extract") {
|
|
3448
|
+
const a = await inquirer2.prompt([
|
|
3449
|
+
{ type: "input", name: "pattern", message: "Regex pattern:", validate: (s) => {
|
|
3450
|
+
try {
|
|
3451
|
+
new RegExp(s);
|
|
3452
|
+
return true;
|
|
3453
|
+
} catch {
|
|
3454
|
+
return "invalid regex";
|
|
3455
|
+
}
|
|
3456
|
+
} },
|
|
3457
|
+
{ type: "input", name: "group", message: "Capture group (default 1):", default: "1" }
|
|
3458
|
+
]);
|
|
3459
|
+
fns.push({ fn, pattern: a.pattern, ...Number(a.group) !== 1 ? { group: Number(a.group) } : {} });
|
|
3460
|
+
} else if (fn === "concat") {
|
|
3461
|
+
const a = await inquirer2.prompt([
|
|
3462
|
+
{ type: "input", name: "before", message: "Text before (empty = none):" },
|
|
3463
|
+
{ type: "input", name: "after", message: "Text after (empty = none):" }
|
|
3464
|
+
]);
|
|
3465
|
+
fns.push({ fn, ...a.before ? { before: a.before } : {}, ...a.after ? { after: a.after } : {} });
|
|
3466
|
+
} else {
|
|
3467
|
+
fns.push({ fn });
|
|
3468
|
+
}
|
|
3469
|
+
}
|
|
3470
|
+
}
|
|
3471
|
+
async function buildCondition(phase) {
|
|
3472
|
+
const { default: inquirer2 } = await import("inquirer");
|
|
3473
|
+
const { want } = await inquirer2.prompt([{
|
|
3474
|
+
type: "confirm",
|
|
3475
|
+
name: "want",
|
|
3476
|
+
default: false,
|
|
3477
|
+
message: "Only apply when a condition matches?"
|
|
3478
|
+
}]);
|
|
3479
|
+
if (!want) return void 0;
|
|
3480
|
+
const srcHint = phase === "response" ? "(header:x-foo, bodyvar:user.id, status:)" : "(header:x-foo, param:limit, bodyvar:user.id, jwt:sub)";
|
|
3481
|
+
const items = [];
|
|
3482
|
+
for (; ; ) {
|
|
3483
|
+
const a = await inquirer2.prompt([
|
|
3484
|
+
{ type: "input", name: "source", message: `Condition field ${import_chalk26.default.dim(srcHint)}:`, validate: (s) => !!s || "required" },
|
|
3485
|
+
{ type: "list", name: "operator", message: "Operator:", choices: [
|
|
3486
|
+
"eq",
|
|
3487
|
+
"neq",
|
|
3488
|
+
"contains",
|
|
3489
|
+
"starts_with",
|
|
3490
|
+
"ends_with",
|
|
3491
|
+
"exists",
|
|
3492
|
+
"not_exists",
|
|
3493
|
+
"regex"
|
|
3494
|
+
] }
|
|
3495
|
+
]);
|
|
3496
|
+
let value;
|
|
3497
|
+
if (a.operator !== "exists" && a.operator !== "not_exists") {
|
|
3498
|
+
const v = await inquirer2.prompt([{ type: "input", name: "value", message: a.operator === "regex" ? "Pattern:" : "Value:" }]);
|
|
3499
|
+
value = v.value;
|
|
3500
|
+
}
|
|
3501
|
+
items.push({ id: `c${items.length + 1}`, openParen: false, closeParen: false, source: a.source, operator: a.operator, ...value !== void 0 ? { value } : {} });
|
|
3502
|
+
const { more } = await inquirer2.prompt([{
|
|
3503
|
+
type: "list",
|
|
3504
|
+
name: "more",
|
|
3505
|
+
message: "Combine with another condition?",
|
|
3506
|
+
choices: [{ name: "\u2713 Done", value: null }, { name: "AND \u2026", value: "AND" }, { name: "OR \u2026", value: "OR" }]
|
|
3507
|
+
}]);
|
|
3508
|
+
if (!more) return items;
|
|
3509
|
+
items[items.length - 1].logicOp = more;
|
|
3510
|
+
}
|
|
3511
|
+
}
|
|
3512
|
+
function showCondition(cond) {
|
|
3513
|
+
if (!Array.isArray(cond) || !cond.length) return "";
|
|
3514
|
+
const s = cond.map((c) => `${c.source} ${c.operator}${c.value !== void 0 ? ` "${c.value}"` : ""}${c.logicOp ? ` ${c.logicOp}` : ""}`).join(" ");
|
|
3515
|
+
return import_chalk26.default.dim(` when ${s}`);
|
|
3516
|
+
}
|
|
3517
|
+
async function transformsMenu(proj2) {
|
|
3518
|
+
const { default: inquirer2 } = await import("inquirer");
|
|
3519
|
+
const base = `/projects/${proj2.projectId}/${proj2.apiVersion}/transforms`;
|
|
3520
|
+
for (; ; ) {
|
|
3521
|
+
const out = await admin({ method: "GET", path: base, summary: "List transform rules" });
|
|
3522
|
+
const rules = out?.rules ?? [];
|
|
3523
|
+
console.log();
|
|
3524
|
+
if (!rules.length) console.log(import_chalk26.default.dim(" No transform rules yet."));
|
|
3525
|
+
for (const r of rules) {
|
|
3526
|
+
const a = r.action ?? {};
|
|
3527
|
+
const fns = [...a.source_fns ?? [], ...a.dest_fns ?? []].map((f) => f.fn);
|
|
3528
|
+
const what = a.type === "hardcode" ? `${a.destination} = "${a.value}"` : a.type === "remove" ? `remove ${a.field}` : `${a.source} \u2192 ${a.destination}${a.lookup ? " (mapped)" : ""}${fns.length ? import_chalk26.default.dim(` via ${fns.join("\u2192")}`) : ""}`;
|
|
3529
|
+
console.log(` ${r.enabled ? import_chalk26.default.green("\u25CF") : import_chalk26.default.dim("\u25CB")} ${import_chalk26.default.bold(r.name)} ${import_chalk26.default.dim(`[${r.phase ?? "request"}]`)} ${what}${showCondition(r.condition)}`);
|
|
3530
|
+
}
|
|
3531
|
+
const { act } = await inquirer2.prompt([{
|
|
3532
|
+
type: "list",
|
|
3533
|
+
name: "act",
|
|
3534
|
+
message: "Transforms:",
|
|
3535
|
+
choices: [
|
|
3536
|
+
{ name: "Add a rule", value: "add" },
|
|
3537
|
+
...rules.length ? [
|
|
3538
|
+
{ name: "Enable/disable a rule", value: "toggle" },
|
|
3539
|
+
{ name: "Delete a rule", value: "delete" }
|
|
3540
|
+
] : [],
|
|
3541
|
+
{ name: import_chalk26.default.dim("Add from raw JSON (grouped conditions, lookup tables, \u2026)"), value: "raw" },
|
|
3542
|
+
{ name: "\u2190 Back", value: "back" }
|
|
3543
|
+
]
|
|
3544
|
+
}]);
|
|
3545
|
+
if (act === "back") return;
|
|
3546
|
+
if (act === "raw") {
|
|
3547
|
+
const { raw } = await inquirer2.prompt([{
|
|
3548
|
+
type: "input",
|
|
3549
|
+
name: "raw",
|
|
3550
|
+
message: "Rule JSON ({name, phase, enabled, action, condition?}):"
|
|
3551
|
+
}]);
|
|
3552
|
+
const body = parseValue(raw);
|
|
3553
|
+
if (!body || typeof body !== "object" || Array.isArray(body)) {
|
|
3554
|
+
console.log(import_chalk26.default.yellow(" Not a JSON object \u2014 skipped."));
|
|
3555
|
+
continue;
|
|
3556
|
+
}
|
|
3557
|
+
await admin({ method: "POST", path: base, body, summary: "Create transform rule (raw JSON)" });
|
|
3558
|
+
console.log(import_chalk26.default.green(" Rule created."));
|
|
3559
|
+
continue;
|
|
3560
|
+
}
|
|
3561
|
+
if (act === "add") {
|
|
3562
|
+
const ans = await inquirer2.prompt([
|
|
3563
|
+
{ type: "input", name: "name", message: "Rule name:", validate: (s) => !!s || "required" },
|
|
3564
|
+
{ type: "list", name: "phase", message: "Phase:", choices: [
|
|
3565
|
+
{ name: "request \u2014 before forwarding upstream", value: "request" },
|
|
3566
|
+
{ name: "response \u2014 on the reply before it returns", value: "response" }
|
|
3567
|
+
] },
|
|
3568
|
+
{ type: "list", name: "kind", message: "Action:", choices: [
|
|
3569
|
+
{ name: "Set a value (header/param/body field)", value: "hardcode" },
|
|
3570
|
+
{ name: "Copy/map one field to another", value: "map" },
|
|
3571
|
+
{ name: "Remove a field", value: "remove" }
|
|
3572
|
+
] }
|
|
3573
|
+
]);
|
|
3574
|
+
const fieldHint = import_chalk26.default.dim("(e.g. header:x-api-version, param:limit, bodyvar:user.id)");
|
|
3575
|
+
let action2;
|
|
3576
|
+
if (ans.kind === "hardcode") {
|
|
3577
|
+
const a = await inquirer2.prompt([
|
|
3578
|
+
{ type: "input", name: "destination", message: `Destination field ${fieldHint}:`, validate: (s) => !!s || "required" },
|
|
3579
|
+
{ type: "input", name: "value", message: "Value:" }
|
|
3580
|
+
]);
|
|
3581
|
+
action2 = { type: "hardcode", destination: a.destination, value: a.value };
|
|
3582
|
+
} else if (ans.kind === "remove") {
|
|
3583
|
+
const a = await inquirer2.prompt([
|
|
3584
|
+
{ type: "input", name: "field", message: `Field to remove ${fieldHint}:`, validate: (s) => !!s || "required" }
|
|
3585
|
+
]);
|
|
3586
|
+
action2 = { type: "remove", field: a.field };
|
|
3587
|
+
} else {
|
|
3588
|
+
const a = await inquirer2.prompt([
|
|
3589
|
+
{ type: "input", name: "source", message: `Source field ${fieldHint}:`, validate: (s) => !!s || "required" },
|
|
3590
|
+
{ type: "input", name: "destination", message: `Destination field ${fieldHint}:`, validate: (s) => !!s || "required" },
|
|
3591
|
+
{ type: "confirm", name: "strip", message: "Remove the source field after copying?", default: false }
|
|
3592
|
+
]);
|
|
3593
|
+
const source_fns = await buildFns("Transform the value as it is READ (source functions)");
|
|
3594
|
+
const dest_fns = await buildFns("Transform the value as it is WRITTEN (destination functions)");
|
|
3595
|
+
action2 = {
|
|
3596
|
+
type: "copy",
|
|
3597
|
+
source: a.source,
|
|
3598
|
+
destination: a.destination,
|
|
3599
|
+
...a.strip ? { strip_source: true } : {},
|
|
3600
|
+
...source_fns.length ? { source_fns } : {},
|
|
3601
|
+
...dest_fns.length ? { dest_fns } : {}
|
|
3602
|
+
};
|
|
3603
|
+
}
|
|
3604
|
+
const condition = await buildCondition(ans.phase);
|
|
3605
|
+
const spinner = (0, import_ora11.default)("Creating rule...").start();
|
|
3606
|
+
try {
|
|
3607
|
+
await admin({
|
|
3608
|
+
method: "POST",
|
|
3609
|
+
path: base,
|
|
3610
|
+
body: { name: ans.name, phase: ans.phase, enabled: true, action: action2, ...condition ? { condition } : {} },
|
|
3611
|
+
summary: `Create transform "${ans.name}"`
|
|
3612
|
+
});
|
|
3613
|
+
spinner.succeed(`Rule "${ans.name}" created.`);
|
|
3614
|
+
} catch (err) {
|
|
3615
|
+
spinner.fail("Create failed.");
|
|
3616
|
+
throw err;
|
|
3617
|
+
}
|
|
3618
|
+
} else {
|
|
3619
|
+
const { rule } = await inquirer2.prompt([{
|
|
3620
|
+
type: "list",
|
|
3621
|
+
name: "rule",
|
|
3622
|
+
message: act === "toggle" ? "Which rule?" : "Delete which rule?",
|
|
3623
|
+
choices: [...rules.map((r) => ({ name: `${r.name} ${import_chalk26.default.dim(`[${r.phase ?? "request"}]`)}`, value: r })), { name: "\u2190 Back", value: null }]
|
|
3624
|
+
}]);
|
|
3625
|
+
if (!rule) continue;
|
|
3626
|
+
if (act === "toggle") {
|
|
3627
|
+
const flipped = { ...rule, enabled: rule.enabled === false };
|
|
3628
|
+
await admin({ method: "PUT", path: `${base}/${rule.id}`, body: flipped, summary: `${flipped.enabled ? "Enable" : "Disable"} transform "${rule.name}"` });
|
|
3629
|
+
console.log(import_chalk26.default.green(` ${rule.name} \u2192 ${flipped.enabled ? "enabled" : "disabled"}`));
|
|
3630
|
+
} else {
|
|
3631
|
+
await admin({ method: "DELETE", path: `${base}/${rule.id}`, summary: `Delete transform "${rule.name}"` });
|
|
3632
|
+
console.log(import_chalk26.default.green(` ${rule.name} deleted.`));
|
|
3633
|
+
}
|
|
3634
|
+
}
|
|
3635
|
+
}
|
|
3636
|
+
}
|
|
3637
|
+
async function mappingsMenu(proj2) {
|
|
3638
|
+
const { default: inquirer2 } = await import("inquirer");
|
|
3639
|
+
const base = `/projects/${proj2.projectId}/${proj2.apiVersion}/mappings`;
|
|
3640
|
+
for (; ; ) {
|
|
3641
|
+
const out = await admin({ method: "GET", path: base, summary: "List mapping tables" });
|
|
3642
|
+
const tables = out?.mappings ?? out?.tables ?? [];
|
|
3643
|
+
console.log();
|
|
3644
|
+
if (!tables.length) console.log(import_chalk26.default.dim(" No mapping tables yet."));
|
|
3645
|
+
for (const t of tables) {
|
|
3646
|
+
console.log(` ${import_chalk26.default.bold(t.name)} ${import_chalk26.default.dim(`${t.entries?.length ?? "?"} entries${t.hide_map_values ? ", hidden" : ""}${t.encrypt_values ? ", encrypted" : ""}`)}`);
|
|
3647
|
+
}
|
|
3648
|
+
const { act } = await inquirer2.prompt([{
|
|
3649
|
+
type: "list",
|
|
3650
|
+
name: "act",
|
|
3651
|
+
message: "Mapping tables:",
|
|
3652
|
+
choices: [
|
|
3653
|
+
{ name: "Create a table", value: "add" },
|
|
3654
|
+
...tables.length ? [{ name: "Delete a table", value: "delete" }] : [],
|
|
3655
|
+
{ name: "\u2190 Back", value: "back" }
|
|
3656
|
+
]
|
|
3657
|
+
}]);
|
|
3658
|
+
if (act === "back") return;
|
|
3659
|
+
if (act === "add") {
|
|
3660
|
+
const a = await inquirer2.prompt([
|
|
3661
|
+
{ type: "input", name: "name", message: "Table name:", validate: (s) => !!s || "required" },
|
|
3662
|
+
{ type: "input", name: "entries", message: 'Entries as JSON, e.g. [{"from":"a","to":"b"}]:', default: "[]" }
|
|
3663
|
+
]);
|
|
3664
|
+
const entries2 = parseValue(a.entries);
|
|
3665
|
+
if (!Array.isArray(entries2)) {
|
|
3666
|
+
console.log(import_chalk26.default.yellow(" Entries must be a JSON array \u2014 not created."));
|
|
3667
|
+
continue;
|
|
3668
|
+
}
|
|
3669
|
+
await admin({ method: "POST", path: base, body: { name: a.name, entries: entries2 }, summary: `Create mapping table "${a.name}"` });
|
|
3670
|
+
console.log(import_chalk26.default.green(` Table "${a.name}" created.`));
|
|
3671
|
+
} else {
|
|
3672
|
+
const { table } = await inquirer2.prompt([{
|
|
3673
|
+
type: "list",
|
|
3674
|
+
name: "table",
|
|
3675
|
+
message: "Delete which table?",
|
|
3676
|
+
choices: [...tables.map((t) => ({ name: t.name, value: t })), { name: "\u2190 Back", value: null }]
|
|
3677
|
+
}]);
|
|
3678
|
+
if (!table) continue;
|
|
3679
|
+
await admin({ method: "DELETE", path: `${base}/${table.id}`, summary: `Delete mapping table "${table.name}"` });
|
|
3680
|
+
console.log(import_chalk26.default.green(` ${table.name} deleted.`));
|
|
3681
|
+
}
|
|
3682
|
+
}
|
|
3683
|
+
}
|
|
3684
|
+
async function tenantsMenu(proj2, opts) {
|
|
3685
|
+
const { default: inquirer2 } = await import("inquirer");
|
|
3686
|
+
const base = `/projects/${proj2.projectId}/${proj2.apiVersion}/tenants`;
|
|
3687
|
+
for (; ; ) {
|
|
3688
|
+
const out = await admin({ method: "GET", path: base, summary: "List attached tenants" });
|
|
3689
|
+
const tenants = out?.tenants ?? [];
|
|
3690
|
+
console.log();
|
|
3691
|
+
if (!tenants.length) console.log(import_chalk26.default.dim(" No tenants attached (consumers use the default tenant)."));
|
|
3692
|
+
for (const t of tenants) console.log(` ${import_chalk26.default.bold(t.tenant_name ?? t.name)} ${import_chalk26.default.dim(t.display_name ?? "")}`);
|
|
3693
|
+
const { act } = await inquirer2.prompt([{
|
|
3694
|
+
type: "list",
|
|
3695
|
+
name: "act",
|
|
3696
|
+
message: "Tenants:",
|
|
3697
|
+
choices: [
|
|
3698
|
+
{ name: "Attach a tenant", value: "attach" },
|
|
3699
|
+
...tenants.length ? [{ name: "Detach a tenant", value: "detach" }] : [],
|
|
3700
|
+
{ name: import_chalk26.default.dim("Manage team tenants (create/CORS/login) \u2192 `apiblaze tenant --help`"), value: "hint" },
|
|
3701
|
+
{ name: "\u2190 Back", value: "back" }
|
|
3702
|
+
]
|
|
3703
|
+
}]);
|
|
3704
|
+
if (act === "back") return;
|
|
3705
|
+
if (act === "hint") {
|
|
3706
|
+
console.log(import_chalk26.default.dim("\n apiblaze tenant list | create | attach | cors | delete\n"));
|
|
3707
|
+
continue;
|
|
3708
|
+
}
|
|
3709
|
+
if (act === "attach") {
|
|
3710
|
+
const { slug } = await inquirer2.prompt([{ type: "input", name: "slug", message: "Tenant slug to attach:", validate: (s) => !!s || "required" }]);
|
|
3711
|
+
await runTenantAttach(proj2.projectName, { tenant: slug, team: opts.team, apiversion: proj2.apiVersion });
|
|
3712
|
+
} else {
|
|
3713
|
+
const { t } = await inquirer2.prompt([{
|
|
3714
|
+
type: "list",
|
|
3715
|
+
name: "t",
|
|
3716
|
+
message: "Detach which tenant?",
|
|
3717
|
+
choices: [...tenants.map((x) => ({ name: x.tenant_name ?? x.name, value: x })), { name: "\u2190 Back", value: null }]
|
|
3718
|
+
}]);
|
|
3719
|
+
if (!t) continue;
|
|
3720
|
+
await admin({ method: "DELETE", path: `${base}/${encodeURIComponent(t.tenant_name ?? t.name)}`, summary: `Detach tenant ${t.tenant_name ?? t.name}` });
|
|
3721
|
+
console.log(import_chalk26.default.green(` Detached ${t.tenant_name ?? t.name}.`));
|
|
3722
|
+
}
|
|
3723
|
+
}
|
|
3724
|
+
}
|
|
3725
|
+
async function domainsMenu(proj2, opts) {
|
|
3726
|
+
const { default: inquirer2 } = await import("inquirer");
|
|
3727
|
+
for (; ; ) {
|
|
3728
|
+
const { act } = await inquirer2.prompt([{
|
|
3729
|
+
type: "list",
|
|
3730
|
+
name: "act",
|
|
3731
|
+
message: "Custom domains:",
|
|
3732
|
+
choices: [
|
|
3733
|
+
{ name: "List domains", value: "list" },
|
|
3734
|
+
{ name: "Add a domain (shows the DNS records to set)", value: "add" },
|
|
3735
|
+
{ name: "Remove a domain", value: "remove" },
|
|
3736
|
+
{ name: "Set what the bare URL serves (base domain)", value: "base" },
|
|
3737
|
+
{ name: "\u2190 Back", value: "back" }
|
|
3738
|
+
]
|
|
3739
|
+
}]);
|
|
3740
|
+
const common = { team: opts.team, apiversion: proj2.apiVersion };
|
|
3741
|
+
if (act === "back") return;
|
|
3742
|
+
if (act === "list") await runDomainList(proj2.projectName, common);
|
|
3743
|
+
else if (act === "add") {
|
|
3744
|
+
const { host } = await inquirer2.prompt([{ type: "input", name: "host", message: "Hostname (e.g. api.example.com):", validate: (s) => !!s || "required" }]);
|
|
3745
|
+
await runDomainAdd(proj2.projectName, { ...common, domain: host });
|
|
3746
|
+
} else if (act === "remove") {
|
|
3747
|
+
const { id } = await inquirer2.prompt([{ type: "input", name: "id", message: "Domain id (see List):", validate: (s) => !!s || "required" }]);
|
|
3748
|
+
await runDomainRemove(proj2.projectName, { ...common, id });
|
|
3749
|
+
} else {
|
|
3750
|
+
const { env } = await inquirer2.prompt([{ type: "input", name: "env", message: "Environment (e.g. prod):", default: "prod" }]);
|
|
3751
|
+
await runDomainSetBase(proj2.projectName, { ...common, env });
|
|
3752
|
+
}
|
|
3753
|
+
}
|
|
3754
|
+
}
|
|
3755
|
+
async function specMenu(proj2, opts) {
|
|
3756
|
+
const { default: inquirer2 } = await import("inquirer");
|
|
3757
|
+
const { act } = await inquirer2.prompt([{
|
|
3758
|
+
type: "list",
|
|
3759
|
+
name: "act",
|
|
3760
|
+
message: "OpenAPI spec & traffic:",
|
|
3761
|
+
choices: [
|
|
3762
|
+
{ name: "Print the stored spec", value: "get" },
|
|
3763
|
+
{ name: "Refresh the spec from its source", value: "refresh" },
|
|
3764
|
+
{ name: import_chalk26.default.dim("Build the spec by chatting over real traffic \u2192 agent"), value: "agent" },
|
|
3765
|
+
{ name: "\u2190 Back", value: "back" }
|
|
3766
|
+
]
|
|
3767
|
+
}]);
|
|
3768
|
+
if (act === "back") return;
|
|
3769
|
+
if (act === "get") await runSpecGet(proj2.projectName, { team: opts.team, apiversion: proj2.apiVersion });
|
|
3770
|
+
else if (act === "refresh") {
|
|
3771
|
+
await admin({ method: "POST", path: `/projects/${proj2.projectId}/${proj2.apiVersion}/refresh-spec`, summary: "Refresh spec from source" });
|
|
3772
|
+
console.log(import_chalk26.default.green(" Spec refresh triggered."));
|
|
3773
|
+
} else await runOpenapi(proj2.projectName, proj2.apiVersion);
|
|
3774
|
+
}
|
|
3775
|
+
async function agentsMenu(proj2, opts) {
|
|
3776
|
+
const { default: inquirer2 } = await import("inquirer");
|
|
3777
|
+
const { act } = await inquirer2.prompt([{
|
|
3778
|
+
type: "list",
|
|
3779
|
+
name: "act",
|
|
3780
|
+
message: "AI agents (billed per turn):",
|
|
3781
|
+
choices: [
|
|
3782
|
+
{ name: "General chat \u2014 build and run your APIs", value: "agent" },
|
|
3783
|
+
{ name: "OpenAPI builder \u2014 build your spec from real traffic", value: "openapi" },
|
|
3784
|
+
{ name: "Authorization designer \u2014 draft access rules", value: "authz" },
|
|
3785
|
+
{ name: "MCP builder \u2014 publish this API as an MCP server", value: "mcp" },
|
|
3786
|
+
{ name: "\u2190 Back", value: "back" }
|
|
3787
|
+
]
|
|
3788
|
+
}]);
|
|
3789
|
+
if (act === "back") return;
|
|
3790
|
+
if (act === "agent") await runAgent({ team: opts.team });
|
|
3791
|
+
else if (act === "openapi") await runOpenapi(proj2.projectName, proj2.apiVersion);
|
|
3792
|
+
else if (act === "authz") await runAuthz(proj2.projectName, proj2.apiVersion);
|
|
3793
|
+
else await runMcp(proj2.projectName, proj2.apiVersion, {});
|
|
3794
|
+
}
|
|
3795
|
+
|
|
3796
|
+
// src/commands/key.ts
|
|
3073
3797
|
var import_chalk27 = __toESM(require("chalk"));
|
|
3074
3798
|
var import_ora12 = __toESM(require("ora"));
|
|
3799
|
+
async function runApikeysMenu(opts) {
|
|
3800
|
+
await runKeyList(opts);
|
|
3801
|
+
if (opts.json) return;
|
|
3802
|
+
const { default: inquirer2 } = await import("inquirer");
|
|
3803
|
+
const { make } = await inquirer2.prompt([
|
|
3804
|
+
{ type: "confirm", name: "make", message: "Generate a new control-plane API key?", default: false }
|
|
3805
|
+
]);
|
|
3806
|
+
if (!make) return;
|
|
3807
|
+
const { desc } = await inquirer2.prompt([
|
|
3808
|
+
{ type: "input", name: "desc", message: "Description (optional):" }
|
|
3809
|
+
]);
|
|
3810
|
+
await runKeyMint({ ...opts, desc: desc || void 0 });
|
|
3811
|
+
}
|
|
3812
|
+
async function runKeyList(opts) {
|
|
3813
|
+
const { teamId, teamName } = await resolveTeam(opts.team);
|
|
3814
|
+
const out = await admin({
|
|
3815
|
+
method: "GET",
|
|
3816
|
+
path: `/teams/${encodeURIComponent(teamId)}/developer-keys`,
|
|
3817
|
+
summary: `List developer keys for team ${teamName ?? teamId}`
|
|
3818
|
+
});
|
|
3819
|
+
const keys = out?.keys ?? [];
|
|
3820
|
+
if (opts.json) {
|
|
3821
|
+
console.log(JSON.stringify(keys));
|
|
3822
|
+
return;
|
|
3823
|
+
}
|
|
3824
|
+
if (!keys.length) {
|
|
3825
|
+
console.log(import_chalk27.default.yellow("No developer keys."));
|
|
3826
|
+
return;
|
|
3827
|
+
}
|
|
3828
|
+
for (const k of keys) {
|
|
3829
|
+
console.log(` ${import_chalk27.default.bold(k.key_id ?? k.id)} ${import_chalk27.default.dim(k.description ?? "")} ${import_chalk27.default.dim(k.expires_at ?? "no expiry")}`);
|
|
3830
|
+
}
|
|
3831
|
+
}
|
|
3832
|
+
async function runKeyMint(opts) {
|
|
3833
|
+
const { teamId } = await resolveTeam(opts.team);
|
|
3834
|
+
const body = { role: "consumer-admin" };
|
|
3835
|
+
if (opts.desc) body.description = opts.desc;
|
|
3836
|
+
if (opts.expiresDays) body.expires_in_seconds = Number(opts.expiresDays) * 24 * 60 * 60;
|
|
3837
|
+
const spinner = (0, import_ora12.default)("Minting key...").start();
|
|
3838
|
+
try {
|
|
3839
|
+
const out = await admin({
|
|
3840
|
+
method: "POST",
|
|
3841
|
+
path: `/teams/${encodeURIComponent(teamId)}/developer-keys`,
|
|
3842
|
+
body,
|
|
3843
|
+
summary: `Mint a consumer-admin developer key`
|
|
3844
|
+
});
|
|
3845
|
+
spinner.succeed("Key minted.");
|
|
3846
|
+
if (opts.json) {
|
|
3847
|
+
console.log(JSON.stringify(out));
|
|
3848
|
+
return;
|
|
3849
|
+
}
|
|
3850
|
+
console.log(` ${import_chalk27.default.bold("key_id")}: ${out?.key_id}`);
|
|
3851
|
+
console.log(` ${import_chalk27.default.bold("key")}: ${import_chalk27.default.green(out?.key)} ${import_chalk27.default.dim("(shown once \u2014 store it now)")}`);
|
|
3852
|
+
if (out?.expires_at) console.log(` ${import_chalk27.default.dim("expires:")} ${out.expires_at}`);
|
|
3853
|
+
} catch (err) {
|
|
3854
|
+
spinner.fail("Mint failed.");
|
|
3855
|
+
throw err;
|
|
3856
|
+
}
|
|
3857
|
+
}
|
|
3858
|
+
async function runKeyRevoke(keyId, opts) {
|
|
3859
|
+
const { teamId } = await resolveTeam(opts.team);
|
|
3860
|
+
const spinner = (0, import_ora12.default)("Revoking key...").start();
|
|
3861
|
+
try {
|
|
3862
|
+
await admin({
|
|
3863
|
+
method: "DELETE",
|
|
3864
|
+
path: `/teams/${encodeURIComponent(teamId)}/developer-keys/${encodeURIComponent(keyId)}`,
|
|
3865
|
+
summary: `Revoke developer key ${keyId}`
|
|
3866
|
+
});
|
|
3867
|
+
spinner.succeed(`Revoked ${keyId}.`);
|
|
3868
|
+
} catch (err) {
|
|
3869
|
+
spinner.fail("Revoke failed.");
|
|
3870
|
+
throw err;
|
|
3871
|
+
}
|
|
3872
|
+
}
|
|
3873
|
+
|
|
3874
|
+
// src/commands/consumer.ts
|
|
3875
|
+
var import_chalk28 = __toESM(require("chalk"));
|
|
3876
|
+
var import_ora13 = __toESM(require("ora"));
|
|
3075
3877
|
var DEFAULT_SCOPE = "openid email profile offline_access";
|
|
3076
3878
|
var APIKEYS_BASE = process.env.APIBLAZE_APIKEYS_BASE || "https://apikeys.apiblaze.com";
|
|
3077
3879
|
async function consumerFetch(creds, suffix, init) {
|
|
@@ -3090,7 +3892,7 @@ async function consumerFetch(creds, suffix, init) {
|
|
|
3090
3892
|
function requireConsumer() {
|
|
3091
3893
|
const c = loadConsumer();
|
|
3092
3894
|
if (!c) {
|
|
3093
|
-
console.error(
|
|
3895
|
+
console.error(import_chalk28.default.red("Not logged in as a consumer. Run `apiblaze consumer login` first."));
|
|
3094
3896
|
process.exit(1);
|
|
3095
3897
|
}
|
|
3096
3898
|
return c;
|
|
@@ -3101,20 +3903,20 @@ async function runConsumerLogin(opts) {
|
|
|
3101
3903
|
let clientId = opts.client;
|
|
3102
3904
|
if (clientId) {
|
|
3103
3905
|
if (!tenant2) {
|
|
3104
|
-
console.error(
|
|
3906
|
+
console.error(import_chalk28.default.red("When using --client, also pass --tenant <slug> (it sets which portal/keys host to use)."));
|
|
3105
3907
|
process.exit(1);
|
|
3106
3908
|
}
|
|
3107
3909
|
} else {
|
|
3108
3910
|
requireAuth();
|
|
3109
3911
|
const { teamId, teamName } = await resolveTeam(opts.team);
|
|
3110
|
-
const spinner = (0,
|
|
3912
|
+
const spinner = (0, import_ora13.default)("Loading your tenants...").start();
|
|
3111
3913
|
const tdata = await admin({ method: "GET", path: `/teams/${encodeURIComponent(teamId)}/tenants?detail=1`, summary: `List tenants for ${teamName ?? teamId}` });
|
|
3112
3914
|
spinner.stop();
|
|
3113
3915
|
const tenants = (tdata?.tenants ?? []).map(
|
|
3114
3916
|
(t) => typeof t === "string" ? { tenant_name: t } : t
|
|
3115
3917
|
);
|
|
3116
3918
|
if (!tenants.length) {
|
|
3117
|
-
console.error(
|
|
3919
|
+
console.error(import_chalk28.default.red("This team has no tenants. Create one with `apiblaze tenant create`."));
|
|
3118
3920
|
process.exit(1);
|
|
3119
3921
|
}
|
|
3120
3922
|
if (!tenant2) {
|
|
@@ -3124,25 +3926,25 @@ async function runConsumerLogin(opts) {
|
|
|
3124
3926
|
tenant2 = chosen;
|
|
3125
3927
|
}
|
|
3126
3928
|
}
|
|
3127
|
-
const s2 = (0,
|
|
3929
|
+
const s2 = (0, import_ora13.default)("Finding the login app...").start();
|
|
3128
3930
|
const clients = await admin({ method: "GET", path: `/teams/${encodeURIComponent(teamId)}/tenants/${encodeURIComponent(tenant2)}/app-clients`, summary: `List app clients for ${tenant2}` }).catch(() => []);
|
|
3129
3931
|
s2.stop();
|
|
3130
3932
|
const usable = (Array.isArray(clients) ? clients : []).filter((c) => c && (c.client_id || c.clientId));
|
|
3131
3933
|
const pick2 = usable.find((c) => c.is_default || c.default) ?? usable.find((c) => c.verified !== false) ?? usable[0];
|
|
3132
3934
|
if (!pick2) {
|
|
3133
|
-
console.error(
|
|
3935
|
+
console.error(import_chalk28.default.red(`Tenant "${tenant2}" has no login app configured. Set one up in the dashboard (or \`apiblaze create\` with auth).`));
|
|
3134
3936
|
process.exit(1);
|
|
3135
3937
|
}
|
|
3136
3938
|
clientId = pick2.client_id ?? pick2.clientId;
|
|
3137
3939
|
}
|
|
3138
3940
|
const portalResource = `https://${tenant2}.portal.apiblaze.com/1.0.0`;
|
|
3139
|
-
console.log(`${
|
|
3941
|
+
console.log(`${import_chalk28.default.cyan("\u2192")} Logging in to ${import_chalk28.default.bold(tenant2)} as a consumer...`);
|
|
3140
3942
|
const result = await deviceLogin(clientId, DEFAULT_SCOPE, ({ verificationUri, userCode }) => {
|
|
3141
3943
|
console.log(`
|
|
3142
|
-
Open: ${
|
|
3143
|
-
console.log(` Code: ${
|
|
3944
|
+
Open: ${import_chalk28.default.underline(verificationUri)}`);
|
|
3945
|
+
console.log(` Code: ${import_chalk28.default.bold(userCode)}
|
|
3144
3946
|
`);
|
|
3145
|
-
console.log(
|
|
3947
|
+
console.log(import_chalk28.default.dim(" (opening your browser\u2026 waiting for you to finish)"));
|
|
3146
3948
|
}, portalResource);
|
|
3147
3949
|
const claims = result.idToken && decodeJwt2(result.idToken) || (decodeJwt2(result.accessToken) ?? {});
|
|
3148
3950
|
const creds = {
|
|
@@ -3157,7 +3959,7 @@ async function runConsumerLogin(opts) {
|
|
|
3157
3959
|
obtainedAt: Date.now()
|
|
3158
3960
|
};
|
|
3159
3961
|
saveConsumer(creds);
|
|
3160
|
-
console.log(
|
|
3962
|
+
console.log(import_chalk28.default.green(`\u2714 Logged in as consumer${creds.email ? ` ${creds.email}` : ""} on ${tenant2}.`));
|
|
3161
3963
|
}
|
|
3162
3964
|
async function runConsumerTokens(opts) {
|
|
3163
3965
|
const creds = requireConsumer();
|
|
@@ -3170,29 +3972,29 @@ async function runConsumerTokens(opts) {
|
|
|
3170
3972
|
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));
|
|
3171
3973
|
return;
|
|
3172
3974
|
}
|
|
3173
|
-
console.log(`${
|
|
3975
|
+
console.log(`${import_chalk28.default.cyan("Consumer")} ${import_chalk28.default.bold(fresh.email ?? fresh.tenant)} on ${import_chalk28.default.bold(fresh.tenant)}
|
|
3174
3976
|
`);
|
|
3175
|
-
console.log(`${
|
|
3977
|
+
console.log(`${import_chalk28.default.bold("access_token")} ${import_chalk28.default.dim("exp " + (exp(fresh.accessToken) ?? "?"))}
|
|
3176
3978
|
${fresh.accessToken}
|
|
3177
3979
|
`);
|
|
3178
|
-
if (fresh.idToken) console.log(`${
|
|
3980
|
+
if (fresh.idToken) console.log(`${import_chalk28.default.bold("id_token")} ${import_chalk28.default.dim("exp " + (exp(fresh.idToken) ?? "?"))}
|
|
3179
3981
|
${fresh.idToken}
|
|
3180
3982
|
`);
|
|
3181
|
-
if (fresh.refreshToken) console.log(`${
|
|
3983
|
+
if (fresh.refreshToken) console.log(`${import_chalk28.default.bold("refresh_token")}
|
|
3182
3984
|
${fresh.refreshToken}
|
|
3183
3985
|
`);
|
|
3184
|
-
console.log(
|
|
3986
|
+
console.log(import_chalk28.default.dim("These are your own tokens \u2014 keep them secret."));
|
|
3185
3987
|
}
|
|
3186
3988
|
async function runConsumerApikeys(opts) {
|
|
3187
3989
|
const creds = requireConsumer();
|
|
3188
3990
|
const { default: inquirer2 } = await import("inquirer");
|
|
3189
|
-
const spinner = (0,
|
|
3991
|
+
const spinner = (0, import_ora13.default)("Loading your API keys...").start();
|
|
3190
3992
|
const list = await consumerFetch(creds, "/apikeys");
|
|
3191
3993
|
const revealed = await consumerFetch(list.creds, "/apikeys/reveal").catch(() => ({ status: 0, data: null, creds: list.creds }));
|
|
3192
3994
|
spinner.stop();
|
|
3193
3995
|
if (list.status >= 400) {
|
|
3194
|
-
console.error(
|
|
3195
|
-
if (list.status === 401) console.error(
|
|
3996
|
+
console.error(import_chalk28.default.red(`Failed to list keys (${list.status}): ${list.data?.error ?? ""}`));
|
|
3997
|
+
if (list.status === 401) console.error(import_chalk28.default.dim("Your consumer session may have expired \u2014 run `apiblaze consumer login` again."));
|
|
3196
3998
|
process.exit(1);
|
|
3197
3999
|
}
|
|
3198
4000
|
const keys = list.data?.keys ?? [];
|
|
@@ -3200,16 +4002,16 @@ async function runConsumerApikeys(opts) {
|
|
|
3200
4002
|
if (opts.json) {
|
|
3201
4003
|
console.log(JSON.stringify({ keys, revealed: revealMap }, null, 2));
|
|
3202
4004
|
} else if (!keys.length) {
|
|
3203
|
-
console.log(
|
|
4005
|
+
console.log(import_chalk28.default.yellow("No API keys yet."));
|
|
3204
4006
|
} else {
|
|
3205
4007
|
for (const k of keys) {
|
|
3206
4008
|
const clear = revealMap[k.environment]?.key;
|
|
3207
|
-
const shown = clear ?
|
|
3208
|
-
const exp = k.expires_at ?
|
|
3209
|
-
console.log(` ${
|
|
4009
|
+
const shown = clear ? import_chalk28.default.green(clear) : import_chalk28.default.dim(`${k.key_prefix ?? ""}\u2026${k.key_suffix ?? ""}`);
|
|
4010
|
+
const exp = k.expires_at ? import_chalk28.default.dim(`exp ${k.expires_at}`) : import_chalk28.default.dim("no expiry");
|
|
4011
|
+
console.log(` ${import_chalk28.default.bold(k.environment ?? "")} ${shown} ${exp} ${import_chalk28.default.dim(k.description ?? "")}`);
|
|
3210
4012
|
}
|
|
3211
4013
|
if (Object.keys(revealMap).length === 0 && keys.some((k) => !k.expires_at)) {
|
|
3212
|
-
console.log(
|
|
4014
|
+
console.log(import_chalk28.default.dim("\n(Only expiring keys can be shown in clear; non-expiring keys show a prefix only.)"));
|
|
3213
4015
|
}
|
|
3214
4016
|
}
|
|
3215
4017
|
if (opts.json) return;
|
|
@@ -3223,7 +4025,7 @@ async function runConsumerApikeys(opts) {
|
|
|
3223
4025
|
const body = { environment: answers.environment };
|
|
3224
4026
|
if (answers.description) body.description = answers.description;
|
|
3225
4027
|
if (answers.expiresDays) body.expires_in_seconds = Number(answers.expiresDays) * 86400;
|
|
3226
|
-
const s2 = (0,
|
|
4028
|
+
const s2 = (0, import_ora13.default)("Creating key...").start();
|
|
3227
4029
|
const created = await consumerFetch(list.creds, "/apikeys", { method: "POST", body: JSON.stringify(body) });
|
|
3228
4030
|
if (created.status >= 400) {
|
|
3229
4031
|
s2.fail(`Create failed (${created.status}): ${created.data?.error ?? ""}`);
|
|
@@ -3231,13 +4033,13 @@ async function runConsumerApikeys(opts) {
|
|
|
3231
4033
|
}
|
|
3232
4034
|
s2.succeed("Key created.");
|
|
3233
4035
|
const key = created.data?.key ?? created.data?.fullKey;
|
|
3234
|
-
if (key) console.log(` ${
|
|
3235
|
-
else console.log(
|
|
4036
|
+
if (key) console.log(` ${import_chalk28.default.green(key)} ${import_chalk28.default.dim("(shown once \u2014 store it now)")}`);
|
|
4037
|
+
else console.log(import_chalk28.default.dim(" Key created; run `apiblaze consumer apikeys` to reveal it if it expires."));
|
|
3236
4038
|
}
|
|
3237
4039
|
|
|
3238
4040
|
// src/commands/sidecar.ts
|
|
3239
|
-
var
|
|
3240
|
-
var
|
|
4041
|
+
var import_chalk29 = __toESM(require("chalk"));
|
|
4042
|
+
var import_ora14 = __toESM(require("ora"));
|
|
3241
4043
|
var fs7 = __toESM(require("fs"));
|
|
3242
4044
|
var path4 = __toESM(require("path"));
|
|
3243
4045
|
init_auth();
|
|
@@ -3277,18 +4079,18 @@ function upsertEnvLocal(root, token) {
|
|
|
3277
4079
|
}
|
|
3278
4080
|
function installSidecarPackage(root) {
|
|
3279
4081
|
if (fs7.existsSync(path4.join(root, "node_modules", "apiblaze", "package.json"))) {
|
|
3280
|
-
console.log(` ${
|
|
4082
|
+
console.log(` ${import_chalk29.default.green("\u2713")} apiblaze package already installed`);
|
|
3281
4083
|
return;
|
|
3282
4084
|
}
|
|
3283
4085
|
const has = (f) => fs7.existsSync(path4.join(root, f));
|
|
3284
4086
|
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" };
|
|
3285
|
-
const spinner = (0,
|
|
4087
|
+
const spinner = (0, import_ora14.default)(`Installing the apiblaze package (${pm.cmd})\u2026`).start();
|
|
3286
4088
|
try {
|
|
3287
4089
|
const { execSync } = require("child_process");
|
|
3288
4090
|
execSync(`${pm.cmd} ${pm.add} apiblaze`, { cwd: root, stdio: "ignore" });
|
|
3289
4091
|
spinner.succeed("Installed apiblaze (the sidecar runtime).");
|
|
3290
4092
|
} catch {
|
|
3291
|
-
spinner.warn(`Couldn't auto-install \u2014 run ${
|
|
4093
|
+
spinner.warn(`Couldn't auto-install \u2014 run ${import_chalk29.default.cyan(`${pm.cmd} ${pm.add} apiblaze`)} yourself before ${import_chalk29.default.cyan("npm run dev")}.`);
|
|
3292
4094
|
}
|
|
3293
4095
|
}
|
|
3294
4096
|
function readEnvKey(root) {
|
|
@@ -3343,18 +4145,24 @@ export const dynamic = "force-dynamic";
|
|
|
3343
4145
|
async function probe(u: string) {
|
|
3344
4146
|
try {
|
|
3345
4147
|
const res = await fetch(u, { headers: { "x-api-key": "demo-secret-value" }, cache: "no-store" });
|
|
3346
|
-
|
|
4148
|
+
const echo = await res.json().catch(() => ({}));
|
|
4149
|
+
// A routed call returns THROUGH APIblaze (Cloudflare) \u2192 cf-ray / server:cloudflare.
|
|
4150
|
+
// A direct call to httpbingo (fly.io) has neither. This is the reliable signal.
|
|
4151
|
+
const server = (res.headers.get("server") || "").toLowerCase();
|
|
4152
|
+
const routed = Boolean(res.headers.get("cf-ray")) || server.indexOf("cloudflare") >= 0;
|
|
4153
|
+
return { url: u, status: res.status, routed, echo };
|
|
3347
4154
|
} catch (e: any) { return { url: u, error: e?.message ?? String(e) }; }
|
|
3348
4155
|
}
|
|
3349
4156
|
|
|
3350
4157
|
export default async function Page() {
|
|
3351
4158
|
if (process.env.NODE_ENV === "production") return <main style={{ padding: 24 }}>Inspector disabled in production.</main>;
|
|
3352
4159
|
const r: any = await probe("https://httpbingo.org/headers");
|
|
3353
|
-
const
|
|
3354
|
-
//
|
|
3355
|
-
const
|
|
4160
|
+
const routed = Boolean(r && r.routed);
|
|
4161
|
+
// An anonymous trial proxy routes, but completing the upstream call needs credits.
|
|
4162
|
+
const needsCredits = Boolean(r && r.echo && r.echo.claim_required);
|
|
3356
4163
|
const card: any = { background: "#0b0f17", border: "1px solid #232a36", borderRadius: 14, padding: 20 };
|
|
3357
4164
|
const pill: any = { display: "inline-block", padding: "4px 10px", borderRadius: 999, fontSize: 12, fontWeight: 700 };
|
|
4165
|
+
const codeBox: any = { background: "#070a10", border: "1px solid #232a36", borderRadius: 8, padding: 12, margin: 0, color: "#8ab4ff", overflowX: "auto" };
|
|
3358
4166
|
return (
|
|
3359
4167
|
<main style={{ fontFamily: "ui-monospace, SFMono-Regular, Menlo, monospace", background: "#070a10", minHeight: "100vh", padding: 32, color: "#e6e9ef", lineHeight: 1.6 }}>
|
|
3360
4168
|
<div style={{ maxWidth: 720, margin: "0 auto" }}>
|
|
@@ -3369,16 +4177,23 @@ export default async function Page() {
|
|
|
3369
4177
|
? <span style={{ ...pill, background: "#0f2e20", color: "#4ade80" }}>\u25CF routed through APIblaze</span>
|
|
3370
4178
|
: <span style={{ ...pill, background: "#33280c", color: "#fbbf24" }}>\u25CB going direct \u2014 not approved</span>}
|
|
3371
4179
|
</div>
|
|
3372
|
-
{routed ? (
|
|
4180
|
+
{routed && needsCredits ? (
|
|
4181
|
+
<div style={{ color: "#9aa4b2", fontSize: 14 }}>
|
|
4182
|
+
<p style={{ margin: "0 0 10px" }}>Your call <b style={{ color: "#4ade80" }}>routed through APIblaze</b> \u2014 auth, rate limits, and observability, with no code change. It reached your proxy (not httpbingo directly).</p>
|
|
4183
|
+
<p style={{ margin: "0 0 6px", color: "#e6e9ef" }}>This is an anonymous trial proxy, so completing the upstream call needs credits. Keep it and add credits by claiming your workspace:</p>
|
|
4184
|
+
<pre style={codeBox}>apiblaze login && apiblaze claim</pre>
|
|
4185
|
+
<p style={{ margin: "10px 0 0" }}>\u2026then reload \u2014 the call completes end to end.</p>
|
|
4186
|
+
</div>
|
|
4187
|
+
) : routed ? (
|
|
3373
4188
|
<p style={{ color: "#9aa4b2", fontSize: 14, margin: 0 }}>
|
|
3374
|
-
|
|
4189
|
+
Your call <b style={{ color: "#4ade80" }}>routed through APIblaze</b> \u2014 auth, rate limits, and observability \u2014 and your app code never changed. The proxy carried your original <code>x-api-key</code> upstream so httpbingo still sees it.
|
|
3375
4190
|
</p>
|
|
3376
4191
|
) : (
|
|
3377
4192
|
<div style={{ color: "#9aa4b2", fontSize: 14 }}>
|
|
3378
4193
|
<p style={{ margin: "0 0 10px" }}>This call went <b style={{ color: "#fbbf24" }}>direct</b> \u2014 httpbingo.org isn't approved yet, so the sidecar left it untouched and reported it as a candidate.</p>
|
|
3379
4194
|
<p style={{ margin: "0 0 6px", color: "#e6e9ef" }}>Route it through APIblaze:</p>
|
|
3380
|
-
<pre style={
|
|
3381
|
-
<p style={{ margin: "10px 0 0" }}>\u2026wait ~5 min, then <b style={{ color: "#e6e9ef" }}>reload</b>. The badge above turns green.</p>
|
|
4195
|
+
<pre style={codeBox}>npx apiblaze sidecar approve httpbingo.org</pre>
|
|
4196
|
+
<p style={{ margin: "10px 0 0" }}>\u2026wait ~5 min (or restart the dev server), then <b style={{ color: "#e6e9ef" }}>reload</b>. The badge above turns green.</p>
|
|
3382
4197
|
</div>
|
|
3383
4198
|
)}
|
|
3384
4199
|
</div>
|
|
@@ -3414,7 +4229,7 @@ async function runAnonymousInit(root, router, opts) {
|
|
|
3414
4229
|
const { sidecarInitAnonymous: sidecarInitAnonymous2 } = await Promise.resolve().then(() => (init_api(), api_exports));
|
|
3415
4230
|
const { saveAnonCred: saveAnonCred2, clearAnonCred: clearAnonCred2 } = await Promise.resolve().then(() => (init_anon_cred(), anon_cred_exports));
|
|
3416
4231
|
if (opts.newSession) clearAnonCred2();
|
|
3417
|
-
const spinner = (0,
|
|
4232
|
+
const spinner = (0, import_ora14.default)("Setting up a sidecar (no login needed)...").start();
|
|
3418
4233
|
let out;
|
|
3419
4234
|
try {
|
|
3420
4235
|
out = await sidecarInitAnonymous2();
|
|
@@ -3426,29 +4241,29 @@ async function runAnonymousInit(root, router, opts) {
|
|
|
3426
4241
|
if (out.cp_key && out.team_id) saveAnonCred2(out.cp_key, out.team_id, out.claim_code);
|
|
3427
4242
|
const envState = upsertEnvLocal(root, out.token);
|
|
3428
4243
|
ensureGitignored(root);
|
|
3429
|
-
console.log(` ${
|
|
3430
|
-
console.log(` ${
|
|
4244
|
+
console.log(` ${import_chalk29.default.green("\u2713")} .env.local ${envState} (APIBLAZE_API_KEY) \u2014 gitignored`);
|
|
4245
|
+
console.log(` ${import_chalk29.default.green("\u2713")} instrumentation.ts ${wireInstrumentation(root)}`);
|
|
3431
4246
|
installSidecarPackage(root);
|
|
3432
4247
|
let inspectorPath = null;
|
|
3433
4248
|
if (!opts.noInspector) {
|
|
3434
4249
|
inspectorPath = generateInspector(root, router);
|
|
3435
|
-
if (inspectorPath) console.log(` ${
|
|
4250
|
+
if (inspectorPath) console.log(` ${import_chalk29.default.green("\u2713")} inspector at ${inspectorPath}`);
|
|
3436
4251
|
}
|
|
3437
4252
|
console.log("");
|
|
3438
|
-
console.log(
|
|
3439
|
-
console.log(` 1. ${
|
|
4253
|
+
console.log(import_chalk29.default.bold("Done (no account needed). What happens next:"));
|
|
4254
|
+
console.log(` 1. ${import_chalk29.default.cyan("npm run dev")} and use your app.`);
|
|
3440
4255
|
console.log(` 2. Each external origin your app calls is logged in the console \u2014 approve one with:`);
|
|
3441
|
-
console.log(` ${
|
|
4256
|
+
console.log(` ${import_chalk29.default.cyan("apiblaze sidecar approve api.stripe.com")} (no login needed)`);
|
|
3442
4257
|
console.log("");
|
|
3443
|
-
console.log(
|
|
3444
|
-
console.log(` ${
|
|
3445
|
-
console.log(
|
|
4258
|
+
console.log(import_chalk29.default.bold(" \u{1F511} Keep your setup \u2014 claim it into an account:"));
|
|
4259
|
+
console.log(` ${import_chalk29.default.cyan("apiblaze login")} then ${import_chalk29.default.cyan("apiblaze claim")} ${import_chalk29.default.dim("(no code needed here)")}`);
|
|
4260
|
+
console.log(import_chalk29.default.dim(` From another machine: apiblaze claim ${out.claim_code} \xB7 expires in 30 days`));
|
|
3446
4261
|
}
|
|
3447
4262
|
async function runSidecar(opts) {
|
|
3448
4263
|
const root = path4.resolve(opts.dir ?? process.cwd());
|
|
3449
4264
|
const detected = detectNextProject(root);
|
|
3450
4265
|
if (!detected.found) {
|
|
3451
|
-
console.log(
|
|
4266
|
+
console.log(import_chalk29.default.yellow(`No Next.js project detected in ${root}.`));
|
|
3452
4267
|
console.log("Create one (e.g. `npx create-next-app`) and re-run `apiblaze init` inside it.");
|
|
3453
4268
|
return;
|
|
3454
4269
|
}
|
|
@@ -3459,10 +4274,10 @@ async function runSidecar(opts) {
|
|
|
3459
4274
|
if (!loadCredentials()) {
|
|
3460
4275
|
upsertEnvLocal(root, readEnvKey(root));
|
|
3461
4276
|
ensureGitignored(root);
|
|
3462
|
-
console.log(` ${
|
|
3463
|
-
console.log(` ${
|
|
4277
|
+
console.log(` ${import_chalk29.default.green("\u2713")} .env.local present (APIBLAZE_API_KEY) \u2014 reusing`);
|
|
4278
|
+
console.log(` ${import_chalk29.default.green("\u2713")} instrumentation.ts ${wireInstrumentation(root)}`);
|
|
3464
4279
|
installSidecarPackage(root);
|
|
3465
|
-
console.log(
|
|
4280
|
+
console.log(import_chalk29.default.dim(" Log in and run `apiblaze claim <code>` to keep this setup, or `apiblaze login` to manage it."));
|
|
3466
4281
|
return;
|
|
3467
4282
|
}
|
|
3468
4283
|
const { teamId, teamName } = await resolveTeam(opts.team);
|
|
@@ -3471,7 +4286,7 @@ async function runSidecar(opts) {
|
|
|
3471
4286
|
const mustMint = !existingKey || opts.rotate || switchingTeam;
|
|
3472
4287
|
let token = existingKey ?? "";
|
|
3473
4288
|
if (mustMint) {
|
|
3474
|
-
const spinner = (0,
|
|
4289
|
+
const spinner = (0, import_ora14.default)(existingKey ? "Re-establishing the sidecar (minting a fresh invoke key)..." : "Setting up the sidecar (tenant + non-expiring invoke key)...").start();
|
|
3475
4290
|
try {
|
|
3476
4291
|
const out = await admin({
|
|
3477
4292
|
method: "POST",
|
|
@@ -3485,39 +4300,39 @@ async function runSidecar(opts) {
|
|
|
3485
4300
|
throw err;
|
|
3486
4301
|
}
|
|
3487
4302
|
} else {
|
|
3488
|
-
console.log(
|
|
4303
|
+
console.log(import_chalk29.default.dim(` Reusing the existing APIBLAZE_API_KEY (run with --rotate to mint a fresh one, or --team <name> to switch teams).`));
|
|
3489
4304
|
}
|
|
3490
4305
|
const envState = upsertEnvLocal(root, token);
|
|
3491
4306
|
ensureGitignored(root);
|
|
3492
|
-
console.log(` ${
|
|
4307
|
+
console.log(` ${import_chalk29.default.green("\u2713")} .env.local ${envState} (APIBLAZE_API_KEY) \u2014 gitignored`);
|
|
3493
4308
|
const wireState = wireInstrumentation(root);
|
|
3494
|
-
console.log(` ${
|
|
4309
|
+
console.log(` ${import_chalk29.default.green("\u2713")} instrumentation.ts ${wireState}`);
|
|
3495
4310
|
installSidecarPackage(root);
|
|
3496
4311
|
let inspectorPath = null;
|
|
3497
4312
|
if (!opts.noInspector) {
|
|
3498
4313
|
inspectorPath = generateInspector(root, detected.router);
|
|
3499
|
-
if (inspectorPath) console.log(` ${
|
|
4314
|
+
if (inspectorPath) console.log(` ${import_chalk29.default.green("\u2713")} inspector at ${inspectorPath}`);
|
|
3500
4315
|
}
|
|
3501
4316
|
console.log("");
|
|
3502
|
-
console.log(
|
|
3503
|
-
console.log(` 1. ${
|
|
3504
|
-
console.log(` 2. The origins your app calls appear as ${
|
|
3505
|
-
console.log(` 3. Approve the ones to route: ${
|
|
4317
|
+
console.log(import_chalk29.default.bold("Done. What happens next:"));
|
|
4318
|
+
console.log(` 1. ${import_chalk29.default.cyan("npm run dev")} and use your app \u2014 it works exactly as before (all calls go direct).`);
|
|
4319
|
+
console.log(` 2. The origins your app calls appear as ${import_chalk29.default.bold("candidates")} \u2014 list them: ${import_chalk29.default.cyan("apiblaze sidecar")}`);
|
|
4320
|
+
console.log(` 3. Approve the ones to route: ${import_chalk29.default.cyan("apiblaze sidecar approve api.stripe.com")} (or in the dashboard)`);
|
|
3506
4321
|
console.log(` \u2026within ~5 min your app starts routing that origin through APIblaze.`);
|
|
3507
|
-
if (inspectorPath) console.log(` \u2022 Try it now: open ${
|
|
3508
|
-
if (switchingTeam) console.log(
|
|
4322
|
+
if (inspectorPath) console.log(` \u2022 Try it now: open ${import_chalk29.default.underline("http://localhost:3000/abz-inspector")} (dev only; rm ${path4.dirname(inspectorPath)} before shipping)`);
|
|
4323
|
+
if (switchingTeam) console.log(import_chalk29.default.dim(` \u2022 Approved origins are per-team \u2014 re-approve them on ${teamName ?? teamId} with \`apiblaze sidecar approve <origin>\`.`));
|
|
3509
4324
|
console.log("");
|
|
3510
|
-
console.log(
|
|
3511
|
-
console.log(
|
|
3512
|
-
console.log(
|
|
4325
|
+
console.log(import_chalk29.default.dim(" Manage: apiblaze sidecar (list/approve/deny/remove)"));
|
|
4326
|
+
console.log(import_chalk29.default.dim(" Rotate: apiblaze init --rotate \xB7 Switch team: apiblaze init --team <name>"));
|
|
4327
|
+
console.log(import_chalk29.default.dim(" Turn off: set APIBLAZE_SIDECAR=off in .env.local (flip back to on anytime; key stays put)."));
|
|
3513
4328
|
console.log("");
|
|
3514
|
-
console.log(
|
|
3515
|
-
console.log(
|
|
4329
|
+
console.log(import_chalk29.default.yellow(" \u26A0 APIBLAZE_API_KEY is long-lived and lets a holder call your team's proxies. Never commit it."));
|
|
4330
|
+
console.log(import_chalk29.default.dim(" Your control-plane login stays in ~/.apiblaze \u2014 it never entered this project."));
|
|
3516
4331
|
}
|
|
3517
4332
|
|
|
3518
4333
|
// src/commands/origins.ts
|
|
3519
|
-
var
|
|
3520
|
-
var
|
|
4334
|
+
var import_chalk30 = __toESM(require("chalk"));
|
|
4335
|
+
var import_ora15 = __toESM(require("ora"));
|
|
3521
4336
|
init_auth();
|
|
3522
4337
|
init_anon_cred();
|
|
3523
4338
|
async function runOriginsList(opts) {
|
|
@@ -3525,7 +4340,7 @@ async function runOriginsList(opts) {
|
|
|
3525
4340
|
if (!loadCredentials()) {
|
|
3526
4341
|
const cred = loadAnonCred();
|
|
3527
4342
|
if (!cred) {
|
|
3528
|
-
console.log(
|
|
4343
|
+
console.log(import_chalk30.default.yellow("No anonymous workspace here. Run `apiblaze init` first."));
|
|
3529
4344
|
return;
|
|
3530
4345
|
}
|
|
3531
4346
|
out = await cpFetch(cred.cp_key, `/teams/${encodeURIComponent(cred.team_id)}/sidecar/candidates`, { method: "GET" });
|
|
@@ -3543,30 +4358,30 @@ async function runOriginsList(opts) {
|
|
|
3543
4358
|
}
|
|
3544
4359
|
const routed = out.routed ?? [];
|
|
3545
4360
|
const candidates = out.candidates ?? [];
|
|
3546
|
-
console.log(
|
|
4361
|
+
console.log(import_chalk30.default.bold(`
|
|
3547
4362
|
Routed through APIblaze (${routed.length})`));
|
|
3548
|
-
if (!routed.length) console.log(
|
|
3549
|
-
for (const r of routed) console.log(` ${
|
|
3550
|
-
console.log(
|
|
4363
|
+
if (!routed.length) console.log(import_chalk30.default.dim(" none yet"));
|
|
4364
|
+
for (const r of routed) console.log(` ${import_chalk30.default.green("\u25CF")} ${r.sidecar_origin} ${import_chalk30.default.dim(`\u2192 ${r.project_id}`)}`);
|
|
4365
|
+
console.log(import_chalk30.default.bold(`
|
|
3551
4366
|
Candidates \u2014 going direct, not yet approved (${candidates.length})`));
|
|
3552
|
-
if (!candidates.length) console.log(
|
|
4367
|
+
if (!candidates.length) console.log(import_chalk30.default.dim(" none \u2014 run your app to discover the origins it calls"));
|
|
3553
4368
|
for (const c of candidates) {
|
|
3554
|
-
console.log(` ${
|
|
4369
|
+
console.log(` ${import_chalk30.default.yellow("\u25CB")} ${c.origin} ${import_chalk30.default.dim(`seen ${c.request_count}\xD7, last ${c.last_seen}`)}`);
|
|
3555
4370
|
}
|
|
3556
4371
|
if (candidates.length) {
|
|
3557
|
-
console.log(
|
|
4372
|
+
console.log(import_chalk30.default.dim(`
|
|
3558
4373
|
Approve: apiblaze sidecar approve ${candidates[0].origin.replace("https://", "")}`));
|
|
3559
|
-
console.log(
|
|
4374
|
+
console.log(import_chalk30.default.dim(` Dismiss: apiblaze sidecar deny ${candidates[0].origin.replace("https://", "")}`));
|
|
3560
4375
|
}
|
|
3561
4376
|
}
|
|
3562
4377
|
async function runOriginsApprove(origin, opts) {
|
|
3563
4378
|
if (!loadCredentials()) {
|
|
3564
4379
|
const cred = loadAnonCred();
|
|
3565
4380
|
if (!cred) {
|
|
3566
|
-
console.error(
|
|
4381
|
+
console.error(import_chalk30.default.red("Not logged in and no anonymous workspace. Run `apiblaze init` first."));
|
|
3567
4382
|
process.exit(1);
|
|
3568
4383
|
}
|
|
3569
|
-
const spinner2 = (0,
|
|
4384
|
+
const spinner2 = (0, import_ora15.default)(`Approving ${origin} (anonymous)...`).start();
|
|
3570
4385
|
try {
|
|
3571
4386
|
const out = await cpFetch(cred.cp_key, `/teams/${encodeURIComponent(cred.team_id)}/sidecar/approve`, { method: "POST", body: JSON.stringify({ origin }) });
|
|
3572
4387
|
spinner2.succeed(`Approved ${origin} \u2192 proxy ${out.project_id}. Routing within ~5 min.`);
|
|
@@ -3577,7 +4392,7 @@ async function runOriginsApprove(origin, opts) {
|
|
|
3577
4392
|
return;
|
|
3578
4393
|
}
|
|
3579
4394
|
const { teamId } = await resolveTeam(opts.team);
|
|
3580
|
-
const spinner = (0,
|
|
4395
|
+
const spinner = (0, import_ora15.default)(`Approving ${origin}...`).start();
|
|
3581
4396
|
try {
|
|
3582
4397
|
const out = await admin({
|
|
3583
4398
|
method: "POST",
|
|
@@ -3594,7 +4409,7 @@ async function runOriginsApprove(origin, opts) {
|
|
|
3594
4409
|
}
|
|
3595
4410
|
async function runOriginsDeny(origin, opts) {
|
|
3596
4411
|
const { teamId } = await resolveTeam(opts.team);
|
|
3597
|
-
const spinner = (0,
|
|
4412
|
+
const spinner = (0, import_ora15.default)(`Dismissing ${origin}...`).start();
|
|
3598
4413
|
try {
|
|
3599
4414
|
await admin({ method: "POST", path: `/teams/${encodeURIComponent(teamId)}/sidecar/dismiss`, body: { origin }, summary: `Dismiss sidecar origin ${origin}` });
|
|
3600
4415
|
spinner.succeed(`Dismissed ${origin}. It won't be suggested again.`);
|
|
@@ -3605,7 +4420,7 @@ async function runOriginsDeny(origin, opts) {
|
|
|
3605
4420
|
}
|
|
3606
4421
|
async function runOriginsRemove(origin, opts) {
|
|
3607
4422
|
const { teamId } = await resolveTeam(opts.team);
|
|
3608
|
-
const spinner = (0,
|
|
4423
|
+
const spinner = (0, import_ora15.default)(`Removing the proxy for ${origin}...`).start();
|
|
3609
4424
|
try {
|
|
3610
4425
|
await admin({ method: "POST", path: `/teams/${encodeURIComponent(teamId)}/sidecar/remove`, body: { origin }, summary: `Un-route sidecar origin ${origin}` });
|
|
3611
4426
|
spinner.succeed(`Removed ${origin}. Your app will stop routing it (goes direct) within ~5 min.`);
|
|
@@ -3615,6 +4430,131 @@ async function runOriginsRemove(origin, opts) {
|
|
|
3615
4430
|
}
|
|
3616
4431
|
}
|
|
3617
4432
|
|
|
4433
|
+
// src/commands/op.ts
|
|
4434
|
+
var import_chalk31 = __toESM(require("chalk"));
|
|
4435
|
+
init_auth();
|
|
4436
|
+
init_types();
|
|
4437
|
+
var OPERATOR_EMAILS = /* @__PURE__ */ new Set(["julienpmjacquet@gmail.com", "chkev@umich.edu"]);
|
|
4438
|
+
var DASHBOARD_BASE6 = process.env.APIBLAZE_DASHBOARD_BASE || "https://dashboard.apiblaze.com";
|
|
4439
|
+
function isOperatorLogin() {
|
|
4440
|
+
const email = loadCredentials()?.email?.toLowerCase();
|
|
4441
|
+
return !!email && OPERATOR_EMAILS.has(email);
|
|
4442
|
+
}
|
|
4443
|
+
async function opCall(call) {
|
|
4444
|
+
const token = getAccessToken();
|
|
4445
|
+
const res = await fetch(`${DASHBOARD_BASE6}/api/cli/op`, {
|
|
4446
|
+
method: "POST",
|
|
4447
|
+
headers: { "Content-Type": "application/json", Authorization: `Bearer ${token}` },
|
|
4448
|
+
body: JSON.stringify({ path: call.path, method: call.method, body: call.body })
|
|
4449
|
+
});
|
|
4450
|
+
let data = null;
|
|
4451
|
+
try {
|
|
4452
|
+
data = await res.json();
|
|
4453
|
+
} catch {
|
|
4454
|
+
}
|
|
4455
|
+
recordCall({ method: call.method, path: call.path, body: call.body, status: res.status, summary: call.summary });
|
|
4456
|
+
if (!res.ok) {
|
|
4457
|
+
const msg = data?.details ?? data?.error ?? res.statusText;
|
|
4458
|
+
throw new ApiError(res.status, typeof msg === "string" ? msg : JSON.stringify(msg), data);
|
|
4459
|
+
}
|
|
4460
|
+
return data;
|
|
4461
|
+
}
|
|
4462
|
+
function printResidue(report, applied) {
|
|
4463
|
+
const up = report?.upstash ?? {};
|
|
4464
|
+
const fga = report?.fga ?? {};
|
|
4465
|
+
console.log(import_chalk31.default.bold(applied ? "\nExternal-residue sweep" : "\nExternal residue (dry-run \u2014 nothing deleted)"));
|
|
4466
|
+
console.log(import_chalk31.default.bold("\n Upstash"));
|
|
4467
|
+
const orphans = up.orphans ?? [];
|
|
4468
|
+
if (orphans.length === 0) console.log(import_chalk31.default.green(" no orphaned keys"));
|
|
4469
|
+
for (const o of orphans) console.log(` ${import_chalk31.default.yellow(o.key)} ${import_chalk31.default.dim(`\u2014 ${o.reason}`)}`);
|
|
4470
|
+
console.log(import_chalk31.default.dim(` kept (live principals): ${up.kept ?? 0} \xB7 anon wallets (untouched): ${up.anon_wallets ?? 0}`));
|
|
4471
|
+
if (up.unknown?.length) console.log(import_chalk31.default.dim(` unknown (never deleted): ${up.unknown.join(", ")}`));
|
|
4472
|
+
if (applied) console.log(` ${import_chalk31.default.bold(String(up.deleted ?? 0))} key(s) deleted`);
|
|
4473
|
+
for (const e of up.errors ?? []) console.log(import_chalk31.default.red(` error: ${e}`));
|
|
4474
|
+
console.log(import_chalk31.default.bold("\n OpenFGA / Neon"));
|
|
4475
|
+
if (applied) {
|
|
4476
|
+
const swept = fga?.swept ?? [];
|
|
4477
|
+
if (swept.length === 0) console.log(import_chalk31.default.green(" no orphaned stores"));
|
|
4478
|
+
for (const s of swept) {
|
|
4479
|
+
console.log(
|
|
4480
|
+
` ${import_chalk31.default.yellow(s.store_id)} ${import_chalk31.default.dim(`\u2014 store ${s.openfga_deleted ? "deleted" : "DEFERRED"}, ${s.neon_deleted} Neon tuple(s) purged`)}`
|
|
4481
|
+
);
|
|
4482
|
+
}
|
|
4483
|
+
if (fga?.remaining) console.log(import_chalk31.default.yellow(` ${fga.remaining} more orphan store(s) \u2014 re-run to drain`));
|
|
4484
|
+
} else {
|
|
4485
|
+
const fgaOrphans = fga?.orphans ?? [];
|
|
4486
|
+
if (fgaOrphans.length === 0) console.log(import_chalk31.default.green(" no orphaned stores"));
|
|
4487
|
+
for (const s of fgaOrphans) {
|
|
4488
|
+
const src = s.in_openfga ? "live in OpenFGA" : "Neon tuples only";
|
|
4489
|
+
console.log(` ${import_chalk31.default.yellow(s.store_id)} ${import_chalk31.default.dim(`\u2014 ${src}${s.name ? ` (${s.name})` : ""}, ${s.neon_tuples} Neon tuple(s)`)}`);
|
|
4490
|
+
}
|
|
4491
|
+
console.log(import_chalk31.default.dim(` kept stores: ${(fga?.kept_store_ids ?? []).length}`));
|
|
4492
|
+
}
|
|
4493
|
+
for (const e of fga?.errors ?? []) console.log(import_chalk31.default.red(` error: ${e}`));
|
|
4494
|
+
console.log();
|
|
4495
|
+
}
|
|
4496
|
+
async function runOp(sub, opts = {}) {
|
|
4497
|
+
if (!loadCredentials()) {
|
|
4498
|
+
console.log(import_chalk31.default.dim("Not logged in. Run `apiblaze login`."));
|
|
4499
|
+
return;
|
|
4500
|
+
}
|
|
4501
|
+
if (!isOperatorLogin()) {
|
|
4502
|
+
console.log(import_chalk31.default.dim("`apiblaze op` is only available to platform operators."));
|
|
4503
|
+
return;
|
|
4504
|
+
}
|
|
4505
|
+
switch (sub) {
|
|
4506
|
+
case void 0:
|
|
4507
|
+
case "menu": {
|
|
4508
|
+
console.log(import_chalk31.default.bold("\nOperator menu"));
|
|
4509
|
+
console.log(` ${import_chalk31.default.cyan("apiblaze op residue")} external-store residue report (Upstash + Neon/OpenFGA, dry-run)`);
|
|
4510
|
+
console.log(` ${import_chalk31.default.cyan("apiblaze op sweep")} delete the orphans the report shows (asks first; ${import_chalk31.default.dim("-y to skip")})`);
|
|
4511
|
+
console.log(` ${import_chalk31.default.cyan("apiblaze op credits")} list credit wallets
|
|
4512
|
+
`);
|
|
4513
|
+
return;
|
|
4514
|
+
}
|
|
4515
|
+
case "residue": {
|
|
4516
|
+
const report = await opCall({ method: "GET", path: "/operator/external-residue", summary: "external residue report" });
|
|
4517
|
+
if (opts.json) return void console.log(JSON.stringify(report, null, 2));
|
|
4518
|
+
printResidue(report, false);
|
|
4519
|
+
return;
|
|
4520
|
+
}
|
|
4521
|
+
case "sweep": {
|
|
4522
|
+
const report = await opCall({ method: "GET", path: "/operator/external-residue", summary: "external residue report" });
|
|
4523
|
+
const nUp = report?.upstash?.orphans?.length ?? 0;
|
|
4524
|
+
const nFga = report?.fga?.orphans?.length ?? 0;
|
|
4525
|
+
printResidue(report, false);
|
|
4526
|
+
if (nUp + nFga === 0) {
|
|
4527
|
+
console.log(import_chalk31.default.green("Nothing to sweep."));
|
|
4528
|
+
return;
|
|
4529
|
+
}
|
|
4530
|
+
if (!opts.yes) {
|
|
4531
|
+
const readline2 = await import("readline/promises");
|
|
4532
|
+
const rl = readline2.createInterface({ input: process.stdin, output: process.stdout });
|
|
4533
|
+
const answer = await rl.question(import_chalk31.default.red(`Delete ${nUp} Upstash key(s) + ${nFga} OpenFGA store(s)? Type 'sweep' to confirm: `));
|
|
4534
|
+
rl.close();
|
|
4535
|
+
if (answer.trim() !== "sweep") return void console.log(import_chalk31.default.dim("Aborted."));
|
|
4536
|
+
}
|
|
4537
|
+
const result = await opCall({ method: "POST", path: "/operator/external-residue/sweep", summary: "external residue sweep" });
|
|
4538
|
+
if (opts.json) return void console.log(JSON.stringify(result, null, 2));
|
|
4539
|
+
printResidue(result, true);
|
|
4540
|
+
return;
|
|
4541
|
+
}
|
|
4542
|
+
case "credits": {
|
|
4543
|
+
const data = await opCall({ method: "GET", path: "/operator/credits", summary: "list credit wallets" });
|
|
4544
|
+
if (opts.json) return void console.log(JSON.stringify(data, null, 2));
|
|
4545
|
+
const accounts = data?.accounts ?? [];
|
|
4546
|
+
if (accounts.length === 0) return void console.log(import_chalk31.default.dim("No credit wallets."));
|
|
4547
|
+
for (const a of accounts) {
|
|
4548
|
+
const bal = typeof a.balance_cents === "number" ? `$${(a.balance_cents / 100).toFixed(2)}` : "?";
|
|
4549
|
+
console.log(` ${import_chalk31.default.bold(bal.padStart(9))} ${a.walletId}${a.owner_email ? import_chalk31.default.dim(` \u2014 ${a.owner_email}`) : a.anon ? import_chalk31.default.dim(" \u2014 anon") : ""}`);
|
|
4550
|
+
}
|
|
4551
|
+
return;
|
|
4552
|
+
}
|
|
4553
|
+
default:
|
|
4554
|
+
console.log(import_chalk31.default.red(`Unknown op subcommand '${sub}'. Run \`apiblaze op\` for the menu.`));
|
|
4555
|
+
}
|
|
4556
|
+
}
|
|
4557
|
+
|
|
3618
4558
|
// src/index.ts
|
|
3619
4559
|
var program = new import_commander.Command();
|
|
3620
4560
|
program.name("apiblaze").description("APIblaze CLI \u2014 create & manage API proxies and run dev tunnels").version(version).option("-v, --verbose", "Print the exact series of API calls each command makes (curl-equivalent you could run yourself)");
|
|
@@ -3654,16 +4594,19 @@ var agent = program.command("agent").description("Chat with an assistant that bu
|
|
|
3654
4594
|
agent.command("authz").description("Chat to design and turn on access rules for an API").argument("<project>", "Project name or id").argument("[apiVersion]", "API version (defaults to the project's)").action(action((project, apiVersion) => runAuthz(project, apiVersion)));
|
|
3655
4595
|
agent.command("openapi").description("Chat to build your API spec from real traffic").argument("<project>", "Project name or id").argument("[apiVersion]", "API version (defaults to the project's)").action(action((project, apiVersion) => runOpenapi(project, apiVersion)));
|
|
3656
4596
|
agent.command("mcp").description("Chat to build an MCP server for an API").argument("<project>", "Project name or id").argument("[apiVersion]", "API version (defaults to the project's)").option("--environment <env>", "Environment to publish (default: prod)").action(action((project, apiVersion, opts) => runMcp(project, apiVersion, opts)));
|
|
3657
|
-
|
|
3658
|
-
var
|
|
3659
|
-
|
|
3660
|
-
|
|
3661
|
-
|
|
4597
|
+
var runSetup = (opts) => runSidecar({ team: opts.team, dir: opts.dir, yes: opts.yes, noInspector: opts.inspector === false, rotate: opts.rotate, newSession: opts.newSession });
|
|
4598
|
+
var withSetupOptions = (cmd) => cmd.option("--team <id|name>", "Team to set up under (defaults to your active team)").option("--dir <path>", "Project directory (defaults to cwd)").option("--no-inspector", "Skip generating the dev-only /abz-inspector page").option("--rotate", "Mint a fresh invoke key even if one already exists").option("--new-session", "Start a fresh anonymous session (logged-out only)").option("-y, --yes", "Skip prompts");
|
|
4599
|
+
withSetupOptions(program.command("init").description("Set up the APIblaze sidecar in a Next.js app (shortcut for `apiblaze sidecar setup`)")).action(action((opts) => runSetup(opts)));
|
|
4600
|
+
var sidecar = program.command("sidecar").description("The APIblaze sidecar \u2014 set it up, then approve which origins route through APIblaze").option("--team <id|name>", "Team (defaults to active team)").option("--json", "Machine-readable output").action(action((opts) => runOriginsList(opts)));
|
|
4601
|
+
withSetupOptions(sidecar.command("setup").description("Wire a Next.js app to route its external fetches through APIblaze")).action(action((opts) => runSetup(opts)));
|
|
4602
|
+
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)));
|
|
4603
|
+
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)));
|
|
4604
|
+
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)));
|
|
3662
4605
|
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) => {
|
|
3663
4606
|
try {
|
|
3664
4607
|
const resolved = parseInt(port ?? opts.port, 10);
|
|
3665
4608
|
if (Number.isNaN(resolved)) {
|
|
3666
|
-
console.error(
|
|
4609
|
+
console.error(import_chalk32.default.red(`Invalid port: ${port ?? opts.port}`));
|
|
3667
4610
|
process.exit(1);
|
|
3668
4611
|
}
|
|
3669
4612
|
await runDev({ port: resolved, captureFile: opts.captureFile });
|
|
@@ -3711,6 +4654,7 @@ program.command("projects").description("List the projects in your team").action
|
|
|
3711
4654
|
});
|
|
3712
4655
|
program.command("delete").description("Delete a proxy and everything under it (asks first)").argument("<project>", "Project name or id (see `apiblaze projects`)").argument("[version]", "API version (defaults to the first match)").option("--team <id|name>", "Team the project is in (defaults to active team)").option("-y, --yes", "Skip the confirmation prompt").option("--json", "Output machine-readable JSON").action(action((project, version2, opts) => runDelete(project, version2, opts)));
|
|
3713
4656
|
program.command("export").description("Export config and data for migration out of APIblaze (Kong, ...)").argument("<project>", "Project name or id (see `apiblaze projects`)").argument("[version]", "API version (defaults to the first match)").option("--kong", "Produce a runnable Kong OSS bundle (decK config + plugins + docker-compose)").option("--data", "Plain data export (default)").option("--secrets", "Include decrypted producer-supplied secrets (member role; audit-logged)").option("--keys <mode>", "API-key export: hashes (default, consumers keep keys) | mint | none").option("--no-consumers", "Skip the end-user lane (users, groups, keys)").option("-o, --out <file>", "Output zip path").option("--team <id|name>", "Team the project is in (defaults to active team)").action(action((project, version2, opts) => runExport(project, version2, { ...opts, noConsumers: opts.consumers === false })));
|
|
4657
|
+
program.command("config").description("Browse and change every proxy setting & feature (interactive; git-config-style get/set)").argument("[project]", "Project name or id (omit to pick interactively)").argument("[key]", "Setting key, e.g. throttling.proxyQuota (omit for the menu)").argument("[value]", "New value (omit to read the current value)").option("--list", "Print every setting and its current value").option("--team <id|name>", "Team the project is in").option("--apiversion <version>", "API version (defaults to the first match)").option("--json", "Machine-readable output (with --list or a key read)").action(action((project, key, value, opts) => runConfig(project, key, value, opts)));
|
|
3714
4658
|
program.command("target").description("Change where a proxy forwards requests").argument("<project>", "Project name or id").requiredOption("--url <url>", "Target URL to forward to").option("--env <env>", "Environment to scope the target to (e.g. prod, dev)").option("--team <id|name>", "Team the project is in").option("--apiversion <version>", "API version (defaults to the first match)").option("--json", "Output machine-readable JSON").action(action((project, opts) => runTargetSet(project, opts)));
|
|
3715
4659
|
program.command("throttle").description("Set rate limits and quotas for a proxy").argument("<project>", "Project name or id").option("--rate <n>", "User rate limit (requests/sec)").option("--end-user-rate <n>", "Per-end-user rate limit (requests/sec)").option("--quota <n>", "Proxy quota (requests/period)").option("--period <p>", "Quota period: daily | weekly | monthly").option("--team <id|name>", "Team the project is in").option("--apiversion <version>", "API version").option("--json", "Output machine-readable JSON").action(action((project, opts) => runThrottleSet(project, opts)));
|
|
3716
4660
|
program.command("rename").description("Change a proxy's display name").argument("<project>", "Project name or id").requiredOption("--display-name <name>", "New human-friendly display name").option("--team <id|name>", "Team the project is in").option("--apiversion <version>", "API version").option("--json", "Output machine-readable JSON").action(action((project, opts) => runRename(project, opts)));
|
|
@@ -3731,13 +4675,15 @@ apikeys.command("list").description("List control-plane API keys in your team").
|
|
|
3731
4675
|
apikeys.command("mint").description("Create a control-plane API key (secret shown once)").option("--desc <text>", "Description").option("--expires-days <n>", "Expiry in days (default 90 server-side)").option("--team <id|name>", "Team (defaults to active team)").option("--json", "Output machine-readable JSON").action(action((opts) => runKeyMint(opts)));
|
|
3732
4676
|
apikeys.command("revoke").description("Revoke a control-plane API key").argument("<keyId>", "Key id (see `apikeys list`)").option("--team <id|name>", "Team (defaults to active team)").action(action((keyId, opts) => runKeyRevoke(keyId, opts)));
|
|
3733
4677
|
program.addCommand(apikeys, { hidden: true });
|
|
4678
|
+
var op = new import_commander.Command("op").description("Operator menu (platform operators only)").argument("[action]", "residue | sweep | credits (omit for the menu)").option("-y, --yes", "Skip the sweep confirmation prompt").option("--json", "Output machine-readable JSON").action(action((sub, opts) => runOp(sub, opts)));
|
|
4679
|
+
program.addCommand(op, { hidden: true });
|
|
3734
4680
|
var spec = program.command("spec").description("View or update a proxy's OpenAPI spec (or build one by chatting: apiblaze agent openapi)");
|
|
3735
4681
|
spec.command("get").description("Print the current OpenAPI document").argument("<project>", "Project name or id").option("--team <id|name>", "Team the project is in").option("--apiversion <version>", "API version").option("--json", "Compact JSON output").action(action((project, opts) => runSpecGet(project, opts)));
|
|
3736
4682
|
spec.command("set").description("Replace the stored OpenAPI spec from a local file").argument("<project>", "Project name or id").requiredOption("--file <path>", "OpenAPI JSON or YAML file to upload").option("--team <id|name>", "Team the project is in").option("--apiversion <version>", "API version").option("--json", "Output machine-readable JSON").action(action((project, opts) => runSpecSet(project, opts)));
|
|
3737
4683
|
var HELP_GROUPS = [
|
|
3738
4684
|
{ title: "Chat", commands: ["agent"] },
|
|
3739
4685
|
{ title: "Setup", commands: ["login", "create", "init", "sidecar", "dev", "claim", "team", "whoami", "logout"] },
|
|
3740
|
-
{ title: "Control plane commands", commands: ["projects", "tenant", "domain", "delete", "target", "throttle", "rename", "spec", "export"] },
|
|
4686
|
+
{ title: "Control plane commands", commands: ["config", "projects", "tenant", "domain", "delete", "target", "throttle", "rename", "spec", "export"] },
|
|
3741
4687
|
{ title: "Data plane commands", commands: [
|
|
3742
4688
|
{ parent: "consumer", sub: "login" },
|
|
3743
4689
|
{ parent: "consumer", sub: "apikeys" }
|
|
@@ -3756,7 +4702,7 @@ function groupedCommandHelp() {
|
|
|
3756
4702
|
const sub = byName.get(e.parent)?.commands.find((s) => s.name() === e.sub);
|
|
3757
4703
|
return sub ? ` ${helpLabel(e).padEnd(width)}${sub.description()}` : "";
|
|
3758
4704
|
}).filter(Boolean).join("\n");
|
|
3759
|
-
return `${
|
|
4705
|
+
return `${import_chalk32.default.bold(g.title)}
|
|
3760
4706
|
${rows}`;
|
|
3761
4707
|
}).join("\n\n");
|
|
3762
4708
|
}
|
|
@@ -3770,7 +4716,9 @@ program.addHelpText("after", () => `
|
|
|
3770
4716
|
${groupedCommandHelp()}
|
|
3771
4717
|
|
|
3772
4718
|
Tips:
|
|
4719
|
+
\u2022 \`apiblaze config <project>\` browses EVERY setting & feature (works logged-out to explore).
|
|
3773
4720
|
\u2022 Add --verbose to any command to see the equivalent API calls.
|
|
4721
|
+
\u2022 Full API reference: https://api.apiblaze.com/openapi.json
|
|
3774
4722
|
\u2022 Run \`apiblaze <command> --help\` (e.g. \`apiblaze consumer --help\`) for sub-commands.
|
|
3775
4723
|
|
|
3776
4724
|
Examples:
|
|
@@ -3782,13 +4730,13 @@ Examples:
|
|
|
3782
4730
|
`);
|
|
3783
4731
|
function printError(err) {
|
|
3784
4732
|
if (err instanceof ApiError) {
|
|
3785
|
-
console.error(
|
|
4733
|
+
console.error(import_chalk32.default.red(`
|
|
3786
4734
|
API error (${err.status}): ${err.message}`));
|
|
3787
4735
|
} else if (err instanceof Error) {
|
|
3788
|
-
console.error(
|
|
4736
|
+
console.error(import_chalk32.default.red(`
|
|
3789
4737
|
Error: ${err.message}`));
|
|
3790
4738
|
} else {
|
|
3791
|
-
console.error(
|
|
4739
|
+
console.error(import_chalk32.default.red("\nUnknown error"));
|
|
3792
4740
|
}
|
|
3793
4741
|
}
|
|
3794
4742
|
program.parse(process.argv);
|