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.
- package/dist/cli.js +290 -57
- 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
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
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
|
|
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(` ${
|
|
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
|
|
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
|
|
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/
|
|
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
|
|
556
|
-
var LOG_DIR = (0,
|
|
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
|
|
592
|
-
|
|
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,
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
701
|
-
const
|
|
912
|
+
const fallbackModel = `${activeBackend ?? available[0].name}-default`;
|
|
913
|
+
const backend2 = pickBackend(resolvedModel ?? fallbackModel);
|
|
702
914
|
console.log();
|
|
703
|
-
console.log(` bridgerapi chat \u2014 ${
|
|
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(`${
|
|
725
|
-
for await (const chunk2 of
|
|
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
|
|
747
|
-
bridgerapi chat [--model m]
|
|
748
|
-
bridgerapi
|
|
749
|
-
bridgerapi
|
|
750
|
-
bridgerapi
|
|
751
|
-
bridgerapi
|
|
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 =
|
|
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])
|
|
761
|
-
|
|
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":
|