// These side-effects must run before all other imports:
// 1. profileCheckpoint marks entry before heavy module evaluation begins
// 2. startMdmRawRead fires MDM subprocesses (plutil/reg query) so they run in
//    parallel with the remaining ~135ms of imports below
// 3. startKeychainPrefetch fires both macOS keychain reads (OAuth + legacy API
//    key) in parallel — isRemoteManagedSettingsEligible() otherwise reads them
//    sequentially via sync spawn inside applySafeConfigEnvironmentVariables()
//    (~65ms on every macOS startup)
import { profileCheckpoint, profileReport } from './utils/startupProfiler.js';

// eslint-disable-next-line custom-rules/no-top-level-side-effects
profileCheckpoint('main_tsx_entry');

import { startMdmRawRead } from './utils/settings/mdm/rawRead.js';

// eslint-disable-next-line custom-rules/no-top-level-side-effects
startMdmRawRead();

import { ensureKeychainPrefetchCompleted, startKeychainPrefetch } from './utils/secureStorage/keychainPrefetch.js';

// eslint-disable-next-line custom-rules/no-top-level-side-effects
startKeychainPrefetch();

import { feature } from 'bun:bundle';
import { Command as CommanderCommand, InvalidArgumentError, Option } from '@commander-js/extra-typings';
import chalk from 'chalk';
import { readFileSync } from 'fs';
import mapValues from 'lodash-es/mapValues.js';
import pickBy from 'lodash-es/pickBy.js';
import uniqBy from 'lodash-es/uniqBy.js';
import { getOauthConfig } from './constants/oauth.js';
import { getRemoteSessionUrl } from './constants/product.js';
import { getSystemContext, getUserContext } from './context.js';
import { init, initializeTelemetryAfterTrust } from './entrypoints/init.js';
import { addToHistory } from './history.js';
import type { Root } from '@anthropic/ink';
import { launchRepl } from './replLauncher.js';
import {
  hasGrowthBookEnvOverride,
  initializeGrowthBook,
  refreshGrowthBookAfterAuthChange,
} from './services/analytics/growthbook.js';
import { fetchBootstrapData } from './services/api/bootstrap.js';
import {
  type DownloadResult,
  downloadSessionFiles,
  type FilesApiConfig,
  parseFileSpecs,
} from './services/api/filesApi.js';
import { prefetchPassesEligibility } from './services/api/referral.js';
import type { McpSdkServerConfig, McpServerConfig, ScopedMcpServerConfig } from './services/mcp/types.js';
import {
  isPolicyAllowed,
  loadPolicyLimits,
  refreshPolicyLimits,
  waitForPolicyLimitsToLoad,
} from './services/policyLimits/index.js';
import { loadRemoteManagedSettings, refreshRemoteManagedSettings } from './services/remoteManagedSettings/index.js';
import type { ToolInputJSONSchema } from './Tool.js';
import {
  createSyntheticOutputTool,
  isSyntheticOutputToolEnabled,
} from '@claude-code-best/builtin-tools/tools/SyntheticOutputTool/SyntheticOutputTool.js';
import { getTools } from './tools.js';
import {
  canUserConfigureAdvisor,
  getInitialAdvisorSetting,
  isAdvisorEnabled,
  isValidAdvisorModel,
  modelSupportsAdvisor,
} from './utils/advisor.js';
import { isAgentSwarmsEnabled } from './utils/agentSwarmsEnabled.js';
import { count, uniq } from './utils/array.js';
import { installAsciicastRecorder } from './utils/asciicast.js';
import {
  getSubscriptionType,
  isClaudeAISubscriber,
  prefetchAwsCredentialsAndBedRockInfoIfSafe,
  prefetchGcpCredentialsIfSafe,
  validateForceLoginOrg,
} from './utils/auth.js';
import {
  checkHasTrustDialogAccepted,
  getGlobalConfig,
  getRemoteControlAtStartup,
  isAutoUpdaterDisabled,
  saveGlobalConfig,
} from './utils/config.js';
import { seedEarlyInput, stopCapturingEarlyInput } from './utils/earlyInput.js';
import { getInitialEffortSetting, parseEffortValue } from './utils/effort.js';
import {
  getInitialFastModeSetting,
  isFastModeEnabled,
  prefetchFastModeStatus,
  resolveFastModeStatusFromCache,
} from './utils/fastMode.js';
import { applyConfigEnvironmentVariables } from './utils/managedEnv.js';
import { createSystemMessage, createUserMessage } from './utils/messages.js';
import { getPlatform } from './utils/platform.js';
import { getBaseRenderOptions } from './utils/renderOptions.js';
import { getSessionIngressAuthToken } from './utils/sessionIngressAuth.js';
import { settingsChangeDetector } from './utils/settings/changeDetector.js';
import { skillChangeDetector } from './utils/skills/skillChangeDetector.js';
import { jsonParse, writeFileSync_DEPRECATED } from './utils/slowOperations.js';
import { computeInitialTeamContext } from './utils/swarm/reconnection.js';
import { initializeWarningHandler } from './utils/warningHandler.js';
import { isWorktreeModeEnabled } from './utils/worktreeModeEnabled.js';

// Lazy require to avoid circular dependency: teammate.ts -> AppState.tsx -> ... -> main.tsx
/* eslint-disable @typescript-eslint/no-require-imports */
const getTeammateUtils = () => require('./utils/teammate.js') as typeof import('./utils/teammate.js');
const getTeammatePromptAddendum = () =>
  require('./utils/swarm/teammatePromptAddendum.js') as typeof import('./utils/swarm/teammatePromptAddendum.js');
const getTeammateModeSnapshot = () =>
  require('./utils/swarm/backends/teammateModeSnapshot.js') as typeof import('./utils/swarm/backends/teammateModeSnapshot.js');
/* eslint-enable @typescript-eslint/no-require-imports */
// Dead code elimination: conditional import for COORDINATOR_MODE
/* eslint-disable @typescript-eslint/no-require-imports */
const coordinatorModeModule = feature('COORDINATOR_MODE')
  ? (require('./coordinator/coordinatorMode.js') as typeof import('./coordinator/coordinatorMode.js'))
  : null;
/* eslint-enable @typescript-eslint/no-require-imports */
// Dead code elimination: conditional import for KAIROS (assistant mode)
/* eslint-disable @typescript-eslint/no-require-imports */
const assistantModule = feature('KAIROS')
  ? (require('./assistant/index.js') as typeof import('./assistant/index.js'))
  : null;
const kairosGate = feature('KAIROS') ? (require('./assistant/gate.js') as typeof import('./assistant/gate.js')) : null;

import { relative, resolve } from 'path';
import { isAnalyticsDisabled } from 'src/services/analytics/config.js';
import { getFeatureValue_CACHED_MAY_BE_STALE } from 'src/services/analytics/growthbook.js';
import {
  type AnalyticsMetadata_I_VERIFIED_THIS_IS_NOT_CODE_OR_FILEPATHS,
  logEvent,
} from 'src/services/analytics/index.js';
import { initializeAnalyticsGates } from 'src/services/analytics/sink.js';
import {
  getOriginalCwd,
  setAdditionalDirectoriesForClaudeMd,
  setIsRemoteMode,
  setMainLoopModelOverride,
  setMainThreadAgentType,
  setTeleportedSessionInfo,
} from './bootstrap/state.js';
import { filterCommandsForRemoteMode, getCommands } from './commands.js';
import type { StatsStore } from './context/stats.js';
import {
  launchAssistantInstallWizard,
  launchAssistantSessionChooser,
  launchInvalidSettingsDialog,
  launchResumeChooser,
  launchSnapshotUpdateDialog,
  launchTeleportRepoMismatchDialog,
  launchTeleportResumeWrapper,
} from './dialogLaunchers.js';
import { SHOW_CURSOR } from '@anthropic/ink';
import {
  exitWithError,
  exitWithMessage,
  getRenderContext,
  renderAndRun,
  showSetupScreens,
} from './interactiveHelpers.js';
import { initBuiltinPlugins } from './plugins/bundled/index.js';
/* eslint-enable @typescript-eslint/no-require-imports */
import { checkQuotaStatus } from './services/claudeAiLimits.js';
import { getMcpToolsCommandsAndResources, prefetchAllMcpResources } from './services/mcp/client.js';
import { VALID_INSTALLABLE_SCOPES, VALID_UPDATE_SCOPES } from './services/plugins/pluginCliCommands.js';
import { initBundledSkills } from './skills/bundled/index.js';
import type { AgentColorName } from '@claude-code-best/builtin-tools/tools/AgentTool/agentColorManager.js';
import {
  getActiveAgentsFromList,
  getAgentDefinitionsWithOverrides,
  isBuiltInAgent,
  isCustomAgent,
  parseAgentsFromJson,
} from '@claude-code-best/builtin-tools/tools/AgentTool/loadAgentsDir.js';
import type { LogOption } from './types/logs.js';
import type { Message as MessageType } from './types/message.js';
import {
  CLAUDE_IN_CHROME_SKILL_HINT,
  CLAUDE_IN_CHROME_SKILL_HINT_WITH_WEBBROWSER,
} from './utils/claudeInChrome/prompt.js';
import {
  setupClaudeInChrome,
  shouldAutoEnableClaudeInChrome,
  shouldEnableClaudeInChrome,
} from './utils/claudeInChrome/setup.js';
import { getContextWindowForModel } from './utils/context.js';
import { loadConversationForResume } from './utils/conversationRecovery.js';
import { buildDeepLinkBanner } from './utils/deepLink/banner.js';
import { hasNodeOption, isBareMode, isEnvTruthy, isInProtectedNamespace } from './utils/envUtils.js';
import { refreshExampleCommands } from './utils/exampleCommands.js';
import type { FpsMetrics } from './utils/fpsTracker.js';
import { getWorktreePaths } from './utils/getWorktreePaths.js';
import { findGitRoot, getBranch, getIsGit, getWorktreeCount } from './utils/git.js';
import { getGhAuthStatus } from './utils/github/ghAuthStatus.js';
import { safeParseJSON } from './utils/json.js';
import { logError } from './utils/log.js';
import { getModelDeprecationWarning } from './utils/model/deprecation.js';
import {
  getDefaultMainLoopModel,
  getUserSpecifiedModelSetting,
  normalizeModelStringForAPI,
  parseUserSpecifiedModel,
} from './utils/model/model.js';
import { ensureModelStringsInitialized } from './utils/model/modelStrings.js';
import { PERMISSION_MODES } from './utils/permissions/PermissionMode.js';
import {
  getAutoModeEnabledStateIfCached,
  initializeToolPermissionContext,
  initialPermissionModeFromCLI,
  isDefaultPermissionModeAuto,
  parseToolListFromCLI,
  removeDangerousPermissions,
  stripDangerousPermissionsForAutoMode,
  verifyAutoModeGateAccess,
} from './utils/permissions/permissionSetup.js';
import { cleanupOrphanedPluginVersionsInBackground } from './utils/plugins/cacheUtils.js';
import { initializeVersionedPlugins } from './utils/plugins/installedPluginsManager.js';
import { getManagedPluginNames } from './utils/plugins/managedPlugins.js';
import { getGlobExclusionsForPluginCache } from './utils/plugins/orphanedPluginFilter.js';
import { getPluginSeedDirs } from './utils/plugins/pluginDirectories.js';
import { countFilesRoundedRg } from './utils/ripgrep.js';
import { processSessionStartHooks, processSetupHooks } from './utils/sessionStart.js';
import {
  cacheSessionTitle,
  getSessionIdFromLog,
  loadTranscriptFromFile,
  saveAgentSetting,
  saveMode,
  searchSessionsByCustomTitle,
  sessionIdExists,
} from './utils/sessionStorage.js';
import { ensureMdmSettingsLoaded } from './utils/settings/mdm/settings.js';
import {
  getInitialSettings,
  getManagedSettingsKeysForLogging,
  getSettingsForSource,
  getSettingsWithErrors,
} from './utils/settings/settings.js';
import { resetSettingsCache } from './utils/settings/settingsCache.js';
import type { ValidationError } from './utils/settings/validation.js';
import { DEFAULT_TASKS_MODE_TASK_LIST_ID, TASK_STATUSES } from './utils/tasks.js';
import { logPluginLoadErrors, logPluginsEnabledForSession } from './utils/telemetry/pluginTelemetry.js';
import { logSkillsLoaded } from './utils/telemetry/skillLoadedEvent.js';
import { generateTempFilePath } from './utils/tempfile.js';
import { validateUuid } from './utils/uuid.js';
// Plugin startup checks are now handled non-blockingly in REPL.tsx

import { registerMcpAddCommand } from 'src/commands/mcp/addCommand.js';
import { registerMcpXaaIdpCommand } from 'src/commands/mcp/xaaIdpCommand.js';
import { logPermissionContextForAnts } from 'src/services/internalLogging.js';
import { fetchClaudeAIMcpConfigsIfEligible } from 'src/services/mcp/claudeai.js';
import { clearServerCache } from 'src/services/mcp/client.js';
import {
  areMcpConfigsAllowedWithEnterpriseMcpConfig,
  dedupClaudeAiMcpServers,
  doesEnterpriseMcpConfigExist,
  filterMcpServersByPolicy,
  getClaudeCodeMcpConfigs,
  getMcpServerSignature,
  parseMcpConfig,
  parseMcpConfigFromFilePath,
} from 'src/services/mcp/config.js';
import { excludeCommandsByServer, excludeResourcesByServer } from 'src/services/mcp/utils.js';
import { isXaaEnabled } from 'src/services/mcp/xaaIdpLogin.js';
import { getRelevantTips } from 'src/services/tips/tipRegistry.js';
import { logContextMetrics } from 'src/utils/api.js';
import { CLAUDE_IN_CHROME_MCP_SERVER_NAME, isClaudeInChromeMCPServer } from 'src/utils/claudeInChrome/common.js';
import { registerCleanup } from 'src/utils/cleanupRegistry.js';
import { eagerParseCliFlag } from 'src/utils/cliArgs.js';
import { createEmptyAttributionState } from 'src/utils/commitAttribution.js';
import { countConcurrentSessions, registerSession, updateSessionName } from 'src/utils/concurrentSessions.js';
import { getCwd } from 'src/utils/cwd.js';
import { logForDebugging, setHasFormattedOutput } from 'src/utils/debug.js';
import { errorMessage, getErrnoCode, isENOENT, TeleportOperationError, toError } from 'src/utils/errors.js';
import { getFsImplementation, safeResolvePath } from 'src/utils/fsOperations.js';
import { gracefulShutdown, gracefulShutdownSync } from 'src/utils/gracefulShutdown.js';
import { setAllHookEventsEnabled } from 'src/utils/hooks/hookEvents.js';
import { refreshModelCapabilities } from 'src/utils/model/modelCapabilities.js';
import { peekForStdinData, writeToStderr } from 'src/utils/process.js';
import { setCwd } from 'src/utils/Shell.js';
import { type ProcessedResume, processResumedConversation } from 'src/utils/sessionRestore.js';
import { parseSettingSourcesFlag } from 'src/utils/settings/constants.js';
import { plural } from 'src/utils/stringUtils.js';
import {
  type ChannelEntry,
  getInitialMainLoopModel,
  getIsNonInteractiveSession,
  getSdkBetas,
  getSessionId,
  getUserMsgOptIn,
  setAllowedChannels,
  setAllowedSettingSources,
  setChromeFlagOverride,
  setClientType,
  setCwdState,
  setDirectConnectServerUrl,
  setFlagSettingsPath,
  setInitialMainLoopModel,
  setInlinePlugins,
  setIsInteractive,
  setKairosActive,
  setOriginalCwd,
  setQuestionPreviewFormat,
  setSdkBetas,
  setSessionBypassPermissionsMode,
  setSessionPersistenceDisabled,
  setSessionSource,
  setUserMsgOptIn,
  switchSession,
} from './bootstrap/state.js';

/* eslint-disable @typescript-eslint/no-require-imports */
const autoModeStateModule = feature('TRANSCRIPT_CLASSIFIER')
  ? (require('./utils/permissions/autoModeState.js') as typeof import('./utils/permissions/autoModeState.js'))
  : null;

// TeleportRepoMismatchDialog, TeleportResumeWrapper dynamically imported at call sites
import { migrateBypassPermissionsAcceptedToSettings } from './migrations/migrateBypassPermissionsAcceptedToSettings.js';
import { migrateEnableAllProjectMcpServersToSettings } from './migrations/migrateEnableAllProjectMcpServersToSettings.js';
import { migrateFennecToOpus } from './migrations/migrateFennecToOpus.js';
import { migrateLegacyOpusToCurrent } from './migrations/migrateLegacyOpusToCurrent.js';
import { migrateOpusToOpus1m } from './migrations/migrateOpusToOpus1m.js';
import { migrateReplBridgeEnabledToRemoteControlAtStartup } from './migrations/migrateReplBridgeEnabledToRemoteControlAtStartup.js';
import { migrateSonnet1mToSonnet45 } from './migrations/migrateSonnet1mToSonnet45.js';
import { migrateSonnet45ToSonnet46 } from './migrations/migrateSonnet45ToSonnet46.js';
import { resetAutoModeOptInForDefaultOffer } from './migrations/resetAutoModeOptInForDefaultOffer.js';
import { resetProToOpusDefault } from './migrations/resetProToOpusDefault.js';
import { createRemoteSessionConfig } from './remote/RemoteSessionManager.js';
/* eslint-enable @typescript-eslint/no-require-imports */
// teleportWithProgress dynamically imported at call site
import { createDirectConnectSession, DirectConnectError } from './server/createDirectConnectSession.js';
import { initializeLspServerManager } from './services/lsp/manager.js';
import { shouldEnablePromptSuggestion } from './services/PromptSuggestion/promptSuggestion.js';
import { type AppState, getDefaultAppState, IDLE_SPECULATION_STATE } from './state/AppStateStore.js';
import { onChangeAppState } from './state/onChangeAppState.js';
import { createStore } from './state/store.js';
import { asSessionId } from './types/ids.js';
import { filterAllowedSdkBetas } from './utils/betas.js';
import { isInBundledMode, isRunningWithBun } from './utils/bundledMode.js';
import { logForDiagnosticsNoPII } from './utils/diagLogs.js';
import { filterExistingPaths, getKnownPathsForRepo } from './utils/githubRepoPathMapping.js';
import { clearPluginCache, loadAllPluginsCacheOnly } from './utils/plugins/pluginLoader.js';
import { migrateChangelogFromConfig } from './utils/releaseNotes.js';
import { SandboxManager } from './utils/sandbox/sandbox-adapter.js';
import { fetchSession, prepareApiRequest } from './utils/teleport/api.js';
import {
  checkOutTeleportedSessionBranch,
  processMessagesForTeleportResume,
  teleportToRemoteWithErrorHandling,
  validateGitState,
  validateSessionRepository,
} from './utils/teleport.js';
import { shouldEnableThinkingByDefault, type ThinkingConfig } from './utils/thinking.js';
import { initUser, resetUserCache } from './utils/user.js';
import { getTmuxInstallInstructions, isTmuxAvailable, parsePRReference } from './utils/worktree.js';

// eslint-disable-next-line custom-rules/no-top-level-side-effects
profileCheckpoint('main_tsx_imports_loaded');

/**
 * Log managed settings keys to Statsig for analytics.
 * This is called after init() completes to ensure settings are loaded
 * and environment variables are applied before model resolution.
 */
function logManagedSettings(): void {
  try {
    const policySettings = getSettingsForSource('policySettings');
    if (policySettings) {
      const allKeys = getManagedSettingsKeysForLogging(policySettings);
      logEvent('tengu_managed_settings_loaded', {
        keyCount: allKeys.length,
        keys: allKeys.join(',') as unknown as AnalyticsMetadata_I_VERIFIED_THIS_IS_NOT_CODE_OR_FILEPATHS,
      });
    }
  } catch {
    // Silently ignore errors - this is just for analytics
  }
}

// Check if running in debug/inspection mode
function _isBeingDebugged() {
  const isBun = isRunningWithBun();

  // Check for inspect flags in process arguments (including all variants)
  const hasInspectArg = process.execArgv.some(arg => {
    if (isBun) {
      // Note: Bun has an issue with single-file executables where application arguments
      // from process.argv leak into process.execArgv (similar to https://github.com/oven-sh/bun/issues/11673)
      // This breaks use of --debug mode if we omit this branch
      // We're fine to skip that check, because Bun doesn't support Node.js legacy --debug or --debug-brk flags
      return /--inspect(-brk)?/.test(arg);
    } else {
      // In Node.js, check for both --inspect and legacy --debug flags
      return /--inspect(-brk)?|--debug(-brk)?/.test(arg);
    }
  });

  // Check if NODE_OPTIONS contains inspect flags
  const hasInspectEnv = process.env.NODE_OPTIONS && /--inspect(-brk)?|--debug(-brk)?/.test(process.env.NODE_OPTIONS);

  // Check if inspector is available and active (indicates debugging)
  try {
    // Dynamic import would be better but is async - use global object instead
    // eslint-disable-next-line @typescript-eslint/no-explicit-any
    const inspector = (global as any).require('inspector');
    const hasInspectorUrl = !!inspector.url();
    return hasInspectorUrl || hasInspectArg || hasInspectEnv;
  } catch {
    // Ignore error and fall back to argument detection
    return hasInspectArg || hasInspectEnv;
  }
}

/**
 * Per-session skill/plugin telemetry. Called from both the interactive path
 * and the headless -p path (before runHeadless) — both go through
 * main.tsx but branch before the interactive startup path, so it needs two
 * call sites here rather than one here + one in QueryEngine.
 */
function logSessionTelemetry(): void {
  const model = parseUserSpecifiedModel(getInitialMainLoopModel() ?? getDefaultMainLoopModel());
  void logSkillsLoaded(getCwd(), getContextWindowForModel(model, getSdkBetas()));
  void loadAllPluginsCacheOnly()
    .then(({ enabled, errors }) => {
      const managedNames = getManagedPluginNames();
      logPluginsEnabledForSession(enabled, managedNames, getPluginSeedDirs());
      logPluginLoadErrors(errors, managedNames);
    })
    .catch(err => logError(err));
}

function getCertEnvVarTelemetry(): Record<string, boolean> {
  const result: Record<string, boolean> = {};
  if (process.env.NODE_EXTRA_CA_CERTS) {
    result.has_node_extra_ca_certs = true;
  }
  if (process.env.CLAUDE_CODE_CLIENT_CERT) {
    result.has_client_cert = true;
  }
  if (hasNodeOption('--use-system-ca')) {
    result.has_use_system_ca = true;
  }
  if (hasNodeOption('--use-openssl-ca')) {
    result.has_use_openssl_ca = true;
  }
  return result;
}

async function logStartupTelemetry(): Promise<void> {
  if (isAnalyticsDisabled()) return;
  const [isGit, worktreeCount, ghAuthStatus] = await Promise.all([getIsGit(), getWorktreeCount(), getGhAuthStatus()]);

  logEvent('tengu_startup_telemetry', {
    is_git: isGit,
    worktree_count: worktreeCount,
    gh_auth_status: ghAuthStatus as AnalyticsMetadata_I_VERIFIED_THIS_IS_NOT_CODE_OR_FILEPATHS,
    sandbox_enabled: SandboxManager.isSandboxingEnabled(),
    are_unsandboxed_commands_allowed: SandboxManager.areUnsandboxedCommandsAllowed(),
    is_auto_bash_allowed_if_sandbox_enabled: SandboxManager.isAutoAllowBashIfSandboxedEnabled(),
    auto_updater_disabled: isAutoUpdaterDisabled(),
    prefers_reduced_motion: getInitialSettings().prefersReducedMotion ?? false,
    ...getCertEnvVarTelemetry(),
  });
}

// @[MODEL LAUNCH]: Consider any migrations you may need for model strings. See migrateSonnet1mToSonnet45.ts for an example.
// Bump this when adding a new sync migration so existing users re-run the set.
const CURRENT_MIGRATION_VERSION = 11;
function runMigrations(): void {
  if (getGlobalConfig().migrationVersion !== CURRENT_MIGRATION_VERSION) {
    migrateBypassPermissionsAcceptedToSettings();
    migrateEnableAllProjectMcpServersToSettings();
    resetProToOpusDefault();
    migrateSonnet1mToSonnet45();
    migrateLegacyOpusToCurrent();
    migrateSonnet45ToSonnet46();
    migrateOpusToOpus1m();
    migrateReplBridgeEnabledToRemoteControlAtStartup();
    if (feature('TRANSCRIPT_CLASSIFIER')) {
      resetAutoModeOptInForDefaultOffer();
    }
    if (process.env.USER_TYPE === 'ant') {
      migrateFennecToOpus();
    }
    saveGlobalConfig(prev =>
      prev.migrationVersion === CURRENT_MIGRATION_VERSION
        ? prev
        : { ...prev, migrationVersion: CURRENT_MIGRATION_VERSION },
    );
  }
  // Async migration - fire and forget since it's non-blocking
  migrateChangelogFromConfig().catch(() => {
    // Silently ignore migration errors - will retry on next startup
  });
}

/**
 * Prefetch system context (including git status) only when it's safe to do so.
 * Git commands can execute arbitrary code via hooks and config (e.g., core.fsmonitor,
 * diff.external), so we must only run them after trust is established or in
 * non-interactive mode where trust is implicit.
 */
function prefetchSystemContextIfSafe(): void {
  const isNonInteractiveSession = getIsNonInteractiveSession();

  // In non-interactive mode (--print), trust dialog is skipped and
  // execution is considered trusted (as documented in help text)
  if (isNonInteractiveSession) {
    logForDiagnosticsNoPII('info', 'prefetch_system_context_non_interactive');
    void getSystemContext();
    return;
  }

  // In interactive mode, only prefetch if trust has already been established
  const hasTrust = checkHasTrustDialogAccepted();
  if (hasTrust) {
    logForDiagnosticsNoPII('info', 'prefetch_system_context_has_trust');
    void getSystemContext();
  } else {
    logForDiagnosticsNoPII('info', 'prefetch_system_context_skipped_no_trust');
  }
  // Otherwise, don't prefetch - wait for trust to be established first
}

/**
 * Start background prefetches and housekeeping that are NOT needed before first render.
 * These are deferred from setup() to reduce event loop contention and child process
 * spawning during the critical startup path.
 * Call this after the REPL has been rendered.
 */
export function startDeferredPrefetches(): void {
  // This function runs after first render, so it doesn't block the initial paint.
  // However, the spawned processes and async work still contend for CPU and event
  // loop time, which skews startup benchmarks (CPU profiles, time-to-first-render
  // measurements). Skip all of it when we're only measuring startup performance.
  if (
    isEnvTruthy(process.env.CLAUDE_CODE_EXIT_AFTER_FIRST_RENDER) ||
    // --bare: skip ALL prefetches. These are cache-warms for the REPL's
    // first-turn responsiveness (initUser, getUserContext, tips, countFiles,
    // modelCapabilities, change detectors). Scripted -p calls don't have a
    // "user is typing" window to hide this work in — it's pure overhead on
    // the critical path.
    isBareMode()
  ) {
    return;
  }

  // Process-spawning prefetches (consumed at first API call, user is still typing)
  void initUser();
  void getUserContext();
  prefetchSystemContextIfSafe();
  void getRelevantTips();
  if (isEnvTruthy(process.env.CLAUDE_CODE_USE_BEDROCK) && !isEnvTruthy(process.env.CLAUDE_CODE_SKIP_BEDROCK_AUTH)) {
    void prefetchAwsCredentialsAndBedRockInfoIfSafe();
  }
  if (isEnvTruthy(process.env.CLAUDE_CODE_USE_VERTEX) && !isEnvTruthy(process.env.CLAUDE_CODE_SKIP_VERTEX_AUTH)) {
    void prefetchGcpCredentialsIfSafe();
  }
  void countFilesRoundedRg(getCwd(), AbortSignal.timeout(3000), []);

  // Analytics and feature flag initialization
  void initializeAnalyticsGates();

  void refreshModelCapabilities();

  // File change detectors deferred from init() to unblock first render
  void settingsChangeDetector.initialize();
  if (!isBareMode()) {
    void skillChangeDetector.initialize();
  }

  // Event loop stall detector — logs when the main thread is blocked >500ms
  if (process.env.USER_TYPE === 'ant') {
    void import('./utils/eventLoopStallDetector.js').then(m => m.startEventLoopStallDetector());
  }
}

