apiblaze 0.20.3 → 0.20.5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.js +185 -23
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -932,7 +932,7 @@ var import_commander = require("commander");
|
|
|
932
932
|
var import_chalk51 = __toESM(require("chalk"));
|
|
933
933
|
|
|
934
934
|
// package.json
|
|
935
|
-
var version = "0.20.
|
|
935
|
+
var version = "0.20.5";
|
|
936
936
|
|
|
937
937
|
// src/index.ts
|
|
938
938
|
init_types();
|
|
@@ -3601,11 +3601,11 @@ async function runRecipeShow(nameArg, opts) {
|
|
|
3601
3601
|
console.log(import_chalk25.default.dim(" Sections: --settings --auth --transforms --openapi"));
|
|
3602
3602
|
console.log();
|
|
3603
3603
|
}
|
|
3604
|
-
function describeCreates(keyTypes, routes, mcp) {
|
|
3605
|
-
const parts = ["1 proxy", "1 tenant"];
|
|
3604
|
+
function describeCreates(keyTypes, routes, mcp, includeProxy = true) {
|
|
3605
|
+
const parts = includeProxy ? ["1 proxy", "1 tenant"] : [];
|
|
3606
3606
|
if (keyTypes) parts.push(`${keyTypes} key type${keyTypes === 1 ? "" : "s"}`);
|
|
3607
3607
|
if (routes) parts.push(mcp ? `${routes} MCP tools` : `${routes} route${routes === 1 ? "" : "s"}`);
|
|
3608
|
-
return parts.join(", ");
|
|
3608
|
+
return parts.length ? parts.join(", ") : "nothing else";
|
|
3609
3609
|
}
|
|
3610
3610
|
function printSection(title, value) {
|
|
3611
3611
|
console.log(import_chalk25.default.bold(` ${title}`));
|
|
@@ -3701,6 +3701,7 @@ async function runRecipeInstall(nameArg, opts) {
|
|
|
3701
3701
|
return;
|
|
3702
3702
|
}
|
|
3703
3703
|
}
|
|
3704
|
+
if (recipe.settings) delete recipe.settings.publish_openapi;
|
|
3704
3705
|
const ctx = { project, tenant: tenant2 };
|
|
3705
3706
|
if (reuseTenant) {
|
|
3706
3707
|
recipe = { ...recipe, auth: { app_client: null, provider: null, key_types: [] }, questions: [] };
|
|
@@ -4095,7 +4096,7 @@ async function chooseProxyName(slug, asOpt, teamId, interactive) {
|
|
|
4095
4096
|
continue;
|
|
4096
4097
|
}
|
|
4097
4098
|
if (free === null) console.log(import_chalk26.default.dim(" (could not verify availability; continuing)"));
|
|
4098
|
-
console.log(` ${import_chalk26.default.cyan("\u2192")} ${import_chalk26.default.bold(`https://${name}.abz.run`)}
|
|
4099
|
+
console.log(` ${import_chalk26.default.cyan("\u2192")} ${import_chalk26.default.bold(`https://${name}.abz.run`)}`);
|
|
4099
4100
|
return name;
|
|
4100
4101
|
}
|
|
4101
4102
|
}
|
|
@@ -4111,19 +4112,23 @@ function printSummary(recipe, project, tenant2, reuseTenant = false) {
|
|
|
4111
4112
|
const hosts = upstreamHosts(recipe);
|
|
4112
4113
|
const routes = countRoutes(recipe.openapi);
|
|
4113
4114
|
const keyTypes = recipe.auth?.key_types?.length ?? 0;
|
|
4114
|
-
const questions = recipe.questions?.length ?? 0;
|
|
4115
|
+
const questions = reuseTenant ? 0 : recipe.questions?.length ?? 0;
|
|
4116
|
+
const row = (label3, value, note) => {
|
|
4117
|
+
console.log(` ${import_chalk26.default.dim(label3.padEnd(21))}${value}${note ? import_chalk26.default.dim(` ${note}`) : ""}`);
|
|
4118
|
+
};
|
|
4119
|
+
console.log();
|
|
4120
|
+
console.log(import_chalk26.default.bold(" This will:"));
|
|
4115
4121
|
console.log();
|
|
4116
|
-
|
|
4117
|
-
|
|
4118
|
-
|
|
4122
|
+
row("create a proxy", import_chalk26.default.bold(project), `at https://${project}.abz.run`);
|
|
4123
|
+
row("for its users", import_chalk26.default.bold(tenant2), reuseTenant ? "(one you already have)" : "(a new tenant)");
|
|
4124
|
+
if (hosts.length) row("sending requests to", hosts.join(", "));
|
|
4125
|
+
row("and set up", describeCreates(keyTypes, routes, recipe.settings.mcp_enabled === true, false));
|
|
4126
|
+
row("asking you for", questions ? `${questions} value${questions === 1 ? "" : "s"}` : "nothing");
|
|
4119
4127
|
if (reuseTenant) {
|
|
4120
|
-
console.log(
|
|
4121
|
-
console.log(import_chalk26.default.dim(` ${
|
|
4122
|
-
|
|
4123
|
-
if (recipe.settings.publish_openapi === true) {
|
|
4124
|
-
console.log(` ${import_chalk26.default.dim("makes public ")} this API's spec, at ${project}.abz.run/${recipe.api_version}/openapi.json`);
|
|
4128
|
+
console.log();
|
|
4129
|
+
console.log(import_chalk26.default.dim(` ${tenant2} is used exactly as it is \u2014 its existing login stays, and this`));
|
|
4130
|
+
console.log(import_chalk26.default.dim(" recipe's own login setup is not applied."));
|
|
4125
4131
|
}
|
|
4126
|
-
console.log(import_chalk26.default.dim(` ${"users live in".padEnd(17)} ${tenant2}`));
|
|
4127
4132
|
console.log();
|
|
4128
4133
|
}
|
|
4129
4134
|
function declineAll(items) {
|
|
@@ -9825,6 +9830,36 @@ async function opCall(call) {
|
|
|
9825
9830
|
}
|
|
9826
9831
|
return data;
|
|
9827
9832
|
}
|
|
9833
|
+
var OP_COMMANDS = [
|
|
9834
|
+
{ cmd: "op latency", blurb: "which FEATURE is slow, and what inside it", extra: "--hours N --team T --project P" },
|
|
9835
|
+
{ cmd: "op latency slow", blurb: "slowest requests, each with a request id you can look up", extra: "--min-ms N" },
|
|
9836
|
+
{ cmd: "op latency llm", blurb: "chat timing by provider/model, tokens, credit-reserve cost" },
|
|
9837
|
+
{ cmd: "op mark", blurb: 'record "I changed something significant just now"', extra: 'apiblaze op mark "cached tenant count"' },
|
|
9838
|
+
{ cmd: "op latency compare", blurb: "latency before vs after the last mark, equal windows", extra: "--hours N" },
|
|
9839
|
+
{ cmd: "op credits", blurb: "list credit wallets across all teams" },
|
|
9840
|
+
{ cmd: "op residue", blurb: "external-store residue report (Upstash + Neon/OpenFGA) \u2014 dry run, deletes nothing" },
|
|
9841
|
+
{ cmd: "op sweep", blurb: "delete the orphans the residue report found", extra: "asks first; -y to skip" }
|
|
9842
|
+
];
|
|
9843
|
+
function renderOpCommands() {
|
|
9844
|
+
const width = Math.max(...OP_COMMANDS.map((c) => c.cmd.length)) + 10;
|
|
9845
|
+
const lines = OP_COMMANDS.map((c) => {
|
|
9846
|
+
const left = ` ${import_chalk50.default.cyan(`apiblaze ${c.cmd}`)}`;
|
|
9847
|
+
const pad = " ".repeat(Math.max(1, width - c.cmd.length));
|
|
9848
|
+
return `${left}${pad}${c.blurb}${c.extra ? " " + import_chalk50.default.dim(`(${c.extra})`) : ""}`;
|
|
9849
|
+
});
|
|
9850
|
+
return [
|
|
9851
|
+
import_chalk50.default.bold("Operator commands"),
|
|
9852
|
+
...lines,
|
|
9853
|
+
"",
|
|
9854
|
+
import_chalk50.default.dim(" Operators only. The gate is server-side (dashboard /api/cli/op checks the"),
|
|
9855
|
+
import_chalk50.default.dim(" signed-in email, admin-api re-checks with operatorGate) \u2014 a patched CLI just"),
|
|
9856
|
+
import_chalk50.default.dim(" gets 403s. Every op call is read-only except `op sweep`."),
|
|
9857
|
+
"",
|
|
9858
|
+
import_chalk50.default.dim(" Not a CLI command: to prune all non-CP data run scripts/nuke-but-cp.sh --apply --sweep"),
|
|
9859
|
+
import_chalk50.default.dim(" in the repo. Operator dashboards (dlq, thresholds, throttling, pricing, billing,"),
|
|
9860
|
+
import_chalk50.default.dim(" agent-spend, teams, tests, leak-detection, lifecycle) live at /operator/* in the app.")
|
|
9861
|
+
].join("\n");
|
|
9862
|
+
}
|
|
9828
9863
|
function printResidue(report, applied) {
|
|
9829
9864
|
const up = report?.upstash ?? {};
|
|
9830
9865
|
const fga = report?.fga ?? {};
|
|
@@ -9888,7 +9923,7 @@ function printResidue(report, applied) {
|
|
|
9888
9923
|
for (const e of ghosts?.errors ?? []) console.log(import_chalk50.default.red(` error: ${e}`));
|
|
9889
9924
|
console.log();
|
|
9890
9925
|
}
|
|
9891
|
-
async function runOp(sub, opts = {}) {
|
|
9926
|
+
async function runOp(sub, opts = {}, view) {
|
|
9892
9927
|
if (!loadCredentials()) {
|
|
9893
9928
|
console.log(import_chalk50.default.dim("Not logged in. Run `apiblaze login`."));
|
|
9894
9929
|
return;
|
|
@@ -9900,12 +9935,9 @@ async function runOp(sub, opts = {}) {
|
|
|
9900
9935
|
switch (sub) {
|
|
9901
9936
|
case void 0:
|
|
9902
9937
|
case "menu": {
|
|
9903
|
-
console.log(
|
|
9904
|
-
console.log(
|
|
9905
|
-
console.log(
|
|
9906
|
-
console.log(` ${import_chalk50.default.cyan("apiblaze op credits")} list credit wallets`);
|
|
9907
|
-
console.log(import_chalk50.default.dim(` (to prune all non-CP data: run scripts/nuke-but-cp.sh --apply --sweep in the repo)
|
|
9908
|
-
`));
|
|
9938
|
+
console.log("");
|
|
9939
|
+
console.log(renderOpCommands());
|
|
9940
|
+
console.log("");
|
|
9909
9941
|
return;
|
|
9910
9942
|
}
|
|
9911
9943
|
case "residue": {
|
|
@@ -9940,6 +9972,20 @@ async function runOp(sub, opts = {}) {
|
|
|
9940
9972
|
printResidue(result, true);
|
|
9941
9973
|
return;
|
|
9942
9974
|
}
|
|
9975
|
+
case "mark": {
|
|
9976
|
+
const label3 = (view ?? "").trim();
|
|
9977
|
+
if (!label3) {
|
|
9978
|
+
console.log(import_chalk50.default.red("Give the change a name:") + import_chalk50.default.cyan(' apiblaze op mark "cached tenant count"'));
|
|
9979
|
+
return;
|
|
9980
|
+
}
|
|
9981
|
+
const res = await opCall({ method: "POST", path: "/operator/latency/mark", body: { label: label3 }, summary: "record change marker" });
|
|
9982
|
+
const ts = new Date(res?.marker?.ts ?? Date.now()).toISOString();
|
|
9983
|
+
console.log(import_chalk50.default.green(`
|
|
9984
|
+
Marked: `) + import_chalk50.default.bold(label3));
|
|
9985
|
+
console.log(import_chalk50.default.dim(` ${ts}`));
|
|
9986
|
+
console.log(import_chalk50.default.dim(` Once traffic has run on both sides, compare with: `) + import_chalk50.default.cyan("apiblaze op latency compare") + "\n");
|
|
9987
|
+
return;
|
|
9988
|
+
}
|
|
9943
9989
|
case "credits": {
|
|
9944
9990
|
const data = await opCall({ method: "GET", path: "/operator/credits", summary: "list credit wallets" });
|
|
9945
9991
|
if (opts.json) return void console.log(JSON.stringify(data, null, 2));
|
|
@@ -9951,6 +9997,120 @@ async function runOp(sub, opts = {}) {
|
|
|
9951
9997
|
}
|
|
9952
9998
|
return;
|
|
9953
9999
|
}
|
|
10000
|
+
// ── LATENCY (specs/latency/ §7.4) ─────────────────────────────────────
|
|
10001
|
+
// The blame table first: level 1 (which feature) and level 2 (what inside
|
|
10002
|
+
// it) in one row. `upstream` is excluded from the blame column on purpose —
|
|
10003
|
+
// it is the biggest feature on almost every request, so blaming it would
|
|
10004
|
+
// report "upstream, 94%" forever and tell an operator nothing. The
|
|
10005
|
+
// summary's "us or them" split is where upstream shows up.
|
|
10006
|
+
case "latency": {
|
|
10007
|
+
const qs = new URLSearchParams();
|
|
10008
|
+
const FILTERS = [
|
|
10009
|
+
["hours", "hours"],
|
|
10010
|
+
["team", "team"],
|
|
10011
|
+
["project", "project"],
|
|
10012
|
+
["apiVersion", "api_version"],
|
|
10013
|
+
["tenant", "tenant"],
|
|
10014
|
+
["route", "route"],
|
|
10015
|
+
["environment", "environment"],
|
|
10016
|
+
["statusClass", "status_class"],
|
|
10017
|
+
["minMs", "min_ms"]
|
|
10018
|
+
];
|
|
10019
|
+
for (const [optKey, queryKey] of FILTERS) {
|
|
10020
|
+
const v = opts[optKey];
|
|
10021
|
+
if (v !== void 0 && v !== null && v !== "") qs.set(queryKey, String(v));
|
|
10022
|
+
}
|
|
10023
|
+
const q = qs.toString() ? `?${qs}` : "";
|
|
10024
|
+
const which = view;
|
|
10025
|
+
if (which === "compare") {
|
|
10026
|
+
const data = await opCall({ method: "GET", path: `/operator/latency/compare${q}`, summary: "latency before/after" });
|
|
10027
|
+
if (opts.json) return void console.log(JSON.stringify(data, null, 2));
|
|
10028
|
+
const b = data.before, a = data.after, d = data.delta;
|
|
10029
|
+
console.log(import_chalk50.default.bold(`
|
|
10030
|
+
Before vs after: `) + import_chalk50.default.cyan(data.marker.label));
|
|
10031
|
+
console.log(import_chalk50.default.dim(` marked ${new Date(data.marker.ts).toISOString()} \xB7 ${data.window_hours}h either side
|
|
10032
|
+
`));
|
|
10033
|
+
const row = (name, before, after, delta) => {
|
|
10034
|
+
const arrow = delta === 0 ? "=" : delta < 0 ? "\u2193" : "\u2191";
|
|
10035
|
+
const txt = `${String(before).padStart(6)}ms \u2192${String(after).padStart(7)}ms ${arrow}${Math.abs(delta)}ms`;
|
|
10036
|
+
console.log(` ${name.padEnd(22)}${data.trustworthy ? delta <= 0 ? import_chalk50.default.green(txt) : import_chalk50.default.red(txt) : import_chalk50.default.dim(txt)}`);
|
|
10037
|
+
};
|
|
10038
|
+
console.log(import_chalk50.default.dim(" metric before after change"));
|
|
10039
|
+
row("total p50", b.total_p50, a.total_p50, d.total_p50);
|
|
10040
|
+
row("total p95", b.total_p95, a.total_p95, d.total_p95);
|
|
10041
|
+
row("apiblaze overhead p50", b.gw_p50, a.gw_p50, d.gw_p50);
|
|
10042
|
+
row("apiblaze overhead p95", b.gw_p95, a.gw_p95, d.gw_p95);
|
|
10043
|
+
console.log(import_chalk50.default.dim(`
|
|
10044
|
+
requests: ${b.requests} before \xB7 ${a.requests} after`));
|
|
10045
|
+
for (const w of data.warnings ?? []) {
|
|
10046
|
+
console.log((data.trustworthy ? import_chalk50.default.dim : import_chalk50.default.yellow)(` ${data.trustworthy ? "\xB7" : "\u26A0"} ${w}`));
|
|
10047
|
+
}
|
|
10048
|
+
console.log("");
|
|
10049
|
+
return;
|
|
10050
|
+
}
|
|
10051
|
+
if (which === "slow") {
|
|
10052
|
+
const data = await opCall({ method: "GET", path: `/operator/latency/slow${q}`, summary: "slowest requests" });
|
|
10053
|
+
if (opts.json) return void console.log(JSON.stringify(data, null, 2));
|
|
10054
|
+
const rows2 = data?.rows ?? [];
|
|
10055
|
+
if (!rows2.length) return void console.log(import_chalk50.default.dim("No requests over the threshold in that window."));
|
|
10056
|
+
console.log(import_chalk50.default.bold(`
|
|
10057
|
+
Slowest requests \u2014 last ${data.window_hours}h, over ${data.min_ms}ms
|
|
10058
|
+
`));
|
|
10059
|
+
console.log(import_chalk50.default.dim(" total ours theirs blame request id"));
|
|
10060
|
+
for (const r of rows2.slice(0, 30)) {
|
|
10061
|
+
console.log(
|
|
10062
|
+
` ${String(Math.round(r.duration_ms)).padStart(6)} ${String(Math.round(r.gateway_ms)).padStart(5)} ${String(Math.round(r.upstream_ttfb_ms)).padStart(6)} ${import_chalk50.default.yellow(`${r.slow_gw_feature || "-"}\u2192${r.slow_dep || "-"}`.padEnd(20))} ${import_chalk50.default.dim(r.request_id || "")}`
|
|
10063
|
+
);
|
|
10064
|
+
}
|
|
10065
|
+
console.log(import_chalk50.default.dim(`
|
|
10066
|
+
The last column is the request id (Cloudflare calls it a "cf-ray"). Look one up with`));
|
|
10067
|
+
console.log(import_chalk50.default.dim(` \`apiblaze logs\` for that request's exact per-feature breakdown \u2014 unsampled, unlike the table above.
|
|
10068
|
+
`));
|
|
10069
|
+
return;
|
|
10070
|
+
}
|
|
10071
|
+
if (which === "llm") {
|
|
10072
|
+
const data = await opCall({ method: "GET", path: `/operator/latency/llm${q}`, summary: "llm latency" });
|
|
10073
|
+
if (opts.json) return void console.log(JSON.stringify(data, null, 2));
|
|
10074
|
+
const rows2 = data?.rows ?? [];
|
|
10075
|
+
if (!rows2.length) return void console.log(import_chalk50.default.dim("No LLM traffic in that window."));
|
|
10076
|
+
console.log(import_chalk50.default.bold(`
|
|
10077
|
+
LLM timing \u2014 last ${data.window_hours}h
|
|
10078
|
+
`));
|
|
10079
|
+
console.log(import_chalk50.default.dim(" requests gen p95 reserve p95 in/out tokens p95 model"));
|
|
10080
|
+
for (const r of rows2) {
|
|
10081
|
+
console.log(
|
|
10082
|
+
` ${String(Math.round(r.requests)).padStart(8)} ${String(Math.round(r.gen_p95_ms)).padStart(6)}ms ${String(Math.round(r.reserve_p95_ms)).padStart(9)}ms ${String(Math.round(r.input_tokens_p95)).padStart(6)}/${String(Math.round(r.output_tokens_p95)).padEnd(6)} ${r.model || "-"}`
|
|
10083
|
+
);
|
|
10084
|
+
}
|
|
10085
|
+
console.log(import_chalk50.default.dim(`
|
|
10086
|
+
${data.note}
|
|
10087
|
+
`));
|
|
10088
|
+
return;
|
|
10089
|
+
}
|
|
10090
|
+
const [blame, summary] = await Promise.all([
|
|
10091
|
+
opCall({ method: "GET", path: `/operator/latency${q}`, summary: "latency blame table" }),
|
|
10092
|
+
opCall({ method: "GET", path: `/operator/latency/summary${q}`, summary: "latency summary" })
|
|
10093
|
+
]);
|
|
10094
|
+
if (opts.json) return void console.log(JSON.stringify({ blame, summary }, null, 2));
|
|
10095
|
+
const ov = summary?.apiblaze_overhead_ms ?? {};
|
|
10096
|
+
console.log(import_chalk50.default.bold(`
|
|
10097
|
+
Latency \u2014 last ${summary?.window_hours ?? "?"}h, ${Number(summary?.requests ?? 0).toLocaleString()} requests
|
|
10098
|
+
`));
|
|
10099
|
+
console.log(` ${import_chalk50.default.bold("apiblaze overhead")} p50 ${String(ov.p50 ?? 0).padStart(5)}ms p95 ${String(ov.p95 ?? 0).padStart(6)}ms p99 ${String(ov.p99 ?? 0).padStart(6)}ms ${import_chalk50.default.dim("\u2190 ours")}`);
|
|
10100
|
+
console.log(` ${import_chalk50.default.bold("upstream ttfb ")} ${" ".repeat(24)}p95 ${String(summary?.upstream_ttfb_p95_ms ?? 0).padStart(6)}ms ${import_chalk50.default.dim("\u2190 theirs")}`);
|
|
10101
|
+
console.log(import_chalk50.default.dim(` (per-request percentiles \u2014 never subtract one from the other)
|
|
10102
|
+
`));
|
|
10103
|
+
const rows = blame?.blame ?? [];
|
|
10104
|
+
if (!rows.length) return void console.log(import_chalk50.default.dim("No latency rows in that window."));
|
|
10105
|
+
console.log(import_chalk50.default.bold(" Which feature ate the time, and what inside it\n"));
|
|
10106
|
+
console.log(import_chalk50.default.dim(" share p95 feature \u2192 dependency"));
|
|
10107
|
+
for (const r of rows.slice(0, 15)) {
|
|
10108
|
+
const share = `${(r.share * 100).toFixed(1)}%`;
|
|
10109
|
+
console.log(` ${share.padStart(6)} ${String(r.p95_ms).padStart(6)}ms ${import_chalk50.default.yellow(r.feature)} ${import_chalk50.default.dim("\u2192")} ${import_chalk50.default.cyan(r.dep)}`);
|
|
10110
|
+
}
|
|
10111
|
+
console.log("");
|
|
10112
|
+
return;
|
|
10113
|
+
}
|
|
9954
10114
|
default:
|
|
9955
10115
|
console.log(import_chalk50.default.red(`Unknown op subcommand '${sub}'. Run \`apiblaze op\` for the menu.`));
|
|
9956
10116
|
}
|
|
@@ -10114,7 +10274,9 @@ apikeys.command("list").description("List control-plane API keys in your team").
|
|
|
10114
10274
|
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, cmd) => runKeyMint({ ...cmd.parent?.opts(), ...opts })));
|
|
10115
10275
|
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, cmd) => runKeyRevoke(keyId, { ...cmd.parent?.opts(), ...opts })));
|
|
10116
10276
|
program.addCommand(apikeys, { hidden: true });
|
|
10117
|
-
var op = new import_commander.Command("op").description("Operator menu (platform operators only)").argument("[action]", "
|
|
10277
|
+
var op = new import_commander.Command("op").description("Operator menu (platform operators only)").argument("[action]", "latency | mark | credits | residue | sweep (omit for the menu)").argument("[view]", "for `latency`: slow | llm | compare \xB7 for `mark`: the change name, quoted").option("-y, --yes", "Skip the sweep confirmation prompt").option("--json", "Output machine-readable JSON").option("--hours <n>", "Look-back window in hours (default 1)").option("--team <id>", "Filter to one team").option("--project <name>", "Filter to one proxy").option("--api-version <v>", "Filter to one API version").option("--tenant <name>", "Filter to one tenant").option("--route <path>", "Filter to one route").option("--environment <env>", "Filter to one environment").option("--status-class <c>", "Filter to a status class (2xx/4xx/5xx)").option("--min-ms <n>", "For `latency slow`: minimum duration (default 1000)").action(action((sub, view, opts) => runOp(sub, opts, view))).addHelpText("after", () => `
|
|
10278
|
+
${renderOpCommands()}
|
|
10279
|
+
`);
|
|
10118
10280
|
program.addCommand(op, { hidden: true });
|
|
10119
10281
|
var spec = program.command("spec").description("View or update a proxy's OpenAPI spec (or build one by chatting: apiblaze agent openapi)");
|
|
10120
10282
|
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)));
|