recker 1.0.15-next.eb07368 → 1.0.16

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 +533 -79
  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/shell.d.ts +3 -0
  21. package/dist/cli/tui/shell.d.ts.map +1 -1
  22. package/dist/cli/tui/shell.js +163 -1
  23. package/dist/cli/tui/websocket.js +17 -17
  24. package/dist/core/client.d.ts.map +1 -1
  25. package/dist/core/client.js +18 -26
  26. package/dist/core/errors.d.ts +109 -1
  27. package/dist/core/errors.d.ts.map +1 -1
  28. package/dist/core/errors.js +214 -1
  29. package/dist/core/request-promise.d.ts.map +1 -1
  30. package/dist/core/request-promise.js +5 -6
  31. package/dist/core/response.d.ts.map +1 -1
  32. package/dist/core/response.js +5 -6
  33. package/dist/dns/propagation.d.ts +3 -1
  34. package/dist/dns/propagation.d.ts.map +1 -1
  35. package/dist/dns/propagation.js +99 -59
  36. package/dist/index.d.ts +1 -0
  37. package/dist/index.d.ts.map +1 -1
  38. package/dist/index.js +1 -0
  39. package/dist/mcp/client.d.ts.map +1 -1
  40. package/dist/mcp/client.js +10 -11
  41. package/dist/mcp/embeddings-loader.d.ts.map +1 -1
  42. package/dist/mcp/embeddings-loader.js +12 -2
  43. package/dist/mcp/geoip-loader.d.ts +11 -0
  44. package/dist/mcp/geoip-loader.d.ts.map +1 -0
  45. package/dist/mcp/geoip-loader.js +107 -0
  46. package/dist/mcp/ip-intel.d.ts +28 -0
  47. package/dist/mcp/ip-intel.d.ts.map +1 -0
  48. package/dist/mcp/ip-intel.js +209 -0
  49. package/dist/mcp/search/hybrid-search.d.ts.map +1 -1
  50. package/dist/mcp/search/hybrid-search.js +5 -1
  51. package/dist/mcp/search/math.d.ts.map +1 -1
  52. package/dist/mcp/search/math.js +5 -1
  53. package/dist/mcp/server.d.ts +4 -0
  54. package/dist/mcp/server.d.ts.map +1 -1
  55. package/dist/mcp/server.js +114 -1
  56. package/dist/plugins/compression.js +4 -2
  57. package/dist/plugins/har-player.d.ts.map +1 -1
  58. package/dist/plugins/har-player.js +8 -11
  59. package/dist/plugins/odata.d.ts.map +1 -1
  60. package/dist/plugins/odata.js +5 -2
  61. package/dist/protocols/ftp.d.ts.map +1 -1
  62. package/dist/protocols/ftp.js +69 -16
  63. package/dist/protocols/sftp.d.ts.map +1 -1
  64. package/dist/protocols/sftp.js +13 -3
  65. package/dist/protocols/telnet.d.ts.map +1 -1
  66. package/dist/protocols/telnet.js +25 -6
  67. package/dist/transport/base-udp.d.ts.map +1 -1
  68. package/dist/transport/base-udp.js +7 -4
  69. package/dist/transport/udp-response.d.ts.map +1 -1
  70. package/dist/transport/udp-response.js +10 -3
  71. package/dist/transport/udp.d.ts.map +1 -1
  72. package/dist/transport/udp.js +5 -1
  73. package/dist/transport/undici.d.ts.map +1 -1
  74. package/dist/transport/undici.js +3 -11
  75. package/dist/utils/agent-manager.d.ts +1 -0
  76. package/dist/utils/agent-manager.d.ts.map +1 -1
  77. package/dist/utils/agent-manager.js +11 -0
  78. package/dist/utils/client-pool.d.ts.map +1 -1
  79. package/dist/utils/client-pool.js +4 -1
  80. package/dist/utils/dns-toolkit.d.ts +88 -1
  81. package/dist/utils/dns-toolkit.d.ts.map +1 -1
  82. package/dist/utils/dns-toolkit.js +704 -6
  83. package/dist/utils/doh.d.ts.map +1 -1
  84. package/dist/utils/doh.js +13 -16
  85. package/dist/utils/download.d.ts.map +1 -1
  86. package/dist/utils/download.js +10 -11
  87. package/dist/utils/rdap.d.ts +9 -0
  88. package/dist/utils/rdap.d.ts.map +1 -1
  89. package/dist/utils/rdap.js +78 -9
  90. package/dist/utils/security-grader.d.ts +33 -0
  91. package/dist/utils/security-grader.d.ts.map +1 -1
  92. package/dist/utils/security-grader.js +548 -43
  93. package/dist/utils/sparkline.d.ts +18 -0
  94. package/dist/utils/sparkline.d.ts.map +1 -0
  95. package/dist/utils/sparkline.js +55 -0
  96. package/dist/utils/sse.d.ts.map +1 -1
  97. package/dist/utils/sse.js +5 -6
  98. package/dist/utils/system-metrics.d.ts +26 -0
  99. package/dist/utils/system-metrics.d.ts.map +1 -0
  100. package/dist/utils/system-metrics.js +81 -0
  101. package/dist/webrtc/index.d.ts.map +1 -1
  102. package/dist/webrtc/index.js +21 -7
  103. package/dist/websocket/client.d.ts.map +1 -1
  104. package/dist/websocket/client.js +13 -16
  105. package/package.json +3 -2
  106. package/dist/utils/ip-intel.d.ts +0 -15
  107. package/dist/utils/ip-intel.d.ts.map +0 -1
  108. package/dist/utils/ip-intel.js +0 -30
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);
@@ -289,72 +289,147 @@ complete -F _rek_completions rek
289
289
  url = `https://${url}`;
