diff --git a/src/auth.ts b/src/auth.ts index cb31ff6f8..3ff45ed51 100644 --- a/src/auth.ts +++ b/src/auth.ts @@ -10,6 +10,7 @@ import { setupGitHubToken } from "./lib/token" interface RunAuthOptions { verbose: boolean showToken: boolean + githubUrl?: string } export async function runAuth(options: RunAuthOptions): Promise { @@ -20,6 +21,12 @@ export async function runAuth(options: RunAuthOptions): Promise { state.showToken = options.showToken + const githubUrl = options.githubUrl ?? process.env.GITHUB_URL + if (githubUrl) { + state.githubUrl = githubUrl + consola.info(`Using GitHub Enterprise URL: ${githubUrl}`) + } + await ensurePaths() await setupGitHubToken({ force: true }) consola.success("GitHub token written to", PATHS.GITHUB_TOKEN_PATH) @@ -42,11 +49,17 @@ export const auth = defineCommand({ default: false, description: "Show GitHub token on auth", }, + "github-url": { + type: "string", + description: + "GitHub Enterprise URL (e.g., https://github.example.com). Can also be set via GITHUB_URL env var", + }, }, run({ args }) { return runAuth({ verbose: args.verbose, showToken: args["show-token"], + githubUrl: args["github-url"], }) }, }) diff --git a/src/lib/api-config.ts b/src/lib/api-config.ts index 83bce92ad..d1152a685 100644 --- a/src/lib/api-config.ts +++ b/src/lib/api-config.ts @@ -36,7 +36,11 @@ export const copilotHeaders = (state: State, vision: boolean = false) => { return headers } -export const GITHUB_API_BASE_URL = "https://api.github.com" +export const githubApiBaseUrl = (state: State) => + state.githubUrl ? + `${state.githubUrl.replace(/\/$/, "")}/api/v3` + : "https://api.github.com" + export const githubHeaders = (state: State) => ({ ...standardHeaders(), authorization: `token ${state.githubToken}`, @@ -47,6 +51,8 @@ export const githubHeaders = (state: State) => ({ "x-vscode-user-agent-library-version": "electron-fetch", }) -export const GITHUB_BASE_URL = "https://github.com" +export const githubBaseUrl = (state: State) => + state.githubUrl ? state.githubUrl.replace(/\/$/, "") : "https://github.com" + export const GITHUB_CLIENT_ID = "Iv1.b507a08c87ecfe98" export const GITHUB_APP_SCOPES = ["read:user"].join(" ") diff --git a/src/lib/state.ts b/src/lib/state.ts index 5ba4dc1d1..5c581dc6f 100644 --- a/src/lib/state.ts +++ b/src/lib/state.ts @@ -12,6 +12,9 @@ export interface State { rateLimitWait: boolean showToken: boolean + // GitHub Enterprise URL (e.g., https://github.example.com) + githubUrl?: string + // Rate limiting configuration rateLimitSeconds?: number lastRequestTimestamp?: number diff --git a/src/services/github/get-copilot-token.ts b/src/services/github/get-copilot-token.ts index 98744bab1..28ec6baac 100644 --- a/src/services/github/get-copilot-token.ts +++ b/src/services/github/get-copilot-token.ts @@ -1,10 +1,10 @@ -import { GITHUB_API_BASE_URL, githubHeaders } from "~/lib/api-config" +import { githubApiBaseUrl, githubHeaders } from "~/lib/api-config" import { HTTPError } from "~/lib/error" import { state } from "~/lib/state" export const getCopilotToken = async () => { const response = await fetch( - `${GITHUB_API_BASE_URL}/copilot_internal/v2/token`, + `${githubApiBaseUrl(state)}/copilot_internal/v2/token`, { headers: githubHeaders(state), }, diff --git a/src/services/github/get-copilot-usage.ts b/src/services/github/get-copilot-usage.ts index 6cdd8bc10..718bd0432 100644 --- a/src/services/github/get-copilot-usage.ts +++ b/src/services/github/get-copilot-usage.ts @@ -1,11 +1,14 @@ -import { GITHUB_API_BASE_URL, githubHeaders } from "~/lib/api-config" +import { githubApiBaseUrl, githubHeaders } from "~/lib/api-config" import { HTTPError } from "~/lib/error" import { state } from "~/lib/state" export const getCopilotUsage = async (): Promise => { - const response = await fetch(`${GITHUB_API_BASE_URL}/copilot_internal/user`, { - headers: githubHeaders(state), - }) + const response = await fetch( + `${githubApiBaseUrl(state)}/copilot_internal/user`, + { + headers: githubHeaders(state), + }, + ) if (!response.ok) { throw new HTTPError("Failed to get Copilot usage", response) diff --git a/src/services/github/get-device-code.ts b/src/services/github/get-device-code.ts index cf35f4ec9..9b73b8089 100644 --- a/src/services/github/get-device-code.ts +++ b/src/services/github/get-device-code.ts @@ -1,13 +1,14 @@ import { GITHUB_APP_SCOPES, - GITHUB_BASE_URL, GITHUB_CLIENT_ID, + githubBaseUrl, standardHeaders, } from "~/lib/api-config" import { HTTPError } from "~/lib/error" +import { state } from "~/lib/state" export async function getDeviceCode(): Promise { - const response = await fetch(`${GITHUB_BASE_URL}/login/device/code`, { + const response = await fetch(`${githubBaseUrl(state)}/login/device/code`, { method: "POST", headers: standardHeaders(), body: JSON.stringify({ diff --git a/src/services/github/get-user.ts b/src/services/github/get-user.ts index 23e1b1c1c..680475e4a 100644 --- a/src/services/github/get-user.ts +++ b/src/services/github/get-user.ts @@ -1,9 +1,9 @@ -import { GITHUB_API_BASE_URL, standardHeaders } from "~/lib/api-config" +import { githubApiBaseUrl, standardHeaders } from "~/lib/api-config" import { HTTPError } from "~/lib/error" import { state } from "~/lib/state" export async function getGitHubUser() { - const response = await fetch(`${GITHUB_API_BASE_URL}/user`, { + const response = await fetch(`${githubApiBaseUrl(state)}/user`, { headers: { authorization: `token ${state.githubToken}`, ...standardHeaders(), diff --git a/src/services/github/poll-access-token.ts b/src/services/github/poll-access-token.ts index 4639ee0dc..a8fe038b9 100644 --- a/src/services/github/poll-access-token.ts +++ b/src/services/github/poll-access-token.ts @@ -1,10 +1,11 @@ import consola from "consola" import { - GITHUB_BASE_URL, GITHUB_CLIENT_ID, + githubBaseUrl, standardHeaders, } from "~/lib/api-config" +import { state } from "~/lib/state" import { sleep } from "~/lib/utils" import type { DeviceCodeResponse } from "./get-device-code" @@ -19,7 +20,7 @@ export async function pollAccessToken( while (true) { const response = await fetch( - `${GITHUB_BASE_URL}/login/oauth/access_token`, + `${githubBaseUrl(state)}/login/oauth/access_token`, { method: "POST", headers: standardHeaders(), diff --git a/src/start.ts b/src/start.ts index 14abbbdff..55804b57f 100644 --- a/src/start.ts +++ b/src/start.ts @@ -22,6 +22,7 @@ interface RunServerOptions { rateLimit?: number rateLimitWait: boolean githubToken?: string + githubUrl?: string claudeCode: boolean showToken: boolean proxyEnv: boolean @@ -42,6 +43,12 @@ export async function runServer(options: RunServerOptions): Promise { consola.info(`Using ${options.accountType} plan GitHub account`) } + const githubUrl = options.githubUrl ?? process.env.GITHUB_URL + if (githubUrl) { + state.githubUrl = githubUrl + consola.info(`Using GitHub Enterprise URL: ${githubUrl}`) + } + state.manualApprove = options.manual state.rateLimitSeconds = options.rateLimit state.rateLimitWait = options.rateLimitWait @@ -184,6 +191,11 @@ export const start = defineCommand({ default: false, description: "Initialize proxy from environment variables", }, + "github-url": { + type: "string", + description: + "GitHub Enterprise URL (e.g., https://github.example.com). Can also be set via GITHUB_URL env var", + }, }, run({ args }) { const rateLimitRaw = args["rate-limit"] @@ -199,6 +211,7 @@ export const start = defineCommand({ rateLimit, rateLimitWait: args.wait, githubToken: args["github-token"], + githubUrl: args["github-url"], claudeCode: args["claude-code"], showToken: args["show-token"], proxyEnv: args["proxy-env"],