recker 1.0.15 → 1.0.17

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 (130) hide show
  1. package/README.md +86 -97
  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 +8 -3
  10. package/dist/bench/stats.d.ts +15 -1
  11. package/dist/bench/stats.d.ts.map +1 -1
  12. package/dist/bench/stats.js +117 -5
  13. package/dist/cache/memory-storage.d.ts.map +1 -1
  14. package/dist/cache/memory-storage.js +3 -2
  15. package/dist/cli/handler.js +14 -14
  16. package/dist/cli/index.js +602 -48
  17. package/dist/cli/presets.js +5 -5
  18. package/dist/cli/tui/ai-chat.js +10 -10
  19. package/dist/cli/tui/load-dashboard.d.ts.map +1 -1
  20. package/dist/cli/tui/load-dashboard.js +127 -32
  21. package/dist/cli/tui/scroll-buffer.d.ts +43 -0
  22. package/dist/cli/tui/scroll-buffer.d.ts.map +1 -0
  23. package/dist/cli/tui/scroll-buffer.js +162 -0
  24. package/dist/cli/tui/search-panel.d.ts +41 -0
  25. package/dist/cli/tui/search-panel.d.ts.map +1 -0
  26. package/dist/cli/tui/search-panel.js +420 -0
  27. package/dist/cli/tui/shell.d.ts +14 -0
  28. package/dist/cli/tui/shell.d.ts.map +1 -1
  29. package/dist/cli/tui/shell.js +424 -46
  30. package/dist/cli/tui/websocket.js +17 -17
  31. package/dist/contract/index.js +3 -2
  32. package/dist/core/client.d.ts.map +1 -1
  33. package/dist/core/client.js +18 -26
  34. package/dist/core/errors.d.ts +109 -1
  35. package/dist/core/errors.d.ts.map +1 -1
  36. package/dist/core/errors.js +214 -1
  37. package/dist/core/request-promise.d.ts.map +1 -1
  38. package/dist/core/request-promise.js +5 -6
  39. package/dist/core/response.d.ts.map +1 -1
  40. package/dist/core/response.js +5 -6
  41. package/dist/dns/index.d.ts +1 -0
  42. package/dist/dns/index.d.ts.map +1 -1
  43. package/dist/dns/index.js +1 -0
  44. package/dist/dns/propagation.d.ts +21 -0
  45. package/dist/dns/propagation.d.ts.map +1 -0
  46. package/dist/dns/propagation.js +169 -0
  47. package/dist/index.d.ts +3 -0
  48. package/dist/index.d.ts.map +1 -1
  49. package/dist/index.js +3 -0
  50. package/dist/mcp/client.d.ts.map +1 -1
  51. package/dist/mcp/client.js +10 -11
  52. package/dist/mcp/embeddings-loader.d.ts +18 -0
  53. package/dist/mcp/embeddings-loader.d.ts.map +1 -0
  54. package/dist/mcp/embeddings-loader.js +162 -0
  55. package/dist/mcp/geoip-loader.d.ts +11 -0
  56. package/dist/mcp/geoip-loader.d.ts.map +1 -0
  57. package/dist/mcp/geoip-loader.js +107 -0
  58. package/dist/mcp/index.d.ts +1 -0
  59. package/dist/mcp/index.d.ts.map +1 -1
  60. package/dist/mcp/index.js +1 -0
  61. package/dist/mcp/ip-intel.d.ts +28 -0
  62. package/dist/mcp/ip-intel.d.ts.map +1 -0
  63. package/dist/mcp/ip-intel.js +209 -0
  64. package/dist/mcp/search/hybrid-search.d.ts.map +1 -1
  65. package/dist/mcp/search/hybrid-search.js +59 -38
  66. package/dist/mcp/search/math.d.ts.map +1 -1
  67. package/dist/mcp/search/math.js +5 -1
  68. package/dist/mcp/server.d.ts +6 -0
  69. package/dist/mcp/server.d.ts.map +1 -1
  70. package/dist/mcp/server.js +122 -2
  71. package/dist/plugins/compression.js +4 -2
  72. package/dist/plugins/har-player.d.ts.map +1 -1
  73. package/dist/plugins/har-player.js +8 -11
  74. package/dist/plugins/odata.d.ts.map +1 -1
  75. package/dist/plugins/odata.js +5 -2
  76. package/dist/protocols/ftp.d.ts.map +1 -1
  77. package/dist/protocols/ftp.js +69 -16
  78. package/dist/protocols/sftp.d.ts.map +1 -1
  79. package/dist/protocols/sftp.js +13 -3
  80. package/dist/protocols/telnet.d.ts.map +1 -1
  81. package/dist/protocols/telnet.js +25 -6
  82. package/dist/recker.d.ts +47 -0
  83. package/dist/recker.d.ts.map +1 -0
  84. package/dist/recker.js +99 -0
  85. package/dist/transport/base-udp.d.ts.map +1 -1
  86. package/dist/transport/base-udp.js +7 -4
  87. package/dist/transport/udp-response.d.ts.map +1 -1
  88. package/dist/transport/udp-response.js +10 -3
  89. package/dist/transport/udp.d.ts.map +1 -1
  90. package/dist/transport/udp.js +5 -1
  91. package/dist/transport/undici.d.ts.map +1 -1
  92. package/dist/transport/undici.js +75 -63
  93. package/dist/utils/agent-manager.d.ts +1 -0
  94. package/dist/utils/agent-manager.d.ts.map +1 -1
  95. package/dist/utils/agent-manager.js +11 -0
  96. package/dist/utils/client-pool.d.ts.map +1 -1
  97. package/dist/utils/client-pool.js +4 -1
  98. package/dist/utils/colors.d.ts +16 -0
  99. package/dist/utils/colors.d.ts.map +1 -1
  100. package/dist/utils/colors.js +16 -0
  101. package/dist/utils/dns-toolkit.d.ts +88 -1
  102. package/dist/utils/dns-toolkit.d.ts.map +1 -1
  103. package/dist/utils/dns-toolkit.js +704 -6
  104. package/dist/utils/doh.d.ts.map +1 -1
  105. package/dist/utils/doh.js +13 -16
  106. package/dist/utils/download.d.ts.map +1 -1
  107. package/dist/utils/download.js +10 -11
  108. package/dist/utils/rdap.d.ts +9 -0
  109. package/dist/utils/rdap.d.ts.map +1 -1
  110. package/dist/utils/rdap.js +78 -9
  111. package/dist/utils/security-grader.d.ts +47 -0
  112. package/dist/utils/security-grader.d.ts.map +1 -0
  113. package/dist/utils/security-grader.js +637 -0
  114. package/dist/utils/sparkline.d.ts +18 -0
  115. package/dist/utils/sparkline.d.ts.map +1 -0
  116. package/dist/utils/sparkline.js +55 -0
  117. package/dist/utils/sse.d.ts.map +1 -1
  118. package/dist/utils/sse.js +5 -6
  119. package/dist/utils/system-metrics.d.ts +26 -0
  120. package/dist/utils/system-metrics.d.ts.map +1 -0
  121. package/dist/utils/system-metrics.js +81 -0
  122. package/dist/utils/tls-inspector.d.ts +6 -0
  123. package/dist/utils/tls-inspector.d.ts.map +1 -1
  124. package/dist/utils/tls-inspector.js +35 -1
  125. package/dist/webrtc/index.d.ts.map +1 -1
  126. package/dist/webrtc/index.js +21 -7
  127. package/dist/websocket/client.d.ts.map +1 -1
  128. package/dist/websocket/client.js +13 -16
  129. package/package.json +4 -3
  130. package/dist/mcp/data/embeddings.json +0 -1
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);
@@ -270,23 +270,577 @@ complete -F _rek_completions rek
270
270
  const shell = new RekShell();