290
290
  const { createClient } = await import('../core/client.js');
291
291
  const { analyzeSecurityHeaders } = await import('../utils/security-grader.js');
292
- console.log(pc.gray(`Analyzing security headers for ${url}...`));
292
+ console.log(colors.gray(`Analyzing security headers for ${url}...`));
293
293
  try {
294
294
  const origin = new URL(url).origin;
295
295
  const client = createClient({ baseUrl: origin });
296
296
  const res = await client.get(url);
297
297
  const report = analyzeSecurityHeaders(res.headers);
298
- let gradeColor = pc.red;
298
+ let gradeColor = colors.red;
299
299
  if (report.grade.startsWith('A'))
300
- gradeColor = pc.green;
300
+ gradeColor = colors.green;
301
301
  if (report.grade.startsWith('B'))
302
- gradeColor = pc.blue;
302
+ gradeColor = colors.blue;
303
303
  if (report.grade.startsWith('C'))
304
- gradeColor = pc.yellow;
304
+ gradeColor = colors.yellow;
305
305
  console.log(`
306
- ${pc.bold(pc.cyan('🛡️ Security Headers Report'))}
307
- Grade: ${gradeColor(pc.bold(report.grade))} (${report.score}/100)
306
+ ${colors.bold(colors.cyan('🛡️ Security Headers Report'))}
307
+ Grade: ${gradeColor(colors.bold(report.grade))} (${report.score}/100)
308
308
 
309
- ${pc.bold('Details:')}`);
309
+ ${colors.bold('Details:')}`);
310
310
  report.details.forEach(item => {
311
- const icon = item.status === 'pass' ? pc.green('✔') : item.status === 'warn' ? pc.yellow('⚠') : pc.red('✖');
312
- const headerName = pc.bold(item.header);
313
- const value = item.value ? pc.gray(`= ${item.value.length > 50 ? item.value.slice(0, 47) + '...' : item.value}`) : pc.gray('(missing)');
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
314
  console.log(` ${icon} ${headerName} ${value}`);
315
315
  if (item.status !== 'pass') {
316
- console.log(` ${pc.red('→')} ${item.message}`);
316
+ console.log(` ${colors.red('→')} ${item.message}`);
317
317
  }
318
318
  });
319
319
  console.log('');
320
320
  }
321
321
  catch (error) {
322
- console.error(pc.red(`Analysis failed: ${error.message}`));
322
+ console.error(colors.red(`Analysis failed: ${error.message}`));
323
323
  process.exit(1);
324
324
  }
325
325
  });
326
326
  program
327
327
  .command('ip')
328
- .description('Get IP address intelligence (Geo, ASN, ISP)')
328
+ .description('Get IP address intelligence using local GeoLite2 database')
329
329
  .argument('<address>', 'IP address to lookup')
330
330
  .action(async (address) => {
331
- const { getIpInfo } = await import('../utils/ip-intel.js');
332
- console.log(pc.gray(`Fetching intelligence for ${address}...`));
331
+ const { getIpInfo, isGeoIPAvailable } = await import('../mcp/ip-intel.js');
332
+ if (!isGeoIPAvailable()) {
333
+ console.log(colors.gray(`Downloading GeoLite2 database...`));
334
+ }
333
335
  try {
334
336
  const info = await getIpInfo(address);
335
337
  if (info.bogon) {
336
- console.log(pc.yellow(`\n⚠ ${address} is a Bogon/Private IP.`));
338
+ console.log(colors.yellow(`\n⚠ ${address} is a Bogon/Private IP.`));
339
+ console.log(colors.gray(` Type: ${info.bogonType}`));
337
340
  return;
338
341
  }
339
342
  console.log(`
340
- ${pc.bold(pc.cyan('🌍 IP Intelligence Report'))}
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}
341
397
 
