diff --git a/src/commands/list.ts b/src/commands/list.ts new file mode 100644 index 0000000..10af1eb --- /dev/null +++ b/src/commands/list.ts @@ -0,0 +1,110 @@ +import * as path from 'path'; +import { Command } from 'commander'; +import chalk from 'chalk'; +import { + loadGlobalConfig, + getAllWorkspaces, + loadWorkspaceConfig, + loadChangeTracking +} from '../utils/file-utils'; +import { + createGit, + getWorktreeStatus, + hasUncommittedChanges, + getLastCommitInfo +} from '../utils/git-utils'; +import { GitAgentSyncError } from '../utils/errors'; +import { WorkspaceInfo, WorkspaceChange } from '../types'; + +export function createListCommand(): Command { + const cmd = new Command('list') + .alias('ls') + .description('List all active agent workspaces') + .option('--json', 'Output as JSON') + .option('--verbose', 'Show detailed information') + .action(async (options) => { + try { + const currentPath = process.cwd(); + const globalConfig = await loadGlobalConfig(); + const workspaces = await getAllWorkspaces(currentPath); + + if (workspaces.length === 0) { + console.log(chalk.yellow('\nšŸ“­ No agent workspaces found.')); + console.log(` Create one with: ${chalk.cyan('git-agent-sync create ')}`); + return; + } + + const workspaceDetails: (WorkspaceInfo & { status?: WorkspaceChange; lastCommit?: string })[] = []; + + for (const workspace of workspaces) { + const config = await loadWorkspaceConfig(workspace.path); + if (config) { + const status = await getWorktreeStatus(workspace.path); + const lastCommit = await getLastCommitInfo(workspace.path); + workspaceDetails.push({ + ...workspace, + hasChanges: status.uncommittedCount > 0, + changeCount: status.uncommittedCount, + lastCommit: lastCommit.message || 'No commits' + }); + } + } + + if (options.json) { + console.log(JSON.stringify(workspaceDetails, null, 2)); + return; + } + + console.log(chalk.cyan('\nšŸ¤– Agent Workspaces')); + console.log(chalk.gray('─'.repeat(80))); + + const tableData = workspaceDetails.map(ws => ({ + name: ws.name, + branch: ws.branch, + path: path.relative(currentPath, ws.path), + status: ws.hasChanges ? chalk.yellow('šŸ”„ modified') : chalk.green('āœ“ clean'), + changes: ws.hasChanges ? chalk.yellow(`${ws.changeCount}`) : chalk.gray('0'), + lastCommit: truncateString(ws.lastCommit || '', 30) + })); + + const maxName = Math.max(...tableData.map(d => d.name.length)); + const maxBranch = Math.max(...tableData.map(d => d.branch.length)); + const maxPath = Math.max(...tableData.map(d => d.path.length)); + + for (const row of tableData) { + const nameCol = row.name.padEnd(maxName); + const branchCol = row.branch.padEnd(maxBranch); + const pathCol = row.path.padEnd(maxPath); + const changesCol = row.changes.padStart(3); + + console.log(` ${chalk.white(nameCol)} ${chalk.gray('│')} ${chalk.white(branchCol)} ${chalk.gray('│')} ${chalk.white(pathCol)} ${chalk.gray('│')} ${row.status} ${chalk.gray('│')} ${changesCol} changes`); + } + + console.log(chalk.gray('─'.repeat(80))); + console.log(` ${chalk.white(`${workspaces.length} workspace(s)`)}`); + const totalChanges = workspaceDetails.reduce((sum, ws) => sum + ws.changeCount, 0); + console.log(` ${chalk.white(`${totalChanges} total uncommitted change(s)`)}`); + + if (options.verbose) { + console.log(chalk.cyan('\nšŸ“ Workspace Paths:')); + for (const ws of workspaceDetails) { + console.log(` ${chalk.white(ws.name)}: ${ws.path}`); + } + } + + } 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; +} + +function truncateString(str: string, maxLength: number): string { + if (str.length <= maxLength) { + return str; + } + return str.substring(0, maxLength - 3) + '...'; +}