import { useState } from 'react';
import { Trash2, RefreshCw, GitBranch, Loader2, ServerCrash, ChevronRight } from 'lucide-react';
import { usePlugins } from '../../contexts/PluginsContext';
import PluginIcon from './PluginIcon';
import type { Plugin } from '../../contexts/PluginsContext';
/* ─── Toggle Switch ─────────────────────────────────────────────────────── */
function ToggleSwitch({ checked, onChange }: { checked: boolean; onChange: (v: boolean) => void }) {
return (
);
}
/* ─── Server Dot ────────────────────────────────────────────────────────── */
function ServerDot({ running }: { running: boolean }) {
if (!running) return null;
return (
running
);
}
/* ─── Plugin Card ───────────────────────────────────────────────────────── */
type PluginCardProps = {
plugin: Plugin;
index: number;
onToggle: (enabled: boolean) => void;
onUpdate: () => void;
onUninstall: () => void;
updating: boolean;
confirmingUninstall: boolean;
onCancelUninstall: () => void;
updateError: string | null;
};
function PluginCard({
plugin,
index,
onToggle,
onUpdate,
onUninstall,
updating,
confirmingUninstall,
onCancelUninstall,
updateError,
}: PluginCardProps) {
const accentColor = plugin.enabled
? 'bg-emerald-500'
: 'bg-muted-foreground/20';
return (
{/* Left accent bar */}
{/* Header row */}
{plugin.displayName}
v{plugin.version}
{plugin.type}
{plugin.description && (
{plugin.description}
)}
{plugin.author && (
{plugin.author}
)}
{/* Controls */}
{/* Confirm uninstall banner */}
{confirmingUninstall && (
Remove {plugin.displayName}? This cannot be undone.
)}
{/* Update error */}
{updateError && (
{updateError}
)}
);
}
/* ─── Empty State ───────────────────────────────────────────────────────── */
function EmptyState() {
return (
~/.claude-code-ui/plugins/
(empty)
No plugins installed
Install from git or drop a folder in the plugins directory
);
}
/* ─── Main Component ────────────────────────────────────────────────────── */
export default function PluginSettingsTab() {
const { plugins, loading, installPlugin, uninstallPlugin, updatePlugin, togglePlugin } =
usePlugins();
const [gitUrl, setGitUrl] = useState('');
const [installing, setInstalling] = useState(false);
const [installError, setInstallError] = useState(null);
const [confirmUninstall, setConfirmUninstall] = useState(null);
const [updatingPlugin, setUpdatingPlugin] = useState(null);
const [updateErrors, setUpdateErrors] = useState>({});
const handleUpdate = async (name: string) => {
setUpdatingPlugin(name);
setUpdateErrors((prev) => { const next = { ...prev }; delete next[name]; return next; });
const result = await updatePlugin(name);
if (!result.success) {
setUpdateErrors((prev) => ({ ...prev, [name]: result.error || 'Update failed' }));
}
setUpdatingPlugin(null);
};
const handleInstall = async () => {
if (!gitUrl.trim()) return;
setInstalling(true);
setInstallError(null);
const result = await installPlugin(gitUrl.trim());
if (result.success) {
setGitUrl('');
} else {
setInstallError(result.error || 'Installation failed');
}
setInstalling(false);
};
const handleUninstall = async (name: string) => {
if (confirmUninstall !== name) {
setConfirmUninstall(name);
return;
}
await uninstallPlugin(name);
setConfirmUninstall(null);
};
return (
{/* Header */}
Plugins
Extend the interface with custom tabs. Drop a folder in{' '}
~/.claude-code-ui/plugins/
{' '}
or install from git.
{!loading && plugins.length > 0 && (
{plugins.filter((p) => p.enabled).length}/{plugins.length}
)}
{/* Install from Git */}
Install from git
$
{
setGitUrl(e.target.value);
setInstallError(null);
}}
placeholder="git clone https://github.com/user/my-plugin"
className="flex-1 px-1.5 py-2.5 text-xs font-mono bg-transparent text-foreground placeholder:text-muted-foreground/40 focus:outline-none"
onKeyDown={(e) => {
if (e.key === 'Enter') void handleInstall();
}}
/>
{installError && (
{installError}
)}
{/* Plugin List */}
{loading ? (
scanning plugins…
) : plugins.length === 0 ? (
) : (
plugins.map((plugin, index) => (
void togglePlugin(plugin.name, enabled)}
onUpdate={() => void handleUpdate(plugin.name)}
onUninstall={() => void handleUninstall(plugin.name)}
updating={updatingPlugin === plugin.name}
confirmingUninstall={confirmUninstall === plugin.name}
onCancelUninstall={() => setConfirmUninstall(null)}
updateError={updateErrors[plugin.name] ?? null}
/>
))
)}
);
}