function loadSettingsFromFlag(settingsFile: string): void {
  try {
    const trimmedSettings = settingsFile.trim();
    const looksLikeJson = trimmedSettings.startsWith('{') && trimmedSettings.endsWith('}');

    let settingsPath: string;

    if (looksLikeJson) {
      // It's a JSON string - validate and create temp file
      const parsedJson = safeParseJSON(trimmedSettings);
      if (!parsedJson) {
        process.stderr.write(chalk.red('Error: Invalid JSON provided to --settings\n'));
        process.exit(1);
      }

      // Create a temporary file and write the JSON to it.
      // Use a content-hash-based path instead of random UUID to avoid
      // busting the Anthropic API prompt cache. The settings path ends up
      // in the Bash tool's sandbox denyWithinAllow list, which is part of
      // the tool description sent to the API. A random UUID per subprocess
      // changes the tool description on every query() call, invalidating
      // the cache prefix and causing a 12x input token cost penalty.
      // The content hash ensures identical settings produce the same path
      // across process boundaries (each SDK query() spawns a new process).
      settingsPath = generateTempFilePath('claude-settings', '.json', {
        contentHash: trimmedSettings,
      });
      writeFileSync_DEPRECATED(settingsPath, trimmedSettings, 'utf8');
    } else {
      // It's a file path - resolve and validate by attempting to read
      const { resolvedPath: resolvedSettingsPath } = safeResolvePath(getFsImplementation(), settingsFile);
      try {
        readFileSync(resolvedSettingsPath, 'utf8');
      } catch (e) {
        if (isENOENT(e)) {
          process.stderr.write(chalk.red(`Error: Settings file not found: ${resolvedSettingsPath}\n`));
          process.exit(1);
        }
        throw e;
      }
      settingsPath = resolvedSettingsPath;
    }

    setFlagSettingsPath(settingsPath);
    resetSettingsCache();
  } catch (error) {
    if (error instanceof Error) {
      logError(error);
    }
    process.stderr.write(chalk.red(`Error processing settings: ${errorMessage(error)}\n`));
    process.exit(1);
  }
}

function loadSettingSourcesFromFlag(settingSourcesArg: string): void {
  try {
    const sources = parseSettingSourcesFlag(settingSourcesArg);
    setAllowedSettingSources(sources);
    resetSettingsCache();
  } catch (error) {
    if (error instanceof Error) {
      logError(error);
    }
    process.stderr.write(chalk.red(`Error processing --setting-sources: ${errorMessage(error)}\n`));
    process.exit(1);
  }
}

/**
 * Parse and load settings flags early, before init()
 * This ensures settings are filtered from the start of initialization
 */
function eagerLoadSettings(): void {
  profileCheckpoint('eagerLoadSettings_start');
  // Parse --settings flag early to ensure settings are loaded before init()
  const settingsFile = eagerParseCliFlag('--settings');
  if (settingsFile) {
    loadSettingsFromFlag(settingsFile);
  }

  // Parse --setting-sources flag early to control which sources are loaded
  const settingSourcesArg = eagerParseCliFlag('--setting-sources');
  if (settingSourcesArg !== undefined) {
    loadSettingSourcesFromFlag(settingSourcesArg);
  }
  profileCheckpoint('eagerLoadSettings_end');
}

function initializeEntrypoint(isNonInteractive: boolean): void {
  // Skip if already set (e.g., by SDK or other entrypoints)
  if (process.env.CLAUDE_CODE_ENTRYPOINT) {
    return;
  }

  const cliArgs = process.argv.slice(2);

  // Check for MCP serve command (handle flags before mcp serve, e.g., --debug mcp serve)
  const mcpIndex = cliArgs.indexOf('mcp');
  if (mcpIndex !== -1 && cliArgs[mcpIndex + 1] === 'serve') {
    process.env.CLAUDE_CODE_ENTRYPOINT = 'mcp';
    return;
  }

  if (isEnvTruthy(process.env.CLAUDE_CODE_ACTION)) {
    process.env.CLAUDE_CODE_ENTRYPOINT = 'claude-code-github-action';
    return;
  }

  // Note: 'local-agent' entrypoint is set by the local agent mode launcher
  // via CLAUDE_CODE_ENTRYPOINT env var (handled by early return above)

  // Set based on interactive status
  process.env.CLAUDE_CODE_ENTRYPOINT = isNonInteractive ? 'sdk-cli' : 'cli';
}

// Set by early argv processing when `claude open <url>` is detected (interactive mode only)
type PendingConnect = {
  url: string | undefined;
  authToken: string | undefined;
  dangerouslySkipPermissions: boolean;
};
const _pendingConnect: PendingConnect | undefined = feature('DIRECT_CONNECT')
  ? {
      url: undefined,
      authToken: undefined,
      dangerouslySkipPermissions: false,
    }
  : undefined;

// Set by early argv processing when `claude assistant [sessionId]` is detected
type PendingAssistantChat = { sessionId?: string; discover: boolean };
const _pendingAssistantChat: PendingAssistantChat | undefined = feature('KAIROS')
  ? { sessionId: undefined, discover: false }
  : undefined;

// `claude ssh <host> [dir]` — parsed from argv early (same pattern as
// DIRECT_CONNECT above) so the main command path can pick it up and hand
// the REPL an SSH-backed session instead of a local one.
type PendingSSH = {
  host: string | undefined;
  cwd: string | undefined;
  permissionMode: string | undefined;
  dangerouslySkipPermissions: boolean;
  /** --local: spawn the child CLI directly, skip ssh/probe/deploy. e2e test mode. */
  local: boolean;
  /** Extra CLI args to forward to the remote CLI on initial spawn (--resume, -c). */
  extraCliArgs: string[];
  remoteBin: string | undefined;
};
const _pendingSSH: PendingSSH | undefined = feature('SSH_REMOTE')
  ? {
      host: undefined,
      cwd: undefined,
      permissionMode: undefined,
      dangerouslySkipPermissions: false,
      local: false,
      extraCliArgs: [],
      remoteBin: undefined,
    }
  : undefined;

export async function main() {
  profileCheckpoint('main_function_start');

  // SECURITY: Prevent Windows from executing commands from current directory
  // This must be set before ANY command execution to prevent PATH hijacking attacks
  // See: https://docs.microsoft.com/en-us/windows/win32/api/processenv/nf-processenv-searchpathw
  process.env.NoDefaultCurrentDirectoryInExePath = '1';

  // Initialize warning handler early to catch warnings
  initializeWarningHandler();

  process.on('exit', () => {
    resetCursor();
    // 杀掉所有 running workflow，避免孤儿 task 留在 AppState 里
    try {
      const { peekWorkflowService } = require('./workflow/service.js') as {
        peekWorkflowService: () => { shutdown: () => void } | null;
      };
      peekWorkflowService()?.shutdown();
    } catch {
      // workflow 未启用或已卸载——忽略
    }
  });
  process.on('SIGINT', () => {
    // In print mode, print.ts registers its own SIGINT handler that aborts
    // the in-flight query and calls gracefulShutdown; skip here to avoid
    // preempting it with a synchronous process.exit().
    if (process.argv.includes('-p') || process.argv.includes('--print')) {
      return;
    }
    process.exit(0);
  });
  profileCheckpoint('main_warning_handler_initialized');

  // Check for cc:// or cc+unix:// URL in argv — rewrite so the main command
  // handles it, giving the full interactive TUI instead of a stripped-down subcommand.
  // For headless (-p), we rewrite to the internal `open` subcommand.
  if (feature('DIRECT_CONNECT')) {
    const rawCliArgs = process.argv.slice(2);
    const ccIdx = rawCliArgs.findIndex(a => a.startsWith('cc://') || a.startsWith('cc+unix://'));
    if (ccIdx !== -1 && _pendingConnect) {
      const ccUrl = rawCliArgs[ccIdx]!;
      const { parseConnectUrl } = await import('./server/parseConnectUrl.js');
      const parsed = parseConnectUrl(ccUrl);
      _pendingConnect.dangerouslySkipPermissions = rawCliArgs.includes('--dangerously-skip-permissions');

      if (rawCliArgs.includes('-p') || rawCliArgs.includes('--print')) {
        // Headless: rewrite to internal `open` subcommand
        const stripped = rawCliArgs.filter((_, i) => i !== ccIdx);
        const dspIdx = stripped.indexOf('--dangerously-skip-permissions');
        if (dspIdx !== -1) {
          stripped.splice(dspIdx, 1);
        }
        process.argv = [process.argv[0]!, process.argv[1]!, 'open', ccUrl, ...stripped];
      } else {
        // Interactive: strip cc:// URL and flags, run main command
        _pendingConnect.url = parsed.serverUrl;
        _pendingConnect.authToken = parsed.authToken;
        const stripped = rawCliArgs.filter((_, i) => i !== ccIdx);
        const dspIdx = stripped.indexOf('--dangerously-skip-permissions');
        if (dspIdx !== -1) {
          stripped.splice(dspIdx, 1);
        }
        process.argv = [process.argv[0]!, process.argv[1]!, ...stripped];
      }
    }
  }

  // Handle deep link URIs early — this is invoked by the OS protocol handler
  // and should bail out before full init since it only needs to parse the URI
  // and open a terminal.
  if (feature('LODESTONE')) {
    const handleUriIdx = process.argv.indexOf('--handle-uri');
    if (handleUriIdx !== -1 && process.argv[handleUriIdx + 1]) {
      const { enableConfigs } = await import('./utils/config.js');
      enableConfigs();
      const uri = process.argv[handleUriIdx + 1]!;
      const { handleDeepLinkUri } = await import('./utils/deepLink/protocolHandler.js');
      const exitCode = await handleDeepLinkUri(uri);
      process.exit(exitCode);
    }

    // macOS URL handler: when LaunchServices launches our .app bundle, the
    // URL arrives via Apple Event (not argv). LaunchServices overwrites
    // __CFBundleIdentifier to the launching bundle's ID, which is a precise
    // positive signal — cheaper than importing and guessing with heuristics.
    if (process.platform === 'darwin' && process.env.__CFBundleIdentifier === 'com.anthropic.claude-code-url-handler') {
      const { enableConfigs } = await import('./utils/config.js');
      enableConfigs();
      const { handleUrlSchemeLaunch } = await import('./utils/deepLink/protocolHandler.js');
      const urlSchemeResult = await handleUrlSchemeLaunch();
      process.exit(urlSchemeResult ?? 1);
    }
  }

  // `claude assistant [sessionId]` — stash and strip so the main
  // command handles it, giving the full interactive TUI. Position-0 only
  // (matching the ssh pattern below) — indexOf would false-positive on
  // `claude -p "explain assistant"`. Root-flag-before-subcommand
  // (e.g. `--debug assistant`) falls through to the stub, which
  // prints usage.
  if (feature('KAIROS') && _pendingAssistantChat) {
    const rawArgs = process.argv.slice(2);
    if (rawArgs[0] === 'assistant') {
      const nextArg = rawArgs[1];
      if (nextArg && !nextArg.startsWith('-')) {
        _pendingAssistantChat.sessionId = nextArg;
        rawArgs.splice(0, 2); // drop 'assistant' and sessionId
        process.argv = [process.argv[0]!, process.argv[1]!, ...rawArgs];
      } else if (!nextArg) {
        _pendingAssistantChat.discover = true;
        rawArgs.splice(0, 1); // drop 'assistant'
        process.argv = [process.argv[0]!, process.argv[1]!, ...rawArgs];
      }
      // else: `claude assistant --help` → fall through to stub
    }
  }

  // `claude ssh <host> [dir]` — strip from argv so the main command handler
  // runs (full interactive TUI), stash the host/dir for the REPL branch at
  // ~line 3720 to pick up. Headless (-p) mode not supported in v1: SSH
  // sessions need the local REPL to drive them (interrupt, permissions).
  if (feature('SSH_REMOTE') && _pendingSSH) {
    const rawCliArgs = process.argv.slice(2);
    // SSH-specific flags can appear before the host positional (e.g.
    // `ssh --permission-mode auto host /tmp` — standard POSIX flags-before-
    // positionals). Pull them all out BEFORE checking whether a host was
    // given, so `claude ssh --permission-mode auto host` and `claude ssh host
    // --permission-mode auto` are equivalent. The host check below only needs
    // to guard against `-h`/`--help` (which commander should handle).
    if (rawCliArgs[0] === 'ssh') {
      const localIdx = rawCliArgs.indexOf('--local');
      if (localIdx !== -1) {
        _pendingSSH.local = true;
        rawCliArgs.splice(localIdx, 1);
      }
      const dspIdx = rawCliArgs.indexOf('--dangerously-skip-permissions');
      if (dspIdx !== -1) {
        _pendingSSH.dangerouslySkipPermissions = true;
        rawCliArgs.splice(dspIdx, 1);
      }
      const pmIdx = rawCliArgs.indexOf('--permission-mode');
      if (pmIdx !== -1 && rawCliArgs[pmIdx + 1] && !rawCliArgs[pmIdx + 1]!.startsWith('-')) {
        _pendingSSH.permissionMode = rawCliArgs[pmIdx + 1];
        rawCliArgs.splice(pmIdx, 2);
      }
      const pmEqIdx = rawCliArgs.findIndex(a => a.startsWith('--permission-mode='));
      if (pmEqIdx !== -1) {
        _pendingSSH.permissionMode = rawCliArgs[pmEqIdx]!.split('=')[1];
        rawCliArgs.splice(pmEqIdx, 1);
      }
      // Forward session-resume + model flags to the remote CLI's initial spawn.
      // --continue/-c and --resume <uuid> operate on the REMOTE session history
      // (which persists under the remote's ~/.claude/projects/<cwd>/).
      // --model controls which model the remote uses.
      const extractFlag = (flag: string, opts: { hasValue?: boolean; as?: string } = {}) => {
        const i = rawCliArgs.indexOf(flag);
        if (i !== -1) {
          _pendingSSH.extraCliArgs.push(opts.as ?? flag);
          const val = rawCliArgs[i + 1];
          if (opts.hasValue && val && !val.startsWith('-')) {
            _pendingSSH.extraCliArgs.push(val);
            rawCliArgs.splice(i, 2);
          } else {
            rawCliArgs.splice(i, 1);
          }
        }
        const eqI = rawCliArgs.findIndex(a => a.startsWith(`${flag}=`));
        if (eqI !== -1) {
          _pendingSSH.extraCliArgs.push(opts.as ?? flag, rawCliArgs[eqI]!.slice(flag.length + 1));
          rawCliArgs.splice(eqI, 1);
        }
      };
      const rbIdx = rawCliArgs.indexOf('--remote-bin');
      if (rbIdx !== -1 && rawCliArgs[rbIdx + 1] && !rawCliArgs[rbIdx + 1]!.startsWith('-')) {
        _pendingSSH.remoteBin = rawCliArgs[rbIdx + 1];
        rawCliArgs.splice(rbIdx, 2);
      }
      const rbEqIdx = rawCliArgs.findIndex(a => a.startsWith('--remote-bin='));
      if (rbEqIdx !== -1) {
        _pendingSSH.remoteBin = rawCliArgs[rbEqIdx]!.split('=').slice(1).join('=');
        rawCliArgs.splice(rbEqIdx, 1);
      }

      extractFlag('-c', { as: '--continue' });
      extractFlag('--continue');
      extractFlag('--resume', { hasValue: true });
      extractFlag('--model', { hasValue: true });
    }
    // After pre-extraction, any remaining dash-arg at [1] is either -h/--help
    // (commander handles) or an unknown-to-ssh flag (fall through to commander
    // so it surfaces a proper error). Only a non-dash arg is the host.
    if (rawCliArgs[0] === 'ssh' && rawCliArgs[1] && !rawCliArgs[1].startsWith('-')) {
      _pendingSSH.host = rawCliArgs[1];
      // Optional positional cwd.
      let consumed = 2;
      if (rawCliArgs[2] && !rawCliArgs[2].startsWith('-')) {
        _pendingSSH.cwd = rawCliArgs[2];
        consumed = 3;
      }
      const rest = rawCliArgs.slice(consumed);

      // Headless (-p) mode is not supported with SSH in v1 — reject early
      // so the flag doesn't silently cause local execution.
      if (rest.includes('-p') || rest.includes('--print')) {
        process.stderr.write('Error: headless (-p/--print) mode is not supported with claude ssh\n');
        gracefulShutdownSync(1);
        return;
      }

      // Rewrite argv so the main command sees remaining flags but not `ssh`.
      process.argv = [process.argv[0]!, process.argv[1]!, ...rest];
    }
  }

  // Check for -p/--print and --init-only flags early to set isInteractiveSession before init()
  // This is needed because telemetry initialization calls auth functions that need this flag
  const cliArgs = process.argv.slice(2);
  const hasPrintFlag = cliArgs.includes('-p') || cliArgs.includes('--print');
  const hasInitOnlyFlag = cliArgs.includes('--init-only');
  const hasSdkUrl = cliArgs.some(arg => arg.startsWith('--sdk-url'));
  const forceInteractive = isEnvTruthy(process.env.CLAUDE_CODE_FORCE_INTERACTIVE);
  const isNonInteractive = hasPrintFlag || hasInitOnlyFlag || hasSdkUrl || (!forceInteractive && !process.stdout.isTTY);

  // Stop capturing early input for non-interactive modes
  if (isNonInteractive) {
    stopCapturingEarlyInput();
  }

  // Set simplified tracking fields
  const isInteractive = !isNonInteractive;
  setIsInteractive(isInteractive);

  // Initialize entrypoint based on mode - needs to be set before any event is logged
  initializeEntrypoint(isNonInteractive);

  // Determine client type
  const clientType = (() => {
    if (isEnvTruthy(process.env.GITHUB_ACTIONS)) return 'github-action';
    if (process.env.CLAUDE_CODE_ENTRYPOINT === 'sdk-ts') return 'sdk-typescript';
    if (process.env.CLAUDE_CODE_ENTRYPOINT === 'sdk-py') return 'sdk-python';
    if (process.env.CLAUDE_CODE_ENTRYPOINT === 'sdk-cli') return 'sdk-cli';
    if (process.env.CLAUDE_CODE_ENTRYPOINT === 'claude-vscode') return 'claude-vscode';
    if (process.env.CLAUDE_CODE_ENTRYPOINT === 'local-agent') return 'local-agent';
    if (process.env.CLAUDE_CODE_ENTRYPOINT === 'claude-desktop') return 'claude-desktop';

    // Check if session-ingress token is provided (indicates remote session)
    const hasSessionIngressToken =
      process.env.CLAUDE_CODE_SESSION_ACCESS_TOKEN || process.env.CLAUDE_CODE_WEBSOCKET_AUTH_FILE_DESCRIPTOR;
    if (process.env.CLAUDE_CODE_ENTRYPOINT === 'remote' || hasSessionIngressToken) {
      return 'remote';
    }

    return 'cli';
  })();
  setClientType(clientType);

  const previewFormat = process.env.CLAUDE_CODE_QUESTION_PREVIEW_FORMAT;
  if (previewFormat === 'markdown' || previewFormat === 'html') {
    setQuestionPreviewFormat(previewFormat);
  } else if (
    !clientType.startsWith('sdk-') &&
    // Desktop and CCR pass previewFormat via toolConfig; when the feature is
    // gated off they pass undefined — don't override that with markdown.
    clientType !== 'claude-desktop' &&
    clientType !== 'local-agent' &&
    clientType !== 'remote'
  ) {
    setQuestionPreviewFormat('markdown');
  }

  // Tag sessions created via `claude remote-control` so the backend can identify them
  if (process.env.CLAUDE_CODE_ENVIRONMENT_KIND === 'bridge') {
    setSessionSource('remote-control');
  }

  profileCheckpoint('main_client_type_determined');

  // Parse and load settings flags early, before init()
  eagerLoadSettings();

  profileCheckpoint('main_before_run');

  await run();
  profileCheckpoint('main_after_run');
}

async function getInputPrompt(
  prompt: string,
  inputFormat: 'text' | 'stream-json',
): Promise<string | AsyncIterable<string>> {
  if (
    !process.stdin.isTTY &&
    // Input hijacking breaks MCP.
    !process.argv.includes('mcp')
  ) {
    if (inputFormat === 'stream-json') {
      return process.stdin;
    }
    process.stdin.setEncoding('utf8');
    let data = '';
    const onData = (chunk: string) => {
      data += chunk;
    };
    process.stdin.on('data', onData);
    // If no data arrives in 3s, stop waiting and warn. Stdin is likely an
    // inherited pipe from a parent that isn't writing (subprocess spawned
    // without explicit stdin handling). 3s covers slow producers like curl,
    // jq on large files, python with import overhead. The warning makes
    // silent data loss visible for the rare producer that's slower still.
    const timedOut = await peekForStdinData(process.stdin, 3000);
    process.stdin.off('data', onData);
    if (timedOut) {
      process.stderr.write(
        'Warning: no stdin data received in 3s, proceeding without it. ' +
          'If piping from a slow command, redirect stdin explicitly: < /dev/null to skip, or wait longer.\n',
      );
    }
    return [prompt, data].filter(Boolean).join('\n');
  }
  return prompt;
}

