bridgerapi 1.7.0 → 1.8.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 +276 -89
- package/package.json +1 -1
package/dist/cli.js
CHANGED
|
@@ -57,9 +57,32 @@ function messagesToPrompt(messages) {
|
|
|
57
57
|
|
|
58
58
|
// src/backends.ts
|
|
59
59
|
var import_child_process = require("child_process");
|
|
60
|
+
var import_fs2 = require("fs");
|
|
61
|
+
var import_os2 = require("os");
|
|
62
|
+
var import_path2 = require("path");
|
|
63
|
+
|
|
64
|
+
// src/config.ts
|
|
60
65
|
var import_fs = require("fs");
|
|
61
66
|
var import_os = require("os");
|
|
62
|
-
var
|
|
67
|
+
var import_path = require("path");
|
|
68
|
+
var CONFIG_DIR = (0, import_path.join)((0, import_os.homedir)(), ".bridgerapi");
|
|
69
|
+
var CONFIG_FILE = (0, import_path.join)(CONFIG_DIR, "config.json");
|
|
70
|
+
function loadConfig() {
|
|
71
|
+
try {
|
|
72
|
+
if ((0, import_fs.existsSync)(CONFIG_FILE)) {
|
|
73
|
+
return JSON.parse((0, import_fs.readFileSync)(CONFIG_FILE, "utf8"));
|
|
74
|
+
}
|
|
75
|
+
} catch {
|
|
76
|
+
}
|
|
77
|
+
return {};
|
|
78
|
+
}
|
|
79
|
+
function saveConfig(cfg) {
|
|
80
|
+
(0, import_fs.mkdirSync)(CONFIG_DIR, { recursive: true });
|
|
81
|
+
(0, import_fs.writeFileSync)(CONFIG_FILE, JSON.stringify(cfg, null, 2) + "\n");
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
// src/backends.ts
|
|
85
|
+
var HOME = (0, import_os2.homedir)();
|
|
63
86
|
function which(cmd2) {
|
|
64
87
|
try {
|
|
65
88
|
return (0, import_child_process.execFileSync)("which", [cmd2], { encoding: "utf8" }).trim();
|
|
@@ -67,24 +90,131 @@ function which(cmd2) {
|
|
|
67
90
|
return "";
|
|
68
91
|
}
|
|
69
92
|
}
|
|
93
|
+
var SKIP_TOKENS = /* @__PURE__ */ new Set([
|
|
94
|
+
"output",
|
|
95
|
+
"format",
|
|
96
|
+
"text",
|
|
97
|
+
"json",
|
|
98
|
+
"model",
|
|
99
|
+
"usage",
|
|
100
|
+
"help",
|
|
101
|
+
"exec",
|
|
102
|
+
"run",
|
|
103
|
+
"list",
|
|
104
|
+
"command",
|
|
105
|
+
"option",
|
|
106
|
+
"default",
|
|
107
|
+
"true",
|
|
108
|
+
"false",
|
|
109
|
+
"stdin",
|
|
110
|
+
"stdout",
|
|
111
|
+
"stderr",
|
|
112
|
+
"path",
|
|
113
|
+
"file",
|
|
114
|
+
"config",
|
|
115
|
+
"error",
|
|
116
|
+
"warn",
|
|
117
|
+
"approval",
|
|
118
|
+
"mode",
|
|
119
|
+
"yolo",
|
|
120
|
+
"version",
|
|
121
|
+
"verbose",
|
|
122
|
+
"debug",
|
|
123
|
+
"quiet"
|
|
124
|
+
]);
|
|
125
|
+
function extractModelIds(text) {
|
|
126
|
+
const seen = /* @__PURE__ */ new Set();
|
|
127
|
+
const re = /\b([a-z][a-z0-9]*(?:[.\-][a-z0-9]+)+)\b/g;
|
|
128
|
+
for (const [, id] of text.matchAll(re)) {
|
|
129
|
+
if (id.length >= 5 && !SKIP_TOKENS.has(id)) seen.add(id);
|
|
130
|
+
}
|
|
131
|
+
return [...seen];
|
|
132
|
+
}
|
|
70
133
|
async function* spawnStream(cmd2, args, stdin) {
|
|
71
|
-
const proc = (0, import_child_process.spawn)(cmd2, args, {
|
|
72
|
-
|
|
73
|
-
|
|
134
|
+
const proc = (0, import_child_process.spawn)(cmd2, args, { env: process.env, stdio: ["pipe", "pipe", "pipe"] });
|
|
135
|
+
if (stdin !== void 0) proc.stdin.end(stdin);
|
|
136
|
+
const stderrBufs = [];
|
|
137
|
+
proc.stderr.on("data", (c) => stderrBufs.push(c));
|
|
138
|
+
let hasOutput = false;
|
|
139
|
+
try {
|
|
140
|
+
for await (const chunk2 of proc.stdout) {
|
|
141
|
+
hasOutput = true;
|
|
142
|
+
yield chunk2;
|
|
143
|
+
}
|
|
144
|
+
} finally {
|
|
145
|
+
if (proc.exitCode === null) proc.kill();
|
|
146
|
+
}
|
|
147
|
+
const exitCode = await new Promise((resolve) => {
|
|
148
|
+
proc.exitCode !== null ? resolve(proc.exitCode) : proc.on("close", (c) => resolve(c ?? 0));
|
|
74
149
|
});
|
|
75
|
-
if (
|
|
76
|
-
|
|
150
|
+
if (!hasOutput && exitCode !== 0) {
|
|
151
|
+
const stderr = Buffer.concat(stderrBufs).toString().trim();
|
|
152
|
+
throw new Error(stderr || `${(0, import_path2.basename)(cmd2)} exited with code ${exitCode}`);
|
|
153
|
+
}
|
|
77
154
|
}
|
|
155
|
+
var GenericBackend = class {
|
|
156
|
+
constructor(def) {
|
|
157
|
+
this.name = def.name;
|
|
158
|
+
this.prefixes = def.prefixes;
|
|
159
|
+
this.def = def;
|
|
160
|
+
}
|
|
161
|
+
get bin() {
|
|
162
|
+
return which(this.def.bin) || `${HOME}/.local/bin/${this.def.bin}`;
|
|
163
|
+
}
|
|
164
|
+
available() {
|
|
165
|
+
return Boolean(which(this.def.bin)) || (0, import_fs2.existsSync)(`${HOME}/.local/bin/${this.def.bin}`);
|
|
166
|
+
}
|
|
167
|
+
async models() {
|
|
168
|
+
if (this.def.modelsCmd) {
|
|
169
|
+
try {
|
|
170
|
+
const out = (0, import_child_process.execFileSync)(this.bin, this.def.modelsCmd, {
|
|
171
|
+
encoding: "utf8",
|
|
172
|
+
timeout: 5e3,
|
|
173
|
+
stdio: ["ignore", "pipe", "pipe"]
|
|
174
|
+
});
|
|
175
|
+
const found = extractModelIds(out);
|
|
176
|
+
if (found.length > 0) return found;
|
|
177
|
+
} catch {
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
return this.def.models ?? [];
|
|
181
|
+
}
|
|
182
|
+
buildArgs(model2) {
|
|
183
|
+
return this.def.args.map((a) => a === "{model}" ? model2 : a);
|
|
184
|
+
}
|
|
185
|
+
async runBlocking(prompt, model2) {
|
|
186
|
+
const args = this.buildArgs(model2);
|
|
187
|
+
let out;
|
|
188
|
+
try {
|
|
189
|
+
if (this.def.promptMode === "stdin") {
|
|
190
|
+
out = (0, import_child_process.execFileSync)(this.bin, args, { input: prompt, encoding: "utf8", timeout: 3e5 });
|
|
191
|
+
} else {
|
|
192
|
+
out = (0, import_child_process.execFileSync)(this.bin, [...args, prompt], { encoding: "utf8", timeout: 3e5 });
|
|
193
|
+
}
|
|
194
|
+
} catch (e) {
|
|
195
|
+
throw new Error(e.stderr?.trim() || `${this.def.bin} exited non-zero`);
|
|
196
|
+
}
|
|
197
|
+
return [out.trim(), null];
|
|
198
|
+
}
|
|
199
|
+
async *stream(prompt, model2) {
|
|
200
|
+
const args = this.buildArgs(model2);
|
|
201
|
+
if (this.def.promptMode === "stdin") {
|
|
202
|
+
yield* spawnStream(this.bin, args, prompt);
|
|
203
|
+
} else {
|
|
204
|
+
yield* spawnStream(this.bin, [...args, prompt]);
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
};
|
|
78
208
|
var ClaudeBackend = class {
|
|
79
209
|
constructor() {
|
|
80
210
|
this.name = "claude";
|
|
81
211
|
this.prefixes = ["claude"];
|
|
82
212
|
}
|
|
83
213
|
get bin() {
|
|
84
|
-
return process.env.CLAUDE_BIN ?? `${HOME}/.local/bin/claude`;
|
|
214
|
+
return process.env.CLAUDE_BIN ?? which("claude") ?? `${HOME}/.local/bin/claude`;
|
|
85
215
|
}
|
|
86
216
|
available() {
|
|
87
|
-
return (0,
|
|
217
|
+
return Boolean(which("claude")) || (0, import_fs2.existsSync)(`${HOME}/.local/bin/claude`);
|
|
88
218
|
}
|
|
89
219
|
async models() {
|
|
90
220
|
return [
|
|
@@ -96,10 +226,9 @@ var ClaudeBackend = class {
|
|
|
96
226
|
];
|
|
97
227
|
}
|
|
98
228
|
async runBlocking(prompt, model2) {
|
|
99
|
-
const bin = which("claude") || this.bin;
|
|
100
229
|
let out;
|
|
101
230
|
try {
|
|
102
|
-
out = (0, import_child_process.execFileSync)(bin, ["-p", "--output-format", "json", "--model", model2], {
|
|
231
|
+
out = (0, import_child_process.execFileSync)(this.bin, ["-p", "--output-format", "json", "--model", model2], {
|
|
103
232
|
input: prompt,
|
|
104
233
|
encoding: "utf8",
|
|
105
234
|
timeout: 3e5
|
|
@@ -107,12 +236,15 @@ var ClaudeBackend = class {
|
|
|
107
236
|
} catch (e) {
|
|
108
237
|
throw new Error(e.stderr?.trim() || `claude exited non-zero`);
|
|
109
238
|
}
|
|
110
|
-
|
|
111
|
-
|
|
239
|
+
try {
|
|
240
|
+
const data = JSON.parse(out.trim() || "{}");
|
|
241
|
+
return [data.result ?? "", data.usage ?? null];
|
|
242
|
+
} catch {
|
|
243
|
+
return [out.trim(), null];
|
|
244
|
+
}
|
|
112
245
|
}
|
|
113
246
|
async *stream(prompt, model2) {
|
|
114
|
-
|
|
115
|
-
yield* spawnStream(bin, ["-p", "--output-format", "text", "--model", model2], prompt);
|
|
247
|
+
yield* spawnStream(this.bin, ["-p", "--output-format", "text", "--model", model2], prompt);
|
|
116
248
|
}
|
|
117
249
|
};
|
|
118
250
|
var GeminiBackend = class {
|
|
@@ -124,7 +256,7 @@ var GeminiBackend = class {
|
|
|
124
256
|
return process.env.GEMINI_BIN ?? which("gemini") ?? "/opt/homebrew/bin/gemini";
|
|
125
257
|
}
|
|
126
258
|
available() {
|
|
127
|
-
return Boolean(which("gemini")) || (0,
|
|
259
|
+
return Boolean(which("gemini")) || (0, import_fs2.existsSync)(this.bin);
|
|
128
260
|
}
|
|
129
261
|
async models() {
|
|
130
262
|
return [
|
|
@@ -138,10 +270,9 @@ var GeminiBackend = class {
|
|
|
138
270
|
];
|
|
139
271
|
}
|
|
140
272
|
async runBlocking(prompt, model2) {
|
|
141
|
-
const bin = which("gemini") || this.bin;
|
|
142
273
|
let out;
|
|
143
274
|
try {
|
|
144
|
-
out = (0, import_child_process.execFileSync)(bin, ["--output-format", "json", "--model", model2, "--approval-mode", "yolo"], {
|
|
275
|
+
out = (0, import_child_process.execFileSync)(this.bin, ["--output-format", "json", "--model", model2, "--approval-mode", "yolo"], {
|
|
145
276
|
input: prompt,
|
|
146
277
|
encoding: "utf8",
|
|
147
278
|
timeout: 3e5,
|
|
@@ -162,8 +293,11 @@ var GeminiBackend = class {
|
|
|
162
293
|
}
|
|
163
294
|
}
|
|
164
295
|
async *stream(prompt, model2) {
|
|
165
|
-
|
|
166
|
-
|
|
296
|
+
yield* spawnStream(
|
|
297
|
+
this.bin,
|
|
298
|
+
["--output-format", "text", "--model", model2, "--approval-mode", "yolo"],
|
|
299
|
+
prompt
|
|
300
|
+
);
|
|
167
301
|
}
|
|
168
302
|
};
|
|
169
303
|
var CodexBackend = class {
|
|
@@ -249,9 +383,8 @@ var DroidBackend = class _DroidBackend {
|
|
|
249
383
|
this.prefixes = ["droid", "glm", "kimi", "minimax"];
|
|
250
384
|
}
|
|
251
385
|
static {
|
|
252
|
-
// Up-to-date as of March 2026 — source: droid exec --help
|
|
386
|
+
// Up-to-date as of March 2026 — source: Factory docs + droid exec --help
|
|
253
387
|
this.KNOWN_MODELS = [
|
|
254
|
-
// OpenAI via Droid
|
|
255
388
|
"gpt-5-codex",
|
|
256
389
|
"gpt-5.1-codex",
|
|
257
390
|
"gpt-5.1-codex-max",
|
|
@@ -260,17 +393,14 @@ var DroidBackend = class _DroidBackend {
|
|
|
260
393
|
"gpt-5.2-codex",
|
|
261
394
|
"gpt-5.3-codex",
|
|
262
395
|
"gpt-5-2025-08-07",
|
|
263
|
-
// Anthropic via Droid
|
|
264
396
|
"claude-opus-4-6",
|
|
265
397
|
"claude-opus-4-6-fast",
|
|
266
398
|
"claude-opus-4-1-20250805",
|
|
267
399
|
"claude-sonnet-4-5-20250929",
|
|
268
400
|
"claude-haiku-4-5-20251001",
|
|
269
|
-
// Google via Droid
|
|
270
401
|
"gemini-3.1-pro-preview",
|
|
271
402
|
"gemini-3-pro-preview",
|
|
272
403
|
"gemini-3-flash-preview",
|
|
273
|
-
// Droid-native (GLM / Kimi / MiniMax)
|
|
274
404
|
"glm-4.6",
|
|
275
405
|
"glm-4.7",
|
|
276
406
|
"glm-5",
|
|
@@ -282,7 +412,7 @@ var DroidBackend = class _DroidBackend {
|
|
|
282
412
|
return process.env.DROID_BIN ?? which("droid") ?? `${HOME}/.local/bin/droid`;
|
|
283
413
|
}
|
|
284
414
|
available() {
|
|
285
|
-
return (0,
|
|
415
|
+
return Boolean(which("droid")) || (0, import_fs2.existsSync)(`${HOME}/.local/bin/droid`);
|
|
286
416
|
}
|
|
287
417
|
async models() {
|
|
288
418
|
try {
|
|
@@ -291,44 +421,49 @@ var DroidBackend = class _DroidBackend {
|
|
|
291
421
|
timeout: 5e3,
|
|
292
422
|
stdio: ["ignore", "pipe", "pipe"]
|
|
293
423
|
});
|
|
294
|
-
const
|
|
295
|
-
|
|
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];
|
|
424
|
+
const found = extractModelIds(help);
|
|
425
|
+
if (found.length > 0) return found;
|
|
302
426
|
} catch {
|
|
303
427
|
}
|
|
304
428
|
return _DroidBackend.KNOWN_MODELS;
|
|
305
429
|
}
|
|
306
430
|
async runBlocking(prompt, model2) {
|
|
307
|
-
const bin = which("droid") || this.bin;
|
|
308
431
|
let out;
|
|
309
432
|
try {
|
|
310
|
-
out = (0, import_child_process.execFileSync)(
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
timeout: 3e5
|
|
314
|
-
|
|
433
|
+
out = (0, import_child_process.execFileSync)(
|
|
434
|
+
which("droid") || this.bin,
|
|
435
|
+
["exec", "--output-format", "text", "--model", model2, "-"],
|
|
436
|
+
{ input: prompt, encoding: "utf8", timeout: 3e5 }
|
|
437
|
+
);
|
|
315
438
|
} catch (e) {
|
|
316
439
|
throw new Error(e.stderr?.trim() || `droid exited non-zero`);
|
|
317
440
|
}
|
|
318
441
|
return [out.trim(), null];
|
|
319
442
|
}
|
|
320
443
|
async *stream(prompt, model2) {
|
|
321
|
-
|
|
322
|
-
|
|
444
|
+
yield* spawnStream(
|
|
445
|
+
which("droid") || this.bin,
|
|
446
|
+
["exec", "--output-format", "text", "--model", model2, "-"],
|
|
447
|
+
prompt
|
|
448
|
+
);
|
|
323
449
|
}
|
|
324
450
|
};
|
|
325
|
-
var
|
|
451
|
+
var BUILTIN = [
|
|
326
452
|
new ClaudeBackend(),
|
|
327
453
|
new GeminiBackend(),
|
|
328
454
|
new CodexBackend(),
|
|
329
455
|
new CopilotBackend(),
|
|
330
456
|
new DroidBackend()
|
|
331
457
|
];
|
|
458
|
+
function loadUserBackends() {
|
|
459
|
+
try {
|
|
460
|
+
const cfg = loadConfig();
|
|
461
|
+
return (cfg.customBackends ?? []).map((def) => new GenericBackend(def));
|
|
462
|
+
} catch {
|
|
463
|
+
return [];
|
|
464
|
+
}
|
|
465
|
+
}
|
|
466
|
+
var BACKENDS = [...BUILTIN, ...loadUserBackends()];
|
|
332
467
|
function pickBackend(model2) {
|
|
333
468
|
const override = process.env.BRIDGERAPI_BACKEND?.toLowerCase();
|
|
334
469
|
if (override) {
|
|
@@ -448,7 +583,11 @@ async function handleChat(req, res) {
|
|
|
448
583
|
res.write(chunk(id, ts, model2, { content: raw.toString("utf8") }));
|
|
449
584
|
}
|
|
450
585
|
} catch (err) {
|
|
451
|
-
|
|
586
|
+
const msg = err.message ?? String(err);
|
|
587
|
+
console.error(` stream error [${backend2.name}]: ${msg}`);
|
|
588
|
+
res.write(chunk(id, ts, model2, { content: `
|
|
589
|
+
|
|
590
|
+
\u26A0\uFE0F ${backend2.name} error: ${msg}` }));
|
|
452
591
|
}
|
|
453
592
|
res.write(chunk(id, ts, model2, {}, "stop"));
|
|
454
593
|
res.write("data: [DONE]\n\n");
|
|
@@ -491,18 +630,18 @@ function createBridgeServer(port2) {
|
|
|
491
630
|
|
|
492
631
|
// src/service.ts
|
|
493
632
|
var import_child_process2 = require("child_process");
|
|
494
|
-
var
|
|
495
|
-
var
|
|
496
|
-
var
|
|
497
|
-
var HOME2 = (0,
|
|
633
|
+
var import_fs3 = require("fs");
|
|
634
|
+
var import_os3 = require("os");
|
|
635
|
+
var import_path3 = require("path");
|
|
636
|
+
var HOME2 = (0, import_os3.homedir)();
|
|
498
637
|
var LABEL = "com.bridgerapi.server";
|
|
499
638
|
function plistPath() {
|
|
500
|
-
return (0,
|
|
639
|
+
return (0, import_path3.join)(HOME2, "Library/LaunchAgents", `${LABEL}.plist`);
|
|
501
640
|
}
|
|
502
641
|
function writePlist(port2, scriptPath, nodePath, backend2) {
|
|
503
|
-
const logDir = (0,
|
|
504
|
-
(0,
|
|
505
|
-
(0,
|
|
642
|
+
const logDir = (0, import_path3.join)(HOME2, ".bridgerapi");
|
|
643
|
+
(0, import_fs3.mkdirSync)(logDir, { recursive: true });
|
|
644
|
+
(0, import_fs3.mkdirSync)((0, import_path3.join)(HOME2, "Library/LaunchAgents"), { recursive: true });
|
|
506
645
|
const backendEntry = backend2 ? `
|
|
507
646
|
<key>BRIDGERAPI_BACKEND</key>
|
|
508
647
|
<string>${backend2}</string>` : "";
|
|
@@ -545,16 +684,16 @@ function writePlist(port2, scriptPath, nodePath, backend2) {
|
|
|
545
684
|
<true/>
|
|
546
685
|
</dict>
|
|
547
686
|
</plist>`;
|
|
548
|
-
(0,
|
|
687
|
+
(0, import_fs3.writeFileSync)(plistPath(), plist);
|
|
549
688
|
}
|
|
550
689
|
function unitPath() {
|
|
551
|
-
const configHome = process.env.XDG_CONFIG_HOME ?? (0,
|
|
552
|
-
return (0,
|
|
690
|
+
const configHome = process.env.XDG_CONFIG_HOME ?? (0, import_path3.join)(HOME2, ".config");
|
|
691
|
+
return (0, import_path3.join)(configHome, "systemd/user/bridgerapi.service");
|
|
553
692
|
}
|
|
554
693
|
function writeUnit(port2, scriptPath, nodePath, backend2) {
|
|
555
|
-
const logDir = (0,
|
|
556
|
-
(0,
|
|
557
|
-
(0,
|
|
694
|
+
const logDir = (0, import_path3.join)(HOME2, ".bridgerapi");
|
|
695
|
+
(0, import_fs3.mkdirSync)(logDir, { recursive: true });
|
|
696
|
+
(0, import_fs3.mkdirSync)((0, import_path3.join)(HOME2, ".config/systemd/user"), { recursive: true });
|
|
558
697
|
const backendLine = backend2 ? `
|
|
559
698
|
Environment=BRIDGERAPI_BACKEND=${backend2}` : "";
|
|
560
699
|
const unit = `[Unit]
|
|
@@ -575,12 +714,12 @@ StandardError=append:${logDir}/server.log
|
|
|
575
714
|
[Install]
|
|
576
715
|
WantedBy=default.target
|
|
577
716
|
`;
|
|
578
|
-
(0,
|
|
717
|
+
(0, import_fs3.writeFileSync)(unitPath(), unit);
|
|
579
718
|
}
|
|
580
719
|
function installService(port2, backend2) {
|
|
581
720
|
const scriptPath = process.argv[1];
|
|
582
721
|
const nodePath = process.execPath;
|
|
583
|
-
const os = (0,
|
|
722
|
+
const os = (0, import_os3.platform)();
|
|
584
723
|
if (os === "darwin") {
|
|
585
724
|
try {
|
|
586
725
|
(0, import_child_process2.execSync)(`launchctl unload "${plistPath()}" 2>/dev/null`, { stdio: "ignore" });
|
|
@@ -602,15 +741,15 @@ function installService(port2, backend2) {
|
|
|
602
741
|
}
|
|
603
742
|
}
|
|
604
743
|
function uninstallService() {
|
|
605
|
-
const os = (0,
|
|
744
|
+
const os = (0, import_os3.platform)();
|
|
606
745
|
if (os === "darwin") {
|
|
607
746
|
const p = plistPath();
|
|
608
|
-
if ((0,
|
|
747
|
+
if ((0, import_fs3.existsSync)(p)) {
|
|
609
748
|
try {
|
|
610
749
|
(0, import_child_process2.execSync)(`launchctl unload "${p}"`);
|
|
611
750
|
} catch {
|
|
612
751
|
}
|
|
613
|
-
(0,
|
|
752
|
+
(0, import_fs3.unlinkSync)(p);
|
|
614
753
|
console.log("\u2713 LaunchAgent removed");
|
|
615
754
|
} else {
|
|
616
755
|
console.log(" bridgerapi service is not installed");
|
|
@@ -621,8 +760,8 @@ function uninstallService() {
|
|
|
621
760
|
(0, import_child_process2.execSync)("systemctl --user disable --now bridgerapi");
|
|
622
761
|
} catch {
|
|
623
762
|
}
|
|
624
|
-
if ((0,
|
|
625
|
-
(0,
|
|
763
|
+
if ((0, import_fs3.existsSync)(p)) {
|
|
764
|
+
(0, import_fs3.unlinkSync)(p);
|
|
626
765
|
try {
|
|
627
766
|
(0, import_child_process2.execSync)("systemctl --user daemon-reload");
|
|
628
767
|
} catch {
|
|
@@ -634,7 +773,7 @@ function uninstallService() {
|
|
|
634
773
|
}
|
|
635
774
|
}
|
|
636
775
|
function serviceStatus() {
|
|
637
|
-
const os = (0,
|
|
776
|
+
const os = (0, import_os3.platform)();
|
|
638
777
|
try {
|
|
639
778
|
if (os === "darwin") {
|
|
640
779
|
const out = (0, import_child_process2.execSync)(`launchctl list ${LABEL} 2>/dev/null`, { encoding: "utf8" });
|
|
@@ -649,33 +788,13 @@ function serviceStatus() {
|
|
|
649
788
|
return { running: false };
|
|
650
789
|
}
|
|
651
790
|
|
|
652
|
-
// src/config.ts
|
|
653
|
-
var import_fs3 = require("fs");
|
|
654
|
-
var import_os3 = require("os");
|
|
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
791
|
// src/cli.ts
|
|
673
792
|
var import_fs4 = require("fs");
|
|
674
793
|
var import_os4 = require("os");
|
|
675
|
-
var
|
|
794
|
+
var import_path4 = require("path");
|
|
676
795
|
var import_readline = require("readline");
|
|
677
796
|
var DEFAULT_PORT = parseInt(process.env.BRIDGERAPI_PORT ?? "8082");
|
|
678
|
-
var LOG_DIR = (0,
|
|
797
|
+
var LOG_DIR = (0, import_path4.join)((0, import_os4.homedir)(), ".bridgerapi");
|
|
679
798
|
var INSTALL_HINTS = {
|
|
680
799
|
claude: "claude login (Claude Code \u2014 claude.ai/download)",
|
|
681
800
|
gemini: "gemini auth (Gemini CLI \u2014 npm i -g @google/gemini-cli)",
|
|
@@ -891,7 +1010,7 @@ function cmdConfig(args) {
|
|
|
891
1010
|
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
1011
|
console.log(` backend : ${cfg.backend ?? "(auto \u2014 routed by model prefix)"}`);
|
|
893
1012
|
console.log(` port : ${cfg.port ?? `${DEFAULT_PORT} (default)`}`);
|
|
894
|
-
console.log(` file : ${(0,
|
|
1013
|
+
console.log(` file : ${(0, import_path4.join)((0, import_os4.homedir)(), ".bridgerapi/config.json")}`);
|
|
895
1014
|
console.log();
|
|
896
1015
|
console.log(" To change:");
|
|
897
1016
|
console.log(` bridgerapi config set backend=claude`);
|
|
@@ -899,6 +1018,67 @@ function cmdConfig(args) {
|
|
|
899
1018
|
console.log(` bridgerapi config reset`);
|
|
900
1019
|
console.log();
|
|
901
1020
|
}
|
|
1021
|
+
async function cmdBackendAdd() {
|
|
1022
|
+
console.log();
|
|
1023
|
+
console.log(" Add a custom backend CLI");
|
|
1024
|
+
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");
|
|
1025
|
+
console.log(" This lets bridgerapi drive any AI CLI tool.");
|
|
1026
|
+
console.log();
|
|
1027
|
+
const name = await ask(" Backend name (e.g. opencode): ");
|
|
1028
|
+
if (!name) {
|
|
1029
|
+
console.log(" Cancelled.");
|
|
1030
|
+
return;
|
|
1031
|
+
}
|
|
1032
|
+
const bin = await ask(` Binary name in PATH [${name}]: `) || name;
|
|
1033
|
+
const prefixRaw = await ask(` Model prefixes that route here, comma-separated [${name}]: `);
|
|
1034
|
+
const prefixes = prefixRaw ? prefixRaw.split(",").map((s) => s.trim()).filter(Boolean) : [name];
|
|
1035
|
+
console.log();
|
|
1036
|
+
console.log(" How is the prompt delivered to the CLI?");
|
|
1037
|
+
console.log(" 1 stdin (piped to process.stdin)");
|
|
1038
|
+
console.log(" 2 arg (appended as last argument)");
|
|
1039
|
+
const modeChoice = await ask(" Choose [1/2]: ");
|
|
1040
|
+
const promptMode = modeChoice === "2" ? "arg" : "stdin";
|
|
1041
|
+
console.log();
|
|
1042
|
+
console.log(` Command arguments template. Use {model} as placeholder for the model name.`);
|
|
1043
|
+
console.log(` Example: exec --output-format text --model {model} -`);
|
|
1044
|
+
const argsRaw = await ask(" Args: ");
|
|
1045
|
+
const args = argsRaw.trim().split(/\s+/).filter(Boolean);
|
|
1046
|
+
const modelsCmdRaw = await ask(" Args to list models (leave blank to skip): ");
|
|
1047
|
+
const modelsCmd = modelsCmdRaw.trim() ? modelsCmdRaw.trim().split(/\s+/) : void 0;
|
|
1048
|
+
const modelsRaw = await ask(" Fallback model list, comma-separated (leave blank to skip): ");
|
|
1049
|
+
const models = modelsRaw.trim() ? modelsRaw.split(",").map((s) => s.trim()).filter(Boolean) : void 0;
|
|
1050
|
+
const def = { name, bin, prefixes, promptMode, args, modelsCmd, models };
|
|
1051
|
+
const cfg = loadConfig();
|
|
1052
|
+
const existing = cfg.customBackends ?? [];
|
|
1053
|
+
const idx = existing.findIndex((b) => b.name === name);
|
|
1054
|
+
if (idx >= 0) existing[idx] = def;
|
|
1055
|
+
else existing.push(def);
|
|
1056
|
+
saveConfig({ ...cfg, customBackends: existing });
|
|
1057
|
+
console.log();
|
|
1058
|
+
console.log(` \u2713 ${name} backend saved.`);
|
|
1059
|
+
console.log(` Restart bridgerapi for it to take effect.`);
|
|
1060
|
+
console.log();
|
|
1061
|
+
console.log(" Example JSON entry in ~/.bridgerapi/config.json:");
|
|
1062
|
+
console.log(` ${JSON.stringify(def, null, 2).split("\n").join("\n ")}`);
|
|
1063
|
+
console.log();
|
|
1064
|
+
}
|
|
1065
|
+
function cmdBackendList() {
|
|
1066
|
+
const cfg = loadConfig();
|
|
1067
|
+
console.log();
|
|
1068
|
+
console.log(" Built-in backends:");
|
|
1069
|
+
for (const b of BACKENDS) {
|
|
1070
|
+
const ok = b.available();
|
|
1071
|
+
console.log(` ${ok ? "\u2713" : "\u2717"} ${b.name} [${b.prefixes.join(", ")}]`);
|
|
1072
|
+
}
|
|
1073
|
+
if (cfg.customBackends && cfg.customBackends.length > 0) {
|
|
1074
|
+
console.log();
|
|
1075
|
+
console.log(" Custom backends (from ~/.bridgerapi/config.json):");
|
|
1076
|
+
for (const b of cfg.customBackends) {
|
|
1077
|
+
console.log(` ? ${b.name} [${b.prefixes.join(", ")}] (bin: ${b.bin})`);
|
|
1078
|
+
}
|
|
1079
|
+
}
|
|
1080
|
+
console.log();
|
|
1081
|
+
}
|
|
902
1082
|
async function cmdChat(model2, backendFlag) {
|
|
903
1083
|
const cfg = loadConfig();
|
|
904
1084
|
const activeBackend = backendFlag ?? (model2 && BACKENDS.find((b) => b.name === model2?.toLowerCase())?.name) ?? cfg.backend;
|
|
@@ -967,8 +1147,11 @@ function showHelp() {
|
|
|
967
1147
|
bridgerapi config set backend=<b> Set default backend (claude|gemini|codex|copilot|droid)
|
|
968
1148
|
bridgerapi config set port=<n> Set default port
|
|
969
1149
|
bridgerapi config reset Clear saved configuration
|
|
1150
|
+
bridgerapi backend List all backends (built-in + custom)
|
|
1151
|
+
bridgerapi backend add Add a custom CLI backend interactively
|
|
970
1152
|
|
|
971
|
-
|
|
1153
|
+
Built-in backends: claude, gemini, codex, copilot, droid
|
|
1154
|
+
Custom backends: add any AI CLI via "bridgerapi backend add"
|
|
972
1155
|
`.trim());
|
|
973
1156
|
}
|
|
974
1157
|
function parseArgs() {
|
|
@@ -1016,6 +1199,10 @@ switch (cmd) {
|
|
|
1016
1199
|
case "config":
|
|
1017
1200
|
cmdConfig(rest);
|
|
1018
1201
|
break;
|
|
1202
|
+
case "backend":
|
|
1203
|
+
if (rest[0] === "add") cmdBackendAdd();
|
|
1204
|
+
else cmdBackendList();
|
|
1205
|
+
break;
|
|
1019
1206
|
case "help":
|
|
1020
1207
|
case "--help":
|
|
1021
1208
|
case "-h":
|