recker 1.0.15 → 1.0.17-next.316500c
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +86 -97
- package/dist/ai/providers/anthropic.d.ts.map +1 -1
- package/dist/ai/providers/anthropic.js +4 -1
- package/dist/ai/providers/base.d.ts.map +1 -1
- package/dist/ai/providers/base.js +7 -2
- package/dist/ai/rate-limiter.d.ts.map +1 -1
- package/dist/ai/rate-limiter.js +4 -1
- package/dist/bench/generator.d.ts.map +1 -1
- package/dist/bench/generator.js +8 -3
- package/dist/bench/stats.d.ts +15 -1
- package/dist/bench/stats.d.ts.map +1 -1
- package/dist/bench/stats.js +117 -5
- package/dist/cache/memory-storage.d.ts.map +1 -1
- package/dist/cache/memory-storage.js +3 -2
- package/dist/cli/handler.js +14 -14
- package/dist/cli/index.js +602 -48
- package/dist/cli/presets.js +5 -5
- package/dist/cli/tui/ai-chat.js +10 -10
- package/dist/cli/tui/load-dashboard.d.ts.map +1 -1
- package/dist/cli/tui/load-dashboard.js +127 -32
- package/dist/cli/tui/scroll-buffer.d.ts +43 -0
- package/dist/cli/tui/scroll-buffer.d.ts.map +1 -0
- package/dist/cli/tui/scroll-buffer.js +162 -0
- package/dist/cli/tui/search-panel.d.ts +41 -0
- package/dist/cli/tui/search-panel.d.ts.map +1 -0
- package/dist/cli/tui/search-panel.js +420 -0
- package/dist/cli/tui/shell.d.ts +14 -0
- package/dist/cli/tui/shell.d.ts.map +1 -1
- package/dist/cli/tui/shell.js +424 -46
- package/dist/cli/tui/websocket.js +17 -17
- package/dist/contract/index.d.ts.map +1 -1
- package/dist/contract/index.js +3 -2
- package/dist/core/client.d.ts.map +1 -1
- package/dist/core/client.js +18 -26
- package/dist/core/errors.d.ts +109 -1
- package/dist/core/errors.d.ts.map +1 -1
- package/dist/core/errors.js +214 -1
- package/dist/core/request-promise.d.ts.map +1 -1
- package/dist/core/request-promise.js +5 -6
- package/dist/core/response.d.ts.map +1 -1
- package/dist/core/response.js +5 -6
- package/dist/dns/index.d.ts +1 -0
- package/dist/dns/index.d.ts.map +1 -1
- package/dist/dns/index.js +1 -0
- package/dist/dns/propagation.d.ts +21 -0
- package/dist/dns/propagation.d.ts.map +1 -0
- package/dist/dns/propagation.js +169 -0
- package/dist/index.d.ts +3 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +3 -0
- package/dist/mcp/client.d.ts.map +1 -1
- package/dist/mcp/client.js +10 -11
- package/dist/mcp/embeddings-loader.d.ts +18 -0
- package/dist/mcp/embeddings-loader.d.ts.map +1 -0
- package/dist/mcp/embeddings-loader.js +162 -0
- package/dist/mcp/geoip-loader.d.ts +11 -0
- package/dist/mcp/geoip-loader.d.ts.map +1 -0
- package/dist/mcp/geoip-loader.js +107 -0
- package/dist/mcp/index.d.ts +1 -0
- package/dist/mcp/index.d.ts.map +1 -1
- package/dist/mcp/index.js +1 -0
- package/dist/mcp/ip-intel.d.ts +28 -0
- package/dist/mcp/ip-intel.d.ts.map +1 -0
- package/dist/mcp/ip-intel.js +209 -0
- package/dist/mcp/search/hybrid-search.d.ts.map +1 -1
- package/dist/mcp/search/hybrid-search.js +59 -38
- package/dist/mcp/search/math.d.ts.map +1 -1
- package/dist/mcp/search/math.js +5 -1
- package/dist/mcp/server.d.ts +6 -0
- package/dist/mcp/server.d.ts.map +1 -1
- package/dist/mcp/server.js +122 -2
- package/dist/plugins/compression.js +4 -2
- package/dist/plugins/har-player.d.ts.map +1 -1
- package/dist/plugins/har-player.js +8 -11
- package/dist/plugins/odata.d.ts.map +1 -1
- package/dist/plugins/odata.js +5 -2
- package/dist/presets/aws.d.ts +20 -0
- package/dist/presets/aws.d.ts.map +1 -0
- package/dist/presets/aws.js +68 -0
- package/dist/presets/azure.d.ts +42 -0
- package/dist/presets/azure.d.ts.map +1 -0
- package/dist/presets/azure.js +104 -0
- package/dist/presets/gcp.d.ts +35 -0
- package/dist/presets/gcp.d.ts.map +1 -0
- package/dist/presets/gcp.js +91 -0
- package/dist/presets/index.d.ts +10 -0
- package/dist/presets/index.d.ts.map +1 -1
- package/dist/presets/index.js +10 -0
- package/dist/presets/mailgun.d.ts +8 -0
- package/dist/presets/mailgun.d.ts.map +1 -0
- package/dist/presets/mailgun.js +20 -0
- package/dist/presets/meta.d.ts +11 -0
- package/dist/presets/meta.d.ts.map +1 -0
- package/dist/presets/meta.js +33 -0
- package/dist/presets/oracle.d.ts +20 -0
- package/dist/presets/oracle.d.ts.map +1 -0
- package/dist/presets/oracle.js +117 -0
- package/dist/presets/registry.d.ts.map +1 -1
- package/dist/presets/registry.js +100 -0
- package/dist/presets/sinch.d.ts +10 -0
- package/dist/presets/sinch.d.ts.map +1 -0
- package/dist/presets/sinch.js +39 -0
- package/dist/presets/tiktok.d.ts +11 -0
- package/dist/presets/tiktok.d.ts.map +1 -0
- package/dist/presets/tiktok.js +38 -0
- package/dist/presets/vultr.d.ts +6 -0
- package/dist/presets/vultr.d.ts.map +1 -0
- package/dist/presets/vultr.js +16 -0
- package/dist/presets/youtube.d.ts +6 -0
- package/dist/presets/youtube.d.ts.map +1 -0
- package/dist/presets/youtube.js +20 -0
- package/dist/protocols/ftp.d.ts.map +1 -1
- package/dist/protocols/ftp.js +69 -16
- package/dist/protocols/sftp.d.ts.map +1 -1
- package/dist/protocols/sftp.js +13 -3
- package/dist/protocols/telnet.d.ts.map +1 -1
- package/dist/protocols/telnet.js +25 -6
- package/dist/recker.d.ts +47 -0
- package/dist/recker.d.ts.map +1 -0
- package/dist/recker.js +99 -0
- package/dist/transport/base-udp.d.ts.map +1 -1
- package/dist/transport/base-udp.js +7 -4
- package/dist/transport/udp-response.d.ts.map +1 -1
- package/dist/transport/udp-response.js +10 -3
- package/dist/transport/udp.d.ts.map +1 -1
- package/dist/transport/udp.js +5 -1
- package/dist/transport/undici.d.ts.map +1 -1
- package/dist/transport/undici.js +75 -63
- package/dist/utils/agent-manager.d.ts +1 -0
- package/dist/utils/agent-manager.d.ts.map +1 -1
- package/dist/utils/agent-manager.js +11 -0
- package/dist/utils/client-pool.d.ts.map +1 -1
- package/dist/utils/client-pool.js +4 -1
- package/dist/utils/colors.d.ts +16 -0
- package/dist/utils/colors.d.ts.map +1 -1
- package/dist/utils/colors.js +16 -0
- package/dist/utils/dns-toolkit.d.ts +88 -1
- package/dist/utils/dns-toolkit.d.ts.map +1 -1
- package/dist/utils/dns-toolkit.js +704 -6
- package/dist/utils/doh.d.ts.map +1 -1
- package/dist/utils/doh.js +13 -16
- package/dist/utils/download.d.ts.map +1 -1
- package/dist/utils/download.js +10 -11
- package/dist/utils/rdap.d.ts +9 -0
- package/dist/utils/rdap.d.ts.map +1 -1
- package/dist/utils/rdap.js +78 -9
- package/dist/utils/security-grader.d.ts +47 -0
- package/dist/utils/security-grader.d.ts.map +1 -0
- package/dist/utils/security-grader.js +637 -0
- package/dist/utils/sparkline.d.ts +18 -0
- package/dist/utils/sparkline.d.ts.map +1 -0
- package/dist/utils/sparkline.js +55 -0
- package/dist/utils/sse.d.ts.map +1 -1
- package/dist/utils/sse.js +5 -6
- package/dist/utils/system-metrics.d.ts +26 -0
- package/dist/utils/system-metrics.d.ts.map +1 -0
- package/dist/utils/system-metrics.js +81 -0
- package/dist/utils/tls-inspector.d.ts +6 -0
- package/dist/utils/tls-inspector.d.ts.map +1 -1
- package/dist/utils/tls-inspector.js +35 -1
- package/dist/webrtc/index.d.ts.map +1 -1
- package/dist/webrtc/index.js +21 -7
- package/dist/websocket/client.d.ts.map +1 -1
- package/dist/websocket/client.js +13 -16
- package/package.json +4 -3
- 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
|
|
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(
|
|
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(
|
|
58
|
+
console.log(colors.yellow(`Warning: No .env file found at ${envPath}`));
|
|
59
59
|
}
|
|
60
60
|
else {
|
|
61
|
-
console.log(
|
|
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
|
-
${
|
|
134
|
-
${
|
|
135
|
-
${
|
|
136
|
-
${
|
|
137
|
-
${
|
|
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
|
-
${
|
|
140
|
-
${
|
|
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(
|
|
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(
|
|
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(
|
|
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
|
-
${
|
|
280
|
-
${
|
|
281
|
-
${
|
|
282
|
-
${
|
|
283
|
-
${
|
|
284
|
-
${
|
|
285
|
-
${
|
|
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
|
-
${
|
|
288
|
-
${
|
|
289
|
-
${
|
|
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(
|
|
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
|
-
${
|
|
338
|
-
${
|
|
339
|
-
${
|
|
340
|
-
${
|
|
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
|
-
${
|
|
343
|
-
${
|
|
344
|
-
${
|
|
345
|
-
${
|
|
346
|
-
${
|
|
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
|
-
${
|
|
349
|
-
${
|
|
350
|
-
${
|
|
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
|
-
${
|
|
353
|
-
${
|
|
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(
|
|
937
|
+
console.log(colors.green(`
|
|
384
938
|
┌─────────────────────────────────────────────┐
|
|
385
|
-
│ ${
|
|
939
|
+
│ ${colors.bold('Recker MCP Server')} │
|
|
386
940
|
├─────────────────────────────────────────────┤
|
|
387
|
-
│ Transport: ${
|
|
388
|
-
│ Endpoint: ${
|
|
389
|
-
│ Docs indexed: ${
|
|
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
|
-
│ • ${
|
|
394
|
-
│ • ${
|
|
947
|
+
│ • ${colors.cyan('search_docs')} - Search documentation │
|
|
948
|
+
│ • ${colors.cyan('get_doc')} - Get full doc content │
|
|
395
949
|
│ │
|
|
396
|
-
│ Press ${
|
|
950
|
+
│ Press ${colors.bold('Ctrl+C')} to stop │
|
|
397
951
|
└─────────────────────────────────────────────┘
|
|
398
952
|
`));
|
|
399
953
|
process.on('SIGINT', async () => {
|
|
400
|
-
console.log(
|
|
954
|
+
console.log(colors.yellow('\nShutting down MCP server...'));
|
|
401
955
|
await server.stop();
|
|
402
956
|
process.exit(0);
|
|
403
957
|
});
|