apiblaze 0.20.2 → 0.20.4
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 +158 -10
- 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.4";
|
|
936
936
|
|
|
937
937
|
// src/index.ts
|
|
938
938
|
init_types();
|
|
@@ -3666,7 +3666,7 @@ async function runRecipeInstall(nameArg, opts) {
|
|
|
3666
3666
|
const assumeYes = !!opts.yes;
|
|
3667
3667
|
const interactive = !!process.stdin.isTTY && !assumeYes;
|
|
3668
3668
|
const caller = requireCallerForInstall();
|
|
3669
|
-
|
|
3669
|
+
let recipe = await fetchRecipeFile(ref, caller);
|
|
3670
3670
|
const pinned = `${recipe.name}@${recipe.revision}`;
|
|
3671
3671
|
const picked = await chooseTeam(caller, opts.team, interactive);
|
|
3672
3672
|
const teamId = picked.teamId;
|
|
@@ -3677,6 +3677,7 @@ async function runRecipeInstall(nameArg, opts) {
|
|
|
3677
3677
|
if (recipe.summary) console.log(` ${recipe.summary}`);
|
|
3678
3678
|
console.log();
|
|
3679
3679
|
let project;
|
|
3680
|
+
let reuseTenant = false;
|
|
3680
3681
|
let apiVersion;
|
|
3681
3682
|
let tenant2;
|
|
3682
3683
|
let reinstall = false;
|
|
@@ -3691,15 +3692,19 @@ async function runRecipeInstall(nameArg, opts) {
|
|
|
3691
3692
|
} else {
|
|
3692
3693
|
project = await chooseProxyName(ref.slug, opts.as, teamId, interactive);
|
|
3693
3694
|
apiVersion = recipe.api_version || "1.0.0";
|
|
3694
|
-
|
|
3695
|
-
|
|
3696
|
-
|
|
3695
|
+
const chosenTenant = await chooseTenant(caller, project, teamId, opts.tenant, interactive);
|
|
3696
|
+
tenant2 = chosenTenant.tenant;
|
|
3697
|
+
reuseTenant = chosenTenant.existing;
|
|
3698
|
+
printSummary(recipe, project, tenant2, reuseTenant);
|
|
3697
3699
|
if (!assumeYes && !await confirmYesNo(" continue?", interactive)) {
|
|
3698
3700
|
console.log(import_chalk26.default.yellow(" Cancelled \u2014 nothing was created."));
|
|
3699
3701
|
return;
|
|
3700
3702
|
}
|
|
3701
3703
|
}
|
|
3702
3704
|
const ctx = { project, tenant: tenant2 };
|
|
3705
|
+
if (reuseTenant) {
|
|
3706
|
+
recipe = { ...recipe, auth: { app_client: null, provider: null, key_types: [] }, questions: [] };
|
|
3707
|
+
}
|
|
3703
3708
|
const declined = assumeYes ? declineAll(recipe.requires_confirmation ?? []) : await askConsent(recipe.requires_confirmation ?? [], interactive);
|
|
3704
3709
|
const answers = await askQuestions(recipe.questions ?? [], ctx, interactive);
|
|
3705
3710
|
const started = Date.now();
|
|
@@ -4102,7 +4107,7 @@ async function assertTenantFree(tenant2, teamId) {
|
|
|
4102
4107
|
);
|
|
4103
4108
|
}
|
|
4104
4109
|
}
|
|
4105
|
-
function printSummary(recipe, project, tenant2) {
|
|
4110
|
+
function printSummary(recipe, project, tenant2, reuseTenant = false) {
|
|
4106
4111
|
const hosts = upstreamHosts(recipe);
|
|
4107
4112
|
const routes = countRoutes(recipe.openapi);
|
|
4108
4113
|
const keyTypes = recipe.auth?.key_types?.length ?? 0;
|
|
@@ -4110,7 +4115,11 @@ function printSummary(recipe, project, tenant2) {
|
|
|
4110
4115
|
console.log();
|
|
4111
4116
|
if (hosts.length) console.log(` ${import_chalk26.default.dim("sends requests to")} ${hosts.join(", ")}`);
|
|
4112
4117
|
console.log(` ${import_chalk26.default.dim("creates ")} ${describeCreates(keyTypes, routes, recipe.settings.mcp_enabled === true)}`);
|
|
4113
|
-
console.log(` ${import_chalk26.default.dim("asks you for ")} ${questions} value${questions === 1 ? "" : "s"}`);
|
|
4118
|
+
console.log(` ${import_chalk26.default.dim("asks you for ")} ${reuseTenant ? "nothing" : `${questions} value${questions === 1 ? "" : "s"}`}`);
|
|
4119
|
+
if (reuseTenant) {
|
|
4120
|
+
console.log(` ${import_chalk26.default.dim("users live in ")} ${tenant2} ${import_chalk26.default.dim("(existing \u2014 its login is used as it is)")}`);
|
|
4121
|
+
console.log(import_chalk26.default.dim(` ${" ".repeat(17)} the recipe's own login setup is not applied`));
|
|
4122
|
+
}
|
|
4114
4123
|
if (recipe.settings.publish_openapi === true) {
|
|
4115
4124
|
console.log(` ${import_chalk26.default.dim("makes public ")} this API's spec, at ${project}.abz.run/${recipe.api_version}/openapi.json`);
|
|
4116
4125
|
}
|
|
@@ -4300,6 +4309,55 @@ async function chooseTeam(caller, optTeam, interactive) {
|
|
|
4300
4309
|
const match = teams.find((t) => t.teamId === picked);
|
|
4301
4310
|
return { teamId: picked, teamName: match?.name, chosen: true };
|
|
4302
4311
|
}
|
|
4312
|
+
async function chooseTenant(caller, project, teamId, optTenant, interactive) {
|
|
4313
|
+
const fresh = `${project}users`;
|
|
4314
|
+
const owned = await teamTenants(caller, teamId);
|
|
4315
|
+
const isOwned = (t2) => owned.some((o) => o === t2);
|
|
4316
|
+
if (optTenant) {
|
|
4317
|
+
const t2 = normalizeName2(optTenant);
|
|
4318
|
+
if (isOwned(t2)) return { tenant: t2, existing: true };
|
|
4319
|
+
await assertTenantFree(t2, teamId);
|
|
4320
|
+
return { tenant: t2, existing: false };
|
|
4321
|
+
}
|
|
4322
|
+
if (!interactive || !owned.length) {
|
|
4323
|
+
await assertTenantFree(fresh, teamId);
|
|
4324
|
+
return { tenant: fresh, existing: false };
|
|
4325
|
+
}
|
|
4326
|
+
const { default: inquirer3 } = await import("inquirer");
|
|
4327
|
+
const { picked } = await inquirer3.prompt([{
|
|
4328
|
+
type: "list",
|
|
4329
|
+
name: "picked",
|
|
4330
|
+
message: " where should this proxy's users live?",
|
|
4331
|
+
default: "__new__",
|
|
4332
|
+
choices: [
|
|
4333
|
+
{ name: `create ${fresh} ${import_chalk26.default.dim("(a fresh tenant, set up by the recipe)")}`, value: "__new__" },
|
|
4334
|
+
new inquirer3.Separator(import_chalk26.default.dim(" \u2014 or reuse one you already have \u2014")),
|
|
4335
|
+
...owned.map((t2) => ({
|
|
4336
|
+
name: `${t2} ${import_chalk26.default.dim("(existing \u2014 its own login is kept)")}`,
|
|
4337
|
+
value: t2
|
|
4338
|
+
}))
|
|
4339
|
+
]
|
|
4340
|
+
}]);
|
|
4341
|
+
if (picked !== "__new__") return { tenant: picked, existing: true };
|
|
4342
|
+
const { name } = await inquirer3.prompt([{
|
|
4343
|
+
type: "input",
|
|
4344
|
+
name: "name",
|
|
4345
|
+
message: " new tenant name",
|
|
4346
|
+
default: fresh,
|
|
4347
|
+
filter: (v) => normalizeName2(v)
|
|
4348
|
+
}]);
|
|
4349
|
+
const t = normalizeName2(String(name || fresh));
|
|
4350
|
+
await assertTenantFree(t, teamId);
|
|
4351
|
+
return { tenant: t, existing: false };
|
|
4352
|
+
}
|
|
4353
|
+
async function teamTenants(caller, teamId) {
|
|
4354
|
+
const out = await producer(caller, {
|
|
4355
|
+
method: "GET",
|
|
4356
|
+
path: `/teams/${encodeURIComponent(teamId)}/tenants`,
|
|
4357
|
+
summary: "List the team's tenants"
|
|
4358
|
+
}).catch(() => ({ tenants: [] }));
|
|
4359
|
+
return (out?.tenants ?? []).map((t) => typeof t === "string" ? t : t.disabled ? "" : t.tenant_name ?? t.name ?? "").filter(Boolean);
|
|
4360
|
+
}
|
|
4303
4361
|
|
|
4304
4362
|
// src/commands/recipe-publish.ts
|
|
4305
4363
|
var import_chalk27 = __toESM(require("chalk"));
|
|
@@ -9830,7 +9888,7 @@ function printResidue(report, applied) {
|
|
|
9830
9888
|
for (const e of ghosts?.errors ?? []) console.log(import_chalk50.default.red(` error: ${e}`));
|
|
9831
9889
|
console.log();
|
|
9832
9890
|
}
|
|
9833
|
-
async function runOp(sub, opts = {}) {
|
|
9891
|
+
async function runOp(sub, opts = {}, view) {
|
|
9834
9892
|
if (!loadCredentials()) {
|
|
9835
9893
|
console.log(import_chalk50.default.dim("Not logged in. Run `apiblaze login`."));
|
|
9836
9894
|
return;
|
|
@@ -9846,6 +9904,9 @@ async function runOp(sub, opts = {}) {
|
|
|
9846
9904
|
console.log(` ${import_chalk50.default.cyan("apiblaze op residue")} external-store residue report (Upstash + Neon/OpenFGA, dry-run)`);
|
|
9847
9905
|
console.log(` ${import_chalk50.default.cyan("apiblaze op sweep")} delete the orphans the report shows (asks first; ${import_chalk50.default.dim("-y to skip")})`);
|
|
9848
9906
|
console.log(` ${import_chalk50.default.cyan("apiblaze op credits")} list credit wallets`);
|
|
9907
|
+
console.log(` ${import_chalk50.default.cyan("apiblaze op latency")} which FEATURE is slow, and what inside it ${import_chalk50.default.dim("(--hours N --team T --project P)")}`);
|
|
9908
|
+
console.log(` ${import_chalk50.default.cyan("apiblaze op latency slow")} the slowest requests, with a cf-ray to drill into`);
|
|
9909
|
+
console.log(` ${import_chalk50.default.cyan("apiblaze op latency llm")} chat timing by provider/model, tokens, credit-reserve cost`);
|
|
9849
9910
|
console.log(import_chalk50.default.dim(` (to prune all non-CP data: run scripts/nuke-but-cp.sh --apply --sweep in the repo)
|
|
9850
9911
|
`));
|
|
9851
9912
|
return;
|
|
@@ -9893,6 +9954,93 @@ async function runOp(sub, opts = {}) {
|
|
|
9893
9954
|
}
|
|
9894
9955
|
return;
|
|
9895
9956
|
}
|
|
9957
|
+
// ── LATENCY (specs/latency/ §7.4) ─────────────────────────────────────
|
|
9958
|
+
// The blame table first: level 1 (which feature) and level 2 (what inside
|
|
9959
|
+
// it) in one row. `upstream` is excluded from the blame column on purpose —
|
|
9960
|
+
// it is the biggest feature on almost every request, so blaming it would
|
|
9961
|
+
// report "upstream, 94%" forever and tell an operator nothing. The
|
|
9962
|
+
// summary's "us or them" split is where upstream shows up.
|
|
9963
|
+
case "latency": {
|
|
9964
|
+
const qs = new URLSearchParams();
|
|
9965
|
+
const FILTERS = [
|
|
9966
|
+
["hours", "hours"],
|
|
9967
|
+
["team", "team"],
|
|
9968
|
+
["project", "project"],
|
|
9969
|
+
["apiVersion", "api_version"],
|
|
9970
|
+
["tenant", "tenant"],
|
|
9971
|
+
["route", "route"],
|
|
9972
|
+
["environment", "environment"],
|
|
9973
|
+
["statusClass", "status_class"],
|
|
9974
|
+
["minMs", "min_ms"]
|
|
9975
|
+
];
|
|
9976
|
+
for (const [optKey, queryKey] of FILTERS) {
|
|
9977
|
+
const v = opts[optKey];
|
|
9978
|
+
if (v !== void 0 && v !== null && v !== "") qs.set(queryKey, String(v));
|
|
9979
|
+
}
|
|
9980
|
+
const q = qs.toString() ? `?${qs}` : "";
|
|
9981
|
+
const which = view;
|
|
9982
|
+
if (which === "slow") {
|
|
9983
|
+
const data = await opCall({ method: "GET", path: `/operator/latency/slow${q}`, summary: "slowest requests" });
|
|
9984
|
+
if (opts.json) return void console.log(JSON.stringify(data, null, 2));
|
|
9985
|
+
const rows2 = data?.rows ?? [];
|
|
9986
|
+
if (!rows2.length) return void console.log(import_chalk50.default.dim("No requests over the threshold in that window."));
|
|
9987
|
+
console.log(import_chalk50.default.bold(`
|
|
9988
|
+
Slowest requests \u2014 last ${data.window_hours}h, over ${data.min_ms}ms
|
|
9989
|
+
`));
|
|
9990
|
+
console.log(import_chalk50.default.dim(" total ours theirs blame request (cf-ray)"));
|
|
9991
|
+
for (const r of rows2.slice(0, 30)) {
|
|
9992
|
+
console.log(
|
|
9993
|
+
` ${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 || "")}`
|
|
9994
|
+
);
|
|
9995
|
+
}
|
|
9996
|
+
console.log(import_chalk50.default.dim(`
|
|
9997
|
+
Take a cf-ray to \`apiblaze logs\` for the exact per-feature breakdown \u2014 that row is unsampled.
|
|
9998
|
+
`));
|
|
9999
|
+
return;
|
|
10000
|
+
}
|
|
10001
|
+
if (which === "llm") {
|
|
10002
|
+
const data = await opCall({ method: "GET", path: `/operator/latency/llm${q}`, summary: "llm latency" });
|
|
10003
|
+
if (opts.json) return void console.log(JSON.stringify(data, null, 2));
|
|
10004
|
+
const rows2 = data?.rows ?? [];
|
|
10005
|
+
if (!rows2.length) return void console.log(import_chalk50.default.dim("No LLM traffic in that window."));
|
|
10006
|
+
console.log(import_chalk50.default.bold(`
|
|
10007
|
+
LLM timing \u2014 last ${data.window_hours}h
|
|
10008
|
+
`));
|
|
10009
|
+
console.log(import_chalk50.default.dim(" requests gen p95 reserve p95 in/out tokens p95 model"));
|
|
10010
|
+
for (const r of rows2) {
|
|
10011
|
+
console.log(
|
|
10012
|
+
` ${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 || "-"}`
|
|
10013
|
+
);
|
|
10014
|
+
}
|
|
10015
|
+
console.log(import_chalk50.default.dim(`
|
|
10016
|
+
${data.note}
|
|
10017
|
+
`));
|
|
10018
|
+
return;
|
|
10019
|
+
}
|
|
10020
|
+
const [blame, summary] = await Promise.all([
|
|
10021
|
+
opCall({ method: "GET", path: `/operator/latency${q}`, summary: "latency blame table" }),
|
|
10022
|
+
opCall({ method: "GET", path: `/operator/latency/summary${q}`, summary: "latency summary" })
|
|
10023
|
+
]);
|
|
10024
|
+
if (opts.json) return void console.log(JSON.stringify({ blame, summary }, null, 2));
|
|
10025
|
+
const ov = summary?.apiblaze_overhead_ms ?? {};
|
|
10026
|
+
console.log(import_chalk50.default.bold(`
|
|
10027
|
+
Latency \u2014 last ${summary?.window_hours ?? "?"}h, ${Number(summary?.requests ?? 0).toLocaleString()} requests
|
|
10028
|
+
`));
|
|
10029
|
+
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")}`);
|
|
10030
|
+
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")}`);
|
|
10031
|
+
console.log(import_chalk50.default.dim(` (per-request percentiles \u2014 never subtract one from the other)
|
|
10032
|
+
`));
|
|
10033
|
+
const rows = blame?.blame ?? [];
|
|
10034
|
+
if (!rows.length) return void console.log(import_chalk50.default.dim("No latency rows in that window."));
|
|
10035
|
+
console.log(import_chalk50.default.bold(" Which feature ate the time, and what inside it\n"));
|
|
10036
|
+
console.log(import_chalk50.default.dim(" share p95 feature \u2192 dependency"));
|
|
10037
|
+
for (const r of rows.slice(0, 15)) {
|
|
10038
|
+
const share = `${(r.share * 100).toFixed(1)}%`;
|
|
10039
|
+
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)}`);
|
|
10040
|
+
}
|
|
10041
|
+
console.log("");
|
|
10042
|
+
return;
|
|
10043
|
+
}
|
|
9896
10044
|
default:
|
|
9897
10045
|
console.log(import_chalk50.default.red(`Unknown op subcommand '${sub}'. Run \`apiblaze op\` for the menu.`));
|
|
9898
10046
|
}
|
|
@@ -10008,7 +10156,7 @@ program.command("projects").description("List the projects in your team").action
|
|
|
10008
10156
|
});
|
|
10009
10157
|
program.command("search").description("Find a published recipe \u2014 a whole working proxy someone else set up").argument("[query]", "Words to match against a recipe name or summary").option("--limit <n>", "Maximum results (default 25)").option("--json", "Output machine-readable JSON").action(action((query, opts) => runRecipeSearch(query, opts)));
|
|
10010
10158
|
program.command("show").description("Read a recipe before you install it (upstreams, questions, transforms, spec)").argument("<recipe>", "Recipe name, e.g. @julien/gmail or @julien/gmail@2").option("--settings", "Print the settings section").option("--auth", "Print the auth section (login provider, key types)").option("--transforms", "Print the transform rules and mapping tables").option("--openapi", "Print the OpenAPI document").option("--json", "Output machine-readable JSON").action(action((recipe, opts) => runRecipeShow(recipe, opts)));
|
|
10011
|
-
program.command("install").description("Create a proxy from a recipe, with your own credentials").argument("<recipe>", "Recipe name, e.g. @julien/gmail or @julien/gmail@2 to pin a revision").option("--as <name>", "Proxy name (skips the name prompt)").option("--into <proxy>", "Reinstall over an existing proxy \u2014 replaces its settings, transforms and spec").option("--team <id|name>", "Team the proxy is created in (you are asked when you belong to several)").option("--apiversion <v>", "API version of the proxy named by --into").option("-y, --yes", "Skip the routine prompts (installs WITHOUT anything needing consent)").action(action((recipe, opts) => runRecipeInstall(recipe, opts)));
|
|
10159
|
+
program.command("install").description("Create a proxy from a recipe, with your own credentials").argument("<recipe>", "Recipe name, e.g. @julien/gmail or @julien/gmail@2 to pin a revision").option("--as <name>", "Proxy name (skips the name prompt)").option("--into <proxy>", "Reinstall over an existing proxy \u2014 replaces its settings, transforms and spec").option("--team <id|name>", "Team the proxy is created in (you are asked when you belong to several)").option("--apiversion <v>", "API version of the proxy named by --into").option("--tenant <slug>", "Where this proxy's users live. One of yours is reused as it is; a new name is created").option("-y, --yes", "Skip the routine prompts (installs WITHOUT anything needing consent)").action(action((recipe, opts) => runRecipeInstall(recipe, opts)));
|
|
10012
10160
|
program.command("publish").description("Publish one of your proxies as a recipe others can install").argument("<project>", "Project name or id (see `apiblaze projects`)").option("--as <name>", "Recipe name. Defaults to your GitHub handle + the proxy name; pass `pokedex` for just the name, or `@acme/pokedex` to set both").option("--private", "Only your team can see or install it (default is public)").option("--tenant <slug>", "Which tenant's auth setup the recipe carries (asked when a proxy has several)").option("--display-name <text>", "Human-readable title").option("--summary <text>", "One-line description shown in search").option("--license <id>", "Licence identifier, e.g. MIT").option("--team <id|name>", "Team the project is in (defaults to active team)").option("--apiversion <v>", "API version to publish (defaults to the first match)").option("-y, --yes", "Skip routine confirmations (never accepts scanner findings)").option("--json", "Output machine-readable JSON").action(action((project, opts) => runRecipePublish(project, opts)));
|
|
10013
10161
|
program.command("withdraw").description("Permanently delete one published recipe revision").argument("<recipe>", "Recipe revision, e.g. @julien/gmail@3").option("--team <id|name>", "Team that published it (defaults to active team)").option("--json", "Output machine-readable JSON").action(action((recipe, opts) => runRecipeWithdraw(recipe, opts)));
|
|
10014
10162
|
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)));
|
|
@@ -10056,7 +10204,7 @@ apikeys.command("list").description("List control-plane API keys in your team").
|
|
|
10056
10204
|
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 })));
|
|
10057
10205
|
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 })));
|
|
10058
10206
|
program.addCommand(apikeys, { hidden: true });
|
|
10059
|
-
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)));
|
|
10207
|
+
var op = new import_commander.Command("op").description("Operator menu (platform operators only)").argument("[action]", "residue | sweep | credits | latency (omit for the menu)").argument("[view]", "for `latency`: slow | llm (omit for the blame table)").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)));
|
|
10060
10208
|
program.addCommand(op, { hidden: true });
|
|
10061
10209
|
var spec = program.command("spec").description("View or update a proxy's OpenAPI spec (or build one by chatting: apiblaze agent openapi)");
|
|
10062
10210
|
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)));
|