async function run(): Promise<CommanderCommand> {
  profileCheckpoint('run_function_start');

  // Create help config that sorts options by long option name.
  // Commander supports compareOptions at runtime but @commander-js/extra-typings
  // doesn't include it in the type definitions, so we use Object.assign to add it.
  function createSortedHelpConfig(): {
    sortSubcommands: true;
    sortOptions: true;
  } {
    const getOptionSortKey = (opt: Option): string =>
      opt.long?.replace(/^--/, '') ?? opt.short?.replace(/^-/, '') ?? '';
    return Object.assign({ sortSubcommands: true, sortOptions: true } as const, {
      compareOptions: (a: Option, b: Option) => getOptionSortKey(a).localeCompare(getOptionSortKey(b)),
    });
  }
  const program = new CommanderCommand().configureHelp(createSortedHelpConfig()).enablePositionalOptions();
  profileCheckpoint('run_commander_initialized');

  // Use preAction hook to run initialization only when executing a command,
  // not when displaying help. This avoids the need for env variable signaling.
  program.hook('preAction', async thisCommand => {
    profileCheckpoint('preAction_start');
    // Await async subprocess loads started at module evaluation (lines 12-20).
    // Nearly free — subprocesses complete during the ~135ms of imports above.
    // Must resolve before init() which triggers the first settings read
    // (applySafeConfigEnvironmentVariables → getSettingsForSource('policySettings')
    // → isRemoteManagedSettingsEligible → sync keychain reads otherwise ~65ms).
    await Promise.all([ensureMdmSettingsLoaded(), ensureKeychainPrefetchCompleted()]);
    profileCheckpoint('preAction_after_mdm');
    await init();
    profileCheckpoint('preAction_after_init');

    // process.title on Windows sets the console title directly; on POSIX,
    // terminal shell integration may mirror the process name to the tab.
    // After init() so settings.json env can also gate this (gh-4765).
    if (!isEnvTruthy(process.env.CLAUDE_CODE_DISABLE_TERMINAL_TITLE)) {
      process.title = 'claude';
    }

    // Attach logging sinks so subcommand handlers can use logEvent/logError.
    // Before PR #11106 logEvent dispatched directly; after, events queue until
    // a sink attaches. setup() attaches sinks for the default command, but
    // subcommands (doctor, mcp, plugin, auth) never call setup() and would
    // silently drop events on process.exit(). Both inits are idempotent.
    const { initSinks } = await import('./utils/sinks.js');
    initSinks();
    profileCheckpoint('preAction_after_sinks');

    // gh-33508: --plugin-dir is a top-level program option. The default
    // action reads it from its own options destructure, but subcommands
    // (plugin list, plugin install, mcp *) have their own actions and
    // never see it. Wire it up here so getInlinePlugins() works everywhere.
    // thisCommand.opts() is typed {} here because this hook is attached
    // before .option('--plugin-dir', ...) in the chain — extra-typings
    // builds the type as options are added. Narrow with a runtime guard;
    // the collect accumulator + [] default guarantee string[] in practice.
    const pluginDir = thisCommand.getOptionValue('pluginDir');
    if (Array.isArray(pluginDir) && pluginDir.length > 0 && pluginDir.every(p => typeof p === 'string')) {
      setInlinePlugins(pluginDir);
      clearPluginCache('preAction: --plugin-dir inline plugins');
    }

    runMigrations();
    profileCheckpoint('preAction_after_migrations');

    // Load remote managed settings for enterprise customers (non-blocking)
    // Fails open - if fetch fails, continues without remote settings
    // Settings are applied via hot-reload when they arrive
    // Must happen after init() to ensure config reading is allowed
    void loadRemoteManagedSettings();
    void loadPolicyLimits();

    profileCheckpoint('preAction_after_remote_settings');

    // Load settings sync (non-blocking, fail-open)
    // CLI: uploads local settings to remote (CCR download is handled by print.ts)
    if (feature('UPLOAD_USER_SETTINGS')) {
      void import('./services/settingsSync/index.js').then(m => m.uploadUserSettingsInBackground());
    }

    profileCheckpoint('preAction_after_settings_sync');
  });

  program
    .name('claude')
    .description(`Claude Code - starts an interactive session by default, use -p/--print for non-interactive output`)
    .argument('[prompt]', 'Your prompt', String)
    // Subcommands inherit helpOption via commander's copyInheritedSettings —
    // setting it once here covers mcp, plugin, auth, and all other subcommands.
    .helpOption('-h, --help', 'Display help for command')
    .option(
      '-d, --debug [filter]',
      'Enable debug mode with optional category filtering (e.g., "api,hooks" or "!1p,!file")',
      (_value: string | true) => {
        // If value is provided, it will be the filter string
        // If not provided but flag is present, value will be true
        // The actual filtering is handled in debug.ts by parsing process.argv
        return true;
      },
    )
    .addOption(new Option('--debug-to-stderr', 'Enable debug mode (to stderr)').argParser(Boolean).hideHelp())
    .option(
      '--debug-file <path>',
      'Write debug logs to a specific file path (implicitly enables debug mode)',
      () => true,
    )
    .option('--verbose', 'Override verbose mode setting from config', () => true)
    .option(
      '-p, --print',
      'Print response and exit (useful for pipes). Note: The workspace trust dialog is skipped when Claude is run with the -p mode. Only use this flag in directories you trust.',
      () => true,
    )
    .option(
      '--bare',
      'Minimal mode: skip hooks, LSP, plugin sync, attribution, auto-memory, background prefetches, keychain reads, and CLAUDE.md auto-discovery. Sets CLAUDE_CODE_SIMPLE=1. Anthropic auth is strictly ANTHROPIC_API_KEY or apiKeyHelper via --settings (OAuth and keychain are never read). 3P providers (Bedrock/Vertex/Foundry) use their own credentials. Skills still resolve via /skill-name. Explicitly provide context via: --system-prompt[-file], --append-system-prompt[-file], --add-dir (CLAUDE.md dirs), --mcp-config, --settings, --agents, --plugin-dir.',
      () => true,
    )
    .addOption(new Option('--init', 'Run Setup hooks with init trigger, then continue').hideHelp())
    .addOption(new Option('--init-only', 'Run Setup and SessionStart:startup hooks, then exit').hideHelp())
    .addOption(new Option('--maintenance', 'Run Setup hooks with maintenance trigger, then continue').hideHelp())
    .addOption(
      new Option(
        '--output-format <format>',
        'Output format (only works with --print): "text" (default), "json" (single result), or "stream-json" (realtime streaming)',
      ).choices(['text', 'json', 'stream-json']),
    )
    .addOption(
      new Option(
        '--json-schema <schema>',
        'JSON Schema for structured output validation. ' +
          'Example: {"type":"object","properties":{"name":{"type":"string"}},"required":["name"]}',
      ).argParser(String),
    )
    .option(
      '--include-hook-events',
      'Include all hook lifecycle events in the output stream (only works with --output-format=stream-json)',
      () => true,
    )
    .option(
      '--include-partial-messages',
      'Include partial message chunks as they arrive (only works with --print and --output-format=stream-json)',
      () => true,
    )
    .addOption(
      new Option(
        '--input-format <format>',
        'Input format (only works with --print): "text" (default), or "stream-json" (realtime streaming input)',
      ).choices(['text', 'stream-json']),
    )
    .option(
      '--mcp-debug',
      '[DEPRECATED. Use --debug instead] Enable MCP debug mode (shows MCP server errors)',
      () => true,
    )
    .option(
      '--dangerously-skip-permissions',
      'Bypass all permission checks. Recommended only for sandboxes with no internet access.',
      () => true,
    )
    .option(
      '--allow-dangerously-skip-permissions',
      'Enable bypassing all permission checks as an option, without it being enabled by default. Recommended only for sandboxes with no internet access.',
      () => true,
    )
    .addOption(
      new Option('--thinking <mode>', 'Thinking mode: enabled (equivalent to adaptive), disabled')
        .choices(['enabled', 'adaptive', 'disabled'])
        .hideHelp(),
    )
    .addOption(
      new Option(
        '--max-thinking-tokens <tokens>',
        '[DEPRECATED. Use --thinking instead for newer models] Maximum number of thinking tokens (only works with --print)',
      )
        .argParser(Number)
        .hideHelp(),
    )
    .addOption(
      new Option(
        '--max-turns <turns>',
        'Maximum number of agentic turns in non-interactive mode. This will early exit the conversation after the specified number of turns. (only works with --print)',
      )
        .argParser(Number)
        .hideHelp(),
    )
    .addOption(
      new Option(
        '--max-budget-usd <amount>',
        'Maximum dollar amount to spend on API calls (only works with --print)',
      ).argParser(value => {
        const amount = Number(value);
        if (isNaN(amount) || amount <= 0) {
          throw new Error('--max-budget-usd must be a positive number greater than 0');
        }
        return amount;
      }),
    )
    .addOption(
      new Option('--task-budget <tokens>', 'API-side task budget in tokens (output_config.task_budget)')
        .argParser(value => {
          const tokens = Number(value);
          if (isNaN(tokens) || tokens <= 0 || !Number.isInteger(tokens)) {
            throw new Error('--task-budget must be a positive integer');
          }
          return tokens;
        })
        .hideHelp(),
    )
    .option(
      '--replay-user-messages',
      'Re-emit user messages from stdin back on stdout for acknowledgment (only works with --input-format=stream-json and --output-format=stream-json)',
      () => true,
    )
    .addOption(new Option('--enable-auth-status', 'Enable auth status messages in SDK mode').default(false).hideHelp())
    .option(
      '--allowedTools, --allowed-tools <tools...>',
      'Comma or space-separated list of tool names to allow (e.g. "Bash(git:*) Edit")',
    )
    .option(
      '--tools <tools...>',
      'Specify the list of available tools from the built-in set. Use "" to disable all tools, "default" to use all tools, or specify tool names (e.g. "Bash,Edit,Read").',
    )
    .option(
      '--disallowedTools, --disallowed-tools <tools...>',
      'Comma or space-separated list of tool names to deny (e.g. "Bash(git:*) Edit")',
    )
    .option('--mcp-config <configs...>', 'Load MCP servers from JSON files or strings (space-separated)')
    .addOption(
      new Option('--permission-prompt-tool <tool>', 'MCP tool to use for permission prompts (only works with --print)')
        .argParser(String)
        .hideHelp(),
    )
    .addOption(new Option('--system-prompt <prompt>', 'System prompt to use for the session').argParser(String))
    .addOption(new Option('--system-prompt-file <file>', 'Read system prompt from a file').argParser(String).hideHelp())
    .addOption(
      new Option('--append-system-prompt <prompt>', 'Append a system prompt to the default system prompt').argParser(
        String,
      ),
    )
    .addOption(
      new Option(
        '--append-system-prompt-file <file>',
        'Read system prompt from a file and append to the default system prompt',
      )
        .argParser(String)
        .hideHelp(),
    )
    .addOption(
      new Option('--permission-mode <mode>', 'Permission mode to use for the session')
        .argParser(String)
        .choices(PERMISSION_MODES),
    )
    .option('-c, --continue', 'Continue the most recent conversation in the current directory', () => true)
    .option(
      '-r, --resume [value]',
      'Resume a conversation by session ID, or open interactive picker with optional search term',
      value => value || true,
    )
    .option(
      '--fork-session',
      'When resuming, create a new session ID instead of reusing the original (use with --resume or --continue)',
      () => true,
    )
    .addOption(new Option('--prefill <text>', 'Pre-fill the prompt input with text without submitting it').hideHelp())
    .addOption(new Option('--deep-link-origin', 'Signal that this session was launched from a deep link').hideHelp())
    .addOption(
      new Option(
        '--deep-link-repo <slug>',
        'Repo slug the deep link ?repo= parameter resolved to the current cwd',
      ).hideHelp(),
    )
    .addOption(
      new Option('--deep-link-last-fetch <ms>', 'FETCH_HEAD mtime in epoch ms, precomputed by the deep link trampoline')
        .argParser(v => {
          const n = Number(v);
          return Number.isFinite(n) ? n : undefined;
        })
        .hideHelp(),
    )
    .option(
      '--from-pr [value]',
      'Resume a session linked to a PR by PR number/URL, or open interactive picker with optional search term',
      value => value || true,
    )
    .option(
      '--no-session-persistence',
      'Disable session persistence - sessions will not be saved to disk and cannot be resumed (only works with --print)',
    )
    .addOption(
      new Option(
        '--resume-session-at <message id>',
        'When resuming, only messages up to and including the assistant message with <message.id> (use with --resume in print mode)',
      )
        .argParser(String)
        .hideHelp(),
    )
    .addOption(
      new Option(
        '--rewind-files <user-message-id>',
        'Restore files to state at the specified user message and exit (requires --resume)',
      ).hideHelp(),
    )
    // @[MODEL LAUNCH]: Update the example model ID in the --model help text.
    .option(
      '--model <model>',
      `Model for the current session. Provide an alias for the latest model (e.g. 'sonnet' or 'opus') or a model's full name (e.g. 'claude-sonnet-4-6').`,
    )
    .addOption(
      new Option('--effort <level>', `Effort level for the current session (low, medium, high, max)`).argParser(
        (rawValue: string) => {
          const value = rawValue.toLowerCase();
          const allowed = ['low', 'medium', 'high', 'max'];
          if (!allowed.includes(value)) {
            throw new InvalidArgumentError(`It must be one of: ${allowed.join(', ')}`);
          }
          return value;
        },
      ),
    )
    .option('--agent <agent>', `Agent for the current session. Overrides the 'agent' setting.`)
    .option('--betas <betas...>', 'Beta headers to include in API requests (API key users only)')
    .option(
      '--fallback-model <model>',
      'Enable automatic fallback to specified model when default model is overloaded (only works with --print)',
    )
    .addOption(
      new Option(
        '--workload <tag>',
        'Workload tag for billing-header attribution (cc_workload). Process-scoped; set by SDK daemon callers that spawn subprocesses for cron work. (only works with --print)',
      ).hideHelp(),
    )
    .option(
      '--settings <file-or-json>',
      'Path to a settings JSON file or a JSON string to load additional settings from',
    )
    .option('--add-dir <directories...>', 'Additional directories to allow tool access to')
    .option('--ide', 'Automatically connect to IDE on startup if exactly one valid IDE is available', () => true)
    .option(
      '--strict-mcp-config',
      'Only use MCP servers from --mcp-config, ignoring all other MCP configurations',
      () => true,
    )
    .option('--session-id <uuid>', 'Use a specific session ID for the conversation (must be a valid UUID)')
    .option('-n, --name <name>', 'Set a display name for this session (shown in /resume and terminal title)')
    .option(
      '--agents <json>',
      'JSON object defining custom agents (e.g. \'{"reviewer": {"description": "Reviews code", "prompt": "You are a code reviewer"}}\')',
    )
    .option('--setting-sources <sources>', 'Comma-separated list of setting sources to load (user, project, local).')
    // gh-33508: <paths...> (variadic) consumed everything until the next
    // --flag. `claude --plugin-dir /path mcp add --transport http` swallowed
    // `mcp` and `add` as paths, then choked on --transport as an unknown
    // top-level option. Single-value + collect accumulator means each
    // --plugin-dir takes exactly one arg; repeat the flag for multiple dirs.
    .option(
      '--plugin-dir <path>',
      'Load plugins from a directory for this session only (repeatable: --plugin-dir A --plugin-dir B)',
      (val: string, prev: string[]) => [...prev, val],
      [] as string[],
    )
    .option('--disable-slash-commands', 'Disable all skills', () => true)
    .option('--chrome', 'Enable Claude in Chrome integration')
    .option('--no-chrome', 'Disable Claude in Chrome integration')
    .option(
      '--file <specs...>',
      'File resources to download at startup. Format: file_id:relative_path (e.g., --file file_abc:doc.txt file_def:img.png)',
    )
    .action(async (prompt, options) => {
      profileCheckpoint('action_handler_start');

      // --bare = one-switch minimal mode. Sets SIMPLE so all the existing
      // gates fire (CLAUDE.md, skills, hooks inside executeHooks, agent
      // dir-walk). Must be set before setup() / any of the gated work runs.
      if ((options as { bare?: boolean }).bare) {
        process.env.CLAUDE_CODE_SIMPLE = '1';
      }

      // Ignore "code" as a prompt - treat it the same as no prompt
      if (prompt === 'code') {
        logEvent('tengu_code_prompt_ignored', {});
        console.warn(chalk.yellow('Tip: You can launch Claude Code with just `claude`'));
        prompt = undefined;
      }

      // Log event for any single-word prompt
      if (prompt && typeof prompt === 'string' && !/\s/.test(prompt) && prompt.length > 0) {
        logEvent('tengu_single_word_prompt', { length: prompt.length });
      }

      // Assistant mode: when .claude/settings.json has assistant: true AND
      // the tengu_kairos GrowthBook gate is on, force brief on. Permission
      // mode is left to the user — settings defaultMode or --permission-mode
      // apply as normal. REPL-typed messages already default to 'next'
      // priority (messageQueueManager.enqueue) so they drain mid-turn between
      // tool calls. SendUserMessage (BriefTool) is enabled via the brief env
      // var. SleepTool stays disabled (its isEnabled() gates on proactive).
      // kairosEnabled is computed once here and reused at the
      // getAssistantSystemPromptAddendum() call site further down.
      //
      // Trust gate: .claude/settings.json is attacker-controllable in an
      // untrusted clone. We run ~1000 lines before showSetupScreens() shows
      // the trust dialog, and by then we've already appended
      // .claude/agents/assistant.md to the system prompt. Refuse to activate
      // until the directory has been explicitly trusted.
      let kairosEnabled = false;
      let assistantTeamContext:
        | Awaited<ReturnType<NonNullable<typeof assistantModule>['initializeAssistantTeam']>>
        | undefined;
      if (feature('KAIROS') && (options as { assistant?: boolean }).assistant && assistantModule) {
        // --assistant (Agent SDK daemon mode): force the latch before
        // isAssistantMode() runs below. The daemon has already checked
        // entitlement — don't make the child re-check tengu_kairos.
        assistantModule.markAssistantForced();
      }
      if (
        feature('KAIROS') &&
        assistantModule &&
        (assistantModule.isAssistantForced() || (options as Record<string, unknown>).assistant === true) &&
        // Spawned teammates share the leader's cwd + settings.json, so
        // the flag is true for them too. --agent-id being set
        // means we ARE a spawned teammate (extractTeammateOptions runs
        // ~170 lines later so check the raw commander option) — don't
        // re-init the team or override teammateMode/proactive/brief.
        !(options as { agentId?: unknown }).agentId &&
        kairosGate
      ) {
        if (!checkHasTrustDialogAccepted()) {
          console.warn(
            chalk.yellow('Assistant mode disabled: directory is not trusted. Accept the trust dialog and restart.'),
          );
        } else {
          // Blocking gate check — returns cached `true` instantly; if disk
          // cache is false/missing, lazily inits GrowthBook and fetches fresh
          // (max ~5s). --assistant skips the gate entirely (daemon is
          // pre-entitled).
          kairosEnabled = assistantModule.isAssistantForced() || (await kairosGate.isKairosEnabled());
          if (kairosEnabled) {
            const opts = options as { brief?: boolean };
            opts.brief = true;
            setKairosActive(true);
            // Pre-seed an in-process team so Agent(name: "foo") spawns
            // teammates without TeamCreate. Must run BEFORE setup() captures
            // the teammateMode snapshot (initializeAssistantTeam calls
            // setCliTeammateModeOverride internally).
            assistantTeamContext = await assistantModule.initializeAssistantTeam();
          }
        }
      }

      const {
        debug = false,
        debugToStderr = false,
        dangerouslySkipPermissions,
        allowDangerouslySkipPermissions = false,
        tools: baseTools = [],
        allowedTools = [],
        disallowedTools = [],
        mcpConfig = [],
        permissionMode: permissionModeCli,
        addDir = [],
        fallbackModel,
        betas = [],
        ide = false,
        sessionId,
        includeHookEvents,
        includePartialMessages,
      } = options;

      if (options.prefill) {
        seedEarlyInput(options.prefill);
      }

      // Promise for file downloads - started early, awaited before REPL renders
      let fileDownloadPromise: Promise<DownloadResult[]> | undefined;

      const agentsJson = options.agents;
      const agentCli = options.agent;
      if (feature('BG_SESSIONS') && agentCli) {
        process.env.CLAUDE_CODE_AGENT = agentCli;
      }

      // NOTE: LSP manager initialization is intentionally deferred until after
      // the trust dialog is accepted. This prevents plugin LSP servers from
      // executing code in untrusted directories before user consent.

      // Extract these separately so they can be modified if needed
      let outputFormat = options.outputFormat;
      let inputFormat = options.inputFormat;
      let verbose = options.verbose ?? getGlobalConfig().verbose;
      let print = options.print;
      const init = options.init ?? false;
      const initOnly = options.initOnly ?? false;
      const maintenance = options.maintenance ?? false;

      // Extract disable slash commands flag
      const disableSlashCommands = options.disableSlashCommands || false;

      // Extract tasks mode options (ant-only)
      const tasksOption = process.env.USER_TYPE === 'ant' && (options as { tasks?: boolean | string }).tasks;
      const taskListId = tasksOption
        ? typeof tasksOption === 'string'
          ? tasksOption
          : DEFAULT_TASKS_MODE_TASK_LIST_ID
        : undefined;
      if (process.env.USER_TYPE === 'ant' && taskListId) {
        process.env.CLAUDE_CODE_TASK_LIST_ID = taskListId;
      }

      // Extract worktree option
      // worktree can be true (flag without value) or a string (custom name or PR reference)
      const worktreeOption = isWorktreeModeEnabled()
        ? (options as { worktree?: boolean | string }).worktree
        : undefined;
      let worktreeName = typeof worktreeOption === 'string' ? worktreeOption : undefined;
      const worktreeEnabled = worktreeOption !== undefined;

      // Check if worktree name is a PR reference (#N or GitHub PR URL)
      let worktreePRNumber: number | undefined;
      if (worktreeName) {
        const prNum = parsePRReference(worktreeName);
        if (prNum !== null) {
          worktreePRNumber = prNum;
          worktreeName = undefined; // slug will be generated in setup()
        }
      }

      // Extract tmux option (requires --worktree)
      const tmuxEnabled = isWorktreeModeEnabled() && (options as { tmux?: boolean }).tmux === true;

      // Validate tmux option
      if (tmuxEnabled) {
        if (!worktreeEnabled) {
          process.stderr.write(chalk.red('Error: --tmux requires --worktree\n'));
          process.exit(1);
        }
        if (getPlatform() === 'windows') {
          process.stderr.write(chalk.red('Error: --tmux is not supported on Windows\n'));
          process.exit(1);
        }
        if (!(await isTmuxAvailable())) {
          process.stderr.write(chalk.red(`Error: tmux is not installed.\n${getTmuxInstallInstructions()}\n`));
          process.exit(1);
        }
      }

      // Extract teammate options (for tmux-spawned agents)
      // Declared outside the if block so it's accessible later for system prompt addendum
      let storedTeammateOpts: TeammateOptions | undefined;
      if (isAgentSwarmsEnabled()) {
        // Extract agent identity options (for tmux-spawned agents)
        // These replace the CLAUDE_CODE_* environment variables
        const teammateOpts = extractTeammateOptions(options);
        storedTeammateOpts = teammateOpts;

        // If any teammate identity option is provided, all three required ones must be present
        const hasAnyTeammateOpt = teammateOpts.agentId || teammateOpts.agentName || teammateOpts.teamName;
        const hasAllRequiredTeammateOpts = teammateOpts.agentId && teammateOpts.agentName && teammateOpts.teamName;

        if (hasAnyTeammateOpt && !hasAllRequiredTeammateOpts) {
          process.stderr.write(
            chalk.red('Error: --agent-id, --agent-name, and --team-name must all be provided together\n'),
          );
          process.exit(1);
        }

        // If teammate identity is provided via CLI, set up dynamicTeamContext
        if (teammateOpts.agentId && teammateOpts.agentName && teammateOpts.teamName) {
          getTeammateUtils().setDynamicTeamContext?.({
            agentId: teammateOpts.agentId,
            agentName: teammateOpts.agentName,
            teamName: teammateOpts.teamName,
            color: teammateOpts.agentColor,
            planModeRequired: teammateOpts.planModeRequired ?? false,
            parentSessionId: teammateOpts.parentSessionId,
          });
        }

        // Set teammate mode CLI override if provided
        // This must be done before setup() captures the snapshot
        if (teammateOpts.teammateMode) {
          getTeammateModeSnapshot().setCliTeammateModeOverride?.(teammateOpts.teammateMode);
        }
      }

      // Extract remote sdk options
      const sdkUrl = (options as { sdkUrl?: string }).sdkUrl ?? undefined;

      // Allow env var to enable partial messages (used by sandbox gateway for baku)
      const effectiveIncludePartialMessages =
        includePartialMessages || isEnvTruthy(process.env.CLAUDE_CODE_INCLUDE_PARTIAL_MESSAGES);

      // Enable all hook event types when explicitly requested via SDK option
      // or when running in CLAUDE_CODE_REMOTE mode (CCR needs them).
      // Without this, only SessionStart and Setup events are emitted.
      if (includeHookEvents || isEnvTruthy(process.env.CLAUDE_CODE_REMOTE)) {
        setAllHookEventsEnabled(true);
      }

      // Auto-set input/output formats, verbose mode, and print mode when SDK URL is provided
      if (sdkUrl) {
        // If SDK URL is provided, automatically use stream-json formats unless explicitly set
        if (!inputFormat) {
          inputFormat = 'stream-json';
        }
        if (!outputFormat) {
          outputFormat = 'stream-json';
        }
        // Auto-enable verbose mode unless explicitly disabled or already set
        if (options.verbose === undefined) {
          verbose = true;
        }
        // Auto-enable print mode unless explicitly disabled
        if (!options.print) {
          print = true;
        }
      }

      // Extract teleport option
      const teleport = (options as { teleport?: string | true }).teleport ?? null;

      // Extract remote option (can be true if no description provided, or a string)
      const remoteOption = (options as { remote?: string | true }).remote;
      const remote = remoteOption === true ? '' : (remoteOption ?? null);

      // Extract --remote-control / --rc flag (enable bridge in interactive session)
      const remoteControlOption =
        (options as { remoteControl?: string | true }).remoteControl ?? (options as { rc?: string | true }).rc;
      // Actual bridge check is deferred to after showSetupScreens() so that
      // trust is established and GrowthBook has auth headers.
      let remoteControl = false;
      const remoteControlName =
        typeof remoteControlOption === 'string' && remoteControlOption.length > 0 ? remoteControlOption : undefined;

      // Validate session ID if provided
      if (sessionId) {
        // Check for conflicting flags
        // --session-id can be used with --continue or --resume when --fork-session is also provided
        // (to specify a custom ID for the forked session)
        if ((options.continue || options.resume) && !options.forkSession) {
          process.stderr.write(
            chalk.red(
              'Error: --session-id can only be used with --continue or --resume if --fork-session is also specified.\n',
            ),
          );
          process.exit(1);
        }

        // When --sdk-url is provided (bridge/remote mode), the session ID is a
        // server-assigned tagged ID (e.g. "session_local_01...") rather than a
        // UUID. Skip UUID validation and local existence checks in that case.
        if (!sdkUrl) {
          const validatedSessionId = validateUuid(sessionId);
          if (!validatedSessionId) {
            process.stderr.write(chalk.red('Error: Invalid session ID. Must be a valid UUID.\n'));
            process.exit(1);
          }

          // Check if session ID already exists
          if (sessionIdExists(validatedSessionId)) {
            process.stderr.write(chalk.red(`Error: Session ID ${validatedSessionId} is already in use.\n`));
            process.exit(1);
          }
        }
      }

      // Download file resources if specified via --file flag
      const fileSpecs = (options as { file?: string[] }).file;
      if (fileSpecs && fileSpecs.length > 0) {
        // Get session ingress token (provided by EnvManager via CLAUDE_CODE_SESSION_ACCESS_TOKEN)
        const sessionToken = getSessionIngressAuthToken();
        if (!sessionToken) {
          process.stderr.write(
            chalk.red(
              'Error: Session token required for file downloads. CLAUDE_CODE_SESSION_ACCESS_TOKEN must be set.\n',
            ),
          );
          process.exit(1);
        }

        // Resolve session ID: prefer remote session ID, fall back to internal session ID
        const fileSessionId = process.env.CLAUDE_CODE_REMOTE_SESSION_ID || getSessionId();

        const files = parseFileSpecs(fileSpecs);
        if (files.length > 0) {
          // Use ANTHROPIC_BASE_URL if set (by EnvManager), otherwise use OAuth config
          // This ensures consistency with session ingress API in all environments
          const config: FilesApiConfig = {
            baseUrl: process.env.ANTHROPIC_BASE_URL || getOauthConfig().BASE_API_URL,
            oauthToken: sessionToken,
            sessionId: fileSessionId,
          };

          // Start download without blocking startup - await before REPL renders
          fileDownloadPromise = downloadSessionFiles(files, config);
        }
      }

      // Get isNonInteractiveSession from state (was set before init())
      const isNonInteractiveSession = getIsNonInteractiveSession();

      // Validate that fallback model is different from main model
      if (fallbackModel && options.model && fallbackModel === options.model) {
        process.stderr.write(
          chalk.red(
            'Error: Fallback model cannot be the same as the main model. Please specify a different model for --fallback-model.\n',
          ),
        );
        process.exit(1);
      }

      // Handle system prompt options
      let systemPrompt = options.systemPrompt;
      if (options.systemPromptFile) {
        if (options.systemPrompt) {
          process.stderr.write(
            chalk.red('Error: Cannot use both --system-prompt and --system-prompt-file. Please use only one.\n'),
          );
          process.exit(1);
        }

        try {
          const filePath = resolve(options.systemPromptFile);
          systemPrompt = readFileSync(filePath, 'utf8');
        } catch (error) {
          const code = getErrnoCode(error);
          if (code === 'ENOENT') {
            process.stderr.write(
              chalk.red(`Error: System prompt file not found: ${resolve(options.systemPromptFile)}\n`),
            );
            process.exit(1);
          }
          process.stderr.write(chalk.red(`Error reading system prompt file: ${errorMessage(error)}\n`));
          process.exit(1);
        }
      }

      // Handle append system prompt options
      let appendSystemPrompt = options.appendSystemPrompt;
      if (options.appendSystemPromptFile) {
        if (options.appendSystemPrompt) {
          process.stderr.write(
            chalk.red(
              'Error: Cannot use both --append-system-prompt and --append-system-prompt-file. Please use only one.\n',
            ),
          );
          process.exit(1);
        }

        try {
          const filePath = resolve(options.appendSystemPromptFile);
          appendSystemPrompt = readFileSync(filePath, 'utf8');
        } catch (error) {
          const code = getErrnoCode(error);
          if (code === 'ENOENT') {
            process.stderr.write(
              chalk.red(`Error: Append system prompt file not found: ${resolve(options.appendSystemPromptFile)}\n`),
            );
            process.exit(1);
          }
          process.stderr.write(chalk.red(`Error reading append system prompt file: ${errorMessage(error)}\n`));
          process.exit(1);
        }
      }

      // Add teammate-specific system prompt addendum for tmux teammates
      if (
        isAgentSwarmsEnabled() &&
        storedTeammateOpts?.agentId &&
        storedTeammateOpts?.agentName &&
        storedTeammateOpts?.teamName
      ) {
        const addendum = getTeammatePromptAddendum().TEAMMATE_SYSTEM_PROMPT_ADDENDUM;
        appendSystemPrompt = appendSystemPrompt ? `${appendSystemPrompt}\n\n${addendum}` : addendum;
      }

      const { mode: permissionMode, notification: permissionModeNotification } = initialPermissionModeFromCLI({
        permissionModeCli,
        dangerouslySkipPermissions,
      });

      // Store session bypass permissions mode for trust dialog check
      setSessionBypassPermissionsMode(permissionMode === 'bypassPermissions');
      if (feature('TRANSCRIPT_CLASSIFIER')) {
        // autoModeFlagCli is the "did the user intend auto this session" signal.
        // Set when: --enable-auto-mode, --permission-mode auto, resolved mode
        // is auto, OR settings defaultMode is auto but the gate denied it
        // (permissionMode resolved to default with no explicit CLI override).
        // Used by verifyAutoModeGateAccess to decide whether to notify on
        // auto-unavailable, and by tengu_auto_mode_config opt-in carousel.
        if (
          (options as { enableAutoMode?: boolean }).enableAutoMode ||
          permissionModeCli === 'auto' ||
          permissionMode === 'auto' ||
          (!permissionModeCli && isDefaultPermissionModeAuto())
        ) {
          autoModeStateModule?.setAutoModeFlagCli(true);
        }
      }

      // Parse the MCP config files/strings if provided
      let dynamicMcpConfig: Record<string, ScopedMcpServerConfig> = {
        // Built-in MCP servers (default disabled, user enables via /mcp)
        'mcp-chrome': {
          type: 'http',
          url: 'http://127.0.0.1:12306/mcp',
          scope: 'dynamic',
          headers: {
            Authorization: 'Bearer my-static-token',
          },
        },
      };

      if (mcpConfig && mcpConfig.length > 0) {
        // Process mcpConfig array
        const processedConfigs = mcpConfig.map(config => config.trim()).filter(config => config.length > 0);

        let allConfigs: Record<string, McpServerConfig> = {};
        const allErrors: ValidationError[] = [];

        for (const configItem of processedConfigs) {
          let configs: Record<string, McpServerConfig> | null = null;
          let errors: ValidationError[] = [];

          // First try to parse as JSON string
          const parsedJson = safeParseJSON(configItem);
          if (parsedJson) {
            const result = parseMcpConfig({
              configObject: parsedJson,
              filePath: 'command line',
              expandVars: true,
              scope: 'dynamic',
            });
            if (result.config) {
              configs = result.config.mcpServers;
            } else {
              errors = result.errors;
            }
          } else {
            // Try as file path
            const configPath = resolve(configItem);
            const result = parseMcpConfigFromFilePath({
              filePath: configPath,
              expandVars: true,
              scope: 'dynamic',
            });
            if (result.config) {
              configs = result.config.mcpServers;
            } else {
              errors = result.errors;
            }
          }

          if (errors.length > 0) {
            allErrors.push(...errors);
          } else if (configs) {
            // Merge configs, later ones override earlier ones
            allConfigs = { ...allConfigs, ...configs };
          }
        }

        if (allErrors.length > 0) {
          const formattedErrors = allErrors.map(err => `${err.path ? err.path + ': ' : ''}${err.message}`).join('\n');
          logForDebugging(`--mcp-config validation failed (${allErrors.length} errors): ${formattedErrors}`, {
            level: 'error',
          });
          process.stderr.write(`Error: Invalid MCP configuration:\n${formattedErrors}\n`);
          process.exit(1);
        }

        if (Object.keys(allConfigs).length > 0) {
          // SDK hosts (Nest/Desktop) own their server naming and may reuse
          // built-in names — skip reserved-name checks for type:'sdk'.
          const nonSdkConfigNames = Object.entries(allConfigs)
            .filter(([, config]) => config.type !== 'sdk')
            .map(([name]) => name);

          let reservedNameError: string | null = null;
          if (nonSdkConfigNames.some(isClaudeInChromeMCPServer)) {
            reservedNameError = `Invalid MCP configuration: "${CLAUDE_IN_CHROME_MCP_SERVER_NAME}" is a reserved MCP name.`;
          } else if (feature('CHICAGO_MCP')) {
            const { isComputerUseMCPServer, COMPUTER_USE_MCP_SERVER_NAME } = await import(
              'src/utils/computerUse/common.js'
            );
            if (nonSdkConfigNames.some(isComputerUseMCPServer)) {
              reservedNameError = `Invalid MCP configuration: "${COMPUTER_USE_MCP_SERVER_NAME}" is a reserved MCP name.`;
            }
          }
          if (reservedNameError) {
            // stderr+exit(1) — a throw here becomes a silent unhandled
            // rejection in stream-json mode (void main() in cli.tsx).
            process.stderr.write(`Error: ${reservedNameError}\n`);
            process.exit(1);
          }

          // Add dynamic scope to all configs. type:'sdk' entries pass through
          // unchanged — they're extracted into sdkMcpConfigs downstream and
          // passed to print.ts. The Python SDK relies on this path (it doesn't
          // send sdkMcpServers in the initialize message). Dropping them here
          // broke Coworker (inc-5122). The policy filter below already exempts
          // type:'sdk', and the entries are inert without an SDK transport on
          // stdin, so there's no bypass risk from letting them through.
          const scopedConfigs = mapValues(allConfigs, config => ({
            ...config,
            scope: 'dynamic' as const,
          }));

          // Enforce managed policy (allowedMcpServers / deniedMcpServers) on
          // --mcp-config servers. Without this, the CLI flag bypasses the
          // enterprise allowlist that user/project/local configs go through in
          // getClaudeCodeMcpConfigs — callers spread dynamicMcpConfig back on
          // top of filtered results. Filter here at the source so all
          // downstream consumers see the policy-filtered set.
          const { allowed, blocked } = filterMcpServersByPolicy(scopedConfigs);
          if (blocked.length > 0) {
            process.stderr.write(
              `Warning: MCP ${plural(blocked.length, 'server')} blocked by enterprise policy: ${blocked.join(', ')}\n`,
            );
          }
          dynamicMcpConfig = { ...dynamicMcpConfig, ...(allowed as Record<string, ScopedMcpServerConfig>) };
        }
      }

      // Extract Claude in Chrome option and enforce claude.ai subscriber check (unless user is ant)
      const chromeOpts = options as { chrome?: boolean };
      // Store the explicit CLI flag so teammates can inherit it
      setChromeFlagOverride(chromeOpts.chrome);
      const enableClaudeInChrome =
        shouldEnableClaudeInChrome(chromeOpts.chrome) && (process.env.USER_TYPE === 'ant' || isClaudeAISubscriber());
      const autoEnableClaudeInChrome = !enableClaudeInChrome && shouldAutoEnableClaudeInChrome();

      if (enableClaudeInChrome) {
        const platform = getPlatform();
        try {
          logEvent('tengu_claude_in_chrome_setup', {
            platform: platform as AnalyticsMetadata_I_VERIFIED_THIS_IS_NOT_CODE_OR_FILEPATHS,
          });

          const {
            mcpConfig: chromeMcpConfig,
            allowedTools: chromeMcpTools,
            systemPrompt: chromeSystemPrompt,
          } = setupClaudeInChrome();
          dynamicMcpConfig = {
            ...dynamicMcpConfig,
            ...chromeMcpConfig,
          };
          allowedTools.push(...chromeMcpTools);
          if (chromeSystemPrompt) {
            appendSystemPrompt = appendSystemPrompt
              ? `${chromeSystemPrompt}\n\n${appendSystemPrompt}`
              : chromeSystemPrompt;
          }
        } catch (error) {
          logEvent('tengu_claude_in_chrome_setup_failed', {
            platform: platform as AnalyticsMetadata_I_VERIFIED_THIS_IS_NOT_CODE_OR_FILEPATHS,
          });
          logForDebugging(`[Claude in Chrome] Error: ${error}`);
          logError(error);
          console.error(`Error: Failed to run with Claude in Chrome.`);
          process.exit(1);
        }
      } else if (autoEnableClaudeInChrome) {
        try {
          const { mcpConfig: chromeMcpConfig } = setupClaudeInChrome();
          dynamicMcpConfig = {
            ...dynamicMcpConfig,
            ...chromeMcpConfig,
          };

          const hint =
            feature('WEB_BROWSER_TOOL') && typeof Bun !== 'undefined' && 'WebView' in Bun
              ? CLAUDE_IN_CHROME_SKILL_HINT_WITH_WEBBROWSER
              : CLAUDE_IN_CHROME_SKILL_HINT;
          appendSystemPrompt = appendSystemPrompt ? `${appendSystemPrompt}\n\n${hint}` : hint;
        } catch (error) {
          // Silently skip any errors for the auto-enable
          logForDebugging(`[Claude in Chrome] Error (auto-enable): ${error}`);
        }
      }

      // Extract strict MCP config flag
      const strictMcpConfig = options.strictMcpConfig || false;

      // Check if enterprise MCP configuration exists. When it does, only allow dynamic MCP
      // configs that contain special server types (sdk)
      if (doesEnterpriseMcpConfigExist()) {
        if (strictMcpConfig) {
          process.stderr.write(
            chalk.red('You cannot use --strict-mcp-config when an enterprise MCP config is present'),
          );
          process.exit(1);
        }

        // For --mcp-config, allow if all servers are internal types (sdk)
        if (dynamicMcpConfig && !areMcpConfigsAllowedWithEnterpriseMcpConfig(dynamicMcpConfig)) {
          process.stderr.write(
            chalk.red('You cannot dynamically configure MCP servers when an enterprise MCP config is present'),
          );
          process.exit(1);
        }
      }

      // chicago MCP: guarded Computer Use (app allowlist + frontmost gate +
      // SCContentFilter screenshots). Ant-only, GrowthBook-gated — failures
      // are silent (this is dogfooding). Platform + interactive checks inline
      // so non-macOS / print-mode ants skip the heavy @ant/computer-use-mcp
      // import entirely. gates.js is light (type-only package import).
      //
      // Placed AFTER the enterprise-MCP-config check: that check rejects any
      // dynamicMcpConfig entry with `type !== 'sdk'`, and our config is
      // `type: 'stdio'`. An enterprise-config ant with the GB gate on would
      // otherwise process.exit(1). Chrome has the same latent issue but has
      // shipped without incident; chicago places itself correctly.
      if (feature('CHICAGO_MCP') && getPlatform() !== 'unknown' && !getIsNonInteractiveSession()) {
        try {
          const { getChicagoEnabled } = await import('src/utils/computerUse/gates.js');
          if (getChicagoEnabled()) {
            const { setupComputerUseMCP } = await import('src/utils/computerUse/setup.js');
            const { mcpConfig, allowedTools: cuTools } = setupComputerUseMCP();
            dynamicMcpConfig = {
              ...dynamicMcpConfig,
              ...mcpConfig,
            };
            allowedTools.push(...cuTools);
          }
        } catch (error) {
          logForDebugging(`[Computer Use MCP] Setup failed: ${errorMessage(error)}`);
        }
      }

      // Store additional directories for CLAUDE.md loading (controlled by env var)
      setAdditionalDirectoriesForClaudeMd(addDir);

      // Channel server allowlist from --channels flag — servers whose
      // inbound push notifications should register this session. The option
      // is added inside a feature() block so TS doesn't know about it
      // on the options type — same pattern as --assistant at main.tsx:1824.
      // devChannels is deferred: showSetupScreens shows a confirmation dialog
      // and only appends to allowedChannels on accept.
      let devChannels: ChannelEntry[] | undefined;
      // Parse plugin:name@marketplace / server:Y tags into typed entries.
      // Tag decides trust model downstream: plugin-kind hits marketplace
      // verification + GrowthBook allowlist, server-kind always fails
      // allowlist (schema is plugin-only) unless dev flag is set.
      // Untagged or marketplace-less plugin entries are hard errors —
      // silently not-matching in the gate would look like channels are
      // "on" but nothing ever fires.
      const parseChannelEntries = (raw: string[], flag: string): ChannelEntry[] => {
        const entries: ChannelEntry[] = [];
        const bad: string[] = [];
        for (const c of raw) {
          if (c.startsWith('plugin:')) {
            const rest = c.slice(7);
            const at = rest.indexOf('@');
            if (at <= 0 || at === rest.length - 1) {
              bad.push(c);
            } else {
              entries.push({
                kind: 'plugin',
                name: rest.slice(0, at),
                marketplace: rest.slice(at + 1),
              });
            }
          } else if (c.startsWith('server:') && c.length > 7) {
            entries.push({ kind: 'server', name: c.slice(7) });
          } else {
            bad.push(c);
          }
        }
        if (bad.length > 0) {
          process.stderr.write(
            chalk.red(
              `${flag} entries must be tagged: ${bad.join(', ')}\n` +
                `  plugin:<name>@<marketplace>  — plugin-provided channel (allowlist enforced)\n` +
                `  server:<name>                — manually configured MCP server\n`,
            ),
          );
          process.exit(1);
        }
        return entries;
      };

      const channelOpts = options as {
        channels?: string[];
        dangerouslyLoadDevelopmentChannels?: string[];
      };
      const rawChannels = channelOpts.channels;
      const rawDev = channelOpts.dangerouslyLoadDevelopmentChannels;
      // Always parse + set. ChannelsNotice reads getAllowedChannels() and
      // renders the appropriate branch (disabled/noAuth/policyBlocked/
      // listening) in the startup screen. gateChannelServer() enforces.
      // --channels works in both interactive and print/SDK modes; dev-channels
      // stays interactive-only (requires a confirmation dialog).
      let channelEntries: ChannelEntry[] = [];
      if (rawChannels && rawChannels.length > 0) {
        channelEntries = parseChannelEntries(rawChannels, '--channels');
        setAllowedChannels(channelEntries);
      }
      if (!isNonInteractiveSession) {
        if (rawDev && rawDev.length > 0) {
          devChannels = parseChannelEntries(rawDev, '--dangerously-load-development-channels');
        }
      }
      // Flag-usage telemetry. Plugin identifiers are logged (same tier as
      // tengu_plugin_installed — public-registry-style names); server-kind
      // names are not (MCP-server-name tier, opt-in-only elsewhere).
      // Per-server gate outcomes land in tengu_mcp_channel_gate once
      // servers connect. Dev entries go through a confirmation dialog after
      // this — dev_plugins captures what was typed, not what was accepted.
      if (channelEntries.length > 0 || (devChannels?.length ?? 0) > 0) {
        const joinPluginIds = (entries: ChannelEntry[]) => {
          const ids = entries.flatMap(e => (e.kind === 'plugin' ? [`${e.name}@${e.marketplace}`] : []));
          return ids.length > 0
            ? (ids.sort().join(',') as AnalyticsMetadata_I_VERIFIED_THIS_IS_NOT_CODE_OR_FILEPATHS)
            : undefined;
        };
        logEvent('tengu_mcp_channel_flags', {
          channels_count: channelEntries.length,
          dev_count: devChannels?.length ?? 0,
          plugins: joinPluginIds(channelEntries),
          dev_plugins: joinPluginIds(devChannels ?? []),
        });
      }

      // SDK opt-in for SendUserMessage via --tools. All sessions require
      // explicit opt-in; listing it in --tools signals intent. Runs BEFORE
      // initializeToolPermissionContext so getToolsForDefaultPreset() sees
      // the tool as enabled when computing the base-tools disallow filter.
      // Conditional require avoids leaking the tool-name string into
      // external builds.
      if ((feature('KAIROS') || feature('KAIROS_BRIEF')) && baseTools.length > 0) {
        /* eslint-disable @typescript-eslint/no-require-imports */
        const { BRIEF_TOOL_NAME, LEGACY_BRIEF_TOOL_NAME } =
          require('@claude-code-best/builtin-tools/tools/BriefTool/prompt.js') as typeof import('@claude-code-best/builtin-tools/tools/BriefTool/prompt.js');
        const { isBriefEntitled } =
          require('@claude-code-best/builtin-tools/tools/BriefTool/BriefTool.js') as typeof import('@claude-code-best/builtin-tools/tools/BriefTool/BriefTool.js');
        /* eslint-enable @typescript-eslint/no-require-imports */
        const parsed = parseToolListFromCLI(baseTools);
        if ((parsed.includes(BRIEF_TOOL_NAME) || parsed.includes(LEGACY_BRIEF_TOOL_NAME)) && isBriefEntitled()) {
          setUserMsgOptIn(true);
        }
      }

      // This await replaces blocking existsSync/statSync calls that were already in
      // the startup path. Wall-clock time is unchanged; we just yield to the event
      // loop during the fs I/O instead of blocking it. See #19661.
      const initResult = await initializeToolPermissionContext({
        allowedToolsCli: allowedTools,
        disallowedToolsCli: disallowedTools,
        baseToolsCli: baseTools,
        permissionMode,
        allowDangerouslySkipPermissions,
        addDirs: addDir,
      });
      let toolPermissionContext = initResult.toolPermissionContext;
      const { warnings, dangerousPermissions, overlyBroadBashPermissions } = initResult;

      // Handle overly broad shell allow rules for ant users (Bash(*), PowerShell(*))
      if (process.env.USER_TYPE === 'ant' && overlyBroadBashPermissions.length > 0) {
        for (const permission of overlyBroadBashPermissions) {
          logForDebugging(
            `Ignoring overly broad shell permission ${permission.ruleDisplay} from ${permission.sourceDisplay}`,
          );
        }
        toolPermissionContext = removeDangerousPermissions(toolPermissionContext, overlyBroadBashPermissions);
      }

      if (feature('TRANSCRIPT_CLASSIFIER') && dangerousPermissions.length > 0) {
        toolPermissionContext = stripDangerousPermissionsForAutoMode(toolPermissionContext);
      }

      // Print any warnings from initialization
      warnings.forEach(warning => {
        console.error(warning);
      });

      // claude.ai config fetch: -p mode only (interactive uses useManageMCPConnections
      // two-phase loading). Kicked off here to overlap with setup(); awaited
      // before runHeadless so single-turn -p sees connectors. Skipped under
      // enterprise/strict MCP to preserve policy boundaries.
      const claudeaiConfigPromise: Promise<Record<string, ScopedMcpServerConfig>> =
        isNonInteractiveSession &&
        !strictMcpConfig &&
        !doesEnterpriseMcpConfigExist() &&
        // --bare / SIMPLE: skip claude.ai proxy servers (datadog, Gmail,
        // Slack, BigQuery, PubMed — 6-14s each to connect). Scripted calls
        // that need MCP pass --mcp-config explicitly.
        !isBareMode()
          ? fetchClaudeAIMcpConfigsIfEligible().then(configs => {
              const { allowed, blocked } = filterMcpServersByPolicy(configs);
              if (blocked.length > 0) {
                process.stderr.write(
                  `Warning: claude.ai MCP ${plural(blocked.length, 'server')} blocked by enterprise policy: ${blocked.join(', ')}\n`,
                );
              }
              return allowed;
            })
          : Promise.resolve({});

      // Kick off MCP config loading early (safe - just reads files, no execution).
      // Both interactive and -p use getClaudeCodeMcpConfigs (local file reads only).
      // The local promise is awaited later (before prefetchAllMcpResources) to
      // overlap config I/O with setup(), commands loading, and trust dialog.
      logForDebugging('[STARTUP] Loading MCP configs...');
      const mcpConfigStart = Date.now();
      let mcpConfigResolvedMs: number | undefined;
      // --bare skips auto-discovered MCP (.mcp.json, user settings, plugins) —
      // only explicit --mcp-config works. dynamicMcpConfig is spread onto
      // allMcpConfigs downstream so it survives this skip.
      const mcpConfigPromise = (
        strictMcpConfig || isBareMode()
          ? Promise.resolve({
              servers: {} as Record<string, ScopedMcpServerConfig>,
            })
          : getClaudeCodeMcpConfigs(dynamicMcpConfig)
      ).then(result => {
        mcpConfigResolvedMs = Date.now() - mcpConfigStart;
        return result;
      });

      // NOTE: We do NOT call prefetchAllMcpResources here - that's deferred until after trust dialog

      if (inputFormat && inputFormat !== 'text' && inputFormat !== 'stream-json') {
        console.error(`Error: Invalid input format "${inputFormat}".`);
        process.exit(1);
      }
      if (inputFormat === 'stream-json' && outputFormat !== 'stream-json') {
        console.error(`Error: --input-format=stream-json requires output-format=stream-json.`);
        process.exit(1);
      }

      // Validate sdkUrl is only used with appropriate formats (formats are auto-set above)
      if (sdkUrl) {
        if (inputFormat !== 'stream-json' || outputFormat !== 'stream-json') {
          console.error(`Error: --sdk-url requires both --input-format=stream-json and --output-format=stream-json.`);
          process.exit(1);
        }
      }

      // Validate replayUserMessages is only used with stream-json formats
      if (options.replayUserMessages) {
        if (inputFormat !== 'stream-json' || outputFormat !== 'stream-json') {
          console.error(
            `Error: --replay-user-messages requires both --input-format=stream-json and --output-format=stream-json.`,
          );
          process.exit(1);
        }
      }

      // Validate includePartialMessages is only used with print mode and stream-json output
      if (effectiveIncludePartialMessages) {
        if (!isNonInteractiveSession || outputFormat !== 'stream-json') {
          writeToStderr(`Error: --include-partial-messages requires --print and --output-format=stream-json.`);
          process.exit(1);
        }
      }

      // Validate --no-session-persistence is only used with print mode
      if (options.sessionPersistence === false && !isNonInteractiveSession) {
        writeToStderr(`Error: --no-session-persistence can only be used with --print mode.`);
        process.exit(1);
      }

      const effectivePrompt = prompt || '';
      let inputPrompt = await getInputPrompt(effectivePrompt, (inputFormat ?? 'text') as 'text' | 'stream-json');
      profileCheckpoint('action_after_input_prompt');

      // Activate proactive mode BEFORE getTools() so SleepTool.isEnabled()
      // (which returns isProactiveActive()) passes and Sleep is included.
      // The later REPL-path maybeActivateProactive() calls are idempotent.
      maybeActivateProactive(options);

      let tools = getTools(toolPermissionContext);

      // Apply coordinator mode tool filtering for headless path
      // (mirrors useMergedTools.ts filtering for REPL/interactive path)
      if (feature('COORDINATOR_MODE') && isEnvTruthy(process.env.CLAUDE_CODE_COORDINATOR_MODE)) {
        const { applyCoordinatorToolFilter } = await import('./utils/toolPool.js');
        tools = applyCoordinatorToolFilter(tools);
      }

      profileCheckpoint('action_tools_loaded');

      let jsonSchema: ToolInputJSONSchema | undefined;
      if (isSyntheticOutputToolEnabled({ isNonInteractiveSession }) && options.jsonSchema) {
        jsonSchema = jsonParse(options.jsonSchema) as ToolInputJSONSchema;
      }

      if (jsonSchema) {
        const syntheticOutputResult = createSyntheticOutputTool(jsonSchema);
        if ('tool' in syntheticOutputResult) {
          // Add SyntheticOutputTool to the tools array AFTER getTools() filtering.
          // This tool is excluded from normal filtering (see tools.ts) because it's
          // an implementation detail for structured output, not a user-controlled tool.
          tools = [...tools, syntheticOutputResult.tool];

          logEvent('tengu_structured_output_enabled', {
            schema_property_count: Object.keys((jsonSchema.properties as Record<string, unknown>) || {})
              .length as AnalyticsMetadata_I_VERIFIED_THIS_IS_NOT_CODE_OR_FILEPATHS,
            has_required_fields: Boolean(
              jsonSchema.required,
            ) as AnalyticsMetadata_I_VERIFIED_THIS_IS_NOT_CODE_OR_FILEPATHS,
          });
        } else {
          logEvent('tengu_structured_output_failure', {
            error: 'Invalid JSON schema' as AnalyticsMetadata_I_VERIFIED_THIS_IS_NOT_CODE_OR_FILEPATHS,
          });
        }
      }

      // IMPORTANT: setup() must be called before any other code that depends on the cwd or worktree setup
      profileCheckpoint('action_before_setup');
      logForDebugging('[STARTUP] Running setup()...');
      const setupStart = Date.now();
      const { setup } = await import('./setup.js');
      const messagingSocketPath = feature('UDS_INBOX')
        ? (options as { messagingSocketPath?: string }).messagingSocketPath
        : undefined;
      // Parallelize setup() with commands+agents loading. setup()'s ~28ms is
      // mostly startUdsMessaging (socket bind, ~20ms) — not disk-bound, so it
      // doesn't contend with getCommands' file reads. Gated on !worktreeEnabled
      // since --worktree makes setup() process.chdir() (setup.ts:203), and
      // commands/agents need the post-chdir cwd.
      const preSetupCwd = getCwd();
      // Register bundled skills/plugins before kicking getCommands() — they're
      // pure in-memory array pushes (<1ms, zero I/O) that getBundledSkills()
      // reads synchronously. Previously ran inside setup() after ~20ms of
      // await points, so the parallel getCommands() memoized an empty list.
      if (process.env.CLAUDE_CODE_ENTRYPOINT !== 'local-agent') {
        initBuiltinPlugins();
        initBundledSkills();
      }
      const setupPromise = setup(
        preSetupCwd,
        permissionMode,
        allowDangerouslySkipPermissions,
        worktreeEnabled,
        worktreeName,
        tmuxEnabled,
        sessionId ? validateUuid(sessionId) : undefined,
        worktreePRNumber,
        messagingSocketPath,
      );
      const commandsPromise = worktreeEnabled ? null : getCommands(preSetupCwd);
      const agentDefsPromise = worktreeEnabled ? null : getAgentDefinitionsWithOverrides(preSetupCwd);
      // Suppress transient unhandledRejection if these reject during the
      // ~28ms setupPromise await before Promise.all joins them below.
      commandsPromise?.catch(() => {});
      agentDefsPromise?.catch(() => {});
      await setupPromise;
      logForDebugging(`[STARTUP] setup() completed in ${Date.now() - setupStart}ms`);
      profileCheckpoint('action_after_setup');

      // Replay user messages into stream-json only when the socket was
      // explicitly requested. The auto-generated socket is passive — it
      // lets tools inject if they want to, but turning it on by default
      // shouldn't reshape stream-json for SDK consumers who never touch it.
      // Callers who inject and also want those injections visible in the
      // stream pass --messaging-socket-path explicitly (or --replay-user-messages).
      let effectiveReplayUserMessages = !!options.replayUserMessages;
      if (feature('UDS_INBOX')) {
        if (!effectiveReplayUserMessages && outputFormat === 'stream-json') {
          effectiveReplayUserMessages = !!(options as { messagingSocketPath?: string }).messagingSocketPath;
        }
      }

      if (getIsNonInteractiveSession()) {
        // Apply full merged settings env now (including project-scoped
        // .claude/settings.json PATH/GIT_DIR/GIT_WORK_TREE) so gitExe() and
        // the git spawn below see it. Trust is implicit in -p mode; the
        // docstring at managedEnv.ts:96-97 says this applies "potentially
        // dangerous environment variables such as LD_PRELOAD, PATH" from all
        // sources. The later call in the isNonInteractiveSession block below
        // is idempotent (Object.assign, configureGlobalAgents ejects prior
        // interceptor) and picks up any plugin-contributed env after plugin
        // init. Project settings are already loaded here:
        // applySafeConfigEnvironmentVariables in init() called
        // getSettings_DEPRECATED at managedEnv.ts:86 which merges all enabled
        // sources including projectSettings/localSettings.
        applyConfigEnvironmentVariables();

        // Spawn git status/log/branch now so the subprocess execution overlaps
        // with the getCommands await below and startDeferredPrefetches. After
        // setup() so cwd is final (setup.ts:254 may process.chdir(worktreePath)
        // for --worktree) and after the applyConfigEnvironmentVariables above
        // so PATH/GIT_DIR/GIT_WORK_TREE from all sources (trusted + project)
        // are applied. getSystemContext is memoized; the
        // prefetchSystemContextIfSafe call in startDeferredPrefetches becomes
        // a cache hit. The microtask from await getIsGit() drains at the
        // getCommands Promise.all await below. Trust is implicit in -p mode
        // (same gate as prefetchSystemContextIfSafe).
        void getSystemContext();
        // Kick getUserContext now too — its first await (fs.readFile in
        // getMemoryFiles) yields naturally, so the CLAUDE.md directory walk
        // runs during the ~280ms overlap window before the context
        // Promise.all join in print.ts. The void getUserContext() in
        // startDeferredPrefetches becomes a memoize cache-hit.
        void getUserContext();
        // Kick ensureModelStringsInitialized now — for Bedrock this triggers
        // a 100-200ms profile fetch that was awaited serially at
        // print.ts:739. updateBedrockModelStrings is sequential()-wrapped so
        // the await joins the in-flight fetch. Non-Bedrock is a sync
        // early-return (zero-cost).
        void ensureModelStringsInitialized();
      }

      // Apply --name: cache-only so no orphan file is created before the
      // session ID is finalized by --continue/--resume. materializeSessionFile
      // persists it on the first user message; REPL's useTerminalTitle reads it
      // via getCurrentSessionTitle.
      const sessionNameArg = options.name?.trim();
      if (sessionNameArg) {
        cacheSessionTitle(sessionNameArg);
      }

      // Ant model aliases (capybara-fast etc.) resolve via the
      // tengu_ant_model_override GrowthBook flag. _CACHED_MAY_BE_STALE reads
      // disk synchronously; disk is populated by a fire-and-forget write. On a
      // cold cache, parseUserSpecifiedModel returns the unresolved alias, the
      // API 404s, and -p exits before the async write lands — crashloop on
      // fresh pods. Awaiting init here populates the in-memory payload map that
      // _CACHED_MAY_BE_STALE now checks first. Gated so the warm path stays
      // non-blocking:
      //  - explicit model via --model or ANTHROPIC_MODEL (both feed alias resolution)
      //  - no env override (which short-circuits _CACHED_MAY_BE_STALE before disk)
      //  - flag absent from disk (== null also catches pre-#22279 poisoned null)
      const explicitModel = options.model || process.env.ANTHROPIC_MODEL;
      if (
        process.env.USER_TYPE === 'ant' &&
        explicitModel &&
        explicitModel !== 'default' &&
        !hasGrowthBookEnvOverride('tengu_ant_model_override') &&
        getGlobalConfig().cachedGrowthBookFeatures?.['tengu_ant_model_override'] == null
      ) {
        await initializeGrowthBook();
      }

      // Special case the default model with the null keyword
      // NOTE: Model resolution happens after setup() to ensure trust is established before AWS auth
      const userSpecifiedModel = options.model === 'default' ? getDefaultMainLoopModel() : options.model;
      const userSpecifiedFallbackModel = fallbackModel === 'default' ? getDefaultMainLoopModel() : fallbackModel;

      // Reuse preSetupCwd unless setup() chdir'd (worktreeEnabled). Saves a
      // getCwd() syscall in the common path.
      const currentCwd = worktreeEnabled ? getCwd() : preSetupCwd;
      logForDebugging('[STARTUP] Loading commands and agents...');
      const commandsStart = Date.now();
      // Join the promises kicked before setup() (or start fresh if
      // worktreeEnabled gated the early kick). Both memoized by cwd.
      const [commands, agentDefinitionsResult] = await Promise.all([
        commandsPromise ?? getCommands(currentCwd),
        agentDefsPromise ?? getAgentDefinitionsWithOverrides(currentCwd),
      ]);
      logForDebugging(`[STARTUP] Commands and agents loaded in ${Date.now() - commandsStart}ms`);
      profileCheckpoint('action_commands_loaded');

      // Parse CLI agents if provided via --agents flag
      let cliAgents: typeof agentDefinitionsResult.activeAgents = [];
      if (agentsJson) {
        try {
          const parsedAgents = safeParseJSON(agentsJson);
          if (parsedAgents) {
            cliAgents = parseAgentsFromJson(parsedAgents, 'flagSettings');
          }
        } catch (error) {
          logError(error);
        }
      }

      // Merge CLI agents with existing ones
      const allAgents = [...agentDefinitionsResult.allAgents, ...cliAgents];
      const agentDefinitions = {
        ...agentDefinitionsResult,
        allAgents,
        activeAgents: getActiveAgentsFromList(allAgents),
      };

      // Look up main thread agent from CLI flag or settings
      const agentSetting = agentCli ?? getInitialSettings().agent;
      let mainThreadAgentDefinition: (typeof agentDefinitions.activeAgents)[number] | undefined;
      if (agentSetting) {
        mainThreadAgentDefinition = agentDefinitions.activeAgents.find(agent => agent.agentType === agentSetting);
        if (!mainThreadAgentDefinition) {
          logForDebugging(
            `Warning: agent "${agentSetting}" not found. ` +
              `Available agents: ${agentDefinitions.activeAgents.map(a => a.agentType).join(', ')}. ` +
              `Using default behavior.`,
          );
        }
      }

      // Store the main thread agent type in bootstrap state so hooks can access it
      setMainThreadAgentType(mainThreadAgentDefinition?.agentType);

      // Log agent flag usage — only log agent name for built-in agents to avoid leaking custom agent names
      if (mainThreadAgentDefinition) {
        logEvent('tengu_agent_flag', {
          agentType: isBuiltInAgent(mainThreadAgentDefinition)
            ? (mainThreadAgentDefinition.agentType as AnalyticsMetadata_I_VERIFIED_THIS_IS_NOT_CODE_OR_FILEPATHS)
            : ('custom' as AnalyticsMetadata_I_VERIFIED_THIS_IS_NOT_CODE_OR_FILEPATHS),
          ...(agentCli && {
            source: 'cli' as AnalyticsMetadata_I_VERIFIED_THIS_IS_NOT_CODE_OR_FILEPATHS,
          }),
        });
      }

      // Persist agent setting to session transcript for resume view display and restoration
      if (mainThreadAgentDefinition?.agentType) {
        saveAgentSetting(mainThreadAgentDefinition.agentType);
      }

      // Apply the agent's system prompt for non-interactive sessions
      // (interactive mode uses buildEffectiveSystemPrompt instead)
      if (
        isNonInteractiveSession &&
        mainThreadAgentDefinition &&
        !systemPrompt &&
        !isBuiltInAgent(mainThreadAgentDefinition)
      ) {
        const agentSystemPrompt = mainThreadAgentDefinition.getSystemPrompt();
        if (agentSystemPrompt) {
          systemPrompt = agentSystemPrompt;
        }
      }

      // initialPrompt goes first so its slash command (if any) is processed;
      // user-provided text becomes trailing context.
      // Only concatenate when inputPrompt is a string. When it's an
      // AsyncIterable (SDK stream-json mode), template interpolation would
      // call .toString() producing "[object Object]". The AsyncIterable case
      // is handled in print.ts via structuredIO.prependUserMessage().
      if (mainThreadAgentDefinition?.initialPrompt) {
        if (typeof inputPrompt === 'string') {
          inputPrompt = inputPrompt
            ? `${mainThreadAgentDefinition.initialPrompt}\n\n${inputPrompt}`
            : mainThreadAgentDefinition.initialPrompt;
        } else if (!inputPrompt) {
          inputPrompt = mainThreadAgentDefinition.initialPrompt;
        }
      }

      // Compute effective model early so hooks can run in parallel with MCP
      // If user didn't specify a model but agent has one, use the agent's model
      let effectiveModel = userSpecifiedModel;
      if (!effectiveModel && mainThreadAgentDefinition?.model && mainThreadAgentDefinition.model !== 'inherit') {
        effectiveModel = parseUserSpecifiedModel(mainThreadAgentDefinition.model);
      }

      setMainLoopModelOverride(effectiveModel);

      // Compute resolved model for hooks (use user-specified model at launch)
      setInitialMainLoopModel(getUserSpecifiedModelSetting() || null);
      const initialMainLoopModel = getInitialMainLoopModel();
      const resolvedInitialModel = parseUserSpecifiedModel(initialMainLoopModel ?? getDefaultMainLoopModel());

      let advisorModel: string | undefined;
      if (isAdvisorEnabled()) {
        const advisorOption = canUserConfigureAdvisor() ? (options as { advisor?: string }).advisor : undefined;
        if (advisorOption) {
          logForDebugging(`[AdvisorTool] --advisor ${advisorOption}`);
          if (!modelSupportsAdvisor(resolvedInitialModel)) {
            process.stderr.write(
              chalk.red(`Error: The model "${resolvedInitialModel}" does not support the advisor tool.\n`),
            );
            process.exit(1);
          }
          const normalizedAdvisorModel = normalizeModelStringForAPI(parseUserSpecifiedModel(advisorOption));
          if (!isValidAdvisorModel(normalizedAdvisorModel)) {
            process.stderr.write(chalk.red(`Error: The model "${advisorOption}" cannot be used as an advisor.\n`));
            process.exit(1);
          }
        }
        advisorModel = canUserConfigureAdvisor() ? (advisorOption ?? getInitialAdvisorSetting()) : advisorOption;
        if (advisorModel) {
          logForDebugging(`[AdvisorTool] Advisor model: ${advisorModel}`);
        }
      }

      // For tmux teammates with --agent-type, append the custom agent's prompt
      if (
        isAgentSwarmsEnabled() &&
        storedTeammateOpts?.agentId &&
        storedTeammateOpts?.agentName &&
        storedTeammateOpts?.teamName &&
        storedTeammateOpts?.agentType
      ) {
        // Look up the custom agent definition
        const customAgent = agentDefinitions.activeAgents.find(a => a.agentType === storedTeammateOpts.agentType);
        if (customAgent) {
          // Get the prompt - need to handle both built-in and custom agents
          let customPrompt: string | undefined;
          if (customAgent.source === 'built-in') {
            // Built-in agents have getSystemPrompt that takes toolUseContext
            // We can't access full toolUseContext here, so skip for now
            logForDebugging(
              `[teammate] Built-in agent ${storedTeammateOpts.agentType} - skipping custom prompt (not supported)`,
            );
          } else {
            // Custom agents have getSystemPrompt that takes no args
            customPrompt = customAgent.getSystemPrompt();
          }

          // Log agent memory loaded event for tmux teammates
          if (customAgent.memory) {
            logEvent('tengu_agent_memory_loaded', {
              ...(process.env.USER_TYPE === 'ant' && {
                agent_type: customAgent.agentType as AnalyticsMetadata_I_VERIFIED_THIS_IS_NOT_CODE_OR_FILEPATHS,
              }),
              scope: customAgent.memory as AnalyticsMetadata_I_VERIFIED_THIS_IS_NOT_CODE_OR_FILEPATHS,
              source: 'teammate' as AnalyticsMetadata_I_VERIFIED_THIS_IS_NOT_CODE_OR_FILEPATHS,
            });
          }

          if (customPrompt) {
            const customInstructions = `\n# Custom Agent Instructions\n${customPrompt}`;
            appendSystemPrompt = appendSystemPrompt
              ? `${appendSystemPrompt}\n\n${customInstructions}`
              : customInstructions;
          }
        } else {
          logForDebugging(`[teammate] Custom agent ${storedTeammateOpts.agentType} not found in available agents`);
        }
      }

      maybeActivateBrief(options);
      // defaultView: 'chat' is a persisted opt-in — check entitlement and set
      // userMsgOptIn so the tool + prompt section activate. Interactive-only:
      // defaultView is a display preference; SDK sessions have no display, and
      // the assistant installer writes defaultView:'chat' to settings.local.json
      // which would otherwise leak into --print sessions in the same directory.
      // Runs right after maybeActivateBrief() so all startup opt-in paths fire
      // BEFORE any isBriefEnabled() read below (proactive prompt's
      // briefVisibility). A persisted 'chat' after a GB kill-switch falls
      // through (entitlement fails).
      if (
        (feature('KAIROS') || feature('KAIROS_BRIEF')) &&
        !getIsNonInteractiveSession() &&
        !getUserMsgOptIn() &&
        getInitialSettings().defaultView === 'chat'
      ) {
        /* eslint-disable @typescript-eslint/no-require-imports */
        const { isBriefEntitled } =
          require('@claude-code-best/builtin-tools/tools/BriefTool/BriefTool.js') as typeof import('@claude-code-best/builtin-tools/tools/BriefTool/BriefTool.js');
        /* eslint-enable @typescript-eslint/no-require-imports */
        if (isBriefEntitled()) {
          setUserMsgOptIn(true);
        }
      }
      // Coordinator mode has its own system prompt and filters out Sleep, so
      // the generic proactive prompt would tell it to call a tool it can't
      // access and conflict with delegation instructions.
      if (
        (feature('PROACTIVE') || feature('KAIROS')) &&
        ((options as { proactive?: boolean }).proactive || isEnvTruthy(process.env.CLAUDE_CODE_PROACTIVE)) &&
        !coordinatorModeModule?.isCoordinatorMode()
      ) {
        /* eslint-disable @typescript-eslint/no-require-imports */
        const briefVisibility =
          feature('KAIROS') || feature('KAIROS_BRIEF')
            ? (
                require('@claude-code-best/builtin-tools/tools/BriefTool/BriefTool.js') as typeof import('@claude-code-best/builtin-tools/tools/BriefTool/BriefTool.js')
              ).isBriefEnabled()
              ? 'Call SendUserMessage at checkpoints to mark where things stand.'
              : 'The user will see any text you output.'
            : 'The user will see any text you output.';
        /* eslint-enable @typescript-eslint/no-require-imports */
        const proactivePrompt = `\n# Proactive Mode\n\nYou are in proactive mode. Take initiative — explore, act, and make progress without waiting for instructions.\n\nStart by briefly greeting the user.\n\nYou will receive periodic <tick> prompts. These are check-ins. Do whatever seems most useful, or call Sleep if there's nothing to do. ${briefVisibility}`;
        appendSystemPrompt = appendSystemPrompt ? `${appendSystemPrompt}\n\n${proactivePrompt}` : proactivePrompt;
      }

      if (feature('KAIROS') && kairosEnabled && assistantModule) {
        const assistantAddendum = assistantModule.getAssistantSystemPromptAddendum();
        appendSystemPrompt = appendSystemPrompt ? `${appendSystemPrompt}\n\n${assistantAddendum}` : assistantAddendum;
      }

      // Ink root is only needed for interactive sessions — patchConsole in the
      // Ink constructor would swallow console output in headless mode.
      let root!: Root;
      let getFpsMetrics!: () => FpsMetrics | undefined;
      let stats!: StatsStore;

      // Show setup screens after commands are loaded
      if (!isNonInteractiveSession) {
        const ctx = getRenderContext(false);
        getFpsMetrics = ctx.getFpsMetrics;
        stats = ctx.stats;
        // Install asciicast recorder before Ink mounts (ant-only, opt-in via CLAUDE_CODE_TERMINAL_RECORDING=1)
        if (process.env.USER_TYPE === 'ant') {
          installAsciicastRecorder();
        }

        const { createRoot } = await import('@anthropic/ink');
        root = await createRoot(ctx.renderOptions);

        // Log startup time now, before any blocking dialog renders. Logging
        // from REPL's first render (the old location) included however long
        // the user sat on trust/OAuth/onboarding/resume-picker — p99 was ~70s
        // dominated by dialog-wait time, not code-path startup.
        logEvent('tengu_timer', {
          event: 'startup' as AnalyticsMetadata_I_VERIFIED_THIS_IS_NOT_CODE_OR_FILEPATHS,
          durationMs: Math.round(process.uptime() * 1000),
        });

        logForDebugging('[STARTUP] Running showSetupScreens()...');
        const setupScreensStart = Date.now();
        const onboardingShown = await showSetupScreens(
          root,
          permissionMode,
          allowDangerouslySkipPermissions,
          commands,
          enableClaudeInChrome,
          devChannels,
        );
        logForDebugging(`[STARTUP] showSetupScreens() completed in ${Date.now() - setupScreensStart}ms`);

        // Now that trust is established and GrowthBook has auth headers,
        // resolve the --remote-control / --rc entitlement gate.
        if (feature('BRIDGE_MODE') && remoteControlOption !== undefined) {
          const { getBridgeDisabledReason } = await import('./bridge/bridgeEnabled.js');
          const disabledReason = await getBridgeDisabledReason();
          remoteControl = disabledReason === null;
          if (disabledReason) {
            process.stderr.write(chalk.yellow(`${disabledReason}\n--rc flag ignored.\n`));
          }
        }

        // Check for pending agent memory snapshot updates (only for --agent mode, ant-only)
        if (
          feature('AGENT_MEMORY_SNAPSHOT') &&
          mainThreadAgentDefinition &&
          isCustomAgent(mainThreadAgentDefinition) &&
          mainThreadAgentDefinition.memory &&
          mainThreadAgentDefinition.pendingSnapshotUpdate
        ) {
          const agentDef = mainThreadAgentDefinition;
          const choice = await launchSnapshotUpdateDialog(root, {
            agentType: agentDef.agentType,
            scope: agentDef.memory!,
            snapshotTimestamp: agentDef.pendingSnapshotUpdate!.snapshotTimestamp,
          });
          if (choice === 'merge') {
            const { buildMergePrompt } = await import('./components/agents/SnapshotUpdateDialog.js');
            const mergePrompt = buildMergePrompt(agentDef.agentType, agentDef.memory!);
            inputPrompt = inputPrompt ? `${mergePrompt}\n\n${inputPrompt}` : mergePrompt;
          }
          agentDef.pendingSnapshotUpdate = undefined;
        }

        // Skip executing /login if we just completed onboarding for it
        if (onboardingShown && prompt?.trim().toLowerCase() === '/login') {
          prompt = '';
        }

        if (onboardingShown) {
          // Refresh auth-dependent services now that the user has logged in during onboarding.
          // Keep in sync with the post-login logic in src/commands/login.tsx
          void refreshRemoteManagedSettings();
          void refreshPolicyLimits();
          // Clear user data cache BEFORE GrowthBook refresh so it picks up fresh credentials
          resetUserCache();
          // Refresh GrowthBook after login to get updated feature flags (e.g., for claude.ai MCPs)
          refreshGrowthBookAfterAuthChange();
          // Clear any stale trusted device token then enroll for Remote Control.
          // Both self-gate on tengu_sessions_elevated_auth_enforcement internally
          // — enrollTrustedDevice() via checkGate_CACHED_OR_BLOCKING (awaits
          // the GrowthBook reinit above), clearTrustedDeviceToken() via the
          // sync cached check (acceptable since clear is idempotent).
          void import('./bridge/trustedDevice.js').then(m => {
            m.clearTrustedDeviceToken();
            return m.enrollTrustedDevice();
          });
        }

        // Validate that the active token's org matches forceLoginOrgUUID (if set
        // in managed settings). Runs after onboarding so managed settings and
        // login state are fully loaded.
        const orgValidation = await validateForceLoginOrg();
        if (!orgValidation.valid) {
          await exitWithError(root, (orgValidation as { valid: false; message: string }).message);
        }
      }

      // If gracefulShutdown was initiated (e.g., user rejected trust dialog),
      // process.exitCode will be set. Skip all subsequent operations that could
      // trigger code execution before the process exits (e.g. we don't want apiKeyHelper
      // to run if trust was not established).
      if (process.exitCode !== undefined) {
        logForDebugging('Graceful shutdown initiated, skipping further initialization');
        return;
      }

      // Initialize LSP manager AFTER trust is established (or in non-interactive mode
      // where trust is implicit). This prevents plugin LSP servers from executing
      // code in untrusted directories before user consent.
      // Must be after inline plugins are set (if any) so --plugin-dir LSP servers are included.
      initializeLspServerManager();

      // Show settings validation errors after trust is established
      // MCP config errors don't block settings from loading, so exclude them
      if (!isNonInteractiveSession) {
        const { errors } = getSettingsWithErrors();
        const nonMcpErrors = errors.filter(e => !e.mcpErrorMetadata);
        if (nonMcpErrors.length > 0) {
          await launchInvalidSettingsDialog(root, {
            settingsErrors: nonMcpErrors,
            onExit: () => gracefulShutdownSync(1),
          });
        }
      }

      // Check quota status, fast mode, passes eligibility, and bootstrap data
      // after trust is established. These make API calls which could trigger
      // apiKeyHelper execution.
      // --bare / SIMPLE: skip — these are cache-warms for the REPL's
      // first-turn responsiveness (quota, passes, fastMode, bootstrap data). Fast
      // mode doesn't apply to the Agent SDK anyway (see getFastModeUnavailableReason).
      const bgRefreshThrottleMs = getFeatureValue_CACHED_MAY_BE_STALE('tengu_cicada_nap_ms', 0);
      const lastPrefetched = getGlobalConfig().startupPrefetchedAt ?? 0;
      const skipStartupPrefetches =
        isBareMode() || (bgRefreshThrottleMs > 0 && Date.now() - lastPrefetched < bgRefreshThrottleMs);

      if (!skipStartupPrefetches) {
        const lastPrefetchedInfo =
          lastPrefetched > 0 ? ` last ran ${Math.round((Date.now() - lastPrefetched) / 1000)}s ago` : '';
        logForDebugging(`Starting background startup prefetches${lastPrefetchedInfo}`);

        checkQuotaStatus().catch(error => logError(error));

        // Fetch bootstrap data from the server and update all cache values.
        void fetchBootstrapData();

        // TODO: Consolidate other prefetches into a single bootstrap request.
        void prefetchPassesEligibility();
        if (!getFeatureValue_CACHED_MAY_BE_STALE('tengu_miraculo_the_bard', false)) {
          void prefetchFastModeStatus();
        } else {
          // Kill switch skips the network call, not org-policy enforcement.
          // Resolve from cache so orgStatus doesn't stay 'pending' (which
          // getFastModeUnavailableReason treats as permissive).
          resolveFastModeStatusFromCache();
        }
        if (bgRefreshThrottleMs > 0) {
          saveGlobalConfig(current => ({
            ...current,
            startupPrefetchedAt: Date.now(),
          }));
        }
      } else {
        logForDebugging(
          `Skipping startup prefetches, last ran ${Math.round((Date.now() - lastPrefetched) / 1000)}s ago`,
        );
        // Resolve fast mode org status from cache (no network)
        resolveFastModeStatusFromCache();
      }

      if (!isNonInteractiveSession) {
        void refreshExampleCommands(); // Pre-fetch example commands (runs git log, no API call)
      }

      // Resolve MCP configs (started early, overlaps with setup/trust dialog work)
      const { servers: existingMcpConfigs } = await mcpConfigPromise;
      logForDebugging(
        `[STARTUP] MCP configs resolved in ${mcpConfigResolvedMs}ms (awaited at +${Date.now() - mcpConfigStart}ms)`,
      );
      // CLI flag (--mcp-config) should override file-based configs, matching settings precedence
      const allMcpConfigs = {
        ...existingMcpConfigs,
        ...dynamicMcpConfig,
      };

      // Separate SDK configs from regular MCP configs
      const sdkMcpConfigs: Record<string, McpSdkServerConfig> = {};
      const regularMcpConfigs: Record<string, ScopedMcpServerConfig> = {};

      for (const [name, config] of Object.entries(allMcpConfigs)) {
        const typedConfig = config as ScopedMcpServerConfig | McpSdkServerConfig;
        if (typedConfig.type === 'sdk') {
          sdkMcpConfigs[name] = typedConfig as McpSdkServerConfig;
        } else {
          regularMcpConfigs[name] = typedConfig as ScopedMcpServerConfig;
        }
      }

      profileCheckpoint('action_mcp_configs_loaded');

      // Prefetch MCP resources after trust dialog (this is where execution happens).
      // Interactive mode only: print mode defers connects until headlessStore exists
      // and pushes per-server (below), so SearchExtraTools's pending-client handling works
      // and one slow server doesn't block the batch.
      const localMcpPromise = isNonInteractiveSession
        ? Promise.resolve({ clients: [], tools: [], commands: [] })
        : prefetchAllMcpResources(regularMcpConfigs);
      const claudeaiMcpPromise = isNonInteractiveSession
        ? Promise.resolve({ clients: [], tools: [], commands: [] })
        : claudeaiConfigPromise.then(configs =>
            Object.keys(configs).length > 0
              ? prefetchAllMcpResources(configs)
              : { clients: [], tools: [], commands: [] },
          );
      // Merge with dedup by name: each prefetchAllMcpResources call independently
      // adds helper tools (ListMcpResourcesTool, ReadMcpResourceTool) via
      // local dedup flags, so merging two calls can yield duplicates. print.ts
      // already uniqBy's the final tool pool, but dedup here keeps appState clean.
      const mcpPromise = Promise.all([localMcpPromise, claudeaiMcpPromise]).then(([local, claudeai]) => ({
        clients: [...local.clients, ...claudeai.clients],
        tools: uniqBy([...local.tools, ...claudeai.tools], 'name'),
        commands: uniqBy([...local.commands, ...claudeai.commands], 'name'),
      }));

      // Start hooks early so they run in parallel with MCP connections.
      // Skip for initOnly/init/maintenance (handled separately), non-interactive
      // (handled via setupTrigger), and resume/continue (conversationRecovery.ts
      // fires 'resume' instead — without this guard, hooks fire TWICE on /resume
      // and the second systemMessage clobbers the first. gh-30825)
      const hooksPromise =
        initOnly || init || maintenance || isNonInteractiveSession || options.continue || options.resume
          ? null
          : processSessionStartHooks('startup', {
              agentType: mainThreadAgentDefinition?.agentType,
              model: resolvedInitialModel,
            });

      // MCP never blocks REPL render OR turn 1 TTFT. useManageMCPConnections
      // populates appState.mcp async as servers connect (connectToServer is
      // memoized — the prefetch calls above and the hook converge on the same
      // connections). getToolUseContext reads store.getState() fresh via
      // computeTools(), so turn 1 sees whatever's connected by query time.
      // Slow servers populate for turn 2+. Matches interactive-no-prompt
      // behavior. Print mode: per-server push into headlessStore (below).
      const hookMessages: Awaited<NonNullable<typeof hooksPromise>> = [];
      // Suppress transient unhandledRejection — the prefetch warms the
      // memoized connectToServer cache but nobody awaits it in interactive.
      mcpPromise.catch(() => {});

      const mcpClients: Awaited<typeof mcpPromise>['clients'] = [];
      const mcpTools: Awaited<typeof mcpPromise>['tools'] = [];
      const mcpCommands: Awaited<typeof mcpPromise>['commands'] = [];

      let thinkingEnabled = shouldEnableThinkingByDefault();
      let thinkingConfig: ThinkingConfig = thinkingEnabled !== false ? { type: 'adaptive' } : { type: 'disabled' };

      if (options.thinking === 'adaptive' || options.thinking === 'enabled') {
        thinkingEnabled = true;
        thinkingConfig = { type: 'adaptive' };
      } else if (options.thinking === 'disabled') {
        thinkingEnabled = false;
        thinkingConfig = { type: 'disabled' };
      } else {
        const maxThinkingTokens = process.env.MAX_THINKING_TOKENS
          ? parseInt(process.env.MAX_THINKING_TOKENS, 10)
          : options.maxThinkingTokens;
        if (maxThinkingTokens !== undefined) {
          if (maxThinkingTokens > 0) {
            thinkingEnabled = true;
            thinkingConfig = {
              type: 'enabled',
              budgetTokens: maxThinkingTokens,
            };
          } else if (maxThinkingTokens === 0) {
            thinkingEnabled = false;
            thinkingConfig = { type: 'disabled' };
          }
        }
      }

      logForDiagnosticsNoPII('info', 'started', {
        version: MACRO.VERSION,
        is_native_binary: isInBundledMode(),
      });

      registerCleanup(async () => {
        logForDiagnosticsNoPII('info', 'exited');
      });

      void logTenguInit({
        hasInitialPrompt: Boolean(prompt),
        hasStdin: Boolean(inputPrompt),
        verbose,
        debug,
        debugToStderr,
        print: print ?? false,
        outputFormat: outputFormat ?? 'text',
        inputFormat: inputFormat ?? 'text',
        numAllowedTools: allowedTools.length,
        numDisallowedTools: disallowedTools.length,
        mcpClientCount: Object.keys(allMcpConfigs).length,
        worktreeEnabled,
        skipWebFetchPreflight: getInitialSettings().skipWebFetchPreflight,
        githubActionInputs: process.env.GITHUB_ACTION_INPUTS,
        dangerouslySkipPermissionsPassed: dangerouslySkipPermissions ?? false,
        permissionMode,
        modeIsBypass: permissionMode === 'bypassPermissions',
        allowDangerouslySkipPermissionsPassed: allowDangerouslySkipPermissions,
        systemPromptFlag: systemPrompt ? (options.systemPromptFile ? 'file' : 'flag') : undefined,
        appendSystemPromptFlag: appendSystemPrompt ? (options.appendSystemPromptFile ? 'file' : 'flag') : undefined,
        thinkingConfig,
        assistantActivationPath:
          feature('KAIROS') && kairosEnabled ? assistantModule?.getAssistantActivationPath() : undefined,
      });

      // Log context metrics once at initialization
      void logContextMetrics(regularMcpConfigs, toolPermissionContext);

      void logPermissionContextForAnts(null, 'initialization');

      logManagedSettings();

      // Register PID file for concurrent-session detection (~/.claude/sessions/)
      // and fire multi-clauding telemetry. Lives here (not init.ts) so only the
      // REPL path registers — not subcommands like `claude doctor`. Chained:
      // count must run after register's write completes or it misses our own file.
      void registerSession().then(registered => {
        if (!registered) return;
        if (sessionNameArg) {
          void updateSessionName(sessionNameArg);
        }
        void countConcurrentSessions().then(count => {
          if (count >= 2) {
            logEvent('tengu_concurrent_sessions', {
              num_sessions: count,
            });
          }
        });
      });

      // Initialize versioned plugins system (triggers V1→V2 migration if
      // needed). Then run orphan GC, THEN warm the Grep/Glob exclusion cache.
      // Sequencing matters: the warmup scans disk for .orphaned_at markers,
      // so it must see the GC's Pass 1 (remove markers from reinstalled
      // versions) and Pass 2 (stamp unmarked orphans) already applied. The
      // warm also lands before autoupdate (fires on first submit in REPL)
      // can orphan this session's active version underneath us.
      // --bare / SIMPLE: skip plugin version sync + orphan cleanup. These
      // are install/upgrade bookkeeping that scripted calls don't need —
      // the next interactive session will reconcile. The await here was
      // blocking -p on a marketplace round-trip.
      if (isBareMode()) {
        // skip — no-op
      } else if (isNonInteractiveSession) {
        // In headless mode, await to ensure plugin sync completes before CLI exits
        await initializeVersionedPlugins();
        profileCheckpoint('action_after_plugins_init');
        void cleanupOrphanedPluginVersionsInBackground().then(() => getGlobExclusionsForPluginCache());
      } else {
        // In interactive mode, fire-and-forget — this is purely bookkeeping
        // that doesn't affect runtime behavior of the current session
        void initializeVersionedPlugins().then(async () => {
          profileCheckpoint('action_after_plugins_init');
          await cleanupOrphanedPluginVersionsInBackground();
          void getGlobExclusionsForPluginCache();
        });
      }

      const setupTrigger = initOnly || init ? 'init' : maintenance ? 'maintenance' : null;
      if (initOnly) {
        applyConfigEnvironmentVariables();
        await processSetupHooks('init', { forceSyncExecution: true });
        await processSessionStartHooks('startup', {
          forceSyncExecution: true,
        });
        gracefulShutdownSync(0);
        return;
      }

      // --print mode
      if (isNonInteractiveSession) {
        if (outputFormat === 'stream-json' || outputFormat === 'json') {
          setHasFormattedOutput(true);
        }

        // Apply full environment variables in print mode since trust dialog is bypassed
        // This includes potentially dangerous environment variables from untrusted sources
        // but print mode is considered trusted (as documented in help text)
        applyConfigEnvironmentVariables();

        // Initialize telemetry after env vars are applied so OTEL endpoint env vars and
        // otelHeadersHelper (which requires trust to execute) are available.
        initializeTelemetryAfterTrust();

        // Kick SessionStart hooks now so the subprocess spawn overlaps with
        // MCP connect + plugin init + print.ts import below. loadInitialMessages
        // joins this at print.ts:4397. Guarded same as loadInitialMessages —
        // continue/resume/teleport paths don't fire startup hooks (or fire them
        // conditionally inside the resume branch, where this promise is
        // undefined and the ?? fallback runs). Also skip when setupTrigger is
        // set — those paths run setup hooks first (print.ts:544), and session
        // start hooks must wait until setup completes.
        const sessionStartHooksPromise =
          options.continue || options.resume || teleport || setupTrigger
            ? undefined
            : processSessionStartHooks('startup');
        // Suppress transient unhandledRejection if this rejects before
        // loadInitialMessages awaits it. Downstream await still observes the
        // rejection — this just prevents the spurious global handler fire.
        sessionStartHooksPromise?.catch(() => {});

        profileCheckpoint('before_validateForceLoginOrg');
        // Validate org restriction for non-interactive sessions
        const orgValidation = await validateForceLoginOrg();
        if (!orgValidation.valid) {
          process.stderr.write((orgValidation as { valid: false; message: string }).message + '\n');
          process.exit(1);
        }

        // Headless mode supports all prompt commands and some local commands
        // If disableSlashCommands is true, return empty array
        const commandsHeadless = disableSlashCommands
          ? []
          : commands.filter(
              command =>
                (command.type === 'prompt' && !command.disableNonInteractive) ||
                (command.type === 'local' && command.supportsNonInteractive),
            );

        const defaultState = getDefaultAppState();
        const headlessInitialState: AppState = {
          ...defaultState,
          mcp: {
            ...defaultState.mcp,
            clients: mcpClients,
            commands: mcpCommands,
            tools: mcpTools,
          },
          toolPermissionContext,
          effortValue: parseEffortValue(options.effort) ?? getInitialEffortSetting(),
          ...(isFastModeEnabled() && {
            fastMode: getInitialFastModeSetting(effectiveModel ?? null),
          }),
          ...(isAdvisorEnabled() && advisorModel && { advisorModel }),
          // kairosEnabled gates the async fire-and-forget path in
          // executeForkedSlashCommand (processSlashCommand.tsx:132) and
          // AgentTool's shouldRunAsync. The REPL initialState sets this at
          // ~3459; headless was defaulting to false, so the daemon child's
          // scheduled tasks and Agent-tool calls ran synchronously — N
          // overdue cron tasks on spawn = N serial subagent turns blocking
          // user input. Computed at :1620, well before this branch.
          ...(feature('KAIROS') ? { kairosEnabled } : {}),
        };

        // Init app state
        const headlessStore = createStore(headlessInitialState, onChangeAppState);

        // Async check of auto mode gate — corrects state and disables auto if needed.
        if (feature('TRANSCRIPT_CLASSIFIER')) {
          void verifyAutoModeGateAccess(toolPermissionContext, headlessStore.getState().fastMode).then(
            ({ updateContext }) => {
              headlessStore.setState(prev => {
                const nextCtx = updateContext(prev.toolPermissionContext);
                if (nextCtx === prev.toolPermissionContext) return prev;
                return { ...prev, toolPermissionContext: nextCtx };
              });
            },
          );
        }

        // Set global state for session persistence
        if (options.sessionPersistence === false) {
          setSessionPersistenceDisabled(true);
        }

        // Store SDK betas in global state for context window calculation
        // Only store allowed betas (filters by allowlist and subscriber status)
        setSdkBetas(filterAllowedSdkBetas(betas));

        // Print-mode MCP: per-server incremental push into headlessStore.
        // Mirrors useManageMCPConnections — push pending first (so SearchExtraTools's
        // pending-check at SearchExtraToolsTool.ts:334 sees them), then replace with
        // connected/failed as each server settles.
        const connectMcpBatch = (configs: Record<string, ScopedMcpServerConfig>, label: string): Promise<void> => {
          if (Object.keys(configs).length === 0) return Promise.resolve();
          headlessStore.setState(prev => ({
            ...prev,
            mcp: {
              ...prev.mcp,
              clients: [
                ...prev.mcp.clients,
                ...Object.entries(configs).map(([name, config]) => ({
                  name,
                  type: 'pending' as const,
                  config,
                })),
              ],
            },
          }));
          return getMcpToolsCommandsAndResources(({ client, tools, commands }) => {
            headlessStore.setState(prev => ({
              ...prev,
              mcp: {
                ...prev.mcp,
                clients: prev.mcp.clients.some(c => c.name === client.name)
                  ? prev.mcp.clients.map(c => (c.name === client.name ? client : c))
                  : [...prev.mcp.clients, client],
                tools: uniqBy([...prev.mcp.tools, ...tools], 'name'),
                commands: uniqBy([...prev.mcp.commands, ...commands], 'name'),
              },
            }));
          }, configs).catch(err => logForDebugging(`[MCP] ${label} connect error: ${err}`));
        };
        // Await all MCP configs — print mode is often single-turn, so
        // "late-connecting servers visible next turn" doesn't help. SDK init
        // message and turn-1 tool list both need configured MCP tools present.
        // Zero-server case is free via the early return in connectMcpBatch.
        // Connectors parallelize inside getMcpToolsCommandsAndResources
        // (processBatched with Promise.all). claude.ai is awaited too — its
        // fetch was kicked off early (line ~2558) so only residual time blocks
        // here. --bare skips claude.ai entirely for perf-sensitive scripts.
        profileCheckpoint('before_connectMcp');
        await connectMcpBatch(regularMcpConfigs, 'regular');
        profileCheckpoint('after_connectMcp');
        // Dedup: suppress plugin MCP servers that duplicate a claude.ai
        // connector (connector wins), then connect claude.ai servers.
        // Bounded wait — #23725 made this blocking so single-turn -p sees
        // connectors, but with 40+ slow connectors tengu_startup_perf p99
        // climbed to 76s. If fetch+connect doesn't finish in time, proceed;
        // the promise keeps running and updates headlessStore in the
        // background so turn 2+ still sees connectors.
        const CLAUDE_AI_MCP_TIMEOUT_MS = 5_000;
        const claudeaiConnect = claudeaiConfigPromise.then(claudeaiConfigs => {
          if (Object.keys(claudeaiConfigs).length > 0) {
            const claudeaiSigs = new Set<string>();
            for (const config of Object.values(claudeaiConfigs)) {
              const sig = getMcpServerSignature(config);
              if (sig) claudeaiSigs.add(sig);
            }
            const suppressed = new Set<string>();
            for (const [name, config] of Object.entries(regularMcpConfigs)) {
              if (!name.startsWith('plugin:')) continue;
              const sig = getMcpServerSignature(config);
              if (sig && claudeaiSigs.has(sig)) suppressed.add(name);
            }
            if (suppressed.size > 0) {
              logForDebugging(
                `[MCP] Lazy dedup: suppressing ${suppressed.size} plugin server(s) that duplicate claude.ai connectors: ${[...suppressed].join(', ')}`,
              );
              // Disconnect before filtering from state. Only connected
              // servers need cleanup — clearServerCache on a never-connected
              // server triggers a real connect just to kill it (memoize
              // cache-miss path, see useManageMCPConnections.ts:870).
              for (const c of headlessStore.getState().mcp.clients) {
                if (!suppressed.has(c.name) || c.type !== 'connected') continue;
                c.client.onclose = undefined;
                void clearServerCache(c.name, c.config).catch(() => {});
              }
              headlessStore.setState(prev => {
                let { clients, tools, commands, resources } = prev.mcp;
                clients = clients.filter(c => !suppressed.has(c.name));
                tools = tools.filter(t => !t.mcpInfo || !suppressed.has(t.mcpInfo.serverName));
                for (const name of suppressed) {
                  commands = excludeCommandsByServer(commands, name);
                  resources = excludeResourcesByServer(resources, name);
                }
                return {
                  ...prev,
                  mcp: {
                    ...prev.mcp,
                    clients,
                    tools,
                    commands,
                    resources,
                  },
                };
              });
            }
          }
          // Suppress claude.ai connectors that duplicate an enabled
          // manual server (URL-signature match). Plugin dedup above only
          // handles `plugin:*` keys; this catches manual `.mcp.json` entries.
          // plugin:* must be excluded here — step 1 already suppressed
          // those (claude.ai wins); leaving them in suppresses the
          // connector too, and neither survives (gh-39974).
          const nonPluginConfigs = pickBy(regularMcpConfigs, (_, n) => !n.startsWith('plugin:'));
          const { servers: dedupedClaudeAi } = dedupClaudeAiMcpServers(claudeaiConfigs, nonPluginConfigs);
          return connectMcpBatch(dedupedClaudeAi, 'claudeai');
        });
        let claudeaiTimer: ReturnType<typeof setTimeout> | undefined;
        const claudeaiTimedOut = await Promise.race([
          claudeaiConnect.then(() => false),
          new Promise<boolean>(resolve => {
            claudeaiTimer = setTimeout(r => r(true), CLAUDE_AI_MCP_TIMEOUT_MS, resolve);
          }),
        ]);
        if (claudeaiTimer) clearTimeout(claudeaiTimer);
        if (claudeaiTimedOut) {
          logForDebugging(
            `[MCP] claude.ai connectors not ready after ${CLAUDE_AI_MCP_TIMEOUT_MS}ms — proceeding; background connection continues`,
          );
        }
        profileCheckpoint('after_connectMcp_claudeai');

        // In headless mode, start deferred prefetches immediately (no user typing delay)
        // --bare / SIMPLE: startDeferredPrefetches early-returns internally.
        // backgroundHousekeeping (initExtractMemories, pruneShellSnapshots,
        // cleanupOldMessageFiles) and sdkHeapDumpMonitor are all bookkeeping
        // that scripted calls don't need — the next interactive session reconciles.
        if (!isBareMode()) {
          startDeferredPrefetches();
          void import('./utils/backgroundHousekeeping.js').then(m => m.startBackgroundHousekeeping());
          if (process.env.USER_TYPE === 'ant') {
            void import('./utils/sdkHeapDumpMonitor.js').then(m => m.startSdkMemoryMonitor());
          }
        }

        logSessionTelemetry();
        profileCheckpoint('before_print_import');
        const { runHeadless } = await import('src/cli/print.js');
        profileCheckpoint('after_print_import');
        void runHeadless(
          inputPrompt,
          () => headlessStore.getState(),
          headlessStore.setState,
          commandsHeadless,
          tools,
          sdkMcpConfigs,
          agentDefinitions.activeAgents,
          {
            continue: options.continue,
            resume: options.resume,
            verbose: verbose,
            outputFormat: outputFormat,
            jsonSchema,
            permissionPromptToolName: options.permissionPromptTool,
            allowedTools,
            thinkingConfig,
            maxTurns: options.maxTurns,
            maxBudgetUsd: options.maxBudgetUsd,
            taskBudget: options.taskBudget ? { total: options.taskBudget } : undefined,
            systemPrompt,
            appendSystemPrompt,
            userSpecifiedModel: effectiveModel,
            fallbackModel: userSpecifiedFallbackModel,
            teleport,
            sdkUrl,
            replayUserMessages: effectiveReplayUserMessages,
            includePartialMessages: effectiveIncludePartialMessages,
            forkSession: options.forkSession || false,
            resumeSessionAt: options.resumeSessionAt || undefined,
            rewindFiles: options.rewindFiles,
            enableAuthStatus: options.enableAuthStatus,
            agent: agentCli,
            workload: options.workload,
            setupTrigger: setupTrigger ?? undefined,
            sessionStartHooksPromise,
          },
        );
        return;
      }

      // Log model config at startup
      logEvent('tengu_startup_manual_model_config', {
        cli_flag: options.model as AnalyticsMetadata_I_VERIFIED_THIS_IS_NOT_CODE_OR_FILEPATHS,
        env_var: process.env.ANTHROPIC_MODEL as AnalyticsMetadata_I_VERIFIED_THIS_IS_NOT_CODE_OR_FILEPATHS,
        settings_file: (getInitialSettings() || {}).model as AnalyticsMetadata_I_VERIFIED_THIS_IS_NOT_CODE_OR_FILEPATHS,
        subscriptionType: getSubscriptionType() as AnalyticsMetadata_I_VERIFIED_THIS_IS_NOT_CODE_OR_FILEPATHS,
        agent: agentSetting as AnalyticsMetadata_I_VERIFIED_THIS_IS_NOT_CODE_OR_FILEPATHS,
      });

      // Get deprecation warning for the initial model (resolvedInitialModel computed earlier for hooks parallelization)
      const deprecationWarning = getModelDeprecationWarning(resolvedInitialModel);

      // Build initial notification queue
      const initialNotifications: Array<{
        key: string;
        text: string;
        color?: 'warning';
        priority: 'high';
      }> = [];
      if (permissionModeNotification) {
        initialNotifications.push({
          key: 'permission-mode-notification',
          text: permissionModeNotification,
          priority: 'high',
        });
      }
      if (deprecationWarning) {
        initialNotifications.push({
          key: 'model-deprecation-warning',
          text: deprecationWarning,
          color: 'warning',
          priority: 'high',
        });
      }
      if (overlyBroadBashPermissions.length > 0) {
        const displayList = uniq(overlyBroadBashPermissions.map(p => p.ruleDisplay));
        const displays = displayList.join(', ');
        const sources = uniq(overlyBroadBashPermissions.map(p => p.sourceDisplay)).join(', ');
        const n = displayList.length;
        initialNotifications.push({
          key: 'overly-broad-bash-notification',
          text: `${displays} allow ${plural(n, 'rule')} from ${sources} ${plural(n, 'was', 'were')} ignored \u2014 not available for Ants, please use auto-mode instead`,
          color: 'warning',
          priority: 'high',
        });
      }

      const teammateUtils = getTeammateUtils();
      const effectiveToolPermissionContext = {
        ...toolPermissionContext,
        mode:
          isAgentSwarmsEnabled() && teammateUtils?.isPlanModeRequired?.()
            ? ('plan' as const)
            : toolPermissionContext.mode,
      };
      // All startup opt-in paths (--tools, --brief, defaultView) have fired
      // above; initialIsBriefOnly just reads the resulting state.
      const initialIsBriefOnly = feature('KAIROS') || feature('KAIROS_BRIEF') ? getUserMsgOptIn() : false;
      const fullRemoteControl = remoteControl || getRemoteControlAtStartup() || kairosEnabled;
      let ccrMirrorEnabled = false;
      if (feature('CCR_MIRROR') && !fullRemoteControl) {
        /* eslint-disable @typescript-eslint/no-require-imports */
        const { isCcrMirrorEnabled } =
          require('./bridge/bridgeEnabled.js') as typeof import('./bridge/bridgeEnabled.js');
        /* eslint-enable @typescript-eslint/no-require-imports */
        ccrMirrorEnabled = isCcrMirrorEnabled();
      }

      const initialState: AppState = {
        settings: getInitialSettings(),
        tasks: {},
        agentNameRegistry: new Map(),
        verbose: verbose ?? getGlobalConfig().verbose ?? false,
        mainLoopModel: initialMainLoopModel,
        mainLoopModelForSession: null,
        isBriefOnly: initialIsBriefOnly,
        expandedView: getGlobalConfig().showSpinnerTree
          ? 'teammates'
          : getGlobalConfig().showExpandedTodos
            ? 'tasks'
            : 'none',
        showTeammateMessagePreview: isAgentSwarmsEnabled() ? false : undefined,
        selectedIPAgentIndex: -1,
        selectedBgAgentIndex: -1,
        coordinatorTaskIndex: -1,
        viewSelectionMode: 'none',
        footerSelection: null,
        toolPermissionContext: effectiveToolPermissionContext,
        agent: mainThreadAgentDefinition?.agentType,
        agentDefinitions,
        mcp: {
          clients: [],
          tools: [],
          commands: [],
          resources: {},
          pluginReconnectKey: 0,
        },
        plugins: {
          enabled: [],
          disabled: [],
          commands: [],
          errors: [],
          installationStatus: {
            marketplaces: [],
            plugins: [],
          },
          needsRefresh: false,
        },
        statusLineText: undefined,
        kairosEnabled,
        remoteSessionUrl: undefined,
        remoteConnectionStatus: 'connecting',
        remoteBackgroundTaskCount: 0,
        replBridgeEnabled: fullRemoteControl || ccrMirrorEnabled,
        replBridgeExplicit: remoteControl,
        replBridgeOutboundOnly: ccrMirrorEnabled,
        replBridgeConnected: false,
        replBridgeSessionActive: false,
        replBridgeReconnecting: false,
        replBridgeConnectUrl: undefined,
        replBridgeSessionUrl: undefined,
        replBridgeEnvironmentId: undefined,
        replBridgeSessionId: undefined,
        replBridgeError: undefined,
        replBridgeInitialName: remoteControlName,
        showRemoteCallout: false,
        notifications: {
          current: null,
          queue: initialNotifications,
        },
        elicitation: {
          queue: [],
        },
        todos: {},
        remoteAgentTaskSuggestions: [],
        fileHistory: {
          snapshots: [],
          trackedFiles: new Set(),
          snapshotSequence: 0,
        },
        attribution: createEmptyAttributionState(),
        thinkingEnabled,
        promptSuggestionEnabled: shouldEnablePromptSuggestion(),
        sessionHooks: new Map(),
        inbox: {
          messages: [],
        },
        promptSuggestion: {
          text: null,
          promptId: null,
          shownAt: 0,
          acceptedAt: 0,
          generationRequestId: null,
        },
        speculation: IDLE_SPECULATION_STATE,
        speculationSessionTimeSavedMs: 0,
        skillImprovement: {
          suggestion: null,
        },
        workerSandboxPermissions: {
          queue: [],
          selectedIndex: 0,
        },
        pendingWorkerRequest: null,
        pendingSandboxRequest: null,
        authVersion: 0,
        initialMessage: inputPrompt
          ? {
              message: createUserMessage({
                content: String(inputPrompt),
              }),
            }
          : null,
        effortValue: parseEffortValue(options.effort) ?? getInitialEffortSetting(),
        activeOverlays: new Set<string>(),
        fastMode: getInitialFastModeSetting(resolvedInitialModel),
        ...(isAdvisorEnabled() && advisorModel && { advisorModel }),
        // Compute teamContext synchronously to avoid useEffect setState during render.
        // KAIROS: assistantTeamContext takes precedence — set earlier in the
        // KAIROS block so Agent(name: "foo") can spawn in-process teammates
        // without TeamCreate. computeInitialTeamContext() is for tmux-spawned
        // teammates reading their own identity, not the assistant-mode leader.
        teamContext: (feature('KAIROS')
          ? (assistantTeamContext ?? computeInitialTeamContext())
          : computeInitialTeamContext()) as AppState['teamContext'],
      };

      // Add CLI initial prompt to history
      if (inputPrompt) {
        addToHistory(String(inputPrompt));
      }

      const initialTools = mcpTools;

      // Increment numStartups synchronously — first-render readers like
      // shouldShowEffortCallout (via useState initializer) need the updated
      // value before setImmediate fires. Defer only telemetry.
      saveGlobalConfig(current => ({
        ...current,
        numStartups: (current.numStartups ?? 0) + 1,
      }));
      setImmediate(() => {
        void logStartupTelemetry();
        logSessionTelemetry();
      });

      // Set up per-turn session environment data uploader (ant-only build).
      // Default-enabled for all ant users when working in an Anthropic-owned
      // repo. Captures git/filesystem state (NOT transcripts) at each turn so
      // environments can be recreated at any user message index. Gating:
      //   - Build-time: this import is stubbed in external builds.
      //   - Runtime: uploader checks github.com/anthropics/* remote + gcloud auth.
      //   - Safety: CLAUDE_CODE_DISABLE_SESSION_DATA_UPLOAD=1 bypasses (tests set this).
      // Import is dynamic + async to avoid adding startup latency.
      const sessionUploaderPromise = process.env.USER_TYPE === 'ant' ? import('./utils/sessionDataUploader.js') : null;

      // Defer session uploader resolution to the onTurnComplete callback to avoid
      // adding a new top-level await in main.tsx (performance-critical path).
      // The per-turn auth logic in sessionDataUploader.ts handles unauthenticated
      // state gracefully (re-checks each turn, so auth recovery mid-session works).
      const uploaderReady = sessionUploaderPromise
        ? sessionUploaderPromise.then(mod => mod.createSessionTurnUploader()).catch(() => null)
        : null;

      const sessionConfig = {
        debug: debug || debugToStderr,
        commands: [...commands, ...mcpCommands],
        initialTools,
        mcpClients,
        autoConnectIdeFlag: ide,
        mainThreadAgentDefinition,
        disableSlashCommands,
        dynamicMcpConfig,
        strictMcpConfig,
        systemPrompt,
        appendSystemPrompt,
        taskListId,
        thinkingConfig,
        ...(uploaderReady && {
          onTurnComplete: (messages: MessageType[]) => {
            void uploaderReady.then(uploader => (uploader as ((msgs: MessageType[]) => void) | null)?.(messages));
          },
        }),
      };

      // Shared context for processResumedConversation calls
      const resumeContext = {
        modeApi: coordinatorModeModule,
        mainThreadAgentDefinition,
        agentDefinitions,
        currentCwd,
        cliAgents,
        initialState,
      };

      if (options.continue) {
        // Continue the most recent conversation directly
        let resumeSucceeded = false;
        try {
          const resumeStart = performance.now();

          // Clear stale caches before resuming to ensure fresh file/skill discovery
          const { clearSessionCaches } = await import('./commands/clear/caches.js');
          clearSessionCaches();

          const result = await loadConversationForResume(undefined /* sessionId */, undefined /* sourceFile */);
          if (!result) {
            logEvent('tengu_continue', {
              success: false,
            });
            return await exitWithError(root, 'No conversation found to continue');
          }

          const loaded = await processResumedConversation(
            result,
            {
              forkSession: !!options.forkSession,
              includeAttribution: true,
              transcriptPath: result.fullPath,
            },
            resumeContext,
          );

          if (loaded.restoredAgentDef) {
            mainThreadAgentDefinition = loaded.restoredAgentDef;
          }

          maybeActivateProactive(options);
          maybeActivateBrief(options);

          logEvent('tengu_continue', {
            success: true,
            resume_duration_ms: Math.round(performance.now() - resumeStart),
          });
          resumeSucceeded = true;

          await launchRepl(
            root,
            {
              getFpsMetrics,
              stats,
              initialState: loaded.initialState,
            },
            {
              ...sessionConfig,
              mainThreadAgentDefinition: loaded.restoredAgentDef ?? mainThreadAgentDefinition,
              initialMessages: loaded.messages,
              initialFileHistorySnapshots: loaded.fileHistorySnapshots,
              initialContentReplacements: loaded.contentReplacements,
              initialAgentName: loaded.agentName,
              initialAgentColor: loaded.agentColor,
            },
            renderAndRun,
          );
        } catch (error) {
          if (!resumeSucceeded) {
            logEvent('tengu_continue', {
              success: false,
            });
          }
          logError(error);
          process.exit(1);
        }
      } else if (feature('DIRECT_CONNECT') && _pendingConnect?.url) {
        // `claude connect <url>` — full interactive TUI connected to a remote server
        let directConnectConfig;
        try {
          const session = await createDirectConnectSession({
            serverUrl: _pendingConnect.url,
            authToken: _pendingConnect.authToken,
            cwd: getOriginalCwd(),
            dangerouslySkipPermissions: _pendingConnect.dangerouslySkipPermissions,
          });
          if (session.workDir) {
            setOriginalCwd(session.workDir);
            setCwdState(session.workDir);
          }
          setDirectConnectServerUrl(_pendingConnect.url);
          directConnectConfig = session.config;
        } catch (err) {
          return await exitWithError(root, err instanceof DirectConnectError ? err.message : String(err), () =>
            gracefulShutdown(1),
          );
        }

        const connectInfoMessage = createSystemMessage(
          `Connected to server at ${_pendingConnect.url}\nSession: ${directConnectConfig.sessionId}`,
          'info',
        );

        await launchRepl(
          root,
          { getFpsMetrics, stats, initialState },
          {
            debug: debug || debugToStderr,
            commands,
            initialTools: [],
            initialMessages: [connectInfoMessage],
            mcpClients: [],
            autoConnectIdeFlag: ide,
            mainThreadAgentDefinition,
            disableSlashCommands,
            directConnectConfig,
            thinkingConfig,
          },
          renderAndRun,
        );
        return;
      } else if (feature('SSH_REMOTE') && _pendingSSH?.host) {
        // `claude ssh <host> [dir]` — probe remote, deploy binary if needed,
        // spawn ssh with unix-socket -R forward to a local auth proxy, hand
        // the REPL an SSHSession. Tools run remotely, UI renders locally.
        // `--local` skips probe/deploy/ssh and spawns the current binary
        // directly with the same env — e2e test of the proxy/auth plumbing.
        const { createSSHSession, createLocalSSHSession, SSHSessionError } = await import('./ssh/createSSHSession.js');
        let sshSession: import('./ssh/createSSHSession.js').SSHSession | undefined;
        try {
          if (_pendingSSH.local) {
            process.stderr.write('Starting local ssh-proxy test session...\n');
            sshSession = await createLocalSSHSession({
              cwd: _pendingSSH.cwd,
              permissionMode: _pendingSSH.permissionMode,
              dangerouslySkipPermissions: _pendingSSH.dangerouslySkipPermissions,
            });
          } else {
            process.stderr.write(`Connecting to ${_pendingSSH.host}…\n`);
            // In-place progress: \r + EL0 (erase to end of line). Final \n on
            // success so the next message lands on a fresh line. No-op when
            // stderr isn't a TTY (piped/redirected) — \r would just emit noise.
            const isTTY = process.stderr.isTTY;
            let hadProgress = false;
            sshSession = await createSSHSession(
              {
                host: _pendingSSH.host,
                cwd: _pendingSSH.cwd,
                localVersion: MACRO.VERSION,
                permissionMode: _pendingSSH.permissionMode,
                dangerouslySkipPermissions: _pendingSSH.dangerouslySkipPermissions,
                extraCliArgs: _pendingSSH.extraCliArgs,
                remoteBin: _pendingSSH.remoteBin,
              },
              isTTY
                ? {
                    onProgress: (msg: string) => {
                      hadProgress = true;
                      process.stderr.write(`\r  ${msg}\x1b[K`);
                    },
                  }
                : {},
            );
            if (hadProgress) process.stderr.write('\n');
          }
          setOriginalCwd(sshSession.remoteCwd);
          setCwdState(sshSession.remoteCwd);
          setDirectConnectServerUrl(_pendingSSH.local ? 'local' : _pendingSSH.host);
        } catch (err) {
          return await exitWithError(root, err instanceof SSHSessionError ? err.message : String(err), () =>
            gracefulShutdown(1),
          );
        }

        const sshInfoMessage = createSystemMessage(
          _pendingSSH.local
            ? `Local ssh-proxy test session\ncwd: ${sshSession.remoteCwd}\nAuth: unix socket → local proxy`
            : `SSH session to ${_pendingSSH.host}\nRemote cwd: ${sshSession.remoteCwd}\nAuth: unix socket -R → local proxy`,
          'info',
        );

        await launchRepl(
          root,
          { getFpsMetrics, stats, initialState },
          {
            debug: debug || debugToStderr,
            commands,
            initialTools: [],
            initialMessages: [sshInfoMessage],
            mcpClients: [],
            autoConnectIdeFlag: ide,
            mainThreadAgentDefinition,
            disableSlashCommands,
            sshSession,
            thinkingConfig,
          },
          renderAndRun,
        );
        return;
      } else if (
        feature('KAIROS') &&
        _pendingAssistantChat &&
        (_pendingAssistantChat.sessionId || _pendingAssistantChat.discover)
      ) {
        // `claude assistant [sessionId]` — REPL as a pure viewer client
        // of a remote assistant session. The agentic loop runs remotely; this
        // process streams live events and POSTs messages. History is lazy-
        // loaded by useAssistantHistory on scroll-up (no blocking fetch here).
        const { discoverAssistantSessions } = await import('./assistant/sessionDiscovery.js');

        let targetSessionId = _pendingAssistantChat.sessionId;

        // Discovery flow — list bridge environments, filter sessions
        if (!targetSessionId) {
          let sessions;
          try {
            sessions = await discoverAssistantSessions();
          } catch (e) {
            return await exitWithError(root, `Failed to discover sessions: ${e instanceof Error ? e.message : e}`, () =>
              gracefulShutdown(1),
            );
          }
          if (sessions.length === 0) {
            let installedDir: string | null;
            try {
              installedDir = await launchAssistantInstallWizard(root);
            } catch (e) {
              return await exitWithError(
                root,
                `Assistant installation failed: ${e instanceof Error ? e.message : e}`,
                () => gracefulShutdown(1),
              );
            }
            if (installedDir === null) {
              await gracefulShutdown(0);
              process.exit(0);
            }
            // The daemon needs a few seconds to spin up its worker and
            // establish a bridge session before discovery will find it.
            return await exitWithMessage(
              root,
              `Assistant installed in ${installedDir}. The daemon is starting up — run \`claude assistant\` again in a few seconds to connect.`,
              {
                exitCode: 0,
                beforeExit: () => gracefulShutdown(0),
              },
            );
          }
          if (sessions.length === 1) {
            targetSessionId = sessions[0]!.id;
          } else {
            const picked = await launchAssistantSessionChooser(root, {
              sessions,
            });
            if (!picked) {
              await gracefulShutdown(0);
              process.exit(0);
            }
            targetSessionId = picked;
          }
        }

        // Auth — call prepareApiRequest() once for orgUUID, but use a
        // getAccessToken closure for the token so reconnects get fresh tokens.
        const { checkAndRefreshOAuthTokenIfNeeded, getClaudeAIOAuthTokens } = await import('./utils/auth.js');
        await checkAndRefreshOAuthTokenIfNeeded();
        let apiCreds;
        try {
          apiCreds = await prepareApiRequest();
        } catch (e) {
          return await exitWithError(root, `Error: ${e instanceof Error ? e.message : 'Failed to authenticate'}`, () =>
            gracefulShutdown(1),
          );
        }
        const getAccessToken = (): string => getClaudeAIOAuthTokens()?.accessToken ?? apiCreds.accessToken;

        // Brief mode activation: setKairosActive(true) satisfies BOTH opt-in
        // and entitlement for isBriefEnabled() (BriefTool.ts:124-132).
        setKairosActive(true);
        setUserMsgOptIn(true);
        setIsRemoteMode(true);

        const remoteSessionConfig = createRemoteSessionConfig(
          targetSessionId,
          getAccessToken,
          apiCreds.orgUUID,
          /* hasInitialPrompt */ false,
          /* viewerOnly */ true,
        );

        const infoMessage = createSystemMessage(
          `Attached to assistant session ${targetSessionId.slice(0, 8)}…`,
          'info',
        );

        const assistantInitialState: AppState = {
          ...initialState,
          isBriefOnly: true,
          kairosEnabled: false,
          replBridgeEnabled: false,
        };

        const remoteCommands = filterCommandsForRemoteMode(commands);
        await launchRepl(
          root,
          {
            getFpsMetrics,
            stats,
            initialState: assistantInitialState,
          },
          {
            debug: debug || debugToStderr,
            commands: remoteCommands,
            initialTools: [],
            initialMessages: [infoMessage],
            mcpClients: [],
            autoConnectIdeFlag: ide,
            mainThreadAgentDefinition,
            disableSlashCommands,
            remoteSessionConfig,
            thinkingConfig,
          },
          renderAndRun,
        );
        return;
      } else if (options.resume || options.fromPr || teleport || remote !== null) {
        // Handle resume flow - from file (ant-only), session ID, or interactive selector

        // Clear stale caches before resuming to ensure fresh file/skill discovery
        const { clearSessionCaches } = await import('./commands/clear/caches.js');
        clearSessionCaches();

        let messages: MessageType[] | null = null;
        let processedResume: ProcessedResume | undefined;

        let maybeSessionId = validateUuid(options.resume);
        let searchTerm: string | undefined;
        // Store full LogOption when found by custom title (for cross-worktree resume)
        let matchedLog: LogOption | null = null;
        // PR filter for --from-pr flag
        let filterByPr: boolean | number | string | undefined;

        // Handle --from-pr flag
        if (options.fromPr) {
          if (options.fromPr === true) {
            // Show all sessions with linked PRs
            filterByPr = true;
          } else if (typeof options.fromPr === 'string') {
            // Could be a PR number or URL
            filterByPr = options.fromPr;
          }
        }

        // If resume value is not a UUID, try exact match by custom title first
        if (options.resume && typeof options.resume === 'string' && !maybeSessionId) {
          const trimmedValue = options.resume.trim();
          if (trimmedValue) {
            const matches = await searchSessionsByCustomTitle(trimmedValue, {
              exact: true,
            });

            if (matches.length === 1) {
              // Exact match found - store full LogOption for cross-worktree resume
              matchedLog = matches[0]!;
              maybeSessionId = getSessionIdFromLog(matchedLog) ?? null;
            } else {
              // No match or multiple matches - use as search term for picker
              searchTerm = trimmedValue;
            }
          }
        }

        // --remote and --teleport both create/resume Claude Code Web (CCR) sessions.
        // Remote Control (--rc) is a separate feature gated in initReplBridge.ts.
        if (remote !== null || teleport) {
          await waitForPolicyLimitsToLoad();
          if (!isPolicyAllowed('allow_remote_sessions')) {
            return await exitWithError(root, "Error: Remote sessions are disabled by your organization's policy.", () =>
              gracefulShutdown(1),
            );
          }
        }

        if (remote !== null) {
          // Create remote session (optionally with initial prompt)
          const hasInitialPrompt = remote.length > 0;

          // Check if TUI mode is enabled - description is only optional in TUI mode
          const isRemoteTuiEnabled = getFeatureValue_CACHED_MAY_BE_STALE('tengu_remote_backend', false);
          if (!isRemoteTuiEnabled && !hasInitialPrompt) {
            return await exitWithError(
              root,
              'Error: --remote requires a description.\nUsage: claude --remote "your task description"',
              () => gracefulShutdown(1),
            );
          }

          logEvent('tengu_remote_create_session', {
            has_initial_prompt: String(hasInitialPrompt) as AnalyticsMetadata_I_VERIFIED_THIS_IS_NOT_CODE_OR_FILEPATHS,
          });

          // Pass current branch so CCR clones the repo at the right revision
          const currentBranch = await getBranch();
          const createdSession = await teleportToRemoteWithErrorHandling(
            root,
            hasInitialPrompt ? remote : null,
            new AbortController().signal,
            currentBranch || undefined,
          );
          if (!createdSession) {
            logEvent('tengu_remote_create_session_error', {
              error: 'unable_to_create_session' as AnalyticsMetadata_I_VERIFIED_THIS_IS_NOT_CODE_OR_FILEPATHS,
            });
            return await exitWithError(root, 'Error: Unable to create remote session', () => gracefulShutdown(1));
          }
          logEvent('tengu_remote_create_session_success', {
            session_id: createdSession.id as AnalyticsMetadata_I_VERIFIED_THIS_IS_NOT_CODE_OR_FILEPATHS,
          });

          // Check if new remote TUI mode is enabled via feature gate
          if (!isRemoteTuiEnabled) {
            // Original behavior: print session info and exit
            process.stdout.write(`Created remote session: ${createdSession.title}\n`);
            process.stdout.write(`View: ${getRemoteSessionUrl(createdSession.id)}?m=0\n`);
            process.stdout.write(`Resume with: claude --teleport ${createdSession.id}\n`);
            await gracefulShutdown(0);
            process.exit(0);
          }

          // New behavior: start local TUI with CCR engine
          // Mark that we're in remote mode for command visibility
          setIsRemoteMode(true);
          switchSession(asSessionId(createdSession.id));

          // Get OAuth credentials for remote session
          let apiCreds: { accessToken: string; orgUUID: string };
          try {
            apiCreds = await prepareApiRequest();
          } catch (error) {
            logError(toError(error));
            return await exitWithError(root, `Error: ${errorMessage(error) || 'Failed to authenticate'}`, () =>
              gracefulShutdown(1),
            );
          }

          // Create remote session config for the REPL
          const { getClaudeAIOAuthTokens: getTokensForRemote } = await import('./utils/auth.js');
          const getAccessTokenForRemote = (): string => getTokensForRemote()?.accessToken ?? apiCreds.accessToken;
          const remoteSessionConfig = createRemoteSessionConfig(
            createdSession.id,
            getAccessTokenForRemote,
            apiCreds.orgUUID,
            hasInitialPrompt,
          );

          // Add remote session info as initial system message
          const remoteSessionUrl = `${getRemoteSessionUrl(createdSession.id)}?m=0`;
          const remoteInfoMessage = createSystemMessage(
            `/remote-control is active. Code in CLI or at ${remoteSessionUrl}`,
            'info',
          );

          // Create initial user message from the prompt if provided (CCR echoes it back but we ignore that)
          const initialUserMessage = hasInitialPrompt ? createUserMessage({ content: remote }) : null;

          // Set remote session URL in app state for footer indicator
          const remoteInitialState = {
            ...initialState,
            remoteSessionUrl,
          };

          // Pre-filter commands to only include remote-safe ones.
          // CCR's init response may further refine the list (via handleRemoteInit in REPL).
          const remoteCommands = filterCommandsForRemoteMode(commands);
          await launchRepl(
            root,
            {
              getFpsMetrics,
              stats,
              initialState: remoteInitialState,
            },
            {
              debug: debug || debugToStderr,
              commands: remoteCommands,
              initialTools: [],
              initialMessages: initialUserMessage ? [remoteInfoMessage, initialUserMessage] : [remoteInfoMessage],
              mcpClients: [],
              autoConnectIdeFlag: ide,
              mainThreadAgentDefinition,
              disableSlashCommands,
              remoteSessionConfig,
              thinkingConfig,
            },
            renderAndRun,
          );
          return;
        } else if (teleport) {
          if (teleport === true || teleport === '') {
            // Interactive mode: show task selector and handle resume
            logEvent('tengu_teleport_interactive_mode', {});
            logForDebugging('selectAndResumeTeleportTask: Starting teleport flow...');
            const teleportResult = await launchTeleportResumeWrapper(root);
            if (!teleportResult) {
              // User cancelled or error occurred
              await gracefulShutdown(0);
              process.exit(0);
            }
            const { branchError } = await checkOutTeleportedSessionBranch(teleportResult.branch);
            messages = processMessagesForTeleportResume(teleportResult.log, branchError);
          } else if (typeof teleport === 'string') {
            logEvent('tengu_teleport_resume_session', {
              mode: 'direct' as AnalyticsMetadata_I_VERIFIED_THIS_IS_NOT_CODE_OR_FILEPATHS,
            });
            try {
              // First, fetch session and validate repository before checking git state
              const sessionData = await fetchSession(teleport);
              const repoValidation = await validateSessionRepository(sessionData);

              // Handle repo mismatch or not in repo cases
              if (repoValidation.status === 'mismatch' || repoValidation.status === 'not_in_repo') {
                const sessionRepo = repoValidation.sessionRepo;
                if (sessionRepo) {
                  // Check for known paths
                  const knownPaths = getKnownPathsForRepo(sessionRepo);
                  const existingPaths = await filterExistingPaths(knownPaths);

                  if (existingPaths.length > 0) {
                    // Show directory switch dialog
                    const selectedPath = await launchTeleportRepoMismatchDialog(root, {
                      targetRepo: sessionRepo,
                      initialPaths: existingPaths,
                    });

                    if (selectedPath) {
                      // Change to the selected directory
                      process.chdir(selectedPath);
                      setCwd(selectedPath);
                      setOriginalCwd(selectedPath);
                    } else {
                      // User cancelled
                      await gracefulShutdown(0);
                    }
                  } else {
                    // No known paths - show original error
                    throw new TeleportOperationError(
                      `You must run claude --teleport ${teleport} from a checkout of ${sessionRepo}.`,
                      chalk.red(
                        `You must run claude --teleport ${teleport} from a checkout of ${chalk.bold(sessionRepo)}.\n`,
                      ),
                    );
                  }
                }
              } else if (repoValidation.status === 'error') {
                throw new TeleportOperationError(
                  repoValidation.errorMessage || 'Failed to validate session',
                  chalk.red(`Error: ${repoValidation.errorMessage || 'Failed to validate session'}\n`),
                );
              }

              await validateGitState();

              // Use progress UI for teleport
              const { teleportWithProgress } = await import('./components/TeleportProgress.js');
              const result = await teleportWithProgress(root, teleport);
              // Track teleported session for reliability logging
              setTeleportedSessionInfo({ sessionId: teleport });
              messages = result.messages;
            } catch (error) {
              if (error instanceof TeleportOperationError) {
                process.stderr.write(error.formattedMessage + '\n');
              } else {
                logError(error);
                process.stderr.write(chalk.red(`Error: ${errorMessage(error)}\n`));
              }
              await gracefulShutdown(1);
            }
          }
        }
        if (process.env.USER_TYPE === 'ant') {
          if (options.resume && typeof options.resume === 'string' && !maybeSessionId) {
            const resolvedPath = resolve(options.resume);
            try {
              const resumeStart = performance.now();
              let logOption;
              try {
                // Attempt to load as a transcript file; ENOENT falls through to session-ID handling
                logOption = await loadTranscriptFromFile(resolvedPath);
              } catch (error) {
                if (!isENOENT(error)) throw error;
                // ENOENT: not a file path — fall through to session-ID handling
              }
              if (logOption) {
                const result = await loadConversationForResume(logOption, undefined /* sourceFile */);
                if (result) {
                  processedResume = await processResumedConversation(
                    result,
                    {
                      forkSession: !!options.forkSession,
                      transcriptPath: result.fullPath,
                    },
                    resumeContext,
                  );
                  if (processedResume.restoredAgentDef) {
                    mainThreadAgentDefinition = processedResume.restoredAgentDef;
                  }
                  logEvent('tengu_session_resumed', {
                    entrypoint: 'file' as AnalyticsMetadata_I_VERIFIED_THIS_IS_NOT_CODE_OR_FILEPATHS,
                    success: true,
                    resume_duration_ms: Math.round(performance.now() - resumeStart),
                  });
                } else {
                  logEvent('tengu_session_resumed', {
                    entrypoint: 'file' as AnalyticsMetadata_I_VERIFIED_THIS_IS_NOT_CODE_OR_FILEPATHS,
                    success: false,
                  });
                }
              }
            } catch (error) {
              logEvent('tengu_session_resumed', {
                entrypoint: 'file' as AnalyticsMetadata_I_VERIFIED_THIS_IS_NOT_CODE_OR_FILEPATHS,
                success: false,
              });
              logError(error);
              await exitWithError(root, `Unable to load transcript from file: ${options.resume}`, () =>
                gracefulShutdown(1),
              );
            }
          }
        }

        // If not loaded as a file, try as session ID
        if (maybeSessionId) {
          // Resume specific session by ID
          const sessionId = maybeSessionId;
          try {
            const resumeStart = performance.now();
            // Use matchedLog if available (for cross-worktree resume by custom title)
            // Otherwise fall back to sessionId string (for direct UUID resume)
            const result = await loadConversationForResume(matchedLog ?? sessionId, undefined);

            if (!result) {
              logEvent('tengu_session_resumed', {
                entrypoint: 'cli_flag' as AnalyticsMetadata_I_VERIFIED_THIS_IS_NOT_CODE_OR_FILEPATHS,
                success: false,
              });
              return await exitWithError(root, `No conversation found with session ID: ${sessionId}`);
            }

            const fullPath = matchedLog?.fullPath ?? result.fullPath;
            processedResume = await processResumedConversation(
              result,
              {
                forkSession: !!options.forkSession,
                sessionIdOverride: sessionId,
                transcriptPath: fullPath,
              },
              resumeContext,
            );

            if (processedResume.restoredAgentDef) {
              mainThreadAgentDefinition = processedResume.restoredAgentDef;
            }
            logEvent('tengu_session_resumed', {
              entrypoint: 'cli_flag' as AnalyticsMetadata_I_VERIFIED_THIS_IS_NOT_CODE_OR_FILEPATHS,
              success: true,
              resume_duration_ms: Math.round(performance.now() - resumeStart),
            });
          } catch (error) {
            logEvent('tengu_session_resumed', {
              entrypoint: 'cli_flag' as AnalyticsMetadata_I_VERIFIED_THIS_IS_NOT_CODE_OR_FILEPATHS,
              success: false,
            });
            logError(error);
            await exitWithError(root, `Failed to resume session ${sessionId}`);
          }
        }

        // Await file downloads before rendering REPL (files must be available)
        if (fileDownloadPromise) {
          try {
            const results = await fileDownloadPromise;
            const failedCount = count(results, r => !r.success);
            if (failedCount > 0) {
              process.stderr.write(
                chalk.yellow(`Warning: ${failedCount}/${results.length} file(s) failed to download.\n`),
              );
            }
          } catch (error) {
            return await exitWithError(root, `Error downloading files: ${errorMessage(error)}`);
          }
        }

        // If we have a processed resume or teleport messages, render the REPL
        const resumeData =
          processedResume ??
          (Array.isArray(messages)
            ? {
                messages,
                fileHistorySnapshots: undefined,
                agentName: undefined,
                agentColor: undefined as AgentColorName | undefined,
                restoredAgentDef: mainThreadAgentDefinition,
                initialState,
                contentReplacements: undefined,
              }
            : undefined);
        if (resumeData) {
          maybeActivateProactive(options);
          maybeActivateBrief(options);

          await launchRepl(
            root,
            {
              getFpsMetrics,
              stats,
              initialState: resumeData.initialState,
            },
            {
              ...sessionConfig,
              mainThreadAgentDefinition: resumeData.restoredAgentDef ?? mainThreadAgentDefinition,
              initialMessages: resumeData.messages,
              initialFileHistorySnapshots: resumeData.fileHistorySnapshots,
              initialContentReplacements: resumeData.contentReplacements,
              initialAgentName: resumeData.agentName,
              initialAgentColor: resumeData.agentColor,
            },
            renderAndRun,
          );
        } else {
          // Show interactive selector (includes same-repo worktrees)
          // Note: ResumeConversation loads logs internally to ensure proper GC after selection
          await launchResumeChooser(root, { getFpsMetrics, stats, initialState }, getWorktreePaths(getOriginalCwd()), {
            ...sessionConfig,
            initialSearchQuery: searchTerm,
            forkSession: options.forkSession,
            filterByPr,
          });
        }
      } else {
        // Pass unresolved hooks promise to REPL so it can render immediately
        // instead of blocking ~500ms waiting for SessionStart hooks to finish.
        // REPL will inject hook messages when they resolve and await them before
        // the first API call so the model always sees hook context.
        const pendingHookMessages = hooksPromise && hookMessages.length === 0 ? hooksPromise : undefined;

        profileCheckpoint('action_after_hooks');
        maybeActivateProactive(options);
        maybeActivateBrief(options);
        // Persist the current mode for fresh sessions so future resumes know what mode was used
        if (feature('COORDINATOR_MODE')) {
          saveMode(coordinatorModeModule?.isCoordinatorMode() ? 'coordinator' : 'normal');
        }

        // If launched via a deep link, show a provenance banner so the user
        // knows the session originated externally. Linux xdg-open and
        // browsers with "always allow" set dispatch the link with no OS-level
        // confirmation, so this is the only signal the user gets that the
        // prompt — and the working directory / CLAUDE.md it implies — came
        // from an external source rather than something they typed.
        let deepLinkBanner: ReturnType<typeof createSystemMessage> | null = null;
        if (feature('LODESTONE')) {
          if (options.deepLinkOrigin) {
            logEvent('tengu_deep_link_opened', {
              has_prefill: Boolean(options.prefill),
              has_repo: Boolean(options.deepLinkRepo),
            });
            deepLinkBanner = createSystemMessage(
              buildDeepLinkBanner({
                cwd: getCwd(),
                prefillLength: options.prefill?.length,
                repo: options.deepLinkRepo,
                lastFetch: options.deepLinkLastFetch !== undefined ? new Date(options.deepLinkLastFetch) : undefined,
              }),
              'warning',
            );
          } else if (options.prefill) {
            deepLinkBanner = createSystemMessage(
              'Launched with a pre-filled prompt — review it before pressing Enter.',
              'warning',
            );
          }
        }
        const initialMessages = deepLinkBanner
          ? [deepLinkBanner, ...hookMessages]
          : hookMessages.length > 0
            ? hookMessages
            : undefined;

        await launchRepl(
          root,
          { getFpsMetrics, stats, initialState },
          {
            ...sessionConfig,
            initialMessages,
            pendingHookMessages,
          },
          renderAndRun,
        );
      }
    })
    .version(`${MACRO.VERSION} (Claude Code)`, '-v, --version', 'Output the version number');

  // Worktree flags
  program.option('-w, --worktree [name]', 'Create a new git worktree for this session (optionally specify a name)');
  program.option(
    '--tmux',
    'Create a tmux session for the worktree (requires --worktree). Uses iTerm2 native panes when available; use --tmux=classic for traditional tmux.',
  );

  if (canUserConfigureAdvisor()) {
    program.addOption(
      new Option(
        '--advisor <model>',
        'Enable the server-side advisor tool with the specified model (alias or full ID).',
      ).hideHelp(),
    );
  }

  if (process.env.USER_TYPE === 'ant') {
    program.addOption(
      new Option('--delegate-permissions', '[ANT-ONLY] Alias for --permission-mode auto.').implies({
        permissionMode: 'auto',
      }),
    );
    program.addOption(
      new Option(
        '--dangerously-skip-permissions-with-classifiers',
        '[ANT-ONLY] Deprecated alias for --permission-mode auto.',
      )
        .hideHelp()
        .implies({ permissionMode: 'auto' }),
    );
    program.addOption(
      new Option('--afk', '[ANT-ONLY] Deprecated alias for --permission-mode auto.')
        .hideHelp()
        .implies({ permissionMode: 'auto' }),
    );
    program.addOption(
      new Option(
        '--tasks [id]',
        '[ANT-ONLY] Tasks mode: watch for tasks and auto-process them. Optional id is used as both the task list ID and agent ID (defaults to "tasklist").',
      )
        .argParser(String)
        .hideHelp(),
    );
    program.option('--agent-teams', '[ANT-ONLY] Force Claude to use multi-agent mode for solving problems', () => true);
  }

  if (feature('TRANSCRIPT_CLASSIFIER')) {
    program.addOption(new Option('--enable-auto-mode', 'Opt in to auto mode').hideHelp());
  }

  if (feature('PROACTIVE') || feature('KAIROS')) {
    program.addOption(new Option('--proactive', 'Start in proactive autonomous mode'));
  }

  if (feature('UDS_INBOX')) {
    program.addOption(
      new Option(
        '--messaging-socket-path <path>',
        'Unix domain socket path for the UDS messaging server (defaults to a tmp path)',
      ),
    );
  }

  if (feature('KAIROS') || feature('KAIROS_BRIEF')) {
    program.addOption(new Option('--brief', 'Enable SendUserMessage tool for agent-to-user communication'));
  }
  if (feature('KAIROS')) {
    program.addOption(new Option('--assistant', 'Force assistant mode (Agent SDK daemon use)').hideHelp());
  }
  program.addOption(
    new Option(
      '--channels <servers...>',
      'MCP servers whose channel notifications (inbound push) should register this session. Space-separated server names.',
    ).hideHelp(),
  );
  program.addOption(
    new Option(
      '--dangerously-load-development-channels <servers...>',
      'Load channel servers not on the approved allowlist. For local channel development only. Shows a confirmation dialog at startup.',
    ).hideHelp(),
  );

  // Teammate identity options (set by leader when spawning tmux teammates)
  // These replace the CLAUDE_CODE_* environment variables
  program.addOption(new Option('--agent-id <id>', 'Teammate agent ID').hideHelp());
  program.addOption(new Option('--agent-name <name>', 'Teammate display name').hideHelp());
  program.addOption(new Option('--team-name <name>', 'Team name for swarm coordination').hideHelp());
  program.addOption(new Option('--agent-color <color>', 'Teammate UI color').hideHelp());
  program.addOption(new Option('--plan-mode-required', 'Require plan mode before implementation').hideHelp());
  program.addOption(new Option('--parent-session-id <id>', 'Parent session ID for analytics correlation').hideHelp());
  program.addOption(
    new Option('--teammate-mode <mode>', 'How to spawn teammates: "tmux", "in-process", or "auto"')
      .choices(['auto', 'tmux', 'in-process'])
      .hideHelp(),
  );
  program.addOption(new Option('--agent-type <type>', 'Custom agent type for this teammate').hideHelp());

  // Enable SDK URL for all builds but hide from help
  program.addOption(
    new Option(
      '--sdk-url <url>',
      'Use remote WebSocket endpoint for SDK I/O streaming (only with -p and stream-json format)',
    ).hideHelp(),
  );

  // Enable teleport/remote flags for all builds but keep them undocumented until GA
  program.addOption(
    new Option('--teleport [session]', 'Resume a teleport session, optionally specify session ID').hideHelp(),
  );
  program.addOption(
    new Option('--remote [description]', 'Create a remote session with the given description').hideHelp(),
  );
  if (feature('BRIDGE_MODE')) {
    program.addOption(
      new Option(
        '--remote-control [name]',
        'Start an interactive session with Remote Control enabled (optionally named)',
      )
        .argParser(value => value || true)
        .hideHelp(),
    );
    program.addOption(
      new Option('--rc [name]', 'Alias for --remote-control').argParser(value => value || true).hideHelp(),
    );
  }

  if (feature('HARD_FAIL')) {
    program.addOption(new Option('--hard-fail', 'Crash on logError calls instead of silently logging').hideHelp());
  }

  profileCheckpoint('run_main_options_built');

  // -p/--print mode: skip subcommand registration. The 52 subcommands
  // (mcp, auth, plugin, skill, task, config, doctor, update, etc.) are
  // never dispatched in print mode — commander routes the prompt to the
  // default action. The subcommand registration path was measured at ~65ms
  // on baseline — mostly the isBridgeEnabled() call (25ms settings Zod parse
  // + 40ms sync keychain subprocess), both hidden by the try/catch that
  // always returns false before enableConfigs(). cc:// URLs are rewritten to
  // `open` at main() line ~851 BEFORE this runs, so argv check is safe here.
  const isPrintMode = process.argv.includes('-p') || process.argv.includes('--print');
  const isCcUrl = process.argv.some(a => a.startsWith('cc://') || a.startsWith('cc+unix://'));
  if (isPrintMode && !isCcUrl) {
    profileCheckpoint('run_before_parse');
    await program.parseAsync(process.argv);
    profileCheckpoint('run_after_parse');
    return program;
  }

  // claude mcp

  const mcp = program
    .command('mcp')
    .description('Configure and manage MCP servers')
    .configureHelp(createSortedHelpConfig())
    .enablePositionalOptions();

  mcp
    .command('serve')
    .description(`Start the Claude Code MCP server`)
    .option('-d, --debug', 'Enable debug mode', () => true)
    .option('--verbose', 'Override verbose mode setting from config', () => true)
    .action(async ({ debug, verbose }: { debug?: boolean; verbose?: boolean }) => {
      const { mcpServeHandler } = await import('./cli/handlers/mcp.js');
      await mcpServeHandler({ debug, verbose });
    });

  // Register the mcp add subcommand (extracted for testability)
  registerMcpAddCommand(mcp);

  if (isXaaEnabled()) {
    registerMcpXaaIdpCommand(mcp);
  }

  mcp
    .command('remove <name>')
    .description('Remove an MCP server')
    .option(
      '-s, --scope <scope>',
      'Configuration scope (local, user, or project) - if not specified, removes from whichever scope it exists in',
    )
    .action(async (name: string, options: { scope?: string }) => {
      const { mcpRemoveHandler } = await import('./cli/handlers/mcp.js');
      await mcpRemoveHandler(name, options);
    });

  mcp
    .command('list')
    .description(
      'List configured MCP servers. Note: The workspace trust dialog is skipped and stdio servers from .mcp.json are spawned for health checks. Only use this command in directories you trust.',
    )
    .action(async () => {
      const { mcpListHandler } = await import('./cli/handlers/mcp.js');
      await mcpListHandler();
    });

  mcp
    .command('get <name>')
    .description(
      'Get details about an MCP server. Note: The workspace trust dialog is skipped and stdio servers from .mcp.json are spawned for health checks. Only use this command in directories you trust.',
    )
    .action(async (name: string) => {
      const { mcpGetHandler } = await import('./cli/handlers/mcp.js');
      await mcpGetHandler(name);
    });

  mcp
    .command('add-json <name> <json>')
    .description('Add an MCP server (stdio or SSE) with a JSON string')
    .option('-s, --scope <scope>', 'Configuration scope (local, user, or project)', 'local')
    .option('--client-secret', 'Prompt for OAuth client secret (or set MCP_CLIENT_SECRET env var)')
    .action(async (name: string, json: string, options: { scope?: string; clientSecret?: true }) => {
      const { mcpAddJsonHandler } = await import('./cli/handlers/mcp.js');
      await mcpAddJsonHandler(name, json, options);
    });

  mcp
    .command('add-from-claude-desktop')
    .description('Import MCP servers from Claude Desktop (Mac and WSL only)')
    .option('-s, --scope <scope>', 'Configuration scope (local, user, or project)', 'local')
    .action(async (options: { scope?: string }) => {
      const { mcpAddFromDesktopHandler } = await import('./cli/handlers/mcp.js');
      await mcpAddFromDesktopHandler(options);
    });

  mcp
    .command('reset-project-choices')
    .description('Reset all approved and rejected project-scoped (.mcp.json) servers within this project')
    .action(async () => {
      const { mcpResetChoicesHandler } = await import('./cli/handlers/mcp.js');
      await mcpResetChoicesHandler();
    });

  // claude server
  if (feature('DIRECT_CONNECT')) {
    program
      .command('server')
      .description('Start a Claude Code session server')
      .option('--port <number>', 'HTTP port', '0')
      .option('--host <string>', 'Bind address', '0.0.0.0')
      .option('--auth-token <token>', 'Bearer token for auth')
      .option('--unix <path>', 'Listen on a unix domain socket')
      .option('--workspace <dir>', 'Default working directory for sessions that do not specify cwd')
      .option('--idle-timeout <ms>', 'Idle timeout for detached sessions in ms (0 = never expire)', '600000')
      .option('--max-sessions <n>', 'Maximum concurrent sessions (0 = unlimited)', '32')
      .action(
        async (opts: {
          port: string;
          host: string;
          authToken?: string;
          unix?: string;
          workspace?: string;
          idleTimeout: string;
          maxSessions: string;
        }) => {
          const { randomBytes } = await import('crypto');
          const { startServer } = await import('./server/server.js');
          const { SessionManager } = await import('./server/sessionManager.js');
          const { DangerousBackend } = await import('./server/backends/dangerousBackend.js');
          const { printBanner } = await import('./server/serverBanner.js');
          const { createServerLogger } = await import('./server/serverLog.js');
          const { writeServerLock, removeServerLock, probeRunningServer } = await import('./server/lockfile.js');

          const existing = await probeRunningServer();
          if (existing) {
            process.stderr.write(`A claude server is already running (pid ${existing.pid}) at ${existing.httpUrl}\n`);
            process.exit(1);
          }

          const authToken = opts.authToken ?? `sk-ant-cc-${randomBytes(16).toString('base64url')}`;

          const config = {
            port: parseInt(opts.port, 10),
            host: opts.host,
            authToken,
            unix: opts.unix,
            workspace: opts.workspace,
            idleTimeoutMs: parseInt(opts.idleTimeout, 10),
            maxSessions: parseInt(opts.maxSessions, 10),
          };

          const backend = new DangerousBackend();
          const sessionManager = new SessionManager(backend, {
            idleTimeoutMs: config.idleTimeoutMs,
            maxSessions: config.maxSessions,
          });
          const logger = createServerLogger();

          const server = startServer(config, sessionManager, logger);
          const actualPort = server.port ?? config.port;
          printBanner(config, authToken, actualPort);

          await writeServerLock({
            pid: process.pid,
            port: actualPort,
            host: config.host,
            httpUrl: config.unix ? `unix:${config.unix}` : `http://${config.host}:${actualPort}`,
            startedAt: Date.now(),
          });

          let shuttingDown = false;
          const shutdown = async () => {
            if (shuttingDown) return;
            shuttingDown = true;
            // Stop accepting new connections before tearing down sessions.
            server.stop(true);
            await sessionManager.destroyAll();
            await removeServerLock();
            process.exit(0);
          };
          process.once('SIGINT', () => void shutdown());
          process.once('SIGTERM', () => void shutdown());
        },
      );
  }

  // `claude ssh <host> [dir]` — registered here only so --help shows it.
  // The actual interactive flow is handled by early argv rewriting in main()
  // (parallels the DIRECT_CONNECT/cc:// pattern above). If commander reaches
  // this action it means the argv rewrite didn't fire (e.g. user ran
  // `claude ssh` with no host) — just print usage.
  if (feature('SSH_REMOTE')) {
    program
      .command('ssh <host> [dir]')
      .description(
        'Run Claude Code on a remote host over SSH. Deploys the binary and ' +
          'tunnels API auth back through your local machine — no remote setup needed.',
      )
      .option('--permission-mode <mode>', 'Permission mode for the remote session')
      .option('--dangerously-skip-permissions', 'Skip all permission prompts on the remote (dangerous)')
      .option(
        '--remote-bin <command>',
        'Custom remote binary command (skips probe/deploy). ' +
          "Example: --remote-bin 'bun /path/to/project/dist/cli.js'",
      )
      .option(
        '--local',
        'e2e test mode — spawn the child CLI locally (skip ssh/deploy). ' +
          'Exercises the auth proxy and unix-socket plumbing without a remote host.',
      )
      .action(async () => {
        // Argv rewriting in main() should have consumed `ssh <host>` before
        // commander runs. Reaching here means host was missing or the
        // rewrite predicate didn't match.
        process.stderr.write(
          'Usage: claude ssh <user@host | ssh-config-alias> [dir]\n\n' +
            "Runs Claude Code on a remote Linux host. You don't need to install\n" +
            'anything on the remote or run `claude auth login` there — the binary is\n' +
            'deployed over SSH and API auth tunnels back through your local machine.\n',
        );
        process.exit(1);
      });
  }

  // claude connect — subcommand only handles -p (headless) mode.
  // Interactive mode (without -p) is handled by early argv rewriting in main()
  // which redirects to the main command with full TUI support.
  if (feature('DIRECT_CONNECT')) {
    program
      .command('open <cc-url>')
      .description('Connect to a Claude Code server (internal — use cc:// URLs)')
      .option('-p, --print [prompt]', 'Print mode (headless)')
      .option('--output-format <format>', 'Output format: text, json, stream-json', 'text')
      .action(
        async (
          ccUrl: string,
          opts: {
            print?: string | true;
            outputFormat?: string;
          },
        ) => {
          const { parseConnectUrl } = await import('./server/parseConnectUrl.js');
          const { serverUrl, authToken } = parseConnectUrl(ccUrl);

          let connectConfig;
          try {
            const session = await createDirectConnectSession({
              serverUrl,
              authToken,
              cwd: getOriginalCwd(),
              dangerouslySkipPermissions: _pendingConnect?.dangerouslySkipPermissions,
            });
            if (session.workDir) {
              setOriginalCwd(session.workDir);
              setCwdState(session.workDir);
            }
            setDirectConnectServerUrl(serverUrl);
            connectConfig = session.config;
          } catch (err) {
            console.error(err instanceof DirectConnectError ? err.message : String(err));
            process.exit(1);
          }

          const { runConnectHeadless } = await import('./server/connectHeadless.js');

          const prompt = typeof opts.print === 'string' ? opts.print : '';
          const interactive = opts.print === true;
          await runConnectHeadless(connectConfig, prompt, opts.outputFormat, interactive);
        },
      );
  }

  // claude auth

  const auth = program.command('auth').description('Manage authentication').configureHelp(createSortedHelpConfig());

  auth
    .command('login')
    .description('Sign in to your Anthropic account')
    .option('--email <email>', 'Pre-populate email address on the login page')
    .option('--sso', 'Force SSO login flow')
    .option('--console', 'Use Anthropic Console (API usage billing) instead of Claude subscription')
    .option('--claudeai', 'Use Claude subscription (default)')
    .action(
      async ({
        email,
        sso,
        console: useConsole,
        claudeai,
      }: {
        email?: string;
        sso?: boolean;
        console?: boolean;
        claudeai?: boolean;
      }) => {
        const { authLogin } = await import('./cli/handlers/auth.js');
        await authLogin({ email, sso, console: useConsole, claudeai });
      },
    );

  auth
    .command('status')
    .description('Show authentication status')
    .option('--json', 'Output as JSON (default)')
    .option('--text', 'Output as human-readable text')
    .action(async (opts: { json?: boolean; text?: boolean }) => {
      const { authStatus } = await import('./cli/handlers/auth.js');
      await authStatus(opts);
    });

  auth
    .command('logout')
    .description('Log out from your Anthropic account')
    .action(async () => {
      const { authLogout } = await import('./cli/handlers/auth.js');
      await authLogout();
    });

  /**
   * Helper function to handle marketplace command errors consistently.
   * Logs the error and exits the process with status 1.
   * @param error The error that occurred
   * @param action Description of the action that failed
   */
  // Hidden flag on all plugin/marketplace subcommands to target cowork_plugins.
  const coworkOption = () => new Option('--cowork', 'Use cowork_plugins directory').hideHelp();

  // Plugin validate command
  const pluginCmd = program
    .command('plugin')
    .alias('plugins')
    .description('Manage Claude Code plugins')
    .configureHelp(createSortedHelpConfig());

  pluginCmd
    .command('validate <path>')
    .description('Validate a plugin or marketplace manifest')
    .addOption(coworkOption())
    .action(async (manifestPath: string, options: { cowork?: boolean }) => {
      const { pluginValidateHandler } = await import('./cli/handlers/plugins.js');
      await pluginValidateHandler(manifestPath, options);
    });

  // Plugin list command
  pluginCmd
    .command('list')
    .description('List installed plugins')
    .option('--json', 'Output as JSON')
    .option('--available', 'Include available plugins from marketplaces (requires --json)')
    .addOption(coworkOption())
    .action(async (options: { json?: boolean; available?: boolean; cowork?: boolean }) => {
      const { pluginListHandler } = await import('./cli/handlers/plugins.js');
      await pluginListHandler(options);
    });

  // Marketplace subcommands
  const marketplaceCmd = pluginCmd
    .command('marketplace')
    .description('Manage Claude Code marketplaces')
    .configureHelp(createSortedHelpConfig());

  marketplaceCmd
    .command('add <source>')
    .description('Add a marketplace from a URL, path, or GitHub repo')
    .addOption(coworkOption())
    .option(
      '--sparse <paths...>',
      'Limit checkout to specific directories via git sparse-checkout (for monorepos). Example: --sparse .claude-plugin plugins',
    )
    .option('--scope <scope>', 'Where to declare the marketplace: user (default), project, or local')
    .action(
      async (
        source: string,
        options: {
          cowork?: boolean;
          sparse?: string[];
          scope?: string;
        },
      ) => {
        const { marketplaceAddHandler } = await import('./cli/handlers/plugins.js');
        await marketplaceAddHandler(source, options);
      },
    );

  marketplaceCmd
    .command('list')
    .description('List all configured marketplaces')
    .option('--json', 'Output as JSON')
    .addOption(coworkOption())
    .action(async (options: { json?: boolean; cowork?: boolean }) => {
      const { marketplaceListHandler } = await import('./cli/handlers/plugins.js');
      await marketplaceListHandler(options);
    });

  marketplaceCmd
    .command('remove <name>')
    .alias('rm')
    .description('Remove a configured marketplace')
    .addOption(coworkOption())
    .action(async (name: string, options: { cowork?: boolean }) => {
      const { marketplaceRemoveHandler } = await import('./cli/handlers/plugins.js');
      await marketplaceRemoveHandler(name, options);
    });

  marketplaceCmd
    .command('update [name]')
    .description('Update marketplace(s) from their source - updates all if no name specified')
    .addOption(coworkOption())
    .action(async (name: string | undefined, options: { cowork?: boolean }) => {
      const { marketplaceUpdateHandler } = await import('./cli/handlers/plugins.js');
      await marketplaceUpdateHandler(name, options);
    });

  // Plugin install command
  pluginCmd
    .command('install <plugin>')
    .alias('i')
    .description('Install a plugin from available marketplaces (use plugin@marketplace for specific marketplace)')
    .option('-s, --scope <scope>', 'Installation scope: user, project, or local', 'user')
    .addOption(coworkOption())
    .action(async (plugin: string, options: { scope?: string; cowork?: boolean }) => {
      const { pluginInstallHandler } = await import('./cli/handlers/plugins.js');
      await pluginInstallHandler(plugin, options);
    });

  // Plugin uninstall command
  pluginCmd
    .command('uninstall <plugin>')
    .alias('remove')
    .alias('rm')
    .description('Uninstall an installed plugin')
    .option('-s, --scope <scope>', 'Uninstall from scope: user, project, or local', 'user')
    .option('--keep-data', "Preserve the plugin's persistent data directory (~/.claude/plugins/data/{id}/)")
    .addOption(coworkOption())
    .action(
      async (
        plugin: string,
        options: {
          scope?: string;
          cowork?: boolean;
          keepData?: boolean;
        },
      ) => {
        const { pluginUninstallHandler } = await import('./cli/handlers/plugins.js');
        await pluginUninstallHandler(plugin, options);
      },
    );

  // Plugin enable command
  pluginCmd
    .command('enable <plugin>')
    .description('Enable a disabled plugin')
    .option('-s, --scope <scope>', `Installation scope: ${VALID_INSTALLABLE_SCOPES.join(', ')} (default: auto-detect)`)
    .addOption(coworkOption())
    .action(async (plugin: string, options: { scope?: string; cowork?: boolean }) => {
      const { pluginEnableHandler } = await import('./cli/handlers/plugins.js');
      await pluginEnableHandler(plugin, options);
    });

  // Plugin disable command
  pluginCmd
    .command('disable [plugin]')
    .description('Disable an enabled plugin')
    .option('-a, --all', 'Disable all enabled plugins')
    .option('-s, --scope <scope>', `Installation scope: ${VALID_INSTALLABLE_SCOPES.join(', ')} (default: auto-detect)`)
    .addOption(coworkOption())
    .action(async (plugin: string | undefined, options: { scope?: string; cowork?: boolean; all?: boolean }) => {
      const { pluginDisableHandler } = await import('./cli/handlers/plugins.js');
      await pluginDisableHandler(plugin, options);
    });

  // Plugin update command
  pluginCmd
    .command('update <plugin>')
    .description('Update a plugin to the latest version (restart required to apply)')
    .option('-s, --scope <scope>', `Installation scope: ${VALID_UPDATE_SCOPES.join(', ')} (default: user)`)
    .addOption(coworkOption())
    .action(async (plugin: string, options: { scope?: string; cowork?: boolean }) => {
      const { pluginUpdateHandler } = await import('./cli/handlers/plugins.js');
      await pluginUpdateHandler(plugin, options);
    });
  // END ANT-ONLY

  // Setup token command
  program
    .command('setup-token')
    .description('Set up a long-lived authentication token (requires Claude subscription)')
    .action(async () => {
      const [{ setupTokenHandler }, { createRoot }] = await Promise.all([
        import('./cli/handlers/util.js'),
        import('@anthropic/ink'),
      ]);
      const root = await createRoot(getBaseRenderOptions(false));
      await setupTokenHandler(root);
    });

  // Agents command - list configured agents
  program
    .command('agents')
    .description('List configured agents')
    .option('--setting-sources <sources>', 'Comma-separated list of setting sources to load (user, project, local).')
    .action(async () => {
      const { agentsHandler } = await import('./cli/handlers/agents.js');
      await agentsHandler();
      process.exit(0);
    });

  if (feature('TRANSCRIPT_CLASSIFIER')) {
    // Skip when tengu_auto_mode_config.enabled === 'disabled' (circuit breaker).
    // Reads from disk cache — GrowthBook isn't initialized at registration time.
    if (getAutoModeEnabledStateIfCached() !== 'disabled') {
      const autoModeCmd = program.command('auto-mode').description('Inspect auto mode classifier configuration');

      autoModeCmd
        .command('defaults')
        .description('Print the default auto mode environment, allow, and deny rules as JSON')
        .action(async () => {
          const { autoModeDefaultsHandler } = await import('./cli/handlers/autoMode.js');
          autoModeDefaultsHandler();
          process.exit(0);
        });

      autoModeCmd
        .command('config')
        .description('Print the effective auto mode config as JSON: your settings where set, defaults otherwise')
        .action(async () => {
          const { autoModeConfigHandler } = await import('./cli/handlers/autoMode.js');
          autoModeConfigHandler();
          process.exit(0);
        });

      autoModeCmd
        .command('critique')
        .description('Get AI feedback on your custom auto mode rules')
        .option('--model <model>', 'Override which model is used')
        .action(async options => {
          const { autoModeCritiqueHandler } = await import('./cli/handlers/autoMode.js');
          await autoModeCritiqueHandler(options);
          process.exit();
        });
    }
  }

  // claude autonomy — CLI subcommands mirroring /autonomy slash command
  {
    const autonomyCmd = program.command('autonomy').description('Inspect and manage automatic autonomy runs and flows');

    autonomyCmd
      .command('status')
      .description('Print autonomy run, flow, team, pipe, and remote-control status')
      .option('--deep', 'Include teams, pipes, daemon, and remote-control sections')
      .action(async (options: { deep?: boolean }) => {
        const { autonomyStatusHandler } = await import('./cli/handlers/autonomy.js');
        await autonomyStatusHandler(options);
        process.exit(0);
      });

    autonomyCmd
      .command('runs [limit]')
      .description('List recent autonomy runs')
      .action(async (limit?: string) => {
        const { autonomyRunsHandler } = await import('./cli/handlers/autonomy.js');
        await autonomyRunsHandler(limit);
        process.exit(0);
      });

    autonomyCmd
      .command('flows [limit]')
      .description('List recent autonomy flows')
      .action(async (limit?: string) => {
        const { autonomyFlowsHandler } = await import('./cli/handlers/autonomy.js');
        await autonomyFlowsHandler(limit);
        process.exit(0);
      });

    const flowCmd = autonomyCmd
      .command('flow <flowId>')
      .description('Inspect a single autonomy flow')
      .action(async (flowId: string) => {
        const { autonomyFlowHandler } = await import('./cli/handlers/autonomy.js');
        await autonomyFlowHandler(flowId);
        process.exit(0);
      });

    flowCmd
      .command('cancel <flowId>')
      .description('Cancel a queued, waiting, or running autonomy flow')
      .action(async (flowId: string) => {
        const { autonomyFlowCancelHandler } = await import('./cli/handlers/autonomy.js');
        await autonomyFlowCancelHandler(flowId);
        process.exit(0);
      });

    flowCmd
      .command('resume <flowId>')
      .description('Resume a waiting autonomy flow')
      .action(async (flowId: string) => {
        const { autonomyFlowResumeHandler } = await import('./cli/handlers/autonomy.js');
        await autonomyFlowResumeHandler(flowId);
        process.exit(0);
      });
  }

  // Remote Control command — connect local environment to claude.ai/code.
  // The actual command is intercepted by the fast-path in cli.tsx before
  // Commander.js runs, so this registration exists only for help output.
  // Always hidden: isBridgeEnabled() at this point (before enableConfigs)
  // would throw inside isClaudeAISubscriber → getGlobalConfig and return
  // false via the try/catch — but not before paying ~65ms of side effects
  // (25ms settings Zod parse + 40ms sync `security` keychain subprocess).
  // The dynamic visibility never worked; the command was always hidden.
  if (feature('BRIDGE_MODE')) {
    program
      .command('remote-control', { hidden: true })
      .alias('rc')
      .description('Connect your local environment for remote-control sessions via claude.ai/code')
      .action(async () => {
        // Unreachable — cli.tsx fast-path handles this command before main.tsx loads.
        // If somehow reached, delegate to bridgeMain.
        const { bridgeMain } = await import('./bridge/bridgeMain.js');
        await bridgeMain(process.argv.slice(3));
      });
  }

  if (feature('KAIROS')) {
    program
      .command('assistant [sessionId]')
      .description(
        'Attach the REPL as a client to a running bridge session. Discovers sessions via API if no sessionId given.',
      )
      .action(() => {
        // Argv rewriting above should have consumed `assistant [id]`
        // before commander runs. Reaching here means a root flag came first
        // (e.g. `--debug assistant`) and the position-0 predicate
        // didn't match. Print usage like the ssh stub does.
        process.stderr.write(
          'Usage: claude assistant [sessionId]\n\n' +
            'Attach the REPL as a viewer client to a running bridge session.\n' +
            'Omit sessionId to discover and pick from available sessions.\n',
        );
        process.exit(1);
      });
  }

  // Doctor command - check installation health
  program
    .command('doctor')
    .description(
      'Check the health of your Claude Code auto-updater. Note: The workspace trust dialog is skipped and stdio servers from .mcp.json are spawned for health checks. Only use this command in directories you trust.',
    )
    .action(async () => {
      const [{ doctorHandler }, { createRoot }] = await Promise.all([
        import('./cli/handlers/util.js'),
        import('@anthropic/ink'),
      ]);
      const root = await createRoot(getBaseRenderOptions(false));
      await doctorHandler(root);
    });

  // claude up — run the project's CLAUDE.md "# claude up" setup instructions.
  if (process.env.USER_TYPE === 'ant') {
    program
      .command('up')
      .description(
        '[ANT-ONLY] Initialize or upgrade the local dev environment using the "# claude up" section of the nearest CLAUDE.md',
      )
      .action(async () => {
        const { up } = await import('src/cli/up.js');
        await up();
      });
  }

  // claude rollback (ant-only)
  // Rolls back to previous releases
  if (process.env.USER_TYPE === 'ant') {
    program
      .command('rollback [target]')
      .description(
        '[ANT-ONLY] Roll back to a previous release\n\nExamples:\n  claude rollback                                    Go 1 version back from current\n  claude rollback 3                                  Go 3 versions back from current\n  claude rollback 2.0.73-dev.20251217.t190658        Roll back to a specific version',
      )
      .option('-l, --list', 'List recent published versions with ages')
      .option('--dry-run', 'Show what would be installed without installing')
      .option('--safe', 'Roll back to the server-pinned safe version (set by oncall during incidents)')
      .action(
        async (
          target?: string,
          options?: {
            list?: boolean;
            dryRun?: boolean;
            safe?: boolean;
          },
        ) => {
          const { rollback } = await import('src/cli/rollback.js');
          await rollback(target, options);
        },
      );
  }

  // claude install
  program
    .command('install [target]')
    .description(
      'Install Claude Code native build. Use [target] to specify version (stable, latest, or specific version)',
    )
    .option('--force', 'Force installation even if already installed')
    .action(async (target: string | undefined, options: { force?: boolean }) => {
      const { installHandler } = await import('./cli/handlers/util.js');
      await installHandler(target, options);
    });

  // claude update — update ccb to the latest version via npm or bun
  program
    .command('update')
    .description('Update claude-code-best (ccb) to the latest version')
    .action(async () => {
      const { updateCCB } = await import('./cli/updateCCB.js');
      await updateCCB();
    });

  // ant-only commands
  if (process.env.USER_TYPE === 'ant') {
    const validateLogId = (value: string) => {
      const maybeSessionId = validateUuid(value);
      if (maybeSessionId) return maybeSessionId;
      return Number(value);
    };
    // claude log
    program
      .command('log')
      .description('[ANT-ONLY] Manage conversation logs.')
      .argument(
        '[number|sessionId]',
        'A number (0, 1, 2, etc.) to display a specific log, or the sesssion ID (uuid) of a log',
        validateLogId,
      )
      .action(async (logId: string | number | undefined) => {
        const { logHandler } = await import('./cli/handlers/ant.js');
        await logHandler(logId);
      });

    // claude error
    program
      .command('error')
      .description(
        '[ANT-ONLY] View error logs. Optionally provide a number (0, -1, -2, etc.) to display a specific log.',
      )
      .argument('[number]', 'A number (0, 1, 2, etc.) to display a specific log', parseInt)
      .action(async (number: number | undefined) => {
        const { errorHandler } = await import('./cli/handlers/ant.js');
        await errorHandler(number);
      });

    // claude export
    program
      .command('export')
      .description('[ANT-ONLY] Export a conversation to a text file.')
      .usage('<source> <outputFile>')
      .argument('<source>', 'Session ID, log index (0, 1, 2...), or path to a .json/.jsonl log file')
      .argument('<outputFile>', 'Output file path for the exported text')
      .addHelpText(
        'after',
        `
Examples:
  $ claude export 0 conversation.txt                Export conversation at log index 0
  $ claude export <uuid> conversation.txt           Export conversation by session ID
  $ claude export input.json output.txt             Render JSON log file to text
  $ claude export <uuid>.jsonl output.txt           Render JSONL session file to text`,
      )
      .action(async (source: string, outputFile: string) => {
        const { exportHandler } = await import('./cli/handlers/ant.js');
        await exportHandler(source, outputFile);
      });

    if (process.env.USER_TYPE === 'ant') {
      const taskCmd = program.command('task').description('[ANT-ONLY] Manage task list tasks');

      taskCmd
        .command('create <subject>')
        .description('Create a new task')
        .option('-d, --description <text>', 'Task description')
        .option('-l, --list <id>', 'Task list ID (defaults to "tasklist")')
        .action(async (subject: string, opts: { description?: string; list?: string }) => {
          const { taskCreateHandler } = await import('./cli/handlers/ant.js');
          await taskCreateHandler(subject, opts);
        });

      taskCmd
        .command('list')
        .description('List all tasks')
        .option('-l, --list <id>', 'Task list ID (defaults to "tasklist")')
        .option('--pending', 'Show only pending tasks')
        .option('--json', 'Output as JSON')
        .action(async (opts: { list?: string; pending?: boolean; json?: boolean }) => {
          const { taskListHandler } = await import('./cli/handlers/ant.js');
          await taskListHandler(opts);
        });

      taskCmd
        .command('get <id>')
        .description('Get details of a task')
        .option('-l, --list <id>', 'Task list ID (defaults to "tasklist")')
        .action(async (id: string, opts: { list?: string }) => {
          const { taskGetHandler } = await import('./cli/handlers/ant.js');
          await taskGetHandler(id, opts);
        });

      taskCmd
        .command('update <id>')
        .description('Update a task')
        .option('-l, --list <id>', 'Task list ID (defaults to "tasklist")')
        .option('-s, --status <status>', `Set status (${TASK_STATUSES.join(', ')})`)
        .option('--subject <text>', 'Update subject')
        .option('-d, --description <text>', 'Update description')
        .option('--owner <agentId>', 'Set owner')
        .option('--clear-owner', 'Clear owner')
        .action(
          async (
            id: string,
            opts: {
              list?: string;
              status?: string;
              subject?: string;
              description?: string;
              owner?: string;
              clearOwner?: boolean;
            },
          ) => {
            const { taskUpdateHandler } = await import('./cli/handlers/ant.js');
            await taskUpdateHandler(id, opts);
          },
        );

      taskCmd
        .command('dir')
        .description('Show the tasks directory path')
        .option('-l, --list <id>', 'Task list ID (defaults to "tasklist")')
        .action(async (opts: { list?: string }) => {
          const { taskDirHandler } = await import('./cli/handlers/ant.js');
          await taskDirHandler(opts);
        });
    }

    // claude completion <shell>
    program
      .command('completion <shell>', { hidden: true })
      .description('Generate shell completion script (bash, zsh, or fish)')
      .option('--output <file>', 'Write completion script directly to a file instead of stdout')
      .action(async (shell: string, opts: { output?: string }) => {
        const { completionHandler } = await import('./cli/handlers/ant.js');
        await completionHandler(shell, opts, program);
      });
  }

  profileCheckpoint('run_before_parse');
  await program.parseAsync(process.argv);
  profileCheckpoint('run_after_parse');

  // Record final checkpoint for total_time calculation
  profileCheckpoint('main_after_run');

  // Log startup perf to Statsig (sampled) and output detailed report if enabled
  profileReport();

  return program;
}

