copilot-api-plus 1.0.10 → 1.0.11

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 CHANGED
@@ -507,11 +507,14 @@ curl http://localhost:4141/v1/messages \
507
507
 
508
508
  ### logout 命令参数
509
509
 
510
- | 参数 | 说明 |
511
- |------|------|
512
- | `--zen` | 仅清除 Zen 凭证 |
513
- | `--antigravity` | 仅清除 Antigravity 凭证 |
514
- | `--all` | 清除所有凭证 |
510
+ | 参数 | 别名 | 说明 |
511
+ |------|------|------|
512
+ | `--github` | `-g` | 仅清除 GitHub Copilot 凭证 |
513
+ | `--zen` | `-z` | 仅清除 Zen 凭证 |
514
+ | `--antigravity` | - | 仅清除 Antigravity 凭证 |
515
+ | `--all` | `-a` | 清除所有凭证 |
516
+
517
+ > **提示**:不带参数运行 `logout` 会显示交互式菜单供选择。
515
518
 
516
519
  ---
517
520
 
@@ -582,9 +585,14 @@ docker run -p 4141:4141 \
582
585
  ### 切换账户
583
586
 
584
587
  ```bash
585
- # 清除 GitHub 凭证
588
+ # 交互式选择要清除的凭证
586
589
  npx copilot-api-plus@latest logout
587
590
 
591
+ # 仅清除 GitHub Copilot 凭证
592
+ npx copilot-api-plus@latest logout --github
593
+ # 或简写
594
+ npx copilot-api-plus@latest logout -g
595
+
588
596
  # 清除 Zen 凭证
589
597
  npx copilot-api-plus@latest logout --zen
590
598
 
@@ -1 +1 @@
1
- {"version":3,"file":"auth-Cm_0h9bp.js","names":[],"sources":["../src/services/antigravity/auth.ts"],"sourcesContent":["/**\n * Google Antigravity Authentication\n *\n * Handles OAuth token management for Google Antigravity API.\n * Supports multiple accounts with auto-rotation and token refresh.\n */\n\nimport consola from \"consola\"\n\nimport { PATHS, ensurePaths } from \"~/lib/paths\"\n\nexport interface AntigravityAccount {\n access_token: string\n refresh_token: string\n expires_in: number\n timestamp: number\n enable: boolean\n project_id?: string\n}\n\nexport interface AntigravityAuth {\n accounts: Array<AntigravityAccount>\n currentIndex: number\n}\n\nconst ANTIGRAVITY_AUTH_FILENAME = \"antigravity-accounts.json\"\n\n// Google OAuth credentials\n// These are the same public credentials used by antigravity2api-nodejs\n// These are public OAuth client credentials embedded in the official Antigravity client\n// Obfuscated to avoid false positives from secret scanners\nconst _d = (s: string) => s.split(\"\").map((c, i) => String.fromCharCode(c.charCodeAt(0) - (i % 3))).join(\"\")\nconst GOOGLE_CLIENT_ID =\n process.env.ANTIGRAVITY_CLIENT_ID ||\n _d(\"9582:895427;-f:rdlg48v5nguqv4ivc9mfl1k5sl:c87.brpt0gpqgmgutgrdqnugnu0cpo\")\nconst GOOGLE_CLIENT_SECRET =\n process.env.ANTIGRAVITY_CLIENT_SECRET ||\n _d(\"GPESQZ-9fPsMZCyYWHD69k1lfXtn3ek8MCo\")\nconst GOOGLE_REDIRECT_URI = \"http://localhost:8046/callback\"\n\n/**\n * Get the path to the Antigravity auth file\n */\nexport function getAntigravityAuthPath(): string {\n return `${PATHS.DATA_DIR}/${ANTIGRAVITY_AUTH_FILENAME}`\n}\n\n/**\n * Save Antigravity accounts to file\n */\nexport async function saveAntigravityAuth(\n auth: AntigravityAuth,\n): Promise<void> {\n await ensurePaths()\n const authPath = getAntigravityAuthPath()\n await Bun.write(authPath, JSON.stringify(auth, null, 2))\n consola.success(\"Antigravity accounts saved to\", authPath)\n}\n\n/**\n * Load Antigravity accounts from file\n */\nexport async function loadAntigravityAuth(): Promise<AntigravityAuth | null> {\n try {\n const authPath = getAntigravityAuthPath()\n const file = Bun.file(authPath)\n\n if (!(await file.exists())) {\n return null\n }\n\n const content = await file.text()\n const data = JSON.parse(content)\n\n // Handle both array format (legacy) and object format\n if (Array.isArray(data)) {\n return {\n accounts: data,\n currentIndex: 0,\n }\n }\n\n return data as AntigravityAuth\n } catch {\n return null\n }\n}\n\n/**\n * Clear Antigravity accounts\n */\nexport async function clearAntigravityAuth(): Promise<void> {\n try {\n const authPath = getAntigravityAuthPath()\n const fs = await import(\"node:fs/promises\")\n await fs.unlink(authPath)\n consola.success(\"Antigravity accounts cleared\")\n } catch {\n // File might not exist, ignore\n }\n}\n\n/**\n * Add a new account to Antigravity auth\n */\nexport async function addAntigravityAccount(\n account: AntigravityAccount,\n): Promise<void> {\n let auth = await loadAntigravityAuth()\n\n if (!auth) {\n auth = {\n accounts: [],\n currentIndex: 0,\n }\n }\n\n auth.accounts.push(account)\n await saveAntigravityAuth(auth)\n consola.success(\"Added new Antigravity account\")\n}\n\n/**\n * Get the current active account\n */\nexport async function getCurrentAccount(): Promise<AntigravityAccount | null> {\n const auth = await loadAntigravityAuth()\n\n if (!auth || auth.accounts.length === 0) {\n return null\n }\n\n // Find enabled account starting from current index\n const enabledAccounts = auth.accounts.filter((a) => a.enable)\n\n if (enabledAccounts.length === 0) {\n return null\n }\n\n // Get current account or first enabled one\n const currentAccount = auth.accounts[auth.currentIndex]\n if (currentAccount && currentAccount.enable) {\n return currentAccount\n }\n\n return enabledAccounts[0]\n}\n\n/**\n * Rotate to the next account\n */\nexport async function rotateAccount(): Promise<void> {\n const auth = await loadAntigravityAuth()\n\n if (!auth || auth.accounts.length <= 1) {\n return\n }\n\n // Find next enabled account\n let nextIndex = (auth.currentIndex + 1) % auth.accounts.length\n let attempts = 0\n\n while (!auth.accounts[nextIndex].enable && attempts < auth.accounts.length) {\n nextIndex = (nextIndex + 1) % auth.accounts.length\n attempts++\n }\n\n auth.currentIndex = nextIndex\n await saveAntigravityAuth(auth)\n consola.info(`Rotated to account ${nextIndex}`)\n}\n\n/**\n * Disable current account\n */\nexport async function disableCurrentAccount(): Promise<void> {\n const auth = await loadAntigravityAuth()\n\n if (!auth || auth.accounts.length === 0) {\n return\n }\n\n auth.accounts[auth.currentIndex].enable = false\n await saveAntigravityAuth(auth)\n consola.warn(`Disabled account ${auth.currentIndex}`)\n\n // Rotate to next account\n await rotateAccount()\n}\n\n/**\n * Refresh access token using refresh token\n */\nexport async function refreshAccessToken(\n account: AntigravityAccount,\n): Promise<AntigravityAccount | null> {\n try {\n const response = await fetch(\"https://oauth2.googleapis.com/token\", {\n method: \"POST\",\n headers: {\n \"Content-Type\": \"application/x-www-form-urlencoded\",\n },\n body: new URLSearchParams({\n client_id: GOOGLE_CLIENT_ID,\n client_secret: GOOGLE_CLIENT_SECRET,\n refresh_token: account.refresh_token,\n grant_type: \"refresh_token\",\n }),\n })\n\n if (!response.ok) {\n const error = await response.text()\n consola.error(\"Token refresh failed:\", error)\n return null\n }\n\n const data = (await response.json()) as {\n access_token: string\n expires_in: number\n }\n\n return {\n ...account,\n access_token: data.access_token,\n expires_in: data.expires_in,\n timestamp: Date.now(),\n }\n } catch (error) {\n consola.error(\"Token refresh error:\", error)\n return null\n }\n}\n\n/**\n * Check if token is expired\n */\nexport function isTokenExpired(account: AntigravityAccount): boolean {\n const expirationTime = account.timestamp + account.expires_in * 1000\n // Refresh 5 minutes before expiration\n return Date.now() > expirationTime - 5 * 60 * 1000\n}\n\n/**\n * Get valid access token, refreshing if needed\n */\nexport async function getValidAccessToken(): Promise<string | null> {\n const auth = await loadAntigravityAuth()\n\n if (!auth || auth.accounts.length === 0) {\n return null\n }\n\n let account = auth.accounts[auth.currentIndex]\n\n if (!account || !account.enable) {\n const enabledAccount = auth.accounts.find((a) => a.enable)\n if (!enabledAccount) {\n return null\n }\n account = enabledAccount\n }\n\n // Check if token needs refresh\n if (isTokenExpired(account)) {\n consola.info(\"Access token expired, refreshing...\")\n const refreshedAccount = await refreshAccessToken(account)\n\n if (!refreshedAccount) {\n consola.error(\"Token refresh failed, disabling account\")\n await disableCurrentAccount()\n return getValidAccessToken() // Try next account\n }\n\n // Update account in storage\n auth.accounts[auth.currentIndex] = refreshedAccount\n await saveAntigravityAuth(auth)\n\n return refreshedAccount.access_token\n }\n\n return account.access_token\n}\n\n/**\n * Generate a random project ID for Pro accounts\n */\nexport function generateRandomProjectId(): string {\n const chars = \"0123456789\"\n let projectId = \"\"\n for (let i = 0; i < 12; i++) {\n projectId += chars.charAt(Math.floor(Math.random() * chars.length))\n }\n return projectId\n}\n\n/**\n * Get OAuth authorization URL\n */\nexport function getOAuthUrl(): string {\n const params = new URLSearchParams({\n client_id: GOOGLE_CLIENT_ID,\n redirect_uri: GOOGLE_REDIRECT_URI,\n response_type: \"code\",\n scope: \"openid email profile\",\n access_type: \"offline\",\n prompt: \"consent\",\n })\n\n return `https://accounts.google.com/o/oauth2/v2/auth?${params.toString()}`\n}\n\n/**\n * Exchange authorization code for tokens\n */\nexport async function exchangeCodeForTokens(\n code: string,\n): Promise<AntigravityAccount | null> {\n try {\n const response = await fetch(\"https://oauth2.googleapis.com/token\", {\n method: \"POST\",\n headers: {\n \"Content-Type\": \"application/x-www-form-urlencoded\",\n },\n body: new URLSearchParams({\n client_id: GOOGLE_CLIENT_ID,\n client_secret: GOOGLE_CLIENT_SECRET,\n code,\n redirect_uri: GOOGLE_REDIRECT_URI,\n grant_type: \"authorization_code\",\n }),\n })\n\n if (!response.ok) {\n const error = await response.text()\n consola.error(\"Token exchange failed:\", error)\n return null\n }\n\n const data = (await response.json()) as {\n access_token: string\n refresh_token: string\n expires_in: number\n }\n\n return {\n access_token: data.access_token,\n refresh_token: data.refresh_token,\n expires_in: data.expires_in,\n timestamp: Date.now(),\n enable: true,\n project_id: generateRandomProjectId(),\n }\n } catch (error) {\n consola.error(\"Token exchange error:\", error)\n return null\n }\n}\n\n/**\n * Setup Antigravity interactively\n */\nexport async function setupAntigravity(): Promise<void> {\n const auth = await loadAntigravityAuth()\n\n if (auth && auth.accounts.length > 0) {\n const enabledCount = auth.accounts.filter((a) => a.enable).length\n consola.info(\n `Found ${auth.accounts.length} Antigravity accounts (${enabledCount} enabled)`,\n )\n\n const addMore = await consola.prompt(\"Add another account?\", {\n type: \"confirm\",\n initial: false,\n })\n\n if (!addMore) {\n return\n }\n }\n\n consola.info(\"\")\n consola.info(\"Google Antigravity OAuth Setup\")\n consola.info(\"==============================\")\n consola.info(\"\")\n consola.info(\"You need to authorize with Google to use Antigravity API.\")\n consola.info(\"Please follow these steps:\")\n consola.info(\"\")\n consola.info(\"1. Open this URL in your browser:\")\n consola.info(` ${getOAuthUrl()}`)\n consola.info(\"\")\n consola.info(\"2. Complete the Google sign-in process\")\n consola.info(\"3. After authorization, you'll be redirected to a callback URL\")\n consola.info(\"4. Copy the full callback URL and paste it below\")\n consola.info(\"\")\n\n const callbackUrl = await consola.prompt(\"Enter the callback URL:\", {\n type: \"text\",\n })\n\n if (!callbackUrl || typeof callbackUrl !== \"string\") {\n throw new Error(\"Callback URL is required\")\n }\n\n // Extract code from callback URL\n const url = new URL(callbackUrl)\n const code = url.searchParams.get(\"code\")\n\n if (!code) {\n throw new Error(\"Authorization code not found in URL\")\n }\n\n consola.info(\"Exchanging authorization code for tokens...\")\n\n const account = await exchangeCodeForTokens(code)\n\n if (!account) {\n throw new Error(\"Failed to exchange authorization code\")\n }\n\n await addAntigravityAccount(account)\n consola.success(\"Antigravity account added successfully!\")\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"auth-Cm_0h9bp.js","names":[],"sources":["../src/services/antigravity/auth.ts"],"sourcesContent":["/**\n * Google Antigravity Authentication\n *\n * Handles OAuth token management for Google Antigravity API.\n * Supports multiple accounts with auto-rotation and token refresh.\n */\n\nimport consola from \"consola\"\n\nimport { PATHS, ensurePaths } from \"~/lib/paths\"\n\nexport interface AntigravityAccount {\n access_token: string\n refresh_token: string\n expires_in: number\n timestamp: number\n enable: boolean\n project_id?: string\n}\n\nexport interface AntigravityAuth {\n accounts: Array<AntigravityAccount>\n currentIndex: number\n}\n\nconst ANTIGRAVITY_AUTH_FILENAME = \"antigravity-accounts.json\"\n\n// Google OAuth credentials\n// These are the same public credentials used by antigravity2api-nodejs\n// These are public OAuth client credentials embedded in the official Antigravity client\n// Obfuscated to avoid false positives from secret scanners\nconst _d = (s: string) =>\n s\n .split(\"\")\n .map((c, i) => String.fromCharCode(c.charCodeAt(0) - (i % 3)))\n .join(\"\")\nconst GOOGLE_CLIENT_ID =\n process.env.ANTIGRAVITY_CLIENT_ID\n || _d(\n \"9582:895427;-f:rdlg48v5nguqv4ivc9mfl1k5sl:c87.brpt0gpqgmgutgrdqnugnu0cpo\",\n )\nconst GOOGLE_CLIENT_SECRET =\n process.env.ANTIGRAVITY_CLIENT_SECRET\n || _d(\"GPESQZ-9fPsMZCyYWHD69k1lfXtn3ek8MCo\")\nconst GOOGLE_REDIRECT_URI = \"http://localhost:8046/callback\"\n\n/**\n * Get the path to the Antigravity auth file\n */\nexport function getAntigravityAuthPath(): string {\n return `${PATHS.DATA_DIR}/${ANTIGRAVITY_AUTH_FILENAME}`\n}\n\n/**\n * Save Antigravity accounts to file\n */\nexport async function saveAntigravityAuth(\n auth: AntigravityAuth,\n): Promise<void> {\n await ensurePaths()\n const authPath = getAntigravityAuthPath()\n await Bun.write(authPath, JSON.stringify(auth, null, 2))\n consola.success(\"Antigravity accounts saved to\", authPath)\n}\n\n/**\n * Load Antigravity accounts from file\n */\nexport async function loadAntigravityAuth(): Promise<AntigravityAuth | null> {\n try {\n const authPath = getAntigravityAuthPath()\n const file = Bun.file(authPath)\n\n if (!(await file.exists())) {\n return null\n }\n\n const content = await file.text()\n const data = JSON.parse(content)\n\n // Handle both array format (legacy) and object format\n if (Array.isArray(data)) {\n return {\n accounts: data,\n currentIndex: 0,\n }\n }\n\n return data as AntigravityAuth\n } catch {\n return null\n }\n}\n\n/**\n * Clear Antigravity accounts\n */\nexport async function clearAntigravityAuth(): Promise<void> {\n try {\n const authPath = getAntigravityAuthPath()\n const fs = await import(\"node:fs/promises\")\n await fs.unlink(authPath)\n consola.success(\"Antigravity accounts cleared\")\n } catch {\n // File might not exist, ignore\n }\n}\n\n/**\n * Add a new account to Antigravity auth\n */\nexport async function addAntigravityAccount(\n account: AntigravityAccount,\n): Promise<void> {\n let auth = await loadAntigravityAuth()\n\n if (!auth) {\n auth = {\n accounts: [],\n currentIndex: 0,\n }\n }\n\n auth.accounts.push(account)\n await saveAntigravityAuth(auth)\n consola.success(\"Added new Antigravity account\")\n}\n\n/**\n * Get the current active account\n */\nexport async function getCurrentAccount(): Promise<AntigravityAccount | null> {\n const auth = await loadAntigravityAuth()\n\n if (!auth || auth.accounts.length === 0) {\n return null\n }\n\n // Find enabled account starting from current index\n const enabledAccounts = auth.accounts.filter((a) => a.enable)\n\n if (enabledAccounts.length === 0) {\n return null\n }\n\n // Get current account or first enabled one\n const currentAccount = auth.accounts[auth.currentIndex]\n if (currentAccount && currentAccount.enable) {\n return currentAccount\n }\n\n return enabledAccounts[0]\n}\n\n/**\n * Rotate to the next account\n */\nexport async function rotateAccount(): Promise<void> {\n const auth = await loadAntigravityAuth()\n\n if (!auth || auth.accounts.length <= 1) {\n return\n }\n\n // Find next enabled account\n let nextIndex = (auth.currentIndex + 1) % auth.accounts.length\n let attempts = 0\n\n while (!auth.accounts[nextIndex].enable && attempts < auth.accounts.length) {\n nextIndex = (nextIndex + 1) % auth.accounts.length\n attempts++\n }\n\n auth.currentIndex = nextIndex\n await saveAntigravityAuth(auth)\n consola.info(`Rotated to account ${nextIndex}`)\n}\n\n/**\n * Disable current account\n */\nexport async function disableCurrentAccount(): Promise<void> {\n const auth = await loadAntigravityAuth()\n\n if (!auth || auth.accounts.length === 0) {\n return\n }\n\n auth.accounts[auth.currentIndex].enable = false\n await saveAntigravityAuth(auth)\n consola.warn(`Disabled account ${auth.currentIndex}`)\n\n // Rotate to next account\n await rotateAccount()\n}\n\n/**\n * Refresh access token using refresh token\n */\nexport async function refreshAccessToken(\n account: AntigravityAccount,\n): Promise<AntigravityAccount | null> {\n try {\n const response = await fetch(\"https://oauth2.googleapis.com/token\", {\n method: \"POST\",\n headers: {\n \"Content-Type\": \"application/x-www-form-urlencoded\",\n },\n body: new URLSearchParams({\n client_id: GOOGLE_CLIENT_ID,\n client_secret: GOOGLE_CLIENT_SECRET,\n refresh_token: account.refresh_token,\n grant_type: \"refresh_token\",\n }),\n })\n\n if (!response.ok) {\n const error = await response.text()\n consola.error(\"Token refresh failed:\", error)\n return null\n }\n\n const data = (await response.json()) as {\n access_token: string\n expires_in: number\n }\n\n return {\n ...account,\n access_token: data.access_token,\n expires_in: data.expires_in,\n timestamp: Date.now(),\n }\n } catch (error) {\n consola.error(\"Token refresh error:\", error)\n return null\n }\n}\n\n/**\n * Check if token is expired\n */\nexport function isTokenExpired(account: AntigravityAccount): boolean {\n const expirationTime = account.timestamp + account.expires_in * 1000\n // Refresh 5 minutes before expiration\n return Date.now() > expirationTime - 5 * 60 * 1000\n}\n\n/**\n * Get valid access token, refreshing if needed\n */\nexport async function getValidAccessToken(): Promise<string | null> {\n const auth = await loadAntigravityAuth()\n\n if (!auth || auth.accounts.length === 0) {\n return null\n }\n\n let account = auth.accounts[auth.currentIndex]\n\n if (!account || !account.enable) {\n const enabledAccount = auth.accounts.find((a) => a.enable)\n if (!enabledAccount) {\n return null\n }\n account = enabledAccount\n }\n\n // Check if token needs refresh\n if (isTokenExpired(account)) {\n consola.info(\"Access token expired, refreshing...\")\n const refreshedAccount = await refreshAccessToken(account)\n\n if (!refreshedAccount) {\n consola.error(\"Token refresh failed, disabling account\")\n await disableCurrentAccount()\n return getValidAccessToken() // Try next account\n }\n\n // Update account in storage\n auth.accounts[auth.currentIndex] = refreshedAccount\n await saveAntigravityAuth(auth)\n\n return refreshedAccount.access_token\n }\n\n return account.access_token\n}\n\n/**\n * Generate a random project ID for Pro accounts\n */\nexport function generateRandomProjectId(): string {\n const chars = \"0123456789\"\n let projectId = \"\"\n for (let i = 0; i < 12; i++) {\n projectId += chars.charAt(Math.floor(Math.random() * chars.length))\n }\n return projectId\n}\n\n/**\n * Get OAuth authorization URL\n */\nexport function getOAuthUrl(): string {\n const params = new URLSearchParams({\n client_id: GOOGLE_CLIENT_ID,\n redirect_uri: GOOGLE_REDIRECT_URI,\n response_type: \"code\",\n scope: \"openid email profile\",\n access_type: \"offline\",\n prompt: \"consent\",\n })\n\n return `https://accounts.google.com/o/oauth2/v2/auth?${params.toString()}`\n}\n\n/**\n * Exchange authorization code for tokens\n */\nexport async function exchangeCodeForTokens(\n code: string,\n): Promise<AntigravityAccount | null> {\n try {\n const response = await fetch(\"https://oauth2.googleapis.com/token\", {\n method: \"POST\",\n headers: {\n \"Content-Type\": \"application/x-www-form-urlencoded\",\n },\n body: new URLSearchParams({\n client_id: GOOGLE_CLIENT_ID,\n client_secret: GOOGLE_CLIENT_SECRET,\n code,\n redirect_uri: GOOGLE_REDIRECT_URI,\n grant_type: \"authorization_code\",\n }),\n })\n\n if (!response.ok) {\n const error = await response.text()\n consola.error(\"Token exchange failed:\", error)\n return null\n }\n\n const data = (await response.json()) as {\n access_token: string\n refresh_token: string\n expires_in: number\n }\n\n return {\n access_token: data.access_token,\n refresh_token: data.refresh_token,\n expires_in: data.expires_in,\n timestamp: Date.now(),\n enable: true,\n project_id: generateRandomProjectId(),\n }\n } catch (error) {\n consola.error(\"Token exchange error:\", error)\n return null\n }\n}\n\n/**\n * Setup Antigravity interactively\n */\nexport async function setupAntigravity(): Promise<void> {\n const auth = await loadAntigravityAuth()\n\n if (auth && auth.accounts.length > 0) {\n const enabledCount = auth.accounts.filter((a) => a.enable).length\n consola.info(\n `Found ${auth.accounts.length} Antigravity accounts (${enabledCount} enabled)`,\n )\n\n const addMore = await consola.prompt(\"Add another account?\", {\n type: \"confirm\",\n initial: false,\n })\n\n if (!addMore) {\n return\n }\n }\n\n consola.info(\"\")\n consola.info(\"Google Antigravity OAuth Setup\")\n consola.info(\"==============================\")\n consola.info(\"\")\n consola.info(\"You need to authorize with Google to use Antigravity API.\")\n consola.info(\"Please follow these steps:\")\n consola.info(\"\")\n consola.info(\"1. Open this URL in your browser:\")\n consola.info(` ${getOAuthUrl()}`)\n consola.info(\"\")\n consola.info(\"2. Complete the Google sign-in process\")\n consola.info(\"3. After authorization, you'll be redirected to a callback URL\")\n consola.info(\"4. Copy the full callback URL and paste it below\")\n consola.info(\"\")\n\n const callbackUrl = await consola.prompt(\"Enter the callback URL:\", {\n type: \"text\",\n })\n\n if (!callbackUrl || typeof callbackUrl !== \"string\") {\n throw new Error(\"Callback URL is required\")\n }\n\n // Extract code from callback URL\n const url = new URL(callbackUrl)\n const code = url.searchParams.get(\"code\")\n\n if (!code) {\n throw new Error(\"Authorization code not found in URL\")\n }\n\n consola.info(\"Exchanging authorization code for tokens...\")\n\n const account = await exchangeCodeForTokens(code)\n\n if (!account) {\n throw new Error(\"Failed to exchange authorization code\")\n }\n\n await addAntigravityAccount(account)\n consola.success(\"Antigravity account added successfully!\")\n}\n"],"mappings":"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"}
package/dist/main.js CHANGED
@@ -108,6 +108,83 @@ const checkUsage = defineCommand({
108
108
  }
109
109
  });
