/** * Terminal Config Section - Custom terminal configurations with theme synchronization * * This component provides UI for enabling custom terminal prompts that automatically * sync with Automaker's 40 themes. It's an opt-in feature that generates shell configs * in .automaker/terminal/ without modifying user's existing RC files. */ import { useCallback, useEffect, useRef, useState } from 'react'; import { Label } from '@/components/ui/label'; import { Switch } from '@/components/ui/switch'; import { Textarea } from '@/components/ui/textarea'; import { Input } from '@/components/ui/input'; import { Button } from '@/components/ui/button'; import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue, } from '@/components/ui/select'; import { Wand2, GitBranch, Info, Plus, X } from 'lucide-react'; import { cn } from '@/lib/utils'; import { useAppStore } from '@/store/app-store'; import { toast } from 'sonner'; import { PromptPreview } from './prompt-preview'; import type { TerminalPromptTheme } from '@automaker/types'; import { PROMPT_THEME_CUSTOM_ID, PROMPT_THEME_PRESETS, getMatchingPromptThemeId, getPromptThemePreset, type PromptThemeConfig, } from './prompt-theme-presets'; import { useUpdateGlobalSettings } from '@/hooks/mutations/use-settings-mutations'; import { useGlobalSettings } from '@/hooks/queries/use-settings'; import { ConfirmDialog } from '@/components/ui/confirm-dialog'; export function TerminalConfigSection() { const PATH_DEPTH_MIN = 0; const PATH_DEPTH_MAX = 10; const ENV_VAR_UPDATE_DEBOUNCE_MS = 400; const ENV_VAR_ID_PREFIX = 'env'; const TERMINAL_RC_FILE_VERSION = 11; const { theme } = useAppStore(); const { data: globalSettings } = useGlobalSettings(); const updateGlobalSettings = useUpdateGlobalSettings({ showSuccessToast: false }); const envVarIdRef = useRef(0); const envVarUpdateTimeoutRef = useRef | null>(null); const createEnvVarEntry = useCallback( (key = '', value = '') => { envVarIdRef.current += 1; return { id: `${ENV_VAR_ID_PREFIX}-${envVarIdRef.current}`, key, value, }; }, [ENV_VAR_ID_PREFIX] ); const [localEnvVars, setLocalEnvVars] = useState< Array<{ id: string; key: string; value: string }> >(() => Object.entries(globalSettings?.terminalConfig?.customEnvVars || {}).map(([key, value]) => createEnvVarEntry(key, value) ) ); const [showEnableConfirm, setShowEnableConfirm] = useState(false); const clampPathDepth = (value: number) => Math.min(PATH_DEPTH_MAX, Math.max(PATH_DEPTH_MIN, value)); const defaultTerminalConfig = { enabled: false, customPrompt: true, promptFormat: 'standard' as const, promptTheme: PROMPT_THEME_CUSTOM_ID, showGitBranch: true, showGitStatus: true, showUserHost: true, showPath: true, pathStyle: 'full' as const, pathDepth: PATH_DEPTH_MIN, showTime: false, showExitStatus: false, customAliases: '', customEnvVars: {}, }; const terminalConfig = { ...defaultTerminalConfig, ...globalSettings?.terminalConfig, customAliases: globalSettings?.terminalConfig?.customAliases ?? defaultTerminalConfig.customAliases, customEnvVars: globalSettings?.terminalConfig?.customEnvVars ?? defaultTerminalConfig.customEnvVars, }; const promptThemeConfig: PromptThemeConfig = { promptFormat: terminalConfig.promptFormat, showGitBranch: terminalConfig.showGitBranch, showGitStatus: terminalConfig.showGitStatus, showUserHost: terminalConfig.showUserHost, showPath: terminalConfig.showPath, pathStyle: terminalConfig.pathStyle, pathDepth: terminalConfig.pathDepth, showTime: terminalConfig.showTime, showExitStatus: terminalConfig.showExitStatus, }; const storedPromptTheme = terminalConfig.promptTheme; const activePromptThemeId = storedPromptTheme === PROMPT_THEME_CUSTOM_ID ? PROMPT_THEME_CUSTOM_ID : (storedPromptTheme ?? getMatchingPromptThemeId(promptThemeConfig)); const isOmpTheme = storedPromptTheme !== undefined && storedPromptTheme !== PROMPT_THEME_CUSTOM_ID; const promptThemePreset = isOmpTheme ? getPromptThemePreset(storedPromptTheme as TerminalPromptTheme) : null; const applyEnabledUpdate = (enabled: boolean) => { // Ensure all required fields are present const updatedConfig = { enabled, customPrompt: terminalConfig.customPrompt, promptFormat: terminalConfig.promptFormat, showGitBranch: terminalConfig.showGitBranch, showGitStatus: terminalConfig.showGitStatus, showUserHost: terminalConfig.showUserHost, showPath: terminalConfig.showPath, pathStyle: terminalConfig.pathStyle, pathDepth: terminalConfig.pathDepth, showTime: terminalConfig.showTime, showExitStatus: terminalConfig.showExitStatus, promptTheme: terminalConfig.promptTheme ?? PROMPT_THEME_CUSTOM_ID, customAliases: terminalConfig.customAliases, customEnvVars: terminalConfig.customEnvVars, rcFileVersion: TERMINAL_RC_FILE_VERSION, }; updateGlobalSettings.mutate( { terminalConfig: updatedConfig }, { onSuccess: () => { toast.success( enabled ? 'Custom terminal configs enabled' : 'Custom terminal configs disabled', { description: enabled ? 'New terminals will use custom prompts' : '.automaker/terminal/ will be cleaned up', } ); }, onError: (error) => { console.error('[TerminalConfig] Failed to update settings:', error); toast.error('Failed to update terminal config', { description: error instanceof Error ? error.message : 'Unknown error', }); }, } ); }; useEffect(() => { setLocalEnvVars( Object.entries(globalSettings?.terminalConfig?.customEnvVars || {}).map(([key, value]) => createEnvVarEntry(key, value) ) ); }, [createEnvVarEntry, globalSettings?.terminalConfig?.customEnvVars]); useEffect(() => { return () => { if (envVarUpdateTimeoutRef.current) { clearTimeout(envVarUpdateTimeoutRef.current); } }; }, []); const handleToggleEnabled = async (enabled: boolean) => { if (enabled) { setShowEnableConfirm(true); return; } applyEnabledUpdate(false); }; const handleUpdateConfig = (updates: Partial) => { const nextPromptTheme = updates.promptTheme ?? PROMPT_THEME_CUSTOM_ID; updateGlobalSettings.mutate( { terminalConfig: { ...terminalConfig, ...updates, promptTheme: nextPromptTheme, }, }, { onError: (error) => { console.error('[TerminalConfig] Failed to update settings:', error); toast.error('Failed to update terminal config', { description: error instanceof Error ? error.message : 'Unknown error', }); }, } ); }; const scheduleEnvVarsUpdate = (envVarsObject: Record) => { if (envVarUpdateTimeoutRef.current) { clearTimeout(envVarUpdateTimeoutRef.current); } envVarUpdateTimeoutRef.current = setTimeout(() => { handleUpdateConfig({ customEnvVars: envVarsObject }); }, ENV_VAR_UPDATE_DEBOUNCE_MS); }; const handlePromptThemeChange = (themeId: string) => { if (themeId === PROMPT_THEME_CUSTOM_ID) { handleUpdateConfig({ promptTheme: PROMPT_THEME_CUSTOM_ID }); return; } const preset = getPromptThemePreset(themeId as TerminalPromptTheme); if (!preset) { handleUpdateConfig({ promptTheme: PROMPT_THEME_CUSTOM_ID }); return; } handleUpdateConfig({ ...preset.config, promptTheme: preset.id, }); }; const addEnvVar = () => { setLocalEnvVars([...localEnvVars, createEnvVarEntry()]); }; const removeEnvVar = (id: string) => { const newVars = localEnvVars.filter((envVar) => envVar.id !== id); setLocalEnvVars(newVars); // Update settings const envVarsObject = newVars.reduce( (acc, { key, value }) => { if (key) acc[key] = value; return acc; }, {} as Record ); scheduleEnvVarsUpdate(envVarsObject); }; const updateEnvVar = (id: string, field: 'key' | 'value', newValue: string) => { const newVars = localEnvVars.map((envVar) => envVar.id === id ? { ...envVar, [field]: newValue } : envVar ); setLocalEnvVars(newVars); // Validate and update settings (only if key is valid) const envVarsObject = newVars.reduce( (acc, { key, value }) => { // Only include vars with valid keys (alphanumeric + underscore) if (key && /^[a-zA-Z_][a-zA-Z0-9_]*$/.test(key)) { acc[key] = value; } return acc; }, {} as Record ); scheduleEnvVarsUpdate(envVarsObject); }; return (

Custom Terminal Configurations

Generate custom shell prompts that automatically sync with your app theme. Opt-in feature that creates configs in .automaker/terminal/ without modifying your existing RC files.

{/* Enable Toggle */}

Create theme-synced shell configs in .automaker/terminal/

{terminalConfig.enabled && ( <> {/* Info Box */}
How it works: Custom configs are applied to new terminals only. Your ~/.bashrc and ~/.zshrc are still loaded first. Close and reopen terminals to see changes.
{/* Custom Prompt Toggle */}

Override default shell prompt with themed version

handleUpdateConfig({ customPrompt: checked })} />
{terminalConfig.customPrompt && ( <> {/* Prompt Format */}
{isOmpTheme && (
{promptThemePreset?.label ?? 'Oh My Posh theme'} uses the oh-my-posh CLI for rendering. Ensure it's installed for the full theme. Prompt format and segment toggles are ignored while an OMP theme is selected.
)}
{/* Git Info Toggles */}
handleUpdateConfig({ showGitBranch: checked })} disabled={isOmpTheme} />
*
handleUpdateConfig({ showGitStatus: checked })} disabled={!terminalConfig.showGitBranch || isOmpTheme} />
{/* Prompt Segments */}
handleUpdateConfig({ showUserHost: checked })} disabled={isOmpTheme} />
~/
handleUpdateConfig({ showPath: checked })} disabled={isOmpTheme} />
handleUpdateConfig({ showTime: checked })} disabled={isOmpTheme} />
handleUpdateConfig({ showExitStatus: checked })} disabled={isOmpTheme} />
handleUpdateConfig({ pathDepth: clampPathDepth(Number(event.target.value) || 0), }) } disabled={!terminalConfig.showPath || isOmpTheme} />
{/* Live Preview */}
)} {/* Custom Aliases */}

Add shell aliases (one per line, e.g., alias ll='ls -la')