bridgerapi 1.5.0 → 1.6.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 +185 -44
  2. package/package.json +1 -1
package/dist/cli.js CHANGED
@@ -245,6 +245,11 @@ var BACKENDS = [
245
245
  new DroidBackend()
246
246
  ];
247
247
  function pickBackend(model2) {
248
+ const override = process.env.BRIDGERAPI_BACKEND?.toLowerCase();
249
+ if (override) {
250
+ const forced = BACKENDS.find((b) => b.name === override && b.available());
251
+ if (forced) return forced;
252
+ }
248
253
  const m = model2.toLowerCase();
249
254
  for (const b of BACKENDS) {
250
255
  if (b.prefixes.some((p) => m.startsWith(p)) && b.available()) return b;
@@ -335,10 +340,10 @@ async function handleChat(req, res) {
335
340
  const model2 = body.model ?? "claude-sonnet-4-6";
336
341
  const streaming = Boolean(body.stream);
337
342
  const prompt = messagesToPrompt(messages);
338
- const backend = pickBackend(model2);
343
+ const backend2 = pickBackend(model2);
339
344
  const id = `chatcmpl-${(0, import_crypto.randomUUID)().replace(/-/g, "").slice(0, 20)}`;
340
345
  const ts = Math.floor(Date.now() / 1e3);
341
- console.log(` ${backend.name} model=${model2} stream=${streaming} turns=${messages.length}`);
346
+ console.log(` ${backend2.name} model=${model2} stream=${streaming} turns=${messages.length}`);
342
347
  if (streaming) {
343
348
  cors(res, 200);
344
349
  res.setHeader("Content-Type", "text/event-stream");
@@ -347,7 +352,7 @@ async function handleChat(req, res) {
347
352
  res.flushHeaders();
348
353
  res.write(chunk(id, ts, model2, { role: "assistant" }));
349
354
  try {
350
- for await (const raw of backend.stream(prompt, model2)) {
355
+ for await (const raw of backend2.stream(prompt, model2)) {
351
356
  res.write(chunk(id, ts, model2, { content: raw.toString("utf8") }));
352
357
  }
353
358
  } catch (err) {
@@ -358,7 +363,7 @@ async function handleChat(req, res) {
358
363
  res.end();
359
364
  } else {
360
365
  try {
361
- const [text, usage] = await backend.runBlocking(prompt, model2);
366
+ const [text, usage] = await backend2.runBlocking(prompt, model2);
362
367
  sendJson(res, 200, completion(id, ts, model2, text, usage));
363
368
  } catch (err) {
364
369
  console.error(` error: ${err.message}`);
@@ -402,10 +407,13 @@ var LABEL = "com.bridgerapi.server";
402
407
  function plistPath() {
403
408
  return (0, import_path.join)(HOME2, "Library/LaunchAgents", `${LABEL}.plist`);
404
409
  }
405
- function writePlist(port2, scriptPath, nodePath) {
410
+ function writePlist(port2, scriptPath, nodePath, backend2) {
406
411
  const logDir = (0, import_path.join)(HOME2, ".bridgerapi");
407
412
  (0, import_fs2.mkdirSync)(logDir, { recursive: true });
408
413
  (0, import_fs2.mkdirSync)((0, import_path.join)(HOME2, "Library/LaunchAgents"), { recursive: true });
414
+ const backendEntry = backend2 ? `
415
+ <key>BRIDGERAPI_BACKEND</key>
416
+ <string>${backend2}</string>` : "";
409
417
  const plist = `<?xml version="1.0" encoding="UTF-8"?>
410
418
  <!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN"
411
419
  "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
@@ -430,7 +438,7 @@ function writePlist(port2, scriptPath, nodePath) {
430
438
  <key>HOME</key>
431
439
  <string>${HOME2}</string>
432
440
  <key>CUSTOM_OKBRIDGER_API_KEY</key>
433
- <string>local</string>
441
+ <string>local</string>${backendEntry}
434
442
  </dict>
435
443
 
436
444
  <key>StandardOutPath</key>
@@ -451,10 +459,12 @@ function unitPath() {
451
459
  const configHome = process.env.XDG_CONFIG_HOME ?? (0, import_path.join)(HOME2, ".config");
452
460
  return (0, import_path.join)(configHome, "systemd/user/bridgerapi.service");
453
461
  }
454
- function writeUnit(port2, scriptPath, nodePath) {
462
+ function writeUnit(port2, scriptPath, nodePath, backend2) {
455
463
  const logDir = (0, import_path.join)(HOME2, ".bridgerapi");
456
464
  (0, import_fs2.mkdirSync)(logDir, { recursive: true });
457
465
  (0, import_fs2.mkdirSync)((0, import_path.join)(HOME2, ".config/systemd/user"), { recursive: true });
466
+ const backendLine = backend2 ? `
467
+ Environment=BRIDGERAPI_BACKEND=${backend2}` : "";
458
468
  const unit = `[Unit]
459
469
  Description=bridgerapi \u2014 OpenAI-compatible bridge for AI CLIs
460
470
  After=network.target
@@ -465,7 +475,7 @@ ExecStart=${nodePath} ${scriptPath} start
465
475
  Environment=BRIDGERAPI_PORT=${port2}
466
476
  Environment=HOME=${HOME2}
467
477
  Environment=CUSTOM_OKBRIDGER_API_KEY=local
468
- Environment=PATH=${HOME2}/.local/bin:/usr/local/bin:/usr/bin:/bin
478
+ Environment=PATH=${HOME2}/.local/bin:/usr/local/bin:/usr/bin:/bin${backendLine}
469
479
  Restart=always
470
480
  StandardOutput=append:${logDir}/server.log
471
481
  StandardError=append:${logDir}/server.log
@@ -475,7 +485,7 @@ WantedBy=default.target
475
485
  `;
476
486
  (0, import_fs2.writeFileSync)(unitPath(), unit);
477
487
  }
478
- function installService(port2) {
488
+ function installService(port2, backend2) {
479
489
  const scriptPath = process.argv[1];
480
490
  const nodePath = process.execPath;
481
491
  const os = (0, import_os2.platform)();
@@ -484,11 +494,11 @@ function installService(port2) {
484
494
  (0, import_child_process2.execSync)(`launchctl unload "${plistPath()}" 2>/dev/null`, { stdio: "ignore" });
485
495
  } catch {
486
496
  }
487
- writePlist(port2, scriptPath, nodePath);
497
+ writePlist(port2, scriptPath, nodePath, backend2);
488
498
  (0, import_child_process2.execSync)(`launchctl load -w "${plistPath()}"`);
489
499
  console.log(`\u2713 LaunchAgent installed \u2192 ${plistPath()}`);
490
500
  } else if (os === "linux") {
491
- writeUnit(port2, scriptPath, nodePath);
501
+ writeUnit(port2, scriptPath, nodePath, backend2);
492
502
  try {
493
503
  (0, import_child_process2.execSync)("systemctl --user daemon-reload");
494
504
  } catch {
@@ -547,13 +557,33 @@ function serviceStatus() {
547
557
  return { running: false };
548
558
  }
549
559
 
550
- // src/cli.ts
560
+ // src/config.ts
551
561
  var import_fs3 = require("fs");
552
562
  var import_os3 = require("os");
553
563
  var import_path2 = require("path");
564
+ var CONFIG_DIR = (0, import_path2.join)((0, import_os3.homedir)(), ".bridgerapi");
565
+ var CONFIG_FILE = (0, import_path2.join)(CONFIG_DIR, "config.json");
566
+ function loadConfig() {
567
+ try {
568
+ if ((0, import_fs3.existsSync)(CONFIG_FILE)) {
569
+ return JSON.parse((0, import_fs3.readFileSync)(CONFIG_FILE, "utf8"));
570
+ }
571
+ } catch {
572
+ }
573
+ return {};
574
+ }
575
+ function saveConfig(cfg) {
576
+ (0, import_fs3.mkdirSync)(CONFIG_DIR, { recursive: true });
577
+ (0, import_fs3.writeFileSync)(CONFIG_FILE, JSON.stringify(cfg, null, 2) + "\n");
578
+ }
579
+
580
+ // src/cli.ts
581
+ var import_fs4 = require("fs");
582
+ var import_os4 = require("os");
583
+ var import_path3 = require("path");
554
584
  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");
585
+ var DEFAULT_PORT = parseInt(process.env.BRIDGERAPI_PORT ?? "8082");
586
+ var LOG_DIR = (0, import_path3.join)((0, import_os4.homedir)(), ".bridgerapi");
557
587
  var INSTALL_HINTS = {
558
588
  claude: "claude login (Claude Code \u2014 claude.ai/download)",
559
589
  gemini: "gemini auth (Gemini CLI \u2014 npm i -g @google/gemini-cli)",
@@ -576,7 +606,6 @@ async function cmdSetup() {
576
606
  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
607
  console.log();
578
608
  const available = BACKENDS.filter((b) => b.available());
579
- const missing = BACKENDS.filter((b) => !b.available());
580
609
  console.log(" Backends detected:\n");
581
610
  for (const b of BACKENDS) {
582
611
  const ok = b.available();
@@ -588,8 +617,30 @@ async function cmdSetup() {
588
617
  console.log(" No backends found. Install at least one CLI above, then re-run: bridgerapi");
589
618
  process.exit(1);
590
619
  }
591
- const portAnswer = await ask(` Port [${PORT}]: `);
592
- const port2 = portAnswer ? parseInt(portAnswer) || PORT : PORT;
620
+ const cfg = loadConfig();
621
+ let chosenBackend;
622
+ if (available.length === 1) {
623
+ chosenBackend = available[0].name;
624
+ } else {
625
+ const names = available.map((b) => b.name);
626
+ const currentDefault = cfg.backend && names.includes(cfg.backend) ? cfg.backend : names[0];
627
+ const defaultIdx = names.indexOf(currentDefault);
628
+ console.log(" Which backend do you want to use as default?\n");
629
+ names.forEach((name, i) => {
630
+ const marker = i === defaultIdx ? " \u2190 default" : "";
631
+ console.log(` ${i + 1} ${name}${marker}`);
632
+ });
633
+ console.log();
634
+ const backendAnswer = await ask(` Choose [${defaultIdx + 1}]: `);
635
+ const parsed = parseInt(backendAnswer);
636
+ const backendIdx = backendAnswer && !isNaN(parsed) ? parsed - 1 : defaultIdx;
637
+ chosenBackend = names[Math.max(0, Math.min(backendIdx, names.length - 1))];
638
+ console.log();
639
+ }
640
+ const defaultPort = cfg.port ?? DEFAULT_PORT;
641
+ const portAnswer = await ask(` Port [${defaultPort}]: `);
642
+ const port2 = portAnswer ? parseInt(portAnswer) || defaultPort : defaultPort;
643
+ saveConfig({ backend: chosenBackend, port: port2 });
593
644
  console.log();
594
645
  console.log(" How do you want to run bridgerapi?");
595
646
  console.log(" 1 Foreground (stops when terminal closes)");
@@ -598,13 +649,16 @@ async function cmdSetup() {
598
649
  const choice = await ask(" Choose [1/2]: ");
599
650
  console.log();
600
651
  if (choice === "2") {
601
- cmdInstall(port2);
652
+ cmdInstall(port2, chosenBackend);
602
653
  } else {
603
- cmdStart(port2);
654
+ cmdStart(port2, chosenBackend);
604
655
  }
605
656
  }
606
- function cmdStart(port2) {
607
- (0, import_fs3.mkdirSync)(LOG_DIR, { recursive: true });
657
+ function cmdStart(port2, backend2) {
658
+ (0, import_fs4.mkdirSync)(LOG_DIR, { recursive: true });
659
+ const cfg = loadConfig();
660
+ const activeBackend = backend2 ?? cfg.backend;
661
+ if (activeBackend) process.env.BRIDGERAPI_BACKEND = activeBackend;
608
662
  const available = BACKENDS.filter((b) => b.available());
609
663
  if (available.length === 0) {
610
664
  console.error(" No CLI backends found. Run: bridgerapi to see setup instructions.");
@@ -617,7 +671,8 @@ function cmdStart(port2) {
617
671
  console.log(` Base URL : http://127.0.0.1:${port2}/v1`);
618
672
  console.log(` API Key : local`);
619
673
  console.log();
620
- console.log(` Backends : ${available.map((b) => b.name).join(", ")}`);
674
+ const backendLabel = activeBackend ? `${activeBackend} (all requests routed here)` : available.map((b) => b.name).join(", ") + " (auto-routed by model prefix)";
675
+ console.log(` Backend : ${backendLabel}`);
621
676
  console.log(` Logs : ${LOG_DIR}/server.log`);
622
677
  console.log();
623
678
  console.log(" Ctrl+C to stop.");
@@ -631,9 +686,11 @@ function cmdStart(port2) {
631
686
  process.exit(1);
632
687
  });
633
688
  }
634
- function cmdInstall(port2) {
689
+ function cmdInstall(port2, backend2) {
690
+ const cfg = loadConfig();
691
+ const activeBackend = backend2 ?? cfg.backend;
635
692
  try {
636
- installService(port2);
693
+ installService(port2, activeBackend);
637
694
  console.log();
638
695
  console.log(" Waiting for server to start\u2026");
639
696
  let attempts = 0;
@@ -649,6 +706,7 @@ function cmdInstall(port2) {
649
706
  console.log();
650
707
  console.log(` Base URL : http://127.0.0.1:${port2}/v1`);
651
708
  console.log(` API Key : local`);
709
+ if (activeBackend) console.log(` Backend : ${activeBackend}`);
652
710
  console.log();
653
711
  console.log(` Logs : tail -f ${LOG_DIR}/server.log`);
654
712
  console.log(` Stop : bridgerapi uninstall`);
@@ -679,11 +737,13 @@ function cmdUninstall() {
679
737
  }
680
738
  }
681
739
  function cmdStatus(port2) {
740
+ const cfg = loadConfig();
682
741
  const { running, pid } = serviceStatus();
683
742
  if (running) {
684
743
  console.log(` bridgerapi is running${pid ? ` (pid ${pid})` : ""}`);
685
744
  console.log(` Base URL : http://127.0.0.1:${port2}/v1`);
686
745
  console.log(` API Key : local`);
746
+ if (cfg.backend) console.log(` Backend : ${cfg.backend}`);
687
747
  } else {
688
748
  console.log(" bridgerapi is not running.");
689
749
  console.log(" Run: bridgerapi \u2192 setup wizard");
@@ -691,16 +751,76 @@ function cmdStatus(port2) {
691
751
  console.log(" Run: bridgerapi install \u2192 install background service");
692
752
  }
693
753
  }
694
- async function cmdChat(model2) {
754
+ function cmdConfig(args) {
755
+ const cfg = loadConfig();
756
+ if (args[0] === "set") {
757
+ for (const pair of args.slice(1)) {
758
+ const eqIdx = pair.indexOf("=");
759
+ if (eqIdx === -1) {
760
+ console.error(` Invalid format: ${pair} (use key=value)`);
761
+ process.exit(1);
762
+ }
763
+ const key = pair.slice(0, eqIdx);
764
+ const val = pair.slice(eqIdx + 1);
765
+ if (key === "backend") {
766
+ const known = BACKENDS.find((b) => b.name === val);
767
+ if (!known) {
768
+ console.error(` Unknown backend: ${val}`);
769
+ console.error(` Valid options: ${BACKENDS.map((b) => b.name).join(", ")}`);
770
+ process.exit(1);
771
+ }
772
+ cfg.backend = val;
773
+ console.log(` backend \u2192 ${val}`);
774
+ } else if (key === "port") {
775
+ const p = parseInt(val);
776
+ if (isNaN(p) || p < 1 || p > 65535) {
777
+ console.error(" Invalid port number");
778
+ process.exit(1);
779
+ }
780
+ cfg.port = p;
781
+ console.log(` port \u2192 ${p}`);
782
+ } else {
783
+ console.error(` Unknown key: ${key}`);
784
+ console.error(` Valid keys: backend, port`);
785
+ process.exit(1);
786
+ }
787
+ }
788
+ saveConfig(cfg);
789
+ console.log(" Saved.");
790
+ return;
791
+ }
792
+ if (args[0] === "reset") {
793
+ saveConfig({});
794
+ console.log(" Config reset to defaults.");
795
+ return;
796
+ }
797
+ console.log();
798
+ console.log(" bridgerapi config");
799
+ 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");
800
+ console.log(` backend : ${cfg.backend ?? "(auto \u2014 routed by model prefix)"}`);
801
+ console.log(` port : ${cfg.port ?? `${DEFAULT_PORT} (default)`}`);
802
+ console.log(` file : ${(0, import_path3.join)((0, import_os4.homedir)(), ".bridgerapi/config.json")}`);
803
+ console.log();
804
+ console.log(" To change:");
805
+ console.log(` bridgerapi config set backend=claude`);
806
+ console.log(` bridgerapi config set port=9000`);
807
+ console.log(` bridgerapi config reset`);
808
+ console.log();
809
+ }
810
+ async function cmdChat(model2, backendFlag) {
811
+ const cfg = loadConfig();
812
+ const activeBackend = backendFlag ?? (model2 && BACKENDS.find((b) => b.name === model2?.toLowerCase())?.name) ?? cfg.backend;
813
+ if (activeBackend) process.env.BRIDGERAPI_BACKEND = activeBackend;
814
+ const resolvedModel = model2 && BACKENDS.find((b) => b.name === model2.toLowerCase()) ? void 0 : model2;
695
815
  const available = BACKENDS.filter((b) => b.available());
696
816
  if (available.length === 0) {
697
817
  console.error(" No backends found. Run: bridgerapi to see setup instructions.");
698
818
  process.exit(1);
699
819
  }
700
- const resolvedModel = model2 ?? `${available[0].name}-default`;
701
- const backend = pickBackend(resolvedModel);
820
+ const fallbackModel = `${activeBackend ?? available[0].name}-default`;
821
+ const backend2 = pickBackend(resolvedModel ?? fallbackModel);
702
822
  console.log();
703
- console.log(` bridgerapi chat \u2014 ${backend.name}`);
823
+ console.log(` bridgerapi chat \u2014 ${backend2.name}`);
704
824
  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
825
  console.log(" Type a message and press Enter. Ctrl+C to exit.");
706
826
  console.log();
@@ -721,8 +841,8 @@ async function cmdChat(model2) {
721
841
  process.stdout.write("\n");
722
842
  let reply = "";
723
843
  try {
724
- process.stdout.write(`${backend.name}: `);
725
- for await (const chunk2 of backend.stream(messagesToPrompt(history), resolvedModel)) {
844
+ process.stdout.write(`${backend2.name}: `);
845
+ for await (const chunk2 of backend2.stream(messagesToPrompt(history), resolvedModel ?? fallbackModel)) {
726
846
  const piece = chunk2.toString("utf8");
727
847
  process.stdout.write(piece);
728
848
  reply += piece;
@@ -743,39 +863,57 @@ function showHelp() {
743
863
  bridgerapi \u2014 OpenAI-compatible API bridge for AI CLI tools
744
864
 
745
865
  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
866
+ bridgerapi Interactive setup wizard
867
+ bridgerapi chat [--model m] Chat in the terminal (routes by model prefix)
868
+ bridgerapi chat --backend <name> Chat using a specific backend
869
+ bridgerapi start [--port n] Start API server in the foreground
870
+ bridgerapi start --backend <name> Start forcing a specific backend for all requests
871
+ bridgerapi install [--port n] Install as a background service
872
+ bridgerapi uninstall Remove background service
873
+ bridgerapi status Show service status
874
+ bridgerapi config Show saved configuration
875
+ bridgerapi config set backend=<b> Set default backend (claude|gemini|codex|copilot|droid)
876
+ bridgerapi config set port=<n> Set default port
877
+ bridgerapi config reset Clear saved configuration
878
+
879
+ Backends: claude, gemini, codex, copilot, droid
752
880
  `.trim());
753
881
  }
754
882
  function parseArgs() {
883
+ const cfg = loadConfig();
755
884
  const args = process.argv.slice(2);
756
885
  const cmd2 = args[0] ?? "";
757
- let port2 = PORT;
886
+ let port2 = cfg.port ?? DEFAULT_PORT;
758
887
  let model2;
888
+ let backend2;
889
+ const rest2 = [];
759
890
  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];
891
+ if ((args[i] === "--port" || args[i] === "-p") && args[i + 1]) {
892
+ port2 = parseInt(args[++i]);
893
+ } else if ((args[i] === "--model" || args[i] === "-m") && args[i + 1]) {
894
+ model2 = args[++i];
895
+ } else if ((args[i] === "--backend" || args[i] === "-b") && args[i + 1]) {
896
+ backend2 = args[++i];
897
+ } else {
898
+ rest2.push(args[i]);
899
+ }
762
900
  }
763
- return { cmd: cmd2, port: port2, model: model2 };
901
+ return { cmd: cmd2, port: port2, model: model2, backend: backend2, rest: rest2 };
764
902
  }
765
- var { cmd, port, model } = parseArgs();
903
+ var { cmd, port, model, backend, rest } = parseArgs();
766
904
  switch (cmd) {
767
905
  case "":
768
906
  case "setup":
769
907
  cmdSetup();
770
908
  break;
771
909
  case "chat":
772
- cmdChat(model);
910
+ cmdChat(model, backend);
773
911
  break;
774
912
  case "start":
775
- cmdStart(port);
913
+ cmdStart(port, backend);
776
914
  break;
777
915
  case "install":
778
- cmdInstall(port);
916
+ cmdInstall(port, backend);
779
917
  break;
780
918
  case "uninstall":
781
919
  cmdUninstall();
@@ -783,6 +921,9 @@ switch (cmd) {
783
921
  case "status":
784
922
  cmdStatus(port);
785
923
  break;
924
+ case "config":
925
+ cmdConfig(rest);
926
+ break;
786
927
  case "help":
787
928
  case "--help":
788
929
  case "-h":
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "bridgerapi",
3
- "version": "1.5.0",
3
+ "version": "1.6.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",