import * as path from 'path'; import { Command } from 'commander'; import chalk from 'chalk'; import inquirer from 'inquirer'; import ora from 'ora'; import { getWorkspacePath, loadWorkspaceConfig, sanitizeAgentName, hasUncommittedChanges, exportChangesAsPatch, getAllWorkspaces } from '../utils/file-utils'; import { createGit, createWorktree, removeWorktree, deleteBranch, hasUncommittedChanges as gitHasUncommittedChanges, stashChanges } from '../utils/git-utils'; import { GitAgentSyncError, WorkspaceNotFoundError } from '../utils/errors'; export function createDestroyCommand(): Command { const cmd = new Command('destroy') .alias('rm') .description('Safely remove an agent workspace') .argument('', 'Name of the agent workspace to destroy') .option('--force', 'Skip confirmation and remove without preservation') .option('--preserve', 'Preserve uncommitted changes as a patch file') .option('--output ', 'Path for patch file (if --preserve is used)') .action(async (agentName, options) => { try { const currentPath = process.cwd(); const sanitizedName = sanitizeAgentName(agentName); const workspacePath = getWorkspacePath(currentPath, sanitizedName); const config = await loadWorkspaceConfig(workspacePath); if (!config) { throw new WorkspaceNotFoundError(sanitizedName); } const uncommitted = await gitHasUncommittedChanges(workspacePath); const spinner = ora('Checking workspace status...').start(); if (uncommitted && !options.force) { if (!options.preserve) { spinner.stop(); const { shouldPreserve } = await inquirer.prompt([ { type: 'confirm', name: 'shouldPreserve', message: `Workspace has uncommitted changes. Preserve them as a patch file?`, default: true } ]); if (shouldPreserve) { options.preserve = true; } } } spinner.text = 'Preparing to destroy workspace...'; spinner.stop(); if (!options.force) { console.log(chalk.cyan('\n🗑️ Workspace Destruction Summary')); console.log(chalk.gray('─'.repeat(50))); console.log(` Agent: ${chalk.white(sanitizedName)}`); console.log(` Branch: ${chalk.white(config.branch)}`); console.log(` Path: ${chalk.white(workspacePath)}`); console.log(` Created: ${chalk.white(new Date(config.createdAt).toLocaleString())}`); console.log(` Uncommitted changes: ${uncommitted ? chalk.yellow('Yes') : chalk.green('No')}`); console.log(chalk.gray('─'.repeat(50))); const { confirm } = await inquirer.prompt([ { type: 'confirm', name: 'confirm', message: `Are you sure you want to destroy workspace '${sanitizedName}'?`, default: false } ]); if (!confirm) { console.log(chalk.yellow('\n⏸️ Destruction cancelled.')); return; } } const destroySpinner = ora('Destroying workspace...').start(); if (uncommitted && options.preserve) { const patchPath = options.output || path.join(currentPath, `${sanitizedName}-changes.patch`); await exportChangesAsPatch(workspacePath, patchPath); destroySpinner.text = `Changes exported to ${patchPath}`; } await removeWorktree(currentPath, workspacePath, config.branch); await deleteBranch(createGit(currentPath), config.branch, true); const fs = await import('fs-extra'); const configPath = path.join(workspacePath, '.agent-workspace.json'); const envPath = path.join(workspacePath, '.env'); await fs.remove(configPath); await fs.remove(envPath); const workspaceRoot = path.dirname(workspacePath); if (await fs.pathExists(workspaceRoot)) { const remaining = await fs.readdir(workspaceRoot); if (remaining.length === 0) { await fs.remove(workspaceRoot); } } destroySpinner.succeed(chalk.green('Workspace destroyed successfully!')); if (uncommitted && options.preserve) { console.log(chalk.yellow(`\n📦 Changes preserved at: ${options.output || `${sanitizedName}-changes.patch`}`)); } console.log(chalk.cyan('\n✨ Done')); } catch (error) { const gitError = error instanceof GitAgentSyncError ? error : new GitAgentSyncError(String(error)); console.error(chalk.red(`\n❌ Error: ${gitError.message}`)); process.exit(1); } }); return cmd; }