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