@webdevtoday/claude-agents
Version:
AI-powered development shop with 15 specialized agents for Claude Code. Features concurrent execution, shared memory, context-forge integration, and web dashboard for 80% faster development.
244 lines (212 loc) • 7.39 kB
JavaScript
import chalk from 'chalk';
import ora from 'ora';
import { existsSync, unlinkSync, readdirSync, rmdirSync, statSync } from 'fs';
import { join } from 'path';
import inquirer from 'inquirer';
import {
getAgentsDir,
getCommandsDir,
CLAUDE_USER_DIR,
CLAUDE_PROJECT_DIR,
CLAUDE_USER_AGENTS_DIR,
CLAUDE_PROJECT_AGENTS_DIR,
CLAUDE_USER_COMMANDS_DIR,
CLAUDE_PROJECT_COMMANDS_DIR
} from '../utils/paths.js';
import {
loadConfig,
saveConfig,
removeInstalledAgent,
getInstalledAgents
} from '../utils/config.js';
import { detectContextForge } from '../utils/contextForgeDetector.js';
export async function uninstallCommand(options) {
const spinner = ora();
try {
// Detect context-forge project
const contextForgeInfo = detectContextForge();
// Determine scope
let scope = 'both';
if (options.user) {
scope = 'user';
} else if (options.project) {
scope = 'project';
} else if (!options.all && !options.agent) {
// Ask for scope if not specified
const { selectedScope } = await inquirer.prompt([{
type: 'list',
name: 'selectedScope',
message: 'Which scope to uninstall from?',
choices: [
{ name: 'User directory (~/.claude/)', value: 'user' },
{ name: 'Project directory (.claude/)', value: 'project' },
{ name: 'Both', value: 'both' }
],
default: 'user'
}]);
scope = selectedScope;
}
// Get installed agents
const userAgents = scope !== 'project' ? Object.keys(loadConfig(false).installedAgents) : [];
const projectAgents = scope !== 'user' ? Object.keys(loadConfig(true).installedAgents) : [];
const allAgents = [...new Set([...userAgents, ...projectAgents])];
if (allAgents.length === 0) {
console.log(chalk.yellow('No agents installed.'));
return;
}
// Determine which agents to uninstall
let agentsToUninstall = [];
if (options.all) {
agentsToUninstall = allAgents;
} else if (options.agent) {
if (!allAgents.includes(options.agent)) {
console.log(chalk.red(`Agent "${options.agent}" is not installed.`));
return;
}
agentsToUninstall = [options.agent];
} else {
// Interactive selection
const { selectedAgents } = await inquirer.prompt([{
type: 'checkbox',
name: 'selectedAgents',
message: 'Select agents to uninstall:',
choices: allAgents.map(agent => ({
name: agent,
value: agent,
checked: false
})),
validate: (answers) => {
if (answers.length === 0) {
return 'You must select at least one agent';
}
return true;
}
}]);
agentsToUninstall = selectedAgents;
}
// Confirm uninstallation
const confirmMessage = `Uninstall ${agentsToUninstall.length} agent(s) from ${scope} scope?`;
const { confirmed } = await inquirer.prompt([{
type: 'confirm',
name: 'confirmed',
message: confirmMessage,
default: false
}]);
if (!confirmed) {
console.log(chalk.yellow('Uninstallation cancelled.'));
return;
}
// Uninstall agents
console.log('');
let uninstalledCount = 0;
for (const agentName of agentsToUninstall) {
spinner.start(`Uninstalling ${chalk.bold(agentName)}...`);
try {
// Remove from user scope
if (scope !== 'project' && userAgents.includes(agentName)) {
// Remove agent file
const userAgentPath = join(CLAUDE_USER_AGENTS_DIR, `${agentName}.md`);
if (existsSync(userAgentPath)) {
unlinkSync(userAgentPath);
}
// Remove commands
removeAgentCommands(agentName, CLAUDE_USER_COMMANDS_DIR, false);
// Update config
removeInstalledAgent(agentName, false);
}
// Remove from project scope
if (scope !== 'user' && projectAgents.includes(agentName)) {
// Remove agent file
const projectAgentPath = join(CLAUDE_PROJECT_AGENTS_DIR, `${agentName}.md`);
if (existsSync(projectAgentPath)) {
unlinkSync(projectAgentPath);
}
// Remove commands
const isContextForge = contextForgeInfo.hasContextForge;
removeAgentCommands(agentName, CLAUDE_PROJECT_COMMANDS_DIR, isContextForge);
// Update config
removeInstalledAgent(agentName, true);
}
uninstalledCount++;
spinner.succeed(`Uninstalled ${chalk.bold(agentName)}`);
} catch (error) {
spinner.fail(`Failed to uninstall ${agentName}: ${error.message}`);
}
}
// Clean up empty directories if requested
if (options.clean) {
spinner.start('Cleaning up empty directories...');
if (scope !== 'project') {
cleanEmptyDirectories(CLAUDE_USER_DIR);
}
if (scope !== 'user' && !contextForgeInfo.hasContextForge) {
cleanEmptyDirectories(CLAUDE_PROJECT_DIR);
}
spinner.succeed('Cleaned up empty directories');
}
// Final message
console.log('');
console.log(chalk.green(`✓ Uninstalled ${uninstalledCount} agent(s)`));
if (contextForgeInfo.hasContextForge && scope !== 'user') {
console.log(chalk.gray('Context-forge files were preserved.'));
}
} catch (error) {
spinner.fail('Uninstallation failed');
console.error(chalk.red('Error:'), error.message);
process.exit(1);
}
}
function removeAgentCommands(agentName, commandsDir, isContextForge) {
// Map of agent names to their command files
const agentCommands = {
'api-developer': ['api'],
'api-documenter': ['apidoc'],
'code-reviewer': ['review'],
'debugger': ['debug'],
'devops-engineer': ['devops'],
'doc-writer': ['document'],
'frontend-developer': ['frontend'],
'marketing-writer': ['marketing'],
'product-manager': ['product'],
'project-planner': ['plan'],
'refactor': ['refactor'],
'security-scanner': ['security-scan'],
'shadcn-ui-builder': ['ui', 'shadcn'],
'tdd-specialist': ['tdd'],
'test-runner': ['test']
};
const commands = agentCommands[agentName] || [];
for (const command of commands) {
const fileName = isContextForge ? `agent-${command}.md` : `${command}.md`;
const targetDir = isContextForge ? join(commandsDir, 'agents') : commandsDir;
const commandPath = join(targetDir, fileName);
if (existsSync(commandPath)) {
unlinkSync(commandPath);
}
}
}
function cleanEmptyDirectories(dir) {
if (!existsSync(dir)) return;
try {
const files = readdirSync(dir);
if (files.length === 0) {
rmdirSync(dir);
} else {
// Recursively clean subdirectories
for (const file of files) {
const fullPath = join(dir, file);
const stat = statSync(fullPath);
if (stat.isDirectory()) {
cleanEmptyDirectories(fullPath);
}
}
// Check again after cleaning subdirectories
const remainingFiles = readdirSync(dir);
if (remainingFiles.length === 0) {
rmdirSync(dir);
}
}
} catch (error) {
// Ignore errors (e.g., permission issues)
}
}