recker 1.0.15-next.0dab95d → 1.0.15-next.0eabd39

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.
Files changed (108) hide show
  1. package/README.md +10 -1
  2. package/dist/ai/providers/anthropic.d.ts.map +1 -1
  3. package/dist/ai/providers/anthropic.js +4 -1
  4. package/dist/ai/providers/base.d.ts.map +1 -1
  5. package/dist/ai/providers/base.js +7 -2
  6. package/dist/ai/rate-limiter.d.ts.map +1 -1
  7. package/dist/ai/rate-limiter.js +4 -1
  8. package/dist/bench/generator.d.ts.map +1 -1
  9. package/dist/bench/generator.js +7 -3
  10. package/dist/bench/stats.d.ts.map +1 -1
  11. package/dist/bench/stats.js +43 -10
  12. package/dist/cache/memory-storage.d.ts.map +1 -1
  13. package/dist/cache/memory-storage.js +3 -2
  14. package/dist/cli/handler.js +14 -14
  15. package/dist/cli/index.js +582 -49
  16. package/dist/cli/presets.js +5 -5
  17. package/dist/cli/tui/ai-chat.js +10 -10
  18. package/dist/cli/tui/load-dashboard.d.ts.map +1 -1
  19. package/dist/cli/tui/load-dashboard.js +96 -55
  20. package/dist/cli/tui/scroll-buffer.d.ts +1 -1
  21. package/dist/cli/tui/scroll-buffer.d.ts.map +1 -1
  22. package/dist/cli/tui/scroll-buffer.js +2 -2
  23. package/dist/cli/tui/shell.d.ts +3 -0
  24. package/dist/cli/tui/shell.d.ts.map +1 -1
  25. package/dist/cli/tui/shell.js +188 -6
  26. package/dist/cli/tui/websocket.js +17 -17
  27. package/dist/core/client.d.ts.map +1 -1
  28. package/dist/core/client.js +18 -26
  29. package/dist/core/errors.d.ts +109 -1
  30. package/dist/core/errors.d.ts.map +1 -1
  31. package/dist/core/errors.js +214 -1
  32. package/dist/core/request-promise.d.ts.map +1 -1
  33. package/dist/core/request-promise.js +5 -6
  34. package/dist/core/response.d.ts.map +1 -1
  35. package/dist/core/response.js +5 -6
  36. package/dist/dns/propagation.d.ts +3 -1
  37. package/dist/dns/propagation.d.ts.map +1 -1
  38. package/dist/dns/propagation.js +99 -59
  39. package/dist/index.d.ts +1 -0
  40. package/dist/index.d.ts.map +1 -1
  41. package/dist/index.js +1 -0
  42. package/dist/mcp/client.d.ts.map +1 -1
  43. package/dist/mcp/client.js +10 -11
  44. package/dist/mcp/embeddings-loader.d.ts.map +1 -1
  45. package/dist/mcp/embeddings-loader.js +12 -2
  46. package/dist/mcp/geoip-loader.d.ts +11 -0
  47. package/dist/mcp/geoip-loader.d.ts.map +1 -0
  48. package/dist/mcp/geoip-loader.js +107 -0
  49. package/dist/mcp/ip-intel.d.ts +28 -0
  50. package/dist/mcp/ip-intel.d.ts.map +1 -0
  51. package/dist/mcp/ip-intel.js +209 -0
  52. package/dist/mcp/search/hybrid-search.d.ts.map +1 -1
  53. package/dist/mcp/search/hybrid-search.js +5 -1
  54. package/dist/mcp/search/math.d.ts.map +1 -1
  55. package/dist/mcp/search/math.js +5 -1
  56. package/dist/mcp/server.d.ts +4 -0
  57. package/dist/mcp/server.d.ts.map +1 -1
  58. package/dist/mcp/server.js +114 -1
  59. package/dist/plugins/compression.js +4 -2
  60. package/dist/plugins/har-player.d.ts.map +1 -1
  61. package/dist/plugins/har-player.js +8 -11
  62. package/dist/plugins/odata.d.ts.map +1 -1
  63. package/dist/plugins/odata.js +5 -2
  64. package/dist/protocols/ftp.d.ts.map +1 -1
  65. package/dist/protocols/ftp.js +69 -16
  66. package/dist/protocols/sftp.d.ts.map +1 -1
  67. package/dist/protocols/sftp.js +13 -3
  68. package/dist/protocols/telnet.d.ts.map +1 -1
  69. package/dist/protocols/telnet.js +25 -6
  70. package/dist/transport/base-udp.d.ts.map +1 -1
  71. package/dist/transport/base-udp.js +7 -4
  72. package/dist/transport/udp-response.d.ts.map +1 -1
  73. package/dist/transport/udp-response.js +10 -3
  74. package/dist/transport/udp.d.ts.map +1 -1
  75. package/dist/transport/udp.js +5 -1
  76. package/dist/transport/undici.d.ts.map +1 -1
  77. package/dist/transport/undici.js +75 -63
  78. package/dist/utils/agent-manager.d.ts +1 -0
  79. package/dist/utils/agent-manager.d.ts.map +1 -1
  80. package/dist/utils/agent-manager.js +11 -0
  81. package/dist/utils/client-pool.d.ts.map +1 -1
  82. package/dist/utils/client-pool.js +4 -1
  83. package/dist/utils/dns-toolkit.d.ts +88 -1
  84. package/dist/utils/dns-toolkit.d.ts.map +1 -1
  85. package/dist/utils/dns-toolkit.js +704 -6
  86. package/dist/utils/doh.d.ts.map +1 -1
  87. package/dist/utils/doh.js +13 -16
  88. package/dist/utils/download.d.ts.map +1 -1
  89. package/dist/utils/download.js +10 -11
  90. package/dist/utils/rdap.d.ts +9 -0
  91. package/dist/utils/rdap.d.ts.map +1 -1
  92. package/dist/utils/rdap.js +78 -9
  93. package/dist/utils/security-grader.d.ts +47 -0
  94. package/dist/utils/security-grader.d.ts.map +1 -0
  95. package/dist/utils/security-grader.js +637 -0
  96. package/dist/utils/sparkline.d.ts +18 -0
  97. package/dist/utils/sparkline.d.ts.map +1 -0
  98. package/dist/utils/sparkline.js +55 -0
  99. package/dist/utils/sse.d.ts.map +1 -1
  100. package/dist/utils/sse.js +5 -6
  101. package/dist/utils/system-metrics.d.ts +26 -0
  102. package/dist/utils/system-metrics.d.ts.map +1 -0
  103. package/dist/utils/system-metrics.js +81 -0
  104. package/dist/webrtc/index.d.ts.map +1 -1
  105. package/dist/webrtc/index.js +21 -7
  106. package/dist/websocket/client.d.ts.map +1 -1
  107. package/dist/websocket/client.js +13 -16
  108. package/package.json +3 -2
