apiblaze 0.21.27 → 0.21.29
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 +902 -397
- 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.29";
|
|
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,17 +7794,31 @@ 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
|
}
|
|
7755
7807
|
}
|
|
7808
|
+
function titleCard(lines) {
|
|
7809
|
+
const inner = Math.max(...lines.map((l) => l.length)) + 12;
|
|
7810
|
+
const center = (t) => {
|
|
7811
|
+
const left = Math.floor((inner - t.length) / 2);
|
|
7812
|
+
return " ".repeat(left) + t + " ".repeat(inner - t.length - left);
|
|
7813
|
+
};
|
|
7814
|
+
const edge = import_chalk24.default.magenta;
|
|
7815
|
+
console.log();
|
|
7816
|
+
console.log(` ${edge(`\u256D${"\u2500".repeat(inner)}\u256E`)}`);
|
|
7817
|
+
console.log(` ${edge("\u2502")}${" ".repeat(inner)}${edge("\u2502")}`);
|
|
7818
|
+
lines.forEach((l, i) => console.log(` ${edge("\u2502")}${i === 0 ? import_chalk24.default.bold(center(l)) : import_chalk24.default.dim(center(l))}${edge("\u2502")}`));
|
|
7819
|
+
console.log(` ${edge("\u2502")}${" ".repeat(inner)}${edge("\u2502")}`);
|
|
7820
|
+
console.log(` ${edge(`\u2570${"\u2500".repeat(inner)}\u256F`)}`);
|
|
7821
|
+
}
|
|
7756
7822
|
function printTable(rows) {
|
|
7757
7823
|
const width = Math.max(100, Math.min(process.stdout.columns || 140, 220));
|
|
7758
7824
|
const leftW = Math.min(64, Math.max(...rows.map((r) => `\u2713 ${r.label.padEnd(17)} ${r.what}`.length)));
|
|
@@ -7769,68 +7835,82 @@ function printTable(rows) {
|
|
|
7769
7835
|
if (line) out.push(line);
|
|
7770
7836
|
return out;
|
|
7771
7837
|
};
|
|
7772
|
-
const head = (l, r) => console.log(` ${
|
|
7773
|
-
head(`What ${B.product} set up`, "The command that does
|
|
7838
|
+
const head = (l, r) => console.log(` ${import_chalk24.default.dim(l.padEnd(leftW))} ${import_chalk24.default.dim(r)}`);
|
|
7839
|
+
head(`What ${B.product} set up`, "The command that does this");
|
|
7774
7840
|
head("\u2500".repeat(leftW), "\u2500".repeat(Math.min(rightW, 60)));
|
|
7775
7841
|
for (const r of rows) {
|
|
7776
|
-
const left = `${
|
|
7842
|
+
const left = `${import_chalk24.default.green("\u2713")} ${import_chalk24.default.bold(r.label.padEnd(17))} ${r.what}`;
|
|
7777
7843
|
const leftPlain = `\u2713 ${r.label.padEnd(17)} ${r.what}`;
|
|
7778
7844
|
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)} ${
|
|
7845
|
+
console.log(` ${left}${" ".repeat(Math.max(0, leftW - leftPlain.length))} ${import_chalk24.default.cyan(right[0])}`);
|
|
7846
|
+
for (const extra of right.slice(1)) console.log(` ${" ".repeat(leftW)} ${import_chalk24.default.cyan(extra)}`);
|
|
7781
7847
|
}
|
|
7782
7848
|
}
|
|
7783
7849
|
async function startDemo(opts) {
|
|
7784
7850
|
const t0 = Date.now();
|
|
7785
7851
|
const here = process.cwd();
|
|
7786
|
-
const dir =
|
|
7787
|
-
const rel =
|
|
7852
|
+
const dir = import_fs6.default.existsSync(import_path5.default.join(here, STATE_DIR, "state.json")) ? here : import_path5.default.resolve(here, DIR_NAME);
|
|
7853
|
+
const rel = import_path5.default.relative(here, dir) || ".";
|
|
7788
7854
|
const cdDir = rel === "." ? "" : `cd ${rel} && `;
|
|
7789
|
-
const shell = (c, note = "") => console.log(` ${
|
|
7790
|
-
const ok = (text, note = "") => console.log(` ${
|
|
7855
|
+
const shell = (c, note = "") => console.log(` ${import_chalk24.default.dim("$")} ${import_chalk24.default.cyan(c)}${note ? ` ${import_chalk24.default.dim(note)}` : ""}`);
|
|
7856
|
+
const ok = (text, note = "") => console.log(` ${import_chalk24.default.green("\u2713")} ${text}${note ? ` ${import_chalk24.default.dim(note)}` : ""}`);
|
|
7791
7857
|
const stopHere = (lines) => {
|
|
7792
7858
|
console.log();
|
|
7793
7859
|
for (const l of lines) console.log(` ${l}`);
|
|
7794
7860
|
};
|
|
7861
|
+
titleCard(["Restaurant Reservation Service Demo", `ResiResi, with ${B.product} in front`]);
|
|
7795
7862
|
await stopDemos();
|
|
7796
7863
|
writeApp(dir);
|
|
7797
7864
|
const appPort = await freePort(3e3);
|
|
7798
|
-
const
|
|
7799
|
-
const directPort = await freePort(3002, [appPort,
|
|
7865
|
+
const backendPort2 = await freePort(3001, [appPort]);
|
|
7866
|
+
const directPort = await freePort(3002, [appPort, backendPort2]);
|
|
7800
7867
|
const name = demoName();
|
|
7801
7868
|
const ninoTenant = `ninopizza${import_crypto.default.randomBytes(2).toString("hex")}`;
|
|
7802
7869
|
const backendSecret = import_crypto.default.randomBytes(18).toString("base64url");
|
|
7803
7870
|
const openapi = DEMO_FILES["openapi.yaml"];
|
|
7871
|
+
try {
|
|
7872
|
+
const specPath = import_path5.default.join(dir, "openapi.yaml");
|
|
7873
|
+
import_fs6.default.writeFileSync(specPath, import_fs6.default.readFileSync(specPath, "utf8").replace(/^paths:/m, `servers:
|
|
7874
|
+
- url: http://localhost:${backendPort2}
|
|
7875
|
+
paths:`));
|
|
7876
|
+
} catch {
|
|
7877
|
+
}
|
|
7804
7878
|
const L = (c) => `${LATEST} ${c}`;
|
|
7805
|
-
writeEnv(dir, { BACKEND_SECRET: backendSecret, DEMO_NINO_TENANT: ninoTenant, APP_PORT: appPort, BACKEND_PORT:
|
|
7879
|
+
writeEnv(dir, { BACKEND_SECRET: backendSecret, DEMO_NINO_TENANT: ninoTenant, APP_PORT: appPort, BACKEND_PORT: backendPort2, DIRECT_PORT: directPort });
|
|
7806
7880
|
console.log();
|
|
7807
|
-
console.log(
|
|
7881
|
+
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
7882
|
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(
|
|
7883
|
+
for (const r of routesOf(openapi)) console.log(` ${r.method.padEnd(7)}${r.path.padEnd(21)}${import_chalk24.default.dim(r.summary)}`);
|
|
7884
|
+
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)."));
|
|
7885
|
+
if (!await confirmStep(opts, `Ok to serve the backend on localhost:${backendPort2}?`)) {
|
|
7886
|
+
return stopHere([
|
|
7887
|
+
`Stopped before starting anything. ResiResi is written to ${rel}.`,
|
|
7888
|
+
`Start it any time: ${cdDir}npm run start ${import_chalk24.default.dim("\xB7")} or run the whole demo again: ${LATEST} demo`
|
|
7889
|
+
]);
|
|
7890
|
+
}
|
|
7811
7891
|
shell(`${cdDir}npm run start`, "npm run stop \xB7 npm run restart");
|
|
7812
7892
|
if (!appCtl(dir, "--background")) throw new DemoError("The ResiResi app could not start.", `Run again: ${LATEST} demo`);
|
|
7813
7893
|
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(`${
|
|
7894
|
+
const appUp = await busy("Starting the reservation backend and its front-end\u2026", () => waitPorts([backendPort2, directPort]), 1800);
|
|
7895
|
+
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`);
|
|
7896
|
+
ok(`${import_chalk24.default.bold("Reservation backend")}: localhost:${backendPort2}`, "unprotected");
|
|
7897
|
+
ok(`${import_chalk24.default.bold("Unprotected reservation front-end")}, going directly to the reservation backend: ${import_chalk24.default.bold(`http://localhost:${directPort}`)}`);
|
|
7818
7898
|
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(
|
|
7899
|
+
console.log(import_chalk24.default.cyan(` curl -X POST http://localhost:${backendPort2}/reservations \\`));
|
|
7900
|
+
console.log(import_chalk24.default.cyan(` -H "x-target-api-key: $(grep BACKEND_SECRET ${import_path5.default.join(rel, ".env.local")} | cut -d= -f2)" \\`));
|
|
7901
|
+
console.log(import_chalk24.default.cyan(` -H "x-abz-user-id: ana" -H "x-abz-tenant-id: ${ninoTenant}" \\`));
|
|
7902
|
+
console.log(import_chalk24.default.cyan(` -H "content-type: application/json" -d '{"time":"19:30","guests":2}'`));
|
|
7823
7903
|
if (!await confirmStep(opts, `Ok to put an ${B.product} proxy and MCP in front of the reservation backend?`)) {
|
|
7824
7904
|
return stopHere([
|
|
7825
|
-
`Stopped before ${B.product}. ResiResi keeps running on this laptop: http://localhost:${directPort} ${
|
|
7905
|
+
`Stopped before ${B.product}. ResiResi keeps running on this laptop: http://localhost:${directPort} ${import_chalk24.default.dim(`(${cdDir}npm run stop stops it)`)}`,
|
|
7826
7906
|
`Run the whole demo again any time: ${LATEST} demo`
|
|
7827
7907
|
]);
|
|
7828
7908
|
}
|
|
7829
7909
|
console.log();
|
|
7830
|
-
console.log(
|
|
7910
|
+
console.log(import_chalk24.default.bold(`2. ${B.product}, in front of it`));
|
|
7831
7911
|
const tCreate = Date.now();
|
|
7832
7912
|
const setup = await busy(`Creating the proxy, its tenants, admins, rules and limits\u2026`, async () => {
|
|
7833
|
-
const r = await createProxy2(name,
|
|
7913
|
+
const r = await createProxy2(name, backendPort2, openapi, ninoTenant);
|
|
7834
7914
|
const cpKey2 = r.cp_key;
|
|
7835
7915
|
const teamId2 = r.team_id;
|
|
7836
7916
|
const keys = r.api_keys ?? {};
|
|
@@ -7884,17 +7964,19 @@ async function startDemo(opts) {
|
|
|
7884
7964
|
BACKEND_SECRET: backendSecret,
|
|
7885
7965
|
DEMO_NINO_TENANT: ninoTenant,
|
|
7886
7966
|
APP_PORT: appPort,
|
|
7887
|
-
BACKEND_PORT:
|
|
7967
|
+
BACKEND_PORT: backendPort2,
|
|
7888
7968
|
DIRECT_PORT: directPort
|
|
7889
7969
|
});
|
|
7890
|
-
const state = { name: project, version: version2, tenant: tenant2, teamId, cpKey, appPort, backendPort, apiUrl, claimCode: setup.claimCode };
|
|
7970
|
+
const state = { name: project, version: version2, tenant: tenant2, teamId, cpKey, appPort, backendPort: backendPort2, apiUrl, claimCode: setup.claimCode };
|
|
7891
7971
|
saveState(dir, state);
|
|
7892
7972
|
printTable([
|
|
7893
|
-
|
|
7973
|
+
// `create` from the spec in the folder (it names localhost:<backend port> under servers:); step 3's
|
|
7974
|
+
// `dev --project` then opens the tunnel to that same proxy and creates nothing.
|
|
7975
|
+
{ label: "Proxy", what: `${project} (${createSecs} s, no account)`, cmds: [L(`create --target ./openapi.yaml --name ${name} --tenant ${ninoTenant} --auto`)] },
|
|
7894
7976
|
{
|
|
7895
7977
|
label: "Customers/Tenants",
|
|
7896
7978
|
what: gino ? "Nino Pizza and Gino Pizza, own API keys" : "Nino Pizza, its own API key",
|
|
7897
|
-
cmds: gino?.tenant ? [L(`tenant create --name "Gino Pizza" --slug ${gino.tenant}`), L(`tenant attach ${project} --tenant ${gino.tenant}`)
|
|
7979
|
+
cmds: gino?.tenant ? [L(`tenant create --name "Gino Pizza" --slug ${gino.tenant}`), L(`tenant attach ${project} --tenant ${gino.tenant}`)] : []
|
|
7898
7980
|
},
|
|
7899
7981
|
...adminOk ? [{
|
|
7900
7982
|
label: "Admins",
|
|
@@ -7902,25 +7984,29 @@ async function startDemo(opts) {
|
|
|
7902
7984
|
cmds: [L(`admins add ${DEMO_ADMIN_EMAIL} --tenant ${tenant2}`), ...ginaOk && gino?.tenant ? [L(`admins add ${DEMO_GINO_ADMIN_EMAIL} --tenant ${gino.tenant}`)] : []]
|
|
7903
7985
|
}] : [],
|
|
7904
7986
|
...applied.length ? [{ label: "Rules", what: "only who made a reservation can change it", cmds: [L(`rule ${project} "${RULE_SENTENCE}"`)] }] : [],
|
|
7905
|
-
{ label: "AI (MCP Tools)", what: `${tools.length} tools, one per route`, cmds: ["(part of
|
|
7987
|
+
{ label: "AI (MCP Tools)", what: `${tools.length} tools, one per route`, cmds: ["(part of create, above)"] },
|
|
7906
7988
|
...limited ? [{ label: "Limits", what: `${DEMO_RPS} calls a second per customer`, cmds: [L(`limits set ${project} --rate ${DEMO_RPS} --end-user-rate ${DEMO_RPS}`)] }] : [],
|
|
7907
7989
|
{ 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
|
-
{
|
|
7990
|
+
{
|
|
7991
|
+
label: "Keys",
|
|
7992
|
+
what: "saved to .env.local, never printed",
|
|
7993
|
+
cmds: [L(`apikeys mint --tenant ${tenant2} --backend --env dev`), ...gino?.tenant ? [L(`apikeys mint --tenant ${gino.tenant} --backend --env dev`)] : []]
|
|
7994
|
+
}
|
|
7909
7995
|
]);
|
|
7910
|
-
if (!await confirmStep(opts, `Ok to create a tunnel allowing ${project} to forward requests to localhost:${
|
|
7996
|
+
if (!await confirmStep(opts, `Ok to create a tunnel allowing ${project} to forward requests to localhost:${backendPort2}?`)) {
|
|
7911
7997
|
return stopHere([
|
|
7912
7998
|
`Stopped before the tunnel. The proxy exists but cannot reach this laptop yet.`,
|
|
7913
7999
|
`Open the tunnel any time: ${LATEST} demo start`
|
|
7914
8000
|
]);
|
|
7915
8001
|
}
|
|
7916
8002
|
console.log();
|
|
7917
|
-
console.log(
|
|
7918
|
-
shell(`${LATEST} dev --port ${
|
|
7919
|
-
await busy(`Opening the tunnel to localhost:${
|
|
8003
|
+
console.log(import_chalk24.default.bold("3. Live on the internet"));
|
|
8004
|
+
shell(`${LATEST} dev --port ${backendPort2} --project ${project} --auto`, "the tunnel, in the background");
|
|
8005
|
+
await busy(`Opening the tunnel to localhost:${backendPort2} and checking it with a real call\u2026`, async () => {
|
|
7920
8006
|
await spawnTunnel(dir, state);
|
|
7921
8007
|
await waitThrough(apiUrl, serverKey);
|
|
7922
8008
|
}, 1200);
|
|
7923
|
-
ok(`${
|
|
8009
|
+
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
8010
|
printAddresses(dir);
|
|
7925
8011
|
if (!await confirmStep(opts, "Ok to launch the protected website, which will use the proxy to reach the backend?")) {
|
|
7926
8012
|
return stopHere([
|
|
@@ -7929,29 +8015,29 @@ async function startDemo(opts) {
|
|
|
7929
8015
|
]);
|
|
7930
8016
|
}
|
|
7931
8017
|
console.log();
|
|
7932
|
-
console.log(
|
|
8018
|
+
console.log(import_chalk24.default.bold(`4. The protected website, through ${B.product}`));
|
|
7933
8019
|
shell(`${cdDir}npm run restart`, "picks up the keys in .env.local");
|
|
7934
8020
|
const siteUp = await busy("Starting the protected website\u2026", async () => {
|
|
7935
8021
|
appCtl(dir, "--stop");
|
|
7936
8022
|
appCtl(dir, "--background");
|
|
7937
|
-
return waitPorts([appPort, directPort,
|
|
8023
|
+
return waitPorts([appPort, directPort, backendPort2]);
|
|
7938
8024
|
}, 1500);
|
|
7939
|
-
if (!siteUp) throw new DemoError(`The protected website did not start (see ${
|
|
7940
|
-
ok(`${
|
|
7941
|
-
ok(`${
|
|
8025
|
+
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`);
|
|
8026
|
+
ok(`${import_chalk24.default.bold("Protected reservation website")}, going through the ${B.product} proxy ${apiUrl}: ${import_chalk24.default.bold(`http://localhost:${appPort}`)}`);
|
|
8027
|
+
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
8028
|
console.log();
|
|
7943
|
-
console.log(
|
|
8029
|
+
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
8030
|
await offerBrowserAndLogs(dir, opts);
|
|
7945
8031
|
}
|
|
7946
8032
|
function printAddresses(dir) {
|
|
7947
|
-
const env = readEnvFile(
|
|
7948
|
-
const row = (label3, value, note = "") => console.log(` ${label3.padEnd(11)} ${value}${note ? ` ${
|
|
8033
|
+
const env = readEnvFile(import_path5.default.join(dir, ".env.local"));
|
|
8034
|
+
const row = (label3, value, note = "") => console.log(` ${label3.padEnd(11)} ${value}${note ? ` ${import_chalk24.default.dim(note)}` : ""}`);
|
|
7949
8035
|
row("Public API", env.APIBLAZE_URL ?? "", `${B.product} tunnel \u2192 localhost:${env.BACKEND_PORT}`);
|
|
7950
8036
|
row("MCP", env.APIBLAZE_MCP_URL ?? "", "for Claude, ChatGPT, Cursor (Nino Pizza)");
|
|
7951
8037
|
if (env.APIBLAZE_PORTAL_URL) row("Dev portal", env.APIBLAZE_PORTAL_URL, "sign in, get a key, try the API");
|
|
7952
8038
|
}
|
|
7953
8039
|
async function spawnTunnel(dir, state) {
|
|
7954
|
-
const log4 =
|
|
8040
|
+
const log4 = import_fs6.default.openSync(import_path5.default.join(dir, STATE_DIR, "tunnel.log"), "a");
|
|
7955
8041
|
const child = (0, import_child_process4.spawn)(process.execPath, [process.argv[1], "demo", "--serve", dir], { cwd: dir, detached: true, stdio: ["ignore", log4, log4] });
|
|
7956
8042
|
child.unref();
|
|
7957
8043
|
saveState(dir, { ...state, pid: child.pid, ready: false, error: void 0 });
|
|
@@ -7962,9 +8048,9 @@ async function runDemoStart(opts = {}) {
|
|
|
7962
8048
|
const t0 = Date.now();
|
|
7963
8049
|
const dir = demoDir();
|
|
7964
8050
|
const state = loadState(dir);
|
|
7965
|
-
const env = readEnvFile(
|
|
8051
|
+
const env = readEnvFile(import_path5.default.join(dir, ".env.local"));
|
|
7966
8052
|
if (!state || !env.APIBLAZE_SERVER_KEY) {
|
|
7967
|
-
console.log(`${
|
|
8053
|
+
console.log(`${import_chalk24.default.red("\u2717")} There is no demo here to start.`);
|
|
7968
8054
|
console.log(` Make one: ${LATEST} demo`);
|
|
7969
8055
|
process.exit(1);
|
|
7970
8056
|
}
|
|
@@ -7973,30 +8059,30 @@ async function runDemoStart(opts = {}) {
|
|
|
7973
8059
|
await spawnTunnel(dir, state);
|
|
7974
8060
|
} catch (err) {
|
|
7975
8061
|
const e = err instanceof DemoError ? err : new DemoError(`The tunnel could not start (${err.message}).`, `Start over: ${LATEST} demo`);
|
|
7976
|
-
console.log(`${
|
|
8062
|
+
console.log(`${import_chalk24.default.red("\u2717")} ${e.message}`);
|
|
7977
8063
|
console.log(` ${e.fix}`);
|
|
7978
8064
|
process.exit(1);
|
|
7979
8065
|
}
|
|
7980
|
-
console.log(`${
|
|
8066
|
+
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
8067
|
printAddresses(dir);
|
|
7982
8068
|
const appUp = await fetch(`http://127.0.0.1:${env.APP_PORT}/api/config`).then(() => true).catch(() => false);
|
|
7983
|
-
if (!appUp) console.log(
|
|
8069
|
+
if (!appUp) console.log(import_chalk24.default.yellow(` ResiResi is not running: cd ${import_path5.default.relative(process.cwd(), dir) || "."} && npm run start`));
|
|
7984
8070
|
await offerBrowserAndLogs(dir, opts);
|
|
7985
8071
|
}
|
|
7986
8072
|
async function runDemoLogs() {
|
|
7987
|
-
const env = readEnvFile(
|
|
8073
|
+
const env = readEnvFile(import_path5.default.join(demoDir(), ".env.local"));
|
|
7988
8074
|
if (!env.APIBLAZE_NAME) {
|
|
7989
|
-
console.log(`${
|
|
8075
|
+
console.log(`${import_chalk24.default.red("\u2717")} There is no demo here.`);
|
|
7990
8076
|
console.log(` Make one: ${LATEST} demo`);
|
|
7991
8077
|
process.exit(1);
|
|
7992
8078
|
}
|
|
7993
|
-
console.log(
|
|
8079
|
+
console.log(import_chalk24.default.dim(`$ ${logsCommand(env)}`));
|
|
7994
8080
|
const { runLogsTail: runLogsTail2 } = await Promise.resolve().then(() => (init_logs(), logs_exports));
|
|
7995
8081
|
await runLogsTail2(env.APIBLAZE_NAME, void 0, { tenant: env.APIBLAZE_TENANT });
|
|
7996
8082
|
}
|
|
7997
8083
|
async function runDemoStop() {
|
|
7998
8084
|
const n = await stopTunnels();
|
|
7999
|
-
console.log(n ? `${
|
|
8085
|
+
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
8086
|
}
|
|
8001
8087
|
async function serveTunnel(dir) {
|
|
8002
8088
|
const state = loadState(dir);
|
|
@@ -8054,17 +8140,17 @@ async function serveTunnel(dir) {
|
|
|
8054
8140
|
await new Promise(() => {
|
|
8055
8141
|
});
|
|
8056
8142
|
}
|
|
8057
|
-
var
|
|
8143
|
+
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
8144
|
var init_demo = __esm({
|
|
8059
8145
|
"src/commands/demo.ts"() {
|
|
8060
8146
|
"use strict";
|
|
8061
|
-
|
|
8062
|
-
|
|
8063
|
-
|
|
8147
|
+
import_fs6 = __toESM(require("fs"));
|
|
8148
|
+
import_path5 = __toESM(require("path"));
|
|
8149
|
+
import_net2 = __toESM(require("net"));
|
|
8064
8150
|
import_child_process4 = require("child_process");
|
|
8065
8151
|
import_crypto = __toESM(require("crypto"));
|
|
8066
|
-
|
|
8067
|
-
|
|
8152
|
+
import_chalk24 = __toESM(require("chalk"));
|
|
8153
|
+
import_ora9 = __toESM(require("ora"));
|
|
8068
8154
|
init_api();
|
|
8069
8155
|
init_tunnel_client();
|
|
8070
8156
|
init_auth();
|
|
@@ -8088,7 +8174,7 @@ var init_demo = __esm({
|
|
|
8088
8174
|
ADJ = ["brave", "calm", "clever", "eager", "happy", "jolly", "lucky", "merry", "noble", "quick", "sunny", "swift", "witty", "zesty", "bright", "gentle"];
|
|
8089
8175
|
NOUN = ["otter", "falcon", "panda", "heron", "koala", "lemur", "puffin", "robin", "tiger", "walrus", "wombat", "badger", "comet", "maple", "river", "finch"];
|
|
8090
8176
|
pick2 = (a) => a[import_crypto.default.randomInt(a.length)];
|
|
8091
|
-
registryFile = () =>
|
|
8177
|
+
registryFile = () => import_path5.default.join(getApiblazeDir(), "demo-runs.json");
|
|
8092
8178
|
alive = (pid) => {
|
|
8093
8179
|
try {
|
|
8094
8180
|
process.kill(pid, 0);
|
|
@@ -8353,7 +8439,7 @@ function runCli(args, cwd, home, timeoutMs) {
|
|
|
8353
8439
|
function readEnv(file2) {
|
|
8354
8440
|
const out = {};
|
|
8355
8441
|
try {
|
|
8356
|
-
for (const line of
|
|
8442
|
+
for (const line of import_fs10.default.readFileSync(file2, "utf8").split("\n")) {
|
|
8357
8443
|
const m = /^([A-Z0-9_]+)=(.*)$/.exec(line.trim());
|
|
8358
8444
|
if (m) out[m[1]] = m[2];
|
|
8359
8445
|
}
|
|
@@ -8386,7 +8472,7 @@ async function journey(label3, work, home, checks) {
|
|
|
8386
8472
|
`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
8473
|
);
|
|
8388
8474
|
if (run2.code !== 0) return null;
|
|
8389
|
-
const env = readEnv(
|
|
8475
|
+
const env = readEnv(import_path7.default.join(work, `${B.cli}-demo`, ".env.local"));
|
|
8390
8476
|
const api = env.APIBLAZE_URL;
|
|
8391
8477
|
const nino = env.APIBLAZE_SERVER_KEY;
|
|
8392
8478
|
const as = (user, key = nino) => ({ "x-api-key": key, "x-end-user-id": user, "content-type": "application/json" });
|
|
@@ -8451,11 +8537,11 @@ async function journey(label3, work, home, checks) {
|
|
|
8451
8537
|
}
|
|
8452
8538
|
async function runOpAnon(o = {}) {
|
|
8453
8539
|
const t0 = Date.now();
|
|
8454
|
-
const root =
|
|
8455
|
-
const home =
|
|
8456
|
-
const work =
|
|
8457
|
-
|
|
8458
|
-
|
|
8540
|
+
const root = import_fs10.default.mkdtempSync(import_path7.default.join(import_os2.default.tmpdir(), `${B.cli}-op-anon-`));
|
|
8541
|
+
const home = import_path7.default.join(root, "home");
|
|
8542
|
+
const work = import_path7.default.join(root, "work");
|
|
8543
|
+
import_fs10.default.mkdirSync(home);
|
|
8544
|
+
import_fs10.default.mkdirSync(work);
|
|
8459
8545
|
const checks = [];
|
|
8460
8546
|
const log4 = (s) => {
|
|
8461
8547
|
if (!o.json) console.log(s);
|
|
@@ -8466,21 +8552,21 @@ async function runOpAnon(o = {}) {
|
|
|
8466
8552
|
await journey("rerun", work, home, checks);
|
|
8467
8553
|
} finally {
|
|
8468
8554
|
const stop = await runCli(["demo", "stop"], work, home, 3e4);
|
|
8469
|
-
const demoFolder =
|
|
8470
|
-
if (
|
|
8555
|
+
const demoFolder = import_path7.default.join(work, `${B.cli}-demo`);
|
|
8556
|
+
if (import_fs10.default.existsSync(import_path7.default.join(demoFolder, "start.js"))) {
|
|
8471
8557
|
await new Promise((res) => {
|
|
8472
|
-
const c = (0, import_child_process5.spawn)(process.execPath, [
|
|
8558
|
+
const c = (0, import_child_process5.spawn)(process.execPath, [import_path7.default.join(demoFolder, "start.js"), "--stop"], { cwd: demoFolder, stdio: "ignore" });
|
|
8473
8559
|
c.on("close", () => res());
|
|
8474
8560
|
});
|
|
8475
8561
|
}
|
|
8476
|
-
const env = readEnv(
|
|
8562
|
+
const env = readEnv(import_path7.default.join(work, `${B.cli}-demo`, ".env.local"));
|
|
8477
8563
|
let portsFree = true;
|
|
8478
8564
|
for (const p of [env.APP_PORT, env.BACKEND_PORT].filter(Boolean)) {
|
|
8479
8565
|
const r = await http(`http://127.0.0.1:${p}/`, { timeoutMs: 2e3 });
|
|
8480
8566
|
if (r.status !== 0) portsFree = false;
|
|
8481
8567
|
}
|
|
8482
8568
|
checks.push({ id: "stop", ok: stop.code === 0 && portsFree, detail: `exit ${stop.code}${portsFree ? "" : " \xB7 ports still answering"}` });
|
|
8483
|
-
|
|
8569
|
+
import_fs10.default.rmSync(root, { recursive: true, force: true });
|
|
8484
8570
|
}
|
|
8485
8571
|
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
8572
|
const failed = checks.filter((c) => !c.ok).length;
|
|
@@ -8493,13 +8579,13 @@ async function runOpAnon(o = {}) {
|
|
|
8493
8579
|
if (failed) process.exitCode = 1;
|
|
8494
8580
|
return { ok: !failed, checks };
|
|
8495
8581
|
}
|
|
8496
|
-
var
|
|
8582
|
+
var import_fs10, import_os2, import_path7, import_child_process5, import_chalk70, SECRET;
|
|
8497
8583
|
var init_op_anon = __esm({
|
|
8498
8584
|
"src/commands/op-anon.ts"() {
|
|
8499
8585
|
"use strict";
|
|
8500
|
-
|
|
8586
|
+
import_fs10 = __toESM(require("fs"));
|
|
8501
8587
|
import_os2 = __toESM(require("os"));
|
|
8502
|
-
|
|
8588
|
+
import_path7 = __toESM(require("path"));
|
|
8503
8589
|
import_child_process5 = require("child_process");
|
|
8504
8590
|
import_chalk70 = __toESM(require("chalk"));
|
|
8505
8591
|
init_brand();
|
|
@@ -8508,7 +8594,7 @@ var init_op_anon = __esm({
|
|
|
8508
8594
|
});
|
|
8509
8595
|
|
|
8510
8596
|
// src/index.ts
|
|
8511
|
-
var
|
|
8597
|
+
var import_fs11 = __toESM(require("fs"));
|
|
8512
8598
|
var import_commander = require("commander");
|
|
8513
8599
|
var import_chalk72 = __toESM(require("chalk"));
|
|
8514
8600
|
init_package();
|
|
@@ -8517,7 +8603,7 @@ init_types();
|
|
|
8517
8603
|
init_login();
|
|
8518
8604
|
|
|
8519
8605
|
// src/commands/dev.ts
|
|
8520
|
-
var
|
|
8606
|
+
var import_fs4 = __toESM(require("fs"));
|
|
8521
8607
|
init_package();
|
|
8522
8608
|
init_version_guard();
|
|
8523
8609
|
var import_chalk20 = __toESM(require("chalk"));
|
|
@@ -8839,7 +8925,7 @@ async function runDev(options) {
|
|
|
8839
8925
|
let recordSink;
|
|
8840
8926
|
let captureStream;
|
|
8841
8927
|
if (options.captureFile) {
|
|
8842
|
-
captureStream =
|
|
8928
|
+
captureStream = import_fs4.default.createWriteStream(options.captureFile, { flags: "a" });
|
|
8843
8929
|
recordSink = (r) => captureStream.write(JSON.stringify(r) + "\n");
|
|
8844
8930
|
}
|
|
8845
8931
|
let restore = [];
|
|
@@ -9026,12 +9112,199 @@ ${projects.length} project${projects.length === 1 ? "" : "s"}`));
|
|
|
9026
9112
|
init_create();
|
|
9027
9113
|
|
|
9028
9114
|
// src/commands/skill.ts
|
|
9029
|
-
var
|
|
9030
|
-
var
|
|
9031
|
-
var
|
|
9115
|
+
var import_fs7 = __toESM(require("fs"));
|
|
9116
|
+
var import_path6 = __toESM(require("path"));
|
|
9117
|
+
var import_chalk25 = __toESM(require("chalk"));
|
|
9032
9118
|
init_brand();
|
|
9119
|
+
|
|
9120
|
+
// src/lib/detect-backend.ts
|
|
9121
|
+
var import_fs5 = __toESM(require("fs"));
|
|
9122
|
+
var import_path4 = __toESM(require("path"));
|
|
9123
|
+
var import_net = __toESM(require("net"));
|
|
9124
|
+
var NODE_SERVERS = [
|
|
9125
|
+
["express", "Express", 3e3],
|
|
9126
|
+
["fastify", "Fastify", 3e3],
|
|
9127
|
+
["koa", "Koa", 3e3],
|
|
9128
|
+
["hono", "Hono", 3e3],
|
|
9129
|
+
["@nestjs/core", "NestJS", 3e3],
|
|
9130
|
+
["@hapi/hapi", "hapi", 3e3],
|
|
9131
|
+
["restify", "Restify", 8080],
|
|
9132
|
+
["elysia", "Elysia", 3e3],
|
|
9133
|
+
["next", "Next.js", 3e3],
|
|
9134
|
+
["nuxt", "Nuxt", 3e3],
|
|
9135
|
+
["@remix-run/node", "Remix", 3e3]
|
|
9136
|
+
];
|
|
9137
|
+
var NODE_FRONTENDS = [["vite", "Vite"], ["react-scripts", "Create React App"], ["@angular/core", "Angular"], ["svelte", "Svelte"]];
|
|
9138
|
+
var PY_SERVERS = [
|
|
9139
|
+
[/\bfastapi\b/i, "FastAPI", 8e3],
|
|
9140
|
+
[/\bdjango\b/i, "Django", 8e3],
|
|
9141
|
+
[/\bflask\b/i, "Flask", 5e3],
|
|
9142
|
+
[/\bstarlette\b/i, "Starlette", 8e3],
|
|
9143
|
+
[/\baiohttp\b/i, "aiohttp", 8080]
|
|
9144
|
+
];
|
|
9145
|
+
var GO_SERVERS = [[/gin-gonic\/gin/, "Gin"], [/labstack\/echo/, "Echo"], [/go-chi\/chi/, "chi"], [/gofiber\/fiber/, "Fiber"]];
|
|
9146
|
+
var RUBY_SERVERS = [[/^\s*gem ['"]rails['"]/m, "Rails", 3e3], [/^\s*gem ['"]sinatra['"]/m, "Sinatra", 4567]];
|
|
9147
|
+
var SPEC_NAMES = ["openapi.yaml", "openapi.yml", "openapi.json", "swagger.yaml", "swagger.yml", "swagger.json"];
|
|
9148
|
+
var SPEC_DIRS = [".", "api", "spec", "specs", "docs", "openapi", "src"];
|
|
9149
|
+
var COMMON_PORTS = [3e3, 3001, 4e3, 5e3, 8e3, 8080, 8787];
|
|
9150
|
+
var SPEC_PATHS = ["/openapi.json", "/openapi.yaml", "/docs/openapi.json", "/swagger.json", "/api/openapi.json"];
|
|
9151
|
+
function readText(file2, max = 256 * 1024) {
|
|
9152
|
+
try {
|
|
9153
|
+
const st = import_fs5.default.statSync(file2);
|
|
9154
|
+
if (!st.isFile() || st.size > max) return null;
|
|
9155
|
+
return import_fs5.default.readFileSync(file2, "utf8");
|
|
9156
|
+
} catch {
|
|
9157
|
+
return null;
|
|
9158
|
+
}
|
|
9159
|
+
}
|
|
9160
|
+
function findSpecFile(dir) {
|
|
9161
|
+
for (const d of SPEC_DIRS) {
|
|
9162
|
+
for (const n of SPEC_NAMES) {
|
|
9163
|
+
const rel = d === "." ? n : `${d}/${n}`;
|
|
9164
|
+
const text = readText(import_path4.default.join(dir, rel));
|
|
9165
|
+
if (text && /\b(openapi|swagger)\s*["']?\s*:/.test(text)) {
|
|
9166
|
+
const m = /\b(?:localhost|127\.0\.0\.1):(\d{2,5})\b/.exec(text);
|
|
9167
|
+
return { file: `./${rel}`, ...m ? { port: Number(m[1]) } : {} };
|
|
9168
|
+
}
|
|
9169
|
+
}
|
|
9170
|
+
}
|
|
9171
|
+
return void 0;
|
|
9172
|
+
}
|
|
9173
|
+
function portFromScripts(scripts) {
|
|
9174
|
+
for (const s of Object.values(scripts)) {
|
|
9175
|
+
const m = /(?:--port[= ]|-p |PORT=)(\d{2,5})\b/.exec(s);
|
|
9176
|
+
if (m) return Number(m[1]);
|
|
9177
|
+
}
|
|
9178
|
+
return void 0;
|
|
9179
|
+
}
|
|
9180
|
+
function fromNode(dir, out) {
|
|
9181
|
+
const text = readText(import_path4.default.join(dir, "package.json"));
|
|
9182
|
+
if (!text) return;
|
|
9183
|
+
let pkg = {};
|
|
9184
|
+
try {
|
|
9185
|
+
pkg = JSON.parse(text);
|
|
9186
|
+
} catch {
|
|
9187
|
+
return;
|
|
9188
|
+
}
|
|
9189
|
+
const deps = { ...pkg.devDependencies ?? {}, ...pkg.dependencies ?? {} };
|
|
9190
|
+
const scripts = pkg.scripts ?? {};
|
|
9191
|
+
const server = NODE_SERVERS.find(([dep]) => dep in deps);
|
|
9192
|
+
if (server) {
|
|
9193
|
+
out.framework = server[1];
|
|
9194
|
+
out.source = "package.json";
|
|
9195
|
+
out.defaultPort = server[2];
|
|
9196
|
+
} else if (/\bnode\s+\S+\.(c|m)?js\b/.test(Object.values(scripts).join(" "))) {
|
|
9197
|
+
out.framework = "a Node server";
|
|
9198
|
+
out.source = "package.json";
|
|
9199
|
+
out.defaultPort = 3e3;
|
|
9200
|
+
} else {
|
|
9201
|
+
const fe = NODE_FRONTENDS.find(([dep]) => dep in deps);
|
|
9202
|
+
if (fe) out.frontendOnly = fe[1];
|
|
9203
|
+
return;
|
|
9204
|
+
}
|
|
9205
|
+
out.configuredPort = portFromScripts(scripts);
|
|
9206
|
+
out.start = scripts.dev ? "npm run dev" : scripts.start ? "npm start" : void 0;
|
|
9207
|
+
}
|
|
9208
|
+
function fromPython(dir, out) {
|
|
9209
|
+
for (const f of ["requirements.txt", "pyproject.toml", "Pipfile"]) {
|
|
9210
|
+
const text = readText(import_path4.default.join(dir, f));
|
|
9211
|
+
if (!text) continue;
|
|
9212
|
+
const hit = PY_SERVERS.find(([re]) => re.test(text));
|
|
9213
|
+
if (hit) {
|
|
9214
|
+
out.framework = hit[1];
|
|
9215
|
+
out.source = f;
|
|
9216
|
+
out.defaultPort = hit[2];
|
|
9217
|
+
return;
|
|
9218
|
+
}
|
|
9219
|
+
}
|
|
9220
|
+
}
|
|
9221
|
+
function fromOthers(dir, out) {
|
|
9222
|
+
const gomod = readText(import_path4.default.join(dir, "go.mod"));
|
|
9223
|
+
if (gomod) {
|
|
9224
|
+
const hit = GO_SERVERS.find(([re]) => re.test(gomod));
|
|
9225
|
+
out.framework = hit ? hit[1] : "a Go server";
|
|
9226
|
+
out.source = "go.mod";
|
|
9227
|
+
out.defaultPort = 8080;
|
|
9228
|
+
return;
|
|
9229
|
+
}
|
|
9230
|
+
const gemfile = readText(import_path4.default.join(dir, "Gemfile"));
|
|
9231
|
+
if (gemfile) {
|
|
9232
|
+
const hit = RUBY_SERVERS.find(([re]) => re.test(gemfile));
|
|
9233
|
+
if (hit) {
|
|
9234
|
+
out.framework = hit[1];
|
|
9235
|
+
out.source = "Gemfile";
|
|
9236
|
+
out.defaultPort = hit[2];
|
|
9237
|
+
return;
|
|
9238
|
+
}
|
|
9239
|
+
}
|
|
9240
|
+
for (const f of ["pom.xml", "build.gradle", "build.gradle.kts"]) {
|
|
9241
|
+
const text = readText(import_path4.default.join(dir, f));
|
|
9242
|
+
if (text && /spring-boot/.test(text)) {
|
|
9243
|
+
out.framework = "Spring Boot";
|
|
9244
|
+
out.source = f;
|
|
9245
|
+
out.defaultPort = 8080;
|
|
9246
|
+
return;
|
|
9247
|
+
}
|
|
9248
|
+
}
|
|
9249
|
+
}
|
|
9250
|
+
function portAnswers(port, timeoutMs = 300) {
|
|
9251
|
+
return new Promise((resolve4) => {
|
|
9252
|
+
const sock = import_net.default.connect({ port, host: "127.0.0.1" });
|
|
9253
|
+
const done = (v) => {
|
|
9254
|
+
sock.destroy();
|
|
9255
|
+
resolve4(v);
|
|
9256
|
+
};
|
|
9257
|
+
sock.setTimeout(timeoutMs, () => done(false));
|
|
9258
|
+
sock.once("connect", () => done(true));
|
|
9259
|
+
sock.once("error", () => done(false));
|
|
9260
|
+
});
|
|
9261
|
+
}
|
|
9262
|
+
async function liveSpec(port) {
|
|
9263
|
+
for (const p of SPEC_PATHS) {
|
|
9264
|
+
try {
|
|
9265
|
+
const r = await fetch(`http://localhost:${port}${p}`, { signal: AbortSignal.timeout(800) });
|
|
9266
|
+
if (!r.ok) continue;
|
|
9267
|
+
const text = (await r.text()).slice(0, 4096);
|
|
9268
|
+
if (/\b(openapi|swagger)\s*["']?\s*:/.test(text)) return `http://localhost:${port}${p}`;
|
|
9269
|
+
} catch {
|
|
9270
|
+
}
|
|
9271
|
+
}
|
|
9272
|
+
return void 0;
|
|
9273
|
+
}
|
|
9274
|
+
async function detectBackend(dir, opts = {}) {
|
|
9275
|
+
const out = {};
|
|
9276
|
+
try {
|
|
9277
|
+
if (import_fs5.default.existsSync(import_path4.default.join(dir, ".demo", "state.json"))) out.isDemo = true;
|
|
9278
|
+
fromNode(dir, out);
|
|
9279
|
+
if (!out.framework) fromPython(dir, out);
|
|
9280
|
+
if (!out.framework) fromOthers(dir, out);
|
|
9281
|
+
const spec2 = findSpecFile(dir);
|
|
9282
|
+
if (spec2) {
|
|
9283
|
+
out.specFile = spec2.file;
|
|
9284
|
+
if (spec2.port && !out.configuredPort) out.configuredPort = spec2.port;
|
|
9285
|
+
}
|
|
9286
|
+
} catch {
|
|
9287
|
+
}
|
|
9288
|
+
const preferred = [...new Set([out.configuredPort, out.defaultPort].filter((p) => !!p))];
|
|
9289
|
+
const ports = [.../* @__PURE__ */ new Set([...preferred, ...opts.ports ?? COMMON_PORTS])];
|
|
9290
|
+
const answering = await Promise.all(ports.map(async (p) => await portAnswers(p) ? p : 0));
|
|
9291
|
+
const up = answering.filter(Boolean);
|
|
9292
|
+
const mine = preferred.find((p) => up.includes(p));
|
|
9293
|
+
if (mine && (out.framework || out.specFile)) out.listening = mine;
|
|
9294
|
+
else if (!out.framework && !out.specFile && up.length) out.strayPorts = up;
|
|
9295
|
+
if (out.listening) out.specUrl = await liveSpec(out.listening);
|
|
9296
|
+
return out;
|
|
9297
|
+
}
|
|
9298
|
+
function hasBackend(f) {
|
|
9299
|
+
return !f.isDemo && !!(f.framework || f.specFile);
|
|
9300
|
+
}
|
|
9301
|
+
function backendPort(f) {
|
|
9302
|
+
return f.listening ?? f.configuredPort ?? f.defaultPort;
|
|
9303
|
+
}
|
|
9304
|
+
|
|
9305
|
+
// src/commands/skill.ts
|
|
9033
9306
|
var SKILL_NAME = "ship-safely";
|
|
9034
|
-
var SKILL_RELATIVE_PATH =
|
|
9307
|
+
var SKILL_RELATIVE_PATH = import_path6.default.join(".claude", "skills", SKILL_NAME, "SKILL.md");
|
|
9035
9308
|
var CP_TOOLS = [
|
|
9036
9309
|
"describe_server",
|
|
9037
9310
|
"propose_rules",
|
|
@@ -9046,9 +9319,82 @@ var CP_TOOLS = [
|
|
|
9046
9319
|
function withLatest(md) {
|
|
9047
9320
|
return md.replace(/npx (apiblaze|mcpblaze)(?![@\w-])/g, "npx $1@latest");
|
|
9048
9321
|
}
|
|
9049
|
-
function
|
|
9322
|
+
function pitchText() {
|
|
9323
|
+
const kind = BRAND === "mcpblaze" ? "MCP and API" : "API and MCP";
|
|
9324
|
+
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.`;
|
|
9325
|
+
}
|
|
9326
|
+
function appName(f) {
|
|
9327
|
+
const fw = f.framework ?? "an API";
|
|
9328
|
+
if (/^an? /.test(fw)) return fw[0].toUpperCase() + fw.slice(1);
|
|
9329
|
+
return `${/^[AEIOU]/i.test(fw) ? "An" : "A"} ${fw} app`;
|
|
9330
|
+
}
|
|
9331
|
+
function folderReport(f, opts = {}) {
|
|
9332
|
+
const P = B.product;
|
|
9333
|
+
const demoQuestion = "Want me to show you on a sample app? About 30 seconds, nothing to install.";
|
|
9334
|
+
if (f.isDemo) {
|
|
9335
|
+
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 };
|
|
9336
|
+
}
|
|
9337
|
+
if (!hasBackend(f)) {
|
|
9338
|
+
const extra = [
|
|
9339
|
+
f.frontendOnly ? ` It is a ${f.frontendOnly} frontend with no server of its own.` : "",
|
|
9340
|
+
// A person may know what that is; an agent would only be distracted by it.
|
|
9341
|
+
opts.forPerson && f.strayPorts?.length ? ` Something answers on localhost:${f.strayPorts[0]}, but nothing in this folder points at it.` : ""
|
|
9342
|
+
].join("");
|
|
9343
|
+
return { lines: [`No backend here: no server framework in the project files and no OpenAPI spec.${extra}`], question: demoQuestion, ownApi: false };
|
|
9344
|
+
}
|
|
9345
|
+
const port = backendPort(f);
|
|
9346
|
+
const spec2 = f.specFile ? `Spec: ${f.specFile}.` : f.specUrl ? `It serves its spec at ${f.specUrl}.` : "No OpenAPI spec yet.";
|
|
9347
|
+
const from = f.source ? ` (${f.source})` : "";
|
|
9348
|
+
if (f.listening) {
|
|
9349
|
+
return {
|
|
9350
|
+
lines: [`${appName(f)}${from}, answering on localhost:${f.listening} right now. ${spec2}`],
|
|
9351
|
+
question: `Want me to put your API on port ${f.listening} behind ${P}? About 30 seconds, no sign-up.`,
|
|
9352
|
+
ownApi: true
|
|
9353
|
+
};
|
|
9354
|
+
}
|
|
9355
|
+
const starts = f.start ? ` It starts with \`${f.start}\`` : " Its start command is not in its files";
|
|
9356
|
+
const listens = port ? ` and listens on ${port}.` : ".";
|
|
9357
|
+
return {
|
|
9358
|
+
lines: [`${appName(f)}${from}, not running.${starts}${listens} ${spec2}`],
|
|
9359
|
+
question: `Want me to start your app and put it behind ${P}? About a minute, no sign-up.`,
|
|
9360
|
+
ownApi: true
|
|
9361
|
+
};
|
|
9362
|
+
}
|
|
9363
|
+
function heroMarkdown(found) {
|
|
9050
9364
|
const P = B.product;
|
|
9051
9365
|
const run2 = `npx ${B.cli}@latest`;
|
|
9366
|
+
const report = found ? folderReport(found) : null;
|
|
9367
|
+
const port = found ? backendPort(found) : void 0;
|
|
9368
|
+
const folder = report ? `## This folder, checked by the CLI just now: read no files to decide what to offer
|
|
9369
|
+
|
|
9370
|
+
${report.lines.map((l) => `- ${l}`).join("\n")}
|
|
9371
|
+
` : `## First, the folder
|
|
9372
|
+
|
|
9373
|
+
Run \`${run2} skills\` in the project folder and follow what it prints: it checks the folder
|
|
9374
|
+
and gives the exact question to ask.
|
|
9375
|
+
`;
|
|
9376
|
+
const question = report?.question ?? "<the question it prints>";
|
|
9377
|
+
const ownApi = report?.ownApi && found ? (() => {
|
|
9378
|
+
const specArg = found.specFile ? ` --openapi ${found.specFile}` : "";
|
|
9379
|
+
const steps = [`Say one line: "Putting your API behind ${P}, about 30 seconds."`];
|
|
9380
|
+
if (!found.listening) {
|
|
9381
|
+
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.`);
|
|
9382
|
+
}
|
|
9383
|
+
if (!found.specFile && !found.specUrl) {
|
|
9384
|
+
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."');
|
|
9385
|
+
}
|
|
9386
|
+
const spec2 = found.specFile ? specArg : !found.specUrl ? " --openapi ./openapi.yaml" : "";
|
|
9387
|
+
steps.push(`Open the tunnel as its own detached process, never \`cmd &\` inside a shell call that ends:
|
|
9388
|
+
\`nohup ${run2} dev --port ${port ?? "<port>"}${spec2} --auto > ${B.cli}-dev.log 2>&1 < /dev/null &\`
|
|
9389
|
+
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.`);
|
|
9390
|
+
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.`);
|
|
9391
|
+
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".`);
|
|
9392
|
+
return `
|
|
9393
|
+
## Their API: they say yes to putting it behind ${P}
|
|
9394
|
+
|
|
9395
|
+
${steps.map((t, i) => `${i + 1}. ${t}`).join("\n")}
|
|
9396
|
+
`;
|
|
9397
|
+
})() : "";
|
|
9052
9398
|
return `---
|
|
9053
9399
|
name: ${SKILL_NAME}
|
|
9054
9400
|
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 +9402,16 @@ description: Use when the user asks what ${P} (or ${B.cli}) does or can do for t
|
|
|
9056
9402
|
|
|
9057
9403
|
# ${P}
|
|
9058
9404
|
|
|
9059
|
-
|
|
9405
|
+
${folder}
|
|
9406
|
+
## Explain: they ask what ${P} does or can do for them
|
|
9060
9407
|
|
|
9061
|
-
Run nothing. Say exactly this and nothing else: no list of use cases, no commands, no options. Then stop:
|
|
9408
|
+
Run nothing else. Say exactly this and nothing else: no list of use cases, no commands, no options. Then stop:
|
|
9062
9409
|
|
|
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?"
|
|
9410
|
+
"${pitchText()} ${question}"
|
|
9066
9411
|
|
|
9067
|
-
Do not use the words proxy,
|
|
9068
|
-
|
|
9069
|
-
## Demo: they say yes, or ask to see or try it
|
|
9412
|
+
Do not use the words proxy, tenant, OpenAPI or header unless the user does first.
|
|
9413
|
+
${ownApi}
|
|
9414
|
+
## Demo: they say yes to the sample app, or ask to see or try it
|
|
9070
9415
|
|
|
9071
9416
|
1. Say one line: "Starting the demo, about 30 seconds."
|
|
9072
9417
|
2. Run exactly this, in the foreground. It needs no login and asks nothing. It sets
|
|
@@ -9083,10 +9428,17 @@ Rules for the demo: keep each update to one line. Do not narrate steps, read the
|
|
|
9083
9428
|
files, open logs, run other commands, or write code. Never show a key: the demo saves it
|
|
9084
9429
|
in \`.env.local\` and never prints it.
|
|
9085
9430
|
|
|
9086
|
-
## Their own app: they say yes
|
|
9431
|
+
## Their own app: they say yes after the demo
|
|
9087
9432
|
|
|
9088
9433
|
Their app runs on this machine \u2192 it will use \`${run2} dev\`; it is deployed \u2192 \`${run2} create\`.
|
|
9089
|
-
Run \`${run2} skills
|
|
9434
|
+
Run \`${run2} skills ship\` and follow what it prints.
|
|
9435
|
+
|
|
9436
|
+
## One thing at a time, later
|
|
9437
|
+
|
|
9438
|
+
When they ask about one thing, read only its page: \`${run2} skills keys\` (API keys) \xB7
|
|
9439
|
+
\`signin\` (OAuth sign-in and the developer portal) \xB7 \`rules\` (who can change what) \xB7
|
|
9440
|
+
\`limits\` (rate limits and quotas) \xB7 \`mcp\` (AI agents) \xB7 \`widgets\` (keys, users and chat on
|
|
9441
|
+
their site) \xB7 \`ship\` (the whole procedure for their own app).
|
|
9090
9442
|
`;
|
|
9091
9443
|
}
|
|
9092
9444
|
function shipMarkdown() {
|
|
@@ -9299,8 +9651,11 @@ ONLY for what the scan could not find \u2014 one short list of questions, not an
|
|
|
9299
9651
|
read the log. If it stops, re-run the SAME command: the same \`--name\` reattaches and
|
|
9300
9652
|
creates nothing. Never pick a new name to get it back \u2014 that is a second proxy. **Deployed code** \u2192
|
|
9301
9653
|
\`npx ${cli} create --target <url-or-spec> --name <name> --auto <door flags>\`. Both print the
|
|
9302
|
-
URL, the MCP URL (the "agents:" line)
|
|
9303
|
-
|
|
9654
|
+
URL, the MCP URL (the "agents:" line) and what is locked. Run by you (no terminal), \`dev\`
|
|
9655
|
+
saves the bootstrap key to \`.env.local\` as \`APIBLAZE_SERVER_KEY\` and never prints it;
|
|
9656
|
+
\`create\` prints it ONCE \u2014 save it there yourself. Either way say only "saved your key to
|
|
9657
|
+
.env.local": that key goes in the frontend-server's env, never a browser, a repo, an agent
|
|
9658
|
+
config or the chat. The tenant is
|
|
9304
9659
|
not labelled: read it from the \`admins add \u2026 --tenant <tenant>\` hint (it is also the middle
|
|
9305
9660
|
of the MCP hostname). Save \`APIBLAZE_URL\`, \`APIBLAZE_SERVER_KEY\`, \`APIBLAZE_TENANT\` in
|
|
9306
9661
|
the frontend-server's env (\`.env.local\` for Next.js). Then prove it with three curls and
|
|
@@ -9335,6 +9690,8 @@ ONLY for what the scan could not find \u2014 one short list of questions, not an
|
|
|
9335
9690
|
- the command to chat with their API: \`npx ${cli} apichat <name>\` (add
|
|
9336
9691
|
\`--xenduserid ana\` when \xA75b is on).
|
|
9337
9692
|
Then ask once: "Would you like me to add the self-serve API key widget to your site?"
|
|
9693
|
+
After that answer, offer once: "Want me to save this as a skill in your project, so I know
|
|
9694
|
+
${P} next time?" Yes \u2192 \`npx ${cli} skills --install\`.
|
|
9338
9695
|
|
|
9339
9696
|
## Report like this
|
|
9340
9697
|
|
|
@@ -9410,36 +9767,184 @@ through one MCP: \`claude mcp add --transport http ${BRAND} ${B.controlPlaneMcp}
|
|
|
9410
9767
|
${CP_TOOLS.join(", ")} \u2014 the same steps, one tool each.
|
|
9411
9768
|
`;
|
|
9412
9769
|
}
|
|
9770
|
+
function sectionOf(md, heading) {
|
|
9771
|
+
const at = md.indexOf(`
|
|
9772
|
+
${heading}`);
|
|
9773
|
+
if (at < 0) return "";
|
|
9774
|
+
const next = md.indexOf("\n## ", at + heading.length + 1);
|
|
9775
|
+
return md.slice(at + 1, next < 0 ? void 0 : next).trim();
|
|
9776
|
+
}
|
|
9777
|
+
var TOPICS = ["ship", "keys", "signin", "rules", "limits", "mcp", "widgets"];
|
|
9778
|
+
function topicMarkdown(topic) {
|
|
9779
|
+
const P = B.product;
|
|
9780
|
+
const cli = B.cli;
|
|
9781
|
+
const legacy = legacySkillMarkdown();
|
|
9782
|
+
const page = (title, body) => withLatest(`# ${title}
|
|
9783
|
+
|
|
9784
|
+
${body.trim()}
|
|
9785
|
+
`);
|
|
9786
|
+
switch (topic) {
|
|
9787
|
+
case "ship":
|
|
9788
|
+
return shipMarkdown();
|
|
9789
|
+
case "keys":
|
|
9790
|
+
return page(`API keys with ${P}`, `
|
|
9791
|
+
Every ${B.thingShort} has a key door. Three kinds of key for a tenant (one of their customers), one flag each:
|
|
9792
|
+
- \`npx ${cli} apikeys mint --tenant <tenant> --backend\`: a server key. Their server sends it as
|
|
9793
|
+
\`X-API-Key\` and names the person in \`X-End-User-Id\`.
|
|
9794
|
+
- \`--client\`: a key that cannot name a user (a public client, a script).
|
|
9795
|
+
- \`--for <email>\`: bound to one person, like an agent or a CI job. Calls count as that person, and
|
|
9796
|
+
ownership rules trust it. It is a DIFFERENT identity from a bare \`X-End-User-Id\` string: rows
|
|
9797
|
+
created as "ana" through the header are not owned by ana@example.com's key.
|
|
9798
|
+
|
|
9799
|
+
Their customers get a key per tenant from the self-serve keys widget (\`npx ${cli} skills widgets\`):
|
|
9800
|
+
each customer mints their own, in their own tenant. The bootstrap key is only for their own server.
|
|
9801
|
+
Never show a key in the chat: save it to \`.env.local\` and say "saved your key to .env.local".`);
|
|
9802
|
+
case "signin":
|
|
9803
|
+
return page(`Sign-in (OAuth) with ${P}`, `
|
|
9804
|
+
${sectionOf(legacy, "## Sign-in shapes")}
|
|
9805
|
+
|
|
9806
|
+
Each tenant has its own developer portal, where people sign in and get a key:
|
|
9807
|
+
\`https://<tenant>.portal.apiblaze.com/<version>\`. A tenant added later gets the same sign-in:
|
|
9808
|
+
\`npx ${cli} tenant attach <name> --tenant <tenant>\`.
|
|
9809
|
+
|
|
9810
|
+
Tenant admins, by email, even before that person ever signs in:
|
|
9811
|
+
\`npx ${cli} admins add <email> --tenant <tenant>\` \xB7 \`admins list --tenant <tenant>\` \xB7 \`admins remove <email> --tenant <tenant>\`.`);
|
|
9812
|
+
case "rules":
|
|
9813
|
+
return page("Who can change what", `
|
|
9814
|
+
- With a spec, \`dev\` and \`create\` lock every resource to the person who created it.
|
|
9815
|
+
- \`npx ${cli} rule <name>\` shows the rules. \`npx ${cli} rule <name> "<sentence>"\` adds one in plain
|
|
9816
|
+
words, for example "Only the person who made a reservation can change or cancel it". Both need
|
|
9817
|
+
a login (\`npx ${cli} login\`); an anonymous workspace keeps the creator locks \`dev\` applied.
|
|
9818
|
+
- \`npx ${cli} identified <name> require\`: every call must name a person (\`X-End-User-Id\` or a
|
|
9819
|
+
sign-in token). \`allow-anon\` undoes it.
|
|
9820
|
+
- \`npx ${cli} config <name> authorization.enforce_authorization false\` turns enforcement off
|
|
9821
|
+
without losing the rules.
|
|
9822
|
+
- The gateway locks single records. Lists are the backend's job: filter every collection GET by
|
|
9823
|
+
\`x-abz-tenant-id\` AND \`x-abz-user-id\`, since a user id is not unique across tenants.`);
|
|
9824
|
+
case "limits":
|
|
9825
|
+
return page("Rate limits and quotas", `
|
|
9826
|
+
Every new ${B.thingShort} has a rate limit of 10 requests/s per consumer (100 per 10 s) and a quota
|
|
9827
|
+
of 3,000 a day. Say so in those words (rate limit, quota).
|
|
9828
|
+
- \`npx ${cli} limits get <name>\` shows them.
|
|
9829
|
+
- \`npx ${cli} limits set <name> --rate <n> --quota <n> --period daily\`, plus \`--spend-cap <usd>\` for
|
|
9830
|
+
a monthly spend cap.
|
|
9831
|
+
- Rates are per consumer key and approximate per location. A rate above the self-serve ceiling is
|
|
9832
|
+
refused with the exact \`limits request-max\` command to run: relay it, do not retry.
|
|
9833
|
+
- It works logged out; an anonymous workspace is capped until claimed.`);
|
|
9834
|
+
case "mcp":
|
|
9835
|
+
return page(`AI agents (MCP) with ${P}`, `
|
|
9836
|
+
- Every ${B.thingShort} has an MCP address for AI agents, one per tenant: use the "MCP" line that
|
|
9837
|
+
\`dev\` or \`create\` printed. Its tools are the spec's operations.
|
|
9838
|
+
- Agents sign in with the tenant's sign-in (OAuth), or use a person-bound key:
|
|
9839
|
+
\`npx ${cli} apikeys mint --tenant <tenant> --for <email>\`. Never hand an agent a server key: it
|
|
9840
|
+
names no person, so protected routes refuse it.
|
|
9841
|
+
- Chat with the API from the terminal: \`npx ${cli} apichat <name>\` (add \`--xenduserid ana\` when calls
|
|
9842
|
+
must name a person). Connect an agent: \`npx ${cli} apichat <name> --install-mcp claude\`.
|
|
9843
|
+
|
|
9844
|
+
${sectionOf(legacy, "## No shell? The control-plane MCP")}`);
|
|
9845
|
+
case "widgets":
|
|
9846
|
+
return page(`Keys, users and chat on their site`, sectionOf(legacy, "## Wiring a real site").replace(/^## Wiring a real site\n+/, ""));
|
|
9847
|
+
default:
|
|
9848
|
+
return null;
|
|
9849
|
+
}
|
|
9850
|
+
}
|
|
9851
|
+
function readByAgent(opts) {
|
|
9852
|
+
if (opts.agent) return true;
|
|
9853
|
+
if (process.env.CLAUDECODE || process.env.APIBLAZE_AGENT) return true;
|
|
9854
|
+
return !(process.stdin.isTTY && process.stdout.isTTY);
|
|
9855
|
+
}
|
|
9856
|
+
async function ask2(question) {
|
|
9857
|
+
const readline3 = await import("readline");
|
|
9858
|
+
const rl = readline3.createInterface({ input: process.stdin, output: process.stdout });
|
|
9859
|
+
const answer = await new Promise((res) => rl.question(question, res));
|
|
9860
|
+
rl.close();
|
|
9861
|
+
return !/^n/i.test(answer.trim());
|
|
9862
|
+
}
|
|
9863
|
+
async function runForPerson(found) {
|
|
9864
|
+
const P = B.product;
|
|
9865
|
+
const run2 = `npx ${B.cli}@latest`;
|
|
9866
|
+
console.log();
|
|
9867
|
+
console.log(` ${import_chalk25.default.bold(pitchText().split(". ")[0] + ".")}`);
|
|
9868
|
+
console.log(` ${pitchText().split(". ").slice(1).join(". ")}`);
|
|
9869
|
+
console.log();
|
|
9870
|
+
console.log(import_chalk25.default.dim(` With an AI assistant? Tell it: "Tell me what ${B.cli} can do for me using ${run2} skills"`));
|
|
9871
|
+
console.log();
|
|
9872
|
+
const report = folderReport(found, { forPerson: true });
|
|
9873
|
+
for (const l of report.lines) console.log(` ${l.replace(/`/g, "")}`);
|
|
9874
|
+
const port = backendPort(found);
|
|
9875
|
+
if (report.ownApi && found.listening) {
|
|
9876
|
+
const args = ["dev", "--port", String(found.listening), ...found.specFile ? ["--openapi", found.specFile] : []];
|
|
9877
|
+
if (!await ask2(` ${import_chalk25.default.bold("?")} Put your API on port ${found.listening} behind ${P} now? ${import_chalk25.default.dim("(Y/n)")} `)) {
|
|
9878
|
+
console.log(` Any time: ${import_chalk25.default.cyan(`${run2} ${args.join(" ")}`)}`);
|
|
9879
|
+
return;
|
|
9880
|
+
}
|
|
9881
|
+
console.log(import_chalk25.default.dim(` $ ${run2} ${args.join(" ")}`));
|
|
9882
|
+
const { spawnSync: spawnSync4 } = await import("child_process");
|
|
9883
|
+
const r = spawnSync4(process.execPath, [process.argv[1], ...args], { stdio: "inherit" });
|
|
9884
|
+
process.exitCode = r.status ?? 0;
|
|
9885
|
+
return;
|
|
9886
|
+
}
|
|
9887
|
+
if (report.ownApi) {
|
|
9888
|
+
console.log(` Start it${found.start ? ` (${found.start})` : ""}, then put it on the internet:`);
|
|
9889
|
+
console.log(` ${import_chalk25.default.cyan(`${run2} dev --port ${port ?? "<port>"}${found.specFile ? ` --openapi ${found.specFile}` : ""}`)}`);
|
|
9890
|
+
return;
|
|
9891
|
+
}
|
|
9892
|
+
if (!await ask2(` ${import_chalk25.default.bold("?")} See it on a sample app first? ${import_chalk25.default.dim("(Y/n)")} `)) {
|
|
9893
|
+
console.log(` Your own app, when it runs: ${import_chalk25.default.cyan(`${run2} dev --port <port>`)}`);
|
|
9894
|
+
return;
|
|
9895
|
+
}
|
|
9896
|
+
const { runDemo: runDemo2 } = await Promise.resolve().then(() => (init_demo(), demo_exports));
|
|
9897
|
+
await runDemo2({});
|
|
9898
|
+
}
|
|
9413
9899
|
async function runSkill(opts = {}) {
|
|
9414
|
-
if (
|
|
9415
|
-
|
|
9900
|
+
if (opts.install) return installSkill(opts);
|
|
9901
|
+
if (opts.static) {
|
|
9902
|
+
process.stdout.write(skillMarkdown());
|
|
9903
|
+
return;
|
|
9904
|
+
}
|
|
9905
|
+
const topic = opts.ship ? "ship" : opts.topic;
|
|
9906
|
+
if (topic) {
|
|
9907
|
+
const md = topicMarkdown(topic);
|
|
9908
|
+
if (!md) {
|
|
9909
|
+
console.error(`Unknown page "${topic}". Pages: ${TOPICS.join(" \xB7 ")}`);
|
|
9910
|
+
process.exit(1);
|
|
9911
|
+
}
|
|
9912
|
+
process.stdout.write(md);
|
|
9416
9913
|
return;
|
|
9417
9914
|
}
|
|
9915
|
+
const found = await detectBackend(process.cwd());
|
|
9916
|
+
if (readByAgent(opts)) {
|
|
9917
|
+
process.stdout.write(heroMarkdown(found));
|
|
9918
|
+
return;
|
|
9919
|
+
}
|
|
9920
|
+
await runForPerson(found);
|
|
9921
|
+
}
|
|
9922
|
+
function installSkill(opts) {
|
|
9418
9923
|
const md = skillMarkdown();
|
|
9419
|
-
const target2 =
|
|
9420
|
-
if (
|
|
9421
|
-
console.error(
|
|
9924
|
+
const target2 = import_path6.default.resolve(process.cwd(), SKILL_RELATIVE_PATH);
|
|
9925
|
+
if (import_fs7.default.existsSync(target2) && !opts.force) {
|
|
9926
|
+
console.error(import_chalk25.default.yellow(`${SKILL_RELATIVE_PATH} already exists \u2014 re-run with --force to overwrite it.`));
|
|
9422
9927
|
process.exit(1);
|
|
9423
9928
|
}
|
|
9424
|
-
|
|
9425
|
-
|
|
9426
|
-
console.log(`${
|
|
9427
|
-
console.log(
|
|
9929
|
+
import_fs7.default.mkdirSync(import_path6.default.dirname(target2), { recursive: true });
|
|
9930
|
+
import_fs7.default.writeFileSync(target2, md, "utf8");
|
|
9931
|
+
console.log(`${import_chalk25.default.green("\u2713")} Installed the ${import_chalk25.default.bold(SKILL_NAME)} skill: ${import_chalk25.default.bold(target2)}`);
|
|
9932
|
+
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
9933
|
}
|
|
9429
9934
|
|
|
9430
9935
|
// src/commands/integration.ts
|
|
9431
|
-
var
|
|
9432
|
-
var
|
|
9936
|
+
var import_chalk26 = __toESM(require("chalk"));
|
|
9937
|
+
var import_ora10 = __toESM(require("ora"));
|
|
9433
9938
|
init_caller();
|
|
9434
9939
|
init_api();
|
|
9435
9940
|
init_brand();
|
|
9436
9941
|
function file(f, indent2 = " ") {
|
|
9437
|
-
console.log(` ${
|
|
9942
|
+
console.log(` ${import_chalk26.default.cyan(f.file)}`);
|
|
9438
9943
|
for (const line of f.code.split("\n")) console.log(`${indent2}${line}`);
|
|
9439
9944
|
console.log();
|
|
9440
9945
|
}
|
|
9441
9946
|
function bullets(lines, indent2 = " ") {
|
|
9442
|
-
for (const l of lines) console.log(`${indent2}${
|
|
9947
|
+
for (const l of lines) console.log(`${indent2}${import_chalk26.default.dim("\u2022")} ${l}`);
|
|
9443
9948
|
}
|
|
9444
9949
|
async function runIntegration(name, opts = {}) {
|
|
9445
9950
|
const stackRaw = (opts.stack ?? "nextjs").trim().toLowerCase();
|
|
@@ -9450,7 +9955,7 @@ async function runIntegration(name, opts = {}) {
|
|
|
9450
9955
|
const project = name.trim().toLowerCase();
|
|
9451
9956
|
const caller = requireCaller();
|
|
9452
9957
|
if (opts.team && caller.mode === "anon") throw new Error("--team is not available in an anonymous workspace.");
|
|
9453
|
-
const spinner = opts.json ? null : (0,
|
|
9958
|
+
const spinner = opts.json ? null : (0, import_ora10.default)(`Rendering the ${stack} kit for ${project}\u2026`).start();
|
|
9454
9959
|
let kit;
|
|
9455
9960
|
try {
|
|
9456
9961
|
kit = await producer(caller, integrationKitCall(project, stack));
|
|
@@ -9464,44 +9969,44 @@ async function runIntegration(name, opts = {}) {
|
|
|
9464
9969
|
return;
|
|
9465
9970
|
}
|
|
9466
9971
|
console.log();
|
|
9467
|
-
console.log(
|
|
9468
|
-
console.log(
|
|
9972
|
+
console.log(import_chalk26.default.bold(`Integration kit \u2014 ${kit.project} (${kit.stack})`));
|
|
9973
|
+
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
9974
|
console.log();
|
|
9470
|
-
console.log(` ${
|
|
9471
|
-
console.log(` ${
|
|
9472
|
-
console.log(` ${
|
|
9473
|
-
console.log(` ${
|
|
9975
|
+
console.log(` ${import_chalk26.default.dim("Proxy: ")} ${import_chalk26.default.bold(kit.urls.proxy)}`);
|
|
9976
|
+
console.log(` ${import_chalk26.default.dim("MCP: ")} ${kit.urls.mcp.join(import_chalk26.default.dim(" \xB7 "))}`);
|
|
9977
|
+
console.log(` ${import_chalk26.default.dim("Portal: ")} ${kit.urls.portal}`);
|
|
9978
|
+
console.log(` ${import_chalk26.default.dim("Spec: ")} ${kit.urls.openapi}`);
|
|
9474
9979
|
const w = kit["1_frontend_widgets"];
|
|
9475
9980
|
console.log();
|
|
9476
|
-
console.log(
|
|
9477
|
-
console.log(
|
|
9981
|
+
console.log(import_chalk26.default.bold("1. Frontend \u2014 the widgets (keys + chat)"));
|
|
9982
|
+
console.log(import_chalk26.default.dim(` ${w.note}`));
|
|
9478
9983
|
console.log();
|
|
9479
9984
|
file(w.frontend);
|
|
9480
9985
|
for (const r of w.server_routes) file(r);
|
|
9481
9986
|
const c = kit["2_frontend_server_call_through"];
|
|
9482
|
-
console.log(
|
|
9987
|
+
console.log(import_chalk26.default.bold("2. Frontend-server \u2014 call the proxy with the server key and say who is calling"));
|
|
9483
9988
|
console.log();
|
|
9484
9989
|
file(c);
|
|
9485
9990
|
const t = kit["3_backend_trust_contract"];
|
|
9486
|
-
console.log(
|
|
9487
|
-
for (const [h, why] of Object.entries(t.headers_you_receive)) console.log(` ${
|
|
9991
|
+
console.log(import_chalk26.default.bold("3. Backend \u2014 trust the identity headers, and nothing else"));
|
|
9992
|
+
for (const [h, why] of Object.entries(t.headers_you_receive)) console.log(` ${import_chalk26.default.cyan(h)} \u2014 ${why}`);
|
|
9488
9993
|
console.log();
|
|
9489
|
-
console.log(` ${
|
|
9994
|
+
console.log(` ${import_chalk26.default.dim("How to verify:")}`);
|
|
9490
9995
|
bullets(t.how_to_verify);
|
|
9491
9996
|
console.log();
|
|
9492
|
-
console.log(` ${
|
|
9997
|
+
console.log(` ${import_chalk26.default.dim("Never:")}`);
|
|
9493
9998
|
bullets(t.what_not_to_do);
|
|
9494
9999
|
console.log();
|
|
9495
|
-
console.log(
|
|
9496
|
-
for (const [k, v] of Object.entries(kit["4_env_vars"])) console.log(` ${
|
|
10000
|
+
console.log(import_chalk26.default.bold("4. Env vars (server only)"));
|
|
10001
|
+
for (const [k, v] of Object.entries(kit["4_env_vars"])) console.log(` ${import_chalk26.default.cyan(k)} ${import_chalk26.default.dim("=")} ${v}`);
|
|
9497
10002
|
const o = kit["5_what_stays_open"];
|
|
9498
10003
|
console.log();
|
|
9499
|
-
console.log(
|
|
9500
|
-
console.log(` ${
|
|
9501
|
-
console.log(` ${
|
|
9502
|
-
console.log(
|
|
10004
|
+
console.log(import_chalk26.default.bold("5. What stays open"));
|
|
10005
|
+
console.log(` ${import_chalk26.default.dim("Protected by a rule:")} ${o.protected.length ? o.protected.join(", ") : import_chalk26.default.dim("nothing")}`);
|
|
10006
|
+
console.log(` ${import_chalk26.default.dim("Open to any identified caller:")} ${o.open.length ? o.open.join(", ") : import_chalk26.default.dim("nothing")}`);
|
|
10007
|
+
console.log(import_chalk26.default.dim(` ${o.note}`));
|
|
9503
10008
|
console.log();
|
|
9504
|
-
console.log(
|
|
10009
|
+
console.log(import_chalk26.default.dim(` Raw JSON: npx ${B.cli} integration ${kit.project} --stack ${kit.stack} --json`));
|
|
9505
10010
|
console.log();
|
|
9506
10011
|
}
|
|
9507
10012
|
|
|
@@ -9509,14 +10014,14 @@ async function runIntegration(name, opts = {}) {
|
|
|
9509
10014
|
init_claim();
|
|
9510
10015
|
|
|
9511
10016
|
// src/commands/logout.ts
|
|
9512
|
-
var
|
|
10017
|
+
var import_chalk27 = __toESM(require("chalk"));
|
|
9513
10018
|
init_auth();
|
|
9514
10019
|
init_consumer_auth();
|
|
9515
10020
|
async function runLogout(opts = {}) {
|
|
9516
10021
|
const creds = loadCredentials();
|
|
9517
10022
|
const consumer2 = loadConsumer();
|
|
9518
10023
|
if (!creds && !consumer2) {
|
|
9519
|
-
console.log(
|
|
10024
|
+
console.log(import_chalk27.default.yellow("You were not logged in."));
|
|
9520
10025
|
return;
|
|
9521
10026
|
}
|
|
9522
10027
|
let target2;
|
|
@@ -9543,18 +10048,18 @@ async function runLogout(opts = {}) {
|
|
|
9543
10048
|
}
|
|
9544
10049
|
if (target2 === "producer" || target2 === "all") {
|
|
9545
10050
|
clearCredentials();
|
|
9546
|
-
console.log(
|
|
10051
|
+
console.log(import_chalk27.default.green(`\u2714 Logged out of API Producer${creds?.githubHandle ? ` (${creds.githubHandle})` : ""}.`));
|
|
9547
10052
|
}
|
|
9548
10053
|
if (target2 === "consumer" || target2 === "all") {
|
|
9549
10054
|
clearConsumer();
|
|
9550
|
-
console.log(
|
|
10055
|
+
console.log(import_chalk27.default.green(`\u2714 Logged out of API Consumer${consumer2?.tenant ? ` (${consumer2.tenant})` : ""}.`));
|
|
9551
10056
|
}
|
|
9552
10057
|
}
|
|
9553
10058
|
|
|
9554
10059
|
// src/commands/flush.ts
|
|
9555
10060
|
var import_chalk28 = __toESM(require("chalk"));
|
|
9556
|
-
var
|
|
9557
|
-
var
|
|
10061
|
+
var fs15 = __toESM(require("fs"));
|
|
10062
|
+
var path13 = __toESM(require("path"));
|
|
9558
10063
|
init_auth();
|
|
9559
10064
|
init_external_mcp();
|
|
9560
10065
|
async function runFlush(opts) {
|
|
@@ -9562,7 +10067,7 @@ async function runFlush(opts) {
|
|
|
9562
10067
|
const demos = await stopDemos2().catch(() => 0);
|
|
9563
10068
|
if (demos) console.log(import_chalk28.default.dim(` Stopped ${demos} running demo(s).`));
|
|
9564
10069
|
const dir = getApiblazeDir();
|
|
9565
|
-
const entries2 =
|
|
10070
|
+
const entries2 = fs15.existsSync(dir) ? fs15.readdirSync(dir) : [];
|
|
9566
10071
|
if (!entries2.length) {
|
|
9567
10072
|
console.log(import_chalk28.default.dim("Nothing to flush \u2014 no local apiblaze config found."));
|
|
9568
10073
|
return;
|
|
@@ -9575,7 +10080,7 @@ async function runFlush(opts) {
|
|
|
9575
10080
|
const footprint = apiblazeMcpFootprint();
|
|
9576
10081
|
const mdFiles = ["claude", "codex"].map((k) => ({ k, file: agentInstructionFile(k) })).filter(({ file: file2 }) => {
|
|
9577
10082
|
try {
|
|
9578
|
-
return
|
|
10083
|
+
return fs15.readFileSync(file2, "utf-8").includes("<!-- apiblaze:mcp:");
|
|
9579
10084
|
} catch {
|
|
9580
10085
|
return false;
|
|
9581
10086
|
}
|
|
@@ -9584,7 +10089,7 @@ async function runFlush(opts) {
|
|
|
9584
10089
|
console.log(import_chalk28.default.dim(` \u2022 MCP servers installed in other tools: ${[...footprint.claude, ...footprint.codex].join(", ")}`));
|
|
9585
10090
|
}
|
|
9586
10091
|
if (mdFiles.length) {
|
|
9587
|
-
console.log(import_chalk28.default.dim(` \u2022 the "use this MCP" blocks in ${mdFiles.map((m) =>
|
|
10092
|
+
console.log(import_chalk28.default.dim(` \u2022 the "use this MCP" blocks in ${mdFiles.map((m) => path13.basename(m.file)).join(" and ")} (here)`));
|
|
9588
10093
|
}
|
|
9589
10094
|
console.log(import_chalk28.default.dim(" It does NOT delete server-side proxies/keys \u2014 those GC or are revoked separately."));
|
|
9590
10095
|
if (!opts.yes) {
|
|
@@ -9600,7 +10105,7 @@ async function runFlush(opts) {
|
|
|
9600
10105
|
let removed = 0;
|
|
9601
10106
|
for (const e of entries2) {
|
|
9602
10107
|
try {
|
|
9603
|
-
|
|
10108
|
+
fs15.rmSync(path13.join(dir, e), { recursive: true, force: true });
|
|
9604
10109
|
removed++;
|
|
9605
10110
|
} catch (err) {
|
|
9606
10111
|
console.log(import_chalk28.default.red(` \u2717 could not remove ${e}: ${err instanceof Error ? err.message : "error"}`));
|
|
@@ -9615,14 +10120,14 @@ async function runFlush(opts) {
|
|
|
9615
10120
|
for (const { k, file: file2 } of mdFiles) {
|
|
9616
10121
|
let text = "";
|
|
9617
10122
|
try {
|
|
9618
|
-
text =
|
|
10123
|
+
text = fs15.readFileSync(file2, "utf-8");
|
|
9619
10124
|
} catch {
|
|
9620
10125
|
continue;
|
|
9621
10126
|
}
|
|
9622
10127
|
const names = [...text.matchAll(/<!-- apiblaze:mcp:([^\s>]+) -->/g)].map((m) => m[1]);
|
|
9623
10128
|
let cleared = 0;
|
|
9624
10129
|
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(
|
|
10130
|
+
if (cleared) console.log(` ${import_chalk28.default.dim("\u2022")} removed ${cleared} APIblaze block(s) from ${import_chalk28.default.bold(path13.basename(file2))}`);
|
|
9626
10131
|
}
|
|
9627
10132
|
console.log(`${import_chalk28.default.green("\u2714")} Flushed \u2014 removed ${removed} item(s). You're fully logged out and clean.`);
|
|
9628
10133
|
console.log(import_chalk28.default.dim(" Next `apiblaze create` starts a brand-new workspace with nothing carried over."));
|
|
@@ -9941,11 +10446,11 @@ async function runAgentChatRepl(opts) {
|
|
|
9941
10446
|
}
|
|
9942
10447
|
}
|
|
9943
10448
|
const rl = import_readline.default.createInterface({ input: process.stdin, output: process.stdout });
|
|
9944
|
-
const
|
|
10449
|
+
const ask3 = (q) => new Promise((res) => rl.question(q, res));
|
|
9945
10450
|
await turn(seedPrompt);
|
|
9946
10451
|
for (; ; ) {
|
|
9947
10452
|
console.log(footer());
|
|
9948
|
-
const line = (await
|
|
10453
|
+
const line = (await ask3(import_chalk32.default.green("you \u203A "))).trim();
|
|
9949
10454
|
if (!line) continue;
|
|
9950
10455
|
const lower = line.toLowerCase();
|
|
9951
10456
|
if (line === "/exit" || line === "/quit" || lower === "exit" || lower === "quit" || lower === "q") {
|
|
@@ -10323,7 +10828,7 @@ async function runOpenapi(projectArg, apiVersionArg) {
|
|
|
10323
10828
|
}
|
|
10324
10829
|
|
|
10325
10830
|
// src/commands/mcp.ts
|
|
10326
|
-
var
|
|
10831
|
+
var import_fs8 = __toESM(require("fs"));
|
|
10327
10832
|
var import_chalk37 = __toESM(require("chalk"));
|
|
10328
10833
|
init_brand();
|
|
10329
10834
|
init_auth();
|
|
@@ -10535,7 +11040,7 @@ async function runMcpPrompts(action2, project, name, opts) {
|
|
|
10535
11040
|
if (!opts.file) throw new Error(`Usage: ${B.cli} mcp prompts set <project> <name> --file prompt.json`);
|
|
10536
11041
|
let raw;
|
|
10537
11042
|
try {
|
|
10538
|
-
raw = JSON.parse(
|
|
11043
|
+
raw = JSON.parse(import_fs8.default.readFileSync(opts.file, "utf8"));
|
|
10539
11044
|
} catch (e) {
|
|
10540
11045
|
throw new Error(`Could not read ${opts.file}: ${e instanceof Error ? e.message : String(e)}`);
|
|
10541
11046
|
}
|
|
@@ -10634,7 +11139,7 @@ function describeJob(job) {
|
|
|
10634
11139
|
}
|
|
10635
11140
|
async function pollJob(jobId, opts = {}) {
|
|
10636
11141
|
const started = Date.now();
|
|
10637
|
-
const
|
|
11142
|
+
const path16 = jobPath(jobId, opts.teamId);
|
|
10638
11143
|
let lastPhase = "";
|
|
10639
11144
|
if (!opts.quiet) process.stdout.write(import_chalk38.default.dim(opts.label ?? "Deleting"));
|
|
10640
11145
|
for (; ; ) {
|
|
@@ -10646,7 +11151,7 @@ async function pollJob(jobId, opts = {}) {
|
|
|
10646
11151
|
);
|
|
10647
11152
|
}
|
|
10648
11153
|
await new Promise((r) => setTimeout(r, POLL_MS));
|
|
10649
|
-
const job = await admin({ method: "GET", path:
|
|
11154
|
+
const job = await admin({ method: "GET", path: path16, summary: "poll cascade job" });
|
|
10650
11155
|
if (!opts.quiet) {
|
|
10651
11156
|
const phase = describeJob(job);
|
|
10652
11157
|
if (phase !== lastPhase) {
|
|
@@ -10791,7 +11296,7 @@ async function runJobStatus(jobId, opts) {
|
|
|
10791
11296
|
|
|
10792
11297
|
// src/commands/export.ts
|
|
10793
11298
|
var import_chalk41 = __toESM(require("chalk"));
|
|
10794
|
-
var
|
|
11299
|
+
var import_fs9 = require("fs");
|
|
10795
11300
|
init_admin();
|
|
10796
11301
|
init_auth();
|
|
10797
11302
|
init_resolve();
|
|
@@ -10840,7 +11345,7 @@ async function runExport(projectArg, versionArg, opts) {
|
|
|
10840
11345
|
});
|
|
10841
11346
|
if (!res.ok) throw new Error(`Download failed (${res.status}): ${await res.text().catch(() => "")}`);
|
|
10842
11347
|
const buf = Buffer.from(await res.arrayBuffer());
|
|
10843
|
-
(0,
|
|
11348
|
+
(0, import_fs9.writeFileSync)(outFile, buf);
|
|
10844
11349
|
console.log(import_chalk41.default.green(`\u2713 Saved ${outFile} (${(buf.length / 1024).toFixed(0)} KB)`));
|
|
10845
11350
|
if (job.decryption_key) console.log(import_chalk41.default.yellow(`Decryption key (shown once): ${job.decryption_key}`));
|
|
10846
11351
|
console.log(import_chalk41.default.dim("Read MIGRATION-REPORT.json inside the zip for the full fidelity ledger."));
|
|
@@ -10897,8 +11402,8 @@ function resolveRecipeName(raw, githubHandle, proxyName) {
|
|
|
10897
11402
|
function normalizeName3(raw) {
|
|
10898
11403
|
return (raw || "").toLowerCase().replace(/[^a-z0-9]/g, "");
|
|
10899
11404
|
}
|
|
10900
|
-
async function publicFetch(
|
|
10901
|
-
const res = await fetch(`${RECIPES_BASE}/api/recipes${
|
|
11405
|
+
async function publicFetch(path16) {
|
|
11406
|
+
const res = await fetch(`${RECIPES_BASE}/api/recipes${path16}`, {
|
|
10902
11407
|
headers: { accept: "application/json" }
|
|
10903
11408
|
}).catch((err) => {
|
|
10904
11409
|
throw new Error(`Could not reach the recipe registry at ${RECIPES_BASE} \u2014 ${err.message}`);
|
|
@@ -10951,11 +11456,11 @@ async function fetchRecipeFile(ref, caller) {
|
|
|
10951
11456
|
Private recipes are visible only to the publisher's team \u2014 run \`apiblaze login\` if you are on it.`
|
|
10952
11457
|
);
|
|
10953
11458
|
}
|
|
10954
|
-
const
|
|
11459
|
+
const path16 = revision !== void 0 ? `/recipes/${ref.handle}/${ref.slug}/${revision}` : `/recipes/${ref.handle}/${ref.slug}/file`;
|
|
10955
11460
|
try {
|
|
10956
11461
|
return await producer(caller, {
|
|
10957
11462
|
method: "GET",
|
|
10958
|
-
path:
|
|
11463
|
+
path: path16,
|
|
10959
11464
|
summary: `Read private recipe ${ref.name}${revision !== void 0 ? `@${revision}` : ""}`
|
|
10960
11465
|
});
|
|
10961
11466
|
} catch (err) {
|
|
@@ -12091,9 +12596,9 @@ function printEnvelopeViolations(violations) {
|
|
|
12091
12596
|
console.log("bring-your-own-key case. Change the rules above, then publish again.");
|
|
12092
12597
|
console.log();
|
|
12093
12598
|
}
|
|
12094
|
-
async function askPromotion(
|
|
12599
|
+
async function askPromotion(path16) {
|
|
12095
12600
|
const { default: inquirer3 } = await import("inquirer");
|
|
12096
|
-
const suggestedId =
|
|
12601
|
+
const suggestedId = path16.split(/[.[\]]/).filter(Boolean).slice(-2).join("_").toLowerCase().replace(/[^a-z0-9_]/g, "_").replace(/_+/g, "_").replace(/^_|_$/g, "") || "value";
|
|
12097
12602
|
const answers = await inquirer3.prompt([
|
|
12098
12603
|
{ type: "input", name: "prompt", message: " prompt them with:", validate: (v) => v.trim() ? true : "Required." },
|
|
12099
12604
|
{ type: "input", name: "example", message: " example:" },
|
|
@@ -12104,7 +12609,7 @@ async function askPromotion(path14) {
|
|
|
12104
12609
|
const id = String(answers.id || suggestedId).toLowerCase().replace(/[^a-z0-9_]/g, "_");
|
|
12105
12610
|
console.log(import_chalk44.default.dim(` \u2192 replaced with {{${id}}}`));
|
|
12106
12611
|
return {
|
|
12107
|
-
path:
|
|
12612
|
+
path: path16,
|
|
12108
12613
|
id,
|
|
12109
12614
|
prompt: String(answers.prompt).trim(),
|
|
12110
12615
|
...answers.example ? { example: String(answers.example).trim() } : {},
|
|
@@ -13870,7 +14375,7 @@ async function providersMenu(cBase, clientLabel) {
|
|
|
13870
14375
|
}
|
|
13871
14376
|
|
|
13872
14377
|
// src/commands/spec.ts
|
|
13873
|
-
var
|
|
14378
|
+
var fs17 = __toESM(require("fs"));
|
|
13874
14379
|
var import_chalk55 = __toESM(require("chalk"));
|
|
13875
14380
|
var import_ora25 = __toESM(require("ora"));
|
|
13876
14381
|
init_admin();
|
|
@@ -13893,7 +14398,7 @@ async function runSpecSet(project, opts) {
|
|
|
13893
14398
|
}
|
|
13894
14399
|
let specContent;
|
|
13895
14400
|
try {
|
|
13896
|
-
specContent =
|
|
14401
|
+
specContent = fs17.readFileSync(opts.file, "utf-8");
|
|
13897
14402
|
} catch {
|
|
13898
14403
|
console.error(import_chalk55.default.red(`Cannot read file: ${opts.file}`));
|
|
13899
14404
|
process.exit(1);
|
|
@@ -14653,7 +15158,7 @@ async function patchSetting(proj2, s, value, cfg) {
|
|
|
14653
15158
|
}
|
|
14654
15159
|
const body = s.toPatch(value, cfg);
|
|
14655
15160
|
const method = s.putLimits ? "PUT" : "PATCH";
|
|
14656
|
-
const
|
|
15161
|
+
const path16 = s.putLimits ? `/projects/${proj2.projectId}/${proj2.apiVersion}/limits` : `/projects/${proj2.projectId}/${proj2.apiVersion}`;
|
|
14657
15162
|
if (s.needsTenant) {
|
|
14658
15163
|
const tenant2 = proj2.tenant ?? cfg.tenant ?? cfg.default_tenant;
|
|
14659
15164
|
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 +15168,7 @@ async function patchSetting(proj2, s, value, cfg) {
|
|
|
14663
15168
|
try {
|
|
14664
15169
|
await admin({
|
|
14665
15170
|
method,
|
|
14666
|
-
path:
|
|
15171
|
+
path: path16,
|
|
14667
15172
|
body,
|
|
14668
15173
|
summary: `Set ${s.key} = ${JSON.stringify(value)}`
|
|
14669
15174
|
});
|
|
@@ -15299,11 +15804,11 @@ async function resolveTransport(opts) {
|
|
|
15299
15804
|
const token = getAccessToken();
|
|
15300
15805
|
return {
|
|
15301
15806
|
tenant: tenant2,
|
|
15302
|
-
call: async (
|
|
15807
|
+
call: async (path16, method = "GET", body) => {
|
|
15303
15808
|
const res = await fetch(`${DASHBOARD_BASE9}/api/cli/iam`, {
|
|
15304
15809
|
method: "POST",
|
|
15305
15810
|
headers: { "Content-Type": "application/json", Authorization: `Bearer ${token}` },
|
|
15306
|
-
body: JSON.stringify({ tenant: tenant2, path:
|
|
15811
|
+
body: JSON.stringify({ tenant: tenant2, path: path16, method, body })
|
|
15307
15812
|
});
|
|
15308
15813
|
const data = await res.json().catch(() => ({}));
|
|
15309
15814
|
if (!res.ok) throw new ApiError(res.status, data?.details ?? data?.error ?? res.statusText, data);
|
|
@@ -15321,8 +15826,8 @@ async function resolveTransport(opts) {
|
|
|
15321
15826
|
return {
|
|
15322
15827
|
tenant: fresh.tenant,
|
|
15323
15828
|
selfEmail: fresh.email,
|
|
15324
|
-
call: async (
|
|
15325
|
-
const res = await fetch(`${IAM_BASE}${
|
|
15829
|
+
call: async (path16, method = "GET", body) => {
|
|
15830
|
+
const res = await fetch(`${IAM_BASE}${path16}`, {
|
|
15326
15831
|
method,
|
|
15327
15832
|
headers: {
|
|
15328
15833
|
"Content-Type": "application/json",
|
|
@@ -15747,28 +16252,28 @@ async function runConsumerApikeys(opts) {
|
|
|
15747
16252
|
// src/commands/sidecar.ts
|
|
15748
16253
|
var import_chalk62 = __toESM(require("chalk"));
|
|
15749
16254
|
var import_ora31 = __toESM(require("ora"));
|
|
15750
|
-
var
|
|
15751
|
-
var
|
|
16255
|
+
var fs18 = __toESM(require("fs"));
|
|
16256
|
+
var path14 = __toESM(require("path"));
|
|
15752
16257
|
init_admin();
|
|
15753
16258
|
init_resolve();
|
|
15754
16259
|
init_auth();
|
|
15755
16260
|
function detectNextProject(root) {
|
|
15756
|
-
const hasConfig = ["next.config.js", "next.config.mjs", "next.config.ts"].some((f) =>
|
|
16261
|
+
const hasConfig = ["next.config.js", "next.config.mjs", "next.config.ts"].some((f) => fs18.existsSync(path14.join(root, f)));
|
|
15757
16262
|
let hasDep = false;
|
|
15758
16263
|
try {
|
|
15759
|
-
const pkg = JSON.parse(
|
|
16264
|
+
const pkg = JSON.parse(fs18.readFileSync(path14.join(root, "package.json"), "utf8"));
|
|
15760
16265
|
hasDep = !!(pkg.dependencies?.next || pkg.devDependencies?.next);
|
|
15761
16266
|
} catch {
|
|
15762
16267
|
}
|
|
15763
|
-
const appDir =
|
|
15764
|
-
const pagesDir =
|
|
16268
|
+
const appDir = fs18.existsSync(path14.join(root, "app")) || fs18.existsSync(path14.join(root, "src", "app"));
|
|
16269
|
+
const pagesDir = fs18.existsSync(path14.join(root, "pages")) || fs18.existsSync(path14.join(root, "src", "pages"));
|
|
15765
16270
|
return { found: hasConfig || hasDep || appDir || pagesDir, router: appDir ? "app" : pagesDir ? "pages" : null };
|
|
15766
16271
|
}
|
|
15767
16272
|
function upsertEnvLocal(root, token) {
|
|
15768
|
-
const p =
|
|
16273
|
+
const p = path14.join(root, ".env.local");
|
|
15769
16274
|
let existing = "";
|
|
15770
16275
|
try {
|
|
15771
|
-
existing =
|
|
16276
|
+
existing = fs18.readFileSync(p, "utf8");
|
|
15772
16277
|
} catch {
|
|
15773
16278
|
}
|
|
15774
16279
|
const had = /^APIBLAZE_API_KEY=/m.test(existing) || /^APIBLAZE_TOKEN=/m.test(existing);
|
|
@@ -15783,15 +16288,15 @@ function upsertEnvLocal(root, token) {
|
|
|
15783
16288
|
next = (next.endsWith("\n") ? next : next + "\n") + `APIBLAZE_SIDECAR_VERBOSE=true
|
|
15784
16289
|
`;
|
|
15785
16290
|
}
|
|
15786
|
-
|
|
16291
|
+
fs18.writeFileSync(p, next);
|
|
15787
16292
|
return had ? "rotated" : "created";
|
|
15788
16293
|
}
|
|
15789
16294
|
function installSidecarPackage(root) {
|
|
15790
|
-
if (
|
|
16295
|
+
if (fs18.existsSync(path14.join(root, "node_modules", "apiblaze", "package.json"))) {
|
|
15791
16296
|
console.log(` ${import_chalk62.default.green("\u2713")} apiblaze package already installed`);
|
|
15792
16297
|
return;
|
|
15793
16298
|
}
|
|
15794
|
-
const has = (f) =>
|
|
16299
|
+
const has = (f) => fs18.existsSync(path14.join(root, f));
|
|
15795
16300
|
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
16301
|
const spinner = (0, import_ora31.default)(`Installing the apiblaze package (${pm.cmd})\u2026`).start();
|
|
15797
16302
|
try {
|
|
@@ -15804,7 +16309,7 @@ function installSidecarPackage(root) {
|
|
|
15804
16309
|
}
|
|
15805
16310
|
function readEnvKey(root) {
|
|
15806
16311
|
try {
|
|
15807
|
-
const s =
|
|
16312
|
+
const s = fs18.readFileSync(path14.join(root, ".env.local"), "utf8");
|
|
15808
16313
|
const m = s.match(/^APIBLAZE_API_KEY=(.+)$/m) ?? s.match(/^APIBLAZE_TOKEN=(.+)$/m);
|
|
15809
16314
|
return m ? m[1].trim() : null;
|
|
15810
16315
|
} catch {
|
|
@@ -15812,16 +16317,16 @@ function readEnvKey(root) {
|
|
|
15812
16317
|
}
|
|
15813
16318
|
}
|
|
15814
16319
|
function ensureGitignored(root) {
|
|
15815
|
-
const p =
|
|
16320
|
+
const p = path14.join(root, ".gitignore");
|
|
15816
16321
|
let c = "";
|
|
15817
16322
|
try {
|
|
15818
|
-
c =
|
|
16323
|
+
c = fs18.readFileSync(p, "utf8");
|
|
15819
16324
|
} catch {
|
|
15820
16325
|
}
|
|
15821
|
-
if (!/^\.env\.local$/m.test(c) && !/^\.env\*/m.test(c))
|
|
16326
|
+
if (!/^\.env\.local$/m.test(c) && !/^\.env\*/m.test(c)) fs18.writeFileSync(p, (c && !c.endsWith("\n") ? c + "\n" : c) + ".env.local\n");
|
|
15822
16327
|
}
|
|
15823
16328
|
function wireInstrumentation(root) {
|
|
15824
|
-
const existing = ["instrumentation.ts", "instrumentation.js",
|
|
16329
|
+
const existing = ["instrumentation.ts", "instrumentation.js", path14.join("src", "instrumentation.ts")].map((c) => path14.join(root, c)).find((f) => fs18.existsSync(f));
|
|
15825
16330
|
const body = `import { register as apiblaze } from "apiblaze/sidecar";
|
|
15826
16331
|
|
|
15827
16332
|
export function register() {
|
|
@@ -15829,18 +16334,18 @@ export function register() {
|
|
|
15829
16334
|
}
|
|
15830
16335
|
`;
|
|
15831
16336
|
if (!existing) {
|
|
15832
|
-
|
|
16337
|
+
fs18.writeFileSync(path14.join(root, "instrumentation.ts"), body);
|
|
15833
16338
|
return "created";
|
|
15834
16339
|
}
|
|
15835
|
-
const cur =
|
|
16340
|
+
const cur = fs18.readFileSync(existing, "utf8");
|
|
15836
16341
|
if (cur.includes("apiblaze/sidecar")) return "present";
|
|
15837
16342
|
if (/export\s+function\s+register\s*\(/.test(cur)) {
|
|
15838
|
-
|
|
16343
|
+
fs18.writeFileSync(existing, `import { register as apiblaze } from "apiblaze/sidecar";
|
|
15839
16344
|
` + cur.replace(/export\s+function\s+register\s*\(\s*\)\s*\{/, (m) => `${m}
|
|
15840
16345
|
apiblaze();`));
|
|
15841
16346
|
return "patched";
|
|
15842
16347
|
}
|
|
15843
|
-
|
|
16348
|
+
fs18.writeFileSync(existing, `import { register as apiblaze } from "apiblaze/sidecar";
|
|
15844
16349
|
${cur}
|
|
15845
16350
|
// call apiblaze() inside your register() export.
|
|
15846
16351
|
`);
|
|
@@ -15923,17 +16428,17 @@ export default async function Page() {
|
|
|
15923
16428
|
function generateInspector(root, router) {
|
|
15924
16429
|
try {
|
|
15925
16430
|
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
|
|
16431
|
+
const dir2 = fs18.existsSync(path14.join(root, "src", "pages")) ? path14.join(root, "src", "pages") : path14.join(root, "pages");
|
|
16432
|
+
const f2 = path14.join(dir2, "abz-inspector.tsx");
|
|
16433
|
+
fs18.writeFileSync(f2, INSPECTOR_PAGE);
|
|
16434
|
+
return path14.relative(root, f2);
|
|
16435
|
+
}
|
|
16436
|
+
const base2 = fs18.existsSync(path14.join(root, "src", "app")) ? path14.join(root, "src", "app") : path14.join(root, "app");
|
|
16437
|
+
const dir = path14.join(base2, "abz-inspector");
|
|
16438
|
+
fs18.mkdirSync(dir, { recursive: true });
|
|
16439
|
+
const f = path14.join(dir, "page.tsx");
|
|
16440
|
+
fs18.writeFileSync(f, INSPECTOR_PAGE);
|
|
16441
|
+
return path14.relative(root, f);
|
|
15937
16442
|
} catch {
|
|
15938
16443
|
return null;
|
|
15939
16444
|
}
|
|
@@ -15973,7 +16478,7 @@ async function runAnonymousInit(root, router, opts) {
|
|
|
15973
16478
|
console.log(import_chalk62.default.dim(` From another machine: apiblaze claim ${out.claim_code} \xB7 expires in 30 days`));
|
|
15974
16479
|
}
|
|
15975
16480
|
async function runSidecar(opts) {
|
|
15976
|
-
const root =
|
|
16481
|
+
const root = path14.resolve(opts.dir ?? process.cwd());
|
|
15977
16482
|
const detected2 = detectNextProject(root);
|
|
15978
16483
|
if (!detected2.found) {
|
|
15979
16484
|
console.log(import_chalk62.default.yellow(`No Next.js project detected in ${root}.`));
|
|
@@ -16032,7 +16537,7 @@ async function runSidecar(opts) {
|
|
|
16032
16537
|
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
16538
|
console.log(` 3. Approve the ones to route: ${import_chalk62.default.cyan("apiblaze sidecar approve api.stripe.com")} (or in the dashboard)`);
|
|
16034
16539
|
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 ${
|
|
16540
|
+
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
16541
|
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
16542
|
console.log("");
|
|
16038
16543
|
console.log(import_chalk62.default.dim(" Manage: apiblaze sidecar (list/approve/deny/remove)"));
|
|
@@ -16768,17 +17273,17 @@ var import_chalk68 = __toESM(require("chalk"));
|
|
|
16768
17273
|
init_consumer_auth();
|
|
16769
17274
|
var SANDBOX_TENANT = "portalteststenant";
|
|
16770
17275
|
var SANDBOX_PROXY = "portaltestproxypreapproved@2.7.0";
|
|
16771
|
-
function proxyUrlFor(spec2,
|
|
17276
|
+
function proxyUrlFor(spec2, path16 = "/get") {
|
|
16772
17277
|
const [name, version2 = "1.0.0"] = spec2.split("@");
|
|
16773
|
-
return `https://${name}.abz.run/${version2}/prod${
|
|
17278
|
+
return `https://${name}.abz.run/${version2}/prod${path16.startsWith("/") ? path16 : `/${path16}`}`;
|
|
16774
17279
|
}
|
|
16775
17280
|
function isPortalAudience(aud) {
|
|
16776
17281
|
return /^https:\/\/[a-z0-9-]+\.portal\.apiblaze\.com\b/i.test(aud);
|
|
16777
17282
|
}
|
|
16778
17283
|
var log2 = (s) => process.stderr.write(s + "\n");
|
|
16779
|
-
function probeUrlFor(audience,
|
|
17284
|
+
function probeUrlFor(audience, path16 = "/") {
|
|
16780
17285
|
const base2 = audience.replace(/\/+$/, "");
|
|
16781
|
-
return `${base2}/prod${
|
|
17286
|
+
return `${base2}/prod${path16.startsWith("/") ? path16 : `/${path16}`}`;
|
|
16782
17287
|
}
|
|
16783
17288
|
function tamper(token) {
|
|
16784
17289
|
const parts = token.split(".");
|
|
@@ -17723,7 +18228,7 @@ program.command("demo").description(`See what ${B.product} does in a minute: a s
|
|
|
17723
18228
|
}
|
|
17724
18229
|
await runDemo(opts);
|
|
17725
18230
|
});
|
|
17726
|
-
program.command("skill").alias("skills").description(`
|
|
18231
|
+
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
18232
|
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
18233
|
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
18234
|
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 +18247,7 @@ mcpCommon(mcpOps.command("set").description("Override one tool").argument("<proj
|
|
|
17742
18247
|
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
18248
|
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
18249
|
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) ||
|
|
18250
|
+
const looksLikeSource = !!project && (/^https?:\/\//i.test(project) || /\.(ya?ml|json)$/i.test(project) || import_fs11.default.existsSync(project));
|
|
17746
18251
|
const target2 = opts.target ?? (looksLikeSource ? project : void 0);
|
|
17747
18252
|
return runApichat({ ...opts, target: target2, project: looksLikeSource ? void 0 : project, openapispec: opts.openapispec ?? opts.openapi });
|
|
17748
18253
|
}));
|