openai-oauth 2.0.0-beta.2 → 2.0.0-beta.4

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 CHANGED
@@ -1,20 +1,17 @@
1
1
  #!/usr/bin/env node
2
2
  import {
3
3
  DEFAULT_PORT,
4
- resolveOpenAIOAuthModels,
5
4
  runOpenAIOAuthLogin,
6
5
  startOpenAIOAuthServer
7
- } from "./chunk-UZ3AHAWX.js";
6
+ } from "./chunk-INHW7GRB.js";
8
7
 
9
8
  // src/cli-app.ts
10
9
  import { access } from "fs/promises";
11
10
  import { createInterface } from "readline/promises";
12
11
  import {
13
- createCodexOAuthClient,
14
12
  resolveAuthFileCandidates,
15
13
  resolveCodexAuthFilePath
16
- } from "@openai-oauth/core";
17
- import { openaiCredentials } from "@openai-oauth/local";
14
+ } from "@openai-oauth/local/auth-file";
18
15
  import yargs from "yargs";
19
16
  import { hideBin } from "yargs/helpers";
20
17
 
@@ -61,6 +58,53 @@ var toStartupMessage = (baseUrl, availableModels, options) => [
61
58
  "",
62
59
  `Available Models: ${availableModels.join(", ")}`
63
60
  ].join("\n");
