bridgerapi 1.5.0 → 1.7.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 +290 -57
  2. package/package.json +1 -1
package/dist/cli.js CHANGED
@@ -86,6 +86,15 @@ var ClaudeBackend = class {
86
86
  available() {
87
87
  return (0, import_fs.existsSync)(this.bin) || Boolean(which("claude"));
88
88
  }
89
+ async models() {
90
+ return [
91
+ "claude-opus-4-6",
92
+ "claude-opus-4-6-fast",
93
+ "claude-sonnet-4-6",
94
+ "claude-sonnet-4-5-20250929",
95
+ "claude-haiku-4-5-20251001"
96
+ ];
97
+ }
89
98
  async runBlocking(prompt, model2) {
90
99
  const bin = which("claude") || this.bin;
91
100
  let out;
@@ -117,6 +126,17 @@ var GeminiBackend = class {
117
126
  available() {
118
127
  return Boolean(which("gemini")) || (0, import_fs.existsSync)(this.bin);
119
128
  }
129
+ async models() {
130
+ return [
131
+ "gemini-3.1-pro-preview",
132
+ "gemini-3-pro-preview",
133
+ "gemini-3-flash-preview",
134
+ "gemini-2.5-pro",
135
+ "gemini-2.5-flash",
136
+ "gemini-2.0-flash",
137
+ "gemini-1.5-pro"
138
+ ];
139
+ }
120
140
  async runBlocking(prompt, model2) {
121
141
  const bin = which("gemini") || this.bin;
122
142
  let out;
@@ -157,6 +177,20 @@ var CodexBackend = class {
157
177
  available() {
158
178
  return Boolean(which("codex"));
159
179
  }
180
+ async models() {
181
+ return [
182
+ "gpt-5-codex",
183
+ "gpt-5.1-codex",
184
+ "gpt-5.1-codex-max",
185
+ "gpt-5.1",
186
+ "gpt-5.2",
187
+ "gpt-5.2-codex",
188
+ "gpt-5.3-codex",
189
+ "gpt-5-2025-08-07",
190
+ "o4-mini",
191
+ "o3"
192
+ ];
193
+ }
160
194
  async runBlocking(prompt, model2) {
161
195
  let out;
162
196
  try {
@@ -190,6 +224,9 @@ var CopilotBackend = class {
190
224
  return false;
191
225
  }
192
226
  }
227
+ async models() {
228
+ return ["copilot"];
229
+ }
193
230
  async runBlocking(prompt, model2) {
194
231
  let out;
195
232
  try {
@@ -206,18 +243,66 @@ var CopilotBackend = class {
206
243
  yield* spawnStream(this.bin, ["copilot", "suggest", "-t", "general", prompt]);
207
244
  }
208
245
  };
209
- var DroidBackend = class {
246
+ var DroidBackend = class _DroidBackend {
210
247
  constructor() {
211
248
  this.name = "droid";
212
- // Route Droid-exclusive model families + explicit "droid" prefix
213
249
  this.prefixes = ["droid", "glm", "kimi", "minimax"];
214
250
  }
251
+ static {
252
+ // Up-to-date as of March 2026 — source: droid exec --help + Factory docs
253
+ this.KNOWN_MODELS = [
254
+ // OpenAI via Droid
255
+ "gpt-5-codex",
256
+ "gpt-5.1-codex",
257
+ "gpt-5.1-codex-max",
258
+ "gpt-5.1",
259
+ "gpt-5.2",
260
+ "gpt-5.2-codex",
261
+ "gpt-5.3-codex",
262
+ "gpt-5-2025-08-07",
263
+ // Anthropic via Droid
264
+ "claude-opus-4-6",
265
+ "claude-opus-4-6-fast",
266
+ "claude-opus-4-1-20250805",
267
+ "claude-sonnet-4-5-20250929",
268
+ "claude-haiku-4-5-20251001",
269
+ // Google via Droid
270
+ "gemini-3.1-pro-preview",
271
+ "gemini-3-pro-preview",
272
+ "gemini-3-flash-preview",
273
+ // Droid-native (GLM / Kimi / MiniMax)
274
+ "glm-4.6",
275
+ "glm-4.7",
276
+ "glm-5",
277
+ "kimi-k2.5",
278
+ "minimax-m2.5"
279
+ ];
280
+ }
215
281
  get bin() {
216
282
  return process.env.DROID_BIN ?? which("droid") ?? `${HOME}/.local/bin/droid`;
217
283
  }
218
284
  available() {
219
285
  return (0, import_fs.existsSync)(this.bin) || Boolean(which("droid"));
220
286
  }
287
+ async models() {
288
+ try {
289
+ const help = (0, import_child_process.execFileSync)(which("droid") || this.bin, ["exec", "--help"], {
290
+ encoding: "utf8",
291
+ timeout: 5e3,
292
+ stdio: ["ignore", "pipe", "pipe"]
293
+ });
294
+ const MODEL_RE = /\b([a-z][a-z0-9]+(?:[.\-][a-z0-9]+){1,})\b/g;
295
+ const found = /* @__PURE__ */ new Set();
296
+ for (const [, id] of help.matchAll(MODEL_RE)) {
297
+ if (id.length >= 5 && !["help", "exec", "droid", "text", "json", "output", "format", "model", "usage"].includes(id)) {
298
+ found.add(id);
299
+ }
300
+ }
301
+ if (found.size > 0) return [...found];
302
+ } catch {
303
+ }
304
+ return _DroidBackend.KNOWN_MODELS;
305
+ }
221
306
  async runBlocking(prompt, model2) {
222
307
  const bin = which("droid") || this.bin;
223
308
  let out;
@@ -245,6 +330,11 @@ var BACKENDS = [
245
330
  new DroidBackend()
246
331
  ];
247
332
  function pickBackend(model2) {
333
+ const override = process.env.BRIDGERAPI_BACKEND?.toLowerCase();
334
+ if (override) {
335
+ const forced = BACKENDS.find((b) => b.name === override && b.available());
336
+ if (forced) return forced;
337
+ }
248
338
  const m = model2.toLowerCase();
249
339
  for (const b of BACKENDS) {
250
340
  if (b.prefixes.some((p) => m.startsWith(p)) && b.available()) return b;
@@ -301,18 +391,25 @@ async function readBody(req) {
301
391
  req.on("error", reject);
302
392
  });
303
393
  }
304
- function handleModels(res) {
394
+ async function handleModels(res) {
305
395
  const ts = Math.floor(Date.now() / 1e3);
396
+ const override = process.env.BRIDGERAPI_BACKEND?.toLowerCase();
397
+ const pinned = override ? BACKENDS.find((b) => b.name === override && b.available()) : null;
398
+ if (pinned) {
399
+ const ids = await pinned.models();
400
+ sendJson(res, 200, {
401
+ object: "list",
402
+ data: ids.map((id) => ({ id, object: "model", created: ts, owned_by: pinned.name }))
403
+ });
404
+ return;
405
+ }
306
406
  const available = BACKENDS.filter((b) => b.available());
307
- sendJson(res, 200, {
308
- object: "list",
309
- data: available.map((b) => ({
310
- id: b.name,
311
- object: "model",
312
- created: ts,
313
- owned_by: "bridgerapi"
314
- }))
315
- });
407
+ const allModels = [];
408
+ for (const b of available) {
409
+ const ids = await b.models();
410
+ for (const id of ids) allModels.push({ id, object: "model", created: ts, owned_by: b.name });
411
+ }
412
+ sendJson(res, 200, { object: "list", data: allModels });
316
413
  }
317
414
  function handleHealth(res, port2) {
318
415
  const backends = {};
@@ -335,10 +432,10 @@ async function handleChat(req, res) {
335
432
  const model2 = body.model ?? "claude-sonnet-4-6";
336
433
  const streaming = Boolean(body.stream);
337
434
  const prompt = messagesToPrompt(messages);
338
- const backend = pickBackend(model2);
435
+ const backend2 = pickBackend(model2);
339
436
  const id = `chatcmpl-${(0, import_crypto.randomUUID)().replace(/-/g, "").slice(0, 20)}`;
340
437
  const ts = Math.floor(Date.now() / 1e3);
341
- console.log(` ${backend.name} model=${model2} stream=${streaming} turns=${messages.length}`);
438
+ console.log(` ${backend2.name} model=${model2} stream=${streaming} turns=${messages.length}`);
342
439
  if (streaming) {
343
440
  cors(res, 200);
344
441
  res.setHeader("Content-Type", "text/event-stream");
@@ -347,7 +444,7 @@ async function handleChat(req, res) {
347
444
  res.flushHeaders();
348
445
  res.write(chunk(id, ts, model2, { role: "assistant" }));
349
446
  try {
350
- for await (const raw of backend.stream(prompt, model2)) {
447
+ for await (const raw of backend2.stream(prompt, model2)) {
351
448
  res.write(chunk(id, ts, model2, { content: raw.toString("utf8") }));
352
449
  }
353
450
  } catch (err) {
@@ -358,7 +455,7 @@ async function handleChat(req, res) {
358
455
  res.end();
359
456
  } else {
360
457
  try {
361
- const [text, usage] = await backend.runBlocking(prompt, model2);
458
+ const [text, usage] = await backend2.runBlocking(prompt, model2);
362
459
  sendJson(res, 200, completion(id, ts, model2, text, usage));
363
460
  } catch (err) {
364
461
  console.error(` error: ${err.message}`);
@@ -376,7 +473,7 @@ function createBridgeServer(port2) {
376
473
  return;
377
474
  }
378
475
  if (method === "GET" && (path === "/v1/models" || path === "/models")) {
379
- handleModels(res);
476
+ await handleModels(res);
380
477
  return;
381
478
  }
382
479
  if (method === "GET" && path === "/health") {
@@ -402,10 +499,13 @@ var LABEL = "com.bridgerapi.server";
402
499
  function plistPath() {
403
500
  return (0, import_path.join)(HOME2, "Library/LaunchAgents", `${LABEL}.plist`);
404
501
  }
405
- function writePlist(port2, scriptPath, nodePath) {
502
+ function writePlist(port2, scriptPath, nodePath, backend2) {
406
503
  const logDir = (0, import_path.join)(HOME2, ".bridgerapi");
407
504
  (0, import_fs2.mkdirSync)(logDir, { recursive: true });
408
505
  (0, import_fs2.mkdirSync)((0, import_path.join)(HOME2, "Library/LaunchAgents"), { recursive: true });
506
+ const backendEntry = backend2 ? `
507
+ <key>BRIDGERAPI_BACKEND</key>
508
+ <string>${backend2}</string>` : "";
409
509
  const plist = `<?xml version="1.0" encoding="UTF-8"?>
410
510
  <!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN"
411
511
  "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
@@ -430,7 +530,7 @@ function writePlist(port2, scriptPath, nodePath) {
430
530
  <key>HOME</key>
431
531
  <string>${HOME2}</string>
432
532
  <key>CUSTOM_OKBRIDGER_API_KEY</key>
433
- <string>local</string>
533
+ <string>local</string>${backendEntry}
434
534
  </dict>
435
535
 
436
536
  <key>StandardOutPath</key>
@@ -451,10 +551,12 @@ function unitPath() {
451
551
  const configHome = process.env.XDG_CONFIG_HOME ?? (0, import_path.join)(HOME2, ".config");
452
552
  return (0, import_path.join)(configHome, "systemd/user/bridgerapi.service");
453
553
  }
454
- function writeUnit(port2, scriptPath, nodePath) {
554
+ function writeUnit(port2, scriptPath, nodePath, backend2) {
455
555
  const logDir = (0, import_path.join)(HOME2, ".bridgerapi");
456
556
  (0, import_fs2.mkdirSync)(logDir, { recursive: true });
457
557
  (0, import_fs2.mkdirSync)((0, import_path.join)(HOME2, ".config/systemd/user"), { recursive: true });
558
+ const backendLine = backend2 ? `
559
+ Environment=BRIDGERAPI_BACKEND=${backend2}` : "";
458
560
  const unit = `[Unit]
459
561
  Description=bridgerapi \u2014 OpenAI-compatible bridge for AI CLIs
460
562
  After=network.target
@@ -465,7 +567,7 @@ ExecStart=${nodePath} ${scriptPath} start
465
567
  Environment=BRIDGERAPI_PORT=${port2}
466
568
  Environment=HOME=${HOME2}
467
569
  Environment=CUSTOM_OKBRIDGER_API_KEY=local
468
- Environment=PATH=${HOME2}/.local/bin:/usr/local/bin:/usr/bin:/bin
570
+ Environment=PATH=${HOME2}/.local/bin:/usr/local/bin:/usr/bin:/bin${backendLine}
469
571
  Restart=always
470
572
  StandardOutput=append:${logDir}/server.log
471
573
  StandardError=append:${logDir}/server.log
@@ -475,7 +577,7 @@ WantedBy=default.target
475
577
  `;
476
578
  (0, import_fs2.writeFileSync)(unitPath(), unit);
477
579
  }
478
- function installService(port2) {
580
+ function installService(port2, backend2) {
479
581
  const scriptPath = process.argv[1];
480
582
  const nodePath = process.execPath;
481
583
  const os = (0, import_os2.platform)();
@@ -484,11 +586,11 @@ function installService(port2) {
484
586
  (0, import_child_process2.execSync)(`launchctl unload "${plistPath()}" 2>/dev/null`, { stdio: "ignore" });
485
587
  } catch {
486
588
  }
487
- writePlist(port2, scriptPath, nodePath);
589
+ writePlist(port2, scriptPath, nodePath, backend2);
488
590
  (0, import_child_process2.execSync)(`launchctl load -w "${plistPath()}"`);
489
591
  console.log(`\u2713 LaunchAgent installed \u2192 ${plistPath()}`);
490
592
  } else if (os === "linux") {
491
- writeUnit(port2, scriptPath, nodePath);
593
+ writeUnit(port2, scriptPath, nodePath, backend2);
492
594
  try {
493
595
  (0, import_child_process2.execSync)("systemctl --user daemon-reload");
494
596
  } catch {
@@ -547,13 +649,33 @@ function serviceStatus() {
547
649
  return { running: false };
548
650
  }
549
651
 
550
- // src/cli.ts
652
+ // src/config.ts
551
653
  var import_fs3 = require("fs");
552
654
  var import_os3 = require("os");
553
655
  var import_path2 = require("path");
656
+ var CONFIG_DIR = (0, import_path2.join)((0, import_os3.homedir)(), ".bridgerapi");
657
+ var CONFIG_FILE = (0, import_path2.join)(CONFIG_DIR, "config.json");
658
+ function loadConfig() {
659
+ try {
660
+ if ((0, import_fs3.existsSync)(CONFIG_FILE)) {
661
+ return JSON.parse((0, import_fs3.readFileSync)(CONFIG_FILE, "utf8"));
662
+ }
663
+ } catch {
664
+ }
665
+ return {};
666
+ }
667
+ function saveConfig(cfg) {
668
+ (0, import_fs3.mkdirSync)(CONFIG_DIR, { recursive: true });
669
+ (0, import_fs3.writeFileSync)(CONFIG_FILE, JSON.stringify(cfg, null, 2) + "\n");
670
+ }
671
+
672
+ // src/cli.ts
673
+ var import_fs4 = require("fs");
674
+ var import_os4 = require("os");
675
+ var import_path3 = require("path");
554
676
  var import_readline = require("readline");
555
- var PORT = parseInt(process.env.BRIDGERAPI_PORT ?? "8082");
556
- var LOG_DIR = (0, import_path2.join)((0, import_os3.homedir)(), ".bridgerapi");
677
+ var DEFAULT_PORT = parseInt(process.env.BRIDGERAPI_PORT ?? "8082");
678
+ var LOG_DIR = (0, import_path3.join)((0, import_os4.homedir)(), ".bridgerapi");
557
679
  var INSTALL_HINTS = {
558
680
  claude: "claude login (Claude Code \u2014 claude.ai/download)",
559
681
  gemini: "gemini auth (Gemini CLI \u2014 npm i -g @google/gemini-cli)",
@@ -576,7 +698,6 @@ async function cmdSetup() {
576
698
  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\u2500\u2500\u2500");
577
699
  console.log();
578
700
  const available = BACKENDS.filter((b) => b.available());
579
- const missing = BACKENDS.filter((b) => !b.available());
580
701
  console.log(" Backends detected:\n");
581
702
  for (const b of BACKENDS) {
582
703
  const ok = b.available();
@@ -588,8 +709,30 @@ async function cmdSetup() {
588
709
  console.log(" No backends found. Install at least one CLI above, then re-run: bridgerapi");
589
710
  process.exit(1);
590
711
  }
591
- const portAnswer = await ask(` Port [${PORT}]: `);
592
- const port2 = portAnswer ? parseInt(portAnswer) || PORT : PORT;
712
+ const cfg = loadConfig();
713
+ let chosenBackend;
714
+ if (available.length === 1) {
715
+ chosenBackend = available[0].name;
716
+ } else {
717
+ const names = available.map((b) => b.name);
718
+ const currentDefault = cfg.backend && names.includes(cfg.backend) ? cfg.backend : names[0];
719
+ const defaultIdx = names.indexOf(currentDefault);
720
+ console.log(" Which backend do you want to use as default?\n");
721
+ names.forEach((name, i) => {
722
+ const marker = i === defaultIdx ? " \u2190 default" : "";
723
+ console.log(` ${i + 1} ${name}${marker}`);
724
+ });
725
+ console.log();
726
+ const backendAnswer = await ask(` Choose [${defaultIdx + 1}]: `);
727
+ const parsed = parseInt(backendAnswer);
728
+ const backendIdx = backendAnswer && !isNaN(parsed) ? parsed - 1 : defaultIdx;
729
+ chosenBackend = names[Math.max(0, Math.min(backendIdx, names.length - 1))];
730
+ console.log();
731
+ }
732
+ const defaultPort = cfg.port ?? DEFAULT_PORT;
733
+ const portAnswer = await ask(` Port [${defaultPort}]: `);
734
+ const port2 = portAnswer ? parseInt(portAnswer) || defaultPort : defaultPort;
735
+ saveConfig({ backend: chosenBackend, port: port2 });
593
736
  console.log();
594
737
  console.log(" How do you want to run bridgerapi?");
595
738
  console.log(" 1 Foreground (stops when terminal closes)");
@@ -598,13 +741,16 @@ async function cmdSetup() {
598
741
  const choice = await ask(" Choose [1/2]: ");
599
742
  console.log();
600
743
  if (choice === "2") {
601
- cmdInstall(port2);
744
+ cmdInstall(port2, chosenBackend);
602
745
  } else {
603
- cmdStart(port2);
746
+ cmdStart(port2, chosenBackend);
604
747
  }
605
748
  }
606
- function cmdStart(port2) {
607
- (0, import_fs3.mkdirSync)(LOG_DIR, { recursive: true });
749
+ function cmdStart(port2, backend2) {
750
+ (0, import_fs4.mkdirSync)(LOG_DIR, { recursive: true });
751
+ const cfg = loadConfig();
752
+ const activeBackend = backend2 ?? cfg.backend;
753
+ if (activeBackend) process.env.BRIDGERAPI_BACKEND = activeBackend;
608
754
  const available = BACKENDS.filter((b) => b.available());
609
755
  if (available.length === 0) {
610
756
  console.error(" No CLI backends found. Run: bridgerapi to see setup instructions.");
@@ -617,7 +763,8 @@ function cmdStart(port2) {
617
763
  console.log(` Base URL : http://127.0.0.1:${port2}/v1`);
618
764
  console.log(` API Key : local`);
619
765
  console.log();
620
- console.log(` Backends : ${available.map((b) => b.name).join(", ")}`);
766
+ const backendLabel = activeBackend ? `${activeBackend} (all requests routed here)` : available.map((b) => b.name).join(", ") + " (auto-routed by model prefix)";
767
+ console.log(` Backend : ${backendLabel}`);
621
768
  console.log(` Logs : ${LOG_DIR}/server.log`);
622
769
  console.log();
623
770
  console.log(" Ctrl+C to stop.");
@@ -631,9 +778,11 @@ function cmdStart(port2) {
631
778
  process.exit(1);
632
779
  });
633
780
  }
634
- function cmdInstall(port2) {
781
+ function cmdInstall(port2, backend2) {
782
+ const cfg = loadConfig();
783
+ const activeBackend = backend2 ?? cfg.backend;
635
784
  try {
636
- installService(port2);
785
+ installService(port2, activeBackend);
637
786
  console.log();
638
787
  console.log(" Waiting for server to start\u2026");
639
788
  let attempts = 0;
@@ -649,6 +798,7 @@ function cmdInstall(port2) {
649
798
  console.log();
650
799
  console.log(` Base URL : http://127.0.0.1:${port2}/v1`);
651
800
  console.log(` API Key : local`);
801
+ if (activeBackend) console.log(` Backend : ${activeBackend}`);
652
802
  console.log();
653
803
  console.log(` Logs : tail -f ${LOG_DIR}/server.log`);
654
804
  console.log(` Stop : bridgerapi uninstall`);
@@ -679,11 +829,13 @@ function cmdUninstall() {
679
829
  }
680
830
  }
681
831
  function cmdStatus(port2) {
832
+ const cfg = loadConfig();
682
833
  const { running, pid } = serviceStatus();
683
834
  if (running) {
684
835
  console.log(` bridgerapi is running${pid ? ` (pid ${pid})` : ""}`);
685
836
  console.log(` Base URL : http://127.0.0.1:${port2}/v1`);
686
837
  console.log(` API Key : local`);
838
+ if (cfg.backend) console.log(` Backend : ${cfg.backend}`);
687
839
  } else {
688
840
  console.log(" bridgerapi is not running.");
689
841
  console.log(" Run: bridgerapi \u2192 setup wizard");
@@ -691,16 +843,76 @@ function cmdStatus(port2) {
691
843
  console.log(" Run: bridgerapi install \u2192 install background service");
692
844
  }
693
845
  }
694
- async function cmdChat(model2) {
846
+ function cmdConfig(args) {
847
+ const cfg = loadConfig();
848
+ if (args[0] === "set") {
849
+ for (const pair of args.slice(1)) {
850
+ const eqIdx = pair.indexOf("=");
851
+ if (eqIdx === -1) {
852
+ console.error(` Invalid format: ${pair} (use key=value)`);
853
+ process.exit(1);
854
+ }
855
+ const key = pair.slice(0, eqIdx);
856
+ const val = pair.slice(eqIdx + 1);
857
+ if (key === "backend") {
858
+ const known = BACKENDS.find((b) => b.name === val);
859
+ if (!known) {
860
+ console.error(` Unknown backend: ${val}`);
861
+ console.error(` Valid options: ${BACKENDS.map((b) => b.name).join(", ")}`);
862
+ process.exit(1);
863
+ }
864
+ cfg.backend = val;
865
+ console.log(` backend \u2192 ${val}`);
866
+ } else if (key === "port") {
867
+ const p = parseInt(val);
868
+ if (isNaN(p) || p < 1 || p > 65535) {
869
+ console.error(" Invalid port number");
870
+ process.exit(1);
871
+ }
872
+ cfg.port = p;
873
+ console.log(` port \u2192 ${p}`);
874
+ } else {
875
+ console.error(` Unknown key: ${key}`);
876
+ console.error(` Valid keys: backend, port`);
877
+ process.exit(1);
878
+ }
879
+ }
880
+ saveConfig(cfg);
881
+ console.log(" Saved.");
882
+ return;
883
+ }
884
+ if (args[0] === "reset") {
885
+ saveConfig({});
886
+ console.log(" Config reset to defaults.");
887
+ return;
888
+ }
889
+ console.log();
890
+ console.log(" bridgerapi config");
891
+ 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");
892
+ console.log(` backend : ${cfg.backend ?? "(auto \u2014 routed by model prefix)"}`);
893
+ console.log(` port : ${cfg.port ?? `${DEFAULT_PORT} (default)`}`);
894
+ console.log(` file : ${(0, import_path3.join)((0, import_os4.homedir)(), ".bridgerapi/config.json")}`);
895
+ console.log();
896
+ console.log(" To change:");
897
+ console.log(` bridgerapi config set backend=claude`);
898
+ console.log(` bridgerapi config set port=9000`);
899
+ console.log(` bridgerapi config reset`);
900
+ console.log();
901
+ }
902
+ async function cmdChat(model2, backendFlag) {
903
+ const cfg = loadConfig();
904
+ const activeBackend = backendFlag ?? (model2 && BACKENDS.find((b) => b.name === model2?.toLowerCase())?.name) ?? cfg.backend;
905
+ if (activeBackend) process.env.BRIDGERAPI_BACKEND = activeBackend;
906
+ const resolvedModel = model2 && BACKENDS.find((b) => b.name === model2.toLowerCase()) ? void 0 : model2;
695
907
  const available = BACKENDS.filter((b) => b.available());
696
908
  if (available.length === 0) {
697
909
  console.error(" No backends found. Run: bridgerapi to see setup instructions.");
698
910
  process.exit(1);
699
911
  }
700
- const resolvedModel = model2 ?? `${available[0].name}-default`;
701
- const backend = pickBackend(resolvedModel);
912
+ const fallbackModel = `${activeBackend ?? available[0].name}-default`;
913
+ const backend2 = pickBackend(resolvedModel ?? fallbackModel);
702
914
  console.log();
703
- console.log(` bridgerapi chat \u2014 ${backend.name}`);
915
+ console.log(` bridgerapi chat \u2014 ${backend2.name}`);
704
916
  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");
705
917
  console.log(" Type a message and press Enter. Ctrl+C to exit.");
706
918
  console.log();
@@ -721,8 +933,8 @@ async function cmdChat(model2) {
721
933
  process.stdout.write("\n");
722
934
  let reply = "";
723
935
  try {
724
- process.stdout.write(`${backend.name}: `);
725
- for await (const chunk2 of backend.stream(messagesToPrompt(history), resolvedModel)) {
936
+ process.stdout.write(`${backend2.name}: `);
937
+ for await (const chunk2 of backend2.stream(messagesToPrompt(history), resolvedModel ?? fallbackModel)) {
726
938
  const piece = chunk2.toString("utf8");
727
939
  process.stdout.write(piece);
728
940
  reply += piece;
@@ -743,39 +955,57 @@ function showHelp() {
743
955
  bridgerapi \u2014 OpenAI-compatible API bridge for AI CLI tools
744
956
 
745
957
  Usage:
746
- bridgerapi Interactive setup wizard
747
- bridgerapi chat [--model m] Chat directly in the terminal
748
- bridgerapi start [--port n] Start API server in the foreground
749
- bridgerapi install [--port n] Install as a background service
750
- bridgerapi uninstall Remove background service
751
- bridgerapi status Show service status
958
+ bridgerapi Interactive setup wizard
959
+ bridgerapi chat [--model m] Chat in the terminal (routes by model prefix)
960
+ bridgerapi chat --backend <name> Chat using a specific backend
961
+ bridgerapi start [--port n] Start API server in the foreground
962
+ bridgerapi start --backend <name> Start forcing a specific backend for all requests
963
+ bridgerapi install [--port n] Install as a background service
964
+ bridgerapi uninstall Remove background service
965
+ bridgerapi status Show service status
966
+ bridgerapi config Show saved configuration
967
+ bridgerapi config set backend=<b> Set default backend (claude|gemini|codex|copilot|droid)
968
+ bridgerapi config set port=<n> Set default port
969
+ bridgerapi config reset Clear saved configuration
970
+
971
+ Backends: claude, gemini, codex, copilot, droid
752
972
  `.trim());
753
973
  }
754
974
  function parseArgs() {
975
+ const cfg = loadConfig();
755
976
  const args = process.argv.slice(2);
756
977
  const cmd2 = args[0] ?? "";
757
- let port2 = PORT;
978
+ let port2 = cfg.port ?? DEFAULT_PORT;
758
979
  let model2;
980
+ let backend2;
981
+ const rest2 = [];
759
982
  for (let i = 1; i < args.length; i++) {
760
- if ((args[i] === "--port" || args[i] === "-p") && args[i + 1]) port2 = parseInt(args[++i]);
761
- if ((args[i] === "--model" || args[i] === "-m") && args[i + 1]) model2 = args[++i];
983
+ if ((args[i] === "--port" || args[i] === "-p") && args[i + 1]) {
984
+ port2 = parseInt(args[++i]);
985
+ } else if ((args[i] === "--model" || args[i] === "-m") && args[i + 1]) {
986
+ model2 = args[++i];
987
+ } else if ((args[i] === "--backend" || args[i] === "-b") && args[i + 1]) {
988
+ backend2 = args[++i];
989
+ } else {
990
+ rest2.push(args[i]);
991
+ }
762
992
  }
763
- return { cmd: cmd2, port: port2, model: model2 };
993
+ return { cmd: cmd2, port: port2, model: model2, backend: backend2, rest: rest2 };
764
994
  }
765
- var { cmd, port, model } = parseArgs();
995
+ var { cmd, port, model, backend, rest } = parseArgs();
766
996
  switch (cmd) {
767
997
  case "":
768
998
  case "setup":
769
999
  cmdSetup();
770
1000
  break;
771
1001
  case "chat":
772
- cmdChat(model);
1002
+ cmdChat(model, backend);
773
1003
  break;
774
1004
  case "start":
775
- cmdStart(port);
1005
+ cmdStart(port, backend);
776
1006
  break;
777
1007
  case "install":
778
- cmdInstall(port);
1008
+ cmdInstall(port, backend);
779
1009
  break;
780
1010
  case "uninstall":
781
1011
  cmdUninstall();
@@ -783,6 +1013,9 @@ switch (cmd) {
783
1013
  case "status":
784
1014
  cmdStatus(port);
785
1015
  break;
1016
+ case "config":
1017
+ cmdConfig(rest);
1018
+ break;
786
1019
  case "help":
787
1020
  case "--help":
788
1021
  case "-h":
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "bridgerapi",
3
- "version": "1.5.0",
3
+ "version": "1.7.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",