@hanzo/dev 1.0.1 → 2.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/src/cli/dev.ts CHANGED
@@ -1,36 +1,779 @@
1
1
  #!/usr/bin/env node
2
2
  import { Command } from 'commander';
3
3
  import chalk from 'chalk';
4
+ import inquirer from 'inquirer';
5
+ import { spawn, execSync } from 'child_process';
6
+ import * as fs from 'fs';
7
+ import * as path from 'path';
8
+ import * as os from 'os';
9
+ import { FileEditor } from '../lib/editor';
10
+ import { MCPClient, DEFAULT_MCP_SERVERS } from '../lib/mcp-client';
11
+ import { FunctionCallingSystem } from '../lib/function-calling';
12
+ import { ConfigManager } from '../lib/config';
13
+ import { CodeActAgent } from '../lib/code-act-agent';
14
+ import { UnifiedWorkspace, WorkspaceSession } from '../lib/unified-workspace';
15
+ import { PeerAgentNetwork } from '../lib/peer-agent-network';
4
16
 
5
17
  const program = new Command();
6
18
 
19
+ // Load environment variables from .env files
20
+ function loadEnvFiles(): void {
21
+ const envFiles = ['.env', '.env.local', '.env.development', '.env.production'];
22
+ const cwd = process.cwd();
23
+
24
+ envFiles.forEach(file => {
25
+ const filePath = path.join(cwd, file);
26
+ if (fs.existsSync(filePath)) {
27
+ const content = fs.readFileSync(filePath, 'utf-8');
28
+ content.split('\n').forEach(line => {
29
+ const match = line.match(/^([^=]+)=(.*)$/);
30
+ if (match) {
31
+ const key = match[1].trim();
32
+ const value = match[2].trim();
33
+ if (!process.env[key]) {
34
+ process.env[key] = value;
35
+ }
36
+ }
37
+ });
38
+ }
39
+ });
40
+ }
41
+
42
+ // Load env files on startup
43
+ loadEnvFiles();
44
+
45
+ // Check if uvx is available
46
+ function hasUvx(): boolean {
47
+ try {
48
+ execSync('which uvx', { stdio: 'ignore' });
49
+ return true;
50
+ } catch {
51
+ return false;
52
+ }
53
+ }
54
+
55
+ // Available tools configuration
56
+ const TOOLS = {
57
+ 'hanzo-dev': {
58
+ name: 'Hanzo Dev (OpenHands)',
59
+ command: hasUvx() ? 'uvx hanzo-dev' : 'hanzo-dev',
60
+ checkCommand: hasUvx() ? 'which uvx' : 'which hanzo-dev',
61
+ description: 'Hanzo AI software development agent - Full featured dev environment',
62
+ color: chalk.magenta,
63
+ apiKeys: ['OPENAI_API_KEY', 'ANTHROPIC_API_KEY', 'LLM_API_KEY', 'HANZO_API_KEY'],
64
+ priority: 1,
65
+ isDefault: true
66
+ },
67
+ claude: {
68
+ name: 'Claude (Anthropic)',
69
+ command: 'claude-code',
70
+ checkCommand: 'which claude-code',
71
+ description: 'Claude Code - AI coding assistant',
72
+ color: chalk.blue,
73
+ apiKeys: ['ANTHROPIC_API_KEY', 'CLAUDE_API_KEY'],
74
+ priority: 2
75
+ },
76
+ aider: {
77
+ name: 'Aider',
78
+ command: 'aider',
79
+ checkCommand: 'which aider',
80
+ description: 'AI pair programming in your terminal',
81
+ color: chalk.green,
82
+ apiKeys: ['OPENAI_API_KEY', 'ANTHROPIC_API_KEY', 'CLAUDE_API_KEY'],
83
+ priority: 3
84
+ },
85
+ gemini: {
86
+ name: 'Gemini (Google)',
87
+ command: 'gemini',
88
+ checkCommand: 'which gemini',
89
+ description: 'Google Gemini AI assistant',
90
+ color: chalk.yellow,
91
+ apiKeys: ['GOOGLE_API_KEY', 'GEMINI_API_KEY'],
92
+ priority: 4
93
+ },
94
+ codex: {
95
+ name: 'OpenAI Codex',
96
+ command: 'codex',
97
+ checkCommand: 'which codex',
98
+ description: 'OpenAI coding assistant',
99
+ color: chalk.cyan,
100
+ apiKeys: ['OPENAI_API_KEY'],
101
+ priority: 5
102
+ }
103
+ };
104
+
105
+ // Check if a tool has API keys configured
106
+ function hasApiKey(tool: string): boolean {
107
+ const toolConfig = TOOLS[tool as keyof typeof TOOLS];
108
+ if (!toolConfig || !toolConfig.apiKeys) return false;
109
+
110
+ return toolConfig.apiKeys.some(key => !!process.env[key]);
111
+ }
112
+
113
+ // Check if a tool is installed
114
+ async function isToolInstalled(tool: string): Promise<boolean> {
115
+ return new Promise((resolve) => {
116
+ const checkCmd = TOOLS[tool as keyof typeof TOOLS]?.checkCommand || `which ${tool}`;
117
+ const check = spawn('sh', ['-c', checkCmd]);
118
+ check.on('close', (code) => {
119
+ resolve(code === 0);
120
+ });
121
+ });
122
+ }
123
+
124
+ // Get list of available tools
125
+ async function getAvailableTools(): Promise<string[]> {
126
+ const available: string[] = [];
127
+ for (const toolKey of Object.keys(TOOLS)) {
128
+ const isInstalled = await isToolInstalled(toolKey);
129
+ const hasKey = hasApiKey(toolKey);
130
+ if (isInstalled || hasKey) {
131
+ available.push(toolKey);
132
+ }
133
+ }
134
+ return available.sort((a, b) => {
135
+ const priorityA = TOOLS[a as keyof typeof TOOLS].priority;
136
+ const priorityB = TOOLS[b as keyof typeof TOOLS].priority;
137
+ return priorityA - priorityB;
138
+ });
139
+ }
140
+
141
+ // Get default tool
142
+ async function getDefaultTool(): Promise<string | null> {
143
+ const availableTools = await getAvailableTools();
144
+ if (availableTools.length === 0) return null;
145
+
146
+ if (availableTools.includes('hanzo-dev')) {
147
+ return 'hanzo-dev';
148
+ }
149
+
150
+ for (const tool of availableTools) {
151
+ if (await isToolInstalled(tool) && hasApiKey(tool)) {
152
+ return tool;
153
+ }
154
+ }
155
+
156
+ return availableTools[0];
157
+ }
158
+
159
+ // Run a tool
160
+ function runTool(tool: string, args: string[] = []): void {
161
+ const toolConfig = TOOLS[tool as keyof typeof TOOLS];
162
+ if (!toolConfig) {
163
+ console.error(chalk.red(`Unknown tool: ${tool}`));
164
+ process.exit(1);
165
+ }
166
+
167
+ console.log(toolConfig.color(`\nšŸš€ Launching ${toolConfig.name}...\n`));
168
+
169
+ if (tool === 'hanzo-dev' && hasUvx()) {
170
+ console.log(chalk.gray('Using uvx to run hanzo-dev...'));
171
+ }
172
+
173
+ const child = spawn(toolConfig.command, args, {
174
+ stdio: 'inherit',
175
+ shell: true,
176
+ env: process.env
177
+ });
178
+
179
+ child.on('error', (error) => {
180
+ console.error(chalk.red(`Failed to start ${toolConfig.name}: ${error.message}`));
181
+
182
+ if (tool === 'hanzo-dev') {
183
+ console.log(chalk.yellow('\nTo install hanzo-dev:'));
184
+ console.log(chalk.gray(' pip install hanzo-dev'));
185
+ console.log(chalk.gray(' # or'));
186
+ console.log(chalk.gray(' uvx hanzo-dev # (if you have uv installed)'));
187
+ }
188
+ process.exit(1);
189
+ });
190
+
191
+ child.on('exit', (code) => {
192
+ if (code !== 0) {
193
+ console.error(chalk.red(`${toolConfig.name} exited with code ${code}`));
194
+ }
195
+ process.exit(code || 0);
196
+ });
197
+ }
198
+
199
+ // Interactive editing mode using built-in editor
200
+ async function interactiveEditMode(): Promise<void> {
201
+ const editor = new FileEditor();
202
+ const functionCalling = new FunctionCallingSystem();
203
+ const mcpClient = new MCPClient();
204
+
205
+ console.log(chalk.bold.cyan('\nšŸ“ Hanzo Dev Editor - Interactive Mode\n'));
206
+ console.log(chalk.gray('Commands: view, create, str_replace, insert, undo_edit, run, list, search, mcp, help, exit\n'));
207
+
208
+ // Connect to default MCP servers if available
209
+ for (const serverConfig of DEFAULT_MCP_SERVERS) {
210
+ try {
211
+ console.log(chalk.gray(`Connecting to MCP server: ${serverConfig.name}...`));
212
+ const session = await mcpClient.connect(serverConfig);
213
+ await functionCalling.registerMCPServer(serverConfig.name, session);
214
+ console.log(chalk.green(`āœ“ Connected to ${serverConfig.name}`));
215
+ } catch (error) {
216
+ console.log(chalk.yellow(`⚠ Could not connect to ${serverConfig.name}`));
217
+ }
218
+ }
219
+
220
+ while (true) {
221
+ const { command } = await inquirer.prompt([{
222
+ type: 'input',
223
+ name: 'command',
224
+ message: chalk.green('editor>'),
225
+ prefix: ''
226
+ }]);
227
+
228
+ if (command === 'exit' || command === 'quit') {
229
+ break;
230
+ }
231
+
232
+ if (command === 'help') {
233
+ console.log(chalk.cyan('\nAvailable commands:'));
234
+ console.log(' view <file> [start] [end] - View file contents');
235
+ console.log(' create <file> - Create new file');
236
+ console.log(' str_replace <file> - Replace string in file');
237
+ console.log(' insert <file> <line> - Insert line in file');
238
+ console.log(' undo_edit <file> - Undo last edit');
239
+ console.log(' run <command> - Run shell command');
240
+ console.log(' list <directory> - List directory contents');
241
+ console.log(' search <pattern> [path] - Search for files');
242
+ console.log(' mcp - List MCP tools');
243
+ console.log(' tools - List all available tools');
244
+ console.log(' help - Show this help');
245
+ console.log(' exit - Exit editor\n');
246
+ continue;
247
+ }
248
+
249
+ if (command === 'tools') {
250
+ const tools = functionCalling.getAvailableTools();
251
+ console.log(chalk.cyan('\nAvailable tools:'));
252
+ tools.forEach(tool => {
253
+ console.log(` ${chalk.yellow(tool.name)} - ${tool.description}`);
254
+ });
255
+ console.log();
256
+ continue;
257
+ }
258
+
259
+ if (command === 'mcp') {
260
+ const sessions = mcpClient.getAllSessions();
261
+ console.log(chalk.cyan('\nMCP Sessions:'));
262
+ sessions.forEach(session => {
263
+ console.log(` ${chalk.yellow(session.id)}:`);
264
+ session.tools.forEach(tool => {
265
+ console.log(` - ${tool.name}: ${tool.description}`);
266
+ });
267
+ });
268
+ console.log();
269
+ continue;
270
+ }
271
+
272
+ // Parse and execute commands
273
+ const parts = command.split(' ');
274
+ const cmd = parts[0];
275
+
276
+ try {
277
+ let result;
278
+
279
+ switch (cmd) {
280
+ case 'view':
281
+ result = await editor.execute({
282
+ command: 'view',
283
+ path: parts[1],
284
+ startLine: parts[2] ? parseInt(parts[2]) : undefined,
285
+ endLine: parts[3] ? parseInt(parts[3]) : undefined
286
+ });
287
+ break;
288
+
289
+ case 'create':
290
+ const { content } = await inquirer.prompt([{
291
+ type: 'editor',
292
+ name: 'content',
293
+ message: 'Enter file content:'
294
+ }]);
295
+ result = await editor.execute({
296
+ command: 'create',
297
+ path: parts[1],
298
+ content
299
+ });
300
+ break;
301
+
302
+ case 'str_replace':
303
+ const { oldStr } = await inquirer.prompt([{
304
+ type: 'input',
305
+ name: 'oldStr',
306
+ message: 'String to replace:'
307
+ }]);
308
+ const { newStr } = await inquirer.prompt([{
309
+ type: 'input',
310
+ name: 'newStr',
311
+ message: 'Replacement string:'
312
+ }]);
313
+ result = await editor.execute({
314
+ command: 'str_replace',
315
+ path: parts[1],
316
+ oldStr,
317
+ newStr
318
+ });
319
+ break;
320
+
321
+ case 'insert':
322
+ const { lineContent } = await inquirer.prompt([{
323
+ type: 'input',
324
+ name: 'lineContent',
325
+ message: 'Line content:'
326
+ }]);
327
+ result = await editor.execute({
328
+ command: 'insert',
329
+ path: parts[1],
330
+ lineNumber: parseInt(parts[2]),
331
+ content: lineContent
332
+ });
333
+ break;
334
+
335
+ case 'undo_edit':
336
+ result = await editor.execute({
337
+ command: 'undo_edit',
338
+ path: parts[1]
339
+ });
340
+ break;
341
+
342
+ case 'run':
343
+ const runCommand = parts.slice(1).join(' ');
344
+ result = await functionCalling.callFunction({
345
+ id: Date.now().toString(),
346
+ name: 'run_command',
347
+ arguments: { command: runCommand }
348
+ });
349
+ break;
350
+
351
+ case 'list':
352
+ result = await functionCalling.callFunction({
353
+ id: Date.now().toString(),
354
+ name: 'list_directory',
355
+ arguments: { path: parts[1] || '.' }
356
+ });
357
+ break;
358
+
359
+ case 'search':
360
+ result = await functionCalling.callFunction({
361
+ id: Date.now().toString(),
362
+ name: 'search_files',
363
+ arguments: {
364
+ pattern: parts[1],
365
+ path: parts[2] || '.',
366
+ regex: false
367
+ }
368
+ });
369
+ break;
370
+
371
+ default:
372
+ console.log(chalk.red(`Unknown command: ${cmd}`));
373
+ continue;
374
+ }
375
+
376
+ if (result) {
377
+ if (result.success || result.result?.success) {
378
+ console.log(chalk.green('āœ“'), result.message || 'Success');
379
+ if (result.content || result.result?.stdout) {
380
+ console.log(result.content || result.result.stdout);
381
+ }
382
+ if (result.result?.files) {
383
+ result.result.files.forEach((file: any) => {
384
+ const icon = file.type === 'directory' ? 'šŸ“' : 'šŸ“„';
385
+ console.log(` ${icon} ${file.name}`);
386
+ });
387
+ }
388
+ if (result.result?.matches) {
389
+ console.log(`Found ${result.result.total} matches:`);
390
+ result.result.matches.forEach((match: string) => {
391
+ console.log(` šŸ“„ ${match}`);
392
+ });
393
+ }
394
+ } else {
395
+ console.log(chalk.red('āœ—'), result.message || result.error || 'Error');
396
+ if (result.result?.stderr) {
397
+ console.log(chalk.red(result.result.stderr));
398
+ }
399
+ }
400
+ }
401
+ } catch (error) {
402
+ console.log(chalk.red('Error:'), error);
403
+ }
404
+ }
405
+
406
+ console.log(chalk.gray('\nExiting editor mode...'));
407
+ }
408
+
409
+ // Interactive mode for tool selection
410
+ async function interactiveMode(): Promise<void> {
411
+ console.log(chalk.bold.cyan('\nšŸ¤– Hanzo Dev - AI Development Assistant\n'));
412
+
413
+ const envFiles = ['.env', '.env.local', '.env.development', '.env.production'];
414
+ const detectedEnvFiles = envFiles.filter(file => fs.existsSync(path.join(process.cwd(), file)));
415
+ if (detectedEnvFiles.length > 0) {
416
+ console.log(chalk.gray('šŸ“„ Detected environment files:'));
417
+ detectedEnvFiles.forEach(file => {
418
+ console.log(chalk.gray(` - ${file}`));
419
+ });
420
+ console.log();
421
+ }
422
+
423
+ const availableTools = await getAvailableTools();
424
+ const defaultTool = await getDefaultTool();
425
+
426
+ if (availableTools.length === 0 && !hasApiKey('hanzo-dev')) {
427
+ console.log(chalk.yellow('No AI tools available. Please either:'));
428
+ console.log(chalk.yellow('\n1. Install hanzo-dev (recommended):'));
429
+ console.log(chalk.gray(' pip install hanzo-dev'));
430
+ console.log(chalk.gray(' # or'));
431
+ console.log(chalk.gray(' uvx hanzo-dev'));
432
+
433
+ console.log(chalk.yellow('\n2. Install other tools:'));
434
+ console.log(chalk.gray(' npm install -g @hanzo/claude-code'));
435
+ console.log(chalk.gray(' pip install aider-chat'));
436
+
437
+ console.log(chalk.yellow('\n3. Or configure API keys in your .env file:'));
438
+ console.log(chalk.gray(' ANTHROPIC_API_KEY=sk-ant-...'));
439
+ console.log(chalk.gray(' OPENAI_API_KEY=sk-...'));
440
+ process.exit(1);
441
+ }
442
+
443
+ // Add built-in editor option
444
+ const choices = [
445
+ {
446
+ name: chalk.bold.yellow('šŸ”§ Built-in Editor - Interactive file editing and MCP tools'),
447
+ value: 'builtin-editor',
448
+ short: 'Built-in Editor'
449
+ }
450
+ ];
451
+
452
+ // Add external tools
453
+ for (const tool of availableTools) {
454
+ const toolConfig = TOOLS[tool as keyof typeof TOOLS];
455
+ const isInstalled = await isToolInstalled(tool);
456
+ const hasKey = hasApiKey(tool);
457
+
458
+ let status = '';
459
+ if (isInstalled && hasKey) {
460
+ status = chalk.green(' [Installed + API Key]');
461
+ } else if (isInstalled) {
462
+ status = chalk.yellow(' [Installed]');
463
+ } else if (hasKey) {
464
+ status = chalk.cyan(' [API Key Only]');
465
+ }
466
+
467
+ if (toolConfig.isDefault) {
468
+ status += chalk.bold.magenta(' ā˜… DEFAULT');
469
+ }
470
+
471
+ choices.push({
472
+ name: `${toolConfig.name} - ${toolConfig.description}${status}`,
473
+ value: tool,
474
+ short: toolConfig.name
475
+ });
476
+ }
477
+
478
+ const { selectedTool } = await inquirer.prompt([
479
+ {
480
+ type: 'list',
481
+ name: 'selectedTool',
482
+ message: 'Select a tool to launch:',
483
+ choices: choices,
484
+ default: defaultTool || 'builtin-editor'
485
+ }
486
+ ]);
487
+
488
+ if (selectedTool === 'builtin-editor') {
489
+ await interactiveEditMode();
490
+ return;
491
+ }
492
+
493
+ const { passDirectory } = await inquirer.prompt([
494
+ {
495
+ type: 'confirm',
496
+ name: 'passDirectory',
497
+ message: `Open in current directory (${process.cwd()})?`,
498
+ default: true
499
+ }
500
+ ]);
501
+
502
+ const args = passDirectory ? ['.'] : [];
503
+ runTool(selectedTool, args);
504
+ }
505
+
506
+ // Setup version
507
+ const packagePath = path.join(__dirname, '../../package.json');
508
+ let version = '2.0.0';
509
+ try {
510
+ const packageJson = JSON.parse(fs.readFileSync(packagePath, 'utf-8'));
511
+ version = packageJson.version;
512
+ } catch (error) {
513
+ // Use default version
514
+ }
515
+
516
+ program
517
+ .name('dev')
518
+ .description('Hanzo Dev - Meta AI development CLI with built-in editor and tool orchestration')
519
+ .version(version);
520
+
521
+ // Built-in editor command
7
522
  program
