copilot-api-plus 1.0.57 → 1.0.58
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.en.md +3 -311
- package/README.md +3 -249
- package/dist/error-BfWAfRit.js +2 -0
- package/dist/{error-CvvAyU1E.js → error-u36czEGD.js} +1 -1
- package/dist/{error-CvvAyU1E.js.map → error-u36czEGD.js.map} +1 -1
- package/dist/{get-user-DOv07Myc.js → get-user-BxebJZB6.js} +11 -4
- package/dist/get-user-BxebJZB6.js.map +1 -0
- package/dist/get-user-D2HfoqjP.js +3 -0
- package/dist/main.js +619 -2884
- package/dist/main.js.map +1 -1
- package/dist/token-DT9ko4dY.js +4 -0
- package/dist/{token-D8U-wBLK.js → token-DmGYG1Z1.js} +26 -6
- package/dist/token-DmGYG1Z1.js.map +1 -0
- package/package.json +2 -4
- package/dist/auth-BfBWPtWx.js +0 -3
- package/dist/auth-C7a3n_4O.js +0 -536
- package/dist/auth-C7a3n_4O.js.map +0 -1
- package/dist/auth-DIDShcrD.js +0 -82
- package/dist/auth-DIDShcrD.js.map +0 -1
- package/dist/auth-g7psLP1B.js +0 -3
- package/dist/error-Djpro28X.js +0 -2
- package/dist/get-models-DIOdRXYx.js +0 -4
- package/dist/get-models-DmIjteNk.js +0 -205
- package/dist/get-models-DmIjteNk.js.map +0 -1
- package/dist/get-models-onnSXkNI.js +0 -37
- package/dist/get-models-onnSXkNI.js.map +0 -1
- package/dist/get-user-CGhBmkXO.js +0 -3
- package/dist/get-user-DOv07Myc.js.map +0 -1
- package/dist/paths-BdbyVdad.js +0 -26
- package/dist/paths-BdbyVdad.js.map +0 -1
- package/dist/state-CRpaW-qc.js +0 -13
- package/dist/state-CRpaW-qc.js.map +0 -1
- package/dist/token-CoKq3Guw.js +0 -5
- package/dist/token-D8U-wBLK.js.map +0 -1
|
@@ -1,9 +1,29 @@
|
|
|
1
|
-
import { t as
|
|
2
|
-
import { t as
|
|
3
|
-
import { a as GITHUB_CLIENT_ID, c as githubHeaders, i as GITHUB_BASE_URL, l as standardHeaders, n as GITHUB_API_BASE_URL, o as copilotBaseUrl, r as GITHUB_APP_SCOPES, s as copilotHeaders, t as getGitHubUser } from "./get-user-DOv07Myc.js";
|
|
4
|
-
import { t as HTTPError } from "./error-CvvAyU1E.js";
|
|
1
|
+
import { a as GITHUB_CLIENT_ID, c as githubHeaders, i as GITHUB_BASE_URL, l as standardHeaders, n as GITHUB_API_BASE_URL, o as copilotBaseUrl, r as GITHUB_APP_SCOPES, s as copilotHeaders, t as getGitHubUser, u as state } from "./get-user-BxebJZB6.js";
|
|
2
|
+
import { t as HTTPError } from "./error-u36czEGD.js";
|
|
5
3
|
import consola from "consola";
|
|
6
4
|
import fs from "node:fs/promises";
|
|
5
|
+
import os from "node:os";
|
|
6
|
+
import path from "node:path";
|
|
7
|
+
//#region src/lib/paths.ts
|
|
8
|
+
const APP_DIR = path.join(os.homedir(), ".local", "share", "copilot-api-plus");
|
|
9
|
+
const PATHS = {
|
|
10
|
+
APP_DIR,
|
|
11
|
+
DATA_DIR: APP_DIR,
|
|
12
|
+
GITHUB_TOKEN_PATH: path.join(APP_DIR, "github_token")
|
|
13
|
+
};
|
|
14
|
+
async function ensurePaths() {
|
|
15
|
+
await fs.mkdir(PATHS.APP_DIR, { recursive: true });
|
|
16
|
+
await ensureFile(PATHS.GITHUB_TOKEN_PATH);
|
|
17
|
+
}
|
|
18
|
+
async function ensureFile(filePath) {
|
|
19
|
+
try {
|
|
20
|
+
await fs.access(filePath, fs.constants.W_OK);
|
|
21
|
+
} catch {
|
|
22
|
+
await fs.writeFile(filePath, "");
|
|
23
|
+
await fs.chmod(filePath, 384);
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
//#endregion
|
|
7
27
|
//#region src/services/github/get-copilot-token.ts
|
|
8
28
|
const getCopilotToken = async () => {
|
|
9
29
|
const url = `${GITHUB_API_BASE_URL}/copilot_internal/v2/token`;
|
|
@@ -250,6 +270,6 @@ async function logUser() {
|
|
|
250
270
|
consola.info(`Logged in as ${user.login}`);
|
|
251
271
|
}
|
|
252
272
|
//#endregion
|
|
253
|
-
export { cacheModels as a, isNullish as c, setupGitHubToken as i, sleep as l, refreshCopilotToken as n, cacheVSCodeVersion as o, setupCopilotToken as r, findModel as s, clearGithubToken as t };
|
|
273
|
+
export { cacheModels as a, isNullish as c, ensurePaths as d, setupGitHubToken as i, sleep as l, refreshCopilotToken as n, cacheVSCodeVersion as o, setupCopilotToken as r, findModel as s, clearGithubToken as t, PATHS as u };
|
|
254
274
|
|
|
255
|
-
//# sourceMappingURL=token-
|
|
275
|
+
//# sourceMappingURL=token-DmGYG1Z1.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"token-DmGYG1Z1.js","names":[],"sources":["../src/lib/paths.ts","../src/services/github/get-copilot-token.ts","../src/services/github/get-device-code.ts","../src/services/copilot/get-models.ts","../src/services/get-vscode-version.ts","../src/lib/utils.ts","../src/services/github/poll-access-token.ts","../src/lib/token.ts"],"sourcesContent":["import fs from \"node:fs/promises\"\nimport os from \"node:os\"\nimport path from \"node:path\"\n\nconst APP_DIR = path.join(os.homedir(), \".local\", \"share\", \"copilot-api-plus\")\n\nconst GITHUB_TOKEN_PATH = path.join(APP_DIR, \"github_token\")\n\nexport const PATHS = {\n APP_DIR,\n DATA_DIR: APP_DIR,\n GITHUB_TOKEN_PATH,\n}\n\nexport async function ensurePaths(): Promise<void> {\n await fs.mkdir(PATHS.APP_DIR, { recursive: true })\n await ensureFile(PATHS.GITHUB_TOKEN_PATH)\n}\n\nasync function ensureFile(filePath: string): Promise<void> {\n try {\n await fs.access(filePath, fs.constants.W_OK)\n } catch {\n await fs.writeFile(filePath, \"\")\n await fs.chmod(filePath, 0o600)\n }\n}\n","import consola from \"consola\"\n\nimport { GITHUB_API_BASE_URL, githubHeaders } from \"~/lib/api-config\"\nimport { HTTPError } from \"~/lib/error\"\nimport { state } from \"~/lib/state\"\n\nexport const getCopilotToken = async () => {\n const url = `${GITHUB_API_BASE_URL}/copilot_internal/v2/token`\n const fetchOptions: RequestInit = {\n headers: githubHeaders(state),\n }\n\n // Retry on transient network errors (TLS disconnect, connection timeout, etc.)\n const maxRetries = 2\n let lastError: unknown\n let response: Response | undefined\n\n for (let attempt = 0; attempt <= maxRetries; attempt++) {\n try {\n response = await fetch(url, fetchOptions)\n break\n } catch (error: unknown) {\n lastError = error\n if (attempt < maxRetries) {\n const delay = 1000 * (attempt + 1)\n consola.warn(\n `Token fetch error on attempt ${attempt + 1}/${maxRetries + 1}, retrying in ${delay}ms:`,\n error instanceof Error ? error.message : error,\n )\n await new Promise((r) => setTimeout(r, delay))\n }\n }\n }\n\n if (!response) {\n throw lastError\n }\n\n if (!response.ok) throw new HTTPError(\"Failed to get Copilot token\", response)\n\n const data = (await response.json()) as GetCopilotTokenResponse\n\n // Store the API endpoint if available\n if (data.endpoints?.api) {\n // eslint-disable-next-line require-atomic-updates\n state.copilotApiEndpoint = data.endpoints.api\n }\n\n return data\n}\n\n// Full interface matching Zed's implementation\ninterface GetCopilotTokenResponse {\n expires_at: number\n refresh_in: number\n token: string\n endpoints?: {\n api: string\n \"origin-tracker\"?: string\n proxy?: string\n telemetry?: string\n }\n annotations_enabled?: boolean\n chat_enabled?: boolean\n chat_jetbrains_enabled?: boolean\n code_quote_enabled?: boolean\n codesearch?: boolean\n copilot_ide_agent_chat_gpt4_small_prompt?: boolean\n copilotignore_enabled?: boolean\n individual?: boolean\n sku?: string\n tracking_id?: string\n limited_user_quotas?: unknown // Premium request quotas\n}\n","import {\n GITHUB_APP_SCOPES,\n GITHUB_BASE_URL,\n GITHUB_CLIENT_ID,\n standardHeaders,\n} from \"~/lib/api-config\"\nimport { HTTPError } from \"~/lib/error\"\n\nexport async function getDeviceCode(): Promise<DeviceCodeResponse> {\n const response = await fetch(`${GITHUB_BASE_URL}/login/device/code`, {\n method: \"POST\",\n headers: standardHeaders(),\n body: JSON.stringify({\n client_id: GITHUB_CLIENT_ID,\n scope: GITHUB_APP_SCOPES,\n }),\n })\n\n if (!response.ok) throw new HTTPError(\"Failed to get device code\", response)\n\n return (await response.json()) as DeviceCodeResponse\n}\n\nexport interface DeviceCodeResponse {\n device_code: string\n user_code: string\n verification_uri: string\n expires_in: number\n interval: number\n}\n","import { copilotBaseUrl, copilotHeaders } from \"~/lib/api-config\"\nimport { HTTPError } from \"~/lib/error\"\nimport { state } from \"~/lib/state\"\n\nexport const getModels = async () => {\n const url = `${copilotBaseUrl(state)}/models`\n\n const response = await fetch(url, {\n headers: copilotHeaders(state),\n })\n\n if (!response.ok) throw new HTTPError(\"Failed to get models\", response)\n\n const data = (await response.json()) as ModelsResponse\n\n return data\n}\n\nexport interface ModelsResponse {\n data: Array<Model>\n object: string\n}\n\ninterface ModelLimits {\n max_context_window_tokens?: number\n max_output_tokens?: number\n max_prompt_tokens?: number\n max_inputs?: number\n}\n\ninterface ModelSupports {\n tool_calls?: boolean\n parallel_tool_calls?: boolean\n dimensions?: boolean\n}\n\ninterface ModelCapabilities {\n family: string\n limits: ModelLimits\n object: string\n supports: ModelSupports\n tokenizer: string\n type: string\n}\n\nexport interface Model {\n capabilities: ModelCapabilities\n id: string\n model_picker_enabled: boolean\n name: string\n object: string\n preview: boolean\n vendor: string\n version: string\n policy?: {\n state: string\n terms: string\n }\n billing?: {\n is_premium: boolean\n multiplier: number\n restricted_to?: Array<string>\n }\n}\n","const FALLBACK = \"1.104.3\"\n\nexport async function getVSCodeVersion() {\n const controller = new AbortController()\n const timeout = setTimeout(() => {\n controller.abort()\n }, 5000)\n\n try {\n const response = await fetch(\n \"https://aur.archlinux.org/cgit/aur.git/plain/PKGBUILD?h=visual-studio-code-bin\",\n {\n signal: controller.signal,\n },\n )\n\n const pkgbuild = await response.text()\n const pkgverRegex = /pkgver=([0-9.]+)/\n const match = pkgbuild.match(pkgverRegex)\n\n if (match) {\n return match[1]\n }\n\n return FALLBACK\n } catch {\n return FALLBACK\n } finally {\n clearTimeout(timeout)\n }\n}\n\nawait getVSCodeVersion()\n","import consola from \"consola\"\n\nimport type { Model } from \"~/services/copilot/get-models\"\n\nimport { getModels } from \"~/services/copilot/get-models\"\nimport { getVSCodeVersion } from \"~/services/get-vscode-version\"\n\nimport { state } from \"./state\"\n\nexport const sleep = (ms: number) =>\n new Promise((resolve) => {\n setTimeout(resolve, ms)\n })\n\nexport const isNullish = (value: unknown): value is null | undefined =>\n value === null || value === undefined\n\nexport async function cacheModels(): Promise<void> {\n const models = await getModels()\n state.models = models\n\n const claudeModels = models.data.filter(\n (m) =>\n m.id.includes(\"claude\")\n || m.vendor.toLowerCase().includes(\"anthropic\")\n || m.name.toLowerCase().includes(\"claude\"),\n )\n if (claudeModels.length > 0) {\n consola.info(\n \"Found Claude models:\",\n claudeModels.map((m) => ({ id: m.id, policy: m.policy })),\n )\n } else {\n consola.warn(\"No Claude models found in API response\")\n }\n}\n\nexport const cacheVSCodeVersion = async () => {\n const response = await getVSCodeVersion()\n state.vsCodeVersion = response\n\n consola.info(`Using VSCode version: ${response}`)\n}\n\n/**\n * Find a model in state.models using multi-strategy exact matching.\n *\n * Strategies (in order):\n * 1. Exact match on model ID\n * 2. Strip date suffix (claude-opus-4-6-20251101 → claude-opus-4-6)\n * 3. Dash to dot version (claude-opus-4-5 → claude-opus-4.5)\n * 4. Dot to dash version (claude-opus-4.5 → claude-opus-4-5)\n *\n * No fuzzy/family matching — all strategies produce deterministic exact IDs.\n */\nexport function findModel(modelName: string): Model | undefined {\n const models = state.models?.data\n if (!models || models.length === 0) {\n return undefined\n }\n\n // 1. Exact match\n const exact = models.find((m) => m.id === modelName)\n if (exact) return exact\n\n // 2. Strip date suffix\n const base = modelName.replace(/-\\d{8}$/, \"\")\n if (base !== modelName) {\n const baseMatch = models.find((m) => m.id === base)\n if (baseMatch) return baseMatch\n }\n\n // 3. Dash to dot version (4-5 → 4.5)\n const withDot = base.replace(/-(\\d+)-(\\d+)$/, \"-$1.$2\")\n if (withDot !== base) {\n const dotMatch = models.find((m) => m.id === withDot)\n if (dotMatch) return dotMatch\n }\n\n // 4. Dot to dash version (4.5 → 4-5)\n const withDash = modelName.replace(/(\\d+)\\.(\\d+)/, \"$1-$2\")\n if (withDash !== modelName) {\n const dashMatch = models.find((m) => m.id === withDash)\n if (dashMatch) return dashMatch\n }\n\n return undefined\n}\n","import consola from \"consola\"\n\nimport {\n GITHUB_BASE_URL,\n GITHUB_CLIENT_ID,\n standardHeaders,\n} from \"~/lib/api-config\"\nimport { sleep } from \"~/lib/utils\"\n\nimport type { DeviceCodeResponse } from \"./get-device-code\"\n\nexport async function pollAccessToken(\n deviceCode: DeviceCodeResponse,\n): Promise<string> {\n // Interval is in seconds, we need to multiply by 1000 to get milliseconds\n // I'm also adding another second, just to be safe\n const sleepDuration = (deviceCode.interval + 1) * 1000\n consola.debug(`Polling access token with interval of ${sleepDuration}ms`)\n\n // Calculate expiration time - device code expires after expires_in seconds\n const expirationTime = Date.now() + deviceCode.expires_in * 1000\n\n while (Date.now() < expirationTime) {\n const response = await fetch(\n `${GITHUB_BASE_URL}/login/oauth/access_token`,\n {\n method: \"POST\",\n headers: standardHeaders(),\n body: JSON.stringify({\n client_id: GITHUB_CLIENT_ID,\n device_code: deviceCode.device_code,\n grant_type: \"urn:ietf:params:oauth:grant-type:device_code\",\n }),\n },\n )\n\n if (!response.ok) {\n await sleep(sleepDuration)\n consola.error(\"Failed to poll access token:\", await response.text())\n\n continue\n }\n\n const json = (await response.json()) as\n | AccessTokenResponse\n | AccessTokenErrorResponse\n consola.debug(\"Polling access token response:\", json)\n\n if (\"access_token\" in json && json.access_token) {\n return json.access_token\n }\n\n // Handle specific error cases\n if (\"error\" in json) {\n switch (json.error) {\n case \"authorization_pending\": {\n // User hasn't authorized yet, continue polling\n await sleep(sleepDuration)\n continue\n\n break\n }\n case \"slow_down\": {\n // We're polling too fast, increase interval\n await sleep(sleepDuration * 2)\n continue\n\n break\n }\n case \"expired_token\": {\n throw new Error(\"Device code expired. Please try again.\")\n }\n case \"access_denied\": {\n throw new Error(\"Authorization was denied by the user.\")\n }\n default: {\n throw new Error(\n `Authentication failed: ${json.error_description || json.error}`,\n )\n }\n }\n }\n\n await sleep(sleepDuration)\n }\n\n throw new Error(\"Device code expired. Please try again.\")\n}\n\ninterface AccessTokenResponse {\n access_token: string\n token_type: string\n scope: string\n}\n\ninterface AccessTokenErrorResponse {\n error: string\n error_description?: string\n error_uri?: string\n}\n","import consola from \"consola\"\nimport fs from \"node:fs/promises\"\n\nimport { PATHS } from \"~/lib/paths\"\nimport { getCopilotToken } from \"~/services/github/get-copilot-token\"\nimport { getDeviceCode } from \"~/services/github/get-device-code\"\nimport { getGitHubUser } from \"~/services/github/get-user\"\nimport { pollAccessToken } from \"~/services/github/poll-access-token\"\n\nimport { HTTPError } from \"./error\"\nimport { state } from \"./state\"\n\nconst readGithubToken = () => fs.readFile(PATHS.GITHUB_TOKEN_PATH, \"utf8\")\n\nconst writeGithubToken = (token: string) =>\n fs.writeFile(PATHS.GITHUB_TOKEN_PATH, token)\n\n/**\n * Clear the stored GitHub token from disk and state.\n * This allows the user to logout or re-authenticate.\n */\nexport async function clearGithubToken(): Promise<void> {\n state.githubToken = undefined\n state.copilotToken = undefined\n await fs.writeFile(PATHS.GITHUB_TOKEN_PATH, \"\")\n consola.info(\"GitHub token cleared\")\n}\n\nexport const setupCopilotToken = async () => {\n const { token, refresh_in } = await getCopilotToken()\n state.copilotToken = token\n\n // Display the Copilot token to the screen\n consola.debug(\"GitHub Copilot Token fetched successfully!\")\n if (state.showToken) {\n consola.info(\"Copilot token:\", token)\n }\n\n const refreshInterval = (refresh_in - 60) * 1000\n setInterval(async () => {\n consola.debug(\"Refreshing Copilot token\")\n try {\n await refreshCopilotToken()\n } catch (error) {\n consola.error(\"Failed to refresh Copilot token:\", error)\n\n // If we get a 401, the GitHub token might be invalid\n // Log the error but don't crash - the next API request will fail\n // and the user can restart with valid credentials\n if (error instanceof HTTPError && error.response.status === 401) {\n consola.warn(\n \"GitHub token may have been revoked. Please restart and re-authenticate.\",\n )\n state.copilotToken = undefined\n }\n // Don't throw here - it would cause an unhandled rejection in setInterval\n }\n }, refreshInterval)\n}\n\n/**\n * Refresh the Copilot token on demand (e.g. after a 401 error).\n */\nexport async function refreshCopilotToken(): Promise<void> {\n const { token } = await getCopilotToken()\n state.copilotToken = token\n consola.debug(\"Copilot token refreshed\")\n if (state.showToken) {\n consola.info(\"Refreshed Copilot token:\", token)\n }\n}\n\ninterface SetupGitHubTokenOptions {\n force?: boolean\n}\n\n/**\n * Perform a fresh GitHub authentication flow.\n * Gets a device code and polls for the access token.\n */\nasync function performFreshAuthentication(): Promise<void> {\n consola.info(\"Starting GitHub authentication flow...\")\n const response = await getDeviceCode()\n consola.debug(\"Device code response:\", response)\n\n consola.info(\n `Please enter the code \"${response.user_code}\" in ${response.verification_uri}`,\n )\n\n const token = await pollAccessToken(response)\n await writeGithubToken(token)\n state.githubToken = token\n\n if (state.showToken) {\n consola.info(\"GitHub token:\", token)\n }\n await logUser()\n}\n\nexport async function setupGitHubToken(\n options?: SetupGitHubTokenOptions,\n): Promise<void> {\n try {\n const githubToken = await readGithubToken()\n\n if (githubToken && !options?.force) {\n state.githubToken = githubToken\n if (state.showToken) {\n consola.info(\"GitHub token:\", githubToken)\n }\n\n // Validate the token by checking if we can get the user\n try {\n await logUser()\n return\n } catch (error) {\n // Token is invalid or expired, clear it and re-authenticate\n if (error instanceof HTTPError && error.response.status === 401) {\n consola.warn(\n \"Stored GitHub token is invalid or expired, clearing and re-authenticating...\",\n )\n await clearGithubToken()\n // Fall through to perform fresh authentication\n } else {\n throw error\n }\n }\n }\n\n consola.info(\"Not logged in, getting new access token\")\n await performFreshAuthentication()\n } catch (error) {\n if (error instanceof HTTPError) {\n consola.error(\"Failed to get GitHub token:\", await error.response.json())\n throw error\n }\n\n consola.error(\"Failed to get GitHub token:\", error)\n throw error\n }\n}\n\nasync function logUser() {\n const user = await getGitHubUser()\n consola.info(`Logged in as ${user.login}`)\n}\n"],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -1,14 +1,12 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "copilot-api-plus",
|
|
3
|
-
"version": "1.0.
|
|
4
|
-
"description": "Turn GitHub Copilot
|
|
3
|
+
"version": "1.0.58",
|
|
4
|
+
"description": "Turn GitHub Copilot into OpenAI/Anthropic API compatible server. Features: smart model matching, API key auth, and more.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"proxy",
|
|
7
7
|
"github-copilot",
|
|
8
8
|
"openai-compatible",
|
|
9
9
|
"anthropic-compatible",
|
|
10
|
-
"opencode-zen",
|
|
11
|
-
"google-antigravity",
|
|
12
10
|
"claude-code"
|
|
13
11
|
],
|
|
14
12
|
"homepage": "https://github.com/imbuxiangnan-cyber/copilot-api-plus",
|
package/dist/auth-BfBWPtWx.js
DELETED
|
@@ -1,3 +0,0 @@
|
|
|
1
|
-
import "./paths-BdbyVdad.js";
|
|
2
|
-
import { b as setupAntigravity, c as getCurrentAccount, h as loadAntigravityAuth, n as clearAntigravityAuth, p as hasApiKey, s as getApiKey, v as saveAntigravityAuth, y as setOAuthCredentials } from "./auth-C7a3n_4O.js";
|
|
3
|
-
export { clearAntigravityAuth, getApiKey, getCurrentAccount, hasApiKey, loadAntigravityAuth, saveAntigravityAuth, setOAuthCredentials, setupAntigravity };
|
package/dist/auth-C7a3n_4O.js
DELETED
|
@@ -1,536 +0,0 @@
|
|
|
1
|
-
import { n as ensurePaths, t as PATHS } from "./paths-BdbyVdad.js";
|
|
2
|
-
import consola from "consola";
|
|
3
|
-
//#region src/services/antigravity/account-scorer.ts
|
|
4
|
-
/**
|
|
5
|
-
* Antigravity Account Scorer
|
|
6
|
-
*
|
|
7
|
-
* Weighted account selection for multi-account management.
|
|
8
|
-
* Replaces simple round-robin rotation with score-based selection
|
|
9
|
-
* considering quota health, token freshness, and reliability.
|
|
10
|
-
*/
|
|
11
|
-
const statsMap = /* @__PURE__ */ new Map();
|
|
12
|
-
function getOrCreateStats(index) {
|
|
13
|
-
let stats = statsMap.get(index);
|
|
14
|
-
if (!stats) {
|
|
15
|
-
stats = {
|
|
16
|
-
successes: 0,
|
|
17
|
-
failures: 0,
|
|
18
|
-
lastUsed: 0
|
|
19
|
-
};
|
|
20
|
-
statsMap.set(index, stats);
|
|
21
|
-
}
|
|
22
|
-
return stats;
|
|
23
|
-
}
|
|
24
|
-
/**
|
|
25
|
-
* Record a successful request for the given account index.
|
|
26
|
-
*/
|
|
27
|
-
function recordAccountSuccess(index) {
|
|
28
|
-
const stats = getOrCreateStats(index);
|
|
29
|
-
stats.successes++;
|
|
30
|
-
stats.lastUsed = Date.now();
|
|
31
|
-
}
|
|
32
|
-
/**
|
|
33
|
-
* Record a failed request for the given account index.
|
|
34
|
-
*/
|
|
35
|
-
function recordAccountFailure(index) {
|
|
36
|
-
const stats = getOrCreateStats(index);
|
|
37
|
-
stats.failures++;
|
|
38
|
-
stats.lastUsed = Date.now();
|
|
39
|
-
}
|
|
40
|
-
const WEIGHT_QUOTA_HEALTH = .6;
|
|
41
|
-
const WEIGHT_FRESHNESS = .2;
|
|
42
|
-
const WEIGHT_RELIABILITY = .2;
|
|
43
|
-
/**
|
|
44
|
-
* Score a single account based on quota health, token freshness, and
|
|
45
|
-
* historical reliability.
|
|
46
|
-
*/
|
|
47
|
-
function scoreAccount(account, index) {
|
|
48
|
-
const stats = statsMap.get(index);
|
|
49
|
-
const total = stats ? stats.successes + stats.failures : 0;
|
|
50
|
-
const quotaHealth = total === 0 || !stats ? 1 : 1 - stats.failures / Math.max(total, 1);
|
|
51
|
-
const remaining = account.timestamp + account.expires_in * 1e3 - Date.now();
|
|
52
|
-
const freshness = remaining <= 0 ? 0 : Math.min(remaining / (account.expires_in * 1e3), 1);
|
|
53
|
-
const reliability = stats && total > 0 ? stats.successes / Math.max(total, 1) : .5;
|
|
54
|
-
return {
|
|
55
|
-
accountIndex: index,
|
|
56
|
-
score: quotaHealth * WEIGHT_QUOTA_HEALTH + freshness * WEIGHT_FRESHNESS + reliability * WEIGHT_RELIABILITY,
|
|
57
|
-
breakdown: {
|
|
58
|
-
quotaHealth,
|
|
59
|
-
freshness,
|
|
60
|
-
reliability
|
|
61
|
-
}
|
|
62
|
-
};
|
|
63
|
-
}
|
|
64
|
-
/** Tracks the last index returned for round-robin fallback. */
|
|
65
|
-
let lastSelectedIndex = -1;
|
|
66
|
-
/**
|
|
67
|
-
* Select the best enabled account by score.
|
|
68
|
-
*
|
|
69
|
-
* Falls back to round-robin when all scores are equal or no stats exist.
|
|
70
|
-
* Returns 0 when no enabled accounts are found.
|
|
71
|
-
*/
|
|
72
|
-
function selectBestAccount(accounts) {
|
|
73
|
-
if (accounts.length === 0) return 0;
|
|
74
|
-
const enabled = [];
|
|
75
|
-
for (const [i, account] of accounts.entries()) if (account.enable) enabled.push({
|
|
76
|
-
account,
|
|
77
|
-
index: i
|
|
78
|
-
});
|
|
79
|
-
if (enabled.length === 0) return 0;
|
|
80
|
-
const scored = enabled.map(({ account, index }) => scoreAccount(account, index));
|
|
81
|
-
if (scored.every((s) => Math.abs(s.score - scored[0].score) < 1e-9)) {
|
|
82
|
-
const sortedIndices = enabled.map((e) => e.index).sort((a, b) => a - b);
|
|
83
|
-
const nextIdx = sortedIndices.find((i) => i > lastSelectedIndex);
|
|
84
|
-
const selected = nextIdx !== void 0 ? nextIdx : sortedIndices[0];
|
|
85
|
-
lastSelectedIndex = selected;
|
|
86
|
-
consola.debug(`[account-scorer] round-robin fallback -> account ${selected}`);
|
|
87
|
-
return selected;
|
|
88
|
-
}
|
|
89
|
-
scored.sort((a, b) => b.score - a.score);
|
|
90
|
-
const best = scored[0];
|
|
91
|
-
lastSelectedIndex = best.accountIndex;
|
|
92
|
-
consola.debug(`[account-scorer] selected account ${best.accountIndex} (score=${best.score.toFixed(3)}, quota=${best.breakdown.quotaHealth.toFixed(2)}, fresh=${best.breakdown.freshness.toFixed(2)}, rel=${best.breakdown.reliability.toFixed(2)})`);
|
|
93
|
-
return best.accountIndex;
|
|
94
|
-
}
|
|
95
|
-
//#endregion
|
|
96
|
-
//#region src/services/antigravity/auth.ts
|
|
97
|
-
/**
|
|
98
|
-
* Google Antigravity Authentication
|
|
99
|
-
*
|
|
100
|
-
* Handles OAuth token management for Google Antigravity API.
|
|
101
|
-
* Supports multiple accounts with auto-rotation and token refresh.
|
|
102
|
-
*/
|
|
103
|
-
const ANTIGRAVITY_AUTH_FILENAME = "antigravity-accounts.json";
|
|
104
|
-
const GEMINI_API_KEY = process.env.GEMINI_API_KEY || "";
|
|
105
|
-
/**
|
|
106
|
-
* Check if API Key authentication is available
|
|
107
|
-
*/
|
|
108
|
-
function hasApiKey() {
|
|
109
|
-
return GEMINI_API_KEY.length > 0;
|
|
110
|
-
}
|
|
111
|
-
/**
|
|
112
|
-
* Get API Key for authentication
|
|
113
|
-
*/
|
|
114
|
-
function getApiKey() {
|
|
115
|
-
return GEMINI_API_KEY || null;
|
|
116
|
-
}
|
|
117
|
-
const _P = {
|
|
118
|
-
A1: "1071006060591",
|
|
119
|
-
A2: "tmhssin2h21lcre235vtolojh4g403ep",
|
|
120
|
-
A3: "apps.googleusercontent.com",
|
|
121
|
-
S1: "GOCSPX",
|
|
122
|
-
S2: "K58FWR486LdLJ1mLB8sXC4z6qDAf"
|
|
123
|
-
};
|
|
124
|
-
const DEFAULT_CLIENT_ID = `${_P.A1}-${_P.A2}.${_P.A3}`;
|
|
125
|
-
const DEFAULT_CLIENT_SECRET = `${_P.S1}-${_P.S2}`;
|
|
126
|
-
let GOOGLE_CLIENT_ID = process.env.ANTIGRAVITY_CLIENT_ID || DEFAULT_CLIENT_ID;
|
|
127
|
-
let GOOGLE_CLIENT_SECRET = process.env.ANTIGRAVITY_CLIENT_SECRET || DEFAULT_CLIENT_SECRET;
|
|
128
|
-
const GOOGLE_REDIRECT_URI = "http://localhost:8046/callback";
|
|
129
|
-
const OAUTH_SCOPES = [
|
|
130
|
-
"https://www.googleapis.com/auth/cloud-platform",
|
|
131
|
-
"https://www.googleapis.com/auth/userinfo.email",
|
|
132
|
-
"https://www.googleapis.com/auth/userinfo.profile",
|
|
133
|
-
"https://www.googleapis.com/auth/cclog",
|
|
134
|
-
"https://www.googleapis.com/auth/experimentsandconfigs"
|
|
135
|
-
];
|
|
136
|
-
/**
|
|
137
|
-
* Set OAuth credentials from CLI arguments
|
|
138
|
-
* This overrides environment variables and defaults
|
|
139
|
-
*/
|
|
140
|
-
function setOAuthCredentials(clientId, clientSecret) {
|
|
141
|
-
GOOGLE_CLIENT_ID = clientId;
|
|
142
|
-
GOOGLE_CLIENT_SECRET = clientSecret;
|
|
143
|
-
}
|
|
144
|
-
/**
|
|
145
|
-
* Get the path to the Antigravity auth file
|
|
146
|
-
*/
|
|
147
|
-
function getAntigravityAuthPath() {
|
|
148
|
-
return `${PATHS.DATA_DIR}/${ANTIGRAVITY_AUTH_FILENAME}`;
|
|
149
|
-
}
|
|
150
|
-
/**
|
|
151
|
-
* Save Antigravity accounts to file
|
|
152
|
-
*/
|
|
153
|
-
async function saveAntigravityAuth(auth) {
|
|
154
|
-
await ensurePaths();
|
|
155
|
-
const authPath = getAntigravityAuthPath();
|
|
156
|
-
await (await import("node:fs/promises")).writeFile(authPath, JSON.stringify(auth, null, 2), "utf8");
|
|
157
|
-
consola.success("Antigravity accounts saved to", authPath);
|
|
158
|
-
}
|
|
159
|
-
/**
|
|
160
|
-
* Load Antigravity accounts from file
|
|
161
|
-
*/
|
|
162
|
-
async function loadAntigravityAuth() {
|
|
163
|
-
try {
|
|
164
|
-
const authPath = getAntigravityAuthPath();
|
|
165
|
-
const fs = await import("node:fs/promises");
|
|
166
|
-
try {
|
|
167
|
-
await fs.access(authPath);
|
|
168
|
-
} catch {
|
|
169
|
-
return null;
|
|
170
|
-
}
|
|
171
|
-
const content = await fs.readFile(authPath);
|
|
172
|
-
const data = JSON.parse(content);
|
|
173
|
-
if (Array.isArray(data)) return {
|
|
174
|
-
accounts: data,
|
|
175
|
-
currentIndex: 0
|
|
176
|
-
};
|
|
177
|
-
return data;
|
|
178
|
-
} catch {
|
|
179
|
-
return null;
|
|
180
|
-
}
|
|
181
|
-
}
|
|
182
|
-
/**
|
|
183
|
-
* Clear Antigravity accounts
|
|
184
|
-
*/
|
|
185
|
-
async function clearAntigravityAuth() {
|
|
186
|
-
try {
|
|
187
|
-
const authPath = getAntigravityAuthPath();
|
|
188
|
-
await (await import("node:fs/promises")).unlink(authPath);
|
|
189
|
-
consola.success("Antigravity accounts cleared");
|
|
190
|
-
} catch {}
|
|
191
|
-
}
|
|
192
|
-
/**
|
|
193
|
-
* Add a new account to Antigravity auth
|
|
194
|
-
*/
|
|
195
|
-
async function addAntigravityAccount(account) {
|
|
196
|
-
let auth = await loadAntigravityAuth();
|
|
197
|
-
if (!auth) auth = {
|
|
198
|
-
accounts: [],
|
|
199
|
-
currentIndex: 0
|
|
200
|
-
};
|
|
201
|
-
auth.accounts.push(account);
|
|
202
|
-
await saveAntigravityAuth(auth);
|
|
203
|
-
consola.success("Added new Antigravity account");
|
|
204
|
-
}
|
|
205
|
-
/**
|
|
206
|
-
* Get the current account index
|
|
207
|
-
*/
|
|
208
|
-
async function getCurrentAccountIndex() {
|
|
209
|
-
return (await loadAntigravityAuth())?.currentIndex ?? 0;
|
|
210
|
-
}
|
|
211
|
-
/**
|
|
212
|
-
* Get the current active account
|
|
213
|
-
*/
|
|
214
|
-
async function getCurrentAccount() {
|
|
215
|
-
const auth = await loadAntigravityAuth();
|
|
216
|
-
if (!auth || auth.accounts.length === 0) return null;
|
|
217
|
-
const enabledAccounts = auth.accounts.filter((a) => a.enable);
|
|
218
|
-
if (enabledAccounts.length === 0) return null;
|
|
219
|
-
const currentAccount = auth.accounts[auth.currentIndex];
|
|
220
|
-
if (currentAccount && currentAccount.enable) return currentAccount;
|
|
221
|
-
return enabledAccounts[0];
|
|
222
|
-
}
|
|
223
|
-
/**
|
|
224
|
-
* Rotate to the best available account using weighted scoring
|
|
225
|
-
*/
|
|
226
|
-
async function rotateAccount() {
|
|
227
|
-
const auth = await loadAntigravityAuth();
|
|
228
|
-
if (!auth || auth.accounts.length <= 1) return;
|
|
229
|
-
const bestIndex = selectBestAccount(auth.accounts);
|
|
230
|
-
if (bestIndex !== auth.currentIndex) {
|
|
231
|
-
auth.currentIndex = bestIndex;
|
|
232
|
-
await saveAntigravityAuth(auth);
|
|
233
|
-
consola.info(`Rotated to account ${bestIndex} (weighted)`);
|
|
234
|
-
}
|
|
235
|
-
}
|
|
236
|
-
/**
|
|
237
|
-
* Disable current account
|
|
238
|
-
*/
|
|
239
|
-
async function disableCurrentAccount() {
|
|
240
|
-
const auth = await loadAntigravityAuth();
|
|
241
|
-
if (!auth || auth.accounts.length === 0) return;
|
|
242
|
-
auth.accounts[auth.currentIndex].enable = false;
|
|
243
|
-
await saveAntigravityAuth(auth);
|
|
244
|
-
consola.warn(`Disabled account ${auth.currentIndex}`);
|
|
245
|
-
await rotateAccount();
|
|
246
|
-
}
|
|
247
|
-
/**
|
|
248
|
-
* Refresh access token using refresh token
|
|
249
|
-
*/
|
|
250
|
-
async function refreshAccessToken(account) {
|
|
251
|
-
try {
|
|
252
|
-
const response = await fetch("https://oauth2.googleapis.com/token", {
|
|
253
|
-
method: "POST",
|
|
254
|
-
headers: { "Content-Type": "application/x-www-form-urlencoded" },
|
|
255
|
-
body: new URLSearchParams({
|
|
256
|
-
client_id: GOOGLE_CLIENT_ID,
|
|
257
|
-
client_secret: GOOGLE_CLIENT_SECRET,
|
|
258
|
-
refresh_token: account.refresh_token,
|
|
259
|
-
grant_type: "refresh_token"
|
|
260
|
-
})
|
|
261
|
-
});
|
|
262
|
-
if (!response.ok) {
|
|
263
|
-
const error = await response.text();
|
|
264
|
-
consola.error("Token refresh failed:", error);
|
|
265
|
-
return null;
|
|
266
|
-
}
|
|
267
|
-
const data = await response.json();
|
|
268
|
-
return {
|
|
269
|
-
...account,
|
|
270
|
-
access_token: data.access_token,
|
|
271
|
-
expires_in: data.expires_in,
|
|
272
|
-
timestamp: Date.now()
|
|
273
|
-
};
|
|
274
|
-
} catch (error) {
|
|
275
|
-
consola.error("Token refresh error:", error);
|
|
276
|
-
return null;
|
|
277
|
-
}
|
|
278
|
-
}
|
|
279
|
-
/**
|
|
280
|
-
* Check if token is expired
|
|
281
|
-
*/
|
|
282
|
-
function isTokenExpired(account) {
|
|
283
|
-
const expirationTime = account.timestamp + account.expires_in * 1e3;
|
|
284
|
-
return Date.now() > expirationTime - 300 * 1e3;
|
|
285
|
-
}
|
|
286
|
-
/**
|
|
287
|
-
* Get valid access token, refreshing if needed
|
|
288
|
-
*/
|
|
289
|
-
async function getValidAccessToken() {
|
|
290
|
-
const auth = await loadAntigravityAuth();
|
|
291
|
-
if (!auth || auth.accounts.length === 0) return null;
|
|
292
|
-
let account = auth.accounts[auth.currentIndex];
|
|
293
|
-
if (!account || !account.enable) {
|
|
294
|
-
const enabledAccount = auth.accounts.find((a) => a.enable);
|
|
295
|
-
if (!enabledAccount) return null;
|
|
296
|
-
account = enabledAccount;
|
|
297
|
-
}
|
|
298
|
-
if (isTokenExpired(account)) {
|
|
299
|
-
consola.info("Access token expired, refreshing...");
|
|
300
|
-
const refreshedAccount = await refreshAccessToken(account);
|
|
301
|
-
if (!refreshedAccount) {
|
|
302
|
-
consola.error("Token refresh failed, disabling account");
|
|
303
|
-
await disableCurrentAccount();
|
|
304
|
-
return getValidAccessToken();
|
|
305
|
-
}
|
|
306
|
-
auth.accounts[auth.currentIndex] = refreshedAccount;
|
|
307
|
-
await saveAntigravityAuth(auth);
|
|
308
|
-
return refreshedAccount.access_token;
|
|
309
|
-
}
|
|
310
|
-
return account.access_token;
|
|
311
|
-
}
|
|
312
|
-
/**
|
|
313
|
-
* Get current project ID
|
|
314
|
-
*/
|
|
315
|
-
async function getCurrentProjectId() {
|
|
316
|
-
const auth = await loadAntigravityAuth();
|
|
317
|
-
if (!auth || auth.accounts.length === 0) return null;
|
|
318
|
-
let account = auth.accounts[auth.currentIndex];
|
|
319
|
-
if (!account || !account.enable) {
|
|
320
|
-
const enabledAccount = auth.accounts.find((a) => a.enable);
|
|
321
|
-
if (!enabledAccount) return null;
|
|
322
|
-
account = enabledAccount;
|
|
323
|
-
}
|
|
324
|
-
return account.project_id ?? null;
|
|
325
|
-
}
|
|
326
|
-
/**
|
|
327
|
-
* Generate a random project ID for Pro accounts
|
|
328
|
-
*/
|
|
329
|
-
function generateRandomProjectId() {
|
|
330
|
-
const chars = "0123456789";
|
|
331
|
-
let projectId = "";
|
|
332
|
-
for (let i = 0; i < 12; i++) projectId += chars.charAt(Math.floor(Math.random() * 10));
|
|
333
|
-
return projectId;
|
|
334
|
-
}
|
|
335
|
-
/**
|
|
336
|
-
* Get OAuth authorization URL
|
|
337
|
-
*/
|
|
338
|
-
function getOAuthUrl() {
|
|
339
|
-
return `https://accounts.google.com/o/oauth2/v2/auth?${new URLSearchParams({
|
|
340
|
-
client_id: GOOGLE_CLIENT_ID,
|
|
341
|
-
redirect_uri: GOOGLE_REDIRECT_URI,
|
|
342
|
-
response_type: "code",
|
|
343
|
-
scope: OAUTH_SCOPES.join(" "),
|
|
344
|
-
access_type: "offline",
|
|
345
|
-
prompt: "consent",
|
|
346
|
-
include_granted_scopes: "true"
|
|
347
|
-
}).toString()}`;
|
|
348
|
-
}
|
|
349
|
-
/**
|
|
350
|
-
* Exchange authorization code for tokens
|
|
351
|
-
*/
|
|
352
|
-
async function exchangeCodeForTokens(code) {
|
|
353
|
-
try {
|
|
354
|
-
const response = await fetch("https://oauth2.googleapis.com/token", {
|
|
355
|
-
method: "POST",
|
|
356
|
-
headers: { "Content-Type": "application/x-www-form-urlencoded" },
|
|
357
|
-
body: new URLSearchParams({
|
|
358
|
-
client_id: GOOGLE_CLIENT_ID,
|
|
359
|
-
client_secret: GOOGLE_CLIENT_SECRET,
|
|
360
|
-
code,
|
|
361
|
-
redirect_uri: GOOGLE_REDIRECT_URI,
|
|
362
|
-
grant_type: "authorization_code"
|
|
363
|
-
})
|
|
364
|
-
});
|
|
365
|
-
if (!response.ok) {
|
|
366
|
-
const error = await response.text();
|
|
367
|
-
consola.error("Token exchange failed:", error);
|
|
368
|
-
return null;
|
|
369
|
-
}
|
|
370
|
-
const data = await response.json();
|
|
371
|
-
return {
|
|
372
|
-
access_token: data.access_token,
|
|
373
|
-
refresh_token: data.refresh_token,
|
|
374
|
-
expires_in: data.expires_in,
|
|
375
|
-
timestamp: Date.now(),
|
|
376
|
-
enable: true,
|
|
377
|
-
project_id: generateRandomProjectId()
|
|
378
|
-
};
|
|
379
|
-
} catch (error) {
|
|
380
|
-
consola.error("Token exchange error:", error);
|
|
381
|
-
return null;
|
|
382
|
-
}
|
|
383
|
-
}
|
|
384
|
-
/**
|
|
385
|
-
* Start a local OAuth callback server and wait for authorization
|
|
386
|
-
* This provides a seamless web-based login experience
|
|
387
|
-
* Uses Node.js http module for compatibility with both Node.js and Bun
|
|
388
|
-
*
|
|
389
|
-
* @param onServerReady - Callback function called when server is ready to accept connections
|
|
390
|
-
*/
|
|
391
|
-
async function startOAuthCallbackServer(onServerReady) {
|
|
392
|
-
const http = await import("node:http");
|
|
393
|
-
return new Promise((resolve, reject) => {
|
|
394
|
-
const server = http.createServer((req, res) => {
|
|
395
|
-
const url = new URL(req.url || "/", `http://localhost:8046`);
|
|
396
|
-
if (url.pathname === "/callback") {
|
|
397
|
-
const code = url.searchParams.get("code");
|
|
398
|
-
const error = url.searchParams.get("error");
|
|
399
|
-
if (error) {
|
|
400
|
-
res.writeHead(200, { "Content-Type": "text/html" });
|
|
401
|
-
res.end(`<html><body><h1>Authorization Failed</h1><p>Error: ${error}</p><p>You can close this window.</p></body></html>`);
|
|
402
|
-
setTimeout(() => server.close(), 100);
|
|
403
|
-
reject(/* @__PURE__ */ new Error(`OAuth error: ${error}`));
|
|
404
|
-
return;
|
|
405
|
-
}
|
|
406
|
-
if (code) {
|
|
407
|
-
res.writeHead(200, { "Content-Type": "text/html" });
|
|
408
|
-
res.end(`<html><body><h1>Authorization Successful!</h1><p>You can close this window and return to the terminal.</p></body></html>`);
|
|
409
|
-
setTimeout(() => server.close(), 100);
|
|
410
|
-
resolve(code);
|
|
411
|
-
return;
|
|
412
|
-
}
|
|
413
|
-
res.writeHead(400, { "Content-Type": "text/plain" });
|
|
414
|
-
res.end("Missing authorization code");
|
|
415
|
-
return;
|
|
416
|
-
}
|
|
417
|
-
res.writeHead(404, { "Content-Type": "text/plain" });
|
|
418
|
-
res.end("Not found");
|
|
419
|
-
});
|
|
420
|
-
server.listen(8046, () => {
|
|
421
|
-
consola.info(`OAuth callback server started on http://localhost:8046`);
|
|
422
|
-
if (onServerReady) onServerReady();
|
|
423
|
-
});
|
|
424
|
-
setTimeout(() => {
|
|
425
|
-
server.close();
|
|
426
|
-
reject(/* @__PURE__ */ new Error("OAuth timeout - no callback received within 5 minutes"));
|
|
427
|
-
}, 300 * 1e3);
|
|
428
|
-
});
|
|
429
|
-
}
|
|
430
|
-
/**
|
|
431
|
-
* Setup Antigravity with web-based OAuth flow
|
|
432
|
-
* Opens browser for login and automatically captures the callback
|
|
433
|
-
*/
|
|
434
|
-
async function setupAntigravityWeb() {
|
|
435
|
-
const auth = await loadAntigravityAuth();
|
|
436
|
-
if (auth && auth.accounts.length > 0) {
|
|
437
|
-
const enabledCount = auth.accounts.filter((a) => a.enable).length;
|
|
438
|
-
consola.info(`Found ${auth.accounts.length} Antigravity accounts (${enabledCount} enabled)`);
|
|
439
|
-
if (!await consola.prompt("Add another account?", {
|
|
440
|
-
type: "confirm",
|
|
441
|
-
initial: false
|
|
442
|
-
})) return;
|
|
443
|
-
}
|
|
444
|
-
consola.info("");
|
|
445
|
-
consola.info("Google Antigravity OAuth Setup (Web Flow)");
|
|
446
|
-
consola.info("=========================================");
|
|
447
|
-
consola.info("");
|
|
448
|
-
const oauthUrl = getOAuthUrl();
|
|
449
|
-
const openBrowser = async () => {
|
|
450
|
-
consola.info("Opening browser for Google login...");
|
|
451
|
-
consola.info(`If browser doesn't open, visit: ${oauthUrl}`);
|
|
452
|
-
consola.info("");
|
|
453
|
-
try {
|
|
454
|
-
const { exec } = await import("node:child_process");
|
|
455
|
-
const platform = process.platform;
|
|
456
|
-
if (platform === "win32") exec(`start "" "${oauthUrl}"`);
|
|
457
|
-
else if (platform === "darwin") exec(`open "${oauthUrl}"`);
|
|
458
|
-
else exec(`xdg-open "${oauthUrl}"`);
|
|
459
|
-
} catch {
|
|
460
|
-
consola.warn("Could not open browser automatically");
|
|
461
|
-
}
|
|
462
|
-
consola.info("Waiting for authorization...");
|
|
463
|
-
};
|
|
464
|
-
try {
|
|
465
|
-
const code = await startOAuthCallbackServer(() => {
|
|
466
|
-
setImmediate(() => {
|
|
467
|
-
openBrowser().catch(() => {});
|
|
468
|
-
});
|
|
469
|
-
});
|
|
470
|
-
consola.info("Authorization code received, exchanging for tokens...");
|
|
471
|
-
const account = await exchangeCodeForTokens(code);
|
|
472
|
-
if (!account) throw new Error("Failed to exchange authorization code");
|
|
473
|
-
await addAntigravityAccount(account);
|
|
474
|
-
consola.success("Antigravity account added successfully!");
|
|
475
|
-
} catch (error) {
|
|
476
|
-
consola.error("OAuth flow failed:", error);
|
|
477
|
-
throw error;
|
|
478
|
-
}
|
|
479
|
-
}
|
|
480
|
-
/**
|
|
481
|
-
* Setup Antigravity interactively (manual URL copy method)
|
|
482
|
-
*/
|
|
483
|
-
async function setupAntigravityManual() {
|
|
484
|
-
const auth = await loadAntigravityAuth();
|
|
485
|
-
if (auth && auth.accounts.length > 0) {
|
|
486
|
-
const enabledCount = auth.accounts.filter((a) => a.enable).length;
|
|
487
|
-
consola.info(`Found ${auth.accounts.length} Antigravity accounts (${enabledCount} enabled)`);
|
|
488
|
-
if (!await consola.prompt("Add another account?", {
|
|
489
|
-
type: "confirm",
|
|
490
|
-
initial: false
|
|
491
|
-
})) return;
|
|
492
|
-
}
|
|
493
|
-
consola.info("");
|
|
494
|
-
consola.info("Google Antigravity OAuth Setup (Manual)");
|
|
495
|
-
consola.info("=======================================");
|
|
496
|
-
consola.info("");
|
|
497
|
-
consola.info("You need to authorize with Google to use Antigravity API.");
|
|
498
|
-
consola.info("Please follow these steps:");
|
|
499
|
-
consola.info("");
|
|
500
|
-
consola.info("1. Open this URL in your browser:");
|
|
501
|
-
consola.info(` ${getOAuthUrl()}`);
|
|
502
|
-
consola.info("");
|
|
503
|
-
consola.info("2. Complete the Google sign-in process");
|
|
504
|
-
consola.info("3. After authorization, you'll be redirected to a callback URL");
|
|
505
|
-
consola.info("4. Copy the full callback URL and paste it below");
|
|
506
|
-
consola.info("");
|
|
507
|
-
const callbackUrl = await consola.prompt("Enter the callback URL:", { type: "text" });
|
|
508
|
-
if (!callbackUrl || typeof callbackUrl !== "string") throw new Error("Callback URL is required");
|
|
509
|
-
const code = new URL(callbackUrl).searchParams.get("code");
|
|
510
|
-
if (!code) throw new Error("Authorization code not found in URL");
|
|
511
|
-
consola.info("Exchanging authorization code for tokens...");
|
|
512
|
-
const account = await exchangeCodeForTokens(code);
|
|
513
|
-
if (!account) throw new Error("Failed to exchange authorization code");
|
|
514
|
-
await addAntigravityAccount(account);
|
|
515
|
-
consola.success("Antigravity account added successfully!");
|
|
516
|
-
}
|
|
517
|
-
/**
|
|
518
|
-
* Setup Antigravity interactively
|
|
519
|
-
* Offers choice between web-based and manual OAuth flow
|
|
520
|
-
*/
|
|
521
|
-
async function setupAntigravity() {
|
|
522
|
-
await (await consola.prompt("Choose OAuth login method:", {
|
|
523
|
-
type: "select",
|
|
524
|
-
options: [{
|
|
525
|
-
value: "web",
|
|
526
|
-
label: "Web (auto-open browser, recommended)"
|
|
527
|
-
}, {
|
|
528
|
-
value: "manual",
|
|
529
|
-
label: "Manual (copy/paste callback URL)"
|
|
530
|
-
}]
|
|
531
|
-
}) === "web" ? setupAntigravityWeb() : setupAntigravityManual());
|
|
532
|
-
}
|
|
533
|
-
//#endregion
|
|
534
|
-
export { recordAccountFailure as C, setupAntigravityWeb as S, rotateAccount as _, generateRandomProjectId as a, setupAntigravity as b, getCurrentAccount as c, getOAuthUrl as d, getValidAccessToken as f, refreshAccessToken as g, loadAntigravityAuth as h, exchangeCodeForTokens as i, getCurrentAccountIndex as l, isTokenExpired as m, clearAntigravityAuth as n, getAntigravityAuthPath as o, hasApiKey as p, disableCurrentAccount as r, getApiKey as s, addAntigravityAccount as t, getCurrentProjectId as u, saveAntigravityAuth as v, recordAccountSuccess as w, setupAntigravityManual as x, setOAuthCredentials as y };
|
|
535
|
-
|
|
536
|
-
//# sourceMappingURL=auth-C7a3n_4O.js.map
|