Command Architecture
Understanding the CLI's internal architecture, dependency injection, and design patterns.
Overview
The CLI is built with TypeScript, using TypeDI for dependency injection, Commander.js for CLI parsing, and Zod for validation.
Core Components
Commands
Commands implement TypedCommand<TInputs>:
interface TypedCommand<TInputs extends CommandInputs = CommandInputs> {
name: string;
description: string;
category: string;
aliases?: string[];
inputs: TInputs;
execute(inputs: TypedInputs<TInputs>): Promise<unknown>;
}
Services
Reusable business logic lives in services:
src/services/
├── ticket-service-factory.ts # JIRA/GitHub ticket operations
├── file-service.ts # File system operations
├── input/input-service.ts # User prompts
├── git-service.ts # Git operations
└── ...
Services are injectable with @Service() decorator.
Integrations
Third-party integrations:
src/integrations/
├── jira/ # JIRA API
├── github/ # GitHub API
├── google/ # Google Drive, Sheets
└── yaml/ # YAML validation and storage
Controllers
For REST API and Data API:
src/controllers/
├── command-controller.ts # Execute commands via HTTP
├── ticket-controller.ts # Ticket data endpoints
└── ...
Dependency Injection
The CLI uses TypeDI for dependency injection:
@Service()
export class MyCommand implements TypedCommand {
constructor(
private readonly fileService: FileService,
private readonly gitService: GitService,
) {}
}
Container isolation: Different containers for different contexts (CLI, Lambda, tests).
Configuration
Configuration lives in two places:
- Personal config:
~/.wseng(user-specific) - Project config:
.wseng(repository-specific)
Access via ConfigService:
@Service()
export class MyCommand {
constructor(private readonly config: ConfigService) {}
async execute() {
const personal = await this.config.getPersonalConfiguration();
const project = await this.config.getProjectConfiguration();
}
}
Command Categories
Commands are organized by category:
common- General commands (commit, start-ticket, etc.)competencies- Code quality commands (review-work, unit-test, etc.)ws- Internal WS.Eng commands (build-codemap, save-plan, etc.)qc- QC workflow commands (qc start, qc pass, etc.)- Product-specific (athena, cat, tap, tk, xo)
Category determines command invocation:
common:wseng commitws:wseng ws save-planathena:wseng athena graphql
Logging
Use log4js for logging:
import { getLogger } from "log4js";
const logger = getLogger("MyCommand");
logger.debug("Debug message");
logger.info("Info message");
logger.warn("Warning message");
logger.error("Error message");
Log levels configurable via --debug and --quiet flags.
Error Handling
Use custom error types for better error messages:
import { ValidationError } from "../../utils/error-utils";
if (!ticket) {
throw new ValidationError("No ticket found in context");
}
Errors are automatically formatted and displayed to users.
Testing
Commands can be tested by:
- Manual testing:
pnpm run build && wseng <command> - Integration tests: Call commands programmatically via container
Project Structure
src/
├── commands/ # All commands
│ ├── common/
│ │ ├── commit-command.ts # Simple command (single file)
│ │ └── record-demo-command/ # Complex command (folder)
│ │ ├── record-demo.command.ts
│ │ ├── services/
│ │ ├── models/
│ │ └── constants/
├── services/ # Reusable business logic
├── integrations/ # Third-party APIs
├── controllers/ # REST API controllers
├── models/ # Data models
├── utils/ # Utilities
├── containers/ # DI containers
└── lambdas/ # Lambda entry points
Command Organization
Simple commands: Single file (e.g., commit-command.ts)
Complex commands: Folder structure (e.g., record-demo-command/)
- Main command file:
{command-name}.command.ts services/- Command-specific servicesmodels/- Command-specific modelsconstants/- Command-specific constants
Use folder structure when command has multiple services, models, or significant complexity.
Related Docs
Creating Commands
Step-by-step guide to creating your first command.
Python Integration
How Python commands integrate with the TypeScript CLI.