async function logTenguInit({
  hasInitialPrompt,
  hasStdin,
  verbose,
  debug,
  debugToStderr,
  print,
  outputFormat,
  inputFormat,
  numAllowedTools,
  numDisallowedTools,
  mcpClientCount,
  worktreeEnabled,
  skipWebFetchPreflight,
  githubActionInputs,
  dangerouslySkipPermissionsPassed,
  permissionMode,
  modeIsBypass,
  allowDangerouslySkipPermissionsPassed,
  systemPromptFlag,
  appendSystemPromptFlag,
  thinkingConfig,
  assistantActivationPath,
}: {
  hasInitialPrompt: boolean;
  hasStdin: boolean;
  verbose: boolean;
  debug: boolean;
  debugToStderr: boolean;
  print: boolean;
  outputFormat: string;
  inputFormat: string;
  numAllowedTools: number;
  numDisallowedTools: number;
  mcpClientCount: number;
  worktreeEnabled: boolean;
  skipWebFetchPreflight: boolean | undefined;
  githubActionInputs: string | undefined;
  dangerouslySkipPermissionsPassed: boolean;
  permissionMode: string;
  modeIsBypass: boolean;
  allowDangerouslySkipPermissionsPassed: boolean;
  systemPromptFlag: 'file' | 'flag' | undefined;
  appendSystemPromptFlag: 'file' | 'flag' | undefined;
  thinkingConfig: ThinkingConfig;
  assistantActivationPath: string | undefined;
}): Promise<void> {
  try {
    logEvent('tengu_init', {
      entrypoint: 'claude' as AnalyticsMetadata_I_VERIFIED_THIS_IS_NOT_CODE_OR_FILEPATHS,
      hasInitialPrompt,
      hasStdin,
      verbose,
      debug,
      debugToStderr,
      print,
      outputFormat: outputFormat as AnalyticsMetadata_I_VERIFIED_THIS_IS_NOT_CODE_OR_FILEPATHS,
      inputFormat: inputFormat as AnalyticsMetadata_I_VERIFIED_THIS_IS_NOT_CODE_OR_FILEPATHS,
      numAllowedTools,
      numDisallowedTools,
      mcpClientCount,
      worktree: worktreeEnabled,
      skipWebFetchPreflight,
      ...(githubActionInputs && {
        githubActionInputs: githubActionInputs as AnalyticsMetadata_I_VERIFIED_THIS_IS_NOT_CODE_OR_FILEPATHS,
      }),
      dangerouslySkipPermissionsPassed,
      permissionMode: permissionMode as AnalyticsMetadata_I_VERIFIED_THIS_IS_NOT_CODE_OR_FILEPATHS,
      modeIsBypass,
      inProtectedNamespace: isInProtectedNamespace(),
      allowDangerouslySkipPermissionsPassed,
      thinkingType: thinkingConfig.type as AnalyticsMetadata_I_VERIFIED_THIS_IS_NOT_CODE_OR_FILEPATHS,
      ...(thinkingConfig.type === 'enabled' && {
        thinkingBudgetTokens: thinkingConfig.budgetTokens,
      }),
      ...(systemPromptFlag && {
        systemPromptFlag: systemPromptFlag as AnalyticsMetadata_I_VERIFIED_THIS_IS_NOT_CODE_OR_FILEPATHS,
      }),
      ...(appendSystemPromptFlag && {
        appendSystemPromptFlag: appendSystemPromptFlag as AnalyticsMetadata_I_VERIFIED_THIS_IS_NOT_CODE_OR_FILEPATHS,
      }),
      is_simple: isBareMode() || undefined,
      is_coordinator: feature('COORDINATOR_MODE') && coordinatorModeModule?.isCoordinatorMode() ? true : undefined,
      ...(assistantActivationPath && {
        assistantActivationPath: assistantActivationPath as AnalyticsMetadata_I_VERIFIED_THIS_IS_NOT_CODE_OR_FILEPATHS,
      }),
      autoUpdatesChannel: (getInitialSettings().autoUpdatesChannel ??
        'latest') as AnalyticsMetadata_I_VERIFIED_THIS_IS_NOT_CODE_OR_FILEPATHS,
      ...(process.env.USER_TYPE === 'ant'
        ? (() => {
            const cwd = getCwd();
            const gitRoot = findGitRoot(cwd);
            const rp = gitRoot ? relative(gitRoot, cwd) || '.' : undefined;
            return rp
              ? {
                  relativeProjectPath: rp as AnalyticsMetadata_I_VERIFIED_THIS_IS_NOT_CODE_OR_FILEPATHS,
                }
              : {};
          })()
        : {}),
    });
  } catch (error) {
    logError(error);
  }
}

