diff --git a/.src/commands/destroy.ts b/.src/commands/destroy.ts new file mode 100644 index 0000000..c481a2a --- /dev/null +++ b/.src/commands/destroy.ts @@ -0,0 +1,134 @@ +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; +}