claude-flow 3.5.71 → 3.5.72

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "claude-flow",
3
- "version": "3.5.71",
3
+ "version": "3.5.72",
4
4
  "description": "Ruflo - Enterprise AI agent orchestration for Claude Code. Deploy 60+ specialized agents in coordinated swarms with self-learning, fault-tolerant consensus, vector memory, and MCP integration",
5
5
  "main": "dist/index.js",
6
6
  "type": "module",
@@ -113,12 +113,14 @@ const listCommand = {
113
113
  output.writeln(output.dim('─'.repeat(70)));
114
114
  // Fetch real ratings from Cloud Function (non-blocking)
115
115
  let realRatings = {};
116
+ let ratingsSource = 'live';
116
117
  try {
117
118
  const pluginIds = plugins.map(p => p.name);
118
119
  realRatings = await getBulkRatings(pluginIds, 'plugin');
119
120
  }
120
121
  catch {
121
122
  // Fall back to static ratings if Cloud Function unavailable
123
+ ratingsSource = 'unavailable';
122
124
  }
123
125
  if (ctx.flags.format === 'json') {
124
126
  // Merge real ratings into plugin data
@@ -126,6 +128,7 @@ const listCommand = {
126
128
  ...p,
127
129
  rating: realRatings[p.name]?.average || p.rating,
128
130
  ratingCount: realRatings[p.name]?.count || 0,
131
+ ...(ratingsSource === 'unavailable' ? { ratingsSource: 'cached' } : {}),
129
132
  }));
130
133
  output.printJson(pluginsWithRatings);
131
134
  return { success: true, data: pluginsWithRatings };
@@ -157,6 +160,9 @@ const listCommand = {
157
160
  }),
158
161
  });
159
162
  output.writeln();
163
+ if (ratingsSource === 'unavailable') {
164
+ output.writeln(output.dim('(ratings: cached — cloud unavailable)'));
165
+ }
160
166
  output.writeln(output.dim(`Source: ${result.source}${result.fromCache ? ' (cached)' : ''}`));