61
+ var notRunning = "OpenAI OAuth is not running.";
62
+ var cliMessages = {
63
+ foregroundControls: "[d] Run in background [q] Quit",
64
+ backgroundActions: "Stop with `npx openai-oauth stop` or see logs with `npx openai-oauth logs --follow`",
65
+ notRunning,
66
+ notRunningWithStart: [notRunning, "", "Start with `npx openai-oauth`"].join(
67
+ "\n"
68
+ ),
69
+ noLogs: "No OpenAI OAuth logs found.",
70
+ followingLogs: "Following OpenAI OAuth logs. Press Ctrl+C to stop following.",
71
+ stopped: "OpenAI OAuth stopped.",
72
+ stoppedRemotely: "OpenAI OAuth was stopped from another terminal.",
73
+ couldNotStop: "Could not stop OpenAI OAuth.",
74
+ couldNotDetach: (details) => {
75
+ if (details?.startsWith("Could not move OpenAI OAuth to the background")) {
76
+ return details;
77
+ }
78
+ return details ? `Could not move OpenAI OAuth to the background: ${details}` : "Could not move OpenAI OAuth to the background.";
79
+ },
80
+ stoppedUnexpectedly: (details) => `OpenAI OAuth stopped unexpectedly (${details}).`,
81
+ workerStarted: (baseUrl) => `OpenAI OAuth started at ${baseUrl}`
82
+ };
83
+ var toForegroundStartupMessage = (baseUrl, availableModels, options) => [
84
+ toStartupMessage(baseUrl, availableModels, options),
85
+ "",
86
+ dim(cliMessages.foregroundControls, options)
87
+ ].join("\n");
88
+ var toBackgroundStartupMessage = (baseUrl, availableModels, options) => [
89
+ toStartupMessage(baseUrl, availableModels, options),
90
+ "",
91
+ cliMessages.backgroundActions
92
+ ].join("\n");
93
+ var toDetachedMessage = (baseUrl) => [
94
+ `OpenAI OAuth is now running in the background at ${baseUrl}`,
95
+ "",
96
+ cliMessages.backgroundActions
97
+ ].join("\n");
98
+ var toAlreadyRunningMessage = (baseUrl) => [
99
+ `OpenAI OAuth is already running at ${baseUrl}`,
100
+ "",
101
+ cliMessages.backgroundActions
102
+ ].join("\n");
103
+ var toRunningMessage = (baseUrl) => [
104
+ `OpenAI OAuth is running at ${baseUrl}`,
105
+ "",
106
+ cliMessages.backgroundActions
107
+ ].join("\n");
64
108
  var installCliWarningLogger = () => {
65
109
  let hasLoggedWarningSystemMessage = false;
66
110
  globalThis.AI_SDK_LOG_WARNINGS = ({ warnings, provider, model }) => {
@@ -80,18 +124,650 @@ var installCliWarningLogger = () => {
80
124
  };
81
125
  };
82
126
 
127
+ // src/cli-runtime.ts
128
+ import { spawn } from "child_process";
129
+ import { randomBytes } from "crypto";
130
+ import { promises as fs } from "fs";
131
+ import { createServer } from "http";
132
+ import os from "os";
133
+ import path from "path";
134
+ import { setTimeout as delay } from "timers/promises";
135
+ var runtimeSchemaVersion = 1;
136
+ var controlHost = "127.0.0.1";
137
+ var internalRuntimeDirectoryVariable = "OPENAI_OAUTH_INTERNAL_RUNTIME_DIR";
138
+ var internalDetachedFlag = "--internal-detached-child";
139
+ var waitForChildExit = (child, timeoutMs) => {
140
+ if (child.exitCode != null || child.signalCode != null) {
141
+ return Promise.resolve(true);
142
+ }
143
+ return new Promise((resolve) => {
144
+ const onExit = () => {
145
+ clearTimeout(timeout);
146
+ resolve(true);
147
+ };
148
+ const timeout = setTimeout(() => {
149
+ child.off("exit", onExit);
150
+ resolve(false);
151
+ }, timeoutMs);
152
+ child.once("exit", onExit);
153
+ });
154
+ };
155
+ var isNodeError = (error) => error instanceof Error;
156
+ var resolveRuntimeDirectory = () => {
157
+ const override = process.env[internalRuntimeDirectoryVariable];
158
+ if (override) {
159
+ return override;
160
+ }
161
+ if (process.platform === "darwin") {
162
+ return path.join(
163
+ os.homedir(),
164
+ "Library",
165
+ "Application Support",
166
+ "openai-oauth"
167
+ );
168
+ }
169
+ if (process.platform === "win32") {
170
+ return path.join(
171
+ process.env.LOCALAPPDATA ?? path.join(os.homedir(), "AppData", "Local"),
172
+ "openai-oauth"
173
+ );
174
+ }
175
+ return path.join(
176
+ process.env.XDG_STATE_HOME ?? path.join(os.homedir(), ".local", "state"),
177
+ "openai-oauth"
178
+ );
179
+ };
180
+ var resolveCliRuntimePaths = (directory = resolveRuntimeDirectory()) => ({
181
+ directory,
182
+ state: path.join(directory, "runtime.json"),
183
+ lock: path.join(directory, "runtime.lock"),
184
+ log: path.join(directory, "server.log")
185
+ });
186
+ var ensureRuntimeDirectory = async (paths) => {
187
+ await fs.mkdir(paths.directory, { recursive: true, mode: 448 });
188
+ try {
189
+ await fs.chmod(paths.directory, 448);
190
+ } catch {
191
+ }
192
+ };
193
+ var isRuntimeState = (value) => {
194
+ if (typeof value !== "object" || value == null) {
195
+ return false;
196
+ }
197
+ const state = value;
198
+ return state.schemaVersion === runtimeSchemaVersion && typeof state.instanceId === "string" && typeof state.pid === "number" && (state.mode === "foreground" || state.mode === "background") && typeof state.url === "string" && typeof state.startedAt === "string" && typeof state.version === "string" && typeof state.controlPort === "number" && typeof state.controlToken === "string";
199
+ };
200
+ var readJson = async (filePath) => {
201
+ try {
202
+ return JSON.parse(await fs.readFile(filePath, "utf8"));
203
+ } catch {
204
+ return void 0;
205
+ }
206
+ };
207
+ var readRuntimeState = async (paths) => {
208
+ const parsed = await readJson(paths.state);
209
+ return isRuntimeState(parsed) ? parsed : void 0;
210
+ };
211
+ var writeRuntimeState = async (paths, state) => {
212
+ const temporaryPath = `${paths.state}.${process.pid}.${randomBytes(4).toString("hex")}`;
213
+ await fs.writeFile(temporaryPath, `${JSON.stringify(state, null, 2)}
214
+ `, {
215
+ mode: 384
216
+ });
217
+ await fs.rename(temporaryPath, paths.state);
218
+ try {
219
+ await fs.chmod(paths.state, 384);
220
+ } catch {
221
+ }
222
+ };
223
+ var isProcessAlive = (pid) => {
224
+ try {
225
+ process.kill(pid, 0);
226
+ return true;
227
+ } catch (error) {
228
+ return isNodeError(error) && error.code === "EPERM";
229
+ }
230
+ };
231
+ var readLockPid = async (paths) => {
232
+ const parsed = await readJson(paths.lock);
233
+ if (typeof parsed !== "object" || parsed == null) {
234
+ return void 0;
235
+ }
236
+ const pid = parsed.pid;
237
+ return typeof pid === "number" ? pid : void 0;
238
+ };
239
+ var removeIfPresent = async (filePath) => {
240
+ await fs.rm(filePath, { force: true }).catch(() => void 0);
241
+ };
242
+ var fetchWithTimeout = async (input, init, timeoutMs = 750) => {
243
+ const controller = new AbortController();
244
+ const timeout = setTimeout(() => controller.abort(), timeoutMs);
245
+ if (typeof timeout === "object" && "unref" in timeout) {
246
+ timeout.unref();
247
+ }
248
+ try {
249
+ return await fetch(input, { ...init, signal: controller.signal });
250
+ } finally {
251
+ clearTimeout(timeout);
252
+ }
253
+ };
254
+ var probeRuntime = async (state) => {
255
+ try {
256
+ const response = await fetchWithTimeout(
257
+ `http://${controlHost}:${state.controlPort}/state`,
258
+ {
259
+ headers: { authorization: `Bearer ${state.controlToken}` }
260
+ }
261
+ );
262
+ if (!response.ok) {
263
+ return false;
264
+ }
265
+ const body = await response.json();
266
+ return body.instanceId === state.instanceId;
267
+ } catch {
268
+ return false;
269
+ }
270
+ };
271
+ var findRunningCliRuntime = async (directory) => {
272
+ const paths = resolveCliRuntimePaths(directory);
273
+ const state = await readRuntimeState(paths);
274
+ if (!state) {
275
+ return void 0;
276
+ }
277
+ for (let attempt = 0; attempt < 3; attempt += 1) {
278
+ if (await probeRuntime(state)) {
279
+ return state;
280
+ }
281
+ await delay(50);
282
+ }
283
+ await removeIfPresent(paths.state);
284
+ await removeIfPresent(paths.lock);
285
+ return void 0;
286
+ };
287
+ var CliRuntimeAlreadyRunningError = class extends Error {
288
+ state;
289
+ constructor(state) {
290
+ super(toAlreadyRunningMessage(state.url));
291
+ this.name = "CliRuntimeAlreadyRunningError";
292
+ this.state = state;
293
+ }
294
+ };
295
+ var acquireCliRuntimeLock = async (directory) => {
296
+ const paths = resolveCliRuntimePaths(directory);
297
+ await ensureRuntimeDirectory(paths);
298
+ for (let attempt = 0; attempt < 50; attempt += 1) {
299
+ try {
300
+ const handle = await fs.open(paths.lock, "wx", 384);
301
+ await handle.writeFile(
302
+ `${JSON.stringify({ pid: process.pid, startedAt: (/* @__PURE__ */ new Date()).toISOString() })}
303
+ `
304
+ );
305
+ await handle.close();
306
+ let released = false;
307
+ return {
308
+ paths,
309
+ release: async () => {
310
+ if (released) {
311
+ return;
312
+ }
313
+ released = true;
314
+ await removeIfPresent(paths.lock);
315
+ }
316
+ };
317
+ } catch (error) {
318
+ if (!isNodeError(error) || error.code !== "EEXIST") {
319
+ throw error;
320
+ }
321
+ const running = await findRunningCliRuntime(directory);
322
+ if (running) {
323
+ throw new CliRuntimeAlreadyRunningError(running);
324
+ }
325
+ const ownerPid = await readLockPid(paths);
326
+ if (ownerPid == null || !isProcessAlive(ownerPid)) {
327
+ await removeIfPresent(paths.lock);
328
+ continue;
329
+ }
330
+ await delay(100);
331
+ }
332
+ }
333
+ throw new Error("OpenAI OAuth is already starting in another process.");
334
+ };
335
+ var closeServer = (server) => new Promise((resolve, reject) => {
336
+ if (!server.listening) {
337
+ resolve();
338
+ return;
339
+ }
340
+ server.close((error) => {
341
+ if (error) {
342
+ reject(error);
343
+ return;
344
+ }
345
+ resolve();
346
+ });
347
+ });
348
+ var activateCliRuntime = async (lock, options) => {
349
+ const instanceId = randomBytes(16).toString("hex");
350
+ const controlToken = randomBytes(32).toString("hex");
351
+ let state;
352
+ const controlServer = createServer((request, response) => {
353
+ if (request.headers.authorization !== `Bearer ${controlToken}`) {
354
+ response.writeHead(401).end();
355
+ return;
356
+ }
357
+ if (request.method === "GET" && request.url === "/state") {
358
+ response.writeHead(200, { "content-type": "application/json" });
359
+ response.end(JSON.stringify({ instanceId: state.instanceId }));
360
+ return;
361
+ }
362
+ if (request.method === "POST" && request.url === "/stop") {
363
+ response.writeHead(202).end();
364
+ setImmediate(options.onStop);
365
+ return;
366
+ }
367
+ response.writeHead(404).end();
368
+ });
369
+ try {
370
+ await new Promise((resolve, reject) => {
371
+ controlServer.once("error", reject);
372
+ controlServer.listen(0, controlHost, () => {
373
+ controlServer.off("error", reject);
374
+ resolve();
375
+ });
376
+ });
377
+ const address = controlServer.address();
378
+ state = {
379
+ schemaVersion: runtimeSchemaVersion,
380
+ instanceId,
381
+ pid: process.pid,
382
+ mode: options.mode,
383
+ url: options.url,
384
+ startedAt: (/* @__PURE__ */ new Date()).toISOString(),
385
+ version: options.version,
386
+ controlPort: address.port,
387
+ controlToken
388
+ };
389
+ await writeRuntimeState(lock.paths, state);
390
+ } catch (error) {
391
+ await closeServer(controlServer).catch(() => void 0);
392
+ await lock.release();
393
+ throw error;
394
+ }
395
+ let closed = false;
396
+ return {
397
+ state,
398
+ logPath: lock.paths.log,
399
+ setMode: async (mode) => {
400
+ if (state.mode === mode) {
401
+ return;
402
+ }
403
+ state = { ...state, mode };
404
+ await writeRuntimeState(lock.paths, state);
405
+ },
406
+ close: async () => {
407
+ if (closed) {
408
+ return;
409
+ }
410
+ closed = true;
411
+ const current = await readRuntimeState(lock.paths);
412
+ if (current?.instanceId === state.instanceId) {
413
+ await removeIfPresent(lock.paths.state);
414
+ }
415
+ await closeServer(controlServer).catch(() => void 0);
416
+ await lock.release();
417
+ }
418
+ };
419
+ };
420
+ var stopCliRuntime = async (state, directory) => {
421
+ try {
422
+ const response = await fetchWithTimeout(
423
+ `http://${controlHost}:${state.controlPort}/stop`,
424
+ {
425
+ method: "POST",
426
+ headers: { authorization: `Bearer ${state.controlToken}` }
427
+ }
428
+ );
429
+ if (!response.ok) {
430
+ return false;
431
+ }
432
+ } catch {
433
+ return false;
434
+ }
435
+ for (let attempt = 0; attempt < 50; attempt += 1) {
436
+ if (!await findRunningCliRuntime(directory)) {
437
+ return true;
438
+ }
439
+ await delay(100);
440
+ }
441
+ return false;
442
+ };
443
+ var isCliWorkerMessage = (value) => {
444
+ if (typeof value !== "object" || value == null) {
445
+ return false;
446
+ }
447
+ const message = value;
448
+ return message.type === "openai-oauth:ready" && typeof message.url === "string" && Array.isArray(message.models) || message.type === "openai-oauth:error" && typeof message.message === "string" || message.type === "openai-oauth:detached" || message.type === "openai-oauth:stopping" && message.source === "remote";
449
+ };
450
+ var waitForWorkerMessage = (child, predicate, timeoutMs) => new Promise((resolve, reject) => {
451
+ let settled = false;
452
+ const timeout = setTimeout(() => {
453
+ finish(new Error(`OpenAI OAuth did not respond within ${timeoutMs}ms.`));
454
+ }, timeoutMs);
455
+ const cleanup = () => {
456
+ clearTimeout(timeout);
457
+ child.off("message", onMessage);
458
+ child.off("error", onError);
459
+ child.off("exit", onExit);
460
+ };
461
+ const finish = (error, message) => {
462
+ if (settled) {
463
+ return;
464
+ }
465
+ settled = true;
466
+ cleanup();
467
+ if (error) {
468
+ reject(error);
469
+ return;
470
+ }
471
+ if (!message) {
472
+ reject(new Error("OpenAI OAuth worker returned an empty response."));
473
+ return;
474
+ }
475
+ resolve(message);
476
+ };
477
+ const onMessage = (value) => {
478
+ if (!isCliWorkerMessage(value)) {
479
+ return;
480
+ }
481
+ if (value.type === "openai-oauth:error") {
482
+ finish(new Error(value.message));
483
+ return;
484
+ }
485
+ if (predicate(value)) {
486
+ finish(void 0, value);
487
+ }
488
+ };
489
+ const onError = (error) => finish(error);
490
+ const onExit = (code, signal) => finish(
491
+ new Error(
492
+ `OpenAI OAuth exited unexpectedly (${signal ?? code ?? "unknown"}).`
493
+ )
494
+ );
495
+ child.on("message", onMessage);
496
+ child.once("error", onError);
497
+ child.once("exit", onExit);
498
+ });
499
+ var startCliWorker = async (argv, options = {}) => {
500
+ const paths = resolveCliRuntimePaths(options.runtimeDirectory);
501
+ await ensureRuntimeDirectory(paths);
502
+ const logHandle = await fs.open(paths.log, "a", 384);
503
+ let child;
504
+ const entryPath = options.entryPath ?? process.argv[1];
505
+ if (!entryPath) {
506
+ await logHandle.close();
507
+ throw new Error("Could not resolve the OpenAI OAuth CLI entrypoint.");
508
+ }
509
+ try {
510
+ child = spawn(
511
+ process.execPath,
512
+ [
513
+ entryPath,
514
+ ...argv.filter((argument) => argument !== internalDetachedFlag),
515
+ internalDetachedFlag
516
+ ],
517
+ {
518
+ detached: true,
519
+ windowsHide: true,
520
+ stdio: ["ignore", logHandle.fd, logHandle.fd, "ipc"],
521
+ env: {
522
+ ...process.env,
523
+ ...options.runtimeDirectory ? {
524
+ [internalRuntimeDirectoryVariable]: options.runtimeDirectory
525
+ } : {}
526
+ }
527
+ }
528
+ );
529
+ } finally {
530
+ await logHandle.close();
531
+ }
532
+ let ready;
533
+ try {
534
+ ready = await waitForWorkerMessage(
535
+ child,
536
+ (message) => message.type === "openai-oauth:ready",
537
+ options.timeoutMs ?? 15e3
538
+ );
539
+ } catch (error) {
540
+ if (child.pid) {
541
+ try {
542
+ process.kill(child.pid, "SIGTERM");
543
+ } catch {
544
+ }
545
+ }
546
+ if (child.connected) {
547
+ child.disconnect();
548
+ }
549
+ child.unref();
550
+ throw error;
551
+ }
552
+ return {
553
+ child,
554
+ url: ready.url,
555
+ models: ready.models,
556
+ logPath: paths.log,
557
+ detach: async () => {
558
+ const acknowledged = waitForWorkerMessage(
559
+ child,
560
+ (message) => message.type === "openai-oauth:detached",
561
+ 5e3
562
+ );
563
+ child.send?.({ type: "openai-oauth:detach" });
564
+ await acknowledged;
565
+ if (child.connected) {
566
+ child.disconnect();
567
+ }
568
+ child.unref();
569
+ },
570
+ stop: async () => {
571
+ child.send?.({ type: "openai-oauth:stop" });
572
+ if (await waitForChildExit(child, 5e3)) {
573
+ return;
574
+ }
575
+ child.kill("SIGTERM");
576
+ if (await waitForChildExit(child, 1e3)) {
577
+ return;
578
+ }
579
+ if (child.connected) {
580
+ child.disconnect();
581
+ }
582
+ child.unref();
583
+ throw new Error("OpenAI OAuth did not stop within 6000ms.");
584
+ }
585
+ };
586
+ };
587
+ var readLogSlice = async (filePath, start) => {
588
+ try {
589
+ const stat = await fs.stat(filePath);
590
+ const safeStart = stat.size < start ? 0 : start;
591
+ if (stat.size === safeStart) {
592
+ return { text: "", size: stat.size };
593
+ }
594
+ const handle = await fs.open(filePath, "r");
595
+ try {
596
+ const buffer = Buffer.alloc(stat.size - safeStart);
597
+ await handle.read(buffer, 0, buffer.length, safeStart);
598
+ return { text: buffer.toString("utf8"), size: stat.size };
599
+ } finally {
600
+ await handle.close();
601
+ }
602
+ } catch (error) {
603
+ if (isNodeError(error) && error.code === "ENOENT") {
604
+ return { text: "", size: 0 };
605
+ }
606
+ throw error;
607
+ }
608
+ };
609
+ var readRecentCliLogs = async (directory, lineCount = 50) => {
610
+ const { log } = resolveCliRuntimePaths(directory);
611
+ try {
612
+ const stat = await fs.stat(log);
613
+ const start = Math.max(0, stat.size - 64 * 1024);
614
+ const { text } = await readLogSlice(log, start);
615
+ return text.split("\n").slice(-lineCount - 1).join("\n").trimEnd();
616
+ } catch (error) {
617
+ if (isNodeError(error) && error.code === "ENOENT") {
618
+ return "";
619
+ }
620
+ throw error;
621
+ }
622
+ };
623
+ var followCliLogs = async (write, signal, directory, fromStart = false) => {
624
+ const { log } = resolveCliRuntimePaths(directory);
625
+ let offset = 0;
626
+ if (!fromStart) {
627
+ try {
628
+ offset = (await fs.stat(log)).size;
629
+ } catch {
630
+ }
631
+ }
632
+ while (!signal.aborted) {
633
+ try {
634
+ await delay(250, void 0, { signal });
635
+ } catch {
636
+ break;
637
+ }
638
+ const next = await readLogSlice(log, offset);
639
+ offset = next.size;
640
+ if (next.text) {
641
+ write(next.text);
642
+ }
643
+ }
644
+ };
645
+ var sendDetachedReady = (url, models) => {
646
+ process.send?.({ type: "openai-oauth:ready", url, models });
647
+ };
648
+ var sendDetachedAcknowledgement = () => {
649
+ process.send?.({ type: "openai-oauth:detached" });
650
+ };
651
+ var sendRemoteStopping = () => new Promise((resolve) => {
652
+ if (!process.send) {
653
+ resolve();
654
+ return;
655
+ }
656
+ process.send(
657
+ { type: "openai-oauth:stopping", source: "remote" },
658
+ () => resolve()
659
+ );
660
+ });
661
+ var sendDetachedError = (message) => new Promise((resolve) => {
662
+ if (!process.send) {
663
+ resolve();
664
+ return;
665
+ }
666
+ process.send({ type: "openai-oauth:error", message }, () => resolve());
667
+ });
668
+
669
+ // src/cli-worker.ts
670
+ var isWorkerCommand = (value) => {
671
+ if (typeof value !== "object" || value == null) {
672
+ return false;
673
+ }
674
+ const type = value.type;
675
+ return type === "openai-oauth:detach" || type === "openai-oauth:stop";
676
+ };
677
+ var startCliInstance = async (options, mode, version, onRemoteStop) => {
678
+ const lock = await acquireCliRuntimeLock();
679
+ let server;
680
+ try {
681
+ server = await startOpenAIOAuthServer(options);
682
+ const runtime = await activateCliRuntime(lock, {
683
+ mode,
684
+ url: server.url,
685
+ version,
686
+ onStop: onRemoteStop
687
+ });
688
+ return { server, runtime, models: server.models };
689
+ } catch (error) {
690
+ await server?.close().catch(() => void 0);
691
+ await lock.release();
692
+ throw error;
693
+ }
694
+ };
695
+ var runCliWorker = async (options, initialMode, version) => {
696
+ if (typeof process.send !== "function") {
697
+ throw new Error("OpenAI OAuth worker requires an IPC channel.");
698
+ }
699
+ let detached = false;
700
+ let stopping = false;
701
+ let instance;
702
+ const shutdown = async (source) => {
703
+ if (stopping) {
704
+ return;
705
+ }
706
+ stopping = true;
707
+ if (source === "remote") {
708
+ await sendRemoteStopping();
709
+ }
710
+ await instance?.server.close().catch(() => void 0);
711
+ await instance?.runtime.close().catch(() => void 0);
712
+ if (process.connected) {
713
+ process.disconnect();
714
+ }
715
+ process.exit(0);
716
+ };
717
+ instance = await startCliInstance(
718
+ options,
719
+ initialMode,
720
+ version,
721
+ () => shutdown("remote")
722
+ );
723
+ const onMessage = (value) => {
724
+ if (!isWorkerCommand(value) || stopping) {
725
+ return;
726
+ }
727
+ if (value.type === "openai-oauth:stop") {
728
+ void shutdown("parent");
729
+ return;
730
+ }
731
+ void (async () => {
732
+ try {
733
+ await instance?.runtime.setMode("background");
734
+ detached = true;
735
+ sendDetachedAcknowledgement();
736
+ } catch (error) {
737
+ await sendDetachedError(
738
+ error instanceof Error ? error.message : cliMessages.couldNotDetach()
739
+ );
740
+ }
741
+ })();
742
+ };
743
+ const onDisconnect = () => {
744
+ if (!detached) {
745
+ void shutdown("disconnect");
746
+ }
747
+ };
748
+ const onSignal = () => shutdown("signal");
749
+ process.on("message", onMessage);
750
+ process.once("disconnect", onDisconnect);
751
+ process.once("SIGINT", onSignal);
752
+ process.once("SIGTERM", onSignal);
753
+ console.log(cliMessages.workerStarted(instance.server.url));
754
+ sendDetachedReady(instance.server.url, instance.models);
755
+ };
756
+
83
757
  // src/update-check.ts
84
- var REGISTRY_URL = "https://registry.npmjs.org/openai-oauth/latest";
758
+ var registryUrl = "https://registry.npmjs.org/openai-oauth/latest";
85
759
  var normalizeVersion = (value) => {
86
760
  if (typeof value !== "string") {
87
761
  return void 0;
88
762
  }
89
- const match = value.trim().match(/^\d+\.\d+\.\d+$/);
763
+ const match = value.trim().match(/^\d+\.\d+\.\d+(?:-[0-9A-Za-z.-]+)?$/);
90
764
  return match?.[0];
91
765
  };
92
766
  var compareSemver = (left, right) => {
93
- const leftParts = left.split(".").map(Number);
94
- const rightParts = right.split(".").map(Number);
767
+ const [leftCore = "", leftPrerelease] = left.split("-", 2);
768
+ const [rightCore = "", rightPrerelease] = right.split("-", 2);
769
+ const leftParts = leftCore.split(".").map(Number);
770
+ const rightParts = rightCore.split(".").map(Number);
95
771
  for (let index = 0; index < 3; index += 1) {
96
772
  const leftPart = leftParts[index] ?? 0;
97
773
  const rightPart = rightParts[index] ?? 0;
@@ -102,6 +778,26 @@ var compareSemver = (left, right) => {
102
778
  return 1;
103
779
  }
104
780
  }
781
+ if (leftPrerelease == null) return rightPrerelease == null ? 0 : 1;
782
+ if (rightPrerelease == null) return -1;
783
+ const leftIdentifiers = leftPrerelease.split(".");
784
+ const rightIdentifiers = rightPrerelease.split(".");
785
+ const length = Math.max(leftIdentifiers.length, rightIdentifiers.length);
786
+ for (let index = 0; index < length; index += 1) {
787
+ const leftIdentifier = leftIdentifiers[index];
788
+ const rightIdentifier = rightIdentifiers[index];
789
+ if (leftIdentifier == null) return -1;
790
+ if (rightIdentifier == null) return 1;
791
+ if (leftIdentifier === rightIdentifier) continue;
792
+ const leftNumber = /^\d+$/.test(leftIdentifier) ? Number(leftIdentifier) : void 0;
793
+ const rightNumber = /^\d+$/.test(rightIdentifier) ? Number(rightIdentifier) : void 0;
794
+ if (leftNumber != null && rightNumber != null) {
795
+ return leftNumber < rightNumber ? -1 : 1;
796
+ }
797
+ if (leftNumber != null) return -1;
798
+ if (rightNumber != null) return 1;
799
+ return leftIdentifier < rightIdentifier ? -1 : 1;
800
+ }
105
801
  return 0;
106
802
  };
107
803
  var fetchLatestVersion = async (fetchImpl, signal) => {
@@ -114,7 +810,7 @@ var fetchLatestVersion = async (fetchImpl, signal) => {
114
810
  if (signal) {
115
811
  requestInit.signal = signal;
116
812
  }
117
- const response = await fetchImpl(REGISTRY_URL, requestInit);
813
+ const response = await fetchImpl(registryUrl, requestInit);
118
814
  if (!response.ok) {
119
815
  return void 0;
120
816
  }
@@ -153,10 +849,82 @@ Run \`npx openai-oauth@latest\` to use the newest version.`
153
849
  );
154
850
  };
155
851
 
852
+ // package.json
853
+ var package_default = {
854
+ name: "openai-oauth",
855
+ version: "2.0.0-beta.4",
856
+ description: "Free OpenAI API access with your ChatGPT account.",
857
+ keywords: [
858
+ "openai",
859
+ "oauth",
860
+ "chatgpt",
861
+ "codex",
862
+ "cli",
863
+ "openai-compatible"
864
+ ],
865
+ type: "module",
866
+ engines: {
867
+ node: ">=20"
868
+ },
869
+ sideEffects: false,
870
+ main: "./dist/index.js",
871
+ types: "./dist/index.d.ts",
872
+ bin: {
873
+ "openai-oauth": "dist/cli.js"
874
+ },
875
+ exports: {
876
+ ".": {
877
+ types: "./dist/index.d.ts",
878
+ import: "./dist/index.js",
879
+ default: "./dist/index.js"
880
+ }
881
+ },
882
+ files: [
883
+ "dist",
884
+ "README.md",
885
+ "LICENSE"
886
+ ],
887
+ scripts: {
888
+ build: "tsup src/index.ts src/cli.ts --format esm --dts --outDir dist --clean --loader .html=text",
889
+ dev: "bun ./src/cli.ts",
890
+ prepublishOnly: "bun run build",
891
+ typecheck: "tsc --noEmit",
892
+ test: "vitest run",
893
+ "test:live": "LIVE_CODEX_E2E=1 vitest run test/live.e2e.test.ts"
894
+ },
895
+ dependencies: {
896
+ "@openai-oauth/ai-sdk": "workspace:*",
897
+ "@openai-oauth/core": "workspace:*",
898
+ "@openai-oauth/local": "workspace:*",
899
+ ai: "6.0.116",
900
+ yargs: "17.7.3"
901
+ },
902
+ devDependencies: {
903
+ "@ai-sdk/openai": "3.0.41",
904
+ "@types/node": "20.17.24",
905
+ "@types/yargs": "^17.0.33",
906
+ typescript: "5.9.2",
907
+ vitest: "4.1.10",
908
+ zod: "^3.25.76"
909
+ },
910
+ author: "EvanZhouDev",
911
+ license: "AGPL-3.0-only",
912
+ repository: {
913
+ type: "git",
914
+ url: "git+https://github.com/EvanZhouDev/openai-oauth.git",
915
+ directory: "packages/openai-oauth"
916
+ },
917
+ bugs: {
918
+ url: "https://github.com/EvanZhouDev/openai-oauth/issues"
919
+ },
920
+ homepage: "https://github.com/EvanZhouDev/openai-oauth#readme"
921
+ };
922
+
156
923
  // src/version.ts
157
- var packageVersion = "2.0.0-beta.2";
924
+ var packageVersion = package_default.version;
158
925
 
159
926
  // src/cli-app.ts
927
+ var cliCommand = "npx openai-oauth@latest";
160
928
  var defaultUpdateCheckWarning = (message) => {
161
929
  console.error(message);
162
930
  };
@@ -171,8 +939,12 @@ var helpLines = [
171
939
  "Free OpenAI API access with your ChatGPT account.",
172
940
  "",
173
941
  "Usage",
174
- " npx openai-oauth@latest [options]",
175
- " npx openai-oauth@latest login [options]",
942
+ ` ${cliCommand} [options]`,
943
+ ` ${cliCommand} --detach [options]`,
944
+ ` ${cliCommand} status`,
945
+ ` ${cliCommand} logs [--follow]`,
946
+ ` ${cliCommand} stop`,
947
+ ` ${cliCommand} login [options]`,
176
948
  "",
177
949
  "Options",
178
950
  " --host <host> Proxy host. Login callback always listens on loopback.",
@@ -187,6 +959,8 @@ var helpLines = [
187
959
  " --login-timeout-ms <ms> Login timeout. Default: 300000",
188
960
  "",
189
961
  "Flags",
962
+ " -d, --detach Run in the background",
963
+ " -f, --follow Follow logs",
190
964
  " --help Show help",
191
965
  ` --version Show version (${packageVersion})`,
192
966
  "",
@@ -226,14 +1000,31 @@ var createCliParser = (argv) => yargs(argv).scriptName("openai-oauth").strict().
226
1000
  }).option("login-timeout-ms", {
227
1001
  type: "number",
228
1002
  describe: "Login timeout in milliseconds. Default: 300000"
229
- });
1003
+ }).option("detach", {
1004
+ alias: "d",
1005
+ type: "boolean",
1006
+ default: false,
1007
+ describe: "Run in the background."
1008
+ }).option("follow", {
1009
+ alias: "f",
1010
+ type: "boolean",
1011
+ default: false,
1012
+ describe: "Follow logs."
1013
+ }).option("internal-detached-child", {
1014
+ type: "boolean",
1015
+ default: false
1016
+ }).hide("internal-detached-child");
230
1017
  var isHelpFlag = (argv) => argv.includes("--help") || argv.includes("-h");
