betahi-copilot-bridge 0.1.0 → 0.1.2
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/README.md +8 -2
- package/dist/main.js +186 -108
- package/dist/main.js.map +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -54,7 +54,7 @@ npx betahi-copilot-bridge@latest start
|
|
|
54
54
|
`--select-model`, `--no-prompt`, `--rate-limit <seconds>`, `--wait`.
|
|
55
55
|
|
|
56
56
|
After startup the banner prints a **Usage Viewer** link of the form
|
|
57
|
-
`https://betahi.github.io/copilot-
|
|
57
|
+
`https://betahi.github.io/copilot-bridge?endpoint=http://127.0.0.1:4142/usage`,
|
|
58
58
|
which renders the Copilot quota snapshot (chat / completions / premium
|
|
59
59
|
interactions) read from `GET /usage`.
|
|
60
60
|
|
|
@@ -112,7 +112,13 @@ limit (about 900k succeeds; around 1,000,046 is rejected as too long).
|
|
|
112
112
|
|
|
113
113
|
Bridge endpoints: `POST /v1/messages`, `POST /v1/messages/count_tokens`.
|
|
114
114
|
|
|
115
|
-
|
|
115
|
+
`copilot-bridge start` automatically writes `ANTHROPIC_BASE_URL` (and a dummy
|
|
116
|
+
`ANTHROPIC_AUTH_TOKEN` if absent) into **`~/.claude/settings.json`**, so
|
|
117
|
+
`claude` works the moment the bridge is listening — even on a different host
|
|
118
|
+
or port. Pass `--no-claude-setup` to skip this. All other keys in your
|
|
119
|
+
settings file (model overrides, plugins, marketplaces) are preserved.
|
|
120
|
+
|
|
121
|
+
If you prefer to wire it yourself:
|
|
116
122
|
|
|
117
123
|
```json
|
|
118
124
|
{
|
package/dist/main.js
CHANGED
|
@@ -281,6 +281,111 @@ const auth = defineCommand({
|
|
|
281
281
|
}
|
|
282
282
|
});
|
|
283
283
|
|
|
284
|
+
//#endregion
|
|
285
|
+
//#region src/lib/claude-settings.ts
|
|
286
|
+
const getClaudeSettingsPaths = () => {
|
|
287
|
+
const cwd = process.cwd();
|
|
288
|
+
const home = process.env.HOME ?? os.homedir();
|
|
289
|
+
return [
|
|
290
|
+
path.join(home, ".claude", "settings.json"),
|
|
291
|
+
path.join(cwd, ".claude", "settings.json"),
|
|
292
|
+
path.join(cwd, ".claude", "settings.local.json")
|
|
293
|
+
];
|
|
294
|
+
};
|
|
295
|
+
const readClaudeSettingsFile = async (filePath) => {
|
|
296
|
+
try {
|
|
297
|
+
const content = await fs.readFile(filePath, "utf8");
|
|
298
|
+
return JSON.parse(content);
|
|
299
|
+
} catch (error) {
|
|
300
|
+
if (error.code === "ENOENT") return;
|
|
301
|
+
consola.warn(`Failed to read Claude settings from ${filePath}:`, error);
|
|
302
|
+
return;
|
|
303
|
+
}
|
|
304
|
+
};
|
|
305
|
+
/**
|
|
306
|
+
* Read just the `ANTHROPIC_BASE_URL` from a single Claude settings file.
|
|
307
|
+
* Used at startup so the user can pin the bridge port by editing one place
|
|
308
|
+
* instead of passing `--port` every time.
|
|
309
|
+
*/
|
|
310
|
+
const readClaudeBaseUrl = async (configPath) => {
|
|
311
|
+
const value = (await readClaudeSettingsFile(configPath))?.env?.ANTHROPIC_BASE_URL;
|
|
312
|
+
return typeof value === "string" ? value : void 0;
|
|
313
|
+
};
|
|
314
|
+
/**
|
|
315
|
+
* Parse the port from an `ANTHROPIC_BASE_URL`-style string. Returns
|
|
316
|
+
* `undefined` if the URL is malformed or has no explicit port.
|
|
317
|
+
*/
|
|
318
|
+
const parsePortFromBaseUrl = (baseUrl) => {
|
|
319
|
+
if (!baseUrl) return void 0;
|
|
320
|
+
try {
|
|
321
|
+
const url = new URL(baseUrl);
|
|
322
|
+
if (!url.port) return void 0;
|
|
323
|
+
const port = Number.parseInt(url.port, 10);
|
|
324
|
+
return Number.isFinite(port) && port > 0 ? port : void 0;
|
|
325
|
+
} catch {
|
|
326
|
+
return;
|
|
327
|
+
}
|
|
328
|
+
};
|
|
329
|
+
const getClaudeSettingsEnv = async () => {
|
|
330
|
+
const merged = {};
|
|
331
|
+
for (const filePath of getClaudeSettingsPaths()) {
|
|
332
|
+
const settings = await readClaudeSettingsFile(filePath);
|
|
333
|
+
if (!settings?.env) continue;
|
|
334
|
+
for (const [key, value] of Object.entries(settings.env)) if (typeof value === "string") merged[key] = value;
|
|
335
|
+
}
|
|
336
|
+
return merged;
|
|
337
|
+
};
|
|
338
|
+
/**
|
|
339
|
+
* Update `~/.claude/settings.json` so its env block points Claude Code at the
|
|
340
|
+
* running bridge. Preserves all unrelated keys (model overrides, plugins,
|
|
341
|
+
* marketplaces, etc.). Sets a dummy auth token only if none is present.
|
|
342
|
+
*/
|
|
343
|
+
async function applyClaudeConfig(input) {
|
|
344
|
+
const { configPath, baseUrl } = input;
|
|
345
|
+
let raw = "";
|
|
346
|
+
let created = false;
|
|
347
|
+
try {
|
|
348
|
+
raw = await fs.readFile(configPath, "utf8");
|
|
349
|
+
} catch (error) {
|
|
350
|
+
if (error.code === "ENOENT") created = true;
|
|
351
|
+
else throw error;
|
|
352
|
+
}
|
|
353
|
+
let parsed = {};
|
|
354
|
+
if (raw.trim().length > 0) try {
|
|
355
|
+
parsed = JSON.parse(raw);
|
|
356
|
+
if (typeof parsed !== "object" || parsed === null || Array.isArray(parsed)) parsed = {};
|
|
357
|
+
} catch {
|
|
358
|
+
return {
|
|
359
|
+
configPath,
|
|
360
|
+
changed: false,
|
|
361
|
+
created: false
|
|
362
|
+
};
|
|
363
|
+
}
|
|
364
|
+
const env = typeof parsed.env === "object" && parsed.env !== null && !Array.isArray(parsed.env) ? { ...parsed.env } : {};
|
|
365
|
+
const previousBaseUrl = typeof env.ANTHROPIC_BASE_URL === "string" ? env.ANTHROPIC_BASE_URL : void 0;
|
|
366
|
+
env.ANTHROPIC_BASE_URL = baseUrl;
|
|
367
|
+
if (typeof env.ANTHROPIC_AUTH_TOKEN !== "string" || !env.ANTHROPIC_AUTH_TOKEN) env.ANTHROPIC_AUTH_TOKEN = "dummy";
|
|
368
|
+
const next = {
|
|
369
|
+
...parsed,
|
|
370
|
+
env
|
|
371
|
+
};
|
|
372
|
+
const serialized = `${JSON.stringify(next, null, 2)}\n`;
|
|
373
|
+
if (serialized === raw) return {
|
|
374
|
+
configPath,
|
|
375
|
+
changed: false,
|
|
376
|
+
created: false,
|
|
377
|
+
previousBaseUrl
|
|
378
|
+
};
|
|
379
|
+
await fs.mkdir(path.dirname(configPath), { recursive: true });
|
|
380
|
+
await fs.writeFile(configPath, serialized);
|
|
381
|
+
return {
|
|
382
|
+
configPath,
|
|
383
|
+
changed: true,
|
|
384
|
+
created,
|
|
385
|
+
previousBaseUrl
|
|
386
|
+
};
|
|
387
|
+
}
|
|
388
|
+
|
|
284
389
|
//#endregion
|
|
285
390
|
//#region src/lib/codex-config.ts
|
|
286
391
|
const BEGIN_MARK = "# >>> copilot-bridge managed block — auto-generated, do not edit between markers >>>";
|
|
@@ -395,6 +500,18 @@ async function readCodexUserConfigFromDisk(configPath) {
|
|
|
395
500
|
}
|
|
396
501
|
}
|
|
397
502
|
|
|
503
|
+
//#endregion
|
|
504
|
+
//#region src/lib/defaults.ts
|
|
505
|
+
const DEFAULT_HOST = "127.0.0.1";
|
|
506
|
+
const DEFAULT_PORT = 4142;
|
|
507
|
+
const CODEX_DEFAULTS = {
|
|
508
|
+
providerId: "bridge",
|
|
509
|
+
providerName: "Copilot Bridge",
|
|
510
|
+
setAsDefault: true,
|
|
511
|
+
configPath: path.join(os.homedir(), ".codex", "config.toml")
|
|
512
|
+
};
|
|
513
|
+
const CLAUDE_CONFIG_PATH = path.join(os.homedir(), ".claude", "settings.json");
|
|
514
|
+
|
|
398
515
|
//#endregion
|
|
399
516
|
//#region src/lib/model-capabilities.ts
|
|
400
517
|
const MODEL_CAPABILITIES = [
|
|
@@ -615,49 +732,6 @@ const clampReasoningEffort = (modelId, requested) => {
|
|
|
615
732
|
};
|
|
616
733
|
};
|
|
617
734
|
|
|
618
|
-
//#endregion
|
|
619
|
-
//#region src/lib/settings.ts
|
|
620
|
-
const SETTINGS_DIR = path.join(os.homedir(), ".config", "copilot-bridge");
|
|
621
|
-
const SETTINGS_FILE = path.join(SETTINGS_DIR, "settings.json");
|
|
622
|
-
const DEFAULT_SETTINGS = {
|
|
623
|
-
host: "127.0.0.1",
|
|
624
|
-
port: 4142,
|
|
625
|
-
codex: {
|
|
626
|
-
enabled: true,
|
|
627
|
-
providerId: "bridge",
|
|
628
|
-
providerName: "Copilot Bridge",
|
|
629
|
-
setAsDefault: true,
|
|
630
|
-
configPath: path.join(os.homedir(), ".codex", "config.toml")
|
|
631
|
-
}
|
|
632
|
-
};
|
|
633
|
-
function deepMerge(base, override) {
|
|
634
|
-
if (typeof base !== "object" || base === null || Array.isArray(base) || typeof override !== "object" || override === null || Array.isArray(override)) return override ?? base;
|
|
635
|
-
const out = { ...base };
|
|
636
|
-
for (const [key, value] of Object.entries(override)) out[key] = deepMerge(base[key], value);
|
|
637
|
-
return out;
|
|
638
|
-
}
|
|
639
|
-
async function loadSettings() {
|
|
640
|
-
try {
|
|
641
|
-
const raw = await fs.readFile(SETTINGS_FILE, "utf8");
|
|
642
|
-
return deepMerge(DEFAULT_SETTINGS, JSON.parse(raw));
|
|
643
|
-
} catch {
|
|
644
|
-
return {
|
|
645
|
-
...DEFAULT_SETTINGS,
|
|
646
|
-
codex: { ...DEFAULT_SETTINGS.codex }
|
|
647
|
-
};
|
|
648
|
-
}
|
|
649
|
-
}
|
|
650
|
-
async function ensureSettingsFile() {
|
|
651
|
-
const settings = await loadSettings();
|
|
652
|
-
try {
|
|
653
|
-
await fs.access(SETTINGS_FILE);
|
|
654
|
-
} catch {
|
|
655
|
-
await fs.mkdir(SETTINGS_DIR, { recursive: true });
|
|
656
|
-
await fs.writeFile(SETTINGS_FILE, `${JSON.stringify(settings, null, 2)}\n`);
|
|
657
|
-
}
|
|
658
|
-
return settings;
|
|
659
|
-
}
|
|
660
|
-
|
|
661
735
|
//#endregion
|
|
662
736
|
//#region src/lib/rate-limit.ts
|
|
663
737
|
const sleep = (ms) => new Promise((resolve) => {
|
|
@@ -702,37 +776,6 @@ var RateLimitError = class extends Error {
|
|
|
702
776
|
}
|
|
703
777
|
};
|
|
704
778
|
|
|
705
|
-
//#endregion
|
|
706
|
-
//#region src/lib/claude-settings.ts
|
|
707
|
-
const getClaudeSettingsPaths = () => {
|
|
708
|
-
const cwd = process.cwd();
|
|
709
|
-
const home = process.env.HOME ?? os.homedir();
|
|
710
|
-
return [
|
|
711
|
-
path.join(home, ".claude", "settings.json"),
|
|
712
|
-
path.join(cwd, ".claude", "settings.json"),
|
|
713
|
-
path.join(cwd, ".claude", "settings.local.json")
|
|
714
|
-
];
|
|
715
|
-
};
|
|
716
|
-
const readClaudeSettingsFile = async (filePath) => {
|
|
717
|
-
try {
|
|
718
|
-
const content = await fs.readFile(filePath, "utf8");
|
|
719
|
-
return JSON.parse(content);
|
|
720
|
-
} catch (error) {
|
|
721
|
-
if (error.code === "ENOENT") return;
|
|
722
|
-
consola.warn(`Failed to read Claude settings from ${filePath}:`, error);
|
|
723
|
-
return;
|
|
724
|
-
}
|
|
725
|
-
};
|
|
726
|
-
const getClaudeSettingsEnv = async () => {
|
|
727
|
-
const merged = {};
|
|
728
|
-
for (const filePath of getClaudeSettingsPaths()) {
|
|
729
|
-
const settings = await readClaudeSettingsFile(filePath);
|
|
730
|
-
if (!settings?.env) continue;
|
|
731
|
-
for (const [key, value] of Object.entries(settings.env)) if (typeof value === "string") merged[key] = value;
|
|
732
|
-
}
|
|
733
|
-
return merged;
|
|
734
|
-
};
|
|
735
|
-
|
|
736
779
|
//#endregion
|
|
737
780
|
//#region src/services/copilot/responses.ts
|
|
738
781
|
const RESPONSES_ONLY_MODEL_PATTERN = /^(?:gpt-5\.3-codex|gpt-5\.4-mini)(?:-|$)/i;
|
|
@@ -961,7 +1004,7 @@ function getFinishReason(response, hasFunctionCalls) {
|
|
|
961
1004
|
//#endregion
|
|
962
1005
|
//#region src/services/copilot/create-chat-completions.ts
|
|
963
1006
|
const usesMaxCompletionTokens = (modelId) => modelId.startsWith("gpt-5");
|
|
964
|
-
const isClaudeOpus47Model$1 = (modelId) => modelId
|
|
1007
|
+
const isClaudeOpus47Model$1 = (modelId) => modelId.startsWith("claude-opus-4.7");
|
|
965
1008
|
const MAX_USER_LENGTH = 64;
|
|
966
1009
|
const defaultReasoningEffort = (modelId) => usesMaxCompletionTokens(modelId) ? "medium" : void 0;
|
|
967
1010
|
const getAllowedReasoningEfforts = (modelId) => {
|
|
@@ -1272,7 +1315,7 @@ function isClaudeModel(modelId) {
|
|
|
1272
1315
|
return modelId.startsWith("claude-");
|
|
1273
1316
|
}
|
|
1274
1317
|
function isClaudeOpus47Model(modelId) {
|
|
1275
|
-
return modelId
|
|
1318
|
+
return modelId.startsWith("claude-opus-4.7");
|
|
1276
1319
|
}
|
|
1277
1320
|
function normalizeClaudeEffort(value) {
|
|
1278
1321
|
switch (value?.toLowerCase()) {
|
|
@@ -2428,11 +2471,11 @@ const start = defineCommand({
|
|
|
2428
2471
|
args: {
|
|
2429
2472
|
host: {
|
|
2430
2473
|
type: "string",
|
|
2431
|
-
description:
|
|
2474
|
+
description: `Host to bind (default: ${DEFAULT_HOST}).`
|
|
2432
2475
|
},
|
|
2433
2476
|
port: {
|
|
2434
2477
|
type: "string",
|
|
2435
|
-
description:
|
|
2478
|
+
description: `Port to listen on (default: ${DEFAULT_PORT}).`
|
|
2436
2479
|
},
|
|
2437
2480
|
"show-token": {
|
|
2438
2481
|
type: "boolean",
|
|
@@ -2444,15 +2487,20 @@ const start = defineCommand({
|
|
|
2444
2487
|
default: false,
|
|
2445
2488
|
description: "Skip writing the bridge provider into ~/.codex/config.toml."
|
|
2446
2489
|
},
|
|
2490
|
+
"no-claude-setup": {
|
|
2491
|
+
type: "boolean",
|
|
2492
|
+
default: false,
|
|
2493
|
+
description: "Skip writing ANTHROPIC_BASE_URL into ~/.claude/settings.json."
|
|
2494
|
+
},
|
|
2447
2495
|
"select-model": {
|
|
2448
2496
|
type: "boolean",
|
|
2449
2497
|
default: false,
|
|
2450
|
-
description: "Force the model picker even when codex.
|
|
2498
|
+
description: "Force the model picker even when ~/.codex/config.toml already has a model."
|
|
2451
2499
|
},
|
|
2452
2500
|
"no-prompt": {
|
|
2453
2501
|
type: "boolean",
|
|
2454
2502
|
default: false,
|
|
2455
|
-
description: "Never prompt; use
|
|
2503
|
+
description: "Never prompt; use the existing model from ~/.codex/config.toml as-is."
|
|
2456
2504
|
},
|
|
2457
2505
|
"rate-limit": {
|
|
2458
2506
|
type: "string",
|
|
@@ -2465,10 +2513,13 @@ const start = defineCommand({
|
|
|
2465
2513
|
}
|
|
2466
2514
|
},
|
|
2467
2515
|
async run({ args }) {
|
|
2468
|
-
const
|
|
2516
|
+
const claudeConfigPath = CLAUDE_CONFIG_PATH;
|
|
2517
|
+
const claudePort = parsePortFromBaseUrl(await readClaudeBaseUrl(claudeConfigPath));
|
|
2518
|
+
const envPortRaw = process.env.PORT;
|
|
2519
|
+
const envPort = envPortRaw && Number.isFinite(Number(envPortRaw)) ? Number(envPortRaw) : void 0;
|
|
2469
2520
|
const config = readBridgeConfig({
|
|
2470
|
-
host: args.host ? String(args.host) :
|
|
2471
|
-
port: args.port ? Number(args.port) :
|
|
2521
|
+
host: args.host ? String(args.host) : DEFAULT_HOST,
|
|
2522
|
+
port: args.port ? Number(args.port) : envPort !== void 0 ? envPort : claudePort !== void 0 ? claudePort : DEFAULT_PORT
|
|
2472
2523
|
});
|
|
2473
2524
|
const rateLimitRaw = args["rate-limit"];
|
|
2474
2525
|
if (rateLimitRaw !== void 0) {
|
|
@@ -2485,48 +2536,75 @@ const start = defineCommand({
|
|
|
2485
2536
|
const server = startServer(config);
|
|
2486
2537
|
consola.success(`copilot-bridge listening on http://${config.host}:${config.port}`);
|
|
2487
2538
|
consola.info(`copilot base url: ${config.copilotBaseUrl}`);
|
|
2539
|
+
const baseUrl = `http://${config.host}:${config.port}`;
|
|
2540
|
+
const codexConfigPath = CODEX_DEFAULTS.configPath;
|
|
2541
|
+
if (!args["no-claude-setup"]) try {
|
|
2542
|
+
const claudeResult = await applyClaudeConfig({
|
|
2543
|
+
baseUrl,
|
|
2544
|
+
configPath: claudeConfigPath
|
|
2545
|
+
});
|
|
2546
|
+
if (claudeResult.changed) {
|
|
2547
|
+
consola.success(`claude settings ${claudeResult.created ? "created" : "updated"}: ${claudeResult.configPath}`);
|
|
2548
|
+
if (claudeResult.previousBaseUrl && claudeResult.previousBaseUrl !== baseUrl) consola.info(`ANTHROPIC_BASE_URL: ${claudeResult.previousBaseUrl} → ${baseUrl}`);
|
|
2549
|
+
} else consola.info(`claude settings already up to date: ${claudeResult.configPath}`);
|
|
2550
|
+
} catch (error) {
|
|
2551
|
+
consola.warn(`Could not update claude settings (${claudeConfigPath}):`, error);
|
|
2552
|
+
}
|
|
2553
|
+
const codexUserConfig = await readCodexUserConfigFromDisk(codexConfigPath);
|
|
2554
|
+
let chosenModel = codexUserConfig.model;
|
|
2555
|
+
let chosenEffort = codexUserConfig.modelReasoningEffort;
|
|
2556
|
+
if (!args["no-codex-setup"]) try {
|
|
2557
|
+
const result = await applyCodexConfig({
|
|
2558
|
+
baseUrl: `${baseUrl}/v1`,
|
|
2559
|
+
configPath: codexConfigPath,
|
|
2560
|
+
settings: CODEX_DEFAULTS,
|
|
2561
|
+
model: chosenModel,
|
|
2562
|
+
modelReasoningEffort: chosenEffort
|
|
2563
|
+
});
|
|
2564
|
+
if (result.changed) {
|
|
2565
|
+
consola.success(`codex config ${result.created ? "created" : "updated"}: ${result.configPath}`);
|
|
2566
|
+
if (CODEX_DEFAULTS.setAsDefault) consola.info(`codex now defaults to provider "${CODEX_DEFAULTS.providerId}". Run \`codex exec "..."\` directly.`);
|
|
2567
|
+
else consola.info(`provider "${CODEX_DEFAULTS.providerId}" registered. Use \`codex -c model_provider="${CODEX_DEFAULTS.providerId}" ...\``);
|
|
2568
|
+
} else consola.info(`codex config already up to date: ${result.configPath}`);
|
|
2569
|
+
} catch (error) {
|
|
2570
|
+
consola.warn(`Could not update codex config (${codexConfigPath}):`, error);
|
|
2571
|
+
}
|
|
2488
2572
|
const models = await fetchAvailableModels(config);
|
|
2489
2573
|
const supportedIds = new Set(MODEL_CAPABILITIES.map((m) => m.id));
|
|
2490
2574
|
const pickable = models.length > 0 ? models.filter((id) => supportedIds.has(id)) : MODEL_CAPABILITIES.map((m) => m.id);
|
|
2491
2575
|
const finalPickable = pickable.length > 0 ? pickable : MODEL_CAPABILITIES.map((m) => m.id);
|
|
2492
2576
|
if (models.length > 0) consola.info(`Available models:\n${models.map((id) => `- ${id}${supportedIds.has(id) ? " (bridge-supported)" : ""}`).join("\n")}`);
|
|
2493
2577
|
else consola.warn("Could not fetch model list from upstream Copilot API");
|
|
2494
|
-
|
|
2495
|
-
|
|
2496
|
-
|
|
2578
|
+
consola.box([
|
|
2579
|
+
`🌐 Usage viewer`,
|
|
2580
|
+
``,
|
|
2581
|
+
` https://betahi.github.io/copilot-bridge?endpoint=${baseUrl}/usage`
|
|
2582
|
+
].join("\n"));
|
|
2497
2583
|
if (!args["no-prompt"] && finalPickable.length > 0 && (args["select-model"] || !chosenModel)) {
|
|
2498
2584
|
const defaultId = chosenModel && finalPickable.includes(chosenModel) ? chosenModel : finalPickable.includes("gpt-5.3-codex") ? "gpt-5.3-codex" : finalPickable[0];
|
|
2499
|
-
const selected = await consola.prompt(`Select a model for codex (writes to ${
|
|
2585
|
+
const selected = await consola.prompt(`Select a model for codex (writes to ${codexConfigPath})`, {
|
|
2500
2586
|
type: "select",
|
|
2501
2587
|
options: finalPickable,
|
|
2502
2588
|
initial: defaultId
|
|
2503
2589
|
});
|
|
2504
|
-
if (selected) {
|
|
2590
|
+
if (selected && selected !== chosenModel) {
|
|
2505
2591
|
chosenModel = selected;
|
|
2506
2592
|
const supported = getModelCapability(selected)?.reasoning?.supported;
|
|
2507
2593
|
if (chosenEffort && supported && !supported.includes(chosenEffort)) chosenEffort = void 0;
|
|
2594
|
+
if (!args["no-codex-setup"]) try {
|
|
2595
|
+
const result = await applyCodexConfig({
|
|
2596
|
+
baseUrl: `${baseUrl}/v1`,
|
|
2597
|
+
configPath: codexConfigPath,
|
|
2598
|
+
settings: CODEX_DEFAULTS,
|
|
2599
|
+
model: chosenModel,
|
|
2600
|
+
modelReasoningEffort: chosenEffort
|
|
2601
|
+
});
|
|
2602
|
+
if (result.changed) consola.success(`codex config updated: ${result.configPath}`);
|
|
2603
|
+
} catch (error) {
|
|
2604
|
+
consola.warn(`Could not update codex config (${codexConfigPath}):`, error);
|
|
2605
|
+
}
|
|
2508
2606
|
}
|
|
2509
2607
|
}
|
|
2510
|
-
const baseUrl = `http://${config.host}:${config.port}`;
|
|
2511
|
-
consola.box([
|
|
2512
|
-
`🌐 Usage viewer`,
|
|
2513
|
-
``,
|
|
2514
|
-
` https://betahi.github.io/copilot-api?endpoint=${baseUrl}/usage`
|
|
2515
|
-
].join("\n"));
|
|
2516
|
-
if (settings.codex.enabled && !args["no-codex-setup"]) {
|
|
2517
|
-
const result = await applyCodexConfig({
|
|
2518
|
-
baseUrl: `${baseUrl}/v1`,
|
|
2519
|
-
configPath: settings.codex.configPath,
|
|
2520
|
-
settings: settings.codex,
|
|
2521
|
-
model: chosenModel,
|
|
2522
|
-
modelReasoningEffort: chosenEffort
|
|
2523
|
-
});
|
|
2524
|
-
if (result.changed) {
|
|
2525
|
-
consola.success(`codex config ${result.created ? "created" : "updated"}: ${result.configPath}`);
|
|
2526
|
-
if (settings.codex.setAsDefault) consola.info(`codex now defaults to provider "${settings.codex.providerId}". Run \`codex exec "..."\` directly.`);
|
|
2527
|
-
else consola.info(`provider "${settings.codex.providerId}" registered. Use \`codex -c model_provider="${settings.codex.providerId}" ...\``);
|
|
2528
|
-
} else consola.info(`codex config already up to date: ${result.configPath}`);
|
|
2529
|
-
}
|
|
2530
2608
|
await new Promise((resolve, reject) => {
|
|
2531
2609
|
server.on("close", resolve);
|
|
2532
2610
|
server.on("error", reject);
|