apiblaze 0.20.28 → 0.20.34
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 +214 -7
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -1024,7 +1024,7 @@ var import_commander = require("commander");
|
|
|
1024
1024
|
var import_chalk54 = __toESM(require("chalk"));
|
|
1025
1025
|
|
|
1026
1026
|
// package.json
|
|
1027
|
-
var version = "0.20.
|
|
1027
|
+
var version = "0.20.33";
|
|
1028
1028
|
|
|
1029
1029
|
// src/index.ts
|
|
1030
1030
|
init_types();
|
|
@@ -2102,6 +2102,7 @@ function suggestTenantName(projectSlug, taken) {
|
|
|
2102
2102
|
var fs8 = __toESM(require("fs"));
|
|
2103
2103
|
var path6 = __toESM(require("path"));
|
|
2104
2104
|
var crypto2 = __toESM(require("crypto"));
|
|
2105
|
+
var import_child_process3 = require("child_process");
|
|
2105
2106
|
var import_chalk14 = __toESM(require("chalk"));
|
|
2106
2107
|
var import_ora4 = __toESM(require("ora"));
|
|
2107
2108
|
var import_yaml2 = require("yaml");
|
|
@@ -2414,7 +2415,7 @@ function claudeOneShot(spec2, prompt) {
|
|
|
2414
2415
|
return { argv, status: r.status };
|
|
2415
2416
|
}
|
|
2416
2417
|
function claudeInteractive(spec2, prompt) {
|
|
2417
|
-
const argv = ["claude", prompt, "--allowedTools", `mcp__${spec2.name}__
|
|
2418
|
+
const argv = ["claude", prompt, "--allowedTools", `mcp__${spec2.name}__*`, "--append-system-prompt", claudeSystemHint(spec2)];
|
|
2418
2419
|
const r = run(argv[0], argv.slice(1), { interactive: true });
|
|
2419
2420
|
return { argv, status: r.status };
|
|
2420
2421
|
}
|
|
@@ -2492,6 +2493,75 @@ function installIntoCodex(spec2) {
|
|
|
2492
2493
|
return { ok: false, error: err instanceof Error ? err.message : String(err), path: file };
|
|
2493
2494
|
}
|
|
2494
2495
|
}
|
|
2496
|
+
function codexSiblingApiblazeServers(keepName) {
|
|
2497
|
+
try {
|
|
2498
|
+
const text = fs5.readFileSync(codexConfigPath(), "utf-8");
|
|
2499
|
+
const lines = text.split("\n");
|
|
2500
|
+
const out = [];
|
|
2501
|
+
const header = /^\[mcp_servers\.(?:"([^"]+)"|([^\]]+))\]\s*(#.*)?$/;
|
|
2502
|
+
const anyHeader = /^\s*\[[^\]]+\]\s*(#.*)?$/;
|
|
2503
|
+
for (let i = 0; i < lines.length; i++) {
|
|
2504
|
+
const m = lines[i].match(header);
|
|
2505
|
+
if (!m) continue;
|
|
2506
|
+
const name = m[1] ?? m[2];
|
|
2507
|
+
let end = lines.length;
|
|
2508
|
+
for (let j = i + 1; j < lines.length; j++) {
|
|
2509
|
+
if (anyHeader.test(lines[j])) {
|
|
2510
|
+
end = j;
|
|
2511
|
+
break;
|
|
2512
|
+
}
|
|
2513
|
+
}
|
|
2514
|
+
const body = lines.slice(i, end).join("\n");
|
|
2515
|
+
const url = body.match(/^\s*url\s*=\s*"([^"]*)"/m)?.[1];
|
|
2516
|
+
if (name && name !== keepName && url && OURS.test(url)) out.push(name);
|
|
2517
|
+
i = end - 1;
|
|
2518
|
+
}
|
|
2519
|
+
return out;
|
|
2520
|
+
} catch {
|
|
2521
|
+
return [];
|
|
2522
|
+
}
|
|
2523
|
+
}
|
|
2524
|
+
async function mcpEndpointIsDead(url) {
|
|
2525
|
+
try {
|
|
2526
|
+
const res = await fetch(url, {
|
|
2527
|
+
method: "POST",
|
|
2528
|
+
headers: { "Content-Type": "application/json" },
|
|
2529
|
+
body: JSON.stringify({ jsonrpc: "2.0", id: 1, method: "tools/list" }),
|
|
2530
|
+
signal: AbortSignal.timeout(8e3)
|
|
2531
|
+
});
|
|
2532
|
+
if (res.status === 401 || res.status === 403) return false;
|
|
2533
|
+
if (res.status === 404 || res.status >= 500) return true;
|
|
2534
|
+
return false;
|
|
2535
|
+
} catch {
|
|
2536
|
+
return false;
|
|
2537
|
+
}
|
|
2538
|
+
}
|
|
2539
|
+
function codexServerUrl(name) {
|
|
2540
|
+
try {
|
|
2541
|
+
const lines = fs5.readFileSync(codexConfigPath(), "utf-8").split("\n");
|
|
2542
|
+
const ranges = codexSectionRanges(lines, name);
|
|
2543
|
+
if (!ranges.length) return null;
|
|
2544
|
+
const body = lines.slice(ranges[0].start, ranges[0].end).join("\n");
|
|
2545
|
+
return body.match(/^\s*url\s*=\s*"([^"]*)"/m)?.[1] ?? null;
|
|
2546
|
+
} catch {
|
|
2547
|
+
return null;
|
|
2548
|
+
}
|
|
2549
|
+
}
|
|
2550
|
+
function removeCodexServer(name) {
|
|
2551
|
+
try {
|
|
2552
|
+
const file = codexConfigPath();
|
|
2553
|
+
const lines = fs5.readFileSync(file, "utf-8").split("\n");
|
|
2554
|
+
const ranges = codexSectionRanges(lines, name);
|
|
2555
|
+
if (!ranges.length) return false;
|
|
2556
|
+
for (const r of [...ranges].reverse()) lines.splice(r.start, r.end - r.start);
|
|
2557
|
+
const tmp = `${file}.tmp-${process.pid}`;
|
|
2558
|
+
fs5.writeFileSync(tmp, lines.join("\n"), { encoding: "utf-8", mode: 384 });
|
|
2559
|
+
fs5.renameSync(tmp, file);
|
|
2560
|
+
return true;
|
|
2561
|
+
} catch {
|
|
2562
|
+
return false;
|
|
2563
|
+
}
|
|
2564
|
+
}
|
|
2495
2565
|
function codexOneShot(_spec, prompt) {
|
|
2496
2566
|
const argv = ["codex", "exec", "--skip-git-repo-check", prompt];
|
|
2497
2567
|
const r = run(argv[0], argv.slice(1), { inherit: true });
|
|
@@ -2503,6 +2573,73 @@ function codexInteractive(_spec, prompt) {
|
|
|
2503
2573
|
return { argv, status: r.status };
|
|
2504
2574
|
}
|
|
2505
2575
|
var shellQuote = (s) => `"${s.replace(/(["\\$`])/g, "\\$1")}"`;
|
|
2576
|
+
function directedPrompt(spec2, question) {
|
|
2577
|
+
return `Use the "${spec2.name}" MCP server (it exposes this API's tools) to answer: ${question}`;
|
|
2578
|
+
}
|
|
2579
|
+
function agentInstructionFile(cli, dir = process.cwd()) {
|
|
2580
|
+
return path4.join(dir, cli === "claude" ? "CLAUDE.md" : "AGENTS.md");
|
|
2581
|
+
}
|
|
2582
|
+
var marker = (name) => ({
|
|
2583
|
+
start: `<!-- apiblaze:mcp:${name} -->`,
|
|
2584
|
+
end: `<!-- /apiblaze:mcp:${name} -->`
|
|
2585
|
+
});
|
|
2586
|
+
function stripMarkedBlock(text, name) {
|
|
2587
|
+
const m = marker(name);
|
|
2588
|
+
for (; ; ) {
|
|
2589
|
+
const i = text.indexOf(m.start);
|
|
2590
|
+
if (i < 0) return text;
|
|
2591
|
+
const j = text.indexOf(m.end, i);
|
|
2592
|
+
if (j < 0) return text.slice(0, i);
|
|
2593
|
+
let k = j + m.end.length;
|
|
2594
|
+
if (text[k] === "\n") k++;
|
|
2595
|
+
text = text.slice(0, i) + text.slice(k);
|
|
2596
|
+
}
|
|
2597
|
+
}
|
|
2598
|
+
function writeAgentInstruction(cli, spec2, dir = process.cwd()) {
|
|
2599
|
+
try {
|
|
2600
|
+
const file = agentInstructionFile(cli, dir);
|
|
2601
|
+
const m = marker(spec2.name);
|
|
2602
|
+
const block = [
|
|
2603
|
+
m.start,
|
|
2604
|
+
`## ${spec2.projectLabel} API \u2014 use the "${spec2.name}" MCP server`,
|
|
2605
|
+
"",
|
|
2606
|
+
`This directory is connected to the \`${spec2.name}\` MCP server, which exposes the`,
|
|
2607
|
+
`${spec2.projectLabel} API as tools. For any question about this API \u2014 its data, its`,
|
|
2608
|
+
"records, what it can do \u2014 call those tools rather than answering from memory or",
|
|
2609
|
+
"asking for data the API can provide.",
|
|
2610
|
+
"",
|
|
2611
|
+
`_Added by \`apiblaze apichat\`. Remove this block, or run \`npx apiblaze apichat ${spec2.name} --remove-mcp ${cli}\`._`,
|
|
2612
|
+
m.end,
|
|
2613
|
+
""
|
|
2614
|
+
].join("\n");
|
|
2615
|
+
let text = "";
|
|
2616
|
+
try {
|
|
2617
|
+
text = fs5.readFileSync(file, "utf-8");
|
|
2618
|
+
} catch {
|
|
2619
|
+
}
|
|
2620
|
+
const cleaned = stripMarkedBlock(text, spec2.name);
|
|
2621
|
+
const sep = cleaned.length && !cleaned.endsWith("\n\n") ? cleaned.endsWith("\n") ? "\n" : "\n\n" : "";
|
|
2622
|
+
fs5.writeFileSync(file, cleaned + sep + block, "utf-8");
|
|
2623
|
+
return file;
|
|
2624
|
+
} catch {
|
|
2625
|
+
return null;
|
|
2626
|
+
}
|
|
2627
|
+
}
|
|
2628
|
+
function removeAgentInstruction(cli, name, dir = process.cwd()) {
|
|
2629
|
+
try {
|
|
2630
|
+
const file = agentInstructionFile(cli, dir);
|
|
2631
|
+
const text = fs5.readFileSync(file, "utf-8");
|
|
2632
|
+
const cleaned = stripMarkedBlock(text, name);
|
|
2633
|
+
if (cleaned === text) return false;
|
|
2634
|
+
fs5.writeFileSync(file, cleaned.replace(/\n{3,}/g, "\n\n"), "utf-8");
|
|
2635
|
+
return true;
|
|
2636
|
+
} catch {
|
|
2637
|
+
return false;
|
|
2638
|
+
}
|
|
2639
|
+
}
|
|
2640
|
+
function claudeSystemHint(spec2) {
|
|
2641
|
+
return `You are connected to the "${spec2.name}" MCP server, which exposes the ${spec2.projectLabel} API as tools (mcp__${spec2.name}__*). For any question about this API, call those tools rather than answering from memory or asking the user for data the API can provide.`;
|
|
2642
|
+
}
|
|
2506
2643
|
function renderCommand(argv) {
|
|
2507
2644
|
return argv.map((a, i) => i === 0 || /^[A-Za-z0-9_@%+=:,./-]+$/.test(a) ? a : shellQuote(a)).join(" ");
|
|
2508
2645
|
}
|
|
@@ -2575,15 +2712,28 @@ async function installAndDemo(cli, spec2, getQuestion, log = console.log) {
|
|
|
2575
2712
|
const ask = handoff ? cli.kind === "claude" ? claudeInteractive : codexInteractive : oneShot;
|
|
2576
2713
|
log(`
|
|
2577
2714
|
${import_chalk12.default.dim(handoff ? `Opening ${cli.label} with your question \u2014 you'll stay in that session:` : `Your question, through ${cli.label}:`)}`);
|
|
2578
|
-
const
|
|
2715
|
+
const directed = directedPrompt(spec2, question);
|
|
2716
|
+
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];
|
|
2579
2717
|
log(` ${import_chalk12.default.dim("$")} ${renderCommand(shown)}
|
|
2580
2718
|
`);
|
|
2581
|
-
const ans = ask(spec2, question);
|
|
2719
|
+
const ans = ask(spec2, directedPrompt(spec2, question));
|
|
2582
2720
|
if (ans.status !== 0) log(import_chalk12.default.yellow(`
|
|
2583
2721
|
${cli.label} exited with ${ans.status ?? "no status"} \u2014 the MCP stays installed.`));
|
|
2584
2722
|
}
|
|
2585
|
-
|
|
2586
|
-
|
|
2723
|
+
const instructionFile = writeAgentInstruction(cli.kind, spec2);
|
|
2724
|
+
const off = `npx apiblaze apichat ${spec2.name} --remove-mcp ${cli.kind}`;
|
|
2725
|
+
const mdName = cli.kind === "claude" ? "CLAUDE.md" : "AGENTS.md";
|
|
2726
|
+
log("");
|
|
2727
|
+
log(import_chalk12.default.bgRed.white.bold(` ${cli.label} will launch with your MCP to ${spec2.projectLabel} enabled from now on. `));
|
|
2728
|
+
log(import_chalk12.default.red.bold(` Every ${cli.label} session in this directory can call your API's tools.`));
|
|
2729
|
+
if (instructionFile) {
|
|
2730
|
+
log(import_chalk12.default.red(` We added a "use this MCP" block to your ${import_chalk12.default.bold(mdName)} \u2014 that is what makes it stick:`));
|
|
2731
|
+
log(import_chalk12.default.red(` ${instructionFile}`));
|
|
2732
|
+
} else {
|
|
2733
|
+
log(import_chalk12.default.red(` (Could not write ${mdName} here, so this applies only to sessions apichat starts.)`));
|
|
2734
|
+
}
|
|
2735
|
+
log(import_chalk12.default.red(` Turn it off (removes the MCP server AND the ${mdName} block):`));
|
|
2736
|
+
log(import_chalk12.default.red(` ${import_chalk12.default.bold(off)}`));
|
|
2587
2737
|
return true;
|
|
2588
2738
|
}
|
|
2589
2739
|
|
|
@@ -4160,12 +4310,47 @@ async function maybeInstallExternalCli(p, opts) {
|
|
|
4160
4310
|
const ran = await installAndDemo(pick2, buildInstallSpec(p), () => resolveQuestion(opts));
|
|
4161
4311
|
if (ran) {
|
|
4162
4312
|
rememberCliOffer(p, pick2.kind, "installed");
|
|
4313
|
+
if (pick2.kind === "codex") await offerToPruneCodexSiblings(p.projectId);
|
|
4163
4314
|
if (p.anon) {
|
|
4164
4315
|
console.log(import_chalk14.default.dim(` Anonymous workspace \u2014 run \`apiblaze apichat ${p.projectId}\` and /claim to keep it (and this MCP) beyond 30 days.`));
|
|
4165
4316
|
}
|
|
4166
4317
|
}
|
|
4167
4318
|
return ran;
|
|
4168
4319
|
}
|
|
4320
|
+
async function offerToPruneCodexSiblings(keepName) {
|
|
4321
|
+
const siblings = codexSiblingApiblazeServers(keepName);
|
|
4322
|
+
if (siblings.length === 0) return;
|
|
4323
|
+
const alive = [];
|
|
4324
|
+
for (const name of siblings) {
|
|
4325
|
+
const url = codexServerUrl(name);
|
|
4326
|
+
if (url && await mcpEndpointIsDead(url)) {
|
|
4327
|
+
if (removeCodexServer(name)) {
|
|
4328
|
+
console.log(import_chalk14.default.dim(` Removed ${import_chalk14.default.bold(name)} from ~/.codex/config.toml \u2014 that proxy no longer exists.`));
|
|
4329
|
+
continue;
|
|
4330
|
+
}
|
|
4331
|
+
}
|
|
4332
|
+
alive.push(name);
|
|
4333
|
+
}
|
|
4334
|
+
if (alive.length === 0) return;
|
|
4335
|
+
const siblingsLabel = alive;
|
|
4336
|
+
console.log(import_chalk14.default.yellow(`
|
|
4337
|
+
Codex also has ${siblingsLabel.length} other APIblaze MCP server${siblingsLabel.length > 1 ? "s" : ""} configured: ${import_chalk14.default.bold(siblingsLabel.join(", "))}.`));
|
|
4338
|
+
console.log(import_chalk14.default.dim(' Codex tries every one at startup and reports "MCP startup incomplete" for any that is not signed in.'));
|
|
4339
|
+
if (!process.stdin.isTTY) {
|
|
4340
|
+
console.log(import_chalk14.default.dim(` Remove the ones you don't use: apiblaze apichat <name> --install-mcp codex (or edit ~/.codex/config.toml)`));
|
|
4341
|
+
return;
|
|
4342
|
+
}
|
|
4343
|
+
const { default: inquirer3 } = await import("inquirer");
|
|
4344
|
+
const { drop } = await inquirer3.prompt([{
|
|
4345
|
+
type: "checkbox",
|
|
4346
|
+
name: "drop",
|
|
4347
|
+
message: "Remove any you no longer use? (space to select, enter to confirm)",
|
|
4348
|
+
choices: siblingsLabel.map((n) => ({ name: n, value: n }))
|
|
4349
|
+
}]);
|
|
4350
|
+
for (const n of drop ?? []) {
|
|
4351
|
+
console.log(removeCodexServer(n) ? ` ${import_chalk14.default.green("\u2714")} Removed ${import_chalk14.default.bold(n)} from ~/.codex/config.toml.` : import_chalk14.default.red(` Could not remove ${n}.`));
|
|
4352
|
+
}
|
|
4353
|
+
}
|
|
4169
4354
|
async function authorizeClientRegistration(p, cliLabel) {
|
|
4170
4355
|
if (!p.consumerAuth) return;
|
|
4171
4356
|
const teamId = p.teamId ?? loadCredentials()?.teamId;
|
|
@@ -4223,6 +4408,23 @@ async function startChat(p, messages, opts) {
|
|
|
4223
4408
|
}
|
|
4224
4409
|
await runRepl(p, messages);
|
|
4225
4410
|
}
|
|
4411
|
+
async function removeExternalMcp(projectId, cliKind) {
|
|
4412
|
+
const kind = cliKind.toLowerCase();
|
|
4413
|
+
if (kind !== "claude" && kind !== "codex") fail3(`--remove-mcp takes "claude" or "codex", not "${cliKind}".`);
|
|
4414
|
+
const label3 = kind === "claude" ? "Claude CLI" : "Codex CLI";
|
|
4415
|
+
let removed = false;
|
|
4416
|
+
if (kind === "claude") {
|
|
4417
|
+
const r = (0, import_child_process3.spawnSync)("claude", ["mcp", "remove", projectId], { encoding: "utf-8", stdio: ["ignore", "pipe", "pipe"], timeout: 15e3 });
|
|
4418
|
+
removed = r.status === 0;
|
|
4419
|
+
} else {
|
|
4420
|
+
removed = removeCodexServer(projectId);
|
|
4421
|
+
}
|
|
4422
|
+
console.log(removed ? ` ${import_chalk14.default.green("\u2714")} Removed the ${import_chalk14.default.bold(projectId)} MCP server from ${label3}.` : import_chalk14.default.dim(` ${label3} had no ${projectId} MCP server configured.`));
|
|
4423
|
+
const hadNote = removeAgentInstruction(kind, projectId);
|
|
4424
|
+
console.log(hadNote ? ` ${import_chalk14.default.green("\u2714")} Removed the standing instruction from ${agentInstructionFile(kind)}.` : import_chalk14.default.dim(` No standing instruction found in ${agentInstructionFile(kind)}.`));
|
|
4425
|
+
console.log(import_chalk14.default.dim(`
|
|
4426
|
+
${label3} will no longer launch with your API's tools in this directory.`));
|
|
4427
|
+
}
|
|
4226
4428
|
async function runApichat(opts) {
|
|
4227
4429
|
setVerbose(opts.verbose === true);
|
|
4228
4430
|
console.log(import_chalk14.default.bold("\napichat \u2014 turn any API into a chat\n"));
|
|
@@ -4235,6 +4437,11 @@ async function runApichat(opts) {
|
|
|
4235
4437
|
opts.target = void 0;
|
|
4236
4438
|
}
|
|
4237
4439
|
}
|
|
4440
|
+
if (opts.removeMcp) {
|
|
4441
|
+
if (!opts.project) fail3("Name the proxy: apiblaze apichat <project> --remove-mcp claude|codex");
|
|
4442
|
+
await removeExternalMcp(opts.project, opts.removeMcp);
|
|
4443
|
+
return;
|
|
4444
|
+
}
|
|
4238
4445
|
if (opts.project) {
|
|
4239
4446
|
const opened = await openDirectProject(opts.project, opts);
|
|
4240
4447
|
if (await maybeInstallExternalCli(opened.p, opts)) return;
|
|
@@ -11584,7 +11791,7 @@ agent.command("authz").description("Chat to design and turn on access rules for
|
|
|
11584
11791
|
program.command("rule").description("Author an object-level access rule in plain English, in one shot (billed per turn)").argument("<rule>", 'The rule in plain English, e.g. "users see only their own rows"').argument("<project>", "Project name or id").option("--enforce", "Turn enforcement on immediately (default: shadow-publish only)").option("--apiversion <version>", "API version (defaults to the project's)").action(action((rule, project, opts) => runRule(rule, project, opts)));
|
|
11585
11792
|
agent.command("openapi").description("Chat to build your API spec from real traffic").argument("<project>", "Project name or id").argument("[apiVersion]", "API version (defaults to the project's)").action(action((project, apiVersion) => runOpenapi(project, apiVersion)));
|
|
11586
11793
|
agent.command("mcp").description("Chat to build an MCP server for an API").argument("<project>", "Project name or id").argument("[apiVersion]", "API version (defaults to the project's)").option("--environment <env>", "Environment to publish (default: prod)").action(action((project, apiVersion, opts) => runMcp(project, apiVersion, opts)));
|
|
11587
|
-
program.command("apichat [project]").description("Turn any API into a chat: point at an OpenAPI spec \u2014 or chat an EXISTING proxy by name (no login needed)").option("--target <url|file>", "What to chat with \u2014 pass ANY of: a target server base URL (spec auto-discovered at /openapi.json etc.), a local OpenAPI file (./openapi.yaml), or a remote OpenAPI URL (https://acme.com/openapi.yaml)").addOption(new import_commander.Option("--openapi <file|url>", "Deprecated alias \u2014 --target now detects spec files/URLs itself").hideHelp()).addOption(new import_commander.Option("--openapispec <file|url>", "Deprecated alias for --openapi").hideHelp()).option("--name <name>", "Proxy name (defaults to the target host)").option("--apiversion <version>", "API version to create (e.g. 1.0.0)").option("--environment <env>", "Environment to chat against (default: prod anonymous / dev logged-in)").option("--access <mode>", 'Who can call this API once connected (e.g. via Claude): "open" = anyone who signs in, "invite" = only you + emails you pre-approve. Default: invite when logged in, open when anonymous.').option("--target-auth-env <ENV_VAR>", "Read the upstream credential from this env var (CI-safe; required when there is no TTY and the API needs auth)").option("--force", "Proceed even if the API uses oauth2/openIdConnect target auth (you configure target auth yourself later)").option("-y, --yes", "Skip confirmation prompts").option("--tenant <slug>", "Tenant (consumer namespace: portal, login, users) for the new proxy; omit to be asked").option("--apikey <key>", "Use this API key for the proxy's door (api_key proxies). Without it, apichat detects the door and asks \u2014 or runs the consumer login for OAuth doors.").option("--xenduserid <id>", "Assert this end-user id (X-End-User-Id) \u2014 required by proxies with identified/pre-approved enforcement; you are asked for one when the proxy demands it.").option("--verbose", "Show the per-turn proxy curl trace (hidden by default)").option("-p, --prompt <question>", "One-shot question: answered through the external agent CLI after an MCP install, or by apichat itself (exits after answering when there is no TTY)").option("--install-mcp <cli>", "Install this proxy's MCP into an external agent CLI without asking: claude | codex. Also re-offers after an earlier decline.").action(action((project, opts) => runApichat({ ...opts, project, openapispec: opts.openapispec ?? opts.openapi })));
|
|
11794
|
+
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) => runApichat({ ...opts, project, openapispec: opts.openapispec ?? opts.openapi })));
|
|
11588
11795
|
var llm = program.command("llm").description("Manage a local LLM provider key for chat (optional \u2014 lifts model quality, bills your key)");
|
|
11589
11796
|
llm.command("set-key").description("Store an LLM provider key locally (OpenRouter/Anthropic/DeepSeek/OpenAI)").argument("[key]", "The API key (omit to enter it hidden at a prompt)").option("--model <id>", "Model id to use with this key (e.g. anthropic/claude-haiku-4.5)").action(action((key, opts) => runLlmSetKey(key, opts)));
|
|
11590
11797
|
llm.command("show").description("Show the locally stored LLM key (masked)").action(action(() => runLlmShow()));
|