copilot-api-plus 1.2.3 → 1.2.4
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.
|
@@ -354,7 +354,10 @@ var AccountManager = class {
|
|
|
354
354
|
if (err instanceof HTTPError && err.response.status === 401) {
|
|
355
355
|
this.markAccountStatus(account.id, "banned", "GitHub token invalid");
|
|
356
356
|
consola.warn(`Account ${account.label}: token invalid, marked as banned`);
|
|
357
|
-
} else
|
|
357
|
+
} else {
|
|
358
|
+
consola.warn(`Account ${account.label}: failed to refresh Copilot token: ${rootCause(err)}`);
|
|
359
|
+
consola.debug(`Account ${account.label}: failed to refresh Copilot token:`, err);
|
|
360
|
+
}
|
|
358
361
|
}
|
|
359
362
|
}
|
|
360
363
|
/**
|
|
@@ -564,7 +567,8 @@ const setupCopilotToken = async () => {
|
|
|
564
567
|
try {
|
|
565
568
|
await refreshCopilotToken();
|
|
566
569
|
} catch (error) {
|
|
567
|
-
consola.
|
|
570
|
+
consola.warn(`Failed to refresh Copilot token: ${rootCause(error)}`);
|
|
571
|
+
consola.debug("Failed to refresh Copilot token:", error);
|
|
568
572
|
if (error instanceof HTTPError && error.response.status === 401) {
|
|
569
573
|
consola.warn("GitHub token may have been revoked. Please restart and re-authenticate.");
|
|
570
574
|
state.copilotToken = void 0;
|
|
@@ -619,7 +623,8 @@ async function setupGitHubToken(options) {
|
|
|
619
623
|
consola.error("Failed to get GitHub token:", await error.response.json());
|
|
620
624
|
throw error;
|
|
621
625
|
}
|
|
622
|
-
consola.
|
|
626
|
+
consola.warn(`Failed to get GitHub token: ${rootCause(error)}`);
|
|
627
|
+
consola.debug("Failed to get GitHub token:", error);
|
|
623
628
|
throw error;
|
|
624
629
|
}
|
|
625
630
|
}
|
|
@@ -630,4 +635,4 @@ async function logUser() {
|
|
|
630
635
|
//#endregion
|
|
631
636
|
export { ensurePaths as _, stopCopilotTokenRefresh as a, accountManager as c, cacheVSCodeVersion as d, findModel as f, PATHS as g, sleep as h, setupGitHubToken as i, getCopilotUsage as l, rootCause as m, refreshCopilotToken as n, pollAccessToken as o, isNullish as p, setupCopilotToken as r, getDeviceCode as s, clearGithubToken as t, cacheModels as u };
|
|
632
637
|
|
|
633
|
-
//# sourceMappingURL=token-
|
|
638
|
+
//# sourceMappingURL=token-CMRVLQJZ.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"token-CMRVLQJZ.js","names":["ACCOUNTS_PATH"],"sources":["../src/lib/paths.ts","../src/services/copilot/get-models.ts","../src/services/get-vscode-version.ts","../src/lib/utils.ts","../src/services/github/get-copilot-token.ts","../src/services/github/get-copilot-usage.ts","../src/lib/account-manager.ts","../src/services/github/get-device-code.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\nconst ACCOUNTS_PATH = path.join(APP_DIR, \"accounts.json\")\n\nexport const PATHS = {\n APP_DIR,\n DATA_DIR: APP_DIR,\n GITHUB_TOKEN_PATH,\n ACCOUNTS_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 { accountManager } from \"~/lib/account-manager\"\nimport {\n copilotBaseUrl,\n copilotHeaders,\n type TokenSource,\n} from \"~/lib/api-config\"\nimport { HTTPError } from \"~/lib/error\"\nimport { state } from \"~/lib/state\"\n\nexport const getModels = async () => {\n // In multi-account mode, use the active account's token\n let source: TokenSource = state\n if (state.multiAccountEnabled && accountManager.hasAccounts()) {\n const account = accountManager.getActiveAccount()\n if (account?.copilotToken) {\n source = {\n copilotToken: account.copilotToken,\n copilotApiEndpoint: account.copilotApiEndpoint,\n accountType: account.accountType,\n githubToken: account.githubToken,\n vsCodeVersion: state.vsCodeVersion,\n }\n }\n }\n\n const url = `${copilotBaseUrl(source)}/models`\n\n const response = await fetch(url, {\n headers: copilotHeaders(source),\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 // Thinking/reasoning capabilities\n max_thinking_budget?: number\n min_thinking_budget?: number\n adaptive_thinking?: 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","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\n/**\n * Extract the root-cause message from an unknown thrown value.\n *\n * If the error wraps another error via the standard `cause` property, the\n * inner message is returned instead — giving a one-line summary of *why*\n * the operation failed without the noise of the full stack trace.\n */\nexport function rootCause(err: unknown): string {\n if (err instanceof Error) {\n return err.cause instanceof Error ? err.cause.message : err.message\n }\n return String(err)\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\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 { GITHUB_API_BASE_URL, githubHeaders } from \"~/lib/api-config\"\nimport { HTTPError } from \"~/lib/error\"\nimport { state } from \"~/lib/state\"\n\n/**\n * Fetch a short-lived Copilot JWT from the GitHub API.\n *\n * @param githubToken Optional explicit GitHub PAT. When provided the request\n * uses this token instead of `state.githubToken` and does\n * **not** mutate global state (so multi-account callers\n * stay side-effect-free).\n */\nexport const getCopilotToken = async (githubToken?: string) => {\n const tokenToUse = githubToken ?? state.githubToken\n const isExplicitToken = githubToken !== undefined\n\n const url = `${GITHUB_API_BASE_URL}/copilot_internal/v2/token`\n const fetchOptions: RequestInit = {\n headers: githubHeaders({\n ...state,\n githubToken: tokenToUse,\n }),\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 // Only write to global state when using the default token (single-account mode).\n // When an explicit githubToken is provided (multi-account), the caller is\n // responsible for storing the endpoint on its own Account object.\n if (!isExplicitToken && 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 { GITHUB_API_BASE_URL, githubHeaders } from \"~/lib/api-config\"\nimport { HTTPError } from \"~/lib/error\"\nimport { state } from \"~/lib/state\"\n\n/**\n * Fetch the authenticated user's Copilot usage / quota snapshot.\n *\n * @param githubToken Optional explicit GitHub PAT. When provided the request\n * uses this token instead of `state.githubToken`, allowing\n * multi-account callers to query any account without\n * touching global state.\n */\nexport const getCopilotUsage = async (\n githubToken?: string,\n): Promise<CopilotUsageResponse> => {\n const response = await fetch(`${GITHUB_API_BASE_URL}/copilot_internal/user`, {\n headers: githubHeaders({\n ...state,\n githubToken: githubToken ?? state.githubToken,\n }),\n })\n\n if (!response.ok) {\n throw new HTTPError(\"Failed to get Copilot usage\", response)\n }\n\n return (await response.json()) as CopilotUsageResponse\n}\n\nexport interface QuotaDetail {\n entitlement: number\n overage_count: number\n overage_permitted: boolean\n percent_remaining: number\n quota_id: string\n quota_remaining: number\n remaining: number\n unlimited: boolean\n}\n\ninterface QuotaSnapshots {\n chat: QuotaDetail\n completions: QuotaDetail\n premium_interactions: QuotaDetail\n}\n\ninterface CopilotUsageResponse {\n access_type_sku: string\n analytics_tracking_id: string\n assigned_date: string\n can_signup_for_limited: boolean\n chat_enabled: boolean\n copilot_plan: string\n organization_login_list: Array<unknown>\n organization_list: Array<unknown>\n quota_reset_date: string\n quota_snapshots: QuotaSnapshots\n}\n","import consola from \"consola\"\nimport { randomUUID } from \"node:crypto\"\nimport fs from \"node:fs/promises\"\n\nimport { HTTPError } from \"~/lib/error\"\nimport { PATHS } from \"~/lib/paths\"\nimport { rootCause } from \"~/lib/utils\"\nimport { getCopilotToken } from \"~/services/github/get-copilot-token\"\nimport { getCopilotUsage } from \"~/services/github/get-copilot-usage\"\nimport { getGitHubUser } from \"~/services/github/get-user\"\n\n// ---------------------------------------------------------------------------\n// Types\n// ---------------------------------------------------------------------------\n\nexport type AccountStatus =\n | \"active\"\n | \"exhausted\"\n | \"rate_limited\"\n | \"banned\"\n | \"error\"\n | \"disabled\"\n\nexport interface Account {\n id: string\n label: string\n githubToken: string\n /** Short-lived Copilot JWT – kept in memory only, never persisted. */\n copilotToken?: string\n /** API endpoint extracted from the Copilot token response. */\n copilotApiEndpoint?: string\n accountType: string // \"individual\" | \"business\" | \"enterprise\"\n\n // Status\n status: AccountStatus\n statusMessage?: string\n lastUsedAt?: number\n consecutiveFailures: number\n cooldownUntil?: number\n\n // Quota snapshot (refreshed periodically)\n usage?: {\n premium_remaining: number\n premium_total: number\n chat_remaining: number\n chat_total: number\n quotaResetDate: string\n lastCheckedAt: number\n }\n\n // Metadata\n githubLogin?: string\n addedAt: number\n}\n\n// ---------------------------------------------------------------------------\n// Persistence helpers\n// ---------------------------------------------------------------------------\n\n/** Fields excluded from the JSON file (short-lived / runtime-only). */\ntype PersistedAccount = Omit<Account, \"copilotToken\">\n\nconst ACCOUNTS_PATH = PATHS.ACCOUNTS_PATH\n\n// ---------------------------------------------------------------------------\n// AccountManager\n// ---------------------------------------------------------------------------\n\nconst COOLDOWN_MS = 60 * 1000 // 60 seconds\n\nexport class AccountManager {\n private accounts: Array<Account> = []\n private refreshInterval?: ReturnType<typeof setInterval>\n private usageInterval?: ReturnType<typeof setInterval>\n\n // Debounced save\n private saveTimer?: ReturnType<typeof setTimeout>\n private savePending = false\n\n // ---------- Persistence ------------------------------------------------\n\n /**\n * Load accounts from the JSON file on disk.\n * Missing file is treated as empty list – not an error.\n */\n async loadAccounts(): Promise<void> {\n try {\n // eslint-disable-next-line unicorn/prefer-json-parse-buffer\n const raw = await fs.readFile(ACCOUNTS_PATH, \"utf8\")\n const parsed = JSON.parse(raw) as Array<PersistedAccount>\n this.accounts = parsed.map((a) => ({ ...a, copilotToken: undefined }))\n consola.info(`Loaded ${this.accounts.length} account(s) from disk`)\n } catch (err: unknown) {\n if ((err as NodeJS.ErrnoException).code === \"ENOENT\") {\n this.accounts = []\n return\n }\n consola.warn(`Failed to load accounts: ${rootCause(err)}`)\n consola.debug(\"Failed to load accounts:\", err)\n this.accounts = []\n }\n }\n\n /**\n * Persist accounts to disk. `copilotToken` is excluded because it is\n * short-lived and will be refreshed on every startup.\n */\n async saveAccounts(): Promise<void> {\n const data: Array<PersistedAccount> = this.accounts.map(\n ({ copilotToken: _dropped, ...rest }) => rest,\n )\n try {\n await fs.writeFile(ACCOUNTS_PATH, JSON.stringify(data, null, 2), {\n encoding: \"utf8\",\n mode: 0o600,\n })\n } catch (err) {\n consola.warn(`Failed to save accounts: ${rootCause(err)}`)\n consola.debug(\"Failed to save accounts:\", err)\n }\n }\n\n /** Schedule a debounced save (coalesces rapid status updates). */\n private debouncedSave(): void {\n if (this.saveTimer) return // already scheduled\n this.savePending = true\n this.saveTimer = setTimeout(async () => {\n this.saveTimer = undefined\n if (this.savePending) {\n this.savePending = false\n await this.saveAccounts()\n }\n }, 1_000)\n }\n\n // ---------- CRUD -------------------------------------------------------\n\n /**\n * Add a new account.\n *\n * 1. Validates the GitHub token by fetching user info.\n * 2. Obtains an initial Copilot token.\n * 3. Persists the account to disk.\n */\n async addAccount(\n githubToken: string,\n label: string,\n accountType: string = \"individual\",\n ): Promise<Account> {\n // 1. Validate token – get GitHub login (pass token explicitly, no state mutation)\n const user = await getGitHubUser(githubToken)\n\n // 2. Obtain Copilot token – pass token directly (no state mutation)\n const tokenData = await getCopilotToken(githubToken)\n\n const account: Account = {\n id: randomUUID(),\n label,\n githubToken,\n copilotToken: tokenData.token,\n copilotApiEndpoint: tokenData.endpoints?.api,\n accountType,\n status: \"active\",\n consecutiveFailures: 0,\n githubLogin: user.login,\n addedAt: Date.now(),\n }\n\n this.accounts.push(account)\n await this.saveAccounts()\n\n consola.success(`Account added: ${label} (${user.login})`)\n return account\n }\n\n async removeAccount(id: string): Promise<boolean> {\n const idx = this.accounts.findIndex((a) => a.id === id)\n if (idx === -1) return false\n const [removed] = this.accounts.splice(idx, 1)\n await this.saveAccounts()\n consola.info(`Account removed: ${removed.label}`)\n return true\n }\n\n getAccounts(): Array<Account> {\n return this.accounts\n }\n\n getAccountById(id: string): Account | undefined {\n return this.accounts.find((a) => a.id === id)\n }\n\n // ---------- Smart account selection ------------------------------------\n\n /**\n * Pick the best available account.\n *\n * 1. Filter out disabled, banned, exhausted, and accounts still in cooldown.\n * 2. Prefer accounts with more remaining premium quota.\n * 3. Fall back to round-robin (least-recently-used) when quotas are equal\n * or unknown.\n */\n getActiveAccount(): Account | undefined {\n const now = Date.now()\n\n const eligible = this.accounts.filter((a) => {\n if (\n a.status === \"disabled\"\n || a.status === \"banned\"\n || a.status === \"exhausted\"\n ) {\n return false\n }\n if (a.cooldownUntil && a.cooldownUntil > now) return false\n return true\n })\n\n if (eligible.length === 0) {\n // Fallback: if there is exactly one account and it's only cooling down\n // (not banned/disabled/exhausted), return it anyway. With a single\n // account there is nothing to \"switch to\", so blocking all requests\n // for the cooldown period would be a self-inflicted outage.\n if (this.accounts.length === 1) {\n const solo = this.accounts[0]\n if (\n solo.status !== \"disabled\"\n && solo.status !== \"banned\"\n && solo.status !== \"exhausted\"\n ) {\n return solo\n }\n }\n return undefined\n }\n\n eligible.sort((a, b) => {\n const aRemaining = a.usage?.premium_remaining ?? -1\n const bRemaining = b.usage?.premium_remaining ?? -1\n\n // Higher remaining quota first\n if (aRemaining !== bRemaining) return bRemaining - aRemaining\n\n // Equal / unknown → least recently used first (round-robin)\n const aUsed = a.lastUsedAt ?? 0\n const bUsed = b.lastUsedAt ?? 0\n return aUsed - bUsed\n })\n\n return eligible[0]\n }\n\n // ---------- Status management ------------------------------------------\n\n markAccountStatus(id: string, status: AccountStatus, message?: string): void {\n const account = this.getAccountById(id)\n if (!account) return\n\n account.status = status\n account.statusMessage = message\n\n switch (status) {\n case \"rate_limited\":\n case \"error\": {\n // Only apply cooldown when there are other accounts to switch to.\n // With a single account, cooldown would block ALL requests with\n // \"No available accounts\" — effectively a self-inflicted outage.\n if (this.accounts.length > 1) {\n account.cooldownUntil = Date.now() + COOLDOWN_MS\n }\n account.consecutiveFailures += 1\n\n break\n }\n case \"banned\": {\n account.consecutiveFailures += 1\n\n break\n }\n case \"active\": {\n // Recovering from a failure state — reset\n account.consecutiveFailures = 0\n account.cooldownUntil = undefined\n\n break\n }\n // No default\n }\n // \"exhausted\" and \"disabled\" don't touch consecutiveFailures\n\n this.debouncedSave()\n }\n\n markAccountSuccess(id: string): void {\n const account = this.getAccountById(id)\n if (!account) return\n\n account.consecutiveFailures = 0\n account.lastUsedAt = Date.now()\n\n if (account.status === \"error\" || account.status === \"rate_limited\") {\n account.status = \"active\"\n account.statusMessage = undefined\n account.cooldownUntil = undefined\n }\n\n this.debouncedSave()\n }\n\n // ---------- Token & usage refresh (per account) ------------------------\n\n /**\n * Refresh the short-lived Copilot JWT for a single account.\n *\n * Passes the account's GitHub token directly to `getCopilotToken()` so that\n * global `state.githubToken` is never mutated — safe for concurrent use.\n */\n async refreshAccountToken(account: Account): Promise<void> {\n try {\n const data = await getCopilotToken(account.githubToken)\n // eslint-disable-next-line require-atomic-updates\n account.copilotToken = data.token\n if (data.endpoints?.api) {\n // eslint-disable-next-line require-atomic-updates\n account.copilotApiEndpoint = data.endpoints.api\n }\n } catch (err: unknown) {\n if (err instanceof HTTPError && err.response.status === 401) {\n this.markAccountStatus(account.id, \"banned\", \"GitHub token invalid\")\n consola.warn(\n `Account ${account.label}: token invalid, marked as banned`,\n )\n } else {\n consola.warn(\n `Account ${account.label}: failed to refresh Copilot token: ${rootCause(err)}`,\n )\n consola.debug(\n `Account ${account.label}: failed to refresh Copilot token:`,\n err,\n )\n }\n }\n }\n\n /**\n * Refresh the usage / quota snapshot for a single account.\n *\n * Passes the account's GitHub token directly to `getCopilotUsage()` so that\n * global `state.githubToken` is never mutated — safe for concurrent use.\n */\n async refreshAccountUsage(account: Account): Promise<void> {\n try {\n const data = await getCopilotUsage(account.githubToken)\n const snap = data.quota_snapshots\n\n // eslint-disable-next-line require-atomic-updates\n account.usage = {\n premium_remaining: snap.premium_interactions.remaining,\n premium_total: snap.premium_interactions.entitlement,\n chat_remaining: snap.chat.remaining,\n chat_total: snap.chat.entitlement,\n quotaResetDate: data.quota_reset_date,\n lastCheckedAt: Date.now(),\n }\n\n // Transition between active ↔ exhausted\n if (account.usage.premium_remaining <= 0 && account.status === \"active\") {\n this.markAccountStatus(\n account.id,\n \"exhausted\",\n \"Premium quota exhausted\",\n )\n } else if (\n account.usage.premium_remaining > 0\n && account.status === \"exhausted\"\n ) {\n account.status = \"active\"\n account.statusMessage = undefined\n account.consecutiveFailures = 0\n this.debouncedSave()\n }\n } catch (err) {\n consola.warn(\n `Account ${account.label}: failed to refresh usage: ${rootCause(err)}`,\n )\n consola.debug(`Account ${account.label}: failed to refresh usage:`, err)\n }\n }\n\n // ---------- Background refresh -----------------------------------------\n\n /** Refresh Copilot tokens for all non-disabled accounts. */\n async refreshAllTokens(): Promise<void> {\n const targets = this.accounts.filter((a) => a.status !== \"disabled\")\n await Promise.allSettled(targets.map((a) => this.refreshAccountToken(a)))\n }\n\n /** Refresh usage snapshots for all non-disabled accounts. */\n async refreshAllUsage(): Promise<void> {\n const targets = this.accounts.filter((a) => a.status !== \"disabled\")\n await Promise.allSettled(targets.map((a) => this.refreshAccountUsage(a)))\n }\n\n /**\n * Start periodic background refresh loops.\n *\n * The initial token refresh is awaited to ensure accounts are ready before\n * the first request arrives. Usage refresh runs in the background.\n *\n * @param tokenIntervalMs Token refresh interval (default 25 min).\n * @param usageIntervalMs Usage refresh interval (default 5 min).\n */\n async startBackgroundRefresh(\n tokenIntervalMs: number = 25 * 60 * 1000,\n usageIntervalMs: number = 5 * 60 * 1000,\n ): Promise<void> {\n this.stopBackgroundRefresh()\n\n // Initial refresh — await token refresh so accounts are ready for requests\n await this.refreshAllTokens()\n void this.refreshAllUsage()\n\n this.refreshInterval = setInterval(() => {\n void this.refreshAllTokens()\n }, tokenIntervalMs)\n\n this.usageInterval = setInterval(() => {\n void this.refreshAllUsage()\n }, usageIntervalMs)\n\n consola.info(\n `Background refresh started (tokens: ${tokenIntervalMs / 60_000}m, usage: ${usageIntervalMs / 60_000}m)`,\n )\n }\n\n stopBackgroundRefresh(): void {\n if (this.refreshInterval) {\n clearInterval(this.refreshInterval)\n this.refreshInterval = undefined\n }\n if (this.usageInterval) {\n clearInterval(this.usageInterval)\n this.usageInterval = undefined\n }\n }\n\n // ---------- Helpers ----------------------------------------------------\n\n get accountCount(): number {\n return this.accounts.length\n }\n\n get activeAccountCount(): number {\n return this.accounts.filter((a) => a.status === \"active\").length\n }\n\n hasAccounts(): boolean {\n return this.accounts.length > 0\n }\n\n // ---------- Legacy migration -------------------------------------------\n\n /**\n * Create an Account entry from the legacy single-account global state.\n * Useful for seamless upgrade from single-account to multi-account mode.\n *\n * Falls back to creating a minimal entry if API validation fails — the\n * background refresh will fill in the missing data later.\n */\n async migrateFromLegacy(\n githubToken: string,\n accountType: string,\n ): Promise<Account> {\n // Check if this token is already registered\n const existing = this.accounts.find((a) => a.githubToken === githubToken)\n if (existing) {\n consola.info(\"Legacy account already migrated, skipping\")\n return existing\n }\n\n try {\n const account = await this.addAccount(\n githubToken,\n \"Primary (migrated)\",\n accountType,\n )\n consola.success(\"Legacy single-account migrated to multi-account manager\")\n return account\n } catch (error) {\n // API validation failed — create a minimal entry anyway so the account\n // is visible in the management UI. Background token/usage refresh will\n // fill in the missing data.\n consola.warn(\n \"Could not fully validate legacy account, adding with limited info:\",\n error,\n )\n\n const account: Account = {\n id: randomUUID(),\n label: \"Primary (migrated)\",\n githubToken,\n copilotToken: undefined,\n accountType,\n status: \"active\",\n consecutiveFailures: 0,\n addedAt: Date.now(),\n }\n\n this.accounts.push(account)\n await this.saveAccounts()\n return account\n }\n }\n}\n\n// ---------------------------------------------------------------------------\n// Singleton\n// ---------------------------------------------------------------------------\n\nexport const accountManager = new AccountManager()\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 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\"\nimport { rootCause } from \"./utils\"\n\nconst readGithubToken = () => fs.readFile(PATHS.GITHUB_TOKEN_PATH, \"utf8\")\n\nconst writeGithubToken = (token: string) =>\n fs.writeFile(PATHS.GITHUB_TOKEN_PATH, token, { mode: 0o600 })\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\n/** Handle to the single-account Copilot token refresh interval. */\nlet copilotTokenRefreshTimer: ReturnType<typeof setInterval> | undefined\n\n/**\n * Stop the single-account Copilot token refresh interval.\n * Called when multi-account mode is activated to avoid duplicate refreshes.\n */\nexport function stopCopilotTokenRefresh(): void {\n if (copilotTokenRefreshTimer) {\n clearInterval(copilotTokenRefreshTimer)\n copilotTokenRefreshTimer = undefined\n consola.debug(\n \"Single-account Copilot token refresh stopped (multi-account active)\",\n )\n }\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 = Math.max((refresh_in - 60) * 1000, 60_000)\n copilotTokenRefreshTimer = setInterval(async () => {\n consola.debug(\"Refreshing Copilot token\")\n try {\n await refreshCopilotToken()\n } catch (error) {\n consola.warn(`Failed to refresh Copilot token: ${rootCause(error)}`)\n consola.debug(\"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.warn(`Failed to get GitHub token: ${rootCause(error)}`)\n consola.debug(\"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 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"token-D_wTNufW.js","names":["ACCOUNTS_PATH"],"sources":["../src/lib/paths.ts","../src/services/copilot/get-models.ts","../src/services/get-vscode-version.ts","../src/lib/utils.ts","../src/services/github/get-copilot-token.ts","../src/services/github/get-copilot-usage.ts","../src/lib/account-manager.ts","../src/services/github/get-device-code.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\nconst ACCOUNTS_PATH = path.join(APP_DIR, \"accounts.json\")\n\nexport const PATHS = {\n APP_DIR,\n DATA_DIR: APP_DIR,\n GITHUB_TOKEN_PATH,\n ACCOUNTS_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 { accountManager } from \"~/lib/account-manager\"\nimport {\n copilotBaseUrl,\n copilotHeaders,\n type TokenSource,\n} from \"~/lib/api-config\"\nimport { HTTPError } from \"~/lib/error\"\nimport { state } from \"~/lib/state\"\n\nexport const getModels = async () => {\n // In multi-account mode, use the active account's token\n let source: TokenSource = state\n if (state.multiAccountEnabled && accountManager.hasAccounts()) {\n const account = accountManager.getActiveAccount()\n if (account?.copilotToken) {\n source = {\n copilotToken: account.copilotToken,\n copilotApiEndpoint: account.copilotApiEndpoint,\n accountType: account.accountType,\n githubToken: account.githubToken,\n vsCodeVersion: state.vsCodeVersion,\n }\n }\n }\n\n const url = `${copilotBaseUrl(source)}/models`\n\n const response = await fetch(url, {\n headers: copilotHeaders(source),\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 // Thinking/reasoning capabilities\n max_thinking_budget?: number\n min_thinking_budget?: number\n adaptive_thinking?: 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","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\n/**\n * Extract the root-cause message from an unknown thrown value.\n *\n * If the error wraps another error via the standard `cause` property, the\n * inner message is returned instead — giving a one-line summary of *why*\n * the operation failed without the noise of the full stack trace.\n */\nexport function rootCause(err: unknown): string {\n if (err instanceof Error) {\n return err.cause instanceof Error ? err.cause.message : err.message\n }\n return String(err)\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\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 { GITHUB_API_BASE_URL, githubHeaders } from \"~/lib/api-config\"\nimport { HTTPError } from \"~/lib/error\"\nimport { state } from \"~/lib/state\"\n\n/**\n * Fetch a short-lived Copilot JWT from the GitHub API.\n *\n * @param githubToken Optional explicit GitHub PAT. When provided the request\n * uses this token instead of `state.githubToken` and does\n * **not** mutate global state (so multi-account callers\n * stay side-effect-free).\n */\nexport const getCopilotToken = async (githubToken?: string) => {\n const tokenToUse = githubToken ?? state.githubToken\n const isExplicitToken = githubToken !== undefined\n\n const url = `${GITHUB_API_BASE_URL}/copilot_internal/v2/token`\n const fetchOptions: RequestInit = {\n headers: githubHeaders({\n ...state,\n githubToken: tokenToUse,\n }),\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 // Only write to global state when using the default token (single-account mode).\n // When an explicit githubToken is provided (multi-account), the caller is\n // responsible for storing the endpoint on its own Account object.\n if (!isExplicitToken && 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 { GITHUB_API_BASE_URL, githubHeaders } from \"~/lib/api-config\"\nimport { HTTPError } from \"~/lib/error\"\nimport { state } from \"~/lib/state\"\n\n/**\n * Fetch the authenticated user's Copilot usage / quota snapshot.\n *\n * @param githubToken Optional explicit GitHub PAT. When provided the request\n * uses this token instead of `state.githubToken`, allowing\n * multi-account callers to query any account without\n * touching global state.\n */\nexport const getCopilotUsage = async (\n githubToken?: string,\n): Promise<CopilotUsageResponse> => {\n const response = await fetch(`${GITHUB_API_BASE_URL}/copilot_internal/user`, {\n headers: githubHeaders({\n ...state,\n githubToken: githubToken ?? state.githubToken,\n }),\n })\n\n if (!response.ok) {\n throw new HTTPError(\"Failed to get Copilot usage\", response)\n }\n\n return (await response.json()) as CopilotUsageResponse\n}\n\nexport interface QuotaDetail {\n entitlement: number\n overage_count: number\n overage_permitted: boolean\n percent_remaining: number\n quota_id: string\n quota_remaining: number\n remaining: number\n unlimited: boolean\n}\n\ninterface QuotaSnapshots {\n chat: QuotaDetail\n completions: QuotaDetail\n premium_interactions: QuotaDetail\n}\n\ninterface CopilotUsageResponse {\n access_type_sku: string\n analytics_tracking_id: string\n assigned_date: string\n can_signup_for_limited: boolean\n chat_enabled: boolean\n copilot_plan: string\n organization_login_list: Array<unknown>\n organization_list: Array<unknown>\n quota_reset_date: string\n quota_snapshots: QuotaSnapshots\n}\n","import consola from \"consola\"\nimport { randomUUID } from \"node:crypto\"\nimport fs from \"node:fs/promises\"\n\nimport { HTTPError } from \"~/lib/error\"\nimport { PATHS } from \"~/lib/paths\"\nimport { rootCause } from \"~/lib/utils\"\nimport { getCopilotToken } from \"~/services/github/get-copilot-token\"\nimport { getCopilotUsage } from \"~/services/github/get-copilot-usage\"\nimport { getGitHubUser } from \"~/services/github/get-user\"\n\n// ---------------------------------------------------------------------------\n// Types\n// ---------------------------------------------------------------------------\n\nexport type AccountStatus =\n | \"active\"\n | \"exhausted\"\n | \"rate_limited\"\n | \"banned\"\n | \"error\"\n | \"disabled\"\n\nexport interface Account {\n id: string\n label: string\n githubToken: string\n /** Short-lived Copilot JWT – kept in memory only, never persisted. */\n copilotToken?: string\n /** API endpoint extracted from the Copilot token response. */\n copilotApiEndpoint?: string\n accountType: string // \"individual\" | \"business\" | \"enterprise\"\n\n // Status\n status: AccountStatus\n statusMessage?: string\n lastUsedAt?: number\n consecutiveFailures: number\n cooldownUntil?: number\n\n // Quota snapshot (refreshed periodically)\n usage?: {\n premium_remaining: number\n premium_total: number\n chat_remaining: number\n chat_total: number\n quotaResetDate: string\n lastCheckedAt: number\n }\n\n // Metadata\n githubLogin?: string\n addedAt: number\n}\n\n// ---------------------------------------------------------------------------\n// Persistence helpers\n// ---------------------------------------------------------------------------\n\n/** Fields excluded from the JSON file (short-lived / runtime-only). */\ntype PersistedAccount = Omit<Account, \"copilotToken\">\n\nconst ACCOUNTS_PATH = PATHS.ACCOUNTS_PATH\n\n// ---------------------------------------------------------------------------\n// AccountManager\n// ---------------------------------------------------------------------------\n\nconst COOLDOWN_MS = 60 * 1000 // 60 seconds\n\nexport class AccountManager {\n private accounts: Array<Account> = []\n private refreshInterval?: ReturnType<typeof setInterval>\n private usageInterval?: ReturnType<typeof setInterval>\n\n // Debounced save\n private saveTimer?: ReturnType<typeof setTimeout>\n private savePending = false\n\n // ---------- Persistence ------------------------------------------------\n\n /**\n * Load accounts from the JSON file on disk.\n * Missing file is treated as empty list – not an error.\n */\n async loadAccounts(): Promise<void> {\n try {\n // eslint-disable-next-line unicorn/prefer-json-parse-buffer\n const raw = await fs.readFile(ACCOUNTS_PATH, \"utf8\")\n const parsed = JSON.parse(raw) as Array<PersistedAccount>\n this.accounts = parsed.map((a) => ({ ...a, copilotToken: undefined }))\n consola.info(`Loaded ${this.accounts.length} account(s) from disk`)\n } catch (err: unknown) {\n if ((err as NodeJS.ErrnoException).code === \"ENOENT\") {\n this.accounts = []\n return\n }\n consola.warn(`Failed to load accounts: ${rootCause(err)}`)\n consola.debug(\"Failed to load accounts:\", err)\n this.accounts = []\n }\n }\n\n /**\n * Persist accounts to disk. `copilotToken` is excluded because it is\n * short-lived and will be refreshed on every startup.\n */\n async saveAccounts(): Promise<void> {\n const data: Array<PersistedAccount> = this.accounts.map(\n ({ copilotToken: _dropped, ...rest }) => rest,\n )\n try {\n await fs.writeFile(ACCOUNTS_PATH, JSON.stringify(data, null, 2), {\n encoding: \"utf8\",\n mode: 0o600,\n })\n } catch (err) {\n consola.warn(`Failed to save accounts: ${rootCause(err)}`)\n consola.debug(\"Failed to save accounts:\", err)\n }\n }\n\n /** Schedule a debounced save (coalesces rapid status updates). */\n private debouncedSave(): void {\n if (this.saveTimer) return // already scheduled\n this.savePending = true\n this.saveTimer = setTimeout(async () => {\n this.saveTimer = undefined\n if (this.savePending) {\n this.savePending = false\n await this.saveAccounts()\n }\n }, 1_000)\n }\n\n // ---------- CRUD -------------------------------------------------------\n\n /**\n * Add a new account.\n *\n * 1. Validates the GitHub token by fetching user info.\n * 2. Obtains an initial Copilot token.\n * 3. Persists the account to disk.\n */\n async addAccount(\n githubToken: string,\n label: string,\n accountType: string = \"individual\",\n ): Promise<Account> {\n // 1. Validate token – get GitHub login (pass token explicitly, no state mutation)\n const user = await getGitHubUser(githubToken)\n\n // 2. Obtain Copilot token – pass token directly (no state mutation)\n const tokenData = await getCopilotToken(githubToken)\n\n const account: Account = {\n id: randomUUID(),\n label,\n githubToken,\n copilotToken: tokenData.token,\n copilotApiEndpoint: tokenData.endpoints?.api,\n accountType,\n status: \"active\",\n consecutiveFailures: 0,\n githubLogin: user.login,\n addedAt: Date.now(),\n }\n\n this.accounts.push(account)\n await this.saveAccounts()\n\n consola.success(`Account added: ${label} (${user.login})`)\n return account\n }\n\n async removeAccount(id: string): Promise<boolean> {\n const idx = this.accounts.findIndex((a) => a.id === id)\n if (idx === -1) return false\n const [removed] = this.accounts.splice(idx, 1)\n await this.saveAccounts()\n consola.info(`Account removed: ${removed.label}`)\n return true\n }\n\n getAccounts(): Array<Account> {\n return this.accounts\n }\n\n getAccountById(id: string): Account | undefined {\n return this.accounts.find((a) => a.id === id)\n }\n\n // ---------- Smart account selection ------------------------------------\n\n /**\n * Pick the best available account.\n *\n * 1. Filter out disabled, banned, exhausted, and accounts still in cooldown.\n * 2. Prefer accounts with more remaining premium quota.\n * 3. Fall back to round-robin (least-recently-used) when quotas are equal\n * or unknown.\n */\n getActiveAccount(): Account | undefined {\n const now = Date.now()\n\n const eligible = this.accounts.filter((a) => {\n if (\n a.status === \"disabled\"\n || a.status === \"banned\"\n || a.status === \"exhausted\"\n ) {\n return false\n }\n if (a.cooldownUntil && a.cooldownUntil > now) return false\n return true\n })\n\n if (eligible.length === 0) {\n // Fallback: if there is exactly one account and it's only cooling down\n // (not banned/disabled/exhausted), return it anyway. With a single\n // account there is nothing to \"switch to\", so blocking all requests\n // for the cooldown period would be a self-inflicted outage.\n if (this.accounts.length === 1) {\n const solo = this.accounts[0]\n if (\n solo.status !== \"disabled\"\n && solo.status !== \"banned\"\n && solo.status !== \"exhausted\"\n ) {\n return solo\n }\n }\n return undefined\n }\n\n eligible.sort((a, b) => {\n const aRemaining = a.usage?.premium_remaining ?? -1\n const bRemaining = b.usage?.premium_remaining ?? -1\n\n // Higher remaining quota first\n if (aRemaining !== bRemaining) return bRemaining - aRemaining\n\n // Equal / unknown → least recently used first (round-robin)\n const aUsed = a.lastUsedAt ?? 0\n const bUsed = b.lastUsedAt ?? 0\n return aUsed - bUsed\n })\n\n return eligible[0]\n }\n\n // ---------- Status management ------------------------------------------\n\n markAccountStatus(id: string, status: AccountStatus, message?: string): void {\n const account = this.getAccountById(id)\n if (!account) return\n\n account.status = status\n account.statusMessage = message\n\n switch (status) {\n case \"rate_limited\":\n case \"error\": {\n // Only apply cooldown when there are other accounts to switch to.\n // With a single account, cooldown would block ALL requests with\n // \"No available accounts\" — effectively a self-inflicted outage.\n if (this.accounts.length > 1) {\n account.cooldownUntil = Date.now() + COOLDOWN_MS\n }\n account.consecutiveFailures += 1\n\n break\n }\n case \"banned\": {\n account.consecutiveFailures += 1\n\n break\n }\n case \"active\": {\n // Recovering from a failure state — reset\n account.consecutiveFailures = 0\n account.cooldownUntil = undefined\n\n break\n }\n // No default\n }\n // \"exhausted\" and \"disabled\" don't touch consecutiveFailures\n\n this.debouncedSave()\n }\n\n markAccountSuccess(id: string): void {\n const account = this.getAccountById(id)\n if (!account) return\n\n account.consecutiveFailures = 0\n account.lastUsedAt = Date.now()\n\n if (account.status === \"error\" || account.status === \"rate_limited\") {\n account.status = \"active\"\n account.statusMessage = undefined\n account.cooldownUntil = undefined\n }\n\n this.debouncedSave()\n }\n\n // ---------- Token & usage refresh (per account) ------------------------\n\n /**\n * Refresh the short-lived Copilot JWT for a single account.\n *\n * Passes the account's GitHub token directly to `getCopilotToken()` so that\n * global `state.githubToken` is never mutated — safe for concurrent use.\n */\n async refreshAccountToken(account: Account): Promise<void> {\n try {\n const data = await getCopilotToken(account.githubToken)\n // eslint-disable-next-line require-atomic-updates\n account.copilotToken = data.token\n if (data.endpoints?.api) {\n // eslint-disable-next-line require-atomic-updates\n account.copilotApiEndpoint = data.endpoints.api\n }\n } catch (err: unknown) {\n if (err instanceof HTTPError && err.response.status === 401) {\n this.markAccountStatus(account.id, \"banned\", \"GitHub token invalid\")\n consola.warn(\n `Account ${account.label}: token invalid, marked as banned`,\n )\n } else {\n consola.error(\n `Account ${account.label}: failed to refresh Copilot token:`,\n err,\n )\n }\n }\n }\n\n /**\n * Refresh the usage / quota snapshot for a single account.\n *\n * Passes the account's GitHub token directly to `getCopilotUsage()` so that\n * global `state.githubToken` is never mutated — safe for concurrent use.\n */\n async refreshAccountUsage(account: Account): Promise<void> {\n try {\n const data = await getCopilotUsage(account.githubToken)\n const snap = data.quota_snapshots\n\n // eslint-disable-next-line require-atomic-updates\n account.usage = {\n premium_remaining: snap.premium_interactions.remaining,\n premium_total: snap.premium_interactions.entitlement,\n chat_remaining: snap.chat.remaining,\n chat_total: snap.chat.entitlement,\n quotaResetDate: data.quota_reset_date,\n lastCheckedAt: Date.now(),\n }\n\n // Transition between active ↔ exhausted\n if (account.usage.premium_remaining <= 0 && account.status === \"active\") {\n this.markAccountStatus(\n account.id,\n \"exhausted\",\n \"Premium quota exhausted\",\n )\n } else if (\n account.usage.premium_remaining > 0\n && account.status === \"exhausted\"\n ) {\n account.status = \"active\"\n account.statusMessage = undefined\n account.consecutiveFailures = 0\n this.debouncedSave()\n }\n } catch (err) {\n consola.warn(\n `Account ${account.label}: failed to refresh usage: ${rootCause(err)}`,\n )\n consola.debug(`Account ${account.label}: failed to refresh usage:`, err)\n }\n }\n\n // ---------- Background refresh -----------------------------------------\n\n /** Refresh Copilot tokens for all non-disabled accounts. */\n async refreshAllTokens(): Promise<void> {\n const targets = this.accounts.filter((a) => a.status !== \"disabled\")\n await Promise.allSettled(targets.map((a) => this.refreshAccountToken(a)))\n }\n\n /** Refresh usage snapshots for all non-disabled accounts. */\n async refreshAllUsage(): Promise<void> {\n const targets = this.accounts.filter((a) => a.status !== \"disabled\")\n await Promise.allSettled(targets.map((a) => this.refreshAccountUsage(a)))\n }\n\n /**\n * Start periodic background refresh loops.\n *\n * The initial token refresh is awaited to ensure accounts are ready before\n * the first request arrives. Usage refresh runs in the background.\n *\n * @param tokenIntervalMs Token refresh interval (default 25 min).\n * @param usageIntervalMs Usage refresh interval (default 5 min).\n */\n async startBackgroundRefresh(\n tokenIntervalMs: number = 25 * 60 * 1000,\n usageIntervalMs: number = 5 * 60 * 1000,\n ): Promise<void> {\n this.stopBackgroundRefresh()\n\n // Initial refresh — await token refresh so accounts are ready for requests\n await this.refreshAllTokens()\n void this.refreshAllUsage()\n\n this.refreshInterval = setInterval(() => {\n void this.refreshAllTokens()\n }, tokenIntervalMs)\n\n this.usageInterval = setInterval(() => {\n void this.refreshAllUsage()\n }, usageIntervalMs)\n\n consola.info(\n `Background refresh started (tokens: ${tokenIntervalMs / 60_000}m, usage: ${usageIntervalMs / 60_000}m)`,\n )\n }\n\n stopBackgroundRefresh(): void {\n if (this.refreshInterval) {\n clearInterval(this.refreshInterval)\n this.refreshInterval = undefined\n }\n if (this.usageInterval) {\n clearInterval(this.usageInterval)\n this.usageInterval = undefined\n }\n }\n\n // ---------- Helpers ----------------------------------------------------\n\n get accountCount(): number {\n return this.accounts.length\n }\n\n get activeAccountCount(): number {\n return this.accounts.filter((a) => a.status === \"active\").length\n }\n\n hasAccounts(): boolean {\n return this.accounts.length > 0\n }\n\n // ---------- Legacy migration -------------------------------------------\n\n /**\n * Create an Account entry from the legacy single-account global state.\n * Useful for seamless upgrade from single-account to multi-account mode.\n *\n * Falls back to creating a minimal entry if API validation fails — the\n * background refresh will fill in the missing data later.\n */\n async migrateFromLegacy(\n githubToken: string,\n accountType: string,\n ): Promise<Account> {\n // Check if this token is already registered\n const existing = this.accounts.find((a) => a.githubToken === githubToken)\n if (existing) {\n consola.info(\"Legacy account already migrated, skipping\")\n return existing\n }\n\n try {\n const account = await this.addAccount(\n githubToken,\n \"Primary (migrated)\",\n accountType,\n )\n consola.success(\"Legacy single-account migrated to multi-account manager\")\n return account\n } catch (error) {\n // API validation failed — create a minimal entry anyway so the account\n // is visible in the management UI. Background token/usage refresh will\n // fill in the missing data.\n consola.warn(\n \"Could not fully validate legacy account, adding with limited info:\",\n error,\n )\n\n const account: Account = {\n id: randomUUID(),\n label: \"Primary (migrated)\",\n githubToken,\n copilotToken: undefined,\n accountType,\n status: \"active\",\n consecutiveFailures: 0,\n addedAt: Date.now(),\n }\n\n this.accounts.push(account)\n await this.saveAccounts()\n return account\n }\n }\n}\n\n// ---------------------------------------------------------------------------\n// Singleton\n// ---------------------------------------------------------------------------\n\nexport const accountManager = new AccountManager()\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 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, { mode: 0o600 })\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\n/** Handle to the single-account Copilot token refresh interval. */\nlet copilotTokenRefreshTimer: ReturnType<typeof setInterval> | undefined\n\n/**\n * Stop the single-account Copilot token refresh interval.\n * Called when multi-account mode is activated to avoid duplicate refreshes.\n */\nexport function stopCopilotTokenRefresh(): void {\n if (copilotTokenRefreshTimer) {\n clearInterval(copilotTokenRefreshTimer)\n copilotTokenRefreshTimer = undefined\n consola.debug(\n \"Single-account Copilot token refresh stopped (multi-account active)\",\n )\n }\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 = Math.max((refresh_in - 60) * 1000, 60_000)\n copilotTokenRefreshTimer = 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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"}
|