apiblaze 0.21.27 → 0.21.28
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 +864 -393
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -34,7 +34,7 @@ var __toESM = (mod, isNodeMode, target2) => (target2 = mod != null ? __create(__
|
|
|
34
34
|
var version;
|
|
35
35
|
var init_package = __esm({
|
|
36
36
|
"package.json"() {
|
|
37
|
-
version = "0.21.
|
|
37
|
+
version = "0.21.28";
|
|
38
38
|
}
|
|
39
39
|
});
|
|
40
40
|
|
|
@@ -384,8 +384,8 @@ function dualAuth(opts = {}) {
|
|
|
384
384
|
function bothDoorsBody(opts = {}) {
|
|
385
385
|
return { auth_type: "api_key", requests_auth: dualAuth(opts), login: GITHUB_LOGIN };
|
|
386
386
|
}
|
|
387
|
-
async function apiFetch(
|
|
388
|
-
const url = `${DASHBOARD_BASE2}${
|
|
387
|
+
async function apiFetch(path16, options = {}, auth) {
|
|
388
|
+
const url = `${DASHBOARD_BASE2}${path16}`;
|
|
389
389
|
const res = await fetch(url, {
|
|
390
390
|
...options,
|
|
391
391
|
headers: {
|
|
@@ -411,12 +411,12 @@ async function apiFetch(path14, options = {}, auth) {
|
|
|
411
411
|
}
|
|
412
412
|
return res.json();
|
|
413
413
|
}
|
|
414
|
-
async function agentCall(
|
|
414
|
+
async function agentCall(path16, method, body) {
|
|
415
415
|
const token = getAccessToken();
|
|
416
416
|
const res = await fetch(`${DASHBOARD_BASE2}/api/cli/agents`, {
|
|
417
417
|
method: "POST",
|
|
418
418
|
headers: { "Content-Type": "application/json", Authorization: `Bearer ${token}` },
|
|
419
|
-
body: JSON.stringify({ path:
|
|
419
|
+
body: JSON.stringify({ path: path16, method, body })
|
|
420
420
|
});
|
|
421
421
|
let data = null;
|
|
422
422
|
try {
|
|
@@ -896,11 +896,11 @@ function decodeJwt(token) {
|
|
|
896
896
|
return null;
|
|
897
897
|
}
|
|
898
898
|
}
|
|
899
|
-
function maskPath(
|
|
900
|
-
const q =
|
|
901
|
-
if (q < 0) return
|
|
902
|
-
const base2 =
|
|
903
|
-
const query =
|
|
899
|
+
function maskPath(path16) {
|
|
900
|
+
const q = path16.indexOf("?");
|
|
901
|
+
if (q < 0) return path16;
|
|
902
|
+
const base2 = path16.slice(0, q);
|
|
903
|
+
const query = path16.slice(q + 1);
|
|
904
904
|
const masked = query.split("&").map((pair) => {
|
|
905
905
|
const eq = pair.indexOf("=");
|
|
906
906
|
if (eq < 0) return pair;
|
|
@@ -1197,8 +1197,8 @@ function pageQuery(cursor, limit = PAGE_LIMIT) {
|
|
|
1197
1197
|
if (cursor) qs.set("cursor", cursor);
|
|
1198
1198
|
return qs.toString();
|
|
1199
1199
|
}
|
|
1200
|
-
function withPage(
|
|
1201
|
-
return `${
|
|
1200
|
+
function withPage(path16, cursor, limit = PAGE_LIMIT) {
|
|
1201
|
+
return `${path16}${path16.includes("?") ? "&" : "?"}${pageQuery(cursor, limit)}`;
|
|
1202
1202
|
}
|
|
1203
1203
|
var PAGE_LIMIT, MAX_HOPS;
|
|
1204
1204
|
var init_paging = __esm({
|
|
@@ -1885,13 +1885,53 @@ var init_tunnel_client = __esm({
|
|
|
1885
1885
|
}
|
|
1886
1886
|
});
|
|
1887
1887
|
|
|
1888
|
+
// src/lib/env-local.ts
|
|
1889
|
+
function saveEnvLocal(dir, vars) {
|
|
1890
|
+
const file2 = import_path3.default.join(dir, ".env.local");
|
|
1891
|
+
try {
|
|
1892
|
+
const existed = import_fs2.default.existsSync(file2);
|
|
1893
|
+
const lines = existed ? import_fs2.default.readFileSync(file2, "utf8").split("\n") : [];
|
|
1894
|
+
const left = new Map(Object.entries(vars));
|
|
1895
|
+
const out = lines.map((line) => {
|
|
1896
|
+
const m = /^\s*([A-Za-z_][A-Za-z0-9_]*)\s*=/.exec(line);
|
|
1897
|
+
if (m && left.has(m[1])) {
|
|
1898
|
+
const v = left.get(m[1]);
|
|
1899
|
+
left.delete(m[1]);
|
|
1900
|
+
return `${m[1]}=${v}`;
|
|
1901
|
+
}
|
|
1902
|
+
return line;
|
|
1903
|
+
});
|
|
1904
|
+
while (out.length && out[out.length - 1] === "") out.pop();
|
|
1905
|
+
for (const [k, v] of left) out.push(`${k}=${v}`);
|
|
1906
|
+
import_fs2.default.writeFileSync(file2, `${out.join("\n")}
|
|
1907
|
+
`, existed ? void 0 : { mode: 384 });
|
|
1908
|
+
return { ok: true, gitignoreWarning: !gitIgnores(dir) };
|
|
1909
|
+
} catch {
|
|
1910
|
+
return { ok: false, gitignoreWarning: false };
|
|
1911
|
+
}
|
|
1912
|
+
}
|
|
1913
|
+
function gitIgnores(dir) {
|
|
1914
|
+
const gi = import_path3.default.join(dir, ".gitignore");
|
|
1915
|
+
if (!import_fs2.default.existsSync(gi)) return !import_fs2.default.existsSync(import_path3.default.join(dir, ".git"));
|
|
1916
|
+
const pats = import_fs2.default.readFileSync(gi, "utf8").split("\n").map((l) => l.trim()).filter((l) => l && !l.startsWith("#"));
|
|
1917
|
+
return pats.some((p) => [".env.local", "/.env.local", ".env*", "/.env*", ".env*.local", "*.local", ".env.*"].includes(p));
|
|
1918
|
+
}
|
|
1919
|
+
var import_fs2, import_path3;
|
|
1920
|
+
var init_env_local = __esm({
|
|
1921
|
+
"src/lib/env-local.ts"() {
|
|
1922
|
+
"use strict";
|
|
1923
|
+
import_fs2 = __toESM(require("fs"));
|
|
1924
|
+
import_path3 = __toESM(require("path"));
|
|
1925
|
+
}
|
|
1926
|
+
});
|
|
1927
|
+
|
|
1888
1928
|
// src/lib/preapproval.ts
|
|
1889
|
-
async function iamCall(tenant2,
|
|
1929
|
+
async function iamCall(tenant2, path16, method = "GET", body) {
|
|
1890
1930
|
const token = getAccessToken();
|
|
1891
1931
|
const res = await fetch(`${DASHBOARD_BASE5}/api/cli/iam`, {
|
|
1892
1932
|
method: "POST",
|
|
1893
1933
|
headers: { "Content-Type": "application/json", Authorization: `Bearer ${token}` },
|
|
1894
|
-
body: JSON.stringify({ tenant: tenant2, path:
|
|
1934
|
+
body: JSON.stringify({ tenant: tenant2, path: path16, method, body })
|
|
1895
1935
|
});
|
|
1896
1936
|
const data = await res.json().catch(() => ({}));
|
|
1897
1937
|
if (!res.ok) throw new ApiError(res.status, data?.details ?? data?.error ?? res.statusText, data);
|
|
@@ -1962,15 +2002,15 @@ function detectProvider(key) {
|
|
|
1962
2002
|
}
|
|
1963
2003
|
function loadLlmConfig() {
|
|
1964
2004
|
try {
|
|
1965
|
-
return JSON.parse(
|
|
2005
|
+
return JSON.parse(fs5.readFileSync(LLM_PATH, "utf-8"));
|
|
1966
2006
|
} catch {
|
|
1967
2007
|
return null;
|
|
1968
2008
|
}
|
|
1969
2009
|
}
|
|
1970
2010
|
function saveLlmConfig(cfg) {
|
|
1971
|
-
|
|
1972
|
-
|
|
1973
|
-
|
|
2011
|
+
fs5.mkdirSync(getApiblazeDir(), { recursive: true });
|
|
2012
|
+
fs5.writeFileSync(LLM_PATH, JSON.stringify(cfg, null, 2), "utf-8");
|
|
2013
|
+
fs5.chmodSync(LLM_PATH, 384);
|
|
1974
2014
|
}
|
|
1975
2015
|
async function runLlmSetKey(keyArg, opts) {
|
|
1976
2016
|
let key = keyArg?.trim();
|
|
@@ -2009,23 +2049,23 @@ async function runLlmShow() {
|
|
|
2009
2049
|
}
|
|
2010
2050
|
async function runLlmClearKey() {
|
|
2011
2051
|
try {
|
|
2012
|
-
|
|
2052
|
+
fs5.unlinkSync(LLM_PATH);
|
|
2013
2053
|
console.log(`${import_chalk10.default.green("\u2713")} Removed local LLM key.`);
|
|
2014
2054
|
} catch {
|
|
2015
2055
|
console.log(import_chalk10.default.gray("No LLM key was set."));
|
|
2016
2056
|
}
|
|
2017
2057
|
}
|
|
2018
|
-
var
|
|
2058
|
+
var fs5, path6, import_chalk10, import_inquirer, LLM_PATH;
|
|
2019
2059
|
var init_llm = __esm({
|
|
2020
2060
|
"src/commands/llm.ts"() {
|
|
2021
2061
|
"use strict";
|
|
2022
|
-
|
|
2023
|
-
|
|
2062
|
+
fs5 = __toESM(require("fs"));
|
|
2063
|
+
path6 = __toESM(require("path"));
|
|
2024
2064
|
import_chalk10 = __toESM(require("chalk"));
|
|
2025
2065
|
import_inquirer = __toESM(require("inquirer"));
|
|
2026
2066
|
init_auth();
|
|
2027
2067
|
init_traffic();
|
|
2028
|
-
LLM_PATH =
|
|
2068
|
+
LLM_PATH = path6.join(getApiblazeDir(), "llm.json");
|
|
2029
2069
|
}
|
|
2030
2070
|
});
|
|
2031
2071
|
|
|
@@ -2216,7 +2256,7 @@ function claudeInteractive(spec2, prompt) {
|
|
|
2216
2256
|
return { argv, status: r.status };
|
|
2217
2257
|
}
|
|
2218
2258
|
function codexConfigPath() {
|
|
2219
|
-
return
|
|
2259
|
+
return path7.join(process.env.CODEX_HOME || path7.join(os4.homedir(), ".codex"), "config.toml");
|
|
2220
2260
|
}
|
|
2221
2261
|
function codexServerBlock(spec2) {
|
|
2222
2262
|
const lines = [`[mcp_servers.${tomlStr(spec2.name)}]`, `url = ${tomlStr(spec2.url)}`];
|
|
@@ -2248,11 +2288,11 @@ function codexSectionRanges(lines, name) {
|
|
|
2248
2288
|
function installIntoCodex(spec2) {
|
|
2249
2289
|
const file2 = codexConfigPath();
|
|
2250
2290
|
try {
|
|
2251
|
-
|
|
2291
|
+
fs6.mkdirSync(path7.dirname(file2), { recursive: true });
|
|
2252
2292
|
let text = "";
|
|
2253
2293
|
let existed = true;
|
|
2254
2294
|
try {
|
|
2255
|
-
text =
|
|
2295
|
+
text = fs6.readFileSync(file2, "utf-8");
|
|
2256
2296
|
} catch {
|
|
2257
2297
|
existed = false;
|
|
2258
2298
|
}
|
|
@@ -2275,11 +2315,11 @@ function installIntoCodex(spec2) {
|
|
|
2275
2315
|
if (cleaned.length && !cleaned.endsWith("\n")) cleaned += "\n";
|
|
2276
2316
|
if (cleaned.length && !cleaned.endsWith("\n\n")) cleaned += "\n";
|
|
2277
2317
|
const tmp = `${file2}.tmp-${process.pid}`;
|
|
2278
|
-
|
|
2279
|
-
|
|
2318
|
+
fs6.writeFileSync(tmp, cleaned + codexServerBlock(spec2), { encoding: "utf-8", mode: 384 });
|
|
2319
|
+
fs6.renameSync(tmp, file2);
|
|
2280
2320
|
if (!existed) {
|
|
2281
2321
|
try {
|
|
2282
|
-
|
|
2322
|
+
fs6.chmodSync(file2, 384);
|
|
2283
2323
|
} catch {
|
|
2284
2324
|
}
|
|
2285
2325
|
}
|
|
@@ -2290,7 +2330,7 @@ function installIntoCodex(spec2) {
|
|
|
2290
2330
|
}
|
|
2291
2331
|
function codexSiblingApiblazeServers(keepName) {
|
|
2292
2332
|
try {
|
|
2293
|
-
const text =
|
|
2333
|
+
const text = fs6.readFileSync(codexConfigPath(), "utf-8");
|
|
2294
2334
|
const lines = text.split("\n");
|
|
2295
2335
|
const out = [];
|
|
2296
2336
|
const header = /^\[mcp_servers\.(?:"([^"]+)"|([^\]]+))\]\s*(#.*)?$/;
|
|
@@ -2333,7 +2373,7 @@ async function mcpEndpointIsDead(url) {
|
|
|
2333
2373
|
}
|
|
2334
2374
|
function codexServerUrl(name) {
|
|
2335
2375
|
try {
|
|
2336
|
-
const lines =
|
|
2376
|
+
const lines = fs6.readFileSync(codexConfigPath(), "utf-8").split("\n");
|
|
2337
2377
|
const ranges = codexSectionRanges(lines, name);
|
|
2338
2378
|
if (!ranges.length) return null;
|
|
2339
2379
|
const body = lines.slice(ranges[0].start, ranges[0].end).join("\n");
|
|
@@ -2361,13 +2401,13 @@ function removeClaudeServer(name) {
|
|
|
2361
2401
|
function removeCodexServer(name) {
|
|
2362
2402
|
try {
|
|
2363
2403
|
const file2 = codexConfigPath();
|
|
2364
|
-
const lines =
|
|
2404
|
+
const lines = fs6.readFileSync(file2, "utf-8").split("\n");
|
|
2365
2405
|
const ranges = codexSectionRanges(lines, name);
|
|
2366
2406
|
if (!ranges.length) return false;
|
|
2367
2407
|
for (const r of [...ranges].reverse()) lines.splice(r.start, r.end - r.start);
|
|
2368
2408
|
const tmp = `${file2}.tmp-${process.pid}`;
|
|
2369
|
-
|
|
2370
|
-
|
|
2409
|
+
fs6.writeFileSync(tmp, lines.join("\n"), { encoding: "utf-8", mode: 384 });
|
|
2410
|
+
fs6.renameSync(tmp, file2);
|
|
2371
2411
|
return true;
|
|
2372
2412
|
} catch {
|
|
2373
2413
|
return false;
|
|
@@ -2388,7 +2428,7 @@ function directedPrompt(spec2, question) {
|
|
|
2388
2428
|
return `Use the "${spec2.name}" MCP server (it exposes this API's tools) to answer: ${question}${ctx}`;
|
|
2389
2429
|
}
|
|
2390
2430
|
function agentInstructionFile(cli, dir = process.cwd()) {
|
|
2391
|
-
return
|
|
2431
|
+
return path7.join(dir, cli === "claude" ? "CLAUDE.md" : "AGENTS.md");
|
|
2392
2432
|
}
|
|
2393
2433
|
function stripMarkedBlock(text, name) {
|
|
2394
2434
|
const m = marker(name);
|
|
@@ -2422,12 +2462,12 @@ function writeAgentInstruction(cli, spec2, dir = process.cwd()) {
|
|
|
2422
2462
|
].join("\n");
|
|
2423
2463
|
let text = "";
|
|
2424
2464
|
try {
|
|
2425
|
-
text =
|
|
2465
|
+
text = fs6.readFileSync(file2, "utf-8");
|
|
2426
2466
|
} catch {
|
|
2427
2467
|
}
|
|
2428
2468
|
const cleaned = stripMarkedBlock(text, spec2.name);
|
|
2429
2469
|
const sep = cleaned.length && !cleaned.endsWith("\n\n") ? cleaned.endsWith("\n") ? "\n" : "\n\n" : "";
|
|
2430
|
-
|
|
2470
|
+
fs6.writeFileSync(file2, cleaned + sep + block, "utf-8");
|
|
2431
2471
|
return file2;
|
|
2432
2472
|
} catch {
|
|
2433
2473
|
return null;
|
|
@@ -2436,10 +2476,10 @@ function writeAgentInstruction(cli, spec2, dir = process.cwd()) {
|
|
|
2436
2476
|
function removeAgentInstruction(cli, name, dir = process.cwd()) {
|
|
2437
2477
|
try {
|
|
2438
2478
|
const file2 = agentInstructionFile(cli, dir);
|
|
2439
|
-
const text =
|
|
2479
|
+
const text = fs6.readFileSync(file2, "utf-8");
|
|
2440
2480
|
const cleaned = stripMarkedBlock(text, name);
|
|
2441
2481
|
if (cleaned === text) return false;
|
|
2442
|
-
|
|
2482
|
+
fs6.writeFileSync(file2, cleaned.replace(/\n{3,}/g, "\n\n"), "utf-8");
|
|
2443
2483
|
return true;
|
|
2444
2484
|
} catch {
|
|
2445
2485
|
return false;
|
|
@@ -2526,14 +2566,14 @@ async function installAndDemo(cli, spec2, getQuestion, log4 = console.log) {
|
|
|
2526
2566
|
const question = await getQuestion();
|
|
2527
2567
|
if (question) {
|
|
2528
2568
|
const handoff = !!process.stdin.isTTY;
|
|
2529
|
-
const
|
|
2569
|
+
const ask3 = handoff ? cli.kind === "claude" ? claudeInteractive : codexInteractive : oneShot;
|
|
2530
2570
|
log4(`
|
|
2531
2571
|
${import_chalk12.default.dim(handoff ? `Opening ${cli.label} with your question \u2014 you'll stay in that session:` : `Your question, through ${cli.label}:`)}`);
|
|
2532
2572
|
const directed = directedPrompt(spec2, question);
|
|
2533
2573
|
const shown = cli.kind === "claude" ? handoff ? ["claude", directed, "--allowedTools", `mcp__${spec2.name}__*`, "--append-system-prompt", "\u2026"] : ["claude", "-p", directed, "--allowedTools", `mcp__${spec2.name}__*`] : handoff ? ["codex", directed] : ["codex", "exec", "--skip-git-repo-check", directed];
|
|
2534
2574
|
log4(` ${import_chalk12.default.dim("$")} ${renderCommand(shown)}
|
|
2535
2575
|
`);
|
|
2536
|
-
const ans =
|
|
2576
|
+
const ans = ask3(spec2, directedPrompt(spec2, question));
|
|
2537
2577
|
if (ans.status !== 0) log4(import_chalk12.default.yellow(`
|
|
2538
2578
|
${cli.label} exited with ${ans.status ?? "no status"} \u2014 the MCP stays installed.`));
|
|
2539
2579
|
}
|
|
@@ -2553,13 +2593,13 @@ async function installAndDemo(cli, spec2, getQuestion, log4 = console.log) {
|
|
|
2553
2593
|
log4(import_chalk12.default.red(` ${import_chalk12.default.bold(off)}`));
|
|
2554
2594
|
return true;
|
|
2555
2595
|
}
|
|
2556
|
-
var
|
|
2596
|
+
var fs6, os4, path7, import_child_process, import_chalk12, useShell, winQuote, run, detected, mcpLoginSupport, OURS, mask, tomlStr, shellQuote, marker;
|
|
2557
2597
|
var init_external_mcp = __esm({
|
|
2558
2598
|
"src/lib/external-mcp.ts"() {
|
|
2559
2599
|
"use strict";
|
|
2560
|
-
|
|
2600
|
+
fs6 = __toESM(require("fs"));
|
|
2561
2601
|
os4 = __toESM(require("os"));
|
|
2562
|
-
|
|
2602
|
+
path7 = __toESM(require("path"));
|
|
2563
2603
|
import_child_process = require("child_process");
|
|
2564
2604
|
import_chalk12 = __toESM(require("chalk"));
|
|
2565
2605
|
useShell = process.platform === "win32";
|
|
@@ -2835,19 +2875,19 @@ var init_problem = __esm({
|
|
|
2835
2875
|
// src/lib/consumer-auth.ts
|
|
2836
2876
|
function loadConsumer() {
|
|
2837
2877
|
try {
|
|
2838
|
-
return JSON.parse(
|
|
2878
|
+
return JSON.parse(fs7.readFileSync(CONSUMER_PATH, "utf-8"));
|
|
2839
2879
|
} catch {
|
|
2840
2880
|
return null;
|
|
2841
2881
|
}
|
|
2842
2882
|
}
|
|
2843
2883
|
function saveConsumer(creds) {
|
|
2844
|
-
|
|
2845
|
-
|
|
2846
|
-
|
|
2884
|
+
fs7.mkdirSync(APIBLAZE_DIR2, { recursive: true });
|
|
2885
|
+
fs7.writeFileSync(CONSUMER_PATH, JSON.stringify(creds, null, 2), "utf-8");
|
|
2886
|
+
fs7.chmodSync(CONSUMER_PATH, 384);
|
|
2847
2887
|
}
|
|
2848
2888
|
function clearConsumer() {
|
|
2849
2889
|
try {
|
|
2850
|
-
|
|
2890
|
+
fs7.unlinkSync(CONSUMER_PATH);
|
|
2851
2891
|
return true;
|
|
2852
2892
|
} catch {
|
|
2853
2893
|
return false;
|
|
@@ -2949,18 +2989,18 @@ async function validConsumerToken(creds) {
|
|
|
2949
2989
|
if (Date.now() < creds.expiresAt - 3e4) return creds;
|
|
2950
2990
|
return refreshConsumer(creds);
|
|
2951
2991
|
}
|
|
2952
|
-
var
|
|
2992
|
+
var fs7, os5, path8, crypto, import_child_process2, AUTH_BASE, APIBLAZE_DIR2, CONSUMER_PATH, DEVICE_GRANT;
|
|
2953
2993
|
var init_consumer_auth = __esm({
|
|
2954
2994
|
"src/lib/consumer-auth.ts"() {
|
|
2955
2995
|
"use strict";
|
|
2956
|
-
|
|
2996
|
+
fs7 = __toESM(require("fs"));
|
|
2957
2997
|
os5 = __toESM(require("os"));
|
|
2958
|
-
|
|
2998
|
+
path8 = __toESM(require("path"));
|
|
2959
2999
|
crypto = __toESM(require("crypto"));
|
|
2960
3000
|
import_child_process2 = require("child_process");
|
|
2961
3001
|
AUTH_BASE = process.env.APIBLAZE_AUTH_BASE || "https://auth.apiblaze.com";
|
|
2962
|
-
APIBLAZE_DIR2 =
|
|
2963
|
-
CONSUMER_PATH =
|
|
3002
|
+
APIBLAZE_DIR2 = path8.join(os5.homedir(), ".apiblaze");
|
|
3003
|
+
CONSUMER_PATH = path8.join(APIBLAZE_DIR2, "consumer.json");
|
|
2964
3004
|
DEVICE_GRANT = "urn:ietf:params:oauth:grant-type:device_code";
|
|
2965
3005
|
}
|
|
2966
3006
|
});
|
|
@@ -3021,12 +3061,12 @@ async function classifyTargetInput(input, failFn) {
|
|
|
3021
3061
|
if (!isHttpUrl(t)) {
|
|
3022
3062
|
let isFile = false;
|
|
3023
3063
|
try {
|
|
3024
|
-
isFile =
|
|
3064
|
+
isFile = fs8.existsSync(t) && fs8.statSync(t).isFile();
|
|
3025
3065
|
} catch {
|
|
3026
3066
|
isFile = false;
|
|
3027
3067
|
}
|
|
3028
3068
|
if (isFile) {
|
|
3029
|
-
const text2 =
|
|
3069
|
+
const text2 = fs8.readFileSync(t, "utf-8");
|
|
3030
3070
|
if (looksLikeSpecDoc(text2)) return { kind: "spec", specText: text2, source: t };
|
|
3031
3071
|
failFn(
|
|
3032
3072
|
`${t} exists but is not an OpenAPI/Swagger document (no openapi/swagger version field with paths).`,
|
|
@@ -3047,11 +3087,11 @@ async function classifyTargetInput(input, failFn) {
|
|
|
3047
3087
|
if (text && looksLikeSpecDoc(text)) return { kind: "spec", specText: text, source: t };
|
|
3048
3088
|
return { kind: "server" };
|
|
3049
3089
|
}
|
|
3050
|
-
var
|
|
3090
|
+
var fs8, import_yaml2, MAX_SPEC_BYTES;
|
|
3051
3091
|
var init_spec_or_target = __esm({
|
|
3052
3092
|
"src/lib/spec-or-target.ts"() {
|
|
3053
3093
|
"use strict";
|
|
3054
|
-
|
|
3094
|
+
fs8 = __toESM(require("fs"));
|
|
3055
3095
|
import_yaml2 = require("yaml");
|
|
3056
3096
|
MAX_SPEC_BYTES = 5 * 1024 * 1024;
|
|
3057
3097
|
}
|
|
@@ -3124,7 +3164,7 @@ async function loadSpec(opts) {
|
|
|
3124
3164
|
}
|
|
3125
3165
|
let text;
|
|
3126
3166
|
try {
|
|
3127
|
-
text =
|
|
3167
|
+
text = fs9.readFileSync(opts.openapispec, "utf-8");
|
|
3128
3168
|
} catch {
|
|
3129
3169
|
fail3(`Cannot read spec file: ${opts.openapispec}`);
|
|
3130
3170
|
}
|
|
@@ -3326,13 +3366,13 @@ async function ensureConsumerLogin(teamId, tenant2, version2) {
|
|
|
3326
3366
|
console.log(` ${import_chalk15.default.green("\u2714")} Signed in as${creds.email ? ` ${import_chalk15.default.bold(creds.email)}` : " a consumer"} on ${tenant2}.`);
|
|
3327
3367
|
return creds;
|
|
3328
3368
|
}
|
|
3329
|
-
async function cpPost(anon,
|
|
3369
|
+
async function cpPost(anon, path16, body, summary) {
|
|
3330
3370
|
if (anon) {
|
|
3331
3371
|
const cred = loadAnonCred();
|
|
3332
3372
|
if (!cred) throw new Error("Anonymous workspace credential missing.");
|
|
3333
|
-
return cpFetch(cred.cp_key,
|
|
3373
|
+
return cpFetch(cred.cp_key, path16, { method: "POST", body: JSON.stringify(body) });
|
|
3334
3374
|
}
|
|
3335
|
-
return admin({ method: "POST", path:
|
|
3375
|
+
return admin({ method: "POST", path: path16, body, summary });
|
|
3336
3376
|
}
|
|
3337
3377
|
async function provision(spec2, target2, opts) {
|
|
3338
3378
|
const loggedIn = !!loadCredentials();
|
|
@@ -3882,17 +3922,17 @@ function renderUpsell(p, upsell, opts = {}) {
|
|
|
3882
3922
|
}
|
|
3883
3923
|
function loadApichats() {
|
|
3884
3924
|
try {
|
|
3885
|
-
const list = JSON.parse(
|
|
3925
|
+
const list = JSON.parse(fs9.readFileSync(apichatsPath(), "utf-8"));
|
|
3886
3926
|
return Array.isArray(list) ? list : [];
|
|
3887
3927
|
} catch {
|
|
3888
3928
|
return [];
|
|
3889
3929
|
}
|
|
3890
3930
|
}
|
|
3891
3931
|
function writeApichats(list) {
|
|
3892
|
-
|
|
3893
|
-
|
|
3932
|
+
fs9.mkdirSync(getApiblazeDir(), { recursive: true });
|
|
3933
|
+
fs9.writeFileSync(apichatsPath(), JSON.stringify(list, null, 2), "utf-8");
|
|
3894
3934
|
try {
|
|
3895
|
-
|
|
3935
|
+
fs9.chmodSync(apichatsPath(), 384);
|
|
3896
3936
|
} catch {
|
|
3897
3937
|
}
|
|
3898
3938
|
}
|
|
@@ -4108,15 +4148,15 @@ function discoverLocalSpecs() {
|
|
|
4108
4148
|
const found = [];
|
|
4109
4149
|
for (const n of known) {
|
|
4110
4150
|
try {
|
|
4111
|
-
if (
|
|
4151
|
+
if (fs9.statSync(path9.join(cwd, n)).isFile()) found.push(n);
|
|
4112
4152
|
} catch {
|
|
4113
4153
|
}
|
|
4114
4154
|
}
|
|
4115
4155
|
try {
|
|
4116
|
-
const files =
|
|
4156
|
+
const files = fs9.readdirSync(cwd).filter((f) => /\.(ya?ml|json)$/i.test(f) && !found.includes(f));
|
|
4117
4157
|
for (const f of files.slice(0, 60)) {
|
|
4118
4158
|
try {
|
|
4119
|
-
const head =
|
|
4159
|
+
const head = fs9.readFileSync(path9.join(cwd, f), "utf-8").slice(0, 4e3);
|
|
4120
4160
|
if (/["']?openapi["']?\s*:/i.test(head) || /["']?swagger["']?\s*:/i.test(head) || /^\s*paths\s*:/im.test(head) || /"paths"\s*:/.test(head)) {
|
|
4121
4161
|
found.push(f);
|
|
4122
4162
|
}
|
|
@@ -4763,12 +4803,12 @@ async function runApichat(opts) {
|
|
|
4763
4803
|
if (mcpUrl2 && await maybeInstallExternalCli(p, opts)) return;
|
|
4764
4804
|
await startChat(p, [], opts);
|
|
4765
4805
|
}
|
|
4766
|
-
var
|
|
4806
|
+
var fs9, path9, crypto2, import_child_process3, import_chalk15, import_ora2, import_yaml3, GENERATOR_HINT, revealAuth, apichatsPath;
|
|
4767
4807
|
var init_apichat = __esm({
|
|
4768
4808
|
"src/commands/apichat.ts"() {
|
|
4769
4809
|
"use strict";
|
|
4770
|
-
|
|
4771
|
-
|
|
4810
|
+
fs9 = __toESM(require("fs"));
|
|
4811
|
+
path9 = __toESM(require("path"));
|
|
4772
4812
|
crypto2 = __toESM(require("crypto"));
|
|
4773
4813
|
import_child_process3 = require("child_process");
|
|
4774
4814
|
import_chalk15 = __toESM(require("chalk"));
|
|
@@ -4792,7 +4832,7 @@ var init_apichat = __esm({
|
|
|
4792
4832
|
init_consumer_auth();
|
|
4793
4833
|
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>";
|
|
4794
4834
|
revealAuth = false;
|
|
4795
|
-
apichatsPath = () =>
|
|
4835
|
+
apichatsPath = () => path9.join(getApiblazeDir(), "apichats.json");
|
|
4796
4836
|
}
|
|
4797
4837
|
});
|
|
4798
4838
|
|
|
@@ -5247,7 +5287,7 @@ async function loadOpenapiSource(ref) {
|
|
|
5247
5287
|
text = await res.text();
|
|
5248
5288
|
} else {
|
|
5249
5289
|
try {
|
|
5250
|
-
text =
|
|
5290
|
+
text = import_fs3.default.readFileSync(ref, "utf8");
|
|
5251
5291
|
} catch {
|
|
5252
5292
|
fail4(`Cannot read the OpenAPI spec "${ref}" \u2014 it is not a readable file, and not an http(s) URL.`);
|
|
5253
5293
|
}
|
|
@@ -5760,7 +5800,18 @@ async function printDevOutcome(o, port) {
|
|
|
5760
5800
|
console.log(BRAND === "mcpblaze" ? mcpLine : restLine);
|
|
5761
5801
|
console.log(BRAND === "mcpblaze" ? restLine : mcpLine);
|
|
5762
5802
|
if (o.auth.doors.includes("api_key") && o.apiKey) {
|
|
5763
|
-
|
|
5803
|
+
const saved = process.stdout.isTTY ? null : saveEnvLocal(process.cwd(), {
|
|
5804
|
+
APIBLAZE_URL: rest,
|
|
5805
|
+
APIBLAZE_MCP_URL: agents,
|
|
5806
|
+
APIBLAZE_SERVER_KEY: o.apiKey,
|
|
5807
|
+
...o.tenant ? { APIBLAZE_TENANT: o.tenant } : {}
|
|
5808
|
+
});
|
|
5809
|
+
if (saved?.ok) {
|
|
5810
|
+
console.log(` ${import_chalk19.default.dim("Key ")} saved to .env.local as APIBLAZE_SERVER_KEY, never printed ${import_chalk19.default.dim("your server sends it as X-API-Key, naming the user in X-End-User-Id")}`);
|
|
5811
|
+
if (saved.gitignoreWarning) console.log(import_chalk19.default.yellow(" .env.local is not in .gitignore: add it before you commit."));
|
|
5812
|
+
} else {
|
|
5813
|
+
console.log(` ${import_chalk19.default.dim("Key ")} ${import_chalk19.default.bold.green(o.apiKey)} ${import_chalk19.default.dim("shown once \xB7 your server sends it as X-API-Key, naming the user in X-End-User-Id")}`);
|
|
5814
|
+
}
|
|
5764
5815
|
}
|
|
5765
5816
|
console.log(` ${import_chalk19.default.dim("Included")} ${["API keys", "sign-in", lockedPhrase, "rate limits", adminBit].filter(Boolean).join(import_chalk19.default.dim(" \xB7 "))}`);
|
|
5766
5817
|
if (isVerbose() && !o.adminEmail) console.log(import_chalk19.default.dim(` No admin yet: npx ${B.cli}@latest admins add you@example.com --tenant ${o.tenant ?? "<tenant>"}`));
|
|
@@ -6136,7 +6187,7 @@ Create an ${B.thing}`));
|
|
|
6136
6187
|
if (opts.config) {
|
|
6137
6188
|
let raw = "";
|
|
6138
6189
|
try {
|
|
6139
|
-
raw =
|
|
6190
|
+
raw = import_fs3.default.readFileSync(opts.config, "utf8");
|
|
6140
6191
|
} catch {
|
|
6141
6192
|
fail4(`Cannot read --config file: ${opts.config}`);
|
|
6142
6193
|
}
|
|
@@ -6303,11 +6354,12 @@ Create an ${B.thing}`));
|
|
|
6303
6354
|
claim: { claimCode: result.claim_code || cred?.claim_code, claimUrl: result.claim_url }
|
|
6304
6355
|
});
|
|
6305
6356
|
}
|
|
6306
|
-
var
|
|
6357
|
+
var import_fs3, import_yaml4, import_chalk19, import_ora6, VALID_AUTH, BOTH_DOORS, MANAGED_GITHUB, API_KEY, OPEN, PROVIDERS, ISSUER_PROVIDERS, SECRET_REQUIRED_TYPES, OAUTH_SHAPES, EMAIL_RE, AGENT_NAME;
|
|
6307
6358
|
var init_create = __esm({
|
|
6308
6359
|
"src/commands/create.ts"() {
|
|
6309
6360
|
"use strict";
|
|
6310
|
-
|
|
6361
|
+
import_fs3 = __toESM(require("fs"));
|
|
6362
|
+
init_env_local();
|
|
6311
6363
|
init_tenant_choice();
|
|
6312
6364
|
init_random_name();
|
|
6313
6365
|
init_apichat();
|
|
@@ -6678,24 +6730,24 @@ ready.then((c) => {
|
|
|
6678
6730
|
|
|
6679
6731
|
// src/lib/log-view.ts
|
|
6680
6732
|
function statusColor(status2) {
|
|
6681
|
-
if (status2 < 300) return
|
|
6682
|
-
if (status2 < 400) return
|
|
6683
|
-
if (status2 < 500) return
|
|
6684
|
-
return
|
|
6733
|
+
if (status2 < 300) return import_chalk22.default.green;
|
|
6734
|
+
if (status2 < 400) return import_chalk22.default.blue;
|
|
6735
|
+
if (status2 < 500) return import_chalk22.default.yellow;
|
|
6736
|
+
return import_chalk22.default.red;
|
|
6685
6737
|
}
|
|
6686
6738
|
function methodColor(method) {
|
|
6687
6739
|
switch ((method || "").toUpperCase()) {
|
|
6688
6740
|
case "GET":
|
|
6689
|
-
return
|
|
6741
|
+
return import_chalk22.default.green;
|
|
6690
6742
|
case "POST":
|
|
6691
|
-
return
|
|
6743
|
+
return import_chalk22.default.blue;
|
|
6692
6744
|
case "PUT":
|
|
6693
6745
|
case "PATCH":
|
|
6694
|
-
return
|
|
6746
|
+
return import_chalk22.default.yellow;
|
|
6695
6747
|
case "DELETE":
|
|
6696
|
-
return
|
|
6748
|
+
return import_chalk22.default.red;
|
|
6697
6749
|
default:
|
|
6698
|
-
return
|
|
6750
|
+
return import_chalk22.default.white;
|
|
6699
6751
|
}
|
|
6700
6752
|
}
|
|
6701
6753
|
function clock(ts2) {
|
|
@@ -6706,12 +6758,12 @@ function routeCell(route) {
|
|
|
6706
6758
|
return (r.length > ROUTE_W ? `\u2026${r.slice(-(ROUTE_W - 1))}` : r).padEnd(ROUTE_W);
|
|
6707
6759
|
}
|
|
6708
6760
|
function formatRow(r) {
|
|
6709
|
-
const time =
|
|
6761
|
+
const time = import_chalk22.default.dim(clock(r.ts));
|
|
6710
6762
|
const method = methodColor(r.method)(String(r.method || "\u2014").padEnd(6));
|
|
6711
6763
|
const route = routeCell(String(r.route || "\u2014"));
|
|
6712
6764
|
const status2 = statusColor(r.status)(String(r.status ?? "\u2014").padStart(3));
|
|
6713
|
-
const ms2 =
|
|
6714
|
-
const step = r.error_step ?
|
|
6765
|
+
const ms2 = import_chalk22.default.dim(`${r.duration_ms}ms`.padStart(8));
|
|
6766
|
+
const step = r.error_step ? import_chalk22.default.dim(` ${r.error_step}`) : "";
|
|
6715
6767
|
return `${time} ${method} ${route} ${status2} ${ms2}${step}`;
|
|
6716
6768
|
}
|
|
6717
6769
|
function printRow(r, json) {
|
|
@@ -6765,13 +6817,13 @@ async function runTailSession(opts) {
|
|
|
6765
6817
|
if (!held.length && !heldRowsDropped && !heldNotesDropped) return;
|
|
6766
6818
|
const rows = held.filter((h) => h.kind === "row").length;
|
|
6767
6819
|
const total = rows + heldRowsDropped;
|
|
6768
|
-
if (total) console.log(
|
|
6820
|
+
if (total) console.log(import_chalk22.default.dim(`\u2500\u2500\u2500 ${total} request${total === 1 ? "" : "s"} arrived while paused \u2500\u2500\u2500`));
|
|
6769
6821
|
for (const h of held) {
|
|
6770
6822
|
if (h.kind === "row") printRow(h.row, opts.json);
|
|
6771
6823
|
else console.error(h.msg);
|
|
6772
6824
|
}
|
|
6773
6825
|
if (heldRowsDropped || heldNotesDropped) {
|
|
6774
|
-
console.log(
|
|
6826
|
+
console.log(import_chalk22.default.dim(`\u2500\u2500\u2500 ${heldRowsDropped} older one${heldRowsDropped === 1 ? "" : "s"} not shown (pause buffer full) \u2014 browse them with \`logs list\` \u2500\u2500\u2500`));
|
|
6775
6827
|
}
|
|
6776
6828
|
held.length = 0;
|
|
6777
6829
|
heldRowsDropped = 0;
|
|
@@ -6785,7 +6837,7 @@ async function runTailSession(opts) {
|
|
|
6785
6837
|
activeSocket?.close();
|
|
6786
6838
|
} catch {
|
|
6787
6839
|
}
|
|
6788
|
-
if (!opts.json) console.log(
|
|
6840
|
+
if (!opts.json) console.log(import_chalk22.default.dim("\nStopped."));
|
|
6789
6841
|
process.exit(0);
|
|
6790
6842
|
}
|
|
6791
6843
|
function onKeypress(_s, key) {
|
|
@@ -6813,17 +6865,17 @@ async function runTailSession(opts) {
|
|
|
6813
6865
|
async function openInspector() {
|
|
6814
6866
|
if (paused) return;
|
|
6815
6867
|
if (recent.length === 0) {
|
|
6816
|
-
console.log(
|
|
6868
|
+
console.log(import_chalk22.default.dim(" nothing captured yet \u2014 waiting for a request\u2026"));
|
|
6817
6869
|
return;
|
|
6818
6870
|
}
|
|
6819
6871
|
paused = true;
|
|
6820
6872
|
unbindKeys();
|
|
6821
6873
|
try {
|
|
6822
6874
|
const { default: inquirer3 } = await import("inquirer");
|
|
6823
|
-
console.log(
|
|
6875
|
+
console.log(import_chalk22.default.dim("\n\u2500\u2500\u2500 paused \xB7 still capturing in the background \u2500\u2500\u2500"));
|
|
6824
6876
|
for (; ; ) {
|
|
6825
6877
|
const choices = recent.slice(-TAIL_INSPECT_CHOICES).reverse().map((r) => ({ name: formatRow(r), value: r.request_id }));
|
|
6826
|
-
choices.push({ name:
|
|
6878
|
+
choices.push({ name: import_chalk22.default.dim("\u2039resume tail\u203A"), value: "__resume__" });
|
|
6827
6879
|
const { picked } = await inquirer3.prompt([{
|
|
6828
6880
|
type: "list",
|
|
6829
6881
|
name: "picked",
|
|
@@ -6834,12 +6886,12 @@ async function runTailSession(opts) {
|
|
|
6834
6886
|
if (picked === "__resume__") break;
|
|
6835
6887
|
const row = recent.find((r) => r.request_id === picked);
|
|
6836
6888
|
if (row) printDetail(row, opts.detail);
|
|
6837
|
-
else console.log(
|
|
6889
|
+
else console.log(import_chalk22.default.dim(" that request has scrolled out of the tail buffer \u2014 find it with `logs list`.\n"));
|
|
6838
6890
|
}
|
|
6839
6891
|
} finally {
|
|
6840
6892
|
paused = false;
|
|
6841
6893
|
flushHeld();
|
|
6842
|
-
console.log(
|
|
6894
|
+
console.log(import_chalk22.default.dim("\u2500\u2500\u2500 resumed \u2500\u2500\u2500"));
|
|
6843
6895
|
bindKeys();
|
|
6844
6896
|
}
|
|
6845
6897
|
}
|
|
@@ -6849,12 +6901,12 @@ async function runTailSession(opts) {
|
|
|
6849
6901
|
if (reconnects >= MAX_RECONNECTS) {
|
|
6850
6902
|
paused = false;
|
|
6851
6903
|
flushHeld();
|
|
6852
|
-
console.error(
|
|
6904
|
+
console.error(import_chalk22.default.red(`Connection lost \u2014 giving up after repeated reconnects${lastFailure ? ` (last error: ${lastFailure})` : ""}.`));
|
|
6853
6905
|
process.exit(1);
|
|
6854
6906
|
}
|
|
6855
6907
|
const delay = Math.min(1e3 * 2 ** reconnects, 15e3);
|
|
6856
6908
|
reconnects++;
|
|
6857
|
-
diag(
|
|
6909
|
+
diag(import_chalk22.default.dim(`disconnected${lastFailure ? ` (${lastFailure})` : ""} \u2014 reconnecting in ${Math.round(delay / 1e3)}s\u2026`));
|
|
6858
6910
|
setTimeout(() => {
|
|
6859
6911
|
if (!closed) void connect();
|
|
6860
6912
|
}, delay);
|
|
@@ -6865,7 +6917,7 @@ async function runTailSession(opts) {
|
|
|
6865
6917
|
url = await opts.mintTicket();
|
|
6866
6918
|
} catch (err) {
|
|
6867
6919
|
if (!everConnected) {
|
|
6868
|
-
console.error(
|
|
6920
|
+
console.error(import_chalk22.default.red(`Could not start tail: ${err.message}`));
|
|
6869
6921
|
process.exit(1);
|
|
6870
6922
|
}
|
|
6871
6923
|
lastFailure = `ticket mint failed: ${err.message}`;
|
|
@@ -6878,7 +6930,7 @@ async function runTailSession(opts) {
|
|
|
6878
6930
|
} catch (err) {
|
|
6879
6931
|
lastFailure = `bad tail URL: ${err.message}`;
|
|
6880
6932
|
if (!everConnected) {
|
|
6881
|
-
console.error(
|
|
6933
|
+
console.error(import_chalk22.default.red(`Could not start tail: ${lastFailure}`));
|
|
6882
6934
|
process.exit(1);
|
|
6883
6935
|
}
|
|
6884
6936
|
scheduleReconnect();
|
|
@@ -6893,9 +6945,9 @@ async function runTailSession(opts) {
|
|
|
6893
6945
|
lastInbound = Date.now();
|
|
6894
6946
|
lastFailure = "";
|
|
6895
6947
|
if (isReconnect) {
|
|
6896
|
-
diag(
|
|
6948
|
+
diag(import_chalk22.default.dim("reconnected \u2014 requests during the gap were not replayed (browse them with `logs list`)."));
|
|
6897
6949
|
} else {
|
|
6898
|
-
diag(
|
|
6950
|
+
diag(import_chalk22.default.dim(interactive2 ? "[Enter] inspect a request \xB7 [Ctrl+C] stop\n" : "Ctrl+C to stop."));
|
|
6899
6951
|
}
|
|
6900
6952
|
bindKeys();
|
|
6901
6953
|
pingTimer = setInterval(() => {
|
|
@@ -6922,7 +6974,7 @@ async function runTailSession(opts) {
|
|
|
6922
6974
|
return;
|
|
6923
6975
|
}
|
|
6924
6976
|
if (frame?.type === "ceiling") {
|
|
6925
|
-
diag(
|
|
6977
|
+
diag(import_chalk22.default.yellow(
|
|
6926
6978
|
`\u26A0 volume ceiling \u2014 ${Number(frame.dropped_this_hour) || 0} rows dropped this hour; errors keep streaming.`
|
|
6927
6979
|
));
|
|
6928
6980
|
return;
|
|
@@ -6998,10 +7050,10 @@ async function fetchPage(fetchWindow3, params, notices) {
|
|
|
6998
7050
|
return { rows, nextCursor: res.next_cursor ?? void 0 };
|
|
6999
7051
|
}
|
|
7000
7052
|
if (hop >= MAX_EMPTY_HOPS) {
|
|
7001
|
-
console.error(
|
|
7053
|
+
console.error(import_chalk22.default.yellow("\u26A0 stopped after scanning a long empty span \u2014 older rows may still exist."));
|
|
7002
7054
|
return { rows: [], nextCursor: res.next_cursor, fuseTripped: true };
|
|
7003
7055
|
}
|
|
7004
|
-
if (hop === 0) console.error(
|
|
7056
|
+
if (hop === 0) console.error(import_chalk22.default.dim("searching older history\u2026"));
|
|
7005
7057
|
cursor = res.next_cursor;
|
|
7006
7058
|
}
|
|
7007
7059
|
}
|
|
@@ -7039,18 +7091,18 @@ async function runPlainList(opts, notices) {
|
|
|
7039
7091
|
}
|
|
7040
7092
|
}
|
|
7041
7093
|
if (rows.length === 0) {
|
|
7042
|
-
console.log(
|
|
7094
|
+
console.log(import_chalk22.default.dim(fuseTripped ? "Nothing found in the scanned span \u2014 older requests may still exist." : opts.errorsOnly ? "No errors logged yet." : "No requests logged yet."));
|
|
7043
7095
|
return;
|
|
7044
7096
|
}
|
|
7045
7097
|
const now = Date.now();
|
|
7046
7098
|
for (const d of groupIntoDays(rows)) {
|
|
7047
|
-
console.log(
|
|
7099
|
+
console.log(import_chalk22.default.bold(dayLabel(d.dayMs, now)));
|
|
7048
7100
|
for (const r of d.rows) console.log(` ${formatRow(r)}`);
|
|
7049
7101
|
console.log("");
|
|
7050
7102
|
}
|
|
7051
7103
|
const errs = rows.filter((r) => r.status >= 400).length;
|
|
7052
7104
|
const tail = fuseTripped ? " \xB7 stopped early (long empty span) \u2014 older requests may still exist" : more ? " \xB7 showing the most recent \u2014 `--json` streams the rest" : "";
|
|
7053
|
-
console.log(
|
|
7105
|
+
console.log(import_chalk22.default.dim(
|
|
7054
7106
|
`${rows.length} request${rows.length === 1 ? "" : "s"} \xB7 ${errs} error${errs === 1 ? "" : "s"}${tail}`
|
|
7055
7107
|
));
|
|
7056
7108
|
}
|
|
@@ -7069,7 +7121,7 @@ async function runHistoryBrowser(opts) {
|
|
|
7069
7121
|
for (const r of page.rows) console.log(JSON.stringify(r));
|
|
7070
7122
|
any = any || page.rows.length > 0;
|
|
7071
7123
|
if (page.fuseTripped || !page.nextCursor) {
|
|
7072
|
-
if (!any && !page.fuseTripped) console.error(
|
|
7124
|
+
if (!any && !page.fuseTripped) console.error(import_chalk22.default.dim(opts.errorsOnly ? "No errors logged yet." : "No requests logged yet."));
|
|
7073
7125
|
return;
|
|
7074
7126
|
}
|
|
7075
7127
|
cursor2 = page.nextCursor;
|
|
@@ -7090,7 +7142,7 @@ async function runHistoryBrowser(opts) {
|
|
|
7090
7142
|
scanRows = first.rows;
|
|
7091
7143
|
scanCursor = first.nextCursor;
|
|
7092
7144
|
if (scanRows.length === 0) {
|
|
7093
|
-
console.log(
|
|
7145
|
+
console.log(import_chalk22.default.dim(first.fuseTripped ? "Nothing found in the scanned span \u2014 older rows may exist." : opts.errorsOnly ? "No errors logged yet." : "No requests logged yet."));
|
|
7094
7146
|
return;
|
|
7095
7147
|
}
|
|
7096
7148
|
}
|
|
@@ -7101,10 +7153,10 @@ async function runHistoryBrowser(opts) {
|
|
|
7101
7153
|
const now2 = Date.now();
|
|
7102
7154
|
const oldestKey = days[days.length - 1]?.key;
|
|
7103
7155
|
const choices = days.map((d) => ({
|
|
7104
|
-
name: `${dayLabel(d.dayMs, now2).padEnd(12)} ${String(d.rows.length).padStart(4)}${(scanCursor !== void 0 || scanFloorUnknown) && d.key === oldestKey ? "+" : ""} requests${d.errors ?
|
|
7156
|
+
name: `${dayLabel(d.dayMs, now2).padEnd(12)} ${String(d.rows.length).padStart(4)}${(scanCursor !== void 0 || scanFloorUnknown) && d.key === oldestKey ? "+" : ""} requests${d.errors ? import_chalk22.default.red(` ${d.errors} errors`) : ""}`,
|
|
7105
7157
|
value: d.key
|
|
7106
7158
|
}));
|
|
7107
|
-
if (scanCursor) choices.push({ name:
|
|
7159
|
+
if (scanCursor) choices.push({ name: import_chalk22.default.dim("\u2026 older days"), value: "__older__" });
|
|
7108
7160
|
const { chosenDay } = await inquirer3.prompt([{
|
|
7109
7161
|
type: "list",
|
|
7110
7162
|
name: "chosenDay",
|
|
@@ -7124,7 +7176,7 @@ async function runHistoryBrowser(opts) {
|
|
|
7124
7176
|
scanCursor = void 0;
|
|
7125
7177
|
} else {
|
|
7126
7178
|
scanCursor = more.nextCursor;
|
|
7127
|
-
if (more.rows.length === 0 && !scanCursor) console.log(
|
|
7179
|
+
if (more.rows.length === 0 && !scanCursor) console.log(import_chalk22.default.dim("That was everything \u2014 no older requests."));
|
|
7128
7180
|
}
|
|
7129
7181
|
continue;
|
|
7130
7182
|
}
|
|
@@ -7146,11 +7198,11 @@ async function runHistoryBrowser(opts) {
|
|
|
7146
7198
|
cursor
|
|
7147
7199
|
}, notices);
|
|
7148
7200
|
if (page.rows.length === 0 && pageRows.length === 0) {
|
|
7149
|
-
console.log(
|
|
7201
|
+
console.log(import_chalk22.default.dim(page.fuseTripped ? "Stopped scanning this day (long empty span) \u2014 older rows in it may exist." : "No requests for this day."));
|
|
7150
7202
|
return;
|
|
7151
7203
|
}
|
|
7152
7204
|
if (page.rows.length === 0) {
|
|
7153
|
-
console.log(
|
|
7205
|
+
console.log(import_chalk22.default.dim(page.fuseTripped ? "Stopped scanning further into this day (long empty span) \u2014 older rows in it may exist." : "That was everything for this day."));
|
|
7154
7206
|
moreCursor = void 0;
|
|
7155
7207
|
} else {
|
|
7156
7208
|
pageRows = page.rows;
|
|
@@ -7161,8 +7213,8 @@ async function runHistoryBrowser(opts) {
|
|
|
7161
7213
|
name: formatRow(r),
|
|
7162
7214
|
value: r.request_id
|
|
7163
7215
|
}));
|
|
7164
|
-
if (moreCursor) choices.push({ name:
|
|
7165
|
-
choices.push({ name:
|
|
7216
|
+
if (moreCursor) choices.push({ name: import_chalk22.default.dim("\u2026 load more"), value: "__more__" });
|
|
7217
|
+
choices.push({ name: import_chalk22.default.dim("quit"), value: "__quit__" });
|
|
7166
7218
|
for (; ; ) {
|
|
7167
7219
|
const { picked } = await inquirer3.prompt([{
|
|
7168
7220
|
type: "list",
|
|
@@ -7197,19 +7249,19 @@ function wrapCells(items, width) {
|
|
|
7197
7249
|
${PANE_INDENT}`);
|
|
7198
7250
|
}
|
|
7199
7251
|
function renderHeaders(h) {
|
|
7200
|
-
if (!h) return
|
|
7252
|
+
if (!h) return import_chalk22.default.dim("\u2014");
|
|
7201
7253
|
const width = process.stdout.columns || 100;
|
|
7202
7254
|
if (h.values) {
|
|
7203
7255
|
const e = Object.entries(h.values);
|
|
7204
|
-
if (!e.length) return
|
|
7256
|
+
if (!e.length) return import_chalk22.default.dim("none");
|
|
7205
7257
|
const rank = (k) => {
|
|
7206
7258
|
const i = HEADER_PRIORITY.indexOf(k.toLowerCase());
|
|
7207
7259
|
return i === -1 ? NOISE_HEADER.test(k) ? 999 : 500 : i;
|
|
7208
7260
|
};
|
|
7209
|
-
return e.sort((a, b) => rank(a[0]) - rank(b[0])).map(([k, v]) => `${
|
|
7261
|
+
return e.sort((a, b) => rank(a[0]) - rank(b[0])).map(([k, v]) => `${import_chalk22.default.dim(k + ":")} ${v}`).join(`
|
|
7210
7262
|
${PANE_INDENT}`);
|
|
7211
7263
|
}
|
|
7212
|
-
if (!h.names?.length) return
|
|
7264
|
+
if (!h.names?.length) return import_chalk22.default.dim("none");
|
|
7213
7265
|
const names = [...h.names];
|
|
7214
7266
|
const noise = names.filter((n) => NOISE_HEADER.test(n));
|
|
7215
7267
|
const shown = names.filter((n) => !NOISE_HEADER.test(n)).sort((a, b) => {
|
|
@@ -7217,77 +7269,77 @@ ${PANE_INDENT}`);
|
|
|
7217
7269
|
const ib = HEADER_PRIORITY.indexOf(b.toLowerCase());
|
|
7218
7270
|
return (ia === -1 ? 500 : ia) - (ib === -1 ? 500 : ib) || a.localeCompare(b);
|
|
7219
7271
|
});
|
|
7220
|
-
const body = shown.length ? wrapCells(shown, width) :
|
|
7272
|
+
const body = shown.length ? wrapCells(shown, width) : import_chalk22.default.dim("(all forwarding headers)");
|
|
7221
7273
|
const tail = noise.length ? `
|
|
7222
|
-
${PANE_INDENT}${
|
|
7223
|
-
return `${body}${tail} ${
|
|
7274
|
+
${PANE_INDENT}${import_chalk22.default.dim(`+ ${noise.length} forwarding/client-hint header${noise.length === 1 ? "" : "s"} (cf-*, sec-*)`)}` : "";
|
|
7275
|
+
return `${body}${tail} ${import_chalk22.default.dim("(names only)")}`;
|
|
7224
7276
|
}
|
|
7225
7277
|
function renderBody(b) {
|
|
7226
|
-
if (!b) return
|
|
7278
|
+
if (!b) return import_chalk22.default.dim("\u2014");
|
|
7227
7279
|
const meta = [b.content_type, b.size != null ? `${b.size}B` : null, b.truncated ? "truncated" : null].filter(Boolean).join(" \xB7 ");
|
|
7228
|
-
if (b.not_captured) return
|
|
7280
|
+
if (b.not_captured) return import_chalk22.default.dim(`not captured (${b.not_captured})${meta ? " \xB7 " + meta : ""}`);
|
|
7229
7281
|
const isShape = b.value === void 0 && b.shape !== void 0;
|
|
7230
7282
|
const content = b.value !== void 0 ? b.value : b.shape;
|
|
7231
7283
|
if (content === null || content === void 0 || content === "" || content === "empty") {
|
|
7232
|
-
return
|
|
7284
|
+
return import_chalk22.default.dim(`empty${meta ? " \xB7 " + meta : ""}`);
|
|
7233
7285
|
}
|
|
7234
7286
|
const text = typeof content === "string" ? content : JSON.stringify(content, null, 2);
|
|
7235
|
-
const head = isShape ?
|
|
7287
|
+
const head = isShape ? import_chalk22.default.dim("shape only (values not captured)\n ") : "";
|
|
7236
7288
|
return head + text.split("\n").join("\n ");
|
|
7237
7289
|
}
|
|
7238
7290
|
function pane(title, tone, view, empty) {
|
|
7239
7291
|
console.log(` ${tone(title)}`);
|
|
7240
7292
|
if (empty !== void 0) {
|
|
7241
|
-
console.log(` ${
|
|
7293
|
+
console.log(` ${import_chalk22.default.dim(empty)}`);
|
|
7242
7294
|
return;
|
|
7243
7295
|
}
|
|
7244
7296
|
const v = view ?? {};
|
|
7245
|
-
if (v.url) console.log(` ${
|
|
7297
|
+
if (v.url) console.log(` ${import_chalk22.default.dim("target")} ${v.url}`);
|
|
7246
7298
|
if (v.status != null) {
|
|
7247
|
-
console.log(` ${
|
|
7299
|
+
console.log(` ${import_chalk22.default.dim("status")} ${statusColor(v.status)(String(v.status))}${v.status_text ? import_chalk22.default.dim(" " + v.status_text) : ""}`);
|
|
7248
7300
|
}
|
|
7249
|
-
if (v.raw_path) console.log(` ${
|
|
7301
|
+
if (v.raw_path) console.log(` ${import_chalk22.default.dim("path")} ${v.raw_path}`);
|
|
7250
7302
|
if (v.query && Object.keys(v.query).length) {
|
|
7251
|
-
console.log(` ${
|
|
7303
|
+
console.log(` ${import_chalk22.default.dim("query")} ${Object.entries(v.query).map(([k, q]) => `${k}=${q}`).join(" ")}`);
|
|
7252
7304
|
}
|
|
7253
|
-
if (v.jwt !== void 0) console.log(` ${
|
|
7254
|
-
console.log(` ${
|
|
7255
|
-
console.log(` ${
|
|
7305
|
+
if (v.jwt !== void 0) console.log(` ${import_chalk22.default.dim("jwt")} ${JSON.stringify(v.jwt)}`);
|
|
7306
|
+
console.log(` ${import_chalk22.default.dim("headers")} ${renderHeaders(v.headers)}`);
|
|
7307
|
+
console.log(` ${import_chalk22.default.dim("body")} ${renderBody(v.body)}`);
|
|
7256
7308
|
}
|
|
7257
7309
|
function printDetail(r, mode = "producer") {
|
|
7258
7310
|
const v = r.views;
|
|
7259
7311
|
console.log("");
|
|
7260
|
-
console.log(
|
|
7312
|
+
console.log(import_chalk22.default.bold(`${r.method} ${r.route}`) + " " + statusColor(r.status)(String(r.status)) + import_chalk22.default.dim(` ${r.duration_ms}ms`));
|
|
7261
7313
|
const cost = typeof r.cost_cents === "number" && r.cost_cents > 0 ? ` \xB7 ${r.cost_cents}\xA2` : "";
|
|
7262
|
-
console.log(
|
|
7314
|
+
console.log(import_chalk22.default.dim(` request_id ${r.request_id}${r.environment ? ` \xB7 env ${r.environment}` : ""}${r.consumer_id ? ` \xB7 consumer ${r.consumer_id}` : ""}${cost}`));
|
|
7263
7315
|
console.log("");
|
|
7264
7316
|
if (!v) {
|
|
7265
|
-
console.log(
|
|
7317
|
+
console.log(import_chalk22.default.dim(" No view detail captured for this request.\n"));
|
|
7266
7318
|
return;
|
|
7267
7319
|
}
|
|
7268
|
-
pane("Request (before) \u2014 client \u2192 APIblaze",
|
|
7320
|
+
pane("Request (before) \u2014 client \u2192 APIblaze", import_chalk22.default.cyan, v.request);
|
|
7269
7321
|
if (mode === "producer") {
|
|
7270
7322
|
pane(
|
|
7271
7323
|
"Request (after) \u2014 APIblaze \u2192 upstream",
|
|
7272
|
-
|
|
7324
|
+
import_chalk22.default.cyan,
|
|
7273
7325
|
v.forwarded_request,
|
|
7274
7326
|
v.forwarded_request ? void 0 : "Request did not reach an upstream."
|
|
7275
7327
|
);
|
|
7276
7328
|
pane(
|
|
7277
7329
|
"Response (before) \u2014 upstream \u2192 APIblaze",
|
|
7278
|
-
|
|
7330
|
+
import_chalk22.default.magenta,
|
|
7279
7331
|
v.upstream_response,
|
|
7280
7332
|
v.upstream_response ? void 0 : "No upstream response."
|
|
7281
7333
|
);
|
|
7282
7334
|
}
|
|
7283
|
-
pane("Response (after) \u2014 APIblaze \u2192 client",
|
|
7335
|
+
pane("Response (after) \u2014 APIblaze \u2192 client", import_chalk22.default.magenta, v.response);
|
|
7284
7336
|
console.log("");
|
|
7285
7337
|
}
|
|
7286
|
-
var
|
|
7338
|
+
var import_chalk22, readline, ROUTE_W, WindowNotices, MAX_RECONNECTS, PING_MS, LIVENESS_MS, STABLE_MS, SEEN_CAP, TAIL_INSPECT_BUFFER, TAIL_INSPECT_CHOICES, SCAN_LIMIT, PAGE_LIMIT2, JSON_PAGE_LIMIT, MAX_EMPTY_HOPS, epipeGuarded, PLAIN_MAX_ROWS, NOISE_HEADER, HEADER_PRIORITY, PANE_INDENT;
|
|
7287
7339
|
var init_log_view = __esm({
|
|
7288
7340
|
"src/lib/log-view.ts"() {
|
|
7289
7341
|
"use strict";
|
|
7290
|
-
|
|
7342
|
+
import_chalk22 = __toESM(require("chalk"));
|
|
7291
7343
|
readline = __toESM(require("readline"));
|
|
7292
7344
|
ROUTE_W = 34;
|
|
7293
7345
|
WindowNotices = class {
|
|
@@ -7299,15 +7351,15 @@ var init_log_view = __esm({
|
|
|
7299
7351
|
note(res) {
|
|
7300
7352
|
if (res.history_unavailable && !this.saidUnavailable) {
|
|
7301
7353
|
this.saidUnavailable = true;
|
|
7302
|
-
console.error(
|
|
7354
|
+
console.error(import_chalk22.default.yellow("\u26A0 log history is unreachable right now \u2014 showing the live buffer only, older requests exist but cannot be read."));
|
|
7303
7355
|
}
|
|
7304
7356
|
if ((res.history_markers?.length ?? 0) > 0 && !this.saidGap) {
|
|
7305
7357
|
this.saidGap = true;
|
|
7306
|
-
console.error(
|
|
7358
|
+
console.error(import_chalk22.default.yellow("\u26A0 this range includes a volume-ceiling gap \u2014 some rows were dropped at intake and are gone; counts are incomplete."));
|
|
7307
7359
|
}
|
|
7308
7360
|
if ((res.dropped_this_hour ?? 0) > 0 && !this.saidDropped) {
|
|
7309
7361
|
this.saidDropped = true;
|
|
7310
|
-
console.error(
|
|
7362
|
+
console.error(import_chalk22.default.yellow(`\u26A0 volume ceiling \u2014 ${res.dropped_this_hour} rows dropped this hour; errors keep logging.`));
|
|
7311
7363
|
}
|
|
7312
7364
|
}
|
|
7313
7365
|
};
|
|
@@ -7394,7 +7446,7 @@ async function resolve(projectArg, versionArg, opts) {
|
|
|
7394
7446
|
return { projectId, projectName: match.projectName, version: version2, tenant: tenant2 };
|
|
7395
7447
|
}
|
|
7396
7448
|
function otherPlaneHint() {
|
|
7397
|
-
if (loadConsumer()) console.log(
|
|
7449
|
+
if (loadConsumer()) console.log(import_chalk23.default.dim(" \u2039consumer view: apiblaze consumer logs\u203A"));
|
|
7398
7450
|
}
|
|
7399
7451
|
function fetchWindow(projectId, version2, tenant2) {
|
|
7400
7452
|
return (params) => {
|
|
@@ -7413,8 +7465,8 @@ function fetchWindow(projectId, version2, tenant2) {
|
|
|
7413
7465
|
async function runLogsTail(projectArg, versionArg, opts) {
|
|
7414
7466
|
const { projectId, projectName, version: version2, tenant: tenant2 } = await resolve(projectArg, versionArg, opts);
|
|
7415
7467
|
if (!opts.json) {
|
|
7416
|
-
console.log(
|
|
7417
|
-
console.log(
|
|
7468
|
+
console.log(import_chalk23.default.bold("API Producer logs") + import_chalk23.default.dim(` \xB7 ${projectName}@${version2} \xB7 tenant ${tenant2}${opts.errorsOnly ? " \xB7 errors only" : ""}`));
|
|
7469
|
+
console.log(import_chalk23.default.dim("every caller's requests, with cost and upstream detail"));
|
|
7418
7470
|
otherPlaneHint();
|
|
7419
7471
|
}
|
|
7420
7472
|
await runTailSession({
|
|
@@ -7435,8 +7487,8 @@ async function runLogsTail(projectArg, versionArg, opts) {
|
|
|
7435
7487
|
async function runLogsList(projectArg, versionArg, opts) {
|
|
7436
7488
|
const { projectId, projectName, version: version2, tenant: tenant2 } = await resolve(projectArg, versionArg, opts);
|
|
7437
7489
|
if (!opts.json) {
|
|
7438
|
-
console.log(
|
|
7439
|
-
console.log(
|
|
7490
|
+
console.log(import_chalk23.default.bold("API Producer logs") + import_chalk23.default.dim(` \xB7 ${projectName}@${version2} \xB7 tenant ${tenant2}${opts.errorsOnly ? " \xB7 errors only" : ""}`));
|
|
7491
|
+
console.log(import_chalk23.default.dim("every caller's requests, with cost and upstream detail"));
|
|
7440
7492
|
otherPlaneHint();
|
|
7441
7493
|
console.log("");
|
|
7442
7494
|
}
|
|
@@ -7448,11 +7500,11 @@ async function runLogsList(projectArg, versionArg, opts) {
|
|
|
7448
7500
|
detail: "producer"
|
|
7449
7501
|
});
|
|
7450
7502
|
}
|
|
7451
|
-
var
|
|
7503
|
+
var import_chalk23, activeCaller, viaCaller;
|
|
7452
7504
|
var init_logs = __esm({
|
|
7453
7505
|
"src/commands/logs.ts"() {
|
|
7454
7506
|
"use strict";
|
|
7455
|
-
|
|
7507
|
+
import_chalk23 = __toESM(require("chalk"));
|
|
7456
7508
|
init_caller();
|
|
7457
7509
|
init_auth();
|
|
7458
7510
|
init_consumer_auth();
|
|
@@ -7482,7 +7534,7 @@ function demoName() {
|
|
|
7482
7534
|
}
|
|
7483
7535
|
function portFree(port) {
|
|
7484
7536
|
return new Promise((resolve4) => {
|
|
7485
|
-
const s =
|
|
7537
|
+
const s = import_net2.default.createServer();
|
|
7486
7538
|
s.once("error", () => resolve4(false));
|
|
7487
7539
|
s.once("listening", () => s.close(() => resolve4(true)));
|
|
7488
7540
|
s.listen(port, "127.0.0.1");
|
|
@@ -7494,15 +7546,15 @@ async function freePort(from, avoid = []) {
|
|
|
7494
7546
|
}
|
|
7495
7547
|
function readRegistry() {
|
|
7496
7548
|
try {
|
|
7497
|
-
return JSON.parse(
|
|
7549
|
+
return JSON.parse(import_fs6.default.readFileSync(registryFile(), "utf8"));
|
|
7498
7550
|
} catch {
|
|
7499
7551
|
return [];
|
|
7500
7552
|
}
|
|
7501
7553
|
}
|
|
7502
7554
|
function writeRegistry(rows) {
|
|
7503
7555
|
try {
|
|
7504
|
-
|
|
7505
|
-
|
|
7556
|
+
import_fs6.default.mkdirSync(import_path5.default.dirname(registryFile()), { recursive: true });
|
|
7557
|
+
import_fs6.default.writeFileSync(registryFile(), JSON.stringify(rows));
|
|
7506
7558
|
} catch {
|
|
7507
7559
|
}
|
|
7508
7560
|
}
|
|
@@ -7511,7 +7563,7 @@ function registerDemo(dir, pid = 0) {
|
|
|
7511
7563
|
}
|
|
7512
7564
|
async function waitPorts(ports, ms2 = 1e4) {
|
|
7513
7565
|
const listening = (port) => new Promise((resolve4) => {
|
|
7514
|
-
const sock =
|
|
7566
|
+
const sock = import_net2.default.connect({ port, host: "127.0.0.1" });
|
|
7515
7567
|
sock.once("connect", () => {
|
|
7516
7568
|
sock.destroy();
|
|
7517
7569
|
resolve4(true);
|
|
@@ -7546,8 +7598,8 @@ async function stopTunnels() {
|
|
|
7546
7598
|
return stopped;
|
|
7547
7599
|
}
|
|
7548
7600
|
function appCtl(dir, arg) {
|
|
7549
|
-
if (!
|
|
7550
|
-
const r = (0, import_child_process4.spawnSync)(process.execPath, [
|
|
7601
|
+
if (!import_fs6.default.existsSync(import_path5.default.join(dir, "start.js"))) return false;
|
|
7602
|
+
const r = (0, import_child_process4.spawnSync)(process.execPath, [import_path5.default.join(dir, "start.js"), arg], { cwd: dir, stdio: "ignore", timeout: 15e3 });
|
|
7551
7603
|
return r.status === 0;
|
|
7552
7604
|
}
|
|
7553
7605
|
async function stopDemos() {
|
|
@@ -7558,47 +7610,47 @@ async function stopDemos() {
|
|
|
7558
7610
|
return n;
|
|
7559
7611
|
}
|
|
7560
7612
|
function writeApp(dir) {
|
|
7561
|
-
|
|
7562
|
-
for (const entry of
|
|
7613
|
+
import_fs6.default.mkdirSync(dir, { recursive: true });
|
|
7614
|
+
for (const entry of import_fs6.default.readdirSync(dir)) import_fs6.default.rmSync(import_path5.default.join(dir, entry), { recursive: true, force: true });
|
|
7563
7615
|
for (const [rel, text] of Object.entries(DEMO_FILES)) {
|
|
7564
|
-
const f =
|
|
7565
|
-
|
|
7566
|
-
|
|
7616
|
+
const f = import_path5.default.join(dir, rel);
|
|
7617
|
+
import_fs6.default.mkdirSync(import_path5.default.dirname(f), { recursive: true });
|
|
7618
|
+
import_fs6.default.writeFileSync(f, text);
|
|
7567
7619
|
}
|
|
7568
|
-
|
|
7620
|
+
import_fs6.default.writeFileSync(import_path5.default.join(dir, "package.json"), JSON.stringify({
|
|
7569
7621
|
name: "nino-pizza-demo",
|
|
7570
7622
|
private: true,
|
|
7571
7623
|
scripts: { start: "node start.js --background", stop: "node start.js --stop", restart: "node start.js --restart", dev: "node start.js" }
|
|
7572
7624
|
}, null, 2) + "\n");
|
|
7573
|
-
|
|
7574
|
-
for (const [from, to] of [[
|
|
7575
|
-
if (!
|
|
7576
|
-
|
|
7577
|
-
|
|
7625
|
+
import_fs6.default.writeFileSync(import_path5.default.join(dir, ".gitignore"), ".env.local\n.demo/\n");
|
|
7626
|
+
for (const [from, to] of [[import_path5.default.join(__dirname, "server", "index.js"), import_path5.default.join(dir, "lib", "apiblaze-server.js")], [import_path5.default.join(__dirname, "browser", "widgets.js"), import_path5.default.join(dir, "public", "apiblaze-widgets.js")]]) {
|
|
7627
|
+
if (!import_fs6.default.existsSync(from)) continue;
|
|
7628
|
+
import_fs6.default.mkdirSync(import_path5.default.dirname(to), { recursive: true });
|
|
7629
|
+
import_fs6.default.copyFileSync(from, to);
|
|
7578
7630
|
}
|
|
7579
7631
|
}
|
|
7580
7632
|
function saveState(dir, s) {
|
|
7581
|
-
const d =
|
|
7582
|
-
|
|
7583
|
-
|
|
7633
|
+
const d = import_path5.default.join(dir, STATE_DIR);
|
|
7634
|
+
import_fs6.default.mkdirSync(d, { recursive: true });
|
|
7635
|
+
import_fs6.default.writeFileSync(import_path5.default.join(d, "state.json"), JSON.stringify(s, null, 2), { mode: 384 });
|
|
7584
7636
|
}
|
|
7585
7637
|
function loadState(dir) {
|
|
7586
7638
|
try {
|
|
7587
|
-
return JSON.parse(
|
|
7639
|
+
return JSON.parse(import_fs6.default.readFileSync(import_path5.default.join(dir, STATE_DIR, "state.json"), "utf8"));
|
|
7588
7640
|
} catch {
|
|
7589
7641
|
return null;
|
|
7590
7642
|
}
|
|
7591
7643
|
}
|
|
7592
7644
|
function writeEnv(dir, v) {
|
|
7593
7645
|
const lines = Object.entries(v).map(([k, val]) => `${k}=${val}`);
|
|
7594
|
-
|
|
7646
|
+
import_fs6.default.writeFileSync(import_path5.default.join(dir, ".env.local"), `# Written by ${LATEST} demo. Server-side only: never commit or share.
|
|
7595
7647
|
${lines.join("\n")}
|
|
7596
7648
|
`, { mode: 384 });
|
|
7597
7649
|
}
|
|
7598
7650
|
function readEnvFile(file2) {
|
|
7599
7651
|
const out = {};
|
|
7600
7652
|
try {
|
|
7601
|
-
for (const line of
|
|
7653
|
+
for (const line of import_fs6.default.readFileSync(file2, "utf8").split("\n")) {
|
|
7602
7654
|
const m = /^([A-Z0-9_]+)=(.*)$/.exec(line.trim());
|
|
7603
7655
|
if (m) out[m[1]] = m[2];
|
|
7604
7656
|
}
|
|
@@ -7608,20 +7660,20 @@ function readEnvFile(file2) {
|
|
|
7608
7660
|
}
|
|
7609
7661
|
function demoDir() {
|
|
7610
7662
|
const here = process.cwd();
|
|
7611
|
-
if (
|
|
7612
|
-
return
|
|
7663
|
+
if (import_fs6.default.existsSync(import_path5.default.join(here, STATE_DIR, "state.json"))) return here;
|
|
7664
|
+
return import_path5.default.resolve(here, DIR_NAME);
|
|
7613
7665
|
}
|
|
7614
7666
|
function adoptWorkspace(cpKey, teamId, claimCode) {
|
|
7615
7667
|
const prev = loadAnonCred();
|
|
7616
7668
|
if (prev?.cp_key && prev.team_id && prev.team_id !== teamId) {
|
|
7617
7669
|
try {
|
|
7618
|
-
|
|
7670
|
+
import_fs6.default.writeFileSync(import_path5.default.join(getApiblazeDir(), `anon.${prev.team_id}.json`), JSON.stringify(prev, null, 2), { mode: 384 });
|
|
7619
7671
|
} catch {
|
|
7620
7672
|
}
|
|
7621
7673
|
}
|
|
7622
7674
|
saveAnonCred(cpKey, teamId, claimCode);
|
|
7623
7675
|
}
|
|
7624
|
-
async function createProxy2(name,
|
|
7676
|
+
async function createProxy2(name, backendPort2, openapi, tenantName) {
|
|
7625
7677
|
const auth = resolveAuth({});
|
|
7626
7678
|
const proposed = await proposeFromSpec(openapi).catch(() => null);
|
|
7627
7679
|
const families = proposed?.status === 200 && Array.isArray(proposed.data?.families) ? proposed.data.families.filter((f) => !f.skipped_reason).map((f) => f.key) : [];
|
|
@@ -7631,7 +7683,7 @@ async function createProxy2(name, backendPort, openapi, tenantName) {
|
|
|
7631
7683
|
auth_type: auth.auth,
|
|
7632
7684
|
...auth.bodyPatch,
|
|
7633
7685
|
openapi,
|
|
7634
|
-
environments: { dev: { target: `http://localhost:${
|
|
7686
|
+
environments: { dev: { target: `http://localhost:${backendPort2}`, description: `the ${B.cli} demo on this machine` } },
|
|
7635
7687
|
// Two customers of the same app, each with its own key: the proxy's own tenant is Nino
|
|
7636
7688
|
// Pizza; the server adds Gino Pizza and returns its key.
|
|
7637
7689
|
tenant: tenantName,
|
|
@@ -7674,12 +7726,12 @@ async function waitThrough(apiUrl, serverKey) {
|
|
|
7674
7726
|
async function confirmStep(opts, question) {
|
|
7675
7727
|
if (opts.yes || !interactive()) return true;
|
|
7676
7728
|
console.log();
|
|
7677
|
-
return ask(`${
|
|
7729
|
+
return ask(`${import_chalk24.default.bold("?")} ${question} ${import_chalk24.default.dim("(Y/n)")} `);
|
|
7678
7730
|
}
|
|
7679
7731
|
async function busy(text, fn, minMs = 0) {
|
|
7680
7732
|
if (!process.stdout.isTTY) return fn();
|
|
7681
7733
|
const t = Date.now();
|
|
7682
|
-
const spinner = (0,
|
|
7734
|
+
const spinner = (0, import_ora9.default)({ text: import_chalk24.default.dim(text), indent: 3, discardStdin: false }).start();
|
|
7683
7735
|
try {
|
|
7684
7736
|
const out = await fn();
|
|
7685
7737
|
const left = minMs - (Date.now() - t);
|
|
@@ -7707,12 +7759,12 @@ function openBrowser3(url) {
|
|
|
7707
7759
|
}
|
|
7708
7760
|
async function offerBrowserAndLogs(dir, opts) {
|
|
7709
7761
|
if (opts.open === false || opts.yes || !interactive()) return;
|
|
7710
|
-
const env = readEnvFile(
|
|
7762
|
+
const env = readEnvFile(import_path5.default.join(dir, ".env.local"));
|
|
7711
7763
|
const url = `http://localhost:${env.APP_PORT}`;
|
|
7712
7764
|
console.log();
|
|
7713
7765
|
if (await ask(` Open ${url} in your browser? (Y/n) `)) openBrowser3(url);
|
|
7714
7766
|
if (await ask(` Show the requests reaching ${B.product} here, live? (Y/n) `)) {
|
|
7715
|
-
console.log(
|
|
7767
|
+
console.log(import_chalk24.default.dim(` $ ${logsCommand(env)}`));
|
|
7716
7768
|
const { runLogsTail: runLogsTail2 } = await Promise.resolve().then(() => (init_logs(), logs_exports));
|
|
7717
7769
|
await runLogsTail2(env.APIBLAZE_NAME, void 0, { tenant: env.APIBLAZE_TENANT });
|
|
7718
7770
|
}
|
|
@@ -7742,13 +7794,13 @@ function routesOf(openapi) {
|
|
|
7742
7794
|
return out;
|
|
7743
7795
|
}
|
|
7744
7796
|
async function runDemo(opts = {}) {
|
|
7745
|
-
if (opts.serve) return serveTunnel(
|
|
7797
|
+
if (opts.serve) return serveTunnel(import_path5.default.resolve(opts.serve));
|
|
7746
7798
|
try {
|
|
7747
7799
|
await startDemo(opts);
|
|
7748
7800
|
} catch (err) {
|
|
7749
7801
|
const e = err instanceof DemoError ? err : new DemoError(`The demo could not start (${err.message}).`, `Run again: ${LATEST} demo`);
|
|
7750
7802
|
await stopDemos().catch(() => 0);
|
|
7751
|
-
console.log(`${
|
|
7803
|
+
console.log(`${import_chalk24.default.red("\u2717")} ${e.message}`);
|
|
7752
7804
|
console.log(` ${e.fix}`);
|
|
7753
7805
|
process.exit(1);
|
|
7754
7806
|
}
|
|
@@ -7769,25 +7821,25 @@ function printTable(rows) {
|
|
|
7769
7821
|
if (line) out.push(line);
|
|
7770
7822
|
return out;
|
|
7771
7823
|
};
|
|
7772
|
-
const head = (l, r) => console.log(` ${
|
|
7824
|
+
const head = (l, r) => console.log(` ${import_chalk24.default.dim(l.padEnd(leftW))} ${import_chalk24.default.dim(r)}`);
|
|
7773
7825
|
head(`What ${B.product} set up`, "The command that does the same");
|
|
7774
7826
|
head("\u2500".repeat(leftW), "\u2500".repeat(Math.min(rightW, 60)));
|
|
7775
7827
|
for (const r of rows) {
|
|
7776
|
-
const left = `${
|
|
7828
|
+
const left = `${import_chalk24.default.green("\u2713")} ${import_chalk24.default.bold(r.label.padEnd(17))} ${r.what}`;
|
|
7777
7829
|
const leftPlain = `\u2713 ${r.label.padEnd(17)} ${r.what}`;
|
|
7778
7830
|
const right = r.cmds.length ? r.cmds.flatMap(wrap2) : ["\u2014"];
|
|
7779
|
-
console.log(` ${left}${" ".repeat(Math.max(0, leftW - leftPlain.length))} ${
|
|
7780
|
-
for (const extra of right.slice(1)) console.log(` ${" ".repeat(leftW)} ${
|
|
7831
|
+
console.log(` ${left}${" ".repeat(Math.max(0, leftW - leftPlain.length))} ${import_chalk24.default.cyan(right[0])}`);
|
|
7832
|
+
for (const extra of right.slice(1)) console.log(` ${" ".repeat(leftW)} ${import_chalk24.default.cyan(extra)}`);
|
|
7781
7833
|
}
|
|
7782
7834
|
}
|
|
7783
7835
|
async function startDemo(opts) {
|
|
7784
7836
|
const t0 = Date.now();
|
|
7785
7837
|
const here = process.cwd();
|
|
7786
|
-
const dir =
|
|
7787
|
-
const rel =
|
|
7838
|
+
const dir = import_fs6.default.existsSync(import_path5.default.join(here, STATE_DIR, "state.json")) ? here : import_path5.default.resolve(here, DIR_NAME);
|
|
7839
|
+
const rel = import_path5.default.relative(here, dir) || ".";
|
|
7788
7840
|
const cdDir = rel === "." ? "" : `cd ${rel} && `;
|
|
7789
|
-
const shell = (c, note = "") => console.log(` ${
|
|
7790
|
-
const ok = (text, note = "") => console.log(` ${
|
|
7841
|
+
const shell = (c, note = "") => console.log(` ${import_chalk24.default.dim("$")} ${import_chalk24.default.cyan(c)}${note ? ` ${import_chalk24.default.dim(note)}` : ""}`);
|
|
7842
|
+
const ok = (text, note = "") => console.log(` ${import_chalk24.default.green("\u2713")} ${text}${note ? ` ${import_chalk24.default.dim(note)}` : ""}`);
|
|
7791
7843
|
const stopHere = (lines) => {
|
|
7792
7844
|
console.log();
|
|
7793
7845
|
for (const l of lines) console.log(` ${l}`);
|
|
@@ -7795,42 +7847,42 @@ async function startDemo(opts) {
|
|
|
7795
7847
|
await stopDemos();
|
|
7796
7848
|
writeApp(dir);
|
|
7797
7849
|
const appPort = await freePort(3e3);
|
|
7798
|
-
const
|
|
7799
|
-
const directPort = await freePort(3002, [appPort,
|
|
7850
|
+
const backendPort2 = await freePort(3001, [appPort]);
|
|
7851
|
+
const directPort = await freePort(3002, [appPort, backendPort2]);
|
|
7800
7852
|
const name = demoName();
|
|
7801
7853
|
const ninoTenant = `ninopizza${import_crypto.default.randomBytes(2).toString("hex")}`;
|
|
7802
7854
|
const backendSecret = import_crypto.default.randomBytes(18).toString("base64url");
|
|
7803
7855
|
const openapi = DEMO_FILES["openapi.yaml"];
|
|
7804
7856
|
const L = (c) => `${LATEST} ${c}`;
|
|
7805
|
-
writeEnv(dir, { BACKEND_SECRET: backendSecret, DEMO_NINO_TENANT: ninoTenant, APP_PORT: appPort, BACKEND_PORT:
|
|
7857
|
+
writeEnv(dir, { BACKEND_SECRET: backendSecret, DEMO_NINO_TENANT: ninoTenant, APP_PORT: appPort, BACKEND_PORT: backendPort2, DIRECT_PORT: directPort });
|
|
7806
7858
|
console.log();
|
|
7807
|
-
console.log(
|
|
7859
|
+
console.log(import_chalk24.default.bold("1. The app: ResiResi, a table booking app for Tenants (Nino and Gino Pizzas) to allow end-users to book tables in their restaurants") + import_chalk24.default.dim(` (no ${B.product} yet)`));
|
|
7808
7860
|
console.log(` Written to ${rel}. Its routes, from openapi.yaml:`);
|
|
7809
|
-
for (const r of routesOf(openapi)) console.log(` ${r.method.padEnd(7)}${r.path.padEnd(21)}${
|
|
7810
|
-
console.log(
|
|
7861
|
+
for (const r of routesOf(openapi)) console.log(` ${r.method.padEnd(7)}${r.path.padEnd(21)}${import_chalk24.default.dim(r.summary)}`);
|
|
7862
|
+
console.log(import_chalk24.default.dim(" No login, no API keys, no permission checks in its code. It only checks one shared backend secret (in .env.local)."));
|
|
7811
7863
|
shell(`${cdDir}npm run start`, "npm run stop \xB7 npm run restart");
|
|
7812
7864
|
if (!appCtl(dir, "--background")) throw new DemoError("The ResiResi app could not start.", `Run again: ${LATEST} demo`);
|
|
7813
7865
|
registerDemo(dir);
|
|
7814
|
-
const appUp = await busy("Starting the reservation backend and its front-end\u2026", () => waitPorts([
|
|
7815
|
-
if (!appUp) throw new DemoError(`The ResiResi app did not start (see ${
|
|
7816
|
-
ok(`${
|
|
7817
|
-
ok(`${
|
|
7866
|
+
const appUp = await busy("Starting the reservation backend and its front-end\u2026", () => waitPorts([backendPort2, directPort]), 1800);
|
|
7867
|
+
if (!appUp) throw new DemoError(`The ResiResi app did not start (see ${import_path5.default.join(rel, STATE_DIR, "app.log")}).`, `Run again: ${LATEST} demo`);
|
|
7868
|
+
ok(`${import_chalk24.default.bold("Reservation backend")}: localhost:${backendPort2}`, "unprotected");
|
|
7869
|
+
ok(`${import_chalk24.default.bold("Unprotected reservation front-end")}, going directly to the reservation backend: ${import_chalk24.default.bold(`http://localhost:${directPort}`)}`);
|
|
7818
7870
|
console.log(" Book a table directly, the way the unprotected front-end does:");
|
|
7819
|
-
console.log(
|
|
7820
|
-
console.log(
|
|
7821
|
-
console.log(
|
|
7822
|
-
console.log(
|
|
7871
|
+
console.log(import_chalk24.default.cyan(` curl -X POST http://localhost:${backendPort2}/reservations \\`));
|
|
7872
|
+
console.log(import_chalk24.default.cyan(` -H "x-target-api-key: $(grep BACKEND_SECRET ${import_path5.default.join(rel, ".env.local")} | cut -d= -f2)" \\`));
|
|
7873
|
+
console.log(import_chalk24.default.cyan(` -H "x-abz-user-id: ana" -H "x-abz-tenant-id: ${ninoTenant}" \\`));
|
|
7874
|
+
console.log(import_chalk24.default.cyan(` -H "content-type: application/json" -d '{"time":"19:30","guests":2}'`));
|
|
7823
7875
|
if (!await confirmStep(opts, `Ok to put an ${B.product} proxy and MCP in front of the reservation backend?`)) {
|
|
7824
7876
|
return stopHere([
|
|
7825
|
-
`Stopped before ${B.product}. ResiResi keeps running on this laptop: http://localhost:${directPort} ${
|
|
7877
|
+
`Stopped before ${B.product}. ResiResi keeps running on this laptop: http://localhost:${directPort} ${import_chalk24.default.dim(`(${cdDir}npm run stop stops it)`)}`,
|
|
7826
7878
|
`Run the whole demo again any time: ${LATEST} demo`
|
|
7827
7879
|
]);
|
|
7828
7880
|
}
|
|
7829
7881
|
console.log();
|
|
7830
|
-
console.log(
|
|
7882
|
+
console.log(import_chalk24.default.bold(`2. ${B.product}, in front of it`));
|
|
7831
7883
|
const tCreate = Date.now();
|
|
7832
7884
|
const setup = await busy(`Creating the proxy, its tenants, admins, rules and limits\u2026`, async () => {
|
|
7833
|
-
const r = await createProxy2(name,
|
|
7885
|
+
const r = await createProxy2(name, backendPort2, openapi, ninoTenant);
|
|
7834
7886
|
const cpKey2 = r.cp_key;
|
|
7835
7887
|
const teamId2 = r.team_id;
|
|
7836
7888
|
const keys = r.api_keys ?? {};
|
|
@@ -7884,13 +7936,13 @@ async function startDemo(opts) {
|
|
|
7884
7936
|
BACKEND_SECRET: backendSecret,
|
|
7885
7937
|
DEMO_NINO_TENANT: ninoTenant,
|
|
7886
7938
|
APP_PORT: appPort,
|
|
7887
|
-
BACKEND_PORT:
|
|
7939
|
+
BACKEND_PORT: backendPort2,
|
|
7888
7940
|
DIRECT_PORT: directPort
|
|
7889
7941
|
});
|
|
7890
|
-
const state = { name: project, version: version2, tenant: tenant2, teamId, cpKey, appPort, backendPort, apiUrl, claimCode: setup.claimCode };
|
|
7942
|
+
const state = { name: project, version: version2, tenant: tenant2, teamId, cpKey, appPort, backendPort: backendPort2, apiUrl, claimCode: setup.claimCode };
|
|
7891
7943
|
saveState(dir, state);
|
|
7892
7944
|
printTable([
|
|
7893
|
-
{ label: "Proxy", what: `${project} (${createSecs} s, no account)`, cmds: [L(`dev --port ${
|
|
7945
|
+
{ label: "Proxy", what: `${project} (${createSecs} s, no account)`, cmds: [L(`dev --port ${backendPort2} --openapi ./openapi.yaml --name ${name} --tenant ${ninoTenant} --auto`)] },
|
|
7894
7946
|
{
|
|
7895
7947
|
label: "Customers/Tenants",
|
|
7896
7948
|
what: gino ? "Nino Pizza and Gino Pizza, own API keys" : "Nino Pizza, its own API key",
|
|
@@ -7907,20 +7959,20 @@ async function startDemo(opts) {
|
|
|
7907
7959
|
{ label: "Backend secret", what: "added to every call APIblaze forwards", cmds: [L(`transform ${project} set-header x-target-api-key --value-env BACKEND_SECRET --secret`)] },
|
|
7908
7960
|
{ label: "Keys", what: "saved to .env.local, never printed", cmds: [] }
|
|
7909
7961
|
]);
|
|
7910
|
-
if (!await confirmStep(opts, `Ok to create a tunnel allowing ${project} to forward requests to localhost:${
|
|
7962
|
+
if (!await confirmStep(opts, `Ok to create a tunnel allowing ${project} to forward requests to localhost:${backendPort2}?`)) {
|
|
7911
7963
|
return stopHere([
|
|
7912
7964
|
`Stopped before the tunnel. The proxy exists but cannot reach this laptop yet.`,
|
|
7913
7965
|
`Open the tunnel any time: ${LATEST} demo start`
|
|
7914
7966
|
]);
|
|
7915
7967
|
}
|
|
7916
7968
|
console.log();
|
|
7917
|
-
console.log(
|
|
7918
|
-
shell(`${LATEST} dev --port ${
|
|
7919
|
-
await busy(`Opening the tunnel to localhost:${
|
|
7969
|
+
console.log(import_chalk24.default.bold("3. Live on the internet"));
|
|
7970
|
+
shell(`${LATEST} dev --port ${backendPort2} --project ${project} --auto`, "the tunnel, in the background");
|
|
7971
|
+
await busy(`Opening the tunnel to localhost:${backendPort2} and checking it with a real call\u2026`, async () => {
|
|
7920
7972
|
await spawnTunnel(dir, state);
|
|
7921
7973
|
await waitThrough(apiUrl, serverKey);
|
|
7922
7974
|
}, 1200);
|
|
7923
|
-
ok(`${
|
|
7975
|
+
ok(`${import_chalk24.default.bold("Tunnel")}: the public address reaches localhost:${backendPort2} on this laptop`, `checked with a real call \xB7 ${Math.round((Date.now() - t0) / 1e3)} s so far`);
|
|
7924
7976
|
printAddresses(dir);
|
|
7925
7977
|
if (!await confirmStep(opts, "Ok to launch the protected website, which will use the proxy to reach the backend?")) {
|
|
7926
7978
|
return stopHere([
|
|
@@ -7929,29 +7981,29 @@ async function startDemo(opts) {
|
|
|
7929
7981
|
]);
|
|
7930
7982
|
}
|
|
7931
7983
|
console.log();
|
|
7932
|
-
console.log(
|
|
7984
|
+
console.log(import_chalk24.default.bold(`4. The protected website, through ${B.product}`));
|
|
7933
7985
|
shell(`${cdDir}npm run restart`, "picks up the keys in .env.local");
|
|
7934
7986
|
const siteUp = await busy("Starting the protected website\u2026", async () => {
|
|
7935
7987
|
appCtl(dir, "--stop");
|
|
7936
7988
|
appCtl(dir, "--background");
|
|
7937
|
-
return waitPorts([appPort, directPort,
|
|
7989
|
+
return waitPorts([appPort, directPort, backendPort2]);
|
|
7938
7990
|
}, 1500);
|
|
7939
|
-
if (!siteUp) throw new DemoError(`The protected website did not start (see ${
|
|
7940
|
-
ok(`${
|
|
7941
|
-
ok(`${
|
|
7991
|
+
if (!siteUp) throw new DemoError(`The protected website did not start (see ${import_path5.default.join(rel, STATE_DIR, "app.log")}).`, `Try: ${cdDir}npm run restart`);
|
|
7992
|
+
ok(`${import_chalk24.default.bold("Protected reservation website")}, going through the ${B.product} proxy ${apiUrl}: ${import_chalk24.default.bold(`http://localhost:${appPort}`)}`);
|
|
7993
|
+
ok(`${import_chalk24.default.bold("Unprotected reservation front-end")}, still going directly to the backend: http://localhost:${directPort}`, "the switch at the top of the page goes between them");
|
|
7942
7994
|
console.log();
|
|
7943
|
-
console.log(
|
|
7995
|
+
console.log(import_chalk24.default.dim(` Logs: ${logsCommand(readEnvFile(import_path5.default.join(dir, ".env.local")))} \xB7 Stop the tunnel: ${LATEST} demo stop \xB7 Your own app: ${LATEST} dev --port <port>`));
|
|
7944
7996
|
await offerBrowserAndLogs(dir, opts);
|
|
7945
7997
|
}
|
|
7946
7998
|
function printAddresses(dir) {
|
|
7947
|
-
const env = readEnvFile(
|
|
7948
|
-
const row = (label3, value, note = "") => console.log(` ${label3.padEnd(11)} ${value}${note ? ` ${
|
|
7999
|
+
const env = readEnvFile(import_path5.default.join(dir, ".env.local"));
|
|
8000
|
+
const row = (label3, value, note = "") => console.log(` ${label3.padEnd(11)} ${value}${note ? ` ${import_chalk24.default.dim(note)}` : ""}`);
|
|
7949
8001
|
row("Public API", env.APIBLAZE_URL ?? "", `${B.product} tunnel \u2192 localhost:${env.BACKEND_PORT}`);
|
|
7950
8002
|
row("MCP", env.APIBLAZE_MCP_URL ?? "", "for Claude, ChatGPT, Cursor (Nino Pizza)");
|
|
7951
8003
|
if (env.APIBLAZE_PORTAL_URL) row("Dev portal", env.APIBLAZE_PORTAL_URL, "sign in, get a key, try the API");
|
|
7952
8004
|
}
|
|
7953
8005
|
async function spawnTunnel(dir, state) {
|
|
7954
|
-
const log4 =
|
|
8006
|
+
const log4 = import_fs6.default.openSync(import_path5.default.join(dir, STATE_DIR, "tunnel.log"), "a");
|
|
7955
8007
|
const child = (0, import_child_process4.spawn)(process.execPath, [process.argv[1], "demo", "--serve", dir], { cwd: dir, detached: true, stdio: ["ignore", log4, log4] });
|
|
7956
8008
|
child.unref();
|
|
7957
8009
|
saveState(dir, { ...state, pid: child.pid, ready: false, error: void 0 });
|
|
@@ -7962,9 +8014,9 @@ async function runDemoStart(opts = {}) {
|
|
|
7962
8014
|
const t0 = Date.now();
|
|
7963
8015
|
const dir = demoDir();
|
|
7964
8016
|
const state = loadState(dir);
|
|
7965
|
-
const env = readEnvFile(
|
|
8017
|
+
const env = readEnvFile(import_path5.default.join(dir, ".env.local"));
|
|
7966
8018
|
if (!state || !env.APIBLAZE_SERVER_KEY) {
|
|
7967
|
-
console.log(`${
|
|
8019
|
+
console.log(`${import_chalk24.default.red("\u2717")} There is no demo here to start.`);
|
|
7968
8020
|
console.log(` Make one: ${LATEST} demo`);
|
|
7969
8021
|
process.exit(1);
|
|
7970
8022
|
}
|
|
@@ -7973,30 +8025,30 @@ async function runDemoStart(opts = {}) {
|
|
|
7973
8025
|
await spawnTunnel(dir, state);
|
|
7974
8026
|
} catch (err) {
|
|
7975
8027
|
const e = err instanceof DemoError ? err : new DemoError(`The tunnel could not start (${err.message}).`, `Start over: ${LATEST} demo`);
|
|
7976
|
-
console.log(`${
|
|
8028
|
+
console.log(`${import_chalk24.default.red("\u2717")} ${e.message}`);
|
|
7977
8029
|
console.log(` ${e.fix}`);
|
|
7978
8030
|
process.exit(1);
|
|
7979
8031
|
}
|
|
7980
|
-
console.log(`${
|
|
8032
|
+
console.log(`${import_chalk24.default.green("\u2713")} Tunnel open: the public address reaches localhost:${env.BACKEND_PORT} again ${import_chalk24.default.dim(`(${Math.round((Date.now() - t0) / 1e3)} s)`)}`);
|
|
7981
8033
|
printAddresses(dir);
|
|
7982
8034
|
const appUp = await fetch(`http://127.0.0.1:${env.APP_PORT}/api/config`).then(() => true).catch(() => false);
|
|
7983
|
-
if (!appUp) console.log(
|
|
8035
|
+
if (!appUp) console.log(import_chalk24.default.yellow(` ResiResi is not running: cd ${import_path5.default.relative(process.cwd(), dir) || "."} && npm run start`));
|
|
7984
8036
|
await offerBrowserAndLogs(dir, opts);
|
|
7985
8037
|
}
|
|
7986
8038
|
async function runDemoLogs() {
|
|
7987
|
-
const env = readEnvFile(
|
|
8039
|
+
const env = readEnvFile(import_path5.default.join(demoDir(), ".env.local"));
|
|
7988
8040
|
if (!env.APIBLAZE_NAME) {
|
|
7989
|
-
console.log(`${
|
|
8041
|
+
console.log(`${import_chalk24.default.red("\u2717")} There is no demo here.`);
|
|
7990
8042
|
console.log(` Make one: ${LATEST} demo`);
|
|
7991
8043
|
process.exit(1);
|
|
7992
8044
|
}
|
|
7993
|
-
console.log(
|
|
8045
|
+
console.log(import_chalk24.default.dim(`$ ${logsCommand(env)}`));
|
|
7994
8046
|
const { runLogsTail: runLogsTail2 } = await Promise.resolve().then(() => (init_logs(), logs_exports));
|
|
7995
8047
|
await runLogsTail2(env.APIBLAZE_NAME, void 0, { tenant: env.APIBLAZE_TENANT });
|
|
7996
8048
|
}
|
|
7997
8049
|
async function runDemoStop() {
|
|
7998
8050
|
const n = await stopTunnels();
|
|
7999
|
-
console.log(n ? `${
|
|
8051
|
+
console.log(n ? `${import_chalk24.default.green("\u2713")} Stopped the tunnel. ResiResi still runs on this laptop (npm run stop stops it).` : "No demo tunnel is running.");
|
|
8000
8052
|
}
|
|
8001
8053
|
async function serveTunnel(dir) {
|
|
8002
8054
|
const state = loadState(dir);
|
|
@@ -8054,17 +8106,17 @@ async function serveTunnel(dir) {
|
|
|
8054
8106
|
await new Promise(() => {
|
|
8055
8107
|
});
|
|
8056
8108
|
}
|
|
8057
|
-
var
|
|
8109
|
+
var import_fs6, import_path5, import_net2, import_child_process4, import_crypto, import_chalk24, import_ora9, DIR_NAME, STATE_DIR, LATEST, DEMO_RPS, DEMO_ADMIN_EMAIL, DEMO_GINO_ADMIN_EMAIL, RULE_SENTENCE, DemoError, ADJ, NOUN, pick2, registryFile, alive, interactive, logsCommand;
|
|
8058
8110
|
var init_demo = __esm({
|
|
8059
8111
|
"src/commands/demo.ts"() {
|
|
8060
8112
|
"use strict";
|
|
8061
|
-
|
|
8062
|
-
|
|
8063
|
-
|
|
8113
|
+
import_fs6 = __toESM(require("fs"));
|
|
8114
|
+
import_path5 = __toESM(require("path"));
|
|
8115
|
+
import_net2 = __toESM(require("net"));
|
|
8064
8116
|
import_child_process4 = require("child_process");
|
|
8065
8117
|
import_crypto = __toESM(require("crypto"));
|
|
8066
|
-
|
|
8067
|
-
|
|
8118
|
+
import_chalk24 = __toESM(require("chalk"));
|
|
8119
|
+
import_ora9 = __toESM(require("ora"));
|
|
8068
8120
|
init_api();
|
|
8069
8121
|
init_tunnel_client();
|
|
8070
8122
|
init_auth();
|
|
@@ -8088,7 +8140,7 @@ var init_demo = __esm({
|
|
|
8088
8140
|
ADJ = ["brave", "calm", "clever", "eager", "happy", "jolly", "lucky", "merry", "noble", "quick", "sunny", "swift", "witty", "zesty", "bright", "gentle"];
|
|
8089
8141
|
NOUN = ["otter", "falcon", "panda", "heron", "koala", "lemur", "puffin", "robin", "tiger", "walrus", "wombat", "badger", "comet", "maple", "river", "finch"];
|
|
8090
8142
|
pick2 = (a) => a[import_crypto.default.randomInt(a.length)];
|
|
8091
|
-
registryFile = () =>
|
|
8143
|
+
registryFile = () => import_path5.default.join(getApiblazeDir(), "demo-runs.json");
|
|
8092
8144
|
alive = (pid) => {
|
|
8093
8145
|
try {
|
|
8094
8146
|
process.kill(pid, 0);
|
|
@@ -8353,7 +8405,7 @@ function runCli(args, cwd, home, timeoutMs) {
|
|
|
8353
8405
|
function readEnv(file2) {
|
|
8354
8406
|
const out = {};
|
|
8355
8407
|
try {
|
|
8356
|
-
for (const line of
|
|
8408
|
+
for (const line of import_fs10.default.readFileSync(file2, "utf8").split("\n")) {
|
|
8357
8409
|
const m = /^([A-Z0-9_]+)=(.*)$/.exec(line.trim());
|
|
8358
8410
|
if (m) out[m[1]] = m[2];
|
|
8359
8411
|
}
|
|
@@ -8386,7 +8438,7 @@ async function journey(label3, work, home, checks) {
|
|
|
8386
8438
|
`exit ${run2.code} in ${Math.round(run2.ms / 1e3)} s${leaked ? " \xB7 PRINTED A SECRET" : ""}${run2.code !== 0 ? ` \xB7 ${run2.out.trim().split("\n").slice(-2).join(" / ")}` : ""}`
|
|
8387
8439
|
);
|
|
8388
8440
|
if (run2.code !== 0) return null;
|
|
8389
|
-
const env = readEnv(
|
|
8441
|
+
const env = readEnv(import_path7.default.join(work, `${B.cli}-demo`, ".env.local"));
|
|
8390
8442
|
const api = env.APIBLAZE_URL;
|
|
8391
8443
|
const nino = env.APIBLAZE_SERVER_KEY;
|
|
8392
8444
|
const as = (user, key = nino) => ({ "x-api-key": key, "x-end-user-id": user, "content-type": "application/json" });
|
|
@@ -8451,11 +8503,11 @@ async function journey(label3, work, home, checks) {
|
|
|
8451
8503
|
}
|
|
8452
8504
|
async function runOpAnon(o = {}) {
|
|
8453
8505
|
const t0 = Date.now();
|
|
8454
|
-
const root =
|
|
8455
|
-
const home =
|
|
8456
|
-
const work =
|
|
8457
|
-
|
|
8458
|
-
|
|
8506
|
+
const root = import_fs10.default.mkdtempSync(import_path7.default.join(import_os2.default.tmpdir(), `${B.cli}-op-anon-`));
|
|
8507
|
+
const home = import_path7.default.join(root, "home");
|
|
8508
|
+
const work = import_path7.default.join(root, "work");
|
|
8509
|
+
import_fs10.default.mkdirSync(home);
|
|
8510
|
+
import_fs10.default.mkdirSync(work);
|
|
8459
8511
|
const checks = [];
|
|
8460
8512
|
const log4 = (s) => {
|
|
8461
8513
|
if (!o.json) console.log(s);
|
|
@@ -8466,21 +8518,21 @@ async function runOpAnon(o = {}) {
|
|
|
8466
8518
|
await journey("rerun", work, home, checks);
|
|
8467
8519
|
} finally {
|
|
8468
8520
|
const stop = await runCli(["demo", "stop"], work, home, 3e4);
|
|
8469
|
-
const demoFolder =
|
|
8470
|
-
if (
|
|
8521
|
+
const demoFolder = import_path7.default.join(work, `${B.cli}-demo`);
|
|
8522
|
+
if (import_fs10.default.existsSync(import_path7.default.join(demoFolder, "start.js"))) {
|
|
8471
8523
|
await new Promise((res) => {
|
|
8472
|
-
const c = (0, import_child_process5.spawn)(process.execPath, [
|
|
8524
|
+
const c = (0, import_child_process5.spawn)(process.execPath, [import_path7.default.join(demoFolder, "start.js"), "--stop"], { cwd: demoFolder, stdio: "ignore" });
|
|
8473
8525
|
c.on("close", () => res());
|
|
8474
8526
|
});
|
|
8475
8527
|
}
|
|
8476
|
-
const env = readEnv(
|
|
8528
|
+
const env = readEnv(import_path7.default.join(work, `${B.cli}-demo`, ".env.local"));
|
|
8477
8529
|
let portsFree = true;
|
|
8478
8530
|
for (const p of [env.APP_PORT, env.BACKEND_PORT].filter(Boolean)) {
|
|
8479
8531
|
const r = await http(`http://127.0.0.1:${p}/`, { timeoutMs: 2e3 });
|
|
8480
8532
|
if (r.status !== 0) portsFree = false;
|
|
8481
8533
|
}
|
|
8482
8534
|
checks.push({ id: "stop", ok: stop.code === 0 && portsFree, detail: `exit ${stop.code}${portsFree ? "" : " \xB7 ports still answering"}` });
|
|
8483
|
-
|
|
8535
|
+
import_fs10.default.rmSync(root, { recursive: true, force: true });
|
|
8484
8536
|
}
|
|
8485
8537
|
for (const c of checks) log4(` ${c.skipped ? import_chalk70.default.yellow("\u2013") : c.ok ? import_chalk70.default.green("\u2713") : import_chalk70.default.red("\u2717")} ${c.id.padEnd(24)} ${import_chalk70.default.dim(c.detail)}`);
|
|
8486
8538
|
const failed = checks.filter((c) => !c.ok).length;
|
|
@@ -8493,13 +8545,13 @@ async function runOpAnon(o = {}) {
|
|
|
8493
8545
|
if (failed) process.exitCode = 1;
|
|
8494
8546
|
return { ok: !failed, checks };
|
|
8495
8547
|
}
|
|
8496
|
-
var
|
|
8548
|
+
var import_fs10, import_os2, import_path7, import_child_process5, import_chalk70, SECRET;
|
|
8497
8549
|
var init_op_anon = __esm({
|
|
8498
8550
|
"src/commands/op-anon.ts"() {
|
|
8499
8551
|
"use strict";
|
|
8500
|
-
|
|
8552
|
+
import_fs10 = __toESM(require("fs"));
|
|
8501
8553
|
import_os2 = __toESM(require("os"));
|
|
8502
|
-
|
|
8554
|
+
import_path7 = __toESM(require("path"));
|
|
8503
8555
|
import_child_process5 = require("child_process");
|
|
8504
8556
|
import_chalk70 = __toESM(require("chalk"));
|
|
8505
8557
|
init_brand();
|
|
@@ -8508,7 +8560,7 @@ var init_op_anon = __esm({
|
|
|
8508
8560
|
});
|
|
8509
8561
|
|
|
8510
8562
|
// src/index.ts
|
|
8511
|
-
var
|
|
8563
|
+
var import_fs11 = __toESM(require("fs"));
|
|
8512
8564
|
var import_commander = require("commander");
|
|
8513
8565
|
var import_chalk72 = __toESM(require("chalk"));
|
|
8514
8566
|
init_package();
|
|
@@ -8517,7 +8569,7 @@ init_types();
|
|
|
8517
8569
|
init_login();
|
|
8518
8570
|
|
|
8519
8571
|
// src/commands/dev.ts
|
|
8520
|
-
var
|
|
8572
|
+
var import_fs4 = __toESM(require("fs"));
|
|
8521
8573
|
init_package();
|
|
8522
8574
|
init_version_guard();
|
|
8523
8575
|
var import_chalk20 = __toESM(require("chalk"));
|
|
@@ -8839,7 +8891,7 @@ async function runDev(options) {
|
|
|
8839
8891
|
let recordSink;
|
|
8840
8892
|
let captureStream;
|
|
8841
8893
|
if (options.captureFile) {
|
|
8842
|
-
captureStream =
|
|
8894
|
+
captureStream = import_fs4.default.createWriteStream(options.captureFile, { flags: "a" });
|
|
8843
8895
|
recordSink = (r) => captureStream.write(JSON.stringify(r) + "\n");
|
|
8844
8896
|
}
|
|
8845
8897
|
let restore = [];
|
|
@@ -9026,12 +9078,199 @@ ${projects.length} project${projects.length === 1 ? "" : "s"}`));
|
|
|
9026
9078
|
init_create();
|
|
9027
9079
|
|
|
9028
9080
|
// src/commands/skill.ts
|
|
9029
|
-
var
|
|
9030
|
-
var
|
|
9031
|
-
var
|
|
9081
|
+
var import_fs7 = __toESM(require("fs"));
|
|
9082
|
+
var import_path6 = __toESM(require("path"));
|
|
9083
|
+
var import_chalk25 = __toESM(require("chalk"));
|
|
9032
9084
|
init_brand();
|
|
9085
|
+
|
|
9086
|
+
// src/lib/detect-backend.ts
|
|
9087
|
+
var import_fs5 = __toESM(require("fs"));
|
|
9088
|
+
var import_path4 = __toESM(require("path"));
|
|
9089
|
+
var import_net = __toESM(require("net"));
|
|
9090
|
+
var NODE_SERVERS = [
|
|
9091
|
+
["express", "Express", 3e3],
|
|
9092
|
+
["fastify", "Fastify", 3e3],
|
|
9093
|
+
["koa", "Koa", 3e3],
|
|
9094
|
+
["hono", "Hono", 3e3],
|
|
9095
|
+
["@nestjs/core", "NestJS", 3e3],
|
|
9096
|
+
["@hapi/hapi", "hapi", 3e3],
|
|
9097
|
+
["restify", "Restify", 8080],
|
|
9098
|
+
["elysia", "Elysia", 3e3],
|
|
9099
|
+
["next", "Next.js", 3e3],
|
|
9100
|
+
["nuxt", "Nuxt", 3e3],
|
|
9101
|
+
["@remix-run/node", "Remix", 3e3]
|
|
9102
|
+
];
|
|
9103
|
+
var NODE_FRONTENDS = [["vite", "Vite"], ["react-scripts", "Create React App"], ["@angular/core", "Angular"], ["svelte", "Svelte"]];
|
|
9104
|
+
var PY_SERVERS = [
|
|
9105
|
+
[/\bfastapi\b/i, "FastAPI", 8e3],
|
|
9106
|
+
[/\bdjango\b/i, "Django", 8e3],
|
|
9107
|
+
[/\bflask\b/i, "Flask", 5e3],
|
|
9108
|
+
[/\bstarlette\b/i, "Starlette", 8e3],
|
|
9109
|
+
[/\baiohttp\b/i, "aiohttp", 8080]
|
|
9110
|
+
];
|
|
9111
|
+
var GO_SERVERS = [[/gin-gonic\/gin/, "Gin"], [/labstack\/echo/, "Echo"], [/go-chi\/chi/, "chi"], [/gofiber\/fiber/, "Fiber"]];
|
|
9112
|
+
var RUBY_SERVERS = [[/^\s*gem ['"]rails['"]/m, "Rails", 3e3], [/^\s*gem ['"]sinatra['"]/m, "Sinatra", 4567]];
|
|
9113
|
+
var SPEC_NAMES = ["openapi.yaml", "openapi.yml", "openapi.json", "swagger.yaml", "swagger.yml", "swagger.json"];
|
|
9114
|
+
var SPEC_DIRS = [".", "api", "spec", "specs", "docs", "openapi", "src"];
|
|
9115
|
+
var COMMON_PORTS = [3e3, 3001, 4e3, 5e3, 8e3, 8080, 8787];
|
|
9116
|
+
var SPEC_PATHS = ["/openapi.json", "/openapi.yaml", "/docs/openapi.json", "/swagger.json", "/api/openapi.json"];
|
|
9117
|
+
function readText(file2, max = 256 * 1024) {
|
|
9118
|
+
try {
|
|
9119
|
+
const st = import_fs5.default.statSync(file2);
|
|
9120
|
+
if (!st.isFile() || st.size > max) return null;
|
|
9121
|
+
return import_fs5.default.readFileSync(file2, "utf8");
|
|
9122
|
+
} catch {
|
|
9123
|
+
return null;
|
|
9124
|
+
}
|
|
9125
|
+
}
|
|
9126
|
+
function findSpecFile(dir) {
|
|
9127
|
+
for (const d of SPEC_DIRS) {
|
|
9128
|
+
for (const n of SPEC_NAMES) {
|
|
9129
|
+
const rel = d === "." ? n : `${d}/${n}`;
|
|
9130
|
+
const text = readText(import_path4.default.join(dir, rel));
|
|
9131
|
+
if (text && /\b(openapi|swagger)\s*["']?\s*:/.test(text)) {
|
|
9132
|
+
const m = /\b(?:localhost|127\.0\.0\.1):(\d{2,5})\b/.exec(text);
|
|
9133
|
+
return { file: `./${rel}`, ...m ? { port: Number(m[1]) } : {} };
|
|
9134
|
+
}
|
|
9135
|
+
}
|
|
9136
|
+
}
|
|
9137
|
+
return void 0;
|
|
9138
|
+
}
|
|
9139
|
+
function portFromScripts(scripts) {
|
|
9140
|
+
for (const s of Object.values(scripts)) {
|
|
9141
|
+
const m = /(?:--port[= ]|-p |PORT=)(\d{2,5})\b/.exec(s);
|
|
9142
|
+
if (m) return Number(m[1]);
|
|
9143
|
+
}
|
|
9144
|
+
return void 0;
|
|
9145
|
+
}
|
|
9146
|
+
function fromNode(dir, out) {
|
|
9147
|
+
const text = readText(import_path4.default.join(dir, "package.json"));
|
|
9148
|
+
if (!text) return;
|
|
9149
|
+
let pkg = {};
|
|
9150
|
+
try {
|
|
9151
|
+
pkg = JSON.parse(text);
|
|
9152
|
+
} catch {
|
|
9153
|
+
return;
|
|
9154
|
+
}
|
|
9155
|
+
const deps = { ...pkg.devDependencies ?? {}, ...pkg.dependencies ?? {} };
|
|
9156
|
+
const scripts = pkg.scripts ?? {};
|
|
9157
|
+
const server = NODE_SERVERS.find(([dep]) => dep in deps);
|
|
9158
|
+
if (server) {
|
|
9159
|
+
out.framework = server[1];
|
|
9160
|
+
out.source = "package.json";
|
|
9161
|
+
out.defaultPort = server[2];
|
|
9162
|
+
} else if (/\bnode\s+\S+\.(c|m)?js\b/.test(Object.values(scripts).join(" "))) {
|
|
9163
|
+
out.framework = "a Node server";
|
|
9164
|
+
out.source = "package.json";
|
|
9165
|
+
out.defaultPort = 3e3;
|
|
9166
|
+
} else {
|
|
9167
|
+
const fe = NODE_FRONTENDS.find(([dep]) => dep in deps);
|
|
9168
|
+
if (fe) out.frontendOnly = fe[1];
|
|
9169
|
+
return;
|
|
9170
|
+
}
|
|
9171
|
+
out.configuredPort = portFromScripts(scripts);
|
|
9172
|
+
out.start = scripts.dev ? "npm run dev" : scripts.start ? "npm start" : void 0;
|
|
9173
|
+
}
|
|
9174
|
+
function fromPython(dir, out) {
|
|
9175
|
+
for (const f of ["requirements.txt", "pyproject.toml", "Pipfile"]) {
|
|
9176
|
+
const text = readText(import_path4.default.join(dir, f));
|
|
9177
|
+
if (!text) continue;
|
|
9178
|
+
const hit = PY_SERVERS.find(([re]) => re.test(text));
|
|
9179
|
+
if (hit) {
|
|
9180
|
+
out.framework = hit[1];
|
|
9181
|
+
out.source = f;
|
|
9182
|
+
out.defaultPort = hit[2];
|
|
9183
|
+
return;
|
|
9184
|
+
}
|
|
9185
|
+
}
|
|
9186
|
+
}
|
|
9187
|
+
function fromOthers(dir, out) {
|
|
9188
|
+
const gomod = readText(import_path4.default.join(dir, "go.mod"));
|
|
9189
|
+
if (gomod) {
|
|
9190
|
+
const hit = GO_SERVERS.find(([re]) => re.test(gomod));
|
|
9191
|
+
out.framework = hit ? hit[1] : "a Go server";
|
|
9192
|
+
out.source = "go.mod";
|
|
9193
|
+
out.defaultPort = 8080;
|
|
9194
|
+
return;
|
|
9195
|
+
}
|
|
9196
|
+
const gemfile = readText(import_path4.default.join(dir, "Gemfile"));
|
|
9197
|
+
if (gemfile) {
|
|
9198
|
+
const hit = RUBY_SERVERS.find(([re]) => re.test(gemfile));
|
|
9199
|
+
if (hit) {
|
|
9200
|
+
out.framework = hit[1];
|
|
9201
|
+
out.source = "Gemfile";
|
|
9202
|
+
out.defaultPort = hit[2];
|
|
9203
|
+
return;
|
|
9204
|
+
}
|
|
9205
|
+
}
|
|
9206
|
+
for (const f of ["pom.xml", "build.gradle", "build.gradle.kts"]) {
|
|
9207
|
+
const text = readText(import_path4.default.join(dir, f));
|
|
9208
|
+
if (text && /spring-boot/.test(text)) {
|
|
9209
|
+
out.framework = "Spring Boot";
|
|
9210
|
+
out.source = f;
|
|
9211
|
+
out.defaultPort = 8080;
|
|
9212
|
+
return;
|
|
9213
|
+
}
|
|
9214
|
+
}
|
|
9215
|
+
}
|
|
9216
|
+
function portAnswers(port, timeoutMs = 300) {
|
|
9217
|
+
return new Promise((resolve4) => {
|
|
9218
|
+
const sock = import_net.default.connect({ port, host: "127.0.0.1" });
|
|
9219
|
+
const done = (v) => {
|
|
9220
|
+
sock.destroy();
|
|
9221
|
+
resolve4(v);
|
|
9222
|
+
};
|
|
9223
|
+
sock.setTimeout(timeoutMs, () => done(false));
|
|
9224
|
+
sock.once("connect", () => done(true));
|
|
9225
|
+
sock.once("error", () => done(false));
|
|
9226
|
+
});
|
|
9227
|
+
}
|
|
9228
|
+
async function liveSpec(port) {
|
|
9229
|
+
for (const p of SPEC_PATHS) {
|
|
9230
|
+
try {
|
|
9231
|
+
const r = await fetch(`http://localhost:${port}${p}`, { signal: AbortSignal.timeout(800) });
|
|
9232
|
+
if (!r.ok) continue;
|
|
9233
|
+
const text = (await r.text()).slice(0, 4096);
|
|
9234
|
+
if (/\b(openapi|swagger)\s*["']?\s*:/.test(text)) return `http://localhost:${port}${p}`;
|
|
9235
|
+
} catch {
|
|
9236
|
+
}
|
|
9237
|
+
}
|
|
9238
|
+
return void 0;
|
|
9239
|
+
}
|
|
9240
|
+
async function detectBackend(dir, opts = {}) {
|
|
9241
|
+
const out = {};
|
|
9242
|
+
try {
|
|
9243
|
+
if (import_fs5.default.existsSync(import_path4.default.join(dir, ".demo", "state.json"))) out.isDemo = true;
|
|
9244
|
+
fromNode(dir, out);
|
|
9245
|
+
if (!out.framework) fromPython(dir, out);
|
|
9246
|
+
if (!out.framework) fromOthers(dir, out);
|
|
9247
|
+
const spec2 = findSpecFile(dir);
|
|
9248
|
+
if (spec2) {
|
|
9249
|
+
out.specFile = spec2.file;
|
|
9250
|
+
if (spec2.port && !out.configuredPort) out.configuredPort = spec2.port;
|
|
9251
|
+
}
|
|
9252
|
+
} catch {
|
|
9253
|
+
}
|
|
9254
|
+
const preferred = [...new Set([out.configuredPort, out.defaultPort].filter((p) => !!p))];
|
|
9255
|
+
const ports = [.../* @__PURE__ */ new Set([...preferred, ...opts.ports ?? COMMON_PORTS])];
|
|
9256
|
+
const answering = await Promise.all(ports.map(async (p) => await portAnswers(p) ? p : 0));
|
|
9257
|
+
const up = answering.filter(Boolean);
|
|
9258
|
+
const mine = preferred.find((p) => up.includes(p));
|
|
9259
|
+
if (mine && (out.framework || out.specFile)) out.listening = mine;
|
|
9260
|
+
else if (!out.framework && !out.specFile && up.length) out.strayPorts = up;
|
|
9261
|
+
if (out.listening) out.specUrl = await liveSpec(out.listening);
|
|
9262
|
+
return out;
|
|
9263
|
+
}
|
|
9264
|
+
function hasBackend(f) {
|
|
9265
|
+
return !f.isDemo && !!(f.framework || f.specFile);
|
|
9266
|
+
}
|
|
9267
|
+
function backendPort(f) {
|
|
9268
|
+
return f.listening ?? f.configuredPort ?? f.defaultPort;
|
|
9269
|
+
}
|
|
9270
|
+
|
|
9271
|
+
// src/commands/skill.ts
|
|
9033
9272
|
var SKILL_NAME = "ship-safely";
|
|
9034
|
-
var SKILL_RELATIVE_PATH =
|
|
9273
|
+
var SKILL_RELATIVE_PATH = import_path6.default.join(".claude", "skills", SKILL_NAME, "SKILL.md");
|
|
9035
9274
|
var CP_TOOLS = [
|
|
9036
9275
|
"describe_server",
|
|
9037
9276
|
"propose_rules",
|
|
@@ -9046,9 +9285,82 @@ var CP_TOOLS = [
|
|
|
9046
9285
|
function withLatest(md) {
|
|
9047
9286
|
return md.replace(/npx (apiblaze|mcpblaze)(?![@\w-])/g, "npx $1@latest");
|
|
9048
9287
|
}
|
|
9049
|
-
function
|
|
9288
|
+
function pitchText() {
|
|
9289
|
+
const kind = BRAND === "mcpblaze" ? "MCP and API" : "API and MCP";
|
|
9290
|
+
return `${B.product} is a serverless ${kind} gateway. Put it in front of your backend and it's ready for traffic from people and AI agents. Callers sign in with API keys or OAuth, each can only do what their credentials allow, bots get throttled, and every customer gets their own space. Seconds, not weeks, even for an app on your laptop.`;
|
|
9291
|
+
}
|
|
9292
|
+
function appName(f) {
|
|
9293
|
+
const fw = f.framework ?? "an API";
|
|
9294
|
+
if (/^an? /.test(fw)) return fw[0].toUpperCase() + fw.slice(1);
|
|
9295
|
+
return `${/^[AEIOU]/i.test(fw) ? "An" : "A"} ${fw} app`;
|
|
9296
|
+
}
|
|
9297
|
+
function folderReport(f, opts = {}) {
|
|
9298
|
+
const P = B.product;
|
|
9299
|
+
const demoQuestion = "Want me to show you on a sample app? About 30 seconds, nothing to install.";
|
|
9300
|
+
if (f.isDemo) {
|
|
9301
|
+
return { lines: [`This is the ${P} demo folder (ResiResi). It is already behind ${P}; \`npx ${B.cli}@latest demo start\` reopens its tunnel.`], question: demoQuestion, ownApi: false };
|
|
9302
|
+
}
|
|
9303
|
+
if (!hasBackend(f)) {
|
|
9304
|
+
const extra = [
|
|
9305
|
+
f.frontendOnly ? ` It is a ${f.frontendOnly} frontend with no server of its own.` : "",
|
|
9306
|
+
// A person may know what that is; an agent would only be distracted by it.
|
|
9307
|
+
opts.forPerson && f.strayPorts?.length ? ` Something answers on localhost:${f.strayPorts[0]}, but nothing in this folder points at it.` : ""
|
|
9308
|
+
].join("");
|
|
9309
|
+
return { lines: [`No backend here: no server framework in the project files and no OpenAPI spec.${extra}`], question: demoQuestion, ownApi: false };
|
|
9310
|
+
}
|
|
9311
|
+
const port = backendPort(f);
|
|
9312
|
+
const spec2 = f.specFile ? `Spec: ${f.specFile}.` : f.specUrl ? `It serves its spec at ${f.specUrl}.` : "No OpenAPI spec yet.";
|
|
9313
|
+
const from = f.source ? ` (${f.source})` : "";
|
|
9314
|
+
if (f.listening) {
|
|
9315
|
+
return {
|
|
9316
|
+
lines: [`${appName(f)}${from}, answering on localhost:${f.listening} right now. ${spec2}`],
|
|
9317
|
+
question: `Want me to put your API on port ${f.listening} behind ${P}? About 30 seconds, no sign-up.`,
|
|
9318
|
+
ownApi: true
|
|
9319
|
+
};
|
|
9320
|
+
}
|
|
9321
|
+
const starts = f.start ? ` It starts with \`${f.start}\`` : " Its start command is not in its files";
|
|
9322
|
+
const listens = port ? ` and listens on ${port}.` : ".";
|
|
9323
|
+
return {
|
|
9324
|
+
lines: [`${appName(f)}${from}, not running.${starts}${listens} ${spec2}`],
|
|
9325
|
+
question: `Want me to start your app and put it behind ${P}? About a minute, no sign-up.`,
|
|
9326
|
+
ownApi: true
|
|
9327
|
+
};
|
|
9328
|
+
}
|
|
9329
|
+
function heroMarkdown(found) {
|
|
9050
9330
|
const P = B.product;
|
|
9051
9331
|
const run2 = `npx ${B.cli}@latest`;
|
|
9332
|
+
const report = found ? folderReport(found) : null;
|
|
9333
|
+
const port = found ? backendPort(found) : void 0;
|
|
9334
|
+
const folder = report ? `## This folder, checked by the CLI just now: read no files to decide what to offer
|
|
9335
|
+
|
|
9336
|
+
${report.lines.map((l) => `- ${l}`).join("\n")}
|
|
9337
|
+
` : `## First, the folder
|
|
9338
|
+
|
|
9339
|
+
Run \`${run2} skills\` in the project folder and follow what it prints: it checks the folder
|
|
9340
|
+
and gives the exact question to ask.
|
|
9341
|
+
`;
|
|
9342
|
+
const question = report?.question ?? "<the question it prints>";
|
|
9343
|
+
const ownApi = report?.ownApi && found ? (() => {
|
|
9344
|
+
const specArg = found.specFile ? ` --openapi ${found.specFile}` : "";
|
|
9345
|
+
const steps = [`Say one line: "Putting your API behind ${P}, about 30 seconds."`];
|
|
9346
|
+
if (!found.listening) {
|
|
9347
|
+
steps.push(`Start it first as its own detached process${found.start ? `: \`nohup ${found.start} > app.log 2>&1 < /dev/null &\`` : ", with its usual start command"}. Wait until localhost:${port ?? "<port>"} answers.`);
|
|
9348
|
+
}
|
|
9349
|
+
if (!found.specFile && !found.specUrl) {
|
|
9350
|
+
steps.push('Write `./openapi.yaml` from its route files first: every path and method, and the id each POST returns. Say "Writing an OpenAPI spec from your code."');
|
|
9351
|
+
}
|
|
9352
|
+
const spec2 = found.specFile ? specArg : !found.specUrl ? " --openapi ./openapi.yaml" : "";
|
|
9353
|
+
steps.push(`Open the tunnel as its own detached process, never \`cmd &\` inside a shell call that ends:
|
|
9354
|
+
\`nohup ${run2} dev --port ${port ?? "<port>"}${spec2} --auto > ${B.cli}-dev.log 2>&1 < /dev/null &\`
|
|
9355
|
+
Then read ${B.cli}-dev.log until it says "is live on the internet" (about 20 seconds). It saves the key to .env.local itself.`);
|
|
9356
|
+
steps.push(`Tell them in plain words, with the two addresses it printed: their API is on the internet at the API address, and AI assistants can use it at the MCP address. API keys, sign-in, rules for who can change what, and rate limits are already on. Say "saved your key to .env.local". Leave the tunnel running.`);
|
|
9357
|
+
steps.push(`Ask one question: **"Want it ready for real customers: their own API keys, sign-in, and only-the-owner-can-change rules?"** If yes, run \`${run2} skills ship\` and follow it from "Adopt".`);
|
|
9358
|
+
return `
|
|
9359
|
+
## Their API: they say yes to putting it behind ${P}
|
|
9360
|
+
|
|
9361
|
+
${steps.map((t, i) => `${i + 1}. ${t}`).join("\n")}
|
|
9362
|
+
`;
|
|
9363
|
+
})() : "";
|
|
9052
9364
|
return `---
|
|
9053
9365
|
name: ${SKILL_NAME}
|
|
9054
9366
|
description: Use when the user asks what ${P} (or ${B.cli}) does or can do for them; says yes to a demo or asks to see / try / demo it; wants to ship, share or expose an API; wants API keys, sign-in, per-user data, rate limits or quotas; wants AI agents to use their app safely; or says users can reach each other's records.
|
|
@@ -9056,17 +9368,16 @@ description: Use when the user asks what ${P} (or ${B.cli}) does or can do for t
|
|
|
9056
9368
|
|
|
9057
9369
|
# ${P}
|
|
9058
9370
|
|
|
9059
|
-
|
|
9060
|
-
|
|
9061
|
-
Run nothing. Say exactly this and nothing else: no list of use cases, no commands, no options. Then stop:
|
|
9371
|
+
${folder}
|
|
9372
|
+
## Explain: they ask what ${P} does or can do for them
|
|
9062
9373
|
|
|
9063
|
-
|
|
9064
|
-
plus an address AI assistants like me can use to work with it. API keys, sign-in and
|
|
9065
|
-
who-can-change-what come built in, with no code. Want me to show you in about 30 seconds?"
|
|
9374
|
+
Run nothing else. Say exactly this and nothing else: no list of use cases, no commands, no options. Then stop:
|
|
9066
9375
|
|
|
9067
|
-
|
|
9376
|
+
"${pitchText()} ${question}"
|
|
9068
9377
|
|
|
9069
|
-
|
|
9378
|
+
Do not use the words proxy, tenant, OpenAPI or header unless the user does first.
|
|
9379
|
+
${ownApi}
|
|
9380
|
+
## Demo: they say yes to the sample app, or ask to see or try it
|
|
9070
9381
|
|
|
9071
9382
|
1. Say one line: "Starting the demo, about 30 seconds."
|
|
9072
9383
|
2. Run exactly this, in the foreground. It needs no login and asks nothing. It sets
|
|
@@ -9083,10 +9394,17 @@ Rules for the demo: keep each update to one line. Do not narrate steps, read the
|
|
|
9083
9394
|
files, open logs, run other commands, or write code. Never show a key: the demo saves it
|
|
9084
9395
|
in \`.env.local\` and never prints it.
|
|
9085
9396
|
|
|
9086
|
-
## Their own app: they say yes
|
|
9397
|
+
## Their own app: they say yes after the demo
|
|
9087
9398
|
|
|
9088
9399
|
Their app runs on this machine \u2192 it will use \`${run2} dev\`; it is deployed \u2192 \`${run2} create\`.
|
|
9089
|
-
Run \`${run2} skills
|
|
9400
|
+
Run \`${run2} skills ship\` and follow what it prints.
|
|
9401
|
+
|
|
9402
|
+
## One thing at a time, later
|
|
9403
|
+
|
|
9404
|
+
When they ask about one thing, read only its page: \`${run2} skills keys\` (API keys) \xB7
|
|
9405
|
+
\`signin\` (OAuth sign-in and the developer portal) \xB7 \`rules\` (who can change what) \xB7
|
|
9406
|
+
\`limits\` (rate limits and quotas) \xB7 \`mcp\` (AI agents) \xB7 \`widgets\` (keys, users and chat on
|
|
9407
|
+
their site) \xB7 \`ship\` (the whole procedure for their own app).
|
|
9090
9408
|
`;
|
|
9091
9409
|
}
|
|
9092
9410
|
function shipMarkdown() {
|
|
@@ -9299,8 +9617,11 @@ ONLY for what the scan could not find \u2014 one short list of questions, not an
|
|
|
9299
9617
|
read the log. If it stops, re-run the SAME command: the same \`--name\` reattaches and
|
|
9300
9618
|
creates nothing. Never pick a new name to get it back \u2014 that is a second proxy. **Deployed code** \u2192
|
|
9301
9619
|
\`npx ${cli} create --target <url-or-spec> --name <name> --auto <door flags>\`. Both print the
|
|
9302
|
-
URL, the MCP URL (the "agents:" line)
|
|
9303
|
-
|
|
9620
|
+
URL, the MCP URL (the "agents:" line) and what is locked. Run by you (no terminal), \`dev\`
|
|
9621
|
+
saves the bootstrap key to \`.env.local\` as \`APIBLAZE_SERVER_KEY\` and never prints it;
|
|
9622
|
+
\`create\` prints it ONCE \u2014 save it there yourself. Either way say only "saved your key to
|
|
9623
|
+
.env.local": that key goes in the frontend-server's env, never a browser, a repo, an agent
|
|
9624
|
+
config or the chat. The tenant is
|
|
9304
9625
|
not labelled: read it from the \`admins add \u2026 --tenant <tenant>\` hint (it is also the middle
|
|
9305
9626
|
of the MCP hostname). Save \`APIBLAZE_URL\`, \`APIBLAZE_SERVER_KEY\`, \`APIBLAZE_TENANT\` in
|
|
9306
9627
|
the frontend-server's env (\`.env.local\` for Next.js). Then prove it with three curls and
|
|
@@ -9335,6 +9656,8 @@ ONLY for what the scan could not find \u2014 one short list of questions, not an
|
|
|
9335
9656
|
- the command to chat with their API: \`npx ${cli} apichat <name>\` (add
|
|
9336
9657
|
\`--xenduserid ana\` when \xA75b is on).
|
|
9337
9658
|
Then ask once: "Would you like me to add the self-serve API key widget to your site?"
|
|
9659
|
+
After that answer, offer once: "Want me to save this as a skill in your project, so I know
|
|
9660
|
+
${P} next time?" Yes \u2192 \`npx ${cli} skills --install\`.
|
|
9338
9661
|
|
|
9339
9662
|
## Report like this
|
|
9340
9663
|
|
|
@@ -9410,36 +9733,184 @@ through one MCP: \`claude mcp add --transport http ${BRAND} ${B.controlPlaneMcp}
|
|
|
9410
9733
|
${CP_TOOLS.join(", ")} \u2014 the same steps, one tool each.
|
|
9411
9734
|
`;
|
|
9412
9735
|
}
|
|
9736
|
+
function sectionOf(md, heading) {
|
|
9737
|
+
const at = md.indexOf(`
|
|
9738
|
+
${heading}`);
|
|
9739
|
+
if (at < 0) return "";
|
|
9740
|
+
const next = md.indexOf("\n## ", at + heading.length + 1);
|
|
9741
|
+
return md.slice(at + 1, next < 0 ? void 0 : next).trim();
|
|
9742
|
+
}
|
|
9743
|
+
var TOPICS = ["ship", "keys", "signin", "rules", "limits", "mcp", "widgets"];
|
|
9744
|
+
function topicMarkdown(topic) {
|
|
9745
|
+
const P = B.product;
|
|
9746
|
+
const cli = B.cli;
|
|
9747
|
+
const legacy = legacySkillMarkdown();
|
|
9748
|
+
const page = (title, body) => withLatest(`# ${title}
|
|
9749
|
+
|
|
9750
|
+
${body.trim()}
|
|
9751
|
+
`);
|
|
9752
|
+
switch (topic) {
|
|
9753
|
+
case "ship":
|
|
9754
|
+
return shipMarkdown();
|
|
9755
|
+
case "keys":
|
|
9756
|
+
return page(`API keys with ${P}`, `
|
|
9757
|
+
Every ${B.thingShort} has a key door. Three kinds of key for a tenant (one of their customers), one flag each:
|
|
9758
|
+
- \`npx ${cli} apikeys mint --tenant <tenant> --backend\`: a server key. Their server sends it as
|
|
9759
|
+
\`X-API-Key\` and names the person in \`X-End-User-Id\`.
|
|
9760
|
+
- \`--client\`: a key that cannot name a user (a public client, a script).
|
|
9761
|
+
- \`--for <email>\`: bound to one person, like an agent or a CI job. Calls count as that person, and
|
|
9762
|
+
ownership rules trust it. It is a DIFFERENT identity from a bare \`X-End-User-Id\` string: rows
|
|
9763
|
+
created as "ana" through the header are not owned by ana@example.com's key.
|
|
9764
|
+
|
|
9765
|
+
Their customers get a key per tenant from the self-serve keys widget (\`npx ${cli} skills widgets\`):
|
|
9766
|
+
each customer mints their own, in their own tenant. The bootstrap key is only for their own server.
|
|
9767
|
+
Never show a key in the chat: save it to \`.env.local\` and say "saved your key to .env.local".`);
|
|
9768
|
+
case "signin":
|
|
9769
|
+
return page(`Sign-in (OAuth) with ${P}`, `
|
|
9770
|
+
${sectionOf(legacy, "## Sign-in shapes")}
|
|
9771
|
+
|
|
9772
|
+
Each tenant has its own developer portal, where people sign in and get a key:
|
|
9773
|
+
\`https://<tenant>.portal.apiblaze.com/<version>\`. A tenant added later gets the same sign-in:
|
|
9774
|
+
\`npx ${cli} tenant attach <name> --tenant <tenant>\`.
|
|
9775
|
+
|
|
9776
|
+
Tenant admins, by email, even before that person ever signs in:
|
|
9777
|
+
\`npx ${cli} admins add <email> --tenant <tenant>\` \xB7 \`admins list --tenant <tenant>\` \xB7 \`admins remove <email> --tenant <tenant>\`.`);
|
|
9778
|
+
case "rules":
|
|
9779
|
+
return page("Who can change what", `
|
|
9780
|
+
- With a spec, \`dev\` and \`create\` lock every resource to the person who created it.
|
|
9781
|
+
- \`npx ${cli} rule <name>\` shows the rules. \`npx ${cli} rule <name> "<sentence>"\` adds one in plain
|
|
9782
|
+
words, for example "Only the person who made a reservation can change or cancel it". Both need
|
|
9783
|
+
a login (\`npx ${cli} login\`); an anonymous workspace keeps the creator locks \`dev\` applied.
|
|
9784
|
+
- \`npx ${cli} identified <name> require\`: every call must name a person (\`X-End-User-Id\` or a
|
|
9785
|
+
sign-in token). \`allow-anon\` undoes it.
|
|
9786
|
+
- \`npx ${cli} config <name> authorization.enforce_authorization false\` turns enforcement off
|
|
9787
|
+
without losing the rules.
|
|
9788
|
+
- The gateway locks single records. Lists are the backend's job: filter every collection GET by
|
|
9789
|
+
\`x-abz-tenant-id\` AND \`x-abz-user-id\`, since a user id is not unique across tenants.`);
|
|
9790
|
+
case "limits":
|
|
9791
|
+
return page("Rate limits and quotas", `
|
|
9792
|
+
Every new ${B.thingShort} has a rate limit of 10 requests/s per consumer (100 per 10 s) and a quota
|
|
9793
|
+
of 3,000 a day. Say so in those words (rate limit, quota).
|
|
9794
|
+
- \`npx ${cli} limits get <name>\` shows them.
|
|
9795
|
+
- \`npx ${cli} limits set <name> --rate <n> --quota <n> --period daily\`, plus \`--spend-cap <usd>\` for
|
|
9796
|
+
a monthly spend cap.
|
|
9797
|
+
- Rates are per consumer key and approximate per location. A rate above the self-serve ceiling is
|
|
9798
|
+
refused with the exact \`limits request-max\` command to run: relay it, do not retry.
|
|
9799
|
+
- It works logged out; an anonymous workspace is capped until claimed.`);
|
|
9800
|
+
case "mcp":
|
|
9801
|
+
return page(`AI agents (MCP) with ${P}`, `
|
|
9802
|
+
- Every ${B.thingShort} has an MCP address for AI agents, one per tenant: use the "MCP" line that
|
|
9803
|
+
\`dev\` or \`create\` printed. Its tools are the spec's operations.
|
|
9804
|
+
- Agents sign in with the tenant's sign-in (OAuth), or use a person-bound key:
|
|
9805
|
+
\`npx ${cli} apikeys mint --tenant <tenant> --for <email>\`. Never hand an agent a server key: it
|
|
9806
|
+
names no person, so protected routes refuse it.
|
|
9807
|
+
- Chat with the API from the terminal: \`npx ${cli} apichat <name>\` (add \`--xenduserid ana\` when calls
|
|
9808
|
+
must name a person). Connect an agent: \`npx ${cli} apichat <name> --install-mcp claude\`.
|
|
9809
|
+
|
|
9810
|
+
${sectionOf(legacy, "## No shell? The control-plane MCP")}`);
|
|
9811
|
+
case "widgets":
|
|
9812
|
+
return page(`Keys, users and chat on their site`, sectionOf(legacy, "## Wiring a real site").replace(/^## Wiring a real site\n+/, ""));
|
|
9813
|
+
default:
|
|
9814
|
+
return null;
|
|
9815
|
+
}
|
|
9816
|
+
}
|
|
9817
|
+
function readByAgent(opts) {
|
|
9818
|
+
if (opts.agent) return true;
|
|
9819
|
+
if (process.env.CLAUDECODE || process.env.APIBLAZE_AGENT) return true;
|
|
9820
|
+
return !(process.stdin.isTTY && process.stdout.isTTY);
|
|
9821
|
+
}
|
|
9822
|
+
async function ask2(question) {
|
|
9823
|
+
const readline3 = await import("readline");
|
|
9824
|
+
const rl = readline3.createInterface({ input: process.stdin, output: process.stdout });
|
|
9825
|
+
const answer = await new Promise((res) => rl.question(question, res));
|
|
9826
|
+
rl.close();
|
|
9827
|
+
return !/^n/i.test(answer.trim());
|
|
9828
|
+
}
|
|
9829
|
+
async function runForPerson(found) {
|
|
9830
|
+
const P = B.product;
|
|
9831
|
+
const run2 = `npx ${B.cli}@latest`;
|
|
9832
|
+
console.log();
|
|
9833
|
+
console.log(` ${import_chalk25.default.bold(pitchText().split(". ")[0] + ".")}`);
|
|
9834
|
+
console.log(` ${pitchText().split(". ").slice(1).join(". ")}`);
|
|
9835
|
+
console.log();
|
|
9836
|
+
console.log(import_chalk25.default.dim(` With an AI assistant? Tell it: "Tell me what ${B.cli} can do for me using ${run2} skills"`));
|
|
9837
|
+
console.log();
|
|
9838
|
+
const report = folderReport(found, { forPerson: true });
|
|
9839
|
+
for (const l of report.lines) console.log(` ${l.replace(/`/g, "")}`);
|
|
9840
|
+
const port = backendPort(found);
|
|
9841
|
+
if (report.ownApi && found.listening) {
|
|
9842
|
+
const args = ["dev", "--port", String(found.listening), ...found.specFile ? ["--openapi", found.specFile] : []];
|
|
9843
|
+
if (!await ask2(` ${import_chalk25.default.bold("?")} Put your API on port ${found.listening} behind ${P} now? ${import_chalk25.default.dim("(Y/n)")} `)) {
|
|
9844
|
+
console.log(` Any time: ${import_chalk25.default.cyan(`${run2} ${args.join(" ")}`)}`);
|
|
9845
|
+
return;
|
|
9846
|
+
}
|
|
9847
|
+
console.log(import_chalk25.default.dim(` $ ${run2} ${args.join(" ")}`));
|
|
9848
|
+
const { spawnSync: spawnSync4 } = await import("child_process");
|
|
9849
|
+
const r = spawnSync4(process.execPath, [process.argv[1], ...args], { stdio: "inherit" });
|
|
9850
|
+
process.exitCode = r.status ?? 0;
|
|
9851
|
+
return;
|
|
9852
|
+
}
|
|
9853
|
+
if (report.ownApi) {
|
|
9854
|
+
console.log(` Start it${found.start ? ` (${found.start})` : ""}, then put it on the internet:`);
|
|
9855
|
+
console.log(` ${import_chalk25.default.cyan(`${run2} dev --port ${port ?? "<port>"}${found.specFile ? ` --openapi ${found.specFile}` : ""}`)}`);
|
|
9856
|
+
return;
|
|
9857
|
+
}
|
|
9858
|
+
if (!await ask2(` ${import_chalk25.default.bold("?")} See it on a sample app first? ${import_chalk25.default.dim("(Y/n)")} `)) {
|
|
9859
|
+
console.log(` Your own app, when it runs: ${import_chalk25.default.cyan(`${run2} dev --port <port>`)}`);
|
|
9860
|
+
return;
|
|
9861
|
+
}
|
|
9862
|
+
const { runDemo: runDemo2 } = await Promise.resolve().then(() => (init_demo(), demo_exports));
|
|
9863
|
+
await runDemo2({});
|
|
9864
|
+
}
|
|
9413
9865
|
async function runSkill(opts = {}) {
|
|
9414
|
-
if (
|
|
9415
|
-
|
|
9866
|
+
if (opts.install) return installSkill(opts);
|
|
9867
|
+
if (opts.static) {
|
|
9868
|
+
process.stdout.write(skillMarkdown());
|
|
9416
9869
|
return;
|
|
9417
9870
|
}
|
|
9871
|
+
const topic = opts.ship ? "ship" : opts.topic;
|
|
9872
|
+
if (topic) {
|
|
9873
|
+
const md = topicMarkdown(topic);
|
|
9874
|
+
if (!md) {
|
|
9875
|
+
console.error(`Unknown page "${topic}". Pages: ${TOPICS.join(" \xB7 ")}`);
|
|
9876
|
+
process.exit(1);
|
|
9877
|
+
}
|
|
9878
|
+
process.stdout.write(md);
|
|
9879
|
+
return;
|
|
9880
|
+
}
|
|
9881
|
+
const found = await detectBackend(process.cwd());
|
|
9882
|
+
if (readByAgent(opts)) {
|
|
9883
|
+
process.stdout.write(heroMarkdown(found));
|
|
9884
|
+
return;
|
|
9885
|
+
}
|
|
9886
|
+
await runForPerson(found);
|
|
9887
|
+
}
|
|
9888
|
+
function installSkill(opts) {
|
|
9418
9889
|
const md = skillMarkdown();
|
|
9419
|
-
const target2 =
|
|
9420
|
-
if (
|
|
9421
|
-
console.error(
|
|
9890
|
+
const target2 = import_path6.default.resolve(process.cwd(), SKILL_RELATIVE_PATH);
|
|
9891
|
+
if (import_fs7.default.existsSync(target2) && !opts.force) {
|
|
9892
|
+
console.error(import_chalk25.default.yellow(`${SKILL_RELATIVE_PATH} already exists \u2014 re-run with --force to overwrite it.`));
|
|
9422
9893
|
process.exit(1);
|
|
9423
9894
|
}
|
|
9424
|
-
|
|
9425
|
-
|
|
9426
|
-
console.log(`${
|
|
9427
|
-
console.log(
|
|
9895
|
+
import_fs7.default.mkdirSync(import_path6.default.dirname(target2), { recursive: true });
|
|
9896
|
+
import_fs7.default.writeFileSync(target2, md, "utf8");
|
|
9897
|
+
console.log(`${import_chalk25.default.green("\u2713")} Installed the ${import_chalk25.default.bold(SKILL_NAME)} skill: ${import_chalk25.default.bold(target2)}`);
|
|
9898
|
+
console.log(import_chalk25.default.dim(` Claude Code picks it up in this project. Say "ship this API" and it runs npx ${B.cli}@latest dev / create for you.`));
|
|
9428
9899
|
}
|
|
9429
9900
|
|
|
9430
9901
|
// src/commands/integration.ts
|
|
9431
|
-
var
|
|
9432
|
-
var
|
|
9902
|
+
var import_chalk26 = __toESM(require("chalk"));
|
|
9903
|
+
var import_ora10 = __toESM(require("ora"));
|
|
9433
9904
|
init_caller();
|
|
9434
9905
|
init_api();
|
|
9435
9906
|
init_brand();
|
|
9436
9907
|
function file(f, indent2 = " ") {
|
|
9437
|
-
console.log(` ${
|
|
9908
|
+
console.log(` ${import_chalk26.default.cyan(f.file)}`);
|
|
9438
9909
|
for (const line of f.code.split("\n")) console.log(`${indent2}${line}`);
|
|
9439
9910
|
console.log();
|
|
9440
9911
|
}
|
|
9441
9912
|
function bullets(lines, indent2 = " ") {
|
|
9442
|
-
for (const l of lines) console.log(`${indent2}${
|
|
9913
|
+
for (const l of lines) console.log(`${indent2}${import_chalk26.default.dim("\u2022")} ${l}`);
|
|
9443
9914
|
}
|
|
9444
9915
|
async function runIntegration(name, opts = {}) {
|
|
9445
9916
|
const stackRaw = (opts.stack ?? "nextjs").trim().toLowerCase();
|
|
@@ -9450,7 +9921,7 @@ async function runIntegration(name, opts = {}) {
|
|
|
9450
9921
|
const project = name.trim().toLowerCase();
|
|
9451
9922
|
const caller = requireCaller();
|
|
9452
9923
|
if (opts.team && caller.mode === "anon") throw new Error("--team is not available in an anonymous workspace.");
|
|
9453
|
-
const spinner = opts.json ? null : (0,
|
|
9924
|
+
const spinner = opts.json ? null : (0, import_ora10.default)(`Rendering the ${stack} kit for ${project}\u2026`).start();
|
|
9454
9925
|
let kit;
|
|
9455
9926
|
try {
|
|
9456
9927
|
kit = await producer(caller, integrationKitCall(project, stack));
|
|
@@ -9464,44 +9935,44 @@ async function runIntegration(name, opts = {}) {
|
|
|
9464
9935
|
return;
|
|
9465
9936
|
}
|
|
9466
9937
|
console.log();
|
|
9467
|
-
console.log(
|
|
9468
|
-
console.log(
|
|
9938
|
+
console.log(import_chalk26.default.bold(`Integration kit \u2014 ${kit.project} (${kit.stack})`));
|
|
9939
|
+
console.log(import_chalk26.default.dim(` Edit only the frontend and the frontend-server. Your backend changes in one way: it trusts the ${B.product} identity headers.`));
|
|
9469
9940
|
console.log();
|
|
9470
|
-
console.log(` ${
|
|
9471
|
-
console.log(` ${
|
|
9472
|
-
console.log(` ${
|
|
9473
|
-
console.log(` ${
|
|
9941
|
+
console.log(` ${import_chalk26.default.dim("Proxy: ")} ${import_chalk26.default.bold(kit.urls.proxy)}`);
|
|
9942
|
+
console.log(` ${import_chalk26.default.dim("MCP: ")} ${kit.urls.mcp.join(import_chalk26.default.dim(" \xB7 "))}`);
|
|
9943
|
+
console.log(` ${import_chalk26.default.dim("Portal: ")} ${kit.urls.portal}`);
|
|
9944
|
+
console.log(` ${import_chalk26.default.dim("Spec: ")} ${kit.urls.openapi}`);
|
|
9474
9945
|
const w = kit["1_frontend_widgets"];
|
|
9475
9946
|
console.log();
|
|
9476
|
-
console.log(
|
|
9477
|
-
console.log(
|
|
9947
|
+
console.log(import_chalk26.default.bold("1. Frontend \u2014 the widgets (keys + chat)"));
|
|
9948
|
+
console.log(import_chalk26.default.dim(` ${w.note}`));
|
|
9478
9949
|
console.log();
|
|
9479
9950
|
file(w.frontend);
|
|
9480
9951
|
for (const r of w.server_routes) file(r);
|
|
9481
9952
|
const c = kit["2_frontend_server_call_through"];
|
|
9482
|
-
console.log(
|
|
9953
|
+
console.log(import_chalk26.default.bold("2. Frontend-server \u2014 call the proxy with the server key and say who is calling"));
|
|
9483
9954
|
console.log();
|
|
9484
9955
|
file(c);
|
|
9485
9956
|
const t = kit["3_backend_trust_contract"];
|
|
9486
|
-
console.log(
|
|
9487
|
-
for (const [h, why] of Object.entries(t.headers_you_receive)) console.log(` ${
|
|
9957
|
+
console.log(import_chalk26.default.bold("3. Backend \u2014 trust the identity headers, and nothing else"));
|
|
9958
|
+
for (const [h, why] of Object.entries(t.headers_you_receive)) console.log(` ${import_chalk26.default.cyan(h)} \u2014 ${why}`);
|
|
9488
9959
|
console.log();
|
|
9489
|
-
console.log(` ${
|
|
9960
|
+
console.log(` ${import_chalk26.default.dim("How to verify:")}`);
|
|
9490
9961
|
bullets(t.how_to_verify);
|
|
9491
9962
|
console.log();
|
|
9492
|
-
console.log(` ${
|
|
9963
|
+
console.log(` ${import_chalk26.default.dim("Never:")}`);
|
|
9493
9964
|
bullets(t.what_not_to_do);
|
|
9494
9965
|
console.log();
|
|
9495
|
-
console.log(
|
|
9496
|
-
for (const [k, v] of Object.entries(kit["4_env_vars"])) console.log(` ${
|
|
9966
|
+
console.log(import_chalk26.default.bold("4. Env vars (server only)"));
|
|
9967
|
+
for (const [k, v] of Object.entries(kit["4_env_vars"])) console.log(` ${import_chalk26.default.cyan(k)} ${import_chalk26.default.dim("=")} ${v}`);
|
|
9497
9968
|
const o = kit["5_what_stays_open"];
|
|
9498
9969
|
console.log();
|
|
9499
|
-
console.log(
|
|
9500
|
-
console.log(` ${
|
|
9501
|
-
console.log(` ${
|
|
9502
|
-
console.log(
|
|
9970
|
+
console.log(import_chalk26.default.bold("5. What stays open"));
|
|
9971
|
+
console.log(` ${import_chalk26.default.dim("Protected by a rule:")} ${o.protected.length ? o.protected.join(", ") : import_chalk26.default.dim("nothing")}`);
|
|
9972
|
+
console.log(` ${import_chalk26.default.dim("Open to any identified caller:")} ${o.open.length ? o.open.join(", ") : import_chalk26.default.dim("nothing")}`);
|
|
9973
|
+
console.log(import_chalk26.default.dim(` ${o.note}`));
|
|
9503
9974
|
console.log();
|
|
9504
|
-
console.log(
|
|
9975
|
+
console.log(import_chalk26.default.dim(` Raw JSON: npx ${B.cli} integration ${kit.project} --stack ${kit.stack} --json`));
|
|
9505
9976
|
console.log();
|
|
9506
9977
|
}
|
|
9507
9978
|
|
|
@@ -9509,14 +9980,14 @@ async function runIntegration(name, opts = {}) {
|
|
|
9509
9980
|
init_claim();
|
|
9510
9981
|
|
|
9511
9982
|
// src/commands/logout.ts
|
|
9512
|
-
var
|
|
9983
|
+
var import_chalk27 = __toESM(require("chalk"));
|
|
9513
9984
|
init_auth();
|
|
9514
9985
|
init_consumer_auth();
|
|
9515
9986
|
async function runLogout(opts = {}) {
|
|
9516
9987
|
const creds = loadCredentials();
|
|
9517
9988
|
const consumer2 = loadConsumer();
|
|
9518
9989
|
if (!creds && !consumer2) {
|
|
9519
|
-
console.log(
|
|
9990
|
+
console.log(import_chalk27.default.yellow("You were not logged in."));
|
|
9520
9991
|
return;
|
|
9521
9992
|
}
|
|
9522
9993
|
let target2;
|
|
@@ -9543,18 +10014,18 @@ async function runLogout(opts = {}) {
|
|
|
9543
10014
|
}
|
|
9544
10015
|
if (target2 === "producer" || target2 === "all") {
|
|
9545
10016
|
clearCredentials();
|
|
9546
|
-
console.log(
|
|
10017
|
+
console.log(import_chalk27.default.green(`\u2714 Logged out of API Producer${creds?.githubHandle ? ` (${creds.githubHandle})` : ""}.`));
|
|
9547
10018
|
}
|
|
9548
10019
|
if (target2 === "consumer" || target2 === "all") {
|
|
9549
10020
|
clearConsumer();
|
|
9550
|
-
console.log(
|
|
10021
|
+
console.log(import_chalk27.default.green(`\u2714 Logged out of API Consumer${consumer2?.tenant ? ` (${consumer2.tenant})` : ""}.`));
|
|
9551
10022
|
}
|
|
9552
10023
|
}
|
|
9553
10024
|
|
|
9554
10025
|
// src/commands/flush.ts
|
|
9555
10026
|
var import_chalk28 = __toESM(require("chalk"));
|
|
9556
|
-
var
|
|
9557
|
-
var
|
|
10027
|
+
var fs15 = __toESM(require("fs"));
|
|
10028
|
+
var path13 = __toESM(require("path"));
|
|
9558
10029
|
init_auth();
|
|
9559
10030
|
init_external_mcp();
|
|
9560
10031
|
async function runFlush(opts) {
|
|
@@ -9562,7 +10033,7 @@ async function runFlush(opts) {
|
|
|
9562
10033
|
const demos = await stopDemos2().catch(() => 0);
|
|
9563
10034
|
if (demos) console.log(import_chalk28.default.dim(` Stopped ${demos} running demo(s).`));
|
|
9564
10035
|
const dir = getApiblazeDir();
|
|
9565
|
-
const entries2 =
|
|
10036
|
+
const entries2 = fs15.existsSync(dir) ? fs15.readdirSync(dir) : [];
|
|
9566
10037
|
if (!entries2.length) {
|
|
9567
10038
|
console.log(import_chalk28.default.dim("Nothing to flush \u2014 no local apiblaze config found."));
|
|
9568
10039
|
return;
|
|
@@ -9575,7 +10046,7 @@ async function runFlush(opts) {
|
|
|
9575
10046
|
const footprint = apiblazeMcpFootprint();
|
|
9576
10047
|
const mdFiles = ["claude", "codex"].map((k) => ({ k, file: agentInstructionFile(k) })).filter(({ file: file2 }) => {
|
|
9577
10048
|
try {
|
|
9578
|
-
return
|
|
10049
|
+
return fs15.readFileSync(file2, "utf-8").includes("<!-- apiblaze:mcp:");
|
|
9579
10050
|
} catch {
|
|
9580
10051
|
return false;
|
|
9581
10052
|
}
|
|
@@ -9584,7 +10055,7 @@ async function runFlush(opts) {
|
|
|
9584
10055
|
console.log(import_chalk28.default.dim(` \u2022 MCP servers installed in other tools: ${[...footprint.claude, ...footprint.codex].join(", ")}`));
|
|
9585
10056
|
}
|
|
9586
10057
|
if (mdFiles.length) {
|
|
9587
|
-
console.log(import_chalk28.default.dim(` \u2022 the "use this MCP" blocks in ${mdFiles.map((m) =>
|
|
10058
|
+
console.log(import_chalk28.default.dim(` \u2022 the "use this MCP" blocks in ${mdFiles.map((m) => path13.basename(m.file)).join(" and ")} (here)`));
|
|
9588
10059
|
}
|
|
9589
10060
|
console.log(import_chalk28.default.dim(" It does NOT delete server-side proxies/keys \u2014 those GC or are revoked separately."));
|
|
9590
10061
|
if (!opts.yes) {
|
|
@@ -9600,7 +10071,7 @@ async function runFlush(opts) {
|
|
|
9600
10071
|
let removed = 0;
|
|
9601
10072
|
for (const e of entries2) {
|
|
9602
10073
|
try {
|
|
9603
|
-
|
|
10074
|
+
fs15.rmSync(path13.join(dir, e), { recursive: true, force: true });
|
|
9604
10075
|
removed++;
|
|
9605
10076
|
} catch (err) {
|
|
9606
10077
|
console.log(import_chalk28.default.red(` \u2717 could not remove ${e}: ${err instanceof Error ? err.message : "error"}`));
|
|
@@ -9615,14 +10086,14 @@ async function runFlush(opts) {
|
|
|
9615
10086
|
for (const { k, file: file2 } of mdFiles) {
|
|
9616
10087
|
let text = "";
|
|
9617
10088
|
try {
|
|
9618
|
-
text =
|
|
10089
|
+
text = fs15.readFileSync(file2, "utf-8");
|
|
9619
10090
|
} catch {
|
|
9620
10091
|
continue;
|
|
9621
10092
|
}
|
|
9622
10093
|
const names = [...text.matchAll(/<!-- apiblaze:mcp:([^\s>]+) -->/g)].map((m) => m[1]);
|
|
9623
10094
|
let cleared = 0;
|
|
9624
10095
|
for (const n of new Set(names)) if (removeAgentInstruction(k, n)) cleared++;
|
|
9625
|
-
if (cleared) console.log(` ${import_chalk28.default.dim("\u2022")} removed ${cleared} APIblaze block(s) from ${import_chalk28.default.bold(
|
|
10096
|
+
if (cleared) console.log(` ${import_chalk28.default.dim("\u2022")} removed ${cleared} APIblaze block(s) from ${import_chalk28.default.bold(path13.basename(file2))}`);
|
|
9626
10097
|
}
|
|
9627
10098
|
console.log(`${import_chalk28.default.green("\u2714")} Flushed \u2014 removed ${removed} item(s). You're fully logged out and clean.`);
|
|
9628
10099
|
console.log(import_chalk28.default.dim(" Next `apiblaze create` starts a brand-new workspace with nothing carried over."));
|
|
@@ -9941,11 +10412,11 @@ async function runAgentChatRepl(opts) {
|
|
|
9941
10412
|
}
|
|
9942
10413
|
}
|
|
9943
10414
|
const rl = import_readline.default.createInterface({ input: process.stdin, output: process.stdout });
|
|
9944
|
-
const
|
|
10415
|
+
const ask3 = (q) => new Promise((res) => rl.question(q, res));
|
|
9945
10416
|
await turn(seedPrompt);
|
|
9946
10417
|
for (; ; ) {
|
|
9947
10418
|
console.log(footer());
|
|
9948
|
-
const line = (await
|
|
10419
|
+
const line = (await ask3(import_chalk32.default.green("you \u203A "))).trim();
|
|
9949
10420
|
if (!line) continue;
|
|
9950
10421
|
const lower = line.toLowerCase();
|
|
9951
10422
|
if (line === "/exit" || line === "/quit" || lower === "exit" || lower === "quit" || lower === "q") {
|
|
@@ -10323,7 +10794,7 @@ async function runOpenapi(projectArg, apiVersionArg) {
|
|
|
10323
10794
|
}
|
|
10324
10795
|
|
|
10325
10796
|
// src/commands/mcp.ts
|
|
10326
|
-
var
|
|
10797
|
+
var import_fs8 = __toESM(require("fs"));
|
|
10327
10798
|
var import_chalk37 = __toESM(require("chalk"));
|
|
10328
10799
|
init_brand();
|
|
10329
10800
|
init_auth();
|
|
@@ -10535,7 +11006,7 @@ async function runMcpPrompts(action2, project, name, opts) {
|
|
|
10535
11006
|
if (!opts.file) throw new Error(`Usage: ${B.cli} mcp prompts set <project> <name> --file prompt.json`);
|
|
10536
11007
|
let raw;
|
|
10537
11008
|
try {
|
|
10538
|
-
raw = JSON.parse(
|
|
11009
|
+
raw = JSON.parse(import_fs8.default.readFileSync(opts.file, "utf8"));
|
|
10539
11010
|
} catch (e) {
|
|
10540
11011
|
throw new Error(`Could not read ${opts.file}: ${e instanceof Error ? e.message : String(e)}`);
|
|
10541
11012
|
}
|
|
@@ -10634,7 +11105,7 @@ function describeJob(job) {
|
|
|
10634
11105
|
}
|
|
10635
11106
|
async function pollJob(jobId, opts = {}) {
|
|
10636
11107
|
const started = Date.now();
|
|
10637
|
-
const
|
|
11108
|
+
const path16 = jobPath(jobId, opts.teamId);
|
|
10638
11109
|
let lastPhase = "";
|
|
10639
11110
|
if (!opts.quiet) process.stdout.write(import_chalk38.default.dim(opts.label ?? "Deleting"));
|
|
10640
11111
|
for (; ; ) {
|
|
@@ -10646,7 +11117,7 @@ async function pollJob(jobId, opts = {}) {
|
|
|
10646
11117
|
);
|
|
10647
11118
|
}
|
|
10648
11119
|
await new Promise((r) => setTimeout(r, POLL_MS));
|
|
10649
|
-
const job = await admin({ method: "GET", path:
|
|
11120
|
+
const job = await admin({ method: "GET", path: path16, summary: "poll cascade job" });
|
|
10650
11121
|
if (!opts.quiet) {
|
|
10651
11122
|
const phase = describeJob(job);
|
|
10652
11123
|
if (phase !== lastPhase) {
|
|
@@ -10791,7 +11262,7 @@ async function runJobStatus(jobId, opts) {
|
|
|
10791
11262
|
|
|
10792
11263
|
// src/commands/export.ts
|
|
10793
11264
|
var import_chalk41 = __toESM(require("chalk"));
|
|
10794
|
-
var
|
|
11265
|
+
var import_fs9 = require("fs");
|
|
10795
11266
|
init_admin();
|
|
10796
11267
|
init_auth();
|
|
10797
11268
|
init_resolve();
|
|
@@ -10840,7 +11311,7 @@ async function runExport(projectArg, versionArg, opts) {
|
|
|
10840
11311
|
});
|
|
10841
11312
|
if (!res.ok) throw new Error(`Download failed (${res.status}): ${await res.text().catch(() => "")}`);
|
|
10842
11313
|
const buf = Buffer.from(await res.arrayBuffer());
|
|
10843
|
-
(0,
|
|
11314
|
+
(0, import_fs9.writeFileSync)(outFile, buf);
|
|
10844
11315
|
console.log(import_chalk41.default.green(`\u2713 Saved ${outFile} (${(buf.length / 1024).toFixed(0)} KB)`));
|
|
10845
11316
|
if (job.decryption_key) console.log(import_chalk41.default.yellow(`Decryption key (shown once): ${job.decryption_key}`));
|
|
10846
11317
|
console.log(import_chalk41.default.dim("Read MIGRATION-REPORT.json inside the zip for the full fidelity ledger."));
|
|
@@ -10897,8 +11368,8 @@ function resolveRecipeName(raw, githubHandle, proxyName) {
|
|
|
10897
11368
|
function normalizeName3(raw) {
|
|
10898
11369
|
return (raw || "").toLowerCase().replace(/[^a-z0-9]/g, "");
|
|
10899
11370
|
}
|
|
10900
|
-
async function publicFetch(
|
|
10901
|
-
const res = await fetch(`${RECIPES_BASE}/api/recipes${
|
|
11371
|
+
async function publicFetch(path16) {
|
|
11372
|
+
const res = await fetch(`${RECIPES_BASE}/api/recipes${path16}`, {
|
|
10902
11373
|
headers: { accept: "application/json" }
|
|
10903
11374
|
}).catch((err) => {
|
|
10904
11375
|
throw new Error(`Could not reach the recipe registry at ${RECIPES_BASE} \u2014 ${err.message}`);
|
|
@@ -10951,11 +11422,11 @@ async function fetchRecipeFile(ref, caller) {
|
|
|
10951
11422
|
Private recipes are visible only to the publisher's team \u2014 run \`apiblaze login\` if you are on it.`
|
|
10952
11423
|
);
|
|
10953
11424
|
}
|
|
10954
|
-
const
|
|
11425
|
+
const path16 = revision !== void 0 ? `/recipes/${ref.handle}/${ref.slug}/${revision}` : `/recipes/${ref.handle}/${ref.slug}/file`;
|
|
10955
11426
|
try {
|
|
10956
11427
|
return await producer(caller, {
|
|
10957
11428
|
method: "GET",
|
|
10958
|
-
path:
|
|
11429
|
+
path: path16,
|
|
10959
11430
|
summary: `Read private recipe ${ref.name}${revision !== void 0 ? `@${revision}` : ""}`
|
|
10960
11431
|
});
|
|
10961
11432
|
} catch (err) {
|
|
@@ -12091,9 +12562,9 @@ function printEnvelopeViolations(violations) {
|
|
|
12091
12562
|
console.log("bring-your-own-key case. Change the rules above, then publish again.");
|
|
12092
12563
|
console.log();
|
|
12093
12564
|
}
|
|
12094
|
-
async function askPromotion(
|
|
12565
|
+
async function askPromotion(path16) {
|
|
12095
12566
|
const { default: inquirer3 } = await import("inquirer");
|
|
12096
|
-
const suggestedId =
|
|
12567
|
+
const suggestedId = path16.split(/[.[\]]/).filter(Boolean).slice(-2).join("_").toLowerCase().replace(/[^a-z0-9_]/g, "_").replace(/_+/g, "_").replace(/^_|_$/g, "") || "value";
|
|
12097
12568
|
const answers = await inquirer3.prompt([
|
|
12098
12569
|
{ type: "input", name: "prompt", message: " prompt them with:", validate: (v) => v.trim() ? true : "Required." },
|
|
12099
12570
|
{ type: "input", name: "example", message: " example:" },
|
|
@@ -12104,7 +12575,7 @@ async function askPromotion(path14) {
|
|
|
12104
12575
|
const id = String(answers.id || suggestedId).toLowerCase().replace(/[^a-z0-9_]/g, "_");
|
|
12105
12576
|
console.log(import_chalk44.default.dim(` \u2192 replaced with {{${id}}}`));
|
|
12106
12577
|
return {
|
|
12107
|
-
path:
|
|
12578
|
+
path: path16,
|
|
12108
12579
|
id,
|
|
12109
12580
|
prompt: String(answers.prompt).trim(),
|
|
12110
12581
|
...answers.example ? { example: String(answers.example).trim() } : {},
|
|
@@ -13870,7 +14341,7 @@ async function providersMenu(cBase, clientLabel) {
|
|
|
13870
14341
|
}
|
|
13871
14342
|
|
|
13872
14343
|
// src/commands/spec.ts
|
|
13873
|
-
var
|
|
14344
|
+
var fs17 = __toESM(require("fs"));
|
|
13874
14345
|
var import_chalk55 = __toESM(require("chalk"));
|
|
13875
14346
|
var import_ora25 = __toESM(require("ora"));
|
|
13876
14347
|
init_admin();
|
|
@@ -13893,7 +14364,7 @@ async function runSpecSet(project, opts) {
|
|
|
13893
14364
|
}
|
|
13894
14365
|
let specContent;
|
|
13895
14366
|
try {
|
|
13896
|
-
specContent =
|
|
14367
|
+
specContent = fs17.readFileSync(opts.file, "utf-8");
|
|
13897
14368
|
} catch {
|
|
13898
14369
|
console.error(import_chalk55.default.red(`Cannot read file: ${opts.file}`));
|
|
13899
14370
|
process.exit(1);
|
|
@@ -14653,7 +15124,7 @@ async function patchSetting(proj2, s, value, cfg) {
|
|
|
14653
15124
|
}
|
|
14654
15125
|
const body = s.toPatch(value, cfg);
|
|
14655
15126
|
const method = s.putLimits ? "PUT" : "PATCH";
|
|
14656
|
-
const
|
|
15127
|
+
const path16 = s.putLimits ? `/projects/${proj2.projectId}/${proj2.apiVersion}/limits` : `/projects/${proj2.projectId}/${proj2.apiVersion}`;
|
|
14657
15128
|
if (s.needsTenant) {
|
|
14658
15129
|
const tenant2 = proj2.tenant ?? cfg.tenant ?? cfg.default_tenant;
|
|
14659
15130
|
if (!tenant2) throw new Error(`Could not tell which tenant ${proj2.projectName} serves \u2014 pass it via \`apiblaze config ${proj2.projectName} --tenant <slug>\` is not supported yet; run \`apiblaze projects\` and check the project has a tenant.`);
|
|
@@ -14663,7 +15134,7 @@ async function patchSetting(proj2, s, value, cfg) {
|
|
|
14663
15134
|
try {
|
|
14664
15135
|
await admin({
|
|
14665
15136
|
method,
|
|
14666
|
-
path:
|
|
15137
|
+
path: path16,
|
|
14667
15138
|
body,
|
|
14668
15139
|
summary: `Set ${s.key} = ${JSON.stringify(value)}`
|
|
14669
15140
|
});
|
|
@@ -15299,11 +15770,11 @@ async function resolveTransport(opts) {
|
|
|
15299
15770
|
const token = getAccessToken();
|
|
15300
15771
|
return {
|
|
15301
15772
|
tenant: tenant2,
|
|
15302
|
-
call: async (
|
|
15773
|
+
call: async (path16, method = "GET", body) => {
|
|
15303
15774
|
const res = await fetch(`${DASHBOARD_BASE9}/api/cli/iam`, {
|
|
15304
15775
|
method: "POST",
|
|
15305
15776
|
headers: { "Content-Type": "application/json", Authorization: `Bearer ${token}` },
|
|
15306
|
-
body: JSON.stringify({ tenant: tenant2, path:
|
|
15777
|
+
body: JSON.stringify({ tenant: tenant2, path: path16, method, body })
|
|
15307
15778
|
});
|
|
15308
15779
|
const data = await res.json().catch(() => ({}));
|
|
15309
15780
|
if (!res.ok) throw new ApiError(res.status, data?.details ?? data?.error ?? res.statusText, data);
|
|
@@ -15321,8 +15792,8 @@ async function resolveTransport(opts) {
|
|
|
15321
15792
|
return {
|
|
15322
15793
|
tenant: fresh.tenant,
|
|
15323
15794
|
selfEmail: fresh.email,
|
|
15324
|
-
call: async (
|
|
15325
|
-
const res = await fetch(`${IAM_BASE}${
|
|
15795
|
+
call: async (path16, method = "GET", body) => {
|
|
15796
|
+
const res = await fetch(`${IAM_BASE}${path16}`, {
|
|
15326
15797
|
method,
|
|
15327
15798
|
headers: {
|
|
15328
15799
|
"Content-Type": "application/json",
|
|
@@ -15747,28 +16218,28 @@ async function runConsumerApikeys(opts) {
|
|
|
15747
16218
|
// src/commands/sidecar.ts
|
|
15748
16219
|
var import_chalk62 = __toESM(require("chalk"));
|
|
15749
16220
|
var import_ora31 = __toESM(require("ora"));
|
|
15750
|
-
var
|
|
15751
|
-
var
|
|
16221
|
+
var fs18 = __toESM(require("fs"));
|
|
16222
|
+
var path14 = __toESM(require("path"));
|
|
15752
16223
|
init_admin();
|
|
15753
16224
|
init_resolve();
|
|
15754
16225
|
init_auth();
|
|
15755
16226
|
function detectNextProject(root) {
|
|
15756
|
-
const hasConfig = ["next.config.js", "next.config.mjs", "next.config.ts"].some((f) =>
|
|
16227
|
+
const hasConfig = ["next.config.js", "next.config.mjs", "next.config.ts"].some((f) => fs18.existsSync(path14.join(root, f)));
|
|
15757
16228
|
let hasDep = false;
|
|
15758
16229
|
try {
|
|
15759
|
-
const pkg = JSON.parse(
|
|
16230
|
+
const pkg = JSON.parse(fs18.readFileSync(path14.join(root, "package.json"), "utf8"));
|
|
15760
16231
|
hasDep = !!(pkg.dependencies?.next || pkg.devDependencies?.next);
|
|
15761
16232
|
} catch {
|
|
15762
16233
|
}
|
|
15763
|
-
const appDir =
|
|
15764
|
-
const pagesDir =
|
|
16234
|
+
const appDir = fs18.existsSync(path14.join(root, "app")) || fs18.existsSync(path14.join(root, "src", "app"));
|
|
16235
|
+
const pagesDir = fs18.existsSync(path14.join(root, "pages")) || fs18.existsSync(path14.join(root, "src", "pages"));
|
|
15765
16236
|
return { found: hasConfig || hasDep || appDir || pagesDir, router: appDir ? "app" : pagesDir ? "pages" : null };
|
|
15766
16237
|
}
|
|
15767
16238
|
function upsertEnvLocal(root, token) {
|
|
15768
|
-
const p =
|
|
16239
|
+
const p = path14.join(root, ".env.local");
|
|
15769
16240
|
let existing = "";
|
|
15770
16241
|
try {
|
|
15771
|
-
existing =
|
|
16242
|
+
existing = fs18.readFileSync(p, "utf8");
|
|
15772
16243
|
} catch {
|
|
15773
16244
|
}
|
|
15774
16245
|
const had = /^APIBLAZE_API_KEY=/m.test(existing) || /^APIBLAZE_TOKEN=/m.test(existing);
|
|
@@ -15783,15 +16254,15 @@ function upsertEnvLocal(root, token) {
|
|
|
15783
16254
|
next = (next.endsWith("\n") ? next : next + "\n") + `APIBLAZE_SIDECAR_VERBOSE=true
|
|
15784
16255
|
`;
|
|
15785
16256
|
}
|
|
15786
|
-
|
|
16257
|
+
fs18.writeFileSync(p, next);
|
|
15787
16258
|
return had ? "rotated" : "created";
|
|
15788
16259
|
}
|
|
15789
16260
|
function installSidecarPackage(root) {
|
|
15790
|
-
if (
|
|
16261
|
+
if (fs18.existsSync(path14.join(root, "node_modules", "apiblaze", "package.json"))) {
|
|
15791
16262
|
console.log(` ${import_chalk62.default.green("\u2713")} apiblaze package already installed`);
|
|
15792
16263
|
return;
|
|
15793
16264
|
}
|
|
15794
|
-
const has = (f) =>
|
|
16265
|
+
const has = (f) => fs18.existsSync(path14.join(root, f));
|
|
15795
16266
|
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" };
|
|
15796
16267
|
const spinner = (0, import_ora31.default)(`Installing the apiblaze package (${pm.cmd})\u2026`).start();
|
|
15797
16268
|
try {
|
|
@@ -15804,7 +16275,7 @@ function installSidecarPackage(root) {
|
|
|
15804
16275
|
}
|
|
15805
16276
|
function readEnvKey(root) {
|
|
15806
16277
|
try {
|
|
15807
|
-
const s =
|
|
16278
|
+
const s = fs18.readFileSync(path14.join(root, ".env.local"), "utf8");
|
|
15808
16279
|
const m = s.match(/^APIBLAZE_API_KEY=(.+)$/m) ?? s.match(/^APIBLAZE_TOKEN=(.+)$/m);
|
|
15809
16280
|
return m ? m[1].trim() : null;
|
|
15810
16281
|
} catch {
|
|
@@ -15812,16 +16283,16 @@ function readEnvKey(root) {
|
|
|
15812
16283
|
}
|
|
15813
16284
|
}
|
|
15814
16285
|
function ensureGitignored(root) {
|
|
15815
|
-
const p =
|
|
16286
|
+
const p = path14.join(root, ".gitignore");
|
|
15816
16287
|
let c = "";
|
|
15817
16288
|
try {
|
|
15818
|
-
c =
|
|
16289
|
+
c = fs18.readFileSync(p, "utf8");
|
|
15819
16290
|
} catch {
|
|
15820
16291
|
}
|
|
15821
|
-
if (!/^\.env\.local$/m.test(c) && !/^\.env\*/m.test(c))
|
|
16292
|
+
if (!/^\.env\.local$/m.test(c) && !/^\.env\*/m.test(c)) fs18.writeFileSync(p, (c && !c.endsWith("\n") ? c + "\n" : c) + ".env.local\n");
|
|
15822
16293
|
}
|
|
15823
16294
|
function wireInstrumentation(root) {
|
|
15824
|
-
const existing = ["instrumentation.ts", "instrumentation.js",
|
|
16295
|
+
const existing = ["instrumentation.ts", "instrumentation.js", path14.join("src", "instrumentation.ts")].map((c) => path14.join(root, c)).find((f) => fs18.existsSync(f));
|
|
15825
16296
|
const body = `import { register as apiblaze } from "apiblaze/sidecar";
|
|
15826
16297
|
|
|
15827
16298
|
export function register() {
|
|
@@ -15829,18 +16300,18 @@ export function register() {
|
|
|
15829
16300
|
}
|
|
15830
16301
|
`;
|
|
15831
16302
|
if (!existing) {
|
|
15832
|
-
|
|
16303
|
+
fs18.writeFileSync(path14.join(root, "instrumentation.ts"), body);
|
|
15833
16304
|
return "created";
|
|
15834
16305
|
}
|
|
15835
|
-
const cur =
|
|
16306
|
+
const cur = fs18.readFileSync(existing, "utf8");
|
|
15836
16307
|
if (cur.includes("apiblaze/sidecar")) return "present";
|
|
15837
16308
|
if (/export\s+function\s+register\s*\(/.test(cur)) {
|
|
15838
|
-
|
|
16309
|
+
fs18.writeFileSync(existing, `import { register as apiblaze } from "apiblaze/sidecar";
|
|
15839
16310
|
` + cur.replace(/export\s+function\s+register\s*\(\s*\)\s*\{/, (m) => `${m}
|
|
15840
16311
|
apiblaze();`));
|
|
15841
16312
|
return "patched";
|
|
15842
16313
|
}
|
|
15843
|
-
|
|
16314
|
+
fs18.writeFileSync(existing, `import { register as apiblaze } from "apiblaze/sidecar";
|
|
15844
16315
|
${cur}
|
|
15845
16316
|
// call apiblaze() inside your register() export.
|
|
15846
16317
|
`);
|
|
@@ -15923,17 +16394,17 @@ export default async function Page() {
|
|
|
15923
16394
|
function generateInspector(root, router) {
|
|
15924
16395
|
try {
|
|
15925
16396
|
if (router === "pages") {
|
|
15926
|
-
const dir2 =
|
|
15927
|
-
const f2 =
|
|
15928
|
-
|
|
15929
|
-
return
|
|
15930
|
-
}
|
|
15931
|
-
const base2 =
|
|
15932
|
-
const dir =
|
|
15933
|
-
|
|
15934
|
-
const f =
|
|
15935
|
-
|
|
15936
|
-
return
|
|
16397
|
+
const dir2 = fs18.existsSync(path14.join(root, "src", "pages")) ? path14.join(root, "src", "pages") : path14.join(root, "pages");
|
|
16398
|
+
const f2 = path14.join(dir2, "abz-inspector.tsx");
|
|
16399
|
+
fs18.writeFileSync(f2, INSPECTOR_PAGE);
|
|
16400
|
+
return path14.relative(root, f2);
|
|
16401
|
+
}
|
|
16402
|
+
const base2 = fs18.existsSync(path14.join(root, "src", "app")) ? path14.join(root, "src", "app") : path14.join(root, "app");
|
|
16403
|
+
const dir = path14.join(base2, "abz-inspector");
|
|
16404
|
+
fs18.mkdirSync(dir, { recursive: true });
|
|
16405
|
+
const f = path14.join(dir, "page.tsx");
|
|
16406
|
+
fs18.writeFileSync(f, INSPECTOR_PAGE);
|
|
16407
|
+
return path14.relative(root, f);
|
|
15937
16408
|
} catch {
|
|
15938
16409
|
return null;
|
|
15939
16410
|
}
|
|
@@ -15973,7 +16444,7 @@ async function runAnonymousInit(root, router, opts) {
|
|
|
15973
16444
|
console.log(import_chalk62.default.dim(` From another machine: apiblaze claim ${out.claim_code} \xB7 expires in 30 days`));
|
|
15974
16445
|
}
|
|
15975
16446
|
async function runSidecar(opts) {
|
|
15976
|
-
const root =
|
|
16447
|
+
const root = path14.resolve(opts.dir ?? process.cwd());
|
|
15977
16448
|
const detected2 = detectNextProject(root);
|
|
15978
16449
|
if (!detected2.found) {
|
|
15979
16450
|
console.log(import_chalk62.default.yellow(`No Next.js project detected in ${root}.`));
|
|
@@ -16032,7 +16503,7 @@ async function runSidecar(opts) {
|
|
|
16032
16503
|
console.log(` 2. The origins your app calls appear as ${import_chalk62.default.bold("candidates")} \u2014 list them: ${import_chalk62.default.cyan("apiblaze sidecar")}`);
|
|
16033
16504
|
console.log(` 3. Approve the ones to route: ${import_chalk62.default.cyan("apiblaze sidecar approve api.stripe.com")} (or in the dashboard)`);
|
|
16034
16505
|
console.log(` \u2026within ~5 min your app starts routing that origin through APIblaze.`);
|
|
16035
|
-
if (inspectorPath) console.log(` \u2022 Try it now: open ${import_chalk62.default.underline("http://localhost:3000/abz-inspector")} (dev only; rm ${
|
|
16506
|
+
if (inspectorPath) console.log(` \u2022 Try it now: open ${import_chalk62.default.underline("http://localhost:3000/abz-inspector")} (dev only; rm ${path14.dirname(inspectorPath)} before shipping)`);
|
|
16036
16507
|
if (switchingTeam) console.log(import_chalk62.default.dim(` \u2022 Approved origins are per-team \u2014 re-approve them on ${teamName ?? teamId} with \`apiblaze sidecar approve <origin>\`.`));
|
|
16037
16508
|
console.log("");
|
|
16038
16509
|
console.log(import_chalk62.default.dim(" Manage: apiblaze sidecar (list/approve/deny/remove)"));
|
|
@@ -16768,17 +17239,17 @@ var import_chalk68 = __toESM(require("chalk"));
|
|
|
16768
17239
|
init_consumer_auth();
|
|
16769
17240
|
var SANDBOX_TENANT = "portalteststenant";
|
|
16770
17241
|
var SANDBOX_PROXY = "portaltestproxypreapproved@2.7.0";
|
|
16771
|
-
function proxyUrlFor(spec2,
|
|
17242
|
+
function proxyUrlFor(spec2, path16 = "/get") {
|
|
16772
17243
|
const [name, version2 = "1.0.0"] = spec2.split("@");
|
|
16773
|
-
return `https://${name}.abz.run/${version2}/prod${
|
|
17244
|
+
return `https://${name}.abz.run/${version2}/prod${path16.startsWith("/") ? path16 : `/${path16}`}`;
|
|
16774
17245
|
}
|
|
16775
17246
|
function isPortalAudience(aud) {
|
|
16776
17247
|
return /^https:\/\/[a-z0-9-]+\.portal\.apiblaze\.com\b/i.test(aud);
|
|
16777
17248
|
}
|
|
16778
17249
|
var log2 = (s) => process.stderr.write(s + "\n");
|
|
16779
|
-
function probeUrlFor(audience,
|
|
17250
|
+
function probeUrlFor(audience, path16 = "/") {
|
|
16780
17251
|
const base2 = audience.replace(/\/+$/, "");
|
|
16781
|
-
return `${base2}/prod${
|
|
17252
|
+
return `${base2}/prod${path16.startsWith("/") ? path16 : `/${path16}`}`;
|
|
16782
17253
|
}
|
|
16783
17254
|
function tamper(token) {
|
|
16784
17255
|
const parts = token.split(".");
|
|
@@ -17723,7 +18194,7 @@ program.command("demo").description(`See what ${B.product} does in a minute: a s
|
|
|
17723
18194
|
}
|
|
17724
18195
|
await runDemo(opts);
|
|
17725
18196
|
});
|
|
17726
|
-
program.command("skill").alias("skills").description(`
|
|
18197
|
+
program.command("skill").alias("skills").description(`What ${B.product} does, for you or your AI assistant: checks this folder for a backend and offers the next step (put it on the internet, or a sample app)`).argument("[page]", "One page instead: ship \xB7 keys \xB7 signin \xB7 rules \xB7 limits \xB7 mcp \xB7 widgets").option("--agent", "Print what an AI assistant reads, even at a terminal").option("--static", "Print the installed text, with no folder check (for generated docs)").option("--install", "Write it to .claude/skills/ship-safely/SKILL.md in the current project (instead of printing it)").option("--force", "Overwrite an existing SKILL.md").option("--ship", "Same as the ship page: the steps for putting it in front of your own app").action(action((page, opts) => runSkill({ ...opts, topic: page })));
|
|
17727
18198
|
var agent = program.command("agent").description("Chat with an assistant that builds and runs your APIs (billed per turn)").option("--team <id|name>", "Team to work in (defaults to your active team)").action(action((opts) => runAgent(opts)));
|
|
17728
18199
|
agent.command("authz").description("Chat to design and turn on access rules for an API").argument("<project>", "Project name or id").argument("[apiVersion]", "API version (defaults to the project's)").action(action((project, apiVersion) => runAuthz(project, apiVersion)));
|
|
17729
18200
|
program.command("rule").description("Lock resources to the person who created them \u2014 or describe any access rule in plain English (that one is billed per turn)").argument("<project>", "Project name or id").argument("[sentence]", 'Optional rule in plain English, e.g. "users see only their own rows". Omit to pick from what the spec can protect.').option("--enforce", "Turn enforcement on right after saving (default: save in shadow mode, then ask)").option("--apiversion <version>", "API version (defaults to the project's)").action(action((project, sentence, opts) => runRule(project, sentence, opts)));
|
|
@@ -17742,7 +18213,7 @@ mcpCommon(mcpOps.command("set").description("Override one tool").argument("<proj
|
|
|
17742
18213
|
mcpCommon(mcpOps.command("reset").description("Back to the automatic default for one tool").argument("<project>", "Project name or id").argument("<tool>", "Tool name")).action(action((project, tool, opts) => runMcpOps("reset", project, tool, opts)));
|
|
17743
18214
|
mcpCommon(mcpCmd.command("ui").description("Print a tool's MCP Apps UI resource (ui://{project}/{tool}) \u2014 the automatic viewer").argument("<project>", "Project name or id").argument("<tool>", "Tool name")).action(action((project, tool, opts) => runMcpUi(project, tool, opts)));
|
|
17744
18215
|
program.command("apichat [project]").description("Turn any API into a chat: point at an OpenAPI spec \u2014 or chat an EXISTING proxy by name (no login needed)").option("--target <url|file>", "What to chat with \u2014 pass ANY of: a target server base URL (spec auto-discovered at /openapi.json etc.), a local OpenAPI file (./openapi.yaml), or a remote OpenAPI URL (https://acme.com/openapi.yaml)").addOption(new import_commander.Option("--openapi <file|url>", "Deprecated alias \u2014 --target now detects spec files/URLs itself").hideHelp()).addOption(new import_commander.Option("--openapispec <file|url>", "Deprecated alias for --openapi").hideHelp()).option("--name <name>", "Proxy name (defaults to the target host)").option("--apiversion <version>", "API version to create (e.g. 1.0.0)").option("--environment <env>", "Environment to chat against (default: prod anonymous / dev logged-in)").option("--access <mode>", 'Who can call this API once connected (e.g. via Claude): "open" = anyone who signs in, "invite" = only you + emails you pre-approve. Default: invite when logged in, open when anonymous.').option("--target-auth-env <ENV_VAR>", "Read the upstream credential from this env var (CI-safe; required when there is no TTY and the API needs auth)").option("--force", "Proceed even if the API uses oauth2/openIdConnect target auth (you configure target auth yourself later)").option("-y, --yes", "Skip confirmation prompts").option("--tenant <slug>", "Tenant (consumer namespace: portal, login, users) for the new proxy; omit to be asked").option("--apikey <key>", "Use this API key for the proxy's door (api_key proxies). Without it, apichat detects the door and asks \u2014 or runs the consumer login for OAuth doors.").option("--xenduserid <id>", "Assert this end-user id (X-End-User-Id) \u2014 required by proxies with identified/pre-approved enforcement; you are asked for one when the proxy demands it.").option("--verbose", "Show the per-turn proxy curl trace (hidden by default)").option("-p, --prompt <question>", "One-shot question: answered through the external agent CLI after an MCP install, or by apichat itself (exits after answering when there is no TTY)").option("--install-mcp <cli>", "Install this proxy's MCP into an external agent CLI without asking: claude | codex. Also re-offers after an earlier decline.").option("--remove-mcp <cli>", "Disconnect this proxy from an external agent CLI (claude | codex): removes the MCP server and the standing instruction").action(action((project, opts) => {
|
|
17745
|
-
const looksLikeSource = !!project && (/^https?:\/\//i.test(project) || /\.(ya?ml|json)$/i.test(project) ||
|
|
18216
|
+
const looksLikeSource = !!project && (/^https?:\/\//i.test(project) || /\.(ya?ml|json)$/i.test(project) || import_fs11.default.existsSync(project));
|
|
17746
18217
|
const target2 = opts.target ?? (looksLikeSource ? project : void 0);
|
|
17747
18218
|
return runApichat({ ...opts, target: target2, project: looksLikeSource ? void 0 : project, openapispec: opts.openapispec ?? opts.openapi });
|
|
17748
18219
|
}));
|