342
- ${pc.bold('Location:')}
343
- ${pc.gray('City:')} ${info.city || 'N/A'}
344
- ${pc.gray('Region:')} ${info.region || 'N/A'}
345
- ${pc.gray('Country:')} ${info.country || 'N/A'}
346
- ${pc.gray('Timezone:')} ${info.timezone || 'N/A'}
347
- ${pc.gray('Coords:')} ${info.loc ? pc.cyan(info.loc) : 'N/A'}
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'}
348
402
 
349
- ${pc.bold('Network:')}
350
- ${pc.gray('IP:')} ${info.ip}
351
- ${pc.gray('Hostname:')} ${info.hostname || 'N/A'}
352
- ${pc.gray('ASN/Org:')} ${info.org || 'N/A'}
353
- ${pc.gray('Anycast:')} ${info.anycast ? pc.green('Yes') : pc.gray('No')}
403
+ ${colors.bold('Fingerprints:')}
404
+ ${colors.gray('SHA-1:')} ${info.fingerprint}
405
+ ${colors.gray('SHA-256:')} ${info.fingerprint256?.slice(0, 40)}...
354
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
+ }
355
430
  }
356
431
  catch (err) {
357
- console.error(pc.red(`IP Lookup Failed: ${err.message}`));
432
+ console.error(colors.red(`TLS Inspection Failed: ${err.message}`));
358
433
  process.exit(1);
359
434
  }
360
435
  });
@@ -366,27 +441,406 @@ ${pc.bold('Network:')}
366
441
  .argument('[type]', 'Record type (A, AAAA, CNAME, MX, NS, TXT)', 'A')
367
442
  .action(async (domain, type) => {
368
443
  const { checkPropagation, formatPropagationReport } = await import('../dns/propagation.js');
369
- console.log(pc.gray(`Checking propagation for ${domain} (${type})...`));
444
+ console.log(colors.gray(`Checking propagation for ${domain} (${type})...`));
370
445
  const results = await checkPropagation(domain, type);
371
446
  console.log(formatPropagationReport(results, domain, type));
372
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
+ });
373
827
  const bench = program.command('bench').description('Performance benchmarking tools');
374
828
  bench
375
829
  .command('load')
376
830
  .description('Run a load test with real-time dashboard')
377
831
  .argument('[args...]', 'URL and options (users=10 duration=10s mode=throughput http2)')
378
832
  .addHelpText('after', `
379
- ${pc.bold(pc.yellow('Options (key=value):'))}
380
- ${pc.green('users')} Number of concurrent users ${pc.gray('(default: 50)')}
381
- ${pc.green('duration')} Test duration in seconds ${pc.gray('(default: 300)')}
382
- ${pc.green('ramp')} Ramp-up time in seconds ${pc.gray('(default: 5)')}
383
- ${pc.green('mode')} Test mode ${pc.gray('(default: throughput)')}
384
- ${pc.gray('Values: throughput, stress, realistic')}
385
- ${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)')}
386
840
 
387
- ${pc.bold(pc.yellow('Examples:'))}
388
- ${pc.green('$ rek bench load httpbin.org/get users=100 duration=60 ramp=10')}
389
- ${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')}
390
844
  `)