110
110
 
111
+ //#endregion
112
+ //#region src/lib/config.ts
113
+ const CONFIG_FILENAME = "config.json";
114
+ /**
115
+ * Get the path to the config file
116
+ */
117
+ function getConfigPath() {
118
+ return path.join(PATHS.DATA_DIR, CONFIG_FILENAME);
119
+ }
120
+ /**
121
+ * Load configuration from file
122
+ */
123
+ async function loadConfig() {
124
+ try {
125
+ const configPath = getConfigPath();
126
+ const content = await fs.readFile(configPath);
127
+ return JSON.parse(content);
128
+ } catch {
129
+ return {};
130
+ }
131
+ }
132
+ /**
133
+ * Save configuration to file
134
+ */
135
+ async function saveConfig(config) {
136
+ const configPath = getConfigPath();
137
+ await fs.writeFile(configPath, JSON.stringify(config, null, 2), "utf8");
138
+ consola.debug(`Configuration saved to ${configPath}`);
139
+ }
140
+ /**
141
+ * Get proxy configuration
142
+ */
143
+ async function getProxyConfig() {
144
+ return (await loadConfig()).proxy;
145
+ }
146
+ /**
147
+ * Save proxy configuration
148
+ */
149
+ async function saveProxyConfig(proxyConfig) {
150
+ const config = await loadConfig();
151
+ config.proxy = proxyConfig;
152
+ await saveConfig(config);
153
+ }
154
+ /**
155
+ * Clear proxy configuration
156
+ */
157
+ async function clearProxyConfig() {
158
+ const config = await loadConfig();
159
+ delete config.proxy;
160
+ await saveConfig(config);
161
+ }
162
+ /**
163
+ * Apply saved proxy configuration to environment variables
164
+ * This should be called at startup to restore proxy settings
165
+ */
166
+ async function applyProxyConfig() {
167
+ const proxyConfig = await getProxyConfig();
168
+ if (!proxyConfig || !proxyConfig.enabled) return false;
169
+ if (proxyConfig.httpProxy) {
170
+ process.env.HTTP_PROXY = proxyConfig.httpProxy;
171
+ process.env.http_proxy = proxyConfig.httpProxy;
172
+ }
173
+ if (proxyConfig.httpsProxy) {
174
+ process.env.HTTPS_PROXY = proxyConfig.httpsProxy;
175
+ process.env.https_proxy = proxyConfig.httpsProxy;
176
+ }
177
+ if (proxyConfig.noProxy) {
178
+ process.env.NO_PROXY = proxyConfig.noProxy;
179
+ process.env.no_proxy = proxyConfig.noProxy;
180
+ }
181
+ consola.info("Proxy configuration loaded from saved settings");
182
+ if (proxyConfig.httpProxy) consola.info(` HTTP_PROXY: ${proxyConfig.httpProxy}`);
183
+ if (proxyConfig.httpsProxy) consola.info(` HTTPS_PROXY: ${proxyConfig.httpsProxy}`);
184
+ if (proxyConfig.noProxy) consola.info(` NO_PROXY: ${proxyConfig.noProxy}`);
185
+ return true;
186
+ }
187
+
111
188
  //#endregion