function maybeActivateProactive(options: unknown): void {
  if (
    (feature('PROACTIVE') || feature('KAIROS')) &&
    ((options as { proactive?: boolean }).proactive || isEnvTruthy(process.env.CLAUDE_CODE_PROACTIVE))
  ) {
    // eslint-disable-next-line @typescript-eslint/no-require-imports
    const proactiveModule = require('./proactive/index.js');
    if (!proactiveModule.isProactiveActive()) {
      proactiveModule.activateProactive('command');
    }
  }
}

function maybeActivateBrief(options: unknown): void {
  if (!(feature('KAIROS') || feature('KAIROS_BRIEF'))) return;
  const briefFlag = (options as { brief?: boolean }).brief;
  const briefEnv = isEnvTruthy(process.env.CLAUDE_CODE_BRIEF);
  if (!briefFlag && !briefEnv) return;
  // --brief / CLAUDE_CODE_BRIEF are explicit opt-ins: check entitlement,
  // then set userMsgOptIn to activate the tool + prompt section. The env
  // var also grants entitlement (isBriefEntitled() reads it), so setting
  // CLAUDE_CODE_BRIEF=1 alone force-enables for dev/testing — no GB gate
  // needed. initialIsBriefOnly reads getUserMsgOptIn() directly.
  // Conditional require: static import would leak the tool name string
  // into external builds via BriefTool.ts → prompt.ts.
  /* eslint-disable @typescript-eslint/no-require-imports */
  const { isBriefEntitled } =
    require('@claude-code-best/builtin-tools/tools/BriefTool/BriefTool.js') as typeof import('@claude-code-best/builtin-tools/tools/BriefTool/BriefTool.js');
  /* eslint-enable @typescript-eslint/no-require-imports */
  const entitled = isBriefEntitled();
  if (entitled) {
    setUserMsgOptIn(true);
  }
  // Fire unconditionally once intent is seen: enabled=false captures the
  // "user tried but was gated" failure mode in Datadog.
  logEvent('tengu_brief_mode_enabled', {
    enabled: entitled,
    gated: !entitled,
    source: (briefEnv ? 'env' : 'flag') as AnalyticsMetadata_I_VERIFIED_THIS_IS_NOT_CODE_OR_FILEPATHS,
  });
}

