apiblaze 0.20.10 → 0.20.13
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 +753 -365
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -137,8 +137,8 @@ async function createProxyAnonymous(body) {
|
|
|
137
137
|
}
|
|
138
138
|
return res.json();
|
|
139
139
|
}
|
|
140
|
-
async function apiFetch(
|
|
141
|
-
const url = `${DASHBOARD_BASE}${
|
|
140
|
+
async function apiFetch(path9, options = {}, auth) {
|
|
141
|
+
const url = `${DASHBOARD_BASE}${path9}`;
|
|
142
142
|
const res = await fetch(url, {
|
|
143
143
|
...options,
|
|
144
144
|
headers: {
|
|
@@ -164,12 +164,12 @@ async function apiFetch(path8, options = {}, auth) {
|
|
|
164
164
|
}
|
|
165
165
|
return res.json();
|
|
166
166
|
}
|
|
167
|
-
async function agentCall(
|
|
167
|
+
async function agentCall(path9, method, body) {
|
|
168
168
|
const token = getAccessToken();
|
|
169
169
|
const res = await fetch(`${DASHBOARD_BASE}/api/cli/agents`, {
|
|
170
170
|
method: "POST",
|
|
171
171
|
headers: { "Content-Type": "application/json", Authorization: `Bearer ${token}` },
|
|
172
|
-
body: JSON.stringify({ path:
|
|
172
|
+
body: JSON.stringify({ path: path9, method, body })
|
|
173
173
|
});
|
|
174
174
|
let data = null;
|
|
175
175
|
try {
|
|
@@ -1021,10 +1021,10 @@ var init_tenant_pick = __esm({
|
|
|
1021
1021
|
|
|
1022
1022
|
// src/index.ts
|
|
1023
1023
|
var import_commander = require("commander");
|
|
1024
|
-
var
|
|
1024
|
+
var import_chalk53 = __toESM(require("chalk"));
|
|
1025
1025
|
|
|
1026
1026
|
// package.json
|
|
1027
|
-
var version = "0.20.
|
|
1027
|
+
var version = "0.20.13";
|
|
1028
1028
|
|
|
1029
1029
|
// src/index.ts
|
|
1030
1030
|
init_types();
|
|
@@ -1290,11 +1290,11 @@ function decodeJwt(token) {
|
|
|
1290
1290
|
return null;
|
|
1291
1291
|
}
|
|
1292
1292
|
}
|
|
1293
|
-
function maskPath(
|
|
1294
|
-
const q =
|
|
1295
|
-
if (q < 0) return
|
|
1296
|
-
const base2 =
|
|
1297
|
-
const query =
|
|
1293
|
+
function maskPath(path9) {
|
|
1294
|
+
const q = path9.indexOf("?");
|
|
1295
|
+
if (q < 0) return path9;
|
|
1296
|
+
const base2 = path9.slice(0, q);
|
|
1297
|
+
const query = path9.slice(q + 1);
|
|
1298
1298
|
const masked = query.split("&").map((pair) => {
|
|
1299
1299
|
const eq = pair.indexOf("=");
|
|
1300
1300
|
if (eq < 0) return pair;
|
|
@@ -2328,7 +2328,7 @@ async function runCreate(opts = {}) {
|
|
|
2328
2328
|
}
|
|
2329
2329
|
let openapiContent = null;
|
|
2330
2330
|
if (opts.openapi !== void 0) {
|
|
2331
|
-
if (opts.target !== void 0) fail("Provide only one
|
|
2331
|
+
if (opts.target !== void 0) fail("Provide only one source (the smart --target covers servers, spec files and spec URLs).");
|
|
2332
2332
|
openapiContent = await loadOpenapiSource(opts.openapi);
|
|
2333
2333
|
}
|
|
2334
2334
|
let targetUrl = "";
|
|
@@ -2352,7 +2352,7 @@ async function runCreate(opts = {}) {
|
|
|
2352
2352
|
break;
|
|
2353
2353
|
}
|
|
2354
2354
|
} else {
|
|
2355
|
-
fail("--target
|
|
2355
|
+
fail("--target is required in non-interactive mode (a server base URL, a local OpenAPI file, or a remote OpenAPI URL).");
|
|
2356
2356
|
}
|
|
2357
2357
|
if (interactive && !opts.yes) {
|
|
2358
2358
|
const { default: inquirer3 } = await import("inquirer");
|
|
@@ -3529,8 +3529,8 @@ function resolveRecipeName(raw, githubHandle, proxyName) {
|
|
|
3529
3529
|
function normalizeName2(raw) {
|
|
3530
3530
|
return (raw || "").toLowerCase().replace(/[^a-z0-9]/g, "");
|
|
3531
3531
|
}
|
|
3532
|
-
async function publicFetch(
|
|
3533
|
-
const res = await fetch(`${RECIPES_BASE}/api/recipes${
|
|
3532
|
+
async function publicFetch(path9) {
|
|
3533
|
+
const res = await fetch(`${RECIPES_BASE}/api/recipes${path9}`, {
|
|
3534
3534
|
headers: { accept: "application/json" }
|
|
3535
3535
|
}).catch((err) => {
|
|
3536
3536
|
throw new Error(`Could not reach the recipe registry at ${RECIPES_BASE} \u2014 ${err.message}`);
|
|
@@ -3583,11 +3583,11 @@ async function fetchRecipeFile(ref, caller) {
|
|
|
3583
3583
|
Private recipes are visible only to the publisher's team \u2014 run \`apiblaze login\` if you are on it.`
|
|
3584
3584
|
);
|
|
3585
3585
|
}
|
|
3586
|
-
const
|
|
3586
|
+
const path9 = revision !== void 0 ? `/recipes/${ref.handle}/${ref.slug}/${revision}` : `/recipes/${ref.handle}/${ref.slug}/file`;
|
|
3587
3587
|
try {
|
|
3588
3588
|
return await producer(caller, {
|
|
3589
3589
|
method: "GET",
|
|
3590
|
-
path:
|
|
3590
|
+
path: path9,
|
|
3591
3591
|
summary: `Read private recipe ${ref.name}${revision !== void 0 ? `@${revision}` : ""}`
|
|
3592
3592
|
});
|
|
3593
3593
|
} catch (err) {
|
|
@@ -4739,9 +4739,9 @@ function printEnvelopeViolations(violations) {
|
|
|
4739
4739
|
console.log("bring-your-own-key case. Change the rules above, then publish again.");
|
|
4740
4740
|
console.log();
|
|
4741
4741
|
}
|
|
4742
|
-
async function askPromotion(
|
|
4742
|
+
async function askPromotion(path9) {
|
|
4743
4743
|
const { default: inquirer3 } = await import("inquirer");
|
|
4744
|
-
const suggestedId =
|
|
4744
|
+
const suggestedId = path9.split(/[.[\]]/).filter(Boolean).slice(-2).join("_").toLowerCase().replace(/[^a-z0-9_]/g, "_").replace(/_+/g, "_").replace(/^_|_$/g, "") || "value";
|
|
4745
4745
|
const answers = await inquirer3.prompt([
|
|
4746
4746
|
{ type: "input", name: "prompt", message: " prompt them with:", validate: (v) => v.trim() ? true : "Required." },
|
|
4747
4747
|
{ type: "input", name: "example", message: " example:" },
|
|
@@ -4752,7 +4752,7 @@ async function askPromotion(path8) {
|
|
|
4752
4752
|
const id = String(answers.id || suggestedId).toLowerCase().replace(/[^a-z0-9_]/g, "_");
|
|
4753
4753
|
console.log(import_chalk27.default.dim(` \u2192 replaced with {{${id}}}`));
|
|
4754
4754
|
return {
|
|
4755
|
-
path:
|
|
4755
|
+
path: path9,
|
|
4756
4756
|
id,
|
|
4757
4757
|
prompt: String(answers.prompt).trim(),
|
|
4758
4758
|
...answers.example ? { example: String(answers.example).trim() } : {},
|
|
@@ -7802,11 +7802,11 @@ async function resolveTransport(opts) {
|
|
|
7802
7802
|
const token = getAccessToken();
|
|
7803
7803
|
return {
|
|
7804
7804
|
tenant: tenant2,
|
|
7805
|
-
call: async (
|
|
7805
|
+
call: async (path9, method = "GET", body) => {
|
|
7806
7806
|
const res = await fetch(`${DASHBOARD_BASE6}/api/cli/iam`, {
|
|
7807
7807
|
method: "POST",
|
|
7808
7808
|
headers: { "Content-Type": "application/json", Authorization: `Bearer ${token}` },
|
|
7809
|
-
body: JSON.stringify({ tenant: tenant2, path:
|
|
7809
|
+
body: JSON.stringify({ tenant: tenant2, path: path9, method, body })
|
|
7810
7810
|
});
|
|
7811
7811
|
const data = await res.json().catch(() => ({}));
|
|
7812
7812
|
if (!res.ok) throw new ApiError(res.status, data?.details ?? data?.error ?? res.statusText, data);
|
|
@@ -7824,8 +7824,8 @@ async function resolveTransport(opts) {
|
|
|
7824
7824
|
return {
|
|
7825
7825
|
tenant: fresh.tenant,
|
|
7826
7826
|
selfEmail: fresh.email,
|
|
7827
|
-
call: async (
|
|
7828
|
-
const res = await fetch(`${IAM_BASE}${
|
|
7827
|
+
call: async (path9, method = "GET", body) => {
|
|
7828
|
+
const res = await fetch(`${IAM_BASE}${path9}`, {
|
|
7829
7829
|
method,
|
|
7830
7830
|
headers: {
|
|
7831
7831
|
"Content-Type": "application/json",
|
|
@@ -8177,12 +8177,12 @@ init_admin();
|
|
|
8177
8177
|
init_resolve();
|
|
8178
8178
|
init_types();
|
|
8179
8179
|
var DASHBOARD_BASE7 = process.env.APIBLAZE_DASHBOARD_BASE || "https://dashboard.apiblaze.com";
|
|
8180
|
-
async function iamCall(tenant2,
|
|
8180
|
+
async function iamCall(tenant2, path9, method = "GET", body) {
|
|
8181
8181
|
const token = getAccessToken();
|
|
8182
8182
|
const res = await fetch(`${DASHBOARD_BASE7}/api/cli/iam`, {
|
|
8183
8183
|
method: "POST",
|
|
8184
8184
|
headers: { "Content-Type": "application/json", Authorization: `Bearer ${token}` },
|
|
8185
|
-
body: JSON.stringify({ tenant: tenant2, path:
|
|
8185
|
+
body: JSON.stringify({ tenant: tenant2, path: path9, method, body })
|
|
8186
8186
|
});
|
|
8187
8187
|
const data = await res.json().catch(() => ({}));
|
|
8188
8188
|
if (!res.ok) throw new ApiError(res.status, data?.details ?? data?.error ?? res.statusText, data);
|
|
@@ -8269,10 +8269,10 @@ async function runPreapprove(who, opts) {
|
|
|
8269
8269
|
}
|
|
8270
8270
|
|
|
8271
8271
|
// src/commands/apichat.ts
|
|
8272
|
-
var
|
|
8273
|
-
var
|
|
8272
|
+
var fs11 = __toESM(require("fs"));
|
|
8273
|
+
var path7 = __toESM(require("path"));
|
|
8274
8274
|
var crypto2 = __toESM(require("crypto"));
|
|
8275
|
-
var
|
|
8275
|
+
var import_chalk47 = __toESM(require("chalk"));
|
|
8276
8276
|
var import_ora23 = __toESM(require("ora"));
|
|
8277
8277
|
var import_yaml3 = require("yaml");
|
|
8278
8278
|
init_auth();
|
|
@@ -8353,11 +8353,238 @@ async function runLlmClearKey() {
|
|
|
8353
8353
|
|
|
8354
8354
|
// src/commands/apichat.ts
|
|
8355
8355
|
init_trace();
|
|
8356
|
+
|
|
8357
|
+
// src/lib/external-mcp.ts
|
|
8358
|
+
var fs10 = __toESM(require("fs"));
|
|
8359
|
+
var os4 = __toESM(require("os"));
|
|
8360
|
+
var path6 = __toESM(require("path"));
|
|
8361
|
+
var import_child_process2 = require("child_process");
|
|
8362
|
+
var import_chalk46 = __toESM(require("chalk"));
|
|
8363
|
+
var useShell = process.platform === "win32";
|
|
8364
|
+
var winQuote = (a) => /^[A-Za-z0-9_\-.:/\\=]+$/.test(a) ? a : `"${a.replace(/"/g, '""')}"`;
|
|
8365
|
+
var run = (cmd, args, opts = {}) => (0, import_child_process2.spawnSync)(cmd, useShell ? args.map(winQuote) : args, {
|
|
8366
|
+
encoding: "utf-8",
|
|
8367
|
+
stdio: opts.interactive ? ["inherit", "inherit", "inherit"] : opts.inherit ? ["ignore", "inherit", "inherit"] : ["ignore", "pipe", "pipe"],
|
|
8368
|
+
timeout: opts.inherit || opts.interactive ? void 0 : 15e3,
|
|
8369
|
+
shell: useShell
|
|
8370
|
+
});
|
|
8371
|
+
var detected = null;
|
|
8372
|
+
function detectExternalClis() {
|
|
8373
|
+
if (detected) return detected;
|
|
8374
|
+
const found = [];
|
|
8375
|
+
for (const [kind, label3] of [["claude", "Claude CLI"], ["codex", "Codex CLI"]]) {
|
|
8376
|
+
try {
|
|
8377
|
+
const r = run(kind, ["--version"]);
|
|
8378
|
+
if (r.status === 0) found.push({ kind, label: label3, version: (r.stdout || "").trim().split("\n")[0] || void 0 });
|
|
8379
|
+
} catch {
|
|
8380
|
+
}
|
|
8381
|
+
}
|
|
8382
|
+
detected = found;
|
|
8383
|
+
return found;
|
|
8384
|
+
}
|
|
8385
|
+
var OURS = /\.mcp\.(abz\.run|tryabz\.run|apiblaze\.com)\b/;
|
|
8386
|
+
var mask = (k) => k.length > 14 ? `${k.slice(0, 10)}\u2026${k.slice(-4)}` : "\u2022\u2022\u2022";
|
|
8387
|
+
async function verifyMcpEndpoint(spec2) {
|
|
8388
|
+
try {
|
|
8389
|
+
const headers = { "Content-Type": "application/json" };
|
|
8390
|
+
if (spec2.apiKey) headers["X-API-Key"] = spec2.apiKey;
|
|
8391
|
+
if (spec2.endUserId) headers["X-End-User-Id"] = spec2.endUserId;
|
|
8392
|
+
const res = await fetch(spec2.url, {
|
|
8393
|
+
method: "POST",
|
|
8394
|
+
headers,
|
|
8395
|
+
body: JSON.stringify({ jsonrpc: "2.0", id: 1, method: "tools/list" }),
|
|
8396
|
+
signal: AbortSignal.timeout(1e4)
|
|
8397
|
+
});
|
|
8398
|
+
if (!res.ok) return { ok: false, status: res.status, tools: 0 };
|
|
8399
|
+
const body = await res.json().catch(() => null);
|
|
8400
|
+
if (!spec2.apiKey) return { ok: true, status: res.status, tools: body?.result?.tools?.length ?? 0 };
|
|
8401
|
+
if (body?.error) return { ok: false, status: res.status, tools: 0 };
|
|
8402
|
+
return { ok: true, status: res.status, tools: body?.result?.tools?.length ?? 0 };
|
|
8403
|
+
} catch {
|
|
8404
|
+
return { ok: false, status: 0, tools: 0 };
|
|
8405
|
+
}
|
|
8406
|
+
}
|
|
8407
|
+
function claudeInstallArgs(spec2, masked = false) {
|
|
8408
|
+
const args = ["mcp", "add", "--transport", "http", spec2.name, spec2.url];
|
|
8409
|
+
if (spec2.apiKey) args.push("--header", `X-API-Key: ${masked ? mask(spec2.apiKey) : spec2.apiKey}`);
|
|
8410
|
+
if (spec2.endUserId) args.push("--header", `X-End-User-Id: ${spec2.endUserId}`);
|
|
8411
|
+
return args;
|
|
8412
|
+
}
|
|
8413
|
+
function installIntoClaude(spec2) {
|
|
8414
|
+
const existing = run("claude", ["mcp", "get", spec2.name]);
|
|
8415
|
+
if (existing.status === 0) {
|
|
8416
|
+
const desc = existing.stdout || "";
|
|
8417
|
+
if (!OURS.test(desc)) {
|
|
8418
|
+
return {
|
|
8419
|
+
ok: false,
|
|
8420
|
+
conflict: true,
|
|
8421
|
+
error: `Claude CLI already has an MCP server named "${spec2.name}" that is not an APIblaze proxy \u2014 not touching it. Remove or rename it (claude mcp remove ${spec2.name}) and re-run.`
|
|
8422
|
+
};
|
|
8423
|
+
}
|
|
8424
|
+
run("claude", ["mcp", "remove", spec2.name]);
|
|
8425
|
+
}
|
|
8426
|
+
const r = run("claude", claudeInstallArgs(spec2));
|
|
8427
|
+
if (r.status === 0) return { ok: true };
|
|
8428
|
+
return { ok: false, error: (r.stderr || r.stdout || `exit ${r.status}`).trim().slice(0, 400) };
|
|
8429
|
+
}
|
|
8430
|
+
function claudeOneShot(spec2, prompt) {
|
|
8431
|
+
const argv = ["claude", "-p", prompt, "--allowedTools", `mcp__${spec2.name}__*`];
|
|
8432
|
+
const r = run(argv[0], argv.slice(1), { inherit: true });
|
|
8433
|
+
return { argv, status: r.status };
|
|
8434
|
+
}
|
|
8435
|
+
function codexConfigPath() {
|
|
8436
|
+
return path6.join(process.env.CODEX_HOME || path6.join(os4.homedir(), ".codex"), "config.toml");
|
|
8437
|
+
}
|
|
8438
|
+
var tomlStr = (s) => `"${s.replace(/\\/g, "\\\\").replace(/"/g, '\\"')}"`;
|
|
8439
|
+
function codexServerBlock(spec2) {
|
|
8440
|
+
const lines = [`[mcp_servers.${tomlStr(spec2.name)}]`, `url = ${tomlStr(spec2.url)}`];
|
|
8441
|
+
const headers = [];
|
|
8442
|
+
if (spec2.apiKey) headers.push(`"X-API-Key" = ${tomlStr(spec2.apiKey)}`);
|
|
8443
|
+
if (spec2.endUserId) headers.push(`"X-End-User-Id" = ${tomlStr(spec2.endUserId)}`);
|
|
8444
|
+
if (headers.length) lines.push(`http_headers = { ${headers.join(", ")} }`);
|
|
8445
|
+
return lines.join("\n") + "\n";
|
|
8446
|
+
}
|
|
8447
|
+
function codexSectionRanges(lines, name) {
|
|
8448
|
+
const esc = name.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
8449
|
+
const header = new RegExp(`^\\[mcp_servers\\.(?:"${esc}"|${esc})\\]\\s*(#.*)?$`);
|
|
8450
|
+
const anyHeader = /^\s*\[[^\]]+\]\s*(#.*)?$/;
|
|
8451
|
+
const ranges = [];
|
|
8452
|
+
for (let i = 0; i < lines.length; i++) {
|
|
8453
|
+
if (!header.test(lines[i])) continue;
|
|
8454
|
+
let end = lines.length;
|
|
8455
|
+
for (let j = i + 1; j < lines.length; j++) {
|
|
8456
|
+
if (anyHeader.test(lines[j])) {
|
|
8457
|
+
end = j;
|
|
8458
|
+
break;
|
|
8459
|
+
}
|
|
8460
|
+
}
|
|
8461
|
+
ranges.push({ start: i, end });
|
|
8462
|
+
i = end - 1;
|
|
8463
|
+
}
|
|
8464
|
+
return ranges;
|
|
8465
|
+
}
|
|
8466
|
+
function installIntoCodex(spec2) {
|
|
8467
|
+
const file = codexConfigPath();
|
|
8468
|
+
try {
|
|
8469
|
+
fs10.mkdirSync(path6.dirname(file), { recursive: true });
|
|
8470
|
+
let text = "";
|
|
8471
|
+
let existed = true;
|
|
8472
|
+
try {
|
|
8473
|
+
text = fs10.readFileSync(file, "utf-8");
|
|
8474
|
+
} catch {
|
|
8475
|
+
existed = false;
|
|
8476
|
+
}
|
|
8477
|
+
const lines = text.split("\n");
|
|
8478
|
+
const ranges = codexSectionRanges(lines, spec2.name);
|
|
8479
|
+
for (const r of ranges) {
|
|
8480
|
+
const body = lines.slice(r.start, r.end).join("\n");
|
|
8481
|
+
const url = body.match(/^\s*url\s*=\s*"([^"]*)"/m)?.[1];
|
|
8482
|
+
if (url && !OURS.test(url) || !url && /^\s*command\s*=/m.test(body)) {
|
|
8483
|
+
return {
|
|
8484
|
+
ok: false,
|
|
8485
|
+
conflict: true,
|
|
8486
|
+
path: file,
|
|
8487
|
+
error: `~/.codex/config.toml already has an MCP server named "${spec2.name}" that is not an APIblaze proxy \u2014 not touching it. Rename or remove that block and re-run.`
|
|
8488
|
+
};
|
|
8489
|
+
}
|
|
8490
|
+
}
|
|
8491
|
+
for (const r of [...ranges].reverse()) lines.splice(r.start, r.end - r.start);
|
|
8492
|
+
let cleaned = lines.join("\n");
|
|
8493
|
+
if (cleaned.length && !cleaned.endsWith("\n")) cleaned += "\n";
|
|
8494
|
+
if (cleaned.length && !cleaned.endsWith("\n\n")) cleaned += "\n";
|
|
8495
|
+
const tmp = `${file}.tmp-${process.pid}`;
|
|
8496
|
+
fs10.writeFileSync(tmp, cleaned + codexServerBlock(spec2), { encoding: "utf-8", mode: 384 });
|
|
8497
|
+
fs10.renameSync(tmp, file);
|
|
8498
|
+
if (!existed) {
|
|
8499
|
+
try {
|
|
8500
|
+
fs10.chmodSync(file, 384);
|
|
8501
|
+
} catch {
|
|
8502
|
+
}
|
|
8503
|
+
}
|
|
8504
|
+
return { ok: true, path: file };
|
|
8505
|
+
} catch (err) {
|
|
8506
|
+
return { ok: false, error: err instanceof Error ? err.message : String(err), path: file };
|
|
8507
|
+
}
|
|
8508
|
+
}
|
|
8509
|
+
function codexOneShot(_spec, prompt) {
|
|
8510
|
+
const argv = ["codex", "exec", prompt];
|
|
8511
|
+
const r = run(argv[0], argv.slice(1), { inherit: true });
|
|
8512
|
+
return { argv, status: r.status };
|
|
8513
|
+
}
|
|
8514
|
+
var shellQuote = (s) => `"${s.replace(/(["\\$`])/g, "\\$1")}"`;
|
|
8515
|
+
function renderCommand(argv) {
|
|
8516
|
+
return argv.map((a, i) => i === 0 || /^[A-Za-z0-9_@%+=:,./-]+$/.test(a) ? a : shellQuote(a)).join(" ");
|
|
8517
|
+
}
|
|
8518
|
+
function refreshInstall(cli, spec2) {
|
|
8519
|
+
const r = cli === "claude" ? installIntoClaude(spec2) : installIntoCodex(spec2);
|
|
8520
|
+
return r.ok;
|
|
8521
|
+
}
|
|
8522
|
+
async function installAndDemo(cli, spec2, getQuestion, log = console.log) {
|
|
8523
|
+
if (cli.kind === "claude") {
|
|
8524
|
+
log(`
|
|
8525
|
+
${import_chalk46.default.dim("$")} ${renderCommand(["claude", ...claudeInstallArgs(spec2, true)])}`);
|
|
8526
|
+
const r = installIntoClaude(spec2);
|
|
8527
|
+
if (!r.ok) {
|
|
8528
|
+
log(import_chalk46.default.red(` ${r.conflict ? "" : "Install failed: "}${r.error}`));
|
|
8529
|
+
return false;
|
|
8530
|
+
}
|
|
8531
|
+
log(` ${import_chalk46.default.green("\u2714")} MCP ${import_chalk46.default.bold(spec2.name)} added to Claude CLI (local scope \u2014 this directory).`);
|
|
8532
|
+
if (spec2.apiKey) log(import_chalk46.default.dim(" The API key is stored in Claude's local MCP config for this directory."));
|
|
8533
|
+
} else {
|
|
8534
|
+
const r = installIntoCodex(spec2);
|
|
8535
|
+
if (!r.ok) {
|
|
8536
|
+
log(import_chalk46.default.red(` ${r.conflict ? "" : `Could not write ${r.path}: `}${r.error}`));
|
|
8537
|
+
return false;
|
|
8538
|
+
}
|
|
8539
|
+
log(` ${import_chalk46.default.green("\u2714")} MCP ${import_chalk46.default.bold(spec2.name)} added to ${r.path}.`);
|
|
8540
|
+
}
|
|
8541
|
+
if (!spec2.apiKey) {
|
|
8542
|
+
const loginArgv = cli.kind === "claude" ? ["claude", "mcp", "login", spec2.name] : ["codex", "mcp", "login", spec2.name];
|
|
8543
|
+
log(`
|
|
8544
|
+
${import_chalk46.default.dim("This proxy authenticates by login \u2014 signing you in:")}`);
|
|
8545
|
+
log(` ${import_chalk46.default.dim("$")} ${renderCommand(loginArgv)}`);
|
|
8546
|
+
const r = run(loginArgv[0], loginArgv.slice(1), { interactive: true });
|
|
8547
|
+
if (r.status !== 0) {
|
|
8548
|
+
log(import_chalk46.default.yellow(` Sign-in didn't complete (exit ${r.status ?? "?"}). Run \`${renderCommand(loginArgv)}\` yourself, then chat away.`));
|
|
8549
|
+
return true;
|
|
8550
|
+
}
|
|
8551
|
+
log(` ${import_chalk46.default.green("\u2714")} Signed in.`);
|
|
8552
|
+
}
|
|
8553
|
+
const oneShot = cli.kind === "claude" ? claudeOneShot : codexOneShot;
|
|
8554
|
+
const abilities = `What are the tool abilities of the MCP server "${spec2.name}"? List them briefly.`;
|
|
8555
|
+
log(`
|
|
8556
|
+
${import_chalk46.default.dim("Checking what the API can do\u2026")}`);
|
|
8557
|
+
log(` ${import_chalk46.default.dim("$")} ${renderCommand(cli.kind === "claude" ? ["claude", "-p", abilities, "--allowedTools", `mcp__${spec2.name}__*`] : ["codex", "exec", abilities])}
|
|
8558
|
+
`);
|
|
8559
|
+
const check = oneShot(spec2, abilities);
|
|
8560
|
+
if (check.status !== 0) {
|
|
8561
|
+
log(import_chalk46.default.yellow(`
|
|
8562
|
+
${cli.label} exited with ${check.status ?? "no status"} \u2014 the MCP is installed, but the demo call failed.`));
|
|
8563
|
+
log(import_chalk46.default.yellow(` Open ${cli.label} and try it there${cli.kind === "claude" ? " (use /mcp to inspect the connection)" : ""}.`));
|
|
8564
|
+
return true;
|
|
8565
|
+
}
|
|
8566
|
+
const question = await getQuestion();
|
|
8567
|
+
if (question) {
|
|
8568
|
+
log(`
|
|
8569
|
+
${import_chalk46.default.dim("Your question, through " + cli.label + ":")}`);
|
|
8570
|
+
const shown = cli.kind === "claude" ? ["claude", "-p", question, "--allowedTools", `mcp__${spec2.name}__*`] : ["codex", "exec", question];
|
|
8571
|
+
log(` ${import_chalk46.default.dim("$")} ${renderCommand(shown)}
|
|
8572
|
+
`);
|
|
8573
|
+
const ans = oneShot(spec2, question);
|
|
8574
|
+
if (ans.status !== 0) log(import_chalk46.default.yellow(`
|
|
8575
|
+
${cli.label} exited with ${ans.status ?? "no status"} on that one \u2014 the MCP stays installed.`));
|
|
8576
|
+
}
|
|
8577
|
+
log(`
|
|
8578
|
+
${import_chalk46.default.bold(`Your ${cli.label} is now able to talk to the ${spec2.projectLabel} API.`)}`);
|
|
8579
|
+
return true;
|
|
8580
|
+
}
|
|
8581
|
+
|
|
8582
|
+
// src/commands/apichat.ts
|
|
8356
8583
|
init_types();
|
|
8357
8584
|
function fail4(message, hint) {
|
|
8358
|
-
console.error(
|
|
8585
|
+
console.error(import_chalk47.default.red(`
|
|
8359
8586
|
Error: ${message}`));
|
|
8360
|
-
if (hint) console.error(
|
|
8587
|
+
if (hint) console.error(import_chalk47.default.dim(hint));
|
|
8361
8588
|
process.exit(1);
|
|
8362
8589
|
}
|
|
8363
8590
|
function normalizeName3(raw) {
|
|
@@ -8388,7 +8615,7 @@ function parseSpec(text) {
|
|
|
8388
8615
|
}
|
|
8389
8616
|
return parsed;
|
|
8390
8617
|
}
|
|
8391
|
-
var GENERATOR_HINT = "
|
|
8618
|
+
var GENERATOR_HINT = "Pass --target <server-url | spec-file | spec-url>, or build a spec from real traffic:\n apiblaze create --target <url> then apiblaze agent openapi <project>";
|
|
8392
8619
|
async function fetchText(url) {
|
|
8393
8620
|
try {
|
|
8394
8621
|
const res = await fetch(url, { headers: { accept: "application/json, application/yaml, text/yaml, */*" } });
|
|
@@ -8421,7 +8648,7 @@ async function loadSpec(opts) {
|
|
|
8421
8648
|
}
|
|
8422
8649
|
let text;
|
|
8423
8650
|
try {
|
|
8424
|
-
text =
|
|
8651
|
+
text = fs11.readFileSync(opts.openapispec, "utf-8");
|
|
8425
8652
|
} catch {
|
|
8426
8653
|
fail4(`Cannot read spec file: ${opts.openapispec}`);
|
|
8427
8654
|
}
|
|
@@ -8435,7 +8662,7 @@ async function loadSpec(opts) {
|
|
|
8435
8662
|
}
|
|
8436
8663
|
return { spec: found.spec, sourceUrl: found.sourceUrl };
|
|
8437
8664
|
}
|
|
8438
|
-
fail4("No spec source. Pass --
|
|
8665
|
+
fail4("No spec source. Pass --target <server-url | openapi-file | openapi-url>.", GENERATOR_HINT);
|
|
8439
8666
|
}
|
|
8440
8667
|
function resolveTarget(spec2, opts, sourceUrl) {
|
|
8441
8668
|
if (opts.target) {
|
|
@@ -8505,7 +8732,7 @@ async function resolveTargetAuth(spec2, opts) {
|
|
|
8505
8732
|
"Re-run with --force to provision anyway (configure target auth later with `apiblaze config`),\nor use an api_key / bearer / basic scheme."
|
|
8506
8733
|
);
|
|
8507
8734
|
}
|
|
8508
|
-
if (sawOAuth) console.log(
|
|
8735
|
+
if (sawOAuth) console.log(import_chalk47.default.yellow(" --force: skipping OAuth target auth \u2014 configure it later with `apiblaze config`."));
|
|
8509
8736
|
return null;
|
|
8510
8737
|
}
|
|
8511
8738
|
if (candidates.length === 1 && !noneAllowed) return candidates[0];
|
|
@@ -8579,7 +8806,7 @@ async function ensureConsumerLogin(teamId, tenant2, version2) {
|
|
|
8579
8806
|
const fresh = await validConsumerToken(existing);
|
|
8580
8807
|
if (fresh) {
|
|
8581
8808
|
if (fresh.accessToken !== existing.accessToken) saveConsumer({ ...fresh, resource });
|
|
8582
|
-
console.log(
|
|
8809
|
+
console.log(import_chalk47.default.dim(` Using your consumer session on ${import_chalk47.default.bold(tenant2)}${fresh.email ? ` (${fresh.email})` : ""}.`));
|
|
8583
8810
|
return fresh;
|
|
8584
8811
|
}
|
|
8585
8812
|
}
|
|
@@ -8598,13 +8825,13 @@ async function ensureConsumerLogin(teamId, tenant2, version2) {
|
|
|
8598
8825
|
);
|
|
8599
8826
|
}
|
|
8600
8827
|
const clientId = pick2.client_id ?? pick2.clientId;
|
|
8601
|
-
console.log(`${
|
|
8828
|
+
console.log(`${import_chalk47.default.cyan("\u2192")} This proxy signs consumers in with OAuth \u2014 logging you in to ${import_chalk47.default.bold(tenant2)}...`);
|
|
8602
8829
|
const result = await deviceLogin(clientId, "openid email profile offline_access", ({ verificationUri, userCode }) => {
|
|
8603
8830
|
console.log(`
|
|
8604
|
-
Open: ${
|
|
8605
|
-
console.log(` Code: ${
|
|
8831
|
+
Open: ${import_chalk47.default.underline(verificationUri)}`);
|
|
8832
|
+
console.log(` Code: ${import_chalk47.default.bold(userCode)}
|
|
8606
8833
|
`);
|
|
8607
|
-
console.log(
|
|
8834
|
+
console.log(import_chalk47.default.dim(" (opening your browser\u2026 waiting for you to finish)"));
|
|
8608
8835
|
}, resource);
|
|
8609
8836
|
const claims = result.idToken && decodeJwt2(result.idToken) || (decodeJwt2(result.accessToken) ?? {});
|
|
8610
8837
|
const creds = {
|
|
@@ -8620,16 +8847,16 @@ async function ensureConsumerLogin(teamId, tenant2, version2) {
|
|
|
8620
8847
|
resource
|
|
8621
8848
|
};
|
|
8622
8849
|
saveConsumer(creds);
|
|
8623
|
-
console.log(` ${
|
|
8850
|
+
console.log(` ${import_chalk47.default.green("\u2714")} Signed in as${creds.email ? ` ${import_chalk47.default.bold(creds.email)}` : " a consumer"} on ${tenant2}.`);
|
|
8624
8851
|
return creds;
|
|
8625
8852
|
}
|
|
8626
|
-
async function cpPost(anon,
|
|
8853
|
+
async function cpPost(anon, path9, body, summary) {
|
|
8627
8854
|
if (anon) {
|
|
8628
8855
|
const cred = loadAnonCred();
|
|
8629
8856
|
if (!cred) throw new Error("Anonymous workspace credential missing.");
|
|
8630
|
-
return cpFetch(cred.cp_key,
|
|
8857
|
+
return cpFetch(cred.cp_key, path9, { method: "POST", body: JSON.stringify(body) });
|
|
8631
8858
|
}
|
|
8632
|
-
return admin({ method: "POST", path:
|
|
8859
|
+
return admin({ method: "POST", path: path9, body, summary });
|
|
8633
8860
|
}
|
|
8634
8861
|
async function provision(spec2, target, opts) {
|
|
8635
8862
|
const loggedIn = !!loadCredentials();
|
|
@@ -8649,7 +8876,7 @@ async function provision(spec2, target, opts) {
|
|
|
8649
8876
|
let name = opts.name ? base2 : `${base2}${salt()}`;
|
|
8650
8877
|
const access = anon ? "open" : opts.access === "open" ? "open" : "invite";
|
|
8651
8878
|
if (anon && opts.access === "invite") {
|
|
8652
|
-
console.log(
|
|
8879
|
+
console.log(import_chalk47.default.dim(" Note: --access invite needs an account to pre-approve people. Staying open for this anonymous proxy \u2014 run `apiblaze login`, then `apiblaze apichat --access invite`."));
|
|
8653
8880
|
}
|
|
8654
8881
|
const DUAL_AUTH = {
|
|
8655
8882
|
mode: "authenticate",
|
|
@@ -8748,7 +8975,7 @@ async function provision(spec2, target, opts) {
|
|
|
8748
8975
|
try {
|
|
8749
8976
|
await addPreapprovalRule(tenant2, email);
|
|
8750
8977
|
} catch {
|
|
8751
|
-
console.log(
|
|
8978
|
+
console.log(import_chalk47.default.dim(` (Could not auto-approve your email for sign-in \u2014 add it later: apiblaze preapprove ${email})`));
|
|
8752
8979
|
}
|
|
8753
8980
|
}
|
|
8754
8981
|
}
|
|
@@ -8792,7 +9019,7 @@ async function uploadSpec(p, specText, opts) {
|
|
|
8792
9019
|
throw err;
|
|
8793
9020
|
}
|
|
8794
9021
|
if (out && out.reused === true) {
|
|
8795
|
-
console.log(
|
|
9022
|
+
console.log(import_chalk47.default.dim(" Spec unchanged since the last provision \u2014 reusing the existing configuration."));
|
|
8796
9023
|
} else if (out && out.changed === true && out.previous_spec_hash) {
|
|
8797
9024
|
const interactive = !!process.stdin.isTTY && !opts.yes;
|
|
8798
9025
|
if (interactive) {
|
|
@@ -8800,7 +9027,7 @@ async function uploadSpec(p, specText, opts) {
|
|
|
8800
9027
|
const { go } = await inquirer3.prompt([
|
|
8801
9028
|
{ type: "confirm", name: "go", message: "The spec changed since the last provision \u2014 re-publish the MCP catalogue?", default: true }
|
|
8802
9029
|
]);
|
|
8803
|
-
if (!go) console.log(
|
|
9030
|
+
if (!go) console.log(import_chalk47.default.dim(" Keeping the existing MCP catalogue."));
|
|
8804
9031
|
}
|
|
8805
9032
|
}
|
|
8806
9033
|
}
|
|
@@ -8842,9 +9069,9 @@ function billingLine(info) {
|
|
|
8842
9069
|
if (typeof info.free_turns_remaining === "number") return null;
|
|
8843
9070
|
const cents = info.charged_cents;
|
|
8844
9071
|
const usd = (cents / 100).toFixed(Math.abs(cents - Math.round(cents)) < 1e-9 ? 2 : 4);
|
|
8845
|
-
let line =
|
|
9072
|
+
let line = import_chalk47.default.magenta(` \u{1F4B3} $${usd}`);
|
|
8846
9073
|
if (typeof info.credits_remaining === "number") {
|
|
8847
|
-
line +=
|
|
9074
|
+
line += import_chalk47.default.dim(` \xB7 balance $${(info.credits_remaining / 100).toFixed(2)}`);
|
|
8848
9075
|
}
|
|
8849
9076
|
return line;
|
|
8850
9077
|
}
|
|
@@ -8852,15 +9079,15 @@ function freeBudgetWarning(info, anon) {
|
|
|
8852
9079
|
if (!anon || !info) return null;
|
|
8853
9080
|
if (typeof info.free_turns_remaining === "number") {
|
|
8854
9081
|
const left2 = info.free_turns_remaining;
|
|
8855
|
-
if (left2 <= 0) return
|
|
8856
|
-
return
|
|
9082
|
+
if (left2 <= 0) return import_chalk47.default.yellow(" Free chats used up \u2014 `npx apiblaze login` (free) to keep going.");
|
|
9083
|
+
return import_chalk47.default.dim(` ${left2} free chat${left2 === 1 ? "" : "s"} left \xB7 /login to get more`);
|
|
8857
9084
|
}
|
|
8858
9085
|
if (typeof info.free_remaining_cents !== "number") return null;
|
|
8859
9086
|
const perTurn = Math.max(info.charged_cents || 0, 0.02);
|
|
8860
9087
|
const left = Math.floor(info.free_remaining_cents / perTurn);
|
|
8861
9088
|
if (left > 8) return null;
|
|
8862
|
-
if (left <= 0) return
|
|
8863
|
-
return
|
|
9089
|
+
if (left <= 0) return import_chalk47.default.yellow(" Free messages used up \u2014 `npx apiblaze login` (free) to keep chatting.");
|
|
9090
|
+
return import_chalk47.default.yellow(` \u26A0 About ${left} free message${left === 1 ? "" : "s"} left \u2014 \`npx apiblaze login\` (free) for more.`);
|
|
8864
9091
|
}
|
|
8865
9092
|
async function readSse(body, onEvent) {
|
|
8866
9093
|
const reader = body.getReader();
|
|
@@ -8905,7 +9132,7 @@ async function replTurn(p, messages, userText) {
|
|
|
8905
9132
|
});
|
|
8906
9133
|
} catch (err) {
|
|
8907
9134
|
spinner.fail("Network error.");
|
|
8908
|
-
console.log(
|
|
9135
|
+
console.log(import_chalk47.default.red(` Could not reach ${p.mcpHost}: ${err instanceof Error ? err.message : String(err)}`));
|
|
8909
9136
|
return;
|
|
8910
9137
|
}
|
|
8911
9138
|
if ((res.headers.get("content-type") ?? "").includes("text/event-stream") && res.ok && res.body) break;
|
|
@@ -8928,12 +9155,12 @@ async function replTurn(p, messages, userText) {
|
|
|
8928
9155
|
spinner.start("retrying on the " + (p.anon ? "trial" : "claimed") + " plane\u2026");
|
|
8929
9156
|
continue;
|
|
8930
9157
|
}
|
|
8931
|
-
console.log(
|
|
9158
|
+
console.log(import_chalk47.default.red(` No proxy named ${p.projectId} was found (tried both abz.run and tryabz.run).`));
|
|
8932
9159
|
return;
|
|
8933
9160
|
}
|
|
8934
9161
|
if (code === "identity_required" || /identif/i.test(msg) && !code) {
|
|
8935
9162
|
if (!p.endUserId && tty) {
|
|
8936
|
-
console.log(
|
|
9163
|
+
console.log(import_chalk47.default.yellow(" This API requires every call to say WHO is calling."));
|
|
8937
9164
|
const { default: inquirer3 } = await import("inquirer");
|
|
8938
9165
|
const { id } = await inquirer3.prompt([{ type: "input", name: "id", message: "Your end-user id (usually your email):" }]);
|
|
8939
9166
|
if (typeof id === "string" && id.trim()) {
|
|
@@ -8943,27 +9170,27 @@ async function replTurn(p, messages, userText) {
|
|
|
8943
9170
|
continue;
|
|
8944
9171
|
}
|
|
8945
9172
|
}
|
|
8946
|
-
console.log(
|
|
8947
|
-
console.log(
|
|
9173
|
+
console.log(import_chalk47.default.red(" This API requires an identified caller."));
|
|
9174
|
+
console.log(import_chalk47.default.dim(" Re-run with --xenduserid <your id> (usually your email)."));
|
|
8948
9175
|
return;
|
|
8949
9176
|
}
|
|
8950
9177
|
if (code === "user_not_preapproved") {
|
|
8951
|
-
console.log(
|
|
8952
|
-
if (p.endUserId) console.log(
|
|
9178
|
+
console.log(import_chalk47.default.yellow(` ${msg || "You are not pre-approved on this API."}`));
|
|
9179
|
+
if (p.endUserId) console.log(import_chalk47.default.dim(` Identity sent: ${p.endUserId}`));
|
|
8953
9180
|
const accessUrl = errObj && errObj.request_access_url;
|
|
8954
|
-
if (accessUrl) console.log(` Request access: ${
|
|
8955
|
-
console.log(
|
|
9181
|
+
if (accessUrl) console.log(` Request access: ${import_chalk47.default.bold(String(accessUrl))}`);
|
|
9182
|
+
console.log(import_chalk47.default.dim(" Or ask the producer to pre-approve you: `apiblaze preapprove <your-email> --tenant <tenant>`."));
|
|
8956
9183
|
return;
|
|
8957
9184
|
}
|
|
8958
9185
|
if (code === "user_frozen") {
|
|
8959
|
-
console.log(
|
|
9186
|
+
console.log(import_chalk47.default.red(` ${msg || "Your access to this API has been frozen by the producer."}`));
|
|
8960
9187
|
return;
|
|
8961
9188
|
}
|
|
8962
9189
|
const oauthWanted = /oauth token required|authorization: bearer/i.test(msg);
|
|
8963
9190
|
const keyWanted = /api key required|x-api-key/i.test(msg);
|
|
8964
9191
|
if (oauthWanted && !p.consumerAuth) {
|
|
8965
9192
|
if (p.teamId && p.tenant && loadCredentials()) {
|
|
8966
|
-
console.log(
|
|
9193
|
+
console.log(import_chalk47.default.dim(" This proxy signs consumers in with OAuth \u2014 starting the login\u2026"));
|
|
8967
9194
|
try {
|
|
8968
9195
|
await ensureConsumerLogin(p.teamId, p.tenant, p.version);
|
|
8969
9196
|
p.consumerAuth = true;
|
|
@@ -8972,17 +9199,17 @@ async function replTurn(p, messages, userText) {
|
|
|
8972
9199
|
spinner.start("retrying\u2026");
|
|
8973
9200
|
continue;
|
|
8974
9201
|
} catch (err) {
|
|
8975
|
-
console.log(
|
|
9202
|
+
console.log(import_chalk47.default.red(` Login failed: ${err instanceof Error ? err.message : String(err)}`));
|
|
8976
9203
|
return;
|
|
8977
9204
|
}
|
|
8978
9205
|
}
|
|
8979
|
-
console.log(
|
|
8980
|
-
console.log(
|
|
9206
|
+
console.log(import_chalk47.default.red(" This proxy signs consumers in with OAuth (a login), not an API key."));
|
|
9207
|
+
console.log(import_chalk47.default.dim(" Sign in with: `apiblaze consumer login --tenant <tenant> --client <app-client-id>`, then re-run apichat."));
|
|
8981
9208
|
return;
|
|
8982
9209
|
}
|
|
8983
9210
|
if (keyWanted) {
|
|
8984
9211
|
if (tty) {
|
|
8985
|
-
console.log(
|
|
9212
|
+
console.log(import_chalk47.default.yellow(` ${msg || "This API requires an API key."}`));
|
|
8986
9213
|
const { default: inquirer3 } = await import("inquirer");
|
|
8987
9214
|
const { key } = await inquirer3.prompt([{ type: "password", name: "key", mask: "*", message: "API key for this proxy:" }]);
|
|
8988
9215
|
if (typeof key === "string" && key.trim()) {
|
|
@@ -8993,8 +9220,8 @@ async function replTurn(p, messages, userText) {
|
|
|
8993
9220
|
continue;
|
|
8994
9221
|
}
|
|
8995
9222
|
}
|
|
8996
|
-
console.log(
|
|
8997
|
-
console.log(
|
|
9223
|
+
console.log(import_chalk47.default.red(` ${msg || "This API requires an API key."}`));
|
|
9224
|
+
console.log(import_chalk47.default.dim(" Re-run with --apikey <key> (mint one from the producer's site or dev portal)."));
|
|
8998
9225
|
return;
|
|
8999
9226
|
}
|
|
9000
9227
|
if (res && (res.status === 402 || res.status === 403)) {
|
|
@@ -9002,16 +9229,16 @@ async function replTurn(p, messages, userText) {
|
|
|
9002
9229
|
return;
|
|
9003
9230
|
}
|
|
9004
9231
|
if (res && res.status === 401) {
|
|
9005
|
-
console.log(
|
|
9006
|
-
console.log(
|
|
9232
|
+
console.log(import_chalk47.default.red(` The proxy rejected the request (401)${msg ? `: ${msg}` : "."}`));
|
|
9233
|
+
console.log(import_chalk47.default.dim(p.consumerAuth ? " Sent your consumer OAuth token. Run `apiblaze consumer login` again." : " Sent an API key. Pass a different one with --apikey <key>."));
|
|
9007
9234
|
return;
|
|
9008
9235
|
}
|
|
9009
|
-
console.log(
|
|
9236
|
+
console.log(import_chalk47.default.red(` Chat error: ${msg || (res ? `HTTP ${res.status}` : "request failed")}`));
|
|
9010
9237
|
return;
|
|
9011
9238
|
}
|
|
9012
9239
|
if (!res || !res.body || !(res.headers.get("content-type") ?? "").includes("text/event-stream")) {
|
|
9013
9240
|
spinner.stop();
|
|
9014
|
-
console.log(
|
|
9241
|
+
console.log(import_chalk47.default.red(" Chat error: could not authenticate to this proxy after several attempts."));
|
|
9015
9242
|
return;
|
|
9016
9243
|
}
|
|
9017
9244
|
let spinnerLive = true;
|
|
@@ -9052,7 +9279,7 @@ async function replTurn(p, messages, userText) {
|
|
|
9052
9279
|
const name = String(ev.toolName ?? "tool");
|
|
9053
9280
|
parts.push({ type: `tool-${name}`, toolCallId: String(ev.toolCallId ?? ""), state: "input-streaming" });
|
|
9054
9281
|
toolMeta.set(String(ev.toolCallId ?? ""), { name, startedAt: Date.now(), partIdx: parts.length - 1 });
|
|
9055
|
-
console.log(` ${
|
|
9282
|
+
console.log(` ${import_chalk47.default.cyan("\u2699")} ${import_chalk47.default.cyan(name)}${import_chalk47.default.dim("\u2026")}`);
|
|
9056
9283
|
break;
|
|
9057
9284
|
}
|
|
9058
9285
|
case "tool-input-available": {
|
|
@@ -9064,9 +9291,9 @@ async function replTurn(p, messages, userText) {
|
|
|
9064
9291
|
Object.assign(parts[meta.partIdx], { state: "input-available", input });
|
|
9065
9292
|
}
|
|
9066
9293
|
if (isVerbose()) {
|
|
9067
|
-
console.log(
|
|
9294
|
+
console.log(import_chalk47.default.dim(` args ${JSON.stringify(input)}`));
|
|
9068
9295
|
const hint = credHint();
|
|
9069
|
-
if (hint) console.log(
|
|
9296
|
+
if (hint) console.log(import_chalk47.default.dim(` auth ${hint}`) + (revealAuth ? "" : import_chalk47.default.yellow(" \u2190 /showauth reveals")));
|
|
9070
9297
|
}
|
|
9071
9298
|
break;
|
|
9072
9299
|
}
|
|
@@ -9077,8 +9304,8 @@ async function replTurn(p, messages, userText) {
|
|
|
9077
9304
|
const meta = toolMeta.get(id);
|
|
9078
9305
|
const ok = ev.type === "tool-output-available";
|
|
9079
9306
|
const ms = meta ? Date.now() - meta.startedAt : void 0;
|
|
9080
|
-
const mark = ok ?
|
|
9081
|
-
console.log(` ${mark} ${
|
|
9307
|
+
const mark = ok ? import_chalk47.default.green("\u2713") : import_chalk47.default.red("\u2717");
|
|
9308
|
+
console.log(` ${mark} ${import_chalk47.default.cyan(meta?.name ?? "tool")} ${import_chalk47.default.dim(`(${ok ? "ok" : "error"}${ms != null ? `, ${ms}ms` : ""})`)}`);
|
|
9082
9309
|
const detail = ok ? String(ev.output ?? "") : String(ev.errorText ?? "Tool call failed.");
|
|
9083
9310
|
if (meta) {
|
|
9084
9311
|
Object.assign(parts[meta.partIdx], ok ? { state: "output-available", output: detail } : { state: "output-error", errorText: detail });
|
|
@@ -9093,9 +9320,9 @@ async function replTurn(p, messages, userText) {
|
|
|
9093
9320
|
})();
|
|
9094
9321
|
const lines = pretty.split("\n");
|
|
9095
9322
|
const cap = ok ? 12 : 24;
|
|
9096
|
-
console.log(
|
|
9097
|
-
for (const line of lines.slice(0, cap)) console.log(
|
|
9098
|
-
if (lines.length > cap) console.log(
|
|
9323
|
+
console.log(import_chalk47.default.dim(" response:"));
|
|
9324
|
+
for (const line of lines.slice(0, cap)) console.log(import_chalk47.default.dim(` ${line}`));
|
|
9325
|
+
if (lines.length > cap) console.log(import_chalk47.default.dim(` \u2026${lines.length - cap} more lines`));
|
|
9099
9326
|
}
|
|
9100
9327
|
break;
|
|
9101
9328
|
}
|
|
@@ -9104,7 +9331,7 @@ async function replTurn(p, messages, userText) {
|
|
|
9104
9331
|
parts.push({ type: "text", text: "" });
|
|
9105
9332
|
openTextIdx = parts.length - 1;
|
|
9106
9333
|
if (!assistantOpen) {
|
|
9107
|
-
process.stdout.write("\n" +
|
|
9334
|
+
process.stdout.write("\n" + import_chalk47.default.green("assistant \u203A "));
|
|
9108
9335
|
assistantOpen = true;
|
|
9109
9336
|
}
|
|
9110
9337
|
break;
|
|
@@ -9135,12 +9362,12 @@ async function replTurn(p, messages, userText) {
|
|
|
9135
9362
|
});
|
|
9136
9363
|
} catch (err) {
|
|
9137
9364
|
stopSpinner();
|
|
9138
|
-
console.log(
|
|
9365
|
+
console.log(import_chalk47.default.red(` Stream error: ${err instanceof Error ? err.message : String(err)}`));
|
|
9139
9366
|
}
|
|
9140
9367
|
stopSpinner();
|
|
9141
9368
|
if (assistantOpen) process.stdout.write("\n\n");
|
|
9142
9369
|
if (parts.length) messages.push({ id: messageId, role: "assistant", parts });
|
|
9143
|
-
if (errorText) console.log(
|
|
9370
|
+
if (errorText) console.log(import_chalk47.default.red(` ${errorText}`));
|
|
9144
9371
|
if (upsell) {
|
|
9145
9372
|
renderUpsell(p, upsell, { messageAlreadyShown: true });
|
|
9146
9373
|
}
|
|
@@ -9152,39 +9379,39 @@ async function replTurn(p, messages, userText) {
|
|
|
9152
9379
|
function renderUpsell(p, upsell, opts = {}) {
|
|
9153
9380
|
const loggedIn = !!loadCredentials();
|
|
9154
9381
|
if (upsell.reason === "CAPPED" && !loggedIn) {
|
|
9155
|
-
console.log("\n" +
|
|
9156
|
-
console.log(
|
|
9382
|
+
console.log("\n" + import_chalk47.default.yellow(" Type `npx apiblaze login` to claim the rest of your balance."));
|
|
9383
|
+
console.log(import_chalk47.default.dim(" (or `/login` right here \u2014 your chat is preserved \u2014 or `apiblaze llm set-key` for your own model key.)"));
|
|
9157
9384
|
console.log();
|
|
9158
9385
|
return;
|
|
9159
9386
|
}
|
|
9160
9387
|
if (!opts.messageAlreadyShown) {
|
|
9161
|
-
console.log("\n" +
|
|
9388
|
+
console.log("\n" + import_chalk47.default.yellow(` ${upsell.message || "This turn is not available right now."}`));
|
|
9162
9389
|
}
|
|
9163
9390
|
if (upsell.reason === "INSUFFICIENT" || upsell.reason === "BREAKER" || upsell.reason === "CAPPED" || upsell.reason === "QUOTA" || upsell.reason === "PROXY_CAP" || upsell.reason === "PAUSED" || upsell.reason === "BYO_REQUIRED") {
|
|
9164
9391
|
if (!loggedIn) {
|
|
9165
|
-
console.log(
|
|
9392
|
+
console.log(import_chalk47.default.dim(" Options: `/login` for more free chats and requests, or `apiblaze llm set-key` to bring your own model key."));
|
|
9166
9393
|
} else {
|
|
9167
|
-
console.log(
|
|
9394
|
+
console.log(import_chalk47.default.dim(" Options: top up your wallet, or `apiblaze llm set-key` to bring your own model key (bypasses platform limits)."));
|
|
9168
9395
|
}
|
|
9169
9396
|
} else if (upsell.reason === "INFLIGHT") {
|
|
9170
|
-
console.log(
|
|
9397
|
+
console.log(import_chalk47.default.dim(" Another turn is still in flight \u2014 wait a moment and try again."));
|
|
9171
9398
|
}
|
|
9172
9399
|
console.log();
|
|
9173
9400
|
}
|
|
9174
|
-
var apichatsPath = () =>
|
|
9401
|
+
var apichatsPath = () => path7.join(getApiblazeDir(), "apichats.json");
|
|
9175
9402
|
function loadApichats() {
|
|
9176
9403
|
try {
|
|
9177
|
-
const list = JSON.parse(
|
|
9404
|
+
const list = JSON.parse(fs11.readFileSync(apichatsPath(), "utf-8"));
|
|
9178
9405
|
return Array.isArray(list) ? list : [];
|
|
9179
9406
|
} catch {
|
|
9180
9407
|
return [];
|
|
9181
9408
|
}
|
|
9182
9409
|
}
|
|
9183
9410
|
function writeApichats(list) {
|
|
9184
|
-
|
|
9185
|
-
|
|
9411
|
+
fs11.mkdirSync(getApiblazeDir(), { recursive: true });
|
|
9412
|
+
fs11.writeFileSync(apichatsPath(), JSON.stringify(list, null, 2), "utf-8");
|
|
9186
9413
|
try {
|
|
9187
|
-
|
|
9414
|
+
fs11.chmodSync(apichatsPath(), 384);
|
|
9188
9415
|
} catch {
|
|
9189
9416
|
}
|
|
9190
9417
|
}
|
|
@@ -9290,8 +9517,8 @@ async function openDirectProject(projectId, opts) {
|
|
|
9290
9517
|
else p.consumerAuth = true;
|
|
9291
9518
|
}
|
|
9292
9519
|
}
|
|
9293
|
-
console.log(` ${
|
|
9294
|
-
if (p.endUserId) console.log(` ${
|
|
9520
|
+
console.log(` ${import_chalk47.default.dim("Proxy:")} ${import_chalk47.default.bold(p.proxyUrl)}`);
|
|
9521
|
+
if (p.endUserId) console.log(` ${import_chalk47.default.dim("Acting as:")} ${import_chalk47.default.bold(p.endUserId)}`);
|
|
9295
9522
|
upsertApichat({
|
|
9296
9523
|
name: projectId,
|
|
9297
9524
|
target: p.proxyUrl,
|
|
@@ -9321,9 +9548,9 @@ async function openServerProxy(project) {
|
|
|
9321
9548
|
let consumerAuth = false;
|
|
9322
9549
|
if (acceptsApiKey) {
|
|
9323
9550
|
if (!dpKey) {
|
|
9324
|
-
console.log(
|
|
9551
|
+
console.log(import_chalk47.default.dim(` Minting an API key for tenant ${import_chalk47.default.bold(tenant2)} to query project ${import_chalk47.default.bold(project.projectName)}\u2026`));
|
|
9325
9552
|
dpKey = await mintDurableProxyKey(project.teamId, tenant2);
|
|
9326
|
-
console.log(` ${
|
|
9553
|
+
console.log(` ${import_chalk47.default.green("\u2714")} API key: ${import_chalk47.default.dim(maskKey(dpKey))}`);
|
|
9327
9554
|
}
|
|
9328
9555
|
} else {
|
|
9329
9556
|
consumerAuth = true;
|
|
@@ -9352,9 +9579,9 @@ async function openServerProxy(project) {
|
|
|
9352
9579
|
const spec2 = raw && (raw.spec ?? raw);
|
|
9353
9580
|
spinner.stop();
|
|
9354
9581
|
if (!spec2 || !(spec2.paths || spec2.openapi)) {
|
|
9355
|
-
console.log(
|
|
9582
|
+
console.log(import_chalk47.default.yellow(" This proxy has no OpenAPI spec yet \u2014 chat will have no tools. Build one with `apiblaze agent openapi`."));
|
|
9356
9583
|
} else {
|
|
9357
|
-
console.log(
|
|
9584
|
+
console.log(import_chalk47.default.dim(" Using the proxy's existing MCP catalogue (`apiblaze mcp` to rebuild it)."));
|
|
9358
9585
|
}
|
|
9359
9586
|
} catch (err) {
|
|
9360
9587
|
spinner.fail("Could not open the proxy.");
|
|
@@ -9386,15 +9613,15 @@ function discoverLocalSpecs() {
|
|
|
9386
9613
|
const found = [];
|
|
9387
9614
|
for (const n of known) {
|
|
9388
9615
|
try {
|
|
9389
|
-
if (
|
|
9616
|
+
if (fs11.statSync(path7.join(cwd, n)).isFile()) found.push(n);
|
|
9390
9617
|
} catch {
|
|
9391
9618
|
}
|
|
9392
9619
|
}
|
|
9393
9620
|
try {
|
|
9394
|
-
const files =
|
|
9621
|
+
const files = fs11.readdirSync(cwd).filter((f) => /\.(ya?ml|json)$/i.test(f) && !found.includes(f));
|
|
9395
9622
|
for (const f of files.slice(0, 60)) {
|
|
9396
9623
|
try {
|
|
9397
|
-
const head =
|
|
9624
|
+
const head = fs11.readFileSync(path7.join(cwd, f), "utf-8").slice(0, 4e3);
|
|
9398
9625
|
if (/["']?openapi["']?\s*:/i.test(head) || /["']?swagger["']?\s*:/i.test(head) || /^\s*paths\s*:/im.test(head) || /"paths"\s*:/.test(head)) {
|
|
9399
9626
|
found.push(f);
|
|
9400
9627
|
}
|
|
@@ -9410,7 +9637,7 @@ async function noArgsMenu(opts) {
|
|
|
9410
9637
|
const me = loadCredentials()?.apiblazeUserId;
|
|
9411
9638
|
const saved = loadApichats().filter((a) => a.anon ? true : a.ownerUserId !== void 0 && a.ownerUserId === me);
|
|
9412
9639
|
const choices = saved.map((a) => ({
|
|
9413
|
-
name: `Chat with ${
|
|
9640
|
+
name: `Chat with ${import_chalk47.default.bold(a.name)} ${import_chalk47.default.dim(`(${a.target})${a.messages && a.messages.length ? ` \xB7 ${a.messages.length} msgs` : ""}`)}`,
|
|
9414
9641
|
value: { type: "existing", a }
|
|
9415
9642
|
}));
|
|
9416
9643
|
const creds = loadCredentials();
|
|
@@ -9420,14 +9647,14 @@ async function noArgsMenu(opts) {
|
|
|
9420
9647
|
const proxies = (await getProjects(creds.teamId)).filter((pr) => !savedIds.has(pr.projectId));
|
|
9421
9648
|
for (const pr of proxies) {
|
|
9422
9649
|
choices.push({
|
|
9423
|
-
name: `Chat with ${
|
|
9650
|
+
name: `Chat with ${import_chalk47.default.bold(pr.projectName)} ${import_chalk47.default.dim(`(v${pr.apiVersion}) \xB7 your proxy`)}`,
|
|
9424
9651
|
value: { type: "server", project: pr }
|
|
9425
9652
|
});
|
|
9426
9653
|
}
|
|
9427
9654
|
} catch {
|
|
9428
9655
|
}
|
|
9429
9656
|
}
|
|
9430
|
-
choices.push({ name:
|
|
9657
|
+
choices.push({ name: import_chalk47.default.green("\uFF0B Create a new apichat"), value: { type: "new" } });
|
|
9431
9658
|
const { pick: pick2 } = await inquirer3.prompt([
|
|
9432
9659
|
{ type: "list", name: "pick", message: "What would you like to do?", choices }
|
|
9433
9660
|
]);
|
|
@@ -9530,36 +9757,36 @@ async function noArgsMenu(opts) {
|
|
|
9530
9757
|
async function runRepl(p, initialMessages) {
|
|
9531
9758
|
const { default: inquirer3 } = await import("inquirer");
|
|
9532
9759
|
const messages = (initialMessages ?? []).filter((m) => Array.isArray(m.parts));
|
|
9533
|
-
console.log("\n" +
|
|
9534
|
-
if (messages.length) console.log(
|
|
9760
|
+
console.log("\n" + import_chalk47.default.cyan.bold("Chat with your API") + import_chalk47.default.dim(` \xB7 ${p.mcpHost}`));
|
|
9761
|
+
if (messages.length) console.log(import_chalk47.default.dim(` Resumed \u2014 ${messages.length} prior messages.`));
|
|
9535
9762
|
const llm2 = loadLlmConfig();
|
|
9536
9763
|
console.log(
|
|
9537
|
-
|
|
9764
|
+
import_chalk47.default.dim(
|
|
9538
9765
|
llm2 ? `Using your local ${llm2.provider} key for the model. Type a question, or /exit. /login /claim manage your workspace.` : "Ask a question in plain English. /exit to quit \xB7 /login for more free chats \xB7 /claim to keep this workspace \xB7 `apiblaze llm set-key` for BYO models."
|
|
9539
9766
|
)
|
|
9540
9767
|
);
|
|
9541
9768
|
for (; ; ) {
|
|
9542
|
-
const { input } = await inquirer3.prompt([{ type: "input", name: "input", message:
|
|
9769
|
+
const { input } = await inquirer3.prompt([{ type: "input", name: "input", message: import_chalk47.default.green("you \u203A") }]);
|
|
9543
9770
|
const text = (input ?? "").trim();
|
|
9544
9771
|
if (!text) continue;
|
|
9545
9772
|
if (["/exit", "/quit", "exit", "quit", ":q"].includes(text.toLowerCase())) break;
|
|
9546
9773
|
if (text === "/login") {
|
|
9547
9774
|
try {
|
|
9548
9775
|
await runLogin();
|
|
9549
|
-
console.log(
|
|
9776
|
+
console.log(import_chalk47.default.dim(" Logged in \u2014 history preserved. Keep chatting."));
|
|
9550
9777
|
} catch (err) {
|
|
9551
|
-
console.log(
|
|
9778
|
+
console.log(import_chalk47.default.red(` Login failed: ${err instanceof Error ? err.message : String(err)}`));
|
|
9552
9779
|
}
|
|
9553
9780
|
continue;
|
|
9554
9781
|
}
|
|
9555
9782
|
if (text === "/claim") {
|
|
9556
9783
|
const justLoggedIn = !loadCredentials();
|
|
9557
9784
|
if (justLoggedIn) {
|
|
9558
|
-
console.log(
|
|
9785
|
+
console.log(import_chalk47.default.dim(" Logging in to claim your workspace\u2026"));
|
|
9559
9786
|
try {
|
|
9560
9787
|
await runLogin();
|
|
9561
9788
|
} catch (err) {
|
|
9562
|
-
console.log(
|
|
9789
|
+
console.log(import_chalk47.default.red(` Login failed: ${err instanceof Error ? err.message : String(err)}`));
|
|
9563
9790
|
continue;
|
|
9564
9791
|
}
|
|
9565
9792
|
if (!loadCredentials()) continue;
|
|
@@ -9570,57 +9797,217 @@ async function runRepl(p, initialMessages) {
|
|
|
9570
9797
|
p.mcpHost = p.mcpHost.replace(".mcp.tryabz.run", ".mcp.abz.run");
|
|
9571
9798
|
p.anon = false;
|
|
9572
9799
|
claimApichat(p, loadCredentials()?.apiblazeUserId);
|
|
9573
|
-
console.log(
|
|
9800
|
+
console.log(import_chalk47.default.dim(` Workspace claimed \u2014 chat now routes on ${p.mcpHost}. History preserved.`));
|
|
9801
|
+
const entry = loadApichats().find((a) => apichatKey(a) === apichatKey(p));
|
|
9802
|
+
for (const [cli, state] of Object.entries(entry?.cliOffer ?? {})) {
|
|
9803
|
+
if (state === "installed" && (cli === "claude" || cli === "codex")) {
|
|
9804
|
+
if (refreshInstall(cli, buildInstallSpec(p))) {
|
|
9805
|
+
console.log(import_chalk47.default.dim(` ${cli === "claude" ? "Claude" : "Codex"} CLI's MCP entry updated to the new host.`));
|
|
9806
|
+
}
|
|
9807
|
+
}
|
|
9808
|
+
}
|
|
9574
9809
|
}
|
|
9575
9810
|
} catch (err) {
|
|
9576
|
-
console.log(
|
|
9811
|
+
console.log(import_chalk47.default.red(` Claim failed: ${err instanceof Error ? err.message : String(err)}`));
|
|
9577
9812
|
}
|
|
9578
9813
|
continue;
|
|
9579
9814
|
}
|
|
9580
9815
|
if (text === "/showauth") {
|
|
9581
9816
|
revealAuth = !revealAuth;
|
|
9582
|
-
console.log(
|
|
9817
|
+
console.log(import_chalk47.default.dim(revealAuth ? " The real API key will be shown in the next call's curl. /showauth again to re-mask." : " API key re-masked."));
|
|
9583
9818
|
continue;
|
|
9584
9819
|
}
|
|
9585
9820
|
if (text.startsWith("/")) {
|
|
9586
|
-
console.log(
|
|
9821
|
+
console.log(import_chalk47.default.dim(" Commands: /login /claim /showauth /exit"));
|
|
9587
9822
|
continue;
|
|
9588
9823
|
}
|
|
9589
9824
|
await replTurn(p, messages, text);
|
|
9590
9825
|
saveTranscript(p, messages);
|
|
9591
9826
|
}
|
|
9592
|
-
console.log(
|
|
9827
|
+
console.log(import_chalk47.default.dim("\nBye."));
|
|
9828
|
+
}
|
|
9829
|
+
function offerKey(p) {
|
|
9830
|
+
return apichatKey(p);
|
|
9831
|
+
}
|
|
9832
|
+
function rememberCliOffer(p, cli, state) {
|
|
9833
|
+
const list = loadApichats();
|
|
9834
|
+
const i = list.findIndex((a) => apichatKey(a) === offerKey(p));
|
|
9835
|
+
if (i < 0) return;
|
|
9836
|
+
list[i].cliOffer = { ...list[i].cliOffer ?? {}, [cli]: state };
|
|
9837
|
+
list[i].updatedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
9838
|
+
writeApichats(list);
|
|
9839
|
+
}
|
|
9840
|
+
function buildInstallSpec(p) {
|
|
9841
|
+
return {
|
|
9842
|
+
name: p.projectId,
|
|
9843
|
+
url: `https://${p.mcpHost}/${p.version}/${p.environment}`,
|
|
9844
|
+
// consumerAuth means the door is a login — install bare, the CLI signs in.
|
|
9845
|
+
apiKey: p.consumerAuth ? void 0 : p.dpKey,
|
|
9846
|
+
endUserId: p.endUserId,
|
|
9847
|
+
projectLabel: p.projectId
|
|
9848
|
+
};
|
|
9849
|
+
}
|
|
9850
|
+
async function verifyAndHealMcpHost(p) {
|
|
9851
|
+
const ok = (await verifyMcpEndpoint(buildInstallSpec(p))).ok;
|
|
9852
|
+
if (ok) return true;
|
|
9853
|
+
const flipped = p.mcpHost.includes(".mcp.tryabz.run") ? p.mcpHost.replace(".mcp.tryabz.run", ".mcp.abz.run") : p.mcpHost.replace(".mcp.abz.run", ".mcp.tryabz.run");
|
|
9854
|
+
if (flipped === p.mcpHost) return false;
|
|
9855
|
+
const prev = p.mcpHost;
|
|
9856
|
+
p.mcpHost = flipped;
|
|
9857
|
+
if ((await verifyMcpEndpoint(buildInstallSpec(p))).ok) {
|
|
9858
|
+
const list = loadApichats();
|
|
9859
|
+
const i = list.findIndex((a) => apichatKey(a) === offerKey(p));
|
|
9860
|
+
if (i >= 0) {
|
|
9861
|
+
list[i].mcpHost = flipped;
|
|
9862
|
+
list[i].updatedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
9863
|
+
writeApichats(list);
|
|
9864
|
+
}
|
|
9865
|
+
return true;
|
|
9866
|
+
}
|
|
9867
|
+
p.mcpHost = prev;
|
|
9868
|
+
return false;
|
|
9869
|
+
}
|
|
9870
|
+
async function maybeInstallExternalCli(p, opts) {
|
|
9871
|
+
const forced = (opts.installMcp ?? "").toLowerCase();
|
|
9872
|
+
if (forced && forced !== "claude" && forced !== "codex") {
|
|
9873
|
+
fail4(`--install-mcp takes "claude" or "codex", not "${opts.installMcp}".`);
|
|
9874
|
+
}
|
|
9875
|
+
if (!forced && !process.stdin.isTTY) return false;
|
|
9876
|
+
const offer = loadApichats().find((a) => apichatKey(a) === offerKey(p))?.cliOffer ?? {};
|
|
9877
|
+
if (forced) {
|
|
9878
|
+
const cli = detectExternalClis().find((c) => c.kind === forced);
|
|
9879
|
+
if (!cli) fail4(
|
|
9880
|
+
`${forced === "claude" ? "Claude" : "Codex"} CLI not found on this machine.`,
|
|
9881
|
+
forced === "claude" ? "Install it: npm install -g @anthropic-ai/claude-code" : "Install it: npm install -g @openai/codex"
|
|
9882
|
+
);
|
|
9883
|
+
if (!await ensureInstallableDoor(p, opts)) return false;
|
|
9884
|
+
if (!await verifyAndHealMcpHost(p)) {
|
|
9885
|
+
fail4(`The MCP endpoint https://${p.mcpHost}/${p.version}/${p.environment} is not answering \u2014 not installing it into ${cli.label}.`);
|
|
9886
|
+
}
|
|
9887
|
+
const ran2 = await installAndDemo(cli, buildInstallSpec(p), () => resolveQuestion(opts));
|
|
9888
|
+
if (ran2) rememberCliOffer(p, cli.kind, "installed");
|
|
9889
|
+
return ran2;
|
|
9890
|
+
}
|
|
9891
|
+
const clis = detectExternalClis();
|
|
9892
|
+
if (clis.length === 0) return false;
|
|
9893
|
+
for (const c of clis) {
|
|
9894
|
+
if (offer[c.kind] === "installed" && await verifyAndHealMcpHost(p)) {
|
|
9895
|
+
refreshInstall(c.kind, buildInstallSpec(p));
|
|
9896
|
+
}
|
|
9897
|
+
}
|
|
9898
|
+
const fresh = clis.filter((c) => !offer[c.kind]);
|
|
9899
|
+
if (fresh.length === 0) return false;
|
|
9900
|
+
const { default: inquirer3 } = await import("inquirer");
|
|
9901
|
+
const names = fresh.map((c) => c.label).join(" and ");
|
|
9902
|
+
const targetName = fresh.length > 1 ? "one of them" : fresh[0].label;
|
|
9903
|
+
const { pick: pick2 } = await inquirer3.prompt([{
|
|
9904
|
+
type: "list",
|
|
9905
|
+
name: "pick",
|
|
9906
|
+
message: `I see ${names} ${fresh.length > 1 ? "are" : "is"} installed on this computer. Do you want to add the MCP for this proxy to ${targetName} so you can chat with your API from there directly, or chat here directly?`,
|
|
9907
|
+
choices: [
|
|
9908
|
+
...fresh.map((c) => ({ name: c.label, value: c })),
|
|
9909
|
+
{ name: "Chat here directly", value: "here" }
|
|
9910
|
+
]
|
|
9911
|
+
}]);
|
|
9912
|
+
if (pick2 === "here") {
|
|
9913
|
+
for (const c of fresh) rememberCliOffer(p, c.kind, "declined");
|
|
9914
|
+
if (p.consumerAuth && p.teamId && p.tenant) {
|
|
9915
|
+
try {
|
|
9916
|
+
await ensureConsumerLogin(p.teamId, p.tenant, p.version);
|
|
9917
|
+
} catch (err) {
|
|
9918
|
+
console.log(import_chalk47.default.yellow(` Sign-in didn't complete (${err instanceof Error ? err.message : String(err)}) \u2014 the first chat turn will retry it.`));
|
|
9919
|
+
}
|
|
9920
|
+
}
|
|
9921
|
+
return false;
|
|
9922
|
+
}
|
|
9923
|
+
if (!await ensureInstallableDoor(p, opts)) return false;
|
|
9924
|
+
if (!await verifyAndHealMcpHost(p)) {
|
|
9925
|
+
console.log(import_chalk47.default.red(` The MCP endpoint https://${p.mcpHost}/${p.version}/${p.environment} is not answering \u2014 not installing it into ${pick2.label}. Chat here instead.`));
|
|
9926
|
+
return false;
|
|
9927
|
+
}
|
|
9928
|
+
const ran = await installAndDemo(pick2, buildInstallSpec(p), () => resolveQuestion(opts));
|
|
9929
|
+
if (ran) {
|
|
9930
|
+
rememberCliOffer(p, pick2.kind, "installed");
|
|
9931
|
+
if (p.anon) {
|
|
9932
|
+
console.log(import_chalk47.default.dim(` Anonymous workspace \u2014 run \`apiblaze apichat ${p.projectId}\` and /claim to keep it (and this MCP) beyond 30 days.`));
|
|
9933
|
+
}
|
|
9934
|
+
}
|
|
9935
|
+
return ran;
|
|
9936
|
+
}
|
|
9937
|
+
async function ensureInstallableDoor(p, opts) {
|
|
9938
|
+
if (p.dpKey || p.consumerAuth) return true;
|
|
9939
|
+
if (!process.stdin.isTTY) {
|
|
9940
|
+
fail4(
|
|
9941
|
+
`Can't tell how "${p.projectId}" authenticates (no key on file).`,
|
|
9942
|
+
"Pass --apikey <key> for a key-door proxy, or open it interactively once first."
|
|
9943
|
+
);
|
|
9944
|
+
}
|
|
9945
|
+
const { default: inquirer3 } = await import("inquirer");
|
|
9946
|
+
const { key } = await inquirer3.prompt([{
|
|
9947
|
+
type: "password",
|
|
9948
|
+
name: "key",
|
|
9949
|
+
mask: "*",
|
|
9950
|
+
message: `API key for ${p.projectId} (leave empty if it uses a login):`
|
|
9951
|
+
}]);
|
|
9952
|
+
if (typeof key === "string" && key.trim()) p.dpKey = key.trim();
|
|
9953
|
+
else p.consumerAuth = true;
|
|
9954
|
+
void opts;
|
|
9955
|
+
return true;
|
|
9956
|
+
}
|
|
9957
|
+
async function resolveQuestion(opts) {
|
|
9958
|
+
if (opts.prompt) return opts.prompt;
|
|
9959
|
+
if (!process.stdin.isTTY) return void 0;
|
|
9960
|
+
const { default: inquirer3 } = await import("inquirer");
|
|
9961
|
+
const { q } = await inquirer3.prompt([{
|
|
9962
|
+
type: "input",
|
|
9963
|
+
name: "q",
|
|
9964
|
+
message: "What question do you have for this API?"
|
|
9965
|
+
}]);
|
|
9966
|
+
const t = (q ?? "").trim();
|
|
9967
|
+
return t || void 0;
|
|
9968
|
+
}
|
|
9969
|
+
async function startChat(p, messages, opts) {
|
|
9970
|
+
if (opts.prompt) {
|
|
9971
|
+
await replTurn(p, messages, opts.prompt);
|
|
9972
|
+
saveTranscript(p, messages);
|
|
9973
|
+
if (!process.stdin.isTTY) return;
|
|
9974
|
+
} else if (!process.stdin.isTTY) {
|
|
9975
|
+
fail4('Interactive chat needs a terminal. Pass -p "<question>" for a one-shot answer.');
|
|
9976
|
+
}
|
|
9977
|
+
await runRepl(p, messages);
|
|
9593
9978
|
}
|
|
9594
9979
|
async function runApichat(opts) {
|
|
9595
|
-
setVerbose(opts.verbose
|
|
9596
|
-
console.log(
|
|
9980
|
+
setVerbose(opts.verbose === true);
|
|
9981
|
+
console.log(import_chalk47.default.bold("\napichat \u2014 turn any API into a chat\n"));
|
|
9597
9982
|
if (opts.target && !opts.openapispec) {
|
|
9598
9983
|
const { classifyTargetInput: classifyTargetInput2 } = await Promise.resolve().then(() => (init_spec_or_target(), spec_or_target_exports));
|
|
9599
9984
|
const c = await classifyTargetInput2(opts.target, fail4);
|
|
9600
9985
|
if (c.kind === "spec") {
|
|
9601
|
-
console.log(
|
|
9986
|
+
console.log(import_chalk47.default.dim(` --target is an OpenAPI document (${c.source}) \u2014 using it as the spec.`));
|
|
9602
9987
|
opts.openapispec = opts.target;
|
|
9603
9988
|
opts.target = void 0;
|
|
9604
9989
|
}
|
|
9605
9990
|
}
|
|
9606
9991
|
if (opts.project) {
|
|
9607
9992
|
const opened = await openDirectProject(opts.project, opts);
|
|
9608
|
-
await
|
|
9993
|
+
if (await maybeInstallExternalCli(opened.p, opts)) return;
|
|
9994
|
+
await startChat(opened.p, opened.messages, opts);
|
|
9609
9995
|
return;
|
|
9610
9996
|
}
|
|
9611
9997
|
if (!opts.openapispec && !opts.target) {
|
|
9612
9998
|
if (!process.stdin.isTTY) {
|
|
9613
|
-
fail4("No spec source. Pass --
|
|
9999
|
+
fail4("No spec source. Pass --target <server-url | openapi-file | openapi-url>.", GENERATOR_HINT);
|
|
9614
10000
|
}
|
|
9615
10001
|
const resumed = await noArgsMenu(opts);
|
|
9616
10002
|
if (resumed) {
|
|
9617
|
-
await
|
|
10003
|
+
if (await maybeInstallExternalCli(resumed.p, opts)) return;
|
|
10004
|
+
await startChat(resumed.p, resumed.messages, opts);
|
|
9618
10005
|
return;
|
|
9619
10006
|
}
|
|
9620
10007
|
}
|
|
9621
10008
|
const { spec: spec2, sourceUrl } = await loadSpec(opts);
|
|
9622
10009
|
const target = resolveTarget(spec2, opts, sourceUrl);
|
|
9623
|
-
console.log(` ${
|
|
10010
|
+
console.log(` ${import_chalk47.default.dim("Target:")} ${import_chalk47.default.bold(target)}`);
|
|
9624
10011
|
const auth = await resolveTargetAuth(spec2, opts);
|
|
9625
10012
|
if (auth && !process.stdin.isTTY && !opts.targetAuthEnv) {
|
|
9626
10013
|
fail4(
|
|
@@ -9629,7 +10016,7 @@ async function runApichat(opts) {
|
|
|
9629
10016
|
);
|
|
9630
10017
|
}
|
|
9631
10018
|
const p = await provision(spec2, target, opts);
|
|
9632
|
-
console.log(` ${
|
|
10019
|
+
console.log(` ${import_chalk47.default.dim("Proxy: ")} ${import_chalk47.default.bold(p.proxyUrl || `${p.projectId} v${p.version}`)}`);
|
|
9633
10020
|
upsertApichat({
|
|
9634
10021
|
name: p.projectId,
|
|
9635
10022
|
target,
|
|
@@ -9649,30 +10036,31 @@ async function runApichat(opts) {
|
|
|
9649
10036
|
const secret = await captureTargetSecret(auth, opts);
|
|
9650
10037
|
if (secret) await writeTargetAuth(p, auth, secret);
|
|
9651
10038
|
} else {
|
|
9652
|
-
console.log(
|
|
10039
|
+
console.log(import_chalk47.default.dim(" Target auth: none required."));
|
|
9653
10040
|
}
|
|
9654
10041
|
const specText = JSON.stringify(spec2);
|
|
9655
10042
|
await uploadSpec(p, specText, opts);
|
|
9656
10043
|
const mcpUrl = await publishMcp(p, spec2);
|
|
9657
10044
|
console.log();
|
|
9658
|
-
if (p.proxyUrl) console.log(` ${
|
|
10045
|
+
if (p.proxyUrl) console.log(` ${import_chalk47.default.green("\u2713")} proxy ${import_chalk47.default.bold(p.proxyUrl)}`);
|
|
9659
10046
|
if (mcpUrl) {
|
|
9660
|
-
console.log(` ${
|
|
10047
|
+
console.log(` ${import_chalk47.default.green("\u2713")} mcp ${import_chalk47.default.bold(mcpUrl)}`);
|
|
9661
10048
|
if (p.access === "invite") {
|
|
9662
|
-
console.log(
|
|
9663
|
-
console.log(
|
|
10049
|
+
console.log(import_chalk47.default.dim(" Claude/ChatGPT-connectable (GitHub sign-in) \xB7 access: invite \u2014 only you + emails you pre-approve"));
|
|
10050
|
+
console.log(import_chalk47.default.dim(` Let others in: apiblaze preapprove someone@company.com${p.tenant ? ` --tenant ${p.tenant}` : ""} (or re-run with --access open)`));
|
|
9664
10051
|
} else {
|
|
9665
|
-
console.log(
|
|
10052
|
+
console.log(import_chalk47.default.dim(" Claude/ChatGPT-connectable (GitHub sign-in) \xB7 access: open \u2014 anyone who signs in can call this API"));
|
|
9666
10053
|
}
|
|
9667
10054
|
}
|
|
9668
10055
|
if (p.anon) {
|
|
9669
|
-
console.log(
|
|
10056
|
+
console.log(import_chalk47.default.dim("\n Anonymous workspace \u2014 /claim inside the chat to log in and keep it beyond 30 days."));
|
|
9670
10057
|
}
|
|
9671
|
-
await
|
|
10058
|
+
if (mcpUrl && await maybeInstallExternalCli(p, opts)) return;
|
|
10059
|
+
await startChat(p, [], opts);
|
|
9672
10060
|
}
|
|
9673
10061
|
|
|
9674
10062
|
// src/commands/consumer.ts
|
|
9675
|
-
var
|
|
10063
|
+
var import_chalk48 = __toESM(require("chalk"));
|
|
9676
10064
|
var import_ora24 = __toESM(require("ora"));
|
|
9677
10065
|
init_admin();
|
|
9678
10066
|
init_resolve();
|
|
@@ -9694,7 +10082,7 @@ async function consumerFetch(creds, suffix, init) {
|
|
|
9694
10082
|
function requireConsumer2() {
|
|
9695
10083
|
const c = loadConsumer();
|
|
9696
10084
|
if (!c) {
|
|
9697
|
-
console.error(
|
|
10085
|
+
console.error(import_chalk48.default.red("Not logged in as a consumer. Run `apiblaze consumer login` first."));
|
|
9698
10086
|
process.exit(1);
|
|
9699
10087
|
}
|
|
9700
10088
|
return c;
|
|
@@ -9705,7 +10093,7 @@ async function runConsumerLogin(opts) {
|
|
|
9705
10093
|
let clientId = opts.client;
|
|
9706
10094
|
if (clientId) {
|
|
9707
10095
|
if (!tenant2) {
|
|
9708
|
-
console.error(
|
|
10096
|
+
console.error(import_chalk48.default.red("When using --client, also pass --tenant <slug> (it sets which portal/keys host to use)."));
|
|
9709
10097
|
process.exit(1);
|
|
9710
10098
|
}
|
|
9711
10099
|
} else {
|
|
@@ -9723,18 +10111,18 @@ async function runConsumerLogin(opts) {
|
|
|
9723
10111
|
const usable = (Array.isArray(clients) ? clients : []).filter((c) => c && (c.client_id || c.clientId));
|
|
9724
10112
|
const pick2 = usable.find((c) => c.is_default || c.default) ?? usable.find((c) => c.verified !== false) ?? usable[0];
|
|
9725
10113
|
if (!pick2) {
|
|
9726
|
-
console.error(
|
|
10114
|
+
console.error(import_chalk48.default.red(`Tenant "${tenant2}" has no login app configured. Set one up in the dashboard (or \`apiblaze create\` with auth).`));
|
|
9727
10115
|
process.exit(1);
|
|
9728
10116
|
}
|
|
9729
10117
|
clientId = pick2.client_id ?? pick2.clientId;
|
|
9730
10118
|
}
|
|
9731
|
-
console.log(`${
|
|
10119
|
+
console.log(`${import_chalk48.default.cyan("\u2192")} Logging in to ${import_chalk48.default.bold(tenant2)} as a consumer...`);
|
|
9732
10120
|
const result = await deviceLogin(clientId, DEFAULT_SCOPE, ({ verificationUri, userCode }) => {
|
|
9733
10121
|
console.log(`
|
|
9734
|
-
Open: ${
|
|
9735
|
-
console.log(` Code: ${
|
|
10122
|
+
Open: ${import_chalk48.default.underline(verificationUri)}`);
|
|
10123
|
+
console.log(` Code: ${import_chalk48.default.bold(userCode)}
|
|
9736
10124
|
`);
|
|
9737
|
-
console.log(
|
|
10125
|
+
console.log(import_chalk48.default.dim(" (opening your browser\u2026 waiting for you to finish)"));
|
|
9738
10126
|
});
|
|
9739
10127
|
const claims = result.idToken && decodeJwt2(result.idToken) || (decodeJwt2(result.accessToken) ?? {});
|
|
9740
10128
|
const creds = {
|
|
@@ -9749,7 +10137,7 @@ async function runConsumerLogin(opts) {
|
|
|
9749
10137
|
obtainedAt: Date.now()
|
|
9750
10138
|
};
|
|
9751
10139
|
saveConsumer(creds);
|
|
9752
|
-
console.log(
|
|
10140
|
+
console.log(import_chalk48.default.green(`\u2714 Logged in as consumer${creds.email ? ` ${creds.email}` : ""} on ${tenant2}.`));
|
|
9753
10141
|
}
|
|
9754
10142
|
async function runConsumerTokens(opts) {
|
|
9755
10143
|
const creds = requireConsumer2();
|
|
@@ -9762,18 +10150,18 @@ async function runConsumerTokens(opts) {
|
|
|
9762
10150
|
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));
|
|
9763
10151
|
return;
|
|
9764
10152
|
}
|
|
9765
|
-
console.log(`${
|
|
10153
|
+
console.log(`${import_chalk48.default.cyan("Consumer")} ${import_chalk48.default.bold(fresh.email ?? fresh.tenant)} on ${import_chalk48.default.bold(fresh.tenant)}
|
|
9766
10154
|
`);
|
|
9767
|
-
console.log(`${
|
|
10155
|
+
console.log(`${import_chalk48.default.bold("access_token")} ${import_chalk48.default.dim("exp " + (exp(fresh.accessToken) ?? "?"))}
|
|
9768
10156
|
${fresh.accessToken}
|
|
9769
10157
|
`);
|
|
9770
|
-
if (fresh.idToken) console.log(`${
|
|
10158
|
+
if (fresh.idToken) console.log(`${import_chalk48.default.bold("id_token")} ${import_chalk48.default.dim("exp " + (exp(fresh.idToken) ?? "?"))}
|
|
9771
10159
|
${fresh.idToken}
|
|
9772
10160
|
`);
|
|
9773
|
-
if (fresh.refreshToken) console.log(`${
|
|
10161
|
+
if (fresh.refreshToken) console.log(`${import_chalk48.default.bold("refresh_token")}
|
|
9774
10162
|
${fresh.refreshToken}
|
|
9775
10163
|
`);
|
|
9776
|
-
console.log(
|
|
10164
|
+
console.log(import_chalk48.default.dim("These are your own tokens \u2014 keep them secret."));
|
|
9777
10165
|
}
|
|
9778
10166
|
async function runConsumerApikeys(opts) {
|
|
9779
10167
|
const creds = requireConsumer2();
|
|
@@ -9783,8 +10171,8 @@ async function runConsumerApikeys(opts) {
|
|
|
9783
10171
|
const revealed = await consumerFetch(list.creds, "/apikeys/reveal").catch(() => ({ status: 0, data: null, creds: list.creds }));
|
|
9784
10172
|
spinner.stop();
|
|
9785
10173
|
if (list.status >= 400) {
|
|
9786
|
-
console.error(
|
|
9787
|
-
if (list.status === 401) console.error(
|
|
10174
|
+
console.error(import_chalk48.default.red(`Failed to list keys (${list.status}): ${list.data?.error ?? ""}`));
|
|
10175
|
+
if (list.status === 401) console.error(import_chalk48.default.dim("Your consumer session may have expired \u2014 run `apiblaze consumer login` again."));
|
|
9788
10176
|
process.exit(1);
|
|
9789
10177
|
}
|
|
9790
10178
|
const keys = list.data?.keys ?? [];
|
|
@@ -9792,16 +10180,16 @@ async function runConsumerApikeys(opts) {
|
|
|
9792
10180
|
if (opts.json) {
|
|
9793
10181
|
console.log(JSON.stringify({ keys, revealed: revealMap }, null, 2));
|
|
9794
10182
|
} else if (!keys.length) {
|
|
9795
|
-
console.log(
|
|
10183
|
+
console.log(import_chalk48.default.yellow("No API keys yet."));
|
|
9796
10184
|
} else {
|
|
9797
10185
|
for (const k of keys) {
|
|
9798
10186
|
const clear = revealMap[k.environment]?.key;
|
|
9799
|
-
const shown = clear ?
|
|
9800
|
-
const exp = k.expires_at ?
|
|
9801
|
-
console.log(` ${
|
|
10187
|
+
const shown = clear ? import_chalk48.default.green(clear) : import_chalk48.default.dim(`${k.key_prefix ?? ""}\u2026${k.key_suffix ?? ""}`);
|
|
10188
|
+
const exp = k.expires_at ? import_chalk48.default.dim(`exp ${k.expires_at}`) : import_chalk48.default.dim("no expiry");
|
|
10189
|
+
console.log(` ${import_chalk48.default.bold(k.environment ?? "")} ${shown} ${exp} ${import_chalk48.default.dim(k.description ?? "")}`);
|
|
9802
10190
|
}
|
|
9803
10191
|
if (Object.keys(revealMap).length === 0 && keys.some((k) => !k.expires_at)) {
|
|
9804
|
-
console.log(
|
|
10192
|
+
console.log(import_chalk48.default.dim("\n(Only expiring keys can be shown in clear; non-expiring keys show a prefix only.)"));
|
|
9805
10193
|
}
|
|
9806
10194
|
}
|
|
9807
10195
|
if (opts.json) return;
|
|
@@ -9823,35 +10211,35 @@ async function runConsumerApikeys(opts) {
|
|
|
9823
10211
|
}
|
|
9824
10212
|
s2.succeed("Key created.");
|
|
9825
10213
|
const key = created.data?.key ?? created.data?.fullKey;
|
|
9826
|
-
if (key) console.log(` ${
|
|
9827
|
-
else console.log(
|
|
10214
|
+
if (key) console.log(` ${import_chalk48.default.green(key)} ${import_chalk48.default.dim("(shown once \u2014 store it now)")}`);
|
|
10215
|
+
else console.log(import_chalk48.default.dim(" Key created; run `apiblaze consumer apikeys` to reveal it if it expires."));
|
|
9828
10216
|
}
|
|
9829
10217
|
|
|
9830
10218
|
// src/commands/sidecar.ts
|
|
9831
|
-
var
|
|
10219
|
+
var import_chalk49 = __toESM(require("chalk"));
|
|
9832
10220
|
var import_ora25 = __toESM(require("ora"));
|
|
9833
|
-
var
|
|
9834
|
-
var
|
|
10221
|
+
var fs12 = __toESM(require("fs"));
|
|
10222
|
+
var path8 = __toESM(require("path"));
|
|
9835
10223
|
init_admin();
|
|
9836
10224
|
init_resolve();
|
|
9837
10225
|
init_auth();
|
|
9838
10226
|
function detectNextProject(root) {
|
|
9839
|
-
const hasConfig = ["next.config.js", "next.config.mjs", "next.config.ts"].some((f) =>
|
|
10227
|
+
const hasConfig = ["next.config.js", "next.config.mjs", "next.config.ts"].some((f) => fs12.existsSync(path8.join(root, f)));
|
|
9840
10228
|
let hasDep = false;
|
|
9841
10229
|
try {
|
|
9842
|
-
const pkg = JSON.parse(
|
|
10230
|
+
const pkg = JSON.parse(fs12.readFileSync(path8.join(root, "package.json"), "utf8"));
|
|
9843
10231
|
hasDep = !!(pkg.dependencies?.next || pkg.devDependencies?.next);
|
|
9844
10232
|
} catch {
|
|
9845
10233
|
}
|
|
9846
|
-
const appDir =
|
|
9847
|
-
const pagesDir =
|
|
10234
|
+
const appDir = fs12.existsSync(path8.join(root, "app")) || fs12.existsSync(path8.join(root, "src", "app"));
|
|
10235
|
+
const pagesDir = fs12.existsSync(path8.join(root, "pages")) || fs12.existsSync(path8.join(root, "src", "pages"));
|
|
9848
10236
|
return { found: hasConfig || hasDep || appDir || pagesDir, router: appDir ? "app" : pagesDir ? "pages" : null };
|
|
9849
10237
|
}
|
|
9850
10238
|
function upsertEnvLocal(root, token) {
|
|
9851
|
-
const p =
|
|
10239
|
+
const p = path8.join(root, ".env.local");
|
|
9852
10240
|
let existing = "";
|
|
9853
10241
|
try {
|
|
9854
|
-
existing =
|
|
10242
|
+
existing = fs12.readFileSync(p, "utf8");
|
|
9855
10243
|
} catch {
|
|
9856
10244
|
}
|
|
9857
10245
|
const had = /^APIBLAZE_API_KEY=/m.test(existing) || /^APIBLAZE_TOKEN=/m.test(existing);
|
|
@@ -9866,15 +10254,15 @@ function upsertEnvLocal(root, token) {
|
|
|
9866
10254
|
next = (next.endsWith("\n") ? next : next + "\n") + `APIBLAZE_SIDECAR_VERBOSE=true
|
|
9867
10255
|
`;
|
|
9868
10256
|
}
|
|
9869
|
-
|
|
10257
|
+
fs12.writeFileSync(p, next);
|
|
9870
10258
|
return had ? "rotated" : "created";
|
|
9871
10259
|
}
|
|
9872
10260
|
function installSidecarPackage(root) {
|
|
9873
|
-
if (
|
|
9874
|
-
console.log(` ${
|
|
10261
|
+
if (fs12.existsSync(path8.join(root, "node_modules", "apiblaze", "package.json"))) {
|
|
10262
|
+
console.log(` ${import_chalk49.default.green("\u2713")} apiblaze package already installed`);
|
|
9875
10263
|
return;
|
|
9876
10264
|
}
|
|
9877
|
-
const has = (f) =>
|
|
10265
|
+
const has = (f) => fs12.existsSync(path8.join(root, f));
|
|
9878
10266
|
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" };
|
|
9879
10267
|
const spinner = (0, import_ora25.default)(`Installing the apiblaze package (${pm.cmd})\u2026`).start();
|
|
9880
10268
|
try {
|
|
@@ -9882,12 +10270,12 @@ function installSidecarPackage(root) {
|
|
|
9882
10270
|
execSync(`${pm.cmd} ${pm.add} apiblaze`, { cwd: root, stdio: "ignore" });
|
|
9883
10271
|
spinner.succeed("Installed apiblaze (the sidecar runtime).");
|
|
9884
10272
|
} catch {
|
|
9885
|
-
spinner.warn(`Couldn't auto-install \u2014 run ${
|
|
10273
|
+
spinner.warn(`Couldn't auto-install \u2014 run ${import_chalk49.default.cyan(`${pm.cmd} ${pm.add} apiblaze`)} yourself before ${import_chalk49.default.cyan("npm run dev")}.`);
|
|
9886
10274
|
}
|
|
9887
10275
|
}
|
|
9888
10276
|
function readEnvKey(root) {
|
|
9889
10277
|
try {
|
|
9890
|
-
const s =
|
|
10278
|
+
const s = fs12.readFileSync(path8.join(root, ".env.local"), "utf8");
|
|
9891
10279
|
const m = s.match(/^APIBLAZE_API_KEY=(.+)$/m) ?? s.match(/^APIBLAZE_TOKEN=(.+)$/m);
|
|
9892
10280
|
return m ? m[1].trim() : null;
|
|
9893
10281
|
} catch {
|
|
@@ -9895,16 +10283,16 @@ function readEnvKey(root) {
|
|
|
9895
10283
|
}
|
|
9896
10284
|
}
|
|
9897
10285
|
function ensureGitignored(root) {
|
|
9898
|
-
const p =
|
|
10286
|
+
const p = path8.join(root, ".gitignore");
|
|
9899
10287
|
let c = "";
|
|
9900
10288
|
try {
|
|
9901
|
-
c =
|
|
10289
|
+
c = fs12.readFileSync(p, "utf8");
|
|
9902
10290
|
} catch {
|
|
9903
10291
|
}
|
|
9904
|
-
if (!/^\.env\.local$/m.test(c) && !/^\.env\*/m.test(c))
|
|
10292
|
+
if (!/^\.env\.local$/m.test(c) && !/^\.env\*/m.test(c)) fs12.writeFileSync(p, (c && !c.endsWith("\n") ? c + "\n" : c) + ".env.local\n");
|
|
9905
10293
|
}
|
|
9906
10294
|
function wireInstrumentation(root) {
|
|
9907
|
-
const existing = ["instrumentation.ts", "instrumentation.js",
|
|
10295
|
+
const existing = ["instrumentation.ts", "instrumentation.js", path8.join("src", "instrumentation.ts")].map((c) => path8.join(root, c)).find((f) => fs12.existsSync(f));
|
|
9908
10296
|
const body = `import { register as apiblaze } from "apiblaze/sidecar";
|
|
9909
10297
|
|
|
9910
10298
|
export function register() {
|
|
@@ -9912,18 +10300,18 @@ export function register() {
|
|
|
9912
10300
|
}
|
|
9913
10301
|
`;
|
|
9914
10302
|
if (!existing) {
|
|
9915
|
-
|
|
10303
|
+
fs12.writeFileSync(path8.join(root, "instrumentation.ts"), body);
|
|
9916
10304
|
return "created";
|
|
9917
10305
|
}
|
|
9918
|
-
const cur =
|
|
10306
|
+
const cur = fs12.readFileSync(existing, "utf8");
|
|
9919
10307
|
if (cur.includes("apiblaze/sidecar")) return "present";
|
|
9920
10308
|
if (/export\s+function\s+register\s*\(/.test(cur)) {
|
|
9921
|
-
|
|
10309
|
+
fs12.writeFileSync(existing, `import { register as apiblaze } from "apiblaze/sidecar";
|
|
9922
10310
|
` + cur.replace(/export\s+function\s+register\s*\(\s*\)\s*\{/, (m) => `${m}
|
|
9923
10311
|
apiblaze();`));
|
|
9924
10312
|
return "patched";
|
|
9925
10313
|
}
|
|
9926
|
-
|
|
10314
|
+
fs12.writeFileSync(existing, `import { register as apiblaze } from "apiblaze/sidecar";
|
|
9927
10315
|
${cur}
|
|
9928
10316
|
// call apiblaze() inside your register() export.
|
|
9929
10317
|
`);
|
|
@@ -10002,17 +10390,17 @@ export default async function Page() {
|
|
|
10002
10390
|
function generateInspector(root, router) {
|
|
10003
10391
|
try {
|
|
10004
10392
|
if (router === "pages") {
|
|
10005
|
-
const dir2 =
|
|
10006
|
-
const f2 =
|
|
10007
|
-
|
|
10008
|
-
return
|
|
10009
|
-
}
|
|
10010
|
-
const base2 =
|
|
10011
|
-
const dir =
|
|
10012
|
-
|
|
10013
|
-
const f =
|
|
10014
|
-
|
|
10015
|
-
return
|
|
10393
|
+
const dir2 = fs12.existsSync(path8.join(root, "src", "pages")) ? path8.join(root, "src", "pages") : path8.join(root, "pages");
|
|
10394
|
+
const f2 = path8.join(dir2, "abz-inspector.tsx");
|
|
10395
|
+
fs12.writeFileSync(f2, INSPECTOR_PAGE);
|
|
10396
|
+
return path8.relative(root, f2);
|
|
10397
|
+
}
|
|
10398
|
+
const base2 = fs12.existsSync(path8.join(root, "src", "app")) ? path8.join(root, "src", "app") : path8.join(root, "app");
|
|
10399
|
+
const dir = path8.join(base2, "abz-inspector");
|
|
10400
|
+
fs12.mkdirSync(dir, { recursive: true });
|
|
10401
|
+
const f = path8.join(dir, "page.tsx");
|
|
10402
|
+
fs12.writeFileSync(f, INSPECTOR_PAGE);
|
|
10403
|
+
return path8.relative(root, f);
|
|
10016
10404
|
} catch {
|
|
10017
10405
|
return null;
|
|
10018
10406
|
}
|
|
@@ -10033,43 +10421,43 @@ async function runAnonymousInit(root, router, opts) {
|
|
|
10033
10421
|
if (out.cp_key && out.team_id) saveAnonCred2(out.cp_key, out.team_id, out.claim_code);
|
|
10034
10422
|
const envState = upsertEnvLocal(root, out.token);
|
|
10035
10423
|
ensureGitignored(root);
|
|
10036
|
-
console.log(` ${
|
|
10037
|
-
console.log(` ${
|
|
10424
|
+
console.log(` ${import_chalk49.default.green("\u2713")} .env.local ${envState} (APIBLAZE_API_KEY) \u2014 gitignored`);
|
|
10425
|
+
console.log(` ${import_chalk49.default.green("\u2713")} instrumentation.ts ${wireInstrumentation(root)}`);
|
|
10038
10426
|
installSidecarPackage(root);
|
|
10039
10427
|
let inspectorPath = null;
|
|
10040
10428
|
if (!opts.noInspector) {
|
|
10041
10429
|
inspectorPath = generateInspector(root, router);
|
|
10042
|
-
if (inspectorPath) console.log(` ${
|
|
10430
|
+
if (inspectorPath) console.log(` ${import_chalk49.default.green("\u2713")} inspector at ${inspectorPath}`);
|
|
10043
10431
|
}
|
|
10044
10432
|
console.log("");
|
|
10045
|
-
console.log(
|
|
10046
|
-
console.log(` 1. ${
|
|
10433
|
+
console.log(import_chalk49.default.bold("Done (no account needed). What happens next:"));
|
|
10434
|
+
console.log(` 1. ${import_chalk49.default.cyan("npm run dev")} and use your app.`);
|
|
10047
10435
|
console.log(` 2. Each external origin your app calls is logged in the console \u2014 approve one with:`);
|
|
10048
|
-
console.log(` ${
|
|
10436
|
+
console.log(` ${import_chalk49.default.cyan("apiblaze sidecar approve api.stripe.com")} (no login needed)`);
|
|
10049
10437
|
console.log("");
|
|
10050
|
-
console.log(
|
|
10051
|
-
console.log(` ${
|
|
10052
|
-
console.log(
|
|
10438
|
+
console.log(import_chalk49.default.bold(" \u{1F511} Keep your setup \u2014 claim it into an account:"));
|
|
10439
|
+
console.log(` ${import_chalk49.default.cyan("apiblaze login")} then ${import_chalk49.default.cyan("apiblaze claim")} ${import_chalk49.default.dim("(no code needed here)")}`);
|
|
10440
|
+
console.log(import_chalk49.default.dim(` From another machine: apiblaze claim ${out.claim_code} \xB7 expires in 30 days`));
|
|
10053
10441
|
}
|
|
10054
10442
|
async function runSidecar(opts) {
|
|
10055
|
-
const root =
|
|
10056
|
-
const
|
|
10057
|
-
if (!
|
|
10058
|
-
console.log(
|
|
10443
|
+
const root = path8.resolve(opts.dir ?? process.cwd());
|
|
10444
|
+
const detected2 = detectNextProject(root);
|
|
10445
|
+
if (!detected2.found) {
|
|
10446
|
+
console.log(import_chalk49.default.yellow(`No Next.js project detected in ${root}.`));
|
|
10059
10447
|
console.log("Create one (e.g. `npx create-next-app`) and re-run `apiblaze init` inside it.");
|
|
10060
10448
|
return;
|
|
10061
10449
|
}
|
|
10062
10450
|
if (!loadCredentials() && !readEnvKey(root)) {
|
|
10063
|
-
await runAnonymousInit(root,
|
|
10451
|
+
await runAnonymousInit(root, detected2.router, opts);
|
|
10064
10452
|
return;
|
|
10065
10453
|
}
|
|
10066
10454
|
if (!loadCredentials()) {
|
|
10067
10455
|
upsertEnvLocal(root, readEnvKey(root));
|
|
10068
10456
|
ensureGitignored(root);
|
|
10069
|
-
console.log(` ${
|
|
10070
|
-
console.log(` ${
|
|
10457
|
+
console.log(` ${import_chalk49.default.green("\u2713")} .env.local present (APIBLAZE_API_KEY) \u2014 reusing`);
|
|
10458
|
+
console.log(` ${import_chalk49.default.green("\u2713")} instrumentation.ts ${wireInstrumentation(root)}`);
|
|
10071
10459
|
installSidecarPackage(root);
|
|
10072
|
-
console.log(
|
|
10460
|
+
console.log(import_chalk49.default.dim(" Log in and run `apiblaze claim <code>` to keep this setup, or `apiblaze login` to manage it."));
|
|
10073
10461
|
return;
|
|
10074
10462
|
}
|
|
10075
10463
|
const { teamId, teamName } = await resolveTeam(opts.team);
|
|
@@ -10092,38 +10480,38 @@ async function runSidecar(opts) {
|
|
|
10092
10480
|
throw err;
|
|
10093
10481
|
}
|
|
10094
10482
|
} else {
|
|
10095
|
-
console.log(
|
|
10483
|
+
console.log(import_chalk49.default.dim(` Reusing the existing APIBLAZE_API_KEY (run with --rotate to mint a fresh one, or --team <name> to switch teams).`));
|
|
10096
10484
|
}
|
|
10097
10485
|
const envState = upsertEnvLocal(root, token);
|
|
10098
10486
|
ensureGitignored(root);
|
|
10099
|
-
console.log(` ${
|
|
10487
|
+
console.log(` ${import_chalk49.default.green("\u2713")} .env.local ${envState} (APIBLAZE_API_KEY) \u2014 gitignored`);
|
|
10100
10488
|
const wireState = wireInstrumentation(root);
|
|
10101
|
-
console.log(` ${
|
|
10489
|
+
console.log(` ${import_chalk49.default.green("\u2713")} instrumentation.ts ${wireState}`);
|
|
10102
10490
|
installSidecarPackage(root);
|
|
10103
10491
|
let inspectorPath = null;
|
|
10104
10492
|
if (!opts.noInspector) {
|
|
10105
|
-
inspectorPath = generateInspector(root,
|
|
10106
|
-
if (inspectorPath) console.log(` ${
|
|
10493
|
+
inspectorPath = generateInspector(root, detected2.router);
|
|
10494
|
+
if (inspectorPath) console.log(` ${import_chalk49.default.green("\u2713")} inspector at ${inspectorPath}`);
|
|
10107
10495
|
}
|
|
10108
10496
|
console.log("");
|
|
10109
|
-
console.log(
|
|
10110
|
-
console.log(` 1. ${
|
|
10111
|
-
console.log(` 2. The origins your app calls appear as ${
|
|
10112
|
-
console.log(` 3. Approve the ones to route: ${
|
|
10497
|
+
console.log(import_chalk49.default.bold("Done. What happens next:"));
|
|
10498
|
+
console.log(` 1. ${import_chalk49.default.cyan("npm run dev")} and use your app \u2014 it works exactly as before (all calls go direct).`);
|
|
10499
|
+
console.log(` 2. The origins your app calls appear as ${import_chalk49.default.bold("candidates")} \u2014 list them: ${import_chalk49.default.cyan("apiblaze sidecar")}`);
|
|
10500
|
+
console.log(` 3. Approve the ones to route: ${import_chalk49.default.cyan("apiblaze sidecar approve api.stripe.com")} (or in the dashboard)`);
|
|
10113
10501
|
console.log(` \u2026within ~5 min your app starts routing that origin through APIblaze.`);
|
|
10114
|
-
if (inspectorPath) console.log(` \u2022 Try it now: open ${
|
|
10115
|
-
if (switchingTeam) console.log(
|
|
10502
|
+
if (inspectorPath) console.log(` \u2022 Try it now: open ${import_chalk49.default.underline("http://localhost:3000/abz-inspector")} (dev only; rm ${path8.dirname(inspectorPath)} before shipping)`);
|
|
10503
|
+
if (switchingTeam) console.log(import_chalk49.default.dim(` \u2022 Approved origins are per-team \u2014 re-approve them on ${teamName ?? teamId} with \`apiblaze sidecar approve <origin>\`.`));
|
|
10116
10504
|
console.log("");
|
|
10117
|
-
console.log(
|
|
10118
|
-
console.log(
|
|
10119
|
-
console.log(
|
|
10505
|
+
console.log(import_chalk49.default.dim(" Manage: apiblaze sidecar (list/approve/deny/remove)"));
|
|
10506
|
+
console.log(import_chalk49.default.dim(" Rotate: apiblaze init --rotate \xB7 Switch team: apiblaze init --team <name>"));
|
|
10507
|
+
console.log(import_chalk49.default.dim(" Turn off: set APIBLAZE_SIDECAR=off in .env.local (flip back to on anytime; key stays put)."));
|
|
10120
10508
|
console.log("");
|
|
10121
|
-
console.log(
|
|
10122
|
-
console.log(
|
|
10509
|
+
console.log(import_chalk49.default.yellow(" \u26A0 APIBLAZE_API_KEY is long-lived and lets a holder call your team's proxies. Never commit it."));
|
|
10510
|
+
console.log(import_chalk49.default.dim(" Your control-plane login stays in ~/.apiblaze \u2014 it never entered this project."));
|
|
10123
10511
|
}
|
|
10124
10512
|
|
|
10125
10513
|
// src/commands/origins.ts
|
|
10126
|
-
var
|
|
10514
|
+
var import_chalk50 = __toESM(require("chalk"));
|
|
10127
10515
|
var import_ora26 = __toESM(require("ora"));
|
|
10128
10516
|
init_admin();
|
|
10129
10517
|
init_resolve();
|
|
@@ -10134,7 +10522,7 @@ async function runOriginsList(opts) {
|
|
|
10134
10522
|
if (!loadCredentials()) {
|
|
10135
10523
|
const cred = loadAnonCred();
|
|
10136
10524
|
if (!cred) {
|
|
10137
|
-
console.log(
|
|
10525
|
+
console.log(import_chalk50.default.yellow("No anonymous workspace here. Run `apiblaze init` first."));
|
|
10138
10526
|
return;
|
|
10139
10527
|
}
|
|
10140
10528
|
out = await cpFetch(cred.cp_key, `/teams/${encodeURIComponent(cred.team_id)}/sidecar/candidates`, { method: "GET" });
|
|
@@ -10152,27 +10540,27 @@ async function runOriginsList(opts) {
|
|
|
10152
10540
|
}
|
|
10153
10541
|
const routed = out.routed ?? [];
|
|
10154
10542
|
const candidates = out.candidates ?? [];
|
|
10155
|
-
console.log(
|
|
10543
|
+
console.log(import_chalk50.default.bold(`
|
|
10156
10544
|
Routed through APIblaze (${routed.length})`));
|
|
10157
|
-
if (!routed.length) console.log(
|
|
10158
|
-
for (const r of routed) console.log(` ${
|
|
10159
|
-
console.log(
|
|
10545
|
+
if (!routed.length) console.log(import_chalk50.default.dim(" none yet"));
|
|
10546
|
+
for (const r of routed) console.log(` ${import_chalk50.default.green("\u25CF")} ${r.sidecar_origin} ${import_chalk50.default.dim(`\u2192 ${r.project_id}`)}`);
|
|
10547
|
+
console.log(import_chalk50.default.bold(`
|
|
10160
10548
|
Candidates \u2014 going direct, not yet approved (${candidates.length})`));
|
|
10161
|
-
if (!candidates.length) console.log(
|
|
10549
|
+
if (!candidates.length) console.log(import_chalk50.default.dim(" none \u2014 run your app to discover the origins it calls"));
|
|
10162
10550
|
for (const c of candidates) {
|
|
10163
|
-
console.log(` ${
|
|
10551
|
+
console.log(` ${import_chalk50.default.yellow("\u25CB")} ${c.origin} ${import_chalk50.default.dim(`seen ${c.request_count}\xD7, last ${c.last_seen}`)}`);
|
|
10164
10552
|
}
|
|
10165
10553
|
if (candidates.length) {
|
|
10166
|
-
console.log(
|
|
10554
|
+
console.log(import_chalk50.default.dim(`
|
|
10167
10555
|
Approve: apiblaze sidecar approve ${candidates[0].origin.replace("https://", "")}`));
|
|
10168
|
-
console.log(
|
|
10556
|
+
console.log(import_chalk50.default.dim(` Dismiss: apiblaze sidecar deny ${candidates[0].origin.replace("https://", "")}`));
|
|
10169
10557
|
}
|
|
10170
10558
|
}
|
|
10171
10559
|
async function runOriginsApprove(origin, opts) {
|
|
10172
10560
|
if (!loadCredentials()) {
|
|
10173
10561
|
const cred = loadAnonCred();
|
|
10174
10562
|
if (!cred) {
|
|
10175
|
-
console.error(
|
|
10563
|
+
console.error(import_chalk50.default.red("Not logged in and no anonymous workspace. Run `apiblaze init` first."));
|
|
10176
10564
|
process.exit(1);
|
|
10177
10565
|
}
|
|
10178
10566
|
const spinner2 = (0, import_ora26.default)(`Approving ${origin} (anonymous)...`).start();
|
|
@@ -10225,13 +10613,13 @@ async function runOriginsRemove(origin, opts) {
|
|
|
10225
10613
|
}
|
|
10226
10614
|
|
|
10227
10615
|
// src/commands/op.ts
|
|
10228
|
-
var
|
|
10616
|
+
var import_chalk52 = __toESM(require("chalk"));
|
|
10229
10617
|
init_auth();
|
|
10230
10618
|
init_trace();
|
|
10231
10619
|
init_types();
|
|
10232
10620
|
|
|
10233
10621
|
// src/commands/op-billing.ts
|
|
10234
|
-
var
|
|
10622
|
+
var import_chalk51 = __toESM(require("chalk"));
|
|
10235
10623
|
init_admin();
|
|
10236
10624
|
var SANDBOX = {
|
|
10237
10625
|
teamId: "team_1782844865835_zujrf",
|
|
@@ -10290,7 +10678,7 @@ async function rowsForRays(project, version2, tenant2, rays) {
|
|
|
10290
10678
|
}
|
|
10291
10679
|
function printDoors(data) {
|
|
10292
10680
|
const checks = [];
|
|
10293
|
-
console.log(
|
|
10681
|
+
console.log(import_chalk51.default.bold("\n Doors \u2014 is every way in metered?\n"));
|
|
10294
10682
|
const doors = data?.doors ?? [];
|
|
10295
10683
|
const metered = doors.filter((d) => d.verdict === "metered");
|
|
10296
10684
|
const allowed = doors.filter((d) => d.verdict === "allowed-free");
|
|
@@ -10299,29 +10687,29 @@ function printDoors(data) {
|
|
|
10299
10687
|
const errs = data?.errors ?? [];
|
|
10300
10688
|
const routeAuditBroke = errs.some((e) => e.startsWith("zone "));
|
|
10301
10689
|
const devAuditBroke = errs.some((e) => e.startsWith("workers.dev audit"));
|
|
10302
|
-
console.log(
|
|
10303
|
-
for (const d of metered) console.log(
|
|
10304
|
-
console.log(
|
|
10690
|
+
console.log(import_chalk51.default.dim(` ${metered.length} route(s) behind main-proxy (metered)`));
|
|
10691
|
+
for (const d of metered) console.log(import_chalk51.default.green(` \u2713 ${d.pattern}`));
|
|
10692
|
+
console.log(import_chalk51.default.dim(`
|
|
10305
10693
|
${allowed.length} route(s) free ON PURPOSE`));
|
|
10306
10694
|
for (const d of allowed) {
|
|
10307
|
-
console.log(
|
|
10308
|
-
console.log(
|
|
10695
|
+
console.log(import_chalk51.default.cyan(` \u2022 ${d.pattern}`) + import_chalk51.default.dim(` \u2192 ${d.script}`));
|
|
10696
|
+
console.log(import_chalk51.default.dim(` ${d.why}`));
|
|
10309
10697
|
}
|
|
10310
10698
|
if (known.length) {
|
|
10311
|
-
console.log(
|
|
10699
|
+
console.log(import_chalk51.default.yellow(`
|
|
10312
10700
|
${known.length} route(s) KNOWN OPEN \u2014 unmetered, not yet closed`));
|
|
10313
10701
|
for (const d of known) {
|
|
10314
|
-
console.log(
|
|
10315
|
-
console.log(
|
|
10702
|
+
console.log(import_chalk51.default.yellow(` ! ${d.pattern}`) + import_chalk51.default.dim(` \u2192 ${d.script}`));
|
|
10703
|
+
console.log(import_chalk51.default.dim(` ${d.why}`));
|
|
10316
10704
|
}
|
|
10317
10705
|
checks.push({ name: "no known-open doors", status: "KNOWN", detail: `${known.length} unmetered route(s) still open \u2014 see above` });
|
|
10318
10706
|
}
|
|
10319
10707
|
if (stray.length) {
|
|
10320
|
-
console.log(
|
|
10708
|
+
console.log(import_chalk51.default.red(`
|
|
10321
10709
|
${stray.length} STRAY route(s) \u2014 not main-proxy, not on the allowlist`));
|
|
10322
10710
|
for (const d of stray) {
|
|
10323
|
-
console.log(
|
|
10324
|
-
console.log(
|
|
10711
|
+
console.log(import_chalk51.default.red(` \u2717 ${d.pattern}`) + import_chalk51.default.dim(` \u2192 ${d.script}`));
|
|
10712
|
+
console.log(import_chalk51.default.dim(` ${d.why}`));
|
|
10325
10713
|
}
|
|
10326
10714
|
checks.push({ name: "no stray routes", status: "FAIL", detail: `${stray.length}: ${stray.map((s) => s.pattern).join(", ")}` });
|
|
10327
10715
|
} else if (routeAuditBroke) {
|
|
@@ -10332,22 +10720,22 @@ function printDoors(data) {
|
|
|
10332
10720
|
const wd = data?.workers_dev ?? {};
|
|
10333
10721
|
const open = wd.enabled ?? [];
|
|
10334
10722
|
if (open.length) {
|
|
10335
|
-
console.log(
|
|
10723
|
+
console.log(import_chalk51.default.red(`
|
|
10336
10724
|
${open.length} of ${wd.total} worker(s) reachable on *.workers.dev`));
|
|
10337
|
-
for (const s of open) console.log(
|
|
10338
|
-
console.log(
|
|
10725
|
+
for (const s of open) console.log(import_chalk51.default.red(` \u2717 ${s.script}.workers.dev`) + import_chalk51.default.dim(` (enabled=${s.enabled} previews=${s.previews})`));
|
|
10726
|
+
console.log(import_chalk51.default.dim(" A workers.dev hostname bypasses every CF route, WAF rule and the credit gate."));
|
|
10339
10727
|
checks.push({ name: "no workers.dev doors", status: "FAIL", detail: `${open.length} script(s) publicly reachable: ${open.map((s) => s.script).join(", ")}` });
|
|
10340
10728
|
} else if (devAuditBroke || !wd.total) {
|
|
10341
10729
|
checks.push({ name: "no workers.dev doors", status: "SKIP", detail: "script enumeration failed \u2014 NOT a pass, no subdomain was ever read. Needs a CF token with Workers Scripts:Read." });
|
|
10342
10730
|
} else {
|
|
10343
|
-
console.log(
|
|
10731
|
+
console.log(import_chalk51.default.green(`
|
|
10344
10732
|
\u2713 0 of ${wd.total} workers reachable on *.workers.dev`));
|
|
10345
10733
|
checks.push({ name: "no workers.dev doors", status: "PASS", detail: `all ${wd.total} scripts have workers.dev + previews disabled` });
|
|
10346
10734
|
}
|
|
10347
|
-
if (data?.how_to_fix) console.log(
|
|
10348
|
-
${data.reason}`) +
|
|
10735
|
+
if (data?.how_to_fix) console.log(import_chalk51.default.yellow(`
|
|
10736
|
+
${data.reason}`) + import_chalk51.default.dim(`
|
|
10349
10737
|
${data.how_to_fix}`));
|
|
10350
|
-
else for (const e of errs) console.log(
|
|
10738
|
+
else for (const e of errs) console.log(import_chalk51.default.red(`
|
|
10351
10739
|
audit error: ${e}`));
|
|
10352
10740
|
if (errs.length) {
|
|
10353
10741
|
checks.push({ name: "audit completeness", status: "FAIL", detail: `${errs.length} part(s) of the audit could not run \u2014 coverage is INCOMPLETE, and the checks they would have covered are SKIP above` });
|
|
@@ -10357,9 +10745,9 @@ function printDoors(data) {
|
|
|
10357
10745
|
async function runMeter(readLedger, opts) {
|
|
10358
10746
|
const checks = [];
|
|
10359
10747
|
const N = Math.max(1, Math.min(10, opts.count ?? 3));
|
|
10360
|
-
console.log(
|
|
10748
|
+
console.log(import_chalk51.default.bold("\n Meter \u2014 is 1 request charged exactly 1 request?\n"));
|
|
10361
10749
|
const snap = await readLedger();
|
|
10362
|
-
console.log(
|
|
10750
|
+
console.log(import_chalk51.default.dim(` wallet ${snap.billing_account_id} \xB7 band count ${snap.band_count} \xB7 next request ${snap.next_request_cents}\xA2
|
|
10363
10751
|
`));
|
|
10364
10752
|
const url = `https://${dpHost}/${SANDBOX.version}/${SANDBOX.environment}/`;
|
|
10365
10753
|
const headers = opts.key ? { "X-API-Key": opts.key } : {};
|
|
@@ -10441,22 +10829,22 @@ async function runMeter(readLedger, opts) {
|
|
|
10441
10829
|
return checks;
|
|
10442
10830
|
}
|
|
10443
10831
|
function printChecks(checks) {
|
|
10444
|
-
console.log(
|
|
10445
|
-
const mark = { PASS:
|
|
10832
|
+
console.log(import_chalk51.default.bold("\n Results\n"));
|
|
10833
|
+
const mark = { PASS: import_chalk51.default.green(" PASS"), FAIL: import_chalk51.default.red(" FAIL"), SKIP: import_chalk51.default.dim(" SKIP"), KNOWN: import_chalk51.default.yellow(" KNOWN") };
|
|
10446
10834
|
for (const ch of checks) {
|
|
10447
|
-
console.log(` ${mark[ch.status]} ${
|
|
10448
|
-
console.log(
|
|
10835
|
+
console.log(` ${mark[ch.status]} ${import_chalk51.default.bold(ch.name)}`);
|
|
10836
|
+
console.log(import_chalk51.default.dim(` ${ch.detail}`));
|
|
10449
10837
|
}
|
|
10450
10838
|
const fails = checks.filter((c) => c.status === "FAIL").length;
|
|
10451
10839
|
const skips = checks.filter((c) => c.status === "SKIP").length;
|
|
10452
10840
|
const known = checks.filter((c) => c.status === "KNOWN").length;
|
|
10453
10841
|
console.log("");
|
|
10454
10842
|
const passes = checks.filter((c) => c.status === "PASS").length;
|
|
10455
|
-
if (fails) console.log(
|
|
10456
|
-
else if (passes) console.log(
|
|
10457
|
-
else console.log(
|
|
10458
|
-
if (known) console.log(
|
|
10459
|
-
if (skips) console.log(
|
|
10843
|
+
if (fails) console.log(import_chalk51.default.red(` ${fails} check(s) FAILED.`));
|
|
10844
|
+
else if (passes) console.log(import_chalk51.default.green(` ${passes} check(s) passed, 0 failed.`));
|
|
10845
|
+
else console.log(import_chalk51.default.yellow(" NOTHING WAS VERIFIED \u2014 every check was skipped."));
|
|
10846
|
+
if (known) console.log(import_chalk51.default.yellow(` ${known} known-open issue(s) still outstanding.`));
|
|
10847
|
+
if (skips) console.log(import_chalk51.default.dim(` ${skips} check(s) NOT RUN (see SKIP above) \u2014 those invariants are unverified.`));
|
|
10460
10848
|
console.log("");
|
|
10461
10849
|
}
|
|
10462
10850
|
|
|
@@ -10503,93 +10891,93 @@ var OP_COMMANDS = [
|
|
|
10503
10891
|
function renderOpCommands() {
|
|
10504
10892
|
const width = Math.max(...OP_COMMANDS.map((c) => c.cmd.length)) + 10;
|
|
10505
10893
|
const lines = OP_COMMANDS.map((c) => {
|
|
10506
|
-
const left = ` ${
|
|
10894
|
+
const left = ` ${import_chalk52.default.cyan(`apiblaze ${c.cmd}`)}`;
|
|
10507
10895
|
const pad = " ".repeat(Math.max(1, width - c.cmd.length));
|
|
10508
|
-
return `${left}${pad}${c.blurb}${c.extra ? " " +
|
|
10896
|
+
return `${left}${pad}${c.blurb}${c.extra ? " " + import_chalk52.default.dim(`(${c.extra})`) : ""}`;
|
|
10509
10897
|
});
|
|
10510
10898
|
return [
|
|
10511
|
-
|
|
10899
|
+
import_chalk52.default.bold("Operator commands"),
|
|
10512
10900
|
...lines,
|
|
10513
10901
|
"",
|
|
10514
|
-
|
|
10515
|
-
|
|
10516
|
-
|
|
10902
|
+
import_chalk52.default.dim(" Operators only. The gate is server-side (dashboard /api/cli/op checks the"),
|
|
10903
|
+
import_chalk52.default.dim(" signed-in email, admin-api re-checks with operatorGate) \u2014 a patched CLI just"),
|
|
10904
|
+
import_chalk52.default.dim(" gets 403s. Every op call is read-only except `op sweep`."),
|
|
10517
10905
|
"",
|
|
10518
|
-
|
|
10519
|
-
|
|
10520
|
-
|
|
10906
|
+
import_chalk52.default.dim(" Not a CLI command: to prune all non-CP data run scripts/nuke-but-cp.sh --apply --sweep"),
|
|
10907
|
+
import_chalk52.default.dim(" in the repo. Operator dashboards (dlq, thresholds, throttling, pricing, billing,"),
|
|
10908
|
+
import_chalk52.default.dim(" agent-spend, teams, tests, leak-detection, lifecycle) live at /operator/* in the app.")
|
|
10521
10909
|
].join("\n");
|
|
10522
10910
|
}
|
|
10523
10911
|
function printResidue(report, applied) {
|
|
10524
10912
|
const up = report?.upstash ?? {};
|
|
10525
10913
|
const fga = report?.fga ?? {};
|
|
10526
10914
|
const ghosts = report?.ghosts ?? {};
|
|
10527
|
-
console.log(
|
|
10528
|
-
console.log(
|
|
10915
|
+
console.log(import_chalk52.default.bold(applied ? "\nExternal-residue sweep" : "\nExternal residue (dry-run \u2014 nothing deleted)"));
|
|
10916
|
+
console.log(import_chalk52.default.bold("\n Upstash"));
|
|
10529
10917
|
const orphans = up.orphans ?? [];
|
|
10530
|
-
if (orphans.length === 0) console.log(
|
|
10531
|
-
for (const o of orphans) console.log(` ${
|
|
10532
|
-
console.log(
|
|
10918
|
+
if (orphans.length === 0) console.log(import_chalk52.default.green(" no orphaned keys"));
|
|
10919
|
+
for (const o of orphans) console.log(` ${import_chalk52.default.yellow(o.key)} ${import_chalk52.default.dim(`\u2014 ${o.reason}`)}`);
|
|
10920
|
+
console.log(import_chalk52.default.dim(` kept (live principals): ${up.kept ?? 0} \xB7 anon wallets (untouched): ${up.anon_wallets ?? 0}`));
|
|
10533
10921
|
if (up.anon_wallet_detail) {
|
|
10534
10922
|
const d = up.anon_wallet_detail;
|
|
10535
|
-
console.log(
|
|
10923
|
+
console.log(import_chalk52.default.dim(` anon wallets: ${d.count} ($${(d.total_cents / 100).toFixed(2)}), ${d.no_ttl} with NO TTL${d.no_ttl ? " \u26A0" : " (all self-expire)"}`));
|
|
10536
10924
|
}
|
|
10537
10925
|
if (up.keyspace_census) {
|
|
10538
10926
|
const census = Object.entries(up.keyspace_census).map(([k, v]) => `${k}=${v}`).join(" \xB7 ");
|
|
10539
|
-
console.log(
|
|
10927
|
+
console.log(import_chalk52.default.dim(` keyspace: ${census}`));
|
|
10540
10928
|
}
|
|
10541
|
-
if (up.unknown?.length) console.log(
|
|
10542
|
-
if (applied) console.log(` ${
|
|
10543
|
-
for (const e of up.errors ?? []) console.log(
|
|
10544
|
-
console.log(
|
|
10929
|
+
if (up.unknown?.length) console.log(import_chalk52.default.dim(` unknown (never deleted): ${up.unknown.join(", ")}`));
|
|
10930
|
+
if (applied) console.log(` ${import_chalk52.default.bold(String(up.deleted ?? 0))} key(s) deleted`);
|
|
10931
|
+
for (const e of up.errors ?? []) console.log(import_chalk52.default.red(` error: ${e}`));
|
|
10932
|
+
console.log(import_chalk52.default.bold("\n OpenFGA / Neon \u2014 orphan stores"));
|
|
10545
10933
|
if (applied) {
|
|
10546
10934
|
const swept = fga?.swept ?? [];
|
|
10547
|
-
if (swept.length === 0) console.log(
|
|
10935
|
+
if (swept.length === 0) console.log(import_chalk52.default.green(" no orphaned stores"));
|
|
10548
10936
|
for (const s of swept) {
|
|
10549
10937
|
console.log(
|
|
10550
|
-
` ${
|
|
10938
|
+
` ${import_chalk52.default.yellow(s.store_id)} ${import_chalk52.default.dim(`\u2014 store ${s.openfga_deleted ? "deleted" : "DEFERRED"}, ${s.neon_deleted} Neon tuple(s) purged`)}`
|
|
10551
10939
|
);
|
|
10552
10940
|
}
|
|
10553
|
-
if (fga?.remaining) console.log(
|
|
10941
|
+
if (fga?.remaining) console.log(import_chalk52.default.yellow(` ${fga.remaining} more orphan store(s) \u2014 re-run to drain`));
|
|
10554
10942
|
const st = fga?.side_tables;
|
|
10555
|
-
if (st) console.log(
|
|
10943
|
+
if (st) console.log(import_chalk52.default.dim(` Neon side-tables purged: ${st.soft_deleted_stores} store records, ${st.orphan_models} models, ${st.orphan_changelog} changelog rows${st.error ? ` (${st.error})` : ""}`));
|
|
10556
10944
|
} else {
|
|
10557
10945
|
const fgaOrphans = fga?.orphans ?? [];
|
|
10558
|
-
if (fgaOrphans.length === 0) console.log(
|
|
10946
|
+
if (fgaOrphans.length === 0) console.log(import_chalk52.default.green(" no orphaned stores"));
|
|
10559
10947
|
for (const s of fgaOrphans) {
|
|
10560
10948
|
const src = s.in_openfga ? "live in OpenFGA" : "Neon tuples only";
|
|
10561
|
-
console.log(` ${
|
|
10949
|
+
console.log(` ${import_chalk52.default.yellow(s.store_id)} ${import_chalk52.default.dim(`\u2014 ${src}${s.name ? ` (${s.name})` : ""}, ${s.neon_tuples} Neon tuple(s)`)}`);
|
|
10562
10950
|
}
|
|
10563
|
-
console.log(
|
|
10951
|
+
console.log(import_chalk52.default.dim(` kept stores: ${(fga?.kept_store_ids ?? []).length}`));
|
|
10564
10952
|
const st = fga?.side_tables;
|
|
10565
|
-
if (st) console.log(
|
|
10953
|
+
if (st) console.log(import_chalk52.default.dim(` Neon side-table residue: ${st.soft_deleted_stores} soft-deleted store records, ${st.orphan_models} orphan models, ${st.orphan_changelog} orphan changelog rows`));
|
|
10566
10954
|
}
|
|
10567
|
-
for (const e of fga?.errors ?? []) console.log(
|
|
10568
|
-
console.log(
|
|
10955
|
+
for (const e of fga?.errors ?? []) console.log(import_chalk52.default.red(` error: ${e}`));
|
|
10956
|
+
console.log(import_chalk52.default.bold("\n OpenFGA \u2014 ghost tuples in surviving stores"));
|
|
10569
10957
|
if (applied) {
|
|
10570
|
-
if ((ghosts?.ghost_count ?? 0) === 0) console.log(
|
|
10571
|
-
else console.log(` ${
|
|
10958
|
+
if ((ghosts?.ghost_count ?? 0) === 0) console.log(import_chalk52.default.green(" no ghost tuples"));
|
|
10959
|
+
else console.log(` ${import_chalk52.default.bold(String(ghosts.deleted ?? 0))} ghost tuple(s) deleted ${import_chalk52.default.dim(`(of ${ghosts.ghost_count} found, ${ghosts.scanned_tuples} scanned across ${ghosts.live_stores} live stores)`)}`);
|
|
10572
10960
|
} else {
|
|
10573
10961
|
const n = ghosts?.ghost_count ?? 0;
|
|
10574
|
-
if (n === 0) console.log(
|
|
10962
|
+
if (n === 0) console.log(import_chalk52.default.green(` no ghost tuples ${import_chalk52.default.dim(`(${ghosts.scanned_tuples ?? 0} scanned across ${ghosts.live_stores ?? 0} live stores)`)}`));
|
|
10575
10963
|
else {
|
|
10576
|
-
console.log(
|
|
10964
|
+
console.log(import_chalk52.default.yellow(` ${n} ghost tuple(s) referencing entities absent from D1:`));
|
|
10577
10965
|
for (const g of (ghosts.ghosts ?? []).slice(0, 20)) {
|
|
10578
|
-
console.log(
|
|
10966
|
+
console.log(import_chalk52.default.dim(` ${g.object_type}:${g.object_id} ${g.relation} ${g._user}`));
|
|
10579
10967
|
}
|
|
10580
|
-
if (n > 20) console.log(
|
|
10968
|
+
if (n > 20) console.log(import_chalk52.default.dim(` \u2026 and ${n - 20} more`));
|
|
10581
10969
|
}
|
|
10582
10970
|
}
|
|
10583
|
-
for (const e of ghosts?.errors ?? []) console.log(
|
|
10971
|
+
for (const e of ghosts?.errors ?? []) console.log(import_chalk52.default.red(` error: ${e}`));
|
|
10584
10972
|
console.log();
|
|
10585
10973
|
}
|
|
10586
10974
|
async function runOp(sub, opts = {}, view) {
|
|
10587
10975
|
if (!loadCredentials()) {
|
|
10588
|
-
console.log(
|
|
10976
|
+
console.log(import_chalk52.default.dim("Not logged in. Run `apiblaze login`."));
|
|
10589
10977
|
return;
|
|
10590
10978
|
}
|
|
10591
10979
|
if (!isOperatorLogin()) {
|
|
10592
|
-
console.log(
|
|
10980
|
+
console.log(import_chalk52.default.dim("`apiblaze op` is only available to platform operators."));
|
|
10593
10981
|
return;
|
|
10594
10982
|
}
|
|
10595
10983
|
switch (sub) {
|
|
@@ -10615,17 +11003,17 @@ async function runOp(sub, opts = {}, view) {
|
|
|
10615
11003
|
const nSide = (st.soft_deleted_stores ?? 0) + (st.orphan_models ?? 0) + (st.orphan_changelog ?? 0);
|
|
10616
11004
|
printResidue(report, false);
|
|
10617
11005
|
if (nUp + nFga + nGhost + nSide === 0) {
|
|
10618
|
-
console.log(
|
|
11006
|
+
console.log(import_chalk52.default.green("Nothing to sweep."));
|
|
10619
11007
|
return;
|
|
10620
11008
|
}
|
|
10621
11009
|
if (!opts.yes) {
|
|
10622
11010
|
const readline3 = await import("readline/promises");
|
|
10623
11011
|
const rl = readline3.createInterface({ input: process.stdin, output: process.stdout });
|
|
10624
11012
|
const answer = await rl.question(
|
|
10625
|
-
|
|
11013
|
+
import_chalk52.default.red(`Delete ${nUp} Upstash key(s) + ${nFga} OpenFGA store(s) + ${nGhost} ghost tuple(s) + ${nSide} Neon side-table row(s)? Type 'sweep' to confirm: `)
|
|
10626
11014
|
);
|
|
10627
11015
|
rl.close();
|
|
10628
|
-
if (answer.trim() !== "sweep") return void console.log(
|
|
11016
|
+
if (answer.trim() !== "sweep") return void console.log(import_chalk52.default.dim("Aborted."));
|
|
10629
11017
|
}
|
|
10630
11018
|
const result = await opCall({ method: "POST", path: "/operator/external-residue/sweep", summary: "external residue sweep" });
|
|
10631
11019
|
if (opts.json) return void console.log(JSON.stringify(result, null, 2));
|
|
@@ -10635,25 +11023,25 @@ async function runOp(sub, opts = {}, view) {
|
|
|
10635
11023
|
case "mark": {
|
|
10636
11024
|
const label3 = (view ?? "").trim();
|
|
10637
11025
|
if (!label3) {
|
|
10638
|
-
console.log(
|
|
11026
|
+
console.log(import_chalk52.default.red("Give the change a name:") + import_chalk52.default.cyan(' apiblaze op mark "cached tenant count"'));
|
|
10639
11027
|
return;
|
|
10640
11028
|
}
|
|
10641
11029
|
const res = await opCall({ method: "POST", path: "/operator/latency/mark", body: { label: label3 }, summary: "record change marker" });
|
|
10642
11030
|
const ts = new Date(res?.marker?.ts ?? Date.now()).toISOString();
|
|
10643
|
-
console.log(
|
|
10644
|
-
Marked: `) +
|
|
10645
|
-
console.log(
|
|
10646
|
-
console.log(
|
|
11031
|
+
console.log(import_chalk52.default.green(`
|
|
11032
|
+
Marked: `) + import_chalk52.default.bold(label3));
|
|
11033
|
+
console.log(import_chalk52.default.dim(` ${ts}`));
|
|
11034
|
+
console.log(import_chalk52.default.dim(` Once traffic has run on both sides, compare with: `) + import_chalk52.default.cyan("apiblaze op latency compare") + "\n");
|
|
10647
11035
|
return;
|
|
10648
11036
|
}
|
|
10649
11037
|
case "credits": {
|
|
10650
11038
|
const data = await opCall({ method: "GET", path: "/operator/credits", summary: "list credit wallets" });
|
|
10651
11039
|
if (opts.json) return void console.log(JSON.stringify(data, null, 2));
|
|
10652
11040
|
const accounts = data?.accounts ?? [];
|
|
10653
|
-
if (accounts.length === 0) return void console.log(
|
|
11041
|
+
if (accounts.length === 0) return void console.log(import_chalk52.default.dim("No credit wallets."));
|
|
10654
11042
|
for (const a of accounts) {
|
|
10655
11043
|
const bal = typeof a.balance_cents === "number" ? `$${(a.balance_cents / 100).toFixed(2)}` : "?";
|
|
10656
|
-
console.log(` ${
|
|
11044
|
+
console.log(` ${import_chalk52.default.bold(bal.padStart(9))} ${a.walletId}${a.owner_email ? import_chalk52.default.dim(` \u2014 ${a.owner_email}`) : a.anon ? import_chalk52.default.dim(" \u2014 anon") : ""}`);
|
|
10657
11045
|
}
|
|
10658
11046
|
return;
|
|
10659
11047
|
}
|
|
@@ -10664,7 +11052,7 @@ async function runOp(sub, opts = {}, view) {
|
|
|
10664
11052
|
case "billing": {
|
|
10665
11053
|
const which = (view ?? "").trim().toLowerCase();
|
|
10666
11054
|
if (which && which !== "doors" && which !== "meter") {
|
|
10667
|
-
return void console.log(
|
|
11055
|
+
return void console.log(import_chalk52.default.red(`Unknown: apiblaze op billing ${which}. Use 'doors', 'meter', or neither for both.`));
|
|
10668
11056
|
}
|
|
10669
11057
|
const checks = [];
|
|
10670
11058
|
let doorsData = null;
|
|
@@ -10725,29 +11113,29 @@ async function runOp(sub, opts = {}, view) {
|
|
|
10725
11113
|
const data = await opCall({ method: "GET", path: `/operator/latency/grades${q}`, summary: "latency grades" });
|
|
10726
11114
|
if (opts.json) return void console.log(JSON.stringify(data, null, 2));
|
|
10727
11115
|
const t = data.thresholds_ms;
|
|
10728
|
-
console.log(
|
|
10729
|
-
console.log(
|
|
11116
|
+
console.log(import_chalk52.default.bold("\nHow good was apiblaze itself?") + import_chalk52.default.dim(" (our overhead only \u2014 a slow customer API never counts against us)"));
|
|
11117
|
+
console.log(import_chalk52.default.dim(` excellent <${t.excellent.replace("<", "")}ms \xB7 okay ${t.okay}ms \xB7 bad ${t.bad}ms \xB7 terrible ${t.terrible.replace(">=", "")}ms+
|
|
10730
11118
|
`));
|
|
10731
|
-
console.log(
|
|
11119
|
+
console.log(import_chalk52.default.dim(" date reqs excellent okay bad terrible"));
|
|
10732
11120
|
for (const d of data.days ?? []) {
|
|
10733
11121
|
const p = d.pct;
|
|
10734
|
-
const cell = (v, colour) => v > 0 ? colour(`${String(v).padStart(5)}%`) :
|
|
11122
|
+
const cell = (v, colour) => v > 0 ? colour(`${String(v).padStart(5)}%`) : import_chalk52.default.dim(`${String(v).padStart(5)}%`);
|
|
10735
11123
|
console.log(
|
|
10736
|
-
` ${d.date} ${String(d.total).padStart(5)} ${cell(p.excellent,
|
|
11124
|
+
` ${d.date} ${String(d.total).padStart(5)} ${cell(p.excellent, import_chalk52.default.green)} ${cell(p.okay, import_chalk52.default.cyan)} ${cell(p.bad, import_chalk52.default.yellow)} ${cell(p.terrible, import_chalk52.default.red)}`
|
|
10737
11125
|
);
|
|
10738
11126
|
}
|
|
10739
11127
|
const cul = data.culprits ?? [];
|
|
10740
11128
|
if (cul.length) {
|
|
10741
|
-
console.log(
|
|
10742
|
-
console.log(
|
|
11129
|
+
console.log(import_chalk52.default.bold("\n Who caused the bad and terrible ones\n"));
|
|
11130
|
+
console.log(import_chalk52.default.dim(" bad terrible feature \u2192 dependency"));
|
|
10743
11131
|
for (const r of cul.slice(0, 12)) {
|
|
10744
11132
|
if (!r.bad && !r.terrible) continue;
|
|
10745
11133
|
console.log(
|
|
10746
|
-
` ${String(r.bad).padStart(6)} ${
|
|
11134
|
+
` ${String(r.bad).padStart(6)} ${import_chalk52.default.red(String(r.terrible).padStart(8))} ${import_chalk52.default.yellow(r.feature)} ${import_chalk52.default.dim("\u2192")} ${import_chalk52.default.cyan(r.dep)}`
|
|
10747
11135
|
);
|
|
10748
11136
|
}
|
|
10749
11137
|
}
|
|
10750
|
-
if (data.caveat) console.log(
|
|
11138
|
+
if (data.caveat) console.log(import_chalk52.default.dim(`
|
|
10751
11139
|
\u26A0 ${data.caveat}
|
|
10752
11140
|
`));
|
|
10753
11141
|
return;
|
|
@@ -10756,24 +11144,24 @@ async function runOp(sub, opts = {}, view) {
|
|
|
10756
11144
|
const data = await opCall({ method: "GET", path: `/operator/latency/compare${q}`, summary: "latency before/after" });
|
|
10757
11145
|
if (opts.json) return void console.log(JSON.stringify(data, null, 2));
|
|
10758
11146
|
const b = data.before, a = data.after, d = data.delta;
|
|
10759
|
-
console.log(
|
|
10760
|
-
Before vs after: `) +
|
|
10761
|
-
console.log(
|
|
11147
|
+
console.log(import_chalk52.default.bold(`
|
|
11148
|
+
Before vs after: `) + import_chalk52.default.cyan(data.marker.label));
|
|
11149
|
+
console.log(import_chalk52.default.dim(` marked ${new Date(data.marker.ts).toISOString()} \xB7 ${data.window_hours}h either side
|
|
10762
11150
|
`));
|
|
10763
11151
|
const row = (name, before, after, delta) => {
|
|
10764
11152
|
const arrow = delta === 0 ? "=" : delta < 0 ? "\u2193" : "\u2191";
|
|
10765
11153
|
const txt = `${String(before).padStart(6)}ms \u2192${String(after).padStart(7)}ms ${arrow}${Math.abs(delta)}ms`;
|
|
10766
|
-
console.log(` ${name.padEnd(22)}${data.trustworthy ? delta <= 0 ?
|
|
11154
|
+
console.log(` ${name.padEnd(22)}${data.trustworthy ? delta <= 0 ? import_chalk52.default.green(txt) : import_chalk52.default.red(txt) : import_chalk52.default.dim(txt)}`);
|
|
10767
11155
|
};
|
|
10768
|
-
console.log(
|
|
11156
|
+
console.log(import_chalk52.default.dim(" metric before after change"));
|
|
10769
11157
|
row("total p50", b.total_p50, a.total_p50, d.total_p50);
|
|
10770
11158
|
row("total p95", b.total_p95, a.total_p95, d.total_p95);
|
|
10771
11159
|
row("apiblaze overhead p50", b.gw_p50, a.gw_p50, d.gw_p50);
|
|
10772
11160
|
row("apiblaze overhead p95", b.gw_p95, a.gw_p95, d.gw_p95);
|
|
10773
|
-
console.log(
|
|
11161
|
+
console.log(import_chalk52.default.dim(`
|
|
10774
11162
|
requests: ${b.requests} before \xB7 ${a.requests} after`));
|
|
10775
11163
|
for (const w of data.warnings ?? []) {
|
|
10776
|
-
console.log((data.trustworthy ?
|
|
11164
|
+
console.log((data.trustworthy ? import_chalk52.default.dim : import_chalk52.default.yellow)(` ${data.trustworthy ? "\xB7" : "\u26A0"} ${w}`));
|
|
10777
11165
|
}
|
|
10778
11166
|
console.log("");
|
|
10779
11167
|
return;
|
|
@@ -10782,19 +11170,19 @@ Before vs after: `) + import_chalk51.default.cyan(data.marker.label));
|
|
|
10782
11170
|
const data = await opCall({ method: "GET", path: `/operator/latency/slow${q}`, summary: "slowest requests" });
|
|
10783
11171
|
if (opts.json) return void console.log(JSON.stringify(data, null, 2));
|
|
10784
11172
|
const rows2 = data?.rows ?? [];
|
|
10785
|
-
if (!rows2.length) return void console.log(
|
|
10786
|
-
console.log(
|
|
11173
|
+
if (!rows2.length) return void console.log(import_chalk52.default.dim("No requests over the threshold in that window."));
|
|
11174
|
+
console.log(import_chalk52.default.bold(`
|
|
10787
11175
|
Slowest requests \u2014 last ${data.window_hours}h, over ${data.min_ms}ms
|
|
10788
11176
|
`));
|
|
10789
|
-
console.log(
|
|
11177
|
+
console.log(import_chalk52.default.dim(" total ours theirs blame request id"));
|
|
10790
11178
|
for (const r of rows2.slice(0, 30)) {
|
|
10791
11179
|
console.log(
|
|
10792
|
-
` ${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)} ${
|
|
11180
|
+
` ${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_chalk52.default.yellow(`${r.slow_gw_feature || "-"}\u2192${r.slow_dep || "-"}`.padEnd(20))} ${import_chalk52.default.dim(r.request_id || "")}`
|
|
10793
11181
|
);
|
|
10794
11182
|
}
|
|
10795
|
-
console.log(
|
|
11183
|
+
console.log(import_chalk52.default.dim(`
|
|
10796
11184
|
The last column is the request id (Cloudflare calls it a "cf-ray"). Look one up with`));
|
|
10797
|
-
console.log(
|
|
11185
|
+
console.log(import_chalk52.default.dim(` \`apiblaze logs\` for that request's exact per-feature breakdown \u2014 unsampled, unlike the table above.
|
|
10798
11186
|
`));
|
|
10799
11187
|
return;
|
|
10800
11188
|
}
|
|
@@ -10802,18 +11190,18 @@ Slowest requests \u2014 last ${data.window_hours}h, over ${data.min_ms}ms
|
|
|
10802
11190
|
const data = await opCall({ method: "GET", path: `/operator/latency/llm${q}`, summary: "llm latency" });
|
|
10803
11191
|
if (opts.json) return void console.log(JSON.stringify(data, null, 2));
|
|
10804
11192
|
const rows2 = data?.rows ?? [];
|
|
10805
|
-
if (!rows2.length) return void console.log(
|
|
10806
|
-
console.log(
|
|
11193
|
+
if (!rows2.length) return void console.log(import_chalk52.default.dim("No LLM traffic in that window."));
|
|
11194
|
+
console.log(import_chalk52.default.bold(`
|
|
10807
11195
|
LLM timing \u2014 last ${data.window_hours}h
|
|
10808
11196
|
`));
|
|
10809
|
-
console.log(
|
|
11197
|
+
console.log(import_chalk52.default.dim(" turns turn p95 ttfc p95 gen p95 reserve p95 in/out tokens p95 model"));
|
|
10810
11198
|
for (const r of rows2) {
|
|
10811
11199
|
const n = r.turns ?? r.requests ?? 0;
|
|
10812
11200
|
console.log(
|
|
10813
11201
|
` ${String(Math.round(n)).padStart(8)} ${String(Math.round(r.turn_p95_ms ?? 0)).padStart(6)}ms ${String(Math.round(r.ttfc_p95_ms ?? 0)).padStart(6)}ms ${String(Math.round(r.gen_p95_ms ?? 0)).padStart(6)}ms ${String(Math.round(r.reserve_p95_ms ?? 0)).padStart(9)}ms ${String(Math.round(r.input_tokens_p95 ?? 0)).padStart(6)}/${String(Math.round(r.output_tokens_p95 ?? 0)).padEnd(6)} ${r.model || "-"}`
|
|
10814
11202
|
);
|
|
10815
11203
|
}
|
|
10816
|
-
console.log(
|
|
11204
|
+
console.log(import_chalk52.default.dim(`
|
|
10817
11205
|
${data.note}
|
|
10818
11206
|
`));
|
|
10819
11207
|
return;
|
|
@@ -10824,26 +11212,26 @@ LLM timing \u2014 last ${data.window_hours}h
|
|
|
10824
11212
|
]);
|
|
10825
11213
|
if (opts.json) return void console.log(JSON.stringify({ blame, summary }, null, 2));
|
|
10826
11214
|
const ov = summary?.apiblaze_overhead_ms ?? {};
|
|
10827
|
-
console.log(
|
|
11215
|
+
console.log(import_chalk52.default.bold(`
|
|
10828
11216
|
Latency \u2014 last ${summary?.window_hours ?? "?"}h, ${Number(summary?.requests ?? 0).toLocaleString()} requests
|
|
10829
11217
|
`));
|
|
10830
|
-
console.log(` ${
|
|
10831
|
-
console.log(` ${
|
|
10832
|
-
console.log(
|
|
11218
|
+
console.log(` ${import_chalk52.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_chalk52.default.dim("\u2190 ours")}`);
|
|
11219
|
+
console.log(` ${import_chalk52.default.bold("upstream ttfb ")} ${" ".repeat(24)}p95 ${String(summary?.upstream_ttfb_p95_ms ?? 0).padStart(6)}ms ${import_chalk52.default.dim("\u2190 theirs")}`);
|
|
11220
|
+
console.log(import_chalk52.default.dim(` (per-request percentiles \u2014 never subtract one from the other)
|
|
10833
11221
|
`));
|
|
10834
11222
|
const rows = blame?.blame ?? [];
|
|
10835
|
-
if (!rows.length) return void console.log(
|
|
10836
|
-
console.log(
|
|
10837
|
-
console.log(
|
|
11223
|
+
if (!rows.length) return void console.log(import_chalk52.default.dim("No latency rows in that window."));
|
|
11224
|
+
console.log(import_chalk52.default.bold(" Which feature ate the time, and what inside it\n"));
|
|
11225
|
+
console.log(import_chalk52.default.dim(" share p95 feature \u2192 dependency"));
|
|
10838
11226
|
for (const r of rows.slice(0, 15)) {
|
|
10839
11227
|
const share = `${(r.share * 100).toFixed(1)}%`;
|
|
10840
|
-
console.log(` ${share.padStart(6)} ${String(r.p95_ms).padStart(6)}ms ${
|
|
11228
|
+
console.log(` ${share.padStart(6)} ${String(r.p95_ms).padStart(6)}ms ${import_chalk52.default.yellow(r.feature)} ${import_chalk52.default.dim("\u2192")} ${import_chalk52.default.cyan(r.dep)}`);
|
|
10841
11229
|
}
|
|
10842
11230
|
console.log("");
|
|
10843
11231
|
return;
|
|
10844
11232
|
}
|
|
10845
11233
|
default:
|
|
10846
|
-
console.log(
|
|
11234
|
+
console.log(import_chalk52.default.red(`Unknown op subcommand '${sub}'. Run \`apiblaze op\` for the menu.`));
|
|
10847
11235
|
}
|
|
10848
11236
|
}
|
|
10849
11237
|
|
|
@@ -10876,7 +11264,7 @@ program.command("login").description("Authenticate with APIblaze").option("--tea
|
|
|
10876
11264
|
process.exit(1);
|
|
10877
11265
|
}
|
|
10878
11266
|
});
|
|
10879
|
-
program.command("create").description("Create a new API proxy (no login needed \u2014 without auth it creates an anonymous proxy and prints a claim URL)").option("--name <name>", "Proxy name (becomes <name>.abz.run)").option("--target <url>", "
|
|
11267
|
+
program.command("create").description("Create a new API proxy (no login needed \u2014 without auth it creates an anonymous proxy and prints a claim URL)").option("--name <name>", "Proxy name (becomes <name>.abz.run)").option("--target <url|file>", "What to proxy \u2014 pass ANY of: a target server base URL (https://httpbin.org), a local OpenAPI file (./openapi.yaml), or a remote OpenAPI URL (https://acme.com/openapi.yaml). Spec inputs are detected automatically; routes, API version and environments then come from the spec").addOption(new import_commander.Option("--openapi <file|url>", "Deprecated alias \u2014 --target now detects spec files/URLs itself").hideHelp()).addOption(new import_commander.Option("--openapispec <file|url>", "Deprecated alias for --openapi").hideHelp()).option("--team <id|name>", "Team to create under (defaults to your active team)").option("--auth <type>", "Auth type: api_key | none | oauth", "api_key").option("--apikey", "Protect with API keys and print the bootstrap keys (the default, made explicit). Consumers send X-API-Key.").option("--oauth [config]", `Login door. Bare = APIblaze-hosted GitHub sign-in. '{"iss","aud","jwks"}' = trust YOUR hosted login's JWTs. '{"provider","clientId","clientSecret"}' = APIblaze-hosted login page with YOUR OAuth app (github \xB7 google \xB7 microsoft \xB7 facebook \xB7 auth0).`).option("--identified", "Require every call to identify its end user (X-End-User-Id or a login token); unattributed calls are rejected").option("--iam", "Turn IAM on for the proxy's tenant so users & groups apply to identified calls").option("--apiversion <version>", "API version to create (e.g. 2.0.0). Creating a new version of a proxy you own adds a version to the existing project.").option("--tenant <slug>", "Tenant to attach the proxy to (created if new). Omitted \u2192 your team's most-recently-used tenant.").option("--product <slug>", "Product tag to group this project under in the portal (team-scoped; anonymous create). Defaults to your team's existing/placeholder tag.").option("--display-name <name>", "Human-friendly display name").option("--subdomain <slug>", "Explicit subdomain (defaults to --name)").option("--config <file>", "JSON file with the full request body (anonymous create): requests_auth, login providers + client/server token types, scopes, callback URLs, etc. See apiblaze_anonymous.yaml. Flags override its fields.").option("-y, --yes", "Skip the confirmation prompt").option("--new-session", "Start a fresh anonymous session (do not group with prior anonymous creates)").option("--json", "Output machine-readable JSON (non-interactive)").action(async (opts) => {
|
|
10880
11268
|
try {
|
|
10881
11269
|
await runCreate({ ...opts, openapi: opts.openapi ?? opts.openapispec });
|
|
10882
11270
|
} catch (err) {
|
|
@@ -10889,7 +11277,7 @@ agent.command("authz").description("Chat to design and turn on access rules for
|
|
|
10889
11277
|
program.command("rule").description("Author an object-level access rule in plain English, in one shot (billed per turn)").argument("<rule>", 'The rule in plain English, e.g. "users see only their own rows"').argument("<project>", "Project name or id").option("--enforce", "Turn enforcement on immediately (default: shadow-publish only)").option("--apiversion <version>", "API version (defaults to the project's)").action(action((rule, project, opts) => runRule(rule, project, opts)));
|
|
10890
11278
|
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)));
|
|
10891
11279
|
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)));
|
|
10892
|
-
program.command("apichat [project]").description("Turn any API into a chat: point at an OpenAPI spec \u2014 or chat an EXISTING proxy by name (no login needed)").option("--
|
|
11280
|
+
program.command("apichat [project]").description("Turn any API into a chat: point at an OpenAPI spec \u2014 or chat an EXISTING proxy by name (no login needed)").option("--target <url|file>", "What to chat with \u2014 pass ANY of: a target server base URL (spec auto-discovered at /openapi.json etc.), a local OpenAPI file (./openapi.yaml), or a remote OpenAPI URL (https://acme.com/openapi.yaml)").addOption(new import_commander.Option("--openapi <file|url>", "Deprecated alias \u2014 --target now detects spec files/URLs itself").hideHelp()).addOption(new import_commander.Option("--openapispec <file|url>", "Deprecated alias for --openapi").hideHelp()).option("--name <name>", "Proxy name (defaults to the target host)").option("--apiversion <version>", "API version to create (e.g. 1.0.0)").option("--environment <env>", "Environment to chat against (default: prod anonymous / dev logged-in)").option("--access <mode>", 'Who can call this API once connected (e.g. via Claude): "open" = anyone who signs in, "invite" = only you + emails you pre-approve. Default: invite when logged in, open when anonymous.').option("--target-auth-env <ENV_VAR>", "Read the upstream credential from this env var (CI-safe; required when there is no TTY and the API needs auth)").option("--force", "Proceed even if the API uses oauth2/openIdConnect target auth (you configure target auth yourself later)").option("-y, --yes", "Skip confirmation prompts").option("--tenant <slug>", "Tenant (consumer namespace: portal, login, users) for the new proxy; omit to be asked").option("--apikey <key>", "Use this API key for the proxy's door (api_key proxies). Without it, apichat detects the door and asks \u2014 or runs the consumer login for OAuth doors.").option("--xenduserid <id>", "Assert this end-user id (X-End-User-Id) \u2014 required by proxies with identified/pre-approved enforcement; you are asked for one when the proxy demands it.").option("--verbose", "Show the per-turn proxy curl trace (hidden by default)").option("-p, --prompt <question>", "One-shot question: answered through the external agent CLI after an MCP install, or by apichat itself (exits after answering when there is no TTY)").option("--install-mcp <cli>", "Install this proxy's MCP into an external agent CLI without asking: claude | codex. Also re-offers after an earlier decline.").action(action((project, opts) => runApichat({ ...opts, project, openapispec: opts.openapispec ?? opts.openapi })));
|
|
10893
11281
|
var llm = program.command("llm").description("Manage a local LLM provider key for chat (optional \u2014 lifts model quality, bills your key)");
|
|
10894
11282
|
llm.command("set-key").description("Store an LLM provider key locally (OpenRouter/Anthropic/DeepSeek/OpenAI)").argument("[key]", "The API key (omit to enter it hidden at a prompt)").option("--model <id>", "Model id to use with this key (e.g. anthropic/claude-haiku-4.5)").action(action((key, opts) => runLlmSetKey(key, opts)));
|
|
10895
11283
|
llm.command("show").description("Show the locally stored LLM key (masked)").action(action(() => runLlmShow()));
|
|
@@ -10906,7 +11294,7 @@ program.command("dev").description("Put your localhost behind a public URL (dev
|
|
|
10906
11294
|
try {
|
|
10907
11295
|
const resolved = parseInt(port ?? opts.port, 10);
|
|
10908
11296
|
if (Number.isNaN(resolved)) {
|
|
10909
|
-
console.error(
|
|
11297
|
+
console.error(import_chalk53.default.red(`Invalid port: ${port ?? opts.port}`));
|
|
10910
11298
|
process.exit(1);
|
|
10911
11299
|
}
|
|
10912
11300
|
await runDev({ port: resolved, project: opts.project, yes: opts.yes, captureFile: opts.captureFile, newSession: opts.newSession });
|
|
@@ -11038,7 +11426,7 @@ function groupedCommandHelp() {
|
|
|
11038
11426
|
const sub = byName.get(e.parent)?.commands.find((s) => s.name() === e.sub);
|
|
11039
11427
|
return sub ? ` ${helpLabel(e).padEnd(width)}${sub.description()}` : "";
|
|
11040
11428
|
}).filter(Boolean).join("\n");
|
|
11041
|
-
return `${
|
|
11429
|
+
return `${import_chalk53.default.bold(g.title)}
|
|
11042
11430
|
${rows}`;
|
|
11043
11431
|
}).join("\n\n");
|
|
11044
11432
|
}
|
|
@@ -11076,14 +11464,14 @@ async function recoverStaleTeam() {
|
|
|
11076
11464
|
const { resolveLinkedTeam: resolveLinkedTeam2 } = await Promise.resolve().then(() => (init_team(), team_exports));
|
|
11077
11465
|
const linked = await resolveLinkedTeam2({ preferredId: creds.teamId, interactive: !!process.stdin.isTTY });
|
|
11078
11466
|
if (!linked) {
|
|
11079
|
-
console.error(
|
|
11467
|
+
console.error(import_chalk53.default.yellow("Your account has no teams anymore (deleted?). Run `apiblaze login` or `apiblaze create` to get a workspace."));
|
|
11080
11468
|
return;
|
|
11081
11469
|
}
|
|
11082
11470
|
if (linked.teamId === creds.teamId) return;
|
|
11083
11471
|
const next = { ...creds, teamId: linked.teamId, teamName: linked.teamName };
|
|
11084
11472
|
delete next.activeTenant;
|
|
11085
11473
|
saveCredentials(next);
|
|
11086
|
-
console.error(
|
|
11474
|
+
console.error(import_chalk53.default.yellow(`Your previous team no longer exists \u2014 relinked to ${import_chalk53.default.bold(linked.teamName ?? linked.teamId)}. Re-run your command.`));
|
|
11087
11475
|
} catch {
|
|
11088
11476
|
}
|
|
11089
11477
|
}
|
|
@@ -11091,16 +11479,16 @@ async function printError(err) {
|
|
|
11091
11479
|
if (err instanceof ApiError) {
|
|
11092
11480
|
const data = err.body;
|
|
11093
11481
|
const extra = [data?.body?.reason, data?.body?.details, data?.details, data?.body?.error].find((x) => typeof x === "string" && x && x !== err.message);
|
|
11094
|
-
console.error(
|
|
11482
|
+
console.error(import_chalk53.default.red(`
|
|
11095
11483
|
API error (${err.status}): ${err.message}${extra ? ` \u2014 ${extra}` : ""}`));
|
|
11096
11484
|
if (err.status === 403 || err.status === 404) {
|
|
11097
11485
|
await recoverStaleTeam();
|
|
11098
11486
|
}
|
|
11099
11487
|
} else if (err instanceof Error) {
|
|
11100
|
-
console.error(
|
|
11488
|
+
console.error(import_chalk53.default.red(`
|
|
11101
11489
|
Error: ${err.message}`));
|
|
11102
11490
|
} else {
|
|
11103
|
-
console.error(
|
|
11491
|
+
console.error(import_chalk53.default.red("\nUnknown error"));
|
|
11104
11492
|
}
|
|
11105
11493
|
}
|
|
11106
11494
|
program.parse(process.argv);
|