@hanzo/dev 2.1.1 → 3.0.2

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.
@@ -1,389 +0,0 @@
1
- import * as fs from 'fs';
2
- import * as path from 'path';
3
- import { glob } from 'glob';
4
- import chalk from 'chalk';
5
- import ora from 'ora';
6
- import { spawn, ChildProcess } from 'child_process';
7
- import { EventEmitter } from 'events';
8
-
9
- export interface SwarmOptions {
10
- provider: 'claude' | 'openai' | 'gemini' | 'grok' | 'local';
11
- count: number;
12
- prompt: string;
13
- cwd?: string;
14
- pattern?: string;
15
- autoLogin?: boolean;
16
- }
17
-
18
- export interface SwarmAgent {
19
- id: string;
20
- process?: ChildProcess;
21
- file?: string;
22
- status: 'idle' | 'busy' | 'done' | 'error';
23
- result?: string;
24
- error?: string;
25
- }
26
-
27
- export class SwarmRunner extends EventEmitter {
28
- private agents: Map<string, SwarmAgent> = new Map();
29
- private fileQueue: string[] = [];
30
- private options: SwarmOptions;
31
- private activeCount: number = 0;
32
-
33
- constructor(options: SwarmOptions) {
34
- super();
35
- this.options = {
36
- cwd: process.cwd(),
37
- pattern: '**/*',
38
- autoLogin: true,
39
- ...options
40
- };
41
- }
42
-
43
- async run(): Promise<void> {
44
- const spinner = ora(`Initializing swarm with ${this.options.count} agents...`).start();
45
-
46
- try {
47
- // Find files to process
48
- spinner.text = `Searching for files in ${this.options.cwd || process.cwd()}...`;
49
- this.fileQueue = await this.findFiles();
50
- spinner.succeed(`Found ${this.fileQueue.length} files to process`);
51
-
52
- if (this.fileQueue.length === 0) {
53
- console.log(chalk.yellow('No files found matching pattern'));
54
- return;
55
- }
56
-
57
- // Initialize agent pool
58
- const agentCount = Math.min(this.options.count, this.fileQueue.length);
59
- spinner.start(`Spawning ${agentCount} agents...`);
60
-
61
- for (let i = 0; i < agentCount; i++) {
62
- const agent: SwarmAgent = {
63
- id: `agent-${i}`,
64
- status: 'idle'
65
- };
66
- this.agents.set(agent.id, agent);
67
- }
68
-
69
- spinner.succeed(`Spawned ${agentCount} agents`);
70
-
71
- // Process files in parallel
72
- spinner.start('Processing files...');
73
- const startTime = Date.now();
74
-
75
- // Start processing
76
- await this.processFiles();
77
-
78
- const duration = (Date.now() - startTime) / 1000;
79
- spinner.succeed(`Completed in ${duration.toFixed(1)}s`);
80
-
81
- // Show results
82
- this.showResults();
83
-
84
- } catch (error) {
85
- spinner.fail(`Swarm error: ${error}`);
86
- throw error;
87
- }
88
- }
89
-
90
- private async findFiles(): Promise<string[]> {
91
- return new Promise((resolve, reject) => {
92
- const options = {
93
- cwd: this.options.cwd,
94
- nodir: true,
95
- ignore: [
96
- '**/node_modules/**',
97
- '**/.git/**',
98
- '**/dist/**',
99
- '**/build/**',
100
- '**/*.min.js',
101
- '**/*.map'
102
- ]
103
- };
104
-
105
- // Add timeout to prevent hanging
106
- const timeout = setTimeout(() => {
107
- reject(new Error('File search timed out'));
108
- }, 30000);
109
-
110
- const pattern = this.options.pattern || '**/*';
111
- console.log(chalk.gray(`Searching with pattern: ${pattern} in ${options.cwd || process.cwd()}`));
112
-
113
- glob(pattern, options, (err, files) => {
114
- clearTimeout(timeout);
115
- if (err) {
116
- console.error(chalk.red('Glob error:'), err);
117
- reject(err);
118
- } else {
119
- console.log(chalk.gray(`Found ${files.length} total files`));
120
- // Filter to only editable files
121
- const editableFiles = files.filter(file => {
122
- const ext = path.extname(file);
123
- return ['.js', '.ts', '.jsx', '.tsx', '.py', '.java', '.cpp', '.c', '.h', '.go', '.rs', '.rb', '.php', '.swift', '.kt', '.scala', '.r', '.m', '.mm', '.md', '.txt', '.json', '.xml', '.yaml', '.yml', '.toml', '.ini', '.conf', '.sh', '.bash', '.zsh', '.fish', '.ps1', '.bat', '.cmd'].includes(ext);
124
- });
125
- console.log(chalk.gray(`Filtered to ${editableFiles.length} editable files`));
126
- resolve(editableFiles);
127
- }
128
- });
129
- });
130
- }
131
-
132
- private async processFiles(): Promise<void> {
133
- const promises: Promise<void>[] = [];
134
-
135
- // Start initial batch of work
136
- for (const [id, agent] of this.agents) {
137
- if (this.fileQueue.length > 0) {
138
- promises.push(this.processNextFile(agent));
139
- }
140
- }
141
-
142
- // Wait for all agents to complete
143
- await Promise.all(promises);
144
- }
145
-
146
- private async processNextFile(agent: SwarmAgent): Promise<void> {
147
- while (this.fileQueue.length > 0) {
148
- const file = this.fileQueue.shift();
149
- if (!file) break;
150
-
151
- agent.file = file;
152
- agent.status = 'busy';
153
- this.activeCount++;
154
-
155
- try {
156
- await this.processFile(agent, file);
157
- agent.status = 'done';
158
- } catch (error) {
159
- agent.status = 'error';
160
- agent.error = error instanceof Error ? error.message : String(error);
161
- } finally {
162
- this.activeCount--;
163
- }
164
- }
165
- }
166
-
167
- private async processFile(agent: SwarmAgent, file: string): Promise<void> {
168
- const fullPath = path.join(this.options.cwd!, file);
169
-
170
- // Build command based on provider
171
- const command = this.buildCommand(file);
172
-
173
- return new Promise((resolve, reject) => {
174
- const child = spawn(command.cmd, command.args, {
175
- cwd: this.options.cwd,
176
- env: {
177
- ...process.env,
178
- ANTHROPIC_API_KEY: process.env.ANTHROPIC_API_KEY,
179
- OPENAI_API_KEY: process.env.OPENAI_API_KEY,
180
- GOOGLE_API_KEY: process.env.GOOGLE_API_KEY,
181
- GROK_API_KEY: process.env.GROK_API_KEY,
182
- // Auto-accept edits for non-interactive mode
183
- CLAUDE_CODE_PERMISSION_MODE: 'acceptEdits'
184
- }
185
- });
186
-
187
- agent.process = child;
188
-
189
- let output = '';
190
- let error = '';
191
-
192
- child.stdout?.on('data', (data) => {
193
- output += data.toString();
194
- });
195
-
196
- child.stderr?.on('data', (data) => {
197
- error += data.toString();
198
- });
199
-
200
- child.on('close', (code) => {
201
- if (code === 0) {
202
- agent.result = output;
203
- resolve();
204
- } else {
205
- reject(new Error(`Process exited with code ${code}: ${error}`));
206
- }
207
- });
208
-
209
- child.on('error', (err) => {
210
- reject(err);
211
- });
212
- });
213
- }
214
-
215
- private buildCommand(file: string): { cmd: string, args: string[] } {
216
- const fullPath = path.join(this.options.cwd!, file);
217
- const filePrompt = `${this.options.prompt}\n\nFile: ${file}`;
218
-
219
- switch (this.options.provider) {
220
- case 'claude':
221
- return {
222
- cmd: 'claude',
223
- args: [
224
- '-p',
225
- filePrompt,
226
- '--max-turns', '5',
227
- '--allowedTools', 'Read,Write,Edit',
228
- '--permission-mode', 'acceptEdits'
229
- ]
230
- };
231
-
232
- case 'openai':
233
- return {
234
- cmd: 'openai',
235
- args: [
236
- 'chat',
237
- '--prompt', filePrompt,
238
- '--file', fullPath,
239
- '--edit'
240
- ]
241
- };
242
-
243
- case 'gemini':
244
- return {
245
- cmd: 'gemini',
246
- args: [
247
- 'edit',
248
- fullPath,
249
- '--prompt', filePrompt
250
- ]
251
- };
252
-
253
- case 'grok':
254
- return {
255
- cmd: 'grok',
256
- args: [
257
- '--edit',
258
- fullPath,
259
- '--prompt', filePrompt
260
- ]
261
- };
262
-
263
- case 'local':
264
- return {
265
- cmd: 'dev',
266
- args: [
267
- 'agent',
268
- filePrompt
269
- ]
270
- };
271
-
272
- default:
273
- throw new Error(`Unknown provider: ${this.options.provider}`);
274
- }
275
- }
276
-
277
- private showResults(): void {
278
- console.log(chalk.bold.cyan('\nšŸ“Š Swarm Results\n'));
279
-
280
- let successful = 0;
281
- let failed = 0;
282
-
283
- for (const [id, agent] of this.agents) {
284
- if (agent.status === 'done') {
285
- successful++;
286
- console.log(chalk.green(`āœ“ ${agent.file || id}`));
287
- } else if (agent.status === 'error') {
288
- failed++;
289
- console.log(chalk.red(`āœ— ${agent.file || id}: ${agent.error}`));
290
- }
291
- }
292
-
293
- console.log(chalk.gray('\n─────────────────'));
294
- console.log(chalk.white('Total files:'), this.fileQueue.length + successful + failed);
295
- console.log(chalk.green('Successful:'), successful);
296
- if (failed > 0) {
297
- console.log(chalk.red('Failed:'), failed);
298
- }
299
- }
300
-
301
- async ensureProviderAuth(): Promise<boolean> {
302
- switch (this.options.provider) {
303
- case 'claude':
304
- return this.ensureClaudeAuth();
305
- case 'openai':
306
- return !!process.env.OPENAI_API_KEY;
307
- case 'gemini':
308
- return !!process.env.GOOGLE_API_KEY || !!process.env.GEMINI_API_KEY;
309
- case 'grok':
310
- return !!process.env.GROK_API_KEY;
311
- case 'local':
312
- return true;
313
- default:
314
- return false;
315
- }
316
- }
317
-
318
- private async ensureClaudeAuth(): Promise<boolean> {
319
- // Check if already authenticated
320
- try {
321
- const testResult = await new Promise<boolean>((resolve) => {
322
- const child = spawn('claude', ['-p', 'test', '--max-turns', '1'], {
323
- env: process.env
324
- });
325
-
326
- let hasError = false;
327
- let resolved = false;
328
-
329
- const cleanup = () => {
330
- if (!resolved) {
331
- resolved = true;
332
- clearTimeout(timeout);
333
- child.kill();
334
- }
335
- };
336
-
337
- child.stderr?.on('data', (data) => {
338
- const output = data.toString();
339
- if (output.includes('not authenticated') || output.includes('API key')) {
340
- hasError = true;
341
- }
342
- });
343
-
344
- child.on('close', () => {
345
- cleanup();
346
- resolve(!hasError);
347
- });
348
-
349
- // Timeout after 5 seconds
350
- const timeout = setTimeout(() => {
351
- cleanup();
352
- resolve(!hasError);
353
- }, 5000);
354
- });
355
-
356
- if (testResult) {
357
- return true;
358
- }
359
-
360
- // Try to login automatically if we have API key
361
- if (process.env.ANTHROPIC_API_KEY && this.options.autoLogin) {
362
- console.log(chalk.yellow('Attempting automatic Claude login...'));
363
-
364
- const loginResult = await new Promise<boolean>((resolve) => {
365
- const child = spawn('claude', ['login'], {
366
- env: {
367
- ...process.env,
368
- ANTHROPIC_API_KEY: process.env.ANTHROPIC_API_KEY
369
- },
370
- stdio: 'inherit'
371
- });
372
-
373
- child.on('close', (code) => {
374
- resolve(code === 0);
375
- });
376
- });
377
-
378
- if (loginResult) {
379
- console.log(chalk.green('āœ“ Claude login successful'));
380
- return true;
381
- }
382
- }
383
-
384
- return false;
385
- } catch (error) {
386
- return false;
387
- }
388
- }
389
- }