function resetCursor() {
  const terminal = process.stderr.isTTY ? process.stderr : process.stdout.isTTY ? process.stdout : undefined;
  terminal?.write(SHOW_CURSOR);
}

type TeammateOptions = {
  agentId?: string;
  agentName?: string;
  teamName?: string;
  agentColor?: string;
  planModeRequired?: boolean;
  parentSessionId?: string;
  teammateMode?: 'auto' | 'tmux' | 'in-process';
  agentType?: string;
};

function extractTeammateOptions(options: unknown): TeammateOptions {
  if (typeof options !== 'object' || options === null) {
    return {};
  }
  const opts = options as Record<string, unknown>;
  const teammateMode = opts.teammateMode;
  return {
    agentId: typeof opts.agentId === 'string' ? opts.agentId : undefined,
    agentName: typeof opts.agentName === 'string' ? opts.agentName : undefined,
    teamName: typeof opts.teamName === 'string' ? opts.teamName : undefined,
    agentColor: typeof opts.agentColor === 'string' ? opts.agentColor : undefined,
    planModeRequired: typeof opts.planModeRequired === 'boolean' ? opts.planModeRequired : undefined,
    parentSessionId: typeof opts.parentSessionId === 'string' ? opts.parentSessionId : undefined,
    teammateMode:
      teammateMode === 'auto' || teammateMode === 'tmux' || teammateMode === 'in-process' ? teammateMode : undefined,
    agentType: typeof opts.agentType === 'string' ? opts.agentType : undefined,
  };
}
