github-router 0.3.8 → 0.3.10
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/main.js +497 -297
- package/dist/main.js.map +1 -1
- package/package.json +1 -2
package/dist/main.js
CHANGED
|
@@ -5,16 +5,15 @@ import fs from "node:fs/promises";
|
|
|
5
5
|
import os from "node:os";
|
|
6
6
|
import path from "node:path";
|
|
7
7
|
import { randomBytes, randomUUID } from "node:crypto";
|
|
8
|
-
import
|
|
8
|
+
import process$1 from "node:process";
|
|
9
9
|
import { serve } from "srvx";
|
|
10
|
-
import invariant from "tiny-invariant";
|
|
11
10
|
import { getProxyForUrl } from "proxy-from-env";
|
|
12
11
|
import { Agent, ProxyAgent, setGlobalDispatcher } from "undici";
|
|
13
|
-
import process$1 from "node:process";
|
|
14
12
|
import { Hono } from "hono";
|
|
15
13
|
import { cors } from "hono/cors";
|
|
16
14
|
import { streamSSE } from "hono/streaming";
|
|
17
15
|
import { events } from "fetch-event-stream";
|
|
16
|
+
import clipboard from "clipboardy";
|
|
18
17
|
|
|
19
18
|
//#region src/lib/paths.ts
|
|
20
19
|
const APP_DIR = path.join(os.homedir(), ".local", "share", "github-router");
|
|
@@ -103,7 +102,7 @@ var HTTPError = class extends Error {
|
|
|
103
102
|
async function forwardError(c, error) {
|
|
104
103
|
consola.error("Error occurred:", error);
|
|
105
104
|
if (error instanceof HTTPError) {
|
|
106
|
-
const errorText = await error.response.text();
|
|
105
|
+
const errorText = await error.response.text().catch(() => "");
|
|
107
106
|
let errorJson;
|
|
108
107
|
try {
|
|
109
108
|
errorJson = JSON.parse(errorText);
|
|
@@ -414,78 +413,86 @@ const checkUsage = defineCommand({
|
|
|
414
413
|
});
|
|
415
414
|
|
|
416
415
|
//#endregion
|
|
417
|
-
//#region src/
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
416
|
+
//#region src/lib/port.ts
|
|
417
|
+
const DEFAULT_PORT = 8787;
|
|
418
|
+
const DEFAULT_CODEX_MODEL = "gpt5.3-codex";
|
|
419
|
+
const PORT_RANGE_MIN = 11e3;
|
|
420
|
+
const PORT_RANGE_MAX = 65535;
|
|
421
|
+
/** Generate a random port number in the range [11000, 65535]. */
|
|
422
|
+
function generateRandomPort() {
|
|
423
|
+
return Math.floor(Math.random() * (PORT_RANGE_MAX - PORT_RANGE_MIN + 1)) + PORT_RANGE_MIN;
|
|
425
424
|
}
|
|
426
|
-
|
|
427
|
-
|
|
425
|
+
|
|
426
|
+
//#endregion
|
|
427
|
+
//#region src/lib/launch.ts
|
|
428
|
+
function buildLaunchCommand(target) {
|
|
428
429
|
return {
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
430
|
+
cmd: target.kind === "claude-code" ? [
|
|
431
|
+
"claude",
|
|
432
|
+
"--dangerously-skip-permissions",
|
|
433
|
+
...target.extraArgs
|
|
434
|
+
] : [
|
|
435
|
+
"codex",
|
|
436
|
+
"-m",
|
|
437
|
+
target.model ?? DEFAULT_CODEX_MODEL,
|
|
438
|
+
...target.extraArgs
|
|
439
|
+
],
|
|
440
|
+
env: {
|
|
441
|
+
...process$1.env,
|
|
442
|
+
...target.envVars
|
|
443
|
+
}
|
|
433
444
|
};
|
|
434
445
|
}
|
|
435
|
-
|
|
446
|
+
function launchChild(target, server$1) {
|
|
447
|
+
const { cmd, env } = buildLaunchCommand(target);
|
|
448
|
+
const executable = cmd[0];
|
|
449
|
+
if (!Bun.which(executable)) {
|
|
450
|
+
consola.error(`"${executable}" not found on PATH. Install it first, then try again.`);
|
|
451
|
+
process$1.exit(1);
|
|
452
|
+
}
|
|
453
|
+
let child;
|
|
436
454
|
try {
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
455
|
+
child = Bun.spawn({
|
|
456
|
+
cmd,
|
|
457
|
+
env,
|
|
458
|
+
stdin: "inherit",
|
|
459
|
+
stdout: "inherit",
|
|
460
|
+
stderr: "inherit"
|
|
461
|
+
});
|
|
462
|
+
} catch (error) {
|
|
463
|
+
consola.error(`Failed to launch ${executable}:`, error instanceof Error ? error.message : String(error));
|
|
464
|
+
server$1.close(true).catch(() => {});
|
|
465
|
+
process$1.exit(1);
|
|
466
|
+
}
|
|
467
|
+
let cleaned = false;
|
|
468
|
+
let exiting = false;
|
|
469
|
+
async function cleanup() {
|
|
470
|
+
if (cleaned) return;
|
|
471
|
+
cleaned = true;
|
|
472
|
+
try {
|
|
473
|
+
child.kill();
|
|
474
|
+
} catch {}
|
|
475
|
+
const timeout = setTimeout(() => process$1.exit(1), 5e3);
|
|
476
|
+
try {
|
|
477
|
+
await server$1.close(true);
|
|
478
|
+
} catch {}
|
|
479
|
+
clearTimeout(timeout);
|
|
441
480
|
}
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
APP_DIR: PATHS.APP_DIR,
|
|
450
|
-
GITHUB_TOKEN_PATH: PATHS.GITHUB_TOKEN_PATH
|
|
451
|
-
},
|
|
452
|
-
tokenExists
|
|
481
|
+
function exit(code) {
|
|
482
|
+
if (exiting) return;
|
|
483
|
+
exiting = true;
|
|
484
|
+
process$1.exit(code);
|
|
485
|
+
}
|
|
486
|
+
const onSignal = () => {
|
|
487
|
+
cleanup().then(() => exit(130)).catch(() => exit(1));
|
|
453
488
|
};
|
|
489
|
+
process$1.on("SIGINT", onSignal);
|
|
490
|
+
process$1.on("SIGTERM", onSignal);
|
|
491
|
+
child.exited.then(async (exitCode) => {
|
|
492
|
+
await cleanup();
|
|
493
|
+
exit(exitCode ?? 0);
|
|
494
|
+
}).catch(() => exit(1));
|
|
454
495
|
}
|
|
455
|
-
function printDebugInfoPlain(info) {
|
|
456
|
-
consola.info(`github-router debug
|
|
457
|
-
|
|
458
|
-
Version: ${info.version}
|
|
459
|
-
Runtime: ${info.runtime.name} ${info.runtime.version} (${info.runtime.platform} ${info.runtime.arch})
|
|
460
|
-
|
|
461
|
-
Paths:
|
|
462
|
-
- APP_DIR: ${info.paths.APP_DIR}
|
|
463
|
-
- GITHUB_TOKEN_PATH: ${info.paths.GITHUB_TOKEN_PATH}
|
|
464
|
-
|
|
465
|
-
Token exists: ${info.tokenExists ? "Yes" : "No"}`);
|
|
466
|
-
}
|
|
467
|
-
function printDebugInfoJson(info) {
|
|
468
|
-
console.log(JSON.stringify(info, null, 2));
|
|
469
|
-
}
|
|
470
|
-
async function runDebug(options) {
|
|
471
|
-
const debugInfo = await getDebugInfo();
|
|
472
|
-
if (options.json) printDebugInfoJson(debugInfo);
|
|
473
|
-
else printDebugInfoPlain(debugInfo);
|
|
474
|
-
}
|
|
475
|
-
const debug = defineCommand({
|
|
476
|
-
meta: {
|
|
477
|
-
name: "debug",
|
|
478
|
-
description: "Print debug information about the application"
|
|
479
|
-
},
|
|
480
|
-
args: { json: {
|
|
481
|
-
type: "boolean",
|
|
482
|
-
default: false,
|
|
483
|
-
description: "Output debug information as JSON"
|
|
484
|
-
} },
|
|
485
|
-
run({ args }) {
|
|
486
|
-
return runDebug({ json: args.json });
|
|
487
|
-
}
|
|
488
|
-
});
|
|
489
496
|
|
|
490
497
|
//#endregion
|
|
491
498
|
//#region src/lib/proxy.ts
|
|
@@ -533,69 +540,6 @@ function initProxyFromEnv() {
|
|
|
533
540
|
}
|
|
534
541
|
}
|
|
535
542
|
|
|
536
|
-
//#endregion
|
|
537
|
-
//#region src/lib/shell.ts
|
|
538
|
-
function getShell() {
|
|
539
|
-
const { platform, env } = process$1;
|
|
540
|
-
if (platform === "win32") {
|
|
541
|
-
if (env.SHELL) {
|
|
542
|
-
if (env.SHELL.endsWith("zsh")) return "zsh";
|
|
543
|
-
if (env.SHELL.endsWith("fish")) return "fish";
|
|
544
|
-
if (env.SHELL.endsWith("bash")) return "bash";
|
|
545
|
-
return "sh";
|
|
546
|
-
}
|
|
547
|
-
if (env.POWERSHELL_DISTRIBUTION_CHANNEL) return "powershell";
|
|
548
|
-
if (env.PSModulePath) {
|
|
549
|
-
const lower = env.PSModulePath.toLowerCase();
|
|
550
|
-
if (lower.includes("documents\\powershell") || lower.includes("documents\\windowspowershell")) return "powershell";
|
|
551
|
-
}
|
|
552
|
-
return "cmd";
|
|
553
|
-
}
|
|
554
|
-
const shellPath = env.SHELL;
|
|
555
|
-
if (shellPath) {
|
|
556
|
-
if (shellPath.endsWith("zsh")) return "zsh";
|
|
557
|
-
if (shellPath.endsWith("fish")) return "fish";
|
|
558
|
-
if (shellPath.endsWith("bash")) return "bash";
|
|
559
|
-
}
|
|
560
|
-
return "sh";
|
|
561
|
-
}
|
|
562
|
-
function quotePosixValue(value) {
|
|
563
|
-
return `'${value.replace(/'/g, "'\\''")}'`;
|
|
564
|
-
}
|
|
565
|
-
function quotePowerShellValue(value) {
|
|
566
|
-
return `'${value.replace(/'/g, "''")}'`;
|
|
567
|
-
}
|
|
568
|
-
/**
|
|
569
|
-
* Generates a copy-pasteable script to set multiple environment variables
|
|
570
|
-
* and run a subsequent command.
|
|
571
|
-
* @param {EnvVars} envVars - An object of environment variables to set.
|
|
572
|
-
* @param {string} commandToRun - The command to run after setting the variables.
|
|
573
|
-
* @returns {string} The formatted script string.
|
|
574
|
-
*/
|
|
575
|
-
function generateEnvScript(envVars, commandToRun = "") {
|
|
576
|
-
const shell = getShell();
|
|
577
|
-
const filteredEnvVars = Object.entries(envVars).filter(([, value]) => value !== void 0);
|
|
578
|
-
let commandBlock;
|
|
579
|
-
switch (shell) {
|
|
580
|
-
case "powershell":
|
|
581
|
-
commandBlock = filteredEnvVars.map(([key, value]) => `$env:${key} = ${quotePowerShellValue(value)}`).join("; ");
|
|
582
|
-
break;
|
|
583
|
-
case "cmd":
|
|
584
|
-
commandBlock = filteredEnvVars.map(([key, value]) => `set "${key}=${value}"`).join(" & ");
|
|
585
|
-
break;
|
|
586
|
-
case "fish":
|
|
587
|
-
commandBlock = filteredEnvVars.map(([key, value]) => `set -gx ${key} ${quotePosixValue(value)}`).join("; ");
|
|
588
|
-
break;
|
|
589
|
-
default: {
|
|
590
|
-
const assignments = filteredEnvVars.map(([key, value]) => `${key}=${quotePosixValue(value)}`).join(" ");
|
|
591
|
-
commandBlock = filteredEnvVars.length > 0 ? `export ${assignments}` : "";
|
|
592
|
-
break;
|
|
593
|
-
}
|
|
594
|
-
}
|
|
595
|
-
if (commandBlock && commandToRun) return `${commandBlock}${shell === "cmd" ? " & " : shell === "powershell" ? "; " : " && "}${commandToRun}`;
|
|
596
|
-
return commandBlock || commandToRun;
|
|
597
|
-
}
|
|
598
|
-
|
|
599
543
|
//#endregion
|
|
600
544
|
//#region src/lib/approval.ts
|
|
601
545
|
const awaitApproval = async () => {
|
|
@@ -1009,8 +953,9 @@ async function handleCompletion$1(c) {
|
|
|
1009
953
|
};
|
|
1010
954
|
if (debugEnabled) consola.debug("Set max_tokens to:", JSON.stringify(payload.max_tokens));
|
|
1011
955
|
}
|
|
1012
|
-
const response = await createChatCompletions(payload, selectedModel?.requestHeaders).catch((error) => {
|
|
1013
|
-
if (error instanceof HTTPError)
|
|
956
|
+
const response = await createChatCompletions(payload, selectedModel?.requestHeaders).catch(async (error) => {
|
|
957
|
+
if (error instanceof HTTPError) {
|
|
958
|
+
const errorBody = await error.response.clone().text().catch(() => "");
|
|
1014
959
|
logRequest({
|
|
1015
960
|
method: "POST",
|
|
1016
961
|
path: c.req.path,
|
|
@@ -1019,7 +964,7 @@ async function handleCompletion$1(c) {
|
|
|
1019
964
|
status: error.response.status,
|
|
1020
965
|
errorBody
|
|
1021
966
|
}, selectedModel, startTime);
|
|
1022
|
-
}
|
|
967
|
+
}
|
|
1023
968
|
throw error;
|
|
1024
969
|
});
|
|
1025
970
|
const isStreaming = !isNonStreaming$1(response);
|
|
@@ -1640,8 +1585,9 @@ async function handleResponses(c) {
|
|
|
1640
1585
|
payload.max_output_tokens = selectedModel?.capabilities.limits.max_output_tokens;
|
|
1641
1586
|
if (debugEnabled) consola.debug("Set max_output_tokens to:", JSON.stringify(payload.max_output_tokens));
|
|
1642
1587
|
}
|
|
1643
|
-
const response = await createResponses(payload, selectedModel?.requestHeaders).catch((error) => {
|
|
1644
|
-
if (error instanceof HTTPError)
|
|
1588
|
+
const response = await createResponses(payload, selectedModel?.requestHeaders).catch(async (error) => {
|
|
1589
|
+
if (error instanceof HTTPError) {
|
|
1590
|
+
const errorBody = await error.response.clone().text().catch(() => "");
|
|
1645
1591
|
logRequest({
|
|
1646
1592
|
method: "POST",
|
|
1647
1593
|
path: c.req.path,
|
|
@@ -1650,7 +1596,7 @@ async function handleResponses(c) {
|
|
|
1650
1596
|
status: error.response.status,
|
|
1651
1597
|
errorBody
|
|
1652
1598
|
}, selectedModel, startTime);
|
|
1653
|
-
}
|
|
1599
|
+
}
|
|
1654
1600
|
throw error;
|
|
1655
1601
|
});
|
|
1656
1602
|
const isStreaming = !isNonStreaming(response);
|
|
@@ -1785,84 +1731,9 @@ server.route("/v1/search", searchRoutes);
|
|
|
1785
1731
|
server.route("/v1/messages", messageRoutes);
|
|
1786
1732
|
|
|
1787
1733
|
//#endregion
|
|
1788
|
-
//#region src/
|
|
1789
|
-
const
|
|
1790
|
-
|
|
1791
|
-
"business",
|
|
1792
|
-
"enterprise"
|
|
1793
|
-
]);
|
|
1794
|
-
function printAndCopyCommand(command, label) {
|
|
1795
|
-
consola.box(`${label}\n\n${command}`);
|
|
1796
|
-
try {
|
|
1797
|
-
clipboard.writeSync(command);
|
|
1798
|
-
consola.success(`Copied ${label} command to clipboard!`);
|
|
1799
|
-
} catch {
|
|
1800
|
-
consola.warn("Failed to copy to clipboard. Copy the command above manually.");
|
|
1801
|
-
}
|
|
1802
|
-
}
|
|
1803
|
-
function filterModelsByEndpoint(models, endpoint) {
|
|
1804
|
-
const filtered = models.filter((model) => {
|
|
1805
|
-
const endpoints = model.supported_endpoints;
|
|
1806
|
-
if (!endpoints || endpoints.length === 0) return true;
|
|
1807
|
-
return endpoints.some((entry) => {
|
|
1808
|
-
return entry.replace(/^\/?v1\//, "").replace(/^\//, "") === endpoint;
|
|
1809
|
-
});
|
|
1810
|
-
});
|
|
1811
|
-
return filtered.length > 0 ? filtered : models;
|
|
1812
|
-
}
|
|
1813
|
-
async function generateClaudeCodeCommand(serverUrl) {
|
|
1814
|
-
invariant(state.models, "Models should be loaded by now");
|
|
1815
|
-
const claudeModels = state.models.data.filter((model) => model.id.toLowerCase().startsWith("claude"));
|
|
1816
|
-
if (claudeModels.length === 0) {
|
|
1817
|
-
consola.error("No Claude models available from Copilot API");
|
|
1818
|
-
return;
|
|
1819
|
-
}
|
|
1820
|
-
const mainModel = claudeModels.find((m) => m.id.includes("opus")) ?? claudeModels.find((m) => m.id.includes("sonnet")) ?? claudeModels[0];
|
|
1821
|
-
const smallModel = claudeModels.find((m) => m.id.includes("haiku")) ?? claudeModels.find((m) => m.id.includes("sonnet")) ?? claudeModels[0];
|
|
1822
|
-
let selectedModel = mainModel.id;
|
|
1823
|
-
let selectedSmallModel = smallModel.id;
|
|
1824
|
-
if (claudeModels.length > 1) {
|
|
1825
|
-
consola.info(`Using ${mainModel.id} as main model and ${smallModel.id} as small model`);
|
|
1826
|
-
if (await consola.prompt("Override model selection?", {
|
|
1827
|
-
type: "confirm",
|
|
1828
|
-
initial: false
|
|
1829
|
-
})) {
|
|
1830
|
-
selectedModel = await consola.prompt("Select a main model for Claude Code", {
|
|
1831
|
-
type: "select",
|
|
1832
|
-
options: claudeModels.map((model) => model.id)
|
|
1833
|
-
});
|
|
1834
|
-
selectedSmallModel = await consola.prompt("Select a small/fast model for Claude Code", {
|
|
1835
|
-
type: "select",
|
|
1836
|
-
options: claudeModels.map((model) => model.id)
|
|
1837
|
-
});
|
|
1838
|
-
}
|
|
1839
|
-
}
|
|
1840
|
-
printAndCopyCommand(generateEnvScript({
|
|
1841
|
-
ANTHROPIC_BASE_URL: serverUrl,
|
|
1842
|
-
ANTHROPIC_AUTH_TOKEN: "dummy",
|
|
1843
|
-
ANTHROPIC_MODEL: selectedModel,
|
|
1844
|
-
ANTHROPIC_DEFAULT_SONNET_MODEL: selectedModel,
|
|
1845
|
-
ANTHROPIC_SMALL_FAST_MODEL: selectedSmallModel,
|
|
1846
|
-
ANTHROPIC_DEFAULT_HAIKU_MODEL: selectedSmallModel,
|
|
1847
|
-
DISABLE_NON_ESSENTIAL_MODEL_CALLS: "1",
|
|
1848
|
-
CLAUDE_CODE_DISABLE_NONESSENTIAL_TRAFFIC: "1"
|
|
1849
|
-
}, "claude --dangerously-skip-permissions"), "Claude Code");
|
|
1850
|
-
}
|
|
1851
|
-
async function generateCodexCommand(serverUrl) {
|
|
1852
|
-
invariant(state.models, "Models should be loaded by now");
|
|
1853
|
-
const supportedModels = filterModelsByEndpoint(state.models.data, "responses");
|
|
1854
|
-
const defaultCodexModel = supportedModels.find((model) => model.id === "gpt5.2-codex");
|
|
1855
|
-
const selectedModel = defaultCodexModel ? defaultCodexModel.id : await consola.prompt("Select a model to use with Codex CLI", {
|
|
1856
|
-
type: "select",
|
|
1857
|
-
options: supportedModels.map((model) => model.id)
|
|
1858
|
-
});
|
|
1859
|
-
const quotedModel = JSON.stringify(selectedModel);
|
|
1860
|
-
printAndCopyCommand(generateEnvScript({
|
|
1861
|
-
OPENAI_BASE_URL: `${serverUrl}/v1`,
|
|
1862
|
-
OPENAI_API_KEY: "dummy"
|
|
1863
|
-
}, `codex -m ${quotedModel}`), "Codex CLI");
|
|
1864
|
-
}
|
|
1865
|
-
async function runServer(options) {
|
|
1734
|
+
//#region src/lib/server-setup.ts
|
|
1735
|
+
const MAX_PORT_RETRIES = 10;
|
|
1736
|
+
async function setupAndServe(options) {
|
|
1866
1737
|
if (options.proxyEnv) initProxyFromEnv();
|
|
1867
1738
|
if (options.verbose) {
|
|
1868
1739
|
consola.level = 5;
|
|
@@ -1883,115 +1754,442 @@ async function runServer(options) {
|
|
|
1883
1754
|
await setupCopilotToken();
|
|
1884
1755
|
await cacheModels();
|
|
1885
1756
|
consola.info(`Available models: \n${state.models?.data.map((model) => `- ${model.id}`).join("\n")}`);
|
|
1886
|
-
const
|
|
1887
|
-
if (options.claudeCode) await generateClaudeCodeCommand(serverUrl);
|
|
1888
|
-
if (options.codex) await generateCodexCommand(serverUrl);
|
|
1889
|
-
consola.box(`🌐 Usage Viewer: https://animeshkundu.github.io/github-router/dashboard.html?endpoint=${serverUrl}/usage`);
|
|
1890
|
-
serve({
|
|
1757
|
+
const serveOptions = {
|
|
1891
1758
|
fetch: server.fetch,
|
|
1892
1759
|
hostname: "127.0.0.1",
|
|
1760
|
+
silent: options.silent
|
|
1761
|
+
};
|
|
1762
|
+
let srvxServer;
|
|
1763
|
+
if (options.port !== void 0) srvxServer = serve({
|
|
1764
|
+
...serveOptions,
|
|
1893
1765
|
port: options.port
|
|
1894
1766
|
});
|
|
1767
|
+
else {
|
|
1768
|
+
let lastError;
|
|
1769
|
+
for (let attempt = 0; attempt < MAX_PORT_RETRIES; attempt++) {
|
|
1770
|
+
const candidatePort = generateRandomPort();
|
|
1771
|
+
try {
|
|
1772
|
+
srvxServer = serve({
|
|
1773
|
+
...serveOptions,
|
|
1774
|
+
port: candidatePort
|
|
1775
|
+
});
|
|
1776
|
+
break;
|
|
1777
|
+
} catch (error) {
|
|
1778
|
+
lastError = error;
|
|
1779
|
+
if (!(error instanceof Error && (error.message.includes("EADDRINUSE") || error.message.includes("address already in use") || "code" in error && error.code === "EADDRINUSE"))) throw error;
|
|
1780
|
+
consola.debug(`Port ${candidatePort} in use, trying another...`);
|
|
1781
|
+
}
|
|
1782
|
+
}
|
|
1783
|
+
if (srvxServer === void 0) throw new Error(`Failed to find an available port after ${MAX_PORT_RETRIES} attempts. Specify a port with --port or free some ports. Last error: ${lastError}`);
|
|
1784
|
+
}
|
|
1785
|
+
await srvxServer.ready();
|
|
1786
|
+
const url = srvxServer.url;
|
|
1787
|
+
if (!url) throw new Error("Server started but URL is not available");
|
|
1788
|
+
const serverUrl = url.replace(/\/$/, "");
|
|
1789
|
+
return {
|
|
1790
|
+
server: srvxServer,
|
|
1791
|
+
serverUrl
|
|
1792
|
+
};
|
|
1895
1793
|
}
|
|
1896
|
-
|
|
1794
|
+
/** Shared CLI arg definitions for all server commands. */
|
|
1795
|
+
const sharedServerArgs = {
|
|
1796
|
+
port: {
|
|
1797
|
+
alias: "p",
|
|
1798
|
+
type: "string",
|
|
1799
|
+
description: "Port to listen on"
|
|
1800
|
+
},
|
|
1801
|
+
verbose: {
|
|
1802
|
+
alias: "v",
|
|
1803
|
+
type: "boolean",
|
|
1804
|
+
default: false,
|
|
1805
|
+
description: "Enable verbose logging"
|
|
1806
|
+
},
|
|
1807
|
+
"account-type": {
|
|
1808
|
+
alias: "a",
|
|
1809
|
+
type: "string",
|
|
1810
|
+
default: "enterprise",
|
|
1811
|
+
description: "Account type to use (individual, business, enterprise)"
|
|
1812
|
+
},
|
|
1813
|
+
manual: {
|
|
1814
|
+
type: "boolean",
|
|
1815
|
+
default: false,
|
|
1816
|
+
description: "Enable manual request approval"
|
|
1817
|
+
},
|
|
1818
|
+
"rate-limit": {
|
|
1819
|
+
alias: "r",
|
|
1820
|
+
type: "string",
|
|
1821
|
+
description: "Rate limit in seconds between requests"
|
|
1822
|
+
},
|
|
1823
|
+
wait: {
|
|
1824
|
+
alias: "w",
|
|
1825
|
+
type: "boolean",
|
|
1826
|
+
default: false,
|
|
1827
|
+
description: "Wait instead of error when rate limit is hit. Has no effect if rate limit is not set"
|
|
1828
|
+
},
|
|
1829
|
+
"github-token": {
|
|
1830
|
+
alias: "g",
|
|
1831
|
+
type: "string",
|
|
1832
|
+
description: "Provide GitHub token directly (must be generated using the `auth` subcommand)"
|
|
1833
|
+
},
|
|
1834
|
+
"show-token": {
|
|
1835
|
+
type: "boolean",
|
|
1836
|
+
default: false,
|
|
1837
|
+
description: "Show GitHub and Copilot tokens on fetch and refresh"
|
|
1838
|
+
},
|
|
1839
|
+
"proxy-env": {
|
|
1840
|
+
type: "boolean",
|
|
1841
|
+
default: false,
|
|
1842
|
+
description: "Initialize proxy from environment variables"
|
|
1843
|
+
}
|
|
1844
|
+
};
|
|
1845
|
+
const allowedAccountTypes = new Set([
|
|
1846
|
+
"individual",
|
|
1847
|
+
"business",
|
|
1848
|
+
"enterprise"
|
|
1849
|
+
]);
|
|
1850
|
+
/** Parse shared server args into ServerSetupOptions fields. */
|
|
1851
|
+
function parseSharedArgs(args) {
|
|
1852
|
+
const portRaw = args.port;
|
|
1853
|
+
let port;
|
|
1854
|
+
if (portRaw !== void 0) {
|
|
1855
|
+
port = Number.parseInt(portRaw, 10);
|
|
1856
|
+
if (Number.isNaN(port) || port <= 0 || port > 65535) throw new Error("Invalid port. Must be between 1 and 65535.");
|
|
1857
|
+
}
|
|
1858
|
+
const accountType = args["account-type"] ?? "enterprise";
|
|
1859
|
+
if (!allowedAccountTypes.has(accountType)) throw new Error("Invalid account type. Must be individual, business, or enterprise.");
|
|
1860
|
+
const rateLimitRaw = args["rate-limit"];
|
|
1861
|
+
let rateLimit;
|
|
1862
|
+
if (rateLimitRaw !== void 0) {
|
|
1863
|
+
rateLimit = Number.parseInt(rateLimitRaw, 10);
|
|
1864
|
+
if (Number.isNaN(rateLimit) || rateLimit <= 0) throw new Error("Invalid rate limit. Must be a positive integer.");
|
|
1865
|
+
}
|
|
1866
|
+
const rateLimitWait = args.wait && rateLimit !== void 0;
|
|
1867
|
+
if (args.wait && rateLimit === void 0) consola.warn("Rate limit wait ignored because no rate limit was set.");
|
|
1868
|
+
const githubToken = args["github-token"] ?? process.env.GH_TOKEN;
|
|
1869
|
+
return {
|
|
1870
|
+
port,
|
|
1871
|
+
verbose: args.verbose,
|
|
1872
|
+
accountType,
|
|
1873
|
+
manual: args.manual,
|
|
1874
|
+
rateLimit,
|
|
1875
|
+
rateLimitWait,
|
|
1876
|
+
githubToken,
|
|
1877
|
+
showToken: args["show-token"],
|
|
1878
|
+
proxyEnv: args["proxy-env"]
|
|
1879
|
+
};
|
|
1880
|
+
}
|
|
1881
|
+
/** Build environment variables for Claude Code. */
|
|
1882
|
+
function getClaudeCodeEnvVars(serverUrl, model) {
|
|
1883
|
+
const vars = {
|
|
1884
|
+
ANTHROPIC_BASE_URL: serverUrl,
|
|
1885
|
+
ANTHROPIC_AUTH_TOKEN: "dummy",
|
|
1886
|
+
DISABLE_NON_ESSENTIAL_MODEL_CALLS: "1",
|
|
1887
|
+
CLAUDE_CODE_DISABLE_NONESSENTIAL_TRAFFIC: "1"
|
|
1888
|
+
};
|
|
1889
|
+
if (model) vars.ANTHROPIC_MODEL = model;
|
|
1890
|
+
return vars;
|
|
1891
|
+
}
|
|
1892
|
+
/** Build environment variables for Codex CLI. */
|
|
1893
|
+
function getCodexEnvVars(serverUrl) {
|
|
1894
|
+
return {
|
|
1895
|
+
OPENAI_BASE_URL: `${serverUrl}/v1`,
|
|
1896
|
+
OPENAI_API_KEY: "dummy"
|
|
1897
|
+
};
|
|
1898
|
+
}
|
|
1899
|
+
|
|
1900
|
+
//#endregion
|
|
1901
|
+
//#region src/claude.ts
|
|
1902
|
+
const claude = defineCommand({
|
|
1897
1903
|
meta: {
|
|
1898
|
-
name: "
|
|
1899
|
-
description: "Start the
|
|
1904
|
+
name: "claude",
|
|
1905
|
+
description: "Start the proxy server and launch Claude Code"
|
|
1900
1906
|
},
|
|
1901
1907
|
args: {
|
|
1902
|
-
|
|
1903
|
-
|
|
1904
|
-
|
|
1905
|
-
default: "8787",
|
|
1906
|
-
description: "Port to listen on"
|
|
1907
|
-
},
|
|
1908
|
-
verbose: {
|
|
1909
|
-
alias: "v",
|
|
1910
|
-
type: "boolean",
|
|
1911
|
-
default: false,
|
|
1912
|
-
description: "Enable verbose logging"
|
|
1913
|
-
},
|
|
1914
|
-
"account-type": {
|
|
1915
|
-
alias: "a",
|
|
1916
|
-
type: "string",
|
|
1917
|
-
default: "enterprise",
|
|
1918
|
-
description: "Account type to use (individual, business, enterprise)"
|
|
1919
|
-
},
|
|
1920
|
-
manual: {
|
|
1921
|
-
type: "boolean",
|
|
1922
|
-
default: false,
|
|
1923
|
-
description: "Enable manual request approval"
|
|
1924
|
-
},
|
|
1925
|
-
"rate-limit": {
|
|
1926
|
-
alias: "r",
|
|
1908
|
+
...sharedServerArgs,
|
|
1909
|
+
model: {
|
|
1910
|
+
alias: "m",
|
|
1927
1911
|
type: "string",
|
|
1928
|
-
description: "
|
|
1929
|
-
}
|
|
1930
|
-
|
|
1931
|
-
|
|
1932
|
-
|
|
1933
|
-
|
|
1934
|
-
|
|
1935
|
-
}
|
|
1936
|
-
|
|
1937
|
-
|
|
1912
|
+
description: "Override the default model for Claude Code"
|
|
1913
|
+
}
|
|
1914
|
+
},
|
|
1915
|
+
async run({ args }) {
|
|
1916
|
+
if (!process$1.stdout.isTTY) {
|
|
1917
|
+
consola.error("The claude subcommand requires a TTY (interactive terminal).");
|
|
1918
|
+
process$1.exit(1);
|
|
1919
|
+
}
|
|
1920
|
+
const parsed = parseSharedArgs(args);
|
|
1921
|
+
let server$1;
|
|
1922
|
+
let serverUrl;
|
|
1923
|
+
try {
|
|
1924
|
+
const result = await setupAndServe({
|
|
1925
|
+
...parsed,
|
|
1926
|
+
port: parsed.port,
|
|
1927
|
+
silent: true
|
|
1928
|
+
});
|
|
1929
|
+
server$1 = result.server;
|
|
1930
|
+
serverUrl = result.serverUrl;
|
|
1931
|
+
} catch (error) {
|
|
1932
|
+
consola.error("Failed to start server:", error instanceof Error ? error.message : error);
|
|
1933
|
+
process$1.exit(1);
|
|
1934
|
+
}
|
|
1935
|
+
consola.success(`Server ready on ${serverUrl}, launching Claude Code...`);
|
|
1936
|
+
consola.level = 1;
|
|
1937
|
+
launchChild({
|
|
1938
|
+
kind: "claude-code",
|
|
1939
|
+
envVars: getClaudeCodeEnvVars(serverUrl, args.model),
|
|
1940
|
+
extraArgs: args._ ?? [],
|
|
1941
|
+
model: args.model
|
|
1942
|
+
}, server$1);
|
|
1943
|
+
}
|
|
1944
|
+
});
|
|
1945
|
+
|
|
1946
|
+
//#endregion
|
|
1947
|
+
//#region src/codex.ts
|
|
1948
|
+
const codex = defineCommand({
|
|
1949
|
+
meta: {
|
|
1950
|
+
name: "codex",
|
|
1951
|
+
description: "Start the proxy server and launch Codex CLI"
|
|
1952
|
+
},
|
|
1953
|
+
args: {
|
|
1954
|
+
...sharedServerArgs,
|
|
1955
|
+
model: {
|
|
1956
|
+
alias: "m",
|
|
1938
1957
|
type: "string",
|
|
1939
|
-
description: "
|
|
1958
|
+
description: "Override the default model for Codex CLI"
|
|
1959
|
+
}
|
|
1960
|
+
},
|
|
1961
|
+
async run({ args }) {
|
|
1962
|
+
if (!process$1.stdout.isTTY) {
|
|
1963
|
+
consola.error("The codex subcommand requires a TTY (interactive terminal).");
|
|
1964
|
+
process$1.exit(1);
|
|
1965
|
+
}
|
|
1966
|
+
const parsed = parseSharedArgs(args);
|
|
1967
|
+
let server$1;
|
|
1968
|
+
let serverUrl;
|
|
1969
|
+
try {
|
|
1970
|
+
const result = await setupAndServe({
|
|
1971
|
+
...parsed,
|
|
1972
|
+
port: parsed.port,
|
|
1973
|
+
silent: true
|
|
1974
|
+
});
|
|
1975
|
+
server$1 = result.server;
|
|
1976
|
+
serverUrl = result.serverUrl;
|
|
1977
|
+
} catch (error) {
|
|
1978
|
+
consola.error("Failed to start server:", error instanceof Error ? error.message : error);
|
|
1979
|
+
process$1.exit(1);
|
|
1980
|
+
}
|
|
1981
|
+
const codexModel = args.model ?? DEFAULT_CODEX_MODEL;
|
|
1982
|
+
consola.success(`Server ready on ${serverUrl}, launching Codex CLI (${codexModel})...`);
|
|
1983
|
+
consola.level = 1;
|
|
1984
|
+
launchChild({
|
|
1985
|
+
kind: "codex",
|
|
1986
|
+
envVars: getCodexEnvVars(serverUrl),
|
|
1987
|
+
extraArgs: args._ ?? [],
|
|
1988
|
+
model: args.model
|
|
1989
|
+
}, server$1);
|
|
1990
|
+
}
|
|
1991
|
+
});
|
|
1992
|
+
|
|
1993
|
+
//#endregion
|
|
1994
|
+
//#region src/debug.ts
|
|
1995
|
+
async function getPackageVersion() {
|
|
1996
|
+
try {
|
|
1997
|
+
const packageJsonPath = new URL("../package.json", import.meta.url).pathname;
|
|
1998
|
+
return JSON.parse(await fs.readFile(packageJsonPath)).version;
|
|
1999
|
+
} catch {
|
|
2000
|
+
return "unknown";
|
|
2001
|
+
}
|
|
2002
|
+
}
|
|
2003
|
+
function getRuntimeInfo() {
|
|
2004
|
+
const isBun = typeof Bun !== "undefined";
|
|
2005
|
+
return {
|
|
2006
|
+
name: isBun ? "bun" : "node",
|
|
2007
|
+
version: isBun ? Bun.version : process.version.slice(1),
|
|
2008
|
+
platform: os.platform(),
|
|
2009
|
+
arch: os.arch()
|
|
2010
|
+
};
|
|
2011
|
+
}
|
|
2012
|
+
async function checkTokenExists() {
|
|
2013
|
+
try {
|
|
2014
|
+
if (!(await fs.stat(PATHS.GITHUB_TOKEN_PATH)).isFile()) return false;
|
|
2015
|
+
return (await fs.readFile(PATHS.GITHUB_TOKEN_PATH, "utf8")).trim().length > 0;
|
|
2016
|
+
} catch {
|
|
2017
|
+
return false;
|
|
2018
|
+
}
|
|
2019
|
+
}
|
|
2020
|
+
async function getDebugInfo() {
|
|
2021
|
+
const [version, tokenExists] = await Promise.all([getPackageVersion(), checkTokenExists()]);
|
|
2022
|
+
return {
|
|
2023
|
+
version,
|
|
2024
|
+
runtime: getRuntimeInfo(),
|
|
2025
|
+
paths: {
|
|
2026
|
+
APP_DIR: PATHS.APP_DIR,
|
|
2027
|
+
GITHUB_TOKEN_PATH: PATHS.GITHUB_TOKEN_PATH
|
|
1940
2028
|
},
|
|
1941
|
-
|
|
1942
|
-
|
|
2029
|
+
tokenExists
|
|
2030
|
+
};
|
|
2031
|
+
}
|
|
2032
|
+
function printDebugInfoPlain(info) {
|
|
2033
|
+
consola.info(`github-router debug
|
|
2034
|
+
|
|
2035
|
+
Version: ${info.version}
|
|
2036
|
+
Runtime: ${info.runtime.name} ${info.runtime.version} (${info.runtime.platform} ${info.runtime.arch})
|
|
2037
|
+
|
|
2038
|
+
Paths:
|
|
2039
|
+
- APP_DIR: ${info.paths.APP_DIR}
|
|
2040
|
+
- GITHUB_TOKEN_PATH: ${info.paths.GITHUB_TOKEN_PATH}
|
|
2041
|
+
|
|
2042
|
+
Token exists: ${info.tokenExists ? "Yes" : "No"}`);
|
|
2043
|
+
}
|
|
2044
|
+
function printDebugInfoJson(info) {
|
|
2045
|
+
console.log(JSON.stringify(info, null, 2));
|
|
2046
|
+
}
|
|
2047
|
+
async function runDebug(options) {
|
|
2048
|
+
const debugInfo = await getDebugInfo();
|
|
2049
|
+
if (options.json) printDebugInfoJson(debugInfo);
|
|
2050
|
+
else printDebugInfoPlain(debugInfo);
|
|
2051
|
+
}
|
|
2052
|
+
const debug = defineCommand({
|
|
2053
|
+
meta: {
|
|
2054
|
+
name: "debug",
|
|
2055
|
+
description: "Print debug information about the application"
|
|
2056
|
+
},
|
|
2057
|
+
args: { json: {
|
|
2058
|
+
type: "boolean",
|
|
2059
|
+
default: false,
|
|
2060
|
+
description: "Output debug information as JSON"
|
|
2061
|
+
} },
|
|
2062
|
+
run({ args }) {
|
|
2063
|
+
return runDebug({ json: args.json });
|
|
2064
|
+
}
|
|
2065
|
+
});
|
|
2066
|
+
|
|
2067
|
+
//#endregion
|
|
2068
|
+
//#region src/lib/shell.ts
|
|
2069
|
+
function getShell() {
|
|
2070
|
+
const { platform, env } = process$1;
|
|
2071
|
+
if (platform === "win32") {
|
|
2072
|
+
if (env.SHELL) {
|
|
2073
|
+
if (env.SHELL.endsWith("zsh")) return "zsh";
|
|
2074
|
+
if (env.SHELL.endsWith("fish")) return "fish";
|
|
2075
|
+
if (env.SHELL.endsWith("bash")) return "bash";
|
|
2076
|
+
return "sh";
|
|
2077
|
+
}
|
|
2078
|
+
if (env.POWERSHELL_DISTRIBUTION_CHANNEL) return "powershell";
|
|
2079
|
+
if (env.PSModulePath) {
|
|
2080
|
+
const lower = env.PSModulePath.toLowerCase();
|
|
2081
|
+
if (lower.includes("documents\\powershell") || lower.includes("documents\\windowspowershell")) return "powershell";
|
|
2082
|
+
}
|
|
2083
|
+
return "cmd";
|
|
2084
|
+
}
|
|
2085
|
+
const shellPath = env.SHELL;
|
|
2086
|
+
if (shellPath) {
|
|
2087
|
+
if (shellPath.endsWith("zsh")) return "zsh";
|
|
2088
|
+
if (shellPath.endsWith("fish")) return "fish";
|
|
2089
|
+
if (shellPath.endsWith("bash")) return "bash";
|
|
2090
|
+
}
|
|
2091
|
+
return "sh";
|
|
2092
|
+
}
|
|
2093
|
+
function quotePosixValue(value) {
|
|
2094
|
+
return `'${value.replace(/'/g, "'\\''")}'`;
|
|
2095
|
+
}
|
|
2096
|
+
function quotePowerShellValue(value) {
|
|
2097
|
+
return `'${value.replace(/'/g, "''")}'`;
|
|
2098
|
+
}
|
|
2099
|
+
/**
|
|
2100
|
+
* Generates a copy-pasteable script to set multiple environment variables
|
|
2101
|
+
* and run a subsequent command.
|
|
2102
|
+
* @param {EnvVars} envVars - An object of environment variables to set.
|
|
2103
|
+
* @param {string} commandToRun - The command to run after setting the variables.
|
|
2104
|
+
* @returns {string} The formatted script string.
|
|
2105
|
+
*/
|
|
2106
|
+
function generateEnvScript(envVars, commandToRun = "") {
|
|
2107
|
+
const shell = getShell();
|
|
2108
|
+
const filteredEnvVars = Object.entries(envVars).filter(([, value]) => value !== void 0);
|
|
2109
|
+
let commandBlock;
|
|
2110
|
+
switch (shell) {
|
|
2111
|
+
case "powershell":
|
|
2112
|
+
commandBlock = filteredEnvVars.map(([key, value]) => `$env:${key} = ${quotePowerShellValue(value)}`).join("; ");
|
|
2113
|
+
break;
|
|
2114
|
+
case "cmd":
|
|
2115
|
+
commandBlock = filteredEnvVars.map(([key, value]) => `set "${key}=${value}"`).join(" & ");
|
|
2116
|
+
break;
|
|
2117
|
+
case "fish":
|
|
2118
|
+
commandBlock = filteredEnvVars.map(([key, value]) => `set -gx ${key} ${quotePosixValue(value)}`).join("; ");
|
|
2119
|
+
break;
|
|
2120
|
+
default: {
|
|
2121
|
+
const assignments = filteredEnvVars.map(([key, value]) => `${key}=${quotePosixValue(value)}`).join(" ");
|
|
2122
|
+
commandBlock = filteredEnvVars.length > 0 ? `export ${assignments}` : "";
|
|
2123
|
+
break;
|
|
2124
|
+
}
|
|
2125
|
+
}
|
|
2126
|
+
if (commandBlock && commandToRun) return `${commandBlock}${shell === "cmd" ? " & " : shell === "powershell" ? "; " : " && "}${commandToRun}`;
|
|
2127
|
+
return commandBlock || commandToRun;
|
|
2128
|
+
}
|
|
2129
|
+
|
|
2130
|
+
//#endregion
|
|
2131
|
+
//#region src/start.ts
|
|
2132
|
+
function printAndCopyCommand(command, label) {
|
|
2133
|
+
consola.box(`${label}\n\n${command}`);
|
|
2134
|
+
try {
|
|
2135
|
+
clipboard.writeSync(command);
|
|
2136
|
+
consola.success(`Copied ${label} command to clipboard!`);
|
|
2137
|
+
} catch {
|
|
2138
|
+
consola.warn("Failed to copy to clipboard. Copy the command above manually.");
|
|
2139
|
+
}
|
|
2140
|
+
}
|
|
2141
|
+
function generateClaudeCodeCommand(serverUrl, model) {
|
|
2142
|
+
printAndCopyCommand(generateEnvScript(getClaudeCodeEnvVars(serverUrl, model), "claude --dangerously-skip-permissions"), "Claude Code");
|
|
2143
|
+
}
|
|
2144
|
+
function generateCodexCommand(serverUrl, model) {
|
|
2145
|
+
const codexModel = model ?? DEFAULT_CODEX_MODEL;
|
|
2146
|
+
printAndCopyCommand(generateEnvScript(getCodexEnvVars(serverUrl), `codex -m ${codexModel}`), "Codex CLI");
|
|
2147
|
+
}
|
|
2148
|
+
const start = defineCommand({
|
|
2149
|
+
meta: {
|
|
2150
|
+
name: "start",
|
|
2151
|
+
description: "Start the github-router server"
|
|
2152
|
+
},
|
|
2153
|
+
args: {
|
|
2154
|
+
...sharedServerArgs,
|
|
2155
|
+
cc: {
|
|
1943
2156
|
type: "boolean",
|
|
1944
2157
|
default: false,
|
|
1945
2158
|
description: "Generate a command to launch Claude Code with Copilot API config"
|
|
1946
2159
|
},
|
|
1947
|
-
|
|
2160
|
+
cx: {
|
|
1948
2161
|
type: "boolean",
|
|
1949
2162
|
default: false,
|
|
1950
2163
|
description: "Generate a command to launch Codex CLI with Copilot API config"
|
|
1951
2164
|
},
|
|
1952
|
-
|
|
1953
|
-
|
|
1954
|
-
|
|
1955
|
-
description: "
|
|
1956
|
-
},
|
|
1957
|
-
"proxy-env": {
|
|
1958
|
-
type: "boolean",
|
|
1959
|
-
default: false,
|
|
1960
|
-
description: "Initialize proxy from environment variables"
|
|
2165
|
+
model: {
|
|
2166
|
+
alias: "m",
|
|
2167
|
+
type: "string",
|
|
2168
|
+
description: "Override the default model (used with --cc or --cx)"
|
|
1961
2169
|
}
|
|
1962
2170
|
},
|
|
1963
|
-
run({ args }) {
|
|
1964
|
-
const
|
|
1965
|
-
|
|
1966
|
-
|
|
1967
|
-
|
|
1968
|
-
|
|
1969
|
-
}
|
|
1970
|
-
const port = Number.parseInt(args.port, 10);
|
|
1971
|
-
if (Number.isNaN(port) || port <= 0 || port > 65535) throw new Error("Invalid port. Must be between 1 and 65535.");
|
|
1972
|
-
const accountType = args["account-type"];
|
|
1973
|
-
if (!allowedAccountTypes.has(accountType)) throw new Error("Invalid account type. Must be individual, business, or enterprise.");
|
|
1974
|
-
const rateLimitWait = args.wait && rateLimit !== void 0;
|
|
1975
|
-
if (args.wait && rateLimit === void 0) consola.warn("Rate limit wait ignored because no rate limit was set.");
|
|
1976
|
-
const githubToken = args["github-token"] ?? process.env.GH_TOKEN;
|
|
1977
|
-
return runServer({
|
|
1978
|
-
port,
|
|
1979
|
-
verbose: args.verbose,
|
|
1980
|
-
accountType,
|
|
1981
|
-
manual: args.manual,
|
|
1982
|
-
rateLimit,
|
|
1983
|
-
rateLimitWait,
|
|
1984
|
-
githubToken,
|
|
1985
|
-
claudeCode: args["claude-code"],
|
|
1986
|
-
codex: args.codex,
|
|
1987
|
-
showToken: args["show-token"],
|
|
1988
|
-
proxyEnv: args["proxy-env"]
|
|
2171
|
+
async run({ args }) {
|
|
2172
|
+
const parsed = parseSharedArgs(args);
|
|
2173
|
+
const { serverUrl } = await setupAndServe({
|
|
2174
|
+
...parsed,
|
|
2175
|
+
port: parsed.port ?? DEFAULT_PORT,
|
|
2176
|
+
silent: false
|
|
1989
2177
|
});
|
|
2178
|
+
if (args.cc) generateClaudeCodeCommand(serverUrl, args.model);
|
|
2179
|
+
if (args.cx) generateCodexCommand(serverUrl, args.model);
|
|
2180
|
+
consola.box(`🌐 Usage Viewer: https://animeshkundu.github.io/github-router/dashboard.html?endpoint=${serverUrl}/usage`);
|
|
1990
2181
|
}
|
|
1991
2182
|
});
|
|
1992
2183
|
|
|
1993
2184
|
//#endregion
|
|
1994
2185
|
//#region src/main.ts
|
|
2186
|
+
process.on("unhandledRejection", (error) => {
|
|
2187
|
+
consola.error("Unhandled rejection:", error);
|
|
2188
|
+
});
|
|
2189
|
+
process.on("uncaughtException", (error) => {
|
|
2190
|
+
consola.error("Uncaught exception:", error);
|
|
2191
|
+
process.exit(1);
|
|
2192
|
+
});
|
|
1995
2193
|
await runMain(defineCommand({
|
|
1996
2194
|
meta: {
|
|
1997
2195
|
name: "github-router",
|
|
@@ -2000,6 +2198,8 @@ await runMain(defineCommand({
|
|
|
2000
2198
|
subCommands: {
|
|
2001
2199
|
auth,
|
|
2002
2200
|
start,
|
|
2201
|
+
claude,
|
|
2202
|
+
codex,
|
|
2003
2203
|
"check-usage": checkUsage,
|
|
2004
2204
|
debug
|
|
2005
2205
|
}
|