231
1018
  var isVersionFlag = (argv) => argv.includes("--version");
232
1019
  var toHelpMessage = () => helpLines.join("\n");
233
1020
  var parseCliArgs = (argv) => {
234
- const command = argv[0] === "login" ? "login" : "serve";
1021
+ const first = argv[0];
1022
+ if (first && !first.startsWith("-") && first !== "serve" && first !== "login" && first !== "logs" && first !== "status" && first !== "stop") {
1023
+ throw new Error(`Unknown command: ${first}`);
1024
+ }
1025
+ const command = first === "login" || first === "logs" || first === "status" || first === "stop" ? first : "serve";
235
1026
  const parsed = createCliParser(
236
- command === "login" ? argv.slice(1) : argv
1027
+ command === "serve" && first !== "serve" ? argv : argv.slice(1)
237
1028
  ).parseSync();
238
1029
  return {
239
1030
  command,
@@ -246,7 +1037,10 @@ var parseCliArgs = (argv) => {
246
1037
  tokenUrl: parsed.oauthTokenUrl,
247
1038
  authFilePath: parsed.oauthFile,
248
1039
  openBrowser: parsed.open,
249
- loginTimeoutMs: parsed.loginTimeoutMs
1040
+ loginTimeoutMs: parsed.loginTimeoutMs,
1041
+ detach: parsed.detach,
1042
+ follow: parsed.follow,
1043
+ internalDetachedChild: parsed.internalDetachedChild
250
1044
  };
251
1045
  };
252
1046
  var toServerOptions = (args) => ({
@@ -313,8 +1107,9 @@ var toMissingAuthFilePrompt = (authFilePath) => {
313
1107
  `Sign in with ChatGPT now? This will write credentials to ${writePath}.`
314
1108
  ].join("\n");
315
1109
  };
1110
+ var toExistingAuthFileMessage = (authFilePath) => `OpenAI OAuth credentials already exist at ${authFilePath}.`;
316
1111
  var toOverwriteAuthFilePrompt = (authFilePath) => [
317
- `OpenAI OAuth credentials already exist at ${authFilePath}.`,
1112
+ toExistingAuthFileMessage(authFilePath),
318
1113
  "Sign in with ChatGPT again and overwrite them?"
319
1114
  ].join("\n");
320
1115
  var toMissingAuthFileMessage = (authFilePath) => {
@@ -333,6 +1128,13 @@ var toMissingAuthFileMessage = (authFilePath) => {
333
1128
  var runUpdateCheck = () => checkForOpenAIOAuthUpdates(packageVersion, {
334
1129
  onWarning: defaultUpdateCheckWarning
335
1130
  });
1131
+ var warnAboutNetworkBinding = (host) => {
1132
+ if (host && host !== "127.0.0.1" && host !== "localhost" && host !== "::1") {
1133
+ console.warn(
1134
+ `Warning: --host ${host} exposes OpenAI OAuth to your network. Anyone who can reach this port can make requests with your ChatGPT account.`
1135
+ );
1136
+ }
1137
+ };
336
1138
  var createLoginCancellation = () => {
337
1139
  const abortController = new AbortController();
338
1140
  let signalName;
@@ -373,6 +1175,200 @@ var runLoginWithCancellation = async (options) => {
373
1175
  cancellation.dispose();
374
1176
  }
375
1177
  };
1178
+ var runLogsCommand = async (follow) => {
1179
+ const recent = await readRecentCliLogs();
1180
+ const running = await findRunningCliRuntime();
1181
+ if (!running) {
1182
+ console.log(cliMessages.notRunningWithStart);
1183
+ return;
1184
+ }
1185
+ if (!follow) {
1186
+ console.log(recent || cliMessages.noLogs);
1187
+ return;
1188
+ }
1189
+ console.log(cliMessages.followingLogs);
1190
+ if (recent) {
1191
+ console.log(recent);
1192
+ }
1193
+ const controller = new AbortController();
1194
+ const abort = () => controller.abort();
1195
+ process.once("SIGINT", abort);
1196
+ process.once("SIGTERM", abort);
1197
+ try {
1198
+ await followCliLogs((text) => process.stdout.write(text), controller.signal);
1199
+ } finally {
1200
+ process.off("SIGINT", abort);
1201
+ process.off("SIGTERM", abort);
1202
+ }
1203
+ };
1204
+ var runStopCommand = async () => {
1205
+ const running = await findRunningCliRuntime();
1206
+ if (!running) {
1207
+ console.log(cliMessages.notRunning);
1208
+ return;
1209
+ }
1210
+ if (!await stopCliRuntime(running)) {
1211
+ throw new Error(cliMessages.couldNotStop);
1212
+ }
1213
+ console.log(cliMessages.stopped);
1214
+ };
1215
+ var runStatusCommand = async () => {
1216
+ const running = await findRunningCliRuntime();
1217
+ if (!running) {
1218
+ console.log(cliMessages.notRunningWithStart);
1219
+ process.exitCode = 1;
1220
+ return;
1221
+ }
1222
+ console.log(toRunningMessage(running.url));
1223
+ };
1224
+ var runForegroundController = async (worker) => new Promise((resolve, reject) => {
1225
+ const interactive = canPrompt();
1226
+ const stdin = process.stdin;
1227
+ const logController = new AbortController();
1228
+ const previousRawMode = stdin.isRaw;
1229
+ let menuShown = false;
1230
+ let completing = false;
1231
+ let remoteStopping = false;
1232
+ let rawModeEnabled = false;
1233
+ const cleanup = () => {
1234
+ logController.abort();
1235
+ stdin.off("data", onData);
1236
+ process.off("SIGINT", onSigint);
1237
+ process.off("SIGTERM", onSigterm);
1238
+ worker.child.off("message", onWorkerMessage);
1239
+ worker.child.off("error", onWorkerError);
1240
+ worker.child.off("exit", onWorkerExit);
1241
+ if (rawModeEnabled && stdin.setRawMode) {
1242
+ stdin.setRawMode(previousRawMode ?? false);
1243
+ }
1244
+ if (interactive) {
1245
+ stdin.pause();
1246
+ }
1247
+ };
1248
+ const finish = (error) => {
1249
+ cleanup();
1250
+ if (error) {
1251
+ reject(error);
1252
+ return;
1253
+ }
1254
+ resolve();
1255
+ };
1256
+ const quit = async (showMessage) => {
1257
+ if (completing) {
1258
+ return;
1259
+ }
1260
+ completing = true;
1261
+ try {
1262
+ await worker.stop();
1263
+ if (showMessage) {
1264
+ console.log(`
1265
+ ${cliMessages.stopped}`);
1266
+ }
1267
+ finish();
1268
+ } catch (error) {
1269
+ finish(
1270
+ error instanceof Error ? error : new Error(cliMessages.couldNotStop)
1271
+ );
1272
+ }
1273
+ };
1274
+ const detach = async () => {
1275
+ if (completing) {
1276
+ return;
1277
+ }
1278
+ completing = true;
1279
+ try {
1280
+ await worker.detach();
1281
+ console.log(`
1282
+ ${toDetachedMessage(worker.url)}`);
1283
+ finish();
1284
+ } catch (error) {
1285
+ completing = false;
1286
+ console.error(
1287
+ `
1288
+ ${cliMessages.couldNotDetach(
1289
+ error instanceof Error ? error.message : void 0
1290
+ )}`
1291
+ );
1292
+ }
1293
+ };
1294
+ const handleInterrupt = () => {
1295
+ if (!interactive || menuShown) {
1296
+ void quit(interactive);
1297
+ return;
1298
+ }
1299
+ menuShown = true;
1300
+ console.log(`
1301
+ ${cliMessages.foregroundControls}`);
1302
+ };
1303
+ function onData(value) {
1304
+ const input = value.toString().toLowerCase();
1305
+ if (input.includes("")) {
1306
+ handleInterrupt();
1307
+ return;
1308
+ }
1309
+ if (input.includes("d")) {
1310
+ void detach();
1311
+ return;
1312
+ }
1313
+ if (input.includes("q")) {
1314
+ void quit(true);
1315
+ }
1316
+ }
1317
+ function onSigint() {
1318
+ handleInterrupt();
1319
+ }
1320
+ function onSigterm() {
1321
+ void quit(false);
1322
+ }
1323
+ function onWorkerMessage(value) {
1324
+ const message = value;
1325
+ if (message.type === "openai-oauth:stopping" && message.source === "remote") {
1326
+ remoteStopping = true;
1327
+ }
1328
+ }
1329
+ function onWorkerError(error) {
1330
+ if (!completing) {
1331
+ finish(error);
1332
+ }
1333
+ }
1334
+ function onWorkerExit(code, signal) {
1335
+ if (completing) {
1336
+ return;
1337
+ }
1338
+ if (remoteStopping) {
1339
+ console.log(`
1340
+ ${cliMessages.stoppedRemotely}`);
1341
+ finish();
1342
+ return;
1343
+ }
1344
+ finish(
1345
+ new Error(
1346
+ cliMessages.stoppedUnexpectedly(signal ?? `${code ?? "unknown"}`)
1347
+ )
1348
+ );
1349
+ }
1350
+ if (interactive) {
1351
+ if (stdin.setRawMode) {
1352
+ stdin.setRawMode(true);
1353
+ rawModeEnabled = true;
1354
+ }
1355
+ stdin.resume();
1356
+ stdin.on("data", onData);
1357
+ }
1358
+ process.on("SIGINT", onSigint);
1359
+ process.on("SIGTERM", onSigterm);
1360
+ worker.child.on("message", onWorkerMessage);
1361
+ worker.child.once("error", onWorkerError);
1362
+ worker.child.once("exit", onWorkerExit);
1363
+ void followCliLogs(
1364
+ (text) => process.stdout.write(text),
1365
+ logController.signal
1366
+ ).catch((error) => {
1367
+ if (!logController.signal.aborted) {
1368
+ console.error(error instanceof Error ? error.message : error);
1369
+ }
1370
+ });
1371
+ });
376
1372
  var runCli = async (argv = hideBin(process.argv)) => {
377
1373
  if (isHelpFlag(argv)) {
378
1374
  console.log(toHelpMessage());
@@ -384,9 +1380,36 @@ var runCli = async (argv = hideBin(process.argv)) => {
384
1380
  }
385
1381
  installCliWarningLogger();
386
1382
  const args = parseCliArgs(argv);
387
- const updateCheck = runUpdateCheck();
1383
+ if (args.internalDetachedChild) {
1384
+ try {
1385
+ await runCliWorker(
1386
+ toServerOptions(args),
1387
+ args.detach ? "background" : "foreground",
1388
+ packageVersion
1389
+ );
1390
+ } catch (error) {
1391
+ await sendDetachedError(
1392
+ error instanceof Error ? error.message : "Failed to start OpenAI OAuth."
1393
+ );
1394
+ throw error;
1395
+ }
1396
+ return;
1397
+ }
1398
+ if (args.command === "logs") {
1399
+ await runLogsCommand(Boolean(args.follow));
1400
+ return;
1401
+ }
1402
+ if (args.command === "stop") {
1403
+ await runStopCommand();
1404
+ return;
1405
+ }
1406
+ if (args.command === "status") {
1407
+ await runStatusCommand();
1408
+ return;
1409
+ }
388
1410
  if (args.command === "login") {
389
- await updateCheck;
1411
+ const updateCheck2 = runUpdateCheck();
1412
+ await updateCheck2;
390
1413
  const loginOptions = toLoginOptions(args);
391
1414
  const existingAuthFile2 = await findExistingCodexAuthFile(
392
1415
  loginOptions.authFilePath
@@ -395,7 +1418,7 @@ var runCli = async (argv = hideBin(process.argv)) => {
395
1418
  if (!canPrompt()) {
396
1419
  throw new Error(
397
1420
  [
398
- `OpenAI OAuth credentials already exist at ${existingAuthFile2}.`,
1421
+ toExistingAuthFileMessage(existingAuthFile2),
399
1422
  "Run `npx openai-oauth login` in an interactive terminal to confirm overwrite."
400
1423
  ].join("\n")
401
1424
  );
@@ -412,7 +1435,14 @@ var runCli = async (argv = hideBin(process.argv)) => {
412
1435
  await runLoginWithCancellation(loginOptions);
413
1436
  return;
414
1437
  }
1438
+ const running = await findRunningCliRuntime();
1439
+ if (running) {
1440
+ console.log(toAlreadyRunningMessage(running.url));
1441
+ return;
1442
+ }
1443
+ const updateCheck = runUpdateCheck();
415
1444
  const options = toServerOptions(args);
1445
+ warnAboutNetworkBinding(options.host);
416
1446
  const existingAuthFile = await findExistingAuthFile(options.authFilePath);
417
1447
  if (!existingAuthFile) {
418
1448
  await updateCheck;
@@ -432,34 +1462,23 @@ var runCli = async (argv = hideBin(process.argv)) => {
432
1462
  return;
433
1463
  }
434
1464
  }
435
- const auth = openaiCredentials(options);
436
- const client = createCodexOAuthClient({
437
- ...options,
438
- auth: () => auth.getSession(),
439
- responsesState: false
440
- });
441
- const availableModels = await resolveOpenAIOAuthModels(client, options.models);
442
- const server = await startOpenAIOAuthServer(options);
443
- console.log(
444
- toStartupMessage(
445
- `http://${server.host}:${server.port}/v1`,
446
- availableModels,
447
- {
1465
+ const worker = await startCliWorker(argv);
1466
+ await updateCheck;
1467
+ if (args.detach) {
1468
+ await worker.detach();
1469
+ console.log(
1470
+ toBackgroundStartupMessage(worker.url, worker.models, {
448
1471
  useColor: process.stdout.isTTY
449
- }
450
- )
1472
+ })
1473
+ );
1474
+ return;
1475
+ }
1476
+ console.log(
1477
+ toForegroundStartupMessage(worker.url, worker.models, {
1478
+ useColor: process.stdout.isTTY
1479
+ })
451
1480
  );
452
- void updateCheck;
453
- const shutdown = async () => {
454
- await server.close();
455
- process.exit(0);
456
- };
457
- process.on("SIGINT", () => {
458
- void shutdown();
459
- });
460
- process.on("SIGTERM", () => {
461
- void shutdown();
462
- });
1481
+ await runForegroundController(worker);
463
1482
  };
464
1483
 
465
1484
  // src/cli.ts