package/dist/cli/index.js CHANGED
@@ -2,7 +2,7 @@
2
2
  import { program } from 'commander';
3
3
  import { promises as fs } from 'node:fs';
4
4
  import { join } from 'node:path';
5
- import pc from '../utils/colors.js';
5
+ import colors from '../utils/colors.js';
6
6
  async function readStdin() {
7
7
  if (process.stdin.isTTY) {
8
8
  return null;
@@ -51,14 +51,14 @@ async function loadEnvFile(filePath) {
51
51
  process.env[cleanKey] = cleanValue;
52
52
  }
53
53
  }
54
- console.log(pc.gray(`Loaded ${Object.keys(envVars).length} variables from ${envPath}`));
54
+ console.log(colors.gray(`Loaded ${Object.keys(envVars).length} variables from ${envPath}`));
55
55
  }
56
56
  catch (error) {
57
57
  if (error.code === 'ENOENT') {
58
- console.log(pc.yellow(`Warning: No .env file found at ${envPath}`));
58
+ console.log(colors.yellow(`Warning: No .env file found at ${envPath}`));
59
59
  }
60
60
  else {
61
- console.log(pc.red(`Error loading .env: ${error.message}`));
61
+ console.log(colors.red(`Error loading .env: ${error.message}`));
62
62
  }
63
63
  }
64
64
  return envVars;
