bridgerapi 1.6.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 +368 -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,30 +90,145 @@ 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`);
|
|
218
|
+
}
|
|
219
|
+
async models() {
|
|
220
|
+
return [
|
|
221
|
+
"claude-opus-4-6",
|
|
222
|
+
"claude-opus-4-6-fast",
|
|
223
|
+
"claude-sonnet-4-6",
|
|
224
|
+
"claude-sonnet-4-5-20250929",
|
|
225
|
+
"claude-haiku-4-5-20251001"
|
|
226
|
+
];
|
|
88
227
|
}
|
|
89
228
|
async runBlocking(prompt, model2) {
|
|
90
|
-
const bin = which("claude") || this.bin;
|
|
91
229
|
let out;
|
|
92
230
|
try {
|
|
93
|
-
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], {
|
|
94
232
|
input: prompt,
|
|
95
233
|
encoding: "utf8",
|
|
96
234
|
timeout: 3e5
|
|
@@ -98,12 +236,15 @@ var ClaudeBackend = class {
|
|
|
98
236
|
} catch (e) {
|
|
99
237
|
throw new Error(e.stderr?.trim() || `claude exited non-zero`);
|
|
100
238
|
}
|
|
101
|
-
|
|
102
|
-
|
|
239
|
+
try {
|
|
240
|
+
const data = JSON.parse(out.trim() || "{}");
|
|
241
|
+
return [data.result ?? "", data.usage ?? null];
|
|
242
|
+
} catch {
|
|
243
|
+
return [out.trim(), null];
|
|
244
|
+
}
|
|
103
245
|
}
|
|
104
246
|
async *stream(prompt, model2) {
|
|
105
|
-
|
|
106
|
-
yield* spawnStream(bin, ["-p", "--output-format", "text", "--model", model2], prompt);
|
|
247
|
+
yield* spawnStream(this.bin, ["-p", "--output-format", "text", "--model", model2], prompt);
|
|
107
248
|
}
|
|
108
249
|
};
|
|
109
250
|
var GeminiBackend = class {
|
|
@@ -115,13 +256,23 @@ var GeminiBackend = class {
|
|
|
115
256
|
return process.env.GEMINI_BIN ?? which("gemini") ?? "/opt/homebrew/bin/gemini";
|
|
116
257
|
}
|
|
117
258
|
available() {
|
|
118
|
-
return Boolean(which("gemini")) || (0,
|
|
259
|
+
return Boolean(which("gemini")) || (0, import_fs2.existsSync)(this.bin);
|
|
260
|
+
}
|
|
261
|
+
async models() {
|
|
262
|
+
return [
|
|
263
|
+
"gemini-3.1-pro-preview",
|
|
264
|
+
"gemini-3-pro-preview",
|
|
265
|
+
"gemini-3-flash-preview",
|
|
266
|
+
"gemini-2.5-pro",
|
|
267
|
+
"gemini-2.5-flash",
|
|
268
|
+
"gemini-2.0-flash",
|
|
269
|
+
"gemini-1.5-pro"
|
|
270
|
+
];
|
|
119
271
|
}
|
|
120
272
|
async runBlocking(prompt, model2) {
|
|
121
|
-
const bin = which("gemini") || this.bin;
|
|
122
273
|
let out;
|
|
123
274
|
try {
|
|
124
|
-
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"], {
|
|
125
276
|
input: prompt,
|
|
126
277
|
encoding: "utf8",
|
|
127
278
|
timeout: 3e5,
|
|
@@ -142,8 +293,11 @@ var GeminiBackend = class {
|
|
|
142
293
|
}
|
|
143
294
|
}
|
|
144
295
|
async *stream(prompt, model2) {
|
|
145
|
-
|
|
146
|
-
|
|
296
|
+
yield* spawnStream(
|
|
297
|
+
this.bin,
|
|
298
|
+
["--output-format", "text", "--model", model2, "--approval-mode", "yolo"],
|
|
299
|
+
prompt
|
|
300
|
+
);
|
|
147
301
|
}
|
|
148
302
|
};
|
|
149
303
|
var CodexBackend = class {
|
|
@@ -157,6 +311,20 @@ var CodexBackend = class {
|
|
|
157
311
|
available() {
|
|
158
312
|
return Boolean(which("codex"));
|
|
159
313
|
}
|
|
314
|
+
async models() {
|
|
315
|
+
return [
|
|
316
|
+
"gpt-5-codex",
|
|
317
|
+
"gpt-5.1-codex",
|
|
318
|
+
"gpt-5.1-codex-max",
|
|
319
|
+
"gpt-5.1",
|
|
320
|
+
"gpt-5.2",
|
|
321
|
+
"gpt-5.2-codex",
|
|
322
|
+
"gpt-5.3-codex",
|
|
323
|
+
"gpt-5-2025-08-07",
|
|
324
|
+
"o4-mini",
|
|
325
|
+
"o3"
|
|
326
|
+
];
|
|
327
|
+
}
|
|
160
328
|
async runBlocking(prompt, model2) {
|
|
161
329
|
let out;
|
|
162
330
|
try {
|
|
@@ -190,6 +358,9 @@ var CopilotBackend = class {
|
|
|
190
358
|
return false;
|
|
191
359
|
}
|
|
192
360
|
}
|
|
361
|
+
async models() {
|
|
362
|
+
return ["copilot"];
|
|
363
|
+
}
|
|
193
364
|
async runBlocking(prompt, model2) {
|
|
194
365
|
let out;
|
|
195
366
|
try {
|
|
@@ -206,44 +377,93 @@ var CopilotBackend = class {
|
|
|
206
377
|
yield* spawnStream(this.bin, ["copilot", "suggest", "-t", "general", prompt]);
|
|
207
378
|
}
|
|
208
379
|
};
|
|
209
|
-
var DroidBackend = class {
|
|
380
|
+
var DroidBackend = class _DroidBackend {
|
|
210
381
|
constructor() {
|
|
211
382
|
this.name = "droid";
|
|
212
|
-
// Route Droid-exclusive model families + explicit "droid" prefix
|
|
213
383
|
this.prefixes = ["droid", "glm", "kimi", "minimax"];
|
|
214
384
|
}
|
|
385
|
+
static {
|
|
386
|
+
// Up-to-date as of March 2026 — source: Factory docs + droid exec --help
|
|
387
|
+
this.KNOWN_MODELS = [
|
|
388
|
+
"gpt-5-codex",
|
|
389
|
+
"gpt-5.1-codex",
|
|
390
|
+
"gpt-5.1-codex-max",
|
|
391
|
+
"gpt-5.1",
|
|
392
|
+
"gpt-5.2",
|
|
393
|
+
"gpt-5.2-codex",
|
|
394
|
+
"gpt-5.3-codex",
|
|
395
|
+
"gpt-5-2025-08-07",
|
|
396
|
+
"claude-opus-4-6",
|
|
397
|
+
"claude-opus-4-6-fast",
|
|
398
|
+
"claude-opus-4-1-20250805",
|
|
399
|
+
"claude-sonnet-4-5-20250929",
|
|
400
|
+
"claude-haiku-4-5-20251001",
|
|
401
|
+
"gemini-3.1-pro-preview",
|
|
402
|
+
"gemini-3-pro-preview",
|
|
403
|
+
"gemini-3-flash-preview",
|
|
404
|
+
"glm-4.6",
|
|
405
|
+
"glm-4.7",
|
|
406
|
+
"glm-5",
|
|
407
|
+
"kimi-k2.5",
|
|
408
|
+
"minimax-m2.5"
|
|
409
|
+
];
|
|
410
|
+
}
|
|
215
411
|
get bin() {
|
|
216
412
|
return process.env.DROID_BIN ?? which("droid") ?? `${HOME}/.local/bin/droid`;
|
|
217
413
|
}
|
|
218
414
|
available() {
|
|
219
|
-
return (0,
|
|
415
|
+
return Boolean(which("droid")) || (0, import_fs2.existsSync)(`${HOME}/.local/bin/droid`);
|
|
220
416
|
}
|
|
221
|
-
async
|
|
222
|
-
const bin = which("droid") || this.bin;
|
|
223
|
-
let out;
|
|
417
|
+
async models() {
|
|
224
418
|
try {
|
|
225
|
-
|
|
226
|
-
input: prompt,
|
|
419
|
+
const help = (0, import_child_process.execFileSync)(which("droid") || this.bin, ["exec", "--help"], {
|
|
227
420
|
encoding: "utf8",
|
|
228
|
-
timeout:
|
|
421
|
+
timeout: 5e3,
|
|
422
|
+
stdio: ["ignore", "pipe", "pipe"]
|
|
229
423
|
});
|
|
424
|
+
const found = extractModelIds(help);
|
|
425
|
+
if (found.length > 0) return found;
|
|
426
|
+
} catch {
|
|
427
|
+
}
|
|
428
|
+
return _DroidBackend.KNOWN_MODELS;
|
|
429
|
+
}
|
|
430
|
+
async runBlocking(prompt, model2) {
|
|
431
|
+
let out;
|
|
432
|
+
try {
|
|
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
|
+
);
|
|
230
438
|
} catch (e) {
|
|
231
439
|
throw new Error(e.stderr?.trim() || `droid exited non-zero`);
|
|
232
440
|
}
|
|
233
441
|
return [out.trim(), null];
|
|
234
442
|
}
|
|
235
443
|
async *stream(prompt, model2) {
|
|
236
|
-
|
|
237
|
-
|
|
444
|
+
yield* spawnStream(
|
|
445
|
+
which("droid") || this.bin,
|
|
446
|
+
["exec", "--output-format", "text", "--model", model2, "-"],
|
|
447
|
+
prompt
|
|
448
|
+
);
|
|
238
449
|
}
|
|
239
450
|
};
|
|
240
|
-
var
|
|
451
|
+
var BUILTIN = [
|
|
241
452
|
new ClaudeBackend(),
|
|
242
453
|
new GeminiBackend(),
|
|
243
454
|
new CodexBackend(),
|
|
244
455
|
new CopilotBackend(),
|
|
245
456
|
new DroidBackend()
|
|
246
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()];
|
|
247
467
|
function pickBackend(model2) {
|
|
248
468
|
const override = process.env.BRIDGERAPI_BACKEND?.toLowerCase();
|
|
249
469
|
if (override) {
|
|
@@ -306,18 +526,25 @@ async function readBody(req) {
|
|
|
306
526
|
req.on("error", reject);
|
|
307
527
|
});
|
|
308
528
|
}
|
|
309
|
-
function handleModels(res) {
|
|
529
|
+
async function handleModels(res) {
|
|
310
530
|
const ts = Math.floor(Date.now() / 1e3);
|
|
531
|
+
const override = process.env.BRIDGERAPI_BACKEND?.toLowerCase();
|
|
532
|
+
const pinned = override ? BACKENDS.find((b) => b.name === override && b.available()) : null;
|
|
533
|
+
if (pinned) {
|
|
534
|
+
const ids = await pinned.models();
|
|
535
|
+
sendJson(res, 200, {
|
|
536
|
+
object: "list",
|
|
537
|
+
data: ids.map((id) => ({ id, object: "model", created: ts, owned_by: pinned.name }))
|
|
538
|
+
});
|
|
539
|
+
return;
|
|
540
|
+
}
|
|
311
541
|
const available = BACKENDS.filter((b) => b.available());
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
owned_by: "bridgerapi"
|
|
319
|
-
}))
|
|
320
|
-
});
|
|
542
|
+
const allModels = [];
|
|
543
|
+
for (const b of available) {
|
|
544
|
+
const ids = await b.models();
|
|
545
|
+
for (const id of ids) allModels.push({ id, object: "model", created: ts, owned_by: b.name });
|
|
546
|
+
}
|
|
547
|
+
sendJson(res, 200, { object: "list", data: allModels });
|
|
321
548
|
}
|
|
322
549
|
function handleHealth(res, port2) {
|
|
323
550
|
const backends = {};
|
|
@@ -356,7 +583,11 @@ async function handleChat(req, res) {
|
|
|
356
583
|
res.write(chunk(id, ts, model2, { content: raw.toString("utf8") }));
|
|
357
584
|
}
|
|
358
585
|
} catch (err) {
|
|
359
|
-
|
|
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}` }));
|
|
360
591
|
}
|
|
361
592
|
res.write(chunk(id, ts, model2, {}, "stop"));
|
|
362
593
|
res.write("data: [DONE]\n\n");
|
|
@@ -381,7 +612,7 @@ function createBridgeServer(port2) {
|
|
|
381
612
|
return;
|
|
382
613
|
}
|
|
383
614
|
if (method === "GET" && (path === "/v1/models" || path === "/models")) {
|
|
384
|
-
handleModels(res);
|
|
615
|
+
await handleModels(res);
|
|
385
616
|
return;
|
|
386
617
|
}
|
|
387
618
|
if (method === "GET" && path === "/health") {
|
|
@@ -399,18 +630,18 @@ function createBridgeServer(port2) {
|
|
|
399
630
|
|
|
400
631
|
// src/service.ts
|
|
401
632
|
var import_child_process2 = require("child_process");
|
|
402
|
-
var
|
|
403
|
-
var
|
|
404
|
-
var
|
|
405
|
-
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)();
|
|
406
637
|
var LABEL = "com.bridgerapi.server";
|
|
407
638
|
function plistPath() {
|
|
408
|
-
return (0,
|
|
639
|
+
return (0, import_path3.join)(HOME2, "Library/LaunchAgents", `${LABEL}.plist`);
|
|
409
640
|
}
|
|
410
641
|
function writePlist(port2, scriptPath, nodePath, backend2) {
|
|
411
|
-
const logDir = (0,
|
|
412
|
-
(0,
|
|
413
|
-
(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 });
|
|
414
645
|
const backendEntry = backend2 ? `
|
|
415
646
|
<key>BRIDGERAPI_BACKEND</key>
|
|
416
647
|
<string>${backend2}</string>` : "";
|
|
@@ -453,16 +684,16 @@ function writePlist(port2, scriptPath, nodePath, backend2) {
|
|
|
453
684
|
<true/>
|
|
454
685
|
</dict>
|
|
455
686
|
</plist>`;
|
|
456
|
-
(0,
|
|
687
|
+
(0, import_fs3.writeFileSync)(plistPath(), plist);
|
|
457
688
|
}
|
|
458
689
|
function unitPath() {
|
|
459
|
-
const configHome = process.env.XDG_CONFIG_HOME ?? (0,
|
|
460
|
-
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");
|
|
461
692
|
}
|
|
462
693
|
function writeUnit(port2, scriptPath, nodePath, backend2) {
|
|
463
|
-
const logDir = (0,
|
|
464
|
-
(0,
|
|
465
|
-
(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 });
|
|
466
697
|
const backendLine = backend2 ? `
|
|
467
698
|
Environment=BRIDGERAPI_BACKEND=${backend2}` : "";
|
|
468
699
|
const unit = `[Unit]
|
|
@@ -483,12 +714,12 @@ StandardError=append:${logDir}/server.log
|
|
|
483
714
|
[Install]
|
|
484
715
|
WantedBy=default.target
|
|
485
716
|
`;
|
|
486
|
-
(0,
|
|
717
|
+
(0, import_fs3.writeFileSync)(unitPath(), unit);
|
|
487
718
|
}
|
|
488
719
|
function installService(port2, backend2) {
|
|
489
720
|
const scriptPath = process.argv[1];
|
|
490
721
|
const nodePath = process.execPath;
|
|
491
|
-
const os = (0,
|
|
722
|
+
const os = (0, import_os3.platform)();
|
|
492
723
|
if (os === "darwin") {
|
|
493
724
|
try {
|
|
494
725
|
(0, import_child_process2.execSync)(`launchctl unload "${plistPath()}" 2>/dev/null`, { stdio: "ignore" });
|
|
@@ -510,15 +741,15 @@ function installService(port2, backend2) {
|
|
|
510
741
|
}
|
|
511
742
|
}
|
|
512
743
|
function uninstallService() {
|
|
513
|
-
const os = (0,
|
|
744
|
+
const os = (0, import_os3.platform)();
|
|
514
745
|
if (os === "darwin") {
|
|
515
746
|
const p = plistPath();
|
|
516
|
-
if ((0,
|
|
747
|
+
if ((0, import_fs3.existsSync)(p)) {
|
|
517
748
|
try {
|
|
518
749
|
(0, import_child_process2.execSync)(`launchctl unload "${p}"`);
|
|
519
750
|
} catch {
|
|
520
751
|
}
|
|
521
|
-
(0,
|
|
752
|
+
(0, import_fs3.unlinkSync)(p);
|
|
522
753
|
console.log("\u2713 LaunchAgent removed");
|
|
523
754
|
} else {
|
|
524
755
|
console.log(" bridgerapi service is not installed");
|
|
@@ -529,8 +760,8 @@ function uninstallService() {
|
|
|
529
760
|
(0, import_child_process2.execSync)("systemctl --user disable --now bridgerapi");
|
|
530
761
|
} catch {
|
|
531
762
|
}
|
|
532
|
-
if ((0,
|
|
533
|
-
(0,
|
|
763
|
+
if ((0, import_fs3.existsSync)(p)) {
|
|
764
|
+
(0, import_fs3.unlinkSync)(p);
|
|
534
765
|
try {
|
|
535
766
|
(0, import_child_process2.execSync)("systemctl --user daemon-reload");
|
|
536
767
|
} catch {
|
|
@@ -542,7 +773,7 @@ function uninstallService() {
|
|
|
542
773
|
}
|
|
543
774
|
}
|
|
544
775
|
function serviceStatus() {
|
|
545
|
-
const os = (0,
|
|
776
|
+
const os = (0, import_os3.platform)();
|
|
546
777
|
try {
|
|
547
778
|
if (os === "darwin") {
|
|
548
779
|
const out = (0, import_child_process2.execSync)(`launchctl list ${LABEL} 2>/dev/null`, { encoding: "utf8" });
|
|
@@ -557,33 +788,13 @@ function serviceStatus() {
|
|
|
557
788
|
return { running: false };
|
|
558
789
|
}
|
|
559
790
|
|
|
560
|
-
// src/config.ts
|
|
561
|
-
var import_fs3 = require("fs");
|
|
562
|
-
var import_os3 = require("os");
|
|
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
791
|
// src/cli.ts
|
|
581
792
|
var import_fs4 = require("fs");
|
|
582
793
|
var import_os4 = require("os");
|
|
583
|
-
var
|
|
794
|
+
var import_path4 = require("path");
|
|
584
795
|
var import_readline = require("readline");
|
|
585
796
|
var DEFAULT_PORT = parseInt(process.env.BRIDGERAPI_PORT ?? "8082");
|
|
586
|
-
var LOG_DIR = (0,
|
|
797
|
+
var LOG_DIR = (0, import_path4.join)((0, import_os4.homedir)(), ".bridgerapi");
|
|
587
798
|
var INSTALL_HINTS = {
|
|
588
799
|
claude: "claude login (Claude Code \u2014 claude.ai/download)",
|
|
589
800
|
gemini: "gemini auth (Gemini CLI \u2014 npm i -g @google/gemini-cli)",
|
|
@@ -799,7 +1010,7 @@ function cmdConfig(args) {
|
|
|
799
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");
|
|
800
1011
|
console.log(` backend : ${cfg.backend ?? "(auto \u2014 routed by model prefix)"}`);
|
|
801
1012
|
console.log(` port : ${cfg.port ?? `${DEFAULT_PORT} (default)`}`);
|
|
802
|
-
console.log(` file : ${(0,
|
|
1013
|
+
console.log(` file : ${(0, import_path4.join)((0, import_os4.homedir)(), ".bridgerapi/config.json")}`);
|
|
803
1014
|
console.log();
|
|
804
1015
|
console.log(" To change:");
|
|
805
1016
|
console.log(` bridgerapi config set backend=claude`);
|
|
@@ -807,6 +1018,67 @@ function cmdConfig(args) {
|
|
|
807
1018
|
console.log(` bridgerapi config reset`);
|
|
808
1019
|
console.log();
|
|
809
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
|
+
}
|
|
810
1082
|
async function cmdChat(model2, backendFlag) {
|
|
811
1083
|
const cfg = loadConfig();
|
|
812
1084
|
const activeBackend = backendFlag ?? (model2 && BACKENDS.find((b) => b.name === model2?.toLowerCase())?.name) ?? cfg.backend;
|
|
@@ -875,8 +1147,11 @@ function showHelp() {
|
|
|
875
1147
|
bridgerapi config set backend=<b> Set default backend (claude|gemini|codex|copilot|droid)
|
|
876
1148
|
bridgerapi config set port=<n> Set default port
|
|
877
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
|
|
878
1152
|
|
|
879
|
-
|
|
1153
|
+
Built-in backends: claude, gemini, codex, copilot, droid
|
|
1154
|
+
Custom backends: add any AI CLI via "bridgerapi backend add"
|
|
880
1155
|
`.trim());
|
|
881
1156
|
}
|
|
882
1157
|
function parseArgs() {
|
|
@@ -924,6 +1199,10 @@ switch (cmd) {
|
|
|
924
1199
|
case "config":
|
|
925
1200
|
cmdConfig(rest);
|
|
926
1201
|
break;
|
|
1202
|
+
case "backend":
|
|
1203
|
+
if (rest[0] === "add") cmdBackendAdd();
|
|
1204
|
+
else cmdBackendList();
|
|
1205
|
+
break;
|
|
927
1206
|
case "help":
|
|
928
1207
|
case "--help":
|
|
929
1208
|
case "-h":
|