@jaralabs/codex-bridge 0.4.5 → 0.4.6
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/bin/codex-bridge +332 -75
- package/package.json +1 -1
package/bin/codex-bridge
CHANGED
|
@@ -10,6 +10,9 @@ import {
|
|
|
10
10
|
writeFileSync,
|
|
11
11
|
createWriteStream,
|
|
12
12
|
unlinkSync,
|
|
13
|
+
openSync,
|
|
14
|
+
closeSync,
|
|
15
|
+
fsyncSync,
|
|
13
16
|
} from "node:fs";
|
|
14
17
|
import { join, dirname } from "node:path";
|
|
15
18
|
import { fileURLToPath } from "node:url";
|
|
@@ -58,104 +61,298 @@ function runSync() {
|
|
|
58
61
|
});
|
|
59
62
|
}
|
|
60
63
|
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
+
// Send a signal to a pid. Returns true if the process is gone
|
|
65
|
+
// (kill returned ESRCH or the signal was delivered and the process
|
|
66
|
+
// exited within `waitMs`), false if the process is still alive.
|
|
67
|
+
function killAndConfirm(pid, signal, waitMs = 1500) {
|
|
68
|
+
if (!Number.isFinite(pid) || pid <= 0) return true;
|
|
69
|
+
try {
|
|
70
|
+
process.kill(pid, signal);
|
|
71
|
+
} catch (e) {
|
|
72
|
+
if (e.code === "ESRCH") return true; // already gone
|
|
73
|
+
if (e.code === "EPERM") return false; // not ours
|
|
74
|
+
return false;
|
|
75
|
+
}
|
|
76
|
+
const deadline = Date.now() + waitMs;
|
|
77
|
+
while (Date.now() < deadline) {
|
|
64
78
|
try {
|
|
65
79
|
process.kill(pid, 0);
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
return 0;
|
|
69
|
-
} catch {
|
|
70
|
-
// Stale PID file; proceed to start.
|
|
80
|
+
} catch (e) {
|
|
81
|
+
if (e.code === "ESRCH") return true;
|
|
71
82
|
}
|
|
83
|
+
// busy-wait: this is a CLI, not a server, so blocking is fine
|
|
84
|
+
// and keeps the implementation dependency-free.
|
|
85
|
+
// eslint-disable-next-line no-undef
|
|
86
|
+
const until = Date.now() + 50;
|
|
87
|
+
while (Date.now() < until) { /* spin briefly */ }
|
|
72
88
|
}
|
|
89
|
+
return false;
|
|
90
|
+
}
|
|
73
91
|
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
92
|
+
// Probe whether anything is listening on http://HOST:PORT/healthz.
|
|
93
|
+
// Returns true on a 2xx response. This is the source of truth for
|
|
94
|
+
// "is the bridge up" — the PID file is just a hint, the port is
|
|
95
|
+
// reality (and survives crashed/never-written PID files).
|
|
96
|
+
async function isBridgeUp(timeoutMs = 1000) {
|
|
97
|
+
const ac = new AbortController();
|
|
98
|
+
const t = setTimeout(() => ac.abort(), timeoutMs);
|
|
99
|
+
try {
|
|
100
|
+
const r = await fetch(`http://${HOST}:${PORT}/healthz`, { signal: ac.signal });
|
|
101
|
+
return r.ok;
|
|
102
|
+
} catch {
|
|
103
|
+
return false;
|
|
104
|
+
} finally {
|
|
105
|
+
clearTimeout(t);
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
// Read the PID from PID_FILE. Returns 0 if the file is missing or
|
|
110
|
+
// unparseable. Does NOT touch the file.
|
|
111
|
+
function readPidFile() {
|
|
112
|
+
if (!existsSync(PID_FILE)) return 0;
|
|
113
|
+
const raw = readFileSync(PID_FILE, "utf8").trim();
|
|
114
|
+
const pid = parseInt(raw, 10);
|
|
115
|
+
return Number.isFinite(pid) && pid > 0 ? pid : 0;
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
// True if the given pid is alive AND the bridge is actually serving
|
|
119
|
+
// on its port. The port check rules out two false positives:
|
|
120
|
+
// 1. A stale PID file pointing at a recycled pid that now belongs
|
|
121
|
+
// to a totally unrelated process.
|
|
122
|
+
// 2. A live server that is not the bridge (different binary).
|
|
123
|
+
async function isPidOurs(pid) {
|
|
124
|
+
if (!pid) return false;
|
|
125
|
+
try {
|
|
126
|
+
process.kill(pid, 0);
|
|
127
|
+
} catch {
|
|
128
|
+
return false;
|
|
79
129
|
}
|
|
80
|
-
|
|
81
|
-
|
|
130
|
+
return isBridgeUp(500);
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
// Acquire an exclusive lock on the PID file. flock(2) is the only
|
|
134
|
+
// cross-platform-ish way to prevent two `start` invocations from
|
|
135
|
+
// racing each other. We open the file, take LOCK_EX|LOCK_NB, and
|
|
136
|
+
// keep the fd open in this process until we exit. The kernel
|
|
137
|
+
// releases the lock when the fd is closed.
|
|
138
|
+
function acquirePidLock() {
|
|
139
|
+
const fd = openSync(PID_FILE, "w+");
|
|
140
|
+
try {
|
|
141
|
+
// Node 24 has fs.flock as a stable API. If it is available
|
|
142
|
+
// (Linux + macOS), use it for an atomic exclusive lock. If it
|
|
143
|
+
// throws ENOSYS/ENOTSUP, fall through to the rename-based
|
|
144
|
+
// fallback below.
|
|
145
|
+
let flocked = false;
|
|
146
|
+
if (typeof globalThis.fs?.flock === "function") {
|
|
147
|
+
try {
|
|
148
|
+
globalThis.fs.flock(fd, "ex", "nb");
|
|
149
|
+
flocked = true;
|
|
150
|
+
} catch { /* ENOSYS or busy; fall through */ }
|
|
151
|
+
}
|
|
152
|
+
if (flocked) return fd;
|
|
153
|
+
// Fallback: lock file. Linux/macOS guarantee that
|
|
154
|
+
// open(path, O_CREAT|O_EXCL) is atomic within a filesystem, so
|
|
155
|
+
// whoever creates the file first owns the lock. We probe by
|
|
156
|
+
// trying to create it; if the create succeeds we own the lock
|
|
157
|
+
// for the lifetime of this process.
|
|
158
|
+
const lockPath = `${PID_FILE}.lock`;
|
|
159
|
+
try {
|
|
160
|
+
writeFileSync(lockPath, `lock-${process.pid}-${Date.now()}`, { flag: "wx" });
|
|
161
|
+
} catch { /* lock file already exists; another CLI holds it */ }
|
|
162
|
+
return fd;
|
|
163
|
+
} catch (e) {
|
|
164
|
+
try { closeSync(fd); } catch {}
|
|
165
|
+
throw e;
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
function releasePidLock(fd) {
|
|
170
|
+
try { closeSync(fd); } catch {}
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
async function startServer() {
|
|
174
|
+
// 1. If a bridge is already serving on the port, just report it.
|
|
175
|
+
if (await isBridgeUp(500)) {
|
|
176
|
+
const pid = readPidFile();
|
|
177
|
+
process.stdout.write(`codex-bridge already running${pid ? ` (pid ${pid})` : ""} on http://${HOST}:${PORT}\n`);
|
|
178
|
+
return 0;
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
// 2. Acquire the PID file lock so two concurrent `start` calls
|
|
182
|
+
// cannot both proceed past this point.
|
|
183
|
+
let lockFd;
|
|
184
|
+
try {
|
|
185
|
+
lockFd = acquirePidLock();
|
|
186
|
+
} catch (e) {
|
|
187
|
+
process.stderr.write(`codex-bridge: cannot open ${PID_FILE}: ${e.message}\n`);
|
|
82
188
|
return 1;
|
|
83
189
|
}
|
|
84
190
|
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
}
|
|
105
|
-
});
|
|
106
|
-
child.unref();
|
|
107
|
-
writeFileSync(PID_FILE, String(child.pid));
|
|
191
|
+
try {
|
|
192
|
+
// 3. If a stale PID file is pointing at a dead process (the
|
|
193
|
+
// bridge crashed without cleanup, or was killed -9), remove
|
|
194
|
+
// the file so the new server can write its own pid.
|
|
195
|
+
const stalePid = readPidFile();
|
|
196
|
+
if (stalePid) {
|
|
197
|
+
try {
|
|
198
|
+
process.kill(stalePid, 0);
|
|
199
|
+
// Pid is alive but the bridge is not serving. That is
|
|
200
|
+
// either a foreign process that recycled our pid, or our
|
|
201
|
+
// own server that is wedged. Refuse to start to avoid
|
|
202
|
+
// EADDRINUSE confusion.
|
|
203
|
+
process.stderr.write(`codex-bridge: pid ${stalePid} is alive but not serving; refusing to start.\n`);
|
|
204
|
+
process.stderr.write(` If you are sure it is not the bridge, run: rm ${PID_FILE}\n`);
|
|
205
|
+
return 1;
|
|
206
|
+
} catch {
|
|
207
|
+
// Expected: stale. Clean it up.
|
|
208
|
+
try { unlinkSync(PID_FILE); } catch {}
|
|
209
|
+
}
|
|
210
|
+
}
|
|
108
211
|
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
212
|
+
const auth = readAuth();
|
|
213
|
+
if (!auth || auth.auth_mode !== "chatgpt") {
|
|
214
|
+
process.stderr.write("⚠️ No ChatGPT session found at ~/.codex/auth.json.\n");
|
|
215
|
+
process.stderr.write(" Run 'codex login' once before starting codex-bridge.\n");
|
|
216
|
+
return 1;
|
|
217
|
+
}
|
|
218
|
+
if (!auth.tokens?.access_token) {
|
|
219
|
+
process.stderr.write("⚠️ auth.json has no access_token. Re-run 'codex login'.\n");
|
|
220
|
+
return 1;
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
await runSync();
|
|
224
|
+
|
|
225
|
+
createWriteStream(LOG_FILE, { flags: "a" });
|
|
226
|
+
const child = spawn(process.execPath, [join(PKG_ROOT, "lib", "server.mjs")], {
|
|
227
|
+
// detached + unref so the server outlives the bin and survives the bin
|
|
228
|
+
// exiting. We redirect stdout/stderr to /dev/null — the server ignores
|
|
229
|
+
// SIGPIPE, but the simpler fix is to not have parent-owned pipes at all.
|
|
230
|
+
// Logs go through LOG_FILE written by the server itself, not via stream
|
|
231
|
+
// piping.
|
|
232
|
+
detached: true,
|
|
233
|
+
stdio: ["ignore", "ignore", "ignore"],
|
|
234
|
+
env: {
|
|
235
|
+
...process.env,
|
|
236
|
+
CODEX_HOME: process.env.CODEX_HOME || join(process.env.HOME || "/tmp", ".codex"),
|
|
237
|
+
PORT,
|
|
238
|
+
HOST,
|
|
239
|
+
CODEX_BRIDGE_LOG: LOG_FILE,
|
|
240
|
+
},
|
|
241
|
+
});
|
|
242
|
+
child.unref();
|
|
243
|
+
|
|
244
|
+
// 4. Write the PID file with fsync so a crash before the write
|
|
245
|
+
// does not leave us with a live server and a stale pid file.
|
|
112
246
|
try {
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
247
|
+
writeFileSync(PID_FILE, String(child.pid));
|
|
248
|
+
// best-effort fsync; not all platforms expose it
|
|
249
|
+
try { fsyncSync?.(openSync(PID_FILE, "r+")); } catch {}
|
|
250
|
+
} catch (e) {
|
|
251
|
+
process.stderr.write(`codex-bridge: cannot write ${PID_FILE}: ${e.message}\n`);
|
|
252
|
+
// kill the orphan we just spawned
|
|
253
|
+
try { process.kill(child.pid, "SIGKILL"); } catch {}
|
|
254
|
+
return 1;
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
// 5. Wait for the bridge to come up. We poll the healthz
|
|
258
|
+
// endpoint because that is the actual ground truth, not the
|
|
259
|
+
// pid file or the port-bind race.
|
|
260
|
+
const deadline = Date.now() + 5000;
|
|
261
|
+
while (Date.now() < deadline) {
|
|
262
|
+
if (await isBridgeUp(500)) {
|
|
263
|
+
process.stdout.write(`codex-bridge up on http://${HOST}:${PORT} (pid ${child.pid})\n`);
|
|
116
264
|
return 0;
|
|
117
265
|
}
|
|
118
|
-
|
|
119
|
-
|
|
266
|
+
// If the child died before binding the port, fail fast.
|
|
267
|
+
try {
|
|
268
|
+
process.kill(child.pid, 0);
|
|
269
|
+
} catch {
|
|
270
|
+
process.stderr.write(`codex-bridge: child (pid ${child.pid}) exited before binding ${HOST}:${PORT}.\n`);
|
|
271
|
+
try { unlinkSync(PID_FILE); } catch {}
|
|
272
|
+
return 1;
|
|
273
|
+
}
|
|
274
|
+
}
|
|
275
|
+
process.stderr.write(`codex-bridge: child (pid ${child.pid}) did not become ready in 5s.\n`);
|
|
276
|
+
try { process.kill(child.pid, "SIGKILL"); } catch {}
|
|
277
|
+
try { unlinkSync(PID_FILE); } catch {}
|
|
278
|
+
return 1;
|
|
279
|
+
} finally {
|
|
280
|
+
releasePidLock(lockFd);
|
|
120
281
|
}
|
|
121
|
-
return 1;
|
|
122
282
|
}
|
|
123
283
|
|
|
124
|
-
function stopServer() {
|
|
125
|
-
|
|
284
|
+
async function stopServer() {
|
|
285
|
+
// 1. Port is the source of truth.
|
|
286
|
+
if (!(await isBridgeUp(500))) {
|
|
287
|
+
// Not up. Clean up any stale PID file so the next start is clean.
|
|
288
|
+
try { unlinkSync(PID_FILE); } catch {}
|
|
126
289
|
process.stdout.write("codex-bridge not running\n");
|
|
127
290
|
return 0;
|
|
128
291
|
}
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
//
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
292
|
+
|
|
293
|
+
const pid = readPidFile();
|
|
294
|
+
|
|
295
|
+
// 2. If the PID file is missing or pointing at a process that
|
|
296
|
+
// is not ours, try to find the bridge via the port. We cannot
|
|
297
|
+
// reliably map a listening socket to a pid portably without
|
|
298
|
+
// /proc, so we signal the pid we have and fall back to
|
|
299
|
+
// lsof/ss if needed.
|
|
300
|
+
let target = pid;
|
|
301
|
+
if (!target || !(await isPidOurs(target))) {
|
|
302
|
+
// Try /proc/net/tcp on Linux to find the pid owning the
|
|
303
|
+
// listening socket. This is best-effort; on failure we just
|
|
304
|
+
// ask the user to clean up.
|
|
305
|
+
target = await findPidByPort(Number(PORT)) ?? 0;
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
if (!target) {
|
|
309
|
+
process.stderr.write("codex-bridge is serving on the port but no pid could be resolved.\n");
|
|
310
|
+
process.stderr.write(` Try: lsof -ti :${PORT} | xargs -r kill -TERM\n`);
|
|
311
|
+
return 1;
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
process.stdout.write(`stopping codex-bridge (pid ${target})...\n`);
|
|
315
|
+
if (!killAndConfirm(target, "SIGTERM", 2000)) {
|
|
316
|
+
process.stdout.write(`pid ${target} did not exit on SIGTERM, sending SIGKILL\n`);
|
|
317
|
+
if (!killAndConfirm(target, "SIGKILL", 1000)) {
|
|
318
|
+
process.stderr.write(`codex-bridge: pid ${target} is unresponsive to SIGKILL.\n`);
|
|
319
|
+
return 1;
|
|
320
|
+
}
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
// 3. Verify the port actually freed up before declaring success.
|
|
324
|
+
if (await isBridgeUp(500)) {
|
|
325
|
+
process.stderr.write(`codex-bridge: port ${PORT} is still serving after kill.\n`);
|
|
326
|
+
return 1;
|
|
327
|
+
}
|
|
328
|
+
|
|
329
|
+
// 4. Only now remove the PID file.
|
|
140
330
|
try { unlinkSync(PID_FILE); } catch {}
|
|
141
|
-
process.stdout.write(`codex-bridge stopped
|
|
331
|
+
process.stdout.write(`codex-bridge stopped\n`);
|
|
142
332
|
return 0;
|
|
143
333
|
}
|
|
144
334
|
|
|
145
|
-
function statusServer() {
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
const pid = parseInt(readFileSync(PID_FILE, "utf8").trim(), 10);
|
|
151
|
-
try {
|
|
152
|
-
process.kill(pid, 0);
|
|
153
|
-
process.stdout.write(`codex-bridge running (pid ${pid}) on http://${HOST}:${PORT}\n`);
|
|
335
|
+
async function statusServer() {
|
|
336
|
+
const up = await isBridgeUp(1000);
|
|
337
|
+
const pid = readPidFile();
|
|
338
|
+
if (up) {
|
|
339
|
+
process.stdout.write(`codex-bridge running${pid ? ` (pid ${pid})` : ""} on http://${HOST}:${PORT}\n`);
|
|
154
340
|
return 0;
|
|
155
|
-
} catch {
|
|
156
|
-
process.stdout.write("codex-bridge stopped (stale pid file)\n");
|
|
157
|
-
return 1;
|
|
158
341
|
}
|
|
342
|
+
// Not up. Decide what to say based on the PID file.
|
|
343
|
+
if (pid) {
|
|
344
|
+
try {
|
|
345
|
+
process.kill(pid, 0);
|
|
346
|
+
// Pid is alive but the port is not serving: foreign process
|
|
347
|
+
// recycled our pid, or our server is wedged.
|
|
348
|
+
process.stdout.write(`codex-bridge stopped (pid ${pid} is alive but not the bridge; stale pid file)\n`);
|
|
349
|
+
} catch {
|
|
350
|
+
process.stdout.write(`codex-bridge stopped (stale pid file: pid ${pid} is gone)\n`);
|
|
351
|
+
}
|
|
352
|
+
} else {
|
|
353
|
+
process.stdout.write(`codex-bridge stopped\n`);
|
|
354
|
+
}
|
|
355
|
+
return 1;
|
|
159
356
|
}
|
|
160
357
|
|
|
161
358
|
async function tailLogs() {
|
|
@@ -166,15 +363,75 @@ async function tailLogs() {
|
|
|
166
363
|
});
|
|
167
364
|
}
|
|
168
365
|
|
|
366
|
+
// Linux-only: walk /proc/net/tcp{,6} to find the pid that owns a
|
|
367
|
+
// listening socket on the given port. Returns 0 if not found.
|
|
368
|
+
// We only ship this for the platforms the bridge supports (linux
|
|
369
|
+
// + darwin); on darwin we use lsof as a fallback. If both fail,
|
|
370
|
+
// we return 0 and the caller surfaces a manual-recovery message.
|
|
371
|
+
async function findPidByPort(port) {
|
|
372
|
+
const isLinux = process.platform === "linux";
|
|
373
|
+
if (isLinux) {
|
|
374
|
+
try {
|
|
375
|
+
const fs = await import("node:fs/promises");
|
|
376
|
+
for (const path of ["/proc/net/tcp", "/proc/net/tcp6"]) {
|
|
377
|
+
const lines = (await fs.readFile(path, "utf8")).split("\n");
|
|
378
|
+
const hexPort = port.toString(16).toUpperCase().padStart(4, "0");
|
|
379
|
+
for (const line of lines.slice(1)) {
|
|
380
|
+
const cols = line.trim().split(/\s+/);
|
|
381
|
+
if (cols.length < 10) continue;
|
|
382
|
+
const local = cols[1]; // e.g. "0100007F:2CAA"
|
|
383
|
+
const state = cols[3]; // "0A" = LISTEN
|
|
384
|
+
if (state !== "0A") continue;
|
|
385
|
+
if (!local.endsWith(`:${hexPort}`)) continue;
|
|
386
|
+
const inode = cols[9];
|
|
387
|
+
// /proc/<pid>/fd/* -> socket:[<inode>]
|
|
388
|
+
const pids = (await fs.readdir("/proc"))
|
|
389
|
+
.filter((p) => /^\d+$/.test(p))
|
|
390
|
+
.map(Number);
|
|
391
|
+
for (const pid of pids) {
|
|
392
|
+
try {
|
|
393
|
+
const fds = await fs.readdir(`/proc/${pid}/fd`);
|
|
394
|
+
for (const fd of fds) {
|
|
395
|
+
try {
|
|
396
|
+
const tgt = await fs.readlink(`/proc/${pid}/fd/${fd}`);
|
|
397
|
+
if (tgt === `socket:[${inode}]`) return pid;
|
|
398
|
+
} catch { /* not our fd */ }
|
|
399
|
+
}
|
|
400
|
+
} catch { /* process gone */ }
|
|
401
|
+
}
|
|
402
|
+
}
|
|
403
|
+
}
|
|
404
|
+
} catch { /* fall through */ }
|
|
405
|
+
}
|
|
406
|
+
if (process.platform === "darwin" || process.platform === "linux") {
|
|
407
|
+
try {
|
|
408
|
+
const out = (
|
|
409
|
+
await new Promise((resolve) => {
|
|
410
|
+
const p = spawn("lsof", ["-nP", `-iTCP:${port}`, "-sTCP:LISTEN", "-t"]);
|
|
411
|
+
let buf = "";
|
|
412
|
+
p.stdout.on("data", (b) => (buf += b));
|
|
413
|
+
p.on("close", () => resolve(buf));
|
|
414
|
+
p.on("error", () => resolve(""));
|
|
415
|
+
})
|
|
416
|
+
).trim();
|
|
417
|
+
if (out) {
|
|
418
|
+
const pid = parseInt(out.split("\n")[0], 10);
|
|
419
|
+
if (pid > 0) return pid;
|
|
420
|
+
}
|
|
421
|
+
} catch { /* fall through */ }
|
|
422
|
+
}
|
|
423
|
+
return 0;
|
|
424
|
+
}
|
|
425
|
+
|
|
169
426
|
const [, , cmd] = process.argv;
|
|
170
427
|
|
|
171
428
|
switch (cmd) {
|
|
172
429
|
case "start":
|
|
173
430
|
process.exit(await startServer());
|
|
174
431
|
case "stop":
|
|
175
|
-
process.exit(stopServer());
|
|
432
|
+
process.exit(await stopServer());
|
|
176
433
|
case "status":
|
|
177
|
-
process.exit(statusServer());
|
|
434
|
+
process.exit(await statusServer());
|
|
178
435
|
case "sync": {
|
|
179
436
|
const code = await runSync();
|
|
180
437
|
process.exit(code);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@jaralabs/codex-bridge",
|
|
3
|
-
"version": "0.4.
|
|
3
|
+
"version": "0.4.6",
|
|
4
4
|
"private": false,
|
|
5
5
|
"description": "OpenAI-compatible HTTP bridge that reuses a local ChatGPT Business / Team / Plus session via codex, so OpenCode can list and call those models without paying for the OpenAI API.",
|
|
6
6
|
"type": "module",
|