import React, { useCallback, useEffect, useMemo, useState } from 'react'; import { Button } from '@renderer/components/ui/button'; import { Checkbox } from '@renderer/components/ui/checkbox'; import { Input } from '@renderer/components/ui/input'; import { Label } from '@renderer/components/ui/label'; import { Popover, PopoverAnchor, PopoverContent } from '@renderer/components/ui/popover'; import { parseCliArgs, PROTECTED_CLI_FLAGS } from '@shared/utils/cliArgsParser'; import { AlertTriangle, CheckCircle2, ChevronRight, Clock, Loader2, Terminal, XCircle, } from 'lucide-react'; interface AdvancedCliSectionProps { teamName: string; /** All CLI args from parent (model, effort, permissions, resume, etc.) */ internalArgs: string[]; worktreeEnabled: boolean; onWorktreeEnabledChange: (enabled: boolean) => void; worktreeName: string; onWorktreeNameChange: (name: string) => void; customArgs: string; onCustomArgsChange: (args: string) => void; } /** Infrastructure flags that are dimmed in command preview. */ const INFRA_FLAGS = new Set([ '--input-format', '--output-format', '--setting-sources', '--mcp-config', '--disallowedTools', '--verbose', ]); type ValidationState = 'idle' | 'loading' | 'success' | 'error'; type TokenType = 'command' | 'visible' | 'infra' | 'custom'; /** Map token type → Tailwind color class (pure function, no state dependency). */ const TOKEN_COLOR_CLASS: Record = { command: 'text-text', visible: 'text-text', infra: 'text-text-muted', custom: 'text-emerald-400', }; /** Read worktree history from localStorage for a given team. */ function readWorktreeHistory(teamName: string): string[] { try { const raw = localStorage.getItem(`team:worktreeHistory:${teamName}`); if (!raw) { return []; } const parsed: unknown = JSON.parse(raw); return Array.isArray(parsed) ? parsed.filter((item): item is string => typeof item === 'string') : []; } catch { return []; } } /** * Collapsible "Advanced" section for CreateTeamDialog and LaunchTeamDialog. * Contains: worktree checkbox with history, command preview, custom args + validate. */ export const AdvancedCliSection: React.FC = ({ teamName, internalArgs, worktreeEnabled, onWorktreeEnabledChange, worktreeName, onWorktreeNameChange, customArgs, onCustomArgsChange, }) => { const [isOpen, setIsOpen] = useState(false); const [validationState, setValidationState] = useState('idle'); const [validationMessage, setValidationMessage] = useState(null); const [showHistory, setShowHistory] = useState(false); // Read worktree history from localStorage; re-read when teamName changes const [worktreeHistory, setWorktreeHistory] = useState(() => readWorktreeHistory(teamName) ); useEffect(() => { setWorktreeHistory(readWorktreeHistory(teamName)); }, [teamName]); // Commit worktree name to history on blur const commitWorktreeName = useCallback(() => { const name = worktreeName.trim(); if (!name) return; setWorktreeHistory((prev) => { const next = [name, ...prev.filter((n) => n !== name)].slice(0, 10); localStorage.setItem(`team:worktreeHistory:${teamName}`, JSON.stringify(next)); return next; }); }, [worktreeName, teamName]); // Build command preview tokens const previewTokens = useMemo(() => { const tokens: { text: string; type: 'command' | 'visible' | 'infra' | 'custom' }[] = []; tokens.push({ text: 'claude', type: 'command' }); // Process internalArgs: classify each as visible or infra let i = 0; while (i < internalArgs.length) { const arg = internalArgs[i]; const isInfra = INFRA_FLAGS.has(arg); const type = isInfra ? 'infra' : 'visible'; tokens.push({ text: arg, type }); // Check if next token is the value for this flag (not starting with --) if (i + 1 < internalArgs.length && !internalArgs[i + 1].startsWith('--')) { tokens.push({ text: internalArgs[i + 1], type }); i += 2; } else { i += 1; } } // Worktree if (worktreeEnabled && worktreeName.trim()) { tokens.push({ text: '--worktree', type: 'visible' }); tokens.push({ text: worktreeName.trim(), type: 'visible' }); } // Custom args const parsed = parseCliArgs(customArgs); for (const t of parsed) { tokens.push({ text: t, type: 'custom' }); } return tokens; }, [internalArgs, worktreeEnabled, worktreeName, customArgs]); // Validate handler const handleValidate = useCallback(async () => { if (!customArgs.trim()) return; setValidationState('loading'); setValidationMessage(null); try { const result = await window.electronAPI.teams.validateCliArgs(customArgs); if (result.valid) { setValidationState('success'); setValidationMessage('All flags valid'); } else { setValidationState('error'); const flags = result.invalidFlags ?? []; const unknown = flags.filter((f) => !PROTECTED_CLI_FLAGS.has(f)); const protectedOnes = flags.filter((f) => PROTECTED_CLI_FLAGS.has(f)); const parts: string[] = []; if (unknown.length > 0) parts.push(`Unknown: ${unknown.join(', ')}`); if (protectedOnes.length > 0) parts.push(`Protected: ${protectedOnes.join(', ')}`); setValidationMessage(parts.join(' | ')); } } catch (err) { setValidationState('error'); setValidationMessage(err instanceof Error ? err.message : 'Validation failed'); } }, [customArgs]); // Reset validation when custom args change const handleCustomArgsChange = useCallback( (value: string) => { onCustomArgsChange(value); if (validationState !== 'idle') { setValidationState('idle'); setValidationMessage(null); } }, [onCustomArgsChange, validationState] ); const filteredHistory = useMemo( () => worktreeHistory.filter( (name) => name !== worktreeName && (!worktreeName || name.includes(worktreeName)) ), [worktreeHistory, worktreeName] ); return (
{/* Collapsible header */} {isOpen && (
{/* Worktree */}
onWorktreeEnabledChange(value === true)} />
{worktreeEnabled && ( 0}> onWorktreeNameChange(e.target.value)} onFocus={() => setShowHistory(true)} onBlur={() => { // Delay to allow click on history items setTimeout(() => { setShowHistory(false); commitWorktreeName(); }, 150); }} /> e.preventDefault()} >
Recent
{filteredHistory.map((name) => ( ))}
)}
{/* Command preview */}
Command preview
{previewTokens.map((token, i) => ( {token.text} ))}
{/* Custom arguments */}
Custom arguments
handleCustomArgsChange(e.target.value)} /> {customArgs.trim() && ( )}
{/* Validation result */} {validationState === 'success' && validationMessage && (
{validationMessage}
)} {validationState === 'error' && validationMessage && (
{validationMessage.includes('Protected') ? ( ) : ( )} {validationMessage}
)}
)}
); };