@@ -130,14 +130,14 @@ async function main() {
130
130
  .option('-j, --json', 'Force JSON content-type')
131
131
  .option('-e, --env [path]', 'Load .env file from current directory or specified path')
132
132
  .addHelpText('after', `
133
- ${pc.bold(pc.yellow('Examples:'))}
134
- ${pc.green('$ rek httpbin.org/json')}
135
- ${pc.green('$ rek post api.com/users name="Cyber" role="Admin"')}
136
- ${pc.green('$ rek @github/user')}
137
- ${pc.green('$ rek @openai/v1/chat/completions model="gpt-5.1"')}
133
+ ${colors.bold(colors.yellow('Examples:'))}
134
+ ${colors.green('$ rek httpbin.org/json')}
135
+ ${colors.green('$ rek post api.com/users name="Cyber" role="Admin"')}
136
+ ${colors.green('$ rek @github/user')}
137
+ ${colors.green('$ rek @openai/v1/chat/completions model="gpt-5.1"')}
138
138
 
139
- ${pc.bold(pc.yellow('Available Presets:'))}
140
- ${pc.cyan(PRESET_NAMES.map(p => '@' + p).join(', '))}
139
+ ${colors.bold(colors.yellow('Available Presets:'))}
140
+ ${colors.cyan(PRESET_NAMES.map(p => '@' + p).join(', '))}
141
141
  `)
142
142
  .action(async (args, options) => {
143
143
  if (args.length === 0) {
@@ -166,7 +166,7 @@ ${pc.bold(pc.yellow('Available Presets:'))}
166
166
  }
167
167
  const { method, url, headers, data } = parseMixedArgs(argsToParse, !!presetConfig);
168
168
  if (!url) {
169
- console.error(pc.red('Error: URL/Path is required'));
169
+ console.error(colors.red('Error: URL/Path is required'));
170
170
  process.exit(1);
171
171
  }
172
172
  if (options.json) {
@@ -179,7 +179,7 @@ ${pc.bold(pc.yellow('Available Presets:'))}
179
179
  return;
180
180
  }
181
181
  if (url.startsWith('udp://')) {
182
- console.log(pc.yellow('UDP mode coming soon...'));
182
+ console.log(colors.yellow('UDP mode coming soon...'));
183
183
  return;
184
184
  }
185
185
  try {
@@ -205,7 +205,7 @@ ${pc.bold(pc.yellow('Available Presets:'))}
205
205
  });
206
206
  }
207
207
  catch (error) {
208
- console.error(pc.red(`
208
+ console.error(colors.red(`
209
209
  Error: ${error.message}`));
210
210
  if (options.verbose && error.cause) {
211
211
  console.error(error.cause);
@@ -279,6 +279,160 @@ complete -F _rek_completions rek
279
279
  const { openSearchPanel } = await import('./tui/search-panel.js');
280
280
  await openSearchPanel(query || undefined);
281
281
  });
282
+ program
283
+ .command('security')
284
+ .alias('headers')
285
+ .description('Analyze HTTP response headers for security best practices')
286
+ .argument('<url>', 'URL to analyze')
287
+ .action(async (url) => {
288
+ if (!url.startsWith('http'))
289
+ url = `https://${url}`;
290
+ const { createClient } = await import('../core/client.js');
291
+ const { analyzeSecurityHeaders } = await import('../utils/security-grader.js');
292
+ console.log(colors.gray(`Analyzing security headers for ${url}...`));
293
+ try {
294
+ const origin = new URL(url).origin;
295
+ const client = createClient({ baseUrl: origin });
296
+ const res = await client.get(url);
297
+ const report = analyzeSecurityHeaders(res.headers);
298
+ let gradeColor = colors.red;
299
+ if (report.grade.startsWith('A'))
300
+ gradeColor = colors.green;
301
+ if (report.grade.startsWith('B'))
302
+ gradeColor = colors.blue;
303
+ if (report.grade.startsWith('C'))
304
+ gradeColor = colors.yellow;
305
+ console.log(`
306
+ ${colors.bold(colors.cyan('🛡️ Security Headers Report'))}
307
+ Grade: ${gradeColor(colors.bold(report.grade))} (${report.score}/100)
308
+
309
+ ${colors.bold('Details:')}`);
310
+ report.details.forEach(item => {
311
+ const icon = item.status === 'pass' ? colors.green('✔') : item.status === 'warn' ? colors.yellow('⚠') : colors.red('✖');
312
+ const headerName = colors.bold(item.header);
313
+ const value = item.value ? colors.gray(`= ${item.value.length > 50 ? item.value.slice(0, 47) + '...' : item.value}`) : colors.gray('(missing)');
314
+ console.log(` ${icon} ${headerName} ${value}`);
315
+ if (item.status !== 'pass') {
316
+ console.log(` ${colors.red('→')} ${item.message}`);
317
+ }
318
+ });
319
+ console.log('');
320
+ }
321
+ catch (error) {
322
+ console.error(colors.red(`Analysis failed: ${error.message}`));
323
+ process.exit(1);
324
+ }
325
+ });
326
+ program
327
+ .command('ip')
328
+ .description('Get IP address intelligence using local GeoLite2 database')
329
+ .argument('<address>', 'IP address to lookup')
330
+ .action(async (address) => {
331
+ const { getIpInfo, isGeoIPAvailable } = await import('../mcp/ip-intel.js');
332
+ if (!isGeoIPAvailable()) {
333
+ console.log(colors.gray(`Downloading GeoLite2 database...`));
334
+ }
335
+ try {
336
+ const info = await getIpInfo(address);
337
+ if (info.bogon) {
338
+ console.log(colors.yellow(`\n⚠ ${address} is a Bogon/Private IP.`));
339
+ console.log(colors.gray(` Type: ${info.bogonType}`));
340
+ return;
341
+ }
342
+ console.log(`
343
+ ${colors.bold(colors.cyan('🌍 IP Intelligence Report'))}
344
+
345
+ ${colors.bold('Location:')}
346
+ ${colors.gray('City:')} ${info.city || 'N/A'}
347
+ ${colors.gray('Region:')} ${info.region || 'N/A'}
348
+ ${colors.gray('Country:')} ${info.country || 'N/A'} ${info.countryCode ? `(${info.countryCode})` : ''}
349
+ ${colors.gray('Continent:')} ${info.continent || 'N/A'}
350
+ ${colors.gray('Timezone:')} ${info.timezone || 'N/A'}
351
+ ${colors.gray('Coords:')} ${info.loc ? colors.cyan(info.loc) : 'N/A'}
352
+ ${colors.gray('Accuracy:')} ${info.accuracy ? `~${info.accuracy} km` : 'N/A'}
353
+
354
+ ${colors.bold('Network:')}
355
+ ${colors.gray('IP:')} ${info.ip}
356
+ ${colors.gray('Type:')} ${info.isIPv6 ? 'IPv6' : 'IPv4'}
357
+ ${colors.gray('Postal:')} ${info.postal || 'N/A'}
358
+ `);
359
+ }
360
+ catch (err) {
361
+ console.error(colors.red(`IP Lookup Failed: ${err.message}`));
362
+ process.exit(1);
363
+ }
364
+ });
365
+ program
366
+ .command('tls')
367
+ .alias('ssl')
368
+ .description('Inspect TLS/SSL certificate of a host')
369
+ .argument('<host>', 'Hostname or IP address')
370
+ .argument('[port]', 'Port number (default: 443)', '443')
371
+ .action(async (host, port) => {
372
+ const { inspectTLS } = await import('../utils/tls-inspector.js');
373
+ console.log(colors.gray(`Inspecting TLS certificate for ${host}:${port}...`));
374
+ try {
375
+ const info = await inspectTLS(host, parseInt(port));
376
+ let daysColor = colors.green;
377
+ if (info.daysRemaining < 30)
378
+ daysColor = colors.red;
379
+ else if (info.daysRemaining < 90)
380
+ daysColor = colors.yellow;
381
+ const validIcon = info.valid ? colors.green('✔ Valid') : colors.red('✖ Expired');
382
+ const authIcon = info.authorized ? colors.green('✔ Trusted') : colors.yellow('⚠ Self-signed/Untrusted');
383
+ console.log(`
384
+ ${colors.bold(colors.cyan('🔒 TLS Certificate Report'))}
385
+
386
+ ${colors.bold('Status:')}
387
+ ${validIcon}
388
+ ${authIcon}
389
+ ${colors.gray('Days Remaining:')} ${daysColor(info.daysRemaining.toString())}
390
+
391
+ ${colors.bold('Certificate:')}
392
+ ${colors.gray('Subject:')} ${info.subject?.CN || info.subject?.O || 'N/A'}
393
+ ${colors.gray('Issuer:')} ${info.issuer?.CN || info.issuer?.O || 'N/A'}
394
+ ${colors.gray('Valid From:')} ${info.validFrom.toISOString().split('T')[0]}
395
+ ${colors.gray('Valid To:')} ${info.validTo.toISOString().split('T')[0]}
396
+ ${colors.gray('Serial:')} ${info.serialNumber}
397
+
398
+ ${colors.bold('Security:')}
399
+ ${colors.gray('Protocol:')} ${info.protocol || 'N/A'}
400
+ ${colors.gray('Cipher:')} ${info.cipher?.name || 'N/A'}
401
+ ${colors.gray('Key:')} ${info.pubkey ? `${info.pubkey.algo.toUpperCase()} ${info.pubkey.size}-bit` : 'N/A'}
402
+
403
+ ${colors.bold('Fingerprints:')}
404
+ ${colors.gray('SHA-1:')} ${info.fingerprint}
405
+ ${colors.gray('SHA-256:')} ${info.fingerprint256?.slice(0, 40)}...
406
+ `);
407
+ if (info.altNames && info.altNames.length > 0) {
408
+ console.log(`${colors.bold('Subject Alternative Names:')}`);
409
+ info.altNames.slice(0, 10).forEach(san => {
410
+ console.log(` ${colors.gray('•')} ${san}`);
411
+ });
412
+ if (info.altNames.length > 10) {
413
+ console.log(` ${colors.gray(`... and ${info.altNames.length - 10} more`)}`);
414
+ }
415
+ console.log('');
416
+ }
417
+ if (info.extKeyUsage && info.extKeyUsage.length > 0) {
418
+ console.log(`${colors.bold('Extended Key Usage:')}`);
419
+ info.extKeyUsage.forEach(oid => {
420
+ const oidNames = {
421
+ '1.3.6.1.5.5.7.3.1': 'Server Authentication',
422
+ '1.3.6.1.5.5.7.3.2': 'Client Authentication',
423
+ '1.3.6.1.5.5.7.3.3': 'Code Signing',
424
+ '1.3.6.1.5.5.7.3.4': 'Email Protection',
425
+ };
426
+ console.log(` ${colors.gray('•')} ${oidNames[oid] || oid}`);
427
+ });
428
+ console.log('');
429
+ }
430
+ }
431
+ catch (err) {
432
+ console.error(colors.red(`TLS Inspection Failed: ${err.message}`));
433
+ process.exit(1);
434
+ }
435
+ });
282
436
  const dns = program.command('dns').description('DNS tools and diagnostics');
283
437
  dns
284
438
  .command('propagate')
@@ -287,27 +441,406 @@ complete -F _rek_completions rek
287
441
  .argument('[type]', 'Record type (A, AAAA, CNAME, MX, NS, TXT)', 'A')
288
442
  .action(async (domain, type) => {
289
443
  const { checkPropagation, formatPropagationReport } = await import('../dns/propagation.js');
290
- console.log(pc.gray(`Checking propagation for ${domain} (${type})...`));
444
+ console.log(colors.gray(`Checking propagation for ${domain} (${type})...`));
291
445
  const results = await checkPropagation(domain, type);
292
446
  console.log(formatPropagationReport(results, domain, type));
293
447
  });
448
+ dns
449
+ .command('lookup')
450
+ .description('Perform DNS lookup for any record type')
451
+ .argument('<domain>', 'Domain name to lookup')
452
+ .argument('[type]', 'Record type (A, AAAA, CNAME, MX, NS, TXT, SOA, CAA, SRV, ANY)', 'A')
453
+ .action(async (domain, type) => {
454
+ const { dnsLookup } = await import('../utils/dns-toolkit.js');
455
+ console.log(colors.gray(`Looking up ${type.toUpperCase()} records for ${domain}...`));
456
+ try {
457
+ const results = await dnsLookup(domain, type);
458
+ if (results.length === 0) {
459
+ console.log(colors.yellow(`\nNo ${type.toUpperCase()} records found for ${domain}`));
460
+ return;
461
+ }
462
+ console.log(`\n${colors.bold(colors.cyan('DNS Lookup Results'))}`);
463
+ console.log(`${colors.gray('Domain:')} ${domain} ${colors.gray('Type:')} ${type.toUpperCase()}\n`);
464
+ results.forEach(record => {
465
+ const ttl = record.ttl ? colors.gray(`TTL: ${record.ttl}s`) : '';
466
+ const data = typeof record.data === 'object'
467
+ ? JSON.stringify(record.data, null, 2)
468
+ : String(record.data);
469
+ console.log(` ${colors.green('•')} ${colors.bold(record.type.padEnd(6))} ${data} ${ttl}`);
470
+ });
471
+ console.log('');
472
+ }
473
+ catch (err) {
474
+ console.error(colors.red(`DNS Lookup Failed: ${err.message}`));
475
+ process.exit(1);
476
+ }
477
+ });
478
+ dns
479
+ .command('reverse')
480
+ .description('Perform reverse DNS lookup (IP to hostname)')
481
+ .argument('<ip>', 'IP address to reverse lookup')
482
+ .action(async (ip) => {
483
+ const { reverseLookup } = await import('../utils/dns-toolkit.js');
484
+ console.log(colors.gray(`Performing reverse lookup for ${ip}...`));
485
+ try {
486
+ const hostnames = await reverseLookup(ip);
487
+ if (hostnames.length === 0) {
488
+ console.log(colors.yellow(`\nNo PTR records found for ${ip}`));
489
+ return;
490
+ }
491
+ console.log(`\n${colors.bold(colors.cyan('Reverse DNS Lookup'))}`);
492
+ console.log(`${colors.gray('IP:')} ${ip}\n`);
493
+ console.log(`${colors.bold('Hostnames:')}`);
494
+ hostnames.forEach(hostname => {
495
+ console.log(` ${colors.green('•')} ${hostname}`);
496
+ });
497
+ console.log('');
498
+ }
499
+ catch (err) {
500
+ console.error(colors.red(`Reverse Lookup Failed: ${err.message}`));
501
+ process.exit(1);
502
+ }
503
+ });
504
+ dns
505
+ .command('health')
506
+ .description('Comprehensive DNS health check with scoring')
507
+ .argument('<domain>', 'Domain name to check')
508
+ .action(async (domain) => {
509
+ const { checkDnsHealth } = await import('../utils/dns-toolkit.js');
510
+ console.log(colors.gray(`Running DNS health check for ${domain}...`));
511
+ try {
512
+ const report = await checkDnsHealth(domain);
513
+ let gradeColor = colors.red;
514
+ if (report.grade === 'A')
515
+ gradeColor = colors.green;
516
+ else if (report.grade === 'B')
517
+ gradeColor = colors.blue;
518
+ else if (report.grade === 'C')
519
+ gradeColor = colors.yellow;
520
+ console.log(`
521
+ ${colors.bold(colors.cyan('🏥 DNS Health Report'))}
522
+ ${colors.gray('Domain:')} ${domain}
523
+ ${colors.gray('Grade:')} ${gradeColor(colors.bold(report.grade))} ${colors.gray('Score:')} ${report.score}/100
524
+ `);
525
+ console.log(`${colors.bold('Checks:')}`);
526
+ report.checks.forEach(check => {
527
+ const icon = check.status === 'pass' ? colors.green('✔') :
528
+ check.status === 'warn' ? colors.yellow('⚠') : colors.red('✖');
529
+ console.log(` ${icon} ${colors.bold(check.name.padEnd(16))} ${check.message}`);
530
+ });
531
+ console.log('');
532
+ }
533
+ catch (err) {
534
+ console.error(colors.red(`Health Check Failed: ${err.message}`));
535
+ process.exit(1);
536
+ }
537
+ });
538
+ dns
539
+ .command('spf')
540
+ .description('Validate SPF record')
541
+ .argument('<domain>', 'Domain name to validate')
542
+ .action(async (domain) => {
543
+ const { validateSpf } = await import('../utils/dns-toolkit.js');
544
+ console.log(colors.gray(`Validating SPF for ${domain}...`));
545
+ try {
546
+ const result = await validateSpf(domain);
547
+ const statusIcon = result.valid ? colors.green('✔ Valid') : colors.red('✖ Invalid');
548
+ console.log(`
549
+ ${colors.bold(colors.cyan('📧 SPF Validation Report'))}
550
+ ${colors.gray('Domain:')} ${domain}
551
+ ${colors.gray('Status:')} ${statusIcon}
552
+ `);
553
+ if (result.record) {
554
+ console.log(`${colors.bold('Record:')}`);
555
+ console.log(` ${colors.gray(result.record)}\n`);
556
+ console.log(`${colors.bold('Mechanisms:')} ${result.mechanisms.join(', ')}`);
557
+ console.log(`${colors.bold('Includes:')} ${result.includes.length > 0 ? result.includes.join(', ') : colors.gray('None')}`);
558
+ console.log(`${colors.bold('DNS Lookups:')} ${result.lookupCount}/10 ${result.lookupCount > 7 ? colors.yellow('(high)') : ''}`);
559
+ }
560
+ if (result.warnings.length > 0) {
561
+ console.log(`\n${colors.bold(colors.yellow('Warnings:'))}`);
562
+ result.warnings.forEach(w => console.log(` ${colors.yellow('⚠')} ${w}`));
563
+ }
564
+ if (result.errors.length > 0) {
565
+ console.log(`\n${colors.bold(colors.red('Errors:'))}`);
566
+ result.errors.forEach(e => console.log(` ${colors.red('✖')} ${e}`));
567
+ }
568
+ console.log('');
569
+ }
570
+ catch (err) {
571
+ console.error(colors.red(`SPF Validation Failed: ${err.message}`));
572
+ process.exit(1);
573
+ }
574
+ });
575
+ dns
576
+ .command('dmarc')
577
+ .description('Validate DMARC record')
578
+ .argument('<domain>', 'Domain name to validate')
579
+ .action(async (domain) => {
580
+ const { validateDmarc } = await import('../utils/dns-toolkit.js');
581
+ console.log(colors.gray(`Validating DMARC for ${domain}...`));
582
+ try {
583
+ const result = await validateDmarc(domain);
584
+ const statusIcon = result.valid ? colors.green('✔ Found') : colors.yellow('⚠ Not Found');
585
+ const policyColor = result.policy === 'reject' ? colors.green :
586
+ result.policy === 'quarantine' ? colors.yellow : colors.red;
587
+ console.log(`
588
+ ${colors.bold(colors.cyan('🛡️ DMARC Validation Report'))}
589
+ ${colors.gray('Domain:')} ${domain}
590
+ ${colors.gray('Status:')} ${statusIcon}
591
+ `);
592
+ if (result.record) {
593
+ console.log(`${colors.bold('Record:')}`);
594
+ console.log(` ${colors.gray(result.record)}\n`);
595
+ console.log(`${colors.bold('Policy:')} ${policyColor(result.policy)}`);
596
+ if (result.subdomainPolicy) {
597
+ console.log(`${colors.bold('Subdomain Policy:')} ${result.subdomainPolicy}`);
598
+ }
599
+ console.log(`${colors.bold('Percentage:')} ${result.percentage}%`);
600
+ if (result.rua) {
601
+ console.log(`${colors.bold('Aggregate Reports (rua):')} ${result.rua.join(', ')}`);
602
+ }
603
+ if (result.ruf) {
604
+ console.log(`${colors.bold('Forensic Reports (ruf):')} ${result.ruf.join(', ')}`);
605
+ }
606
+ }
607
+ if (result.warnings.length > 0) {
608
+ console.log(`\n${colors.bold(colors.yellow('Warnings:'))}`);
609
+ result.warnings.forEach(w => console.log(` ${colors.yellow('⚠')} ${w}`));
610
+ }
611
+ console.log('');
612
+ }
613
+ catch (err) {
614
+ console.error(colors.red(`DMARC Validation Failed: ${err.message}`));
615
+ process.exit(1);
616
+ }
617
+ });
618
+ dns
619
+ .command('dkim')
620
+ .description('Check DKIM record for a domain')
621
+ .argument('<domain>', 'Domain name to check')
622
+ .argument('[selector]', 'DKIM selector (default: "default")', 'default')
623
+ .action(async (domain, selector) => {
624
+ const { checkDkim } = await import('../utils/dns-toolkit.js');
625
+ console.log(colors.gray(`Checking DKIM for ${selector}._domainkey.${domain}...`));
626
+ try {
627
+ const result = await checkDkim(domain, selector);
628
+ const statusIcon = result.found ? colors.green('✔ Found') : colors.red('✖ Not Found');
629
+ console.log(`
630
+ ${colors.bold(colors.cyan('🔑 DKIM Check Report'))}
631
+ ${colors.gray('Domain:')} ${domain}
632
+ ${colors.gray('Selector:')} ${selector}
633
+ ${colors.gray('Status:')} ${statusIcon}
634
+ `);
635
+ if (result.record) {
636
+ console.log(`${colors.bold('Record:')}`);
637
+ console.log(` ${colors.gray(result.record.length > 100 ? result.record.slice(0, 100) + '...' : result.record)}\n`);
638
+ if (result.publicKey) {
639
+ console.log(`${colors.bold('Public Key:')} ${colors.green('Present')} (${result.publicKey.length} chars)`);
640
+ }
641
+ }
642
+ else {
643
+ console.log(colors.yellow(`No DKIM record found at ${selector}._domainkey.${domain}`));
644
+ console.log(colors.gray('\nCommon selectors to try: google, selector1, selector2, k1, default'));
645
+ }
646
+ console.log('');
647
+ }
648
+ catch (err) {
649
+ console.error(colors.red(`DKIM Check Failed: ${err.message}`));
650
+ process.exit(1);
651
+ }
652
+ });
653
+ dns
654
+ .command('email')
655
+ .description('Full email security audit (SPF + DMARC + DKIM + MX)')
656
+ .argument('<domain>', 'Domain name to audit')
657
+ .option('-s, --selector <selector>', 'DKIM selector to check', 'default')
658
+ .action(async (domain, options) => {
659
+ const { validateSpf, validateDmarc, checkDkim, dnsLookup } = await import('../utils/dns-toolkit.js');
660
+ console.log(colors.gray(`Running email security audit for ${domain}...\n`));
661
+ let score = 0;
662
+ const maxScore = 100;
663
+ console.log(`${colors.bold(colors.cyan('📧 Email Security Audit'))}`);
664
+ console.log(`${colors.gray('Domain:')} ${domain}\n`);
665
+ console.log(`${colors.bold('Mail Servers (MX):')}`);
666
+ try {
667
+ const mx = await dnsLookup(domain, 'MX');
668
+ if (mx.length > 0) {
669
+ score += 20;
670
+ mx.forEach(record => {
671
+ const data = record.data;
672
+ console.log(` ${colors.green('✔')} ${data.exchange} ${colors.gray(`(priority: ${data.priority})`)}`);
673
+ });
674
+ }
675
+ else {
676
+ console.log(` ${colors.red('✖')} No MX records (cannot receive email)`);
677
+ }
678
+ }
679
+ catch {
680
+ console.log(` ${colors.red('✖')} Failed to resolve MX`);
681
+ }
682
+ console.log(`\n${colors.bold('SPF:')}`);
683
+ const spf = await validateSpf(domain);
684
+ if (spf.valid) {
685
+ score += 25;
686
+ console.log(` ${colors.green('✔')} Valid SPF record`);
687
+ console.log(` ${colors.gray(spf.record || '')}`);
688
+ }
689
+ else if (spf.record) {
690
+ score += 10;
691
+ console.log(` ${colors.yellow('⚠')} SPF exists but has issues`);
692
+ spf.errors.forEach(e => console.log(` ${colors.red('→')} ${e}`));
693
+ }
694
+ else {
695
+ console.log(` ${colors.red('✖')} No SPF record`);
696
+ }
697
+ console.log(`\n${colors.bold('DMARC:')}`);
698
+ const dmarc = await validateDmarc(domain);
699
+ if (dmarc.valid && dmarc.policy !== 'none') {
700
+ score += 30;
701
+ console.log(` ${colors.green('✔')} DMARC policy: ${dmarc.policy}`);
702
+ }
703
+ else if (dmarc.valid) {
704
+ score += 15;
705
+ console.log(` ${colors.yellow('⚠')} DMARC exists but policy is "none"`);
706
+ }
707
+ else {
708
+ console.log(` ${colors.red('✖')} No DMARC record`);
709
+ }
710
+ console.log(`\n${colors.bold('DKIM:')}`);
711
+ const dkim = await checkDkim(domain, options.selector);
712
+ if (dkim.found) {
713
+ score += 25;
714
+ console.log(` ${colors.green('✔')} DKIM found (selector: ${options.selector})`);
715
+ }
716
+ else {
717
+ console.log(` ${colors.yellow('⚠')} No DKIM at selector "${options.selector}"`);
718
+ console.log(` ${colors.gray('Try: --selector google, selector1, selector2, k1')}`);
719
+ }
720
+ const grade = score >= 90 ? 'A' : score >= 70 ? 'B' : score >= 50 ? 'C' : score >= 30 ? 'D' : 'F';
721
+ const gradeColor = grade === 'A' ? colors.green : grade === 'B' ? colors.blue : grade === 'C' ? colors.yellow : colors.red;
722
+ console.log(`\n${colors.bold('Score:')} ${score}/${maxScore} ${colors.bold('Grade:')} ${gradeColor(grade)}\n`);
723
+ });
724
+ dns
725
+ .command('generate-dmarc')
726
+ .description('Generate a DMARC record interactively')
727
+ .option('-p, --policy <policy>', 'Policy: none, quarantine, reject', 'none')
728
+ .option('-sp, --subdomain-policy <policy>', 'Subdomain policy')
729
+ .option('--pct <percent>', 'Percentage of emails to apply policy', '100')
730
+ .option('--rua <emails>', 'Aggregate report email(s), comma-separated')
731
+ .option('--ruf <emails>', 'Forensic report email(s), comma-separated')
732
+ .action(async (options) => {
733
+ const { generateDmarc } = await import('../utils/dns-toolkit.js');
734
+ const dmarcOptions = {
735
+ policy: options.policy,
736
+ };
737
+ if (options.subdomainPolicy) {
738
+ dmarcOptions.subdomainPolicy = options.subdomainPolicy;
739
+ }
740
+ if (options.pct && options.pct !== '100') {
741
+ dmarcOptions.percentage = parseInt(options.pct);
742
+ }
743
+ if (options.rua) {
744
+ dmarcOptions.aggregateReports = options.rua.split(',').map((e) => e.trim());
745
+ }
746
+ if (options.ruf) {
747
+ dmarcOptions.forensicReports = options.ruf.split(',').map((e) => e.trim());
748
+ }
749
+ const record = generateDmarc(dmarcOptions);
750
+ console.log(`
751
+ ${colors.bold(colors.cyan('🛡️ DMARC Record Generator'))}
752
+
753
+ ${colors.bold('Add this TXT record to your DNS:')}
754
+ ${colors.gray('Name:')} _dmarc
755
+ ${colors.gray('Type:')} TXT
756
+ ${colors.gray('Value:')} ${colors.green(record)}
757
+
758
+ ${colors.bold('Policy Explanation:')}
759
+ ${colors.gray('none')} - Monitor only, take no action
760
+ ${colors.gray('quarantine')} - Send suspicious emails to spam
761
+ ${colors.gray('reject')} - Reject suspicious emails entirely
762
+
763
+ ${colors.yellow('Tip:')} Start with "none" to monitor, then move to "quarantine", then "reject".
764
+ `);
765
+ });
766
+ program
767
+ .command('dig')
768
+ .description('DNS lookup utility (like the real dig)')
769
+ .argument('[args...]', 'Query arguments: [@server] [domain] [type] [-x] [+short]')
770
+ .option('-x, --reverse', 'Reverse DNS lookup (IP to hostname)')
771
+ .allowUnknownOption()
772
+ .addHelpText('after', `
773
+ ${colors.bold(colors.yellow('Usage:'))}
774
+ ${colors.green('rek dig example.com')} ${colors.gray('Query A records')}
775
+ ${colors.green('rek dig example.com MX')} ${colors.gray('Query MX records')}
776
+ ${colors.green('rek dig example.com ANY')} ${colors.gray('Query all record types')}
777
+ ${colors.green('rek dig @8.8.8.8 example.com')} ${colors.gray('Use Google DNS')}
778
+ ${colors.green('rek dig @1.1.1.1 example.com MX')} ${colors.gray('Use Cloudflare DNS')}
779
+ ${colors.green('rek dig -x 8.8.8.8')} ${colors.gray('Reverse lookup')}
780
+ ${colors.green('rek dig +short example.com')} ${colors.gray('Short output (just answers)')}
781
+
782
+ ${colors.bold(colors.yellow('Common DNS Servers:'))}
783
+ ${colors.cyan('@8.8.8.8')} Google Public DNS
784
+ ${colors.cyan('@1.1.1.1')} Cloudflare DNS
785
+ ${colors.cyan('@9.9.9.9')} Quad9 DNS
786
+ ${colors.cyan('@208.67.222.222')} OpenDNS
787
+
788
+ ${colors.bold(colors.yellow('Record Types:'))}
789
+ A, AAAA, MX, NS, TXT, CNAME, SOA, PTR, SRV, CAA, ANY
790
+ `)
791
+ .action(async (args, cmdOptions) => {
792
+ const { dig, formatDigOutput } = await import('../utils/dns-toolkit.js');
793
+ let domain = '';
794
+ let server;
795
+ let type = 'A';
796
+ let reverse = cmdOptions.reverse || false;
797
+ let short = false;
798
+ for (const arg of args) {
799
+ if (arg.startsWith('@')) {
800
+ server = arg.slice(1);
801
+ }
802
+ else if (arg === '+short') {
803
+ short = true;
804
+ }
805
+ else if (arg.match(/^[A-Z]+$/i) && ['A', 'AAAA', 'MX', 'NS', 'TXT', 'CNAME', 'SOA', 'PTR', 'SRV', 'CAA', 'NAPTR', 'ANY'].includes(arg.toUpperCase())) {
806
+ type = arg.toUpperCase();
807
+ }
808
+ else if (!domain) {
809
+ domain = arg;
810
+ }
811
+ }
812
+ if (!domain) {
813
+ console.error(colors.red('Error: Domain/IP is required'));
814
+ console.log(colors.gray('Usage: rek dig example.com [TYPE]'));
815
+ console.log(colors.gray(' rek dig -x 8.8.8.8'));
816
+ process.exit(1);
817
+ }
818
+ try {
819
+ const result = await dig(domain, { server, type, reverse, short });
820
+ console.log(formatDigOutput(result, short));
821
+ }
822
+ catch (err) {
823
+ console.error(colors.red(`dig: ${err.message}`));
824
+ process.exit(1);
825
+ }
826
+ });
294
827
  const bench = program.command('bench').description('Performance benchmarking tools');
295
828
  bench
296
829
  .command('load')
297
830
  .description('Run a load test with real-time dashboard')
298
831
  .argument('[args...]', 'URL and options (users=10 duration=10s mode=throughput http2)')
299
832
  .addHelpText('after', `
300
- ${pc.bold(pc.yellow('Options (key=value):'))}
301
- ${pc.green('users')} Number of concurrent users ${pc.gray('(default: 50)')}
302
- ${pc.green('duration')} Test duration in seconds ${pc.gray('(default: 300)')}
303
- ${pc.green('ramp')} Ramp-up time in seconds ${pc.gray('(default: 5)')}
304
- ${pc.green('mode')} Test mode ${pc.gray('(default: throughput)')}
305
- ${pc.gray('Values: throughput, stress, realistic')}
306
- ${pc.green('http2')} Force HTTP/2 ${pc.gray('(default: false)')}
833
+ ${colors.bold(colors.yellow('Options (key=value):'))}
834
+ ${colors.green('users')} Number of concurrent users ${colors.gray('(default: 50)')}
835
+ ${colors.green('duration')} Test duration in seconds ${colors.gray('(default: 300)')}
836
+ ${colors.green('ramp')} Ramp-up time in seconds ${colors.gray('(default: 5)')}
837
+ ${colors.green('mode')} Test mode ${colors.gray('(default: throughput)')}
838
+ ${colors.gray('Values: throughput, stress, realistic')}
839
+ ${colors.green('http2')} Force HTTP/2 ${colors.gray('(default: false)')}
307
840
 
308
- ${pc.bold(pc.yellow('Examples:'))}
309
- ${pc.green('$ rek bench load httpbin.org/get users=100 duration=60 ramp=10')}
310
- ${pc.green('$ rek bench load https://api.com/heavy mode=stress http2=true')}
841
+ ${colors.bold(colors.yellow('Examples:'))}
842
+ ${colors.green('$ rek bench load httpbin.org/get users=100 duration=60 ramp=10')}
843
+ ${colors.green('$ rek bench load https://api.com/heavy mode=stress http2=true')}
311
844
  `)
312
845
  .action(async (args) => {
313
846
  let url = '';
@@ -341,7 +874,7 @@ ${pc.bold(pc.yellow('Examples:'))}
341
874
  }
342
875
  }
343
876
  if (!url) {
344
- console.error(pc.red('Error: URL is required. Example: rek bench load httpbin.org users=50'));
877
+ console.error(colors.red('Error: URL is required. Example: rek bench load httpbin.org users=50'));
345
878
  process.exit(1);
346
879
  }
347
880
  const { startLoadDashboard } = await import('./tui/load-dashboard.js');
@@ -355,23 +888,23 @@ ${pc.bold(pc.yellow('Examples:'))}
355
888
  .option('-d, --docs <path>', 'Path to documentation folder')
356
889
  .option('--debug', 'Enable debug logging')
357
890
  .addHelpText('after', `
358
- ${pc.bold(pc.yellow('Transport Modes:'))}
359
- ${pc.cyan('stdio')} ${pc.gray('(default)')} For Claude Code and other CLI tools
360
- ${pc.cyan('http')} Simple HTTP POST endpoint
361
- ${pc.cyan('sse')} HTTP + Server-Sent Events for real-time notifications
891
+ ${colors.bold(colors.yellow('Transport Modes:'))}
892
+ ${colors.cyan('stdio')} ${colors.gray('(default)')} For Claude Code and other CLI tools
893
+ ${colors.cyan('http')} Simple HTTP POST endpoint
894
+ ${colors.cyan('sse')} HTTP + Server-Sent Events for real-time notifications
362
895
 
363
- ${pc.bold(pc.yellow('Usage:'))}
364
- ${pc.green('$ rek mcp')} ${pc.gray('Start in stdio mode (for Claude Code)')}
365
- ${pc.green('$ rek mcp -t http')} ${pc.gray('Start HTTP server on port 3100')}
366
- ${pc.green('$ rek mcp -t sse -p 8080')} ${pc.gray('Start SSE server on custom port')}
367
- ${pc.green('$ rek mcp --debug')} ${pc.gray('Enable debug logging')}
896
+ ${colors.bold(colors.yellow('Usage:'))}
897
+ ${colors.green('$ rek mcp')} ${colors.gray('Start in stdio mode (for Claude Code)')}
898
+ ${colors.green('$ rek mcp -t http')} ${colors.gray('Start HTTP server on port 3100')}
899
+ ${colors.green('$ rek mcp -t sse -p 8080')} ${colors.gray('Start SSE server on custom port')}
900
+ ${colors.green('$ rek mcp --debug')} ${colors.gray('Enable debug logging')}
368
901
 
369
- ${pc.bold(pc.yellow('Tools provided:'))}
370
- ${pc.cyan('search_docs')} Search documentation by keyword
371
- ${pc.cyan('get_doc')} Get full content of a doc file
902
+ ${colors.bold(colors.yellow('Tools provided:'))}
903
+ ${colors.cyan('search_docs')} Search documentation by keyword
904
+ ${colors.cyan('get_doc')} Get full content of a doc file
372
905
 
373
- ${pc.bold(pc.yellow('Claude Code config (~/.claude.json):'))}
374
- ${pc.gray(`{
906
+ ${colors.bold(colors.yellow('Claude Code config (~/.claude.json):'))}
907
+ ${colors.gray(`{
375
908
  "mcpServers": {
376
909
  "recker-docs": {
377
910
  "command": "npx",
@@ -401,24 +934,24 @@ ${pc.bold(pc.yellow('Claude Code config (~/.claude.json):'))}
401
934
  │ GET /health - Health check │`
402
935
  : `
403
936
  │ POST / - JSON-RPC endpoint │`;
404
- console.log(pc.green(`
937
+ console.log(colors.green(`
405
938
  ┌─────────────────────────────────────────────┐
406
- │ ${pc.bold('Recker MCP Server')} │
939
+ │ ${colors.bold('Recker MCP Server')} │
407
940
  ├─────────────────────────────────────────────┤
408
- │ Transport: ${pc.cyan(transport.padEnd(31))}│
409
- │ Endpoint: ${pc.cyan(`http://localhost:${options.port}`.padEnd(32))}│
410
- │ Docs indexed: ${pc.yellow(String(server.getDocsCount()).padEnd(28))}│
941
+ │ Transport: ${colors.cyan(transport.padEnd(31))}│
942
+ │ Endpoint: ${colors.cyan(`http://localhost:${options.port}`.padEnd(32))}│
943
+ │ Docs indexed: ${colors.yellow(String(server.getDocsCount()).padEnd(28))}│
411
944
  ├─────────────────────────────────────────────┤${endpoints}
412
945
  ├─────────────────────────────────────────────┤
413
946
  │ Tools: │
414
- │ • ${pc.cyan('search_docs')} - Search documentation │
415
- │ • ${pc.cyan('get_doc')} - Get full doc content │
947
+ │ • ${colors.cyan('search_docs')} - Search documentation │
948
+ │ • ${colors.cyan('get_doc')} - Get full doc content │
416
949
  │ │
417
- │ Press ${pc.bold('Ctrl+C')} to stop │
950
+ │ Press ${colors.bold('Ctrl+C')} to stop │
418
951
  └─────────────────────────────────────────────┘
419
952
  `));
420
953
  process.on('SIGINT', async () => {
421
- console.log(pc.yellow('\nShutting down MCP server...'));
954
+ console.log(colors.yellow('\nShutting down MCP server...'));
422
955
  await server.stop();
423
956
  process.exit(0);
424
957
  });