diff --git a/apps/frontend/src/renderer/components/settings/DevToolsSettings.tsx b/apps/frontend/src/renderer/components/settings/DevToolsSettings.tsx index 1691f697..0ccef573 100644 --- a/apps/frontend/src/renderer/components/settings/DevToolsSettings.tsx +++ b/apps/frontend/src/renderer/components/settings/DevToolsSettings.tsx @@ -365,6 +365,31 @@ export function DevToolsSettings({ settings, onSettingsChange }: DevToolsSetting )} + {/* Auto-name Claude Terminals Toggle */} +
+
+
+ +

+ {t('devtools.autoNameClaude.description', 'Use AI to generate a descriptive name for Claude terminals based on your first message')} +

+
+ {/* Fallback to true for existing users who don't have this setting in persisted config */} + { + onSettingsChange({ + ...settings, + autoNameClaudeTerminals: checked + }); + }} + /> +
+
+ {/* YOLO Mode Toggle */}
diff --git a/apps/frontend/src/renderer/components/terminal/useAutoNaming.ts b/apps/frontend/src/renderer/components/terminal/useAutoNaming.ts index d9b1122b..e4a1038c 100644 --- a/apps/frontend/src/renderer/components/terminal/useAutoNaming.ts +++ b/apps/frontend/src/renderer/components/terminal/useAutoNaming.ts @@ -11,51 +11,72 @@ export function useAutoNaming({ terminalId, cwd }: UseAutoNamingOptions) { const lastCommandRef = useRef(''); const autoNameTimeoutRef = useRef(null); const autoNameTerminals = useSettingsStore((state) => state.settings.autoNameTerminals); + const autoNameClaudeTerminals = useSettingsStore((state) => state.settings.autoNameClaudeTerminals); const terminal = useTerminalStore((state) => state.terminals.find((t) => t.id === terminalId)); const updateTerminal = useTerminalStore((state) => state.updateTerminal); + const setClaudeNamedOnce = useTerminalStore((state) => state.setClaudeNamedOnce); const triggerAutoNaming = useCallback(async () => { - if (!autoNameTerminals || terminal?.isClaudeMode || !lastCommandRef.current.trim()) { + // Check if we have a command to base the name on + if (!lastCommandRef.current.trim()) { return; } - const command = lastCommandRef.current.trim(); - const commandLower = command.toLowerCase(); - const firstWord = commandLower.split(/\s+/)[0]; + // Handle Claude mode vs regular terminal mode + if (terminal?.isClaudeMode) { + // In Claude mode: only rename if autoNameClaudeTerminals is enabled AND we haven't named yet + if (!autoNameClaudeTerminals || terminal?.claudeNamedOnce) { + return; + } + } else { + // Regular terminal mode: use the standard autoNameTerminals setting + if (!autoNameTerminals) { + return; + } + } - // Skip very short commands + const command = lastCommandRef.current.trim(); + + // Skip very short commands/messages if (command.length < 3) { return; } - // Skip common shell/navigation commands that don't represent meaningful work. - // These commands are too generic to produce useful terminal names - they don't indicate - // a specific task or purpose. For example, "git" could be any git operation, - // "npm" could be install, run, or test. Meaningful names come from project-specific - // commands like "npm run build:prod" or application-specific scripts. - const skipCommands = [ - // Navigation & file listing - 'ls', 'cd', 'll', 'la', 'pwd', 'dir', 'tree', - // Shell control - 'exit', 'clear', 'cls', 'reset', 'history', - // Claude CLI - naming should come from the task description inside Claude, not the launch command - 'claude', - // Common dev tools that are too generic - 'git', 'npm', 'yarn', 'pnpm', 'node', 'python', 'pip', 'cargo', 'go', - 'docker', 'kubectl', 'make', 'cmake', - // Package managers - 'brew', 'apt', 'yum', 'pacman', 'choco', 'scoop', 'winget', - // Editors - 'vim', 'nvim', 'nano', 'code', 'cursor', - // System commands - 'cat', 'head', 'tail', 'less', 'more', 'grep', 'find', 'which', 'where', - 'echo', 'env', 'export', 'set', 'unset', 'alias', 'source', - 'chmod', 'chown', 'mkdir', 'rmdir', 'rm', 'cp', 'mv', 'touch', - 'man', 'help', 'whoami', 'hostname', 'date', 'time', 'top', 'htop', 'ps', - ]; + // In Claude mode, messages are natural language prompts, not shell commands + // Skip the shell command filtering since we want to name based on the first prompt + if (!terminal?.isClaudeMode) { + const commandLower = command.toLowerCase(); + const firstWord = commandLower.split(/\s+/)[0]; - if (skipCommands.includes(firstWord)) { - return; + // Skip common shell/navigation commands that don't represent meaningful work. + // These commands are too generic to produce useful terminal names - they don't indicate + // a specific task or purpose. For example, "git" could be any git operation, + // "npm" could be install, run, or test. Meaningful names come from project-specific + // commands like "npm run build:prod" or application-specific scripts. + const skipCommands = [ + // Navigation & file listing + 'ls', 'cd', 'll', 'la', 'pwd', 'dir', 'tree', + // Shell control + 'exit', 'clear', 'cls', 'reset', 'history', + // Claude CLI - naming should come from the task description inside Claude, not the launch command + 'claude', + // Common dev tools that are too generic + 'git', 'npm', 'yarn', 'pnpm', 'node', 'python', 'pip', 'cargo', 'go', + 'docker', 'kubectl', 'make', 'cmake', + // Package managers + 'brew', 'apt', 'yum', 'pacman', 'choco', 'scoop', 'winget', + // Editors + 'vim', 'nvim', 'nano', 'code', 'cursor', + // System commands + 'cat', 'head', 'tail', 'less', 'more', 'grep', 'find', 'which', 'where', + 'echo', 'env', 'export', 'set', 'unset', 'alias', 'source', + 'chmod', 'chown', 'mkdir', 'rmdir', 'rm', 'cp', 'mv', 'touch', + 'man', 'help', 'whoami', 'hostname', 'date', 'time', 'top', 'htop', 'ps', + ]; + + if (skipCommands.includes(firstWord)) { + return; + } } try { @@ -64,11 +85,18 @@ export function useAutoNaming({ terminalId, cwd }: UseAutoNamingOptions) { updateTerminal(terminalId, { title: result.data }); // Sync to main process so title persists across hot reloads window.electronAPI.setTerminalTitle(terminalId, result.data); + + // Mark Claude terminal as named once to prevent repeated renames + // Re-fetch terminal state after async operation to avoid stale closure + const currentTerminal = useTerminalStore.getState().terminals.find((t) => t.id === terminalId); + if (currentTerminal?.isClaudeMode) { + setClaudeNamedOnce(terminalId, true); + } } } catch (error) { console.warn('[Terminal] Auto-naming failed:', error); } - }, [autoNameTerminals, terminal?.isClaudeMode, terminal?.cwd, cwd, terminalId, updateTerminal]); + }, [autoNameTerminals, autoNameClaudeTerminals, terminal?.isClaudeMode, terminal?.claudeNamedOnce, terminal?.cwd, cwd, terminalId, updateTerminal, setClaudeNamedOnce]); const handleCommandEnter = useCallback((command: string) => { lastCommandRef.current = command; diff --git a/apps/frontend/src/renderer/stores/terminal-store.ts b/apps/frontend/src/renderer/stores/terminal-store.ts index eb4b7dd5..8b156371 100644 --- a/apps/frontend/src/renderer/stores/terminal-store.ts +++ b/apps/frontend/src/renderer/stores/terminal-store.ts @@ -94,6 +94,7 @@ export interface Terminal { isClaudeBusy?: boolean; // Whether Claude Code is actively processing (for visual indicator) pendingClaudeResume?: boolean; // Whether this terminal has a pending Claude resume (deferred until tab activated) displayOrder?: number; // Display order for tab persistence (lower = further left) + claudeNamedOnce?: boolean; // Whether this Claude terminal has been auto-named based on initial message (prevents repeated naming) } interface TerminalLayout { @@ -126,6 +127,7 @@ interface TerminalState { setWorktreeConfig: (id: string, config: TerminalWorktreeConfig | undefined) => void; setClaudeBusy: (id: string, isBusy: boolean) => void; setPendingClaudeResume: (id: string, pending: boolean) => void; + setClaudeNamedOnce: (id: string, named: boolean) => void; clearAllTerminals: () => void; setHasRestoredSessions: (value: boolean) => void; reorderTerminals: (activeId: string, overId: string) => void; @@ -316,8 +318,9 @@ export const useTerminalStore = create((set, get) => ({ ...t, isClaudeMode, status: isClaudeMode ? 'claude-active' : 'running', - // Reset busy state when leaving Claude mode - isClaudeBusy: isClaudeMode ? t.isClaudeBusy : undefined + // Reset busy state and naming flag when leaving Claude mode + isClaudeBusy: isClaudeMode ? t.isClaudeBusy : undefined, + claudeNamedOnce: isClaudeMode ? t.claudeNamedOnce : undefined } : t ), @@ -364,6 +367,14 @@ export const useTerminalStore = create((set, get) => ({ })); }, + setClaudeNamedOnce: (id: string, named: boolean) => { + set((state) => ({ + terminals: state.terminals.map((t) => + t.id === id ? { ...t, claudeNamedOnce: named } : t + ), + })); + }, + clearAllTerminals: () => { set({ terminals: [], activeTerminalId: null, hasRestoredSessions: false }); }, diff --git a/apps/frontend/src/shared/constants/config.ts b/apps/frontend/src/shared/constants/config.ts index ab915a16..e108a448 100644 --- a/apps/frontend/src/shared/constants/config.ts +++ b/apps/frontend/src/shared/constants/config.ts @@ -57,7 +57,9 @@ export const DEFAULT_APP_SETTINGS = { // Language preference (default to English) language: 'en' as const, // Anonymous error reporting (Sentry) - enabled by default to help improve the app - sentryEnabled: true + sentryEnabled: true, + // Auto-name Claude terminals based on initial message (enabled by default) + autoNameClaudeTerminals: true }; // ============================================ diff --git a/apps/frontend/src/shared/i18n/locales/en/settings.json b/apps/frontend/src/shared/i18n/locales/en/settings.json index 698d38f1..6c1942ba 100644 --- a/apps/frontend/src/shared/i18n/locales/en/settings.json +++ b/apps/frontend/src/shared/i18n/locales/en/settings.json @@ -262,6 +262,10 @@ "notInstalled": "Not installed", "detectedSummary": "Detected on your system:", "noToolsDetected": "No additional tools detected (VS Code and system terminal will be used)", + "autoNameClaude": { + "label": "Auto-name Claude terminals", + "description": "Use AI to generate a descriptive name for Claude terminals based on your first message" + }, "yoloMode": { "label": "YOLO Mode", "description": "Start Claude with --dangerously-skip-permissions flag, bypassing all safety prompts. Use with extreme caution.", diff --git a/apps/frontend/src/shared/i18n/locales/fr/settings.json b/apps/frontend/src/shared/i18n/locales/fr/settings.json index fb11b63f..0fcee50e 100644 --- a/apps/frontend/src/shared/i18n/locales/fr/settings.json +++ b/apps/frontend/src/shared/i18n/locales/fr/settings.json @@ -262,6 +262,10 @@ "notInstalled": "Non installé", "detectedSummary": "Détecté sur votre système :", "noToolsDetected": "Aucun outil supplémentaire détecté (VS Code et le terminal système seront utilisés)", + "autoNameClaude": { + "label": "Nommer automatiquement les terminaux Claude", + "description": "Utiliser l'IA pour générer un nom descriptif pour les terminaux Claude basé sur votre premier message" + }, "yoloMode": { "label": "Mode YOLO", "description": "Démarrer Claude avec le flag --dangerously-skip-permissions, contournant toutes les invites de sécurité. À utiliser avec une extrême prudence.", diff --git a/apps/frontend/src/shared/types/settings.ts b/apps/frontend/src/shared/types/settings.ts index 3ca86175..d107a66f 100644 --- a/apps/frontend/src/shared/types/settings.ts +++ b/apps/frontend/src/shared/types/settings.ts @@ -283,6 +283,8 @@ export interface AppSettings { dangerouslySkipPermissions?: boolean; // Anonymous error reporting (Sentry) - enabled by default to help improve the app sentryEnabled?: boolean; + // Auto-name Claude terminals based on initial message (only triggers once per session) + autoNameClaudeTerminals?: boolean; } // Auto-Claude Source Environment Configuration (for auto-claude repo .env)