import { PackagePlus, Plus } from 'lucide-react'; import { useCallback, useState } from 'react'; import type { DashboardPayload } from '../../features/skills/state'; import type { SkillScope } from '../../features/skills/types'; import { installDashboardSkills } from '../api'; import { AnimatedText, Spinner } from '../dashboard/components'; import { showErrorToast, showSuccessToast } from '../dashboard/toasts'; import { createCommandFailureMessage, getErrorMessage } from '../dashboard/utils'; import { cn } from '../lib/utils'; import { Button } from './ui/button'; import { Combobox, ComboboxChip, ComboboxChips, ComboboxChipsInput, ComboboxContent, ComboboxEmpty, ComboboxGroup, ComboboxItem, ComboboxList, } from './ui/combobox'; import { Popover, PopoverContent, PopoverHeader, PopoverTitle, PopoverTrigger } from './ui/popover'; type AddSkillPopoverProps = { installSource: string; launchDirectory: string | undefined; previousState: DashboardPayload['installedState'] | undefined; isInstalling: boolean; onInstallingChange: (isInstalling: boolean) => void; onInstalled: () => Promise; onDialogOpenChange: (open: boolean) => void; }; export function AddSkillPopover({ installSource, launchDirectory, previousState, isInstalling, onInstallingChange, onInstalled, onDialogOpenChange, }: AddSkillPopoverProps) { const [open, setOpen] = useState(false); const [step, setStep] = useState<'scope' | 'agents'>('scope'); const [scope, setScope] = useState('project'); const [agents, setAgents] = useState([]); const handleOpenInstallForm = useCallback(() => { setStep('scope'); setScope('project'); setAgents(['universal']); setOpen(true); }, []); const handleValueChange = useCallback((value: string | string[]) => { setAgents(Array.isArray(value) ? value : [value]); }, []); const handleInstall = async () => { if (!installSource) { return; } setOpen(false); onInstallingChange(true); try { const outcome = await installDashboardSkills({ source: installSource, scope, agents: [...new Set([...agents, 'universal'])], previousState, }); if (outcome.command.ok) { showSuccessToast('Skill installed', `${installSource} was installed in ${scope} scope.`); void onInstalled(); onDialogOpenChange(false); return; } showErrorToast('Install failed', createCommandFailureMessage(outcome.command)); } catch (error) { showErrorToast('Install request failed', getErrorMessage(error)); } finally { onInstallingChange(false); } }; if (isInstalling) { return ( ); } return ( {step === 'scope' ? ( setStep('agents')} /> ) : ( setStep('scope')} onInstall={handleInstall} /> )} ); } function ScopeStep({ launchDirectory, selectedScope, onSelectScope, onContinue, }: { launchDirectory: string | undefined; selectedScope: SkillScope; onSelectScope: (scope: SkillScope) => void; onContinue: () => void; }) { return (
Step 1 of 2

Where to install?

); } const COMMON_AGENTS = [ 'universal', 'aider-desk', 'amp', 'antigravity', 'augment', 'bob', 'claude', 'claude-code', 'cline', 'codeartsdoer', 'codebuddy', 'codemaker', 'codex', 'copilot', 'cursor', 'deep-agents', 'dexto', 'firebender', 'gemini', 'kimi-code-cli', 'openclaw', 'opencode', 'warp', ]; function AgentsStep({ selectedAgents, onValueChange, onBack, onInstall, }: { selectedAgents: string[]; onValueChange: (value: string | string[]) => void; onBack: () => void; onInstall: () => void; }) { return (
Step 2 of 2

Assign to agents

{selectedAgents.map((agent) => ( {agent} ))} {COMMON_AGENTS.map((agent) => ( {agent} ))} No matching agents.

Universal (.agents/skills) is always included.

); }