/** * Account Storage * * Handles loading and saving account configuration to disk. */ import { readFile, writeFile, mkdir, access } from 'fs/promises'; import { constants as fsConstants } from 'fs'; import { dirname } from 'path'; import { ACCOUNT_CONFIG_PATH } from '../constants.js'; import { getAuthStatus } from '../auth/database.js'; import { logger } from '../utils/logger.js'; /** * Load accounts from the config file * * @param {string} configPath - Path to the config file * @returns {Promise<{accounts: Array, settings: Object, activeIndex: number}>} */ export async function loadAccounts(configPath = ACCOUNT_CONFIG_PATH) { try { // Check if config file exists using async access await access(configPath, fsConstants.F_OK); const configData = await readFile(configPath, 'utf-8'); const config = JSON.parse(configData); const accounts = (config.accounts || []).map(acc => ({ ...acc, lastUsed: acc.lastUsed || null, enabled: acc.enabled !== false, // Default to true if not specified // Reset invalid flag on startup - give accounts a fresh chance to refresh isInvalid: false, invalidReason: null, modelRateLimits: acc.modelRateLimits || {}, // New fields for subscription and quota tracking subscription: acc.subscription || { tier: 'unknown', projectId: null, detectedAt: null }, quota: acc.quota || { models: {}, lastChecked: null }, // Quota threshold settings (per-account and per-model overrides) quotaThreshold: acc.quotaThreshold, // undefined means use global modelQuotaThresholds: acc.modelQuotaThresholds || {} })); const settings = config.settings || {}; let activeIndex = config.activeIndex || 0; // Clamp activeIndex to valid range if (activeIndex >= accounts.length) { activeIndex = 0; } logger.info(`[AccountManager] Loaded ${accounts.length} account(s) from config`); return { accounts, settings, activeIndex }; } catch (error) { if (error.code === 'ENOENT') { // No config file - return empty logger.info('[AccountManager] No config file found. Using Antigravity database (single account mode)'); } else { logger.error('[AccountManager] Failed to load config:', error.message); } return { accounts: [], settings: {}, activeIndex: 0 }; } } /** * Load the default account from Antigravity's database * * @param {string} dbPath - Optional path to the database * @returns {{accounts: Array, tokenCache: Map}} */ export function loadDefaultAccount(dbPath) { try { const authData = getAuthStatus(dbPath); if (authData?.apiKey) { const account = { email: authData.email || 'default@antigravity', source: 'database', lastUsed: null, modelRateLimits: {} }; const tokenCache = new Map(); tokenCache.set(account.email, { token: authData.apiKey, extractedAt: Date.now() }); logger.info(`[AccountManager] Loaded default account: ${account.email}`); return { accounts: [account], tokenCache }; } } catch (error) { logger.error('[AccountManager] Failed to load default account:', error.message); } return { accounts: [], tokenCache: new Map() }; } /** * Save account configuration to disk * * @param {string} configPath - Path to the config file * @param {Array} accounts - Array of account objects * @param {Object} settings - Settings object * @param {number} activeIndex - Current active account index */ export async function saveAccounts(configPath, accounts, settings, activeIndex) { try { // Ensure directory exists const dir = dirname(configPath); await mkdir(dir, { recursive: true }); const config = { accounts: accounts.map(acc => ({ email: acc.email, source: acc.source, enabled: acc.enabled !== false, // Persist enabled state dbPath: acc.dbPath || null, refreshToken: acc.source === 'oauth' ? acc.refreshToken : undefined, apiKey: acc.source === 'manual' ? acc.apiKey : undefined, projectId: acc.projectId || undefined, addedAt: acc.addedAt || undefined, isInvalid: acc.isInvalid || false, invalidReason: acc.invalidReason || null, modelRateLimits: acc.modelRateLimits || {}, lastUsed: acc.lastUsed, // Persist subscription and quota data subscription: acc.subscription || { tier: 'unknown', projectId: null, detectedAt: null }, quota: acc.quota || { models: {}, lastChecked: null }, // Persist quota threshold settings quotaThreshold: acc.quotaThreshold, // undefined omitted from JSON modelQuotaThresholds: Object.keys(acc.modelQuotaThresholds || {}).length > 0 ? acc.modelQuotaThresholds : undefined })), settings: settings, activeIndex: activeIndex }; await writeFile(configPath, JSON.stringify(config, null, 2)); } catch (error) { logger.error('[AccountManager] Failed to save config:', error.message); } }