apiblaze 0.20.9 → 0.20.11
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 +1084 -469
- package/dist/react/index.d.mts +110 -1
- package/dist/react/index.d.ts +110 -1
- package/dist/react/index.js +614 -65
- package/dist/react/index.mjs +614 -66
- package/dist/server/index.d.mts +67 -1
- package/dist/server/index.d.ts +67 -1
- package/dist/server/index.js +54 -0
- package/dist/server/index.mjs +53 -0
- 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 {
|
|
@@ -472,6 +472,98 @@ var init_admin = __esm({
|
|
|
472
472
|
}
|
|
473
473
|
});
|
|
474
474
|
|
|
475
|
+
// src/lib/spec-or-target.ts
|
|
476
|
+
var spec_or_target_exports = {};
|
|
477
|
+
__export(spec_or_target_exports, {
|
|
478
|
+
classifyTargetInput: () => classifyTargetInput,
|
|
479
|
+
looksLikeSpecDoc: () => looksLikeSpecDoc
|
|
480
|
+
});
|
|
481
|
+
function looksLikeSpecDoc(text) {
|
|
482
|
+
const probe = (d) => !!d && typeof d === "object" && !Array.isArray(d) && (typeof d.openapi === "string" || typeof d.swagger === "string") && typeof d.paths === "object";
|
|
483
|
+
try {
|
|
484
|
+
if (probe(JSON.parse(text))) return true;
|
|
485
|
+
} catch {
|
|
486
|
+
}
|
|
487
|
+
try {
|
|
488
|
+
if (probe((0, import_yaml.parse)(text))) return true;
|
|
489
|
+
} catch {
|
|
490
|
+
}
|
|
491
|
+
return false;
|
|
492
|
+
}
|
|
493
|
+
function isHttpUrl(s) {
|
|
494
|
+
try {
|
|
495
|
+
const u = new URL(s);
|
|
496
|
+
return u.protocol === "http:" || u.protocol === "https:";
|
|
497
|
+
} catch {
|
|
498
|
+
return false;
|
|
499
|
+
}
|
|
500
|
+
}
|
|
501
|
+
function smellsLikeSpecUrl(u) {
|
|
502
|
+
try {
|
|
503
|
+
const { pathname } = new URL(u);
|
|
504
|
+
return /\.(ya?ml|json)$/i.test(pathname) || /openapi|swagger/i.test(pathname);
|
|
505
|
+
} catch {
|
|
506
|
+
return false;
|
|
507
|
+
}
|
|
508
|
+
}
|
|
509
|
+
async function fetchTextCapped(url, timeoutMs) {
|
|
510
|
+
const controller = new AbortController();
|
|
511
|
+
const timer = setTimeout(() => controller.abort(), timeoutMs);
|
|
512
|
+
try {
|
|
513
|
+
const res = await fetch(url, {
|
|
514
|
+
signal: controller.signal,
|
|
515
|
+
headers: { accept: "application/yaml, application/json, text/yaml, text/plain;q=0.8, */*;q=0.5" }
|
|
516
|
+
});
|
|
517
|
+
if (!res.ok) return null;
|
|
518
|
+
const text = await res.text();
|
|
519
|
+
return text.length > MAX_SPEC_BYTES ? null : text;
|
|
520
|
+
} catch {
|
|
521
|
+
return null;
|
|
522
|
+
} finally {
|
|
523
|
+
clearTimeout(timer);
|
|
524
|
+
}
|
|
525
|
+
}
|
|
526
|
+
async function classifyTargetInput(input, failFn) {
|
|
527
|
+
const t = (input ?? "").trim();
|
|
528
|
+
if (!isHttpUrl(t)) {
|
|
529
|
+
let isFile = false;
|
|
530
|
+
try {
|
|
531
|
+
isFile = fs4.existsSync(t) && fs4.statSync(t).isFile();
|
|
532
|
+
} catch {
|
|
533
|
+
isFile = false;
|
|
534
|
+
}
|
|
535
|
+
if (isFile) {
|
|
536
|
+
const text2 = fs4.readFileSync(t, "utf-8");
|
|
537
|
+
if (looksLikeSpecDoc(text2)) return { kind: "spec", specText: text2, source: t };
|
|
538
|
+
failFn(
|
|
539
|
+
`${t} exists but is not an OpenAPI/Swagger document (no openapi/swagger version field with paths).`,
|
|
540
|
+
"Pass a spec file, a spec URL, or a target server base URL."
|
|
541
|
+
);
|
|
542
|
+
}
|
|
543
|
+
return { kind: "server" };
|
|
544
|
+
}
|
|
545
|
+
if (smellsLikeSpecUrl(t)) {
|
|
546
|
+
const text2 = await fetchTextCapped(t, 2e4);
|
|
547
|
+
if (text2 && looksLikeSpecDoc(text2)) return { kind: "spec", specText: text2, source: t };
|
|
548
|
+
failFn(
|
|
549
|
+
`${t} looks like an OpenAPI file but did not return a valid OpenAPI/Swagger document.`,
|
|
550
|
+
text2 === null ? "The URL could not be fetched (or is over 5MB). Check it in a browser." : "The response parsed, but has no openapi/swagger version field with paths."
|
|
551
|
+
);
|
|
552
|
+
}
|
|
553
|
+
const text = await fetchTextCapped(t, 6e3);
|
|
554
|
+
if (text && looksLikeSpecDoc(text)) return { kind: "spec", specText: text, source: t };
|
|
555
|
+
return { kind: "server" };
|
|
556
|
+
}
|
|
557
|
+
var fs4, import_yaml, MAX_SPEC_BYTES;
|
|
558
|
+
var init_spec_or_target = __esm({
|
|
559
|
+
"src/lib/spec-or-target.ts"() {
|
|
560
|
+
"use strict";
|
|
561
|
+
fs4 = __toESM(require("fs"));
|
|
562
|
+
import_yaml = require("yaml");
|
|
563
|
+
MAX_SPEC_BYTES = 5 * 1024 * 1024;
|
|
564
|
+
}
|
|
565
|
+
});
|
|
566
|
+
|
|
475
567
|
// src/lib/resolve.ts
|
|
476
568
|
function requireAuth() {
|
|
477
569
|
const creds = loadCredentials();
|
|
@@ -929,10 +1021,10 @@ var init_tenant_pick = __esm({
|
|
|
929
1021
|
|
|
930
1022
|
// src/index.ts
|
|
931
1023
|
var import_commander = require("commander");
|
|
932
|
-
var
|
|
1024
|
+
var import_chalk53 = __toESM(require("chalk"));
|
|
933
1025
|
|
|
934
1026
|
// package.json
|
|
935
|
-
var version = "0.20.
|
|
1027
|
+
var version = "0.20.11";
|
|
936
1028
|
|
|
937
1029
|
// src/index.ts
|
|
938
1030
|
init_types();
|
|
@@ -1198,11 +1290,11 @@ function decodeJwt(token) {
|
|
|
1198
1290
|
return null;
|
|
1199
1291
|
}
|
|
1200
1292
|
}
|
|
1201
|
-
function maskPath(
|
|
1202
|
-
const q =
|
|
1203
|
-
if (q < 0) return
|
|
1204
|
-
const base2 =
|
|
1205
|
-
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);
|
|
1206
1298
|
const masked = query.split("&").map((pair) => {
|
|
1207
1299
|
const eq = pair.indexOf("=");
|
|
1208
1300
|
if (eq < 0) return pair;
|
|
@@ -2007,7 +2099,7 @@ function suggestTenantName(projectSlug, taken) {
|
|
|
2007
2099
|
}
|
|
2008
2100
|
|
|
2009
2101
|
// src/commands/create.ts
|
|
2010
|
-
var
|
|
2102
|
+
var import_yaml2 = require("yaml");
|
|
2011
2103
|
var import_chalk11 = __toESM(require("chalk"));
|
|
2012
2104
|
var import_ora5 = __toESM(require("ora"));
|
|
2013
2105
|
init_auth();
|
|
@@ -2015,7 +2107,7 @@ init_api();
|
|
|
2015
2107
|
function normalizeName(raw) {
|
|
2016
2108
|
return (raw || "").toLowerCase().replace(/[^a-z0-9]/g, "");
|
|
2017
2109
|
}
|
|
2018
|
-
function
|
|
2110
|
+
function isHttpUrl2(s) {
|
|
2019
2111
|
try {
|
|
2020
2112
|
const u = new URL((s || "").trim());
|
|
2021
2113
|
return u.protocol === "http:" || u.protocol === "https:";
|
|
@@ -2025,7 +2117,7 @@ function isHttpUrl(s) {
|
|
|
2025
2117
|
}
|
|
2026
2118
|
async function loadOpenapiSource(ref) {
|
|
2027
2119
|
let text;
|
|
2028
|
-
if (
|
|
2120
|
+
if (isHttpUrl2(ref)) {
|
|
2029
2121
|
const res = await fetch(ref.trim(), {
|
|
2030
2122
|
headers: { accept: "application/json, application/yaml, text/yaml, */*" }
|
|
2031
2123
|
}).catch((err) => fail(`Could not fetch the OpenAPI spec at ${ref} \u2014 ${err.message}`));
|
|
@@ -2041,7 +2133,7 @@ async function loadOpenapiSource(ref) {
|
|
|
2041
2133
|
if (!text.trim()) fail(`The OpenAPI spec is empty: ${ref}`);
|
|
2042
2134
|
let parsed;
|
|
2043
2135
|
try {
|
|
2044
|
-
parsed = text.trimStart().startsWith("{") ? JSON.parse(text) : (0,
|
|
2136
|
+
parsed = text.trimStart().startsWith("{") ? JSON.parse(text) : (0, import_yaml2.parse)(text);
|
|
2045
2137
|
} catch (err) {
|
|
2046
2138
|
fail(`Could not parse the OpenAPI spec at ${ref} as JSON or YAML \u2014 ${err.message}`);
|
|
2047
2139
|
}
|
|
@@ -2070,8 +2162,9 @@ function stripTenantFromPortal(devPortal) {
|
|
|
2070
2162
|
return devPortal;
|
|
2071
2163
|
}
|
|
2072
2164
|
}
|
|
2073
|
-
function fail(message) {
|
|
2165
|
+
function fail(message, hint) {
|
|
2074
2166
|
console.error(import_chalk11.default.red(`Error: ${message}`));
|
|
2167
|
+
if (hint) console.error(import_chalk11.default.dim(hint));
|
|
2075
2168
|
process.exit(1);
|
|
2076
2169
|
}
|
|
2077
2170
|
function buildTryItCurl(url, authType, apiKey) {
|
|
@@ -2094,14 +2187,84 @@ function printCurlExample(url, authType, apiKey, devPortal) {
|
|
|
2094
2187
|
}
|
|
2095
2188
|
}
|
|
2096
2189
|
var VALID_AUTH = ["api_key", "none", "oauth"];
|
|
2190
|
+
function parseOauthFlag(raw) {
|
|
2191
|
+
if (raw === void 0 || raw === false) return null;
|
|
2192
|
+
if (raw === true || typeof raw === "string" && raw.trim() === "") {
|
|
2193
|
+
return {
|
|
2194
|
+
auth: "oauth",
|
|
2195
|
+
bodyPatch: { login: { providers: [{ type: "github", managed: true }] } },
|
|
2196
|
+
summary: "APIblaze-hosted GitHub sign-in (managed)"
|
|
2197
|
+
};
|
|
2198
|
+
}
|
|
2199
|
+
let parsed;
|
|
2200
|
+
try {
|
|
2201
|
+
parsed = JSON.parse(String(raw));
|
|
2202
|
+
} catch {
|
|
2203
|
+
fail(
|
|
2204
|
+
`--oauth value is not valid JSON: ${raw}`,
|
|
2205
|
+
`Shapes:
|
|
2206
|
+
--oauth APIblaze GitHub sign-in
|
|
2207
|
+
--oauth '{"iss":"https://login.acme.com/","aud":"acme-api","jwks":"https://login.acme.com/.well-known/jwks.json"}'
|
|
2208
|
+
--oauth '{"provider":"google","clientId":"\u2026","clientSecret":"\u2026"}'`
|
|
2209
|
+
);
|
|
2210
|
+
}
|
|
2211
|
+
if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) fail("--oauth must be a JSON object (or bare).");
|
|
2212
|
+
if (typeof parsed.iss === "string") {
|
|
2213
|
+
const iss = parsed.iss.trim();
|
|
2214
|
+
const aud = typeof parsed.aud === "string" ? parsed.aud.trim() : "";
|
|
2215
|
+
const jwks = typeof parsed.jwks === "string" ? parsed.jwks.trim() : typeof parsed.jwks_url === "string" ? parsed.jwks_url.trim() : "";
|
|
2216
|
+
if (!iss || !aud || !jwks) fail("--oauth issuer shape needs all three of: iss, aud, jwks (a JWKS URL).");
|
|
2217
|
+
if (!isHttpUrl2(jwks)) fail("--oauth: jwks must be an http(s) JWKS URL.");
|
|
2218
|
+
return {
|
|
2219
|
+
auth: "oauth",
|
|
2220
|
+
bodyPatch: {
|
|
2221
|
+
requests_auth: {
|
|
2222
|
+
mode: "authenticate",
|
|
2223
|
+
methods: ["jwt"],
|
|
2224
|
+
jwt: { allowed_pairs: [{ iss, aud, jwks_url: jwks }] }
|
|
2225
|
+
}
|
|
2226
|
+
},
|
|
2227
|
+
summary: `your own hosted login \u2014 JWTs from ${iss} (aud ${aud})`
|
|
2228
|
+
};
|
|
2229
|
+
}
|
|
2230
|
+
if (typeof parsed.provider === "string") {
|
|
2231
|
+
const provider = parsed.provider.trim().toLowerCase();
|
|
2232
|
+
const PROVIDERS2 = ["github", "google", "microsoft", "facebook", "auth0"];
|
|
2233
|
+
if (!PROVIDERS2.includes(provider)) fail(`--oauth: unknown provider "${provider}". One of: ${PROVIDERS2.join(" \xB7 ")}.`);
|
|
2234
|
+
const clientId = typeof parsed.clientId === "string" ? parsed.clientId.trim() : typeof parsed.client_id === "string" ? parsed.client_id.trim() : "";
|
|
2235
|
+
const clientSecret = typeof parsed.clientSecret === "string" ? parsed.clientSecret.trim() : typeof parsed.client_secret === "string" ? parsed.client_secret.trim() : "";
|
|
2236
|
+
if (!clientId || !clientSecret) fail("--oauth provider shape needs clientId and clientSecret (from your OAuth app).");
|
|
2237
|
+
return {
|
|
2238
|
+
auth: "oauth",
|
|
2239
|
+
bodyPatch: {
|
|
2240
|
+
login: {
|
|
2241
|
+
providers: [{ type: provider, client_id: clientId, client_secret: clientSecret }]
|
|
2242
|
+
}
|
|
2243
|
+
},
|
|
2244
|
+
summary: `APIblaze-hosted login page with your own ${provider} app`
|
|
2245
|
+
};
|
|
2246
|
+
}
|
|
2247
|
+
fail('--oauth JSON must contain either "iss" (your JWT issuer) or "provider" (your OAuth app).');
|
|
2248
|
+
}
|
|
2097
2249
|
async function runCreate(opts = {}) {
|
|
2250
|
+
if (opts.target && opts.openapi === void 0) {
|
|
2251
|
+
const { classifyTargetInput: classifyTargetInput2 } = await Promise.resolve().then(() => (init_spec_or_target(), spec_or_target_exports));
|
|
2252
|
+
const c = await classifyTargetInput2(opts.target, fail);
|
|
2253
|
+
if (c.kind === "spec") {
|
|
2254
|
+
console.log(import_chalk11.default.dim(` --target is an OpenAPI document (${c.source}) \u2014 creating FROM the spec.`));
|
|
2255
|
+
opts.openapi = opts.target;
|
|
2256
|
+
opts.target = void 0;
|
|
2257
|
+
}
|
|
2258
|
+
}
|
|
2098
2259
|
const creds = loadCredentials();
|
|
2099
2260
|
if (!creds) {
|
|
2100
2261
|
await runAnonymousCreate(opts);
|
|
2101
2262
|
return;
|
|
2102
2263
|
}
|
|
2103
2264
|
const interactive = !!process.stdin.isTTY && !opts.json;
|
|
2104
|
-
|
|
2265
|
+
if (opts.apikey && opts.oauth !== void 0 && opts.oauth !== false) fail("Pass either --apikey or --oauth, not both.");
|
|
2266
|
+
const oauthPlan = parseOauthFlag(opts.oauth);
|
|
2267
|
+
const auth = (oauthPlan ? oauthPlan.auth : opts.apikey ? "api_key" : opts.auth ?? "api_key").toLowerCase();
|
|
2105
2268
|
if (!VALID_AUTH.includes(auth)) {
|
|
2106
2269
|
fail(`Invalid --auth "${auth}". Use one of: ${VALID_AUTH.join(", ")}.`);
|
|
2107
2270
|
}
|
|
@@ -2165,13 +2328,13 @@ async function runCreate(opts = {}) {
|
|
|
2165
2328
|
}
|
|
2166
2329
|
let openapiContent = null;
|
|
2167
2330
|
if (opts.openapi !== void 0) {
|
|
2168
|
-
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).");
|
|
2169
2332
|
openapiContent = await loadOpenapiSource(opts.openapi);
|
|
2170
2333
|
}
|
|
2171
2334
|
let targetUrl = "";
|
|
2172
2335
|
if (openapiContent) {
|
|
2173
2336
|
} else if (opts.target !== void 0) {
|
|
2174
|
-
if (!
|
|
2337
|
+
if (!isHttpUrl2(opts.target)) fail("--target must be a valid http(s) URL.");
|
|
2175
2338
|
targetUrl = opts.target.trim();
|
|
2176
2339
|
} else if (interactive) {
|
|
2177
2340
|
const { default: inquirer3 } = await import("inquirer");
|
|
@@ -2181,7 +2344,7 @@ async function runCreate(opts = {}) {
|
|
|
2181
2344
|
name: "url",
|
|
2182
2345
|
message: "Target URL to forward requests to (e.g. https://httpbin.org):"
|
|
2183
2346
|
}]);
|
|
2184
|
-
if (!
|
|
2347
|
+
if (!isHttpUrl2(url)) {
|
|
2185
2348
|
console.log(import_chalk11.default.yellow(" Enter a valid http(s) URL.\n"));
|
|
2186
2349
|
continue;
|
|
2187
2350
|
}
|
|
@@ -2189,11 +2352,11 @@ async function runCreate(opts = {}) {
|
|
|
2189
2352
|
break;
|
|
2190
2353
|
}
|
|
2191
2354
|
} else {
|
|
2192
|
-
fail("--target
|
|
2355
|
+
fail("--target is required in non-interactive mode (a server base URL, a local OpenAPI file, or a remote OpenAPI URL).");
|
|
2193
2356
|
}
|
|
2194
2357
|
if (interactive && !opts.yes) {
|
|
2195
2358
|
const { default: inquirer3 } = await import("inquirer");
|
|
2196
|
-
console.log(`${import_chalk11.default.cyan("\u2192")} Auth: ${import_chalk11.default.bold(auth)}${auth === "api_key" ? " \u2014 consumers send an X-API-Key header" : ""}`);
|
|
2359
|
+
console.log(`${import_chalk11.default.cyan("\u2192")} Auth: ${import_chalk11.default.bold(auth)}${auth === "api_key" ? " \u2014 consumers send an X-API-Key header" : oauthPlan ? ` \u2014 ${oauthPlan.summary}` : ""}`);
|
|
2197
2360
|
const { ok } = await inquirer3.prompt([{
|
|
2198
2361
|
type: "confirm",
|
|
2199
2362
|
name: "ok",
|
|
@@ -2218,6 +2381,7 @@ async function runCreate(opts = {}) {
|
|
|
2218
2381
|
...openapiContent ? { openapi: openapiContent } : { target_url: targetUrl },
|
|
2219
2382
|
auth_type: auth,
|
|
2220
2383
|
team_id: teamId,
|
|
2384
|
+
...oauthPlan ? oauthPlan.bodyPatch : {},
|
|
2221
2385
|
...chosenTenant ? { tenant: chosenTenant } : {},
|
|
2222
2386
|
...opts.apiversion ? { api_version: opts.apiversion } : {}
|
|
2223
2387
|
});
|
|
@@ -2298,7 +2462,7 @@ async function runAnonymousCreate(opts) {
|
|
|
2298
2462
|
if (opts.openapi !== void 0) {
|
|
2299
2463
|
body.openapi = await loadOpenapiSource(opts.openapi);
|
|
2300
2464
|
}
|
|
2301
|
-
if (opts.target && !
|
|
2465
|
+
if (opts.target && !isHttpUrl2(opts.target)) fail("--target must be a valid http(s) URL.");
|
|
2302
2466
|
let target = opts.target?.trim() || (typeof body.target === "string" ? body.target : "") || (typeof body.target_url === "string" ? body.target_url : "");
|
|
2303
2467
|
const hasOtherSource = !!(body.openapi || body.github);
|
|
2304
2468
|
if (!target && !hasOtherSource) {
|
|
@@ -2320,7 +2484,7 @@ async function runAnonymousCreate(opts) {
|
|
|
2320
2484
|
name: "url",
|
|
2321
2485
|
message: "Target URL to forward requests to (e.g. https://httpbin.org):"
|
|
2322
2486
|
}]);
|
|
2323
|
-
if (!
|
|
2487
|
+
if (!isHttpUrl2(url)) {
|
|
2324
2488
|
console.log(import_chalk11.default.yellow(" Enter a valid http(s) URL.\n"));
|
|
2325
2489
|
continue;
|
|
2326
2490
|
}
|
|
@@ -2344,7 +2508,15 @@ async function runAnonymousCreate(opts) {
|
|
|
2344
2508
|
if (opts.product) body.product_slug = normalizeName(opts.product);
|
|
2345
2509
|
if (opts.displayName) body.display_name = opts.displayName;
|
|
2346
2510
|
if (opts.apiversion) body.api_version = opts.apiversion;
|
|
2347
|
-
if (opts.
|
|
2511
|
+
if (opts.apikey && opts.oauth !== void 0 && opts.oauth !== false) fail("Pass either --apikey or --oauth, not both.");
|
|
2512
|
+
const oauthPlan = parseOauthFlag(opts.oauth);
|
|
2513
|
+
if (oauthPlan) {
|
|
2514
|
+
Object.assign(body, oauthPlan.bodyPatch);
|
|
2515
|
+
if (oauthPlan.auth !== "api_key" && !("requests_auth" in oauthPlan.bodyPatch)) body.auth_type = oauthPlan.auth;
|
|
2516
|
+
if (!opts.json) console.log(`${import_chalk11.default.cyan("\u2192")} Login: ${oauthPlan.summary}`);
|
|
2517
|
+
} else if (opts.auth && opts.auth !== "api_key" && !opts.apikey) {
|
|
2518
|
+
body.auth_type = opts.auth;
|
|
2519
|
+
}
|
|
2348
2520
|
const { loadAnonCred: loadAnonCred2, saveAnonCred: saveAnonCred2, clearAnonCred: clearAnonCred2, cpFetch: cpFetch2 } = await Promise.resolve().then(() => (init_anon_cred(), anon_cred_exports));
|
|
2349
2521
|
if (opts.newSession) clearAnonCred2();
|
|
2350
2522
|
const cred = loadAnonCred2();
|
|
@@ -2487,7 +2659,7 @@ var import_chalk13 = __toESM(require("chalk"));
|
|
|
2487
2659
|
init_auth();
|
|
2488
2660
|
|
|
2489
2661
|
// src/lib/consumer-auth.ts
|
|
2490
|
-
var
|
|
2662
|
+
var fs6 = __toESM(require("fs"));
|
|
2491
2663
|
var os3 = __toESM(require("os"));
|
|
2492
2664
|
var path3 = __toESM(require("path"));
|
|
2493
2665
|
var crypto = __toESM(require("crypto"));
|
|
@@ -2498,19 +2670,19 @@ var CONSUMER_PATH = path3.join(APIBLAZE_DIR2, "consumer.json");
|
|
|
2498
2670
|
var DEVICE_GRANT = "urn:ietf:params:oauth:grant-type:device_code";
|
|
2499
2671
|
function loadConsumer() {
|
|
2500
2672
|
try {
|
|
2501
|
-
return JSON.parse(
|
|
2673
|
+
return JSON.parse(fs6.readFileSync(CONSUMER_PATH, "utf-8"));
|
|
2502
2674
|
} catch {
|
|
2503
2675
|
return null;
|
|
2504
2676
|
}
|
|
2505
2677
|
}
|
|
2506
2678
|
function saveConsumer(creds) {
|
|
2507
|
-
|
|
2508
|
-
|
|
2509
|
-
|
|
2679
|
+
fs6.mkdirSync(APIBLAZE_DIR2, { recursive: true });
|
|
2680
|
+
fs6.writeFileSync(CONSUMER_PATH, JSON.stringify(creds, null, 2), "utf-8");
|
|
2681
|
+
fs6.chmodSync(CONSUMER_PATH, 384);
|
|
2510
2682
|
}
|
|
2511
2683
|
function clearConsumer() {
|
|
2512
2684
|
try {
|
|
2513
|
-
|
|
2685
|
+
fs6.unlinkSync(CONSUMER_PATH);
|
|
2514
2686
|
return true;
|
|
2515
2687
|
} catch {
|
|
2516
2688
|
return false;
|
|
@@ -2655,12 +2827,12 @@ async function runLogout(opts = {}) {
|
|
|
2655
2827
|
|
|
2656
2828
|
// src/commands/flush.ts
|
|
2657
2829
|
var import_chalk14 = __toESM(require("chalk"));
|
|
2658
|
-
var
|
|
2830
|
+
var fs7 = __toESM(require("fs"));
|
|
2659
2831
|
var path4 = __toESM(require("path"));
|
|
2660
2832
|
init_auth();
|
|
2661
2833
|
async function runFlush(opts) {
|
|
2662
2834
|
const dir = getApiblazeDir();
|
|
2663
|
-
const entries2 =
|
|
2835
|
+
const entries2 = fs7.existsSync(dir) ? fs7.readdirSync(dir) : [];
|
|
2664
2836
|
if (!entries2.length) {
|
|
2665
2837
|
console.log(import_chalk14.default.dim("Nothing to flush \u2014 no local apiblaze config found."));
|
|
2666
2838
|
return;
|
|
@@ -2684,7 +2856,7 @@ async function runFlush(opts) {
|
|
|
2684
2856
|
let removed = 0;
|
|
2685
2857
|
for (const e of entries2) {
|
|
2686
2858
|
try {
|
|
2687
|
-
|
|
2859
|
+
fs7.rmSync(path4.join(dir, e), { recursive: true, force: true });
|
|
2688
2860
|
removed++;
|
|
2689
2861
|
} catch (err) {
|
|
2690
2862
|
console.log(import_chalk14.default.red(` \u2717 could not remove ${e}: ${err instanceof Error ? err.message : "error"}`));
|
|
@@ -3357,8 +3529,8 @@ function resolveRecipeName(raw, githubHandle, proxyName) {
|
|
|
3357
3529
|
function normalizeName2(raw) {
|
|
3358
3530
|
return (raw || "").toLowerCase().replace(/[^a-z0-9]/g, "");
|
|
3359
3531
|
}
|
|
3360
|
-
async function publicFetch(
|
|
3361
|
-
const res = await fetch(`${RECIPES_BASE}/api/recipes${
|
|
3532
|
+
async function publicFetch(path9) {
|
|
3533
|
+
const res = await fetch(`${RECIPES_BASE}/api/recipes${path9}`, {
|
|
3362
3534
|
headers: { accept: "application/json" }
|
|
3363
3535
|
}).catch((err) => {
|
|
3364
3536
|
throw new Error(`Could not reach the recipe registry at ${RECIPES_BASE} \u2014 ${err.message}`);
|
|
@@ -3411,11 +3583,11 @@ async function fetchRecipeFile(ref, caller) {
|
|
|
3411
3583
|
Private recipes are visible only to the publisher's team \u2014 run \`apiblaze login\` if you are on it.`
|
|
3412
3584
|
);
|
|
3413
3585
|
}
|
|
3414
|
-
const
|
|
3586
|
+
const path9 = revision !== void 0 ? `/recipes/${ref.handle}/${ref.slug}/${revision}` : `/recipes/${ref.handle}/${ref.slug}/file`;
|
|
3415
3587
|
try {
|
|
3416
3588
|
return await producer(caller, {
|
|
3417
3589
|
method: "GET",
|
|
3418
|
-
path:
|
|
3590
|
+
path: path9,
|
|
3419
3591
|
summary: `Read private recipe ${ref.name}${revision !== void 0 ? `@${revision}` : ""}`
|
|
3420
3592
|
});
|
|
3421
3593
|
} catch (err) {
|
|
@@ -4567,9 +4739,9 @@ function printEnvelopeViolations(violations) {
|
|
|
4567
4739
|
console.log("bring-your-own-key case. Change the rules above, then publish again.");
|
|
4568
4740
|
console.log();
|
|
4569
4741
|
}
|
|
4570
|
-
async function askPromotion(
|
|
4742
|
+
async function askPromotion(path9) {
|
|
4571
4743
|
const { default: inquirer3 } = await import("inquirer");
|
|
4572
|
-
const suggestedId =
|
|
4744
|
+
const suggestedId = path9.split(/[.[\]]/).filter(Boolean).slice(-2).join("_").toLowerCase().replace(/[^a-z0-9_]/g, "_").replace(/_+/g, "_").replace(/^_|_$/g, "") || "value";
|
|
4573
4745
|
const answers = await inquirer3.prompt([
|
|
4574
4746
|
{ type: "input", name: "prompt", message: " prompt them with:", validate: (v) => v.trim() ? true : "Required." },
|
|
4575
4747
|
{ type: "input", name: "example", message: " example:" },
|
|
@@ -4580,7 +4752,7 @@ async function askPromotion(path8) {
|
|
|
4580
4752
|
const id = String(answers.id || suggestedId).toLowerCase().replace(/[^a-z0-9_]/g, "_");
|
|
4581
4753
|
console.log(import_chalk27.default.dim(` \u2192 replaced with {{${id}}}`));
|
|
4582
4754
|
return {
|
|
4583
|
-
path:
|
|
4755
|
+
path: path9,
|
|
4584
4756
|
id,
|
|
4585
4757
|
prompt: String(answers.prompt).trim(),
|
|
4586
4758
|
...answers.example ? { example: String(answers.example).trim() } : {},
|
|
@@ -6310,7 +6482,7 @@ async function providersMenu(cBase, clientLabel) {
|
|
|
6310
6482
|
}
|
|
6311
6483
|
|
|
6312
6484
|
// src/commands/spec.ts
|
|
6313
|
-
var
|
|
6485
|
+
var fs8 = __toESM(require("fs"));
|
|
6314
6486
|
var import_chalk37 = __toESM(require("chalk"));
|
|
6315
6487
|
var import_ora16 = __toESM(require("ora"));
|
|
6316
6488
|
init_admin();
|
|
@@ -6333,7 +6505,7 @@ async function runSpecSet(project, opts) {
|
|
|
6333
6505
|
}
|
|
6334
6506
|
let specContent;
|
|
6335
6507
|
try {
|
|
6336
|
-
specContent =
|
|
6508
|
+
specContent = fs8.readFileSync(opts.file, "utf-8");
|
|
6337
6509
|
} catch {
|
|
6338
6510
|
console.error(import_chalk37.default.red(`Cannot read file: ${opts.file}`));
|
|
6339
6511
|
process.exit(1);
|
|
@@ -6862,6 +7034,15 @@ var SETTINGS = [
|
|
|
6862
7034
|
read: (cfg) => cfg.chat_sponsorship?.max_chars_per_message ?? 2e3,
|
|
6863
7035
|
toPatch: (v) => ({ chat_sponsorship: { max_chars_per_message: v } })
|
|
6864
7036
|
},
|
|
7037
|
+
{
|
|
7038
|
+
key: "chat_prompt_addendum",
|
|
7039
|
+
label: "AI chat: steering notes",
|
|
7040
|
+
group: "Portal & MCP",
|
|
7041
|
+
type: "string",
|
|
7042
|
+
desc: `Notes the chat assistant follows on this API \u2014 tone, suggestions, house rules ("always suggest the daily special"). Injected beneath the assistant's safety rules, so it can steer style but never override them. Max 1000 chars; empty clears`,
|
|
7043
|
+
read: (cfg) => cfg.chat_prompt_addendum ?? "",
|
|
7044
|
+
toPatch: (v) => ({ chat_prompt_addendum: typeof v === "string" && v.trim() ? v.trim() : null })
|
|
7045
|
+
},
|
|
6865
7046
|
// ── Advanced (specs/missing.md): all optional, default-off; null clears ────
|
|
6866
7047
|
{
|
|
6867
7048
|
key: "ip_access.mode",
|
|
@@ -7621,11 +7802,11 @@ async function resolveTransport(opts) {
|
|
|
7621
7802
|
const token = getAccessToken();
|
|
7622
7803
|
return {
|
|
7623
7804
|
tenant: tenant2,
|
|
7624
|
-
call: async (
|
|
7805
|
+
call: async (path9, method = "GET", body) => {
|
|
7625
7806
|
const res = await fetch(`${DASHBOARD_BASE6}/api/cli/iam`, {
|
|
7626
7807
|
method: "POST",
|
|
7627
7808
|
headers: { "Content-Type": "application/json", Authorization: `Bearer ${token}` },
|
|
7628
|
-
body: JSON.stringify({ tenant: tenant2, path:
|
|
7809
|
+
body: JSON.stringify({ tenant: tenant2, path: path9, method, body })
|
|
7629
7810
|
});
|
|
7630
7811
|
const data = await res.json().catch(() => ({}));
|
|
7631
7812
|
if (!res.ok) throw new ApiError(res.status, data?.details ?? data?.error ?? res.statusText, data);
|
|
@@ -7643,8 +7824,8 @@ async function resolveTransport(opts) {
|
|
|
7643
7824
|
return {
|
|
7644
7825
|
tenant: fresh.tenant,
|
|
7645
7826
|
selfEmail: fresh.email,
|
|
7646
|
-
call: async (
|
|
7647
|
-
const res = await fetch(`${IAM_BASE}${
|
|
7827
|
+
call: async (path9, method = "GET", body) => {
|
|
7828
|
+
const res = await fetch(`${IAM_BASE}${path9}`, {
|
|
7648
7829
|
method,
|
|
7649
7830
|
headers: {
|
|
7650
7831
|
"Content-Type": "application/json",
|
|
@@ -7996,12 +8177,12 @@ init_admin();
|
|
|
7996
8177
|
init_resolve();
|
|
7997
8178
|
init_types();
|
|
7998
8179
|
var DASHBOARD_BASE7 = process.env.APIBLAZE_DASHBOARD_BASE || "https://dashboard.apiblaze.com";
|
|
7999
|
-
async function iamCall(tenant2,
|
|
8180
|
+
async function iamCall(tenant2, path9, method = "GET", body) {
|
|
8000
8181
|
const token = getAccessToken();
|
|
8001
8182
|
const res = await fetch(`${DASHBOARD_BASE7}/api/cli/iam`, {
|
|
8002
8183
|
method: "POST",
|
|
8003
8184
|
headers: { "Content-Type": "application/json", Authorization: `Bearer ${token}` },
|
|
8004
|
-
body: JSON.stringify({ tenant: tenant2, path:
|
|
8185
|
+
body: JSON.stringify({ tenant: tenant2, path: path9, method, body })
|
|
8005
8186
|
});
|
|
8006
8187
|
const data = await res.json().catch(() => ({}));
|
|
8007
8188
|
if (!res.ok) throw new ApiError(res.status, data?.details ?? data?.error ?? res.statusText, data);
|
|
@@ -8088,19 +8269,19 @@ async function runPreapprove(who, opts) {
|
|
|
8088
8269
|
}
|
|
8089
8270
|
|
|
8090
8271
|
// src/commands/apichat.ts
|
|
8091
|
-
var
|
|
8092
|
-
var
|
|
8272
|
+
var fs11 = __toESM(require("fs"));
|
|
8273
|
+
var path7 = __toESM(require("path"));
|
|
8093
8274
|
var crypto2 = __toESM(require("crypto"));
|
|
8094
|
-
var
|
|
8275
|
+
var import_chalk47 = __toESM(require("chalk"));
|
|
8095
8276
|
var import_ora23 = __toESM(require("ora"));
|
|
8096
|
-
var
|
|
8277
|
+
var import_yaml3 = require("yaml");
|
|
8097
8278
|
init_auth();
|
|
8098
8279
|
init_anon_cred();
|
|
8099
8280
|
init_api();
|
|
8100
8281
|
init_admin();
|
|
8101
8282
|
|
|
8102
8283
|
// src/commands/llm.ts
|
|
8103
|
-
var
|
|
8284
|
+
var fs9 = __toESM(require("fs"));
|
|
8104
8285
|
var path5 = __toESM(require("path"));
|
|
8105
8286
|
var import_chalk45 = __toESM(require("chalk"));
|
|
8106
8287
|
var import_inquirer2 = __toESM(require("inquirer"));
|
|
@@ -8116,15 +8297,15 @@ function detectProvider(key) {
|
|
|
8116
8297
|
}
|
|
8117
8298
|
function loadLlmConfig() {
|
|
8118
8299
|
try {
|
|
8119
|
-
return JSON.parse(
|
|
8300
|
+
return JSON.parse(fs9.readFileSync(LLM_PATH, "utf-8"));
|
|
8120
8301
|
} catch {
|
|
8121
8302
|
return null;
|
|
8122
8303
|
}
|
|
8123
8304
|
}
|
|
8124
8305
|
function saveLlmConfig(cfg) {
|
|
8125
|
-
|
|
8126
|
-
|
|
8127
|
-
|
|
8306
|
+
fs9.mkdirSync(getApiblazeDir(), { recursive: true });
|
|
8307
|
+
fs9.writeFileSync(LLM_PATH, JSON.stringify(cfg, null, 2), "utf-8");
|
|
8308
|
+
fs9.chmodSync(LLM_PATH, 384);
|
|
8128
8309
|
}
|
|
8129
8310
|
async function runLlmSetKey(keyArg, opts) {
|
|
8130
8311
|
let key = keyArg?.trim();
|
|
@@ -8163,7 +8344,7 @@ async function runLlmShow() {
|
|
|
8163
8344
|
}
|
|
8164
8345
|
async function runLlmClearKey() {
|
|
8165
8346
|
try {
|
|
8166
|
-
|
|
8347
|
+
fs9.unlinkSync(LLM_PATH);
|
|
8167
8348
|
console.log(`${import_chalk45.default.green("\u2713")} Removed local LLM key.`);
|
|
8168
8349
|
} catch {
|
|
8169
8350
|
console.log(import_chalk45.default.gray("No LLM key was set."));
|
|
@@ -8172,17 +8353,140 @@ async function runLlmClearKey() {
|
|
|
8172
8353
|
|
|
8173
8354
|
// src/commands/apichat.ts
|
|
8174
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 run = (cmd, args, opts = {}) => (0, import_child_process2.spawnSync)(cmd, args, {
|
|
8364
|
+
encoding: "utf-8",
|
|
8365
|
+
stdio: opts.inherit ? ["ignore", "inherit", "inherit"] : ["ignore", "pipe", "pipe"],
|
|
8366
|
+
timeout: opts.inherit ? void 0 : 15e3,
|
|
8367
|
+
shell: process.platform === "win32"
|
|
8368
|
+
// .cmd shims on Windows
|
|
8369
|
+
});
|
|
8370
|
+
function detectExternalClis() {
|
|
8371
|
+
const found = [];
|
|
8372
|
+
for (const [kind, label3] of [["claude", "Claude CLI"], ["codex", "Codex CLI"]]) {
|
|
8373
|
+
try {
|
|
8374
|
+
const r = run(kind, ["--version"]);
|
|
8375
|
+
if (r.status === 0) found.push({ kind, label: label3, version: (r.stdout || "").trim().split("\n")[0] || void 0 });
|
|
8376
|
+
} catch {
|
|
8377
|
+
}
|
|
8378
|
+
}
|
|
8379
|
+
return found;
|
|
8380
|
+
}
|
|
8381
|
+
function claudeInstallArgs(spec2) {
|
|
8382
|
+
const args = ["mcp", "add", "--transport", "http", spec2.name, spec2.url];
|
|
8383
|
+
if (spec2.apiKey) args.push("--header", `X-API-Key: ${spec2.apiKey}`);
|
|
8384
|
+
return args;
|
|
8385
|
+
}
|
|
8386
|
+
function installIntoClaude(spec2) {
|
|
8387
|
+
run("claude", ["mcp", "remove", spec2.name]);
|
|
8388
|
+
const r = run("claude", claudeInstallArgs(spec2));
|
|
8389
|
+
if (r.status === 0) return { ok: true };
|
|
8390
|
+
return { ok: false, error: (r.stderr || r.stdout || `exit ${r.status}`).trim().slice(0, 400) };
|
|
8391
|
+
}
|
|
8392
|
+
function claudeOneShot(spec2, prompt) {
|
|
8393
|
+
const argv = ["claude", "-p", prompt, "--allowedTools", `mcp__${spec2.name}__*`];
|
|
8394
|
+
const r = run(argv[0], argv.slice(1), { inherit: true });
|
|
8395
|
+
return { argv, status: r.status };
|
|
8396
|
+
}
|
|
8397
|
+
function codexConfigPath() {
|
|
8398
|
+
return path6.join(process.env.CODEX_HOME || path6.join(os4.homedir(), ".codex"), "config.toml");
|
|
8399
|
+
}
|
|
8400
|
+
var tomlStr = (s) => `"${s.replace(/\\/g, "\\\\").replace(/"/g, '\\"')}"`;
|
|
8401
|
+
function codexServerBlock(spec2) {
|
|
8402
|
+
const lines = [`[mcp_servers.${tomlStr(spec2.name)}]`, `url = ${tomlStr(spec2.url)}`];
|
|
8403
|
+
if (spec2.apiKey) lines.push(`http_headers = { "X-API-Key" = ${tomlStr(spec2.apiKey)} }`);
|
|
8404
|
+
return lines.join("\n") + "\n";
|
|
8405
|
+
}
|
|
8406
|
+
function installIntoCodex(spec2) {
|
|
8407
|
+
const file = codexConfigPath();
|
|
8408
|
+
try {
|
|
8409
|
+
fs10.mkdirSync(path6.dirname(file), { recursive: true });
|
|
8410
|
+
let text = "";
|
|
8411
|
+
try {
|
|
8412
|
+
text = fs10.readFileSync(file, "utf-8");
|
|
8413
|
+
} catch {
|
|
8414
|
+
}
|
|
8415
|
+
const esc = spec2.name.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
8416
|
+
const section = new RegExp(`(^|\\n)\\[mcp_servers\\.(?:"${esc}"|${esc})\\][^\\[]*`, "g");
|
|
8417
|
+
const cleaned = text.replace(section, "$1");
|
|
8418
|
+
const sep = cleaned.length && !cleaned.endsWith("\n\n") ? cleaned.endsWith("\n") ? "\n" : "\n\n" : "";
|
|
8419
|
+
fs10.writeFileSync(file, cleaned + sep + codexServerBlock(spec2), "utf-8");
|
|
8420
|
+
return { ok: true, path: file };
|
|
8421
|
+
} catch (err) {
|
|
8422
|
+
return { ok: false, error: err instanceof Error ? err.message : String(err), path: file };
|
|
8423
|
+
}
|
|
8424
|
+
}
|
|
8425
|
+
function codexOneShot(_spec, prompt) {
|
|
8426
|
+
const argv = ["codex", "exec", prompt];
|
|
8427
|
+
const r = run(argv[0], argv.slice(1), { inherit: true });
|
|
8428
|
+
return { argv, status: r.status };
|
|
8429
|
+
}
|
|
8430
|
+
var shellQuote = (s) => `"${s.replace(/(["\\$`])/g, "\\$1")}"`;
|
|
8431
|
+
function renderCommand(argv) {
|
|
8432
|
+
return argv.map((a, i) => i === 0 || /^[A-Za-z0-9_@%+=:,./-]+$/.test(a) ? a : shellQuote(a)).join(" ");
|
|
8433
|
+
}
|
|
8434
|
+
function installAndDemo(cli, spec2, question, log = console.log) {
|
|
8435
|
+
if (cli.kind === "claude") {
|
|
8436
|
+
log(`
|
|
8437
|
+
${import_chalk46.default.dim("$")} ${renderCommand(["claude", ...claudeInstallArgs(spec2)])}`);
|
|
8438
|
+
const r = installIntoClaude(spec2);
|
|
8439
|
+
if (!r.ok) {
|
|
8440
|
+
log(import_chalk46.default.red(` Install failed: ${r.error}`));
|
|
8441
|
+
return false;
|
|
8442
|
+
}
|
|
8443
|
+
log(` ${import_chalk46.default.green("\u2714")} MCP ${import_chalk46.default.bold(spec2.name)} added to Claude CLI (local scope \u2014 this directory).`);
|
|
8444
|
+
} else {
|
|
8445
|
+
const r = installIntoCodex(spec2);
|
|
8446
|
+
if (!r.ok) {
|
|
8447
|
+
log(import_chalk46.default.red(` Could not write ${r.path}: ${r.error}`));
|
|
8448
|
+
return false;
|
|
8449
|
+
}
|
|
8450
|
+
log(` ${import_chalk46.default.green("\u2714")} MCP ${import_chalk46.default.bold(spec2.name)} added to ${r.path}.`);
|
|
8451
|
+
}
|
|
8452
|
+
if (!spec2.apiKey) {
|
|
8453
|
+
log(import_chalk46.default.dim(` This proxy authenticates by login: the first call from ${cli.label} will open its sign-in.`));
|
|
8454
|
+
log(`
|
|
8455
|
+
${import_chalk46.default.bold(`Your ${cli.label} is now able to talk to the ${spec2.projectLabel} API.`)}`);
|
|
8456
|
+
return true;
|
|
8457
|
+
}
|
|
8458
|
+
const oneShot = cli.kind === "claude" ? claudeOneShot : codexOneShot;
|
|
8459
|
+
const abilities = `What are the tool abilities of the MCP server "${spec2.name}"? List them briefly.`;
|
|
8460
|
+
log(`
|
|
8461
|
+
${import_chalk46.default.dim("Checking what the MCP exposes\u2026")}`);
|
|
8462
|
+
log(` ${import_chalk46.default.dim("$")} ${renderCommand(cli.kind === "claude" ? ["claude", "-p", abilities, "--allowedTools", `mcp__${spec2.name}__*`] : ["codex", "exec", abilities])}
|
|
8463
|
+
`);
|
|
8464
|
+
oneShot(spec2, abilities);
|
|
8465
|
+
if (question) {
|
|
8466
|
+
log(`
|
|
8467
|
+
${import_chalk46.default.dim("Your question, through " + cli.label + ":")}`);
|
|
8468
|
+
const shown = cli.kind === "claude" ? ["claude", "-p", question, "--allowedTools", `mcp__${spec2.name}__*`] : ["codex", "exec", question];
|
|
8469
|
+
log(` ${import_chalk46.default.dim("$")} ${renderCommand(shown)}
|
|
8470
|
+
`);
|
|
8471
|
+
oneShot(spec2, question);
|
|
8472
|
+
}
|
|
8473
|
+
log(`
|
|
8474
|
+
${import_chalk46.default.bold(`Your ${cli.label} is now able to talk to the ${spec2.projectLabel} API.`)}`);
|
|
8475
|
+
return true;
|
|
8476
|
+
}
|
|
8477
|
+
|
|
8478
|
+
// src/commands/apichat.ts
|
|
8175
8479
|
init_types();
|
|
8176
8480
|
function fail4(message, hint) {
|
|
8177
|
-
console.error(
|
|
8481
|
+
console.error(import_chalk47.default.red(`
|
|
8178
8482
|
Error: ${message}`));
|
|
8179
|
-
if (hint) console.error(
|
|
8483
|
+
if (hint) console.error(import_chalk47.default.dim(hint));
|
|
8180
8484
|
process.exit(1);
|
|
8181
8485
|
}
|
|
8182
8486
|
function normalizeName3(raw) {
|
|
8183
8487
|
return (raw || "").toLowerCase().replace(/[^a-z0-9]/g, "");
|
|
8184
8488
|
}
|
|
8185
|
-
function
|
|
8489
|
+
function isHttpUrl3(s) {
|
|
8186
8490
|
try {
|
|
8187
8491
|
const u = new URL((s || "").trim());
|
|
8188
8492
|
return u.protocol === "http:" || u.protocol === "https:";
|
|
@@ -8196,7 +8500,7 @@ function parseSpec(text) {
|
|
|
8196
8500
|
parsed = JSON.parse(text);
|
|
8197
8501
|
} catch {
|
|
8198
8502
|
try {
|
|
8199
|
-
parsed = (0,
|
|
8503
|
+
parsed = (0, import_yaml3.parse)(text);
|
|
8200
8504
|
} catch {
|
|
8201
8505
|
fail4("Could not parse the spec as JSON or YAML.");
|
|
8202
8506
|
}
|
|
@@ -8207,7 +8511,7 @@ function parseSpec(text) {
|
|
|
8207
8511
|
}
|
|
8208
8512
|
return parsed;
|
|
8209
8513
|
}
|
|
8210
|
-
var GENERATOR_HINT = "
|
|
8514
|
+
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>";
|
|
8211
8515
|
async function fetchText(url) {
|
|
8212
8516
|
try {
|
|
8213
8517
|
const res = await fetch(url, { headers: { accept: "application/json, application/yaml, text/yaml, */*" } });
|
|
@@ -8233,32 +8537,32 @@ async function discoverSpec(target) {
|
|
|
8233
8537
|
}
|
|
8234
8538
|
async function loadSpec(opts) {
|
|
8235
8539
|
if (opts.openapispec) {
|
|
8236
|
-
if (
|
|
8540
|
+
if (isHttpUrl3(opts.openapispec)) {
|
|
8237
8541
|
const text2 = await fetchText(opts.openapispec);
|
|
8238
8542
|
if (!text2) fail4(`Could not fetch the spec at ${opts.openapispec}.`);
|
|
8239
8543
|
return { spec: parseSpec(text2), sourceUrl: opts.openapispec };
|
|
8240
8544
|
}
|
|
8241
8545
|
let text;
|
|
8242
8546
|
try {
|
|
8243
|
-
text =
|
|
8547
|
+
text = fs11.readFileSync(opts.openapispec, "utf-8");
|
|
8244
8548
|
} catch {
|
|
8245
8549
|
fail4(`Cannot read spec file: ${opts.openapispec}`);
|
|
8246
8550
|
}
|
|
8247
8551
|
return { spec: parseSpec(text) };
|
|
8248
8552
|
}
|
|
8249
8553
|
if (opts.target) {
|
|
8250
|
-
if (!
|
|
8554
|
+
if (!isHttpUrl3(opts.target)) fail4("--target must be a valid http(s) URL.");
|
|
8251
8555
|
const found = await discoverSpec(opts.target);
|
|
8252
8556
|
if (!found) {
|
|
8253
8557
|
fail4(`No OpenAPI spec found at ${opts.target} (tried /openapi.json, /openapi.yaml, /swagger.json).`, GENERATOR_HINT);
|
|
8254
8558
|
}
|
|
8255
8559
|
return { spec: found.spec, sourceUrl: found.sourceUrl };
|
|
8256
8560
|
}
|
|
8257
|
-
fail4("No spec source. Pass --
|
|
8561
|
+
fail4("No spec source. Pass --target <server-url | openapi-file | openapi-url>.", GENERATOR_HINT);
|
|
8258
8562
|
}
|
|
8259
8563
|
function resolveTarget(spec2, opts, sourceUrl) {
|
|
8260
8564
|
if (opts.target) {
|
|
8261
|
-
if (!
|
|
8565
|
+
if (!isHttpUrl3(opts.target)) fail4("--target must be a valid http(s) URL.");
|
|
8262
8566
|
return opts.target.trim();
|
|
8263
8567
|
}
|
|
8264
8568
|
const servers = Array.isArray(spec2.servers) ? spec2.servers : [];
|
|
@@ -8268,7 +8572,7 @@ function resolveTarget(spec2, opts, sourceUrl) {
|
|
|
8268
8572
|
}
|
|
8269
8573
|
try {
|
|
8270
8574
|
const resolved = sourceUrl ? new URL(raw, sourceUrl).toString() : raw;
|
|
8271
|
-
if (!
|
|
8575
|
+
if (!isHttpUrl3(resolved)) {
|
|
8272
8576
|
fail4(`servers[0].url ("${raw}") is not an absolute URL and could not be resolved.`, "Re-run with --target <upstream base URL>.");
|
|
8273
8577
|
}
|
|
8274
8578
|
return resolved;
|
|
@@ -8324,7 +8628,7 @@ async function resolveTargetAuth(spec2, opts) {
|
|
|
8324
8628
|
"Re-run with --force to provision anyway (configure target auth later with `apiblaze config`),\nor use an api_key / bearer / basic scheme."
|
|
8325
8629
|
);
|
|
8326
8630
|
}
|
|
8327
|
-
if (sawOAuth) console.log(
|
|
8631
|
+
if (sawOAuth) console.log(import_chalk47.default.yellow(" --force: skipping OAuth target auth \u2014 configure it later with `apiblaze config`."));
|
|
8328
8632
|
return null;
|
|
8329
8633
|
}
|
|
8330
8634
|
if (candidates.length === 1 && !noneAllowed) return candidates[0];
|
|
@@ -8378,9 +8682,10 @@ async function captureTargetSecret(auth, opts) {
|
|
|
8378
8682
|
return secret;
|
|
8379
8683
|
}
|
|
8380
8684
|
async function dataPlaneAuth(p) {
|
|
8685
|
+
const idHeader = p.endUserId ? { "X-End-User-Id": p.endUserId } : {};
|
|
8381
8686
|
if (!p.consumerAuth) {
|
|
8382
|
-
if (!p.dpKey)
|
|
8383
|
-
return { "X-API-Key": p.dpKey };
|
|
8687
|
+
if (!p.dpKey) return idHeader;
|
|
8688
|
+
return { "X-API-Key": p.dpKey, ...idHeader };
|
|
8384
8689
|
}
|
|
8385
8690
|
const stored = loadConsumer();
|
|
8386
8691
|
if (!stored) {
|
|
@@ -8388,7 +8693,7 @@ async function dataPlaneAuth(p) {
|
|
|
8388
8693
|
}
|
|
8389
8694
|
const fresh = await validConsumerToken(stored) ?? stored;
|
|
8390
8695
|
if (fresh.accessToken !== stored.accessToken) saveConsumer(fresh);
|
|
8391
|
-
return { Authorization: `Bearer ${fresh.accessToken}
|
|
8696
|
+
return { Authorization: `Bearer ${fresh.accessToken}`, ...idHeader };
|
|
8392
8697
|
}
|
|
8393
8698
|
async function ensureConsumerLogin(teamId, tenant2, version2) {
|
|
8394
8699
|
const resource = `https://${tenant2}.portal.apiblaze.com/${version2}`;
|
|
@@ -8397,7 +8702,7 @@ async function ensureConsumerLogin(teamId, tenant2, version2) {
|
|
|
8397
8702
|
const fresh = await validConsumerToken(existing);
|
|
8398
8703
|
if (fresh) {
|
|
8399
8704
|
if (fresh.accessToken !== existing.accessToken) saveConsumer({ ...fresh, resource });
|
|
8400
|
-
console.log(
|
|
8705
|
+
console.log(import_chalk47.default.dim(` Using your consumer session on ${import_chalk47.default.bold(tenant2)}${fresh.email ? ` (${fresh.email})` : ""}.`));
|
|
8401
8706
|
return fresh;
|
|
8402
8707
|
}
|
|
8403
8708
|
}
|
|
@@ -8416,13 +8721,13 @@ async function ensureConsumerLogin(teamId, tenant2, version2) {
|
|
|
8416
8721
|
);
|
|
8417
8722
|
}
|
|
8418
8723
|
const clientId = pick2.client_id ?? pick2.clientId;
|
|
8419
|
-
console.log(`${
|
|
8724
|
+
console.log(`${import_chalk47.default.cyan("\u2192")} This proxy signs consumers in with OAuth \u2014 logging you in to ${import_chalk47.default.bold(tenant2)}...`);
|
|
8420
8725
|
const result = await deviceLogin(clientId, "openid email profile offline_access", ({ verificationUri, userCode }) => {
|
|
8421
8726
|
console.log(`
|
|
8422
|
-
Open: ${
|
|
8423
|
-
console.log(` Code: ${
|
|
8727
|
+
Open: ${import_chalk47.default.underline(verificationUri)}`);
|
|
8728
|
+
console.log(` Code: ${import_chalk47.default.bold(userCode)}
|
|
8424
8729
|
`);
|
|
8425
|
-
console.log(
|
|
8730
|
+
console.log(import_chalk47.default.dim(" (opening your browser\u2026 waiting for you to finish)"));
|
|
8426
8731
|
}, resource);
|
|
8427
8732
|
const claims = result.idToken && decodeJwt2(result.idToken) || (decodeJwt2(result.accessToken) ?? {});
|
|
8428
8733
|
const creds = {
|
|
@@ -8438,16 +8743,16 @@ async function ensureConsumerLogin(teamId, tenant2, version2) {
|
|
|
8438
8743
|
resource
|
|
8439
8744
|
};
|
|
8440
8745
|
saveConsumer(creds);
|
|
8441
|
-
console.log(` ${
|
|
8746
|
+
console.log(` ${import_chalk47.default.green("\u2714")} Signed in as${creds.email ? ` ${import_chalk47.default.bold(creds.email)}` : " a consumer"} on ${tenant2}.`);
|
|
8442
8747
|
return creds;
|
|
8443
8748
|
}
|
|
8444
|
-
async function cpPost(anon,
|
|
8749
|
+
async function cpPost(anon, path9, body, summary) {
|
|
8445
8750
|
if (anon) {
|
|
8446
8751
|
const cred = loadAnonCred();
|
|
8447
8752
|
if (!cred) throw new Error("Anonymous workspace credential missing.");
|
|
8448
|
-
return cpFetch(cred.cp_key,
|
|
8753
|
+
return cpFetch(cred.cp_key, path9, { method: "POST", body: JSON.stringify(body) });
|
|
8449
8754
|
}
|
|
8450
|
-
return admin({ method: "POST", path:
|
|
8755
|
+
return admin({ method: "POST", path: path9, body, summary });
|
|
8451
8756
|
}
|
|
8452
8757
|
async function provision(spec2, target, opts) {
|
|
8453
8758
|
const loggedIn = !!loadCredentials();
|
|
@@ -8467,7 +8772,7 @@ async function provision(spec2, target, opts) {
|
|
|
8467
8772
|
let name = opts.name ? base2 : `${base2}${salt()}`;
|
|
8468
8773
|
const access = anon ? "open" : opts.access === "open" ? "open" : "invite";
|
|
8469
8774
|
if (anon && opts.access === "invite") {
|
|
8470
|
-
console.log(
|
|
8775
|
+
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`."));
|
|
8471
8776
|
}
|
|
8472
8777
|
const DUAL_AUTH = {
|
|
8473
8778
|
mode: "authenticate",
|
|
@@ -8566,7 +8871,7 @@ async function provision(spec2, target, opts) {
|
|
|
8566
8871
|
try {
|
|
8567
8872
|
await addPreapprovalRule(tenant2, email);
|
|
8568
8873
|
} catch {
|
|
8569
|
-
console.log(
|
|
8874
|
+
console.log(import_chalk47.default.dim(` (Could not auto-approve your email for sign-in \u2014 add it later: apiblaze preapprove ${email})`));
|
|
8570
8875
|
}
|
|
8571
8876
|
}
|
|
8572
8877
|
}
|
|
@@ -8610,7 +8915,7 @@ async function uploadSpec(p, specText, opts) {
|
|
|
8610
8915
|
throw err;
|
|
8611
8916
|
}
|
|
8612
8917
|
if (out && out.reused === true) {
|
|
8613
|
-
console.log(
|
|
8918
|
+
console.log(import_chalk47.default.dim(" Spec unchanged since the last provision \u2014 reusing the existing configuration."));
|
|
8614
8919
|
} else if (out && out.changed === true && out.previous_spec_hash) {
|
|
8615
8920
|
const interactive = !!process.stdin.isTTY && !opts.yes;
|
|
8616
8921
|
if (interactive) {
|
|
@@ -8618,7 +8923,7 @@ async function uploadSpec(p, specText, opts) {
|
|
|
8618
8923
|
const { go } = await inquirer3.prompt([
|
|
8619
8924
|
{ type: "confirm", name: "go", message: "The spec changed since the last provision \u2014 re-publish the MCP catalogue?", default: true }
|
|
8620
8925
|
]);
|
|
8621
|
-
if (!go) console.log(
|
|
8926
|
+
if (!go) console.log(import_chalk47.default.dim(" Keeping the existing MCP catalogue."));
|
|
8622
8927
|
}
|
|
8623
8928
|
}
|
|
8624
8929
|
}
|
|
@@ -8648,7 +8953,6 @@ async function publishMcp(p, spec2) {
|
|
|
8648
8953
|
return null;
|
|
8649
8954
|
}
|
|
8650
8955
|
}
|
|
8651
|
-
var CLIENT_ROUND_CAP = 12;
|
|
8652
8956
|
function chatUrl(p) {
|
|
8653
8957
|
return `https://${p.mcpHost}/${p.version}/${p.environment}/runtime-chat`;
|
|
8654
8958
|
}
|
|
@@ -8656,91 +8960,66 @@ function maskKey(k) {
|
|
|
8656
8960
|
return k.length <= 8 ? "****" : `${k.slice(0, 4)}\u2026${k.slice(-4)}`;
|
|
8657
8961
|
}
|
|
8658
8962
|
var revealAuth = false;
|
|
8659
|
-
function
|
|
8660
|
-
|
|
8661
|
-
|
|
8662
|
-
|
|
8663
|
-
|
|
8664
|
-
|
|
8665
|
-
|
|
8666
|
-
|
|
8667
|
-
const shown = revealAuth ? cred.value : maskKey(cred.value);
|
|
8668
|
-
const keyLine = ` -H '${cred.header}: ${shown}'`;
|
|
8669
|
-
const hint = revealAuth ? "" : import_chalk46.default.yellow(" \u2190 /showauth will reveal this");
|
|
8670
|
-
console.log(import_chalk46.default.dim(keyLine) + hint);
|
|
8671
|
-
}
|
|
8672
|
-
}
|
|
8673
|
-
}
|
|
8674
|
-
}
|
|
8675
|
-
function renderToolResults(delta, events) {
|
|
8676
|
-
const anyFailed = (events ?? []).some((e) => e.status === "error");
|
|
8677
|
-
if (!anyFailed && !isVerbose()) return;
|
|
8678
|
-
const cap = anyFailed ? 24 : 12;
|
|
8679
|
-
for (const m of delta ?? []) {
|
|
8680
|
-
if (!m || m.role !== "tool" || typeof m.content !== "string") continue;
|
|
8681
|
-
const body = m.content.trim();
|
|
8682
|
-
if (!body) continue;
|
|
8683
|
-
const pretty = (() => {
|
|
8684
|
-
try {
|
|
8685
|
-
return JSON.stringify(JSON.parse(body), null, 2);
|
|
8686
|
-
} catch {
|
|
8687
|
-
return body;
|
|
8688
|
-
}
|
|
8689
|
-
})();
|
|
8690
|
-
const lines = pretty.split("\n");
|
|
8691
|
-
console.log(import_chalk46.default.dim(" response:"));
|
|
8692
|
-
for (const line of lines.slice(0, cap)) {
|
|
8693
|
-
console.log(import_chalk46.default.dim(` ${line}`));
|
|
8694
|
-
}
|
|
8695
|
-
if (lines.length > cap) console.log(import_chalk46.default.dim(` \u2026${lines.length - cap} more lines`));
|
|
8696
|
-
}
|
|
8697
|
-
}
|
|
8698
|
-
function billingLine(billing) {
|
|
8699
|
-
if (!billing || typeof billing.cents !== "number") return null;
|
|
8700
|
-
if (typeof billing.free_turns_remaining === "number") return null;
|
|
8701
|
-
const usd = (billing.cents / 100).toFixed(Math.abs(billing.cents - Math.round(billing.cents)) < 1e-9 ? 2 : 4);
|
|
8702
|
-
let line = import_chalk46.default.magenta(` \u{1F4B3} $${usd}`) + import_chalk46.default.dim(billing.model ? ` \xB7 ${billing.model}` : "");
|
|
8703
|
-
if (typeof billing.credits_remaining === "number") {
|
|
8704
|
-
line += import_chalk46.default.dim(` \xB7 balance $${(billing.credits_remaining / 100).toFixed(2)}`);
|
|
8963
|
+
function billingLine(info) {
|
|
8964
|
+
if (!info || typeof info.charged_cents !== "number" || info.charged_cents <= 0) return null;
|
|
8965
|
+
if (typeof info.free_turns_remaining === "number") return null;
|
|
8966
|
+
const cents = info.charged_cents;
|
|
8967
|
+
const usd = (cents / 100).toFixed(Math.abs(cents - Math.round(cents)) < 1e-9 ? 2 : 4);
|
|
8968
|
+
let line = import_chalk47.default.magenta(` \u{1F4B3} $${usd}`);
|
|
8969
|
+
if (typeof info.credits_remaining === "number") {
|
|
8970
|
+
line += import_chalk47.default.dim(` \xB7 balance $${(info.credits_remaining / 100).toFixed(2)}`);
|
|
8705
8971
|
}
|
|
8706
8972
|
return line;
|
|
8707
8973
|
}
|
|
8708
|
-
function freeBudgetWarning(
|
|
8709
|
-
if (!anon || !
|
|
8710
|
-
if (typeof
|
|
8711
|
-
const left2 =
|
|
8712
|
-
if (left2 <= 0) return
|
|
8713
|
-
return
|
|
8974
|
+
function freeBudgetWarning(info, anon) {
|
|
8975
|
+
if (!anon || !info) return null;
|
|
8976
|
+
if (typeof info.free_turns_remaining === "number") {
|
|
8977
|
+
const left2 = info.free_turns_remaining;
|
|
8978
|
+
if (left2 <= 0) return import_chalk47.default.yellow(" Free chats used up \u2014 `npx apiblaze login` (free) to keep going.");
|
|
8979
|
+
return import_chalk47.default.dim(` ${left2} free chat${left2 === 1 ? "" : "s"} left \xB7 /login to get more`);
|
|
8714
8980
|
}
|
|
8715
|
-
if (typeof
|
|
8716
|
-
const perTurn = Math.max(
|
|
8717
|
-
const left = Math.floor(
|
|
8981
|
+
if (typeof info.free_remaining_cents !== "number") return null;
|
|
8982
|
+
const perTurn = Math.max(info.charged_cents || 0, 0.02);
|
|
8983
|
+
const left = Math.floor(info.free_remaining_cents / perTurn);
|
|
8718
8984
|
if (left > 8) return null;
|
|
8719
|
-
if (left <= 0) return
|
|
8720
|
-
return
|
|
8721
|
-
}
|
|
8722
|
-
function
|
|
8723
|
-
|
|
8724
|
-
|
|
8725
|
-
|
|
8726
|
-
|
|
8727
|
-
|
|
8985
|
+
if (left <= 0) return import_chalk47.default.yellow(" Free messages used up \u2014 `npx apiblaze login` (free) to keep chatting.");
|
|
8986
|
+
return import_chalk47.default.yellow(` \u26A0 About ${left} free message${left === 1 ? "" : "s"} left \u2014 \`npx apiblaze login\` (free) for more.`);
|
|
8987
|
+
}
|
|
8988
|
+
async function readSse(body, onEvent) {
|
|
8989
|
+
const reader = body.getReader();
|
|
8990
|
+
const decoder = new TextDecoder();
|
|
8991
|
+
let buf = "";
|
|
8992
|
+
for (; ; ) {
|
|
8993
|
+
const { done, value } = await reader.read();
|
|
8994
|
+
if (done) break;
|
|
8995
|
+
buf += decoder.decode(value, { stream: true });
|
|
8996
|
+
for (; ; ) {
|
|
8997
|
+
const at = buf.indexOf("\n\n");
|
|
8998
|
+
if (at === -1) break;
|
|
8999
|
+
const rawEvent = buf.slice(0, at);
|
|
9000
|
+
buf = buf.slice(at + 2);
|
|
9001
|
+
const data = rawEvent.split("\n").filter((l) => l.startsWith("data:")).map((l) => l.slice(5).replace(/^ /, "")).join("\n");
|
|
9002
|
+
if (!data || data === "[DONE]") continue;
|
|
9003
|
+
try {
|
|
9004
|
+
onEvent(JSON.parse(data));
|
|
9005
|
+
} catch {
|
|
9006
|
+
}
|
|
8728
9007
|
}
|
|
8729
9008
|
}
|
|
8730
9009
|
}
|
|
8731
9010
|
async function replTurn(p, messages, userText) {
|
|
8732
|
-
messages.push({ role: "user",
|
|
9011
|
+
messages.push({ id: crypto2.randomUUID(), role: "user", parts: [{ type: "text", text: userText }] });
|
|
8733
9012
|
const llm2 = loadLlmConfig();
|
|
8734
9013
|
const turnId = crypto2.randomUUID();
|
|
8735
|
-
|
|
8736
|
-
|
|
8737
|
-
|
|
8738
|
-
|
|
8739
|
-
|
|
8740
|
-
|
|
8741
|
-
|
|
8742
|
-
|
|
8743
|
-
|
|
9014
|
+
const spinner = (0, import_ora23.default)({ text: "thinking...", color: "magenta" }).start();
|
|
9015
|
+
const body = {
|
|
9016
|
+
turn_id: turnId,
|
|
9017
|
+
messages,
|
|
9018
|
+
environment: p.environment,
|
|
9019
|
+
...llm2 ? { llm_api_key: llm2.key, llm_provider: llm2.provider } : {}
|
|
9020
|
+
};
|
|
9021
|
+
let res = null;
|
|
9022
|
+
for (let attempt = 0; attempt < 3; attempt++) {
|
|
8744
9023
|
try {
|
|
8745
9024
|
res = await fetch(chatUrl(p), {
|
|
8746
9025
|
method: "POST",
|
|
@@ -8749,93 +9028,286 @@ async function replTurn(p, messages, userText) {
|
|
|
8749
9028
|
});
|
|
8750
9029
|
} catch (err) {
|
|
8751
9030
|
spinner.fail("Network error.");
|
|
8752
|
-
console.log(
|
|
9031
|
+
console.log(import_chalk47.default.red(` Could not reach ${p.mcpHost}: ${err instanceof Error ? err.message : String(err)}`));
|
|
8753
9032
|
return;
|
|
8754
9033
|
}
|
|
9034
|
+
if ((res.headers.get("content-type") ?? "").includes("text/event-stream") && res.ok && res.body) break;
|
|
9035
|
+
spinner.stop();
|
|
8755
9036
|
let data = null;
|
|
8756
9037
|
try {
|
|
8757
9038
|
data = await res.json();
|
|
8758
9039
|
} catch {
|
|
8759
9040
|
}
|
|
8760
|
-
|
|
8761
|
-
|
|
8762
|
-
|
|
9041
|
+
const errObj = data && typeof data.error === "object" ? data.error : null;
|
|
9042
|
+
const code = errObj && errObj.code || null;
|
|
9043
|
+
const msg = String(errObj && errObj.message || data && (data.error || data.message) || "");
|
|
9044
|
+
const tty = !!process.stdin.isTTY;
|
|
9045
|
+
if (res.status === 404 && /project not found/i.test(msg)) {
|
|
9046
|
+
const flipped = p.mcpHost.includes(".tryabz.run") ? p.mcpHost.replace(".tryabz.run", ".abz.run") : p.mcpHost.replace(".abz.run", ".tryabz.run");
|
|
9047
|
+
if (flipped !== p.mcpHost && attempt === 0) {
|
|
9048
|
+
p.mcpHost = flipped;
|
|
9049
|
+
p.proxyUrl = p.proxyUrl?.includes("tryabz.run") ? p.proxyUrl.replace("tryabz.run", "abz.run") : p.proxyUrl?.replace("abz.run", "tryabz.run");
|
|
9050
|
+
p.anon = flipped.includes(".tryabz.run");
|
|
9051
|
+
spinner.start("retrying on the " + (p.anon ? "trial" : "claimed") + " plane\u2026");
|
|
9052
|
+
continue;
|
|
9053
|
+
}
|
|
9054
|
+
console.log(import_chalk47.default.red(` No proxy named ${p.projectId} was found (tried both abz.run and tryabz.run).`));
|
|
9055
|
+
return;
|
|
9056
|
+
}
|
|
9057
|
+
if (code === "identity_required" || /identif/i.test(msg) && !code) {
|
|
9058
|
+
if (!p.endUserId && tty) {
|
|
9059
|
+
console.log(import_chalk47.default.yellow(" This API requires every call to say WHO is calling."));
|
|
9060
|
+
const { default: inquirer3 } = await import("inquirer");
|
|
9061
|
+
const { id } = await inquirer3.prompt([{ type: "input", name: "id", message: "Your end-user id (usually your email):" }]);
|
|
9062
|
+
if (typeof id === "string" && id.trim()) {
|
|
9063
|
+
p.endUserId = id.trim();
|
|
9064
|
+
persistAuthState(p);
|
|
9065
|
+
spinner.start("retrying\u2026");
|
|
9066
|
+
continue;
|
|
9067
|
+
}
|
|
9068
|
+
}
|
|
9069
|
+
console.log(import_chalk47.default.red(" This API requires an identified caller."));
|
|
9070
|
+
console.log(import_chalk47.default.dim(" Re-run with --xenduserid <your id> (usually your email)."));
|
|
8763
9071
|
return;
|
|
8764
9072
|
}
|
|
8765
|
-
if (
|
|
8766
|
-
|
|
9073
|
+
if (code === "user_not_preapproved") {
|
|
9074
|
+
console.log(import_chalk47.default.yellow(` ${msg || "You are not pre-approved on this API."}`));
|
|
9075
|
+
if (p.endUserId) console.log(import_chalk47.default.dim(` Identity sent: ${p.endUserId}`));
|
|
9076
|
+
const accessUrl = errObj && errObj.request_access_url;
|
|
9077
|
+
if (accessUrl) console.log(` Request access: ${import_chalk47.default.bold(String(accessUrl))}`);
|
|
9078
|
+
console.log(import_chalk47.default.dim(" Or ask the producer to pre-approve you: `apiblaze preapprove <your-email> --tenant <tenant>`."));
|
|
8767
9079
|
return;
|
|
8768
9080
|
}
|
|
8769
|
-
if (
|
|
8770
|
-
|
|
8771
|
-
const usingKey = !p.consumerAuth;
|
|
8772
|
-
console.log(import_chalk46.default.red(` The proxy rejected the request (401)${reason ? `: ${reason}` : "."}`));
|
|
8773
|
-
console.log(
|
|
8774
|
-
import_chalk46.default.dim(
|
|
8775
|
-
usingKey ? " Sent an API key. If the key was revoked, re-run `apiblaze apichat` to re-provision." : " Sent your consumer OAuth token. Re-run `apiblaze apichat` to sign in again."
|
|
8776
|
-
)
|
|
8777
|
-
);
|
|
9081
|
+
if (code === "user_frozen") {
|
|
9082
|
+
console.log(import_chalk47.default.red(` ${msg || "Your access to this API has been frozen by the producer."}`));
|
|
8778
9083
|
return;
|
|
8779
9084
|
}
|
|
8780
|
-
|
|
8781
|
-
|
|
8782
|
-
|
|
9085
|
+
const oauthWanted = /oauth token required|authorization: bearer/i.test(msg);
|
|
9086
|
+
const keyWanted = /api key required|x-api-key/i.test(msg);
|
|
9087
|
+
if (oauthWanted && !p.consumerAuth) {
|
|
9088
|
+
if (p.teamId && p.tenant && loadCredentials()) {
|
|
9089
|
+
console.log(import_chalk47.default.dim(" This proxy signs consumers in with OAuth \u2014 starting the login\u2026"));
|
|
9090
|
+
try {
|
|
9091
|
+
await ensureConsumerLogin(p.teamId, p.tenant, p.version);
|
|
9092
|
+
p.consumerAuth = true;
|
|
9093
|
+
p.dpKey = void 0;
|
|
9094
|
+
persistAuthState(p);
|
|
9095
|
+
spinner.start("retrying\u2026");
|
|
9096
|
+
continue;
|
|
9097
|
+
} catch (err) {
|
|
9098
|
+
console.log(import_chalk47.default.red(` Login failed: ${err instanceof Error ? err.message : String(err)}`));
|
|
9099
|
+
return;
|
|
9100
|
+
}
|
|
9101
|
+
}
|
|
9102
|
+
console.log(import_chalk47.default.red(" This proxy signs consumers in with OAuth (a login), not an API key."));
|
|
9103
|
+
console.log(import_chalk47.default.dim(" Sign in with: `apiblaze consumer login --tenant <tenant> --client <app-client-id>`, then re-run apichat."));
|
|
8783
9104
|
return;
|
|
8784
9105
|
}
|
|
8785
|
-
if (
|
|
8786
|
-
|
|
8787
|
-
|
|
8788
|
-
|
|
8789
|
-
|
|
8790
|
-
|
|
8791
|
-
|
|
8792
|
-
|
|
8793
|
-
|
|
8794
|
-
|
|
8795
|
-
|
|
9106
|
+
if (keyWanted) {
|
|
9107
|
+
if (tty) {
|
|
9108
|
+
console.log(import_chalk47.default.yellow(` ${msg || "This API requires an API key."}`));
|
|
9109
|
+
const { default: inquirer3 } = await import("inquirer");
|
|
9110
|
+
const { key } = await inquirer3.prompt([{ type: "password", name: "key", mask: "*", message: "API key for this proxy:" }]);
|
|
9111
|
+
if (typeof key === "string" && key.trim()) {
|
|
9112
|
+
p.dpKey = key.trim();
|
|
9113
|
+
p.consumerAuth = false;
|
|
9114
|
+
persistAuthState(p);
|
|
9115
|
+
spinner.start("retrying\u2026");
|
|
9116
|
+
continue;
|
|
9117
|
+
}
|
|
9118
|
+
}
|
|
9119
|
+
console.log(import_chalk47.default.red(` ${msg || "This API requires an API key."}`));
|
|
9120
|
+
console.log(import_chalk47.default.dim(" Re-run with --apikey <key> (mint one from the producer's site or dev portal)."));
|
|
9121
|
+
return;
|
|
8796
9122
|
}
|
|
8797
|
-
|
|
8798
|
-
|
|
8799
|
-
|
|
8800
|
-
|
|
8801
|
-
if (
|
|
9123
|
+
if (res && (res.status === 402 || res.status === 403)) {
|
|
9124
|
+
renderUpsell(p, { reason: "PAUSED", message: msg || "This turn is not available on your current plan." });
|
|
9125
|
+
return;
|
|
9126
|
+
}
|
|
9127
|
+
if (res && res.status === 401) {
|
|
9128
|
+
console.log(import_chalk47.default.red(` The proxy rejected the request (401)${msg ? `: ${msg}` : "."}`));
|
|
9129
|
+
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>."));
|
|
9130
|
+
return;
|
|
9131
|
+
}
|
|
9132
|
+
console.log(import_chalk47.default.red(` Chat error: ${msg || (res ? `HTTP ${res.status}` : "request failed")}`));
|
|
9133
|
+
return;
|
|
8802
9134
|
}
|
|
8803
|
-
|
|
8804
|
-
|
|
8805
|
-
|
|
9135
|
+
if (!res || !res.body || !(res.headers.get("content-type") ?? "").includes("text/event-stream")) {
|
|
9136
|
+
spinner.stop();
|
|
9137
|
+
console.log(import_chalk47.default.red(" Chat error: could not authenticate to this proxy after several attempts."));
|
|
9138
|
+
return;
|
|
9139
|
+
}
|
|
9140
|
+
let spinnerLive = true;
|
|
9141
|
+
const stopSpinner = () => {
|
|
9142
|
+
if (spinnerLive) {
|
|
9143
|
+
spinner.stop();
|
|
9144
|
+
spinnerLive = false;
|
|
9145
|
+
}
|
|
9146
|
+
};
|
|
9147
|
+
const parts = [];
|
|
9148
|
+
let openTextIdx = -1;
|
|
9149
|
+
let assistantOpen = false;
|
|
9150
|
+
const toolMeta = /* @__PURE__ */ new Map();
|
|
9151
|
+
let upsell = null;
|
|
9152
|
+
let turnInfo = null;
|
|
9153
|
+
let errorText = null;
|
|
9154
|
+
let messageId = `msg_${Date.now()}`;
|
|
9155
|
+
const credHint = () => {
|
|
9156
|
+
if (!isVerbose()) return null;
|
|
9157
|
+
if (p.consumerAuth) {
|
|
9158
|
+
const t = loadConsumer()?.accessToken;
|
|
9159
|
+
return t ? `Authorization: Bearer ${revealAuth ? t : maskKey(t)}` : null;
|
|
9160
|
+
}
|
|
9161
|
+
return p.dpKey ? `X-API-Key: ${revealAuth ? p.dpKey : maskKey(p.dpKey)}` : null;
|
|
9162
|
+
};
|
|
9163
|
+
try {
|
|
9164
|
+
await readSse(res.body, (ev) => {
|
|
9165
|
+
switch (ev?.type) {
|
|
9166
|
+
case "start":
|
|
9167
|
+
if (typeof ev.messageId === "string") messageId = ev.messageId;
|
|
9168
|
+
break;
|
|
9169
|
+
case "tool-input-start": {
|
|
9170
|
+
stopSpinner();
|
|
9171
|
+
if (assistantOpen) {
|
|
9172
|
+
process.stdout.write("\n");
|
|
9173
|
+
assistantOpen = false;
|
|
9174
|
+
}
|
|
9175
|
+
const name = String(ev.toolName ?? "tool");
|
|
9176
|
+
parts.push({ type: `tool-${name}`, toolCallId: String(ev.toolCallId ?? ""), state: "input-streaming" });
|
|
9177
|
+
toolMeta.set(String(ev.toolCallId ?? ""), { name, startedAt: Date.now(), partIdx: parts.length - 1 });
|
|
9178
|
+
console.log(` ${import_chalk47.default.cyan("\u2699")} ${import_chalk47.default.cyan(name)}${import_chalk47.default.dim("\u2026")}`);
|
|
9179
|
+
break;
|
|
9180
|
+
}
|
|
9181
|
+
case "tool-input-available": {
|
|
9182
|
+
const id = String(ev.toolCallId ?? "");
|
|
9183
|
+
const meta = toolMeta.get(id);
|
|
9184
|
+
const input = ev.input && typeof ev.input === "object" ? ev.input : {};
|
|
9185
|
+
if (meta) {
|
|
9186
|
+
meta.input = input;
|
|
9187
|
+
Object.assign(parts[meta.partIdx], { state: "input-available", input });
|
|
9188
|
+
}
|
|
9189
|
+
if (isVerbose()) {
|
|
9190
|
+
console.log(import_chalk47.default.dim(` args ${JSON.stringify(input)}`));
|
|
9191
|
+
const hint = credHint();
|
|
9192
|
+
if (hint) console.log(import_chalk47.default.dim(` auth ${hint}`) + (revealAuth ? "" : import_chalk47.default.yellow(" \u2190 /showauth reveals")));
|
|
9193
|
+
}
|
|
9194
|
+
break;
|
|
9195
|
+
}
|
|
9196
|
+
case "tool-output-available":
|
|
9197
|
+
case "tool-output-error": {
|
|
9198
|
+
stopSpinner();
|
|
9199
|
+
const id = String(ev.toolCallId ?? "");
|
|
9200
|
+
const meta = toolMeta.get(id);
|
|
9201
|
+
const ok = ev.type === "tool-output-available";
|
|
9202
|
+
const ms = meta ? Date.now() - meta.startedAt : void 0;
|
|
9203
|
+
const mark = ok ? import_chalk47.default.green("\u2713") : import_chalk47.default.red("\u2717");
|
|
9204
|
+
console.log(` ${mark} ${import_chalk47.default.cyan(meta?.name ?? "tool")} ${import_chalk47.default.dim(`(${ok ? "ok" : "error"}${ms != null ? `, ${ms}ms` : ""})`)}`);
|
|
9205
|
+
const detail = ok ? String(ev.output ?? "") : String(ev.errorText ?? "Tool call failed.");
|
|
9206
|
+
if (meta) {
|
|
9207
|
+
Object.assign(parts[meta.partIdx], ok ? { state: "output-available", output: detail } : { state: "output-error", errorText: detail });
|
|
9208
|
+
}
|
|
9209
|
+
if (!ok || isVerbose()) {
|
|
9210
|
+
const pretty = (() => {
|
|
9211
|
+
try {
|
|
9212
|
+
return JSON.stringify(JSON.parse(detail), null, 2);
|
|
9213
|
+
} catch {
|
|
9214
|
+
return detail;
|
|
9215
|
+
}
|
|
9216
|
+
})();
|
|
9217
|
+
const lines = pretty.split("\n");
|
|
9218
|
+
const cap = ok ? 12 : 24;
|
|
9219
|
+
console.log(import_chalk47.default.dim(" response:"));
|
|
9220
|
+
for (const line of lines.slice(0, cap)) console.log(import_chalk47.default.dim(` ${line}`));
|
|
9221
|
+
if (lines.length > cap) console.log(import_chalk47.default.dim(` \u2026${lines.length - cap} more lines`));
|
|
9222
|
+
}
|
|
9223
|
+
break;
|
|
9224
|
+
}
|
|
9225
|
+
case "text-start":
|
|
9226
|
+
stopSpinner();
|
|
9227
|
+
parts.push({ type: "text", text: "" });
|
|
9228
|
+
openTextIdx = parts.length - 1;
|
|
9229
|
+
if (!assistantOpen) {
|
|
9230
|
+
process.stdout.write("\n" + import_chalk47.default.green("assistant \u203A "));
|
|
9231
|
+
assistantOpen = true;
|
|
9232
|
+
}
|
|
9233
|
+
break;
|
|
9234
|
+
case "text-delta": {
|
|
9235
|
+
const delta = String(ev.delta ?? "");
|
|
9236
|
+
if (openTextIdx >= 0) parts[openTextIdx].text = String(parts[openTextIdx].text ?? "") + delta;
|
|
9237
|
+
process.stdout.write(delta);
|
|
9238
|
+
break;
|
|
9239
|
+
}
|
|
9240
|
+
case "text-end":
|
|
9241
|
+
openTextIdx = -1;
|
|
9242
|
+
break;
|
|
9243
|
+
case "data-apiblaze-upsell": {
|
|
9244
|
+
const d = ev.data ?? {};
|
|
9245
|
+
upsell = { reason: String(d.reason ?? ""), message: String(d.message ?? "") };
|
|
9246
|
+
break;
|
|
9247
|
+
}
|
|
9248
|
+
case "data-apiblaze-turn":
|
|
9249
|
+
turnInfo = ev.data ?? {};
|
|
9250
|
+
break;
|
|
9251
|
+
case "error":
|
|
9252
|
+
stopSpinner();
|
|
9253
|
+
errorText = String(ev.errorText ?? "Something went wrong.");
|
|
9254
|
+
break;
|
|
9255
|
+
default:
|
|
9256
|
+
break;
|
|
9257
|
+
}
|
|
9258
|
+
});
|
|
9259
|
+
} catch (err) {
|
|
9260
|
+
stopSpinner();
|
|
9261
|
+
console.log(import_chalk47.default.red(` Stream error: ${err instanceof Error ? err.message : String(err)}`));
|
|
9262
|
+
}
|
|
9263
|
+
stopSpinner();
|
|
9264
|
+
if (assistantOpen) process.stdout.write("\n\n");
|
|
9265
|
+
if (parts.length) messages.push({ id: messageId, role: "assistant", parts });
|
|
9266
|
+
if (errorText) console.log(import_chalk47.default.red(` ${errorText}`));
|
|
9267
|
+
if (upsell) {
|
|
9268
|
+
renderUpsell(p, upsell, { messageAlreadyShown: true });
|
|
9269
|
+
}
|
|
9270
|
+
const bl = billingLine(turnInfo);
|
|
9271
|
+
if (bl) console.log(bl);
|
|
9272
|
+
const warn = freeBudgetWarning(turnInfo, p.anon);
|
|
9273
|
+
if (warn) console.log(warn);
|
|
9274
|
+
}
|
|
9275
|
+
function renderUpsell(p, upsell, opts = {}) {
|
|
8806
9276
|
const loggedIn = !!loadCredentials();
|
|
8807
9277
|
if (upsell.reason === "CAPPED" && !loggedIn) {
|
|
8808
|
-
console.log("\n" +
|
|
8809
|
-
console.log(
|
|
9278
|
+
console.log("\n" + import_chalk47.default.yellow(" Type `npx apiblaze login` to claim the rest of your balance."));
|
|
9279
|
+
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.)"));
|
|
8810
9280
|
console.log();
|
|
8811
9281
|
return;
|
|
8812
9282
|
}
|
|
8813
|
-
|
|
9283
|
+
if (!opts.messageAlreadyShown) {
|
|
9284
|
+
console.log("\n" + import_chalk47.default.yellow(` ${upsell.message || "This turn is not available right now."}`));
|
|
9285
|
+
}
|
|
8814
9286
|
if (upsell.reason === "INSUFFICIENT" || upsell.reason === "BREAKER" || upsell.reason === "CAPPED" || upsell.reason === "QUOTA" || upsell.reason === "PROXY_CAP" || upsell.reason === "PAUSED" || upsell.reason === "BYO_REQUIRED") {
|
|
8815
9287
|
if (!loggedIn) {
|
|
8816
|
-
console.log(
|
|
9288
|
+
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."));
|
|
8817
9289
|
} else {
|
|
8818
|
-
console.log(
|
|
9290
|
+
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)."));
|
|
8819
9291
|
}
|
|
8820
9292
|
} else if (upsell.reason === "INFLIGHT") {
|
|
8821
|
-
console.log(
|
|
9293
|
+
console.log(import_chalk47.default.dim(" Another turn is still in flight \u2014 wait a moment and try again."));
|
|
8822
9294
|
}
|
|
8823
9295
|
console.log();
|
|
8824
9296
|
}
|
|
8825
|
-
var apichatsPath = () =>
|
|
9297
|
+
var apichatsPath = () => path7.join(getApiblazeDir(), "apichats.json");
|
|
8826
9298
|
function loadApichats() {
|
|
8827
9299
|
try {
|
|
8828
|
-
const list = JSON.parse(
|
|
9300
|
+
const list = JSON.parse(fs11.readFileSync(apichatsPath(), "utf-8"));
|
|
8829
9301
|
return Array.isArray(list) ? list : [];
|
|
8830
9302
|
} catch {
|
|
8831
9303
|
return [];
|
|
8832
9304
|
}
|
|
8833
9305
|
}
|
|
8834
9306
|
function writeApichats(list) {
|
|
8835
|
-
|
|
8836
|
-
|
|
9307
|
+
fs11.mkdirSync(getApiblazeDir(), { recursive: true });
|
|
9308
|
+
fs11.writeFileSync(apichatsPath(), JSON.stringify(list, null, 2), "utf-8");
|
|
8837
9309
|
try {
|
|
8838
|
-
|
|
9310
|
+
fs11.chmodSync(apichatsPath(), 384);
|
|
8839
9311
|
} catch {
|
|
8840
9312
|
}
|
|
8841
9313
|
}
|
|
@@ -8849,6 +9321,16 @@ function upsertApichat(entry) {
|
|
|
8849
9321
|
else list.unshift(entry);
|
|
8850
9322
|
writeApichats(list.slice(0, 30));
|
|
8851
9323
|
}
|
|
9324
|
+
function persistAuthState(p) {
|
|
9325
|
+
const list = loadApichats();
|
|
9326
|
+
const i = list.findIndex((a) => a.projectId === p.projectId && a.version === p.version);
|
|
9327
|
+
if (i < 0) return;
|
|
9328
|
+
list[i].dpKey = p.dpKey;
|
|
9329
|
+
list[i].consumerAuth = p.consumerAuth;
|
|
9330
|
+
list[i].endUserId = p.endUserId;
|
|
9331
|
+
list[i].updatedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
9332
|
+
writeApichats(list);
|
|
9333
|
+
}
|
|
8852
9334
|
function saveTranscript(p, messages) {
|
|
8853
9335
|
const list = loadApichats();
|
|
8854
9336
|
const i = list.findIndex((a) => apichatKey(a) === apichatKey(p));
|
|
@@ -8898,6 +9380,57 @@ async function fetchProxyDoor(teamId, projectId, apiVersion) {
|
|
|
8898
9380
|
return { methods: null };
|
|
8899
9381
|
}
|
|
8900
9382
|
}
|
|
9383
|
+
async function openDirectProject(projectId, opts) {
|
|
9384
|
+
const saved = loadApichats().find((a) => a.projectId === projectId);
|
|
9385
|
+
const version2 = opts.apiversion || saved?.version || "1.0.0";
|
|
9386
|
+
const environment = opts.environment || saved?.environment || "prod";
|
|
9387
|
+
const anon = saved?.anon ?? !loadCredentials();
|
|
9388
|
+
const mcpHost = saved?.mcpHost || `${projectId}.mcp.${anon ? "tryabz" : "abz"}.run`;
|
|
9389
|
+
const p = {
|
|
9390
|
+
projectId,
|
|
9391
|
+
version: version2,
|
|
9392
|
+
environment,
|
|
9393
|
+
dpKey: opts.apikey ?? saved?.dpKey,
|
|
9394
|
+
consumerAuth: opts.apikey ? false : saved?.consumerAuth,
|
|
9395
|
+
mcpHost,
|
|
9396
|
+
proxyUrl: `https://${projectId}.${anon ? "tryabz" : "abz"}.run/${version2}/${environment}`,
|
|
9397
|
+
anon,
|
|
9398
|
+
access: "invite",
|
|
9399
|
+
tenant: saved?.tenant,
|
|
9400
|
+
teamId: saved?.teamId,
|
|
9401
|
+
endUserId: opts.xenduserid ?? saved?.endUserId
|
|
9402
|
+
};
|
|
9403
|
+
if (!p.dpKey && !p.consumerAuth) {
|
|
9404
|
+
if (process.stdin.isTTY) {
|
|
9405
|
+
const { default: inquirer3 } = await import("inquirer");
|
|
9406
|
+
const { key } = await inquirer3.prompt([{
|
|
9407
|
+
type: "password",
|
|
9408
|
+
name: "key",
|
|
9409
|
+
mask: "*",
|
|
9410
|
+
message: `API key for ${projectId} (leave empty if it uses a login):`
|
|
9411
|
+
}]);
|
|
9412
|
+
if (typeof key === "string" && key.trim()) p.dpKey = key.trim();
|
|
9413
|
+
else p.consumerAuth = true;
|
|
9414
|
+
}
|
|
9415
|
+
}
|
|
9416
|
+
console.log(` ${import_chalk47.default.dim("Proxy:")} ${import_chalk47.default.bold(p.proxyUrl)}`);
|
|
9417
|
+
if (p.endUserId) console.log(` ${import_chalk47.default.dim("Acting as:")} ${import_chalk47.default.bold(p.endUserId)}`);
|
|
9418
|
+
upsertApichat({
|
|
9419
|
+
name: projectId,
|
|
9420
|
+
target: p.proxyUrl,
|
|
9421
|
+
projectId,
|
|
9422
|
+
version: version2,
|
|
9423
|
+
environment,
|
|
9424
|
+
mcpHost,
|
|
9425
|
+
teamId: p.teamId,
|
|
9426
|
+
tenant: p.tenant,
|
|
9427
|
+
dpKey: p.dpKey,
|
|
9428
|
+
consumerAuth: p.consumerAuth,
|
|
9429
|
+
anon,
|
|
9430
|
+
endUserId: p.endUserId
|
|
9431
|
+
});
|
|
9432
|
+
return { p, messages: saved?.messages?.filter((m) => Array.isArray(m.parts)) ?? [] };
|
|
9433
|
+
}
|
|
8901
9434
|
async function openServerProxy(project) {
|
|
8902
9435
|
const version2 = project.apiVersion || "1.0.0";
|
|
8903
9436
|
const environment = "prod";
|
|
@@ -8911,9 +9444,9 @@ async function openServerProxy(project) {
|
|
|
8911
9444
|
let consumerAuth = false;
|
|
8912
9445
|
if (acceptsApiKey) {
|
|
8913
9446
|
if (!dpKey) {
|
|
8914
|
-
console.log(
|
|
9447
|
+
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`));
|
|
8915
9448
|
dpKey = await mintDurableProxyKey(project.teamId, tenant2);
|
|
8916
|
-
console.log(` ${
|
|
9449
|
+
console.log(` ${import_chalk47.default.green("\u2714")} API key: ${import_chalk47.default.dim(maskKey(dpKey))}`);
|
|
8917
9450
|
}
|
|
8918
9451
|
} else {
|
|
8919
9452
|
consumerAuth = true;
|
|
@@ -8942,9 +9475,9 @@ async function openServerProxy(project) {
|
|
|
8942
9475
|
const spec2 = raw && (raw.spec ?? raw);
|
|
8943
9476
|
spinner.stop();
|
|
8944
9477
|
if (!spec2 || !(spec2.paths || spec2.openapi)) {
|
|
8945
|
-
console.log(
|
|
9478
|
+
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`."));
|
|
8946
9479
|
} else {
|
|
8947
|
-
console.log(
|
|
9480
|
+
console.log(import_chalk47.default.dim(" Using the proxy's existing MCP catalogue (`apiblaze mcp` to rebuild it)."));
|
|
8948
9481
|
}
|
|
8949
9482
|
} catch (err) {
|
|
8950
9483
|
spinner.fail("Could not open the proxy.");
|
|
@@ -8976,15 +9509,15 @@ function discoverLocalSpecs() {
|
|
|
8976
9509
|
const found = [];
|
|
8977
9510
|
for (const n of known) {
|
|
8978
9511
|
try {
|
|
8979
|
-
if (
|
|
9512
|
+
if (fs11.statSync(path7.join(cwd, n)).isFile()) found.push(n);
|
|
8980
9513
|
} catch {
|
|
8981
9514
|
}
|
|
8982
9515
|
}
|
|
8983
9516
|
try {
|
|
8984
|
-
const files =
|
|
9517
|
+
const files = fs11.readdirSync(cwd).filter((f) => /\.(ya?ml|json)$/i.test(f) && !found.includes(f));
|
|
8985
9518
|
for (const f of files.slice(0, 60)) {
|
|
8986
9519
|
try {
|
|
8987
|
-
const head =
|
|
9520
|
+
const head = fs11.readFileSync(path7.join(cwd, f), "utf-8").slice(0, 4e3);
|
|
8988
9521
|
if (/["']?openapi["']?\s*:/i.test(head) || /["']?swagger["']?\s*:/i.test(head) || /^\s*paths\s*:/im.test(head) || /"paths"\s*:/.test(head)) {
|
|
8989
9522
|
found.push(f);
|
|
8990
9523
|
}
|
|
@@ -9000,7 +9533,7 @@ async function noArgsMenu(opts) {
|
|
|
9000
9533
|
const me = loadCredentials()?.apiblazeUserId;
|
|
9001
9534
|
const saved = loadApichats().filter((a) => a.anon ? true : a.ownerUserId !== void 0 && a.ownerUserId === me);
|
|
9002
9535
|
const choices = saved.map((a) => ({
|
|
9003
|
-
name: `Chat with ${
|
|
9536
|
+
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` : ""}`)}`,
|
|
9004
9537
|
value: { type: "existing", a }
|
|
9005
9538
|
}));
|
|
9006
9539
|
const creds = loadCredentials();
|
|
@@ -9010,14 +9543,14 @@ async function noArgsMenu(opts) {
|
|
|
9010
9543
|
const proxies = (await getProjects(creds.teamId)).filter((pr) => !savedIds.has(pr.projectId));
|
|
9011
9544
|
for (const pr of proxies) {
|
|
9012
9545
|
choices.push({
|
|
9013
|
-
name: `Chat with ${
|
|
9546
|
+
name: `Chat with ${import_chalk47.default.bold(pr.projectName)} ${import_chalk47.default.dim(`(v${pr.apiVersion}) \xB7 your proxy`)}`,
|
|
9014
9547
|
value: { type: "server", project: pr }
|
|
9015
9548
|
});
|
|
9016
9549
|
}
|
|
9017
9550
|
} catch {
|
|
9018
9551
|
}
|
|
9019
9552
|
}
|
|
9020
|
-
choices.push({ name:
|
|
9553
|
+
choices.push({ name: import_chalk47.default.green("\uFF0B Create a new apichat"), value: { type: "new" } });
|
|
9021
9554
|
const { pick: pick2 } = await inquirer3.prompt([
|
|
9022
9555
|
{ type: "list", name: "pick", message: "What would you like to do?", choices }
|
|
9023
9556
|
]);
|
|
@@ -9119,37 +9652,37 @@ async function noArgsMenu(opts) {
|
|
|
9119
9652
|
}
|
|
9120
9653
|
async function runRepl(p, initialMessages) {
|
|
9121
9654
|
const { default: inquirer3 } = await import("inquirer");
|
|
9122
|
-
const messages = initialMessages
|
|
9123
|
-
console.log("\n" +
|
|
9124
|
-
if (messages.length) console.log(
|
|
9655
|
+
const messages = (initialMessages ?? []).filter((m) => Array.isArray(m.parts));
|
|
9656
|
+
console.log("\n" + import_chalk47.default.cyan.bold("Chat with your API") + import_chalk47.default.dim(` \xB7 ${p.mcpHost}`));
|
|
9657
|
+
if (messages.length) console.log(import_chalk47.default.dim(` Resumed \u2014 ${messages.length} prior messages.`));
|
|
9125
9658
|
const llm2 = loadLlmConfig();
|
|
9126
9659
|
console.log(
|
|
9127
|
-
|
|
9660
|
+
import_chalk47.default.dim(
|
|
9128
9661
|
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."
|
|
9129
9662
|
)
|
|
9130
9663
|
);
|
|
9131
9664
|
for (; ; ) {
|
|
9132
|
-
const { input } = await inquirer3.prompt([{ type: "input", name: "input", message:
|
|
9665
|
+
const { input } = await inquirer3.prompt([{ type: "input", name: "input", message: import_chalk47.default.green("you \u203A") }]);
|
|
9133
9666
|
const text = (input ?? "").trim();
|
|
9134
9667
|
if (!text) continue;
|
|
9135
9668
|
if (["/exit", "/quit", "exit", "quit", ":q"].includes(text.toLowerCase())) break;
|
|
9136
9669
|
if (text === "/login") {
|
|
9137
9670
|
try {
|
|
9138
9671
|
await runLogin();
|
|
9139
|
-
console.log(
|
|
9672
|
+
console.log(import_chalk47.default.dim(" Logged in \u2014 history preserved. Keep chatting."));
|
|
9140
9673
|
} catch (err) {
|
|
9141
|
-
console.log(
|
|
9674
|
+
console.log(import_chalk47.default.red(` Login failed: ${err instanceof Error ? err.message : String(err)}`));
|
|
9142
9675
|
}
|
|
9143
9676
|
continue;
|
|
9144
9677
|
}
|
|
9145
9678
|
if (text === "/claim") {
|
|
9146
9679
|
const justLoggedIn = !loadCredentials();
|
|
9147
9680
|
if (justLoggedIn) {
|
|
9148
|
-
console.log(
|
|
9681
|
+
console.log(import_chalk47.default.dim(" Logging in to claim your workspace\u2026"));
|
|
9149
9682
|
try {
|
|
9150
9683
|
await runLogin();
|
|
9151
9684
|
} catch (err) {
|
|
9152
|
-
console.log(
|
|
9685
|
+
console.log(import_chalk47.default.red(` Login failed: ${err instanceof Error ? err.message : String(err)}`));
|
|
9153
9686
|
continue;
|
|
9154
9687
|
}
|
|
9155
9688
|
if (!loadCredentials()) continue;
|
|
@@ -9160,43 +9693,123 @@ async function runRepl(p, initialMessages) {
|
|
|
9160
9693
|
p.mcpHost = p.mcpHost.replace(".mcp.tryabz.run", ".mcp.abz.run");
|
|
9161
9694
|
p.anon = false;
|
|
9162
9695
|
claimApichat(p, loadCredentials()?.apiblazeUserId);
|
|
9163
|
-
console.log(
|
|
9696
|
+
console.log(import_chalk47.default.dim(` Workspace claimed \u2014 chat now routes on ${p.mcpHost}. History preserved.`));
|
|
9164
9697
|
}
|
|
9165
9698
|
} catch (err) {
|
|
9166
|
-
console.log(
|
|
9699
|
+
console.log(import_chalk47.default.red(` Claim failed: ${err instanceof Error ? err.message : String(err)}`));
|
|
9167
9700
|
}
|
|
9168
9701
|
continue;
|
|
9169
9702
|
}
|
|
9170
9703
|
if (text === "/showauth") {
|
|
9171
9704
|
revealAuth = !revealAuth;
|
|
9172
|
-
console.log(
|
|
9705
|
+
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."));
|
|
9173
9706
|
continue;
|
|
9174
9707
|
}
|
|
9175
9708
|
if (text.startsWith("/")) {
|
|
9176
|
-
console.log(
|
|
9709
|
+
console.log(import_chalk47.default.dim(" Commands: /login /claim /showauth /exit"));
|
|
9177
9710
|
continue;
|
|
9178
9711
|
}
|
|
9179
9712
|
await replTurn(p, messages, text);
|
|
9180
9713
|
saveTranscript(p, messages);
|
|
9181
9714
|
}
|
|
9182
|
-
console.log(
|
|
9715
|
+
console.log(import_chalk47.default.dim("\nBye."));
|
|
9716
|
+
}
|
|
9717
|
+
function rememberCliOffer(projectId, cli, state) {
|
|
9718
|
+
const list = loadApichats();
|
|
9719
|
+
const i = list.findIndex((a) => a.projectId === projectId);
|
|
9720
|
+
if (i < 0) return;
|
|
9721
|
+
list[i].cliOffer = { ...list[i].cliOffer ?? {}, [cli]: state };
|
|
9722
|
+
list[i].updatedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
9723
|
+
writeApichats(list);
|
|
9724
|
+
}
|
|
9725
|
+
async function maybeInstallExternalCli(p, opts) {
|
|
9726
|
+
const spec2 = {
|
|
9727
|
+
name: p.projectId,
|
|
9728
|
+
url: `https://${p.mcpHost}/${p.version}/${p.environment}`,
|
|
9729
|
+
// consumerAuth means the door is a login — install bare, the CLI signs in.
|
|
9730
|
+
apiKey: p.consumerAuth ? void 0 : p.dpKey,
|
|
9731
|
+
projectLabel: p.projectId
|
|
9732
|
+
};
|
|
9733
|
+
const clis = detectExternalClis();
|
|
9734
|
+
if (opts.installMcp) {
|
|
9735
|
+
const want = opts.installMcp.toLowerCase();
|
|
9736
|
+
if (want !== "claude" && want !== "codex") fail4(`--install-mcp takes "claude" or "codex", not "${opts.installMcp}".`);
|
|
9737
|
+
const cli = clis.find((c) => c.kind === want);
|
|
9738
|
+
if (!cli) fail4(
|
|
9739
|
+
`${want === "claude" ? "Claude" : "Codex"} CLI not found on this machine.`,
|
|
9740
|
+
want === "claude" ? "Install it: npm install -g @anthropic-ai/claude-code" : "Install it: npm install -g @openai/codex"
|
|
9741
|
+
);
|
|
9742
|
+
const question2 = opts.prompt ?? (process.stdin.isTTY ? await askApiQuestion() : void 0);
|
|
9743
|
+
const ran2 = installAndDemo(cli, spec2, question2);
|
|
9744
|
+
if (ran2) rememberCliOffer(p.projectId, cli.kind, "installed");
|
|
9745
|
+
return ran2;
|
|
9746
|
+
}
|
|
9747
|
+
if (!process.stdin.isTTY || clis.length === 0) return false;
|
|
9748
|
+
const offer = loadApichats().find((a) => a.projectId === p.projectId)?.cliOffer ?? {};
|
|
9749
|
+
const fresh = clis.filter((c) => !offer[c.kind]);
|
|
9750
|
+
if (fresh.length === 0) return false;
|
|
9751
|
+
const { default: inquirer3 } = await import("inquirer");
|
|
9752
|
+
const names = fresh.map((c) => c.label).join(" and ");
|
|
9753
|
+
const { pick: pick2 } = await inquirer3.prompt([{
|
|
9754
|
+
type: "list",
|
|
9755
|
+
name: "pick",
|
|
9756
|
+
message: `I see ${names} ${fresh.length > 1 ? "are" : "is"} installed on this computer. Add the MCP for this proxy so you can chat with your API from there directly?`,
|
|
9757
|
+
choices: [
|
|
9758
|
+
...fresh.map((c) => ({ name: `Yes \u2014 add it to ${c.label}`, value: c })),
|
|
9759
|
+
{ name: "No \u2014 chat here instead", value: "no" }
|
|
9760
|
+
]
|
|
9761
|
+
}]);
|
|
9762
|
+
if (pick2 === "no") {
|
|
9763
|
+
for (const c of fresh) rememberCliOffer(p.projectId, c.kind, "declined");
|
|
9764
|
+
return false;
|
|
9765
|
+
}
|
|
9766
|
+
const question = opts.prompt ?? await askApiQuestion();
|
|
9767
|
+
const ran = installAndDemo(pick2, spec2, question);
|
|
9768
|
+
if (ran) rememberCliOffer(p.projectId, pick2.kind, "installed");
|
|
9769
|
+
return ran;
|
|
9770
|
+
}
|
|
9771
|
+
async function askApiQuestion() {
|
|
9772
|
+
const { default: inquirer3 } = await import("inquirer");
|
|
9773
|
+
const { q } = await inquirer3.prompt([{
|
|
9774
|
+
type: "input",
|
|
9775
|
+
name: "q",
|
|
9776
|
+
message: "What question do you have for this API?"
|
|
9777
|
+
}]);
|
|
9778
|
+
const t = (q ?? "").trim();
|
|
9779
|
+
return t || void 0;
|
|
9183
9780
|
}
|
|
9184
9781
|
async function runApichat(opts) {
|
|
9185
9782
|
setVerbose(opts.verbose !== false);
|
|
9186
|
-
console.log(
|
|
9783
|
+
console.log(import_chalk47.default.bold("\napichat \u2014 turn any API into a chat\n"));
|
|
9784
|
+
if (opts.target && !opts.openapispec) {
|
|
9785
|
+
const { classifyTargetInput: classifyTargetInput2 } = await Promise.resolve().then(() => (init_spec_or_target(), spec_or_target_exports));
|
|
9786
|
+
const c = await classifyTargetInput2(opts.target, fail4);
|
|
9787
|
+
if (c.kind === "spec") {
|
|
9788
|
+
console.log(import_chalk47.default.dim(` --target is an OpenAPI document (${c.source}) \u2014 using it as the spec.`));
|
|
9789
|
+
opts.openapispec = opts.target;
|
|
9790
|
+
opts.target = void 0;
|
|
9791
|
+
}
|
|
9792
|
+
}
|
|
9793
|
+
if (opts.project) {
|
|
9794
|
+
const opened = await openDirectProject(opts.project, opts);
|
|
9795
|
+
if (await maybeInstallExternalCli(opened.p, opts)) return;
|
|
9796
|
+
await runRepl(opened.p, opened.messages);
|
|
9797
|
+
return;
|
|
9798
|
+
}
|
|
9187
9799
|
if (!opts.openapispec && !opts.target) {
|
|
9188
9800
|
if (!process.stdin.isTTY) {
|
|
9189
|
-
fail4("No spec source. Pass --
|
|
9801
|
+
fail4("No spec source. Pass --target <server-url | openapi-file | openapi-url>.", GENERATOR_HINT);
|
|
9190
9802
|
}
|
|
9191
9803
|
const resumed = await noArgsMenu(opts);
|
|
9192
9804
|
if (resumed) {
|
|
9805
|
+
if (await maybeInstallExternalCli(resumed.p, opts)) return;
|
|
9193
9806
|
await runRepl(resumed.p, resumed.messages);
|
|
9194
9807
|
return;
|
|
9195
9808
|
}
|
|
9196
9809
|
}
|
|
9197
9810
|
const { spec: spec2, sourceUrl } = await loadSpec(opts);
|
|
9198
9811
|
const target = resolveTarget(spec2, opts, sourceUrl);
|
|
9199
|
-
console.log(` ${
|
|
9812
|
+
console.log(` ${import_chalk47.default.dim("Target:")} ${import_chalk47.default.bold(target)}`);
|
|
9200
9813
|
const auth = await resolveTargetAuth(spec2, opts);
|
|
9201
9814
|
if (auth && !process.stdin.isTTY && !opts.targetAuthEnv) {
|
|
9202
9815
|
fail4(
|
|
@@ -9205,7 +9818,7 @@ async function runApichat(opts) {
|
|
|
9205
9818
|
);
|
|
9206
9819
|
}
|
|
9207
9820
|
const p = await provision(spec2, target, opts);
|
|
9208
|
-
console.log(` ${
|
|
9821
|
+
console.log(` ${import_chalk47.default.dim("Proxy: ")} ${import_chalk47.default.bold(p.proxyUrl || `${p.projectId} v${p.version}`)}`);
|
|
9209
9822
|
upsertApichat({
|
|
9210
9823
|
name: p.projectId,
|
|
9211
9824
|
target,
|
|
@@ -9225,30 +9838,31 @@ async function runApichat(opts) {
|
|
|
9225
9838
|
const secret = await captureTargetSecret(auth, opts);
|
|
9226
9839
|
if (secret) await writeTargetAuth(p, auth, secret);
|
|
9227
9840
|
} else {
|
|
9228
|
-
console.log(
|
|
9841
|
+
console.log(import_chalk47.default.dim(" Target auth: none required."));
|
|
9229
9842
|
}
|
|
9230
9843
|
const specText = JSON.stringify(spec2);
|
|
9231
9844
|
await uploadSpec(p, specText, opts);
|
|
9232
9845
|
const mcpUrl = await publishMcp(p, spec2);
|
|
9233
9846
|
console.log();
|
|
9234
|
-
if (p.proxyUrl) console.log(` ${
|
|
9847
|
+
if (p.proxyUrl) console.log(` ${import_chalk47.default.green("\u2713")} proxy ${import_chalk47.default.bold(p.proxyUrl)}`);
|
|
9235
9848
|
if (mcpUrl) {
|
|
9236
|
-
console.log(` ${
|
|
9849
|
+
console.log(` ${import_chalk47.default.green("\u2713")} mcp ${import_chalk47.default.bold(mcpUrl)}`);
|
|
9237
9850
|
if (p.access === "invite") {
|
|
9238
|
-
console.log(
|
|
9239
|
-
console.log(
|
|
9851
|
+
console.log(import_chalk47.default.dim(" Claude/ChatGPT-connectable (GitHub sign-in) \xB7 access: invite \u2014 only you + emails you pre-approve"));
|
|
9852
|
+
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)`));
|
|
9240
9853
|
} else {
|
|
9241
|
-
console.log(
|
|
9854
|
+
console.log(import_chalk47.default.dim(" Claude/ChatGPT-connectable (GitHub sign-in) \xB7 access: open \u2014 anyone who signs in can call this API"));
|
|
9242
9855
|
}
|
|
9243
9856
|
}
|
|
9244
9857
|
if (p.anon) {
|
|
9245
|
-
console.log(
|
|
9858
|
+
console.log(import_chalk47.default.dim("\n Anonymous workspace \u2014 /claim inside the chat to log in and keep it beyond 30 days."));
|
|
9246
9859
|
}
|
|
9860
|
+
if (await maybeInstallExternalCli(p, opts)) return;
|
|
9247
9861
|
await runRepl(p);
|
|
9248
9862
|
}
|
|
9249
9863
|
|
|
9250
9864
|
// src/commands/consumer.ts
|
|
9251
|
-
var
|
|
9865
|
+
var import_chalk48 = __toESM(require("chalk"));
|
|
9252
9866
|
var import_ora24 = __toESM(require("ora"));
|
|
9253
9867
|
init_admin();
|
|
9254
9868
|
init_resolve();
|
|
@@ -9270,7 +9884,7 @@ async function consumerFetch(creds, suffix, init) {
|
|
|
9270
9884
|
function requireConsumer2() {
|
|
9271
9885
|
const c = loadConsumer();
|
|
9272
9886
|
if (!c) {
|
|
9273
|
-
console.error(
|
|
9887
|
+
console.error(import_chalk48.default.red("Not logged in as a consumer. Run `apiblaze consumer login` first."));
|
|
9274
9888
|
process.exit(1);
|
|
9275
9889
|
}
|
|
9276
9890
|
return c;
|
|
@@ -9281,7 +9895,7 @@ async function runConsumerLogin(opts) {
|
|
|
9281
9895
|
let clientId = opts.client;
|
|
9282
9896
|
if (clientId) {
|
|
9283
9897
|
if (!tenant2) {
|
|
9284
|
-
console.error(
|
|
9898
|
+
console.error(import_chalk48.default.red("When using --client, also pass --tenant <slug> (it sets which portal/keys host to use)."));
|
|
9285
9899
|
process.exit(1);
|
|
9286
9900
|
}
|
|
9287
9901
|
} else {
|
|
@@ -9299,18 +9913,18 @@ async function runConsumerLogin(opts) {
|
|
|
9299
9913
|
const usable = (Array.isArray(clients) ? clients : []).filter((c) => c && (c.client_id || c.clientId));
|
|
9300
9914
|
const pick2 = usable.find((c) => c.is_default || c.default) ?? usable.find((c) => c.verified !== false) ?? usable[0];
|
|
9301
9915
|
if (!pick2) {
|
|
9302
|
-
console.error(
|
|
9916
|
+
console.error(import_chalk48.default.red(`Tenant "${tenant2}" has no login app configured. Set one up in the dashboard (or \`apiblaze create\` with auth).`));
|
|
9303
9917
|
process.exit(1);
|
|
9304
9918
|
}
|
|
9305
9919
|
clientId = pick2.client_id ?? pick2.clientId;
|
|
9306
9920
|
}
|
|
9307
|
-
console.log(`${
|
|
9921
|
+
console.log(`${import_chalk48.default.cyan("\u2192")} Logging in to ${import_chalk48.default.bold(tenant2)} as a consumer...`);
|
|
9308
9922
|
const result = await deviceLogin(clientId, DEFAULT_SCOPE, ({ verificationUri, userCode }) => {
|
|
9309
9923
|
console.log(`
|
|
9310
|
-
Open: ${
|
|
9311
|
-
console.log(` Code: ${
|
|
9924
|
+
Open: ${import_chalk48.default.underline(verificationUri)}`);
|
|
9925
|
+
console.log(` Code: ${import_chalk48.default.bold(userCode)}
|
|
9312
9926
|
`);
|
|
9313
|
-
console.log(
|
|
9927
|
+
console.log(import_chalk48.default.dim(" (opening your browser\u2026 waiting for you to finish)"));
|
|
9314
9928
|
});
|
|
9315
9929
|
const claims = result.idToken && decodeJwt2(result.idToken) || (decodeJwt2(result.accessToken) ?? {});
|
|
9316
9930
|
const creds = {
|
|
@@ -9325,7 +9939,7 @@ async function runConsumerLogin(opts) {
|
|
|
9325
9939
|
obtainedAt: Date.now()
|
|
9326
9940
|
};
|
|
9327
9941
|
saveConsumer(creds);
|
|
9328
|
-
console.log(
|
|
9942
|
+
console.log(import_chalk48.default.green(`\u2714 Logged in as consumer${creds.email ? ` ${creds.email}` : ""} on ${tenant2}.`));
|
|
9329
9943
|
}
|
|
9330
9944
|
async function runConsumerTokens(opts) {
|
|
9331
9945
|
const creds = requireConsumer2();
|
|
@@ -9338,18 +9952,18 @@ async function runConsumerTokens(opts) {
|
|
|
9338
9952
|
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));
|
|
9339
9953
|
return;
|
|
9340
9954
|
}
|
|
9341
|
-
console.log(`${
|
|
9955
|
+
console.log(`${import_chalk48.default.cyan("Consumer")} ${import_chalk48.default.bold(fresh.email ?? fresh.tenant)} on ${import_chalk48.default.bold(fresh.tenant)}
|
|
9342
9956
|
`);
|
|
9343
|
-
console.log(`${
|
|
9957
|
+
console.log(`${import_chalk48.default.bold("access_token")} ${import_chalk48.default.dim("exp " + (exp(fresh.accessToken) ?? "?"))}
|
|
9344
9958
|
${fresh.accessToken}
|
|
9345
9959
|
`);
|
|
9346
|
-
if (fresh.idToken) console.log(`${
|
|
9960
|
+
if (fresh.idToken) console.log(`${import_chalk48.default.bold("id_token")} ${import_chalk48.default.dim("exp " + (exp(fresh.idToken) ?? "?"))}
|
|
9347
9961
|
${fresh.idToken}
|
|
9348
9962
|
`);
|
|
9349
|
-
if (fresh.refreshToken) console.log(`${
|
|
9963
|
+
if (fresh.refreshToken) console.log(`${import_chalk48.default.bold("refresh_token")}
|
|
9350
9964
|
${fresh.refreshToken}
|
|
9351
9965
|
`);
|
|
9352
|
-
console.log(
|
|
9966
|
+
console.log(import_chalk48.default.dim("These are your own tokens \u2014 keep them secret."));
|
|
9353
9967
|
}
|
|
9354
9968
|
async function runConsumerApikeys(opts) {
|
|
9355
9969
|
const creds = requireConsumer2();
|
|
@@ -9359,8 +9973,8 @@ async function runConsumerApikeys(opts) {
|
|
|
9359
9973
|
const revealed = await consumerFetch(list.creds, "/apikeys/reveal").catch(() => ({ status: 0, data: null, creds: list.creds }));
|
|
9360
9974
|
spinner.stop();
|
|
9361
9975
|
if (list.status >= 400) {
|
|
9362
|
-
console.error(
|
|
9363
|
-
if (list.status === 401) console.error(
|
|
9976
|
+
console.error(import_chalk48.default.red(`Failed to list keys (${list.status}): ${list.data?.error ?? ""}`));
|
|
9977
|
+
if (list.status === 401) console.error(import_chalk48.default.dim("Your consumer session may have expired \u2014 run `apiblaze consumer login` again."));
|
|
9364
9978
|
process.exit(1);
|
|
9365
9979
|
}
|
|
9366
9980
|
const keys = list.data?.keys ?? [];
|
|
@@ -9368,16 +9982,16 @@ async function runConsumerApikeys(opts) {
|
|
|
9368
9982
|
if (opts.json) {
|
|
9369
9983
|
console.log(JSON.stringify({ keys, revealed: revealMap }, null, 2));
|
|
9370
9984
|
} else if (!keys.length) {
|
|
9371
|
-
console.log(
|
|
9985
|
+
console.log(import_chalk48.default.yellow("No API keys yet."));
|
|
9372
9986
|
} else {
|
|
9373
9987
|
for (const k of keys) {
|
|
9374
9988
|
const clear = revealMap[k.environment]?.key;
|
|
9375
|
-
const shown = clear ?
|
|
9376
|
-
const exp = k.expires_at ?
|
|
9377
|
-
console.log(` ${
|
|
9989
|
+
const shown = clear ? import_chalk48.default.green(clear) : import_chalk48.default.dim(`${k.key_prefix ?? ""}\u2026${k.key_suffix ?? ""}`);
|
|
9990
|
+
const exp = k.expires_at ? import_chalk48.default.dim(`exp ${k.expires_at}`) : import_chalk48.default.dim("no expiry");
|
|
9991
|
+
console.log(` ${import_chalk48.default.bold(k.environment ?? "")} ${shown} ${exp} ${import_chalk48.default.dim(k.description ?? "")}`);
|
|
9378
9992
|
}
|
|
9379
9993
|
if (Object.keys(revealMap).length === 0 && keys.some((k) => !k.expires_at)) {
|
|
9380
|
-
console.log(
|
|
9994
|
+
console.log(import_chalk48.default.dim("\n(Only expiring keys can be shown in clear; non-expiring keys show a prefix only.)"));
|
|
9381
9995
|
}
|
|
9382
9996
|
}
|
|
9383
9997
|
if (opts.json) return;
|
|
@@ -9399,35 +10013,35 @@ async function runConsumerApikeys(opts) {
|
|
|
9399
10013
|
}
|
|
9400
10014
|
s2.succeed("Key created.");
|
|
9401
10015
|
const key = created.data?.key ?? created.data?.fullKey;
|
|
9402
|
-
if (key) console.log(` ${
|
|
9403
|
-
else console.log(
|
|
10016
|
+
if (key) console.log(` ${import_chalk48.default.green(key)} ${import_chalk48.default.dim("(shown once \u2014 store it now)")}`);
|
|
10017
|
+
else console.log(import_chalk48.default.dim(" Key created; run `apiblaze consumer apikeys` to reveal it if it expires."));
|
|
9404
10018
|
}
|
|
9405
10019
|
|
|
9406
10020
|
// src/commands/sidecar.ts
|
|
9407
|
-
var
|
|
10021
|
+
var import_chalk49 = __toESM(require("chalk"));
|
|
9408
10022
|
var import_ora25 = __toESM(require("ora"));
|
|
9409
|
-
var
|
|
9410
|
-
var
|
|
10023
|
+
var fs12 = __toESM(require("fs"));
|
|
10024
|
+
var path8 = __toESM(require("path"));
|
|
9411
10025
|
init_admin();
|
|
9412
10026
|
init_resolve();
|
|
9413
10027
|
init_auth();
|
|
9414
10028
|
function detectNextProject(root) {
|
|
9415
|
-
const hasConfig = ["next.config.js", "next.config.mjs", "next.config.ts"].some((f) =>
|
|
10029
|
+
const hasConfig = ["next.config.js", "next.config.mjs", "next.config.ts"].some((f) => fs12.existsSync(path8.join(root, f)));
|
|
9416
10030
|
let hasDep = false;
|
|
9417
10031
|
try {
|
|
9418
|
-
const pkg = JSON.parse(
|
|
10032
|
+
const pkg = JSON.parse(fs12.readFileSync(path8.join(root, "package.json"), "utf8"));
|
|
9419
10033
|
hasDep = !!(pkg.dependencies?.next || pkg.devDependencies?.next);
|
|
9420
10034
|
} catch {
|
|
9421
10035
|
}
|
|
9422
|
-
const appDir =
|
|
9423
|
-
const pagesDir =
|
|
10036
|
+
const appDir = fs12.existsSync(path8.join(root, "app")) || fs12.existsSync(path8.join(root, "src", "app"));
|
|
10037
|
+
const pagesDir = fs12.existsSync(path8.join(root, "pages")) || fs12.existsSync(path8.join(root, "src", "pages"));
|
|
9424
10038
|
return { found: hasConfig || hasDep || appDir || pagesDir, router: appDir ? "app" : pagesDir ? "pages" : null };
|
|
9425
10039
|
}
|
|
9426
10040
|
function upsertEnvLocal(root, token) {
|
|
9427
|
-
const p =
|
|
10041
|
+
const p = path8.join(root, ".env.local");
|
|
9428
10042
|
let existing = "";
|
|
9429
10043
|
try {
|
|
9430
|
-
existing =
|
|
10044
|
+
existing = fs12.readFileSync(p, "utf8");
|
|
9431
10045
|
} catch {
|
|
9432
10046
|
}
|
|
9433
10047
|
const had = /^APIBLAZE_API_KEY=/m.test(existing) || /^APIBLAZE_TOKEN=/m.test(existing);
|
|
@@ -9442,15 +10056,15 @@ function upsertEnvLocal(root, token) {
|
|
|
9442
10056
|
next = (next.endsWith("\n") ? next : next + "\n") + `APIBLAZE_SIDECAR_VERBOSE=true
|
|
9443
10057
|
`;
|
|
9444
10058
|
}
|
|
9445
|
-
|
|
10059
|
+
fs12.writeFileSync(p, next);
|
|
9446
10060
|
return had ? "rotated" : "created";
|
|
9447
10061
|
}
|
|
9448
10062
|
function installSidecarPackage(root) {
|
|
9449
|
-
if (
|
|
9450
|
-
console.log(` ${
|
|
10063
|
+
if (fs12.existsSync(path8.join(root, "node_modules", "apiblaze", "package.json"))) {
|
|
10064
|
+
console.log(` ${import_chalk49.default.green("\u2713")} apiblaze package already installed`);
|
|
9451
10065
|
return;
|
|
9452
10066
|
}
|
|
9453
|
-
const has = (f) =>
|
|
10067
|
+
const has = (f) => fs12.existsSync(path8.join(root, f));
|
|
9454
10068
|
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" };
|
|
9455
10069
|
const spinner = (0, import_ora25.default)(`Installing the apiblaze package (${pm.cmd})\u2026`).start();
|
|
9456
10070
|
try {
|
|
@@ -9458,12 +10072,12 @@ function installSidecarPackage(root) {
|
|
|
9458
10072
|
execSync(`${pm.cmd} ${pm.add} apiblaze`, { cwd: root, stdio: "ignore" });
|
|
9459
10073
|
spinner.succeed("Installed apiblaze (the sidecar runtime).");
|
|
9460
10074
|
} catch {
|
|
9461
|
-
spinner.warn(`Couldn't auto-install \u2014 run ${
|
|
10075
|
+
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")}.`);
|
|
9462
10076
|
}
|
|
9463
10077
|
}
|
|
9464
10078
|
function readEnvKey(root) {
|
|
9465
10079
|
try {
|
|
9466
|
-
const s =
|
|
10080
|
+
const s = fs12.readFileSync(path8.join(root, ".env.local"), "utf8");
|
|
9467
10081
|
const m = s.match(/^APIBLAZE_API_KEY=(.+)$/m) ?? s.match(/^APIBLAZE_TOKEN=(.+)$/m);
|
|
9468
10082
|
return m ? m[1].trim() : null;
|
|
9469
10083
|
} catch {
|
|
@@ -9471,16 +10085,16 @@ function readEnvKey(root) {
|
|
|
9471
10085
|
}
|
|
9472
10086
|
}
|
|
9473
10087
|
function ensureGitignored(root) {
|
|
9474
|
-
const p =
|
|
10088
|
+
const p = path8.join(root, ".gitignore");
|
|
9475
10089
|
let c = "";
|
|
9476
10090
|
try {
|
|
9477
|
-
c =
|
|
10091
|
+
c = fs12.readFileSync(p, "utf8");
|
|
9478
10092
|
} catch {
|
|
9479
10093
|
}
|
|
9480
|
-
if (!/^\.env\.local$/m.test(c) && !/^\.env\*/m.test(c))
|
|
10094
|
+
if (!/^\.env\.local$/m.test(c) && !/^\.env\*/m.test(c)) fs12.writeFileSync(p, (c && !c.endsWith("\n") ? c + "\n" : c) + ".env.local\n");
|
|
9481
10095
|
}
|
|
9482
10096
|
function wireInstrumentation(root) {
|
|
9483
|
-
const existing = ["instrumentation.ts", "instrumentation.js",
|
|
10097
|
+
const existing = ["instrumentation.ts", "instrumentation.js", path8.join("src", "instrumentation.ts")].map((c) => path8.join(root, c)).find((f) => fs12.existsSync(f));
|
|
9484
10098
|
const body = `import { register as apiblaze } from "apiblaze/sidecar";
|
|
9485
10099
|
|
|
9486
10100
|
export function register() {
|
|
@@ -9488,18 +10102,18 @@ export function register() {
|
|
|
9488
10102
|
}
|
|
9489
10103
|
`;
|
|
9490
10104
|
if (!existing) {
|
|
9491
|
-
|
|
10105
|
+
fs12.writeFileSync(path8.join(root, "instrumentation.ts"), body);
|
|
9492
10106
|
return "created";
|
|
9493
10107
|
}
|
|
9494
|
-
const cur =
|
|
10108
|
+
const cur = fs12.readFileSync(existing, "utf8");
|
|
9495
10109
|
if (cur.includes("apiblaze/sidecar")) return "present";
|
|
9496
10110
|
if (/export\s+function\s+register\s*\(/.test(cur)) {
|
|
9497
|
-
|
|
10111
|
+
fs12.writeFileSync(existing, `import { register as apiblaze } from "apiblaze/sidecar";
|
|
9498
10112
|
` + cur.replace(/export\s+function\s+register\s*\(\s*\)\s*\{/, (m) => `${m}
|
|
9499
10113
|
apiblaze();`));
|
|
9500
10114
|
return "patched";
|
|
9501
10115
|
}
|
|
9502
|
-
|
|
10116
|
+
fs12.writeFileSync(existing, `import { register as apiblaze } from "apiblaze/sidecar";
|
|
9503
10117
|
${cur}
|
|
9504
10118
|
// call apiblaze() inside your register() export.
|
|
9505
10119
|
`);
|
|
@@ -9578,17 +10192,17 @@ export default async function Page() {
|
|
|
9578
10192
|
function generateInspector(root, router) {
|
|
9579
10193
|
try {
|
|
9580
10194
|
if (router === "pages") {
|
|
9581
|
-
const dir2 =
|
|
9582
|
-
const f2 =
|
|
9583
|
-
|
|
9584
|
-
return
|
|
9585
|
-
}
|
|
9586
|
-
const base2 =
|
|
9587
|
-
const dir =
|
|
9588
|
-
|
|
9589
|
-
const f =
|
|
9590
|
-
|
|
9591
|
-
return
|
|
10195
|
+
const dir2 = fs12.existsSync(path8.join(root, "src", "pages")) ? path8.join(root, "src", "pages") : path8.join(root, "pages");
|
|
10196
|
+
const f2 = path8.join(dir2, "abz-inspector.tsx");
|
|
10197
|
+
fs12.writeFileSync(f2, INSPECTOR_PAGE);
|
|
10198
|
+
return path8.relative(root, f2);
|
|
10199
|
+
}
|
|
10200
|
+
const base2 = fs12.existsSync(path8.join(root, "src", "app")) ? path8.join(root, "src", "app") : path8.join(root, "app");
|
|
10201
|
+
const dir = path8.join(base2, "abz-inspector");
|
|
10202
|
+
fs12.mkdirSync(dir, { recursive: true });
|
|
10203
|
+
const f = path8.join(dir, "page.tsx");
|
|
10204
|
+
fs12.writeFileSync(f, INSPECTOR_PAGE);
|
|
10205
|
+
return path8.relative(root, f);
|
|
9592
10206
|
} catch {
|
|
9593
10207
|
return null;
|
|
9594
10208
|
}
|
|
@@ -9609,29 +10223,29 @@ async function runAnonymousInit(root, router, opts) {
|
|
|
9609
10223
|
if (out.cp_key && out.team_id) saveAnonCred2(out.cp_key, out.team_id, out.claim_code);
|
|
9610
10224
|
const envState = upsertEnvLocal(root, out.token);
|
|
9611
10225
|
ensureGitignored(root);
|
|
9612
|
-
console.log(` ${
|
|
9613
|
-
console.log(` ${
|
|
10226
|
+
console.log(` ${import_chalk49.default.green("\u2713")} .env.local ${envState} (APIBLAZE_API_KEY) \u2014 gitignored`);
|
|
10227
|
+
console.log(` ${import_chalk49.default.green("\u2713")} instrumentation.ts ${wireInstrumentation(root)}`);
|
|
9614
10228
|
installSidecarPackage(root);
|
|
9615
10229
|
let inspectorPath = null;
|
|
9616
10230
|
if (!opts.noInspector) {
|
|
9617
10231
|
inspectorPath = generateInspector(root, router);
|
|
9618
|
-
if (inspectorPath) console.log(` ${
|
|
10232
|
+
if (inspectorPath) console.log(` ${import_chalk49.default.green("\u2713")} inspector at ${inspectorPath}`);
|
|
9619
10233
|
}
|
|
9620
10234
|
console.log("");
|
|
9621
|
-
console.log(
|
|
9622
|
-
console.log(` 1. ${
|
|
10235
|
+
console.log(import_chalk49.default.bold("Done (no account needed). What happens next:"));
|
|
10236
|
+
console.log(` 1. ${import_chalk49.default.cyan("npm run dev")} and use your app.`);
|
|
9623
10237
|
console.log(` 2. Each external origin your app calls is logged in the console \u2014 approve one with:`);
|
|
9624
|
-
console.log(` ${
|
|
10238
|
+
console.log(` ${import_chalk49.default.cyan("apiblaze sidecar approve api.stripe.com")} (no login needed)`);
|
|
9625
10239
|
console.log("");
|
|
9626
|
-
console.log(
|
|
9627
|
-
console.log(` ${
|
|
9628
|
-
console.log(
|
|
10240
|
+
console.log(import_chalk49.default.bold(" \u{1F511} Keep your setup \u2014 claim it into an account:"));
|
|
10241
|
+
console.log(` ${import_chalk49.default.cyan("apiblaze login")} then ${import_chalk49.default.cyan("apiblaze claim")} ${import_chalk49.default.dim("(no code needed here)")}`);
|
|
10242
|
+
console.log(import_chalk49.default.dim(` From another machine: apiblaze claim ${out.claim_code} \xB7 expires in 30 days`));
|
|
9629
10243
|
}
|
|
9630
10244
|
async function runSidecar(opts) {
|
|
9631
|
-
const root =
|
|
10245
|
+
const root = path8.resolve(opts.dir ?? process.cwd());
|
|
9632
10246
|
const detected = detectNextProject(root);
|
|
9633
10247
|
if (!detected.found) {
|
|
9634
|
-
console.log(
|
|
10248
|
+
console.log(import_chalk49.default.yellow(`No Next.js project detected in ${root}.`));
|
|
9635
10249
|
console.log("Create one (e.g. `npx create-next-app`) and re-run `apiblaze init` inside it.");
|
|
9636
10250
|
return;
|
|
9637
10251
|
}
|
|
@@ -9642,10 +10256,10 @@ async function runSidecar(opts) {
|
|
|
9642
10256
|
if (!loadCredentials()) {
|
|
9643
10257
|
upsertEnvLocal(root, readEnvKey(root));
|
|
9644
10258
|
ensureGitignored(root);
|
|
9645
|
-
console.log(` ${
|
|
9646
|
-
console.log(` ${
|
|
10259
|
+
console.log(` ${import_chalk49.default.green("\u2713")} .env.local present (APIBLAZE_API_KEY) \u2014 reusing`);
|
|
10260
|
+
console.log(` ${import_chalk49.default.green("\u2713")} instrumentation.ts ${wireInstrumentation(root)}`);
|
|
9647
10261
|
installSidecarPackage(root);
|
|
9648
|
-
console.log(
|
|
10262
|
+
console.log(import_chalk49.default.dim(" Log in and run `apiblaze claim <code>` to keep this setup, or `apiblaze login` to manage it."));
|
|
9649
10263
|
return;
|
|
9650
10264
|
}
|
|
9651
10265
|
const { teamId, teamName } = await resolveTeam(opts.team);
|
|
@@ -9668,38 +10282,38 @@ async function runSidecar(opts) {
|
|
|
9668
10282
|
throw err;
|
|
9669
10283
|
}
|
|
9670
10284
|
} else {
|
|
9671
|
-
console.log(
|
|
10285
|
+
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).`));
|
|
9672
10286
|
}
|
|
9673
10287
|
const envState = upsertEnvLocal(root, token);
|
|
9674
10288
|
ensureGitignored(root);
|
|
9675
|
-
console.log(` ${
|
|
10289
|
+
console.log(` ${import_chalk49.default.green("\u2713")} .env.local ${envState} (APIBLAZE_API_KEY) \u2014 gitignored`);
|
|
9676
10290
|
const wireState = wireInstrumentation(root);
|
|
9677
|
-
console.log(` ${
|
|
10291
|
+
console.log(` ${import_chalk49.default.green("\u2713")} instrumentation.ts ${wireState}`);
|
|
9678
10292
|
installSidecarPackage(root);
|
|
9679
10293
|
let inspectorPath = null;
|
|
9680
10294
|
if (!opts.noInspector) {
|
|
9681
10295
|
inspectorPath = generateInspector(root, detected.router);
|
|
9682
|
-
if (inspectorPath) console.log(` ${
|
|
10296
|
+
if (inspectorPath) console.log(` ${import_chalk49.default.green("\u2713")} inspector at ${inspectorPath}`);
|
|
9683
10297
|
}
|
|
9684
10298
|
console.log("");
|
|
9685
|
-
console.log(
|
|
9686
|
-
console.log(` 1. ${
|
|
9687
|
-
console.log(` 2. The origins your app calls appear as ${
|
|
9688
|
-
console.log(` 3. Approve the ones to route: ${
|
|
10299
|
+
console.log(import_chalk49.default.bold("Done. What happens next:"));
|
|
10300
|
+
console.log(` 1. ${import_chalk49.default.cyan("npm run dev")} and use your app \u2014 it works exactly as before (all calls go direct).`);
|
|
10301
|
+
console.log(` 2. The origins your app calls appear as ${import_chalk49.default.bold("candidates")} \u2014 list them: ${import_chalk49.default.cyan("apiblaze sidecar")}`);
|
|
10302
|
+
console.log(` 3. Approve the ones to route: ${import_chalk49.default.cyan("apiblaze sidecar approve api.stripe.com")} (or in the dashboard)`);
|
|
9689
10303
|
console.log(` \u2026within ~5 min your app starts routing that origin through APIblaze.`);
|
|
9690
|
-
if (inspectorPath) console.log(` \u2022 Try it now: open ${
|
|
9691
|
-
if (switchingTeam) console.log(
|
|
10304
|
+
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)`);
|
|
10305
|
+
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>\`.`));
|
|
9692
10306
|
console.log("");
|
|
9693
|
-
console.log(
|
|
9694
|
-
console.log(
|
|
9695
|
-
console.log(
|
|
10307
|
+
console.log(import_chalk49.default.dim(" Manage: apiblaze sidecar (list/approve/deny/remove)"));
|
|
10308
|
+
console.log(import_chalk49.default.dim(" Rotate: apiblaze init --rotate \xB7 Switch team: apiblaze init --team <name>"));
|
|
10309
|
+
console.log(import_chalk49.default.dim(" Turn off: set APIBLAZE_SIDECAR=off in .env.local (flip back to on anytime; key stays put)."));
|
|
9696
10310
|
console.log("");
|
|
9697
|
-
console.log(
|
|
9698
|
-
console.log(
|
|
10311
|
+
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."));
|
|
10312
|
+
console.log(import_chalk49.default.dim(" Your control-plane login stays in ~/.apiblaze \u2014 it never entered this project."));
|
|
9699
10313
|
}
|
|
9700
10314
|
|
|
9701
10315
|
// src/commands/origins.ts
|
|
9702
|
-
var
|
|
10316
|
+
var import_chalk50 = __toESM(require("chalk"));
|
|
9703
10317
|
var import_ora26 = __toESM(require("ora"));
|
|
9704
10318
|
init_admin();
|
|
9705
10319
|
init_resolve();
|
|
@@ -9710,7 +10324,7 @@ async function runOriginsList(opts) {
|
|
|
9710
10324
|
if (!loadCredentials()) {
|
|
9711
10325
|
const cred = loadAnonCred();
|
|
9712
10326
|
if (!cred) {
|
|
9713
|
-
console.log(
|
|
10327
|
+
console.log(import_chalk50.default.yellow("No anonymous workspace here. Run `apiblaze init` first."));
|
|
9714
10328
|
return;
|
|
9715
10329
|
}
|
|
9716
10330
|
out = await cpFetch(cred.cp_key, `/teams/${encodeURIComponent(cred.team_id)}/sidecar/candidates`, { method: "GET" });
|
|
@@ -9728,27 +10342,27 @@ async function runOriginsList(opts) {
|
|
|
9728
10342
|
}
|
|
9729
10343
|
const routed = out.routed ?? [];
|
|
9730
10344
|
const candidates = out.candidates ?? [];
|
|
9731
|
-
console.log(
|
|
10345
|
+
console.log(import_chalk50.default.bold(`
|
|
9732
10346
|
Routed through APIblaze (${routed.length})`));
|
|
9733
|
-
if (!routed.length) console.log(
|
|
9734
|
-
for (const r of routed) console.log(` ${
|
|
9735
|
-
console.log(
|
|
10347
|
+
if (!routed.length) console.log(import_chalk50.default.dim(" none yet"));
|
|
10348
|
+
for (const r of routed) console.log(` ${import_chalk50.default.green("\u25CF")} ${r.sidecar_origin} ${import_chalk50.default.dim(`\u2192 ${r.project_id}`)}`);
|
|
10349
|
+
console.log(import_chalk50.default.bold(`
|
|
9736
10350
|
Candidates \u2014 going direct, not yet approved (${candidates.length})`));
|
|
9737
|
-
if (!candidates.length) console.log(
|
|
10351
|
+
if (!candidates.length) console.log(import_chalk50.default.dim(" none \u2014 run your app to discover the origins it calls"));
|
|
9738
10352
|
for (const c of candidates) {
|
|
9739
|
-
console.log(` ${
|
|
10353
|
+
console.log(` ${import_chalk50.default.yellow("\u25CB")} ${c.origin} ${import_chalk50.default.dim(`seen ${c.request_count}\xD7, last ${c.last_seen}`)}`);
|
|
9740
10354
|
}
|
|
9741
10355
|
if (candidates.length) {
|
|
9742
|
-
console.log(
|
|
10356
|
+
console.log(import_chalk50.default.dim(`
|
|
9743
10357
|
Approve: apiblaze sidecar approve ${candidates[0].origin.replace("https://", "")}`));
|
|
9744
|
-
console.log(
|
|
10358
|
+
console.log(import_chalk50.default.dim(` Dismiss: apiblaze sidecar deny ${candidates[0].origin.replace("https://", "")}`));
|
|
9745
10359
|
}
|
|
9746
10360
|
}
|
|
9747
10361
|
async function runOriginsApprove(origin, opts) {
|
|
9748
10362
|
if (!loadCredentials()) {
|
|
9749
10363
|
const cred = loadAnonCred();
|
|
9750
10364
|
if (!cred) {
|
|
9751
|
-
console.error(
|
|
10365
|
+
console.error(import_chalk50.default.red("Not logged in and no anonymous workspace. Run `apiblaze init` first."));
|
|
9752
10366
|
process.exit(1);
|
|
9753
10367
|
}
|
|
9754
10368
|
const spinner2 = (0, import_ora26.default)(`Approving ${origin} (anonymous)...`).start();
|
|
@@ -9801,13 +10415,13 @@ async function runOriginsRemove(origin, opts) {
|
|
|
9801
10415
|
}
|
|
9802
10416
|
|
|
9803
10417
|
// src/commands/op.ts
|
|
9804
|
-
var
|
|
10418
|
+
var import_chalk52 = __toESM(require("chalk"));
|
|
9805
10419
|
init_auth();
|
|
9806
10420
|
init_trace();
|
|
9807
10421
|
init_types();
|
|
9808
10422
|
|
|
9809
10423
|
// src/commands/op-billing.ts
|
|
9810
|
-
var
|
|
10424
|
+
var import_chalk51 = __toESM(require("chalk"));
|
|
9811
10425
|
init_admin();
|
|
9812
10426
|
var SANDBOX = {
|
|
9813
10427
|
teamId: "team_1782844865835_zujrf",
|
|
@@ -9866,7 +10480,7 @@ async function rowsForRays(project, version2, tenant2, rays) {
|
|
|
9866
10480
|
}
|
|
9867
10481
|
function printDoors(data) {
|
|
9868
10482
|
const checks = [];
|
|
9869
|
-
console.log(
|
|
10483
|
+
console.log(import_chalk51.default.bold("\n Doors \u2014 is every way in metered?\n"));
|
|
9870
10484
|
const doors = data?.doors ?? [];
|
|
9871
10485
|
const metered = doors.filter((d) => d.verdict === "metered");
|
|
9872
10486
|
const allowed = doors.filter((d) => d.verdict === "allowed-free");
|
|
@@ -9875,29 +10489,29 @@ function printDoors(data) {
|
|
|
9875
10489
|
const errs = data?.errors ?? [];
|
|
9876
10490
|
const routeAuditBroke = errs.some((e) => e.startsWith("zone "));
|
|
9877
10491
|
const devAuditBroke = errs.some((e) => e.startsWith("workers.dev audit"));
|
|
9878
|
-
console.log(
|
|
9879
|
-
for (const d of metered) console.log(
|
|
9880
|
-
console.log(
|
|
10492
|
+
console.log(import_chalk51.default.dim(` ${metered.length} route(s) behind main-proxy (metered)`));
|
|
10493
|
+
for (const d of metered) console.log(import_chalk51.default.green(` \u2713 ${d.pattern}`));
|
|
10494
|
+
console.log(import_chalk51.default.dim(`
|
|
9881
10495
|
${allowed.length} route(s) free ON PURPOSE`));
|
|
9882
10496
|
for (const d of allowed) {
|
|
9883
|
-
console.log(
|
|
9884
|
-
console.log(
|
|
10497
|
+
console.log(import_chalk51.default.cyan(` \u2022 ${d.pattern}`) + import_chalk51.default.dim(` \u2192 ${d.script}`));
|
|
10498
|
+
console.log(import_chalk51.default.dim(` ${d.why}`));
|
|
9885
10499
|
}
|
|
9886
10500
|
if (known.length) {
|
|
9887
|
-
console.log(
|
|
10501
|
+
console.log(import_chalk51.default.yellow(`
|
|
9888
10502
|
${known.length} route(s) KNOWN OPEN \u2014 unmetered, not yet closed`));
|
|
9889
10503
|
for (const d of known) {
|
|
9890
|
-
console.log(
|
|
9891
|
-
console.log(
|
|
10504
|
+
console.log(import_chalk51.default.yellow(` ! ${d.pattern}`) + import_chalk51.default.dim(` \u2192 ${d.script}`));
|
|
10505
|
+
console.log(import_chalk51.default.dim(` ${d.why}`));
|
|
9892
10506
|
}
|
|
9893
10507
|
checks.push({ name: "no known-open doors", status: "KNOWN", detail: `${known.length} unmetered route(s) still open \u2014 see above` });
|
|
9894
10508
|
}
|
|
9895
10509
|
if (stray.length) {
|
|
9896
|
-
console.log(
|
|
10510
|
+
console.log(import_chalk51.default.red(`
|
|
9897
10511
|
${stray.length} STRAY route(s) \u2014 not main-proxy, not on the allowlist`));
|
|
9898
10512
|
for (const d of stray) {
|
|
9899
|
-
console.log(
|
|
9900
|
-
console.log(
|
|
10513
|
+
console.log(import_chalk51.default.red(` \u2717 ${d.pattern}`) + import_chalk51.default.dim(` \u2192 ${d.script}`));
|
|
10514
|
+
console.log(import_chalk51.default.dim(` ${d.why}`));
|
|
9901
10515
|
}
|
|
9902
10516
|
checks.push({ name: "no stray routes", status: "FAIL", detail: `${stray.length}: ${stray.map((s) => s.pattern).join(", ")}` });
|
|
9903
10517
|
} else if (routeAuditBroke) {
|
|
@@ -9908,22 +10522,22 @@ function printDoors(data) {
|
|
|
9908
10522
|
const wd = data?.workers_dev ?? {};
|
|
9909
10523
|
const open = wd.enabled ?? [];
|
|
9910
10524
|
if (open.length) {
|
|
9911
|
-
console.log(
|
|
10525
|
+
console.log(import_chalk51.default.red(`
|
|
9912
10526
|
${open.length} of ${wd.total} worker(s) reachable on *.workers.dev`));
|
|
9913
|
-
for (const s of open) console.log(
|
|
9914
|
-
console.log(
|
|
10527
|
+
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})`));
|
|
10528
|
+
console.log(import_chalk51.default.dim(" A workers.dev hostname bypasses every CF route, WAF rule and the credit gate."));
|
|
9915
10529
|
checks.push({ name: "no workers.dev doors", status: "FAIL", detail: `${open.length} script(s) publicly reachable: ${open.map((s) => s.script).join(", ")}` });
|
|
9916
10530
|
} else if (devAuditBroke || !wd.total) {
|
|
9917
10531
|
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." });
|
|
9918
10532
|
} else {
|
|
9919
|
-
console.log(
|
|
10533
|
+
console.log(import_chalk51.default.green(`
|
|
9920
10534
|
\u2713 0 of ${wd.total} workers reachable on *.workers.dev`));
|
|
9921
10535
|
checks.push({ name: "no workers.dev doors", status: "PASS", detail: `all ${wd.total} scripts have workers.dev + previews disabled` });
|
|
9922
10536
|
}
|
|
9923
|
-
if (data?.how_to_fix) console.log(
|
|
9924
|
-
${data.reason}`) +
|
|
10537
|
+
if (data?.how_to_fix) console.log(import_chalk51.default.yellow(`
|
|
10538
|
+
${data.reason}`) + import_chalk51.default.dim(`
|
|
9925
10539
|
${data.how_to_fix}`));
|
|
9926
|
-
else for (const e of errs) console.log(
|
|
10540
|
+
else for (const e of errs) console.log(import_chalk51.default.red(`
|
|
9927
10541
|
audit error: ${e}`));
|
|
9928
10542
|
if (errs.length) {
|
|
9929
10543
|
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` });
|
|
@@ -9933,9 +10547,9 @@ function printDoors(data) {
|
|
|
9933
10547
|
async function runMeter(readLedger, opts) {
|
|
9934
10548
|
const checks = [];
|
|
9935
10549
|
const N = Math.max(1, Math.min(10, opts.count ?? 3));
|
|
9936
|
-
console.log(
|
|
10550
|
+
console.log(import_chalk51.default.bold("\n Meter \u2014 is 1 request charged exactly 1 request?\n"));
|
|
9937
10551
|
const snap = await readLedger();
|
|
9938
|
-
console.log(
|
|
10552
|
+
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
|
|
9939
10553
|
`));
|
|
9940
10554
|
const url = `https://${dpHost}/${SANDBOX.version}/${SANDBOX.environment}/`;
|
|
9941
10555
|
const headers = opts.key ? { "X-API-Key": opts.key } : {};
|
|
@@ -10017,22 +10631,22 @@ async function runMeter(readLedger, opts) {
|
|
|
10017
10631
|
return checks;
|
|
10018
10632
|
}
|
|
10019
10633
|
function printChecks(checks) {
|
|
10020
|
-
console.log(
|
|
10021
|
-
const mark = { PASS:
|
|
10634
|
+
console.log(import_chalk51.default.bold("\n Results\n"));
|
|
10635
|
+
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") };
|
|
10022
10636
|
for (const ch of checks) {
|
|
10023
|
-
console.log(` ${mark[ch.status]} ${
|
|
10024
|
-
console.log(
|
|
10637
|
+
console.log(` ${mark[ch.status]} ${import_chalk51.default.bold(ch.name)}`);
|
|
10638
|
+
console.log(import_chalk51.default.dim(` ${ch.detail}`));
|
|
10025
10639
|
}
|
|
10026
10640
|
const fails = checks.filter((c) => c.status === "FAIL").length;
|
|
10027
10641
|
const skips = checks.filter((c) => c.status === "SKIP").length;
|
|
10028
10642
|
const known = checks.filter((c) => c.status === "KNOWN").length;
|
|
10029
10643
|
console.log("");
|
|
10030
10644
|
const passes = checks.filter((c) => c.status === "PASS").length;
|
|
10031
|
-
if (fails) console.log(
|
|
10032
|
-
else if (passes) console.log(
|
|
10033
|
-
else console.log(
|
|
10034
|
-
if (known) console.log(
|
|
10035
|
-
if (skips) console.log(
|
|
10645
|
+
if (fails) console.log(import_chalk51.default.red(` ${fails} check(s) FAILED.`));
|
|
10646
|
+
else if (passes) console.log(import_chalk51.default.green(` ${passes} check(s) passed, 0 failed.`));
|
|
10647
|
+
else console.log(import_chalk51.default.yellow(" NOTHING WAS VERIFIED \u2014 every check was skipped."));
|
|
10648
|
+
if (known) console.log(import_chalk51.default.yellow(` ${known} known-open issue(s) still outstanding.`));
|
|
10649
|
+
if (skips) console.log(import_chalk51.default.dim(` ${skips} check(s) NOT RUN (see SKIP above) \u2014 those invariants are unverified.`));
|
|
10036
10650
|
console.log("");
|
|
10037
10651
|
}
|
|
10038
10652
|
|
|
@@ -10079,93 +10693,93 @@ var OP_COMMANDS = [
|
|
|
10079
10693
|
function renderOpCommands() {
|
|
10080
10694
|
const width = Math.max(...OP_COMMANDS.map((c) => c.cmd.length)) + 10;
|
|
10081
10695
|
const lines = OP_COMMANDS.map((c) => {
|
|
10082
|
-
const left = ` ${
|
|
10696
|
+
const left = ` ${import_chalk52.default.cyan(`apiblaze ${c.cmd}`)}`;
|
|
10083
10697
|
const pad = " ".repeat(Math.max(1, width - c.cmd.length));
|
|
10084
|
-
return `${left}${pad}${c.blurb}${c.extra ? " " +
|
|
10698
|
+
return `${left}${pad}${c.blurb}${c.extra ? " " + import_chalk52.default.dim(`(${c.extra})`) : ""}`;
|
|
10085
10699
|
});
|
|
10086
10700
|
return [
|
|
10087
|
-
|
|
10701
|
+
import_chalk52.default.bold("Operator commands"),
|
|
10088
10702
|
...lines,
|
|
10089
10703
|
"",
|
|
10090
|
-
|
|
10091
|
-
|
|
10092
|
-
|
|
10704
|
+
import_chalk52.default.dim(" Operators only. The gate is server-side (dashboard /api/cli/op checks the"),
|
|
10705
|
+
import_chalk52.default.dim(" signed-in email, admin-api re-checks with operatorGate) \u2014 a patched CLI just"),
|
|
10706
|
+
import_chalk52.default.dim(" gets 403s. Every op call is read-only except `op sweep`."),
|
|
10093
10707
|
"",
|
|
10094
|
-
|
|
10095
|
-
|
|
10096
|
-
|
|
10708
|
+
import_chalk52.default.dim(" Not a CLI command: to prune all non-CP data run scripts/nuke-but-cp.sh --apply --sweep"),
|
|
10709
|
+
import_chalk52.default.dim(" in the repo. Operator dashboards (dlq, thresholds, throttling, pricing, billing,"),
|
|
10710
|
+
import_chalk52.default.dim(" agent-spend, teams, tests, leak-detection, lifecycle) live at /operator/* in the app.")
|
|
10097
10711
|
].join("\n");
|
|
10098
10712
|
}
|
|
10099
10713
|
function printResidue(report, applied) {
|
|
10100
10714
|
const up = report?.upstash ?? {};
|
|
10101
10715
|
const fga = report?.fga ?? {};
|
|
10102
10716
|
const ghosts = report?.ghosts ?? {};
|
|
10103
|
-
console.log(
|
|
10104
|
-
console.log(
|
|
10717
|
+
console.log(import_chalk52.default.bold(applied ? "\nExternal-residue sweep" : "\nExternal residue (dry-run \u2014 nothing deleted)"));
|
|
10718
|
+
console.log(import_chalk52.default.bold("\n Upstash"));
|
|
10105
10719
|
const orphans = up.orphans ?? [];
|
|
10106
|
-
if (orphans.length === 0) console.log(
|
|
10107
|
-
for (const o of orphans) console.log(` ${
|
|
10108
|
-
console.log(
|
|
10720
|
+
if (orphans.length === 0) console.log(import_chalk52.default.green(" no orphaned keys"));
|
|
10721
|
+
for (const o of orphans) console.log(` ${import_chalk52.default.yellow(o.key)} ${import_chalk52.default.dim(`\u2014 ${o.reason}`)}`);
|
|
10722
|
+
console.log(import_chalk52.default.dim(` kept (live principals): ${up.kept ?? 0} \xB7 anon wallets (untouched): ${up.anon_wallets ?? 0}`));
|
|
10109
10723
|
if (up.anon_wallet_detail) {
|
|
10110
10724
|
const d = up.anon_wallet_detail;
|
|
10111
|
-
console.log(
|
|
10725
|
+
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)"}`));
|
|
10112
10726
|
}
|
|
10113
10727
|
if (up.keyspace_census) {
|
|
10114
10728
|
const census = Object.entries(up.keyspace_census).map(([k, v]) => `${k}=${v}`).join(" \xB7 ");
|
|
10115
|
-
console.log(
|
|
10729
|
+
console.log(import_chalk52.default.dim(` keyspace: ${census}`));
|
|
10116
10730
|
}
|
|
10117
|
-
if (up.unknown?.length) console.log(
|
|
10118
|
-
if (applied) console.log(` ${
|
|
10119
|
-
for (const e of up.errors ?? []) console.log(
|
|
10120
|
-
console.log(
|
|
10731
|
+
if (up.unknown?.length) console.log(import_chalk52.default.dim(` unknown (never deleted): ${up.unknown.join(", ")}`));
|
|
10732
|
+
if (applied) console.log(` ${import_chalk52.default.bold(String(up.deleted ?? 0))} key(s) deleted`);
|
|
10733
|
+
for (const e of up.errors ?? []) console.log(import_chalk52.default.red(` error: ${e}`));
|
|
10734
|
+
console.log(import_chalk52.default.bold("\n OpenFGA / Neon \u2014 orphan stores"));
|
|
10121
10735
|
if (applied) {
|
|
10122
10736
|
const swept = fga?.swept ?? [];
|
|
10123
|
-
if (swept.length === 0) console.log(
|
|
10737
|
+
if (swept.length === 0) console.log(import_chalk52.default.green(" no orphaned stores"));
|
|
10124
10738
|
for (const s of swept) {
|
|
10125
10739
|
console.log(
|
|
10126
|
-
` ${
|
|
10740
|
+
` ${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`)}`
|
|
10127
10741
|
);
|
|
10128
10742
|
}
|
|
10129
|
-
if (fga?.remaining) console.log(
|
|
10743
|
+
if (fga?.remaining) console.log(import_chalk52.default.yellow(` ${fga.remaining} more orphan store(s) \u2014 re-run to drain`));
|
|
10130
10744
|
const st = fga?.side_tables;
|
|
10131
|
-
if (st) console.log(
|
|
10745
|
+
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})` : ""}`));
|
|
10132
10746
|
} else {
|
|
10133
10747
|
const fgaOrphans = fga?.orphans ?? [];
|
|
10134
|
-
if (fgaOrphans.length === 0) console.log(
|
|
10748
|
+
if (fgaOrphans.length === 0) console.log(import_chalk52.default.green(" no orphaned stores"));
|
|
10135
10749
|
for (const s of fgaOrphans) {
|
|
10136
10750
|
const src = s.in_openfga ? "live in OpenFGA" : "Neon tuples only";
|
|
10137
|
-
console.log(` ${
|
|
10751
|
+
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)`)}`);
|
|
10138
10752
|
}
|
|
10139
|
-
console.log(
|
|
10753
|
+
console.log(import_chalk52.default.dim(` kept stores: ${(fga?.kept_store_ids ?? []).length}`));
|
|
10140
10754
|
const st = fga?.side_tables;
|
|
10141
|
-
if (st) console.log(
|
|
10755
|
+
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`));
|
|
10142
10756
|
}
|
|
10143
|
-
for (const e of fga?.errors ?? []) console.log(
|
|
10144
|
-
console.log(
|
|
10757
|
+
for (const e of fga?.errors ?? []) console.log(import_chalk52.default.red(` error: ${e}`));
|
|
10758
|
+
console.log(import_chalk52.default.bold("\n OpenFGA \u2014 ghost tuples in surviving stores"));
|
|
10145
10759
|
if (applied) {
|
|
10146
|
-
if ((ghosts?.ghost_count ?? 0) === 0) console.log(
|
|
10147
|
-
else console.log(` ${
|
|
10760
|
+
if ((ghosts?.ghost_count ?? 0) === 0) console.log(import_chalk52.default.green(" no ghost tuples"));
|
|
10761
|
+
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)`)}`);
|
|
10148
10762
|
} else {
|
|
10149
10763
|
const n = ghosts?.ghost_count ?? 0;
|
|
10150
|
-
if (n === 0) console.log(
|
|
10764
|
+
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)`)}`));
|
|
10151
10765
|
else {
|
|
10152
|
-
console.log(
|
|
10766
|
+
console.log(import_chalk52.default.yellow(` ${n} ghost tuple(s) referencing entities absent from D1:`));
|
|
10153
10767
|
for (const g of (ghosts.ghosts ?? []).slice(0, 20)) {
|
|
10154
|
-
console.log(
|
|
10768
|
+
console.log(import_chalk52.default.dim(` ${g.object_type}:${g.object_id} ${g.relation} ${g._user}`));
|
|
10155
10769
|
}
|
|
10156
|
-
if (n > 20) console.log(
|
|
10770
|
+
if (n > 20) console.log(import_chalk52.default.dim(` \u2026 and ${n - 20} more`));
|
|
10157
10771
|
}
|
|
10158
10772
|
}
|
|
10159
|
-
for (const e of ghosts?.errors ?? []) console.log(
|
|
10773
|
+
for (const e of ghosts?.errors ?? []) console.log(import_chalk52.default.red(` error: ${e}`));
|
|
10160
10774
|
console.log();
|
|
10161
10775
|
}
|
|
10162
10776
|
async function runOp(sub, opts = {}, view) {
|
|
10163
10777
|
if (!loadCredentials()) {
|
|
10164
|
-
console.log(
|
|
10778
|
+
console.log(import_chalk52.default.dim("Not logged in. Run `apiblaze login`."));
|
|
10165
10779
|
return;
|
|
10166
10780
|
}
|
|
10167
10781
|
if (!isOperatorLogin()) {
|
|
10168
|
-
console.log(
|
|
10782
|
+
console.log(import_chalk52.default.dim("`apiblaze op` is only available to platform operators."));
|
|
10169
10783
|
return;
|
|
10170
10784
|
}
|
|
10171
10785
|
switch (sub) {
|
|
@@ -10191,17 +10805,17 @@ async function runOp(sub, opts = {}, view) {
|
|
|
10191
10805
|
const nSide = (st.soft_deleted_stores ?? 0) + (st.orphan_models ?? 0) + (st.orphan_changelog ?? 0);
|
|
10192
10806
|
printResidue(report, false);
|
|
10193
10807
|
if (nUp + nFga + nGhost + nSide === 0) {
|
|
10194
|
-
console.log(
|
|
10808
|
+
console.log(import_chalk52.default.green("Nothing to sweep."));
|
|
10195
10809
|
return;
|
|
10196
10810
|
}
|
|
10197
10811
|
if (!opts.yes) {
|
|
10198
10812
|
const readline3 = await import("readline/promises");
|
|
10199
10813
|
const rl = readline3.createInterface({ input: process.stdin, output: process.stdout });
|
|
10200
10814
|
const answer = await rl.question(
|
|
10201
|
-
|
|
10815
|
+
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: `)
|
|
10202
10816
|
);
|
|
10203
10817
|
rl.close();
|
|
10204
|
-
if (answer.trim() !== "sweep") return void console.log(
|
|
10818
|
+
if (answer.trim() !== "sweep") return void console.log(import_chalk52.default.dim("Aborted."));
|
|
10205
10819
|
}
|
|
10206
10820
|
const result = await opCall({ method: "POST", path: "/operator/external-residue/sweep", summary: "external residue sweep" });
|
|
10207
10821
|
if (opts.json) return void console.log(JSON.stringify(result, null, 2));
|
|
@@ -10211,25 +10825,25 @@ async function runOp(sub, opts = {}, view) {
|
|
|
10211
10825
|
case "mark": {
|
|
10212
10826
|
const label3 = (view ?? "").trim();
|
|
10213
10827
|
if (!label3) {
|
|
10214
|
-
console.log(
|
|
10828
|
+
console.log(import_chalk52.default.red("Give the change a name:") + import_chalk52.default.cyan(' apiblaze op mark "cached tenant count"'));
|
|
10215
10829
|
return;
|
|
10216
10830
|
}
|
|
10217
10831
|
const res = await opCall({ method: "POST", path: "/operator/latency/mark", body: { label: label3 }, summary: "record change marker" });
|
|
10218
10832
|
const ts = new Date(res?.marker?.ts ?? Date.now()).toISOString();
|
|
10219
|
-
console.log(
|
|
10220
|
-
Marked: `) +
|
|
10221
|
-
console.log(
|
|
10222
|
-
console.log(
|
|
10833
|
+
console.log(import_chalk52.default.green(`
|
|
10834
|
+
Marked: `) + import_chalk52.default.bold(label3));
|
|
10835
|
+
console.log(import_chalk52.default.dim(` ${ts}`));
|
|
10836
|
+
console.log(import_chalk52.default.dim(` Once traffic has run on both sides, compare with: `) + import_chalk52.default.cyan("apiblaze op latency compare") + "\n");
|
|
10223
10837
|
return;
|
|
10224
10838
|
}
|
|
10225
10839
|
case "credits": {
|
|
10226
10840
|
const data = await opCall({ method: "GET", path: "/operator/credits", summary: "list credit wallets" });
|
|
10227
10841
|
if (opts.json) return void console.log(JSON.stringify(data, null, 2));
|
|
10228
10842
|
const accounts = data?.accounts ?? [];
|
|
10229
|
-
if (accounts.length === 0) return void console.log(
|
|
10843
|
+
if (accounts.length === 0) return void console.log(import_chalk52.default.dim("No credit wallets."));
|
|
10230
10844
|
for (const a of accounts) {
|
|
10231
10845
|
const bal = typeof a.balance_cents === "number" ? `$${(a.balance_cents / 100).toFixed(2)}` : "?";
|
|
10232
|
-
console.log(` ${
|
|
10846
|
+
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") : ""}`);
|
|
10233
10847
|
}
|
|
10234
10848
|
return;
|
|
10235
10849
|
}
|
|
@@ -10240,7 +10854,7 @@ async function runOp(sub, opts = {}, view) {
|
|
|
10240
10854
|
case "billing": {
|
|
10241
10855
|
const which = (view ?? "").trim().toLowerCase();
|
|
10242
10856
|
if (which && which !== "doors" && which !== "meter") {
|
|
10243
|
-
return void console.log(
|
|
10857
|
+
return void console.log(import_chalk52.default.red(`Unknown: apiblaze op billing ${which}. Use 'doors', 'meter', or neither for both.`));
|
|
10244
10858
|
}
|
|
10245
10859
|
const checks = [];
|
|
10246
10860
|
let doorsData = null;
|
|
@@ -10301,29 +10915,29 @@ async function runOp(sub, opts = {}, view) {
|
|
|
10301
10915
|
const data = await opCall({ method: "GET", path: `/operator/latency/grades${q}`, summary: "latency grades" });
|
|
10302
10916
|
if (opts.json) return void console.log(JSON.stringify(data, null, 2));
|
|
10303
10917
|
const t = data.thresholds_ms;
|
|
10304
|
-
console.log(
|
|
10305
|
-
console.log(
|
|
10918
|
+
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)"));
|
|
10919
|
+
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+
|
|
10306
10920
|
`));
|
|
10307
|
-
console.log(
|
|
10921
|
+
console.log(import_chalk52.default.dim(" date reqs excellent okay bad terrible"));
|
|
10308
10922
|
for (const d of data.days ?? []) {
|
|
10309
10923
|
const p = d.pct;
|
|
10310
|
-
const cell = (v, colour) => v > 0 ? colour(`${String(v).padStart(5)}%`) :
|
|
10924
|
+
const cell = (v, colour) => v > 0 ? colour(`${String(v).padStart(5)}%`) : import_chalk52.default.dim(`${String(v).padStart(5)}%`);
|
|
10311
10925
|
console.log(
|
|
10312
|
-
` ${d.date} ${String(d.total).padStart(5)} ${cell(p.excellent,
|
|
10926
|
+
` ${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)}`
|
|
10313
10927
|
);
|
|
10314
10928
|
}
|
|
10315
10929
|
const cul = data.culprits ?? [];
|
|
10316
10930
|
if (cul.length) {
|
|
10317
|
-
console.log(
|
|
10318
|
-
console.log(
|
|
10931
|
+
console.log(import_chalk52.default.bold("\n Who caused the bad and terrible ones\n"));
|
|
10932
|
+
console.log(import_chalk52.default.dim(" bad terrible feature \u2192 dependency"));
|
|
10319
10933
|
for (const r of cul.slice(0, 12)) {
|
|
10320
10934
|
if (!r.bad && !r.terrible) continue;
|
|
10321
10935
|
console.log(
|
|
10322
|
-
` ${String(r.bad).padStart(6)} ${
|
|
10936
|
+
` ${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)}`
|
|
10323
10937
|
);
|
|
10324
10938
|
}
|
|
10325
10939
|
}
|
|
10326
|
-
if (data.caveat) console.log(
|
|
10940
|
+
if (data.caveat) console.log(import_chalk52.default.dim(`
|
|
10327
10941
|
\u26A0 ${data.caveat}
|
|
10328
10942
|
`));
|
|
10329
10943
|
return;
|
|
@@ -10332,24 +10946,24 @@ async function runOp(sub, opts = {}, view) {
|
|
|
10332
10946
|
const data = await opCall({ method: "GET", path: `/operator/latency/compare${q}`, summary: "latency before/after" });
|
|
10333
10947
|
if (opts.json) return void console.log(JSON.stringify(data, null, 2));
|
|
10334
10948
|
const b = data.before, a = data.after, d = data.delta;
|
|
10335
|
-
console.log(
|
|
10336
|
-
Before vs after: `) +
|
|
10337
|
-
console.log(
|
|
10949
|
+
console.log(import_chalk52.default.bold(`
|
|
10950
|
+
Before vs after: `) + import_chalk52.default.cyan(data.marker.label));
|
|
10951
|
+
console.log(import_chalk52.default.dim(` marked ${new Date(data.marker.ts).toISOString()} \xB7 ${data.window_hours}h either side
|
|
10338
10952
|
`));
|
|
10339
10953
|
const row = (name, before, after, delta) => {
|
|
10340
10954
|
const arrow = delta === 0 ? "=" : delta < 0 ? "\u2193" : "\u2191";
|
|
10341
10955
|
const txt = `${String(before).padStart(6)}ms \u2192${String(after).padStart(7)}ms ${arrow}${Math.abs(delta)}ms`;
|
|
10342
|
-
console.log(` ${name.padEnd(22)}${data.trustworthy ? delta <= 0 ?
|
|
10956
|
+
console.log(` ${name.padEnd(22)}${data.trustworthy ? delta <= 0 ? import_chalk52.default.green(txt) : import_chalk52.default.red(txt) : import_chalk52.default.dim(txt)}`);
|
|
10343
10957
|
};
|
|
10344
|
-
console.log(
|
|
10958
|
+
console.log(import_chalk52.default.dim(" metric before after change"));
|
|
10345
10959
|
row("total p50", b.total_p50, a.total_p50, d.total_p50);
|
|
10346
10960
|
row("total p95", b.total_p95, a.total_p95, d.total_p95);
|
|
10347
10961
|
row("apiblaze overhead p50", b.gw_p50, a.gw_p50, d.gw_p50);
|
|
10348
10962
|
row("apiblaze overhead p95", b.gw_p95, a.gw_p95, d.gw_p95);
|
|
10349
|
-
console.log(
|
|
10963
|
+
console.log(import_chalk52.default.dim(`
|
|
10350
10964
|
requests: ${b.requests} before \xB7 ${a.requests} after`));
|
|
10351
10965
|
for (const w of data.warnings ?? []) {
|
|
10352
|
-
console.log((data.trustworthy ?
|
|
10966
|
+
console.log((data.trustworthy ? import_chalk52.default.dim : import_chalk52.default.yellow)(` ${data.trustworthy ? "\xB7" : "\u26A0"} ${w}`));
|
|
10353
10967
|
}
|
|
10354
10968
|
console.log("");
|
|
10355
10969
|
return;
|
|
@@ -10358,19 +10972,19 @@ Before vs after: `) + import_chalk51.default.cyan(data.marker.label));
|
|
|
10358
10972
|
const data = await opCall({ method: "GET", path: `/operator/latency/slow${q}`, summary: "slowest requests" });
|
|
10359
10973
|
if (opts.json) return void console.log(JSON.stringify(data, null, 2));
|
|
10360
10974
|
const rows2 = data?.rows ?? [];
|
|
10361
|
-
if (!rows2.length) return void console.log(
|
|
10362
|
-
console.log(
|
|
10975
|
+
if (!rows2.length) return void console.log(import_chalk52.default.dim("No requests over the threshold in that window."));
|
|
10976
|
+
console.log(import_chalk52.default.bold(`
|
|
10363
10977
|
Slowest requests \u2014 last ${data.window_hours}h, over ${data.min_ms}ms
|
|
10364
10978
|
`));
|
|
10365
|
-
console.log(
|
|
10979
|
+
console.log(import_chalk52.default.dim(" total ours theirs blame request id"));
|
|
10366
10980
|
for (const r of rows2.slice(0, 30)) {
|
|
10367
10981
|
console.log(
|
|
10368
|
-
` ${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)} ${
|
|
10982
|
+
` ${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 || "")}`
|
|
10369
10983
|
);
|
|
10370
10984
|
}
|
|
10371
|
-
console.log(
|
|
10985
|
+
console.log(import_chalk52.default.dim(`
|
|
10372
10986
|
The last column is the request id (Cloudflare calls it a "cf-ray"). Look one up with`));
|
|
10373
|
-
console.log(
|
|
10987
|
+
console.log(import_chalk52.default.dim(` \`apiblaze logs\` for that request's exact per-feature breakdown \u2014 unsampled, unlike the table above.
|
|
10374
10988
|
`));
|
|
10375
10989
|
return;
|
|
10376
10990
|
}
|
|
@@ -10378,17 +10992,18 @@ Slowest requests \u2014 last ${data.window_hours}h, over ${data.min_ms}ms
|
|
|
10378
10992
|
const data = await opCall({ method: "GET", path: `/operator/latency/llm${q}`, summary: "llm latency" });
|
|
10379
10993
|
if (opts.json) return void console.log(JSON.stringify(data, null, 2));
|
|
10380
10994
|
const rows2 = data?.rows ?? [];
|
|
10381
|
-
if (!rows2.length) return void console.log(
|
|
10382
|
-
console.log(
|
|
10995
|
+
if (!rows2.length) return void console.log(import_chalk52.default.dim("No LLM traffic in that window."));
|
|
10996
|
+
console.log(import_chalk52.default.bold(`
|
|
10383
10997
|
LLM timing \u2014 last ${data.window_hours}h
|
|
10384
10998
|
`));
|
|
10385
|
-
console.log(
|
|
10999
|
+
console.log(import_chalk52.default.dim(" turns turn p95 ttfc p95 gen p95 reserve p95 in/out tokens p95 model"));
|
|
10386
11000
|
for (const r of rows2) {
|
|
11001
|
+
const n = r.turns ?? r.requests ?? 0;
|
|
10387
11002
|
console.log(
|
|
10388
|
-
` ${String(Math.round(r.
|
|
11003
|
+
` ${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 || "-"}`
|
|
10389
11004
|
);
|
|
10390
11005
|
}
|
|
10391
|
-
console.log(
|
|
11006
|
+
console.log(import_chalk52.default.dim(`
|
|
10392
11007
|
${data.note}
|
|
10393
11008
|
`));
|
|
10394
11009
|
return;
|
|
@@ -10399,26 +11014,26 @@ LLM timing \u2014 last ${data.window_hours}h
|
|
|
10399
11014
|
]);
|
|
10400
11015
|
if (opts.json) return void console.log(JSON.stringify({ blame, summary }, null, 2));
|
|
10401
11016
|
const ov = summary?.apiblaze_overhead_ms ?? {};
|
|
10402
|
-
console.log(
|
|
11017
|
+
console.log(import_chalk52.default.bold(`
|
|
10403
11018
|
Latency \u2014 last ${summary?.window_hours ?? "?"}h, ${Number(summary?.requests ?? 0).toLocaleString()} requests
|
|
10404
11019
|
`));
|
|
10405
|
-
console.log(` ${
|
|
10406
|
-
console.log(` ${
|
|
10407
|
-
console.log(
|
|
11020
|
+
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")}`);
|
|
11021
|
+
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")}`);
|
|
11022
|
+
console.log(import_chalk52.default.dim(` (per-request percentiles \u2014 never subtract one from the other)
|
|
10408
11023
|
`));
|
|
10409
11024
|
const rows = blame?.blame ?? [];
|
|
10410
|
-
if (!rows.length) return void console.log(
|
|
10411
|
-
console.log(
|
|
10412
|
-
console.log(
|
|
11025
|
+
if (!rows.length) return void console.log(import_chalk52.default.dim("No latency rows in that window."));
|
|
11026
|
+
console.log(import_chalk52.default.bold(" Which feature ate the time, and what inside it\n"));
|
|
11027
|
+
console.log(import_chalk52.default.dim(" share p95 feature \u2192 dependency"));
|
|
10413
11028
|
for (const r of rows.slice(0, 15)) {
|
|
10414
11029
|
const share = `${(r.share * 100).toFixed(1)}%`;
|
|
10415
|
-
console.log(` ${share.padStart(6)} ${String(r.p95_ms).padStart(6)}ms ${
|
|
11030
|
+
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)}`);
|
|
10416
11031
|
}
|
|
10417
11032
|
console.log("");
|
|
10418
11033
|
return;
|
|
10419
11034
|
}
|
|
10420
11035
|
default:
|
|
10421
|
-
console.log(
|
|
11036
|
+
console.log(import_chalk52.default.red(`Unknown op subcommand '${sub}'. Run \`apiblaze op\` for the menu.`));
|
|
10422
11037
|
}
|
|
10423
11038
|
}
|
|
10424
11039
|
|
|
@@ -10451,7 +11066,7 @@ program.command("login").description("Authenticate with APIblaze").option("--tea
|
|
|
10451
11066
|
process.exit(1);
|
|
10452
11067
|
}
|
|
10453
11068
|
});
|
|
10454
|
-
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>", "
|
|
11069
|
+
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) => {
|
|
10455
11070
|
try {
|
|
10456
11071
|
await runCreate({ ...opts, openapi: opts.openapi ?? opts.openapispec });
|
|
10457
11072
|
} catch (err) {
|
|
@@ -10464,7 +11079,7 @@ agent.command("authz").description("Chat to design and turn on access rules for
|
|
|
10464
11079
|
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)));
|
|
10465
11080
|
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)));
|
|
10466
11081
|
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)));
|
|
10467
|
-
program.command("apichat").description("Turn any API into a chat: point at an OpenAPI spec
|
|
11082
|
+
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("--no-verbose", "Hide the per-turn proxy curl trace (shown by default for apichat)").option("-p, --prompt <question>", "One-shot question piped through the external agent CLI after the MCP install (used with --install-mcp or the install offer)").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 })));
|
|
10468
11083
|
var llm = program.command("llm").description("Manage a local LLM provider key for chat (optional \u2014 lifts model quality, bills your key)");
|
|
10469
11084
|
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)));
|
|
10470
11085
|
llm.command("show").description("Show the locally stored LLM key (masked)").action(action(() => runLlmShow()));
|
|
@@ -10481,7 +11096,7 @@ program.command("dev").description("Put your localhost behind a public URL (dev
|
|
|
10481
11096
|
try {
|
|
10482
11097
|
const resolved = parseInt(port ?? opts.port, 10);
|
|
10483
11098
|
if (Number.isNaN(resolved)) {
|
|
10484
|
-
console.error(
|
|
11099
|
+
console.error(import_chalk53.default.red(`Invalid port: ${port ?? opts.port}`));
|
|
10485
11100
|
process.exit(1);
|
|
10486
11101
|
}
|
|
10487
11102
|
await runDev({ port: resolved, project: opts.project, yes: opts.yes, captureFile: opts.captureFile, newSession: opts.newSession });
|
|
@@ -10613,7 +11228,7 @@ function groupedCommandHelp() {
|
|
|
10613
11228
|
const sub = byName.get(e.parent)?.commands.find((s) => s.name() === e.sub);
|
|
10614
11229
|
return sub ? ` ${helpLabel(e).padEnd(width)}${sub.description()}` : "";
|
|
10615
11230
|
}).filter(Boolean).join("\n");
|
|
10616
|
-
return `${
|
|
11231
|
+
return `${import_chalk53.default.bold(g.title)}
|
|
10617
11232
|
${rows}`;
|
|
10618
11233
|
}).join("\n\n");
|
|
10619
11234
|
}
|
|
@@ -10651,14 +11266,14 @@ async function recoverStaleTeam() {
|
|
|
10651
11266
|
const { resolveLinkedTeam: resolveLinkedTeam2 } = await Promise.resolve().then(() => (init_team(), team_exports));
|
|
10652
11267
|
const linked = await resolveLinkedTeam2({ preferredId: creds.teamId, interactive: !!process.stdin.isTTY });
|
|
10653
11268
|
if (!linked) {
|
|
10654
|
-
console.error(
|
|
11269
|
+
console.error(import_chalk53.default.yellow("Your account has no teams anymore (deleted?). Run `apiblaze login` or `apiblaze create` to get a workspace."));
|
|
10655
11270
|
return;
|
|
10656
11271
|
}
|
|
10657
11272
|
if (linked.teamId === creds.teamId) return;
|
|
10658
11273
|
const next = { ...creds, teamId: linked.teamId, teamName: linked.teamName };
|
|
10659
11274
|
delete next.activeTenant;
|
|
10660
11275
|
saveCredentials(next);
|
|
10661
|
-
console.error(
|
|
11276
|
+
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.`));
|
|
10662
11277
|
} catch {
|
|
10663
11278
|
}
|
|
10664
11279
|
}
|
|
@@ -10666,16 +11281,16 @@ async function printError(err) {
|
|
|
10666
11281
|
if (err instanceof ApiError) {
|
|
10667
11282
|
const data = err.body;
|
|
10668
11283
|
const extra = [data?.body?.reason, data?.body?.details, data?.details, data?.body?.error].find((x) => typeof x === "string" && x && x !== err.message);
|
|
10669
|
-
console.error(
|
|
11284
|
+
console.error(import_chalk53.default.red(`
|
|
10670
11285
|
API error (${err.status}): ${err.message}${extra ? ` \u2014 ${extra}` : ""}`));
|
|
10671
11286
|
if (err.status === 403 || err.status === 404) {
|
|
10672
11287
|
await recoverStaleTeam();
|
|
10673
11288
|
}
|
|
10674
11289
|
} else if (err instanceof Error) {
|
|
10675
|
-
console.error(
|
|
11290
|
+
console.error(import_chalk53.default.red(`
|
|
10676
11291
|
Error: ${err.message}`));
|
|
10677
11292
|
} else {
|
|
10678
|
-
console.error(
|
|
11293
|
+
console.error(import_chalk53.default.red("\nUnknown error"));
|
|
10679
11294
|
}
|
|
10680
11295
|
}
|
|
10681
11296
|
program.parse(process.argv);
|