8
- .name('@hanzo/dev')
9
- .description('Hanzo Dev - Meta AI development CLI')
10
- .version('1.0.0');
523
+ .command('edit [path]')
524
+ .description('Launch built-in editor with file editing and MCP tools')
525
+ .action(async (path) => {
526
+ if (path && fs.existsSync(path)) {
527
+ process.chdir(path);
528
+ }
529
+ await interactiveEditMode();
530
+ });
531
+
532
+ // External tool commands
533
+ Object.entries(TOOLS).forEach(([toolKey, toolConfig]) => {
534
+ if (toolKey === 'hanzo-dev') {
535
+ // Special alias for hanzo-dev
536
+ program
537
+ .command('python [args...]')
538
+ .description('Launch Python hanzo-dev (OpenHands)')
539
+ .action(async (args) => {
540
+ const isInstalled = await isToolInstalled('hanzo-dev');
541
+ if (!isInstalled && !hasUvx()) {
542
+ console.error(chalk.red('Hanzo Dev is not installed.'));
543
+ console.log(chalk.yellow('\nTo install:'));
544
+ console.log(chalk.gray(' pip install hanzo-dev'));
545
+ console.log(chalk.gray(' # or'));
546
+ console.log(chalk.gray(' pip install uv && uvx hanzo-dev'));
547
+ process.exit(1);
548
+ }
549
+ runTool('hanzo-dev', args);
550
+ });
551
+ }
552
+
553
+ program
554
+ .command(`${toolKey} [args...]`)
555
+ .description(`Launch ${toolConfig.name}`)
556
+ .action(async (args) => {
557
+ const isInstalled = await isToolInstalled(toolKey);
558
+ const hasKey = hasApiKey(toolKey);
559
+
560
+ if (!isInstalled && !hasKey) {
561
+ console.error(chalk.red(`${toolConfig.name} is not available.`));
562
+ process.exit(1);
563
+ }
564
+
565
+ runTool(toolKey, args);
566
+ });
567
+ });
568
+
569
+ // List command
570
+ program
571
+ .command('list')
572
+ .alias('ls')
573
+ .description('List all available AI tools and API keys')
574
+ .action(async () => {
575
+ console.log(chalk.bold.cyan('\nšŸ“‹ AI Tools Status:\n'));
576
+
577
+ const envFiles = ['.env', '.env.local', '.env.development', '.env.production'];
578
+ const detectedEnvFiles = envFiles.filter(file => fs.existsSync(path.join(process.cwd(), file)));
579
+ if (detectedEnvFiles.length > 0) {
580
+ console.log(chalk.bold('Environment files:'));
581
+ detectedEnvFiles.forEach(file => {
582
+ console.log(chalk.gray(` šŸ“„ ${file}`));
583
+ });
584
+ console.log();
585
+ }
586
+
587
+ console.log(chalk.bold('Built-in Features:'));
588
+ console.log(chalk.green(' āœ“ Interactive Editor') + chalk.gray(' - File editing with view, create, str_replace'));
589
+ console.log(chalk.green(' āœ“ MCP Client') + chalk.gray(' - Model Context Protocol tool integration'));
590
+ console.log(chalk.green(' āœ“ Function Calling') + chalk.gray(' - Unified tool interface'));
591
+ console.log();
592
+
593
+ if (hasUvx()) {
594
+ console.log(chalk.bold('Package Manager:'));
595
+ console.log(chalk.green(' āœ“ uvx available (can run Python tools without installation)'));
596
+ console.log();
597
+ }
598
+
599
+ console.log(chalk.bold('External Tools:'));
600
+ for (const [toolKey, toolConfig] of Object.entries(TOOLS)) {
601
+ const isInstalled = await isToolInstalled(toolKey);
602
+ const hasKey = hasApiKey(toolKey);
603
+
604
+ let status = chalk.red('āœ— Not Available');
605
+ if (toolKey === 'hanzo-dev' && hasUvx()) {
606
+ status = chalk.green('āœ“ Ready (via uvx)');
607
+ } else if (isInstalled && hasKey) {
608
+ status = chalk.green('āœ“ Ready (Installed + API Key)');
609
+ } else if (isInstalled) {
610
+ status = chalk.yellow('⚠ Installed (No API Key)');
611
+ } else if (hasKey) {
612
+ status = chalk.cyan('☁ API Mode (Not Installed)');
613
+ }
614
+
615
+ let displayName = toolConfig.color(toolConfig.name);
616
+ if (toolConfig.isDefault) {
617
+ displayName += chalk.bold.magenta(' ā˜…');
618
+ }
619
+
620
+ console.log(` ${status} ${displayName}`);
621
+ console.log(chalk.gray(` ${toolConfig.description}`));
622
+ }
623
+ });
624
+
625
+ // Status command
626
+ program
627
+ .command('status')
628
+ .description('Show current working directory and environment')
629
+ .action(() => {
630
+ const config = new ConfigManager();
631
+
632
+ console.log(chalk.bold.cyan('\nšŸ“Š Hanzo Dev Status\n'));
633
+ console.log(`Current Directory: ${chalk.green(process.cwd())}`);
634
+ console.log(`User: ${chalk.green(os.userInfo().username)}`);
635
+ console.log(`Node Version: ${chalk.green(process.version)}`);
636
+ console.log(`Platform: ${chalk.green(os.platform())}`);
637
+ console.log(`Dev Version: ${chalk.green(version)}`);
638
+
639
+ if (hasUvx()) {
640
+ console.log(`UV/UVX: ${chalk.green('āœ“ Available')}`);
641
+ }
642
+
643
+ const envFiles = ['.env', '.env.local', '.env.development', '.env.production'];
644
+ const detectedEnvFiles = envFiles.filter(file => fs.existsSync(path.join(process.cwd(), file)));
645
+ if (detectedEnvFiles.length > 0) {
646
+ console.log(`\nEnvironment Files:`);
647
+ detectedEnvFiles.forEach(file => {
648
+ console.log(chalk.green(` āœ“ ${file}`));
649
+ });
650
+ }
651
+
652
+ console.log(`\nConfiguration:`);
653
+ const cfg = config.getConfig();
654
+ console.log(` Default Agent: ${chalk.yellow(cfg.defaultAgent)}`);
655
+ console.log(` Runtime: ${chalk.yellow(cfg.runtime || 'cli')}`);
656
+ console.log(` Confirmation Mode: ${chalk.yellow(cfg.security.confirmationMode ? 'On' : 'Off')}`);
657
+ });
658
+
659
+ // Workspace command - unified workspace with shell, editor, browser, planner
660
+ program
661
+ .command('workspace')
662
+ .alias('ws')
663
+ .description('Launch unified workspace with shell, editor, browser, and planner')
664
+ .action(async () => {
665
+ const session = new WorkspaceSession();
666
+ await session.start();
667
+ });
11
668
 
