apiblaze 0.20.8 → 0.20.10
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 +608 -183
- 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
|
@@ -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();
|
|
@@ -932,7 +1024,7 @@ var import_commander = require("commander");
|
|
|
932
1024
|
var import_chalk52 = __toESM(require("chalk"));
|
|
933
1025
|
|
|
934
1026
|
// package.json
|
|
935
|
-
var version = "0.20.
|
|
1027
|
+
var version = "0.20.10";
|
|
936
1028
|
|
|
937
1029
|
// src/index.ts
|
|
938
1030
|
init_types();
|
|
@@ -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
|
}
|
|
@@ -2171,7 +2334,7 @@ async function runCreate(opts = {}) {
|
|
|
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
|
}
|
|
@@ -2193,7 +2356,7 @@ async function runCreate(opts = {}) {
|
|
|
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"}`));
|
|
@@ -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",
|
|
@@ -8088,19 +8269,19 @@ async function runPreapprove(who, opts) {
|
|
|
8088
8269
|
}
|
|
8089
8270
|
|
|
8090
8271
|
// src/commands/apichat.ts
|
|
8091
|
-
var
|
|
8272
|
+
var fs10 = __toESM(require("fs"));
|
|
8092
8273
|
var path6 = __toESM(require("path"));
|
|
8093
8274
|
var crypto2 = __toESM(require("crypto"));
|
|
8094
8275
|
var import_chalk46 = __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."));
|
|
@@ -8182,7 +8363,7 @@ Error: ${message}`));
|
|
|
8182
8363
|
function normalizeName3(raw) {
|
|
8183
8364
|
return (raw || "").toLowerCase().replace(/[^a-z0-9]/g, "");
|
|
8184
8365
|
}
|
|
8185
|
-
function
|
|
8366
|
+
function isHttpUrl3(s) {
|
|
8186
8367
|
try {
|
|
8187
8368
|
const u = new URL((s || "").trim());
|
|
8188
8369
|
return u.protocol === "http:" || u.protocol === "https:";
|
|
@@ -8196,7 +8377,7 @@ function parseSpec(text) {
|
|
|
8196
8377
|
parsed = JSON.parse(text);
|
|
8197
8378
|
} catch {
|
|
8198
8379
|
try {
|
|
8199
|
-
parsed = (0,
|
|
8380
|
+
parsed = (0, import_yaml3.parse)(text);
|
|
8200
8381
|
} catch {
|
|
8201
8382
|
fail4("Could not parse the spec as JSON or YAML.");
|
|
8202
8383
|
}
|
|
@@ -8233,21 +8414,21 @@ async function discoverSpec(target) {
|
|
|
8233
8414
|
}
|
|
8234
8415
|
async function loadSpec(opts) {
|
|
8235
8416
|
if (opts.openapispec) {
|
|
8236
|
-
if (
|
|
8417
|
+
if (isHttpUrl3(opts.openapispec)) {
|
|
8237
8418
|
const text2 = await fetchText(opts.openapispec);
|
|
8238
8419
|
if (!text2) fail4(`Could not fetch the spec at ${opts.openapispec}.`);
|
|
8239
8420
|
return { spec: parseSpec(text2), sourceUrl: opts.openapispec };
|
|
8240
8421
|
}
|
|
8241
8422
|
let text;
|
|
8242
8423
|
try {
|
|
8243
|
-
text =
|
|
8424
|
+
text = fs10.readFileSync(opts.openapispec, "utf-8");
|
|
8244
8425
|
} catch {
|
|
8245
8426
|
fail4(`Cannot read spec file: ${opts.openapispec}`);
|
|
8246
8427
|
}
|
|
8247
8428
|
return { spec: parseSpec(text) };
|
|
8248
8429
|
}
|
|
8249
8430
|
if (opts.target) {
|
|
8250
|
-
if (!
|
|
8431
|
+
if (!isHttpUrl3(opts.target)) fail4("--target must be a valid http(s) URL.");
|
|
8251
8432
|
const found = await discoverSpec(opts.target);
|
|
8252
8433
|
if (!found) {
|
|
8253
8434
|
fail4(`No OpenAPI spec found at ${opts.target} (tried /openapi.json, /openapi.yaml, /swagger.json).`, GENERATOR_HINT);
|
|
@@ -8258,7 +8439,7 @@ async function loadSpec(opts) {
|
|
|
8258
8439
|
}
|
|
8259
8440
|
function resolveTarget(spec2, opts, sourceUrl) {
|
|
8260
8441
|
if (opts.target) {
|
|
8261
|
-
if (!
|
|
8442
|
+
if (!isHttpUrl3(opts.target)) fail4("--target must be a valid http(s) URL.");
|
|
8262
8443
|
return opts.target.trim();
|
|
8263
8444
|
}
|
|
8264
8445
|
const servers = Array.isArray(spec2.servers) ? spec2.servers : [];
|
|
@@ -8268,7 +8449,7 @@ function resolveTarget(spec2, opts, sourceUrl) {
|
|
|
8268
8449
|
}
|
|
8269
8450
|
try {
|
|
8270
8451
|
const resolved = sourceUrl ? new URL(raw, sourceUrl).toString() : raw;
|
|
8271
|
-
if (!
|
|
8452
|
+
if (!isHttpUrl3(resolved)) {
|
|
8272
8453
|
fail4(`servers[0].url ("${raw}") is not an absolute URL and could not be resolved.`, "Re-run with --target <upstream base URL>.");
|
|
8273
8454
|
}
|
|
8274
8455
|
return resolved;
|
|
@@ -8378,9 +8559,10 @@ async function captureTargetSecret(auth, opts) {
|
|
|
8378
8559
|
return secret;
|
|
8379
8560
|
}
|
|
8380
8561
|
async function dataPlaneAuth(p) {
|
|
8562
|
+
const idHeader = p.endUserId ? { "X-End-User-Id": p.endUserId } : {};
|
|
8381
8563
|
if (!p.consumerAuth) {
|
|
8382
|
-
if (!p.dpKey)
|
|
8383
|
-
return { "X-API-Key": p.dpKey };
|
|
8564
|
+
if (!p.dpKey) return idHeader;
|
|
8565
|
+
return { "X-API-Key": p.dpKey, ...idHeader };
|
|
8384
8566
|
}
|
|
8385
8567
|
const stored = loadConsumer();
|
|
8386
8568
|
if (!stored) {
|
|
@@ -8388,7 +8570,7 @@ async function dataPlaneAuth(p) {
|
|
|
8388
8570
|
}
|
|
8389
8571
|
const fresh = await validConsumerToken(stored) ?? stored;
|
|
8390
8572
|
if (fresh.accessToken !== stored.accessToken) saveConsumer(fresh);
|
|
8391
|
-
return { Authorization: `Bearer ${fresh.accessToken}
|
|
8573
|
+
return { Authorization: `Bearer ${fresh.accessToken}`, ...idHeader };
|
|
8392
8574
|
}
|
|
8393
8575
|
async function ensureConsumerLogin(teamId, tenant2, version2) {
|
|
8394
8576
|
const resource = `https://${tenant2}.portal.apiblaze.com/${version2}`;
|
|
@@ -8648,7 +8830,6 @@ async function publishMcp(p, spec2) {
|
|
|
8648
8830
|
return null;
|
|
8649
8831
|
}
|
|
8650
8832
|
}
|
|
8651
|
-
var CLIENT_ROUND_CAP = 12;
|
|
8652
8833
|
function chatUrl(p) {
|
|
8653
8834
|
return `https://${p.mcpHost}/${p.version}/${p.environment}/runtime-chat`;
|
|
8654
8835
|
}
|
|
@@ -8656,91 +8837,66 @@ function maskKey(k) {
|
|
|
8656
8837
|
return k.length <= 8 ? "****" : `${k.slice(0, 4)}\u2026${k.slice(-4)}`;
|
|
8657
8838
|
}
|
|
8658
8839
|
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)}`);
|
|
8840
|
+
function billingLine(info) {
|
|
8841
|
+
if (!info || typeof info.charged_cents !== "number" || info.charged_cents <= 0) return null;
|
|
8842
|
+
if (typeof info.free_turns_remaining === "number") return null;
|
|
8843
|
+
const cents = info.charged_cents;
|
|
8844
|
+
const usd = (cents / 100).toFixed(Math.abs(cents - Math.round(cents)) < 1e-9 ? 2 : 4);
|
|
8845
|
+
let line = import_chalk46.default.magenta(` \u{1F4B3} $${usd}`);
|
|
8846
|
+
if (typeof info.credits_remaining === "number") {
|
|
8847
|
+
line += import_chalk46.default.dim(` \xB7 balance $${(info.credits_remaining / 100).toFixed(2)}`);
|
|
8705
8848
|
}
|
|
8706
8849
|
return line;
|
|
8707
8850
|
}
|
|
8708
|
-
function freeBudgetWarning(
|
|
8709
|
-
if (!anon || !
|
|
8710
|
-
if (typeof
|
|
8711
|
-
const left2 =
|
|
8851
|
+
function freeBudgetWarning(info, anon) {
|
|
8852
|
+
if (!anon || !info) return null;
|
|
8853
|
+
if (typeof info.free_turns_remaining === "number") {
|
|
8854
|
+
const left2 = info.free_turns_remaining;
|
|
8712
8855
|
if (left2 <= 0) return import_chalk46.default.yellow(" Free chats used up \u2014 `npx apiblaze login` (free) to keep going.");
|
|
8713
8856
|
return import_chalk46.default.dim(` ${left2} free chat${left2 === 1 ? "" : "s"} left \xB7 /login to get more`);
|
|
8714
8857
|
}
|
|
8715
|
-
if (typeof
|
|
8716
|
-
const perTurn = Math.max(
|
|
8717
|
-
const left = Math.floor(
|
|
8858
|
+
if (typeof info.free_remaining_cents !== "number") return null;
|
|
8859
|
+
const perTurn = Math.max(info.charged_cents || 0, 0.02);
|
|
8860
|
+
const left = Math.floor(info.free_remaining_cents / perTurn);
|
|
8718
8861
|
if (left > 8) return null;
|
|
8719
8862
|
if (left <= 0) return import_chalk46.default.yellow(" Free messages used up \u2014 `npx apiblaze login` (free) to keep chatting.");
|
|
8720
8863
|
return import_chalk46.default.yellow(` \u26A0 About ${left} free message${left === 1 ? "" : "s"} left \u2014 \`npx apiblaze login\` (free) for more.`);
|
|
8721
8864
|
}
|
|
8722
|
-
function
|
|
8723
|
-
|
|
8724
|
-
|
|
8725
|
-
|
|
8726
|
-
|
|
8727
|
-
|
|
8865
|
+
async function readSse(body, onEvent) {
|
|
8866
|
+
const reader = body.getReader();
|
|
8867
|
+
const decoder = new TextDecoder();
|
|
8868
|
+
let buf = "";
|
|
8869
|
+
for (; ; ) {
|
|
8870
|
+
const { done, value } = await reader.read();
|
|
8871
|
+
if (done) break;
|
|
8872
|
+
buf += decoder.decode(value, { stream: true });
|
|
8873
|
+
for (; ; ) {
|
|
8874
|
+
const at = buf.indexOf("\n\n");
|
|
8875
|
+
if (at === -1) break;
|
|
8876
|
+
const rawEvent = buf.slice(0, at);
|
|
8877
|
+
buf = buf.slice(at + 2);
|
|
8878
|
+
const data = rawEvent.split("\n").filter((l) => l.startsWith("data:")).map((l) => l.slice(5).replace(/^ /, "")).join("\n");
|
|
8879
|
+
if (!data || data === "[DONE]") continue;
|
|
8880
|
+
try {
|
|
8881
|
+
onEvent(JSON.parse(data));
|
|
8882
|
+
} catch {
|
|
8883
|
+
}
|
|
8728
8884
|
}
|
|
8729
8885
|
}
|
|
8730
8886
|
}
|
|
8731
8887
|
async function replTurn(p, messages, userText) {
|
|
8732
|
-
messages.push({ role: "user",
|
|
8888
|
+
messages.push({ id: crypto2.randomUUID(), role: "user", parts: [{ type: "text", text: userText }] });
|
|
8733
8889
|
const llm2 = loadLlmConfig();
|
|
8734
8890
|
const turnId = crypto2.randomUUID();
|
|
8735
|
-
|
|
8736
|
-
|
|
8737
|
-
|
|
8738
|
-
|
|
8739
|
-
|
|
8740
|
-
|
|
8741
|
-
|
|
8742
|
-
|
|
8743
|
-
|
|
8891
|
+
const spinner = (0, import_ora23.default)({ text: "thinking...", color: "magenta" }).start();
|
|
8892
|
+
const body = {
|
|
8893
|
+
turn_id: turnId,
|
|
8894
|
+
messages,
|
|
8895
|
+
environment: p.environment,
|
|
8896
|
+
...llm2 ? { llm_api_key: llm2.key, llm_provider: llm2.provider } : {}
|
|
8897
|
+
};
|
|
8898
|
+
let res = null;
|
|
8899
|
+
for (let attempt = 0; attempt < 3; attempt++) {
|
|
8744
8900
|
try {
|
|
8745
8901
|
res = await fetch(chatUrl(p), {
|
|
8746
8902
|
method: "POST",
|
|
@@ -8752,57 +8908,248 @@ async function replTurn(p, messages, userText) {
|
|
|
8752
8908
|
console.log(import_chalk46.default.red(` Could not reach ${p.mcpHost}: ${err instanceof Error ? err.message : String(err)}`));
|
|
8753
8909
|
return;
|
|
8754
8910
|
}
|
|
8911
|
+
if ((res.headers.get("content-type") ?? "").includes("text/event-stream") && res.ok && res.body) break;
|
|
8912
|
+
spinner.stop();
|
|
8755
8913
|
let data = null;
|
|
8756
8914
|
try {
|
|
8757
8915
|
data = await res.json();
|
|
8758
8916
|
} catch {
|
|
8759
8917
|
}
|
|
8760
|
-
|
|
8761
|
-
|
|
8762
|
-
|
|
8918
|
+
const errObj = data && typeof data.error === "object" ? data.error : null;
|
|
8919
|
+
const code = errObj && errObj.code || null;
|
|
8920
|
+
const msg = String(errObj && errObj.message || data && (data.error || data.message) || "");
|
|
8921
|
+
const tty = !!process.stdin.isTTY;
|
|
8922
|
+
if (res.status === 404 && /project not found/i.test(msg)) {
|
|
8923
|
+
const flipped = p.mcpHost.includes(".tryabz.run") ? p.mcpHost.replace(".tryabz.run", ".abz.run") : p.mcpHost.replace(".abz.run", ".tryabz.run");
|
|
8924
|
+
if (flipped !== p.mcpHost && attempt === 0) {
|
|
8925
|
+
p.mcpHost = flipped;
|
|
8926
|
+
p.proxyUrl = p.proxyUrl?.includes("tryabz.run") ? p.proxyUrl.replace("tryabz.run", "abz.run") : p.proxyUrl?.replace("abz.run", "tryabz.run");
|
|
8927
|
+
p.anon = flipped.includes(".tryabz.run");
|
|
8928
|
+
spinner.start("retrying on the " + (p.anon ? "trial" : "claimed") + " plane\u2026");
|
|
8929
|
+
continue;
|
|
8930
|
+
}
|
|
8931
|
+
console.log(import_chalk46.default.red(` No proxy named ${p.projectId} was found (tried both abz.run and tryabz.run).`));
|
|
8763
8932
|
return;
|
|
8764
8933
|
}
|
|
8765
|
-
if (
|
|
8766
|
-
|
|
8934
|
+
if (code === "identity_required" || /identif/i.test(msg) && !code) {
|
|
8935
|
+
if (!p.endUserId && tty) {
|
|
8936
|
+
console.log(import_chalk46.default.yellow(" This API requires every call to say WHO is calling."));
|
|
8937
|
+
const { default: inquirer3 } = await import("inquirer");
|
|
8938
|
+
const { id } = await inquirer3.prompt([{ type: "input", name: "id", message: "Your end-user id (usually your email):" }]);
|
|
8939
|
+
if (typeof id === "string" && id.trim()) {
|
|
8940
|
+
p.endUserId = id.trim();
|
|
8941
|
+
persistAuthState(p);
|
|
8942
|
+
spinner.start("retrying\u2026");
|
|
8943
|
+
continue;
|
|
8944
|
+
}
|
|
8945
|
+
}
|
|
8946
|
+
console.log(import_chalk46.default.red(" This API requires an identified caller."));
|
|
8947
|
+
console.log(import_chalk46.default.dim(" Re-run with --xenduserid <your id> (usually your email)."));
|
|
8767
8948
|
return;
|
|
8768
8949
|
}
|
|
8769
|
-
if (
|
|
8770
|
-
|
|
8771
|
-
|
|
8772
|
-
|
|
8773
|
-
console.log(
|
|
8774
|
-
|
|
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
|
-
);
|
|
8950
|
+
if (code === "user_not_preapproved") {
|
|
8951
|
+
console.log(import_chalk46.default.yellow(` ${msg || "You are not pre-approved on this API."}`));
|
|
8952
|
+
if (p.endUserId) console.log(import_chalk46.default.dim(` Identity sent: ${p.endUserId}`));
|
|
8953
|
+
const accessUrl = errObj && errObj.request_access_url;
|
|
8954
|
+
if (accessUrl) console.log(` Request access: ${import_chalk46.default.bold(String(accessUrl))}`);
|
|
8955
|
+
console.log(import_chalk46.default.dim(" Or ask the producer to pre-approve you: `apiblaze preapprove <your-email> --tenant <tenant>`."));
|
|
8778
8956
|
return;
|
|
8779
8957
|
}
|
|
8780
|
-
if (
|
|
8781
|
-
|
|
8782
|
-
console.log(import_chalk46.default.red(` Chat error: ${err}`));
|
|
8958
|
+
if (code === "user_frozen") {
|
|
8959
|
+
console.log(import_chalk46.default.red(` ${msg || "Your access to this API has been frozen by the producer."}`));
|
|
8783
8960
|
return;
|
|
8784
8961
|
}
|
|
8785
|
-
|
|
8786
|
-
|
|
8787
|
-
|
|
8788
|
-
|
|
8789
|
-
|
|
8790
|
-
|
|
8791
|
-
|
|
8792
|
-
|
|
8793
|
-
|
|
8794
|
-
|
|
8795
|
-
|
|
8962
|
+
const oauthWanted = /oauth token required|authorization: bearer/i.test(msg);
|
|
8963
|
+
const keyWanted = /api key required|x-api-key/i.test(msg);
|
|
8964
|
+
if (oauthWanted && !p.consumerAuth) {
|
|
8965
|
+
if (p.teamId && p.tenant && loadCredentials()) {
|
|
8966
|
+
console.log(import_chalk46.default.dim(" This proxy signs consumers in with OAuth \u2014 starting the login\u2026"));
|
|
8967
|
+
try {
|
|
8968
|
+
await ensureConsumerLogin(p.teamId, p.tenant, p.version);
|
|
8969
|
+
p.consumerAuth = true;
|
|
8970
|
+
p.dpKey = void 0;
|
|
8971
|
+
persistAuthState(p);
|
|
8972
|
+
spinner.start("retrying\u2026");
|
|
8973
|
+
continue;
|
|
8974
|
+
} catch (err) {
|
|
8975
|
+
console.log(import_chalk46.default.red(` Login failed: ${err instanceof Error ? err.message : String(err)}`));
|
|
8976
|
+
return;
|
|
8977
|
+
}
|
|
8978
|
+
}
|
|
8979
|
+
console.log(import_chalk46.default.red(" This proxy signs consumers in with OAuth (a login), not an API key."));
|
|
8980
|
+
console.log(import_chalk46.default.dim(" Sign in with: `apiblaze consumer login --tenant <tenant> --client <app-client-id>`, then re-run apichat."));
|
|
8981
|
+
return;
|
|
8796
8982
|
}
|
|
8797
|
-
|
|
8798
|
-
|
|
8799
|
-
|
|
8800
|
-
|
|
8801
|
-
|
|
8983
|
+
if (keyWanted) {
|
|
8984
|
+
if (tty) {
|
|
8985
|
+
console.log(import_chalk46.default.yellow(` ${msg || "This API requires an API key."}`));
|
|
8986
|
+
const { default: inquirer3 } = await import("inquirer");
|
|
8987
|
+
const { key } = await inquirer3.prompt([{ type: "password", name: "key", mask: "*", message: "API key for this proxy:" }]);
|
|
8988
|
+
if (typeof key === "string" && key.trim()) {
|
|
8989
|
+
p.dpKey = key.trim();
|
|
8990
|
+
p.consumerAuth = false;
|
|
8991
|
+
persistAuthState(p);
|
|
8992
|
+
spinner.start("retrying\u2026");
|
|
8993
|
+
continue;
|
|
8994
|
+
}
|
|
8995
|
+
}
|
|
8996
|
+
console.log(import_chalk46.default.red(` ${msg || "This API requires an API key."}`));
|
|
8997
|
+
console.log(import_chalk46.default.dim(" Re-run with --apikey <key> (mint one from the producer's site or dev portal)."));
|
|
8998
|
+
return;
|
|
8999
|
+
}
|
|
9000
|
+
if (res && (res.status === 402 || res.status === 403)) {
|
|
9001
|
+
renderUpsell(p, { reason: "PAUSED", message: msg || "This turn is not available on your current plan." });
|
|
9002
|
+
return;
|
|
9003
|
+
}
|
|
9004
|
+
if (res && res.status === 401) {
|
|
9005
|
+
console.log(import_chalk46.default.red(` The proxy rejected the request (401)${msg ? `: ${msg}` : "."}`));
|
|
9006
|
+
console.log(import_chalk46.default.dim(p.consumerAuth ? " Sent your consumer OAuth token. Run `apiblaze consumer login` again." : " Sent an API key. Pass a different one with --apikey <key>."));
|
|
9007
|
+
return;
|
|
9008
|
+
}
|
|
9009
|
+
console.log(import_chalk46.default.red(` Chat error: ${msg || (res ? `HTTP ${res.status}` : "request failed")}`));
|
|
9010
|
+
return;
|
|
8802
9011
|
}
|
|
8803
|
-
|
|
8804
|
-
|
|
8805
|
-
|
|
9012
|
+
if (!res || !res.body || !(res.headers.get("content-type") ?? "").includes("text/event-stream")) {
|
|
9013
|
+
spinner.stop();
|
|
9014
|
+
console.log(import_chalk46.default.red(" Chat error: could not authenticate to this proxy after several attempts."));
|
|
9015
|
+
return;
|
|
9016
|
+
}
|
|
9017
|
+
let spinnerLive = true;
|
|
9018
|
+
const stopSpinner = () => {
|
|
9019
|
+
if (spinnerLive) {
|
|
9020
|
+
spinner.stop();
|
|
9021
|
+
spinnerLive = false;
|
|
9022
|
+
}
|
|
9023
|
+
};
|
|
9024
|
+
const parts = [];
|
|
9025
|
+
let openTextIdx = -1;
|
|
9026
|
+
let assistantOpen = false;
|
|
9027
|
+
const toolMeta = /* @__PURE__ */ new Map();
|
|
9028
|
+
let upsell = null;
|
|
9029
|
+
let turnInfo = null;
|
|
9030
|
+
let errorText = null;
|
|
9031
|
+
let messageId = `msg_${Date.now()}`;
|
|
9032
|
+
const credHint = () => {
|
|
9033
|
+
if (!isVerbose()) return null;
|
|
9034
|
+
if (p.consumerAuth) {
|
|
9035
|
+
const t = loadConsumer()?.accessToken;
|
|
9036
|
+
return t ? `Authorization: Bearer ${revealAuth ? t : maskKey(t)}` : null;
|
|
9037
|
+
}
|
|
9038
|
+
return p.dpKey ? `X-API-Key: ${revealAuth ? p.dpKey : maskKey(p.dpKey)}` : null;
|
|
9039
|
+
};
|
|
9040
|
+
try {
|
|
9041
|
+
await readSse(res.body, (ev) => {
|
|
9042
|
+
switch (ev?.type) {
|
|
9043
|
+
case "start":
|
|
9044
|
+
if (typeof ev.messageId === "string") messageId = ev.messageId;
|
|
9045
|
+
break;
|
|
9046
|
+
case "tool-input-start": {
|
|
9047
|
+
stopSpinner();
|
|
9048
|
+
if (assistantOpen) {
|
|
9049
|
+
process.stdout.write("\n");
|
|
9050
|
+
assistantOpen = false;
|
|
9051
|
+
}
|
|
9052
|
+
const name = String(ev.toolName ?? "tool");
|
|
9053
|
+
parts.push({ type: `tool-${name}`, toolCallId: String(ev.toolCallId ?? ""), state: "input-streaming" });
|
|
9054
|
+
toolMeta.set(String(ev.toolCallId ?? ""), { name, startedAt: Date.now(), partIdx: parts.length - 1 });
|
|
9055
|
+
console.log(` ${import_chalk46.default.cyan("\u2699")} ${import_chalk46.default.cyan(name)}${import_chalk46.default.dim("\u2026")}`);
|
|
9056
|
+
break;
|
|
9057
|
+
}
|
|
9058
|
+
case "tool-input-available": {
|
|
9059
|
+
const id = String(ev.toolCallId ?? "");
|
|
9060
|
+
const meta = toolMeta.get(id);
|
|
9061
|
+
const input = ev.input && typeof ev.input === "object" ? ev.input : {};
|
|
9062
|
+
if (meta) {
|
|
9063
|
+
meta.input = input;
|
|
9064
|
+
Object.assign(parts[meta.partIdx], { state: "input-available", input });
|
|
9065
|
+
}
|
|
9066
|
+
if (isVerbose()) {
|
|
9067
|
+
console.log(import_chalk46.default.dim(` args ${JSON.stringify(input)}`));
|
|
9068
|
+
const hint = credHint();
|
|
9069
|
+
if (hint) console.log(import_chalk46.default.dim(` auth ${hint}`) + (revealAuth ? "" : import_chalk46.default.yellow(" \u2190 /showauth reveals")));
|
|
9070
|
+
}
|
|
9071
|
+
break;
|
|
9072
|
+
}
|
|
9073
|
+
case "tool-output-available":
|
|
9074
|
+
case "tool-output-error": {
|
|
9075
|
+
stopSpinner();
|
|
9076
|
+
const id = String(ev.toolCallId ?? "");
|
|
9077
|
+
const meta = toolMeta.get(id);
|
|
9078
|
+
const ok = ev.type === "tool-output-available";
|
|
9079
|
+
const ms = meta ? Date.now() - meta.startedAt : void 0;
|
|
9080
|
+
const mark = ok ? import_chalk46.default.green("\u2713") : import_chalk46.default.red("\u2717");
|
|
9081
|
+
console.log(` ${mark} ${import_chalk46.default.cyan(meta?.name ?? "tool")} ${import_chalk46.default.dim(`(${ok ? "ok" : "error"}${ms != null ? `, ${ms}ms` : ""})`)}`);
|
|
9082
|
+
const detail = ok ? String(ev.output ?? "") : String(ev.errorText ?? "Tool call failed.");
|
|
9083
|
+
if (meta) {
|
|
9084
|
+
Object.assign(parts[meta.partIdx], ok ? { state: "output-available", output: detail } : { state: "output-error", errorText: detail });
|
|
9085
|
+
}
|
|
9086
|
+
if (!ok || isVerbose()) {
|
|
9087
|
+
const pretty = (() => {
|
|
9088
|
+
try {
|
|
9089
|
+
return JSON.stringify(JSON.parse(detail), null, 2);
|
|
9090
|
+
} catch {
|
|
9091
|
+
return detail;
|
|
9092
|
+
}
|
|
9093
|
+
})();
|
|
9094
|
+
const lines = pretty.split("\n");
|
|
9095
|
+
const cap = ok ? 12 : 24;
|
|
9096
|
+
console.log(import_chalk46.default.dim(" response:"));
|
|
9097
|
+
for (const line of lines.slice(0, cap)) console.log(import_chalk46.default.dim(` ${line}`));
|
|
9098
|
+
if (lines.length > cap) console.log(import_chalk46.default.dim(` \u2026${lines.length - cap} more lines`));
|
|
9099
|
+
}
|
|
9100
|
+
break;
|
|
9101
|
+
}
|
|
9102
|
+
case "text-start":
|
|
9103
|
+
stopSpinner();
|
|
9104
|
+
parts.push({ type: "text", text: "" });
|
|
9105
|
+
openTextIdx = parts.length - 1;
|
|
9106
|
+
if (!assistantOpen) {
|
|
9107
|
+
process.stdout.write("\n" + import_chalk46.default.green("assistant \u203A "));
|
|
9108
|
+
assistantOpen = true;
|
|
9109
|
+
}
|
|
9110
|
+
break;
|
|
9111
|
+
case "text-delta": {
|
|
9112
|
+
const delta = String(ev.delta ?? "");
|
|
9113
|
+
if (openTextIdx >= 0) parts[openTextIdx].text = String(parts[openTextIdx].text ?? "") + delta;
|
|
9114
|
+
process.stdout.write(delta);
|
|
9115
|
+
break;
|
|
9116
|
+
}
|
|
9117
|
+
case "text-end":
|
|
9118
|
+
openTextIdx = -1;
|
|
9119
|
+
break;
|
|
9120
|
+
case "data-apiblaze-upsell": {
|
|
9121
|
+
const d = ev.data ?? {};
|
|
9122
|
+
upsell = { reason: String(d.reason ?? ""), message: String(d.message ?? "") };
|
|
9123
|
+
break;
|
|
9124
|
+
}
|
|
9125
|
+
case "data-apiblaze-turn":
|
|
9126
|
+
turnInfo = ev.data ?? {};
|
|
9127
|
+
break;
|
|
9128
|
+
case "error":
|
|
9129
|
+
stopSpinner();
|
|
9130
|
+
errorText = String(ev.errorText ?? "Something went wrong.");
|
|
9131
|
+
break;
|
|
9132
|
+
default:
|
|
9133
|
+
break;
|
|
9134
|
+
}
|
|
9135
|
+
});
|
|
9136
|
+
} catch (err) {
|
|
9137
|
+
stopSpinner();
|
|
9138
|
+
console.log(import_chalk46.default.red(` Stream error: ${err instanceof Error ? err.message : String(err)}`));
|
|
9139
|
+
}
|
|
9140
|
+
stopSpinner();
|
|
9141
|
+
if (assistantOpen) process.stdout.write("\n\n");
|
|
9142
|
+
if (parts.length) messages.push({ id: messageId, role: "assistant", parts });
|
|
9143
|
+
if (errorText) console.log(import_chalk46.default.red(` ${errorText}`));
|
|
9144
|
+
if (upsell) {
|
|
9145
|
+
renderUpsell(p, upsell, { messageAlreadyShown: true });
|
|
9146
|
+
}
|
|
9147
|
+
const bl = billingLine(turnInfo);
|
|
9148
|
+
if (bl) console.log(bl);
|
|
9149
|
+
const warn = freeBudgetWarning(turnInfo, p.anon);
|
|
9150
|
+
if (warn) console.log(warn);
|
|
9151
|
+
}
|
|
9152
|
+
function renderUpsell(p, upsell, opts = {}) {
|
|
8806
9153
|
const loggedIn = !!loadCredentials();
|
|
8807
9154
|
if (upsell.reason === "CAPPED" && !loggedIn) {
|
|
8808
9155
|
console.log("\n" + import_chalk46.default.yellow(" Type `npx apiblaze login` to claim the rest of your balance."));
|
|
@@ -8810,7 +9157,9 @@ function renderUpsell(p, upsell) {
|
|
|
8810
9157
|
console.log();
|
|
8811
9158
|
return;
|
|
8812
9159
|
}
|
|
8813
|
-
|
|
9160
|
+
if (!opts.messageAlreadyShown) {
|
|
9161
|
+
console.log("\n" + import_chalk46.default.yellow(` ${upsell.message || "This turn is not available right now."}`));
|
|
9162
|
+
}
|
|
8814
9163
|
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
9164
|
if (!loggedIn) {
|
|
8816
9165
|
console.log(import_chalk46.default.dim(" Options: `/login` for more free chats and requests, or `apiblaze llm set-key` to bring your own model key."));
|
|
@@ -8825,17 +9174,17 @@ function renderUpsell(p, upsell) {
|
|
|
8825
9174
|
var apichatsPath = () => path6.join(getApiblazeDir(), "apichats.json");
|
|
8826
9175
|
function loadApichats() {
|
|
8827
9176
|
try {
|
|
8828
|
-
const list = JSON.parse(
|
|
9177
|
+
const list = JSON.parse(fs10.readFileSync(apichatsPath(), "utf-8"));
|
|
8829
9178
|
return Array.isArray(list) ? list : [];
|
|
8830
9179
|
} catch {
|
|
8831
9180
|
return [];
|
|
8832
9181
|
}
|
|
8833
9182
|
}
|
|
8834
9183
|
function writeApichats(list) {
|
|
8835
|
-
|
|
8836
|
-
|
|
9184
|
+
fs10.mkdirSync(getApiblazeDir(), { recursive: true });
|
|
9185
|
+
fs10.writeFileSync(apichatsPath(), JSON.stringify(list, null, 2), "utf-8");
|
|
8837
9186
|
try {
|
|
8838
|
-
|
|
9187
|
+
fs10.chmodSync(apichatsPath(), 384);
|
|
8839
9188
|
} catch {
|
|
8840
9189
|
}
|
|
8841
9190
|
}
|
|
@@ -8849,6 +9198,16 @@ function upsertApichat(entry) {
|
|
|
8849
9198
|
else list.unshift(entry);
|
|
8850
9199
|
writeApichats(list.slice(0, 30));
|
|
8851
9200
|
}
|
|
9201
|
+
function persistAuthState(p) {
|
|
9202
|
+
const list = loadApichats();
|
|
9203
|
+
const i = list.findIndex((a) => a.projectId === p.projectId && a.version === p.version);
|
|
9204
|
+
if (i < 0) return;
|
|
9205
|
+
list[i].dpKey = p.dpKey;
|
|
9206
|
+
list[i].consumerAuth = p.consumerAuth;
|
|
9207
|
+
list[i].endUserId = p.endUserId;
|
|
9208
|
+
list[i].updatedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
9209
|
+
writeApichats(list);
|
|
9210
|
+
}
|
|
8852
9211
|
function saveTranscript(p, messages) {
|
|
8853
9212
|
const list = loadApichats();
|
|
8854
9213
|
const i = list.findIndex((a) => apichatKey(a) === apichatKey(p));
|
|
@@ -8898,6 +9257,57 @@ async function fetchProxyDoor(teamId, projectId, apiVersion) {
|
|
|
8898
9257
|
return { methods: null };
|
|
8899
9258
|
}
|
|
8900
9259
|
}
|
|
9260
|
+
async function openDirectProject(projectId, opts) {
|
|
9261
|
+
const saved = loadApichats().find((a) => a.projectId === projectId);
|
|
9262
|
+
const version2 = opts.apiversion || saved?.version || "1.0.0";
|
|
9263
|
+
const environment = opts.environment || saved?.environment || "prod";
|
|
9264
|
+
const anon = saved?.anon ?? !loadCredentials();
|
|
9265
|
+
const mcpHost = saved?.mcpHost || `${projectId}.mcp.${anon ? "tryabz" : "abz"}.run`;
|
|
9266
|
+
const p = {
|
|
9267
|
+
projectId,
|
|
9268
|
+
version: version2,
|
|
9269
|
+
environment,
|
|
9270
|
+
dpKey: opts.apikey ?? saved?.dpKey,
|
|
9271
|
+
consumerAuth: opts.apikey ? false : saved?.consumerAuth,
|
|
9272
|
+
mcpHost,
|
|
9273
|
+
proxyUrl: `https://${projectId}.${anon ? "tryabz" : "abz"}.run/${version2}/${environment}`,
|
|
9274
|
+
anon,
|
|
9275
|
+
access: "invite",
|
|
9276
|
+
tenant: saved?.tenant,
|
|
9277
|
+
teamId: saved?.teamId,
|
|
9278
|
+
endUserId: opts.xenduserid ?? saved?.endUserId
|
|
9279
|
+
};
|
|
9280
|
+
if (!p.dpKey && !p.consumerAuth) {
|
|
9281
|
+
if (process.stdin.isTTY) {
|
|
9282
|
+
const { default: inquirer3 } = await import("inquirer");
|
|
9283
|
+
const { key } = await inquirer3.prompt([{
|
|
9284
|
+
type: "password",
|
|
9285
|
+
name: "key",
|
|
9286
|
+
mask: "*",
|
|
9287
|
+
message: `API key for ${projectId} (leave empty if it uses a login):`
|
|
9288
|
+
}]);
|
|
9289
|
+
if (typeof key === "string" && key.trim()) p.dpKey = key.trim();
|
|
9290
|
+
else p.consumerAuth = true;
|
|
9291
|
+
}
|
|
9292
|
+
}
|
|
9293
|
+
console.log(` ${import_chalk46.default.dim("Proxy:")} ${import_chalk46.default.bold(p.proxyUrl)}`);
|
|
9294
|
+
if (p.endUserId) console.log(` ${import_chalk46.default.dim("Acting as:")} ${import_chalk46.default.bold(p.endUserId)}`);
|
|
9295
|
+
upsertApichat({
|
|
9296
|
+
name: projectId,
|
|
9297
|
+
target: p.proxyUrl,
|
|
9298
|
+
projectId,
|
|
9299
|
+
version: version2,
|
|
9300
|
+
environment,
|
|
9301
|
+
mcpHost,
|
|
9302
|
+
teamId: p.teamId,
|
|
9303
|
+
tenant: p.tenant,
|
|
9304
|
+
dpKey: p.dpKey,
|
|
9305
|
+
consumerAuth: p.consumerAuth,
|
|
9306
|
+
anon,
|
|
9307
|
+
endUserId: p.endUserId
|
|
9308
|
+
});
|
|
9309
|
+
return { p, messages: saved?.messages?.filter((m) => Array.isArray(m.parts)) ?? [] };
|
|
9310
|
+
}
|
|
8901
9311
|
async function openServerProxy(project) {
|
|
8902
9312
|
const version2 = project.apiVersion || "1.0.0";
|
|
8903
9313
|
const environment = "prod";
|
|
@@ -8976,15 +9386,15 @@ function discoverLocalSpecs() {
|
|
|
8976
9386
|
const found = [];
|
|
8977
9387
|
for (const n of known) {
|
|
8978
9388
|
try {
|
|
8979
|
-
if (
|
|
9389
|
+
if (fs10.statSync(path6.join(cwd, n)).isFile()) found.push(n);
|
|
8980
9390
|
} catch {
|
|
8981
9391
|
}
|
|
8982
9392
|
}
|
|
8983
9393
|
try {
|
|
8984
|
-
const files =
|
|
9394
|
+
const files = fs10.readdirSync(cwd).filter((f) => /\.(ya?ml|json)$/i.test(f) && !found.includes(f));
|
|
8985
9395
|
for (const f of files.slice(0, 60)) {
|
|
8986
9396
|
try {
|
|
8987
|
-
const head =
|
|
9397
|
+
const head = fs10.readFileSync(path6.join(cwd, f), "utf-8").slice(0, 4e3);
|
|
8988
9398
|
if (/["']?openapi["']?\s*:/i.test(head) || /["']?swagger["']?\s*:/i.test(head) || /^\s*paths\s*:/im.test(head) || /"paths"\s*:/.test(head)) {
|
|
8989
9399
|
found.push(f);
|
|
8990
9400
|
}
|
|
@@ -9119,7 +9529,7 @@ async function noArgsMenu(opts) {
|
|
|
9119
9529
|
}
|
|
9120
9530
|
async function runRepl(p, initialMessages) {
|
|
9121
9531
|
const { default: inquirer3 } = await import("inquirer");
|
|
9122
|
-
const messages = initialMessages
|
|
9532
|
+
const messages = (initialMessages ?? []).filter((m) => Array.isArray(m.parts));
|
|
9123
9533
|
console.log("\n" + import_chalk46.default.cyan.bold("Chat with your API") + import_chalk46.default.dim(` \xB7 ${p.mcpHost}`));
|
|
9124
9534
|
if (messages.length) console.log(import_chalk46.default.dim(` Resumed \u2014 ${messages.length} prior messages.`));
|
|
9125
9535
|
const llm2 = loadLlmConfig();
|
|
@@ -9184,6 +9594,20 @@ async function runRepl(p, initialMessages) {
|
|
|
9184
9594
|
async function runApichat(opts) {
|
|
9185
9595
|
setVerbose(opts.verbose !== false);
|
|
9186
9596
|
console.log(import_chalk46.default.bold("\napichat \u2014 turn any API into a chat\n"));
|
|
9597
|
+
if (opts.target && !opts.openapispec) {
|
|
9598
|
+
const { classifyTargetInput: classifyTargetInput2 } = await Promise.resolve().then(() => (init_spec_or_target(), spec_or_target_exports));
|
|
9599
|
+
const c = await classifyTargetInput2(opts.target, fail4);
|
|
9600
|
+
if (c.kind === "spec") {
|
|
9601
|
+
console.log(import_chalk46.default.dim(` --target is an OpenAPI document (${c.source}) \u2014 using it as the spec.`));
|
|
9602
|
+
opts.openapispec = opts.target;
|
|
9603
|
+
opts.target = void 0;
|
|
9604
|
+
}
|
|
9605
|
+
}
|
|
9606
|
+
if (opts.project) {
|
|
9607
|
+
const opened = await openDirectProject(opts.project, opts);
|
|
9608
|
+
await runRepl(opened.p, opened.messages);
|
|
9609
|
+
return;
|
|
9610
|
+
}
|
|
9187
9611
|
if (!opts.openapispec && !opts.target) {
|
|
9188
9612
|
if (!process.stdin.isTTY) {
|
|
9189
9613
|
fail4("No spec source. Pass --openapispec <file|url> or --target <url>.", GENERATOR_HINT);
|
|
@@ -9406,28 +9830,28 @@ async function runConsumerApikeys(opts) {
|
|
|
9406
9830
|
// src/commands/sidecar.ts
|
|
9407
9831
|
var import_chalk48 = __toESM(require("chalk"));
|
|
9408
9832
|
var import_ora25 = __toESM(require("ora"));
|
|
9409
|
-
var
|
|
9833
|
+
var fs11 = __toESM(require("fs"));
|
|
9410
9834
|
var path7 = __toESM(require("path"));
|
|
9411
9835
|
init_admin();
|
|
9412
9836
|
init_resolve();
|
|
9413
9837
|
init_auth();
|
|
9414
9838
|
function detectNextProject(root) {
|
|
9415
|
-
const hasConfig = ["next.config.js", "next.config.mjs", "next.config.ts"].some((f) =>
|
|
9839
|
+
const hasConfig = ["next.config.js", "next.config.mjs", "next.config.ts"].some((f) => fs11.existsSync(path7.join(root, f)));
|
|
9416
9840
|
let hasDep = false;
|
|
9417
9841
|
try {
|
|
9418
|
-
const pkg = JSON.parse(
|
|
9842
|
+
const pkg = JSON.parse(fs11.readFileSync(path7.join(root, "package.json"), "utf8"));
|
|
9419
9843
|
hasDep = !!(pkg.dependencies?.next || pkg.devDependencies?.next);
|
|
9420
9844
|
} catch {
|
|
9421
9845
|
}
|
|
9422
|
-
const appDir =
|
|
9423
|
-
const pagesDir =
|
|
9846
|
+
const appDir = fs11.existsSync(path7.join(root, "app")) || fs11.existsSync(path7.join(root, "src", "app"));
|
|
9847
|
+
const pagesDir = fs11.existsSync(path7.join(root, "pages")) || fs11.existsSync(path7.join(root, "src", "pages"));
|
|
9424
9848
|
return { found: hasConfig || hasDep || appDir || pagesDir, router: appDir ? "app" : pagesDir ? "pages" : null };
|
|
9425
9849
|
}
|
|
9426
9850
|
function upsertEnvLocal(root, token) {
|
|
9427
9851
|
const p = path7.join(root, ".env.local");
|
|
9428
9852
|
let existing = "";
|
|
9429
9853
|
try {
|
|
9430
|
-
existing =
|
|
9854
|
+
existing = fs11.readFileSync(p, "utf8");
|
|
9431
9855
|
} catch {
|
|
9432
9856
|
}
|
|
9433
9857
|
const had = /^APIBLAZE_API_KEY=/m.test(existing) || /^APIBLAZE_TOKEN=/m.test(existing);
|
|
@@ -9442,15 +9866,15 @@ function upsertEnvLocal(root, token) {
|
|
|
9442
9866
|
next = (next.endsWith("\n") ? next : next + "\n") + `APIBLAZE_SIDECAR_VERBOSE=true
|
|
9443
9867
|
`;
|
|
9444
9868
|
}
|
|
9445
|
-
|
|
9869
|
+
fs11.writeFileSync(p, next);
|
|
9446
9870
|
return had ? "rotated" : "created";
|
|
9447
9871
|
}
|
|
9448
9872
|
function installSidecarPackage(root) {
|
|
9449
|
-
if (
|
|
9873
|
+
if (fs11.existsSync(path7.join(root, "node_modules", "apiblaze", "package.json"))) {
|
|
9450
9874
|
console.log(` ${import_chalk48.default.green("\u2713")} apiblaze package already installed`);
|
|
9451
9875
|
return;
|
|
9452
9876
|
}
|
|
9453
|
-
const has = (f) =>
|
|
9877
|
+
const has = (f) => fs11.existsSync(path7.join(root, f));
|
|
9454
9878
|
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
9879
|
const spinner = (0, import_ora25.default)(`Installing the apiblaze package (${pm.cmd})\u2026`).start();
|
|
9456
9880
|
try {
|
|
@@ -9463,7 +9887,7 @@ function installSidecarPackage(root) {
|
|
|
9463
9887
|
}
|
|
9464
9888
|
function readEnvKey(root) {
|
|
9465
9889
|
try {
|
|
9466
|
-
const s =
|
|
9890
|
+
const s = fs11.readFileSync(path7.join(root, ".env.local"), "utf8");
|
|
9467
9891
|
const m = s.match(/^APIBLAZE_API_KEY=(.+)$/m) ?? s.match(/^APIBLAZE_TOKEN=(.+)$/m);
|
|
9468
9892
|
return m ? m[1].trim() : null;
|
|
9469
9893
|
} catch {
|
|
@@ -9474,13 +9898,13 @@ function ensureGitignored(root) {
|
|
|
9474
9898
|
const p = path7.join(root, ".gitignore");
|
|
9475
9899
|
let c = "";
|
|
9476
9900
|
try {
|
|
9477
|
-
c =
|
|
9901
|
+
c = fs11.readFileSync(p, "utf8");
|
|
9478
9902
|
} catch {
|
|
9479
9903
|
}
|
|
9480
|
-
if (!/^\.env\.local$/m.test(c) && !/^\.env\*/m.test(c))
|
|
9904
|
+
if (!/^\.env\.local$/m.test(c) && !/^\.env\*/m.test(c)) fs11.writeFileSync(p, (c && !c.endsWith("\n") ? c + "\n" : c) + ".env.local\n");
|
|
9481
9905
|
}
|
|
9482
9906
|
function wireInstrumentation(root) {
|
|
9483
|
-
const existing = ["instrumentation.ts", "instrumentation.js", path7.join("src", "instrumentation.ts")].map((c) => path7.join(root, c)).find((f) =>
|
|
9907
|
+
const existing = ["instrumentation.ts", "instrumentation.js", path7.join("src", "instrumentation.ts")].map((c) => path7.join(root, c)).find((f) => fs11.existsSync(f));
|
|
9484
9908
|
const body = `import { register as apiblaze } from "apiblaze/sidecar";
|
|
9485
9909
|
|
|
9486
9910
|
export function register() {
|
|
@@ -9488,18 +9912,18 @@ export function register() {
|
|
|
9488
9912
|
}
|
|
9489
9913
|
`;
|
|
9490
9914
|
if (!existing) {
|
|
9491
|
-
|
|
9915
|
+
fs11.writeFileSync(path7.join(root, "instrumentation.ts"), body);
|
|
9492
9916
|
return "created";
|
|
9493
9917
|
}
|
|
9494
|
-
const cur =
|
|
9918
|
+
const cur = fs11.readFileSync(existing, "utf8");
|
|
9495
9919
|
if (cur.includes("apiblaze/sidecar")) return "present";
|
|
9496
9920
|
if (/export\s+function\s+register\s*\(/.test(cur)) {
|
|
9497
|
-
|
|
9921
|
+
fs11.writeFileSync(existing, `import { register as apiblaze } from "apiblaze/sidecar";
|
|
9498
9922
|
` + cur.replace(/export\s+function\s+register\s*\(\s*\)\s*\{/, (m) => `${m}
|
|
9499
9923
|
apiblaze();`));
|
|
9500
9924
|
return "patched";
|
|
9501
9925
|
}
|
|
9502
|
-
|
|
9926
|
+
fs11.writeFileSync(existing, `import { register as apiblaze } from "apiblaze/sidecar";
|
|
9503
9927
|
${cur}
|
|
9504
9928
|
// call apiblaze() inside your register() export.
|
|
9505
9929
|
`);
|
|
@@ -9578,16 +10002,16 @@ export default async function Page() {
|
|
|
9578
10002
|
function generateInspector(root, router) {
|
|
9579
10003
|
try {
|
|
9580
10004
|
if (router === "pages") {
|
|
9581
|
-
const dir2 =
|
|
10005
|
+
const dir2 = fs11.existsSync(path7.join(root, "src", "pages")) ? path7.join(root, "src", "pages") : path7.join(root, "pages");
|
|
9582
10006
|
const f2 = path7.join(dir2, "abz-inspector.tsx");
|
|
9583
|
-
|
|
10007
|
+
fs11.writeFileSync(f2, INSPECTOR_PAGE);
|
|
9584
10008
|
return path7.relative(root, f2);
|
|
9585
10009
|
}
|
|
9586
|
-
const base2 =
|
|
10010
|
+
const base2 = fs11.existsSync(path7.join(root, "src", "app")) ? path7.join(root, "src", "app") : path7.join(root, "app");
|
|
9587
10011
|
const dir = path7.join(base2, "abz-inspector");
|
|
9588
|
-
|
|
10012
|
+
fs11.mkdirSync(dir, { recursive: true });
|
|
9589
10013
|
const f = path7.join(dir, "page.tsx");
|
|
9590
|
-
|
|
10014
|
+
fs11.writeFileSync(f, INSPECTOR_PAGE);
|
|
9591
10015
|
return path7.relative(root, f);
|
|
9592
10016
|
} catch {
|
|
9593
10017
|
return null;
|
|
@@ -10382,10 +10806,11 @@ Slowest requests \u2014 last ${data.window_hours}h, over ${data.min_ms}ms
|
|
|
10382
10806
|
console.log(import_chalk51.default.bold(`
|
|
10383
10807
|
LLM timing \u2014 last ${data.window_hours}h
|
|
10384
10808
|
`));
|
|
10385
|
-
console.log(import_chalk51.default.dim("
|
|
10809
|
+
console.log(import_chalk51.default.dim(" turns turn p95 ttfc p95 gen p95 reserve p95 in/out tokens p95 model"));
|
|
10386
10810
|
for (const r of rows2) {
|
|
10811
|
+
const n = r.turns ?? r.requests ?? 0;
|
|
10387
10812
|
console.log(
|
|
10388
|
-
` ${String(Math.round(r.
|
|
10813
|
+
` ${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
10814
|
);
|
|
10390
10815
|
}
|
|
10391
10816
|
console.log(import_chalk51.default.dim(`
|
|
@@ -10451,7 +10876,7 @@ program.command("login").description("Authenticate with APIblaze").option("--tea
|
|
|
10451
10876
|
process.exit(1);
|
|
10452
10877
|
}
|
|
10453
10878
|
});
|
|
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>", "Target URL to forward requests to").option("--openapi <file|url>", "Create FROM an OpenAPI spec instead of --target \u2014 a local file or a URL (e.g. https://pokeapi.co/openapi.yaml). Routes, API version and environments (one per `servers` entry) all come from the spec").option("--openapispec <file|url>", "Alias for --openapi").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("--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) => {
|
|
10879
|
+
program.command("create").description("Create a new API proxy (no login needed \u2014 without auth it creates an anonymous proxy and prints a claim URL)").option("--name <name>", "Proxy name (becomes <name>.abz.run)").option("--target <url>", "Target URL to forward requests to").option("--openapi <file|url>", "Create FROM an OpenAPI spec instead of --target \u2014 a local file or a URL (e.g. https://pokeapi.co/openapi.yaml). Routes, API version and environments (one per `servers` entry) all come from the spec").option("--openapispec <file|url>", "Alias for --openapi").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
10880
|
try {
|
|
10456
10881
|
await runCreate({ ...opts, openapi: opts.openapi ?? opts.openapispec });
|
|
10457
10882
|
} catch (err) {
|
|
@@ -10464,7 +10889,7 @@ agent.command("authz").description("Chat to design and turn on access rules for
|
|
|
10464
10889
|
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
10890
|
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
10891
|
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
|
|
10892
|
+
program.command("apichat [project]").description("Turn any API into a chat: point at an OpenAPI spec \u2014 or chat an EXISTING proxy by name (no login needed)").option("--openapi <file|url>", "OpenAPI/Swagger spec to build from (JSON or YAML; file path or URL)").option("--openapispec <file|url>", "Alias for --openapi").option("--target <url>", "Upstream base URL (overrides servers[0].url; enables spec discovery when --openapi is omitted)").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)").action(action((project, opts) => runApichat({ ...opts, project, openapispec: opts.openapispec ?? opts.openapi })));
|
|
10468
10893
|
var llm = program.command("llm").description("Manage a local LLM provider key for chat (optional \u2014 lifts model quality, bills your key)");
|
|
10469
10894
|
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
10895
|
llm.command("show").description("Show the locally stored LLM key (masked)").action(action(() => runLlmShow()));
|