161
167
  if (result.cid) {
162
168
  output.writeln(output.dim(`Registry CID: ${result.cid.slice(0, 30)}...`));
@@ -293,11 +293,201 @@ const threatsCommand = {
293
293
  ],
294
294
  action: async (ctx) => {
295
295
  const model = ctx.flags.model || 'stride';
296
+ const scope = ctx.flags.scope || '.';
297
+ const exportFormat = ctx.flags.export;
296
298
  output.writeln();
297
299
  output.writeln(output.bold(`Threat Model: ${model.toUpperCase()}`));
298
300
  output.writeln(output.dim('─'.repeat(50)));
299
- output.writeln(output.warning('⚠ Automated threat modeling is not yet implemented.'));
300
- output.writeln(output.dim('The table below is a STRIDE reference template, not an analysis of your code.'));
301
+ const spinner = output.createSpinner({ text: `Scanning ${scope} for threat indicators...`, spinner: 'dots' });
302
+ spinner.start();
303
+ const fs = await import('fs');
304
+ const path = await import('path');
305
+ const rootDir = path.resolve(scope);
306
+ const findings = [];
307
+ const extensions = new Set(['.ts', '.js', '.json', '.yaml', '.yml', '.tsx', '.jsx']);
308
+ const skipDirs = new Set(['node_modules', 'dist', '.git']);
309
+ let filesScanned = 0;
310
+ const MAX_FILES = 500;
311
+ // Threat indicator patterns mapped to STRIDE categories
312
+ const threatPatterns = [
313
+ // Spoofing — weak/missing authentication
314
+ { pattern: /(?:app|router|server)\s*\.\s*(?:get|post|put|patch|delete)\s*\(\s*['"][^'"]+['"]\s*,\s*(?:async\s+)?\(?(?:req|request)/g, category: 'Spoofing', severity: 'medium', description: 'HTTP endpoint without auth middleware' },
315
+ // Tampering — code injection vectors
316
+ { pattern: /\beval\s*\(/g, category: 'Tampering', severity: 'high', description: 'eval() usage — arbitrary code execution risk' },
317
+ { pattern: /\bexecSync\s*\(/g, category: 'Tampering', severity: 'high', description: 'execSync() usage — command injection risk' },
318
+ { pattern: /\bexec\s*\(\s*[^)]*\$\{/g, category: 'Tampering', severity: 'high', description: 'exec() with template literal — injection risk' },
319
+ { pattern: /child_process.*\bexec\b/g, category: 'Tampering', severity: 'medium', description: 'child_process exec import — review for injection' },
320
+ { pattern: /new\s+Function\s*\(/g, category: 'Tampering', severity: 'high', description: 'new Function() — dynamic code execution risk' },
321
+ // Repudiation — missing audit/logging
322
+ // (checked via absence of logging imports, handled separately)
323
+ // Info Disclosure — secrets and data leaks
324
+ { pattern: /(?:api[_-]?key|secret|token|password|passwd|credential)\s*[:=]\s*['"][^'"]{8,}['"]/gi, category: 'Info Disclosure', severity: 'high', description: 'Hardcoded credential or secret' },
325
+ { pattern: /AKIA[0-9A-Z]{16}/g, category: 'Info Disclosure', severity: 'critical', description: 'AWS Access Key ID detected' },
326
+ { pattern: /gh[ps]_[A-Za-z0-9_]{36,}/g, category: 'Info Disclosure', severity: 'high', description: 'GitHub token detected' },
327
+ { pattern: /-----BEGIN (?:RSA|EC|DSA|OPENSSH) PRIVATE KEY-----/g, category: 'Info Disclosure', severity: 'critical', description: 'Private key detected' },
328
+ { pattern: /http:\/\/(?!localhost|127\.0\.0\.1|0\.0\.0\.0)/g, category: 'Info Disclosure', severity: 'medium', description: 'Non-localhost HTTP URL — should use HTTPS' },
329
+ // DoS — missing rate limiting / resource protection
330
+ { pattern: /require\s*\(\s*['"]express['"]\s*\)/g, category: 'DoS', severity: 'low', description: 'Express detected — verify rate-limiting is configured' },
331
+ { pattern: /require\s*\(\s*['"]fastify['"]\s*\)/g, category: 'DoS', severity: 'low', description: 'Fastify detected — verify rate-limiting is configured' },
332
+ // Elevation of privilege — unsafe deserialization, prototype pollution
333
+ { pattern: /JSON\.parse\s*\(\s*(?:req\.|request\.)/g, category: 'Elevation', severity: 'medium', description: 'Unsanitized JSON.parse from request — validate input' },
334
+ { pattern: /\.__proto__/g, category: 'Elevation', severity: 'high', description: '__proto__ access — prototype pollution risk' },
335
+ { pattern: /Object\.assign\s*\(\s*\{\s*\}\s*,\s*(?:req|request)\./g, category: 'Elevation', severity: 'medium', description: 'Object.assign from request — prototype pollution risk' },
336
+ ];
337
+ // Check for .env files committed to git
338
+ const checkEnvInGit = () => {
339
+ try {
340
+ const { execSync } = require('child_process');
341
+ const tracked = execSync('git ls-files --cached', { cwd: rootDir, encoding: 'utf-8', stdio: ['pipe', 'pipe', 'pipe'] });
342
+ const envFiles = tracked.split('\n').filter((f) => /(?:^|\/)\.env(?:\.|$)/.test(f));
343
+ for (const envFile of envFiles) {
344
+ findings.push({
345
+ category: 'Info Disclosure',
346
+ severity: output.error('CRITICAL'),
347
+ location: envFile,
348
+ description: '.env file tracked in git — secrets may be exposed',
349
+ });
350
+ }
351
+ }
352
+ catch { /* not a git repo or git not available */ }
353
+ };
354
+ // Recursive file scanner
355
+ const scanDir = (dir) => {
356
+ if (filesScanned >= MAX_FILES)
357
+ return;
358
+ let entries;
359
+ try {
360
+ entries = fs.readdirSync(dir, { withFileTypes: true });
361
+ }
362
+ catch {
363
+ return;
364
+ }
365
+ for (const entry of entries) {
366
+ if (filesScanned >= MAX_FILES)
367
+ break;
368
+ if (skipDirs.has(entry.name) || entry.name.startsWith('.'))
369
+ continue;
370
+ const fullPath = path.join(dir, entry.name);
371
+ if (entry.isDirectory()) {
372
+ scanDir(fullPath);
373
+ }
374
+ else if (entry.isFile() && extensions.has(path.extname(entry.name)) && !entry.name.endsWith('.d.ts')) {
375
+ filesScanned++;
376
+ try {
377
+ const stat = fs.statSync(fullPath);
378
+ if (stat.size > 1024 * 1024)
379
+ continue; // skip files > 1MB
380
+ const content = fs.readFileSync(fullPath, 'utf-8');
381
+ const lines = content.split('\n');
382
+ const relPath = path.relative(rootDir, fullPath);
383
+ for (let i = 0; i < lines.length; i++) {
384
+ for (const tp of threatPatterns) {
385
+ tp.pattern.lastIndex = 0;
386
+ if (tp.pattern.test(lines[i])) {
387
+ const sevLabel = tp.severity === 'critical' ? output.error('CRITICAL') :
388
+ tp.severity === 'high' ? output.warning('HIGH') :
389
+ tp.severity === 'medium' ? output.warning('MEDIUM') : output.info('LOW');
390
+ findings.push({
391
+ category: tp.category,
392
+ severity: sevLabel,
393
+ location: `${relPath}:${i + 1}`,
394
+ description: tp.description,
395
+ });
396
+ tp.pattern.lastIndex = 0;
397
+ }
398
+ }
399
+ }
400
+ }
401
+ catch { /* file read error */ }
402
+ }
403
+ }
404
+ };
405
+ // Check for missing security middleware in Express/Fastify apps
406
+ const checkMissingMiddleware = () => {
407
+ const serverFiles = [];
408
+ const collectServerFiles = (dir, depth) => {
409
+ if (depth <= 0 || filesScanned >= MAX_FILES)
410
+ return;
411
+ try {
412
+ const entries = fs.readdirSync(dir, { withFileTypes: true });
413
+ for (const entry of entries) {
414
+ if (skipDirs.has(entry.name) || entry.name.startsWith('.'))
415
+ continue;
416
+ const fullPath = path.join(dir, entry.name);
417
+ if (entry.isDirectory()) {
418
+ collectServerFiles(fullPath, depth - 1);
419
+ }
420
+ else if (/\.(ts|js)$/.test(entry.name) && !entry.name.endsWith('.d.ts')) {
421
+ try {
422
+ const content = fs.readFileSync(fullPath, 'utf-8');
423
+ if (/require\s*\(\s*['"](?:express|fastify)['"]\s*\)/.test(content) || /from\s+['"](?:express|fastify)['"]/.test(content)) {
424
+ serverFiles.push(fullPath);
425
+ const relPath = path.relative(rootDir, fullPath);
426
+ if (!/(?:helmet|lusca)/.test(content)) {
427
+ findings.push({ category: 'Tampering', severity: output.warning('MEDIUM'), location: relPath, description: 'No helmet/lusca security headers middleware' });
428
+ }
429
+ if (!/(?:cors)/.test(content)) {
430
+ findings.push({ category: 'Spoofing', severity: output.info('LOW'), location: relPath, description: 'No CORS middleware detected' });
431
+ }
432
+ if (!/(?:rate.?limit|throttle)/.test(content)) {
433
+ findings.push({ category: 'DoS', severity: output.warning('MEDIUM'), location: relPath, description: 'No rate-limiting middleware detected' });
434
+ }
435
+ }
436
+ }
437
+ catch { /* skip */ }
438
+ }
439
+ }
440
+ }
441
+ catch { /* skip */ }
442
+ };
443
+ collectServerFiles(rootDir, 5);
444
+ };
445
+ checkEnvInGit();
446
+ scanDir(rootDir);
447
+ checkMissingMiddleware();
448
+ spinner.succeed(`Scanned ${filesScanned} files`);
449
+ // STRIDE reference framework
450
+ const strideRef = [
451
+ { category: 'Spoofing', description: 'Can an attacker impersonate a user or service?', example: 'Strong authentication, mTLS' },
452
+ { category: 'Tampering', description: 'Can data or code be modified without detection?', example: 'Input validation, integrity checks' },
453
+ { category: 'Repudiation', description: 'Can actions be performed without accountability?', example: 'Audit logging, signed commits' },
454
+ { category: 'Info Disclosure', description: 'Can sensitive data leak to unauthorized parties?', example: 'Encryption at rest and in transit' },
455
+ { category: 'DoS', description: 'Can service availability be degraded?', example: 'Rate limiting, resource quotas' },
456
+ { category: 'Elevation', description: 'Can privileges be escalated beyond granted level?', example: 'RBAC, principle of least privilege' },
457
+ ];
458
+ // Display real findings
459
+ output.writeln();
460
+ if (findings.length > 0) {
461
+ output.writeln(output.bold(`Findings (${findings.length}):`));
462
+ output.writeln();
463
+ output.printTable({
464
+ columns: [
465
+ { key: 'category', header: 'STRIDE Category', width: 18 },
466
+ { key: 'severity', header: 'Severity', width: 12 },
467
+ { key: 'location', header: 'Location', width: 30 },
468
+ { key: 'description', header: 'Description', width: 40 },
469
+ ],
470
+ data: findings.slice(0, 30),
471
+ });
472
+ if (findings.length > 30) {
473
+ output.writeln(output.dim(`... and ${findings.length - 30} more findings`));
474
+ }
475
+ // Summary by STRIDE category
476
+ const byCat = {};
477
+ for (const f of findings)
478
+ byCat[f.category] = (byCat[f.category] || 0) + 1;
479
+ output.writeln();
480
+ output.writeln(output.bold('Summary by STRIDE category:'));
481
+ for (const [cat, count] of Object.entries(byCat).sort((a, b) => b[1] - a[1])) {
482
+ output.writeln(` ${cat}: ${count} finding${count === 1 ? '' : 's'}`);
483
+ }
484
+ }
485
+ else {
486
+ output.writeln(output.success('No threat indicators detected in scanned files.'));
487
+ }
488
+ // Always show STRIDE reference
489
+ output.writeln();
490
+ output.writeln(output.bold(`${model.toUpperCase()} Reference Framework${findings.length === 0 ? ' (reference only — no issues detected)' : ''}:`));
301
491
  output.writeln();
302
492
  output.printTable({
303
493
  columns: [
@@ -305,18 +495,26 @@ const threatsCommand = {
305
495
  { key: 'description', header: 'What to Assess', width: 40 },
306
496
  { key: 'example', header: 'Example Mitigation', width: 30 },
307
497
  ],
308
- data: [
309
- { category: 'Spoofing', description: 'Can an attacker impersonate a user or service?', example: 'Strong authentication, mTLS' },
310
- { category: 'Tampering', description: 'Can data or code be modified without detection?', example: 'Input validation, integrity checks' },
311
- { category: 'Repudiation', description: 'Can actions be performed without accountability?', example: 'Audit logging, signed commits' },
312
- { category: 'Info Disclosure', description: 'Can sensitive data leak to unauthorized parties?', example: 'Encryption at rest and in transit' },
313
- { category: 'DoS', description: 'Can service availability be degraded?', example: 'Rate limiting, resource quotas' },
314
- { category: 'Elevation', description: 'Can privileges be escalated beyond granted level?', example: 'RBAC, principle of least privilege' },
315
- ],
498
+ data: strideRef,
316
499
  });
500
+ // Export if requested
501
+ if (exportFormat && findings.length > 0) {
502
+ const exportData = {
503
+ model: model.toUpperCase(),
504
+ timestamp: new Date().toISOString(),
505
+ scope,
506
+ filesScanned,
507
+ totalFindings: findings.length,
508
+ findings: findings.map(f => ({ ...f, severity: f.severity.replace(/\x1b\[[0-9;]*m/g, '') })),
509
+ strideReference: strideRef,
510
+ };
511
+ if (exportFormat === 'json') {
512
+ output.writeln();
513
+ output.writeln(JSON.stringify(exportData, null, 2));
514
+ }
515
+ }
317
516
  output.writeln();
318
- output.writeln(output.dim('For real threat modeling, review your architecture manually using this framework.'));
319
- output.writeln(output.dim('Run "claude-flow security scan" for automated vulnerability detection.'));
517
+ output.writeln(output.dim(`Files scanned: ${filesScanned} (max ${MAX_FILES})`));
320
518
  return { success: true };
321
519
  },
322
520
  };
@@ -401,33 +599,151 @@ const secretsCommand = {
401
599
  { command: 'claude-flow security secrets -a rotate', description: 'Rotate compromised secrets' },
402
600
  ],
403
601
  action: async (ctx) => {
404
- const path = ctx.flags.path || '.';
602
+ const scanPath = ctx.flags.path || '.';
603
+ const ignorePatterns = ctx.flags.ignore;
405
604
  output.writeln();
406
605
  output.writeln(output.bold('Secret Detection'));
407
606
  output.writeln(output.dim('─'.repeat(50)));
408
- const spinner = output.createSpinner({ text: 'Scanning for secrets...', spinner: 'dots' });
607
+ const spinner = output.createSpinner({ text: `Scanning ${scanPath} for secrets...`, spinner: 'dots' });
409
608
  spinner.start();
410
- await new Promise(r => setTimeout(r, 800));
411
- spinner.succeed('Scan complete');
412
- output.writeln();
413
- output.writeln(output.warning('⚠ No real secrets scan performed. Showing example findings.'));
414
- output.writeln(output.dim('Run "claude-flow security scan --depth full" for real secret detection.'));
609
+ const fs = await import('fs');
610
+ const path = await import('path');
611
+ const rootDir = path.resolve(scanPath);
612
+ const skipDirs = new Set(['node_modules', 'dist', '.git']);
613
+ const extensions = new Set(['.ts', '.js', '.json', '.yaml', '.yml', '.tsx', '.jsx', '.env', '.toml', '.cfg', '.conf', '.ini', '.properties', '.sh', '.bash', '.zsh']);
614
+ const ignoreList = ignorePatterns ? ignorePatterns.split(',').map(p => p.trim()) : [];
615
+ const secretPatterns = [
616
+ { pattern: /AKIA[0-9A-Z]{16}/g, type: 'AWS Access Key', risk: 'Critical', action: 'Rotate immediately' },
617
+ { pattern: /gh[ps]_[A-Za-z0-9_]{36,}/g, type: 'GitHub Token', risk: 'Critical', action: 'Revoke and rotate' },
618
+ { pattern: /eyJ[A-Za-z0-9_-]{10,}\.[A-Za-z0-9_-]{10,}/g, type: 'JWT Token', risk: 'High', action: 'Remove from source' },
619
+ { pattern: /-----BEGIN (?:RSA|EC|DSA|OPENSSH) PRIVATE KEY-----/g, type: 'Private Key', risk: 'Critical', action: 'Remove and regenerate' },
620
+ { pattern: /(?:mongodb|postgres|mysql|redis):\/\/[^\s'"]+/g, type: 'Connection String', risk: 'High', action: 'Use env variable' },
621
+ { pattern: /['"](?:sk-|sk_live_|sk_test_)[a-zA-Z0-9]{20,}['"]/g, type: 'API Key (Stripe/OpenAI)', risk: 'Critical', action: 'Rotate immediately' },
622
+ { pattern: /['"]xox[baprs]-[a-zA-Z0-9-]+['"]/g, type: 'Slack Token', risk: 'High', action: 'Revoke and rotate' },
623
+ { pattern: /[a-zA-Z0-9_-]*(?:api[_-]?key|secret[_-]?key|auth[_-]?token|access[_-]?token|private[_-]?key)\s*[:=]\s*['"][^'"]{8,}['"]/gi, type: 'Generic Secret/API Key', risk: 'High', action: 'Use env variable' },
624
+ { pattern: /(?:password|passwd|pwd)\s*[:=]\s*['"][^'"]{8,}['"]/gi, type: 'Hardcoded Password', risk: 'High', action: 'Use secrets manager' },
625
+ ];
626
+ const findings = [];
627
+ let filesScanned = 0;
628
+ const MAX_FILES = 500;
629
+ const shouldIgnore = (filePath) => {
630
+ return ignoreList.some(p => filePath.includes(p));
631
+ };
632
+ const scanDir = (dir) => {
633
+ if (filesScanned >= MAX_FILES)
634
+ return;
635
+ let entries;
636
+ try {
637
+ entries = fs.readdirSync(dir, { withFileTypes: true });
638
+ }
639
+ catch {
640
+ return;
641
+ }
642
+ for (const entry of entries) {
643
+ if (filesScanned >= MAX_FILES)
644
+ break;
645
+ if (skipDirs.has(entry.name))
646
+ continue;
647
+ // Allow dotfiles like .env but skip .git
648
+ const fullPath = path.join(dir, entry.name);
649
+ if (entry.isDirectory()) {
650
+ if (entry.name.startsWith('.') && entry.name !== '.env')
651
+ continue;
652
+ scanDir(fullPath);
653
+ }
654
+ else if (entry.isFile()) {
655
+ const ext = path.extname(entry.name);
656
+ const isEnvFile = entry.name.startsWith('.env');
657
+ if (!extensions.has(ext) && !isEnvFile)
658
+ continue;
659
+ if (entry.name.endsWith('.d.ts'))
660
+ continue;
661
+ const relPath = path.relative(rootDir, fullPath);
662
+ if (shouldIgnore(relPath))
663
+ continue;
664
+ filesScanned++;
665
+ try {
666
+ const stat = fs.statSync(fullPath);
667
+ if (stat.size > 1024 * 1024)
668
+ continue; // skip files > 1MB
669
+ const content = fs.readFileSync(fullPath, 'utf-8');
670
+ // Quick binary check — skip if null bytes present
671
+ if (content.includes('\0'))
672
+ continue;
673
+ const lines = content.split('\n');
674
+ for (let i = 0; i < lines.length; i++) {
675
+ const line = lines[i];
676
+ for (const sp of secretPatterns) {
677
+ sp.pattern.lastIndex = 0;
678
+ const match = sp.pattern.exec(line);
679
+ if (match) {
680
+ // Mask the matched secret for safe display
681
+ const matched = match[0];
682
+ const masked = matched.length > 12
683
+ ? matched.substring(0, 6) + '***' + matched.substring(matched.length - 3)
684
+ : '***';
685
+ findings.push({
686
+ type: sp.type,
687
+ location: `${relPath}:${i + 1}`,
688
+ risk: sp.risk,
689
+ action: sp.action,
690
+ line: masked,
691
+ });
692
+ sp.pattern.lastIndex = 0;
693
+ }
694
+ }
695
+ }
696
+ }
697
+ catch { /* file read error */ }
698
+ }
699
+ }
700
+ };
701
+ scanDir(rootDir);
702
+ spinner.succeed(`Scanned ${filesScanned} files`);
415
703
  output.writeln();
416
- output.printTable({
417
- columns: [
418
- { key: 'type', header: 'Secret Type (Example)', width: 25 },
419
- { key: 'location', header: 'Location', width: 30 },
420
- { key: 'risk', header: 'Risk', width: 12 },
421
- { key: 'action', header: 'Recommended', width: 20 },
422
- ],
423
- data: [
424
- { type: 'AWS Access Key', location: 'example/config.ts:15', risk: output.error('Critical'), action: 'Rotate immediately' },
425
- { type: 'GitHub Token', location: 'example/.env:8', risk: output.warning('High'), action: 'Remove from repo' },
426
- { type: 'JWT Secret', location: 'example/auth.ts:42', risk: output.warning('High'), action: 'Use env variable' },
427
- { type: 'DB Password', location: 'example/compose.yml:23', risk: output.warning('Medium'), action: 'Use secrets mgmt' },
428
- ],
429
- });
430
- return { success: true };
704
+ if (findings.length > 0) {
705
+ const criticalCount = findings.filter(f => f.risk === 'Critical').length;
706
+ const highCount = findings.filter(f => f.risk === 'High').length;
707
+ const mediumCount = findings.filter(f => f.risk === 'Medium').length;
708
+ output.printTable({
709
+ columns: [
710
+ { key: 'type', header: 'Secret Type', width: 25 },
711
+ { key: 'location', header: 'Location', width: 35 },
712
+ { key: 'risk', header: 'Risk', width: 12 },
713
+ { key: 'action', header: 'Recommended', width: 22 },
714
+ ],
715
+ data: findings.slice(0, 25).map(f => ({
716
+ type: f.type,
717
+ location: f.location,
718
+ risk: f.risk === 'Critical' ? output.error(f.risk) :
719
+ f.risk === 'High' ? output.warning(f.risk) :
720
+ output.warning(f.risk),
721
+ action: f.action,
722
+ })),
723
+ });
724
+ if (findings.length > 25) {
725
+ output.writeln(output.dim(`... and ${findings.length - 25} more secrets found`));
726
+ }
727
+ output.writeln();
728
+ output.printBox([
729
+ `Path: ${scanPath}`,
730
+ `Files scanned: ${filesScanned}`,
731
+ ``,
732
+ `Critical: ${criticalCount} High: ${highCount} Medium: ${mediumCount}`,
733
+ `Total secrets found: ${findings.length}`,
734
+ ].join('\n'), 'Secrets Summary');
735
+ }
736
+ else {
737
+ output.writeln(output.success('No secrets detected.'));
738
+ output.writeln();
739
+ output.printBox([
740
+ `Path: ${scanPath}`,
741
+ `Files scanned: ${filesScanned}`,
742
+ ``,
743
+ `No hardcoded secrets, API keys, tokens, or credentials found.`,
744
+ ].join('\n'), 'Secrets Summary');
745
+ }
746
+ return { success: findings.length === 0 };
431
747
  },
432
748
  };
433
749
  // Defend subcommand (AIDefence integration)
@@ -145,16 +145,99 @@ function getSwarmStatus(swarmId) {
145
145
  pending: pendingTasks
146
146
  },
147
147
  metrics: {
148
- tokensUsed: 0,
149
- avgResponseTime: '--',
150
- successRate: totalTasks > 0 ? `${Math.round((completedTasks / totalTasks) * 100)}%` : '--',
151
- elapsedTime: '--'
152
- },
153
- coordination: {
154
- consensusRounds: 0,
155
- messagesSent: 0,
156
- conflictsResolved: 0
148
+ tokensUsed: swarmState?.tokensUsed ?? null,
149
+ avgResponseTime: (() => {
150
+ // Calculate average response time from task files with startedAt/completedAt
151
+ const taskTimesMs = [];
152
+ if (fs.existsSync(tasksDir)) {
153
+ try {
154
+ const taskFiles = fs.readdirSync(tasksDir).filter(f => f.endsWith('.json'));
155
+ for (const file of taskFiles) {
156
+ try {
157
+ const task = JSON.parse(fs.readFileSync(path.join(tasksDir, file), 'utf-8'));
158
+ if (task.startedAt && task.completedAt) {
159
+ const elapsed = new Date(task.completedAt).getTime() - new Date(task.startedAt).getTime();
160
+ if (elapsed > 0)
161
+ taskTimesMs.push(elapsed);
162
+ }
163
+ }
164
+ catch { /* skip malformed task files */ }
165
+ }
166
+ }
167
+ catch { /* skip if dir unreadable */ }
168
+ }
169
+ if (taskTimesMs.length === 0)
170
+ return null;
171
+ const avgMs = Math.round(taskTimesMs.reduce((a, b) => a + b, 0) / taskTimesMs.length);
172
+ return avgMs < 1000 ? `${avgMs}ms` : `${(avgMs / 1000).toFixed(1)}s`;
173
+ })(),
174
+ successRate: totalTasks > 0 ? `${Math.round((completedTasks / totalTasks) * 100)}%` : null,
175
+ elapsedTime: (() => {
176
+ // Calculate from swarm startedAt in state.json
177
+ const startedAt = swarmState?.startedAt
178
+ || swarmState?.initializedAt;
179
+ if (!startedAt)
180
+ return null;
181
+ const elapsedMs = Date.now() - new Date(startedAt).getTime();
182
+ if (elapsedMs < 0)
183
+ return null;
184
+ const secs = Math.floor(elapsedMs / 1000);
185
+ if (secs < 60)
186
+ return `${secs}s`;
187
+ const mins = Math.floor(secs / 60);
188
+ const remSecs = secs % 60;
189
+ if (mins < 60)
190
+ return `${mins}m ${remSecs}s`;
191
+ const hrs = Math.floor(mins / 60);
192
+ const remMins = mins % 60;
193
+ return `${hrs}h ${remMins}m`;
194
+ })()
157
195
  },
196
+ coordination: (() => {
197
+ // Read real coordination counts from .swarm/coordination/ directory
198
+ const coordDir = path.join(swarmDir, 'coordination');
199
+ let consensusRounds = 0;
200
+ let messagesSent = 0;
201
+ let conflictsResolved = 0;
202
+ if (fs.existsSync(coordDir)) {
203
+ try {
204
+ const coordFiles = fs.readdirSync(coordDir).filter(f => f.endsWith('.json'));
205
+ for (const file of coordFiles) {
206
+ try {
207
+ const entry = JSON.parse(fs.readFileSync(path.join(coordDir, file), 'utf-8'));
208
+ if (entry.type === 'consensus')
209
+ consensusRounds++;
210
+ else if (entry.type === 'message')
211
+ messagesSent++;
212
+ else if (entry.type === 'conflict' || entry.type === 'conflict-resolution')
213
+ conflictsResolved++;
214
+ // Also aggregate pre-counted fields if present
215
+ if (typeof entry.consensusRounds === 'number')
216
+ consensusRounds += entry.consensusRounds;
217
+ if (typeof entry.messagesSent === 'number')
218
+ messagesSent += entry.messagesSent;
219
+ if (typeof entry.conflictsResolved === 'number')
220
+ conflictsResolved += entry.conflictsResolved;
221
+ }
222
+ catch { /* skip malformed coordination files */ }
223
+ }
224
+ }
225
+ catch { /* skip if dir unreadable */ }
226
+ }
227
+ // Also check state.json for aggregate coordination stats
228
+ if (swarmState) {
229
+ const coord = swarmState.coordination;
230
+ if (coord) {
231
+ if (typeof coord.consensusRounds === 'number')
232
+ consensusRounds += coord.consensusRounds;
233
+ if (typeof coord.messagesSent === 'number')
234
+ messagesSent += coord.messagesSent;
235
+ if (typeof coord.conflictsResolved === 'number')
236
+ conflictsResolved += coord.conflictsResolved;
237
+ }
238
+ }
239
+ return { consensusRounds, messagesSent, conflictsResolved };
240
+ })(),
158
241
  hasActiveSwarm: !!swarmState || totalAgents > 0
159
242
  };
160
243
  }
@@ -433,8 +516,10 @@ const startCommand = {
433
516
  fs.writeFileSync(path.join(swarmDir, 'state.json'), JSON.stringify(executionState, null, 2));
434
517
  output.writeln();
435
518
  output.printSuccess(`Swarm ${swarmId} initialized with ${totalAgents} agent slots`);
436
- output.writeln(output.dim(' Note: Agents are registered but actual task execution requires'));
437
- output.writeln(output.dim(' Claude Code Task tool or hive-mind spawn --claude to drive work.'));
519
+ output.writeln(output.dim(' This CLI coordinates agent state. Execution happens via:'));
520
+ output.writeln(output.dim(' - Claude Code Agent tool (interactive)'));
521
+ output.writeln(output.dim(' - claude -p (headless background)'));
522
+ output.writeln(output.dim(' - hive-mind spawn --claude (autonomous)'));
438
523
  output.writeln(output.dim(` Monitor: claude-flow swarm status ${swarmId}`));
439
524
  return { success: true, data: executionState };
440
525
  }
@@ -500,10 +585,10 @@ const statusCommand = {
500
585
  // Metrics
501
586
  output.writeln(output.bold('Performance Metrics'));
502
587
  output.printList([
503
- `Tokens Used: ${status.metrics.tokensUsed.toLocaleString()}`,
504
- `Avg Response Time: ${status.metrics.avgResponseTime}`,
505
- `Success Rate: ${status.metrics.successRate}`,
506
- `Elapsed Time: ${status.metrics.elapsedTime}`
588
+ `Tokens Used: ${status.metrics.tokensUsed != null ? status.metrics.tokensUsed.toLocaleString() : output.dim('unknown')}`,
589
+ `Avg Response Time: ${status.metrics.avgResponseTime ?? output.dim('no data')}`,
590
+ `Success Rate: ${status.metrics.successRate ?? output.dim('no data')}`,
591
+ `Elapsed Time: ${status.metrics.elapsedTime ?? output.dim('no data')}`
507
592
  ]);
508
593
  output.writeln();
509
594
  // Coordination stats
@@ -2801,18 +2801,59 @@ export const hooksIntelligenceAttention = {
2801
2801
  return { success: false, error: v.error };
2802
2802
  }
2803
2803
  let implementation = 'placeholder';
2804
+ let embeddingSource = 'none';
2804
2805
  const results = [];
2806
+ // Helper: generate query embedding, preferring real ONNX embeddings over hash fallback
2807
+ async function getQueryEmbedding(text, dims) {
2808
+ // Try ONNX via @claude-flow/embeddings
2809
+ try {
2810
+ const embeddingsModule = await import('@claude-flow/embeddings').catch(() => null);
2811
+ if (embeddingsModule?.createEmbeddingService) {
2812
+ const service = embeddingsModule.createEmbeddingService({ provider: 'onnx' });
2813
+ const result = await service.embed(text);
2814
+ const arr = new Float32Array(dims);
2815
+ for (let i = 0; i < Math.min(dims, result.embedding.length); i++) {
2816
+ arr[i] = result.embedding[i];
2817
+ }
2818
+ return { embedding: arr, source: 'onnx' };
2819
+ }
2820
+ }
2821
+ catch {
2822
+ // ONNX not available, try agentic-flow
2823
+ }
2824
+ // Try agentic-flow embeddings
2825
+ try {
2826
+ const embeddingsModule = await import('@claude-flow/embeddings').catch(() => null);
2827
+ if (embeddingsModule?.createEmbeddingService) {
2828
+ const service = embeddingsModule.createEmbeddingService({ provider: 'agentic-flow' });
2829
+ const result = await service.embed(text);
2830
+ const arr = new Float32Array(dims);
2831
+ for (let i = 0; i < Math.min(dims, result.embedding.length); i++) {
2832
+ arr[i] = result.embedding[i];
2833
+ }
2834
+ return { embedding: arr, source: 'onnx' };
2835
+ }
2836
+ }
2837
+ catch {
2838
+ // agentic-flow not available
2839
+ }
2840
+ // Hash-based fallback (deterministic but not semantic)
2841
+ const arr = new Float32Array(dims);
2842
+ let seed = text.split('').reduce((acc, char, i) => acc + char.charCodeAt(0) * (i + 1), 0);
2843
+ for (let i = 0; i < dims; i++) {
2844
+ seed = (seed * 1103515245 + 12345) & 0x7fffffff;
2845
+ arr[i] = (seed / 0x7fffffff) * 2 - 1;
2846
+ }
2847
+ return { embedding: arr, source: 'hash-fallback' };
2848
+ }
2805
2849
  if (mode === 'moe') {
2806
2850
  // Try MoE routing
2807
2851
  const moe = await getMoERouter();
2808
2852
  if (moe) {
2809
2853
  try {
2810
- // Generate a simple embedding from query (hash-based for demo)
2811
- const embedding = new Float32Array(384);
2812
- for (let i = 0; i < 384; i++) {
2813
- embedding[i] = Math.sin(query.charCodeAt(i % query.length) * (i + 1) * 0.01);
2814
- }
2815
- const routingResult = moe.route(embedding);
2854
+ const embResult = await getQueryEmbedding(query, 384);
2855
+ embeddingSource = embResult.source;
2856
+ const routingResult = moe.route(embResult.embedding);
2816
2857
  for (let i = 0; i < Math.min(topK, routingResult.experts.length); i++) {
2817
2858
  const expert = routingResult.experts[i];
2818
2859
  results.push({
@@ -2834,13 +2875,11 @@ export const hooksIntelligenceAttention = {
2834
2875
  const flash = await getFlashAttention();
2835
2876
  if (flash) {
2836
2877
  try {
2837
- // Generate query/key/value embeddings
2838
- const q = new Float32Array(384);
2878
+ const embResult = await getQueryEmbedding(query, 384);
2879
+ embeddingSource = embResult.source;
2880
+ const q = embResult.embedding;
2839
2881
  const keys = [];
2840
2882
  const values = [];
2841
- for (let i = 0; i < 384; i++) {
2842
- q[i] = Math.sin(query.charCodeAt(i % query.length) * (i + 1) * 0.01);
2843
- }
2844
2883
  // Generate some keys/values
2845
2884
  for (let k = 0; k < topK; k++) {
2846
2885
  const key = new Float32Array(384);
@@ -2883,10 +2922,13 @@ export const hooksIntelligenceAttention = {
2883
2922
  results,
2884
2923
  stats: {
2885
2924
  computeTimeMs,
2886
- speedup: implementation.startsWith('real-') ? (mode === 'flash' ? '2.49x-7.47x' : '1.5x-3x') : null,
2887
- memoryReduction: implementation.startsWith('real-') ? (mode === 'flash' ? '50-75%' : '25-40%') : null,
2925
+ implementation,
2926
+ _embeddingSource: embeddingSource,
2888
2927
  _stub: implementation === 'none',
2889
2928
  _note: implementation === 'none' ? 'No attention backend available. Install @ruvector/attention for real computation.' : undefined,
2929
+ ...(embeddingSource === 'hash-fallback' && implementation !== 'none'
2930
+ ? { _embeddingNote: 'Query embeddings are hash-based (not semantic). Install @claude-flow/embeddings for real ONNX embeddings.' }
2931
+ : {}),
2890
2932
  },
2891
2933
  implementation,
2892
2934
  };
@@ -25,7 +25,7 @@ try {
25
25
  realEmbeddings = { embed: (text) => rb.computeEmbedding(text) };
26
26
  embeddingServiceName = 'agentic-flow/reasoningbank';
27
27
  }
28
- // Tier 2: @claude-flow/embeddings
28
+ // Tier 2: @claude-flow/embeddings with agentic-flow provider
29
29
  if (!realEmbeddings) {
30
30
  const embeddingsModule = await import('@claude-flow/embeddings').catch(() => null);
31
31
  if (embeddingsModule?.createEmbeddingService) {
@@ -40,6 +40,34 @@ try {
40
40
  embeddingServiceName = 'agentic-flow';
41
41
  }
42
42
  catch {
43
+ // agentic-flow provider not available, try ONNX
44
+ }
45
+ }
46
+ }
47
+ // Tier 3: @claude-flow/embeddings with ONNX provider
48
+ if (!realEmbeddings) {
49
+ const embeddingsModule = await import('@claude-flow/embeddings').catch(() => null);
50
+ if (embeddingsModule?.createEmbeddingService) {
51
+ try {
52
+ const service = embeddingsModule.createEmbeddingService({ provider: 'onnx' });
53
+ realEmbeddings = {
54
+ embed: async (text) => {
55
+ const result = await service.embed(text);
56
+ return Array.from(result.embedding);
57
+ },
58
+ };
59
+ embeddingServiceName = 'onnx';
60
+ }
61
+ catch {
62
+ // ONNX provider not available, fall through to mock
63
+ }
64
+ }
65
+ }
66
+ // Tier 4: mock fallback (last resort — embeddings are not semantic)
67
+ if (!realEmbeddings) {
68
+ const embeddingsModule = await import('@claude-flow/embeddings').catch(() => null);
69
+ if (embeddingsModule?.createEmbeddingService) {
70
+ try {
43
71
  const service = embeddingsModule.createEmbeddingService({ provider: 'mock' });
44
72
  realEmbeddings = {
45
73
  embed: async (text) => {
@@ -47,7 +75,10 @@ try {
47
75
  return Array.from(result.embedding);
48
76
  },
49
77
  };
50
- embeddingServiceName = 'mock';
78
+ embeddingServiceName = 'mock-fallback';
79
+ }
80
+ catch {
81
+ // No embedding service available at all
51
82
  }
52
83
  }
53
84
  }
@@ -207,6 +238,7 @@ export const neuralTools = [
207
238
  return {
208
239
  success: true,
209
240
  _realEmbedding: !!realEmbeddings,
241
+ embeddingProvider: embeddingServiceName,
210
242
  modelId,
211
243
  type: modelType,
212
244
  status: model.status,
@@ -275,6 +307,7 @@ export const neuralTools = [
275
307
  return {
276
308
  success: true,
277
309
  _realEmbedding: !!realEmbeddings,
310
+ embeddingProvider: embeddingServiceName,
278
311
  _hasStoredPatterns: storedPatterns.length > 0,
279
312
  modelId: model?.id || 'default',
280
313
  input: inputText,
@@ -364,6 +397,7 @@ export const neuralTools = [
364
397
  return {
365
398
  success: true,
366
399
  _realEmbedding: !!realEmbeddings,
400
+ embeddingProvider: embeddingServiceName,
367
401
  patternId,
368
402
  name: pattern.name,
369
403
  type: pattern.type,
@@ -386,6 +420,7 @@ export const neuralTools = [
386
420
  return {
387
421
  _realSimilarity: true,
388
422
  _realEmbedding: !!realEmbeddings,
423
+ embeddingProvider: embeddingServiceName,
389
424
  query,
390
425
  results: results.map(r => ({
391
426
  id: r.id,
@@ -455,6 +490,7 @@ export const neuralTools = [
455
490
  saveNeuralStore(store);
456
491
  return {
457
492
  success: true, _real: true, method,
493
+ embeddingProvider: embeddingServiceName,
458
494
  patternsCompressed: totalCompressed,
459
495
  compressionRatio: '3.92x (Int8)',
460
496
  beforeBytes: beforeSize,
@@ -478,6 +514,7 @@ export const neuralTools = [
478
514
  saveNeuralStore(store);
479
515
  return {
480
516
  success: true, _real: true, method,
517
+ embeddingProvider: embeddingServiceName,
481
518
  threshold,
482
519
  patternsRemoved: toRemove.length,
483
520
  patternsBefore: beforeCount,
@@ -511,6 +548,7 @@ export const neuralTools = [
511
548
  saveNeuralStore(store);
512
549
  return {
513
550
  success: true, _real: true, method,
551
+ embeddingProvider: embeddingServiceName,
514
552
  patternsMerged: merged.length,
515
553
  patternsBefore: beforeCount,
516
554
  patternsAfter: Object.keys(store.patterns).length,
@@ -670,6 +708,7 @@ export const neuralTools = [
670
708
  const elapsed = Math.round(performance.now() - startTime);
671
709
  return {
672
710
  success: true, _real: true, target,
711
+ embeddingProvider: embeddingServiceName,
673
712
  actions,
674
713
  patternsBefore: beforeCount,
675
714
  patternsAfter: Object.keys(store.patterns).length,
@@ -233,15 +233,15 @@ export const performanceTools = [
233
233
  const suite = input.suite || 'all';
234
234
  const iterations = input.iterations || 100;
235
235
  const warmup = input.warmup !== false;
236
- // REAL benchmark functions
236
+ // Synthetic data benchmarks — measures actual CPU/memory throughput
237
237
  const benchmarkFunctions = {
238
238
  memory: () => {
239
- // Real memory allocation benchmark
239
+ // Synthetic data benchmark — measures actual allocation + sort throughput
240
240
  const arr = new Array(1000).fill(0).map(() => Math.random());
241
241
  arr.sort();
242
242
  },
243
243
  neural: () => {
244
- // Real computation benchmark (matrix-like operations)
244
+ // Synthetic data benchmark — measures actual matrix multiplication throughput
245
245
  const size = 64;
246
246
  const a = Array.from({ length: size }, () => Array.from({ length: size }, () => Math.random()));
247
247
  const b = Array.from({ length: size }, () => Array.from({ length: size }, () => Math.random()));
@@ -255,14 +255,14 @@ export const performanceTools = [
255
255
  }
256
256
  },
257
257
  swarm: () => {
258
- // Real object creation and manipulation
258
+ // Synthetic data benchmark — measures actual object creation + manipulation throughput
259
259
  const agents = Array.from({ length: 10 }, (_, i) => ({ id: i, status: 'active', tasks: [] }));
260
260
  agents.forEach(a => { for (let i = 0; i < 100; i++)
261
261
  a.tasks.push(i); });
262
262
  agents.sort((a, b) => a.tasks.length - b.tasks.length);
263
263
  },
264
264
  io: () => {
265
- // Real JSON serialization benchmark
265
+ // Synthetic data benchmark — measures actual JSON serialization throughput
266
266
  const data = { agents: Array.from({ length: 50 }, (_, i) => ({ id: i, name: `agent-${i}` })) };
267
267
  const json = JSON.stringify(data);
268
268
  JSON.parse(json);
@@ -313,6 +313,7 @@ export const performanceTools = [
313
313
  avgLatency: `${avgLatencyMs.toFixed(3)}ms`,
314
314
  memoryUsage: `${Math.abs(memoryDelta)}KB`,
315
315
  _real: true,
316
+ _dataSource: 'synthetic',
316
317
  });
317
318
  }
318
319
  }
@@ -330,6 +331,7 @@ export const performanceTools = [
330
331
  : { note: 'First benchmark run - no comparison available', totalBenchmarks: allBenchmarks.length };
331
332
  return {
332
333
  _real: true,
334
+ _note: 'Benchmarks use synthetic workloads to measure throughput. Results reflect actual CPU/memory performance.',
333
335
  suite,
334
336
  iterations,
335
337
  warmup,
@@ -10,10 +10,11 @@
10
10
  */
11
11
  import { getProjectCwd } from './types.js';
12
12
  import { validateIdentifier } from './validate-input.js';
13
- import { existsSync, readFileSync, writeFileSync, mkdirSync } from 'node:fs';
13
+ import { existsSync, readFileSync, writeFileSync, mkdirSync, statfsSync } from 'node:fs';
14
14
  import { join, dirname } from 'node:path';
15
15
  import { fileURLToPath } from 'node:url';
16
16
  import * as os from 'node:os';
17
+ import * as dns from 'node:dns';
17
18
  // Read version dynamically from package.json
18
19
  function getPackageVersion() {
19
20
  try {
@@ -332,26 +333,60 @@ export const systemTools = [
332
333
  message: 'Neural network health not monitored',
333
334
  });
334
335
  if (input.deep) {
335
- // Disk check — real free space check via os module
336
+ // Disk check — real free space via statfsSync (Node 18.15+)
336
337
  {
337
338
  const t0 = performance.now();
338
- const freeMem = os.freemem();
339
- const totalMem = os.totalmem();
340
- const elapsed = performance.now() - t0;
341
- // Use memory as a proxy; real disk check would need statvfs
342
- checks.push({
343
- name: 'disk',
344
- status: 'unknown',
345
- latency: Math.round(elapsed * 100) / 100,
346
- message: 'Disk space check not implemented — use os-level tools',
347
- });
339
+ try {
340
+ const stats = statfsSync(projectCwd);
341
+ const totalBytes = stats.blocks * stats.bsize;
342
+ const freeBytes = stats.bfree * stats.bsize;
343
+ const totalGB = Math.round((totalBytes / (1024 ** 3)) * 10) / 10;
344
+ const freeGB = Math.round((freeBytes / (1024 ** 3)) * 10) / 10;
345
+ const freePercent = Math.round((freeBytes / totalBytes) * 100);
346
+ const elapsed = performance.now() - t0;
347
+ checks.push({
348
+ name: 'disk',
349
+ status: freePercent > 10 ? 'healthy' : 'warning',
350
+ latency: Math.round(elapsed * 100) / 100,
351
+ message: `${freeGB}GB free of ${totalGB}GB (${freePercent}%)`,
352
+ });
353
+ }
354
+ catch {
355
+ const elapsed = performance.now() - t0;
356
+ checks.push({
357
+ name: 'disk',
358
+ status: 'unknown',
359
+ latency: Math.round(elapsed * 100) / 100,
360
+ message: 'Disk space check failed — statfsSync unavailable',
361
+ });
362
+ }
363
+ }
364
+ // Network — DNS resolution check with timeout
365
+ {
366
+ const t0 = performance.now();
367
+ try {
368
+ await Promise.race([
369
+ dns.promises.lookup('registry.npmjs.org'),
370
+ new Promise((_, reject) => setTimeout(() => reject(new Error('timeout')), 3000)),
371
+ ]);
372
+ const elapsed = performance.now() - t0;
373
+ checks.push({
374
+ name: 'network',
375
+ status: 'healthy',
376
+ latency: Math.round(elapsed * 100) / 100,
377
+ message: 'DNS resolution working',
378
+ });
379
+ }
380
+ catch {
381
+ const elapsed = performance.now() - t0;
382
+ checks.push({
383
+ name: 'network',
384
+ status: 'warning',
385
+ latency: Math.round(elapsed * 100) / 100,
386
+ message: 'DNS resolution failed — check network',
387
+ });
388
+ }
348
389
  }
349
- // Network — cannot verify without making a request
350
- checks.push({
351
- name: 'network',
352
- status: 'unknown',
353
- message: 'Network connectivity not monitored',
354
- });
355
390
  // Database — check if coordination store exists
356
391
  {
357
392
  const t0 = performance.now();
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@claude-flow/cli",
3
- "version": "3.5.71",
3
+ "version": "3.5.72",
4
4
  "type": "module",
5
5
  "description": "Ruflo CLI - Enterprise AI agent orchestration with 60+ specialized agents, swarm coordination, MCP server, self-learning hooks, and vector memory for Claude Code",
6
6
  "main": "dist/src/index.js",