import { feature } from 'bun:bundle' import { getRemoteControlAtStartup } from '../../utils/config.js' import { EDITOR_MODES, NOTIFICATION_CHANNELS, TEAMMATE_MODES, } from '../../utils/configConstants.js' import { getModelOptions } from '../../utils/model/modelOptions.js' import { validateModel } from '../../utils/model/validateModel.js' import { THEME_NAMES, THEME_SETTINGS } from '../../utils/theme.js' /** AppState keys that can be synced for immediate UI effect */ type SyncableAppStateKey = 'verbose' | 'mainLoopModel' | 'thinkingEnabled' type SettingConfig = { source: 'global' | 'settings' type: 'boolean' | 'string' description: string path?: string[] options?: readonly string[] getOptions?: () => string[] appStateKey?: SyncableAppStateKey /** Async validation called when writing/setting a value */ validateOnWrite?: (v: unknown) => Promise<{ valid: boolean; error?: string }> /** Format value when reading/getting for display */ formatOnRead?: (v: unknown) => unknown } export const SUPPORTED_SETTINGS: Record = { theme: { source: 'global', type: 'string', description: 'Color theme for the UI', options: feature('AUTO_THEME') ? THEME_SETTINGS : THEME_NAMES, }, editorMode: { source: 'global', type: 'string', description: 'Key binding mode', options: EDITOR_MODES, }, verbose: { source: 'global', type: 'boolean', description: 'Show detailed debug output', appStateKey: 'verbose', }, preferredNotifChannel: { source: 'global', type: 'string', description: 'Preferred notification channel', options: NOTIFICATION_CHANNELS, }, autoCompactEnabled: { source: 'global', type: 'boolean', description: 'Auto-compact when context is full', }, autoMemoryEnabled: { source: 'settings', type: 'boolean', description: 'Enable auto-memory', }, autoDreamEnabled: { source: 'settings', type: 'boolean', description: 'Enable background memory consolidation', }, fileCheckpointingEnabled: { source: 'global', type: 'boolean', description: 'Enable file checkpointing for code rewind', }, showTurnDuration: { source: 'global', type: 'boolean', description: 'Show turn duration message after responses (e.g., "Cooked for 1m 6s")', }, terminalProgressBarEnabled: { source: 'global', type: 'boolean', description: 'Show OSC 9;4 progress indicator in supported terminals', }, todoFeatureEnabled: { source: 'global', type: 'boolean', description: 'Enable todo/task tracking', }, model: { source: 'settings', type: 'string', description: 'Override the default model', appStateKey: 'mainLoopModel', getOptions: () => { try { return getModelOptions() .filter(o => o.value !== null) .map(o => o.value as string) } catch { return ['sonnet', 'opus', 'haiku'] } }, validateOnWrite: v => validateModel(String(v)), formatOnRead: v => (v === null ? 'default' : v), }, alwaysThinkingEnabled: { source: 'settings', type: 'boolean', description: 'Enable extended thinking (false to disable)', appStateKey: 'thinkingEnabled', }, 'permissions.defaultMode': { source: 'settings', type: 'string', description: 'Default permission mode for tool usage', options: feature('TRANSCRIPT_CLASSIFIER') ? ['default', 'plan', 'acceptEdits', 'dontAsk', 'auto'] : ['default', 'plan', 'acceptEdits', 'dontAsk'], }, language: { source: 'settings', type: 'string', description: 'Preferred language for Claude responses and voice dictation (e.g., "japanese", "spanish")', }, teammateMode: { source: 'global', type: 'string', description: 'How to spawn teammates: "tmux" for traditional tmux, "in-process" for same process, "auto" to choose automatically', options: TEAMMATE_MODES, }, ...(process.env.USER_TYPE === 'ant' ? { classifierPermissionsEnabled: { source: 'settings' as const, type: 'boolean' as const, description: 'Enable AI-based classification for Bash(prompt:...) permission rules', }, } : {}), ...(feature('VOICE_MODE') ? { voiceEnabled: { source: 'settings' as const, type: 'boolean' as const, description: 'Enable voice dictation (hold-to-talk)', }, } : {}), ...(feature('BRIDGE_MODE') ? { remoteControlAtStartup: { source: 'global' as const, type: 'boolean' as const, description: 'Enable Remote Control for all sessions (true | false | default)', formatOnRead: () => getRemoteControlAtStartup(), }, } : {}), ...(feature('KAIROS') || feature('KAIROS_PUSH_NOTIFICATION') ? { taskCompleteNotifEnabled: { source: 'global' as const, type: 'boolean' as const, description: 'Push to your mobile device when idle after Claude finishes (requires Remote Control)', }, inputNeededNotifEnabled: { source: 'global' as const, type: 'boolean' as const, description: 'Push to your mobile device when a permission prompt or question is waiting (requires Remote Control)', }, agentPushNotifEnabled: { source: 'global' as const, type: 'boolean' as const, description: 'Allow Claude to push to your mobile device when it deems it appropriate (requires Remote Control)', }, } : {}), } export function isSupported(key: string): boolean { return key in SUPPORTED_SETTINGS } export function getConfig(key: string): SettingConfig | undefined { return SUPPORTED_SETTINGS[key] } export function getAllKeys(): string[] { return Object.keys(SUPPORTED_SETTINGS) } export function getOptionsForSetting(key: string): string[] | undefined { const config = SUPPORTED_SETTINGS[key] if (!config) return undefined if (config.options) return [...config.options] if (config.getOptions) return config.getOptions() return undefined } export function getPath(key: string): string[] { const config = SUPPORTED_SETTINGS[key] return config?.path ?? key.split('.') }