391
845
  .action(async (args) => {
392
846
  let url = '';
@@ -420,7 +874,7 @@ ${pc.bold(pc.yellow('Examples:'))}
420
874
  }
421
875
  }
422
876
  if (!url) {
423
- 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'));
424
878
  process.exit(1);
425
879
  }
426
880
  const { startLoadDashboard } = await import('./tui/load-dashboard.js');
@@ -434,23 +888,23 @@ ${pc.bold(pc.yellow('Examples:'))}
434
888
  .option('-d, --docs <path>', 'Path to documentation folder')
435
889
  .option('--debug', 'Enable debug logging')
436
890
  .addHelpText('after', `
437
- ${pc.bold(pc.yellow('Transport Modes:'))}
438
- ${pc.cyan('stdio')} ${pc.gray('(default)')} For Claude Code and other CLI tools
439
- ${pc.cyan('http')} Simple HTTP POST endpoint
440
- ${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
441
895
 
442
- ${pc.bold(pc.yellow('Usage:'))}
443
- ${pc.green('$ rek mcp')} ${pc.gray('Start in stdio mode (for Claude Code)')}
444
- ${pc.green('$ rek mcp -t http')} ${pc.gray('Start HTTP server on port 3100')}
445
- ${pc.green('$ rek mcp -t sse -p 8080')} ${pc.gray('Start SSE server on custom port')}
446
- ${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')}
447
901
 
448
- ${pc.bold(pc.yellow('Tools provided:'))}
449
- ${pc.cyan('search_docs')} Search documentation by keyword
450
- ${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
451
905
 
452
- ${pc.bold(pc.yellow('Claude Code config (~/.claude.json):'))}
453
- ${pc.gray(`{
906
+ ${colors.bold(colors.yellow('Claude Code config (~/.claude.json):'))}
907
+ ${colors.gray(`{
454
908
  "mcpServers": {
455
909
  "recker-docs": {
456
910
  "command": "npx",
@@ -480,24 +934,24 @@ ${pc.bold(pc.yellow('Claude Code config (~/.claude.json):'))}
480
934
  │ GET /health - Health check │`
481
935
  : `
482
936
  │ POST / - JSON-RPC endpoint │`;
483
- console.log(pc.green(`
937
+ console.log(colors.green(`
484
938
  ┌─────────────────────────────────────────────┐
485
- │ ${pc.bold('Recker MCP Server')} │
939
+ │ ${colors.bold('Recker MCP Server')} │
486
940
  ├─────────────────────────────────────────────┤
487
- │ Transport: ${pc.cyan(transport.padEnd(31))}│
488
- │ Endpoint: ${pc.cyan(`http://localhost:${options.port}`.padEnd(32))}│
489
- │ 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))}│
490
944
  ├─────────────────────────────────────────────┤${endpoints}
491
945
  ├─────────────────────────────────────────────┤
492
946
  │ Tools: │
493
- │ • ${pc.cyan('search_docs')} - Search documentation │
494
- │ • ${pc.cyan('get_doc')} - Get full doc content │
947
+ │ • ${colors.cyan('search_docs')} - Search documentation │
948
+ │ • ${colors.cyan('get_doc')} - Get full doc content │
495
949
  │ │
496
- │ Press ${pc.bold('Ctrl+C')} to stop │
950
+ │ Press ${colors.bold('Ctrl+C')} to stop │
497
951
  └─────────────────────────────────────────────┘
498
952
  `));
499
953
  process.on('SIGINT', async () => {
500
- console.log(pc.yellow('\nShutting down MCP server...'));
954
+ console.log(colors.yellow('\nShutting down MCP server...'));
501
955
  await server.stop();
502
956
  process.exit(0);
503
957
  });