apiblaze 0.10.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 +1025 -205
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -302,10 +302,10 @@ var init_anon_cred = __esm({
|
|
|
302
302
|
|
|
303
303
|
// src/index.ts
|
|
304
304
|
var import_commander = require("commander");
|
|
305
|
-
var
|
|
305
|
+
var import_chalk32 = __toESM(require("chalk"));
|
|
306
306
|
|
|
307
307
|
// package.json
|
|
308
|
-
var version = "0.
|
|
308
|
+
var version = "0.11.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();
|
|
@@ -3277,18 +3951,18 @@ function upsertEnvLocal(root, token) {
|
|
|
3277
3951
|
}
|
|
3278
3952
|
function installSidecarPackage(root) {
|
|
3279
3953
|
if (fs7.existsSync(path4.join(root, "node_modules", "apiblaze", "package.json"))) {
|
|
3280
|
-
console.log(` ${
|
|
3954
|
+
console.log(` ${import_chalk29.default.green("\u2713")} apiblaze package already installed`);
|
|
3281
3955
|
return;
|
|
3282
3956
|
}
|
|
3283
3957
|
const has = (f) => fs7.existsSync(path4.join(root, f));
|
|
3284
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" };
|
|
3285
|
-
const spinner = (0,
|
|
3959
|
+
const spinner = (0, import_ora14.default)(`Installing the apiblaze package (${pm.cmd})\u2026`).start();
|
|
3286
3960
|
try {
|
|
3287
3961
|
const { execSync } = require("child_process");
|
|
3288
3962
|
execSync(`${pm.cmd} ${pm.add} apiblaze`, { cwd: root, stdio: "ignore" });
|
|
3289
3963
|
spinner.succeed("Installed apiblaze (the sidecar runtime).");
|
|
3290
3964
|
} catch {
|
|
3291
|
-
spinner.warn(`Couldn't auto-install \u2014 run ${
|
|
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")}.`);
|
|
3292
3966
|
}
|
|
3293
3967
|
}
|
|
3294
3968
|
function readEnvKey(root) {
|
|
@@ -3343,18 +4017,24 @@ export const dynamic = "force-dynamic";
|
|
|
3343
4017
|
async function probe(u: string) {
|
|
3344
4018
|
try {
|
|
3345
4019
|
const res = await fetch(u, { headers: { "x-api-key": "demo-secret-value" }, cache: "no-store" });
|
|
3346
|
-
|
|
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 };
|
|
3347
4026
|
} catch (e: any) { return { url: u, error: e?.message ?? String(e) }; }
|
|
3348
4027
|
}
|
|
3349
4028
|
|
|
3350
4029
|
export default async function Page() {
|
|
3351
4030
|
if (process.env.NODE_ENV === "production") return <main style={{ padding: 24 }}>Inspector disabled in production.</main>;
|
|
3352
4031
|
const r: any = await probe("https://httpbingo.org/headers");
|
|
3353
|
-
const
|
|
3354
|
-
//
|
|
3355
|
-
const
|
|
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);
|
|
3356
4035
|
const card: any = { background: "#0b0f17", border: "1px solid #232a36", borderRadius: 14, padding: 20 };
|
|
3357
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" };
|
|
3358
4038
|
return (
|
|
3359
4039
|
<main style={{ fontFamily: "ui-monospace, SFMono-Regular, Menlo, monospace", background: "#070a10", minHeight: "100vh", padding: 32, color: "#e6e9ef", lineHeight: 1.6 }}>
|
|
3360
4040
|
<div style={{ maxWidth: 720, margin: "0 auto" }}>
|
|
@@ -3369,16 +4049,23 @@ export default async function Page() {
|
|
|
3369
4049
|
? <span style={{ ...pill, background: "#0f2e20", color: "#4ade80" }}>\u25CF routed through APIblaze</span>
|
|
3370
4050
|
: <span style={{ ...pill, background: "#33280c", color: "#fbbf24" }}>\u25CB going direct \u2014 not approved</span>}
|
|
3371
4051
|
</div>
|
|
3372
|
-
{routed ? (
|
|
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 ? (
|
|
3373
4060
|
<p style={{ color: "#9aa4b2", fontSize: 14, margin: 0 }}>
|
|
3374
|
-
|
|
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.
|
|
3375
4062
|
</p>
|
|
3376
4063
|
) : (
|
|
3377
4064
|
<div style={{ color: "#9aa4b2", fontSize: 14 }}>
|
|
3378
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>
|
|
3379
4066
|
<p style={{ margin: "0 0 6px", color: "#e6e9ef" }}>Route it through APIblaze:</p>
|
|
3380
|
-
<pre style={
|
|
3381
|
-
<p style={{ margin: "10px 0 0" }}>\u2026wait ~5 min, then <b style={{ color: "#e6e9ef" }}>reload</b>. The badge above turns green.</p>
|
|
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>
|
|
3382
4069
|
</div>
|
|
3383
4070
|
)}
|
|
3384
4071
|
</div>
|
|
@@ -3414,7 +4101,7 @@ async function runAnonymousInit(root, router, opts) {
|
|
|
3414
4101
|
const { sidecarInitAnonymous: sidecarInitAnonymous2 } = await Promise.resolve().then(() => (init_api(), api_exports));
|
|
3415
4102
|
const { saveAnonCred: saveAnonCred2, clearAnonCred: clearAnonCred2 } = await Promise.resolve().then(() => (init_anon_cred(), anon_cred_exports));
|
|
3416
4103
|
if (opts.newSession) clearAnonCred2();
|
|
3417
|
-
const spinner = (0,
|
|
4104
|
+
const spinner = (0, import_ora14.default)("Setting up a sidecar (no login needed)...").start();
|
|
3418
4105
|
let out;
|
|
3419
4106
|
try {
|
|
3420
4107
|
out = await sidecarInitAnonymous2();
|
|
@@ -3426,29 +4113,29 @@ async function runAnonymousInit(root, router, opts) {
|
|
|
3426
4113
|
if (out.cp_key && out.team_id) saveAnonCred2(out.cp_key, out.team_id, out.claim_code);
|
|
3427
4114
|
const envState = upsertEnvLocal(root, out.token);
|
|
3428
4115
|
ensureGitignored(root);
|
|
3429
|
-
console.log(` ${
|
|
3430
|
-
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)}`);
|
|
3431
4118
|
installSidecarPackage(root);
|
|
3432
4119
|
let inspectorPath = null;
|
|
3433
4120
|
if (!opts.noInspector) {
|
|
3434
4121
|
inspectorPath = generateInspector(root, router);
|
|
3435
|
-
if (inspectorPath) console.log(` ${
|
|
4122
|
+
if (inspectorPath) console.log(` ${import_chalk29.default.green("\u2713")} inspector at ${inspectorPath}`);
|
|
3436
4123
|
}
|
|
3437
4124
|
console.log("");
|
|
3438
|
-
console.log(
|
|
3439
|
-
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.`);
|
|
3440
4127
|
console.log(` 2. Each external origin your app calls is logged in the console \u2014 approve one with:`);
|
|
3441
|
-
console.log(` ${
|
|
4128
|
+
console.log(` ${import_chalk29.default.cyan("apiblaze sidecar approve api.stripe.com")} (no login needed)`);
|
|
3442
4129
|
console.log("");
|
|
3443
|
-
console.log(
|
|
3444
|
-
console.log(` ${
|
|
3445
|
-
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`));
|
|
3446
4133
|
}
|
|
3447
4134
|
async function runSidecar(opts) {
|
|
3448
4135
|
const root = path4.resolve(opts.dir ?? process.cwd());
|
|
3449
4136
|
const detected = detectNextProject(root);
|
|
3450
4137
|
if (!detected.found) {
|
|
3451
|
-
console.log(
|
|
4138
|
+
console.log(import_chalk29.default.yellow(`No Next.js project detected in ${root}.`));
|
|
3452
4139
|
console.log("Create one (e.g. `npx create-next-app`) and re-run `apiblaze init` inside it.");
|
|
3453
4140
|
return;
|
|
3454
4141
|
}
|
|
@@ -3459,10 +4146,10 @@ async function runSidecar(opts) {
|
|
|
3459
4146
|
if (!loadCredentials()) {
|
|
3460
4147
|
upsertEnvLocal(root, readEnvKey(root));
|
|
3461
4148
|
ensureGitignored(root);
|
|
3462
|
-
console.log(` ${
|
|
3463
|
-
console.log(` ${
|
|
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)}`);
|
|
3464
4151
|
installSidecarPackage(root);
|
|
3465
|
-
console.log(
|
|
4152
|
+
console.log(import_chalk29.default.dim(" Log in and run `apiblaze claim <code>` to keep this setup, or `apiblaze login` to manage it."));
|
|
3466
4153
|
return;
|
|
3467
4154
|
}
|
|
3468
4155
|
const { teamId, teamName } = await resolveTeam(opts.team);
|
|
@@ -3471,7 +4158,7 @@ async function runSidecar(opts) {
|
|
|
3471
4158
|
const mustMint = !existingKey || opts.rotate || switchingTeam;
|
|
3472
4159
|
let token = existingKey ?? "";
|
|
3473
4160
|
if (mustMint) {
|
|
3474
|
-
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();
|
|
3475
4162
|
try {
|
|
3476
4163
|
const out = await admin({
|
|
3477
4164
|
method: "POST",
|
|
@@ -3485,39 +4172,39 @@ async function runSidecar(opts) {
|
|
|
3485
4172
|
throw err;
|
|
3486
4173
|
}
|
|
3487
4174
|
} else {
|
|
3488
|
-
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).`));
|
|
3489
4176
|
}
|
|
3490
4177
|
const envState = upsertEnvLocal(root, token);
|
|
3491
4178
|
ensureGitignored(root);
|
|
3492
|
-
console.log(` ${
|
|
4179
|
+
console.log(` ${import_chalk29.default.green("\u2713")} .env.local ${envState} (APIBLAZE_API_KEY) \u2014 gitignored`);
|
|
3493
4180
|
const wireState = wireInstrumentation(root);
|
|
3494
|
-
console.log(` ${
|
|
4181
|
+
console.log(` ${import_chalk29.default.green("\u2713")} instrumentation.ts ${wireState}`);
|
|
3495
4182
|
installSidecarPackage(root);
|
|
3496
4183
|
let inspectorPath = null;
|
|
3497
4184
|
if (!opts.noInspector) {
|
|
3498
4185
|
inspectorPath = generateInspector(root, detected.router);
|
|
3499
|
-
if (inspectorPath) console.log(` ${
|
|
4186
|
+
if (inspectorPath) console.log(` ${import_chalk29.default.green("\u2713")} inspector at ${inspectorPath}`);
|
|
3500
4187
|
}
|
|
3501
4188
|
console.log("");
|
|
3502
|
-
console.log(
|
|
3503
|
-
console.log(` 1. ${
|
|
3504
|
-
console.log(` 2. The origins your app calls appear as ${
|
|
3505
|
-
console.log(` 3. Approve the ones to route: ${
|
|
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)`);
|
|
3506
4193
|
console.log(` \u2026within ~5 min your app starts routing that origin through APIblaze.`);
|
|
3507
|
-
if (inspectorPath) console.log(` \u2022 Try it now: open ${
|
|
3508
|
-
if (switchingTeam) console.log(
|
|
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>\`.`));
|
|
3509
4196
|
console.log("");
|
|
3510
|
-
console.log(
|
|
3511
|
-
console.log(
|
|
3512
|
-
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)."));
|
|
3513
4200
|
console.log("");
|
|
3514
|
-
console.log(
|
|
3515
|
-
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."));
|
|
3516
4203
|
}
|
|
3517
4204
|
|
|
3518
4205
|
// src/commands/origins.ts
|
|
3519
|
-
var
|
|
3520
|
-
var
|
|
4206
|
+
var import_chalk30 = __toESM(require("chalk"));
|
|
4207
|
+
var import_ora15 = __toESM(require("ora"));
|
|
3521
4208
|
init_auth();
|
|
3522
4209
|
init_anon_cred();
|
|
3523
4210
|
async function runOriginsList(opts) {
|
|
@@ -3525,7 +4212,7 @@ async function runOriginsList(opts) {
|
|
|
3525
4212
|
if (!loadCredentials()) {
|
|
3526
4213
|
const cred = loadAnonCred();
|
|
3527
4214
|
if (!cred) {
|
|
3528
|
-
console.log(
|
|
4215
|
+
console.log(import_chalk30.default.yellow("No anonymous workspace here. Run `apiblaze init` first."));
|
|
3529
4216
|
return;
|
|
3530
4217
|
}
|
|
3531
4218
|
out = await cpFetch(cred.cp_key, `/teams/${encodeURIComponent(cred.team_id)}/sidecar/candidates`, { method: "GET" });
|
|
@@ -3543,30 +4230,30 @@ async function runOriginsList(opts) {
|
|
|
3543
4230
|
}
|
|
3544
4231
|
const routed = out.routed ?? [];
|
|
3545
4232
|
const candidates = out.candidates ?? [];
|
|
3546
|
-
console.log(
|
|
4233
|
+
console.log(import_chalk30.default.bold(`
|
|
3547
4234
|
Routed through APIblaze (${routed.length})`));
|
|
3548
|
-
if (!routed.length) console.log(
|
|
3549
|
-
for (const r of routed) console.log(` ${
|
|
3550
|
-
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(`
|
|
3551
4238
|
Candidates \u2014 going direct, not yet approved (${candidates.length})`));
|
|
3552
|
-
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"));
|
|
3553
4240
|
for (const c of candidates) {
|
|
3554
|
-
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}`)}`);
|
|
3555
4242
|
}
|
|
3556
4243
|
if (candidates.length) {
|
|
3557
|
-
console.log(
|
|
4244
|
+
console.log(import_chalk30.default.dim(`
|
|
3558
4245
|
Approve: apiblaze sidecar approve ${candidates[0].origin.replace("https://", "")}`));
|
|
3559
|
-
console.log(
|
|
4246
|
+
console.log(import_chalk30.default.dim(` Dismiss: apiblaze sidecar deny ${candidates[0].origin.replace("https://", "")}`));
|
|
3560
4247
|
}
|
|
3561
4248
|
}
|
|
3562
4249
|
async function runOriginsApprove(origin, opts) {
|
|
3563
4250
|
if (!loadCredentials()) {
|
|
3564
4251
|
const cred = loadAnonCred();
|
|
3565
4252
|
if (!cred) {
|
|
3566
|
-
console.error(
|
|
4253
|
+
console.error(import_chalk30.default.red("Not logged in and no anonymous workspace. Run `apiblaze init` first."));
|
|
3567
4254
|
process.exit(1);
|
|
3568
4255
|
}
|
|
3569
|
-
const spinner2 = (0,
|
|
4256
|
+
const spinner2 = (0, import_ora15.default)(`Approving ${origin} (anonymous)...`).start();
|
|
3570
4257
|
try {
|
|
3571
4258
|
const out = await cpFetch(cred.cp_key, `/teams/${encodeURIComponent(cred.team_id)}/sidecar/approve`, { method: "POST", body: JSON.stringify({ origin }) });
|
|
3572
4259
|
spinner2.succeed(`Approved ${origin} \u2192 proxy ${out.project_id}. Routing within ~5 min.`);
|
|
@@ -3577,7 +4264,7 @@ async function runOriginsApprove(origin, opts) {
|
|
|
3577
4264
|
return;
|
|
3578
4265
|
}
|
|
3579
4266
|
const { teamId } = await resolveTeam(opts.team);
|
|
3580
|
-
const spinner = (0,
|
|
4267
|
+
const spinner = (0, import_ora15.default)(`Approving ${origin}...`).start();
|
|
3581
4268
|
try {
|
|
3582
4269
|
const out = await admin({
|
|
3583
4270
|
method: "POST",
|
|
@@ -3594,7 +4281,7 @@ async function runOriginsApprove(origin, opts) {
|
|
|
3594
4281
|
}
|
|
3595
4282
|
async function runOriginsDeny(origin, opts) {
|
|
3596
4283
|
const { teamId } = await resolveTeam(opts.team);
|
|
3597
|
-
const spinner = (0,
|
|
4284
|
+
const spinner = (0, import_ora15.default)(`Dismissing ${origin}...`).start();
|
|
3598
4285
|
try {
|
|
3599
4286
|
await admin({ method: "POST", path: `/teams/${encodeURIComponent(teamId)}/sidecar/dismiss`, body: { origin }, summary: `Dismiss sidecar origin ${origin}` });
|
|
3600
4287
|
spinner.succeed(`Dismissed ${origin}. It won't be suggested again.`);
|
|
@@ -3605,7 +4292,7 @@ async function runOriginsDeny(origin, opts) {
|
|
|
3605
4292
|
}
|
|
3606
4293
|
async function runOriginsRemove(origin, opts) {
|
|
3607
4294
|
const { teamId } = await resolveTeam(opts.team);
|
|
3608
|
-
const spinner = (0,
|
|
4295
|
+
const spinner = (0, import_ora15.default)(`Removing the proxy for ${origin}...`).start();
|
|
3609
4296
|
try {
|
|
3610
4297
|
await admin({ method: "POST", path: `/teams/${encodeURIComponent(teamId)}/sidecar/remove`, body: { origin }, summary: `Un-route sidecar origin ${origin}` });
|
|
3611
4298
|
spinner.succeed(`Removed ${origin}. Your app will stop routing it (goes direct) within ~5 min.`);
|
|
@@ -3615,6 +4302,131 @@ async function runOriginsRemove(origin, opts) {
|
|
|
3615
4302
|
}
|
|
3616
4303
|
}
|
|
3617
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
|
+
|
|
3618
4430
|
// src/index.ts
|
|
3619
4431
|
var program = new import_commander.Command();
|
|
3620
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)");
|
|
@@ -3654,16 +4466,19 @@ var agent = program.command("agent").description("Chat with an assistant that bu
|
|
|
3654
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)));
|
|
3655
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)));
|
|
3656
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)));
|
|
3657
|
-
|
|
3658
|
-
var
|
|
3659
|
-
|
|
3660
|
-
|
|
3661
|
-
|
|
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)));
|
|
3662
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) => {
|
|
3663
4478
|
try {
|
|
3664
4479
|
const resolved = parseInt(port ?? opts.port, 10);
|
|
3665
4480
|
if (Number.isNaN(resolved)) {
|
|
3666
|
-
console.error(
|
|
4481
|
+
console.error(import_chalk32.default.red(`Invalid port: ${port ?? opts.port}`));
|
|
3667
4482
|
process.exit(1);
|
|
3668
4483
|
}
|
|
3669
4484
|
await runDev({ port: resolved, captureFile: opts.captureFile });
|
|
@@ -3711,6 +4526,7 @@ program.command("projects").description("List the projects in your team").action
|
|
|
3711
4526
|
});
|
|
3712
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)));
|
|
3713
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)));
|
|
3714
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)));
|
|
3715
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)));
|
|
3716
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)));
|
|
@@ -3731,13 +4547,15 @@ apikeys.command("list").description("List control-plane API keys in your team").
|
|
|
3731
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)));
|
|
3732
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)));
|
|
3733
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 });
|
|
3734
4552
|
var spec = program.command("spec").description("View or update a proxy's OpenAPI spec (or build one by chatting: apiblaze agent openapi)");
|
|
3735
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)));
|
|
3736
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)));
|
|
3737
4555
|
var HELP_GROUPS = [
|
|
3738
4556
|
{ title: "Chat", commands: ["agent"] },
|
|
3739
4557
|
{ title: "Setup", commands: ["login", "create", "init", "sidecar", "dev", "claim", "team", "whoami", "logout"] },
|
|
3740
|
-
{ title: "Control plane commands", commands: ["projects", "tenant", "domain", "delete", "target", "throttle", "rename", "spec", "export"] },
|
|
4558
|
+
{ title: "Control plane commands", commands: ["config", "projects", "tenant", "domain", "delete", "target", "throttle", "rename", "spec", "export"] },
|
|
3741
4559
|
{ title: "Data plane commands", commands: [
|
|
3742
4560
|
{ parent: "consumer", sub: "login" },
|
|
3743
4561
|
{ parent: "consumer", sub: "apikeys" }
|
|
@@ -3756,7 +4574,7 @@ function groupedCommandHelp() {
|
|
|
3756
4574
|
const sub = byName.get(e.parent)?.commands.find((s) => s.name() === e.sub);
|
|
3757
4575
|
return sub ? ` ${helpLabel(e).padEnd(width)}${sub.description()}` : "";
|
|
3758
4576
|
}).filter(Boolean).join("\n");
|
|
3759
|
-
return `${
|
|
4577
|
+
return `${import_chalk32.default.bold(g.title)}
|
|
3760
4578
|
${rows}`;
|
|
3761
4579
|
}).join("\n\n");
|
|
3762
4580
|
}
|
|
@@ -3770,7 +4588,9 @@ program.addHelpText("after", () => `
|
|
|
3770
4588
|
${groupedCommandHelp()}
|
|
3771
4589
|
|
|
3772
4590
|
Tips:
|
|
4591
|
+
\u2022 \`apiblaze config <project>\` browses EVERY setting & feature (works logged-out to explore).
|
|
3773
4592
|
\u2022 Add --verbose to any command to see the equivalent API calls.
|
|
4593
|
+
\u2022 Full API reference: https://api.apiblaze.com/openapi.json
|
|
3774
4594
|
\u2022 Run \`apiblaze <command> --help\` (e.g. \`apiblaze consumer --help\`) for sub-commands.
|
|
3775
4595
|
|
|
3776
4596
|
Examples:
|
|
@@ -3782,13 +4602,13 @@ Examples:
|
|
|
3782
4602
|
`);
|
|
3783
4603
|
function printError(err) {
|
|
3784
4604
|
if (err instanceof ApiError) {
|
|
3785
|
-
console.error(
|
|
4605
|
+
console.error(import_chalk32.default.red(`
|
|
3786
4606
|
API error (${err.status}): ${err.message}`));
|
|
3787
4607
|
} else if (err instanceof Error) {
|
|
3788
|
-
console.error(
|
|
4608
|
+
console.error(import_chalk32.default.red(`
|
|
3789
4609
|
Error: ${err.message}`));
|
|
3790
4610
|
} else {
|
|
3791
|
-
console.error(
|
|
4611
|
+
console.error(import_chalk32.default.red("\nUnknown error"));
|
|
3792
4612
|
}
|
|
3793
4613
|
}
|
|
3794
4614
|
program.parse(process.argv);
|