import './utils/fetch-proxy-setup'; import * as fs from 'fs'; import { detectClaudeCli } from './utils/claude-detector'; import { getSettingsPath, loadSettings, setGlobalConfigDir, detectCloudSyncPath, } from './utils/config-manager'; import { expandPath } from './utils/helpers'; import { validateGlmKey, validateMiniMaxKey, validateAnthropicKey, } from './utils/api-key-validator'; import { ErrorManager } from './utils/error-manager'; import { execClaudeWithCLIProxy, CLIProxyProvider, ensureCliproxyService, isAuthenticated, } from './cliproxy'; import { getEffectiveEnvVars, getCompositeEnvVars } from './cliproxy/config/env-builder'; import { CLIPROXY_DEFAULT_PORT } from './cliproxy/config/port-manager'; import { ensureWebSearchMcpOrThrow, displayWebSearchStatus, getWebSearchHookEnv, syncWebSearchMcpToConfigDir, appendThirdPartyWebSearchToolArgs, createWebSearchTraceContext, } from './utils/websearch-manager'; import { ensureImageAnalysisMcpOrThrow, syncImageAnalysisMcpToConfigDir, appendThirdPartyImageAnalysisToolArgs, } from './utils/image-analysis'; import { appendBrowserToolArgs, ensureBrowserMcpOrThrow, getBlockedBrowserOverrideWarning, getEffectiveClaudeBrowserAttachConfig, resolveBrowserExposure, resolveBrowserLaunchFlagResolution, resolveOptionalBrowserAttachRuntime, syncBrowserMcpToConfigDir, } from './utils/browser'; import { getBrowserConfig, getGlobalEnvConfig, getOfficialChannelsConfig, } from './config/unified-config-loader'; import { ensureProfileHooks as ensureImageAnalyzerHooks, removeImageAnalysisProfileHook, } from './utils/hooks/image-analyzer-profile-hook-injector'; import { applyImageAnalysisRuntimeOverrides, getImageAnalysisHookEnv, installImageAnalyzerHook, prepareImageAnalysisFallbackHook, resolveImageAnalysisRuntimeConnection, resolveImageAnalysisRuntimeStatus, } from './utils/hooks'; import { fail, info, warn } from './utils/ui'; import { isCopilotSubcommandToken } from './copilot/constants'; import { buildOfficialChannelsArgs, getOfficialChannelsEnvironmentStatus, officialChannelRequiresMacOS, resolveOfficialChannelsLaunchPlan, } from './channels/official-channels-runtime'; import { getOfficialChannelReadiness } from './channels/official-channels-store'; import { isCursorSubcommandToken, LEGACY_CURSOR_PROFILE_NAME } from './cursor/constants'; import { isCLIProxyProvider } from './cliproxy/provider-capabilities'; // Import centralized error handling import { handleError, runCleanup } from './errors'; import { tryHandleRootCommand } from './commands/root-command-router'; // Import extracted utility functions import { execClaude, stripAnthropicRoutingEnv, stripBrowserEnv } from './utils/shell-executor'; import { isDeprecatedGlmtProfileName, normalizeDeprecatedGlmtEnv } from './utils/glmt-deprecation'; import { createOpenAICompatLaunchSettingsPath } from './utils/openai-compat-launch-settings'; import { maybeWarnAboutResumeLaneMismatch } from './auth/resume-lane-warning'; import { createLogger } from './services/logging'; import { buildCodexBrowserMcpOverrides } from './utils/browser-codex-overrides'; import type { ProfileDetectionResult } from './auth/profile-detector'; import type { BrowserLaunchOverride } from './utils/browser'; // Import target adapter system import { registerTarget, getTarget, ClaudeAdapter, DroidAdapter, CodexAdapter, evaluateTargetRuntimeCompatibility, pruneOrphanedModels, resolveDroidProvider, type TargetCredentials, } from './targets'; import { resolveTargetType, stripTargetFlag } from './targets/target-resolver'; import { DroidReasoningFlagError, resolveDroidReasoningRuntime, } from './targets/droid-reasoning-runtime'; import { DroidCommandRouterError, routeDroidCommandArgs } from './targets/droid-command-router'; import { resolveCliproxyBridgeMetadata } from './api/services/cliproxy-profile-bridge'; import { buildOpenAICompatProxyEnv, resolveOpenAICompatProfileConfig, startOpenAICompatProxy, } from './proxy'; // Version and Update check utilities import { getVersion } from './utils/version'; import { checkForUpdates, showUpdateNotification, checkCachedUpdate, isCacheStale, } from './utils/update-checker'; // Note: npm is now the only supported installation method // ========== Profile Detection ========== interface DetectedProfile { profile: string; remainingArgs: string[]; } interface RuntimeReasoningResolution { argsWithoutReasoningFlags: string[]; reasoningOverride: string | number | undefined; reasoningSource: 'flag' | 'env' | undefined; sourceDisplay: string | undefined; } const CODEX_RUNTIME_REASONING_LEVELS = new Set(['minimal', 'low', 'medium', 'high', 'xhigh']); const CODEX_NATIVE_PASSTHROUGH_FLAGS = new Set(['--help', '-h', '--version', '-v']); function resolveCodexRuntimeConfigOverrides( target: ReturnType, browserLaunchOverride: BrowserLaunchOverride | undefined ): string[] { if (target !== 'codex') { return []; } const codexBrowserExposure = resolveBrowserExposure( getBrowserConfig().codex, browserLaunchOverride ); if (!codexBrowserExposure.exposeForLaunch) { return []; } return buildCodexBrowserMcpOverrides(); } /** * Smart profile detection */ function detectProfile(args: string[]): DetectedProfile { if (args.length === 0 || args[0].startsWith('-')) { // No args or first arg is a flag → use default profile return { profile: 'default', remainingArgs: args }; } else { // First arg doesn't start with '-' → treat as profile name return { profile: args[0], remainingArgs: args.slice(1) }; } } function normalizeLegacyCursorArgs(args: string[]): string[] { if (args[0] === 'legacy' && args[1] === 'cursor') { return [LEGACY_CURSOR_PROFILE_NAME, ...args.slice(2)]; } return args; } function printCursorLegacySubcommandDeprecation(subcommand: string): void { console.error( info(`\`ccs cursor ${subcommand}\` is deprecated for the legacy Cursor IDE bridge.`) ); console.error( info( `Use \`ccs legacy cursor ${subcommand}\` for the old bridge, or \`ccs cursor --auth|--accounts|--config\` for the CLIProxy provider.` ) ); console.error(''); } function resolveRuntimeReasoningFlags( args: string[], envThinkingValue: string | undefined ): RuntimeReasoningResolution { const runtime = resolveDroidReasoningRuntime(args, envThinkingValue); if (runtime.duplicateDisplays.length > 0) { console.error( warn( `[!] Multiple reasoning flags detected. Using first occurrence: ${runtime.sourceDisplay || ''}` ) ); } return { argsWithoutReasoningFlags: runtime.argsWithoutReasoningFlags, reasoningOverride: runtime.reasoningOverride, reasoningSource: runtime.sourceFlag ? 'flag' : runtime.reasoningOverride !== undefined ? 'env' : undefined, sourceDisplay: runtime.sourceDisplay, }; } function normalizeCodexRuntimeReasoningOverride( value: string | number | undefined ): string | undefined { return typeof value === 'string' && CODEX_RUNTIME_REASONING_LEVELS.has(value) ? value : undefined; } function exitWithRuntimeReasoningFlagError( message: string, options: { codexAliasLevels: string; includeDroidExecExample?: boolean; } ): never { console.error(fail(message)); console.error(' Examples: --thinking low, --thinking 8192, --thinking off'); console.error(` Codex alias: --effort ${options.codexAliasLevels}`); if (options.includeDroidExecExample) { console.error(' Droid exec: --reasoning-effort high'); } process.exit(1); } // ========== Main Execution ========== interface ProfileError extends Error { profileName?: string; availableProfiles?: string; suggestions?: string[]; } /** * Perform background update check (refreshes cache, no notification) */ async function refreshUpdateCache(): Promise { try { const currentVersion = getVersion(); // npm is now the only supported installation method await checkForUpdates(currentVersion, true, 'npm'); } catch (_e) { // Silently fail - update check shouldn't crash main CLI } } /** * Show update notification if cached result indicates update available * Returns true if notification was shown */ async function showCachedUpdateNotification(): Promise { try { const currentVersion = getVersion(); const updateInfo = checkCachedUpdate(currentVersion); if (updateInfo) { await showUpdateNotification(updateInfo); return true; } } catch (_e) { // Silently fail } return false; } function resolveNativeClaudeLaunchArgs( args: string[], profileType: 'default' | 'account', targetConfigDir?: string ): string[] { const config = getOfficialChannelsConfig(); const environment = getOfficialChannelsEnvironmentStatus( targetConfigDir ? { CLAUDE_CONFIG_DIR: targetConfigDir } : undefined ); const channelReadiness = { telegram: getOfficialChannelReadiness('telegram'), discord: getOfficialChannelReadiness('discord'), imessage: !officialChannelRequiresMacOS('imessage') || process.platform === 'darwin', }; const plan = resolveOfficialChannelsLaunchPlan({ args, config, target: 'claude', profileType, environment, channelReadiness, }); for (const message of plan.skippedMessages) { console.error(warn(message)); } if ( config.selected.length > 0 && environment.auth.state === 'eligible' && environment.auth.orgRequirementMessage ) { console.error(warn(environment.auth.orgRequirementMessage)); } if (!plan.applied) { return args; } return buildOfficialChannelsArgs(args, plan.appliedChannels, plan.wantsPermissionBypass); } function shouldPassthroughNativeCodexFlagCommand(args: string[]): boolean { return getNativeCodexPassthroughArgs(args) !== null; } function getNativeCodexPassthroughArgs(args: string[]): string[] | null { const targetArgs = stripTargetFlag(args); if (resolveTargetType(args) !== 'codex' || targetArgs.length === 0) { return null; } const firstArg = targetArgs[0] || ''; if (CODEX_NATIVE_PASSTHROUGH_FLAGS.has(firstArg)) { return targetArgs; } const secondArg = targetArgs[1] || ''; if (firstArg === 'codex' && CODEX_NATIVE_PASSTHROUGH_FLAGS.has(secondArg)) { return targetArgs.slice(1); } return null; } function execNativeCodexFlagCommand(args: string[]): void { const adapter = getTarget('codex'); if (!adapter) { console.error(fail('Target adapter not found for "codex"')); process.exit(1); } const binaryInfo = adapter.detectBinary(); if (!binaryInfo) { console.error(fail('Codex CLI not found.')); console.error(info('Install a recent @openai/codex build, then retry.')); process.exit(1); } const targetArgs = getNativeCodexPassthroughArgs(args); if (!targetArgs) { console.error(fail('Native Codex passthrough args could not be resolved.')); process.exit(1); } const creds: TargetCredentials = { profile: 'default', baseUrl: '', apiKey: '', }; const builtArgs = adapter.buildArgs('default', targetArgs, { creds, profileType: 'default', binaryInfo, }); const targetEnv = adapter.buildEnv(creds, 'default'); adapter.exec(builtArgs, targetEnv, { binaryInfo }); } async function main(): Promise { // Register target adapters registerTarget(new ClaudeAdapter()); registerTarget(new DroidAdapter()); registerTarget(new CodexAdapter()); const cliLogger = createLogger('cli'); let args = process.argv.slice(2); const isCompletionCommand = args[0] === '__complete'; // Initialize UI colors early to ensure consistent colored output // Must happen before any status messages (ok, info, fail, etc.) if (!isCompletionCommand && process.stdout.isTTY && !process.env['CI']) { const { initUI } = await import('./utils/ui'); await initUI(); } // Parse --config-dir flag (must happen before any config loading) const configDirIdx = args.findIndex((a) => a === '--config-dir' || a.startsWith('--config-dir=')); if (configDirIdx !== -1) { const arg = args[configDirIdx]; let configDirValue: string | undefined; let spliceCount = 1; if (arg.startsWith('--config-dir=')) { configDirValue = arg.split('=').slice(1).join('='); } else { configDirValue = args[configDirIdx + 1]; spliceCount = 2; } if (!configDirValue || configDirValue.startsWith('-')) { console.error(fail('--config-dir requires a path argument')); process.exit(1); } try { const stat = fs.statSync(configDirValue); if (!stat.isDirectory()) { console.error(fail(`Not a directory: ${configDirValue}`)); process.exit(1); } } catch { console.error(fail(`Config directory not found: ${configDirValue}`)); console.error(info('Create the directory first, then copy your config files into it.')); process.exit(1); } setGlobalConfigDir(configDirValue); // Security warning: cloud sync paths expose OAuth tokens const cloudService = detectCloudSyncPath(configDirValue); if (!isCompletionCommand && cloudService) { console.error(warn(`CCS directory is under ${cloudService}.`)); console.error(' OAuth tokens in cliproxy/auth/ will be synced to cloud.'); console.error(' Consider: CCS_DIR=/path/outside/cloud ccs ...'); } // Remove consumed args so they don't leak to Claude CLI args.splice(configDirIdx, spliceCount); } else if (process.env.CCS_DIR) { // Also warn for CCS_DIR env var pointing to cloud sync const cloudService = detectCloudSyncPath(process.env.CCS_DIR); if (!isCompletionCommand && cloudService) { console.error(warn(`CCS directory is under ${cloudService}.`)); console.error(' OAuth tokens in cliproxy/auth/ will be synced to cloud.'); console.error(' Consider: CCS_DIR=/path/outside/cloud ccs ...'); } } else if (process.env.CCS_HOME) { // Also warn for CCS_HOME env var pointing to cloud sync const cloudService = detectCloudSyncPath(process.env.CCS_HOME); if (!isCompletionCommand && cloudService) { console.error(warn(`CCS directory is under ${cloudService}.`)); console.error(' OAuth tokens in cliproxy/auth/ will be synced to cloud.'); console.error(' Consider: CCS_DIR=/path/outside/cloud ccs ...'); } } if (isCompletionCommand) { await tryHandleRootCommand(args); return; } args = normalizeLegacyCursorArgs(args); let browserLaunchOverride: BrowserLaunchOverride | undefined; try { const browserLaunchFlags = resolveBrowserLaunchFlagResolution(args); browserLaunchOverride = browserLaunchFlags.override; args = browserLaunchFlags.argsWithoutFlags; } catch (error) { console.error(fail((error as Error).message)); process.exit(1); return; } cliLogger.info('command.start', 'CLI invocation started', { command: args[0] || 'default', argCount: args.length, flags: args.filter((arg) => arg.startsWith('-')).slice(0, 20), }); if (shouldPassthroughNativeCodexFlagCommand(args)) { execNativeCodexFlagCommand(args); return; } const firstArg = args[0]; // Trigger update check early for ALL commands except version/help/update // Only if TTY and not CI to avoid noise in automated environments const skipUpdateCheck = [ 'version', '--version', '-v', 'help', '--help', '-h', 'update', '--update', ]; if (process.stdout.isTTY && !process.env['CI'] && !skipUpdateCheck.includes(firstArg)) { // 1. Show cached update notification (async for proper UI) await showCachedUpdateNotification(); // 2. Refresh cache in background if stale (non-blocking) if (isCacheStale()) { refreshUpdateCache(); } } // Auto-migrate to unified config format (silent if already migrated) // Skip if user is explicitly running migrate command if (firstArg !== 'migrate') { const { autoMigrate } = await import('./config/migration-manager'); await autoMigrate(); } // Auto-recovery for missing configuration (BEFORE any early-exit commands) // This ensures ALL commands benefit from auto-recovery, not just profile-switching flow // Recovery is safe to run early - it only creates missing files with safe defaults // Wrapped in try-catch to prevent blocking --version/--help on permission errors try { const RecoveryManagerModule = await import('./management/recovery-manager'); const RecoveryManager = RecoveryManagerModule.default; const recovery = new RecoveryManager(); const recovered = recovery.recoverAll(); if (recovered) { recovery.showRecoveryHints(); } } catch (err) { cliLogger.warn('recovery.failed', 'Auto-recovery failed during CLI startup', { message: (err as Error).message, }); // Recovery is best-effort - don't block basic CLI functionality console.warn('[!] Recovery failed:', (err as Error).message); } if (await tryHandleRootCommand(args)) { return; } if ( typeof firstArg === 'string' && isCLIProxyProvider(firstArg) && args.length > 1 && (args.includes('--help') || args.includes('-h')) ) { const { showProviderShortcutHelp } = await import('./commands/help-command'); await showProviderShortcutHelp(firstArg); return; } // Special case: copilot command (GitHub Copilot integration) // Route known subcommands to command handler, keep all other args as profile passthrough. if (firstArg === 'copilot' && args.length > 1) { const copilotToken = args[1]; const shouldRouteToCopilotCommand = isCopilotSubcommandToken(copilotToken); if (shouldRouteToCopilotCommand) { const { handleCopilotCommand } = await import('./commands/copilot-command'); const exitCode = await handleCopilotCommand(args.slice(1)); process.exit(exitCode); } } // Special case: explicit legacy Cursor bridge namespace. if (firstArg === LEGACY_CURSOR_PROFILE_NAME && args.length > 1) { const { handleCursorCommand } = await import('./commands/cursor-command'); const cursorToken = args[1]; if (isCursorSubcommandToken(cursorToken)) { const exitCode = await handleCursorCommand(args.slice(1)); process.exit(exitCode); } } // Compatibility shim: old `ccs cursor ` still forwards to the legacy bridge // for one migration window, but bare/positional `ccs cursor` now belongs to CLIProxy. if (firstArg === 'cursor' && args.length > 1) { const { handleCursorCommand } = await import('./commands/cursor-command'); const cursorToken = args[1]; if (isCursorSubcommandToken(cursorToken) && cursorToken !== '--help' && cursorToken !== '-h') { printCursorLegacySubcommandDeprecation(cursorToken); const exitCode = await handleCursorCommand(args.slice(1)); process.exit(exitCode); } } // First-time install: offer setup wizard for interactive users // Check independently of recovery status (user may have empty config.yaml) // Skip if headless, CI, or non-TTY environment const { isFirstTimeInstall } = await import('./commands/setup-command'); if (process.stdout.isTTY && !process.env['CI'] && isFirstTimeInstall()) { console.log(''); console.log(info('First-time install detected. Run `ccs setup` for guided configuration.')); console.log(' Or use `ccs config` for the web dashboard.'); console.log(''); } // Use ProfileDetector to determine profile type const ProfileDetectorModule = await import('./auth/profile-detector'); const ProfileDetector = ProfileDetectorModule.default; const InstanceManagerModule = await import('./management/instance-manager'); const InstanceManager = InstanceManagerModule.default; const ProfileRegistryModule = await import('./auth/profile-registry'); const ProfileRegistry = ProfileRegistryModule.default; const AccountContextModule = await import('./auth/account-context'); const { resolveAccountContextPolicy, isAccountContextMetadata } = AccountContextModule; const ProfileContinuityModule = await import('./auth/profile-continuity-inheritance'); const { resolveProfileContinuityInheritance } = ProfileContinuityModule; const detector = new ProfileDetector(); try { // Detect profile (strip --target flags before profile detection) const cleanArgs = stripTargetFlag(args); const { profile, remainingArgs } = detectProfile(cleanArgs); const profileInfo: ProfileDetectionResult = detector.detectProfileType(profile); let resolvedTarget: ReturnType; try { resolvedTarget = resolveTargetType( args, profileInfo.target ? { target: profileInfo.target } : undefined ); } catch (error) { console.error(fail((error as Error).message)); process.exit(1); return; } // Detect Claude CLI (needed for claude target and all CLIProxy-derived flows) const claudeCliRaw = detectClaudeCli(); if (resolvedTarget === 'claude' && !claudeCliRaw) { await ErrorManager.showClaudeNotFound(); process.exit(1); } const claudeCli = claudeCliRaw || ''; // Resolve non-claude target adapter once. const targetAdapter = resolvedTarget !== 'claude' ? getTarget(resolvedTarget) : null; let resolvedSettingsPath: string | undefined; let resolvedSettings: ReturnType | undefined; let resolvedCliproxyBridge: ReturnType | undefined; // Preflight unsupported profile/target combinations BEFORE binary detection, // so users get the most actionable error even when the target CLI is not installed. if (resolvedTarget !== 'claude') { if (!targetAdapter) { console.error(fail(`Target adapter not found for "${resolvedTarget}"`)); process.exit(1); } if (profileInfo.type === 'settings') { resolvedSettingsPath = profileInfo.settingsPath ? expandPath(profileInfo.settingsPath) : getSettingsPath(profileInfo.name); resolvedSettings = loadSettings(resolvedSettingsPath); resolvedCliproxyBridge = resolveCliproxyBridgeMetadata(resolvedSettings); const compatibility = evaluateTargetRuntimeCompatibility({ target: resolvedTarget, profileType: profileInfo.type, cliproxyBridgeProvider: resolvedCliproxyBridge?.provider ?? null, }); if (!compatibility.supported) { console.error( fail( compatibility.reason || `${targetAdapter.displayName} does not support this profile.` ) ); if (compatibility.suggestion) { console.error(info(compatibility.suggestion)); } process.exit(1); } } else { const compatibility = evaluateTargetRuntimeCompatibility({ target: resolvedTarget, profileType: profileInfo.type, cliproxyProvider: profileInfo.type === 'cliproxy' ? profileInfo.provider : undefined, isComposite: profileInfo.type === 'cliproxy' ? Boolean(profileInfo.isComposite) : undefined, }); if (!compatibility.supported) { console.error( fail( compatibility.reason || `${targetAdapter.displayName} does not support this profile.` ) ); if (compatibility.suggestion) { console.error(info(compatibility.suggestion)); } process.exit(1); } } if (profileInfo.type === 'default') { if (!targetAdapter.supportsProfileType('default')) { console.error(fail(`${targetAdapter.displayName} does not support default profile mode`)); process.exit(1); } // For default mode, Droid requires explicit credentials from environment. if (resolvedTarget === 'droid') { const baseUrl = process.env['ANTHROPIC_BASE_URL'] || ''; const apiKey = process.env['ANTHROPIC_AUTH_TOKEN'] || ''; if (!baseUrl.trim() || !apiKey.trim()) { console.error( fail( `${targetAdapter.displayName} default mode requires ANTHROPIC_BASE_URL and ANTHROPIC_AUTH_TOKEN` ) ); console.error(info('Use a settings-based profile instead: ccs glm --target droid')); process.exit(1); } } } } // For non-claude targets, verify target binary exists once and pass it through. const targetBinaryInfo = targetAdapter?.detectBinary() ?? null; const browserConfig = getBrowserConfig(); const claudeAttachConfig = resolvedTarget === 'claude' ? getEffectiveClaudeBrowserAttachConfig(browserConfig) : undefined; const codexRuntimeConfigOverrides = resolveCodexRuntimeConfigOverrides( resolvedTarget, browserLaunchOverride ); const claudeBrowserExposure = resolvedTarget === 'claude' ? resolveBrowserExposure( { enabled: claudeAttachConfig?.enabled ?? browserConfig.claude.enabled, policy: browserConfig.claude.policy, }, browserLaunchOverride ) : undefined; const codexBrowserExposure = resolvedTarget === 'codex' ? resolveBrowserExposure(browserConfig.codex, browserLaunchOverride) : undefined; const blockedBrowserOverrideWarning = resolvedTarget === 'claude' && claudeBrowserExposure ? getBlockedBrowserOverrideWarning('Claude Browser Attach', claudeBrowserExposure) : resolvedTarget === 'codex' && codexBrowserExposure ? getBlockedBrowserOverrideWarning('Codex Browser Tools', codexBrowserExposure) : undefined; if (blockedBrowserOverrideWarning) { console.error(warn(blockedBrowserOverrideWarning)); } if (resolvedTarget !== 'claude' && !targetBinaryInfo) { const displayName = targetAdapter?.displayName || resolvedTarget; console.error(fail(`${displayName} CLI not found.`)); if (resolvedTarget === 'droid') { console.error(info('Install: npm i -g @factory/cli')); } else if (resolvedTarget === 'codex') { console.error(info('Install a recent @openai/codex build, then retry.')); } process.exit(1); } // Best-effort: prune stale Droid model entries at runtime so settings.json stays clean. if (resolvedTarget === 'droid') { try { const allProfiles = detector.getAllProfiles(); const activeProfiles = allProfiles.settings.filter((name) => /^[a-zA-Z0-9._-]+$/.test(name) ); await pruneOrphanedModels(activeProfiles); } catch (error) { console.error(warn(`[!] Droid prune skipped: ${(error as Error).message}`)); } } let targetRemainingArgs = remainingArgs; let runtimeReasoningOverride: string | number | undefined; if (resolvedTarget === 'droid') { try { const droidRoute = routeDroidCommandArgs(remainingArgs); targetRemainingArgs = droidRoute.argsForDroid; if (droidRoute.mode === 'interactive') { const runtime = resolveRuntimeReasoningFlags(remainingArgs, process.env.CCS_THINKING); targetRemainingArgs = runtime.argsWithoutReasoningFlags; runtimeReasoningOverride = runtime.reasoningOverride; } else { if (droidRoute.duplicateReasoningDisplays.length > 0) { console.error( warn( `[!] Multiple reasoning flags detected. Using first occurrence: ${droidRoute.reasoningSourceDisplay || ''}` ) ); } if (droidRoute.autoPrependedExec && process.stdout.isTTY) { console.error( info('Detected Droid exec-only flags. Routing as: droid exec [prompt]') ); } } } catch (error) { if (error instanceof DroidReasoningFlagError || error instanceof DroidCommandRouterError) { exitWithRuntimeReasoningFlagError(error.message, { codexAliasLevels: 'minimal|low|medium|high|xhigh', includeDroidExecExample: true, }); } throw error; } } else if (resolvedTarget === 'codex') { try { const runtime = resolveRuntimeReasoningFlags(remainingArgs, process.env.CCS_THINKING); targetRemainingArgs = runtime.argsWithoutReasoningFlags; const normalizedReasoning = normalizeCodexRuntimeReasoningOverride( runtime.reasoningOverride ); if (runtime.reasoningOverride !== undefined && !normalizedReasoning) { if (runtime.reasoningSource === 'flag') { throw new DroidReasoningFlagError( 'Codex target supports reasoning levels only: minimal, low, medium, high, xhigh.', '--effort' ); } runtimeReasoningOverride = undefined; } else { runtimeReasoningOverride = normalizedReasoning; } } catch (error) { if (error instanceof DroidReasoningFlagError) { exitWithRuntimeReasoningFlagError(error.message, { codexAliasLevels: 'minimal|low|medium|high|xhigh', }); } throw error; } } // Special case: headless delegation (-p/--prompt) // Keep existing behavior for Claude targets only; non-claude targets must continue // through normal adapter dispatch logic. if (args.includes('-p') || args.includes('--prompt')) { const shouldUseDelegation = resolvedTarget === 'claude' && profileInfo.type !== 'cliproxy'; if (shouldUseDelegation) { const { DelegationHandler } = await import('./delegation/delegation-handler'); const handler = new DelegationHandler(); await handler.route(cleanArgs); return; } } if (profileInfo.type === 'cliproxy') { // CLIPROXY FLOW: OAuth-based profiles (gemini, codex, agy, qwen) or user-defined variants const imageAnalysisMcpReady = resolvedTarget === 'claude' ? ensureImageAnalysisMcpOrThrow() : true; if (resolvedTarget === 'claude') { ensureWebSearchMcpOrThrow(); } const provider = profileInfo.provider || (profileInfo.name as CLIProxyProvider); const expandedCliproxySettingsPath = profileInfo.settingsPath ? expandPath(profileInfo.settingsPath) : undefined; if (resolvedTarget === 'claude') { if (imageAnalysisMcpReady) { removeImageAnalysisProfileHook(profileInfo.name, expandedCliproxySettingsPath); } else { const imageAnalysisFallbackHookReady = prepareImageAnalysisFallbackHook(); ensureImageAnalyzerHooks({ profileName: profileInfo.name, profileType: profileInfo.type, cliproxyProvider: provider, isComposite: profileInfo.isComposite, settingsPath: expandedCliproxySettingsPath, sharedHookInstalled: imageAnalysisFallbackHookReady, }); } } const customSettingsPath = profileInfo.settingsPath; // undefined for hardcoded profiles const variantPort = profileInfo.port; // variant-specific port for isolation const cliproxyPort = variantPort || CLIPROXY_DEFAULT_PORT; if (resolvedTarget !== 'claude') { const adapter = targetAdapter; if (!adapter) { console.error(fail(`Target adapter not found for "${resolvedTarget}"`)); process.exitCode = 1; return; } if (!adapter.supportsProfileType('cliproxy')) { console.error(fail(`${adapter.displayName} does not support CLIProxy profiles`)); process.exitCode = 1; return; } // Keep CLIProxy management/auth flags on Claude flow only. const unsupportedCliproxyFlags = [ '--auth', '--logout', '--accounts', '--add', '--use', '--config', '--headless', '--paste-callback', '--port-forward', '--nickname', '--kiro-auth-method', '--kiro-idc-start-url', '--kiro-idc-region', '--kiro-idc-flow', '--backend', '--proxy-host', '--proxy-port', '--proxy-protocol', '--proxy-auth-token', '--proxy-timeout', '--local-proxy', '--remote-only', '--no-fallback', '--allow-self-signed', '--1m', '--no-1m', ]; const providedUnsupportedFlag = unsupportedCliproxyFlags.find( (flag) => targetRemainingArgs.includes(flag) || targetRemainingArgs.some((arg) => arg.startsWith(`${flag}=`)) ); if (providedUnsupportedFlag) { console.error( fail( `${providedUnsupportedFlag} is only supported when running CLIProxy profiles on Claude target` ) ); console.error( info(`Run with Claude target: ccs ${profileInfo.name} --target claude ...`) ); process.exitCode = 1; return; } // For Droid execution path, require existing OAuth auth and running local proxy. if (profileInfo.isComposite && profileInfo.compositeTiers) { const compositeProviders = [ ...new Set(Object.values(profileInfo.compositeTiers).map((tier) => tier.provider)), ] as CLIProxyProvider[]; const missingProvider = compositeProviders.find((p) => !isAuthenticated(p)); if (missingProvider) { console.error( fail(`Missing OAuth auth for composite tier provider: ${missingProvider}`) ); console.error(info(`Authenticate first: ccs ${missingProvider} --auth`)); process.exitCode = 1; return; } } else if (!isAuthenticated(provider)) { console.error(fail(`No OAuth authentication found for provider: ${provider}`)); console.error(info(`Authenticate first: ccs ${provider} --auth`)); process.exitCode = 1; return; } const ensureServiceResult = await ensureCliproxyService( cliproxyPort, targetRemainingArgs.includes('--verbose') || targetRemainingArgs.includes('-v') ); if (!ensureServiceResult.started) { console.error( fail(ensureServiceResult.error || 'Failed to start local CLIProxy service') ); process.exitCode = 1; return; } const envVars = profileInfo.isComposite && profileInfo.compositeTiers && profileInfo.compositeDefaultTier ? getCompositeEnvVars( profileInfo.compositeTiers, profileInfo.compositeDefaultTier, cliproxyPort, customSettingsPath ) : getEffectiveEnvVars(provider, cliproxyPort, customSettingsPath); const creds: TargetCredentials = { profile: profileInfo.name, baseUrl: envVars['ANTHROPIC_BASE_URL'] || '', apiKey: envVars['ANTHROPIC_AUTH_TOKEN'] || '', model: envVars['ANTHROPIC_MODEL'] || undefined, provider: resolveDroidProvider({ provider: envVars['CCS_DROID_PROVIDER'] || envVars['DROID_PROVIDER'], baseUrl: envVars['ANTHROPIC_BASE_URL'], model: envVars['ANTHROPIC_MODEL'], }), reasoningOverride: runtimeReasoningOverride, runtimeConfigOverrides: codexRuntimeConfigOverrides, envVars, }; if (!creds.baseUrl || !creds.apiKey) { console.error( fail( `Missing CLIProxy runtime credentials for ${profileInfo.name} (ANTHROPIC_BASE_URL/AUTH_TOKEN)` ) ); console.error( info('Reconfigure with: ccs config > CLIProxy, or run ccs --config') ); process.exitCode = 1; return; } await adapter.prepareCredentials(creds); const targetArgs = adapter.buildArgs(profileInfo.name, targetRemainingArgs, { creds, profileType: profileInfo.type, binaryInfo: targetBinaryInfo || undefined, }); const targetEnv = adapter.buildEnv(creds, profileInfo.type); adapter.exec(targetArgs, targetEnv, { binaryInfo: targetBinaryInfo || undefined }); return; } await execClaudeWithCLIProxy(claudeCli, provider, remainingArgs, { customSettingsPath, port: cliproxyPort, isComposite: profileInfo.isComposite, compositeTiers: profileInfo.compositeTiers, compositeDefaultTier: profileInfo.compositeDefaultTier, profileName: profileInfo.name, }); } else if (profileInfo.type === 'copilot') { // COPILOT FLOW: GitHub Copilot subscription via copilot-api proxy ensureWebSearchMcpOrThrow(); const imageAnalysisMcpReady = ensureImageAnalysisMcpOrThrow(); if (resolvedTarget === 'claude') { if (imageAnalysisMcpReady) { removeImageAnalysisProfileHook(profileInfo.name); } else { const imageAnalysisFallbackHookReady = prepareImageAnalysisFallbackHook(); ensureImageAnalyzerHooks({ profileName: profileInfo.name, profileType: profileInfo.type, sharedHookInstalled: imageAnalysisFallbackHookReady, }); } } const { executeCopilotProfile } = await import('./copilot'); const copilotConfig = profileInfo.copilotConfig; if (!copilotConfig) { console.error(fail('Copilot configuration not found')); process.exit(1); } const continuityInheritance = await resolveProfileContinuityInheritance({ profileName: profileInfo.name, profileType: profileInfo.type, target: resolvedTarget, }); if (continuityInheritance.sourceAccount && process.env.CCS_DEBUG) { console.error( info( `Continuity inheritance active: profile "${profileInfo.name}" -> account "${continuityInheritance.sourceAccount}"` ) ); } const exitCode = await executeCopilotProfile( copilotConfig, remainingArgs, continuityInheritance.claudeConfigDir, claudeCli ); process.exit(exitCode); } else if (profileInfo.type === 'cursor') { // CURSOR FLOW: local Cursor daemon profile ensureWebSearchMcpOrThrow(); installImageAnalyzerHook(); ensureImageAnalyzerHooks({ profileName: profileInfo.name, profileType: profileInfo.type, }); const { executeCursorProfile } = await import('./cursor'); const cursorConfig = profileInfo.cursorConfig; if (!cursorConfig) { console.error(fail('Cursor configuration not found')); process.exit(1); } const continuityInheritance = await resolveProfileContinuityInheritance({ profileName: profileInfo.name, profileType: profileInfo.type, target: resolvedTarget, }); if (continuityInheritance.sourceAccount && process.env.CCS_DEBUG) { console.error( info( `Continuity inheritance active: profile "${profileInfo.name}" -> account "${continuityInheritance.sourceAccount}"` ) ); } const exitCode = await executeCursorProfile( cursorConfig, remainingArgs, continuityInheritance.claudeConfigDir, claudeCli ); process.exit(exitCode); } else if (profileInfo.type === 'settings') { // Settings-based profiles (glm, glmt) are third-party providers const imageAnalysisMcpReady = resolvedTarget === 'claude' ? ensureImageAnalysisMcpOrThrow() : true; const browserAttachRuntime = resolvedTarget === 'claude' && claudeBrowserExposure?.exposeForLaunch && claudeAttachConfig?.enabled ? await resolveOptionalBrowserAttachRuntime(claudeAttachConfig) : undefined; const browserRuntimeEnv = browserAttachRuntime?.runtimeEnv; if (browserAttachRuntime?.warning) { process.stderr.write(`${warn(browserAttachRuntime.warning)}\n`); } if (resolvedTarget === 'claude') { ensureWebSearchMcpOrThrow(); if (browserRuntimeEnv) { ensureBrowserMcpOrThrow(); } } // Display WebSearch status (single line, equilibrium UX) displayWebSearchStatus(); const continuityInheritance = resolvedTarget === 'claude' ? await resolveProfileContinuityInheritance({ profileName: profileInfo.name, profileType: profileInfo.type, target: resolvedTarget, }) : {}; if (continuityInheritance.sourceAccount && process.env.CCS_DEBUG) { console.error( info( `Continuity inheritance active: profile "${profileInfo.name}" -> account "${continuityInheritance.sourceAccount}"` ) ); } const inheritedClaudeConfigDir = continuityInheritance.claudeConfigDir; syncWebSearchMcpToConfigDir(inheritedClaudeConfigDir); syncImageAnalysisMcpToConfigDir(inheritedClaudeConfigDir); if ( browserRuntimeEnv && inheritedClaudeConfigDir && !syncBrowserMcpToConfigDir(inheritedClaudeConfigDir) ) { throw new Error( 'Browser MCP is enabled, but CCS could not sync the browser MCP config into the inherited Claude instance.' ); } const expandedSettingsPath = resolvedSettingsPath ?? (profileInfo.settingsPath ? expandPath(profileInfo.settingsPath) : getSettingsPath(profileInfo.name)); const settings = resolvedSettings ?? loadSettings(expandedSettingsPath); const cliproxyBridge = resolvedCliproxyBridge ?? resolveCliproxyBridgeMetadata(settings); let imageAnalysisFallbackHookReady: boolean | undefined; if (resolvedTarget === 'claude') { if (imageAnalysisMcpReady) { removeImageAnalysisProfileHook(profileInfo.name, expandedSettingsPath); } else { imageAnalysisFallbackHookReady = prepareImageAnalysisFallbackHook(); ensureImageAnalyzerHooks({ profileName: profileInfo.name, profileType: profileInfo.type, settingsPath: expandedSettingsPath, settings, cliproxyBridge, sharedHookInstalled: imageAnalysisFallbackHookReady, }); } } if (resolvedTarget !== 'claude') { const compatibility = evaluateTargetRuntimeCompatibility({ target: resolvedTarget, profileType: profileInfo.type, cliproxyBridgeProvider: cliproxyBridge?.provider ?? null, }); if (!compatibility.supported) { console.error( fail( compatibility.reason || `${targetAdapter?.displayName || resolvedTarget} does not support this profile.` ) ); if (compatibility.suggestion) { console.error(info(compatibility.suggestion)); } process.exit(1); } } const rawSettingsEnv = profileInfo.env ?? settings.env ?? {}; const isDeprecatedGlmtProfile = isDeprecatedGlmtProfileName(profileInfo.name); const glmtNormalization = isDeprecatedGlmtProfile ? normalizeDeprecatedGlmtEnv(rawSettingsEnv) : null; const settingsEnv = glmtNormalization?.env ?? rawSettingsEnv; if (glmtNormalization) { for (const message of glmtNormalization.warnings) { console.error(warn(message)); } } // Pre-flight validation for Z.AI-compatible profiles. if (profileInfo.name === 'glm' || isDeprecatedGlmtProfile) { const apiKey = settingsEnv['ANTHROPIC_AUTH_TOKEN']; if (apiKey) { const validation = await validateGlmKey(apiKey, settingsEnv['ANTHROPIC_BASE_URL']); if (!validation.valid) { console.error(''); console.error(fail(validation.error || 'API key validation failed')); if (validation.suggestion) { console.error(''); console.error(validation.suggestion); } console.error(''); console.error(info('To skip validation: CCS_SKIP_PREFLIGHT=1 ccs glm "prompt"')); process.exit(1); } } } if (profileInfo.name === 'mm') { const apiKey = settingsEnv['ANTHROPIC_AUTH_TOKEN']; if (apiKey) { const validation = await validateMiniMaxKey(apiKey, settingsEnv['ANTHROPIC_BASE_URL']); if (!validation.valid) { console.error(''); console.error(fail(validation.error || 'API key validation failed')); if (validation.suggestion) { console.error(''); console.error(validation.suggestion); } console.error(''); console.error(info('To skip validation: CCS_SKIP_PREFLIGHT=1 ccs mm "prompt"')); process.exit(1); } } } // Pre-flight validation for Anthropic direct profiles (ANTHROPIC_API_KEY + no BASE_URL) { const anthropicApiKey = settingsEnv['ANTHROPIC_API_KEY']; const hasBaseUrl = !!settingsEnv['ANTHROPIC_BASE_URL']; if (anthropicApiKey && !hasBaseUrl) { const validation = await validateAnthropicKey(anthropicApiKey); if (!validation.valid) { console.error(''); console.error(fail(validation.error || 'API key validation failed')); if (validation.suggestion) { console.error(''); console.error(validation.suggestion); } console.error(''); console.error( info(`To skip validation: CCS_SKIP_PREFLIGHT=1 ccs ${profileInfo.name} "prompt"`) ); process.exit(1); } } } const webSearchEnv = getWebSearchHookEnv(); const imageAnalysisStatus = await resolveImageAnalysisRuntimeStatus({ profileName: profileInfo.name, profileType: profileInfo.type, settings, cliproxyBridge, sharedHookInstalled: imageAnalysisFallbackHookReady, }); const runtimeConnection = resolveImageAnalysisRuntimeConnection(); let imageAnalysisEnv = getImageAnalysisHookEnv({ profileName: profileInfo.name, profileType: profileInfo.type, settings, cliproxyBridge, }); imageAnalysisEnv = applyImageAnalysisRuntimeOverrides(imageAnalysisEnv, { backendId: imageAnalysisStatus.backendId, model: imageAnalysisStatus.model, runtimePath: imageAnalysisStatus.runtimePath, baseUrl: runtimeConnection.baseUrl, apiKey: runtimeConnection.apiKey, allowSelfSigned: runtimeConnection.allowSelfSigned, }); imageAnalysisEnv = { ...imageAnalysisEnv, CCS_IMAGE_ANALYSIS_SKIP_HOOK: resolvedTarget === 'claude' && imageAnalysisMcpReady ? '1' : '0', }; const imageAnalysisProvider = imageAnalysisEnv['CCS_CURRENT_PROVIDER']; if ( resolvedTarget === 'claude' && imageAnalysisEnv['CCS_IMAGE_ANALYSIS_SKIP'] !== '1' && imageAnalysisProvider ) { const verboseProxyLaunch = remainingArgs.includes('--verbose') || remainingArgs.includes('-v') || targetRemainingArgs.includes('--verbose') || targetRemainingArgs.includes('-v'); if (imageAnalysisStatus.effectiveRuntimeMode === 'native-read') { console.error( info( `${imageAnalysisStatus.effectiveRuntimeReason || `Image analysis via ${imageAnalysisProvider} is unavailable.`} This session will use native Read.` ) ); imageAnalysisEnv = { ...imageAnalysisEnv, CCS_CURRENT_PROVIDER: '', CCS_IMAGE_ANALYSIS_SKIP: '1', CCS_IMAGE_ANALYSIS_RUNTIME_PATH: '', CCS_IMAGE_ANALYSIS_RUNTIME_BASE_URL: '', CCS_IMAGE_ANALYSIS_RUNTIME_API_KEY: '', CCS_IMAGE_ANALYSIS_RUNTIME_ALLOW_SELF_SIGNED: '0', }; } else if (imageAnalysisStatus.proxyReadiness === 'stopped') { const ensureServiceResult = await ensureCliproxyService( CLIPROXY_DEFAULT_PORT, verboseProxyLaunch ); if (!ensureServiceResult.started) { console.error( warn( `Image analysis via ${imageAnalysisProvider} is unavailable because CCS could not start the local CLIProxy service. This session will use native Read.` ) ); imageAnalysisEnv = { ...imageAnalysisEnv, CCS_CURRENT_PROVIDER: '', CCS_IMAGE_ANALYSIS_SKIP: '1', }; } } } // Get global env vars (DISABLE_TELEMETRY, etc.) for third-party profiles const globalEnvConfig = getGlobalEnvConfig(); const globalEnv = globalEnvConfig.enabled ? globalEnvConfig.env : {}; // Log global env injection for visibility (debug mode only) if (globalEnvConfig.enabled && Object.keys(globalEnv).length > 0 && process.env.CCS_DEBUG) { const envNames = Object.keys(globalEnv).join(', '); console.error(info(`Global env: ${envNames}`)); } // For Claude target launches that already pass `--settings`, keep runtime // env free of ANTHROPIC routing/auth while preserving non-routing profile // env so nested Team/subagent sessions can still inherit model intent and // other profile-scoped runtime flags. const settingsRuntimeEnv = stripBrowserEnv({ ...globalEnv, ...settingsEnv }); const claudeRuntimeEnvVars: NodeJS.ProcessEnv = { ...stripAnthropicRoutingEnv(settingsRuntimeEnv), ...(inheritedClaudeConfigDir ? { CLAUDE_CONFIG_DIR: inheritedClaudeConfigDir } : {}), ...webSearchEnv, ...imageAnalysisEnv, ...(browserRuntimeEnv || {}), CCS_PROFILE_TYPE: 'settings', CCS_STRIP_INHERITED_ANTHROPIC_ENV: '1', }; // Non-Claude targets still need effective credentials injected directly. const envVars: NodeJS.ProcessEnv = { ...settingsRuntimeEnv, ...(inheritedClaudeConfigDir ? { CLAUDE_CONFIG_DIR: inheritedClaudeConfigDir } : {}), ...webSearchEnv, ...imageAnalysisEnv, ...(browserRuntimeEnv || {}), CCS_PROFILE_TYPE: 'settings', }; // Dispatch through target adapter for non-claude targets if (resolvedTarget !== 'claude') { const adapter = targetAdapter; if (!adapter) { console.error(fail(`Target adapter not found for "${resolvedTarget}"`)); process.exit(1); } const directAnthropicBaseUrl = settingsEnv['ANTHROPIC_BASE_URL'] || (settingsEnv['ANTHROPIC_API_KEY'] ? 'https://api.anthropic.com' : ''); const creds: TargetCredentials = { profile: profileInfo.name, baseUrl: directAnthropicBaseUrl, apiKey: settingsEnv['ANTHROPIC_AUTH_TOKEN'] || settingsEnv['ANTHROPIC_API_KEY'] || '', model: settingsEnv['ANTHROPIC_MODEL'], provider: resolveDroidProvider({ provider: settingsEnv['CCS_DROID_PROVIDER'] || settingsEnv['DROID_PROVIDER'], baseUrl: directAnthropicBaseUrl, model: settingsEnv['ANTHROPIC_MODEL'], }), reasoningOverride: runtimeReasoningOverride, runtimeConfigOverrides: codexRuntimeConfigOverrides, envVars, }; await adapter.prepareCredentials(creds); const targetArgs = adapter.buildArgs(profileInfo.name, targetRemainingArgs, { creds, profileType: profileInfo.type, binaryInfo: targetBinaryInfo || undefined, }); const targetEnv = adapter.buildEnv(creds, profileInfo.type); adapter.exec(targetArgs, targetEnv, { binaryInfo: targetBinaryInfo || undefined }); return; } const imageAnalysisArgs = imageAnalysisMcpReady ? appendThirdPartyImageAnalysisToolArgs(remainingArgs) : remainingArgs; const browserArgs = browserRuntimeEnv ? appendBrowserToolArgs(imageAnalysisArgs) : imageAnalysisArgs; const openAICompatProfile = resolveOpenAICompatProfileConfig( profileInfo.name, expandedSettingsPath, settingsEnv ); if (openAICompatProfile) { const proxyStart = await startOpenAICompatProxy(openAICompatProfile, { insecure: openAICompatProfile.insecure, }); if (!proxyStart.success) { console.error(fail(proxyStart.error || 'Failed to start local OpenAI-compatible proxy')); process.exit(1); } console.error( info( `Using local OpenAI-compatible proxy for "${profileInfo.name}" on port ${proxyStart.port}` ) ); const proxyEnv = { ...envVars, ...buildOpenAICompatProxyEnv( openAICompatProfile, proxyStart.port, proxyStart.authToken || '', inheritedClaudeConfigDir ), }; delete proxyEnv.ANTHROPIC_API_KEY; const launchSettingsPath = createOpenAICompatLaunchSettingsPath( expandedSettingsPath, settings ); const launchArgs = [ '--settings', launchSettingsPath, ...appendThirdPartyWebSearchToolArgs(browserArgs), ]; const traceEnv = createWebSearchTraceContext({ launcher: 'ccs.settings-profile.proxy', args: launchArgs, profile: profileInfo.name, profileType: profileInfo.type, settingsPath: expandedSettingsPath, }); execClaude(claudeCli, launchArgs, { ...proxyEnv, ...traceEnv }); return; } const launchArgs = [ '--settings', expandedSettingsPath, ...appendThirdPartyWebSearchToolArgs(browserArgs), ]; const traceEnv = createWebSearchTraceContext({ launcher: 'ccs.settings-profile', args: launchArgs, profile: profileInfo.name, profileType: profileInfo.type, settingsPath: expandedSettingsPath, }); execClaude(claudeCli, launchArgs, { ...claudeRuntimeEnvVars, ...traceEnv }); } else if (profileInfo.type === 'account') { // NEW FLOW: Account-based profile (work, personal) // All platforms: Use instance isolation with CLAUDE_CONFIG_DIR const registry = new ProfileRegistry(); const instanceMgr = new InstanceManager(); const accountMetadata = isAccountContextMetadata(profileInfo.profile) ? profileInfo.profile : undefined; const isBareProfile = typeof profileInfo.profile === 'object' && profileInfo.profile !== null && (profileInfo.profile as { bare?: unknown }).bare === true; const contextPolicy = resolveAccountContextPolicy(accountMetadata); // Ensure instance exists (lazy init if needed) const instancePath = await instanceMgr.ensureInstance(profileInfo.name, contextPolicy, { bare: isBareProfile, }); // Update last_used timestamp (check unified config first, fallback to legacy) if (registry.hasAccountUnified(profileInfo.name)) { registry.touchAccountUnified(profileInfo.name); } else { registry.touchProfile(profileInfo.name); } // Execute Claude with instance isolation // Skip WebSearch hook - account profiles use native server-side WebSearch // Skip Image Analyzer hook - account profiles have native vision support const envVars: NodeJS.ProcessEnv = { CLAUDE_CONFIG_DIR: instancePath, CCS_PROFILE_TYPE: 'account', CCS_WEBSEARCH_SKIP: '1', CCS_IMAGE_ANALYSIS_SKIP: '1', }; await maybeWarnAboutResumeLaneMismatch(profileInfo.name, instancePath, remainingArgs); const launchArgs = resolveNativeClaudeLaunchArgs(remainingArgs, 'account', instancePath); execClaude(claudeCli, launchArgs, envVars); } else { // DEFAULT: No profile configured, use Claude's own defaults // Skip WebSearch hook - native Claude has server-side WebSearch // Skip Image Analyzer hook - native Claude has native vision support const envVars: NodeJS.ProcessEnv = { CCS_PROFILE_TYPE: 'default', CCS_WEBSEARCH_SKIP: '1', CCS_IMAGE_ANALYSIS_SKIP: '1', }; const browserAttachRuntime = resolvedTarget === 'claude' && claudeBrowserExposure?.exposeForLaunch && claudeAttachConfig?.enabled ? await resolveOptionalBrowserAttachRuntime(claudeAttachConfig) : undefined; const browserRuntimeEnv = browserAttachRuntime?.runtimeEnv; if (browserAttachRuntime?.warning) { process.stderr.write(`${warn(browserAttachRuntime.warning)}\n`); } if (resolvedTarget === 'claude') { if (browserRuntimeEnv) { ensureBrowserMcpOrThrow(); Object.assign(envVars, browserRuntimeEnv); } const defaultContinuityInheritance = await resolveProfileContinuityInheritance({ profileName: profileInfo.name, profileType: profileInfo.type, target: resolvedTarget, }); if (defaultContinuityInheritance.sourceAccount && process.env.CCS_DEBUG) { console.error( info( `Continuity inheritance active: profile "${profileInfo.name}" -> account "${defaultContinuityInheritance.sourceAccount}"` ) ); } if (defaultContinuityInheritance.claudeConfigDir) { envVars.CLAUDE_CONFIG_DIR = defaultContinuityInheritance.claudeConfigDir; if ( browserRuntimeEnv && !syncBrowserMcpToConfigDir(defaultContinuityInheritance.claudeConfigDir) ) { throw new Error( 'Browser MCP is enabled, but CCS could not sync the browser MCP config into the inherited Claude instance.' ); } } } // Dispatch through target adapter for non-claude targets if (resolvedTarget !== 'claude') { const adapter = targetAdapter; if (!adapter) { console.error(fail(`Target adapter not found for "${resolvedTarget}"`)); process.exit(1); } if (!adapter.supportsProfileType('default')) { console.error(fail(`${adapter.displayName} does not support default profile mode`)); process.exit(1); } const creds: TargetCredentials = { profile: 'default', baseUrl: process.env['ANTHROPIC_BASE_URL'] || '', apiKey: process.env['ANTHROPIC_AUTH_TOKEN'] || '', model: process.env['ANTHROPIC_MODEL'], provider: resolveDroidProvider({ provider: process.env['CCS_DROID_PROVIDER'] || process.env['DROID_PROVIDER'], baseUrl: process.env['ANTHROPIC_BASE_URL'], model: process.env['ANTHROPIC_MODEL'], }), reasoningOverride: runtimeReasoningOverride, runtimeConfigOverrides: codexRuntimeConfigOverrides, browserRuntimeEnv, }; if (resolvedTarget === 'droid' && (!creds.baseUrl || !creds.apiKey)) { console.error( fail( `${adapter.displayName} default mode requires ANTHROPIC_BASE_URL and ANTHROPIC_AUTH_TOKEN` ) ); console.error(info('Use a settings-based profile instead: ccs glm --target droid')); process.exit(1); } await adapter.prepareCredentials(creds); const targetArgs = adapter.buildArgs('default', targetRemainingArgs, { creds, profileType: 'default', binaryInfo: targetBinaryInfo || undefined, }); const targetEnv = adapter.buildEnv(creds, 'default'); adapter.exec(targetArgs, targetEnv, { binaryInfo: targetBinaryInfo || undefined }); return; } const launchArgs = resolveNativeClaudeLaunchArgs( browserRuntimeEnv ? appendBrowserToolArgs(remainingArgs) : remainingArgs, 'default', envVars.CLAUDE_CONFIG_DIR ); execClaude(claudeCli, launchArgs, envVars); } } catch (error) { const err = error as ProfileError; // Check if this is a profile not found error with suggestions if (err.profileName && err.availableProfiles !== undefined) { const allProfiles = err.availableProfiles.split('\n'); await ErrorManager.showProfileNotFound(err.profileName, allProfiles, err.suggestions); } else { console.error(fail(err.message)); } process.exit(1); } } // ========== Global Error Handlers ========== // Handle uncaught exceptions process.on('uncaughtException', (error: Error) => { handleError(error); }); // Handle unhandled promise rejections process.on('unhandledRejection', (reason: unknown) => { handleError(reason); }); // Handle process termination signals for cleanup process.on('SIGTERM', () => { try { runCleanup(); } catch { // Cleanup failure should not block termination. } // If a target exec path registered additional signal listeners, let those // listeners forward/coordinate child shutdown and final exit codes. if (process.listenerCount('SIGTERM') <= 1) { process.exit(143); // 128 + SIGTERM(15) } }); process.on('SIGINT', () => { try { runCleanup(); } catch { // Cleanup failure should not block termination. } // Same coordination rule as SIGTERM. if (process.listenerCount('SIGINT') <= 1) { process.exit(130); // 128 + SIGINT(2) } }); // Run main main().catch(handleError);