| 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' |
|
|
| |
| type SyncableAppStateKey = 'verbose' | 'mainLoopModel' | 'thinkingEnabled' |
|
|
| type SettingConfig = { |
| source: 'global' | 'settings' |
| type: 'boolean' | 'string' |
| description: string |
| path?: string[] |
| options?: readonly string[] |
| getOptions?: () => string[] |
| appStateKey?: SyncableAppStateKey |
| |
| validateOnWrite?: (v: unknown) => Promise<{ valid: boolean; error?: string }> |
| |
| formatOnRead?: (v: unknown) => unknown |
| } |
|
|
| export const SUPPORTED_SETTINGS: Record<string, SettingConfig> = { |
| 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('.') |
| } |
|
|