212 lines
6.2 KiB
TypeScript
212 lines
6.2 KiB
TypeScript
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<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('.')
|
|
}
|