From 1e9a7f3fa01e40ab569932e66ce37f8653bf8c97 Mon Sep 17 00:00:00 2001 From: Tam Nhu Tran Date: Thu, 30 Apr 2026 14:03:48 -0400 Subject: [PATCH 1/2] refactor(config): add config-loader-facade with memoization Single import path for all config loading. Re-exports all 26 functions from unified-config-loader and 4 from config-manager. Adds memoization for loadOrCreateUnifiedConfig via getCachedConfig() with automatic cache invalidation on write operations (mutateConfig, updateConfig). Pure structural refactor -- no existing imports modified. --- .../__tests__/config-loader-facade.test.ts | 191 ++++++++++++++++++ src/config/config-loader-facade.ts | 120 +++++++++++ 2 files changed, 311 insertions(+) create mode 100644 src/config/__tests__/config-loader-facade.test.ts create mode 100644 src/config/config-loader-facade.ts diff --git a/src/config/__tests__/config-loader-facade.test.ts b/src/config/__tests__/config-loader-facade.test.ts new file mode 100644 index 00000000..f780bcc8 --- /dev/null +++ b/src/config/__tests__/config-loader-facade.test.ts @@ -0,0 +1,191 @@ +/** + * Config Loader Facade Unit Tests + * + * Tests memoization cache behavior, cache invalidation on write ops, + * and verifies all re-exports are present from underlying modules. + */ + +import { afterEach, beforeEach, describe, expect, it } from 'bun:test'; +import * as fs from 'fs'; +import * as os from 'os'; +import * as path from 'path'; +import * as yaml from 'js-yaml'; + +/** + * Helper: create a temp home dir with a minimal valid config.yaml so + * loadOrCreateUnifiedConfig succeeds without touching the real ~/.ccs. + */ +function createTestHome(): string { + const tempHome = fs.mkdtempSync(path.join(os.tmpdir(), 'ccs-facade-test-')); + const ccsDir = path.join(tempHome, '.ccs'); + fs.mkdirSync(ccsDir, { recursive: true }); + const configPath = path.join(ccsDir, 'config.yaml'); + fs.writeFileSync(configPath, `version: 1\n`, 'utf8'); + return tempHome; +} + +/** + * Helper: get the facade module, bypassing the import cache each time. + * We use dynamic import with a cache-busting query param so beforeEach + * re-imports get a fresh module with a clean cache state. + */ +async function importFacade(): Promise { + return import(`../config-loader-facade?cachebust=${Date.now()}`); +} + +describe('config-loader-facade', () => { + let tempHome: string; + let originalCcsHome: string | undefined; + + beforeEach(() => { + tempHome = createTestHome(); + originalCcsHome = process.env.CCS_HOME; + process.env.CCS_HOME = tempHome; + }); + + afterEach(() => { + // Restore env + if (originalCcsHome !== undefined) { + process.env.CCS_HOME = originalCcsHome; + } else { + delete process.env.CCS_HOME; + } + + // Clean up temp dir + if (tempHome && fs.existsSync(tempHome)) { + fs.rmSync(tempHome, { recursive: true, force: true }); + } + }); + + describe('re-exports from unified-config-loader', () => { + it('should export all core loader functions', async () => { + const facade = await importFacade(); + + expect(typeof facade.loadUnifiedConfig).toBe('function'); + expect(typeof facade.loadOrCreateUnifiedConfig).toBe('function'); + expect(typeof facade.saveUnifiedConfig).toBe('function'); + expect(typeof facade.mutateUnifiedConfig).toBe('function'); + expect(typeof facade.updateUnifiedConfig).toBe('function'); + }); + + it('should export all path/format utilities', async () => { + const facade = await importFacade(); + + expect(typeof facade.getConfigYamlPath).toBe('function'); + expect(typeof facade.getConfigJsonPath).toBe('function'); + expect(typeof facade.hasUnifiedConfig).toBe('function'); + expect(typeof facade.hasLegacyConfig).toBe('function'); + expect(typeof facade.getConfigFormat).toBe('function'); + expect(typeof facade.isUnifiedMode).toBe('function'); + }); + + it('should export all profile getters', async () => { + const facade = await importFacade(); + + expect(typeof facade.getDefaultProfile).toBe('function'); + expect(typeof facade.setDefaultProfile).toBe('function'); + }); + + it('should export all section getters', async () => { + const facade = await importFacade(); + + expect(typeof facade.getWebSearchConfig).toBe('function'); + expect(typeof facade.getGlobalEnvConfig).toBe('function'); + expect(typeof facade.getContinuityInheritanceMap).toBe('function'); + expect(typeof facade.getCliproxySafetyConfig).toBe('function'); + expect(typeof facade.getThinkingConfig).toBe('function'); + expect(typeof facade.getOfficialChannelsConfig).toBe('function'); + expect(typeof facade.isDashboardAuthEnabled).toBe('function'); + expect(typeof facade.getDashboardAuthConfig).toBe('function'); + expect(typeof facade.getBrowserConfig).toBe('function'); + expect(typeof facade.getImageAnalysisConfig).toBe('function'); + expect(typeof facade.getLoggingConfig).toBe('function'); + expect(typeof facade.getCursorConfig).toBe('function'); + }); + }); + + describe('re-exports from config-manager', () => { + it('should export loadSettings, loadConfigSafe, readConfig, getCcsDir', async () => { + const facade = await importFacade(); + + expect(typeof facade.loadSettings).toBe('function'); + expect(typeof facade.loadConfigSafe).toBe('function'); + expect(typeof facade.readConfig).toBe('function'); + expect(typeof facade.getCcsDir).toBe('function'); + }); + }); + + describe('memoization', () => { + it('getCachedConfig returns same object on repeated calls', async () => { + const facade = await importFacade(); + + const first = facade.getCachedConfig(); + const second = facade.getCachedConfig(); + + // Same reference (cached, not re-read) + expect(first).toBe(second); + }); + + it('invalidateConfigCache forces re-read on next getCachedConfig', async () => { + const facade = await importFacade(); + + const first = facade.getCachedConfig(); + facade.invalidateConfigCache(); + const second = facade.getCachedConfig(); + + // Different reference after invalidation + expect(first).not.toBe(second); + // But same content + expect(first.version).toBe(second.version); + }); + + it('saveConfig updates cache and does not invalidate', async () => { + const facade = await importFacade(); + + const config = facade.getCachedConfig(); + config.default = 'test-profile'; + facade.saveConfig(config); + + const cached = facade.getCachedConfig(); + // Cache should hold the just-saved config (no re-read) + expect(cached).toBe(config); + expect(cached.default).toBe('test-profile'); + }); + + it('mutateConfig invalidates the cache', async () => { + const facade = await importFacade(); + + const before = facade.getCachedConfig(); + facade.mutateConfig((cfg) => { + cfg.default = 'mutated-profile'; + }); + const after = facade.getCachedConfig(); + + // Different reference (mutator may change it arbitrarily) + expect(before).not.toBe(after); + expect(after.default).toBe('mutated-profile'); + }); + + it('updateConfig invalidates the cache', async () => { + const facade = await importFacade(); + + const before = facade.getCachedConfig(); + facade.updateConfig({ default: 'updated-profile' }); + const after = facade.getCachedConfig(); + + expect(before).not.toBe(after); + expect(after.default).toBe('updated-profile'); + }); + + it('getCachedConfig returns valid config with expected fields', async () => { + const facade = await importFacade(); + + const config = facade.getCachedConfig(); + expect(config).toBeDefined(); + expect(typeof config.version).toBe('number'); + expect(config.accounts).toBeDefined(); + expect(config.profiles).toBeDefined(); + expect(config.cliproxy).toBeDefined(); + }); + }); +}); diff --git a/src/config/config-loader-facade.ts b/src/config/config-loader-facade.ts new file mode 100644 index 00000000..40b526fc --- /dev/null +++ b/src/config/config-loader-facade.ts @@ -0,0 +1,120 @@ +/** + * Config Loader Facade + * + * Single import path for all config loading operations. + * Re-exports everything from unified-config-loader and config-manager, + * and adds memoization for loadOrCreateUnifiedConfig to reduce file I/O. + * + * Usage: + * import { getCachedConfig, saveConfig, mutateConfig } from '../config/config-loader-facade'; + * import { getCcsDir, loadSettings } from '../config/config-loader-facade'; + */ + +// Re-export all functions from unified-config-loader +export { + loadUnifiedConfig, + loadOrCreateUnifiedConfig, + saveUnifiedConfig, + mutateUnifiedConfig, + updateUnifiedConfig, + getConfigYamlPath, + getConfigJsonPath, + hasUnifiedConfig, + hasLegacyConfig, + getConfigFormat, + isUnifiedMode, + getDefaultProfile, + setDefaultProfile, + getWebSearchConfig, + getGlobalEnvConfig, + getContinuityInheritanceMap, + getCliproxySafetyConfig, + getThinkingConfig, + getOfficialChannelsConfig, + isDashboardAuthEnabled, + getDashboardAuthConfig, + getBrowserConfig, + getImageAnalysisConfig, + getLoggingConfig, + getCursorConfig, +} from './unified-config-loader'; + +// Re-export types from unified-config-loader +export type { GeminiWebSearchInfo } from './unified-config-loader'; + +// Re-export selected functions from config-manager +export { loadSettings, loadConfigSafe, readConfig, getCcsDir } from '../utils/config-manager'; + +// Internal imports for memoization wrappers +import type { UnifiedConfig } from './unified-config-types'; +import { + loadOrCreateUnifiedConfig as _loadOrCreateUnifiedConfig, + saveUnifiedConfig as _saveUnifiedConfig, + mutateUnifiedConfig as _mutateUnifiedConfig, + updateUnifiedConfig as _updateUnifiedConfig, +} from './unified-config-loader'; + +// --------------------------------------------------------------------------- +// Memoization cache +// --------------------------------------------------------------------------- + +let _configCache: UnifiedConfig | null = null; + +/** + * Get the unified config with in-memory caching. + * First call reads from disk; subsequent calls return the cached object. + * + * Call invalidateConfigCache() or use mutateConfig()/updateConfig() + * to force a re-read from disk. + */ +export function getCachedConfig(): UnifiedConfig { + if (!_configCache) { + _configCache = _loadOrCreateUnifiedConfig(); + } + return _configCache; +} + +/** + * Clear the memoization cache. + * The next call to getCachedConfig() will re-read from disk. + */ +export function invalidateConfigCache(): void { + _configCache = null; +} + +/** + * Save config to disk and update the cache to the given object. + * Does NOT invalidate — the provided config IS the new cache value. + */ +export function saveConfig(config: UnifiedConfig): void { + _saveUnifiedConfig(config); + _configCache = config; +} + +/** + * Atomically mutate config (read-modify-write with lock) and invalidate cache. + * After mutation, the next getCachedConfig() call will re-read from disk. + */ +export function mutateConfig(mutator: (config: UnifiedConfig) => void): UnifiedConfig { + const result = _mutateUnifiedConfig(mutator); + _configCache = null; + return result; +} + +/** + * Partial-update config and invalidate cache. + * Shorthand for mutateConfig with Object.assign. + */ +export function updateConfig(updates: Partial): UnifiedConfig { + const result = _updateUnifiedConfig(updates); + _configCache = null; + return result; +} + +/** + * Get the current cache state (for diagnostics/testing). + * Returns true if a cached config exists, false otherwise. + */ +export function hasCachedConfig(): boolean { + return _configCache !== null; +} From 0868e92bb192ff5d584e99fb39216860ced3bc11 Mon Sep 17 00:00:00 2001 From: Tam Nhu Tran Date: Thu, 30 Apr 2026 14:05:33 -0400 Subject: [PATCH 2/2] Revert "Revert "refactor(config): reorganize unified-config-types into schemas directory"" This reverts commit 06bce198eb8b9c6508e7b5fc3339cbda54faf608. --- .../__tests__/schemas-reexport.test.ts | 198 +++ src/config/schemas/auth.ts | 109 ++ src/config/schemas/browser.ts | 71 ++ src/config/schemas/channels.ts | 32 + src/config/schemas/cliproxy.ts | 151 +++ src/config/schemas/copilot-cursor.ts | 93 ++ src/config/schemas/index.ts | 112 ++ src/config/schemas/logging.ts | 53 + src/config/schemas/providers.ts | 30 + src/config/schemas/proxy-server.ts | 193 +++ src/config/schemas/quota.ts | 121 ++ src/config/schemas/thinking.ts | 66 + src/config/schemas/unified-config.ts | 200 +++ src/config/schemas/version.ts | 23 + src/config/schemas/websearch.ts | 148 +++ src/config/unified-config-types.ts | 1120 +---------------- 16 files changed, 1603 insertions(+), 1117 deletions(-) create mode 100644 src/config/schemas/__tests__/schemas-reexport.test.ts create mode 100644 src/config/schemas/auth.ts create mode 100644 src/config/schemas/browser.ts create mode 100644 src/config/schemas/channels.ts create mode 100644 src/config/schemas/cliproxy.ts create mode 100644 src/config/schemas/copilot-cursor.ts create mode 100644 src/config/schemas/index.ts create mode 100644 src/config/schemas/logging.ts create mode 100644 src/config/schemas/providers.ts create mode 100644 src/config/schemas/proxy-server.ts create mode 100644 src/config/schemas/quota.ts create mode 100644 src/config/schemas/thinking.ts create mode 100644 src/config/schemas/unified-config.ts create mode 100644 src/config/schemas/version.ts create mode 100644 src/config/schemas/websearch.ts diff --git a/src/config/schemas/__tests__/schemas-reexport.test.ts b/src/config/schemas/__tests__/schemas-reexport.test.ts new file mode 100644 index 00000000..5bdda5d9 --- /dev/null +++ b/src/config/schemas/__tests__/schemas-reexport.test.ts @@ -0,0 +1,198 @@ +/** + * Tests: config schemas re-export backward compatibility. + * + * Verifies that every type, interface, constant, and function originally + * exported from unified-config-types.ts is still accessible via both + * the barrel file and the schemas/index barrel. + */ + +import { describe, it, expect } from 'bun:test'; + +// Import from the backward-compatible barrel (this is what all existing code uses) +import * as barrel from '../../unified-config-types'; + +// Import from the new schemas barrel (this is what the barrel delegates to) +import * as schemas from '../index'; + +// --------------------------------------------------------------------------- +// Type-level checks (compile-time, not runtime) +// --------------------------------------------------------------------------- + +// Verify key interfaces are accessible as types +import type { + UnifiedConfig, + AccountConfig, + ProfileConfig, + OAuthAccounts, + CLIProxyAuthConfig, + TokenRefreshSettings, + DashboardAuthConfig, + CLIProxyVariantConfig, + CompositeTierConfig, + CompositeVariantConfig, + CLIProxyLoggingConfig, + CLIProxySafetyConfig, + CLIProxyRoutingConfig, + CLIProxyConfig, + AutoQuotaConfig, + RuntimeMonitorConfig, + ManualQuotaConfig, + QuotaManagementMode, + QuotaManagementConfig, + ThinkingMode, + ThinkingTierDefaults, + ThinkingConfig, + OfficialChannelId, + OfficialChannelsConfig, + DuckDuckGoWebSearchConfig, + BraveWebSearchConfig, + ExaWebSearchConfig, + TavilyWebSearchConfig, + SearxngWebSearchConfig, + GeminiWebSearchConfig, + GrokWebSearchConfig, + OpenCodeWebSearchConfig, + WebSearchProvidersConfig, + WebSearchConfig, + BrowserToolPolicy, + BrowserEvalMode, + BrowserClaudeConfig, + BrowserCodexConfig, + BrowserConfig, + LoggingLevel, + LoggingConfig, + PreferencesConfig, + CopilotAccountType, + CopilotConfig, + CursorConfig, + ProxyRemoteConfig, + ProxyFallbackConfig, + ProxyLocalConfig, + OpenAICompatProxyRoutingConfig, + OpenAICompatProxyConfig, + CliproxyServerConfig, + GlobalEnvConfig, + ContinuityConfig, + ImageAnalysisConfig, +} from '../../unified-config-types'; + +describe('config schemas backward compatibility', () => { + // ------------------------------------------------------------------------- + // Constants + // ------------------------------------------------------------------------- + it('re-exports UNIFIED_CONFIG_VERSION', () => { + expect(barrel.UNIFIED_CONFIG_VERSION).toBe(13); + expect(schemas.UNIFIED_CONFIG_VERSION).toBe(13); + }); + + it('re-exports CLIPROXY_SUPPORTED_PROVIDERS', () => { + expect(Array.isArray(barrel.CLIPROXY_SUPPORTED_PROVIDERS)).toBe(true); + expect(barrel.CLIPROXY_SUPPORTED_PROVIDERS.length).toBeGreaterThan(0); + expect(barrel.CLIPROXY_SUPPORTED_PROVIDERS).toEqual(schemas.CLIPROXY_SUPPORTED_PROVIDERS); + }); + + // ------------------------------------------------------------------------- + // Default constants + // ------------------------------------------------------------------------- + const defaultConstants = [ + 'DEFAULT_CLIPROXY_SAFETY_CONFIG', + 'DEFAULT_LOGGING_CONFIG', + 'DEFAULT_OFFICIAL_CHANNELS_CONFIG', + 'DEFAULT_BROWSER_CONFIG', + 'DEFAULT_DASHBOARD_AUTH_CONFIG', + 'DEFAULT_AUTO_QUOTA_CONFIG', + 'DEFAULT_MANUAL_QUOTA_CONFIG', + 'DEFAULT_RUNTIME_MONITOR_CONFIG', + 'DEFAULT_QUOTA_MANAGEMENT_CONFIG', + 'DEFAULT_THINKING_TIER_DEFAULTS', + 'DEFAULT_THINKING_CONFIG', + 'DEFAULT_COPILOT_CONFIG', + 'DEFAULT_CURSOR_CONFIG', + 'DEFAULT_CLIPROXY_SERVER_CONFIG', + 'DEFAULT_OPENAI_COMPAT_PROXY_CONFIG', + 'DEFAULT_GLOBAL_ENV', + 'DEFAULT_IMAGE_ANALYSIS_CONFIG', + ] as const; + + for (const name of defaultConstants) { + it(`re-exports ${name}`, () => { + expect(barrel[name]).toBeDefined(); + expect(barrel[name]).toEqual(schemas[name]); + }); + } + + // ------------------------------------------------------------------------- + // Functions + // ------------------------------------------------------------------------- + it('re-exports createEmptyUnifiedConfig', () => { + expect(typeof barrel.createEmptyUnifiedConfig).toBe('function'); + expect(typeof schemas.createEmptyUnifiedConfig).toBe('function'); + + const config = barrel.createEmptyUnifiedConfig(); + expect(config.version).toBe(13); + expect(config.accounts).toEqual({}); + expect(config.profiles).toEqual({}); + expect(config.cliproxy).toBeDefined(); + expect(config.cliproxy.oauth_accounts).toEqual({}); + expect(config.cliproxy.variants).toEqual({}); + expect(config.logging).toBeDefined(); + expect(config.preferences).toBeDefined(); + expect(config.browser).toBeDefined(); + expect(config.image_analysis).toBeDefined(); + expect(config.quota_management).toBeDefined(); + expect(config.thinking).toBeDefined(); + expect(config.channels).toBeDefined(); + expect(config.dashboard_auth).toBeDefined(); + expect(config.copilot).toBeDefined(); + expect(config.cursor).toBeDefined(); + expect(config.cliproxy_server).toBeDefined(); + expect(config.websearch).toBeDefined(); + }); + + it('re-exports isUnifiedConfig', () => { + expect(typeof barrel.isUnifiedConfig).toBe('function'); + expect(typeof schemas.isUnifiedConfig).toBe('function'); + + expect(barrel.isUnifiedConfig({ version: 13 })).toBe(true); + expect(barrel.isUnifiedConfig(null)).toBe(false); + expect(barrel.isUnifiedConfig({})).toBe(false); + expect(barrel.isUnifiedConfig({ version: 0 })).toBe(false); + expect(barrel.isUnifiedConfig({ version: 1 })).toBe(true); + expect(barrel.isUnifiedConfig('not an object')).toBe(false); + }); + + // ------------------------------------------------------------------------- + // Barrel has all expected runtime exports (type-only exports are verified + // at compile time via the import type block above — they are erased at + // runtime and cannot be checked with the `in` operator). + // ------------------------------------------------------------------------- + const expectedRuntimeExports = [ + 'UNIFIED_CONFIG_VERSION', + 'CLIPROXY_SUPPORTED_PROVIDERS', + 'createEmptyUnifiedConfig', + 'isUnifiedConfig', + 'DEFAULT_CLIPROXY_SAFETY_CONFIG', + 'DEFAULT_LOGGING_CONFIG', + 'DEFAULT_OFFICIAL_CHANNELS_CONFIG', + 'DEFAULT_BROWSER_CONFIG', + 'DEFAULT_DASHBOARD_AUTH_CONFIG', + 'DEFAULT_AUTO_QUOTA_CONFIG', + 'DEFAULT_MANUAL_QUOTA_CONFIG', + 'DEFAULT_RUNTIME_MONITOR_CONFIG', + 'DEFAULT_QUOTA_MANAGEMENT_CONFIG', + 'DEFAULT_THINKING_TIER_DEFAULTS', + 'DEFAULT_THINKING_CONFIG', + 'DEFAULT_COPILOT_CONFIG', + 'DEFAULT_CURSOR_CONFIG', + 'DEFAULT_CLIPROXY_SERVER_CONFIG', + 'DEFAULT_OPENAI_COMPAT_PROXY_CONFIG', + 'DEFAULT_GLOBAL_ENV', + 'DEFAULT_IMAGE_ANALYSIS_CONFIG', + ] as const; + + for (const name of expectedRuntimeExports) { + it(`barrel exports "${name}"`, () => { + expect(name in barrel).toBe(true); + }); + } +}); diff --git a/src/config/schemas/auth.ts b/src/config/schemas/auth.ts new file mode 100644 index 00000000..4056ecf8 --- /dev/null +++ b/src/config/schemas/auth.ts @@ -0,0 +1,109 @@ +/** + * Account, profile, and authentication config types. + * + * Covers: + * - AccountConfig: isolated Claude instances via CLAUDE_CONFIG_DIR + * - ProfileConfig: API-based profiles (env var injection) + * - OAuthAccounts: CLIProxy nickname-to-email mapping + * - CLIProxyAuthConfig: API key and management secret customization + * - TokenRefreshSettings: background token refresh worker config + * - DashboardAuthConfig: dashboard login protection + */ + +import type { TargetType } from '../../targets/target-adapter'; + +/** + * Account configuration (formerly in profiles.json). + * Represents an isolated Claude instance via CLAUDE_CONFIG_DIR. + */ +export interface AccountConfig { + /** ISO timestamp when account was created */ + created: string; + /** ISO timestamp of last usage, null if never used */ + last_used: string | null; + /** Context mode for project workspace data */ + context_mode?: 'isolated' | 'shared'; + /** Context-sharing group when context_mode='shared' */ + context_group?: string; + /** Shared continuity depth when context_mode='shared' */ + continuity_mode?: 'standard' | 'deeper'; + /** Bare profile: no shared symlinks (commands, skills, agents, settings.json) */ + bare?: boolean; +} + +/** + * API-based profile configuration. + * Injects environment variables for alternative providers (GLM, Kimi, etc.). + * + * Settings are stored in separate *.settings.json files (matching Claude's pattern) + * to allow users to edit them directly without touching config.yaml. + */ +export interface ProfileConfig { + /** Profile type - currently only 'api' */ + type: 'api'; + /** Path to settings file (e.g., "~/.ccs/glm.settings.json") */ + settings: string; + /** Target CLI to use for this profile (default: 'claude') */ + target?: TargetType; +} + +/** + * CLIProxy OAuth account nickname mapping. + * Maps user-friendly nicknames to email addresses. + */ +export type OAuthAccounts = Record; + +/** + * CLIProxy authentication configuration. + * Allows customization of API key and management secret for CLIProxyAPI. + */ +export interface CLIProxyAuthConfig { + /** API key for CCS-managed requests (default: 'ccs-internal-managed') */ + api_key?: string; + /** Management secret for Control Panel login (default: 'ccs') */ + management_secret?: string; +} + +/** + * Token refresh configuration. + * Manages background token refresh worker settings. + */ +export interface TokenRefreshSettings { + /** Enable background token refresh (default: false) */ + enabled?: boolean; + /** Refresh check interval in minutes (default: 30) */ + interval_minutes?: number; + /** Preemptive refresh time in minutes (default: 45) */ + preemptive_minutes?: number; + /** Maximum retry attempts per token (default: 3) */ + max_retries?: number; + /** Enable verbose logging (default: false) */ + verbose?: boolean; +} + +/** + * Dashboard authentication configuration. + * Optional login protection for CCS dashboard. + * Disabled by default for backward compatibility. + */ +export interface DashboardAuthConfig { + /** Enable dashboard authentication (default: false) */ + enabled: boolean; + /** Username for dashboard login */ + username: string; + /** Bcrypt-hashed password (use: npx bcrypt-cli hash 'password') */ + password_hash: string; + /** Session timeout in hours (default: 24) */ + session_timeout_hours?: number; +} + +/** + * Default dashboard auth configuration. + * Disabled by default - must be explicitly enabled. + */ +export const DEFAULT_DASHBOARD_AUTH_CONFIG: DashboardAuthConfig = { + enabled: false, + username: '', + password_hash: '', + session_timeout_hours: 24, +}; diff --git a/src/config/schemas/browser.ts b/src/config/schemas/browser.ts new file mode 100644 index 00000000..f4facb4b --- /dev/null +++ b/src/config/schemas/browser.ts @@ -0,0 +1,71 @@ +/** + * Browser automation configuration types and defaults. + * + * Controls Claude browser attach and Codex browser tooling. + * Version 13+ feature. + */ + +/** + * Browser tool exposure policy. + */ +export type BrowserToolPolicy = 'auto' | 'manual'; + +/** + * Browser eval access mode. + */ +export type BrowserEvalMode = 'disabled' | 'readonly' | 'readwrite'; + +/** + * Claude browser attach configuration. + */ +export interface BrowserClaudeConfig { + /** Enable Claude browser attach (default: false) */ + enabled: boolean; + /** Control whether Claude browser attach is exposed automatically or only via --browser */ + policy: BrowserToolPolicy; + /** Chrome user-data directory used for attach mode */ + user_data_dir: string; + /** DevTools port used for attach mode (default: 9222) */ + devtools_port: number; + /** Eval access mode exposed through browser settings/status surfaces */ + eval_mode?: BrowserEvalMode; +} + +/** + * Codex browser tooling configuration. + */ +export interface BrowserCodexConfig { + /** Enable Codex browser tooling injection (default: false) */ + enabled: boolean; + /** Control whether Codex browser tooling is exposed automatically or only via --browser */ + policy: BrowserToolPolicy; + /** Eval access mode exposed through browser settings/status surfaces */ + eval_mode?: BrowserEvalMode; +} + +/** + * Browser automation configuration. + * Controls Claude browser attach and Codex browser tooling. + */ +export interface BrowserConfig { + claude: BrowserClaudeConfig; + codex: BrowserCodexConfig; +} + +/** + * Default browser configuration. + */ +export const DEFAULT_BROWSER_CONFIG: BrowserConfig = { + claude: { + enabled: false, + policy: 'manual', + user_data_dir: '', + devtools_port: 9222, + eval_mode: 'readonly', + }, + codex: { + enabled: false, + policy: 'manual', + eval_mode: 'readonly', + }, +}; diff --git a/src/config/schemas/channels.ts b/src/config/schemas/channels.ts new file mode 100644 index 00000000..f8d5274c --- /dev/null +++ b/src/config/schemas/channels.ts @@ -0,0 +1,32 @@ +/** + * Official Channels configuration types and defaults. + * + * Controls runtime-only injection of Anthropic's official channel plugins + * (Telegram, Discord, iMessage). + * Version 12+ feature. + */ + +/** + * Supported Anthropic official channel IDs. + */ +export type OfficialChannelId = 'telegram' | 'discord' | 'imessage'; + +/** + * Official Channels configuration. + * Controls runtime-only injection of Anthropic's official channel plugins. + */ +export interface OfficialChannelsConfig { + /** Selected official channels to auto-enable for compatible sessions */ + selected: OfficialChannelId[]; + /** Also add --dangerously-skip-permissions when auto-enable is active */ + unattended: boolean; +} + +/** + * Default Official Channels configuration. + * Disabled by default because the feature requires explicit user setup. + */ +export const DEFAULT_OFFICIAL_CHANNELS_CONFIG: OfficialChannelsConfig = { + selected: [], + unattended: false, +}; diff --git a/src/config/schemas/cliproxy.ts b/src/config/schemas/cliproxy.ts new file mode 100644 index 00000000..1765335f --- /dev/null +++ b/src/config/schemas/cliproxy.ts @@ -0,0 +1,151 @@ +/** + * CLIProxy configuration types and defaults. + * + * Covers provider/variant/routing/safety/logging configuration + * for the CLIProxy integration layer. + */ + +import type { TargetType } from '../../targets/target-adapter'; +import type { CLIProxyProvider, CliproxyRoutingStrategy } from '../../cliproxy/types'; +import { CLIPROXY_PROVIDER_IDS } from '../../cliproxy/provider-capabilities'; +import type { OAuthAccounts, CLIProxyAuthConfig, TokenRefreshSettings } from './auth'; + +/** + * Supported CLIProxy providers. + * Derived from CLIPROXY_PROVIDER_IDS — single source of truth in provider-capabilities.ts. + */ +export { CLIPROXY_PROVIDER_IDS as CLIPROXY_SUPPORTED_PROVIDERS }; + +/** + * CLIProxy variant configuration. + * User-defined variants of built-in OAuth providers. + * + * Settings are stored in separate *.settings.json files (matching Claude's pattern) + * to allow users to edit them directly without touching config.yaml. + */ +export interface CLIProxyVariantConfig { + /** Base provider to use */ + provider: CLIProxyProvider; + /** Account nickname (references oauth_accounts) */ + account?: string; + /** Path to settings file (e.g., "~/.ccs/gemini-custom.settings.json") */ + settings?: string; + /** Unique port for variant isolation (8318-8417) */ + port?: number; + /** Per-variant auth override (optional) */ + auth?: CLIProxyAuthConfig; + /** Target CLI to use for this variant (default: 'claude') */ + target?: TargetType; +} + +/** + * Per-tier provider+model mapping for composite variants. + */ +export interface CompositeTierConfig { + /** Provider for this tier */ + provider: CLIProxyProvider; + /** Model ID to use for this tier */ + model: string; + /** Account nickname (optional, references oauth_accounts) */ + account?: string; + /** Fallback provider+model if primary fails */ + fallback?: { + provider: CLIProxyProvider; + model: string; + account?: string; + }; + /** Per-tier thinking budget override (e.g. 'xhigh', 'medium', 'off') */ + thinking?: string; +} + +/** + * Composite variant configuration. + * Mixes different providers per Claude tier (opus, sonnet, haiku) in a single profile. + * Uses CLIProxyAPI root endpoints (/v1/messages) for model-based routing + * instead of provider-specific endpoints (/api/provider/{provider}). + */ +export interface CompositeVariantConfig { + /** Discriminator for composite type */ + type: 'composite'; + /** Which tier ANTHROPIC_MODEL equals (default must be one of the three) */ + default_tier: 'opus' | 'sonnet' | 'haiku'; + /** Per-tier provider+model mapping */ + tiers: { + opus: CompositeTierConfig; + sonnet: CompositeTierConfig; + haiku: CompositeTierConfig; + }; + /** Path to settings file */ + settings?: string; + /** Shared port for the composite profile */ + port?: number; + /** Per-variant auth override (optional) */ + auth?: CLIProxyAuthConfig; + /** Target CLI to use for this composite variant (default: 'claude') */ + target?: TargetType; +} + +/** + * CLIProxy logging configuration. + * Controls whether CLIProxyAPI writes logs to disk. + * Logs can grow to several GB if left enabled. + */ +export interface CLIProxyLoggingConfig { + /** Enable logging to file (default: false to prevent disk bloat) */ + enabled?: boolean; + /** Enable request logging for debugging (default: false) */ + request_log?: boolean; +} + +/** + * CLIProxy safety configuration. + * Controls high-risk flow safeguards for supported providers. + */ +export interface CLIProxySafetyConfig { + /** Allow skipping AGY responsibility checks and Gemini dashboard typed acknowledgement */ + antigravity_ack_bypass?: boolean; +} + +/** + * Default CLIProxy safety configuration. + */ +export const DEFAULT_CLIPROXY_SAFETY_CONFIG: CLIProxySafetyConfig = { + antigravity_ack_bypass: false, +}; + +export interface CLIProxyRoutingConfig { + /** Credential selection strategy when multiple accounts match */ + strategy?: CliproxyRoutingStrategy; + /** Keep one conversation pinned to the same account when possible */ + session_affinity?: boolean; + /** Go-style duration for session-affinity binding retention */ + session_affinity_ttl?: string; +} + +/** + * CLIProxy configuration section. + */ +export interface CLIProxyConfig { + /** Backend selection: 'original' or 'plus' (default: 'original') */ + backend?: 'original' | 'plus'; + /** Nickname to email mapping for OAuth accounts */ + oauth_accounts: OAuthAccounts; + /** Built-in providers (read-only, for reference) */ + providers: readonly string[]; + /** User-defined provider variants (single-provider or composite) */ + variants: Record; + /** Logging configuration (disabled by default) */ + logging?: CLIProxyLoggingConfig; + /** Safety controls for high-risk provider flows */ + safety?: CLIProxySafetyConfig; + /** Kiro: disable incognito browser mode (use normal browser to save credentials) */ + kiro_no_incognito?: boolean; + /** Global auth configuration for CLIProxyAPI */ + auth?: CLIProxyAuthConfig; + /** Background token refresh worker settings */ + token_refresh?: TokenRefreshSettings; + /** Auto-sync API profiles to local CLIProxy config on settings change (default: true) */ + auto_sync?: boolean; + /** Routing strategy for multi-account CLIProxy selection */ + routing?: CLIProxyRoutingConfig; +} diff --git a/src/config/schemas/copilot-cursor.ts b/src/config/schemas/copilot-cursor.ts new file mode 100644 index 00000000..e0b33fbb --- /dev/null +++ b/src/config/schemas/copilot-cursor.ts @@ -0,0 +1,93 @@ +/** + * Copilot and Cursor IDE integration configuration types and defaults. + * + * Covers: + * - CopilotConfig: GitHub Copilot proxy integration (strictly opt-in) + * - CursorConfig: Cursor IDE proxy daemon + */ + +/** + * Copilot API account type. + */ +export type CopilotAccountType = 'individual' | 'business' | 'enterprise'; + +/** + * Copilot API configuration. + * Enables GitHub Copilot subscription usage via copilot-api proxy. + * Strictly opt-in - disabled by default. + * + * !! DISCLAIMER - USE AT YOUR OWN RISK !! + * This uses an UNOFFICIAL reverse-engineered API. + * Excessive usage may trigger GitHub account restrictions. + * CCS provides NO WARRANTY and accepts NO RESPONSIBILITY for any consequences. + */ +export interface CopilotConfig { + /** Enable Copilot integration (default: false) - must be explicitly enabled */ + enabled: boolean; + /** Auto-start copilot-api daemon when using profile (default: false) */ + auto_start: boolean; + /** Port for copilot-api proxy (default: 4141) */ + port: number; + /** GitHub Copilot account type (default: individual) */ + account_type: CopilotAccountType; + /** Rate limit in seconds between requests (null = no limit) */ + rate_limit: number | null; + /** Wait instead of error when rate limit is hit (default: true) */ + wait_on_limit: boolean; + /** Default model ID (e.g., claude-sonnet-4.5) */ + model: string; + /** Model mapping for Claude tiers - maps opus/sonnet/haiku to specific models */ + opus_model?: string; + sonnet_model?: string; + haiku_model?: string; +} + +/** + * Cursor IDE integration configuration. + * Enables Cursor IDE usage via cursor proxy daemon. + */ +export interface CursorConfig { + /** Enable Cursor integration (default: false) */ + enabled: boolean; + /** Port for cursor proxy daemon (default: 20129) */ + port: number; + /** Auto-start daemon when CCS starts (default: false) */ + auto_start: boolean; + /** Enable ghost mode to disable telemetry (default: true) */ + ghost_mode: boolean; + /** Default model ID used by Cursor integration */ + model: string; + /** Optional tier mapping for Claude-compatible model routing */ + opus_model?: string; + /** Optional tier mapping for Claude-compatible model routing */ + sonnet_model?: string; + /** Optional tier mapping for Claude-compatible model routing */ + haiku_model?: string; +} + +/** + * Default Copilot configuration. + * Strictly opt-in - disabled by default. + * Uses gpt-4.1 as default model (free tier compatible). + */ +export const DEFAULT_COPILOT_CONFIG: CopilotConfig = { + enabled: false, + auto_start: false, + port: 4141, + account_type: 'individual', + rate_limit: null, + wait_on_limit: true, + model: 'gpt-4.1', +}; + +/** + * Default Cursor configuration. + * Disabled by default, ghost mode enabled for privacy. + */ +export const DEFAULT_CURSOR_CONFIG: CursorConfig = { + enabled: false, + port: 20129, + auto_start: false, + ghost_mode: true, + model: 'gpt-5.3-codex', +}; diff --git a/src/config/schemas/index.ts b/src/config/schemas/index.ts new file mode 100644 index 00000000..ff0313f3 --- /dev/null +++ b/src/config/schemas/index.ts @@ -0,0 +1,112 @@ +/** + * Config schema barrel re-exports. + * + * All types, interfaces, constants, and functions originally in + * unified-config-types.ts are re-exported here for backward compatibility. + * Each module is responsible for a focused domain of the config schema. + */ + +// Version constant +export { UNIFIED_CONFIG_VERSION } from './version'; + +// Account, profile, OAuth, auth types +export type { + AccountConfig, + ProfileConfig, + OAuthAccounts, + CLIProxyAuthConfig, + TokenRefreshSettings, + DashboardAuthConfig, +} from './auth'; +export { DEFAULT_DASHBOARD_AUTH_CONFIG } from './auth'; + +// CLIProxy provider, variant, routing, safety, logging types +export { CLIPROXY_SUPPORTED_PROVIDERS, DEFAULT_CLIPROXY_SAFETY_CONFIG } from './cliproxy'; +export type { + CLIProxyVariantConfig, + CompositeTierConfig, + CompositeVariantConfig, + CLIProxyLoggingConfig, + CLIProxySafetyConfig, + CLIProxyRoutingConfig, + CLIProxyConfig, +} from './cliproxy'; + +// Quota management types and defaults +export { + DEFAULT_AUTO_QUOTA_CONFIG, + DEFAULT_MANUAL_QUOTA_CONFIG, + DEFAULT_RUNTIME_MONITOR_CONFIG, + DEFAULT_QUOTA_MANAGEMENT_CONFIG, +} from './quota'; +export type { + AutoQuotaConfig, + RuntimeMonitorConfig, + ManualQuotaConfig, + QuotaManagementMode, + QuotaManagementConfig, +} from './quota'; + +// Thinking/reasoning budget types and defaults +export { DEFAULT_THINKING_TIER_DEFAULTS, DEFAULT_THINKING_CONFIG } from './thinking'; +export type { ThinkingMode, ThinkingTierDefaults, ThinkingConfig } from './thinking'; + +// Official channels types and defaults +export { DEFAULT_OFFICIAL_CHANNELS_CONFIG } from './channels'; +export type { OfficialChannelId, OfficialChannelsConfig } from './channels'; + +// WebSearch backend types +export type { + DuckDuckGoWebSearchConfig, + BraveWebSearchConfig, + ExaWebSearchConfig, + TavilyWebSearchConfig, + SearxngWebSearchConfig, + GeminiWebSearchConfig, + GrokWebSearchConfig, + OpenCodeWebSearchConfig, + WebSearchProvidersConfig, + WebSearchConfig, +} from './websearch'; + +// Browser automation types and defaults +export { DEFAULT_BROWSER_CONFIG } from './browser'; +export type { + BrowserToolPolicy, + BrowserEvalMode, + BrowserClaudeConfig, + BrowserCodexConfig, + BrowserConfig, +} from './browser'; + +// Logging and preferences types and defaults +export { DEFAULT_LOGGING_CONFIG } from './logging'; +export type { LoggingLevel, LoggingConfig, PreferencesConfig } from './logging'; + +// Provider integration types and defaults +export { + DEFAULT_GLOBAL_ENV, + DEFAULT_COPILOT_CONFIG, + DEFAULT_CURSOR_CONFIG, + DEFAULT_CLIPROXY_SERVER_CONFIG, + DEFAULT_OPENAI_COMPAT_PROXY_CONFIG, + DEFAULT_IMAGE_ANALYSIS_CONFIG, +} from './providers'; +export type { + CopilotAccountType, + CopilotConfig, + CursorConfig, + ProxyRemoteConfig, + ProxyFallbackConfig, + ProxyLocalConfig, + OpenAICompatProxyRoutingConfig, + OpenAICompatProxyConfig, + CliproxyServerConfig, + GlobalEnvConfig, + ContinuityConfig, + ImageAnalysisConfig, +} from './providers'; + +// Main unified config interface, factory, and type guard +export { createEmptyUnifiedConfig, isUnifiedConfig } from './unified-config'; +export type { UnifiedConfig } from './unified-config'; diff --git a/src/config/schemas/logging.ts b/src/config/schemas/logging.ts new file mode 100644 index 00000000..05fe3061 --- /dev/null +++ b/src/config/schemas/logging.ts @@ -0,0 +1,53 @@ +/** + * Logging and preferences configuration types and defaults. + * + * Covers: + * - LoggingConfig: CCS-owned structured runtime logging + * - LoggingLevel: log severity levels + * - PreferencesConfig: user preferences (theme, telemetry, auto-update) + */ + +export type LoggingLevel = 'error' | 'warn' | 'info' | 'debug'; + +/** + * CCS-owned structured logging configuration. + * Separate from cliproxy.logging, which controls CLIProxy runtime files. + */ +export interface LoggingConfig { + /** Enable CCS-owned structured runtime logging */ + enabled: boolean; + /** Minimum level written to disk */ + level: LoggingLevel; + /** Rotate current log when it reaches this size in MB */ + rotate_mb: number; + /** Keep archived segments for this many days */ + retain_days: number; + /** Redact sensitive values before persistence */ + redact: boolean; + /** In-memory recent event buffer size for dashboard reads */ + live_buffer_size: number; +} + +/** + * Default logging configuration. + */ +export const DEFAULT_LOGGING_CONFIG: LoggingConfig = { + enabled: true, + level: 'info', + rotate_mb: 10, + retain_days: 7, + redact: true, + live_buffer_size: 250, +}; + +/** + * User preferences. + */ +export interface PreferencesConfig { + /** UI theme preference */ + theme?: 'light' | 'dark' | 'system'; + /** Enable anonymous telemetry */ + telemetry?: boolean; + /** Enable automatic update checks */ + auto_update?: boolean; +} diff --git a/src/config/schemas/providers.ts b/src/config/schemas/providers.ts new file mode 100644 index 00000000..78a6dc8f --- /dev/null +++ b/src/config/schemas/providers.ts @@ -0,0 +1,30 @@ +/** + * Provider integration configuration types and defaults. + * + * Re-exports from focused sub-modules for backward compatibility. + * Actual definitions live in: + * - copilot-cursor.ts: CopilotConfig, CursorConfig + defaults + * - proxy-server.ts: CliproxyServerConfig, OpenAICompatProxyConfig, + * GlobalEnvConfig, ContinuityConfig, ImageAnalysisConfig + defaults + */ + +export type { CopilotAccountType, CopilotConfig, CursorConfig } from './copilot-cursor'; +export { DEFAULT_COPILOT_CONFIG, DEFAULT_CURSOR_CONFIG } from './copilot-cursor'; + +export type { + ProxyRemoteConfig, + ProxyFallbackConfig, + ProxyLocalConfig, + OpenAICompatProxyRoutingConfig, + OpenAICompatProxyConfig, + CliproxyServerConfig, + GlobalEnvConfig, + ContinuityConfig, + ImageAnalysisConfig, +} from './proxy-server'; +export { + DEFAULT_CLIPROXY_SERVER_CONFIG, + DEFAULT_OPENAI_COMPAT_PROXY_CONFIG, + DEFAULT_GLOBAL_ENV, + DEFAULT_IMAGE_ANALYSIS_CONFIG, +} from './proxy-server'; diff --git a/src/config/schemas/proxy-server.ts b/src/config/schemas/proxy-server.ts new file mode 100644 index 00000000..745ebab3 --- /dev/null +++ b/src/config/schemas/proxy-server.ts @@ -0,0 +1,193 @@ +/** + * Proxy server, global env, continuity, and image analysis types and defaults. + * + * Covers: + * - CliproxyServerConfig: remote/local CLIProxy server mode + * - OpenAICompatProxyConfig: OpenAI-compatible local proxy + * - GlobalEnvConfig: global environment variable injection + * - ContinuityConfig: cross-profile continuity inheritance + * - ImageAnalysisConfig: vision analysis via CLIProxy + */ + +/** + * Remote proxy configuration. + * Connect to a remote CLIProxyAPI instance instead of spawning local binary. + */ +export interface ProxyRemoteConfig { + /** Enable remote proxy mode (default: false = local mode) */ + enabled: boolean; + /** Remote proxy hostname or IP (empty = not configured) */ + host: string; + /** + * Remote proxy port. + * Optional - defaults based on protocol: + * - HTTPS: 443 + * - HTTP: 8317 + * When empty/undefined, uses protocol default. + */ + port?: number; + /** Protocol for remote connection */ + protocol: 'http' | 'https'; + /** Auth token for remote proxy API endpoints (optional, sent as header) */ + auth_token: string; + /** + * Management key for remote proxy management API endpoints. + * CLIProxyAPI uses separate authentication for management endpoints + * (/v0/management/*) via 'secret-key' config. + * If not set, falls back to auth_token for backwards compatibility. + */ + management_key?: string; + /** Connection timeout in milliseconds (default: 2000) */ + timeout?: number; + /** Enable auto-sync profiles to remote on settings change (default: false) */ + auto_sync?: boolean; +} + +/** + * Fallback configuration when remote proxy is unreachable. + */ +export interface ProxyFallbackConfig { + /** Enable fallback to local proxy (default: true) */ + enabled: boolean; + /** Auto-start local proxy without prompting (default: false = prompt user) */ + auto_start: boolean; +} + +/** + * Local proxy configuration. + */ +export interface ProxyLocalConfig { + /** Local proxy port (default: 8317) */ + port: number; + /** Auto-start local binary (default: true) */ + auto_start: boolean; +} + +export interface OpenAICompatProxyRoutingConfig { + default?: string; + background?: string; + think?: string; + longContext?: string; + webSearch?: string; + longContextThreshold?: number; +} + +export interface OpenAICompatProxyConfig { + /** Default local port for OpenAI-compatible proxy instances */ + port?: number; + /** Optional profile-scoped local port overrides */ + profile_ports?: Record; + routing?: OpenAICompatProxyRoutingConfig; +} + +/** + * CLIProxy server configuration section. + * Controls whether CCS uses local or remote CLIProxyAPI instance. + */ +export interface CliproxyServerConfig { + /** Remote proxy settings */ + remote: ProxyRemoteConfig; + /** Fallback behavior when remote is unreachable */ + fallback: ProxyFallbackConfig; + /** Local proxy settings */ + local: ProxyLocalConfig; +} + +/** + * Global environment variables configuration. + * These env vars are injected into ALL non-Claude subscription profiles. + * Useful for disabling telemetry, bug commands, error reporting, etc. + */ +export interface GlobalEnvConfig { + /** Enable global env injection (default: true) */ + enabled: boolean; + /** Environment variables to inject */ + env: Record; +} + +/** + * Cross-profile continuity inheritance configuration. + * Maps execution profile names to source account profiles for CLAUDE_CONFIG_DIR reuse. + */ +export interface ContinuityConfig { + /** Profile name -> source account profile name */ + inherit_from_account?: Record; +} + +/** + * Default global env vars for third-party profiles. + * These disable Claude Code telemetry/reporting since we're using proxy. + */ +export const DEFAULT_GLOBAL_ENV: Record = { + DISABLE_BUG_COMMAND: '1', + DISABLE_ERROR_REPORTING: '1', + DISABLE_TELEMETRY: '1', +}; + +/** + * Default CLIProxy server configuration. + * Local mode by default - remote must be explicitly enabled. + * Port is optional for remote - defaults based on protocol. + */ +export const DEFAULT_CLIPROXY_SERVER_CONFIG: CliproxyServerConfig = { + remote: { + enabled: false, + host: '', + protocol: 'http', + auth_token: '', + }, + fallback: { + enabled: true, + auto_start: false, + }, + local: { + port: 8317, + auto_start: true, + }, +}; + +export const DEFAULT_OPENAI_COMPAT_PROXY_CONFIG: OpenAICompatProxyConfig = { + profile_ports: {}, + routing: { + longContextThreshold: 60_000, + }, +}; + +/** + * Image analysis configuration. + * Routes image/PDF files through CLIProxy for vision analysis. + */ +export interface ImageAnalysisConfig { + /** Enable image analysis via CLIProxy (default: true) */ + enabled: boolean; + /** Timeout in seconds (default: 60) */ + timeout: number; + /** Provider-to-model mapping for vision analysis */ + provider_models: Record; + /** Fallback backend used when a profile does not resolve to a provider-specific backend */ + fallback_backend?: string; + /** Explicit profile-name-to-backend overrides for settings/custom aliases */ + profile_backends?: Record; +} + +/** + * Default image analysis configuration. + * Enabled by default for CLIProxy providers with vision support. + */ +export const DEFAULT_IMAGE_ANALYSIS_CONFIG: ImageAnalysisConfig = { + enabled: true, + timeout: 60, + provider_models: { + agy: 'gemini-3-1-flash-preview', + gemini: 'gemini-3-flash-preview', + codex: 'gpt-5.1-codex-mini', + kiro: 'kiro-claude-haiku-4-5', + ghcp: 'claude-haiku-4.5', + claude: 'claude-haiku-4.5-20251001', + qwen: 'vision-model', + iflow: 'qwen3-vl-plus', + kimi: 'vision-model', + }, + fallback_backend: 'gemini', + profile_backends: {}, +}; diff --git a/src/config/schemas/quota.ts b/src/config/schemas/quota.ts new file mode 100644 index 00000000..0d380694 --- /dev/null +++ b/src/config/schemas/quota.ts @@ -0,0 +1,121 @@ +/** + * Quota management configuration types and defaults. + * + * Controls hybrid auto+manual account selection for multi-account setups. + * Version 7+ feature. + */ + +// ============================================================================ +// QUOTA MANAGEMENT CONFIGURATION (v7+) +// ============================================================================ + +/** + * Auto quota management configuration. + * Controls automatic failover behavior. + */ +export interface AutoQuotaConfig { + /** Enable pre-flight quota check before requests (default: true) */ + preflight_check: boolean; + /** Quota percentage below which account is "exhausted" (default: 5) */ + exhaustion_threshold: number; + /** Tier priority for failover, highest to lowest (default: ['paid']) */ + tier_priority: string[]; + /** Minutes to skip exhausted account before retry (default: 5) */ + cooldown_minutes: number; +} + +/** + * Runtime quota monitor configuration. + * Controls adaptive polling during active sessions. + */ +export interface RuntimeMonitorConfig { + /** Enable runtime monitoring during sessions (default: true) */ + enabled: boolean; + /** Poll interval in seconds when quota > warn_threshold (default: 300) */ + normal_interval_seconds: number; + /** Poll interval in seconds when quota <= warn_threshold (default: 60) */ + critical_interval_seconds: number; + /** Quota percentage that triggers fast polling + warning (default: 20) */ + warn_threshold: number; + /** Quota percentage that triggers cooldown + switch (default: 5) */ + exhaustion_threshold: number; + /** Minutes to cooldown exhausted account (default: 5) */ + cooldown_minutes: number; +} + +/** + * Manual quota management configuration. + * User-controlled overrides for account selection. + */ +export interface ManualQuotaConfig { + /** User-paused accounts (stored in accounts.json) */ + paused_accounts: string[]; + /** Force use of specific account (overrides auto-selection) */ + forced_default: string | null; + /** Lock to specific tier only */ + tier_lock: string | null; +} + +/** + * Quota management mode. + * - auto: Fully automatic failover based on quota + * - manual: User controls everything, no auto-switching + * - hybrid: Auto-failover with user overrides (default) + */ +export type QuotaManagementMode = 'auto' | 'manual' | 'hybrid'; + +/** + * Quota management configuration section. + * Controls hybrid auto+manual account selection for multi-account setups. + */ +export interface QuotaManagementConfig { + /** Management mode (default: hybrid) */ + mode: QuotaManagementMode; + /** Auto mode settings */ + auto: AutoQuotaConfig; + /** Manual mode settings */ + manual: ManualQuotaConfig; + /** Runtime monitor settings */ + runtime_monitor: RuntimeMonitorConfig; +} + +/** + * Default auto quota configuration. + */ +export const DEFAULT_AUTO_QUOTA_CONFIG: AutoQuotaConfig = { + preflight_check: true, + exhaustion_threshold: 5, + tier_priority: ['ultra', 'pro', 'free'], + cooldown_minutes: 5, +}; + +/** + * Default manual quota configuration. + */ +export const DEFAULT_MANUAL_QUOTA_CONFIG: ManualQuotaConfig = { + paused_accounts: [], + forced_default: null, + tier_lock: null, +}; + +/** + * Default runtime monitor configuration. + */ +export const DEFAULT_RUNTIME_MONITOR_CONFIG: RuntimeMonitorConfig = { + enabled: true, + normal_interval_seconds: 300, + critical_interval_seconds: 60, + warn_threshold: 20, + exhaustion_threshold: 5, + cooldown_minutes: 5, +}; + +/** + * Default quota management configuration. + */ +export const DEFAULT_QUOTA_MANAGEMENT_CONFIG: QuotaManagementConfig = { + mode: 'hybrid', + auto: { ...DEFAULT_AUTO_QUOTA_CONFIG }, + manual: { ...DEFAULT_MANUAL_QUOTA_CONFIG }, + runtime_monitor: { ...DEFAULT_RUNTIME_MONITOR_CONFIG }, +}; diff --git a/src/config/schemas/thinking.ts b/src/config/schemas/thinking.ts new file mode 100644 index 00000000..81951ad2 --- /dev/null +++ b/src/config/schemas/thinking.ts @@ -0,0 +1,66 @@ +/** + * Thinking/reasoning budget configuration types and defaults. + * + * Controls thinking budget injection for CLIProxy providers. + * Version 8+ feature. + */ + +// ============================================================================ +// THINKING CONFIGURATION (v8+) +// ============================================================================ + +/** + * Thinking mode for auto/manual/off control. + * - auto: Apply tier-based defaults (opus→high, sonnet→medium, haiku→low) + * - off: Disable thinking entirely + * - manual: Use explicit override value + */ +export type ThinkingMode = 'auto' | 'off' | 'manual'; + +/** + * Tier-to-thinking level defaults. + * Maps Claude tier names to thinking level names. + */ +export interface ThinkingTierDefaults { + /** Thinking level for opus tier (default: 'high') */ + opus: string; + /** Thinking level for sonnet tier (default: 'medium') */ + sonnet: string; + /** Thinking level for haiku tier (default: 'low') */ + haiku: string; +} + +/** + * Thinking configuration section. + * Controls thinking/reasoning budget injection for CLIProxy providers. + */ +export interface ThinkingConfig { + /** Thinking mode (default: 'auto') */ + mode: ThinkingMode; + /** Manual override value (level name or budget number) */ + override?: string | number; + /** Tier-to-level mapping */ + tier_defaults: ThinkingTierDefaults; + /** Per-provider overrides (e.g., { gemini: { opus: 'high' } }) */ + provider_overrides?: Record>; + /** Show warning when values are clamped (default: true) */ + show_warnings?: boolean; +} + +/** + * Default thinking tier defaults. + */ +export const DEFAULT_THINKING_TIER_DEFAULTS: ThinkingTierDefaults = { + opus: 'high', + sonnet: 'medium', + haiku: 'low', +}; + +/** + * Default thinking configuration. + */ +export const DEFAULT_THINKING_CONFIG: ThinkingConfig = { + mode: 'auto', + tier_defaults: { ...DEFAULT_THINKING_TIER_DEFAULTS }, + show_warnings: true, +}; diff --git a/src/config/schemas/unified-config.ts b/src/config/schemas/unified-config.ts new file mode 100644 index 00000000..78726c46 --- /dev/null +++ b/src/config/schemas/unified-config.ts @@ -0,0 +1,200 @@ +/** + * Main unified configuration interface, factory, and type guard. + * + * The UnifiedConfig type is the root of the entire config.yaml schema. + * This file imports all section types from their respective schema modules. + */ + +import type { AccountConfig, ProfileConfig, DashboardAuthConfig } from './auth'; +import { DEFAULT_DASHBOARD_AUTH_CONFIG } from './auth'; +import type { CLIProxyConfig } from './cliproxy'; +import { CLIPROXY_SUPPORTED_PROVIDERS, DEFAULT_CLIPROXY_SAFETY_CONFIG } from './cliproxy'; +import type { LoggingConfig, PreferencesConfig } from './logging'; +import { DEFAULT_LOGGING_CONFIG } from './logging'; +import type { WebSearchConfig } from './websearch'; +import type { + GlobalEnvConfig, + ContinuityConfig, + CopilotConfig, + CursorConfig, + CliproxyServerConfig, + OpenAICompatProxyConfig, + ImageAnalysisConfig, +} from './providers'; +import { + DEFAULT_COPILOT_CONFIG, + DEFAULT_CURSOR_CONFIG, + DEFAULT_CLIPROXY_SERVER_CONFIG, + DEFAULT_OPENAI_COMPAT_PROXY_CONFIG, + DEFAULT_IMAGE_ANALYSIS_CONFIG, + DEFAULT_GLOBAL_ENV, +} from './providers'; +import { UNIFIED_CONFIG_VERSION } from './version'; +import type { QuotaManagementConfig } from './quota'; +import { DEFAULT_QUOTA_MANAGEMENT_CONFIG } from './quota'; +import type { ThinkingConfig } from './thinking'; +import { DEFAULT_THINKING_CONFIG } from './thinking'; +import type { OfficialChannelsConfig } from './channels'; +import { DEFAULT_OFFICIAL_CHANNELS_CONFIG } from './channels'; +import type { BrowserConfig } from './browser'; +import { DEFAULT_BROWSER_CONFIG } from './browser'; + +/** + * Main unified configuration structure. + * Stored in ~/.ccs/config.yaml + */ +export interface UnifiedConfig { + /** Config version */ + version: number; + /** Flag indicating setup wizard has been completed */ + setup_completed?: boolean; + /** Default profile name to use when none specified */ + default?: string; + /** Account-based profiles (isolated Claude instances) */ + accounts: Record; + /** API-based profiles (env var injection) */ + profiles: Record; + /** CLIProxy configuration */ + cliproxy: CLIProxyConfig; + /** OpenAI-compatible local proxy configuration */ + proxy?: OpenAICompatProxyConfig; + /** CCS-owned structured logging configuration */ + logging?: LoggingConfig; + /** User preferences */ + preferences: PreferencesConfig; + /** WebSearch configuration */ + websearch?: WebSearchConfig; + /** Global environment variables for all non-Claude subscription profiles */ + global_env?: GlobalEnvConfig; + /** Cross-profile continuity inheritance mapping */ + continuity?: ContinuityConfig; + /** Copilot API configuration (GitHub Copilot proxy) */ + copilot?: CopilotConfig; + /** Cursor IDE configuration (Cursor proxy daemon) */ + cursor?: CursorConfig; + /** CLIProxy server configuration for remote/local mode */ + cliproxy_server?: CliproxyServerConfig; + /** Quota management configuration (v7+) */ + quota_management?: QuotaManagementConfig; + /** Thinking/reasoning budget configuration (v8+) */ + thinking?: ThinkingConfig; + /** Official Channels runtime auto-enable preferences (v11+) */ + channels?: OfficialChannelsConfig; + /** Dashboard authentication configuration (optional) */ + dashboard_auth?: DashboardAuthConfig; + /** Browser automation configuration */ + browser?: BrowserConfig; + /** Image analysis configuration (vision via CLIProxy) */ + image_analysis?: ImageAnalysisConfig; +} + +/** + * Create an empty unified config with defaults. + */ +export function createEmptyUnifiedConfig(): UnifiedConfig { + return { + version: UNIFIED_CONFIG_VERSION, + default: undefined, + accounts: {}, + profiles: {}, + cliproxy: { + backend: 'original', + oauth_accounts: {}, + providers: [...CLIPROXY_SUPPORTED_PROVIDERS], + variants: {}, + logging: { + enabled: false, + request_log: false, + }, + safety: { ...DEFAULT_CLIPROXY_SAFETY_CONFIG }, + auto_sync: true, + routing: { + strategy: 'round-robin', + session_affinity: false, + session_affinity_ttl: '1h', + }, + }, + proxy: { + port: DEFAULT_OPENAI_COMPAT_PROXY_CONFIG.port, + profile_ports: { ...DEFAULT_OPENAI_COMPAT_PROXY_CONFIG.profile_ports }, + routing: { + ...DEFAULT_OPENAI_COMPAT_PROXY_CONFIG.routing, + }, + }, + logging: { ...DEFAULT_LOGGING_CONFIG }, + preferences: { + theme: 'system', + telemetry: false, + auto_update: true, + }, + websearch: { + enabled: true, + providers: { + exa: { + enabled: false, + max_results: 5, + }, + tavily: { + enabled: false, + max_results: 5, + }, + brave: { + enabled: false, + max_results: 5, + }, + searxng: { + enabled: false, + url: '', + max_results: 5, + }, + duckduckgo: { + enabled: true, + max_results: 5, + }, + gemini: { + enabled: false, + model: 'gemini-2.5-flash', + timeout: 55, + }, + opencode: { + enabled: false, + model: 'opencode/grok-code', + timeout: 90, + }, + grok: { + enabled: false, + timeout: 55, + }, + }, + }, + global_env: { + enabled: true, + env: { ...DEFAULT_GLOBAL_ENV }, + }, + copilot: { ...DEFAULT_COPILOT_CONFIG }, + cursor: { ...DEFAULT_CURSOR_CONFIG }, + cliproxy_server: { ...DEFAULT_CLIPROXY_SERVER_CONFIG }, + quota_management: { ...DEFAULT_QUOTA_MANAGEMENT_CONFIG }, + thinking: { ...DEFAULT_THINKING_CONFIG }, + channels: { ...DEFAULT_OFFICIAL_CHANNELS_CONFIG }, + dashboard_auth: { ...DEFAULT_DASHBOARD_AUTH_CONFIG }, + browser: { + claude: { ...DEFAULT_BROWSER_CONFIG.claude }, + codex: { ...DEFAULT_BROWSER_CONFIG.codex }, + }, + image_analysis: { ...DEFAULT_IMAGE_ANALYSIS_CONFIG }, + }; +} + +/** + * Type guard for UnifiedConfig. + * Relaxed validation: accepts configs with version >= 1 and any subset of sections. + * Missing sections will be filled with defaults during merge. + */ +export function isUnifiedConfig(obj: unknown): obj is UnifiedConfig { + if (typeof obj !== 'object' || obj === null) return false; + const config = obj as Record; + // Only require version to be a number >= 1 (allow future versions) + // Sections are optional - will be merged with defaults in loadOrCreateUnifiedConfig + return typeof config.version === 'number' && config.version >= 1; +} diff --git a/src/config/schemas/version.ts b/src/config/schemas/version.ts new file mode 100644 index 00000000..98979314 --- /dev/null +++ b/src/config/schemas/version.ts @@ -0,0 +1,23 @@ +/** + * Unified config version constant. + * + * Central source of truth for the current config schema version. + * Incremented whenever new sections are added to config.yaml. + */ + +/** + * Unified config version. + * Version 2 = YAML unified format + * Version 3 = WebSearch config with model configuration for Gemini/OpenCode + * Version 4 = Copilot API integration (GitHub Copilot proxy) + * Version 5 = Remote proxy configuration (connect to remote CLIProxyAPI) + * Version 6 = Customizable auth tokens (API key and management secret) + * Version 7 = Quota management for hybrid auto+manual account control + * Version 8 = Thinking/reasoning budget configuration + * Version 9 = Real WebSearch backends (DuckDuckGo/Brave) with legacy CLI fallback + * Version 10 = Exa + Tavily WebSearch backends + * Version 11 = Discord Channels runtime auto-enable preferences + * Version 12 = Official Channels multi-provider support (Telegram, Discord, iMessage) + * Version 13 = Browser automation defaults to safe manual/off exposure + */ +export const UNIFIED_CONFIG_VERSION = 13; diff --git a/src/config/schemas/websearch.ts b/src/config/schemas/websearch.ts new file mode 100644 index 00000000..d2714c41 --- /dev/null +++ b/src/config/schemas/websearch.ts @@ -0,0 +1,148 @@ +/** + * WebSearch backend configuration types. + * + * Covers all supported search backends: + * - API-backed: Exa, Tavily, Brave + * - Self-hosted: SearXNG + * - Zero-setup: DuckDuckGo + * - Legacy CLI fallbacks: Gemini, Grok, OpenCode + */ + +/** + * DuckDuckGo WebSearch configuration. + */ +export interface DuckDuckGoWebSearchConfig { + /** Enable DuckDuckGo HTML search fallback (default: true) */ + enabled?: boolean; + /** Number of results to fetch (default: 5) */ + max_results?: number; +} + +/** + * Brave WebSearch configuration. + */ +export interface BraveWebSearchConfig { + /** Enable Brave Search when BRAVE_API_KEY is available (default: false) */ + enabled?: boolean; + /** Number of results to fetch (default: 5) */ + max_results?: number; +} + +/** + * Exa WebSearch configuration. + */ +export interface ExaWebSearchConfig { + /** Enable Exa Search when EXA_API_KEY is available (default: false) */ + enabled?: boolean; + /** Number of results to fetch (default: 5) */ + max_results?: number; +} + +/** + * Tavily WebSearch configuration. + */ +export interface TavilyWebSearchConfig { + /** Enable Tavily Search when TAVILY_API_KEY is available (default: false) */ + enabled?: boolean; + /** Number of results to fetch (default: 5) */ + max_results?: number; +} + +/** + * SearXNG WebSearch configuration. + */ +export interface SearxngWebSearchConfig { + /** Enable SearXNG JSON search backend (default: false) */ + enabled?: boolean; + /** Base SearXNG URL, e.g. https://search.example.com (default: '') */ + url?: string; + /** Number of results to fetch (default: 5) */ + max_results?: number; +} + +/** + * Gemini CLI WebSearch configuration. + */ +export interface GeminiWebSearchConfig { + /** Enable Gemini CLI legacy fallback (default: false) */ + enabled?: boolean; + /** Model to use (default: gemini-2.5-flash) */ + model?: string; + /** Timeout in seconds (default: 55) */ + timeout?: number; +} + +/** + * Grok CLI WebSearch configuration. + */ +export interface GrokWebSearchConfig { + /** Enable Grok CLI legacy fallback (default: false - requires GROK_API_KEY) */ + enabled?: boolean; + /** Timeout in seconds (default: 55) */ + timeout?: number; +} + +/** + * OpenCode CLI WebSearch configuration. + */ +export interface OpenCodeWebSearchConfig { + /** Enable OpenCode CLI legacy fallback (default: false) */ + enabled?: boolean; + /** Model to use (default: opencode/grok-code) */ + model?: string; + /** Timeout in seconds (default: 60) */ + timeout?: number; +} + +/** + * WebSearch providers configuration. + * Uses deterministic search backends first, with optional legacy CLI fallback. + */ +export interface WebSearchProvidersConfig { + /** Exa Search API - API-backed search with strong relevance and content extraction */ + exa?: ExaWebSearchConfig; + /** Tavily Search API - API-backed search optimized for agent/tool usage */ + tavily?: TavilyWebSearchConfig; + /** Brave Search API - higher quality results when BRAVE_API_KEY is set */ + brave?: BraveWebSearchConfig; + /** SearXNG JSON search - self-hosted or public instance backend */ + searxng?: SearxngWebSearchConfig; + /** DuckDuckGo HTML search - zero setup default backend */ + duckduckgo?: DuckDuckGoWebSearchConfig; + /** Gemini CLI - optional legacy LLM fallback */ + gemini?: GeminiWebSearchConfig; + /** Grok CLI - optional legacy LLM fallback */ + grok?: GrokWebSearchConfig; + /** OpenCode - optional legacy LLM fallback */ + opencode?: OpenCodeWebSearchConfig; +} + +/** + * WebSearch configuration. + * Uses deterministic local backends for third-party profiles. + * Legacy AI CLI fallbacks remain available for compatibility only. + */ +export interface WebSearchConfig { + /** Master switch - enable/disable WebSearch (default: true) */ + enabled?: boolean; + /** Individual provider configurations */ + providers?: WebSearchProvidersConfig; + // Legacy fields (deprecated, kept for backwards compatibility) + /** @deprecated Use providers.gemini instead */ + gemini?: { + enabled?: boolean; + timeout?: number; + }; + /** @deprecated Unused */ + mode?: 'sequential' | 'parallel'; + /** @deprecated Unused */ + provider?: 'auto' | 'web-search-prime' | 'brave' | 'tavily'; + /** @deprecated Unused */ + fallback?: boolean; + /** @deprecated Unused */ + webSearchPrimeUrl?: string; + /** @deprecated Unused */ + selectedProviders?: string[]; + /** @deprecated Unused */ + customMcp?: unknown[]; +} diff --git a/src/config/unified-config-types.ts b/src/config/unified-config-types.ts index ab4eca6c..ec5eef2f 100644 --- a/src/config/unified-config-types.ts +++ b/src/config/unified-config-types.ts @@ -7,1122 +7,8 @@ * - *.settings.json (env vars) * * Into a single config.yaml structure. - */ - -import type { TargetType } from '../targets/target-adapter'; -import type { CLIProxyProvider, CliproxyRoutingStrategy } from '../cliproxy/types'; -import { CLIPROXY_PROVIDER_IDS } from '../cliproxy/provider-capabilities'; - -/** - * Unified config version. - * Version 2 = YAML unified format - * Version 3 = WebSearch config with model configuration for Gemini/OpenCode - * Version 4 = Copilot API integration (GitHub Copilot proxy) - * Version 5 = Remote proxy configuration (connect to remote CLIProxyAPI) - * Version 6 = Customizable auth tokens (API key and management secret) - * Version 7 = Quota management for hybrid auto+manual account control - * Version 8 = Thinking/reasoning budget configuration - * Version 9 = Real WebSearch backends (DuckDuckGo/Brave) with legacy CLI fallback - * Version 10 = Exa + Tavily WebSearch backends - * Version 11 = Discord Channels runtime auto-enable preferences - * Version 12 = Official Channels multi-provider support (Telegram, Discord, iMessage) - * Version 13 = Browser automation defaults to safe manual/off exposure - */ -export const UNIFIED_CONFIG_VERSION = 13; - -/** - * Supported CLIProxy providers. - * Derived from CLIPROXY_PROVIDER_IDS — single source of truth in provider-capabilities.ts. - */ -export const CLIPROXY_SUPPORTED_PROVIDERS = CLIPROXY_PROVIDER_IDS; - -/** - * Account configuration (formerly in profiles.json). - * Represents an isolated Claude instance via CLAUDE_CONFIG_DIR. - */ -export interface AccountConfig { - /** ISO timestamp when account was created */ - created: string; - /** ISO timestamp of last usage, null if never used */ - last_used: string | null; - /** Context mode for project workspace data */ - context_mode?: 'isolated' | 'shared'; - /** Context-sharing group when context_mode='shared' */ - context_group?: string; - /** Shared continuity depth when context_mode='shared' */ - continuity_mode?: 'standard' | 'deeper'; - /** Bare profile: no shared symlinks (commands, skills, agents, settings.json) */ - bare?: boolean; -} - -/** - * API-based profile configuration. - * Injects environment variables for alternative providers (GLM, Kimi, etc.). * - * Settings are stored in separate *.settings.json files (matching Claude's pattern) - * to allow users to edit them directly without touching config.yaml. + * Types have been reorganized into src/config/schemas/ for maintainability. + * This file re-exports everything for backward compatibility. */ -export interface ProfileConfig { - /** Profile type - currently only 'api' */ - type: 'api'; - /** Path to settings file (e.g., "~/.ccs/glm.settings.json") */ - settings: string; - /** Target CLI to use for this profile (default: 'claude') */ - target?: TargetType; -} - -/** - * CLIProxy OAuth account nickname mapping. - * Maps user-friendly nicknames to email addresses. - */ -export type OAuthAccounts = Record; - -/** - * CLIProxy variant configuration. - * User-defined variants of built-in OAuth providers. - * - * Settings are stored in separate *.settings.json files (matching Claude's pattern) - * to allow users to edit them directly without touching config.yaml. - */ -export interface CLIProxyVariantConfig { - /** Base provider to use */ - provider: CLIProxyProvider; - /** Account nickname (references oauth_accounts) */ - account?: string; - /** Path to settings file (e.g., "~/.ccs/gemini-custom.settings.json") */ - settings?: string; - /** Unique port for variant isolation (8318-8417) */ - port?: number; - /** Per-variant auth override (optional) */ - auth?: CLIProxyAuthConfig; - /** Target CLI to use for this variant (default: 'claude') */ - target?: TargetType; -} - -/** - * Per-tier provider+model mapping for composite variants. - */ -export interface CompositeTierConfig { - /** Provider for this tier */ - provider: CLIProxyProvider; - /** Model ID to use for this tier */ - model: string; - /** Account nickname (optional, references oauth_accounts) */ - account?: string; - /** Fallback provider+model if primary fails */ - fallback?: { - provider: CLIProxyProvider; - model: string; - account?: string; - }; - /** Per-tier thinking budget override (e.g. 'xhigh', 'medium', 'off') */ - thinking?: string; -} - -/** - * Composite variant configuration. - * Mixes different providers per Claude tier (opus, sonnet, haiku) in a single profile. - * Uses CLIProxyAPI root endpoints (/v1/messages) for model-based routing - * instead of provider-specific endpoints (/api/provider/{provider}). - */ -export interface CompositeVariantConfig { - /** Discriminator for composite type */ - type: 'composite'; - /** Which tier ANTHROPIC_MODEL equals (default must be one of the three) */ - default_tier: 'opus' | 'sonnet' | 'haiku'; - /** Per-tier provider+model mapping */ - tiers: { - opus: CompositeTierConfig; - sonnet: CompositeTierConfig; - haiku: CompositeTierConfig; - }; - /** Path to settings file */ - settings?: string; - /** Shared port for the composite profile */ - port?: number; - /** Per-variant auth override (optional) */ - auth?: CLIProxyAuthConfig; - /** Target CLI to use for this composite variant (default: 'claude') */ - target?: TargetType; -} - -/** - * CLIProxy authentication configuration. - * Allows customization of API key and management secret for CLIProxyAPI. - */ -export interface CLIProxyAuthConfig { - /** API key for CCS-managed requests (default: 'ccs-internal-managed') */ - api_key?: string; - /** Management secret for Control Panel login (default: 'ccs') */ - management_secret?: string; -} - -/** - * CLIProxy logging configuration. - * Controls whether CLIProxyAPI writes logs to disk. - * Logs can grow to several GB if left enabled. - */ -export interface CLIProxyLoggingConfig { - /** Enable logging to file (default: false to prevent disk bloat) */ - enabled?: boolean; - /** Enable request logging for debugging (default: false) */ - request_log?: boolean; -} - -/** - * CLIProxy safety configuration. - * Controls high-risk flow safeguards for supported providers. - */ -export interface CLIProxySafetyConfig { - /** Allow skipping AGY responsibility checks and Gemini dashboard typed acknowledgement */ - antigravity_ack_bypass?: boolean; -} - -/** - * Default CLIProxy safety configuration. - */ -export const DEFAULT_CLIPROXY_SAFETY_CONFIG: CLIProxySafetyConfig = { - antigravity_ack_bypass: false, -}; - -/** - * Token refresh configuration. - * Manages background token refresh worker settings. - */ -export interface TokenRefreshSettings { - /** Enable background token refresh (default: false) */ - enabled?: boolean; - /** Refresh check interval in minutes (default: 30) */ - interval_minutes?: number; - /** Preemptive refresh time in minutes (default: 45) */ - preemptive_minutes?: number; - /** Maximum retry attempts per token (default: 3) */ - max_retries?: number; - /** Enable verbose logging (default: false) */ - verbose?: boolean; -} - -export interface CLIProxyRoutingConfig { - /** Credential selection strategy when multiple accounts match */ - strategy?: CliproxyRoutingStrategy; - /** Keep one conversation pinned to the same account when possible */ - session_affinity?: boolean; - /** Go-style duration for session-affinity binding retention */ - session_affinity_ttl?: string; -} - -/** - * CLIProxy configuration section. - */ -export interface CLIProxyConfig { - /** Backend selection: 'original' or 'plus' (default: 'original') */ - backend?: 'original' | 'plus'; - /** Nickname to email mapping for OAuth accounts */ - oauth_accounts: OAuthAccounts; - /** Built-in providers (read-only, for reference) */ - providers: readonly string[]; - /** User-defined provider variants (single-provider or composite) */ - variants: Record; - /** Logging configuration (disabled by default) */ - logging?: CLIProxyLoggingConfig; - /** Safety controls for high-risk provider flows */ - safety?: CLIProxySafetyConfig; - /** Kiro: disable incognito browser mode (use normal browser to save credentials) */ - kiro_no_incognito?: boolean; - /** Global auth configuration for CLIProxyAPI */ - auth?: CLIProxyAuthConfig; - /** Background token refresh worker settings */ - token_refresh?: TokenRefreshSettings; - /** Auto-sync API profiles to local CLIProxy config on settings change (default: true) */ - auto_sync?: boolean; - /** Routing strategy for multi-account CLIProxy selection */ - routing?: CLIProxyRoutingConfig; -} - -export type LoggingLevel = 'error' | 'warn' | 'info' | 'debug'; - -/** - * CCS-owned structured logging configuration. - * Separate from cliproxy.logging, which controls CLIProxy runtime files. - */ -export interface LoggingConfig { - /** Enable CCS-owned structured runtime logging */ - enabled: boolean; - /** Minimum level written to disk */ - level: LoggingLevel; - /** Rotate current log when it reaches this size in MB */ - rotate_mb: number; - /** Keep archived segments for this many days */ - retain_days: number; - /** Redact sensitive values before persistence */ - redact: boolean; - /** In-memory recent event buffer size for dashboard reads */ - live_buffer_size: number; -} - -export const DEFAULT_LOGGING_CONFIG: LoggingConfig = { - enabled: true, - level: 'info', - rotate_mb: 10, - retain_days: 7, - redact: true, - live_buffer_size: 250, -}; - -/** - * User preferences. - */ -export interface PreferencesConfig { - /** UI theme preference */ - theme?: 'light' | 'dark' | 'system'; - /** Enable anonymous telemetry */ - telemetry?: boolean; - /** Enable automatic update checks */ - auto_update?: boolean; -} - -/** - * DuckDuckGo WebSearch configuration. - */ -export interface DuckDuckGoWebSearchConfig { - /** Enable DuckDuckGo HTML search fallback (default: true) */ - enabled?: boolean; - /** Number of results to fetch (default: 5) */ - max_results?: number; -} - -/** - * Brave WebSearch configuration. - */ -export interface BraveWebSearchConfig { - /** Enable Brave Search when BRAVE_API_KEY is available (default: false) */ - enabled?: boolean; - /** Number of results to fetch (default: 5) */ - max_results?: number; -} - -/** - * Exa WebSearch configuration. - */ -export interface ExaWebSearchConfig { - /** Enable Exa Search when EXA_API_KEY is available (default: false) */ - enabled?: boolean; - /** Number of results to fetch (default: 5) */ - max_results?: number; -} - -/** - * Tavily WebSearch configuration. - */ -export interface TavilyWebSearchConfig { - /** Enable Tavily Search when TAVILY_API_KEY is available (default: false) */ - enabled?: boolean; - /** Number of results to fetch (default: 5) */ - max_results?: number; -} - -/** - * SearXNG WebSearch configuration. - */ -export interface SearxngWebSearchConfig { - /** Enable SearXNG JSON search backend (default: false) */ - enabled?: boolean; - /** Base SearXNG URL, e.g. https://search.example.com (default: '') */ - url?: string; - /** Number of results to fetch (default: 5) */ - max_results?: number; -} - -/** - * Gemini CLI WebSearch configuration. - */ -export interface GeminiWebSearchConfig { - /** Enable Gemini CLI legacy fallback (default: false) */ - enabled?: boolean; - /** Model to use (default: gemini-2.5-flash) */ - model?: string; - /** Timeout in seconds (default: 55) */ - timeout?: number; -} - -/** - * Grok CLI WebSearch configuration. - */ -export interface GrokWebSearchConfig { - /** Enable Grok CLI legacy fallback (default: false - requires GROK_API_KEY) */ - enabled?: boolean; - /** Timeout in seconds (default: 55) */ - timeout?: number; -} - -/** - * OpenCode CLI WebSearch configuration. - */ -export interface OpenCodeWebSearchConfig { - /** Enable OpenCode CLI legacy fallback (default: false) */ - enabled?: boolean; - /** Model to use (default: opencode/grok-code) */ - model?: string; - /** Timeout in seconds (default: 60) */ - timeout?: number; -} - -/** - * WebSearch providers configuration. - * Uses deterministic search backends first, with optional legacy CLI fallback. - */ -export interface WebSearchProvidersConfig { - /** Exa Search API - API-backed search with strong relevance and content extraction */ - exa?: ExaWebSearchConfig; - /** Tavily Search API - API-backed search optimized for agent/tool usage */ - tavily?: TavilyWebSearchConfig; - /** Brave Search API - higher quality results when BRAVE_API_KEY is set */ - brave?: BraveWebSearchConfig; - /** SearXNG JSON search - self-hosted or public instance backend */ - searxng?: SearxngWebSearchConfig; - /** DuckDuckGo HTML search - zero setup default backend */ - duckduckgo?: DuckDuckGoWebSearchConfig; - /** Gemini CLI - optional legacy LLM fallback */ - gemini?: GeminiWebSearchConfig; - /** Grok CLI - optional legacy LLM fallback */ - grok?: GrokWebSearchConfig; - /** OpenCode - optional legacy LLM fallback */ - opencode?: OpenCodeWebSearchConfig; -} - -/** - * Copilot API account type. - */ -export type CopilotAccountType = 'individual' | 'business' | 'enterprise'; - -/** - * Copilot API configuration. - * Enables GitHub Copilot subscription usage via copilot-api proxy. - * Strictly opt-in - disabled by default. - * - * !! DISCLAIMER - USE AT YOUR OWN RISK !! - * This uses an UNOFFICIAL reverse-engineered API. - * Excessive usage may trigger GitHub account restrictions. - * CCS provides NO WARRANTY and accepts NO RESPONSIBILITY for any consequences. - */ -export interface CopilotConfig { - /** Enable Copilot integration (default: false) - must be explicitly enabled */ - enabled: boolean; - /** Auto-start copilot-api daemon when using profile (default: false) */ - auto_start: boolean; - /** Port for copilot-api proxy (default: 4141) */ - port: number; - /** GitHub Copilot account type (default: individual) */ - account_type: CopilotAccountType; - /** Rate limit in seconds between requests (null = no limit) */ - rate_limit: number | null; - /** Wait instead of error when rate limit is hit (default: true) */ - wait_on_limit: boolean; - /** Default model ID (e.g., claude-sonnet-4.5) */ - model: string; - /** Model mapping for Claude tiers - maps opus/sonnet/haiku to specific models */ - opus_model?: string; - sonnet_model?: string; - haiku_model?: string; -} - -/** - * Cursor IDE integration configuration. - * Enables Cursor IDE usage via cursor proxy daemon. - */ -export interface CursorConfig { - /** Enable Cursor integration (default: false) */ - enabled: boolean; - /** Port for cursor proxy daemon (default: 20129) */ - port: number; - /** Auto-start daemon when CCS starts (default: false) */ - auto_start: boolean; - /** Enable ghost mode to disable telemetry (default: true) */ - ghost_mode: boolean; - /** Default model ID used by Cursor integration */ - model: string; - /** Optional tier mapping for Claude-compatible model routing */ - opus_model?: string; - /** Optional tier mapping for Claude-compatible model routing */ - sonnet_model?: string; - /** Optional tier mapping for Claude-compatible model routing */ - haiku_model?: string; -} - -/** - * Remote proxy configuration. - * Connect to a remote CLIProxyAPI instance instead of spawning local binary. - */ -export interface ProxyRemoteConfig { - /** Enable remote proxy mode (default: false = local mode) */ - enabled: boolean; - /** Remote proxy hostname or IP (empty = not configured) */ - host: string; - /** - * Remote proxy port. - * Optional - defaults based on protocol: - * - HTTPS: 443 - * - HTTP: 8317 - * When empty/undefined, uses protocol default. - */ - port?: number; - /** Protocol for remote connection */ - protocol: 'http' | 'https'; - /** Auth token for remote proxy API endpoints (optional, sent as header) */ - auth_token: string; - /** - * Management key for remote proxy management API endpoints. - * CLIProxyAPI uses separate authentication for management endpoints - * (/v0/management/*) via 'secret-key' config. - * If not set, falls back to auth_token for backwards compatibility. - */ - management_key?: string; - /** Connection timeout in milliseconds (default: 2000) */ - timeout?: number; - /** Enable auto-sync profiles to remote on settings change (default: false) */ - auto_sync?: boolean; -} - -/** - * Fallback configuration when remote proxy is unreachable. - */ -export interface ProxyFallbackConfig { - /** Enable fallback to local proxy (default: true) */ - enabled: boolean; - /** Auto-start local proxy without prompting (default: false = prompt user) */ - auto_start: boolean; -} - -/** - * Local proxy configuration. - */ -export interface ProxyLocalConfig { - /** Local proxy port (default: 8317) */ - port: number; - /** Auto-start local binary (default: true) */ - auto_start: boolean; -} - -export interface OpenAICompatProxyRoutingConfig { - default?: string; - background?: string; - think?: string; - longContext?: string; - webSearch?: string; - longContextThreshold?: number; -} - -export interface OpenAICompatProxyConfig { - /** Default local port for OpenAI-compatible proxy instances */ - port?: number; - /** Optional profile-scoped local port overrides */ - profile_ports?: Record; - routing?: OpenAICompatProxyRoutingConfig; -} - -/** - * CLIProxy server configuration section. - * Controls whether CCS uses local or remote CLIProxyAPI instance. - */ -export interface CliproxyServerConfig { - /** Remote proxy settings */ - remote: ProxyRemoteConfig; - /** Fallback behavior when remote is unreachable */ - fallback: ProxyFallbackConfig; - /** Local proxy settings */ - local: ProxyLocalConfig; -} - -/** - * Global environment variables configuration. - * These env vars are injected into ALL non-Claude subscription profiles. - * Useful for disabling telemetry, bug commands, error reporting, etc. - */ -export interface GlobalEnvConfig { - /** Enable global env injection (default: true) */ - enabled: boolean; - /** Environment variables to inject */ - env: Record; -} - -/** - * Cross-profile continuity inheritance configuration. - * Maps execution profile names to source account profiles for CLAUDE_CONFIG_DIR reuse. - */ -export interface ContinuityConfig { - /** Profile name -> source account profile name */ - inherit_from_account?: Record; -} - -/** - * Default global env vars for third-party profiles. - * These disable Claude Code telemetry/reporting since we're using proxy. - */ -export const DEFAULT_GLOBAL_ENV: Record = { - DISABLE_BUG_COMMAND: '1', - DISABLE_ERROR_REPORTING: '1', - DISABLE_TELEMETRY: '1', -}; - -/** - * WebSearch configuration. - * Uses deterministic local backends for third-party profiles. - * Legacy AI CLI fallbacks remain available for compatibility only. - */ -export interface WebSearchConfig { - /** Master switch - enable/disable WebSearch (default: true) */ - enabled?: boolean; - /** Individual provider configurations */ - providers?: WebSearchProvidersConfig; - // Legacy fields (deprecated, kept for backwards compatibility) - /** @deprecated Use providers.gemini instead */ - gemini?: { - enabled?: boolean; - timeout?: number; - }; - /** @deprecated Unused */ - mode?: 'sequential' | 'parallel'; - /** @deprecated Unused */ - provider?: 'auto' | 'web-search-prime' | 'brave' | 'tavily'; - /** @deprecated Unused */ - fallback?: boolean; - /** @deprecated Unused */ - webSearchPrimeUrl?: string; - /** @deprecated Unused */ - selectedProviders?: string[]; - /** @deprecated Unused */ - customMcp?: unknown[]; -} - -// ============================================================================ -// QUOTA MANAGEMENT CONFIGURATION (v7+) -// ============================================================================ - -/** - * Auto quota management configuration. - * Controls automatic failover behavior. - */ -export interface AutoQuotaConfig { - /** Enable pre-flight quota check before requests (default: true) */ - preflight_check: boolean; - /** Quota percentage below which account is "exhausted" (default: 5) */ - exhaustion_threshold: number; - /** Tier priority for failover, highest to lowest (default: ['paid']) */ - tier_priority: string[]; - /** Minutes to skip exhausted account before retry (default: 5) */ - cooldown_minutes: number; -} - -/** - * Runtime quota monitor configuration. - * Controls adaptive polling during active sessions. - */ -export interface RuntimeMonitorConfig { - /** Enable runtime monitoring during sessions (default: true) */ - enabled: boolean; - /** Poll interval in seconds when quota > warn_threshold (default: 300) */ - normal_interval_seconds: number; - /** Poll interval in seconds when quota <= warn_threshold (default: 60) */ - critical_interval_seconds: number; - /** Quota percentage that triggers fast polling + warning (default: 20) */ - warn_threshold: number; - /** Quota percentage that triggers cooldown + switch (default: 5) */ - exhaustion_threshold: number; - /** Minutes to cooldown exhausted account (default: 5) */ - cooldown_minutes: number; -} - -/** - * Manual quota management configuration. - * User-controlled overrides for account selection. - */ -export interface ManualQuotaConfig { - /** User-paused accounts (stored in accounts.json) */ - paused_accounts: string[]; - /** Force use of specific account (overrides auto-selection) */ - forced_default: string | null; - /** Lock to specific tier only */ - tier_lock: string | null; -} - -/** - * Quota management mode. - * - auto: Fully automatic failover based on quota - * - manual: User controls everything, no auto-switching - * - hybrid: Auto-failover with user overrides (default) - */ -export type QuotaManagementMode = 'auto' | 'manual' | 'hybrid'; - -/** - * Quota management configuration section. - * Controls hybrid auto+manual account selection for multi-account setups. - */ -export interface QuotaManagementConfig { - /** Management mode (default: hybrid) */ - mode: QuotaManagementMode; - /** Auto mode settings */ - auto: AutoQuotaConfig; - /** Manual mode settings */ - manual: ManualQuotaConfig; - /** Runtime monitor settings */ - runtime_monitor: RuntimeMonitorConfig; -} - -/** - * Default auto quota configuration. - */ -export const DEFAULT_AUTO_QUOTA_CONFIG: AutoQuotaConfig = { - preflight_check: true, - exhaustion_threshold: 5, - tier_priority: ['ultra', 'pro', 'free'], - cooldown_minutes: 5, -}; - -/** - * Default manual quota configuration. - */ -export const DEFAULT_MANUAL_QUOTA_CONFIG: ManualQuotaConfig = { - paused_accounts: [], - forced_default: null, - tier_lock: null, -}; - -/** - * Default runtime monitor configuration. - */ -export const DEFAULT_RUNTIME_MONITOR_CONFIG: RuntimeMonitorConfig = { - enabled: true, - normal_interval_seconds: 300, - critical_interval_seconds: 60, - warn_threshold: 20, - exhaustion_threshold: 5, - cooldown_minutes: 5, -}; - -/** - * Default quota management configuration. - */ -export const DEFAULT_QUOTA_MANAGEMENT_CONFIG: QuotaManagementConfig = { - mode: 'hybrid', - auto: { ...DEFAULT_AUTO_QUOTA_CONFIG }, - manual: { ...DEFAULT_MANUAL_QUOTA_CONFIG }, - runtime_monitor: { ...DEFAULT_RUNTIME_MONITOR_CONFIG }, -}; - -// ============================================================================ -// THINKING CONFIGURATION (v8+) -// ============================================================================ - -/** - * Thinking mode for auto/manual/off control. - * - auto: Apply tier-based defaults (opus→high, sonnet→medium, haiku→low) - * - off: Disable thinking entirely - * - manual: Use explicit override value - */ -export type ThinkingMode = 'auto' | 'off' | 'manual'; - -/** - * Tier-to-thinking level defaults. - * Maps Claude tier names to thinking level names. - */ -export interface ThinkingTierDefaults { - /** Thinking level for opus tier (default: 'high') */ - opus: string; - /** Thinking level for sonnet tier (default: 'medium') */ - sonnet: string; - /** Thinking level for haiku tier (default: 'low') */ - haiku: string; -} - -/** - * Thinking configuration section. - * Controls thinking/reasoning budget injection for CLIProxy providers. - */ -export interface ThinkingConfig { - /** Thinking mode (default: 'auto') */ - mode: ThinkingMode; - /** Manual override value (level name or budget number) */ - override?: string | number; - /** Tier-to-level mapping */ - tier_defaults: ThinkingTierDefaults; - /** Per-provider overrides (e.g., { gemini: { opus: 'high' } }) */ - provider_overrides?: Record>; - /** Show warning when values are clamped (default: true) */ - show_warnings?: boolean; -} - -/** - * Default thinking tier defaults. - */ -export const DEFAULT_THINKING_TIER_DEFAULTS: ThinkingTierDefaults = { - opus: 'high', - sonnet: 'medium', - haiku: 'low', -}; - -/** - * Default thinking configuration. - */ -export const DEFAULT_THINKING_CONFIG: ThinkingConfig = { - mode: 'auto', - tier_defaults: { ...DEFAULT_THINKING_TIER_DEFAULTS }, - show_warnings: true, -}; - -/** - * Supported Anthropic official channel IDs. - */ -export type OfficialChannelId = 'telegram' | 'discord' | 'imessage'; - -/** - * Official Channels configuration. - * Controls runtime-only injection of Anthropic's official channel plugins. - */ -export interface OfficialChannelsConfig { - /** Selected official channels to auto-enable for compatible sessions */ - selected: OfficialChannelId[]; - /** Also add --dangerously-skip-permissions when auto-enable is active */ - unattended: boolean; -} - -/** - * Default Official Channels configuration. - * Disabled by default because the feature requires explicit user setup. - */ -export const DEFAULT_OFFICIAL_CHANNELS_CONFIG: OfficialChannelsConfig = { - selected: [], - unattended: false, -}; - -/** - * Dashboard authentication configuration. - * Optional login protection for CCS dashboard. - * Disabled by default for backward compatibility. - */ -export interface DashboardAuthConfig { - /** Enable dashboard authentication (default: false) */ - enabled: boolean; - /** Username for dashboard login */ - username: string; - /** Bcrypt-hashed password (use: npx bcrypt-cli hash 'password') */ - password_hash: string; - /** Session timeout in hours (default: 24) */ - session_timeout_hours?: number; -} - -/** - * Default dashboard auth configuration. - * Disabled by default - must be explicitly enabled. - */ -export const DEFAULT_DASHBOARD_AUTH_CONFIG: DashboardAuthConfig = { - enabled: false, - username: '', - password_hash: '', - session_timeout_hours: 24, -}; - -/** - * Browser automation configuration. - * Controls Claude browser attach and Codex browser tooling. - */ -export type BrowserToolPolicy = 'auto' | 'manual'; -export type BrowserEvalMode = 'disabled' | 'readonly' | 'readwrite'; - -export interface BrowserClaudeConfig { - /** Enable Claude browser attach (default: false) */ - enabled: boolean; - /** Control whether Claude browser attach is exposed automatically or only via --browser */ - policy: BrowserToolPolicy; - /** Chrome user-data directory used for attach mode */ - user_data_dir: string; - /** DevTools port used for attach mode (default: 9222) */ - devtools_port: number; - /** Eval access mode exposed through browser settings/status surfaces */ - eval_mode?: BrowserEvalMode; -} - -export interface BrowserCodexConfig { - /** Enable Codex browser tooling injection (default: false) */ - enabled: boolean; - /** Control whether Codex browser tooling is exposed automatically or only via --browser */ - policy: BrowserToolPolicy; - /** Eval access mode exposed through browser settings/status surfaces */ - eval_mode?: BrowserEvalMode; -} - -export interface BrowserConfig { - claude: BrowserClaudeConfig; - codex: BrowserCodexConfig; -} - -export const DEFAULT_BROWSER_CONFIG: BrowserConfig = { - claude: { - enabled: false, - policy: 'manual', - user_data_dir: '', - devtools_port: 9222, - eval_mode: 'readonly', - }, - codex: { - enabled: false, - policy: 'manual', - eval_mode: 'readonly', - }, -}; - -/** - * Image analysis configuration. - * Routes image/PDF files through CLIProxy for vision analysis. - */ -export interface ImageAnalysisConfig { - /** Enable image analysis via CLIProxy (default: true) */ - enabled: boolean; - /** Timeout in seconds (default: 60) */ - timeout: number; - /** Provider-to-model mapping for vision analysis */ - provider_models: Record; - /** Fallback backend used when a profile does not resolve to a provider-specific backend */ - fallback_backend?: string; - /** Explicit profile-name-to-backend overrides for settings/custom aliases */ - profile_backends?: Record; -} - -/** - * Default image analysis configuration. - * Enabled by default for CLIProxy providers with vision support. - */ -export const DEFAULT_IMAGE_ANALYSIS_CONFIG: ImageAnalysisConfig = { - enabled: true, - timeout: 60, - provider_models: { - agy: 'gemini-3-1-flash-preview', - gemini: 'gemini-3-flash-preview', - codex: 'gpt-5.1-codex-mini', - kiro: 'kiro-claude-haiku-4-5', - ghcp: 'claude-haiku-4.5', - claude: 'claude-haiku-4-5-20251001', - // 'vision-model' is a generic placeholder - users can override via config.yaml - qwen: 'vision-model', - iflow: 'qwen3-vl-plus', - kimi: 'vision-model', - }, - fallback_backend: 'gemini', - profile_backends: {}, -}; - -/** - * Main unified configuration structure. - * Stored in ~/.ccs/config.yaml - */ -export interface UnifiedConfig { - /** Config version (7 for quota management) */ - version: number; - /** Flag indicating setup wizard has been completed */ - setup_completed?: boolean; - /** Default profile name to use when none specified */ - default?: string; - /** Account-based profiles (isolated Claude instances) */ - accounts: Record; - /** API-based profiles (env var injection) */ - profiles: Record; - /** CLIProxy configuration */ - cliproxy: CLIProxyConfig; - /** OpenAI-compatible local proxy configuration */ - proxy?: OpenAICompatProxyConfig; - /** CCS-owned structured logging configuration */ - logging?: LoggingConfig; - /** User preferences */ - preferences: PreferencesConfig; - /** WebSearch configuration */ - websearch?: WebSearchConfig; - /** Global environment variables for all non-Claude subscription profiles */ - global_env?: GlobalEnvConfig; - /** Cross-profile continuity inheritance mapping */ - continuity?: ContinuityConfig; - /** Copilot API configuration (GitHub Copilot proxy) */ - copilot?: CopilotConfig; - /** Cursor IDE configuration (Cursor proxy daemon) */ - cursor?: CursorConfig; - /** CLIProxy server configuration for remote/local mode */ - cliproxy_server?: CliproxyServerConfig; - /** Quota management configuration (v7+) */ - quota_management?: QuotaManagementConfig; - /** Thinking/reasoning budget configuration (v8+) */ - thinking?: ThinkingConfig; - /** Discord Channels runtime auto-enable preferences (v11+) */ - channels?: OfficialChannelsConfig; - /** Dashboard authentication configuration (optional) */ - dashboard_auth?: DashboardAuthConfig; - /** Browser automation configuration */ - browser?: BrowserConfig; - /** Image analysis configuration (vision via CLIProxy) */ - image_analysis?: ImageAnalysisConfig; -} - -/** - * Default Copilot configuration. - * Strictly opt-in - disabled by default. - * Uses gpt-4.1 as default model (free tier compatible). - */ -export const DEFAULT_COPILOT_CONFIG: CopilotConfig = { - enabled: false, - auto_start: false, - port: 4141, - account_type: 'individual', - rate_limit: null, - wait_on_limit: true, - model: 'gpt-4.1', // Free tier compatible -}; - -/** - * Default Cursor configuration. - * Disabled by default, ghost mode enabled for privacy. - */ -export const DEFAULT_CURSOR_CONFIG: CursorConfig = { - enabled: false, - port: 20129, - auto_start: false, - ghost_mode: true, - model: 'gpt-5.3-codex', -}; - -/** - * Default CLIProxy server configuration. - * Local mode by default - remote must be explicitly enabled. - * Port is optional for remote - defaults based on protocol. - */ -export const DEFAULT_CLIPROXY_SERVER_CONFIG: CliproxyServerConfig = { - remote: { - enabled: false, - host: '', - // port is intentionally omitted - will use protocol default (443 for HTTPS, 8317 for HTTP) - protocol: 'http', - auth_token: '', - }, - fallback: { - enabled: true, - auto_start: false, - }, - local: { - port: 8317, - auto_start: true, - }, -}; - -export const DEFAULT_OPENAI_COMPAT_PROXY_CONFIG: OpenAICompatProxyConfig = { - profile_ports: {}, - routing: { - longContextThreshold: 60_000, - }, -}; - -/** - * Create an empty unified config with defaults. - */ -export function createEmptyUnifiedConfig(): UnifiedConfig { - return { - version: UNIFIED_CONFIG_VERSION, - default: undefined, - accounts: {}, - profiles: {}, - cliproxy: { - backend: 'original', - oauth_accounts: {}, - providers: [...CLIPROXY_SUPPORTED_PROVIDERS], - variants: {}, - logging: { - enabled: false, - request_log: false, - }, - safety: { ...DEFAULT_CLIPROXY_SAFETY_CONFIG }, - auto_sync: true, - routing: { - strategy: 'round-robin', - session_affinity: false, - session_affinity_ttl: '1h', - }, - }, - proxy: { - port: DEFAULT_OPENAI_COMPAT_PROXY_CONFIG.port, - profile_ports: { ...DEFAULT_OPENAI_COMPAT_PROXY_CONFIG.profile_ports }, - routing: { - ...DEFAULT_OPENAI_COMPAT_PROXY_CONFIG.routing, - }, - }, - logging: { ...DEFAULT_LOGGING_CONFIG }, - preferences: { - theme: 'system', - telemetry: false, - auto_update: true, - }, - websearch: { - enabled: true, - providers: { - exa: { - enabled: false, - max_results: 5, - }, - tavily: { - enabled: false, - max_results: 5, - }, - brave: { - enabled: false, - max_results: 5, - }, - searxng: { - enabled: false, - url: '', - max_results: 5, - }, - duckduckgo: { - enabled: true, - max_results: 5, - }, - gemini: { - enabled: false, - model: 'gemini-2.5-flash', - timeout: 55, - }, - opencode: { - enabled: false, - model: 'opencode/grok-code', - timeout: 90, - }, - grok: { - enabled: false, - timeout: 55, - }, - }, - }, - global_env: { - enabled: true, - env: { ...DEFAULT_GLOBAL_ENV }, - }, - copilot: { ...DEFAULT_COPILOT_CONFIG }, - cursor: { ...DEFAULT_CURSOR_CONFIG }, - cliproxy_server: { ...DEFAULT_CLIPROXY_SERVER_CONFIG }, - quota_management: { ...DEFAULT_QUOTA_MANAGEMENT_CONFIG }, - thinking: { ...DEFAULT_THINKING_CONFIG }, - channels: { ...DEFAULT_OFFICIAL_CHANNELS_CONFIG }, - dashboard_auth: { ...DEFAULT_DASHBOARD_AUTH_CONFIG }, - browser: { - claude: { ...DEFAULT_BROWSER_CONFIG.claude }, - codex: { ...DEFAULT_BROWSER_CONFIG.codex }, - }, - image_analysis: { ...DEFAULT_IMAGE_ANALYSIS_CONFIG }, - }; -} - -/** - * Type guard for UnifiedConfig. - * Relaxed validation: accepts configs with version >= 1 and any subset of sections. - * Missing sections will be filled with defaults during merge. - */ -export function isUnifiedConfig(obj: unknown): obj is UnifiedConfig { - if (typeof obj !== 'object' || obj === null) return false; - const config = obj as Record; - // Only require version to be a number >= 1 (allow future versions) - // Sections are optional - will be merged with defaults in loadOrCreateUnifiedConfig - return typeof config.version === 'number' && config.version >= 1; -} +export * from './schemas/index';