669
+ // Swarm command - spawn multiple agents for parallel work
12
670
  program
13
- .command('claude [query...]')
14
- .description('Run Claude AI')
15
- .action((query) => {
16
- console.log(chalk.blue('šŸ¤– Running Claude AI...'));
17
- console.log('Query:', query.join(' '));
671
+ .command('swarm [path]')
672
+ .description('Spawn agent swarm for codebase (one agent per file/directory)')
673
+ .option('-t, --type <type>', 'Agent type (claude-code, aider, openhands)', 'claude-code')
674
+ .option('-s, --strategy <strategy>', 'Assignment strategy (one-per-file, one-per-directory, by-complexity)', 'one-per-file')
675
+ .action(async (path, options) => {
676
+ const targetPath = path || process.cwd();
677
+ const network = new PeerAgentNetwork();
678
+
679
+ try {
680
+ await network.spawnAgentsForCodebase(targetPath, options.type, options.strategy);
681
+
682
+ // Show network status
683
+ const status = network.getNetworkStatus();
684
+ console.log(chalk.cyan('\nšŸ“Š Network Status:'));
685
+ console.log(` Agents: ${status.totalAgents} (${status.activeAgents} active)`);
686
+ console.log(` Connections: ${status.totalConnections}`);
687
+
688
+ // Interactive swarm control
689
+ const { action } = await inquirer.prompt([{
690
+ type: 'list',
691
+ name: 'action',
692
+ message: 'What would you like to do?',
693
+ choices: [
694
+ { name: 'Execute task on swarm', value: 'task' },
695
+ { name: 'Run parallel tasks', value: 'parallel' },
696
+ { name: 'Show network status', value: 'status' },
697
+ { name: 'Exit', value: 'exit' }
698
+ ]
699
+ }]);
700
+
701
+ if (action === 'task') {
702
+ const { task } = await inquirer.prompt([{
703
+ type: 'input',
704
+ name: 'task',
705
+ message: 'Enter task for swarm:'
706
+ }]);
707
+
708
+ await network.coordinateSwarm(task);
709
+ } else if (action === 'parallel') {
710
+ console.log(chalk.yellow('Enter tasks (one per line, empty line to finish):'));
711
+ const tasks: Array<{task: string}> = [];
712
+
713
+ while (true) {
714
+ const { task } = await inquirer.prompt([{
715
+ type: 'input',
716
+ name: 'task',
717
+ message: '>'
718
+ }]);
719
+
720
+ if (!task) break;
721
+ tasks.push({ task });
722
+ }
723
+
724
+ if (tasks.length > 0) {
725
+ await network.executeParallelTasks(tasks);
726
+ }
727
+ } else if (action === 'status') {
728
+ const status = network.getNetworkStatus();
729
+ console.log(chalk.cyan('\nšŸ“Š Detailed Network Status:'));
730
+ console.log(JSON.stringify(status, null, 2));
731
+ }
732
+
733
+ await network.cleanup();
734
+ } catch (error) {
735
+ console.error(chalk.red(`Swarm error: ${error}`));
736
+ await network.cleanup();
737
+ }
18
738
  });
19
739
 
740
+ // Agent command - run a task with CodeAct agent
20
741
  program
21
- .command('codex [query...]')
22
- .description('Run OpenAI Codex')
23
- .action((query) => {
24
- console.log(chalk.green('šŸ¤– Running OpenAI Codex...'));
25
- console.log('Query:', query.join(' '));
742
+ .command('agent <task>')
743
+ .description('Execute a task using CodeAct agent with automatic planning and error correction')
744
+ .action(async (task) => {
745
+ const agent = new CodeActAgent();
746
+ try {
747
+ await agent.executeTask(task);
748
+ } catch (error) {
749
+ console.error(chalk.red(`Agent error: ${error}`));
750
+ }
26
751
  });
27
752
 
753
+ // Default action
28
754
  program
29
- .command('gemini [query...]')
30
- .description('Run Google Gemini')
31
- .action((query) => {
32
- console.log(chalk.yellow('šŸ¤– Running Google Gemini...'));
33
- console.log('Query:', query.join(' '));
755
+ .action(async () => {
756
+ const defaultTool = await getDefaultTool();
757
+ if (defaultTool && process.argv.length === 2) {
758
+ console.log(chalk.gray(`Auto-launching ${TOOLS[defaultTool as keyof typeof TOOLS].name}...`));
759
+ runTool(defaultTool, ['.']);
760
+ } else {
761
+ interactiveMode();
762
+ }
34
763
  });
35
764
 
36
- program.parse();
765
+ // Parse arguments
766
+ program.parse();
767
+
768
+ // If no arguments, run interactive mode
769
+ if (process.argv.length === 2) {
770
+ (async () => {
771
+ const defaultTool = await getDefaultTool();
772
+ if (defaultTool) {
773
+ console.log(chalk.gray(`Auto-launching ${TOOLS[defaultTool as keyof typeof TOOLS].name}...`));
774
+ runTool(defaultTool, ['.']);
775
+ } else {
776
+ interactiveMode();
777
+ }
778
+ })();
779
+ }