271
271
  shell.start();
272
272
  });
273
+ program
274
+ .command('docs [query...]')
275
+ .alias('?')
276
+ .description('Search Recker documentation (opens fullscreen panel)')
277
+ .action(async (queryParts) => {
278
+ const query = queryParts.join(' ').trim();
279
+ const { openSearchPanel } = await import('./tui/search-panel.js');
280
+ await openSearchPanel(query || undefined);
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
+ });
436
+ const dns = program.command('dns').description('DNS tools and diagnostics');
437
+ dns
438
+ .command('propagate')
439
+ .description('Check global DNS propagation across multiple providers')
440
+ .argument('<domain>', 'Domain name to check')
441
+ .argument('[type]', 'Record type (A, AAAA, CNAME, MX, NS, TXT)', 'A')
442
+ .action(async (domain, type) => {
443
+ const { checkPropagation, formatPropagationReport } = await import('../dns/propagation.js');
444
+ console.log(colors.gray(`Checking propagation for ${domain} (${type})...`));
445
+ const results = await checkPropagation(domain, type);
446
+ console.log(formatPropagationReport(results, domain, type));
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
+ });
273
827
  const bench = program.command('bench').description('Performance benchmarking tools');
274
828
  bench
275
829
  .command('load')
276
830
  .description('Run a load test with real-time dashboard')