112
189
  //#region src/debug.ts
113
190
  async function getPackageVersion() {
@@ -135,29 +212,61 @@ async function checkTokenExists() {
135
212
  return false;
136
213
  }
137
214
  }
215
+ async function checkFileExists(path$1) {
216
+ try {
217
+ if (!(await fs.stat(path$1)).isFile()) return false;
218
+ return (await fs.readFile(path$1, "utf8")).trim().length > 0;
219
+ } catch {
220
+ return false;
221
+ }
222
+ }
138
223
  async function getDebugInfo() {
139
- const [version, tokenExists] = await Promise.all([getPackageVersion(), checkTokenExists()]);
224
+ const zenAuthPath = getZenAuthPath();
225
+ const antigravityAuthPath = getAntigravityAuthPath();
226
+ const [version, githubExists, zenExists, antigravityExists, proxyConfig] = await Promise.all([
227
+ getPackageVersion(),
228
+ checkTokenExists(),
229
+ checkFileExists(zenAuthPath),
230
+ checkFileExists(antigravityAuthPath),
231
+ getProxyConfig()
232
+ ]);
140
233
  return {
141
234
  version,
142
235
  runtime: getRuntimeInfo(),
143
236
  paths: {
144
237
  APP_DIR: PATHS.APP_DIR,
145
- GITHUB_TOKEN_PATH: PATHS.GITHUB_TOKEN_PATH
238
+ GITHUB_TOKEN_PATH: PATHS.GITHUB_TOKEN_PATH,
239
+ ZEN_AUTH_PATH: zenAuthPath,
240
+ ANTIGRAVITY_AUTH_PATH: antigravityAuthPath
146
241
  },
147
- tokenExists
242
+ credentials: {
243
+ github: githubExists,
244
+ zen: zenExists,
245
+ antigravity: antigravityExists
246
+ },
247
+ proxy: proxyConfig
148
248
  };
149
249
  }
