PackageManager.tsxā¢3.62 kB
import React, { useState, useEffect, useCallback } from 'react';
import { useTool } from '@modelcontextprotocol/sdk/react';
import Spinner from './Spinner.js';
interface PackageManagerProps {
addToTerminal: (output: string) => void;
}
const getCommandOutput = (result: { content: { type: "text"; text: string }[] } | null | undefined): string => {
if (result?.content?.[0]?.type === 'text') {
return result.content[0].text.split('\nCWD_MARKER:')[0].trim();
}
return '';
};
const PackageManager: React.FC<PackageManagerProps> = ({ addToTerminal }) => {
const [packageName, setPackageName] = useState('');
const [installedPackages, setInstalledPackages] = useState<string[]>([]);
const { call: runCommand, isPending } = useTool('run_bash_command');
const fetchPackages = useCallback(async () => {
const result = await runCommand({ command: 'pip list' });
const output = getCommandOutput(result);
if (output) {
// pip list has a 2-line header, skip it.
const lines = output.split('\n').slice(2);
setInstalledPackages(lines);
}
}, [runCommand]);
useEffect(() => {
fetchPackages();
}, [fetchPackages]);
const handleInstall = async () => {
if (!packageName.trim()) return;
addToTerminal(`Running: pip install ${packageName}...`);
const result = await runCommand({ command: `pip install ${packageName}` });
const output = getCommandOutput(result);
addToTerminal(output || 'Installation complete.');
setPackageName('');
fetchPackages(); // Refresh the list of installed packages
};
return (
<div style={styles.container}>
<div style={styles.header}>PACKAGE MANAGER</div>
<div style={styles.installSection}>
<input
type="text"
value={packageName}
onChange={(e) => setPackageName(e.target.value)}
placeholder="e.g., pandas"
style={styles.input}
/>
<button onClick={handleInstall} disabled={isPending} style={styles.button}>
{isPending ? <Spinner size={16} /> : 'Install'}
</button>
</div>
<div style={styles.listSection}>
<h3 style={styles.listHeader}>Installed Packages (via pip)</h3>
{isPending ? <Spinner /> : (
<ul style={styles.packageList}>
{installedPackages.map((pkg, i) => (
<li key={i} style={styles.packageItem}>{pkg}</li>
))}
</ul>
)}
</div>
</div>
);
};
const styles: { [key: string]: React.CSSProperties } = {
container: { display: 'flex', flexDirection: 'column', height: '100%', color: 'var(--text-primary)' },
header: { padding: '10px', fontWeight: 'bold', borderBottom: '1px solid var(--border-color)', flexShrink: 0 },
installSection: { padding: '10px', display: 'flex', gap: '10px', borderBottom: '1px solid var(--border-color)', flexShrink: 0 },
input: { flex: 1, padding: '5px', backgroundColor: 'var(--background-tertiary)', border: '1px solid var(--border-color)', color: 'var(--text-primary)', borderRadius: '3px' },
button: { padding: '5px 10px', backgroundColor: 'var(--accent-primary)', color: 'white', border: 'none', borderRadius: '3px', cursor: 'pointer', minWidth: '70px', display: 'flex', justifyContent: 'center', alignItems: 'center' },
listSection: { flex: 1, padding: '10px', overflowY: 'auto' },
listHeader: { marginTop: 0, fontSize: '14px', color: 'var(--text-secondary)' },
packageList: { listStyle: 'none', padding: 0, margin: 0 },
packageItem: { padding: '4px 0', fontSize: '13px', fontFamily: 'monospace' },
};
export default PackageManager;