The goal is to make cli.ts more modular by extracting functionality into dedicated modules and simplifying the main CLI class.
-
Create
cli-handlers/auth.ts:// Handle login/logout commands export async function handleLogin(client: Client): Promise<void> { // Move login logic here } export async function handleLogout(client: Client): Promise<void> { // Move logout logic here }
-
Create
cli-handlers/info.ts:// Handle usage/help/diff commands export async function handleUsage(client: Client): Promise<void> { // Move usage logic here } export function handleHelp(): void { // Move displayMenu call here } // Note: handleDiff already exists
-
Create
cli-handlers/lifecycle.ts:// Handle exit/quit commands and process signals export function handleExit(client: Client, spinner: Spinner): void { // Move exit logic here }
-
Create
cli-handlers/command-processor.ts:export type CommandResult = | { type: 'command'; handler: () => Promise<void> } | { type: 'prompt'; text: string } | { type: 'not_handled' } export function processCommand( input: string, client: Client, readyPromise: Promise<any> ): CommandResult { // Move command detection and handler selection here }
-
Create
ui/readline-manager.ts:export class ReadlineManager { private rl: readline.Interface private isPasting: boolean = false private lastInputTime: number = 0 private consecutiveFastInputs: number = 0 constructor(options: { completer: (line: string) => [string[], string] onLine: (line: string) => void onSigint: () => void }) { // Move readline setup here } setPrompt(prompt: string): void { // Move prompt management here } // ... other readline-related methods }
-
Refactor
cli.ts:export class CLI { private client: Client private readlineManager: ReadlineManager private isReceivingResponse: boolean = false private stopResponse: (() => void) | null = null constructor(options: CliOptions) { this.client = new Client(/* ... */) this.readlineManager = new ReadlineManager({ completer: this.completer.bind(this), onLine: this.handleLine.bind(this), onSigint: this.handleSigint.bind(this) }) // ... minimal initialization } private async handleLine(line: string) { const result = processCommand(line, this.client, this.readyPromise) if (result.type === 'command') { await result.handler() } else if (result.type === 'prompt') { await this.forwardUserInput(result.text) } } // ... minimal set of methods }
-
Keep the
CLIclass focused on orchestration:- Initialize components
- Wire up event handlers
- Maintain core state
- Delegate to handlers
-
Handler modules should:
- Be pure functions where possible
- Take required dependencies as parameters
- Return promises for async operations
- Use types for parameters and return values
-
The
ReadlineManagershould:- Encapsulate readline setup and management
- Handle keypress events
- Manage the prompt
- Detect pasting
- Provide a clean API for the CLI class
-
Command processing should:
- Use a clear type system for commands
- Make it easy to add new commands
- Keep command implementations separate from detection
- Support async command handlers
-
State management:
- Keep state close to where it's used
- Pass state explicitly to handlers
- Avoid global state
- Use TypeScript to enforce state constraints
After each module is created:
- Run type checking
- Test the module in isolation if possible
- Test integration with the CLI class
- Verify all commands still work as expected
- Consider adding a proper command registry
- Add command documentation
- Support command aliases
- Add command validation
- Support command composition