apiblaze 0.9.0 → 0.11.0
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 +1079 -207
- package/dist/sidecar/index.js +7 -6
- package/dist/sidecar/index.mjs +7 -6
- 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.0";
|
|
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,763 @@ 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
|
+
async function transformsMenu(proj2) {
|
|
3422
|
+
const { default: inquirer2 } = await import("inquirer");
|
|
3423
|
+
const base = `/projects/${proj2.projectId}/${proj2.apiVersion}/transforms`;
|
|
3424
|
+
for (; ; ) {
|
|
3425
|
+
const out = await admin({ method: "GET", path: base, summary: "List transform rules" });
|
|
3426
|
+
const rules = out?.rules ?? [];
|
|
3427
|
+
console.log();
|
|
3428
|
+
if (!rules.length) console.log(import_chalk26.default.dim(" No transform rules yet."));
|
|
3429
|
+
for (const r of rules) {
|
|
3430
|
+
const a = r.action ?? {};
|
|
3431
|
+
const what = a.type === "hardcode" ? `${a.destination} = "${a.value}"` : a.type === "remove" ? `remove ${a.field}` : `${a.source} \u2192 ${a.destination}${a.lookup ? " (mapped)" : ""}`;
|
|
3432
|
+
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}`);
|
|
3433
|
+
}
|
|
3434
|
+
const { act } = await inquirer2.prompt([{
|
|
3435
|
+
type: "list",
|
|
3436
|
+
name: "act",
|
|
3437
|
+
message: "Transforms:",
|
|
3438
|
+
choices: [
|
|
3439
|
+
{ name: "Add a rule", value: "add" },
|
|
3440
|
+
...rules.length ? [
|
|
3441
|
+
{ name: "Enable/disable a rule", value: "toggle" },
|
|
3442
|
+
{ name: "Delete a rule", value: "delete" }
|
|
3443
|
+
] : [],
|
|
3444
|
+
{ name: "\u2190 Back", value: "back" }
|
|
3445
|
+
]
|
|
3446
|
+
}]);
|
|
3447
|
+
if (act === "back") return;
|
|
3448
|
+
if (act === "add") {
|
|
3449
|
+
const ans = await inquirer2.prompt([
|
|
3450
|
+
{ type: "input", name: "name", message: "Rule name:", validate: (s) => !!s || "required" },
|
|
3451
|
+
{ type: "list", name: "phase", message: "Phase:", choices: [
|
|
3452
|
+
{ name: "request \u2014 before forwarding upstream", value: "request" },
|
|
3453
|
+
{ name: "response \u2014 on the reply before it returns", value: "response" }
|
|
3454
|
+
] },
|
|
3455
|
+
{ type: "list", name: "kind", message: "Action:", choices: [
|
|
3456
|
+
{ name: "Set a value (header/param/body field)", value: "hardcode" },
|
|
3457
|
+
{ name: "Copy/map one field to another", value: "map" },
|
|
3458
|
+
{ name: "Remove a field", value: "remove" }
|
|
3459
|
+
] }
|
|
3460
|
+
]);
|
|
3461
|
+
const fieldHint = import_chalk26.default.dim("(e.g. header:x-api-version, param:limit, bodyvar:user.id)");
|
|
3462
|
+
let action2;
|
|
3463
|
+
if (ans.kind === "hardcode") {
|
|
3464
|
+
const a = await inquirer2.prompt([
|
|
3465
|
+
{ type: "input", name: "destination", message: `Destination field ${fieldHint}:`, validate: (s) => !!s || "required" },
|
|
3466
|
+
{ type: "input", name: "value", message: "Value:" }
|
|
3467
|
+
]);
|
|
3468
|
+
action2 = { type: "hardcode", destination: a.destination, value: a.value };
|
|
3469
|
+
} else if (ans.kind === "remove") {
|
|
3470
|
+
const a = await inquirer2.prompt([
|
|
3471
|
+
{ type: "input", name: "field", message: `Field to remove ${fieldHint}:`, validate: (s) => !!s || "required" }
|
|
3472
|
+
]);
|
|
3473
|
+
action2 = { type: "remove", field: a.field };
|
|
3474
|
+
} else {
|
|
3475
|
+
const a = await inquirer2.prompt([
|
|
3476
|
+
{ type: "input", name: "source", message: `Source field ${fieldHint}:`, validate: (s) => !!s || "required" },
|
|
3477
|
+
{ type: "input", name: "destination", message: `Destination field ${fieldHint}:`, validate: (s) => !!s || "required" },
|
|
3478
|
+
{ type: "confirm", name: "strip", message: "Remove the source field after copying?", default: false }
|
|
3479
|
+
]);
|
|
3480
|
+
action2 = { type: "copy", source: a.source, destination: a.destination, ...a.strip ? { strip_source: true } : {} };
|
|
3481
|
+
}
|
|
3482
|
+
const spinner = (0, import_ora11.default)("Creating rule...").start();
|
|
3483
|
+
try {
|
|
3484
|
+
await admin({ method: "POST", path: base, body: { name: ans.name, phase: ans.phase, enabled: true, action: action2 }, summary: `Create transform "${ans.name}"` });
|
|
3485
|
+
spinner.succeed(`Rule "${ans.name}" created.`);
|
|
3486
|
+
} catch (err) {
|
|
3487
|
+
spinner.fail("Create failed.");
|
|
3488
|
+
throw err;
|
|
3489
|
+
}
|
|
3490
|
+
} else {
|
|
3491
|
+
const { rule } = await inquirer2.prompt([{
|
|
3492
|
+
type: "list",
|
|
3493
|
+
name: "rule",
|
|
3494
|
+
message: act === "toggle" ? "Which rule?" : "Delete which rule?",
|
|
3495
|
+
choices: [...rules.map((r) => ({ name: `${r.name} ${import_chalk26.default.dim(`[${r.phase ?? "request"}]`)}`, value: r })), { name: "\u2190 Back", value: null }]
|
|
3496
|
+
}]);
|
|
3497
|
+
if (!rule) continue;
|
|
3498
|
+
if (act === "toggle") {
|
|
3499
|
+
const flipped = { ...rule, enabled: rule.enabled === false };
|
|
3500
|
+
await admin({ method: "PUT", path: `${base}/${rule.id}`, body: flipped, summary: `${flipped.enabled ? "Enable" : "Disable"} transform "${rule.name}"` });
|
|
3501
|
+
console.log(import_chalk26.default.green(` ${rule.name} \u2192 ${flipped.enabled ? "enabled" : "disabled"}`));
|
|
3502
|
+
} else {
|
|
3503
|
+
await admin({ method: "DELETE", path: `${base}/${rule.id}`, summary: `Delete transform "${rule.name}"` });
|
|
3504
|
+
console.log(import_chalk26.default.green(` ${rule.name} deleted.`));
|
|
3505
|
+
}
|
|
3506
|
+
}
|
|
3507
|
+
}
|
|
3508
|
+
}
|
|
3509
|
+
async function mappingsMenu(proj2) {
|
|
3510
|
+
const { default: inquirer2 } = await import("inquirer");
|
|
3511
|
+
const base = `/projects/${proj2.projectId}/${proj2.apiVersion}/mappings`;
|
|
3512
|
+
for (; ; ) {
|
|
3513
|
+
const out = await admin({ method: "GET", path: base, summary: "List mapping tables" });
|
|
3514
|
+
const tables = out?.mappings ?? out?.tables ?? [];
|
|
3515
|
+
console.log();
|
|
3516
|
+
if (!tables.length) console.log(import_chalk26.default.dim(" No mapping tables yet."));
|
|
3517
|
+
for (const t of tables) {
|
|
3518
|
+
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" : ""}`)}`);
|
|
3519
|
+
}
|
|
3520
|
+
const { act } = await inquirer2.prompt([{
|
|
3521
|
+
type: "list",
|
|
3522
|
+
name: "act",
|
|
3523
|
+
message: "Mapping tables:",
|
|
3524
|
+
choices: [
|
|
3525
|
+
{ name: "Create a table", value: "add" },
|
|
3526
|
+
...tables.length ? [{ name: "Delete a table", value: "delete" }] : [],
|
|
3527
|
+
{ name: "\u2190 Back", value: "back" }
|
|
3528
|
+
]
|
|
3529
|
+
}]);
|
|
3530
|
+
if (act === "back") return;
|
|
3531
|
+
if (act === "add") {
|
|
3532
|
+
const a = await inquirer2.prompt([
|
|
3533
|
+
{ type: "input", name: "name", message: "Table name:", validate: (s) => !!s || "required" },
|
|
3534
|
+
{ type: "input", name: "entries", message: 'Entries as JSON, e.g. [{"from":"a","to":"b"}]:', default: "[]" }
|
|
3535
|
+
]);
|
|
3536
|
+
const entries2 = parseValue(a.entries);
|
|
3537
|
+
if (!Array.isArray(entries2)) {
|
|
3538
|
+
console.log(import_chalk26.default.yellow(" Entries must be a JSON array \u2014 not created."));
|
|
3539
|
+
continue;
|
|
3540
|
+
}
|
|
3541
|
+
await admin({ method: "POST", path: base, body: { name: a.name, entries: entries2 }, summary: `Create mapping table "${a.name}"` });
|
|
3542
|
+
console.log(import_chalk26.default.green(` Table "${a.name}" created.`));
|
|
3543
|
+
} else {
|
|
3544
|
+
const { table } = await inquirer2.prompt([{
|
|
3545
|
+
type: "list",
|
|
3546
|
+
name: "table",
|
|
3547
|
+
message: "Delete which table?",
|
|
3548
|
+
choices: [...tables.map((t) => ({ name: t.name, value: t })), { name: "\u2190 Back", value: null }]
|
|
3549
|
+
}]);
|
|
3550
|
+
if (!table) continue;
|
|
3551
|
+
await admin({ method: "DELETE", path: `${base}/${table.id}`, summary: `Delete mapping table "${table.name}"` });
|
|
3552
|
+
console.log(import_chalk26.default.green(` ${table.name} deleted.`));
|
|
3553
|
+
}
|
|
3554
|
+
}
|
|
3555
|
+
}
|
|
3556
|
+
async function tenantsMenu(proj2, opts) {
|
|
3557
|
+
const { default: inquirer2 } = await import("inquirer");
|
|
3558
|
+
const base = `/projects/${proj2.projectId}/${proj2.apiVersion}/tenants`;
|
|
3559
|
+
for (; ; ) {
|
|
3560
|
+
const out = await admin({ method: "GET", path: base, summary: "List attached tenants" });
|
|
3561
|
+
const tenants = out?.tenants ?? [];
|
|
3562
|
+
console.log();
|
|
3563
|
+
if (!tenants.length) console.log(import_chalk26.default.dim(" No tenants attached (consumers use the default tenant)."));
|
|
3564
|
+
for (const t of tenants) console.log(` ${import_chalk26.default.bold(t.tenant_name ?? t.name)} ${import_chalk26.default.dim(t.display_name ?? "")}`);
|
|
3565
|
+
const { act } = await inquirer2.prompt([{
|
|
3566
|
+
type: "list",
|
|
3567
|
+
name: "act",
|
|
3568
|
+
message: "Tenants:",
|
|
3569
|
+
choices: [
|
|
3570
|
+
{ name: "Attach a tenant", value: "attach" },
|
|
3571
|
+
...tenants.length ? [{ name: "Detach a tenant", value: "detach" }] : [],
|
|
3572
|
+
{ name: import_chalk26.default.dim("Manage team tenants (create/CORS/login) \u2192 `apiblaze tenant --help`"), value: "hint" },
|
|
3573
|
+
{ name: "\u2190 Back", value: "back" }
|
|
3574
|
+
]
|
|
3575
|
+
}]);
|
|
3576
|
+
if (act === "back") return;
|
|
3577
|
+
if (act === "hint") {
|
|
3578
|
+
console.log(import_chalk26.default.dim("\n apiblaze tenant list | create | attach | cors | delete\n"));
|
|
3579
|
+
continue;
|
|
3580
|
+
}
|
|
3581
|
+
if (act === "attach") {
|
|
3582
|
+
const { slug } = await inquirer2.prompt([{ type: "input", name: "slug", message: "Tenant slug to attach:", validate: (s) => !!s || "required" }]);
|
|
3583
|
+
await runTenantAttach(proj2.projectName, { tenant: slug, team: opts.team, apiversion: proj2.apiVersion });
|
|
3584
|
+
} else {
|
|
3585
|
+
const { t } = await inquirer2.prompt([{
|
|
3586
|
+
type: "list",
|
|
3587
|
+
name: "t",
|
|
3588
|
+
message: "Detach which tenant?",
|
|
3589
|
+
choices: [...tenants.map((x) => ({ name: x.tenant_name ?? x.name, value: x })), { name: "\u2190 Back", value: null }]
|
|
3590
|
+
}]);
|
|
3591
|
+
if (!t) continue;
|
|
3592
|
+
await admin({ method: "DELETE", path: `${base}/${encodeURIComponent(t.tenant_name ?? t.name)}`, summary: `Detach tenant ${t.tenant_name ?? t.name}` });
|
|
3593
|
+
console.log(import_chalk26.default.green(` Detached ${t.tenant_name ?? t.name}.`));
|
|
3594
|
+
}
|
|
3595
|
+
}
|
|
3596
|
+
}
|
|
3597
|
+
async function domainsMenu(proj2, opts) {
|
|
3598
|
+
const { default: inquirer2 } = await import("inquirer");
|
|
3599
|
+
for (; ; ) {
|
|
3600
|
+
const { act } = await inquirer2.prompt([{
|
|
3601
|
+
type: "list",
|
|
3602
|
+
name: "act",
|
|
3603
|
+
message: "Custom domains:",
|
|
3604
|
+
choices: [
|
|
3605
|
+
{ name: "List domains", value: "list" },
|
|
3606
|
+
{ name: "Add a domain (shows the DNS records to set)", value: "add" },
|
|
3607
|
+
{ name: "Remove a domain", value: "remove" },
|
|
3608
|
+
{ name: "Set what the bare URL serves (base domain)", value: "base" },
|
|
3609
|
+
{ name: "\u2190 Back", value: "back" }
|
|
3610
|
+
]
|
|
3611
|
+
}]);
|
|
3612
|
+
const common = { team: opts.team, apiversion: proj2.apiVersion };
|
|
3613
|
+
if (act === "back") return;
|
|
3614
|
+
if (act === "list") await runDomainList(proj2.projectName, common);
|
|
3615
|
+
else if (act === "add") {
|
|
3616
|
+
const { host } = await inquirer2.prompt([{ type: "input", name: "host", message: "Hostname (e.g. api.example.com):", validate: (s) => !!s || "required" }]);
|
|
3617
|
+
await runDomainAdd(proj2.projectName, { ...common, domain: host });
|
|
3618
|
+
} else if (act === "remove") {
|
|
3619
|
+
const { id } = await inquirer2.prompt([{ type: "input", name: "id", message: "Domain id (see List):", validate: (s) => !!s || "required" }]);
|
|
3620
|
+
await runDomainRemove(proj2.projectName, { ...common, id });
|
|
3621
|
+
} else {
|
|
3622
|
+
const { env } = await inquirer2.prompt([{ type: "input", name: "env", message: "Environment (e.g. prod):", default: "prod" }]);
|
|
3623
|
+
await runDomainSetBase(proj2.projectName, { ...common, env });
|
|
3624
|
+
}
|
|
3625
|
+
}
|
|
3626
|
+
}
|
|
3627
|
+
async function specMenu(proj2, opts) {
|
|
3628
|
+
const { default: inquirer2 } = await import("inquirer");
|
|
3629
|
+
const { act } = await inquirer2.prompt([{
|
|
3630
|
+
type: "list",
|
|
3631
|
+
name: "act",
|
|
3632
|
+
message: "OpenAPI spec & traffic:",
|
|
3633
|
+
choices: [
|
|
3634
|
+
{ name: "Print the stored spec", value: "get" },
|
|
3635
|
+
{ name: "Refresh the spec from its source", value: "refresh" },
|
|
3636
|
+
{ name: import_chalk26.default.dim("Build the spec by chatting over real traffic \u2192 agent"), value: "agent" },
|
|
3637
|
+
{ name: "\u2190 Back", value: "back" }
|
|
3638
|
+
]
|
|
3639
|
+
}]);
|
|
3640
|
+
if (act === "back") return;
|
|
3641
|
+
if (act === "get") await runSpecGet(proj2.projectName, { team: opts.team, apiversion: proj2.apiVersion });
|
|
3642
|
+
else if (act === "refresh") {
|
|
3643
|
+
await admin({ method: "POST", path: `/projects/${proj2.projectId}/${proj2.apiVersion}/refresh-spec`, summary: "Refresh spec from source" });
|
|
3644
|
+
console.log(import_chalk26.default.green(" Spec refresh triggered."));
|
|
3645
|
+
} else await runOpenapi(proj2.projectName, proj2.apiVersion);
|
|
3646
|
+
}
|
|
3647
|
+
async function agentsMenu(proj2, opts) {
|
|
3648
|
+
const { default: inquirer2 } = await import("inquirer");
|
|
3649
|
+
const { act } = await inquirer2.prompt([{
|
|
3650
|
+
type: "list",
|
|
3651
|
+
name: "act",
|
|
3652
|
+
message: "AI agents (billed per turn):",
|
|
3653
|
+
choices: [
|
|
3654
|
+
{ name: "General chat \u2014 build and run your APIs", value: "agent" },
|
|
3655
|
+
{ name: "OpenAPI builder \u2014 build your spec from real traffic", value: "openapi" },
|
|
3656
|
+
{ name: "Authorization designer \u2014 draft access rules", value: "authz" },
|
|
3657
|
+
{ name: "MCP builder \u2014 publish this API as an MCP server", value: "mcp" },
|
|
3658
|
+
{ name: "\u2190 Back", value: "back" }
|
|
3659
|
+
]
|
|
3660
|
+
}]);
|
|
3661
|
+
if (act === "back") return;
|
|
3662
|
+
if (act === "agent") await runAgent({ team: opts.team });
|
|
3663
|
+
else if (act === "openapi") await runOpenapi(proj2.projectName, proj2.apiVersion);
|
|
3664
|
+
else if (act === "authz") await runAuthz(proj2.projectName, proj2.apiVersion);
|
|
3665
|
+
else await runMcp(proj2.projectName, proj2.apiVersion, {});
|
|
3666
|
+
}
|
|
3667
|
+
|
|
3668
|
+
// src/commands/key.ts
|
|
3073
3669
|
var import_chalk27 = __toESM(require("chalk"));
|
|
3074
3670
|
var import_ora12 = __toESM(require("ora"));
|
|
3671
|
+
async function runApikeysMenu(opts) {
|
|
3672
|
+
await runKeyList(opts);
|
|
3673
|
+
if (opts.json) return;
|
|
3674
|
+
const { default: inquirer2 } = await import("inquirer");
|
|
3675
|
+
const { make } = await inquirer2.prompt([
|
|
3676
|
+
{ type: "confirm", name: "make", message: "Generate a new control-plane API key?", default: false }
|
|
3677
|
+
]);
|
|
3678
|
+
if (!make) return;
|
|
3679
|
+
const { desc } = await inquirer2.prompt([
|
|
3680
|
+
{ type: "input", name: "desc", message: "Description (optional):" }
|
|
3681
|
+
]);
|
|
3682
|
+
await runKeyMint({ ...opts, desc: desc || void 0 });
|
|
3683
|
+
}
|
|
3684
|
+
async function runKeyList(opts) {
|
|
3685
|
+
const { teamId, teamName } = await resolveTeam(opts.team);
|
|
3686
|
+
const out = await admin({
|
|
3687
|
+
method: "GET",
|
|
3688
|
+
path: `/teams/${encodeURIComponent(teamId)}/developer-keys`,
|
|
3689
|
+
summary: `List developer keys for team ${teamName ?? teamId}`
|
|
3690
|
+
});
|
|
3691
|
+
const keys = out?.keys ?? [];
|
|
3692
|
+
if (opts.json) {
|
|
3693
|
+
console.log(JSON.stringify(keys));
|
|
3694
|
+
return;
|
|
3695
|
+
}
|
|
3696
|
+
if (!keys.length) {
|
|
3697
|
+
console.log(import_chalk27.default.yellow("No developer keys."));
|
|
3698
|
+
return;
|
|
3699
|
+
}
|
|
3700
|
+
for (const k of keys) {
|
|
3701
|
+
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")}`);
|
|
3702
|
+
}
|
|
3703
|
+
}
|
|
3704
|
+
async function runKeyMint(opts) {
|
|
3705
|
+
const { teamId } = await resolveTeam(opts.team);
|
|
3706
|
+
const body = { role: "consumer-admin" };
|
|
3707
|
+
if (opts.desc) body.description = opts.desc;
|
|
3708
|
+
if (opts.expiresDays) body.expires_in_seconds = Number(opts.expiresDays) * 24 * 60 * 60;
|
|
3709
|
+
const spinner = (0, import_ora12.default)("Minting key...").start();
|
|
3710
|
+
try {
|
|
3711
|
+
const out = await admin({
|
|
3712
|
+
method: "POST",
|
|
3713
|
+
path: `/teams/${encodeURIComponent(teamId)}/developer-keys`,
|
|
3714
|
+
body,
|
|
3715
|
+
summary: `Mint a consumer-admin developer key`
|
|
3716
|
+
});
|
|
3717
|
+
spinner.succeed("Key minted.");
|
|
3718
|
+
if (opts.json) {
|
|
3719
|
+
console.log(JSON.stringify(out));
|
|
3720
|
+
return;
|
|
3721
|
+
}
|
|
3722
|
+
console.log(` ${import_chalk27.default.bold("key_id")}: ${out?.key_id}`);
|
|
3723
|
+
console.log(` ${import_chalk27.default.bold("key")}: ${import_chalk27.default.green(out?.key)} ${import_chalk27.default.dim("(shown once \u2014 store it now)")}`);
|
|
3724
|
+
if (out?.expires_at) console.log(` ${import_chalk27.default.dim("expires:")} ${out.expires_at}`);
|
|
3725
|
+
} catch (err) {
|
|
3726
|
+
spinner.fail("Mint failed.");
|
|
3727
|
+
throw err;
|
|
3728
|
+
}
|
|
3729
|
+
}
|
|
3730
|
+
async function runKeyRevoke(keyId, opts) {
|
|
3731
|
+
const { teamId } = await resolveTeam(opts.team);
|
|
3732
|
+
const spinner = (0, import_ora12.default)("Revoking key...").start();
|
|
3733
|
+
try {
|
|
3734
|
+
await admin({
|
|
3735
|
+
method: "DELETE",
|
|
3736
|
+
path: `/teams/${encodeURIComponent(teamId)}/developer-keys/${encodeURIComponent(keyId)}`,
|
|
3737
|
+
summary: `Revoke developer key ${keyId}`
|
|
3738
|
+
});
|
|
3739
|
+
spinner.succeed(`Revoked ${keyId}.`);
|
|
3740
|
+
} catch (err) {
|
|
3741
|
+
spinner.fail("Revoke failed.");
|
|
3742
|
+
throw err;
|
|
3743
|
+
}
|
|
3744
|
+
}
|
|
3745
|
+
|
|
3746
|
+
// src/commands/consumer.ts
|
|
3747
|
+
var import_chalk28 = __toESM(require("chalk"));
|
|
3748
|
+
var import_ora13 = __toESM(require("ora"));
|
|
3075
3749
|
var DEFAULT_SCOPE = "openid email profile offline_access";
|
|
3076
3750
|
var APIKEYS_BASE = process.env.APIBLAZE_APIKEYS_BASE || "https://apikeys.apiblaze.com";
|
|
3077
3751
|
async function consumerFetch(creds, suffix, init) {
|
|
@@ -3090,7 +3764,7 @@ async function consumerFetch(creds, suffix, init) {
|
|
|
3090
3764
|
function requireConsumer() {
|
|
3091
3765
|
const c = loadConsumer();
|
|
3092
3766
|
if (!c) {
|
|
3093
|
-
console.error(
|
|
3767
|
+
console.error(import_chalk28.default.red("Not logged in as a consumer. Run `apiblaze consumer login` first."));
|
|
3094
3768
|
process.exit(1);
|
|
3095
3769
|
}
|
|
3096
3770
|
return c;
|
|
@@ -3101,20 +3775,20 @@ async function runConsumerLogin(opts) {
|
|
|
3101
3775
|
let clientId = opts.client;
|
|
3102
3776
|
if (clientId) {
|
|
3103
3777
|
if (!tenant2) {
|
|
3104
|
-
console.error(
|
|
3778
|
+
console.error(import_chalk28.default.red("When using --client, also pass --tenant <slug> (it sets which portal/keys host to use)."));
|
|
3105
3779
|
process.exit(1);
|
|
3106
3780
|
}
|
|
3107
3781
|
} else {
|
|
3108
3782
|
requireAuth();
|
|
3109
3783
|
const { teamId, teamName } = await resolveTeam(opts.team);
|
|
3110
|
-
const spinner = (0,
|
|
3784
|
+
const spinner = (0, import_ora13.default)("Loading your tenants...").start();
|
|
3111
3785
|
const tdata = await admin({ method: "GET", path: `/teams/${encodeURIComponent(teamId)}/tenants?detail=1`, summary: `List tenants for ${teamName ?? teamId}` });
|
|
3112
3786
|
spinner.stop();
|
|
3113
3787
|
const tenants = (tdata?.tenants ?? []).map(
|
|
3114
3788
|
(t) => typeof t === "string" ? { tenant_name: t } : t
|
|
3115
3789
|
);
|
|
3116
3790
|
if (!tenants.length) {
|
|
3117
|
-
console.error(
|
|
3791
|
+
console.error(import_chalk28.default.red("This team has no tenants. Create one with `apiblaze tenant create`."));
|
|
3118
3792
|
process.exit(1);
|
|
3119
3793
|
}
|
|
3120
3794
|
if (!tenant2) {
|
|
@@ -3124,25 +3798,25 @@ async function runConsumerLogin(opts) {
|
|
|
3124
3798
|
tenant2 = chosen;
|
|
3125
3799
|
}
|
|
3126
3800
|
}
|
|
3127
|
-
const s2 = (0,
|
|
3801
|
+
const s2 = (0, import_ora13.default)("Finding the login app...").start();
|
|
3128
3802
|
const clients = await admin({ method: "GET", path: `/teams/${encodeURIComponent(teamId)}/tenants/${encodeURIComponent(tenant2)}/app-clients`, summary: `List app clients for ${tenant2}` }).catch(() => []);
|
|
3129
3803
|
s2.stop();
|
|
3130
3804
|
const usable = (Array.isArray(clients) ? clients : []).filter((c) => c && (c.client_id || c.clientId));
|
|
3131
3805
|
const pick2 = usable.find((c) => c.is_default || c.default) ?? usable.find((c) => c.verified !== false) ?? usable[0];
|
|
3132
3806
|
if (!pick2) {
|
|
3133
|
-
console.error(
|
|
3807
|
+
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
3808
|
process.exit(1);
|
|
3135
3809
|
}
|
|
3136
3810
|
clientId = pick2.client_id ?? pick2.clientId;
|
|
3137
3811
|
}
|
|
3138
3812
|
const portalResource = `https://${tenant2}.portal.apiblaze.com/1.0.0`;
|
|
3139
|
-
console.log(`${
|
|
3813
|
+
console.log(`${import_chalk28.default.cyan("\u2192")} Logging in to ${import_chalk28.default.bold(tenant2)} as a consumer...`);
|
|
3140
3814
|
const result = await deviceLogin(clientId, DEFAULT_SCOPE, ({ verificationUri, userCode }) => {
|
|
3141
3815
|
console.log(`
|
|
3142
|
-
Open: ${
|
|
3143
|
-
console.log(` Code: ${
|
|
3816
|
+
Open: ${import_chalk28.default.underline(verificationUri)}`);
|
|
3817
|
+
console.log(` Code: ${import_chalk28.default.bold(userCode)}
|
|
3144
3818
|
`);
|
|
3145
|
-
console.log(
|
|
3819
|
+
console.log(import_chalk28.default.dim(" (opening your browser\u2026 waiting for you to finish)"));
|
|
3146
3820
|
}, portalResource);
|
|
3147
3821
|
const claims = result.idToken && decodeJwt2(result.idToken) || (decodeJwt2(result.accessToken) ?? {});
|
|
3148
3822
|
const creds = {
|
|
@@ -3157,7 +3831,7 @@ async function runConsumerLogin(opts) {
|
|
|
3157
3831
|
obtainedAt: Date.now()
|
|
3158
3832
|
};
|
|
3159
3833
|
saveConsumer(creds);
|
|
3160
|
-
console.log(
|
|
3834
|
+
console.log(import_chalk28.default.green(`\u2714 Logged in as consumer${creds.email ? ` ${creds.email}` : ""} on ${tenant2}.`));
|
|
3161
3835
|
}
|
|
3162
3836
|
async function runConsumerTokens(opts) {
|
|
3163
3837
|
const creds = requireConsumer();
|
|
@@ -3170,29 +3844,29 @@ async function runConsumerTokens(opts) {
|
|
|
3170
3844
|
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
3845
|
return;
|
|
3172
3846
|
}
|
|
3173
|
-
console.log(`${
|
|
3847
|
+
console.log(`${import_chalk28.default.cyan("Consumer")} ${import_chalk28.default.bold(fresh.email ?? fresh.tenant)} on ${import_chalk28.default.bold(fresh.tenant)}
|
|
3174
3848
|
`);
|
|
3175
|
-
console.log(`${
|
|
3849
|
+
console.log(`${import_chalk28.default.bold("access_token")} ${import_chalk28.default.dim("exp " + (exp(fresh.accessToken) ?? "?"))}
|
|
3176
3850
|
${fresh.accessToken}
|
|
3177
3851
|
`);
|
|
3178
|
-
if (fresh.idToken) console.log(`${
|
|
3852
|
+
if (fresh.idToken) console.log(`${import_chalk28.default.bold("id_token")} ${import_chalk28.default.dim("exp " + (exp(fresh.idToken) ?? "?"))}
|
|
3179
3853
|
${fresh.idToken}
|
|
3180
3854
|
`);
|
|
3181
|
-
if (fresh.refreshToken) console.log(`${
|
|
3855
|
+
if (fresh.refreshToken) console.log(`${import_chalk28.default.bold("refresh_token")}
|
|
3182
3856
|
${fresh.refreshToken}
|
|
3183
3857
|
`);
|
|
3184
|
-
console.log(
|
|
3858
|
+
console.log(import_chalk28.default.dim("These are your own tokens \u2014 keep them secret."));
|
|
3185
3859
|
}
|
|
3186
3860
|
async function runConsumerApikeys(opts) {
|
|
3187
3861
|
const creds = requireConsumer();
|
|
3188
3862
|
const { default: inquirer2 } = await import("inquirer");
|
|
3189
|
-
const spinner = (0,
|
|
3863
|
+
const spinner = (0, import_ora13.default)("Loading your API keys...").start();
|
|
3190
3864
|
const list = await consumerFetch(creds, "/apikeys");
|
|
3191
3865
|
const revealed = await consumerFetch(list.creds, "/apikeys/reveal").catch(() => ({ status: 0, data: null, creds: list.creds }));
|
|
3192
3866
|
spinner.stop();
|
|
3193
3867
|
if (list.status >= 400) {
|
|
3194
|
-
console.error(
|
|
3195
|
-
if (list.status === 401) console.error(
|
|
3868
|
+
console.error(import_chalk28.default.red(`Failed to list keys (${list.status}): ${list.data?.error ?? ""}`));
|
|
3869
|
+
if (list.status === 401) console.error(import_chalk28.default.dim("Your consumer session may have expired \u2014 run `apiblaze consumer login` again."));
|
|
3196
3870
|
process.exit(1);
|
|
3197
3871
|
}
|
|
3198
3872
|
const keys = list.data?.keys ?? [];
|
|
@@ -3200,16 +3874,16 @@ async function runConsumerApikeys(opts) {
|
|
|
3200
3874
|
if (opts.json) {
|
|
3201
3875
|
console.log(JSON.stringify({ keys, revealed: revealMap }, null, 2));
|
|
3202
3876
|
} else if (!keys.length) {
|
|
3203
|
-
console.log(
|
|
3877
|
+
console.log(import_chalk28.default.yellow("No API keys yet."));
|
|
3204
3878
|
} else {
|
|
3205
3879
|
for (const k of keys) {
|
|
3206
3880
|
const clear = revealMap[k.environment]?.key;
|
|
3207
|
-
const shown = clear ?
|
|
3208
|
-
const exp = k.expires_at ?
|
|
3209
|
-
console.log(` ${
|
|
3881
|
+
const shown = clear ? import_chalk28.default.green(clear) : import_chalk28.default.dim(`${k.key_prefix ?? ""}\u2026${k.key_suffix ?? ""}`);
|
|
3882
|
+
const exp = k.expires_at ? import_chalk28.default.dim(`exp ${k.expires_at}`) : import_chalk28.default.dim("no expiry");
|
|
3883
|
+
console.log(` ${import_chalk28.default.bold(k.environment ?? "")} ${shown} ${exp} ${import_chalk28.default.dim(k.description ?? "")}`);
|
|
3210
3884
|
}
|
|
3211
3885
|
if (Object.keys(revealMap).length === 0 && keys.some((k) => !k.expires_at)) {
|
|
3212
|
-
console.log(
|
|
3886
|
+
console.log(import_chalk28.default.dim("\n(Only expiring keys can be shown in clear; non-expiring keys show a prefix only.)"));
|
|
3213
3887
|
}
|
|
3214
3888
|
}
|
|
3215
3889
|
if (opts.json) return;
|
|
@@ -3223,7 +3897,7 @@ async function runConsumerApikeys(opts) {
|
|
|
3223
3897
|
const body = { environment: answers.environment };
|
|
3224
3898
|
if (answers.description) body.description = answers.description;
|
|
3225
3899
|
if (answers.expiresDays) body.expires_in_seconds = Number(answers.expiresDays) * 86400;
|
|
3226
|
-
const s2 = (0,
|
|
3900
|
+
const s2 = (0, import_ora13.default)("Creating key...").start();
|
|
3227
3901
|
const created = await consumerFetch(list.creds, "/apikeys", { method: "POST", body: JSON.stringify(body) });
|
|
3228
3902
|
if (created.status >= 400) {
|
|
3229
3903
|
s2.fail(`Create failed (${created.status}): ${created.data?.error ?? ""}`);
|
|
@@ -3231,13 +3905,13 @@ async function runConsumerApikeys(opts) {
|
|
|
3231
3905
|
}
|
|
3232
3906
|
s2.succeed("Key created.");
|
|
3233
3907
|
const key = created.data?.key ?? created.data?.fullKey;
|
|
3234
|
-
if (key) console.log(` ${
|
|
3235
|
-
else console.log(
|
|
3908
|
+
if (key) console.log(` ${import_chalk28.default.green(key)} ${import_chalk28.default.dim("(shown once \u2014 store it now)")}`);
|
|
3909
|
+
else console.log(import_chalk28.default.dim(" Key created; run `apiblaze consumer apikeys` to reveal it if it expires."));
|
|
3236
3910
|
}
|
|
3237
3911
|
|
|
3238
3912
|
// src/commands/sidecar.ts
|
|
3239
|
-
var
|
|
3240
|
-
var
|
|
3913
|
+
var import_chalk29 = __toESM(require("chalk"));
|
|
3914
|
+
var import_ora14 = __toESM(require("ora"));
|
|
3241
3915
|
var fs7 = __toESM(require("fs"));
|
|
3242
3916
|
var path4 = __toESM(require("path"));
|
|
3243
3917
|
init_auth();
|
|
@@ -3266,11 +3940,31 @@ function upsertEnvLocal(root, token) {
|
|
|
3266
3940
|
`;
|
|
3267
3941
|
if (!/^APIBLAZE_SIDECAR=/m.test(next)) {
|
|
3268
3942
|
next = (next.endsWith("\n") ? next : next + "\n") + `APIBLAZE_SIDECAR=on
|
|
3943
|
+
`;
|
|
3944
|
+
}
|
|
3945
|
+
if (!/^APIBLAZE_SIDECAR_VERBOSE=/m.test(next)) {
|
|
3946
|
+
next = (next.endsWith("\n") ? next : next + "\n") + `APIBLAZE_SIDECAR_VERBOSE=true
|
|
3269
3947
|
`;
|
|
3270
3948
|
}
|
|
3271
3949
|
fs7.writeFileSync(p, next);
|
|
3272
3950
|
return had ? "rotated" : "created";
|
|
3273
3951
|
}
|
|
3952
|
+
function installSidecarPackage(root) {
|
|
3953
|
+
if (fs7.existsSync(path4.join(root, "node_modules", "apiblaze", "package.json"))) {
|
|
3954
|
+
console.log(` ${import_chalk29.default.green("\u2713")} apiblaze package already installed`);
|
|
3955
|
+
return;
|
|
3956
|
+
}
|
|
3957
|
+
const has = (f) => fs7.existsSync(path4.join(root, f));
|
|
3958
|
+
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" };
|
|
3959
|
+
const spinner = (0, import_ora14.default)(`Installing the apiblaze package (${pm.cmd})\u2026`).start();
|
|
3960
|
+
try {
|
|
3961
|
+
const { execSync } = require("child_process");
|
|
3962
|
+
execSync(`${pm.cmd} ${pm.add} apiblaze`, { cwd: root, stdio: "ignore" });
|
|
3963
|
+
spinner.succeed("Installed apiblaze (the sidecar runtime).");
|
|
3964
|
+
} catch {
|
|
3965
|
+
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")}.`);
|
|
3966
|
+
}
|
|
3967
|
+
}
|
|
3274
3968
|
function readEnvKey(root) {
|
|
3275
3969
|
try {
|
|
3276
3970
|
const s = fs7.readFileSync(path4.join(root, ".env.local"), "utf8");
|
|
@@ -3323,21 +4017,64 @@ export const dynamic = "force-dynamic";
|
|
|
3323
4017
|
async function probe(u: string) {
|
|
3324
4018
|
try {
|
|
3325
4019
|
const res = await fetch(u, { headers: { "x-api-key": "demo-secret-value" }, cache: "no-store" });
|
|
3326
|
-
|
|
4020
|
+
const echo = await res.json().catch(() => ({}));
|
|
4021
|
+
// A routed call returns THROUGH APIblaze (Cloudflare) \u2192 cf-ray / server:cloudflare.
|
|
4022
|
+
// A direct call to httpbingo (fly.io) has neither. This is the reliable signal.
|
|
4023
|
+
const server = (res.headers.get("server") || "").toLowerCase();
|
|
4024
|
+
const routed = Boolean(res.headers.get("cf-ray")) || server.indexOf("cloudflare") >= 0;
|
|
4025
|
+
return { url: u, status: res.status, routed, echo };
|
|
3327
4026
|
} catch (e: any) { return { url: u, error: e?.message ?? String(e) }; }
|
|
3328
4027
|
}
|
|
3329
4028
|
|
|
3330
4029
|
export default async function Page() {
|
|
3331
|
-
if (process.env.NODE_ENV === "production") return <main style={{padding:24}}>Inspector disabled in production.</main>;
|
|
3332
|
-
const r = await probe("https://httpbingo.org/headers");
|
|
4030
|
+
if (process.env.NODE_ENV === "production") return <main style={{ padding: 24 }}>Inspector disabled in production.</main>;
|
|
4031
|
+
const r: any = await probe("https://httpbingo.org/headers");
|
|
4032
|
+
const routed = Boolean(r && r.routed);
|
|
4033
|
+
// An anonymous trial proxy routes, but completing the upstream call needs credits.
|
|
4034
|
+
const needsCredits = Boolean(r && r.echo && r.echo.claim_required);
|
|
4035
|
+
const card: any = { background: "#0b0f17", border: "1px solid #232a36", borderRadius: 14, padding: 20 };
|
|
4036
|
+
const pill: any = { display: "inline-block", padding: "4px 10px", borderRadius: 999, fontSize: 12, fontWeight: 700 };
|
|
4037
|
+
const codeBox: any = { background: "#070a10", border: "1px solid #232a36", borderRadius: 8, padding: 12, margin: 0, color: "#8ab4ff", overflowX: "auto" };
|
|
3333
4038
|
return (
|
|
3334
|
-
<main style={{ fontFamily: "ui-monospace, monospace", padding:
|
|
3335
|
-
<
|
|
3336
|
-
|
|
3337
|
-
|
|
3338
|
-
|
|
3339
|
-
|
|
3340
|
-
|
|
4039
|
+
<main style={{ fontFamily: "ui-monospace, SFMono-Regular, Menlo, monospace", background: "#070a10", minHeight: "100vh", padding: 32, color: "#e6e9ef", lineHeight: 1.6 }}>
|
|
4040
|
+
<div style={{ maxWidth: 720, margin: "0 auto" }}>
|
|
4041
|
+
<h1 style={{ fontSize: 20, margin: "0 0 6px" }}>APIblaze sidecar \u2014 live inspector</h1>
|
|
4042
|
+
<p style={{ color: "#9aa4b2", margin: "0 0 20px", fontSize: 14 }}>
|
|
4043
|
+
This dev-only page makes one real call to <b style={{ color: "#e6e9ef" }}>httpbingo.org</b> from your server so you can watch the sidecar work. Every external fetch your app makes either <b>routes</b> through APIblaze (once you approve that origin) or goes <b>direct</b>, untouched.
|
|
4044
|
+
</p>
|
|
4045
|
+
<div style={card}>
|
|
4046
|
+
<div style={{ display: "flex", alignItems: "center", justifyContent: "space-between", gap: 12, marginBottom: 14, flexWrap: "wrap" }}>
|
|
4047
|
+
<span style={{ fontSize: 15 }}>GET https://httpbingo.org/headers</span>
|
|
4048
|
+
{routed
|
|
4049
|
+
? <span style={{ ...pill, background: "#0f2e20", color: "#4ade80" }}>\u25CF routed through APIblaze</span>
|
|
4050
|
+
: <span style={{ ...pill, background: "#33280c", color: "#fbbf24" }}>\u25CB going direct \u2014 not approved</span>}
|
|
4051
|
+
</div>
|
|
4052
|
+
{routed && needsCredits ? (
|
|
4053
|
+
<div style={{ color: "#9aa4b2", fontSize: 14 }}>
|
|
4054
|
+
<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>
|
|
4055
|
+
<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>
|
|
4056
|
+
<pre style={codeBox}>apiblaze login && apiblaze claim</pre>
|
|
4057
|
+
<p style={{ margin: "10px 0 0" }}>\u2026then reload \u2014 the call completes end to end.</p>
|
|
4058
|
+
</div>
|
|
4059
|
+
) : routed ? (
|
|
4060
|
+
<p style={{ color: "#9aa4b2", fontSize: 14, margin: 0 }}>
|
|
4061
|
+
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.
|
|
4062
|
+
</p>
|
|
4063
|
+
) : (
|
|
4064
|
+
<div style={{ color: "#9aa4b2", fontSize: 14 }}>
|
|
4065
|
+
<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>
|
|
4066
|
+
<p style={{ margin: "0 0 6px", color: "#e6e9ef" }}>Route it through APIblaze:</p>
|
|
4067
|
+
<pre style={codeBox}>npx apiblaze sidecar approve httpbingo.org</pre>
|
|
4068
|
+
<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>
|
|
4069
|
+
</div>
|
|
4070
|
+
)}
|
|
4071
|
+
</div>
|
|
4072
|
+
<details style={{ marginTop: 16, color: "#9aa4b2", fontSize: 13 }}>
|
|
4073
|
+
<summary style={{ cursor: "pointer" }}>Raw response {r && r.status ? "(HTTP " + r.status + ")" : "(error)"}</summary>
|
|
4074
|
+
<pre style={{ background: "#0b0f17", border: "1px solid #232a36", borderRadius: 10, padding: 14, overflowX: "auto", marginTop: 8 }}>{JSON.stringify(r, null, 2)}</pre>
|
|
4075
|
+
</details>
|
|
4076
|
+
<p style={{ color: "#5b6472", fontSize: 12, marginTop: 20 }}>Dev-only \u2014 delete <code>app/abz-inspector</code> before shipping.</p>
|
|
4077
|
+
</div>
|
|
3341
4078
|
</main>
|
|
3342
4079
|
);
|
|
3343
4080
|
}
|
|
@@ -3364,7 +4101,7 @@ async function runAnonymousInit(root, router, opts) {
|
|
|
3364
4101
|
const { sidecarInitAnonymous: sidecarInitAnonymous2 } = await Promise.resolve().then(() => (init_api(), api_exports));
|
|
3365
4102
|
const { saveAnonCred: saveAnonCred2, clearAnonCred: clearAnonCred2 } = await Promise.resolve().then(() => (init_anon_cred(), anon_cred_exports));
|
|
3366
4103
|
if (opts.newSession) clearAnonCred2();
|
|
3367
|
-
const spinner = (0,
|
|
4104
|
+
const spinner = (0, import_ora14.default)("Setting up a sidecar (no login needed)...").start();
|
|
3368
4105
|
let out;
|
|
3369
4106
|
try {
|
|
3370
4107
|
out = await sidecarInitAnonymous2();
|
|
@@ -3376,28 +4113,29 @@ async function runAnonymousInit(root, router, opts) {
|
|
|
3376
4113
|
if (out.cp_key && out.team_id) saveAnonCred2(out.cp_key, out.team_id, out.claim_code);
|
|
3377
4114
|
const envState = upsertEnvLocal(root, out.token);
|
|
3378
4115
|
ensureGitignored(root);
|
|
3379
|
-
console.log(` ${
|
|
3380
|
-
console.log(` ${
|
|
4116
|
+
console.log(` ${import_chalk29.default.green("\u2713")} .env.local ${envState} (APIBLAZE_API_KEY) \u2014 gitignored`);
|
|
4117
|
+
console.log(` ${import_chalk29.default.green("\u2713")} instrumentation.ts ${wireInstrumentation(root)}`);
|
|
4118
|
+
installSidecarPackage(root);
|
|
3381
4119
|
let inspectorPath = null;
|
|
3382
4120
|
if (!opts.noInspector) {
|
|
3383
4121
|
inspectorPath = generateInspector(root, router);
|
|
3384
|
-
if (inspectorPath) console.log(` ${
|
|
4122
|
+
if (inspectorPath) console.log(` ${import_chalk29.default.green("\u2713")} inspector at ${inspectorPath}`);
|
|
3385
4123
|
}
|
|
3386
4124
|
console.log("");
|
|
3387
|
-
console.log(
|
|
3388
|
-
console.log(` 1. ${
|
|
4125
|
+
console.log(import_chalk29.default.bold("Done (no account needed). What happens next:"));
|
|
4126
|
+
console.log(` 1. ${import_chalk29.default.cyan("npm run dev")} and use your app.`);
|
|
3389
4127
|
console.log(` 2. Each external origin your app calls is logged in the console \u2014 approve one with:`);
|
|
3390
|
-
console.log(` ${
|
|
4128
|
+
console.log(` ${import_chalk29.default.cyan("apiblaze sidecar approve api.stripe.com")} (no login needed)`);
|
|
3391
4129
|
console.log("");
|
|
3392
|
-
console.log(
|
|
3393
|
-
console.log(` ${
|
|
3394
|
-
console.log(
|
|
4130
|
+
console.log(import_chalk29.default.bold(" \u{1F511} Keep your setup \u2014 claim it into an account:"));
|
|
4131
|
+
console.log(` ${import_chalk29.default.cyan("apiblaze login")} then ${import_chalk29.default.cyan("apiblaze claim")} ${import_chalk29.default.dim("(no code needed here)")}`);
|
|
4132
|
+
console.log(import_chalk29.default.dim(` From another machine: apiblaze claim ${out.claim_code} \xB7 expires in 30 days`));
|
|
3395
4133
|
}
|
|
3396
4134
|
async function runSidecar(opts) {
|
|
3397
4135
|
const root = path4.resolve(opts.dir ?? process.cwd());
|
|
3398
4136
|
const detected = detectNextProject(root);
|
|
3399
4137
|
if (!detected.found) {
|
|
3400
|
-
console.log(
|
|
4138
|
+
console.log(import_chalk29.default.yellow(`No Next.js project detected in ${root}.`));
|
|
3401
4139
|
console.log("Create one (e.g. `npx create-next-app`) and re-run `apiblaze init` inside it.");
|
|
3402
4140
|
return;
|
|
3403
4141
|
}
|
|
@@ -3408,9 +4146,10 @@ async function runSidecar(opts) {
|
|
|
3408
4146
|
if (!loadCredentials()) {
|
|
3409
4147
|
upsertEnvLocal(root, readEnvKey(root));
|
|
3410
4148
|
ensureGitignored(root);
|
|
3411
|
-
console.log(` ${
|
|
3412
|
-
console.log(` ${
|
|
3413
|
-
|
|
4149
|
+
console.log(` ${import_chalk29.default.green("\u2713")} .env.local present (APIBLAZE_API_KEY) \u2014 reusing`);
|
|
4150
|
+
console.log(` ${import_chalk29.default.green("\u2713")} instrumentation.ts ${wireInstrumentation(root)}`);
|
|
4151
|
+
installSidecarPackage(root);
|
|
4152
|
+
console.log(import_chalk29.default.dim(" Log in and run `apiblaze claim <code>` to keep this setup, or `apiblaze login` to manage it."));
|
|
3414
4153
|
return;
|
|
3415
4154
|
}
|
|
3416
4155
|
const { teamId, teamName } = await resolveTeam(opts.team);
|
|
@@ -3419,7 +4158,7 @@ async function runSidecar(opts) {
|
|
|
3419
4158
|
const mustMint = !existingKey || opts.rotate || switchingTeam;
|
|
3420
4159
|
let token = existingKey ?? "";
|
|
3421
4160
|
if (mustMint) {
|
|
3422
|
-
const spinner = (0,
|
|
4161
|
+
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();
|
|
3423
4162
|
try {
|
|
3424
4163
|
const out = await admin({
|
|
3425
4164
|
method: "POST",
|
|
@@ -3433,39 +4172,39 @@ async function runSidecar(opts) {
|
|
|
3433
4172
|
throw err;
|
|
3434
4173
|
}
|
|
3435
4174
|
} else {
|
|
3436
|
-
console.log(
|
|
4175
|
+
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).`));
|
|
3437
4176
|
}
|
|
3438
4177
|
const envState = upsertEnvLocal(root, token);
|
|
3439
4178
|
ensureGitignored(root);
|
|
3440
|
-
console.log(` ${
|
|
4179
|
+
console.log(` ${import_chalk29.default.green("\u2713")} .env.local ${envState} (APIBLAZE_API_KEY) \u2014 gitignored`);
|
|
3441
4180
|
const wireState = wireInstrumentation(root);
|
|
3442
|
-
console.log(` ${
|
|
4181
|
+
console.log(` ${import_chalk29.default.green("\u2713")} instrumentation.ts ${wireState}`);
|
|
4182
|
+
installSidecarPackage(root);
|
|
3443
4183
|
let inspectorPath = null;
|
|
3444
4184
|
if (!opts.noInspector) {
|
|
3445
4185
|
inspectorPath = generateInspector(root, detected.router);
|
|
3446
|
-
if (inspectorPath) console.log(` ${
|
|
4186
|
+
if (inspectorPath) console.log(` ${import_chalk29.default.green("\u2713")} inspector at ${inspectorPath}`);
|
|
3447
4187
|
}
|
|
3448
4188
|
console.log("");
|
|
3449
|
-
console.log(
|
|
3450
|
-
console.log(` 1. ${
|
|
3451
|
-
console.log(` 2. ${
|
|
3452
|
-
console.log(` 3.
|
|
3453
|
-
console.log(` 4. Approve the ones to route: ${import_chalk28.default.cyan("apiblaze origins approve api.stripe.com")} (or in the dashboard)`);
|
|
4189
|
+
console.log(import_chalk29.default.bold("Done. What happens next:"));
|
|
4190
|
+
console.log(` 1. ${import_chalk29.default.cyan("npm run dev")} and use your app \u2014 it works exactly as before (all calls go direct).`);
|
|
4191
|
+
console.log(` 2. The origins your app calls appear as ${import_chalk29.default.bold("candidates")} \u2014 list them: ${import_chalk29.default.cyan("apiblaze sidecar")}`);
|
|
4192
|
+
console.log(` 3. Approve the ones to route: ${import_chalk29.default.cyan("apiblaze sidecar approve api.stripe.com")} (or in the dashboard)`);
|
|
3454
4193
|
console.log(` \u2026within ~5 min your app starts routing that origin through APIblaze.`);
|
|
3455
|
-
if (inspectorPath) console.log(` \u2022 Try it now: open ${
|
|
3456
|
-
if (switchingTeam) console.log(
|
|
4194
|
+
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)`);
|
|
4195
|
+
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>\`.`));
|
|
3457
4196
|
console.log("");
|
|
3458
|
-
console.log(
|
|
3459
|
-
console.log(
|
|
3460
|
-
console.log(
|
|
4197
|
+
console.log(import_chalk29.default.dim(" Manage: apiblaze sidecar (list/approve/deny/remove)"));
|
|
4198
|
+
console.log(import_chalk29.default.dim(" Rotate: apiblaze init --rotate \xB7 Switch team: apiblaze init --team <name>"));
|
|
4199
|
+
console.log(import_chalk29.default.dim(" Turn off: set APIBLAZE_SIDECAR=off in .env.local (flip back to on anytime; key stays put)."));
|
|
3461
4200
|
console.log("");
|
|
3462
|
-
console.log(
|
|
3463
|
-
console.log(
|
|
4201
|
+
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."));
|
|
4202
|
+
console.log(import_chalk29.default.dim(" Your control-plane login stays in ~/.apiblaze \u2014 it never entered this project."));
|
|
3464
4203
|
}
|
|
3465
4204
|
|
|
3466
4205
|
// src/commands/origins.ts
|
|
3467
|
-
var
|
|
3468
|
-
var
|
|
4206
|
+
var import_chalk30 = __toESM(require("chalk"));
|
|
4207
|
+
var import_ora15 = __toESM(require("ora"));
|
|
3469
4208
|
init_auth();
|
|
3470
4209
|
init_anon_cred();
|
|
3471
4210
|
async function runOriginsList(opts) {
|
|
@@ -3473,7 +4212,7 @@ async function runOriginsList(opts) {
|
|
|
3473
4212
|
if (!loadCredentials()) {
|
|
3474
4213
|
const cred = loadAnonCred();
|
|
3475
4214
|
if (!cred) {
|
|
3476
|
-
console.log(
|
|
4215
|
+
console.log(import_chalk30.default.yellow("No anonymous workspace here. Run `apiblaze init` first."));
|
|
3477
4216
|
return;
|
|
3478
4217
|
}
|
|
3479
4218
|
out = await cpFetch(cred.cp_key, `/teams/${encodeURIComponent(cred.team_id)}/sidecar/candidates`, { method: "GET" });
|
|
@@ -3491,30 +4230,30 @@ async function runOriginsList(opts) {
|
|
|
3491
4230
|
}
|
|
3492
4231
|
const routed = out.routed ?? [];
|
|
3493
4232
|
const candidates = out.candidates ?? [];
|
|
3494
|
-
console.log(
|
|
4233
|
+
console.log(import_chalk30.default.bold(`
|
|
3495
4234
|
Routed through APIblaze (${routed.length})`));
|
|
3496
|
-
if (!routed.length) console.log(
|
|
3497
|
-
for (const r of routed) console.log(` ${
|
|
3498
|
-
console.log(
|
|
4235
|
+
if (!routed.length) console.log(import_chalk30.default.dim(" none yet"));
|
|
4236
|
+
for (const r of routed) console.log(` ${import_chalk30.default.green("\u25CF")} ${r.sidecar_origin} ${import_chalk30.default.dim(`\u2192 ${r.project_id}`)}`);
|
|
4237
|
+
console.log(import_chalk30.default.bold(`
|
|
3499
4238
|
Candidates \u2014 going direct, not yet approved (${candidates.length})`));
|
|
3500
|
-
if (!candidates.length) console.log(
|
|
4239
|
+
if (!candidates.length) console.log(import_chalk30.default.dim(" none \u2014 run your app to discover the origins it calls"));
|
|
3501
4240
|
for (const c of candidates) {
|
|
3502
|
-
console.log(` ${
|
|
4241
|
+
console.log(` ${import_chalk30.default.yellow("\u25CB")} ${c.origin} ${import_chalk30.default.dim(`seen ${c.request_count}\xD7, last ${c.last_seen}`)}`);
|
|
3503
4242
|
}
|
|
3504
4243
|
if (candidates.length) {
|
|
3505
|
-
console.log(
|
|
3506
|
-
Approve: apiblaze
|
|
3507
|
-
console.log(
|
|
4244
|
+
console.log(import_chalk30.default.dim(`
|
|
4245
|
+
Approve: apiblaze sidecar approve ${candidates[0].origin.replace("https://", "")}`));
|
|
4246
|
+
console.log(import_chalk30.default.dim(` Dismiss: apiblaze sidecar deny ${candidates[0].origin.replace("https://", "")}`));
|
|
3508
4247
|
}
|
|
3509
4248
|
}
|
|
3510
4249
|
async function runOriginsApprove(origin, opts) {
|
|
3511
4250
|
if (!loadCredentials()) {
|
|
3512
4251
|
const cred = loadAnonCred();
|
|
3513
4252
|
if (!cred) {
|
|
3514
|
-
console.error(
|
|
4253
|
+
console.error(import_chalk30.default.red("Not logged in and no anonymous workspace. Run `apiblaze init` first."));
|
|
3515
4254
|
process.exit(1);
|
|
3516
4255
|
}
|
|
3517
|
-
const spinner2 = (0,
|
|
4256
|
+
const spinner2 = (0, import_ora15.default)(`Approving ${origin} (anonymous)...`).start();
|
|
3518
4257
|
try {
|
|
3519
4258
|
const out = await cpFetch(cred.cp_key, `/teams/${encodeURIComponent(cred.team_id)}/sidecar/approve`, { method: "POST", body: JSON.stringify({ origin }) });
|
|
3520
4259
|
spinner2.succeed(`Approved ${origin} \u2192 proxy ${out.project_id}. Routing within ~5 min.`);
|
|
@@ -3525,7 +4264,7 @@ async function runOriginsApprove(origin, opts) {
|
|
|
3525
4264
|
return;
|
|
3526
4265
|
}
|
|
3527
4266
|
const { teamId } = await resolveTeam(opts.team);
|
|
3528
|
-
const spinner = (0,
|
|
4267
|
+
const spinner = (0, import_ora15.default)(`Approving ${origin}...`).start();
|
|
3529
4268
|
try {
|
|
3530
4269
|
const out = await admin({
|
|
3531
4270
|
method: "POST",
|
|
@@ -3542,7 +4281,7 @@ async function runOriginsApprove(origin, opts) {
|
|
|
3542
4281
|
}
|
|
3543
4282
|
async function runOriginsDeny(origin, opts) {
|
|
3544
4283
|
const { teamId } = await resolveTeam(opts.team);
|
|
3545
|
-
const spinner = (0,
|
|
4284
|
+
const spinner = (0, import_ora15.default)(`Dismissing ${origin}...`).start();
|
|
3546
4285
|
try {
|
|
3547
4286
|
await admin({ method: "POST", path: `/teams/${encodeURIComponent(teamId)}/sidecar/dismiss`, body: { origin }, summary: `Dismiss sidecar origin ${origin}` });
|
|
3548
4287
|
spinner.succeed(`Dismissed ${origin}. It won't be suggested again.`);
|
|
@@ -3553,7 +4292,7 @@ async function runOriginsDeny(origin, opts) {
|
|
|
3553
4292
|
}
|
|
3554
4293
|
async function runOriginsRemove(origin, opts) {
|
|
3555
4294
|
const { teamId } = await resolveTeam(opts.team);
|
|
3556
|
-
const spinner = (0,
|
|
4295
|
+
const spinner = (0, import_ora15.default)(`Removing the proxy for ${origin}...`).start();
|
|
3557
4296
|
try {
|
|
3558
4297
|
await admin({ method: "POST", path: `/teams/${encodeURIComponent(teamId)}/sidecar/remove`, body: { origin }, summary: `Un-route sidecar origin ${origin}` });
|
|
3559
4298
|
spinner.succeed(`Removed ${origin}. Your app will stop routing it (goes direct) within ~5 min.`);
|
|
@@ -3563,6 +4302,131 @@ async function runOriginsRemove(origin, opts) {
|
|
|
3563
4302
|
}
|
|
3564
4303
|
}
|
|
3565
4304
|
|
|
4305
|
+
// src/commands/op.ts
|
|
4306
|
+
var import_chalk31 = __toESM(require("chalk"));
|
|
4307
|
+
init_auth();
|
|
4308
|
+
init_types();
|
|
4309
|
+
var OPERATOR_EMAILS = /* @__PURE__ */ new Set(["julienpmjacquet@gmail.com", "chkev@umich.edu"]);
|
|
4310
|
+
var DASHBOARD_BASE6 = process.env.APIBLAZE_DASHBOARD_BASE || "https://dashboard.apiblaze.com";
|
|
4311
|
+
function isOperatorLogin() {
|
|
4312
|
+
const email = loadCredentials()?.email?.toLowerCase();
|
|
4313
|
+
return !!email && OPERATOR_EMAILS.has(email);
|
|
4314
|
+
}
|
|
4315
|
+
async function opCall(call) {
|
|
4316
|
+
const token = getAccessToken();
|
|
4317
|
+
const res = await fetch(`${DASHBOARD_BASE6}/api/cli/op`, {
|
|
4318
|
+
method: "POST",
|
|
4319
|
+
headers: { "Content-Type": "application/json", Authorization: `Bearer ${token}` },
|
|
4320
|
+
body: JSON.stringify({ path: call.path, method: call.method, body: call.body })
|
|
4321
|
+
});
|
|
4322
|
+
let data = null;
|
|
4323
|
+
try {
|
|
4324
|
+
data = await res.json();
|
|
4325
|
+
} catch {
|
|
4326
|
+
}
|
|
4327
|
+
recordCall({ method: call.method, path: call.path, body: call.body, status: res.status, summary: call.summary });
|
|
4328
|
+
if (!res.ok) {
|
|
4329
|
+
const msg = data?.details ?? data?.error ?? res.statusText;
|
|
4330
|
+
throw new ApiError(res.status, typeof msg === "string" ? msg : JSON.stringify(msg), data);
|
|
4331
|
+
}
|
|
4332
|
+
return data;
|
|
4333
|
+
}
|
|
4334
|
+
function printResidue(report, applied) {
|
|
4335
|
+
const up = report?.upstash ?? {};
|
|
4336
|
+
const fga = report?.fga ?? {};
|
|
4337
|
+
console.log(import_chalk31.default.bold(applied ? "\nExternal-residue sweep" : "\nExternal residue (dry-run \u2014 nothing deleted)"));
|
|
4338
|
+
console.log(import_chalk31.default.bold("\n Upstash"));
|
|
4339
|
+
const orphans = up.orphans ?? [];
|
|
4340
|
+
if (orphans.length === 0) console.log(import_chalk31.default.green(" no orphaned keys"));
|
|
4341
|
+
for (const o of orphans) console.log(` ${import_chalk31.default.yellow(o.key)} ${import_chalk31.default.dim(`\u2014 ${o.reason}`)}`);
|
|
4342
|
+
console.log(import_chalk31.default.dim(` kept (live principals): ${up.kept ?? 0} \xB7 anon wallets (untouched): ${up.anon_wallets ?? 0}`));
|
|
4343
|
+
if (up.unknown?.length) console.log(import_chalk31.default.dim(` unknown (never deleted): ${up.unknown.join(", ")}`));
|
|
4344
|
+
if (applied) console.log(` ${import_chalk31.default.bold(String(up.deleted ?? 0))} key(s) deleted`);
|
|
4345
|
+
for (const e of up.errors ?? []) console.log(import_chalk31.default.red(` error: ${e}`));
|
|
4346
|
+
console.log(import_chalk31.default.bold("\n OpenFGA / Neon"));
|
|
4347
|
+
if (applied) {
|
|
4348
|
+
const swept = fga?.swept ?? [];
|
|
4349
|
+
if (swept.length === 0) console.log(import_chalk31.default.green(" no orphaned stores"));
|
|
4350
|
+
for (const s of swept) {
|
|
4351
|
+
console.log(
|
|
4352
|
+
` ${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`)}`
|
|
4353
|
+
);
|
|
4354
|
+
}
|
|
4355
|
+
if (fga?.remaining) console.log(import_chalk31.default.yellow(` ${fga.remaining} more orphan store(s) \u2014 re-run to drain`));
|
|
4356
|
+
} else {
|
|
4357
|
+
const fgaOrphans = fga?.orphans ?? [];
|
|
4358
|
+
if (fgaOrphans.length === 0) console.log(import_chalk31.default.green(" no orphaned stores"));
|
|
4359
|
+
for (const s of fgaOrphans) {
|
|
4360
|
+
const src = s.in_openfga ? "live in OpenFGA" : "Neon tuples only";
|
|
4361
|
+
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)`)}`);
|
|
4362
|
+
}
|
|
4363
|
+
console.log(import_chalk31.default.dim(` kept stores: ${(fga?.kept_store_ids ?? []).length}`));
|
|
4364
|
+
}
|
|
4365
|
+
for (const e of fga?.errors ?? []) console.log(import_chalk31.default.red(` error: ${e}`));
|
|
4366
|
+
console.log();
|
|
4367
|
+
}
|
|
4368
|
+
async function runOp(sub, opts = {}) {
|
|
4369
|
+
if (!loadCredentials()) {
|
|
4370
|
+
console.log(import_chalk31.default.dim("Not logged in. Run `apiblaze login`."));
|
|
4371
|
+
return;
|
|
4372
|
+
}
|
|
4373
|
+
if (!isOperatorLogin()) {
|
|
4374
|
+
console.log(import_chalk31.default.dim("`apiblaze op` is only available to platform operators."));
|
|
4375
|
+
return;
|
|
4376
|
+
}
|
|
4377
|
+
switch (sub) {
|
|
4378
|
+
case void 0:
|
|
4379
|
+
case "menu": {
|
|
4380
|
+
console.log(import_chalk31.default.bold("\nOperator menu"));
|
|
4381
|
+
console.log(` ${import_chalk31.default.cyan("apiblaze op residue")} external-store residue report (Upstash + Neon/OpenFGA, dry-run)`);
|
|
4382
|
+
console.log(` ${import_chalk31.default.cyan("apiblaze op sweep")} delete the orphans the report shows (asks first; ${import_chalk31.default.dim("-y to skip")})`);
|
|
4383
|
+
console.log(` ${import_chalk31.default.cyan("apiblaze op credits")} list credit wallets
|
|
4384
|
+
`);
|
|
4385
|
+
return;
|
|
4386
|
+
}
|
|
4387
|
+
case "residue": {
|
|
4388
|
+
const report = await opCall({ method: "GET", path: "/operator/external-residue", summary: "external residue report" });
|
|
4389
|
+
if (opts.json) return void console.log(JSON.stringify(report, null, 2));
|
|
4390
|
+
printResidue(report, false);
|
|
4391
|
+
return;
|
|
4392
|
+
}
|
|
4393
|
+
case "sweep": {
|
|
4394
|
+
const report = await opCall({ method: "GET", path: "/operator/external-residue", summary: "external residue report" });
|
|
4395
|
+
const nUp = report?.upstash?.orphans?.length ?? 0;
|
|
4396
|
+
const nFga = report?.fga?.orphans?.length ?? 0;
|
|
4397
|
+
printResidue(report, false);
|
|
4398
|
+
if (nUp + nFga === 0) {
|
|
4399
|
+
console.log(import_chalk31.default.green("Nothing to sweep."));
|
|
4400
|
+
return;
|
|
4401
|
+
}
|
|
4402
|
+
if (!opts.yes) {
|
|
4403
|
+
const readline2 = await import("readline/promises");
|
|
4404
|
+
const rl = readline2.createInterface({ input: process.stdin, output: process.stdout });
|
|
4405
|
+
const answer = await rl.question(import_chalk31.default.red(`Delete ${nUp} Upstash key(s) + ${nFga} OpenFGA store(s)? Type 'sweep' to confirm: `));
|
|
4406
|
+
rl.close();
|
|
4407
|
+
if (answer.trim() !== "sweep") return void console.log(import_chalk31.default.dim("Aborted."));
|
|
4408
|
+
}
|
|
4409
|
+
const result = await opCall({ method: "POST", path: "/operator/external-residue/sweep", summary: "external residue sweep" });
|
|
4410
|
+
if (opts.json) return void console.log(JSON.stringify(result, null, 2));
|
|
4411
|
+
printResidue(result, true);
|
|
4412
|
+
return;
|
|
4413
|
+
}
|
|
4414
|
+
case "credits": {
|
|
4415
|
+
const data = await opCall({ method: "GET", path: "/operator/credits", summary: "list credit wallets" });
|
|
4416
|
+
if (opts.json) return void console.log(JSON.stringify(data, null, 2));
|
|
4417
|
+
const accounts = data?.accounts ?? [];
|
|
4418
|
+
if (accounts.length === 0) return void console.log(import_chalk31.default.dim("No credit wallets."));
|
|
4419
|
+
for (const a of accounts) {
|
|
4420
|
+
const bal = typeof a.balance_cents === "number" ? `$${(a.balance_cents / 100).toFixed(2)}` : "?";
|
|
4421
|
+
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") : ""}`);
|
|
4422
|
+
}
|
|
4423
|
+
return;
|
|
4424
|
+
}
|
|
4425
|
+
default:
|
|
4426
|
+
console.log(import_chalk31.default.red(`Unknown op subcommand '${sub}'. Run \`apiblaze op\` for the menu.`));
|
|
4427
|
+
}
|
|
4428
|
+
}
|
|
4429
|
+
|
|
3566
4430
|
// src/index.ts
|
|
3567
4431
|
var program = new import_commander.Command();
|
|
3568
4432
|
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)");
|
|
@@ -3602,16 +4466,19 @@ var agent = program.command("agent").description("Chat with an assistant that bu
|
|
|
3602
4466
|
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)));
|
|
3603
4467
|
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)));
|
|
3604
4468
|
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)));
|
|
3605
|
-
|
|
3606
|
-
var
|
|
3607
|
-
|
|
3608
|
-
|
|
3609
|
-
|
|
4469
|
+
var runSetup = (opts) => runSidecar({ team: opts.team, dir: opts.dir, yes: opts.yes, noInspector: opts.inspector === false, rotate: opts.rotate, newSession: opts.newSession });
|
|
4470
|
+
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");
|
|
4471
|
+
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)));
|
|
4472
|
+
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)));
|
|
4473
|
+
withSetupOptions(sidecar.command("setup").description("Wire a Next.js app to route its external fetches through APIblaze")).action(action((opts) => runSetup(opts)));
|
|
4474
|
+
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)));
|
|
4475
|
+
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)));
|
|
4476
|
+
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)));
|
|
3610
4477
|
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) => {
|
|
3611
4478
|
try {
|
|
3612
4479
|
const resolved = parseInt(port ?? opts.port, 10);
|
|
3613
4480
|
if (Number.isNaN(resolved)) {
|
|
3614
|
-
console.error(
|
|
4481
|
+
console.error(import_chalk32.default.red(`Invalid port: ${port ?? opts.port}`));
|
|
3615
4482
|
process.exit(1);
|
|
3616
4483
|
}
|
|
3617
4484
|
await runDev({ port: resolved, captureFile: opts.captureFile });
|
|
@@ -3659,6 +4526,7 @@ program.command("projects").description("List the projects in your team").action
|
|
|
3659
4526
|
});
|
|
3660
4527
|
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)));
|
|
3661
4528
|
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 })));
|
|
4529
|
+
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)));
|
|
3662
4530
|
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)));
|
|
3663
4531
|
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)));
|
|
3664
4532
|
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)));
|
|
@@ -3679,13 +4547,15 @@ apikeys.command("list").description("List control-plane API keys in your team").
|
|
|
3679
4547
|
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)));
|
|
3680
4548
|
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)));
|
|
3681
4549
|
program.addCommand(apikeys, { hidden: true });
|
|
4550
|
+
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)));
|
|
4551
|
+
program.addCommand(op, { hidden: true });
|
|
3682
4552
|
var spec = program.command("spec").description("View or update a proxy's OpenAPI spec (or build one by chatting: apiblaze agent openapi)");
|
|
3683
4553
|
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)));
|
|
3684
4554
|
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)));
|
|
3685
4555
|
var HELP_GROUPS = [
|
|
3686
4556
|
{ title: "Chat", commands: ["agent"] },
|
|
3687
|
-
{ title: "Setup", commands: ["login", "create", "init", "
|
|
3688
|
-
{ title: "Control plane commands", commands: ["projects", "tenant", "domain", "delete", "target", "throttle", "rename", "spec", "export"] },
|
|
4557
|
+
{ title: "Setup", commands: ["login", "create", "init", "sidecar", "dev", "claim", "team", "whoami", "logout"] },
|
|
4558
|
+
{ title: "Control plane commands", commands: ["config", "projects", "tenant", "domain", "delete", "target", "throttle", "rename", "spec", "export"] },
|
|
3689
4559
|
{ title: "Data plane commands", commands: [
|
|
3690
4560
|
{ parent: "consumer", sub: "login" },
|
|
3691
4561
|
{ parent: "consumer", sub: "apikeys" }
|
|
@@ -3704,7 +4574,7 @@ function groupedCommandHelp() {
|
|
|
3704
4574
|
const sub = byName.get(e.parent)?.commands.find((s) => s.name() === e.sub);
|
|
3705
4575
|
return sub ? ` ${helpLabel(e).padEnd(width)}${sub.description()}` : "";
|
|
3706
4576
|
}).filter(Boolean).join("\n");
|
|
3707
|
-
return `${
|
|
4577
|
+
return `${import_chalk32.default.bold(g.title)}
|
|
3708
4578
|
${rows}`;
|
|
3709
4579
|
}).join("\n\n");
|
|
3710
4580
|
}
|
|
@@ -3718,7 +4588,9 @@ program.addHelpText("after", () => `
|
|
|
3718
4588
|
${groupedCommandHelp()}
|
|
3719
4589
|
|
|
3720
4590
|
Tips:
|
|
4591
|
+
\u2022 \`apiblaze config <project>\` browses EVERY setting & feature (works logged-out to explore).
|
|
3721
4592
|
\u2022 Add --verbose to any command to see the equivalent API calls.
|
|
4593
|
+
\u2022 Full API reference: https://api.apiblaze.com/openapi.json
|
|
3722
4594
|
\u2022 Run \`apiblaze <command> --help\` (e.g. \`apiblaze consumer --help\`) for sub-commands.
|
|
3723
4595
|
|
|
3724
4596
|
Examples:
|
|
@@ -3730,13 +4602,13 @@ Examples:
|
|
|
3730
4602
|
`);
|
|
3731
4603
|
function printError(err) {
|
|
3732
4604
|
if (err instanceof ApiError) {
|
|
3733
|
-
console.error(
|
|
4605
|
+
console.error(import_chalk32.default.red(`
|
|
3734
4606
|
API error (${err.status}): ${err.message}`));
|
|
3735
4607
|
} else if (err instanceof Error) {
|
|
3736
|
-
console.error(
|
|
4608
|
+
console.error(import_chalk32.default.red(`
|
|
3737
4609
|
Error: ${err.message}`));
|
|
3738
4610
|
} else {
|
|
3739
|
-
console.error(
|
|
4611
|
+
console.error(import_chalk32.default.red("\nUnknown error"));
|
|
3740
4612
|
}
|
|
3741
4613
|
}
|
|
3742
4614
|
program.parse(process.argv);
|