claude-task-master/docs/CLI-COMMANDER-PATTERN.md
Ralph Khreish 0f3ab00f26 feat: create tm-core and apps/cli (#1093)
- add typescript
- add npm workspaces
2025-09-09 03:32:48 +02:00

3.8 KiB

CLI Commander Class Pattern

Overview

We're using Commander.js's native class pattern instead of custom abstractions. This is cleaner, more maintainable, and uses the framework as designed.

Architecture

@tm/core (Business Logic)          @tm/cli (Presentation)
┌─────────────────────┐            ┌──────────────────────────┐
│ TaskMasterCore      │◄───────────│ ListTasksCommand         │
│ - getTaskList()     │            │ extends Commander.Command│
│ - getTask()         │            │ - display logic only     │
│ - getNextTask()     │            │ - formatting             │
└─────────────────────┘            └──────────────────────────┘
         ▲                                      ▲
         │                                      │
         └──────── Gets Data ──────────────────┘
                                     Displays Data

Implementation

Command Class Pattern

// apps/cli/src/commands/list-tasks-commander.ts
export class ListTasksCommand extends Command {
  constructor(name?: string) {
    super(name || 'list');
    
    this
      .description('List tasks')
      .option('-s, --status <status>', 'Filter by status')
      .action(async (options) => {
        // 1. Get data from @tm/core
        const result = await this.tmCore.getTaskList(options);
        
        // 2. Display data (presentation only)
        this.displayResults(result, options);
      });
  }
}

Main CLI Class

// apps/cli/src/cli-commander.ts
class TaskMasterCLI extends Command {
  createCommand(name?: string): Command {
    switch (name) {
      case 'list':
        return new ListTasksCommand(name);
      default:
        return new Command(name);
    }
  }
}

Integration with Existing Scripts

Gradual Migration Path

// scripts/modules/commands.js

// OLD WAY (keep working during migration)
program
  .command('old-list')
  .action(async (options) => {
    await listTasksV2(...);
  });

// NEW WAY (add alongside old)
import { ListTasksCommand } from '@tm/cli';
program.addCommand(new ListTasksCommand());

Benefits

  1. No Custom Abstractions: Using Commander.js as designed
  2. Clean Separation: Business logic in core, presentation in CLI
  3. Gradual Migration: Can migrate one command at a time
  4. Type Safety: Full TypeScript support with Commander types
  5. Framework Native: Better documentation, examples, and community support

Migration Steps

  1. Phase 1: Build command classes in @tm/cli (current)
  2. Phase 2: Import in scripts/modules/commands.js
  3. Phase 3: Replace old implementations one by one
  4. Phase 4: Remove old code when all migrated

Example Usage

In New Code

import { ListTasksCommand } from '@tm/cli';
const program = new Command();
program.addCommand(new ListTasksCommand());

In Existing Scripts

// Gradual adoption
const listCmd = new ListTasksCommand();
program.addCommand(listCmd);

Programmatic Usage

const listCommand = new ListTasksCommand();
await listCommand.parseAsync(['node', 'script', '--format', 'json']);

POC Status

Completed:

  • ListTasksCommand extends Commander.Command
  • Clean separation of concerns
  • Integration examples
  • Build configuration

🚧 Next Steps:

  • Migrate more commands
  • Update existing scripts to use new classes
  • Remove old implementations gradually

This POC proves the pattern works and provides a clean migration path!