277
831
  .argument('[args...]', 'URL and options (users=10 duration=10s mode=throughput http2)')
278
832
  .addHelpText('after', `
279
- ${pc.bold(pc.yellow('Options (key=value):'))}
280
- ${pc.green('users')} Number of concurrent users ${pc.gray('(default: 50)')}
281
- ${pc.green('duration')} Test duration in seconds ${pc.gray('(default: 300)')}
282
- ${pc.green('ramp')} Ramp-up time in seconds ${pc.gray('(default: 5)')}
283
- ${pc.green('mode')} Test mode ${pc.gray('(default: throughput)')}
284
- ${pc.gray('Values: throughput, stress, realistic')}
285
- ${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)')}
286
840
 
287
- ${pc.bold(pc.yellow('Examples:'))}
288
- ${pc.green('$ rek bench load httpbin.org/get users=100 duration=60 ramp=10')}
289
- ${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')}
290
844
  `)
291
845
  .action(async (args) => {
292
846
  let url = '';
@@ -320,7 +874,7 @@ ${pc.bold(pc.yellow('Examples:'))}
320
874
  }
321
875
  }
322
876
  if (!url) {
323
- 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'));
324
878
  process.exit(1);
325
879
  }
326
880
  const { startLoadDashboard } = await import('./tui/load-dashboard.js');
@@ -334,23 +888,23 @@ ${pc.bold(pc.yellow('Examples:'))}
334
888
  .option('-d, --docs <path>', 'Path to documentation folder')
335
889
  .option('--debug', 'Enable debug logging')
336
890
  .addHelpText('after', `
337
- ${pc.bold(pc.yellow('Transport Modes:'))}
338
- ${pc.cyan('stdio')} ${pc.gray('(default)')} For Claude Code and other CLI tools
339
- ${pc.cyan('http')} Simple HTTP POST endpoint
340
- ${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
341
895
 
342
- ${pc.bold(pc.yellow('Usage:'))}
343
- ${pc.green('$ rek mcp')} ${pc.gray('Start in stdio mode (for Claude Code)')}
344
- ${pc.green('$ rek mcp -t http')} ${pc.gray('Start HTTP server on port 3100')}
345
- ${pc.green('$ rek mcp -t sse -p 8080')} ${pc.gray('Start SSE server on custom port')}
346
- ${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')}
347
901
 
348
- ${pc.bold(pc.yellow('Tools provided:'))}
349
- ${pc.cyan('search_docs')} Search documentation by keyword
350
- ${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
351
905
 
352
- ${pc.bold(pc.yellow('Claude Code config (~/.claude.json):'))}
353
- ${pc.gray(`{
906
+ ${colors.bold(colors.yellow('Claude Code config (~/.claude.json):'))}
907
+ ${colors.gray(`{
354
908
  "mcpServers": {
355
909
  "recker-docs": {
356
910
  "command": "npx",
@@ -380,24 +934,24 @@ ${pc.bold(pc.yellow('Claude Code config (~/.claude.json):'))}
380
934
  │ GET /health - Health check │`
381
935
  : `
382
936
  │ POST / - JSON-RPC endpoint │`;
383
- console.log(pc.green(`
937
+ console.log(colors.green(`
384
938
  ┌─────────────────────────────────────────────┐
385
- │ ${pc.bold('Recker MCP Server')} │
939
+ │ ${colors.bold('Recker MCP Server')} │
386
940
  ├─────────────────────────────────────────────┤
387
- │ Transport: ${pc.cyan(transport.padEnd(31))}│
388
- │ Endpoint: ${pc.cyan(`http://localhost:${options.port}`.padEnd(32))}│
389
- │ 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))}│
390
944
  ├─────────────────────────────────────────────┤${endpoints}
391
945
  ├─────────────────────────────────────────────┤
392
946
  │ Tools: │
393
- │ • ${pc.cyan('search_docs')} - Search documentation │
394
- │ • ${pc.cyan('get_doc')} - Get full doc content │
947
+ │ • ${colors.cyan('search_docs')} - Search documentation │
948
+ │ • ${colors.cyan('get_doc')} - Get full doc content │
395
949
  │ │
396
- │ Press ${pc.bold('Ctrl+C')} to stop │
950
+ │ Press ${colors.bold('Ctrl+C')} to stop │
397
951
  └─────────────────────────────────────────────┘
398
952
  `));
399
953
  process.on('SIGINT', async () => {
400
- console.log(pc.yellow('\nShutting down MCP server...'));
954
+ console.log(colors.yellow('\nShutting down MCP server...'));
401
955
  await server.stop();
402
956
  process.exit(0);
403
957
  });