bridgerapi 1.1.0 → 1.2.0

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.
Files changed (2) hide show
  1. package/dist/cli.js +91 -37
  2. package/package.json +1 -1
package/dist/cli.js CHANGED
@@ -91,11 +91,11 @@ var ClaudeBackend = class {
91
91
  available() {
92
92
  return (0, import_fs.existsSync)(this.bin) || Boolean(which("claude"));
93
93
  }
94
- async runBlocking(prompt, model) {
94
+ async runBlocking(prompt, model2) {
95
95
  const bin = which("claude") || this.bin;
96
96
  let out;
97
97
  try {
98
- out = (0, import_child_process.execFileSync)(bin, ["-p", "--output-format", "json", "--model", model], {
98
+ out = (0, import_child_process.execFileSync)(bin, ["-p", "--output-format", "json", "--model", model2], {
99
99
  input: prompt,
100
100
  encoding: "utf8",
101
101
  timeout: 3e5
@@ -106,9 +106,9 @@ var ClaudeBackend = class {
106
106
  const data = JSON.parse(out.trim() || "{}");
107
107
  return [data.result ?? "", data.usage ?? null];
108
108
  }
109
- async *stream(prompt, model) {
109
+ async *stream(prompt, model2) {
110
110
  const bin = which("claude") || this.bin;
111
- yield* spawnStream(bin, ["-p", "--output-format", "text", "--model", model], prompt);
111
+ yield* spawnStream(bin, ["-p", "--output-format", "text", "--model", model2], prompt);
112
112
  }
113
113
  };
114
114
  var GeminiBackend = class {
@@ -123,13 +123,13 @@ var GeminiBackend = class {
123
123
  available() {
124
124
  return Boolean(which("gemini")) || (0, import_fs.existsSync)(this.bin);
125
125
  }
126
- async runBlocking(prompt, model) {
126
+ async runBlocking(prompt, model2) {
127
127
  const bin = which("gemini") || this.bin;
128
128
  let out;
129
129
  try {
130
130
  out = (0, import_child_process.execFileSync)(
131
131
  bin,
132
- ["--output-format", "json", "--model", model, "--approval-mode", "yolo"],
132
+ ["--output-format", "json", "--model", model2, "--approval-mode", "yolo"],
133
133
  { input: prompt, encoding: "utf8", timeout: 3e5, env: process.env }
134
134
  );
135
135
  } catch (e) {
@@ -149,11 +149,11 @@ var GeminiBackend = class {
149
149
  return [raw, null];
150
150
  }
151
151
  }
152
- async *stream(prompt, model) {
152
+ async *stream(prompt, model2) {
153
153
  const bin = which("gemini") || this.bin;
154
154
  yield* spawnStream(
155
155
  bin,
156
- ["--output-format", "text", "--model", model, "--approval-mode", "yolo"],
156
+ ["--output-format", "text", "--model", model2, "--approval-mode", "yolo"],
157
157
  prompt
158
158
  );
159
159
  }
@@ -170,10 +170,10 @@ var CodexBackend = class {
170
170
  available() {
171
171
  return Boolean(which("codex"));
172
172
  }
173
- async runBlocking(prompt, model) {
173
+ async runBlocking(prompt, model2) {
174
174
  let out;
175
175
  try {
176
- out = (0, import_child_process.execFileSync)(this.bin, ["-q", "--model", model, prompt], {
176
+ out = (0, import_child_process.execFileSync)(this.bin, ["-q", "--model", model2, prompt], {
177
177
  encoding: "utf8",
178
178
  timeout: 3e5
179
179
  });
@@ -182,8 +182,8 @@ var CodexBackend = class {
182
182
  }
183
183
  return [out.trim(), null];
184
184
  }
185
- async *stream(prompt, model) {
186
- yield* spawnStream(this.bin, ["-q", "--model", model, prompt]);
185
+ async *stream(prompt, model2) {
186
+ yield* spawnStream(this.bin, ["-q", "--model", model2, prompt]);
187
187
  }
188
188
  };
189
189
  var CopilotBackend = class {
@@ -204,7 +204,7 @@ var CopilotBackend = class {
204
204
  return false;
205
205
  }
206
206
  }
207
- async runBlocking(prompt, model) {
207
+ async runBlocking(prompt, model2) {
208
208
  let out;
209
209
  try {
210
210
  out = (0, import_child_process.execFileSync)(this.bin, ["copilot", "suggest", "-t", "general", prompt], {
@@ -216,7 +216,7 @@ var CopilotBackend = class {
216
216
  }
217
217
  return [out.trim(), null];
218
218
  }
219
- async *stream(prompt, model) {
219
+ async *stream(prompt, model2) {
220
220
  yield* spawnStream(this.bin, ["copilot", "suggest", "-t", "general", prompt]);
221
221
  }
222
222
  };
@@ -226,8 +226,8 @@ var BACKENDS = [
226
226
  new CodexBackend(),
227
227
  new CopilotBackend()
228
228
  ];
229
- function pickBackend(model) {
230
- const m = model.toLowerCase();
229
+ function pickBackend(model2) {
230
+ const m = model2.toLowerCase();
231
231
  for (const b of BACKENDS) {
232
232
  if (b.prefixes.some((p) => m.startsWith(p))) {
233
233
  if (b.available()) return b;
@@ -245,23 +245,23 @@ function sse(data) {
245
245
 
246
246
  `;
247
247
  }
248
- function chunk(id, ts, model, delta, finish) {
248
+ function chunk(id, ts, model2, delta, finish) {
249
249
  return sse({
250
250
  id,
251
251
  object: "chat.completion.chunk",
252
252
  created: ts,
253
- model,
253
+ model: model2,
254
254
  choices: [{ index: 0, delta, finish_reason: finish ?? null }]
255
255
  });
256
256
  }
257
- function completion(id, ts, model, text, usage) {
257
+ function completion(id, ts, model2, text, usage) {
258
258
  const pt = usage ? (usage.input_tokens ?? 0) + (usage.cache_creation_input_tokens ?? 0) + (usage.cache_read_input_tokens ?? 0) + (usage.promptTokenCount ?? 0) : 0;
259
259
  const ct = usage ? (usage.output_tokens ?? 0) + (usage.candidatesTokenCount ?? 0) : 0;
260
260
  return {
261
261
  id,
262
262
  object: "chat.completion",
263
263
  created: ts,
264
- model,
264
+ model: model2,
265
265
  choices: [{ index: 0, message: { role: "assistant", content: text }, finish_reason: "stop" }],
266
266
  usage: { prompt_tokens: pt, completion_tokens: ct, total_tokens: pt + ct }
267
267
  };
@@ -313,34 +313,34 @@ async function handleChat(req, res) {
313
313
  sendJson(res, 400, { error: { message: "messages required", type: "invalid_request_error" } });
314
314
  return;
315
315
  }
316
- const model = body.model ?? "claude-sonnet-4-6";
316
+ const model2 = body.model ?? "claude-sonnet-4-6";
317
317
  const streaming = Boolean(body.stream);
318
318
  const prompt = messagesToPrompt(messages);
319
- const backend = pickBackend(model);
319
+ const backend = pickBackend(model2);
320
320
  const id = `chatcmpl-${(0, import_crypto.randomUUID)().replace(/-/g, "").slice(0, 20)}`;
321
321
  const ts = Math.floor(Date.now() / 1e3);
322
- console.log(` ${backend.name} model=${model} stream=${streaming} turns=${messages.length}`);
322
+ console.log(` ${backend.name} model=${model2} stream=${streaming} turns=${messages.length}`);
323
323
  if (streaming) {
324
324
  cors(res, 200);
325
325
  res.setHeader("Content-Type", "text/event-stream");
326
326
  res.setHeader("Cache-Control", "no-cache");
327
327
  res.setHeader("X-Accel-Buffering", "no");
328
328
  res.flushHeaders();
329
- res.write(chunk(id, ts, model, { role: "assistant" }));
329
+ res.write(chunk(id, ts, model2, { role: "assistant" }));
330
330
  try {
331
- for await (const raw of backend.stream(prompt, model)) {
332
- res.write(chunk(id, ts, model, { content: raw.toString("utf8") }));
331
+ for await (const raw of backend.stream(prompt, model2)) {
332
+ res.write(chunk(id, ts, model2, { content: raw.toString("utf8") }));
333
333
  }
334
334
  } catch (err) {
335
335
  console.error(` stream error: ${err.message}`);
336
336
  }
337
- res.write(chunk(id, ts, model, {}, "stop"));
337
+ res.write(chunk(id, ts, model2, {}, "stop"));
338
338
  res.write("data: [DONE]\n\n");
339
339
  res.end();
340
340
  } else {
341
341
  try {
342
- const [text, usage] = await backend.runBlocking(prompt, model);
343
- sendJson(res, 200, completion(id, ts, model, text, usage));
342
+ const [text, usage] = await backend.runBlocking(prompt, model2);
343
+ sendJson(res, 200, completion(id, ts, model2, text, usage));
344
344
  } catch (err) {
345
345
  console.error(` error: ${err.message}`);
346
346
  sendJson(res, 500, { error: { message: err.message, type: "server_error" } });
@@ -598,12 +598,15 @@ function parseArgs() {
598
598
  const args = process.argv.slice(2);
599
599
  const cmd2 = args[0] ?? "";
600
600
  let port2 = PORT;
601
+ let model2;
601
602
  for (let i = 1; i < args.length; i++) {
602
603
  if ((args[i] === "--port" || args[i] === "-p") && args[i + 1]) {
603
604
  port2 = parseInt(args[++i]);
605
+ } else if ((args[i] === "--model" || args[i] === "-m") && args[i + 1]) {
606
+ model2 = args[++i];
604
607
  }
605
608
  }
606
- return { cmd: cmd2, port: port2 };
609
+ return { cmd: cmd2, port: port2, model: model2 };
607
610
  }
608
611
  function cmdStart(port2) {
609
612
  (0, import_fs3.mkdirSync)(LOG_DIR, { recursive: true });
@@ -719,17 +722,65 @@ function cmdBackends() {
719
722
  `);
720
723
  }
721
724
  }
725
+ async function cmdChat(model2) {
726
+ const available = BACKENDS.filter((b) => b.available());
727
+ if (available.length === 0) {
728
+ console.error(" No backends found. Run: bridgerapi to see setup instructions.");
729
+ process.exit(1);
730
+ }
731
+ const resolvedModel = model2 ?? available[0].models[0];
732
+ const backend = pickBackend(resolvedModel);
733
+ console.log();
734
+ console.log(` bridgerapi chat \u2014 ${backend.name} \u2014 ${resolvedModel}`);
735
+ console.log(" \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500");
736
+ console.log(" Type your message and press Enter. Ctrl+C to exit.");
737
+ console.log();
738
+ const history = [];
739
+ const rl = (0, import_readline.createInterface)({ input: process.stdin, output: process.stdout });
740
+ const prompt = () => {
741
+ rl.question("You: ", async (input) => {
742
+ const text = input.trim();
743
+ if (!text) {
744
+ prompt();
745
+ return;
746
+ }
747
+ history.push({ role: "user", content: text });
748
+ process.stdout.write("\n");
749
+ let reply = "";
750
+ try {
751
+ process.stdout.write(`${backend.name}: `);
752
+ for await (const chunk2 of backend.stream(messagesToPrompt(history), resolvedModel)) {
753
+ const piece = chunk2.toString("utf8");
754
+ process.stdout.write(piece);
755
+ reply += piece;
756
+ }
757
+ } catch (err) {
758
+ process.stdout.write(`
759
+ Error: ${err.message}`);
760
+ }
761
+ process.stdout.write("\n\n");
762
+ if (reply) history.push({ role: "assistant", content: reply });
763
+ prompt();
764
+ });
765
+ };
766
+ rl.on("close", () => {
767
+ console.log("\n Goodbye.");
768
+ process.exit(0);
769
+ });
770
+ prompt();
771
+ }
722
772
  function showHelp() {
723
773
  console.log(`
724
774
  bridgerapi \u2014 OpenAI-compatible API bridge for AI CLI tools
725
775
 
726
776
  Usage:
727
- bridgerapi Interactive setup wizard
728
- bridgerapi start [--port n] Start server in the foreground
729
- bridgerapi install [--port n] Install as a background service
730
- bridgerapi uninstall Remove background service
731
- bridgerapi status Show service status
732
- bridgerapi backends List detected backends
777
+ bridgerapi Interactive setup wizard
778
+ bridgerapi chat [--model <name>] Interactive chat session in terminal
779
+ bridgerapi start [--port n] Start API server in the foreground
780
+ bridgerapi install [--port n] Install as a background service
781
+ bridgerapi uninstall Remove background service
782
+ bridgerapi status Show service status
783
+ bridgerapi backends List detected backends
733
784
 
734
785
  Supported backends (auto-detected):
735
786
  claude-* \u2192 Claude Code CLI (claude login)
@@ -738,12 +789,15 @@ function showHelp() {
738
789
  copilot \u2192 GitHub Copilot (gh auth login)
739
790
  `.trim());
740
791
  }
741
- var { cmd, port } = parseArgs();
792
+ var { cmd, port, model } = parseArgs();
742
793
  switch (cmd) {
743
794
  case "":
744
795
  case "setup":
745
796
  cmdSetup();
746
797
  break;
798
+ case "chat":
799
+ cmdChat(model);
800
+ break;
747
801
  case "start":
748
802
  cmdStart(port);
749
803
  break;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "bridgerapi",
3
- "version": "1.1.0",
3
+ "version": "1.2.0",
4
4
  "description": "Turn any AI CLI (Claude Code, Gemini, Codex, GitHub Copilot) into an OpenAI-compatible API — no API keys needed",
5
5
  "keywords": [
6
6
  "claude",