@hanzo/dev 1.2.0 → 2.1.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/.eslintrc.json +24 -0
- package/README.md +359 -0
- package/dist/cli/dev.js +21724 -602
- package/package.json +19 -4
- package/src/cli/dev.ts +623 -106
- package/src/lib/agent-loop.ts +552 -0
- package/src/lib/benchmark-runner.ts +431 -0
- package/src/lib/code-act-agent.ts +378 -0
- package/src/lib/config.ts +163 -0
- package/src/lib/editor.ts +395 -0
- package/src/lib/function-calling.ts +318 -0
- package/src/lib/mcp-client.ts +259 -0
- package/src/lib/peer-agent-network.ts +584 -0
- package/src/lib/swarm-runner.ts +379 -0
- package/src/lib/unified-workspace.ts +435 -0
- package/test-swarm/file1.js +6 -0
- package/test-swarm/file2.ts +12 -0
- package/test-swarm/file3.py +15 -0
- package/test-swarm/file4.md +13 -0
- package/test-swarm/file5.json +12 -0
- package/test-swarm-demo.sh +22 -0
- package/tests/browser-integration.test.ts +242 -0
- package/tests/code-act-agent.test.ts +305 -0
- package/tests/editor.test.ts +223 -0
- package/tests/fixtures/sample-code.js +13 -0
- package/tests/fixtures/sample-code.py +28 -0
- package/tests/fixtures/sample-code.ts +22 -0
- package/tests/mcp-client.test.ts +238 -0
- package/tests/peer-agent-network.test.ts +340 -0
- package/tests/swarm-runner.test.ts +301 -0
- package/tests/swe-bench.test.ts +357 -0
- package/tsconfig.json +13 -15
- package/vitest.config.ts +37 -0
package/src/cli/dev.ts
CHANGED
|
@@ -2,10 +2,20 @@
|
|
|
2
2
|
import { Command } from 'commander';
|
|
3
3
|
import chalk from 'chalk';
|
|
4
4
|
import inquirer from 'inquirer';
|
|
5
|
-
import { spawn } from 'child_process';
|
|
5
|
+
import { spawn, execSync } from 'child_process';
|
|
6
6
|
import * as fs from 'fs';
|
|
7
7
|
import * as path from 'path';
|
|
8
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';
|
|
16
|
+
import { BenchmarkRunner, BenchmarkConfig } from '../lib/benchmark-runner';
|
|
17
|
+
import { ConfigurableAgentLoop, LLMProvider } from '../lib/agent-loop';
|
|
18
|
+
import { SwarmRunner, SwarmOptions } from '../lib/swarm-runner';
|
|
9
19
|
|
|
10
20
|
const program = new Command();
|
|
11
21
|
|
|
@@ -23,7 +33,6 @@ function loadEnvFiles(): void {
|
|
|
23
33
|
if (match) {
|
|
24
34
|
const key = match[1].trim();
|
|
25
35
|
const value = match[2].trim();
|
|
26
|
-
// Only set if not already in environment
|
|
27
36
|
if (!process.env[key]) {
|
|
28
37
|
process.env[key] = value;
|
|
29
38
|
}
|
|
@@ -36,8 +45,28 @@ function loadEnvFiles(): void {
|
|
|
36
45
|
// Load env files on startup
|
|
37
46
|
loadEnvFiles();
|
|
38
47
|
|
|
39
|
-
//
|
|
48
|
+
// Check if uvx is available
|
|
49
|
+
function hasUvx(): boolean {
|
|
50
|
+
try {
|
|
51
|
+
execSync('which uvx', { stdio: 'ignore' });
|
|
52
|
+
return true;
|
|
53
|
+
} catch {
|
|
54
|
+
return false;
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
// Available tools configuration
|
|
40
59
|
const TOOLS = {
|
|
60
|
+
'hanzo-dev': {
|
|
61
|
+
name: 'Hanzo Dev (OpenHands)',
|
|
62
|
+
command: hasUvx() ? 'uvx hanzo-dev' : 'hanzo-dev',
|
|
63
|
+
checkCommand: hasUvx() ? 'which uvx' : 'which hanzo-dev',
|
|
64
|
+
description: 'Hanzo AI software development agent - Full featured dev environment',
|
|
65
|
+
color: chalk.magenta,
|
|
66
|
+
apiKeys: ['OPENAI_API_KEY', 'ANTHROPIC_API_KEY', 'LLM_API_KEY', 'HANZO_API_KEY'],
|
|
67
|
+
priority: 1,
|
|
68
|
+
isDefault: true
|
|
69
|
+
},
|
|
41
70
|
claude: {
|
|
42
71
|
name: 'Claude (Anthropic)',
|
|
43
72
|
command: 'claude-code',
|
|
@@ -45,7 +74,7 @@ const TOOLS = {
|
|
|
45
74
|
description: 'Claude Code - AI coding assistant',
|
|
46
75
|
color: chalk.blue,
|
|
47
76
|
apiKeys: ['ANTHROPIC_API_KEY', 'CLAUDE_API_KEY'],
|
|
48
|
-
priority:
|
|
77
|
+
priority: 2
|
|
49
78
|
},
|
|
50
79
|
aider: {
|
|
51
80
|
name: 'Aider',
|
|
@@ -54,15 +83,6 @@ const TOOLS = {
|
|
|
54
83
|
description: 'AI pair programming in your terminal',
|
|
55
84
|
color: chalk.green,
|
|
56
85
|
apiKeys: ['OPENAI_API_KEY', 'ANTHROPIC_API_KEY', 'CLAUDE_API_KEY'],
|
|
57
|
-
priority: 2
|
|
58
|
-
},
|
|
59
|
-
openhands: {
|
|
60
|
-
name: 'OpenHands',
|
|
61
|
-
command: 'openhands',
|
|
62
|
-
checkCommand: 'which openhands',
|
|
63
|
-
description: 'AI software development agent',
|
|
64
|
-
color: chalk.magenta,
|
|
65
|
-
apiKeys: ['OPENAI_API_KEY', 'ANTHROPIC_API_KEY', 'LLM_API_KEY'],
|
|
66
86
|
priority: 3
|
|
67
87
|
},
|
|
68
88
|
gemini: {
|
|
@@ -104,7 +124,7 @@ async function isToolInstalled(tool: string): Promise<boolean> {
|
|
|
104
124
|
});
|
|
105
125
|
}
|
|
106
126
|
|
|
107
|
-
// Get list of available tools
|
|
127
|
+
// Get list of available tools
|
|
108
128
|
async function getAvailableTools(): Promise<string[]> {
|
|
109
129
|
const available: string[] = [];
|
|
110
130
|
for (const toolKey of Object.keys(TOOLS)) {
|
|
@@ -114,7 +134,6 @@ async function getAvailableTools(): Promise<string[]> {
|
|
|
114
134
|
available.push(toolKey);
|
|
115
135
|
}
|
|
116
136
|
}
|
|
117
|
-
// Sort by priority
|
|
118
137
|
return available.sort((a, b) => {
|
|
119
138
|
const priorityA = TOOLS[a as keyof typeof TOOLS].priority;
|
|
120
139
|
const priorityB = TOOLS[b as keyof typeof TOOLS].priority;
|
|
@@ -122,23 +141,25 @@ async function getAvailableTools(): Promise<string[]> {
|
|
|
122
141
|
});
|
|
123
142
|
}
|
|
124
143
|
|
|
125
|
-
// Get default tool
|
|
144
|
+
// Get default tool
|
|
126
145
|
async function getDefaultTool(): Promise<string | null> {
|
|
127
146
|
const availableTools = await getAvailableTools();
|
|
128
147
|
if (availableTools.length === 0) return null;
|
|
129
148
|
|
|
130
|
-
|
|
149
|
+
if (availableTools.includes('hanzo-dev')) {
|
|
150
|
+
return 'hanzo-dev';
|
|
151
|
+
}
|
|
152
|
+
|
|
131
153
|
for (const tool of availableTools) {
|
|
132
154
|
if (await isToolInstalled(tool) && hasApiKey(tool)) {
|
|
133
155
|
return tool;
|
|
134
156
|
}
|
|
135
157
|
}
|
|
136
158
|
|
|
137
|
-
// Otherwise return first available
|
|
138
159
|
return availableTools[0];
|
|
139
160
|
}
|
|
140
161
|
|
|
141
|
-
// Run a tool
|
|
162
|
+
// Run a tool
|
|
142
163
|
function runTool(tool: string, args: string[] = []): void {
|
|
143
164
|
const toolConfig = TOOLS[tool as keyof typeof TOOLS];
|
|
144
165
|
if (!toolConfig) {
|
|
@@ -148,19 +169,24 @@ function runTool(tool: string, args: string[] = []): void {
|
|
|
148
169
|
|
|
149
170
|
console.log(toolConfig.color(`\n🚀 Launching ${toolConfig.name}...\n`));
|
|
150
171
|
|
|
172
|
+
if (tool === 'hanzo-dev' && hasUvx()) {
|
|
173
|
+
console.log(chalk.gray('Using uvx to run hanzo-dev...'));
|
|
174
|
+
}
|
|
175
|
+
|
|
151
176
|
const child = spawn(toolConfig.command, args, {
|
|
152
177
|
stdio: 'inherit',
|
|
153
178
|
shell: true,
|
|
154
|
-
env: process.env
|
|
179
|
+
env: process.env
|
|
155
180
|
});
|
|
156
181
|
|
|
157
182
|
child.on('error', (error) => {
|
|
158
183
|
console.error(chalk.red(`Failed to start ${toolConfig.name}: ${error.message}`));
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
184
|
+
|
|
185
|
+
if (tool === 'hanzo-dev') {
|
|
186
|
+
console.log(chalk.yellow('\nTo install hanzo-dev:'));
|
|
187
|
+
console.log(chalk.gray(' pip install hanzo-dev'));
|
|
188
|
+
console.log(chalk.gray(' # or'));
|
|
189
|
+
console.log(chalk.gray(' uvx hanzo-dev # (if you have uv installed)'));
|
|
164
190
|
}
|
|
165
191
|
process.exit(1);
|
|
166
192
|
});
|
|
@@ -173,11 +199,220 @@ function runTool(tool: string, args: string[] = []): void {
|
|
|
173
199
|
});
|
|
174
200
|
}
|
|
175
201
|
|
|
176
|
-
// Interactive mode
|
|
202
|
+
// Interactive editing mode using built-in editor
|
|
203
|
+
async function interactiveEditMode(): Promise<void> {
|
|
204
|
+
const editor = new FileEditor();
|
|
205
|
+
const functionCalling = new FunctionCallingSystem();
|
|
206
|
+
const mcpClient = new MCPClient();
|
|
207
|
+
|
|
208
|
+
console.log(chalk.bold.cyan('\n📝 Hanzo Dev Editor - Interactive Mode\n'));
|
|
209
|
+
console.log(chalk.gray('Commands: view, create, str_replace, insert, undo_edit, run, list, search, mcp, help, exit\n'));
|
|
210
|
+
|
|
211
|
+
// Connect to default MCP servers if available
|
|
212
|
+
for (const serverConfig of DEFAULT_MCP_SERVERS) {
|
|
213
|
+
try {
|
|
214
|
+
console.log(chalk.gray(`Connecting to MCP server: ${serverConfig.name}...`));
|
|
215
|
+
const session = await mcpClient.connect(serverConfig);
|
|
216
|
+
await functionCalling.registerMCPServer(serverConfig.name, session);
|
|
217
|
+
console.log(chalk.green(`✓ Connected to ${serverConfig.name}`));
|
|
218
|
+
} catch (error) {
|
|
219
|
+
console.log(chalk.yellow(`⚠ Could not connect to ${serverConfig.name}`));
|
|
220
|
+
}
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
while (true) {
|
|
224
|
+
const { command } = await inquirer.prompt([{
|
|
225
|
+
type: 'input',
|
|
226
|
+
name: 'command',
|
|
227
|
+
message: chalk.green('editor>'),
|
|
228
|
+
prefix: ''
|
|
229
|
+
}]);
|
|
230
|
+
|
|
231
|
+
if (command === 'exit' || command === 'quit') {
|
|
232
|
+
break;
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
if (command === 'help') {
|
|
236
|
+
console.log(chalk.cyan('\nAvailable commands:'));
|
|
237
|
+
console.log(' view <file> [start] [end] - View file contents');
|
|
238
|
+
console.log(' create <file> - Create new file');
|
|
239
|
+
console.log(' str_replace <file> - Replace string in file');
|
|
240
|
+
console.log(' insert <file> <line> - Insert line in file');
|
|
241
|
+
console.log(' undo_edit <file> - Undo last edit');
|
|
242
|
+
console.log(' run <command> - Run shell command');
|
|
243
|
+
console.log(' list <directory> - List directory contents');
|
|
244
|
+
console.log(' search <pattern> [path] - Search for files');
|
|
245
|
+
console.log(' mcp - List MCP tools');
|
|
246
|
+
console.log(' tools - List all available tools');
|
|
247
|
+
console.log(' help - Show this help');
|
|
248
|
+
console.log(' exit - Exit editor\n');
|
|
249
|
+
continue;
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
if (command === 'tools') {
|
|
253
|
+
const tools = functionCalling.getAvailableTools();
|
|
254
|
+
console.log(chalk.cyan('\nAvailable tools:'));
|
|
255
|
+
tools.forEach(tool => {
|
|
256
|
+
console.log(` ${chalk.yellow(tool.name)} - ${tool.description}`);
|
|
257
|
+
});
|
|
258
|
+
console.log();
|
|
259
|
+
continue;
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
if (command === 'mcp') {
|
|
263
|
+
const sessions = mcpClient.getAllSessions();
|
|
264
|
+
console.log(chalk.cyan('\nMCP Sessions:'));
|
|
265
|
+
sessions.forEach(session => {
|
|
266
|
+
console.log(` ${chalk.yellow(session.id)}:`);
|
|
267
|
+
session.tools.forEach(tool => {
|
|
268
|
+
console.log(` - ${tool.name}: ${tool.description}`);
|
|
269
|
+
});
|
|
270
|
+
});
|
|
271
|
+
console.log();
|
|
272
|
+
continue;
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
// Parse and execute commands
|
|
276
|
+
const parts = command.split(' ');
|
|
277
|
+
const cmd = parts[0];
|
|
278
|
+
|
|
279
|
+
try {
|
|
280
|
+
let result;
|
|
281
|
+
|
|
282
|
+
switch (cmd) {
|
|
283
|
+
case 'view':
|
|
284
|
+
result = await editor.execute({
|
|
285
|
+
command: 'view',
|
|
286
|
+
path: parts[1],
|
|
287
|
+
startLine: parts[2] ? parseInt(parts[2]) : undefined,
|
|
288
|
+
endLine: parts[3] ? parseInt(parts[3]) : undefined
|
|
289
|
+
});
|
|
290
|
+
break;
|
|
291
|
+
|
|
292
|
+
case 'create':
|
|
293
|
+
const { content } = await inquirer.prompt([{
|
|
294
|
+
type: 'editor',
|
|
295
|
+
name: 'content',
|
|
296
|
+
message: 'Enter file content:'
|
|
297
|
+
}]);
|
|
298
|
+
result = await editor.execute({
|
|
299
|
+
command: 'create',
|
|
300
|
+
path: parts[1],
|
|
301
|
+
content
|
|
302
|
+
});
|
|
303
|
+
break;
|
|
304
|
+
|
|
305
|
+
case 'str_replace':
|
|
306
|
+
const { oldStr } = await inquirer.prompt([{
|
|
307
|
+
type: 'input',
|
|
308
|
+
name: 'oldStr',
|
|
309
|
+
message: 'String to replace:'
|
|
310
|
+
}]);
|
|
311
|
+
const { newStr } = await inquirer.prompt([{
|
|
312
|
+
type: 'input',
|
|
313
|
+
name: 'newStr',
|
|
314
|
+
message: 'Replacement string:'
|
|
315
|
+
}]);
|
|
316
|
+
result = await editor.execute({
|
|
317
|
+
command: 'str_replace',
|
|
318
|
+
path: parts[1],
|
|
319
|
+
oldStr,
|
|
320
|
+
newStr
|
|
321
|
+
});
|
|
322
|
+
break;
|
|
323
|
+
|
|
324
|
+
case 'insert':
|
|
325
|
+
const { lineContent } = await inquirer.prompt([{
|
|
326
|
+
type: 'input',
|
|
327
|
+
name: 'lineContent',
|
|
328
|
+
message: 'Line content:'
|
|
329
|
+
}]);
|
|
330
|
+
result = await editor.execute({
|
|
331
|
+
command: 'insert',
|
|
332
|
+
path: parts[1],
|
|
333
|
+
lineNumber: parseInt(parts[2]),
|
|
334
|
+
content: lineContent
|
|
335
|
+
});
|
|
336
|
+
break;
|
|
337
|
+
|
|
338
|
+
case 'undo_edit':
|
|
339
|
+
result = await editor.execute({
|
|
340
|
+
command: 'undo_edit',
|
|
341
|
+
path: parts[1]
|
|
342
|
+
});
|
|
343
|
+
break;
|
|
344
|
+
|
|
345
|
+
case 'run':
|
|
346
|
+
const runCommand = parts.slice(1).join(' ');
|
|
347
|
+
result = await functionCalling.callFunction({
|
|
348
|
+
id: Date.now().toString(),
|
|
349
|
+
name: 'run_command',
|
|
350
|
+
arguments: { command: runCommand }
|
|
351
|
+
});
|
|
352
|
+
break;
|
|
353
|
+
|
|
354
|
+
case 'list':
|
|
355
|
+
result = await functionCalling.callFunction({
|
|
356
|
+
id: Date.now().toString(),
|
|
357
|
+
name: 'list_directory',
|
|
358
|
+
arguments: { path: parts[1] || '.' }
|
|
359
|
+
});
|
|
360
|
+
break;
|
|
361
|
+
|
|
362
|
+
case 'search':
|
|
363
|
+
result = await functionCalling.callFunction({
|
|
364
|
+
id: Date.now().toString(),
|
|
365
|
+
name: 'search_files',
|
|
366
|
+
arguments: {
|
|
367
|
+
pattern: parts[1],
|
|
368
|
+
path: parts[2] || '.',
|
|
369
|
+
regex: false
|
|
370
|
+
}
|
|
371
|
+
});
|
|
372
|
+
break;
|
|
373
|
+
|
|
374
|
+
default:
|
|
375
|
+
console.log(chalk.red(`Unknown command: ${cmd}`));
|
|
376
|
+
continue;
|
|
377
|
+
}
|
|
378
|
+
|
|
379
|
+
if (result) {
|
|
380
|
+
if (result.success || result.result?.success) {
|
|
381
|
+
console.log(chalk.green('✓'), result.message || 'Success');
|
|
382
|
+
if (result.content || result.result?.stdout) {
|
|
383
|
+
console.log(result.content || result.result.stdout);
|
|
384
|
+
}
|
|
385
|
+
if (result.result?.files) {
|
|
386
|
+
result.result.files.forEach((file: any) => {
|
|
387
|
+
const icon = file.type === 'directory' ? '📁' : '📄';
|
|
388
|
+
console.log(` ${icon} ${file.name}`);
|
|
389
|
+
});
|
|
390
|
+
}
|
|
391
|
+
if (result.result?.matches) {
|
|
392
|
+
console.log(`Found ${result.result.total} matches:`);
|
|
393
|
+
result.result.matches.forEach((match: string) => {
|
|
394
|
+
console.log(` 📄 ${match}`);
|
|
395
|
+
});
|
|
396
|
+
}
|
|
397
|
+
} else {
|
|
398
|
+
console.log(chalk.red('✗'), result.message || result.error || 'Error');
|
|
399
|
+
if (result.result?.stderr) {
|
|
400
|
+
console.log(chalk.red(result.result.stderr));
|
|
401
|
+
}
|
|
402
|
+
}
|
|
403
|
+
}
|
|
404
|
+
} catch (error) {
|
|
405
|
+
console.log(chalk.red('Error:'), error);
|
|
406
|
+
}
|
|
407
|
+
}
|
|
408
|
+
|
|
409
|
+
console.log(chalk.gray('\nExiting editor mode...'));
|
|
410
|
+
}
|
|
411
|
+
|
|
412
|
+
// Interactive mode for tool selection
|
|
177
413
|
async function interactiveMode(): Promise<void> {
|
|
178
414
|
console.log(chalk.bold.cyan('\n🤖 Hanzo Dev - AI Development Assistant\n'));
|
|
179
415
|
|
|
180
|
-
// Show detected environment files
|
|
181
416
|
const envFiles = ['.env', '.env.local', '.env.development', '.env.production'];
|
|
182
417
|
const detectedEnvFiles = envFiles.filter(file => fs.existsSync(path.join(process.cwd(), file)));
|
|
183
418
|
if (detectedEnvFiles.length > 0) {
|
|
@@ -191,41 +426,34 @@ async function interactiveMode(): Promise<void> {
|
|
|
191
426
|
const availableTools = await getAvailableTools();
|
|
192
427
|
const defaultTool = await getDefaultTool();
|
|
193
428
|
|
|
194
|
-
if (availableTools.length === 0) {
|
|
429
|
+
if (availableTools.length === 0 && !hasApiKey('hanzo-dev')) {
|
|
195
430
|
console.log(chalk.yellow('No AI tools available. Please either:'));
|
|
196
|
-
console.log(chalk.yellow('\n1. Install
|
|
431
|
+
console.log(chalk.yellow('\n1. Install hanzo-dev (recommended):'));
|
|
432
|
+
console.log(chalk.gray(' pip install hanzo-dev'));
|
|
433
|
+
console.log(chalk.gray(' # or'));
|
|
434
|
+
console.log(chalk.gray(' uvx hanzo-dev'));
|
|
435
|
+
|
|
436
|
+
console.log(chalk.yellow('\n2. Install other tools:'));
|
|
197
437
|
console.log(chalk.gray(' npm install -g @hanzo/claude-code'));
|
|
198
438
|
console.log(chalk.gray(' pip install aider-chat'));
|
|
199
|
-
console.log(chalk.gray(' pip install openhands'));
|
|
200
439
|
|
|
201
|
-
console.log(chalk.yellow('\
|
|
440
|
+
console.log(chalk.yellow('\n3. Or configure API keys in your .env file:'));
|
|
202
441
|
console.log(chalk.gray(' ANTHROPIC_API_KEY=sk-ant-...'));
|
|
203
442
|
console.log(chalk.gray(' OPENAI_API_KEY=sk-...'));
|
|
204
|
-
console.log(chalk.gray(' GOOGLE_API_KEY=...'));
|
|
205
443
|
process.exit(1);
|
|
206
444
|
}
|
|
207
445
|
|
|
208
|
-
//
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
let hasAnyKey = false;
|
|
216
|
-
allApiKeys.forEach(key => {
|
|
217
|
-
if (process.env[key]) {
|
|
218
|
-
console.log(chalk.green(` ✓ ${key}`));
|
|
219
|
-
hasAnyKey = true;
|
|
446
|
+
// Add built-in editor option
|
|
447
|
+
const choices = [
|
|
448
|
+
{
|
|
449
|
+
name: chalk.bold.yellow('🔧 Built-in Editor - Interactive file editing and MCP tools'),
|
|
450
|
+
value: 'builtin-editor',
|
|
451
|
+
short: 'Built-in Editor'
|
|
220
452
|
}
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
if (!hasAnyKey) {
|
|
224
|
-
console.log(chalk.gray(' (none detected)'));
|
|
225
|
-
}
|
|
226
|
-
console.log();
|
|
453
|
+
];
|
|
227
454
|
|
|
228
|
-
|
|
455
|
+
// Add external tools
|
|
456
|
+
for (const tool of availableTools) {
|
|
229
457
|
const toolConfig = TOOLS[tool as keyof typeof TOOLS];
|
|
230
458
|
const isInstalled = await isToolInstalled(tool);
|
|
231
459
|
const hasKey = hasApiKey(tool);
|
|
@@ -239,24 +467,32 @@ async function interactiveMode(): Promise<void> {
|
|
|
239
467
|
status = chalk.cyan(' [API Key Only]');
|
|
240
468
|
}
|
|
241
469
|
|
|
242
|
-
|
|
470
|
+
if (toolConfig.isDefault) {
|
|
471
|
+
status += chalk.bold.magenta(' ★ DEFAULT');
|
|
472
|
+
}
|
|
473
|
+
|
|
474
|
+
choices.push({
|
|
243
475
|
name: `${toolConfig.name} - ${toolConfig.description}${status}`,
|
|
244
476
|
value: tool,
|
|
245
477
|
short: toolConfig.name
|
|
246
|
-
};
|
|
247
|
-
}
|
|
478
|
+
});
|
|
479
|
+
}
|
|
248
480
|
|
|
249
481
|
const { selectedTool } = await inquirer.prompt([
|
|
250
482
|
{
|
|
251
483
|
type: 'list',
|
|
252
484
|
name: 'selectedTool',
|
|
253
|
-
message: 'Select
|
|
485
|
+
message: 'Select a tool to launch:',
|
|
254
486
|
choices: choices,
|
|
255
|
-
default: defaultTool
|
|
487
|
+
default: defaultTool || 'builtin-editor'
|
|
256
488
|
}
|
|
257
489
|
]);
|
|
258
490
|
|
|
259
|
-
|
|
491
|
+
if (selectedTool === 'builtin-editor') {
|
|
492
|
+
await interactiveEditMode();
|
|
493
|
+
return;
|
|
494
|
+
}
|
|
495
|
+
|
|
260
496
|
const { passDirectory } = await inquirer.prompt([
|
|
261
497
|
{
|
|
262
498
|
type: 'confirm',
|
|
@@ -270,23 +506,53 @@ async function interactiveMode(): Promise<void> {
|
|
|
270
506
|
runTool(selectedTool, args);
|
|
271
507
|
}
|
|
272
508
|
|
|
273
|
-
// Setup version
|
|
509
|
+
// Setup version
|
|
274
510
|
const packagePath = path.join(__dirname, '../../package.json');
|
|
275
|
-
let version = '
|
|
511
|
+
let version = '2.0.0';
|
|
276
512
|
try {
|
|
277
513
|
const packageJson = JSON.parse(fs.readFileSync(packagePath, 'utf-8'));
|
|
278
514
|
version = packageJson.version;
|
|
279
515
|
} catch (error) {
|
|
280
|
-
// Use default version
|
|
516
|
+
// Use default version
|
|
281
517
|
}
|
|
282
518
|
|
|
283
519
|
program
|
|
284
520
|
.name('dev')
|
|
285
|
-
.description('Hanzo Dev - Meta AI development CLI
|
|
521
|
+
.description('Hanzo Dev - Meta AI development CLI with built-in editor and tool orchestration')
|
|
286
522
|
.version(version);
|
|
287
523
|
|
|
288
|
-
//
|
|
524
|
+
// Built-in editor command
|
|
525
|
+
program
|
|
526
|
+
.command('edit [path]')
|
|
527
|
+
.description('Launch built-in editor with file editing and MCP tools')
|
|
528
|
+
.action(async (path) => {
|
|
529
|
+
if (path && fs.existsSync(path)) {
|
|
530
|
+
process.chdir(path);
|
|
531
|
+
}
|
|
532
|
+
await interactiveEditMode();
|
|
533
|
+
});
|
|
534
|
+
|
|
535
|
+
// External tool commands
|
|
289
536
|
Object.entries(TOOLS).forEach(([toolKey, toolConfig]) => {
|
|
537
|
+
if (toolKey === 'hanzo-dev') {
|
|
538
|
+
// Special alias for hanzo-dev
|
|
539
|
+
program
|
|
540
|
+
.command('python [args...]')
|
|
541
|
+
.description('Launch Python hanzo-dev (OpenHands)')
|
|
542
|
+
.action(async (args) => {
|
|
543
|
+
const isInstalled = await isToolInstalled('hanzo-dev');
|
|
544
|
+
if (!isInstalled && !hasUvx()) {
|
|
545
|
+
console.error(chalk.red('Hanzo Dev is not installed.'));
|
|
546
|
+
console.log(chalk.yellow('\nTo install:'));
|
|
547
|
+
console.log(chalk.gray(' pip install hanzo-dev'));
|
|
548
|
+
console.log(chalk.gray(' # or'));
|
|
549
|
+
console.log(chalk.gray(' pip install uv && uvx hanzo-dev'));
|
|
550
|
+
process.exit(1);
|
|
551
|
+
}
|
|
552
|
+
runTool('hanzo-dev', args);
|
|
553
|
+
});
|
|
554
|
+
}
|
|
555
|
+
|
|
290
556
|
program
|
|
291
557
|
.command(`${toolKey} [args...]`)
|
|
292
558
|
.description(`Launch ${toolConfig.name}`)
|
|
@@ -296,25 +562,14 @@ Object.entries(TOOLS).forEach(([toolKey, toolConfig]) => {
|
|
|
296
562
|
|
|
297
563
|
if (!isInstalled && !hasKey) {
|
|
298
564
|
console.error(chalk.red(`${toolConfig.name} is not available.`));
|
|
299
|
-
console.log(chalk.yellow(`\nTo use ${toolConfig.name}, you need to either:`));
|
|
300
|
-
console.log(chalk.yellow('1. Install it:'));
|
|
301
|
-
console.log(chalk.gray(` Follow installation instructions for ${toolConfig.name}`));
|
|
302
|
-
console.log(chalk.yellow('\n2. Configure API keys:'));
|
|
303
|
-
toolConfig.apiKeys?.forEach(key => {
|
|
304
|
-
console.log(chalk.gray(` ${key}=your-api-key`));
|
|
305
|
-
});
|
|
306
565
|
process.exit(1);
|
|
307
566
|
}
|
|
308
567
|
|
|
309
|
-
if (!isInstalled) {
|
|
310
|
-
console.log(chalk.yellow(`Note: ${toolConfig.name} is not installed locally, using API mode.`));
|
|
311
|
-
}
|
|
312
|
-
|
|
313
568
|
runTool(toolKey, args);
|
|
314
569
|
});
|
|
315
570
|
});
|
|
316
571
|
|
|
317
|
-
// List
|
|
572
|
+
// List command
|
|
318
573
|
program
|
|
319
574
|
.command('list')
|
|
320
575
|
.alias('ls')
|
|
@@ -322,7 +577,6 @@ program
|
|
|
322
577
|
.action(async () => {
|
|
323
578
|
console.log(chalk.bold.cyan('\n📋 AI Tools Status:\n'));
|
|
324
579
|
|
|
325
|
-
// Show environment files
|
|
326
580
|
const envFiles = ['.env', '.env.local', '.env.development', '.env.production'];
|
|
327
581
|
const detectedEnvFiles = envFiles.filter(file => fs.existsSync(path.join(process.cwd(), file)));
|
|
328
582
|
if (detectedEnvFiles.length > 0) {
|
|
@@ -333,34 +587,27 @@ program
|
|
|
333
587
|
console.log();
|
|
334
588
|
}
|
|
335
589
|
|
|
336
|
-
|
|
337
|
-
console.log(chalk.
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
});
|
|
342
|
-
|
|
343
|
-
let hasAnyKey = false;
|
|
344
|
-
allApiKeys.forEach(key => {
|
|
345
|
-
if (process.env[key]) {
|
|
346
|
-
console.log(chalk.green(` ✓ ${key} = ${process.env[key]?.substring(0, 10)}...`));
|
|
347
|
-
hasAnyKey = true;
|
|
348
|
-
}
|
|
349
|
-
});
|
|
590
|
+
console.log(chalk.bold('Built-in Features:'));
|
|
591
|
+
console.log(chalk.green(' ✓ Interactive Editor') + chalk.gray(' - File editing with view, create, str_replace'));
|
|
592
|
+
console.log(chalk.green(' ✓ MCP Client') + chalk.gray(' - Model Context Protocol tool integration'));
|
|
593
|
+
console.log(chalk.green(' ✓ Function Calling') + chalk.gray(' - Unified tool interface'));
|
|
594
|
+
console.log();
|
|
350
595
|
|
|
351
|
-
if (
|
|
352
|
-
console.log(chalk.
|
|
596
|
+
if (hasUvx()) {
|
|
597
|
+
console.log(chalk.bold('Package Manager:'));
|
|
598
|
+
console.log(chalk.green(' ✓ uvx available (can run Python tools without installation)'));
|
|
599
|
+
console.log();
|
|
353
600
|
}
|
|
354
|
-
console.log();
|
|
355
601
|
|
|
356
|
-
|
|
357
|
-
console.log(chalk.bold('Tools:'));
|
|
602
|
+
console.log(chalk.bold('External Tools:'));
|
|
358
603
|
for (const [toolKey, toolConfig] of Object.entries(TOOLS)) {
|
|
359
604
|
const isInstalled = await isToolInstalled(toolKey);
|
|
360
605
|
const hasKey = hasApiKey(toolKey);
|
|
361
606
|
|
|
362
607
|
let status = chalk.red('✗ Not Available');
|
|
363
|
-
if (
|
|
608
|
+
if (toolKey === 'hanzo-dev' && hasUvx()) {
|
|
609
|
+
status = chalk.green('✓ Ready (via uvx)');
|
|
610
|
+
} else if (isInstalled && hasKey) {
|
|
364
611
|
status = chalk.green('✓ Ready (Installed + API Key)');
|
|
365
612
|
} else if (isInstalled) {
|
|
366
613
|
status = chalk.yellow('⚠ Installed (No API Key)');
|
|
@@ -368,11 +615,13 @@ program
|
|
|
368
615
|
status = chalk.cyan('☁ API Mode (Not Installed)');
|
|
369
616
|
}
|
|
370
617
|
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
console.log(chalk.gray(` Requires: ${toolConfig.apiKeys.join(' or ')}`));
|
|
618
|
+
let displayName = toolConfig.color(toolConfig.name);
|
|
619
|
+
if (toolConfig.isDefault) {
|
|
620
|
+
displayName += chalk.bold.magenta(' ★');
|
|
375
621
|
}
|
|
622
|
+
|
|
623
|
+
console.log(` ${status} ${displayName}`);
|
|
624
|
+
console.log(chalk.gray(` ${toolConfig.description}`));
|
|
376
625
|
}
|
|
377
626
|
});
|
|
378
627
|
|
|
@@ -381,13 +630,19 @@ program
|
|
|
381
630
|
.command('status')
|
|
382
631
|
.description('Show current working directory and environment')
|
|
383
632
|
.action(() => {
|
|
633
|
+
const config = new ConfigManager();
|
|
634
|
+
|
|
384
635
|
console.log(chalk.bold.cyan('\n📊 Hanzo Dev Status\n'));
|
|
385
636
|
console.log(`Current Directory: ${chalk.green(process.cwd())}`);
|
|
386
637
|
console.log(`User: ${chalk.green(os.userInfo().username)}`);
|
|
387
638
|
console.log(`Node Version: ${chalk.green(process.version)}`);
|
|
388
639
|
console.log(`Platform: ${chalk.green(os.platform())}`);
|
|
640
|
+
console.log(`Dev Version: ${chalk.green(version)}`);
|
|
641
|
+
|
|
642
|
+
if (hasUvx()) {
|
|
643
|
+
console.log(`UV/UVX: ${chalk.green('✓ Available')}`);
|
|
644
|
+
}
|
|
389
645
|
|
|
390
|
-
// Show environment files
|
|
391
646
|
const envFiles = ['.env', '.env.local', '.env.development', '.env.production'];
|
|
392
647
|
const detectedEnvFiles = envFiles.filter(file => fs.existsSync(path.join(process.cwd(), file)));
|
|
393
648
|
if (detectedEnvFiles.length > 0) {
|
|
@@ -396,18 +651,280 @@ program
|
|
|
396
651
|
console.log(chalk.green(` ✓ ${file}`));
|
|
397
652
|
});
|
|
398
653
|
}
|
|
654
|
+
|
|
655
|
+
console.log(`\nConfiguration:`);
|
|
656
|
+
const cfg = config.getConfig();
|
|
657
|
+
console.log(` Default Agent: ${chalk.yellow(cfg.defaultAgent)}`);
|
|
658
|
+
console.log(` Runtime: ${chalk.yellow(cfg.runtime || 'cli')}`);
|
|
659
|
+
console.log(` Confirmation Mode: ${chalk.yellow(cfg.security.confirmationMode ? 'On' : 'Off')}`);
|
|
399
660
|
});
|
|
400
661
|
|
|
401
|
-
//
|
|
662
|
+
// Workspace command - unified workspace with shell, editor, browser, planner
|
|
402
663
|
program
|
|
664
|
+
.command('workspace')
|
|
665
|
+
.alias('ws')
|
|
666
|
+
.description('Launch unified workspace with shell, editor, browser, and planner')
|
|
403
667
|
.action(async () => {
|
|
668
|
+
const session = new WorkspaceSession();
|
|
669
|
+
await session.start();
|
|
670
|
+
});
|
|
671
|
+
|
|
672
|
+
// Swarm command - spawn multiple agents for parallel work
|
|
673
|
+
program
|
|
674
|
+
.command('swarm [path]')
|
|
675
|
+
.description('Spawn agent swarm for codebase (one agent per file/directory)')
|
|
676
|
+
.option('-t, --type <type>', 'Agent type (claude-code, aider, openhands)', 'claude-code')
|
|
677
|
+
.option('-s, --strategy <strategy>', 'Assignment strategy (one-per-file, one-per-directory, by-complexity)', 'one-per-file')
|
|
678
|
+
.action(async (path, options) => {
|
|
679
|
+
const targetPath = path || process.cwd();
|
|
680
|
+
const network = new PeerAgentNetwork();
|
|
681
|
+
|
|
682
|
+
try {
|
|
683
|
+
await network.spawnAgentsForCodebase(targetPath, options.type, options.strategy);
|
|
684
|
+
|
|
685
|
+
// Show network status
|
|
686
|
+
const status = network.getNetworkStatus();
|
|
687
|
+
console.log(chalk.cyan('\n📊 Network Status:'));
|
|
688
|
+
console.log(` Agents: ${status.totalAgents} (${status.activeAgents} active)`);
|
|
689
|
+
console.log(` Connections: ${status.totalConnections}`);
|
|
690
|
+
|
|
691
|
+
// Interactive swarm control
|
|
692
|
+
const { action } = await inquirer.prompt([{
|
|
693
|
+
type: 'list',
|
|
694
|
+
name: 'action',
|
|
695
|
+
message: 'What would you like to do?',
|
|
696
|
+
choices: [
|
|
697
|
+
{ name: 'Execute task on swarm', value: 'task' },
|
|
698
|
+
{ name: 'Run parallel tasks', value: 'parallel' },
|
|
699
|
+
{ name: 'Show network status', value: 'status' },
|
|
700
|
+
{ name: 'Exit', value: 'exit' }
|
|
701
|
+
]
|
|
702
|
+
}]);
|
|
703
|
+
|
|
704
|
+
if (action === 'task') {
|
|
705
|
+
const { task } = await inquirer.prompt([{
|
|
706
|
+
type: 'input',
|
|
707
|
+
name: 'task',
|
|
708
|
+
message: 'Enter task for swarm:'
|
|
709
|
+
}]);
|
|
710
|
+
|
|
711
|
+
await network.coordinateSwarm(task);
|
|
712
|
+
} else if (action === 'parallel') {
|
|
713
|
+
console.log(chalk.yellow('Enter tasks (one per line, empty line to finish):'));
|
|
714
|
+
const tasks: Array<{task: string}> = [];
|
|
715
|
+
|
|
716
|
+
while (true) {
|
|
717
|
+
const { task } = await inquirer.prompt([{
|
|
718
|
+
type: 'input',
|
|
719
|
+
name: 'task',
|
|
720
|
+
message: '>'
|
|
721
|
+
}]);
|
|
722
|
+
|
|
723
|
+
if (!task) break;
|
|
724
|
+
tasks.push({ task });
|
|
725
|
+
}
|
|
726
|
+
|
|
727
|
+
if (tasks.length > 0) {
|
|
728
|
+
await network.executeParallelTasks(tasks);
|
|
729
|
+
}
|
|
730
|
+
} else if (action === 'status') {
|
|
731
|
+
const status = network.getNetworkStatus();
|
|
732
|
+
console.log(chalk.cyan('\n📊 Detailed Network Status:'));
|
|
733
|
+
console.log(JSON.stringify(status, null, 2));
|
|
734
|
+
}
|
|
735
|
+
|
|
736
|
+
await network.cleanup();
|
|
737
|
+
} catch (error) {
|
|
738
|
+
console.error(chalk.red(`Swarm error: ${error}`));
|
|
739
|
+
await network.cleanup();
|
|
740
|
+
}
|
|
741
|
+
});
|
|
742
|
+
|
|
743
|
+
// Agent command - run a task with CodeAct agent
|
|
744
|
+
program
|
|
745
|
+
.command('agent <task>')
|
|
746
|
+
.description('Execute a task using CodeAct agent with automatic planning and error correction')
|
|
747
|
+
.action(async (task) => {
|
|
748
|
+
const agent = new CodeActAgent();
|
|
749
|
+
try {
|
|
750
|
+
await agent.executeTask(task);
|
|
751
|
+
} catch (error) {
|
|
752
|
+
console.error(chalk.red(`Agent error: ${error}`));
|
|
753
|
+
}
|
|
754
|
+
});
|
|
755
|
+
|
|
756
|
+
// Benchmark command - run SWE-bench evaluation
|
|
757
|
+
program
|
|
758
|
+
.command('benchmark')
|
|
759
|
+
.alias('bench')
|
|
760
|
+
.description('Run SWE-bench evaluation to measure performance')
|
|
761
|
+
.option('-d, --dataset <dataset>', 'Dataset to use (swe-bench, swe-bench-lite, custom)', 'swe-bench-lite')
|
|
762
|
+
.option('-a, --agents <number>', 'Number of agents for parallel execution', '5')
|
|
763
|
+
.option('-p, --parallel', 'Run tasks in parallel', true)
|
|
764
|
+
.option('-t, --timeout <ms>', 'Timeout per task in milliseconds', '300000')
|
|
765
|
+
.option('-o, --output <file>', 'Output file for results', 'benchmark-results.json')
|
|
766
|
+
.option('--provider <provider>', 'LLM provider (claude, openai, gemini, local)')
|
|
767
|
+
.option('--max-tasks <number>', 'Maximum number of tasks to run')
|
|
768
|
+
.action(async (options) => {
|
|
769
|
+
console.log(chalk.bold.cyan('\n🏃 Starting Hanzo Dev Benchmark\n'));
|
|
770
|
+
|
|
771
|
+
// Parse options
|
|
772
|
+
const config: BenchmarkConfig = {
|
|
773
|
+
dataset: options.dataset as any,
|
|
774
|
+
agents: parseInt(options.agents),
|
|
775
|
+
parallel: options.parallel !== 'false',
|
|
776
|
+
timeout: parseInt(options.timeout),
|
|
777
|
+
output: options.output,
|
|
778
|
+
maxTasks: options.maxTasks ? parseInt(options.maxTasks) : undefined
|
|
779
|
+
};
|
|
780
|
+
|
|
781
|
+
// Set provider if specified
|
|
782
|
+
if (options.provider) {
|
|
783
|
+
const providers = ConfigurableAgentLoop.getAvailableProviders();
|
|
784
|
+
const provider = providers.find(p =>
|
|
785
|
+
p.type === options.provider ||
|
|
786
|
+
p.name.toLowerCase().includes(options.provider.toLowerCase())
|
|
787
|
+
);
|
|
788
|
+
|
|
789
|
+
if (provider) {
|
|
790
|
+
config.provider = provider;
|
|
791
|
+
} else {
|
|
792
|
+
console.error(chalk.red(`Provider '${options.provider}' not found or not configured`));
|
|
793
|
+
console.log(chalk.yellow('\nAvailable providers:'));
|
|
794
|
+
providers.forEach(p => {
|
|
795
|
+
console.log(` - ${p.name} (${p.type})`);
|
|
796
|
+
});
|
|
797
|
+
process.exit(1);
|
|
798
|
+
}
|
|
799
|
+
}
|
|
800
|
+
|
|
801
|
+
// Run benchmark
|
|
802
|
+
const runner = new BenchmarkRunner(config);
|
|
803
|
+
|
|
804
|
+
try {
|
|
805
|
+
await runner.run();
|
|
806
|
+
console.log(chalk.green('\n✅ Benchmark completed successfully'));
|
|
807
|
+
} catch (error) {
|
|
808
|
+
console.error(chalk.red(`\n❌ Benchmark failed: ${error}`));
|
|
809
|
+
process.exit(1);
|
|
810
|
+
}
|
|
811
|
+
});
|
|
812
|
+
|
|
813
|
+
// Add global options for provider and swarm
|
|
814
|
+
program
|
|
815
|
+
.option('--claude', 'Use Claude AI provider')
|
|
816
|
+
.option('--openai', 'Use OpenAI provider')
|
|
817
|
+
.option('--gemini', 'Use Gemini provider')
|
|
818
|
+
.option('--grok', 'Use Grok provider')
|
|
819
|
+
.option('--local', 'Use local AI provider')
|
|
820
|
+
.option('--swarm <count>', 'Launch swarm of agents (up to 100)')
|
|
821
|
+
.option('-p, --prompt <prompt>', 'Task prompt for agents');
|
|
822
|
+
|
|
823
|
+
// Swarm mode function
|
|
824
|
+
async function runSwarmMode(options: any): Promise<void> {
|
|
825
|
+
// Determine provider
|
|
826
|
+
let provider: SwarmOptions['provider'] = 'claude';
|
|
827
|
+
if (options.claude) provider = 'claude';
|
|
828
|
+
else if (options.openai) provider = 'openai';
|
|
829
|
+
else if (options.gemini) provider = 'gemini';
|
|
830
|
+
else if (options.grok) provider = 'grok';
|
|
831
|
+
else if (options.local) provider = 'local';
|
|
832
|
+
|
|
833
|
+
// Parse swarm count
|
|
834
|
+
const count = Math.min(parseInt(options.swarm) || 5, 100);
|
|
835
|
+
|
|
836
|
+
if (!options.prompt) {
|
|
837
|
+
console.error(chalk.red('Error: --prompt is required when using --swarm'));
|
|
838
|
+
process.exit(1);
|
|
839
|
+
}
|
|
840
|
+
|
|
841
|
+
const swarmOptions: SwarmOptions = {
|
|
842
|
+
provider,
|
|
843
|
+
count,
|
|
844
|
+
prompt: options.prompt,
|
|
845
|
+
cwd: process.cwd(),
|
|
846
|
+
autoLogin: true
|
|
847
|
+
};
|
|
848
|
+
|
|
849
|
+
console.log(chalk.bold.cyan(`\n🐝 Hanzo Dev Swarm Mode\n`));
|
|
850
|
+
console.log(chalk.gray(`Provider: ${provider}`));
|
|
851
|
+
console.log(chalk.gray(`Agents: ${count}`));
|
|
852
|
+
console.log(chalk.gray(`Prompt: ${options.prompt}\n`));
|
|
853
|
+
|
|
854
|
+
const runner = new SwarmRunner(swarmOptions);
|
|
855
|
+
|
|
856
|
+
// Check authentication
|
|
857
|
+
const hasAuth = await runner.ensureProviderAuth();
|
|
858
|
+
if (!hasAuth) {
|
|
859
|
+
console.error(chalk.red(`\nError: ${provider} is not authenticated`));
|
|
860
|
+
console.log(chalk.yellow('\nTo authenticate:'));
|
|
861
|
+
|
|
862
|
+
switch (provider) {
|
|
863
|
+
case 'claude':
|
|
864
|
+
console.log(chalk.gray(' 1. Set ANTHROPIC_API_KEY environment variable'));
|
|
865
|
+
console.log(chalk.gray(' 2. Run: claude login'));
|
|
866
|
+
break;
|
|
867
|
+
case 'openai':
|
|
868
|
+
console.log(chalk.gray(' Set OPENAI_API_KEY environment variable'));
|
|
869
|
+
break;
|
|
870
|
+
case 'gemini':
|
|
871
|
+
console.log(chalk.gray(' Set GOOGLE_API_KEY or GEMINI_API_KEY environment variable'));
|
|
872
|
+
break;
|
|
873
|
+
case 'grok':
|
|
874
|
+
console.log(chalk.gray(' Set GROK_API_KEY environment variable'));
|
|
875
|
+
break;
|
|
876
|
+
}
|
|
877
|
+
process.exit(1);
|
|
878
|
+
}
|
|
879
|
+
|
|
880
|
+
try {
|
|
881
|
+
await runner.run();
|
|
882
|
+
} catch (error) {
|
|
883
|
+
console.error(chalk.red(`\nSwarm error: ${error}`));
|
|
884
|
+
process.exit(1);
|
|
885
|
+
}
|
|
886
|
+
}
|
|
887
|
+
|
|
888
|
+
// Default action
|
|
889
|
+
program
|
|
890
|
+
.action(async (options) => {
|
|
891
|
+
// Check if swarm mode is requested
|
|
892
|
+
if (options.swarm) {
|
|
893
|
+
await runSwarmMode(options);
|
|
894
|
+
return;
|
|
895
|
+
}
|
|
896
|
+
|
|
897
|
+
// Check if a specific provider is requested
|
|
898
|
+
if (options.claude || options.openai || options.gemini || options.grok || options.local) {
|
|
899
|
+
let provider = 'claude';
|
|
900
|
+
if (options.claude) provider = 'claude';
|
|
901
|
+
else if (options.openai) provider = 'openai';
|
|
902
|
+
else if (options.gemini) provider = 'gemini';
|
|
903
|
+
else if (options.grok) provider = 'grok';
|
|
904
|
+
else if (options.local) provider = 'local';
|
|
905
|
+
|
|
906
|
+
// Map provider to tool name
|
|
907
|
+
const toolMap: Record<string, string> = {
|
|
908
|
+
claude: 'claude',
|
|
909
|
+
openai: 'codex',
|
|
910
|
+
gemini: 'gemini',
|
|
911
|
+
grok: 'grok',
|
|
912
|
+
local: 'hanzo-dev'
|
|
913
|
+
};
|
|
914
|
+
|
|
915
|
+
const toolName = toolMap[provider];
|
|
916
|
+
if (toolName && TOOLS[toolName as keyof typeof TOOLS]) {
|
|
917
|
+
console.log(chalk.gray(`Launching ${TOOLS[toolName as keyof typeof TOOLS].name}...`));
|
|
918
|
+
runTool(toolName, options.prompt ? [options.prompt] : ['.']);
|
|
919
|
+
return;
|
|
920
|
+
}
|
|
921
|
+
}
|
|
922
|
+
|
|
404
923
|
const defaultTool = await getDefaultTool();
|
|
405
924
|
if (defaultTool && process.argv.length === 2) {
|
|
406
|
-
|
|
407
|
-
console.log(chalk.gray(`Auto-selecting ${TOOLS[defaultTool as keyof typeof TOOLS].name} based on available API keys...`));
|
|
925
|
+
console.log(chalk.gray(`Auto-launching ${TOOLS[defaultTool as keyof typeof TOOLS].name}...`));
|
|
408
926
|
runTool(defaultTool, ['.']);
|
|
409
927
|
} else {
|
|
410
|
-
// Otherwise show interactive mode
|
|
411
928
|
interactiveMode();
|
|
412
929
|
}
|
|
413
930
|
});
|
|
@@ -415,12 +932,12 @@ program
|
|
|
415
932
|
// Parse arguments
|
|
416
933
|
program.parse();
|
|
417
934
|
|
|
418
|
-
// If no arguments
|
|
935
|
+
// If no arguments, run interactive mode
|
|
419
936
|
if (process.argv.length === 2) {
|
|
420
937
|
(async () => {
|
|
421
938
|
const defaultTool = await getDefaultTool();
|
|
422
939
|
if (defaultTool) {
|
|
423
|
-
console.log(chalk.gray(`Auto-
|
|
940
|
+
console.log(chalk.gray(`Auto-launching ${TOOLS[defaultTool as keyof typeof TOOLS].name}...`));
|
|
424
941
|
runTool(defaultTool, ['.']);
|
|
425
942
|
} else {
|
|
426
943
|
interactiveMode();
|