150
250
  function printDebugInfoPlain(info) {
151
- consola.info(`copilot-api debug
251
+ let proxyStatus = "Not configured";
252
+ if (info.proxy) proxyStatus = info.proxy.enabled ? `Enabled (${info.proxy.httpProxy || info.proxy.httpsProxy})` : "Disabled";
253
+ consola.info(`copilot-api-plus debug
152
254
 
153
255
  Version: ${info.version}
154
256
  Runtime: ${info.runtime.name} ${info.runtime.version} (${info.runtime.platform} ${info.runtime.arch})
155
257
 
156
258
  Paths:
157
- - APP_DIR: ${info.paths.APP_DIR}
158
- - GITHUB_TOKEN_PATH: ${info.paths.GITHUB_TOKEN_PATH}
259
+ APP_DIR: ${info.paths.APP_DIR}
260
+ GITHUB_TOKEN_PATH: ${info.paths.GITHUB_TOKEN_PATH}
261
+ ZEN_AUTH_PATH: ${info.paths.ZEN_AUTH_PATH}
262
+ ANTIGRAVITY_AUTH_PATH: ${info.paths.ANTIGRAVITY_AUTH_PATH}
263
+
264
+ Credentials:
265
+ GitHub Copilot: ${info.credentials.github ? "✅ Configured" : "❌ Not configured"}
266
+ OpenCode Zen: ${info.credentials.zen ? "✅ Configured" : "❌ Not configured"}
267
+ Google Antigravity: ${info.credentials.antigravity ? "✅ Configured" : "❌ Not configured"}
159
268
 
160
- Token exists: ${info.tokenExists ? "Yes" : "No"}`);
269
+ Proxy: ${proxyStatus}`);
161
270
  }
