recker 1.0.15-next.905f0e7 → 1.0.15-next.b59c9d1

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 +166 -4
  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
@@ -1,5 +1,5 @@
1
1
  import * as presets from '../presets/index.js';
2
- import pc from '../utils/colors.js';
2
+ import colors from '../utils/colors.js';
3
3
  const ENV_MAPPING = {
4
4
  openai: ['OPENAI_API_KEY'],
5
5
  anthropic: ['ANTHROPIC_API_KEY'],
@@ -14,7 +14,7 @@ const ENV_MAPPING = {
14
14
  export function resolvePreset(name) {
15
15
  const presetFn = presets[name];
16
16
  if (!presetFn) {
17
- console.error(pc.red(`Error: Unknown preset '@${name}'`));
17
+ console.error(colors.red(`Error: Unknown preset '@${name}'`));
18
18
  process.exit(1);
19
19
  }
20
20
  const requiredEnvs = ENV_MAPPING[name];
@@ -24,7 +24,7 @@ export function resolvePreset(name) {
24
24
  for (const envVar of requiredEnvs) {
25
25
  const value = process.env[envVar];
26
26
  if (!value) {
27
- console.error(pc.yellow(`Warning: Missing env variable ${envVar} for preset @${name}`));
27
+ console.error(colors.yellow(`Warning: Missing env variable ${envVar} for preset @${name}`));
28
28
  missing = true;
29
29
  }
30
30
  else {
@@ -33,14 +33,14 @@ export function resolvePreset(name) {
33
33
  }
34
34
  }
35
35
  if (missing) {
36
- console.log(pc.gray(`Tip: export ${requiredEnvs.join('=... ')}=...`));
36
+ console.log(colors.gray(`Tip: export ${requiredEnvs.join('=... ')}=...`));
37
37
  }
38
38
  }
39
39
  try {
40
40
  return presetFn(options);
41
41
  }
42
42
  catch (error) {
43
- console.error(pc.red(`Error initializing preset @${name}: ${error.message}`));
43
+ console.error(colors.red(`Error initializing preset @${name}: ${error.message}`));
44
44
  process.exit(1);
45
45
  }
46
46
  }
@@ -1,16 +1,16 @@
1
1
  import readline from 'node:readline';
2
- import pc from '../../utils/colors.js';
2
+ import colors from '../../utils/colors.js';
3
3
  import { createAI } from '../../ai/client.js';
4
4
  export async function startAIChat(rl, provider = 'openai', apiKey, model) {
5
5
  console.clear();
6
- console.log(pc.bold(pc.magenta(`šŸ¤– Rek AI Chat (${provider})`)));
7
- console.log(pc.gray('Type your message. Ctrl+C to exit.'));
6
+ console.log(colors.bold(colors.magenta(`šŸ¤– Rek AI Chat (${provider})`)));
7
+ console.log(colors.gray('Type your message. Ctrl+C to exit.'));
8
8
  const envKey = provider === 'openai' ? 'OPENAI_API_KEY' : 'ANTHROPIC_API_KEY';
9
9
  const key = apiKey || process.env[envKey];
10
10
  if (!key) {
11
- console.log(pc.yellow(`
11
+ console.log(colors.yellow(`
12
12
  Warning: No API Key found for ${provider}.`));
13
- console.log(`Please set it via environment variable ${pc.bold(envKey)} or passing it to the command.`);
13
+ console.log(`Please set it via environment variable ${colors.bold(envKey)} or passing it to the command.`);
14
14
  console.log(`Example: set ${envKey}=sk-... inside the shell.`);
15
15
  return;
16
16
  }
@@ -24,7 +24,7 @@ Warning: No API Key found for ${provider}.`));
24
24
  const history = [
25
25
  { role: 'system', content: 'You are Recker AI, a helpful and concise assistant in a terminal environment.' }
26
26
  ];
27
- rl.setPrompt(pc.magenta('You › '));
27
+ rl.setPrompt(colors.magenta('You › '));
28
28
  rl.prompt();
29
29
  return new Promise((resolve) => {
30
30
  const onLine = async (line) => {
@@ -36,7 +36,7 @@ Warning: No API Key found for ${provider}.`));
36
36
  if (input.toLowerCase() === '/clear') {
37
37
  history.length = 1;
38
38
  console.clear();
39
- console.log(pc.gray('Context cleared.'));
39
+ console.log(colors.gray('Context cleared.'));
40
40
  rl.prompt();
41
41
  return;
42
42
  }
@@ -47,7 +47,7 @@ Warning: No API Key found for ${provider}.`));
47
47
  }
48
48
  history.push({ role: 'user', content: input });
49
49
  rl.pause();
50
- process.stdout.write(pc.cyan('AI › '));
50
+ process.stdout.write(colors.cyan('AI › '));
51
51
  let fullResponse = '';
52
52
  try {
53
53
  const stream = await client.stream({
@@ -66,10 +66,10 @@ Warning: No API Key found for ${provider}.`));
66
66
  history.push({ role: 'assistant', content: fullResponse });
67
67
  }
68
68
  catch (error) {
69
- console.log(pc.red(`
69
+ console.log(colors.red(`
70
70
  Error: ${error.message}`));
71
71
  if (error.cause)
72
- console.log(pc.gray(error.cause));
72
+ console.log(colors.gray(error.cause));
73
73
  }
74
74
  finally {
75
75
  rl.resume();
@@ -1 +1 @@
1
- {"version":3,"file":"load-dashboard.d.ts","sourceRoot":"","sources":["../../../src/cli/tui/load-dashboard.ts"],"names":[],"mappings":"AAGA,OAAO,EAAiB,UAAU,EAAE,MAAM,0BAA0B,CAAC;AAMrE,wBAAsB,kBAAkB,CAAC,MAAM,EAAE,UAAU,iBA4E1D"}
1
+ {"version":3,"file":"load-dashboard.d.ts","sourceRoot":"","sources":["../../../src/cli/tui/load-dashboard.ts"],"names":[],"mappings":"AAGA,OAAO,EAAiB,UAAU,EAAE,MAAM,0BAA0B,CAAC;AASrE,wBAAsB,kBAAkB,CAAC,MAAM,EAAE,UAAU,iBAuG1D"}
@@ -1,9 +1,12 @@
1
- import pc from '../../utils/colors.js';
1
+ import colors from '../../utils/colors.js';
2
2
  import { plot } from '../../utils/chart.js';
3
3
  import readline from 'node:readline';
4
4
  import { LoadGenerator } from '../../bench/generator.js';
5
+ import { SparklineBuffer } from '../../utils/sparkline.js';
6
+ import { SystemMetrics } from '../../utils/system-metrics.js';
5
7
  const ALTERNATE_SCREEN_ENTER = '\x1b[?1049h';
6
8
  const ALTERNATE_SCREEN_EXIT = '\x1b[?1049l';
9
+ const SPARKLINE_WIDTH = 40;
7
10
  export async function startLoadDashboard(config) {
8
11
  process.stdout.write(ALTERNATE_SCREEN_ENTER);
9
12
  readline.emitKeypressEvents(process.stdin);
@@ -12,6 +15,22 @@ export async function startLoadDashboard(config) {
12
15
  process.stdin.resume();
13
16
  }
14
17
  const generator = new LoadGenerator(config);
18
+ const sysMetrics = new SystemMetrics();
19
+ const cpuBuffer = new SparklineBuffer(SPARKLINE_WIDTH);
20
+ const memBuffer = new SparklineBuffer(SPARKLINE_WIDTH);
21
+ let currentCpu = 0;
22
+ let currentMem = { percent: 0, used: 0, total: 0 };
23
+ sysMetrics.onSnapshot((snap) => {
24
+ cpuBuffer.push(snap.cpu);
25
+ memBuffer.push(snap.memory);
26
+ currentCpu = snap.cpu;
27
+ currentMem = {
28
+ percent: snap.memory,
29
+ used: snap.memoryUsed,
30
+ total: snap.memoryTotal
31
+ };
32
+ });
33
+ sysMetrics.startPolling(1000);
15
34
  let abortReject;
16
35
  const abortPromise = new Promise((_, reject) => {
17
36
  abortReject = reject;
@@ -19,6 +38,7 @@ export async function startLoadDashboard(config) {
19
38
  const onKeypress = (_str, key) => {
20
39
  if (key && (key.name === 'escape' || (key.ctrl && key.name === 'c'))) {
21
40
  generator.stop();
41
+ sysMetrics.stopPolling();
22
42
  if (abortReject)
23
43
  abortReject(new Error('User aborted'));
24
44
  }
@@ -43,7 +63,7 @@ export async function startLoadDashboard(config) {
43
63
  latencyHistory.push(snapshot.p95);
44
64
  usersHistory.shift();
45
65
  usersHistory.push(snapshot.activeUsers);
46
- render(config, elapsed, remaining, snapshot, rpsHistory, latencyHistory, usersHistory, generator.stats);
66
+ render(config, elapsed, remaining, snapshot, rpsHistory, latencyHistory, usersHistory, generator.stats, cpuBuffer, memBuffer, currentCpu, currentMem);
47
67
  }, 1000);
48
68
  try {
49
69
  await Promise.race([runPromise, abortPromise]);
@@ -54,6 +74,7 @@ export async function startLoadDashboard(config) {
54
74
  }
55
75
  finally {
56
76
  clearInterval(interval);
77
+ sysMetrics.stopPolling();
57
78
  process.stdin.off('keypress', onKeypress);
58
79
  if (process.stdin.isTTY)
59
80
  process.stdin.setRawMode(false);
@@ -61,55 +82,63 @@ export async function startLoadDashboard(config) {
61
82
  }
62
83
  renderFinalReport(generator.stats, config);
63
84
  }
64
- function render(config, elapsed, remaining, snapshot, rpsHistory, latencyHistory, usersHistory, stats) {
85
+ function render(config, elapsed, remaining, snapshot, rpsHistory, latencyHistory, usersHistory, stats, cpuBuffer, memBuffer, currentCpu, currentMem) {
65
86
  readline.cursorTo(process.stdout, 0, 0);
66
87
  readline.clearScreenDown(process.stdout);
67
- console.log(pc.bold(pc.cyan('šŸ”„ Rek Load Generator')));
68
- console.log(pc.gray(`Target: ${config.url}`));
69
- console.log(pc.gray(`Mode: ${config.mode.toUpperCase()} ${config.http2 ? '(HTTP/2)' : ''}`));
70
- console.log(pc.gray('Press ESC to stop'));
88
+ console.log(colors.bold(colors.cyan('šŸ”„ Rek Load Generator')));
89
+ console.log(colors.gray(`Target: ${config.url}`));
90
+ console.log(colors.gray(`Mode: ${config.mode.toUpperCase()} ${config.http2 ? '(HTTP/2)' : ''}`));
91
+ console.log(colors.gray('Press ESC to stop'));
92
+ console.log('');
93
+ console.log(`${colors.white('Time:')} ${colors.green(elapsed + 's')} ` +
94
+ `${colors.gray('/')} ${config.duration}s ` +
95
+ `${colors.gray('(')}${colors.yellow(remaining + 's left')}${colors.gray(')')} ` +
96
+ `${colors.white('Reqs:')} ${colors.bold(String(stats.totalRequests))}`);
97
+ console.log(`${colors.blue('Users:')} ${colors.bold(String(snapshot.activeUsers))} ` +
98
+ `${colors.green('RPS:')} ${colors.bold(snapshot.rps.toFixed(0))} ` +
99
+ `${colors.magenta('Latency (P95):')} ${colors.bold(snapshot.p95.toFixed(0) + 'ms')} ` +
100
+ `${colors.white('Errors:')} ${stats.failed > 0 ? colors.red(String(stats.failed)) : colors.green('0')}`);
101
+ console.log(colors.gray('──────────────────────────────────────────────────'));
102
+ console.log(colors.bold(colors.blue('šŸ‘„ Active Users')));
103
+ console.log(colors.blue(plot(usersHistory, { height: 4 })));
71
104
  console.log('');
72
- console.log(`${pc.white('Time:')} ${pc.green(elapsed + 's')} ` +
73
- `${pc.gray('/')} ${config.duration}s ` +
74
- `${pc.gray('(')}${pc.yellow(remaining + 's left')}${pc.gray(')')} ` +
75
- `${pc.white('Reqs:')} ${pc.bold(String(stats.totalRequests))}`);
76
- console.log(`${pc.blue('Users:')} ${pc.bold(String(snapshot.activeUsers))} ` +
77
- `${pc.green('RPS:')} ${pc.bold(snapshot.rps.toFixed(0))} ` +
78
- `${pc.magenta('Latency (P95):')} ${pc.bold(snapshot.p95.toFixed(0) + 'ms')} ` +
79
- `${pc.white('Errors:')} ${stats.failed > 0 ? pc.red(String(stats.failed)) : pc.green('0')}`);
80
- console.log(pc.gray('──────────────────────────────────────────────────'));
81
- console.log(pc.bold(pc.blue('šŸ‘„ Active Users')));
82
- console.log(pc.blue(plot(usersHistory, { height: 4 })));
105
+ console.log(colors.bold(colors.green('⚔ Requests per Second')));
106
+ console.log(colors.green(plot(rpsHistory, { height: 6 })));
83
107
  console.log('');
84
- console.log(pc.bold(pc.green('⚔ Requests per Second')));
85
- console.log(pc.green(plot(rpsHistory, { height: 6 })));
108
+ console.log(colors.bold(colors.magenta('ā±ļø Latency P95 (ms)')));
109
+ console.log(colors.magenta(plot(latencyHistory, { height: 4 })));
86
110
  console.log('');
87
- console.log(pc.bold(pc.magenta('ā±ļø Latency P95 (ms)')));
88
- console.log(pc.magenta(plot(latencyHistory, { height: 4 })));
111
+ console.log(colors.bold(colors.yellow('šŸ’» System Resources')));
112
+ const cpuSparkline = cpuBuffer.render({ min: 0, max: 100 });
113
+ const memSparkline = memBuffer.render({ min: 0, max: 100 });
114
+ const memUsed = SystemMetrics.formatBytes(currentMem.used);
115
+ const memTotal = SystemMetrics.formatBytes(currentMem.total);
116
+ console.log(` ${colors.yellow('CPU')} ${colors.gray(cpuSparkline)} ${colors.bold(currentCpu.toFixed(0) + '%')}`);
117
+ console.log(` ${colors.yellow('RAM')} ${colors.gray(memSparkline)} ${colors.bold(currentMem.percent.toFixed(0) + '%')} ${colors.gray(`(${memUsed}/${memTotal})`)}`);
89
118
  const recentErrors = stats.getRecentErrors();
90
119
  if (recentErrors.length > 0) {
91
120
  console.log('');
92
- console.log(pc.bold(pc.red('āš ļø Recent Errors')));
121
+ console.log(colors.bold(colors.red('āš ļø Recent Errors')));
93
122
  renderErrorList(recentErrors, 5);
94
123
  }
95
124
  }
96
125
  function formatErrorEntry(entry) {
97
- const count = pc.gray(`${entry.count}x`);
126
+ const count = colors.white(`${entry.count}x`);
98
127
  if (entry.status === 0) {
99
- return ` ${count} ${pc.red('NET')} ${entry.message}`;
128
+ return ` ${count} ${colors.red('ERR')} ${colors.gray(entry.message)}`;
100
129
  }
101
- const statusBadge = formatStatusBadge(entry.status);
102
- return ` ${count} ${statusBadge} ${entry.message}`;
130
+ const statusColor = entry.status >= 500 ? colors.red : colors.yellow;
131
+ return ` ${count} ${statusColor(String(entry.status))} ${colors.gray(entry.message)}`;
103
132
  }
104
133
  function formatStatusBadge(status) {
105
134
  const code = String(status);
106
135
  if (status >= 500)
107
- return pc.bgRed(pc.white(` ${code} `));
136
+ return colors.bgRed(colors.white(` ${code} `));
108
137
  if (status >= 400)
109
- return pc.bgYellow(pc.black(` ${code} `));
138
+ return colors.bgYellow(colors.black(` ${code} `));
110
139
  if (status >= 300)
111
- return pc.bgCyan(pc.black(` ${code} `));
112
- return pc.bgGreen(pc.black(` ${code} `));
140
+ return colors.bgCyan(colors.black(` ${code} `));
141
+ return colors.bgGreen(colors.black(` ${code} `));
113
142
  }
114
143
  function renderErrorList(errors, maxItems = 10) {
115
144
  const toShow = errors.slice(-maxItems);
@@ -117,53 +146,65 @@ function renderErrorList(errors, maxItems = 10) {
117
146
  console.log(formatErrorEntry(entry));
118
147
  }
119
148
  if (errors.length > maxItems) {
120
- console.log(pc.gray(` ... and ${errors.length - maxItems} more`));
149
+ console.log(colors.gray(` ... and ${errors.length - maxItems} more`));
121
150
  }
122
151
  }
123
152
  function renderFinalReport(stats, config) {
124
153
  const summary = stats.getSummary();
125
- console.log(pc.bold(pc.green('\nāœ… Load Test Complete')));
126
- console.log(pc.bold('Configuration:'));
154
+ console.log(colors.bold(colors.green('\nāœ… Load Test Complete')));
155
+ console.log(colors.bold('Configuration:'));
127
156
  console.log(` URL: ${config.url}`);
128
157
  console.log(` Mode: ${config.mode}`);
129
158
  console.log(` Users: ${config.users}`);
130
159
  console.log(` Duration: ${config.duration}s`);
131
- console.log('\n' + pc.bold('Traffic:'));
160
+ console.log('\n' + colors.bold('Traffic:'));
132
161
  console.log(` Total Requests: ${summary.total}`);
133
- console.log(` Successful: ${pc.green(String(summary.success))}`);
134
- console.log(` Failed: ${summary.failed > 0 ? pc.red(String(summary.failed)) : pc.gray('0')}`);
162
+ console.log(` Successful: ${colors.green(String(summary.success))}`);
163
+ console.log(` Failed: ${summary.failed > 0 ? colors.red(String(summary.failed)) : colors.gray('0')}`);
135
164
  console.log(` Total Bytes: ${(summary.bytes / 1024 / 1024).toFixed(2)} MB`);
136
- console.log('\n' + pc.bold('Latency (ms):'));
165
+ console.log('\n' + colors.bold('Latency (ms):'));
137
166
  console.log(` Avg: ${summary.latency.avg.toFixed(2)}`);
138
167
  console.log(` P50: ${summary.latency.p50.toFixed(0)}`);
139
168
  console.log(` P95: ${summary.latency.p95.toFixed(0)}`);
140
169
  console.log(` P99: ${summary.latency.p99.toFixed(0)}`);
141
170
  console.log(` Max: ${summary.latency.max.toFixed(0)}`);
142
171
  if (Object.keys(summary.codes).length > 0) {
143
- console.log('\n' + pc.bold('Status Codes:'));
144
- Object.entries(summary.codes)
172
+ console.log('\n' + colors.bold('Status Codes:'));
173
+ const codeEntries = Object.entries(summary.codes)
145
174
  .sort(([a], [b]) => Number(a) - Number(b))
146
- .forEach(([code, count]) => {
147
- const badge = formatStatusBadge(Number(code));
148
- console.log(` ${badge} ${count}`);
175
+ .map(([code, count]) => {
176
+ const c = Number(code);
177
+ const color = c >= 500 ? colors.red : c >= 400 ? colors.yellow : colors.green;
178
+ return `${color(code)}: ${count}`;
149
179
  });
180
+ console.log(` ${codeEntries.join(' ')}`);
150
181
  }
151
182
  const allErrors = stats.getErrors();
152
183
  if (allErrors.length > 0) {
153
- console.log('\n' + pc.bold(pc.red('Errors:')));
154
- renderErrorList(allErrors, 15);
155
- const networkErrors = allErrors.filter(e => e.status === 0);
184
+ console.log('\n' + colors.bold(colors.red('Errors:')));
156
185
  const httpErrors = allErrors.filter(e => e.status > 0);
157
- if (networkErrors.length > 0 || httpErrors.length > 0) {
158
- console.log('');
159
- console.log(pc.gray(' Summary:'));
160
- if (networkErrors.length > 0) {
161
- const total = networkErrors.reduce((sum, e) => sum + e.count, 0);
162
- console.log(pc.gray(` Network errors: ${total} (${networkErrors.length} types)`));
186
+ const netErrors = allErrors.filter(e => e.status === 0);
187
+ if (httpErrors.length > 0) {
188
+ const httpLine = httpErrors
189
+ .slice(0, 5)
190
+ .map(e => {
191
+ const color = e.status >= 500 ? colors.red : colors.yellow;
192
+ return `${e.count}x ${color(String(e.status))} ${colors.gray(e.message)}`;
193
+ })
194
+ .join(colors.gray(', '));
195
+ console.log(` ${httpLine}`);
196
+ if (httpErrors.length > 5) {
197
+ console.log(colors.gray(` ... +${httpErrors.length - 5} more HTTP errors`));
163
198
  }
164
- if (httpErrors.length > 0) {
165
- const total = httpErrors.reduce((sum, e) => sum + e.count, 0);
166
- console.log(pc.gray(` HTTP errors: ${total} (${httpErrors.length} types)`));
199
+ }
200
+ if (netErrors.length > 0) {
201
+ const netLine = netErrors
202
+ .slice(0, 5)
203
+ .map(e => `${e.count}x ${colors.red('ERR')} ${colors.gray(e.message)}`)
204
+ .join(colors.gray(', '));
205
+ console.log(` ${netLine}`);
206
+ if (netErrors.length > 5) {
207
+ console.log(colors.gray(` ... +${netErrors.length - 5} more network errors`));
167
208
  }
168
209
  }
169
210
  }
@@ -36,7 +36,7 @@ export declare class ScrollBuffer extends EventEmitter {
36
36
  percent: number;
37
37
  };
38
38
  }
39
- export declare function parseScrollKey(data: Buffer): 'pageUp' | 'pageDown' | 'scrollUp' | 'scrollDown' | 'home' | 'end' | 'escape' | null;
39
+ export declare function parseScrollKey(data: Buffer): 'pageUp' | 'pageDown' | 'scrollUp' | 'scrollDown' | 'home' | 'end' | 'quit' | null;
40
40
  export declare function parseMouseScroll(data: Buffer): 'scrollUp' | 'scrollDown' | null;
41
41
  export declare function enableMouseReporting(): void;
42
42
  export declare function disableMouseReporting(): void;
@@ -1 +1 @@
1
- {"version":3,"file":"scroll-buffer.d.ts","sourceRoot":"","sources":["../../../src/cli/tui/scroll-buffer.ts"],"names":[],"mappings":"AAKA,OAAO,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAE3C,MAAM,WAAW,mBAAmB;IAClC,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,cAAc,CAAC,EAAE,MAAM,CAAC;CACzB;AAED,qBAAa,YAAa,SAAQ,YAAY;IAC5C,OAAO,CAAC,KAAK,CAAgB;IAC7B,OAAO,CAAC,YAAY,CAAa;IACjC,OAAO,CAAC,QAAQ,CAAS;IACzB,OAAO,CAAC,cAAc,CAAS;IAC/B,OAAO,CAAC,YAAY,CAAkB;IACtC,OAAO,CAAC,aAAa,CAA4C;IACjE,OAAO,CAAC,aAAa,CAAc;gBAEvB,OAAO,GAAE,mBAAwB;IAS7C,KAAK,CAAC,OAAO,EAAE,MAAM,GAAG,IAAI;IAuB5B,KAAK,IAAI,IAAI;IAUb,IAAI,SAAS,IAAI,MAAM,CAEtB;IAKD,IAAI,QAAQ,IAAI,MAAM,CAErB;IAKD,IAAI,YAAY,IAAI,OAAO,CAE1B;IAKD,QAAQ,CAAC,KAAK,GAAE,MAAU,GAAG,OAAO;IAcpC,UAAU,CAAC,KAAK,GAAE,MAAU,GAAG,OAAO;IAatC,MAAM,IAAI,OAAO;IAOjB,QAAQ,IAAI,OAAO;IAOnB,WAAW,IAAI,IAAI;IAOnB,cAAc,IAAI,IAAI;IAOtB,eAAe,IAAI,MAAM,EAAE;IAS3B,MAAM,IAAI,MAAM;IAsBhB,cAAc,CAAC,MAAM,CAAC,EAAE,MAAM,GAAG,IAAI;IAOrC,eAAe,IAAI,IAAI;IAcvB,cAAc,IAAI,IAAI;IAWtB,IAAI,YAAY,IAAI,OAAO,CAE1B;IAKD,KAAK,IAAI,IAAI;IASb,aAAa,IAAI;QAAE,OAAO,EAAE,MAAM,CAAC;QAAC,KAAK,EAAE,MAAM,CAAC;QAAC,OAAO,EAAE,MAAM,CAAA;KAAE;CAOrE;AAKD,wBAAgB,cAAc,CAAC,IAAI,EAAE,MAAM,GAAG,QAAQ,GAAG,UAAU,GAAG,UAAU,GAAG,YAAY,GAAG,MAAM,GAAG,KAAK,GAAG,QAAQ,GAAG,IAAI,CAyBjI;AAOD,wBAAgB,gBAAgB,CAAC,IAAI,EAAE,MAAM,GAAG,UAAU,GAAG,YAAY,GAAG,IAAI,CAqB/E;AAKD,wBAAgB,oBAAoB,IAAI,IAAI,CAK3C;AAKD,wBAAgB,qBAAqB,IAAI,IAAI,CAE5C"}
1
+ {"version":3,"file":"scroll-buffer.d.ts","sourceRoot":"","sources":["../../../src/cli/tui/scroll-buffer.ts"],"names":[],"mappings":"AAKA,OAAO,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAE3C,MAAM,WAAW,mBAAmB;IAClC,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,cAAc,CAAC,EAAE,MAAM,CAAC;CACzB;AAED,qBAAa,YAAa,SAAQ,YAAY;IAC5C,OAAO,CAAC,KAAK,CAAgB;IAC7B,OAAO,CAAC,YAAY,CAAa;IACjC,OAAO,CAAC,QAAQ,CAAS;IACzB,OAAO,CAAC,cAAc,CAAS;IAC/B,OAAO,CAAC,YAAY,CAAkB;IACtC,OAAO,CAAC,aAAa,CAA4C;IACjE,OAAO,CAAC,aAAa,CAAc;gBAEvB,OAAO,GAAE,mBAAwB;IAS7C,KAAK,CAAC,OAAO,EAAE,MAAM,GAAG,IAAI;IAuB5B,KAAK,IAAI,IAAI;IAUb,IAAI,SAAS,IAAI,MAAM,CAEtB;IAKD,IAAI,QAAQ,IAAI,MAAM,CAErB;IAKD,IAAI,YAAY,IAAI,OAAO,CAE1B;IAKD,QAAQ,CAAC,KAAK,GAAE,MAAU,GAAG,OAAO;IAcpC,UAAU,CAAC,KAAK,GAAE,MAAU,GAAG,OAAO;IAatC,MAAM,IAAI,OAAO;IAOjB,QAAQ,IAAI,OAAO;IAOnB,WAAW,IAAI,IAAI;IAOnB,cAAc,IAAI,IAAI;IAOtB,eAAe,IAAI,MAAM,EAAE;IAS3B,MAAM,IAAI,MAAM;IAsBhB,cAAc,CAAC,MAAM,CAAC,EAAE,MAAM,GAAG,IAAI;IAOrC,eAAe,IAAI,IAAI;IAcvB,cAAc,IAAI,IAAI;IAWtB,IAAI,YAAY,IAAI,OAAO,CAE1B;IAKD,KAAK,IAAI,IAAI;IASb,aAAa,IAAI;QAAE,OAAO,EAAE,MAAM,CAAC;QAAC,KAAK,EAAE,MAAM,CAAC;QAAC,OAAO,EAAE,MAAM,CAAA;KAAE;CAOrE;AAKD,wBAAgB,cAAc,CAAC,IAAI,EAAE,MAAM,GAAG,QAAQ,GAAG,UAAU,GAAG,UAAU,GAAG,YAAY,GAAG,MAAM,GAAG,KAAK,GAAG,MAAM,GAAG,IAAI,CAyB/H;AAOD,wBAAgB,gBAAgB,CAAC,IAAI,EAAE,MAAM,GAAG,UAAU,GAAG,YAAY,GAAG,IAAI,CAqB/E;AAKD,wBAAgB,oBAAoB,IAAI,IAAI,CAK3C;AAKD,wBAAgB,qBAAqB,IAAI,IAAI,CAE5C"}
@@ -131,8 +131,8 @@ export function parseScrollKey(data) {
131
131
  return 'home';
132
132
  if (str === '\x1b[F' || str === '\x1b[4~' || str === '\x1bOF')
133
133
  return 'end';
134
- if (str === '\x1b' || str === '\x1b\x1b')
135
- return 'escape';
134
+ if (str === 'q' || str === 'Q')
135
+ return 'quit';
136
136
  return null;
137
137
  }
138
138
  export function parseMouseScroll(data) {
@@ -42,8 +42,11 @@ export declare class RekShell {
42
42
  private executeRequest;
43
43
  private runWhois;
44
44
  private runTLS;
45
+ private runSecurityGrader;
46
+ private runIpIntelligence;
45
47
  private runDNS;
46
48
  private runDNSPropagation;
49
+ private runDnsEmailCheck;
47
50
  private runRDAP;
48
51
  private runPing;
49
52
  private runScrap;
@@ -1 +1 @@
1
- {"version":3,"file":"shell.d.ts","sourceRoot":"","sources":["../../../src/cli/tui/shell.ts"],"names":[],"mappings":"AAsCA,qBAAa,QAAQ;IACnB,OAAO,CAAC,EAAE,CAAsB;IAChC,OAAO,CAAC,MAAM,CAAM;IACpB,OAAO,CAAC,OAAO,CAAqB;IACpC,OAAO,CAAC,OAAO,CAAc;IAC7B,OAAO,CAAC,YAAY,CAAa;IACjC,OAAO,CAAC,SAAS,CAA2B;IAC5C,OAAO,CAAC,OAAO,CAA8B;IAC7C,OAAO,CAAC,SAAS,CAAkB;IACnC,OAAO,CAAC,WAAW,CAAS;IAC5B,OAAO,CAAC,UAAU,CAA+B;IACjD,OAAO,CAAC,aAAa,CAAc;IACnC,OAAO,CAAC,YAAY,CAAe;IACnC,OAAO,CAAC,mBAAmB,CAA4C;IACvE,OAAO,CAAC,YAAY,CAAkB;;YAgBxB,iBAAiB;IAe/B,OAAO,CAAC,SAAS;IAMjB,OAAO,CAAC,aAAa;IAUrB,OAAO,CAAC,aAAa;IAiBrB,OAAO,CAAC,SAAS;IAcJ,KAAK;IA8ClB,OAAO,CAAC,kBAAkB;IA2B1B,OAAO,CAAC,oBAAoB;IAW5B,OAAO,CAAC,qBAAqB;IAqE7B,OAAO,CAAC,eAAe;IAmEvB,OAAO,CAAC,eAAe;IAmBvB,OAAO,CAAC,cAAc;IA0BtB,OAAO,CAAC,gBAAgB;IA8BxB,OAAO,CAAC,MAAM;YAKA,aAAa;YAoMb,kBAAkB;YAkBlB,SAAS;YAkBT,WAAW;IA0DzB,OAAO,CAAC,SAAS;IAMjB,OAAO,CAAC,UAAU;IAMlB,OAAO,CAAC,WAAW;IASnB,OAAO,CAAC,QAAQ;IAoCV,WAAW,CAAC,QAAQ,CAAC,EAAE,MAAM;IA6CnC,OAAO,CAAC,gBAAgB;IAqBxB,OAAO,CAAC,UAAU;YAeJ,cAAc;YAyEd,QAAQ;YA8GR,MAAM;YA4FN,MAAM;YA2EN,iBAAiB;YAuBjB,OAAO;YA+DP,OAAO;YA0CP,QAAQ;YAoER,SAAS;YAsCT,aAAa;YA8Bb,aAAa;YA+Bb,aAAa;YA6Bb,cAAc;YAkCd,eAAe;YA+Ef,gBAAgB;YAmEhB,YAAY;YAiEZ,mBAAmB;YAsFnB,QAAQ;YA0FR,YAAY;YAoCZ,YAAY;YA6CZ,WAAW;IA6CzB,OAAO,CAAC,UAAU;IA4GlB,OAAO,CAAC,WAAW;YAgFL,eAAe;YAkBf,cAAc;YAgDd,SAAS;YAgBT,UAAU;YAuBV,UAAU;IAwBxB,OAAO,CAAC,aAAa;IAuCrB,OAAO,CAAC,SAAS;CA8ElB"}
1
+ {"version":3,"file":"shell.d.ts","sourceRoot":"","sources":["../../../src/cli/tui/shell.ts"],"names":[],"mappings":"AAyCA,qBAAa,QAAQ;IACnB,OAAO,CAAC,EAAE,CAAsB;IAChC,OAAO,CAAC,MAAM,CAAM;IACpB,OAAO,CAAC,OAAO,CAAqB;IACpC,OAAO,CAAC,OAAO,CAAc;IAC7B,OAAO,CAAC,YAAY,CAAa;IACjC,OAAO,CAAC,SAAS,CAA2B;IAC5C,OAAO,CAAC,OAAO,CAA8B;IAC7C,OAAO,CAAC,SAAS,CAAkB;IACnC,OAAO,CAAC,WAAW,CAAS;IAC5B,OAAO,CAAC,UAAU,CAA+B;IACjD,OAAO,CAAC,aAAa,CAAc;IACnC,OAAO,CAAC,YAAY,CAAe;IACnC,OAAO,CAAC,mBAAmB,CAA4C;IACvE,OAAO,CAAC,YAAY,CAAkB;;YAgBxB,iBAAiB;IAe/B,OAAO,CAAC,SAAS;IAMjB,OAAO,CAAC,aAAa;IAUrB,OAAO,CAAC,aAAa;IAiBrB,OAAO,CAAC,SAAS;IAcJ,KAAK;IA8ClB,OAAO,CAAC,kBAAkB;IA2B1B,OAAO,CAAC,oBAAoB;IAW5B,OAAO,CAAC,qBAAqB;IAqE7B,OAAO,CAAC,eAAe;IAmEvB,OAAO,CAAC,eAAe;IAmBvB,OAAO,CAAC,cAAc;IA0BtB,OAAO,CAAC,gBAAgB;IA8BxB,OAAO,CAAC,MAAM;YAKA,aAAa;YA6Mb,kBAAkB;YAkBlB,SAAS;YAkBT,WAAW;IA0DzB,OAAO,CAAC,SAAS;IAMjB,OAAO,CAAC,UAAU;IAMlB,OAAO,CAAC,WAAW;IASnB,OAAO,CAAC,QAAQ;IAoCV,WAAW,CAAC,QAAQ,CAAC,EAAE,MAAM;IA6CnC,OAAO,CAAC,gBAAgB;IAqBxB,OAAO,CAAC,UAAU;YAeJ,cAAc;YAyEd,QAAQ;YA8GR,MAAM;YA4FN,iBAAiB;YAqDjB,iBAAiB;YAkDjB,MAAM;YA2EN,iBAAiB;YAuBjB,gBAAgB;YAwEhB,OAAO;YA+DP,OAAO;YA0CP,QAAQ;YAoER,SAAS;YAsCT,aAAa;YA8Bb,aAAa;YA+Bb,aAAa;YA6Bb,cAAc;YAkCd,eAAe;YA+Ef,gBAAgB;YAmEhB,YAAY;YAiEZ,mBAAmB;YAsFnB,QAAQ;YA0FR,YAAY;YAoCZ,YAAY;YA6CZ,WAAW;IA6CzB,OAAO,CAAC,UAAU;IA4GlB,OAAO,CAAC,WAAW;YAgFL,eAAe;YAkBf,cAAc;YAgDd,SAAS;YAgBT,UAAU;YAuBV,UAAU;IAwBxB,OAAO,CAAC,aAAa;IAuCrB,OAAO,CAAC,SAAS;CA8ElB"}
@@ -92,7 +92,7 @@ export class RekShell {
92
92
  const commands = [
93
93
  'get', 'post', 'put', 'delete', 'patch', 'head', 'options',
94
94
  'ws', 'udp', 'load', 'chat', 'ai',
95
- 'whois', 'tls', 'ssl', 'dns', 'dns:propagate', 'rdap', 'ping',
95
+ 'whois', 'tls', 'ssl', 'security', 'ip', 'dns', 'dns:propagate', 'dns:email', 'rdap', 'ping',
96
96
  'scrap', '$', '$text', '$attr', '$html', '$links', '$images', '$scripts', '$css', '$sourcemaps', '$unmap', '$unmap:view', '$unmap:save', '$beautify', '$beautify:save', '$table',
97
97
  '?', 'search', 'suggest', 'example',
98
98
  'help', 'clear', 'exit', 'set', 'url', 'vars', 'env'
@@ -174,7 +174,7 @@ export class RekShell {
174
174
  }
175
175
  const scrollKey = parseScrollKey(data);
176
176
  if (scrollKey) {
177
- if (scrollKey === 'escape') {
177
+ if (scrollKey === 'quit') {
178
178
  if (self.inScrollMode) {
179
179
  self.exitScrollMode();
180
180
  return true;
@@ -235,7 +235,7 @@ export class RekShell {
235
235
  return;
236
236
  }
237
237
  break;
238
- case 'escape':
238
+ case 'quit':
239
239
  if (this.inScrollMode) {
240
240
  this.exitScrollMode();
241
241
  return;
@@ -287,7 +287,7 @@ export class RekShell {
287
287
  const scrollInfo = this.scrollBuffer.isScrolledUp
288
288
  ? colors.yellow(`↑ ${this.scrollBuffer.position} lines | ${info.percent}% | `)
289
289
  : '';
290
- const helpText = colors.gray('Page Up/Down • Home/End • Esc to exit');
290
+ const helpText = colors.gray('Page Up/Down • Home/End • Q to exit');
291
291
  const statusBar = `\x1b[${rows};1H\x1b[7m ${scrollInfo}${helpText} \x1b[0m`;
292
292
  this.originalStdoutWrite(statusBar);
293
293
  }
@@ -341,12 +341,21 @@ export class RekShell {
341
341
  case 'ssl':
342
342
  await this.runTLS(parts[1], parts[2] ? parseInt(parts[2]) : 443);
343
343
  return;
344
+ case 'security':
345
+ await this.runSecurityGrader(parts[1]);
346
+ return;
347
+ case 'ip':
348
+ await this.runIpIntelligence(parts[1]);
349
+ return;
344
350
  case 'dns':
345
351
  await this.runDNS(parts[1]);
346
352
  return;
347
353
  case 'dns:propagate':
348
354
  await this.runDNSPropagation(parts[1], parts[2]);
349
355
  return;
356
+ case 'dns:email':
357
+ await this.runDnsEmailCheck(parts[1], parts[2]);
358
+ return;
350
359
  case 'rdap':
351
360
  await this.runRDAP(parts[1]);
352
361
  return;
@@ -889,6 +898,96 @@ export class RekShell {
889
898
  }
890
899
  console.log('');
891
900
  }
901
+ async runSecurityGrader(url) {
902
+ if (!url) {
903
+ url = this.baseUrl || '';
904
+ if (!url) {
905
+ console.log(colors.yellow('Usage: security <url>'));
906
+ console.log(colors.gray(' Examples: security google.com | security https://example.com'));
907
+ console.log(colors.gray(' Or set a base URL first: url https://example.com'));
908
+ return;
909
+ }
910
+ }
911
+ else if (!url.startsWith('http')) {
912
+ url = `https://${url}`;
913
+ }
914
+ console.log(colors.gray(`Analyzing security headers for ${url}...`));
915
+ try {
916
+ const { analyzeSecurityHeaders } = await import('../../utils/security-grader.js');
917
+ const res = await this.client.get(url);
918
+ const report = analyzeSecurityHeaders(res.headers);
919
+ let gradeColor = colors.red;
920
+ if (report.grade.startsWith('A'))
921
+ gradeColor = colors.green;
922
+ else if (report.grade.startsWith('B'))
923
+ gradeColor = colors.blue;
924
+ else if (report.grade.startsWith('C'))
925
+ gradeColor = colors.yellow;
926
+ console.log(`
927
+ ${colors.bold(colors.cyan('šŸ›”ļø Security Headers Report'))}
928
+ Grade: ${gradeColor(colors.bold(report.grade))} (${report.score}/100)
929
+
930
+ ${colors.bold('Details:')}`);
931
+ report.details.forEach(item => {
932
+ const icon = item.status === 'pass' ? colors.green('āœ”') : item.status === 'warn' ? colors.yellow('⚠') : colors.red('āœ–');
933
+ const headerName = colors.bold(item.header);
934
+ const value = item.value ? colors.gray(`= ${item.value.length > 50 ? item.value.slice(0, 47) + '...' : item.value}`) : colors.gray('(missing)');
935
+ console.log(` ${icon} ${headerName} ${value}`);
936
+ if (item.status !== 'pass') {
937
+ console.log(` ${colors.red('→')} ${item.message}`);
938
+ }
939
+ });
940
+ console.log('');
941
+ this.lastResponse = report;
942
+ }
943
+ catch (error) {
944
+ console.error(colors.red(`Analysis failed: ${error.message}`));
945
+ }
946
+ console.log('');
947
+ }
948
+ async runIpIntelligence(address) {
949
+ if (!address) {
950
+ console.log(colors.yellow('Usage: ip <address>'));
951
+ console.log(colors.gray(' Examples: ip 8.8.8.8 | ip 192.168.1.1'));
952
+ return;
953
+ }
954
+ console.log(colors.gray(`Looking up ${address} using local GeoLite2 database...`));
955
+ try {
956
+ const { getIpInfo, isGeoIPAvailable } = await import('../../mcp/ip-intel.js');
957
+ if (!isGeoIPAvailable()) {
958
+ console.log(colors.gray(`Downloading GeoLite2 database...`));
959
+ }
960
+ const info = await getIpInfo(address);
961
+ if (info.bogon) {
962
+ console.log(colors.yellow(`\n⚠ ${address} is a Bogon/Private IP.`));
963
+ console.log(colors.gray(` Type: ${info.bogonType}`));
964
+ this.lastResponse = info;
965
+ return;
966
+ }
967
+ console.log(`
968
+ ${colors.bold(colors.cyan('šŸŒ IP Intelligence Report'))}
969
+
970
+ ${colors.bold('Location:')}
971
+ ${colors.gray('City:')} ${info.city || 'N/A'}
972
+ ${colors.gray('Region:')} ${info.region || 'N/A'}
973
+ ${colors.gray('Country:')} ${info.country || 'N/A'} ${info.countryCode ? `(${info.countryCode})` : ''}
974
+ ${colors.gray('Continent:')} ${info.continent || 'N/A'}
975
+ ${colors.gray('Timezone:')} ${info.timezone || 'N/A'}
976
+ ${colors.gray('Coords:')} ${info.loc ? colors.cyan(info.loc) : 'N/A'}
977
+ ${colors.gray('Accuracy:')} ${info.accuracy ? `~${info.accuracy} km` : 'N/A'}
978
+
979
+ ${colors.bold('Network:')}
980
+ ${colors.gray('IP:')} ${info.ip}
981
+ ${colors.gray('Type:')} ${info.isIPv6 ? 'IPv6' : 'IPv4'}
982
+ ${colors.gray('Postal:')} ${info.postal || 'N/A'}
983
+ `);
984
+ this.lastResponse = info;
985
+ }
986
+ catch (error) {
987
+ console.error(colors.red(`IP Lookup Failed: ${error.message}`));
988
+ }
989
+ console.log('');
990
+ }
892
991
  async runDNS(domain) {
893
992
  if (!domain) {
894
993
  domain = this.getBaseDomain() || '';
@@ -970,6 +1069,69 @@ export class RekShell {
970
1069
  console.error(colors.red(`Propagation check failed: ${error.message}`));
971
1070
  }
972
1071
  }
1072
+ async runDnsEmailCheck(domain, selector) {
1073
+ if (!domain) {
1074
+ domain = this.getBaseDomain() || '';
1075
+ if (!domain) {
1076
+ console.log(colors.yellow('Usage: dns:email <domain> [dkim-selector]'));
1077
+ console.log(colors.gray(' Examples: dns:email google.com | dns:email github.com google'));
1078
+ console.log(colors.gray(' Or set a base URL first: url https://example.com'));
1079
+ return;
1080
+ }
1081
+ }
1082
+ console.log(colors.gray(`Checking email security for ${domain}...`));
1083
+ const startTime = performance.now();
1084
+ try {
1085
+ const { validateSpf, validateDmarc, checkDkim } = await import('../../utils/dns-toolkit.js');
1086
+ const [spf, dmarc, dkim] = await Promise.all([
1087
+ validateSpf(domain),
1088
+ validateDmarc(domain),
1089
+ checkDkim(domain, selector || 'default')
1090
+ ]);
1091
+ const duration = Math.round(performance.now() - startTime);
1092
+ console.log(colors.green(`āœ” Email security check completed`) + colors.gray(` (${duration}ms)\n`));
1093
+ console.log(colors.bold('SPF:'));
1094
+ if (spf.valid) {
1095
+ console.log(` ${colors.green('āœ”')} ${spf.record || 'No record'}`);
1096
+ }
1097
+ else {
1098
+ console.log(` ${colors.red('āœ–')} ${spf.errors?.join(', ') || 'Invalid'}`);
1099
+ }
1100
+ if (spf.warnings?.length) {
1101
+ spf.warnings.forEach((w) => console.log(` ${colors.yellow('⚠')} ${w}`));
1102
+ }
1103
+ console.log(colors.bold('\nDMARC:'));
1104
+ if (dmarc.valid) {
1105
+ console.log(` ${colors.green('āœ”')} Policy: ${dmarc.policy || 'none'}`);
1106
+ if (dmarc.percentage !== undefined && dmarc.percentage < 100) {
1107
+ console.log(` ${colors.yellow('⚠')} Only ${dmarc.percentage}% of emails affected`);
1108
+ }
1109
+ }
1110
+ else {
1111
+ console.log(` ${colors.red('āœ–')} No DMARC record found`);
1112
+ }
1113
+ if (dmarc.warnings?.length) {
1114
+ dmarc.warnings.forEach((w) => console.log(` ${colors.yellow('⚠')} ${w}`));
1115
+ }
1116
+ console.log(colors.bold(`\nDKIM (${selector || 'default'}):`));
1117
+ if (dkim.found) {
1118
+ console.log(` ${colors.green('āœ”')} Record found`);
1119
+ if (dkim.publicKey) {
1120
+ const keyPreview = dkim.publicKey.substring(0, 40) + '...';
1121
+ console.log(` ${colors.gray('Key:')} ${keyPreview}`);
1122
+ }
1123
+ }
1124
+ else {
1125
+ console.log(` ${colors.yellow('⚠')} No DKIM record for selector "${selector || 'default'}"`);
1126
+ console.log(` ${colors.gray('Try: dns:email ' + domain + ' <selector>')}`);
1127
+ }
1128
+ console.log('');
1129
+ this.lastResponse = { spf, dmarc, dkim };
1130
+ }
1131
+ catch (error) {
1132
+ console.error(colors.red(`Email security check failed: ${error.message}`));
1133
+ }
1134
+ }
973
1135
  async runRDAP(domain) {
974
1136
  if (!domain) {
975
1137
  domain = this.getRootDomain() || '';