Files
Codewalkers/src/cli/index.ts
Lukas May 59b233792a feat(01-05): implement graceful shutdown with signal handlers
- GracefulShutdown class handles SIGTERM, SIGINT, SIGHUP
- 10-second timeout before force exit
- Double SIGINT forces immediate exit
- Shutdown sequence: stop HTTP server, stop processes, cleanup
- Integrates with CoordinationServer and ProcessManager
2026-01-30 13:23:58 +01:00

128 lines
3.6 KiB
TypeScript

/**
* Codewalk District CLI
*
* Commander-based CLI with help system and version display.
* Supports server mode via --server flag.
*/
import { Command } from 'commander';
import { VERSION } from '../index.js';
import { CoordinationServer } from '../server/index.js';
import { GracefulShutdown } from '../server/shutdown.js';
import { ProcessManager, ProcessRegistry } from '../process/index.js';
import { LogManager } from '../logging/index.js';
/** Environment variable for custom port */
const CW_PORT_ENV = 'CW_PORT';
/**
* Starts the coordination server in foreground mode.
* Server runs until terminated via SIGTERM/SIGINT.
*/
async function startServer(port?: number): Promise<void> {
// Get port from option, env var, or default
const serverPort = port ??
(process.env[CW_PORT_ENV] ? parseInt(process.env[CW_PORT_ENV], 10) : undefined);
// Create dependencies
const registry = new ProcessRegistry();
const processManager = new ProcessManager(registry);
const logManager = new LogManager();
// Create and start server
const server = new CoordinationServer(
{ port: serverPort },
processManager,
logManager
);
try {
await server.start();
} catch (error) {
console.error('Failed to start server:', (error as Error).message);
process.exit(1);
}
// Install graceful shutdown handlers
const shutdown = new GracefulShutdown(server, processManager, logManager);
shutdown.install();
}
/**
* Creates and configures the CLI program.
*
* @param serverHandler - Optional handler to be called for server mode
* @returns Configured Commander program ready for parsing
*/
export function createCli(serverHandler?: (port?: number) => Promise<void>): Command {
const program = new Command();
program
.name('cw')
.description('Multi-agent workspace for orchestrating multiple Claude Code agents')
.version(VERSION, '-v, --version', 'Display version number');
// Server mode option (global flag)
program
.option('-s, --server', 'Start the coordination server')
.option('-p, --port <number>', 'Port for the server (default: 3847, env: CW_PORT)', parseInt);
// Handle the case where --server is provided without a command
// This makes --server work as a standalone action
program.hook('preAction', async (_thisCommand, _actionCommand) => {
const opts = program.opts();
if (opts.server && serverHandler) {
await serverHandler(opts.port);
process.exit(0);
}
});
// Placeholder commands - will be implemented in later phases
program
.command('status')
.description('Show workspace status')
.action(() => {
console.log('cw status: not implemented');
});
program
.command('agent')
.description('Manage agents')
.action(() => {
console.log('cw agent: not implemented');
});
program
.command('task')
.description('Manage tasks')
.action(() => {
console.log('cw task: not implemented');
});
return program;
}
/**
* Runs the CLI, handling server mode and commands.
*/
export async function runCli(): Promise<void> {
// Check for server flag early, before Commander processes
const hasServerFlag = process.argv.includes('--server') || process.argv.includes('-s');
if (hasServerFlag) {
// Get port from args if present
const portIndex = process.argv.findIndex(arg => arg === '-p' || arg === '--port');
const port = portIndex !== -1 && process.argv[portIndex + 1]
? parseInt(process.argv[portIndex + 1], 10)
: undefined;
await startServer(port);
// Server runs indefinitely until signal
return;
}
// Normal CLI processing
const program = createCli();
program.parse(process.argv);
}