162
271
  function printDebugInfoJson(info) {
163
272
  console.log(JSON.stringify(info, null, 2));
@@ -196,6 +305,12 @@ async function runLogout(options) {
196
305
  consola.info(`Antigravity accounts: ${getAntigravityAuthPath()}`);
197
306
  return;
198
307
  }
308
+ if (options.github) {
309
+ await clearGithubToken();
310
+ consola.success("Logged out from GitHub Copilot");
311
+ consola.info(`Token file location: ${PATHS.GITHUB_TOKEN_PATH}`);
312
+ return;
313
+ }
199
314
  if (options.zen) {
200
315
  await clearZenAuth();
201
316
  consola.success("Logged out from OpenCode Zen");
@@ -246,6 +361,12 @@ const logout = defineCommand({
246
361
  description: "Clear stored credentials and logout"
247
362
  },
248
363
  args: {
364
+ github: {
365
+ alias: "g",
366
+ type: "boolean",
367
+ default: false,
368
+ description: "Clear only GitHub Copilot token"
369
+ },
249
370
  zen: {
250
371
  alias: "z",
251
372
  type: "boolean",
@@ -266,6 +387,7 @@ const logout = defineCommand({
266
387
  },
267
388
  run({ args }) {
268
389
  return runLogout({
390
+ github: args.github,
269
391
  zen: args.zen,
270
392
  antigravity: args.antigravity,
271
393
  all: args.all
@@ -273,83 +395,6 @@ const logout = defineCommand({
273
395
  }
274
396
  });
275
397
 
276
- //#endregion
277
- //#region src/lib/config.ts
278
- const CONFIG_FILENAME = "config.json";
279
- /**
280
- * Get the path to the config file
281
- */
282
- function getConfigPath() {
283
- return path.join(PATHS.DATA_DIR, CONFIG_FILENAME);
284
- }
285
- /**
286
- * Load configuration from file
287
- */
288
- async function loadConfig() {
289
- try {
290
- const configPath = getConfigPath();
291
- const content = await fs.readFile(configPath, "utf-8");
292
- return JSON.parse(content);
293
- } catch {
294
- return {};
295
- }
296
- }
297
- /**
298
- * Save configuration to file
299
- */
300
- async function saveConfig(config) {
301
- const configPath = getConfigPath();
302
- await fs.writeFile(configPath, JSON.stringify(config, null, 2), "utf-8");
303
- consola.debug(`Configuration saved to ${configPath}`);
304
- }
305
- /**
306
- * Get proxy configuration
307
- */
308
- async function getProxyConfig() {
309
- return (await loadConfig()).proxy;
310
- }
311
- /**
312
- * Save proxy configuration
313
- */
314
- async function saveProxyConfig(proxyConfig) {
315
- const config = await loadConfig();
316
- config.proxy = proxyConfig;
317
- await saveConfig(config);
318
- }
319
- /**
320
- * Clear proxy configuration
321
- */
322
- async function clearProxyConfig() {
323
- const config = await loadConfig();
324
- delete config.proxy;
325
- await saveConfig(config);
326
- }
327
- /**
328
- * Apply saved proxy configuration to environment variables
329
- * This should be called at startup to restore proxy settings
330
- */
331
- async function applyProxyConfig() {
332
- const proxyConfig = await getProxyConfig();
333
- if (!proxyConfig || !proxyConfig.enabled) return false;
334
- if (proxyConfig.httpProxy) {
335
- process.env.HTTP_PROXY = proxyConfig.httpProxy;
336
- process.env.http_proxy = proxyConfig.httpProxy;
337
- }
338
- if (proxyConfig.httpsProxy) {
339
- process.env.HTTPS_PROXY = proxyConfig.httpsProxy;
340
- process.env.https_proxy = proxyConfig.httpsProxy;
341
- }
342
- if (proxyConfig.noProxy) {
343
- process.env.NO_PROXY = proxyConfig.noProxy;
344
- process.env.no_proxy = proxyConfig.noProxy;
345
- }
346
- consola.info("Proxy configuration loaded from saved settings");
347
- if (proxyConfig.httpProxy) consola.info(` HTTP_PROXY: ${proxyConfig.httpProxy}`);
348
- if (proxyConfig.httpsProxy) consola.info(` HTTPS_PROXY: ${proxyConfig.httpsProxy}`);
349
- if (proxyConfig.noProxy) consola.info(` NO_PROXY: ${proxyConfig.noProxy}`);
350
- return true;
351
- }
352
-
353
398
  //#endregion
354
399
  //#region src/proxy-config.ts
355
400
  const proxy = defineCommand({
@@ -1702,7 +1747,10 @@ function translateModelName(model) {
1702
1747
  "claude-3-5-haiku": "claude-haiku-4.5",
1703
1748
  "claude-3-haiku": "claude-haiku-4.5"
1704
1749
  })) if (modelBase.startsWith(oldFormat) && supportedModels.includes(newFormat)) return newFormat;
1705
- const modelFamily = model.includes("opus") ? "opus" : model.includes("sonnet") ? "sonnet" : model.includes("haiku") ? "haiku" : null;
1750
+ let modelFamily = null;
1751
+ if (model.includes("opus")) modelFamily = "opus";
1752
+ else if (model.includes("sonnet")) modelFamily = "sonnet";
1753
+ else if (model.includes("haiku")) modelFamily = "haiku";
1706
1754
  if (modelFamily) {
1707
1755
  const familyModel = supportedModels.find((m) => m.includes(modelFamily));
1708
1756
  if (familyModel) return familyModel;
@@ -2429,7 +2477,9 @@ async function runServer(options) {
2429
2477
  }
2430
2478
  const serverUrl = `http://localhost:${options.port}`;
2431
2479
  if (options.claudeCode) {
2432
- const models = state.zenMode ? state.zenModels : state.antigravityMode ? state.antigravityModels : state.models;
2480
+ let models = state.models;
2481
+ if (state.zenMode) models = state.zenModels;
2482
+ else if (state.antigravityMode) models = state.antigravityModels;
2433
2483
  invariant(models, "Models should be loaded by now");
2434
2484
  const selectedModel = await consola.prompt("Select a model to use with Claude Code", {
2435
2485
  type: "select",