recker 1.0.39-next.ae39611 → 1.0.39
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/dist/cli/index.js +361 -1330
- package/dist/version.js +1 -1
- package/package.json +1 -1
package/dist/cli/index.js
CHANGED
|
@@ -128,7 +128,6 @@ async function main() {
|
|
|
128
128
|
.name('rek')
|
|
129
129
|
.description('The HTTP Client for Humans (and Robots)')
|
|
130
130
|
.version(version)
|
|
131
|
-
.showHelpAfterError(true)
|
|
132
131
|
.argument('[args...]', 'URL, Method, Headers (Key:Value), Data (key=value)')
|
|
133
132
|
.option('-v, --verbose', 'Show full request/response details')
|
|
134
133
|
.option('-q, --quiet', 'Output only response body (no colors, perfect for piping)')
|
|
@@ -277,27 +276,7 @@ Error: ${error.message}`));
|
|
|
277
276
|
});
|
|
278
277
|
program
|
|
279
278
|
.command('completion')
|
|
280
|
-
.description('Generate shell
|
|
281
|
-
.addHelpText('after', `
|
|
282
|
-
${colors.bold(colors.blue('What it does:'))}
|
|
283
|
-
Generates a shell completion script for bash/zsh that enables tab-completion
|
|
284
|
-
for rek commands, options, HTTP methods, and API presets (@github, @openai, etc).
|
|
285
|
-
|
|
286
|
-
Once installed, pressing TAB will auto-complete commands and show suggestions,
|
|
287
|
-
making the CLI much faster to use.
|
|
288
|
-
|
|
289
|
-
${colors.bold(colors.yellow('Installation:'))}
|
|
290
|
-
${colors.cyan('# Bash (add to ~/.bashrc)')}
|
|
291
|
-
${colors.green('$ rek completion >> ~/.bashrc')}
|
|
292
|
-
${colors.green('$ source ~/.bashrc')}
|
|
293
|
-
|
|
294
|
-
${colors.cyan('# Zsh (add to ~/.zshrc)')}
|
|
295
|
-
${colors.green('$ rek completion >> ~/.zshrc')}
|
|
296
|
-
${colors.green('$ source ~/.zshrc')}
|
|
297
|
-
|
|
298
|
-
${colors.cyan('# One-time use (current session only)')}
|
|
299
|
-
${colors.green('$ source <(rek completion)')}
|
|
300
|
-
`)
|
|
279
|
+
.description('Generate shell completion script')
|
|
301
280
|
.action(() => {
|
|
302
281
|
const script = `
|
|
303
282
|
###-begin-rek-completion-###
|
|
@@ -345,32 +324,15 @@ complete -F _rek_completions rek
|
|
|
345
324
|
program
|
|
346
325
|
.command('version')
|
|
347
326
|
.alias('info')
|
|
348
|
-
.description('Show version
|
|
349
|
-
.
|
|
350
|
-
.
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
operating system, and architecture. Useful for debugging, reporting issues,
|
|
354
|
-
or verifying which version is running in production environments.
|
|
355
|
-
|
|
356
|
-
${colors.bold(colors.yellow('Examples:'))}
|
|
357
|
-
${colors.green('$ rek version')} Show full version info
|
|
358
|
-
${colors.green('$ rek version short')} Just the version number (for scripts)
|
|
359
|
-
${colors.green('$ rek version format=json')} Machine-readable JSON output
|
|
360
|
-
${colors.green('$ rek info')} Alias for version
|
|
361
|
-
|
|
362
|
-
${colors.bold(colors.yellow('Options:'))}
|
|
363
|
-
${colors.cyan('short')} Show only version number
|
|
364
|
-
${colors.cyan('format=json')} Output as JSON
|
|
365
|
-
`)
|
|
366
|
-
.action(async (args) => {
|
|
367
|
-
const isShort = args.includes('short');
|
|
368
|
-
const formatJson = args.some(a => a === 'format=json' || a === 'json');
|
|
369
|
-
if (isShort) {
|
|
327
|
+
.description('Show detailed version information')
|
|
328
|
+
.option('-s, --short', 'Show only version number')
|
|
329
|
+
.option('--format <type>', 'Output format: text or json', 'text')
|
|
330
|
+
.action(async (options) => {
|
|
331
|
+
if (options.short) {
|
|
370
332
|
console.log(version);
|
|
371
333
|
return;
|
|
372
334
|
}
|
|
373
|
-
if (
|
|
335
|
+
if (options.format === 'json') {
|
|
374
336
|
const { getVersionInfo } = await import('../version.js');
|
|
375
337
|
const info = await getVersionInfo();
|
|
376
338
|
console.log(JSON.stringify(info, null, 2));
|
|
@@ -386,27 +348,6 @@ ${colors.bold(colors.yellow('Options:'))}
|
|
|
386
348
|
.alias('repl')
|
|
387
349
|
.description('Start the interactive Rek Shell')
|
|
388
350
|
.option('-e, --env [path]', 'Load .env file (auto-loads from cwd by default)')
|
|
389
|
-
.addHelpText('after', `
|
|
390
|
-
${colors.bold(colors.blue('What it does:'))}
|
|
391
|
-
Launches an interactive REPL (Read-Eval-Print Loop) for exploring APIs.
|
|
392
|
-
The shell provides auto-completion, command history, and a rich set of
|
|
393
|
-
built-in commands for HTTP requests, DNS lookups, WHOIS queries, and more.
|
|
394
|
-
|
|
395
|
-
Perfect for API exploration, debugging, and quick prototyping without
|
|
396
|
-
writing scripts. Environment variables from .env are loaded automatically.
|
|
397
|
-
|
|
398
|
-
${colors.bold(colors.yellow('Shell Commands:'))}
|
|
399
|
-
get/post/put/delete <url> Make HTTP requests
|
|
400
|
-
whois <domain> WHOIS lookup
|
|
401
|
-
dns <domain> DNS resolution
|
|
402
|
-
tls <domain> TLS certificate inspection
|
|
403
|
-
help Show all available commands
|
|
404
|
-
|
|
405
|
-
${colors.bold(colors.yellow('Examples:'))}
|
|
406
|
-
${colors.green('$ rek shell')} Start interactive shell
|
|
407
|
-
${colors.green('$ rek shell -e .env.local')} Load custom env file
|
|
408
|
-
${colors.green('$ rek repl')} Alias for shell
|
|
409
|
-
`)
|
|
410
351
|
.action(async (options) => {
|
|
411
352
|
if (options.env !== false) {
|
|
412
353
|
try {
|
|
@@ -424,22 +365,7 @@ ${colors.bold(colors.yellow('Examples:'))}
|
|
|
424
365
|
program
|
|
425
366
|
.command('docs [query...]')
|
|
426
367
|
.alias('?')
|
|
427
|
-
.description('Search Recker documentation')
|
|
428
|
-
.addHelpText('after', `
|
|
429
|
-
${colors.bold(colors.blue('What it does:'))}
|
|
430
|
-
Opens a fullscreen interactive panel to search Recker's documentation.
|
|
431
|
-
Uses fuzzy search to find relevant docs about HTTP clients, plugins,
|
|
432
|
-
authentication, caching, and all other features.
|
|
433
|
-
|
|
434
|
-
The search is powered by semantic embeddings for accurate results.
|
|
435
|
-
Navigate with arrow keys, press Enter to view, Esc to close.
|
|
436
|
-
|
|
437
|
-
${colors.bold(colors.yellow('Examples:'))}
|
|
438
|
-
${colors.green('$ rek docs')} Open documentation browser
|
|
439
|
-
${colors.green('$ rek docs retry')} Search for retry-related docs
|
|
440
|
-
${colors.green('$ rek docs "rate limit"')} Search for rate limiting
|
|
441
|
-
${colors.green('$ rek ? oauth')} Quick search with ? alias
|
|
442
|
-
`)
|
|
368
|
+
.description('Search Recker documentation (opens fullscreen panel)')
|
|
443
369
|
.action(async (queryParts) => {
|
|
444
370
|
const query = queryParts.join(' ').trim();
|
|
445
371
|
const { openSearchPanel } = await import('./tui/search-panel.js');
|
|
@@ -448,39 +374,8 @@ ${colors.bold(colors.yellow('Examples:'))}
|
|
|
448
374
|
program
|
|
449
375
|
.command('security')
|
|
450
376
|
.alias('headers')
|
|
451
|
-
.
|
|
452
|
-
.description('Grade a website\'s security headers (A+ to F)')
|
|
377
|
+
.description('Analyze HTTP response headers for security best practices')
|
|
453
378
|
.argument('<url>', 'URL to analyze')
|
|
454
|
-
.addHelpText('after', `
|
|
455
|
-
${colors.bold(colors.blue('What it does:'))}
|
|
456
|
-
Fetches a URL and analyzes its HTTP response headers for security best
|
|
457
|
-
practices. Assigns a grade from A+ to F based on the presence and correct
|
|
458
|
-
configuration of security headers.
|
|
459
|
-
|
|
460
|
-
Checks for HSTS, CSP, X-Frame-Options, X-Content-Type-Options, and other
|
|
461
|
-
important security headers. Great for security audits, DevSecOps pipelines,
|
|
462
|
-
or verifying your site's security configuration.
|
|
463
|
-
|
|
464
|
-
${colors.bold(colors.yellow('Headers Analyzed:'))}
|
|
465
|
-
- Strict-Transport-Security (HSTS)
|
|
466
|
-
- Content-Security-Policy (CSP)
|
|
467
|
-
- X-Frame-Options (clickjacking protection)
|
|
468
|
-
- X-Content-Type-Options (MIME sniffing)
|
|
469
|
-
- Referrer-Policy
|
|
470
|
-
- Permissions-Policy
|
|
471
|
-
- X-XSS-Protection (legacy)
|
|
472
|
-
|
|
473
|
-
${colors.bold(colors.yellow('Grade Scale:'))}
|
|
474
|
-
${colors.green('A+/A/A-')} Excellent - all critical headers present
|
|
475
|
-
${colors.blue('B+/B/B-')} Good - most headers present
|
|
476
|
-
${colors.yellow('C+/C/C-')} Fair - some headers missing
|
|
477
|
-
${colors.red('D/F')} Poor - critical headers missing
|
|
478
|
-
|
|
479
|
-
${colors.bold(colors.yellow('Examples:'))}
|
|
480
|
-
${colors.green('$ rek security github.com')} Grade GitHub's headers
|
|
481
|
-
${colors.green('$ rek headers api.stripe.com')} Using headers alias
|
|
482
|
-
${colors.green('$ rek grade mysite.com')} Using grade alias
|
|
483
|
-
`)
|
|
484
379
|
.action(async (url) => {
|
|
485
380
|
if (!url.startsWith('http'))
|
|
486
381
|
url = `https://${url}`;
|
|
@@ -522,30 +417,16 @@ ${colors.bold('Details:')}`);
|
|
|
522
417
|
});
|
|
523
418
|
program
|
|
524
419
|
.command('seo')
|
|
525
|
-
.
|
|
526
|
-
.description('Analyze a page\'s SEO health (80+ checks)')
|
|
420
|
+
.description('Analyze page SEO (title, meta, headings, links, images, structured data)')
|
|
527
421
|
.argument('<url>', 'URL to analyze')
|
|
528
|
-
.
|
|
422
|
+
.option('-a, --all', 'Show all checks including passed ones')
|
|
423
|
+
.option('--format <format>', 'Output format: text (default) or json', 'text')
|
|
529
424
|
.addHelpText('after', `
|
|
530
|
-
${colors.bold(colors.blue('What it does:'))}
|
|
531
|
-
Performs a comprehensive SEO audit on a single page. Analyzes title, meta
|
|
532
|
-
description, headings hierarchy, images, links, structured data, OpenGraph
|
|
533
|
-
tags, Twitter cards, and technical SEO factors.
|
|
534
|
-
|
|
535
|
-
Returns a score with detailed recommendations. Use format=json for
|
|
536
|
-
integration with CI/CD pipelines or automated monitoring.
|
|
537
|
-
|
|
538
|
-
For full site audits, use ${colors.cyan('rek spider <url> seo')} instead.
|
|
539
|
-
|
|
540
425
|
${colors.bold(colors.yellow('Examples:'))}
|
|
541
426
|
${colors.green('$ rek seo example.com')} ${colors.gray('Basic SEO analysis')}
|
|
542
|
-
${colors.green('$ rek seo example.com
|
|
543
|
-
${colors.green('$ rek seo example.com format
|
|
544
|
-
${colors.green('$ rek seo example.com format
|
|
545
|
-
|
|
546
|
-
${colors.bold(colors.yellow('Options:'))}
|
|
547
|
-
${colors.cyan('all')} Show all checks including passed ones
|
|
548
|
-
${colors.cyan('format=json')} Output as JSON
|
|
427
|
+
${colors.green('$ rek seo example.com -a')} ${colors.gray('Show all checks')}
|
|
428
|
+
${colors.green('$ rek seo example.com --format json')} ${colors.gray('Output as JSON')}
|
|
429
|
+
${colors.green('$ rek seo example.com --format json | jq')} ${colors.gray('Pipe to jq for processing')}
|
|
549
430
|
|
|
550
431
|
${colors.bold(colors.yellow('Checks:'))}
|
|
551
432
|
${colors.cyan('Title Tag')} Length and presence
|
|
@@ -558,11 +439,10 @@ ${colors.bold(colors.yellow('Checks:'))}
|
|
|
558
439
|
${colors.cyan('Structured Data')} JSON-LD presence
|
|
559
440
|
${colors.cyan('Technical')} Canonical, viewport, lang
|
|
560
441
|
`)
|
|
561
|
-
.action(async (url,
|
|
442
|
+
.action(async (url, options) => {
|
|
562
443
|
if (!url.startsWith('http'))
|
|
563
444
|
url = `https://${url}`;
|
|
564
|
-
const
|
|
565
|
-
const isJson = args.some(a => a === 'format=json' || a === 'json');
|
|
445
|
+
const isJson = options.format === 'json';
|
|
566
446
|
const { createClient } = await import('../core/client.js');
|
|
567
447
|
const { analyzeSeo } = await import('../seo/analyzer.js');
|
|
568
448
|
if (!isJson) {
|
|
@@ -639,11 +519,11 @@ ${colors.gray('Grade:')} ${gradeColor(colors.bold(report.grade))} ${colors.gray
|
|
|
639
519
|
console.log(` ${colors.gray('Images:')} ${report.images.total} (${report.images.withAlt} with alt, ${report.images.withoutAlt} without)`);
|
|
640
520
|
console.log('');
|
|
641
521
|
console.log(`${colors.bold('Checks:')}`);
|
|
642
|
-
const checksToShow =
|
|
522
|
+
const checksToShow = options.all
|
|
643
523
|
? report.checks
|
|
644
524
|
: report.checks.filter(c => c.status !== 'pass' && c.status !== 'info');
|
|
645
|
-
if (checksToShow.length === 0 && !
|
|
646
|
-
console.log(colors.green(' All checks passed! Use
|
|
525
|
+
if (checksToShow.length === 0 && !options.all) {
|
|
526
|
+
console.log(colors.green(' All checks passed! Use -a to see details.'));
|
|
647
527
|
}
|
|
648
528
|
else {
|
|
649
529
|
for (const check of checksToShow) {
|
|
@@ -679,15 +559,12 @@ ${colors.gray('Grade:')} ${gradeColor(colors.bold(report.grade))} ${colors.gray
|
|
|
679
559
|
.command('robots')
|
|
680
560
|
.description('Validate and analyze robots.txt file')
|
|
681
561
|
.argument('<url>', 'Website URL or direct robots.txt URL')
|
|
682
|
-
.
|
|
562
|
+
.option('--format <format>', 'Output format: text (default) or json', 'text')
|
|
683
563
|
.addHelpText('after', `
|
|
684
564
|
${colors.bold(colors.yellow('Examples:'))}
|
|
685
565
|
${colors.green('$ rek robots example.com')} ${colors.gray('Validate robots.txt')}
|
|
686
566
|
${colors.green('$ rek robots example.com/robots.txt')} ${colors.gray('Direct URL')}
|
|
687
|
-
${colors.green('$ rek robots example.com format
|
|
688
|
-
|
|
689
|
-
${colors.bold(colors.yellow('Options:'))}
|
|
690
|
-
${colors.cyan('format=json')} Output as JSON
|
|
567
|
+
${colors.green('$ rek robots example.com --format json')} ${colors.gray('JSON output')}
|
|
691
568
|
|
|
692
569
|
${colors.bold(colors.yellow('Checks:'))}
|
|
693
570
|
${colors.cyan('Syntax')} Valid robots.txt syntax
|
|
@@ -696,14 +573,14 @@ ${colors.bold(colors.yellow('Checks:'))}
|
|
|
696
573
|
${colors.cyan('Crawl-delay')} Aggressive crawl delay
|
|
697
574
|
${colors.cyan('AI Bots')} GPTBot, ClaudeBot, Anthropic blocks
|
|
698
575
|
`)
|
|
699
|
-
.action(async (url,
|
|
576
|
+
.action(async (url, options) => {
|
|
700
577
|
if (!url.startsWith('http'))
|
|
701
578
|
url = `https://${url}`;
|
|
702
579
|
if (!url.includes('robots.txt')) {
|
|
703
580
|
const urlObj = new URL(url);
|
|
704
581
|
url = `${urlObj.origin}/robots.txt`;
|
|
705
582
|
}
|
|
706
|
-
const isJson =
|
|
583
|
+
const isJson = options.format === 'json';
|
|
707
584
|
if (!isJson) {
|
|
708
585
|
console.log(colors.gray(`Fetching robots.txt from ${url}...`));
|
|
709
586
|
}
|
|
@@ -793,17 +670,14 @@ ${colors.gray('Valid:')} ${result.valid ? colors.green('Yes') : colors.red('No')
|
|
|
793
670
|
.command('sitemap')
|
|
794
671
|
.description('Validate and analyze sitemap.xml file')
|
|
795
672
|
.argument('<url>', 'Website URL or direct sitemap URL')
|
|
796
|
-
.
|
|
673
|
+
.option('--format <format>', 'Output format: text (default) or json', 'text')
|
|
674
|
+
.option('--discover', 'Discover all sitemaps via robots.txt')
|
|
797
675
|
.addHelpText('after', `
|
|
798
676
|
${colors.bold(colors.yellow('Examples:'))}
|
|
799
677
|
${colors.green('$ rek sitemap example.com')} ${colors.gray('Validate sitemap')}
|
|
800
678
|
${colors.green('$ rek sitemap example.com/sitemap.xml')} ${colors.gray('Direct URL')}
|
|
801
|
-
${colors.green('$ rek sitemap example.com discover')}
|
|
802
|
-
${colors.green('$ rek sitemap example.com format
|
|
803
|
-
|
|
804
|
-
${colors.bold(colors.yellow('Options:'))}
|
|
805
|
-
${colors.cyan('discover')} Discover all sitemaps via robots.txt
|
|
806
|
-
${colors.cyan('format=json')} Output as JSON
|
|
679
|
+
${colors.green('$ rek sitemap example.com --discover')} ${colors.gray('Find all sitemaps')}
|
|
680
|
+
${colors.green('$ rek sitemap example.com --format json')} ${colors.gray('JSON output')}
|
|
807
681
|
|
|
808
682
|
${colors.bold(colors.yellow('Checks:'))}
|
|
809
683
|
${colors.cyan('Structure')} Valid XML sitemap format
|
|
@@ -812,13 +686,12 @@ ${colors.bold(colors.yellow('Checks:'))}
|
|
|
812
686
|
${colors.cyan('URLs')} Valid, no duplicates, same domain
|
|
813
687
|
${colors.cyan('Lastmod')} Valid dates, not in future
|
|
814
688
|
`)
|
|
815
|
-
.action(async (url,
|
|
689
|
+
.action(async (url, options) => {
|
|
816
690
|
if (!url.startsWith('http'))
|
|
817
691
|
url = `https://${url}`;
|
|
818
|
-
const isJson =
|
|
819
|
-
const doDiscover = args.includes('discover');
|
|
692
|
+
const isJson = options.format === 'json';
|
|
820
693
|
try {
|
|
821
|
-
if (
|
|
694
|
+
if (options.discover) {
|
|
822
695
|
const { discoverSitemaps } = await import('../seo/validators/sitemap.js');
|
|
823
696
|
if (!isJson) {
|
|
824
697
|
console.log(colors.gray(`Discovering sitemaps for ${new URL(url).origin}...`));
|
|
@@ -928,17 +801,14 @@ ${colors.gray('Type:')} ${result.parseResult?.type === 'sitemapindex' ? 'Sitemap
|
|
|
928
801
|
.command('llms')
|
|
929
802
|
.description('Validate and analyze llms.txt file (AI/LLM optimization)')
|
|
930
803
|
.argument('[url]', 'Website URL or direct llms.txt URL')
|
|
931
|
-
.
|
|
804
|
+
.option('--format <format>', 'Output format: text (default) or json', 'text')
|
|
805
|
+
.option('--template', 'Generate a template llms.txt file')
|
|
932
806
|
.addHelpText('after', `
|
|
933
807
|
${colors.bold(colors.yellow('Examples:'))}
|
|
934
808
|
${colors.green('$ rek llms example.com')} ${colors.gray('Validate llms.txt')}
|
|
935
809
|
${colors.green('$ rek llms example.com/llms.txt')} ${colors.gray('Direct URL')}
|
|
936
|
-
${colors.green('$ rek llms example.com format
|
|
937
|
-
${colors.green('$ rek llms template > llms.txt')}
|
|
938
|
-
|
|
939
|
-
${colors.bold(colors.yellow('Options:'))}
|
|
940
|
-
${colors.cyan('template')} Generate a template llms.txt file
|
|
941
|
-
${colors.cyan('format=json')} Output as JSON
|
|
810
|
+
${colors.green('$ rek llms example.com --format json')} ${colors.gray('JSON output')}
|
|
811
|
+
${colors.green('$ rek llms --template > llms.txt')} ${colors.gray('Generate template')}
|
|
942
812
|
|
|
943
813
|
${colors.bold(colors.yellow('About llms.txt:'))}
|
|
944
814
|
A proposed standard for providing LLM-friendly content.
|
|
@@ -951,10 +821,9 @@ ${colors.bold(colors.yellow('Checks:'))}
|
|
|
951
821
|
${colors.cyan('Description')} Site description block
|
|
952
822
|
${colors.cyan('Sections')} Content sections with links
|
|
953
823
|
`)
|
|
954
|
-
.action(async (url,
|
|
955
|
-
const isJson =
|
|
956
|
-
|
|
957
|
-
if (isTemplate) {
|
|
824
|
+
.action(async (url, options) => {
|
|
825
|
+
const isJson = options.format === 'json';
|
|
826
|
+
if (options.template) {
|
|
958
827
|
const { generateLlmsTxtTemplate } = await import('../seo/validators/llms-txt.js');
|
|
959
828
|
const template = generateLlmsTxtTemplate({
|
|
960
829
|
siteName: 'Your Site Name',
|
|
@@ -1076,20 +945,10 @@ ${colors.gray('Valid:')} ${result.valid ? colors.green('Yes') : colors.red('No')
|
|
|
1076
945
|
});
|
|
1077
946
|
program
|
|
1078
947
|
.command('spider')
|
|
1079
|
-
.
|
|
1080
|
-
.description('Crawl a website and analyze all pages')
|
|
948
|
+
.description('Crawl a website following internal links')
|
|
1081
949
|
.argument('<url>', 'Starting URL to crawl')
|
|
1082
950
|
.argument('[args...]', 'Options: depth=N limit=N concurrency=N seo focus=MODE output=file.json')
|
|
1083
951
|
.addHelpText('after', `
|
|
1084
|
-
${colors.bold(colors.blue('What it does:'))}
|
|
1085
|
-
Crawls a website starting from the given URL, following internal links up to
|
|
1086
|
-
a specified depth. Discovers all pages, collects metadata, and optionally
|
|
1087
|
-
performs comprehensive SEO analysis.
|
|
1088
|
-
|
|
1089
|
-
The crawler respects robots.txt, handles JavaScript-rendered content, and
|
|
1090
|
-
provides detailed reports on site structure, broken links, and SEO issues.
|
|
1091
|
-
Perfect for site audits, migration planning, or competitive analysis.
|
|
1092
|
-
|
|
1093
952
|
${colors.bold(colors.yellow('Examples:'))}
|
|
1094
953
|
${colors.green('$ rek spider example.com')} ${colors.gray('Crawl with defaults')}
|
|
1095
954
|
${colors.green('$ rek spider example.com depth=3 limit=50')} ${colors.gray('Depth 3, max 50 pages')}
|
|
@@ -1098,7 +957,6 @@ ${colors.bold(colors.yellow('Examples:'))}
|
|
|
1098
957
|
${colors.green('$ rek spider example.com seo focus=links')} ${colors.gray('Focus on link issues')}
|
|
1099
958
|
${colors.green('$ rek spider example.com seo focus=security')} ${colors.gray('Focus on security issues')}
|
|
1100
959
|
${colors.green('$ rek spider example.com seo focus=duplicates')} ${colors.gray('Focus on duplicate content')}
|
|
1101
|
-
${colors.green('$ rek spider example.com seo format=json')} ${colors.gray('JSON output to stdout')}
|
|
1102
960
|
|
|
1103
961
|
${colors.bold(colors.yellow('Options:'))}
|
|
1104
962
|
${colors.cyan('depth=N')} Max link depth to follow (default: 5)
|
|
@@ -1106,7 +964,6 @@ ${colors.bold(colors.yellow('Options:'))}
|
|
|
1106
964
|
${colors.cyan('concurrency=N')} Parallel requests (default: 5)
|
|
1107
965
|
${colors.cyan('seo')} Enable SEO analysis mode
|
|
1108
966
|
${colors.cyan('focus=MODE')} Focus analysis on specific area (requires seo)
|
|
1109
|
-
${colors.cyan('format=json')} Output JSON to stdout (for piping)
|
|
1110
967
|
${colors.cyan('output=file.json')} Save JSON report to file
|
|
1111
968
|
|
|
1112
969
|
${colors.bold(colors.yellow('Focus Modes:'))}
|
|
@@ -1123,7 +980,6 @@ ${colors.bold(colors.yellow('Focus Modes:'))}
|
|
|
1123
980
|
let concurrency = 5;
|
|
1124
981
|
let seoEnabled = false;
|
|
1125
982
|
let outputFile = '';
|
|
1126
|
-
let formatJson = false;
|
|
1127
983
|
let focusMode = 'all';
|
|
1128
984
|
const focusCategories = {
|
|
1129
985
|
links: ['links'],
|
|
@@ -1149,9 +1005,6 @@ ${colors.bold(colors.yellow('Focus Modes:'))}
|
|
|
1149
1005
|
else if (arg.startsWith('output=')) {
|
|
1150
1006
|
outputFile = arg.split('=')[1] || '';
|
|
1151
1007
|
}
|
|
1152
|
-
else if (arg === 'format=json' || arg === '--format=json') {
|
|
1153
|
-
formatJson = true;
|
|
1154
|
-
}
|
|
1155
1008
|
else if (arg.startsWith('focus=')) {
|
|
1156
1009
|
const mode = arg.split('=')[1] || 'all';
|
|
1157
1010
|
if (mode in focusCategories) {
|
|
@@ -1166,16 +1019,14 @@ ${colors.bold(colors.yellow('Focus Modes:'))}
|
|
|
1166
1019
|
}
|
|
1167
1020
|
if (!url.startsWith('http'))
|
|
1168
1021
|
url = `https://${url}`;
|
|
1169
|
-
|
|
1170
|
-
|
|
1171
|
-
|
|
1172
|
-
|
|
1173
|
-
|
|
1174
|
-
|
|
1175
|
-
console.log(colors.gray(` Output: ${outputFile}`));
|
|
1176
|
-
}
|
|
1177
|
-
console.log('');
|
|
1022
|
+
const modeLabel = seoEnabled ? colors.magenta(' + SEO') : '';
|
|
1023
|
+
const focusLabel = focusMode !== 'all' ? colors.cyan(` [focus: ${focusMode}]`) : '';
|
|
1024
|
+
console.log(colors.cyan(`\nSpider starting: ${url}`));
|
|
1025
|
+
console.log(colors.gray(` Depth: ${maxDepth} | Limit: ${maxPages} | Concurrency: ${concurrency}${modeLabel}${focusLabel}`));
|
|
1026
|
+
if (outputFile) {
|
|
1027
|
+
console.log(colors.gray(` Output: ${outputFile}`));
|
|
1178
1028
|
}
|
|
1029
|
+
console.log('');
|
|
1179
1030
|
try {
|
|
1180
1031
|
if (seoEnabled) {
|
|
1181
1032
|
const { SeoSpider } = await import('../seo/index.js');
|
|
@@ -1189,91 +1040,11 @@ ${colors.bold(colors.yellow('Focus Modes:'))}
|
|
|
1189
1040
|
output: outputFile || undefined,
|
|
1190
1041
|
focusCategories: focusCategories[focusMode],
|
|
1191
1042
|
focusMode,
|
|
1192
|
-
onProgress:
|
|
1043
|
+
onProgress: (progress) => {
|
|
1193
1044
|
process.stdout.write(`\r${colors.gray(' Crawling:')} ${colors.cyan(progress.crawled.toString())} pages | ${colors.gray('Queue:')} ${progress.queued} | ${colors.gray('Depth:')} ${progress.depth} `);
|
|
1194
1045
|
},
|
|
1195
1046
|
});
|
|
1196
1047
|
const result = await seoSpider.crawl(url);
|
|
1197
|
-
if (formatJson) {
|
|
1198
|
-
const responseTimes = result.pages.filter(p => p.duration > 0).map(p => p.duration);
|
|
1199
|
-
const avgResponseTime = responseTimes.length > 0
|
|
1200
|
-
? Math.round(responseTimes.reduce((a, b) => a + b, 0) / responseTimes.length)
|
|
1201
|
-
: 0;
|
|
1202
|
-
const statusCounts = {};
|
|
1203
|
-
for (const page of result.pages) {
|
|
1204
|
-
const key = page.status?.toString() || 'error';
|
|
1205
|
-
statusCounts[key] = (statusCounts[key] || 0) + 1;
|
|
1206
|
-
}
|
|
1207
|
-
let totalInternalLinks = 0;
|
|
1208
|
-
let totalExternalLinks = 0;
|
|
1209
|
-
let totalImages = 0;
|
|
1210
|
-
let imagesWithoutAlt = 0;
|
|
1211
|
-
for (const page of result.pages) {
|
|
1212
|
-
if (page.seoReport) {
|
|
1213
|
-
totalInternalLinks += page.seoReport.links?.internal || 0;
|
|
1214
|
-
totalExternalLinks += page.seoReport.links?.external || 0;
|
|
1215
|
-
totalImages += page.seoReport.images?.total || 0;
|
|
1216
|
-
imagesWithoutAlt += page.seoReport.images?.withoutAlt || 0;
|
|
1217
|
-
}
|
|
1218
|
-
}
|
|
1219
|
-
const jsonOutput = {
|
|
1220
|
-
startUrl: url,
|
|
1221
|
-
crawledAt: new Date().toISOString(),
|
|
1222
|
-
duration: result.duration,
|
|
1223
|
-
config: {
|
|
1224
|
-
maxDepth,
|
|
1225
|
-
maxPages,
|
|
1226
|
-
concurrency,
|
|
1227
|
-
focusMode,
|
|
1228
|
-
},
|
|
1229
|
-
summary: {
|
|
1230
|
-
totalPages: result.pages.length,
|
|
1231
|
-
uniqueUrls: result.visited.size,
|
|
1232
|
-
avgSeoScore: result.summary.avgScore,
|
|
1233
|
-
avgResponseTime,
|
|
1234
|
-
pagesWithErrors: result.summary.pagesWithErrors,
|
|
1235
|
-
pagesWithWarnings: result.summary.pagesWithWarnings,
|
|
1236
|
-
duplicateTitles: result.summary.duplicateTitles,
|
|
1237
|
-
duplicateDescriptions: result.summary.duplicateDescriptions,
|
|
1238
|
-
duplicateH1s: result.summary.duplicateH1s,
|
|
1239
|
-
orphanPages: result.summary.orphanPages,
|
|
1240
|
-
},
|
|
1241
|
-
content: {
|
|
1242
|
-
totalInternalLinks,
|
|
1243
|
-
totalExternalLinks,
|
|
1244
|
-
totalImages,
|
|
1245
|
-
imagesWithoutAlt,
|
|
1246
|
-
},
|
|
1247
|
-
httpStatus: statusCounts,
|
|
1248
|
-
siteWideIssues: result.siteWideIssues.map(issue => ({
|
|
1249
|
-
type: issue.type,
|
|
1250
|
-
severity: issue.severity,
|
|
1251
|
-
message: issue.message,
|
|
1252
|
-
value: issue.value,
|
|
1253
|
-
affectedUrls: issue.affectedUrls,
|
|
1254
|
-
})),
|
|
1255
|
-
pages: result.pages.map(page => ({
|
|
1256
|
-
url: page.url,
|
|
1257
|
-
status: page.status,
|
|
1258
|
-
depth: page.depth,
|
|
1259
|
-
duration: page.duration,
|
|
1260
|
-
title: page.title,
|
|
1261
|
-
error: page.error,
|
|
1262
|
-
seo: page.seoReport ? {
|
|
1263
|
-
score: page.seoReport.score,
|
|
1264
|
-
grade: page.seoReport.grade,
|
|
1265
|
-
title: page.seoReport.title,
|
|
1266
|
-
metaDescription: page.seoReport.metaDescription,
|
|
1267
|
-
headings: page.seoReport.headings,
|
|
1268
|
-
links: page.seoReport.links,
|
|
1269
|
-
images: page.seoReport.images,
|
|
1270
|
-
checks: page.seoReport.checks,
|
|
1271
|
-
} : null,
|
|
1272
|
-
})),
|
|
1273
|
-
};
|
|
1274
|
-
console.log(JSON.stringify(jsonOutput, null, 2));
|
|
1275
|
-
return;
|
|
1276
|
-
}
|
|
1277
1048
|
process.stdout.write('\r' + ' '.repeat(80) + '\r');
|
|
1278
1049
|
console.log(colors.green(`\n✔ SEO Spider complete`) + colors.gray(` (${(result.duration / 1000).toFixed(1)}s)`));
|
|
1279
1050
|
console.log(` ${colors.cyan('Pages crawled')}: ${result.pages.length}`);
|
|
@@ -1453,41 +1224,11 @@ ${colors.bold(colors.yellow('Focus Modes:'))}
|
|
|
1453
1224
|
concurrency,
|
|
1454
1225
|
sameDomain: true,
|
|
1455
1226
|
delay: 100,
|
|
1456
|
-
onProgress:
|
|
1227
|
+
onProgress: (progress) => {
|
|
1457
1228
|
process.stdout.write(`\r${colors.gray(' Crawling:')} ${colors.cyan(progress.crawled.toString())} pages | ${colors.gray('Queue:')} ${progress.queued} | ${colors.gray('Depth:')} ${progress.depth} `);
|
|
1458
1229
|
},
|
|
1459
1230
|
});
|
|
1460
1231
|
const result = await spider.crawl(url);
|
|
1461
|
-
if (formatJson) {
|
|
1462
|
-
const jsonOutput = {
|
|
1463
|
-
startUrl: result.startUrl,
|
|
1464
|
-
crawledAt: new Date().toISOString(),
|
|
1465
|
-
duration: result.duration,
|
|
1466
|
-
config: {
|
|
1467
|
-
maxDepth,
|
|
1468
|
-
maxPages,
|
|
1469
|
-
concurrency,
|
|
1470
|
-
},
|
|
1471
|
-
summary: {
|
|
1472
|
-
totalPages: result.pages.length,
|
|
1473
|
-
successCount: result.pages.filter(p => !p.error).length,
|
|
1474
|
-
errorCount: result.errors.length,
|
|
1475
|
-
uniqueUrls: result.visited.size,
|
|
1476
|
-
},
|
|
1477
|
-
pages: result.pages.map(p => ({
|
|
1478
|
-
url: p.url,
|
|
1479
|
-
status: p.status,
|
|
1480
|
-
title: p.title,
|
|
1481
|
-
depth: p.depth,
|
|
1482
|
-
linksCount: p.links.length,
|
|
1483
|
-
duration: p.duration,
|
|
1484
|
-
error: p.error,
|
|
1485
|
-
})),
|
|
1486
|
-
errors: result.errors,
|
|
1487
|
-
};
|
|
1488
|
-
console.log(JSON.stringify(jsonOutput, null, 2));
|
|
1489
|
-
return;
|
|
1490
|
-
}
|
|
1491
1232
|
process.stdout.write('\r' + ' '.repeat(80) + '\r');
|
|
1492
1233
|
console.log(colors.green(`\n✔ Spider complete`) + colors.gray(` (${(result.duration / 1000).toFixed(1)}s)`));
|
|
1493
1234
|
console.log(` ${colors.cyan('Pages crawled')}: ${result.pages.length}`);
|
|
@@ -1552,19 +1293,10 @@ ${colors.bold(colors.yellow('Focus Modes:'))}
|
|
|
1552
1293
|
});
|
|
1553
1294
|
program
|
|
1554
1295
|
.command('scrape')
|
|
1555
|
-
.
|
|
1556
|
-
.description('Extract data from web pages with CSS selectors')
|
|
1296
|
+
.description('Scrape data from a web page using CSS selectors')
|
|
1557
1297
|
.argument('<url>', 'URL to scrape')
|
|
1558
1298
|
.argument('[args...]', 'Options: select=SELECTOR, attr=NAME, links, images, meta, tables, scripts, jsonld')
|
|
1559
1299
|
.addHelpText('after', `
|
|
1560
|
-
${colors.bold(colors.blue('What it does:'))}
|
|
1561
|
-
Fetches a web page and extracts data using CSS selectors. Can extract
|
|
1562
|
-
text content, specific attributes, links, images, meta tags, tables,
|
|
1563
|
-
scripts, and JSON-LD structured data.
|
|
1564
|
-
|
|
1565
|
-
Perfect for quick data extraction, competitive research, price monitoring,
|
|
1566
|
-
or building datasets. Outputs clean, structured data ready for processing.
|
|
1567
|
-
|
|
1568
1300
|
${colors.bold(colors.yellow('Examples:'))}
|
|
1569
1301
|
${colors.green('$ rek scrape example.com')} ${colors.gray('# Basic page info')}
|
|
1570
1302
|
${colors.green('$ rek scrape example.com select="h1"')} ${colors.gray('# Extract h1 text')}
|
|
@@ -1762,32 +1494,20 @@ ${colors.bold('Images:')} ${imageCount}
|
|
|
1762
1494
|
});
|
|
1763
1495
|
program
|
|
1764
1496
|
.command('ai')
|
|
1765
|
-
.
|
|
1766
|
-
.alias('ask')
|
|
1767
|
-
.description('Chat with AI models (OpenAI, Claude, Groq, etc)')
|
|
1497
|
+
.description('Send a single AI prompt (no memory/context)')
|
|
1768
1498
|
.argument('<preset>', 'AI preset to use (e.g., @openai, @anthropic, @groq)')
|
|
1769
|
-
.argument('<prompt...>', 'The prompt
|
|
1499
|
+
.argument('<prompt...>', 'The prompt to send')
|
|
1500
|
+
.option('-m, --model <model>', 'Override default model')
|
|
1501
|
+
.option('-t, --temperature <temp>', 'Temperature (0-1)', '0.7')
|
|
1502
|
+
.option('--max-tokens <tokens>', 'Max tokens in response', '2048')
|
|
1503
|
+
.option('-w, --wait', 'Wait for full response (disable streaming)')
|
|
1504
|
+
.option('-j, --json', 'Output raw JSON response')
|
|
1505
|
+
.option('-e, --env [path]', 'Load .env file (auto-loads from cwd if exists)')
|
|
1770
1506
|
.addHelpText('after', `
|
|
1771
|
-
${colors.bold(colors.blue('What it does:'))}
|
|
1772
|
-
Sends a prompt to an AI language model and streams the response back.
|
|
1773
|
-
Supports all major AI providers including OpenAI, Anthropic, Google, Groq,
|
|
1774
|
-
Mistral, and more. API keys are loaded from environment variables.
|
|
1775
|
-
|
|
1776
|
-
Each provider uses sensible defaults but you can override the model,
|
|
1777
|
-
temperature, and max tokens. Responses stream in real-time by default.
|
|
1778
|
-
|
|
1779
|
-
${colors.bold(colors.yellow('Options:'))}
|
|
1780
|
-
${colors.cyan('model=<model>')} Override default model
|
|
1781
|
-
${colors.cyan('temperature=<temp>')} Temperature (0-1, default: 0.7)
|
|
1782
|
-
${colors.cyan('max-tokens=<tokens>')} Max tokens in response (default: 2048)
|
|
1783
|
-
${colors.cyan('wait')} Wait for full response (disable streaming)
|
|
1784
|
-
${colors.cyan('json')} Output raw JSON response
|
|
1785
|
-
${colors.cyan('env=<path>')} Load .env file (auto-loads from cwd if exists)
|
|
1786
|
-
|
|
1787
1507
|
${colors.bold(colors.yellow('Examples:'))}
|
|
1788
1508
|
${colors.green('$ rek ai @openai "What is the capital of France?"')}
|
|
1789
|
-
${colors.green('$ rek ai @anthropic "Explain quantum computing"
|
|
1790
|
-
${colors.green('$ rek ai @groq "Write a haiku" wait')}
|
|
1509
|
+
${colors.green('$ rek ai @anthropic "Explain quantum computing" -m claude-sonnet-4-20250514')}
|
|
1510
|
+
${colors.green('$ rek ai @groq "Write a haiku" --wait')}
|
|
1791
1511
|
${colors.green('$ rek ai @openai "Translate to Spanish: Hello world"')}
|
|
1792
1512
|
|
|
1793
1513
|
${colors.bold(colors.yellow('Available AI Presets:'))}
|
|
@@ -1809,39 +1529,14 @@ ${colors.bold(colors.yellow('Note:'))}
|
|
|
1809
1529
|
This command sends a single prompt without conversation memory.
|
|
1810
1530
|
For chat with memory, use: ${colors.cyan('rek shell')} then ${colors.cyan('@openai Your message')}
|
|
1811
1531
|
`)
|
|
1812
|
-
.action(async (preset, promptParts) => {
|
|
1813
|
-
|
|
1814
|
-
|
|
1815
|
-
let maxTokens = '2048';
|
|
1816
|
-
let wait = false;
|
|
1817
|
-
let jsonOutput = false;
|
|
1818
|
-
let envPath;
|
|
1819
|
-
const actualPromptParts = [];
|
|
1820
|
-
for (const part of promptParts) {
|
|
1821
|
-
if (part.startsWith('model='))
|
|
1822
|
-
model = part.split('=')[1];
|
|
1823
|
-
else if (part.startsWith('temperature='))
|
|
1824
|
-
temperature = part.split('=')[1];
|
|
1825
|
-
else if (part.startsWith('max-tokens='))
|
|
1826
|
-
maxTokens = part.split('=')[1];
|
|
1827
|
-
else if (part === 'wait')
|
|
1828
|
-
wait = true;
|
|
1829
|
-
else if (part === 'json')
|
|
1830
|
-
jsonOutput = true;
|
|
1831
|
-
else if (part.startsWith('env='))
|
|
1832
|
-
envPath = part.split('=')[1];
|
|
1833
|
-
else if (part === 'env')
|
|
1834
|
-
envPath = true;
|
|
1835
|
-
else
|
|
1836
|
-
actualPromptParts.push(part);
|
|
1837
|
-
}
|
|
1838
|
-
if (envPath !== undefined) {
|
|
1839
|
-
await loadEnvFile(envPath);
|
|
1532
|
+
.action(async (preset, promptParts, options) => {
|
|
1533
|
+
if (options.env !== undefined) {
|
|
1534
|
+
await loadEnvFile(options.env);
|
|
1840
1535
|
}
|
|
1841
1536
|
else {
|
|
1842
1537
|
try {
|
|
1843
|
-
const
|
|
1844
|
-
await fs.access(
|
|
1538
|
+
const envPath = join(process.cwd(), '.env');
|
|
1539
|
+
await fs.access(envPath);
|
|
1845
1540
|
await loadEnvFile(true);
|
|
1846
1541
|
}
|
|
1847
1542
|
catch {
|
|
@@ -1862,7 +1557,7 @@ ${colors.bold(colors.yellow('Note:'))}
|
|
|
1862
1557
|
console.log(colors.gray('Use an AI preset like @openai, @anthropic, @groq, etc.'));
|
|
1863
1558
|
process.exit(1);
|
|
1864
1559
|
}
|
|
1865
|
-
const prompt =
|
|
1560
|
+
const prompt = promptParts.join(' ');
|
|
1866
1561
|
if (!prompt.trim()) {
|
|
1867
1562
|
console.error(colors.red('Error: Prompt is required'));
|
|
1868
1563
|
process.exit(1);
|
|
@@ -1870,16 +1565,16 @@ ${colors.bold(colors.yellow('Note:'))}
|
|
|
1870
1565
|
try {
|
|
1871
1566
|
const { createClient } = await import('../core/client.js');
|
|
1872
1567
|
const client = createClient(presetConfig);
|
|
1873
|
-
if (model) {
|
|
1568
|
+
if (options.model) {
|
|
1874
1569
|
client.ai.setMemoryConfig({ systemPrompt: undefined });
|
|
1875
|
-
client._aiConfig.model = model;
|
|
1570
|
+
client._aiConfig.model = options.model;
|
|
1876
1571
|
}
|
|
1877
|
-
if (!
|
|
1572
|
+
if (!options.json) {
|
|
1878
1573
|
console.log(colors.gray(`Using @${presetName} (${client._aiConfig.model})...\n`));
|
|
1879
1574
|
}
|
|
1880
|
-
if (wait ||
|
|
1575
|
+
if (options.wait || options.json) {
|
|
1881
1576
|
const response = await client.ai.prompt(prompt);
|
|
1882
|
-
if (
|
|
1577
|
+
if (options.json) {
|
|
1883
1578
|
console.log(JSON.stringify({
|
|
1884
1579
|
content: response.content,
|
|
1885
1580
|
model: response.model,
|
|
@@ -1915,36 +1610,8 @@ ${colors.bold(colors.yellow('Note:'))}
|
|
|
1915
1610
|
});
|
|
1916
1611
|
program
|
|
1917
1612
|
.command('ip')
|
|
1918
|
-
.
|
|
1919
|
-
.alias('geoip')
|
|
1920
|
-
.description('Look up geolocation and ISP info for an IP address')
|
|
1613
|
+
.description('Get IP address intelligence using local GeoLite2 database')
|
|
1921
1614
|
.argument('<address>', 'IP address to lookup')
|
|
1922
|
-
.addHelpText('after', `
|
|
1923
|
-
${colors.bold(colors.blue('What it does:'))}
|
|
1924
|
-
Looks up geographic location and network information for an IP address
|
|
1925
|
-
using the MaxMind GeoLite2 database (downloaded automatically on first use).
|
|
1926
|
-
|
|
1927
|
-
Shows city, region, country, coordinates, timezone, ISP/organization, and
|
|
1928
|
-
whether it's a bogon (private/reserved) address. Works offline after the
|
|
1929
|
-
initial database download.
|
|
1930
|
-
|
|
1931
|
-
${colors.bold(colors.yellow('Information Displayed:'))}
|
|
1932
|
-
- City, region, country
|
|
1933
|
-
- Geographic coordinates (lat/long)
|
|
1934
|
-
- Timezone
|
|
1935
|
-
- ISP/Organization name
|
|
1936
|
-
- ASN (Autonomous System Number)
|
|
1937
|
-
|
|
1938
|
-
${colors.bold(colors.yellow('Examples:'))}
|
|
1939
|
-
${colors.green('$ rek ip 8.8.8.8')} Google DNS
|
|
1940
|
-
${colors.green('$ rek geo 1.1.1.1')} Cloudflare DNS
|
|
1941
|
-
${colors.green('$ rek geoip 151.101.1.140')} GitHub's IP
|
|
1942
|
-
${colors.green('$ rek ip 192.168.1.1')} Shows "Bogon/Private IP"
|
|
1943
|
-
|
|
1944
|
-
${colors.bold(colors.yellow('Note:'))}
|
|
1945
|
-
The GeoLite2 database (~70MB) is downloaded on first use and cached
|
|
1946
|
-
in ~/.cache/recker/. Updates automatically when stale.
|
|
1947
|
-
`)
|
|
1948
1615
|
.action(async (address) => {
|
|
1949
1616
|
const { getIpInfo, isGeoIPAvailable } = await import('../mcp/ip-intel.js');
|
|
1950
1617
|
if (!isGeoIPAvailable()) {
|
|
@@ -1983,34 +1650,9 @@ ${colors.bold('Network:')}
|
|
|
1983
1650
|
program
|
|
1984
1651
|
.command('tls')
|
|
1985
1652
|
.alias('ssl')
|
|
1986
|
-
.alias('cert')
|
|
1987
1653
|
.description('Inspect TLS/SSL certificate of a host')
|
|
1988
1654
|
.argument('<host>', 'Hostname or IP address')
|
|
1989
1655
|
.argument('[port]', 'Port number (default: 443)', '443')
|
|
1990
|
-
.addHelpText('after', `
|
|
1991
|
-
${colors.bold(colors.blue('What it does:'))}
|
|
1992
|
-
Connects to a server and inspects its TLS/SSL certificate. Shows the
|
|
1993
|
-
certificate issuer, validity dates, days until expiration, subject
|
|
1994
|
-
alternative names (SANs), and whether the certificate is trusted.
|
|
1995
|
-
|
|
1996
|
-
Useful for debugging SSL issues, checking certificate expiration before
|
|
1997
|
-
it causes outages, or verifying a site's security configuration.
|
|
1998
|
-
|
|
1999
|
-
${colors.bold(colors.yellow('Information Displayed:'))}
|
|
2000
|
-
- Certificate validity status (valid/expired)
|
|
2001
|
-
- Trust status (CA-signed or self-signed)
|
|
2002
|
-
- Days remaining until expiration
|
|
2003
|
-
- Issuer (Certificate Authority)
|
|
2004
|
-
- Subject (domain name)
|
|
2005
|
-
- Serial number and fingerprints
|
|
2006
|
-
- Subject Alternative Names (SANs)
|
|
2007
|
-
|
|
2008
|
-
${colors.bold(colors.yellow('Examples:'))}
|
|
2009
|
-
${colors.green('$ rek tls google.com')} Inspect Google's cert
|
|
2010
|
-
${colors.green('$ rek ssl api.stripe.com')} Using ssl alias
|
|
2011
|
-
${colors.green('$ rek cert example.com 8443')} Custom port
|
|
2012
|
-
${colors.green('$ rek tls 192.168.1.1 443')} Check IP directly
|
|
2013
|
-
`)
|
|
2014
1656
|
.action(async (host, port) => {
|
|
2015
1657
|
const { inspectTLS } = await import('../utils/tls-inspector.js');
|
|
2016
1658
|
console.log(colors.gray(`Inspecting TLS certificate for ${host}:${port}...`));
|
|
@@ -2078,43 +1720,15 @@ ${colors.bold('Fingerprints:')}
|
|
|
2078
1720
|
});
|
|
2079
1721
|
program
|
|
2080
1722
|
.command('whois')
|
|
2081
|
-
.description('
|
|
1723
|
+
.description('WHOIS lookup for domains and IP addresses')
|
|
2082
1724
|
.argument('<query>', 'Domain name or IP address')
|
|
2083
|
-
.
|
|
2084
|
-
.
|
|
2085
|
-
${colors.bold(colors.blue('What it does:'))}
|
|
2086
|
-
Queries WHOIS servers to retrieve domain registration information.
|
|
2087
|
-
Shows registrar, creation/expiration dates, nameservers, and registrant
|
|
2088
|
-
contact information (when available, many use privacy protection).
|
|
2089
|
-
|
|
2090
|
-
Also works with IP addresses to find network ownership information.
|
|
2091
|
-
|
|
2092
|
-
${colors.bold(colors.yellow('Options:'))}
|
|
2093
|
-
${colors.cyan('raw')} Show raw WHOIS response
|
|
2094
|
-
|
|
2095
|
-
${colors.bold(colors.yellow('Information Displayed:'))}
|
|
2096
|
-
- Domain status (active, expired, pending delete)
|
|
2097
|
-
- Registrar name
|
|
2098
|
-
- Creation/update/expiration dates
|
|
2099
|
-
- Name servers
|
|
2100
|
-
- Registrant, admin, tech contacts (if public)
|
|
2101
|
-
|
|
2102
|
-
${colors.bold(colors.yellow('Examples:'))}
|
|
2103
|
-
${colors.green('$ rek whois github.com')} Domain registration info
|
|
2104
|
-
${colors.green('$ rek whois google.com raw')} Raw WHOIS response
|
|
2105
|
-
${colors.green('$ rek whois 8.8.8.8')} IP address ownership
|
|
2106
|
-
${colors.green('$ rek whois example.co.uk')} ccTLD domains work too
|
|
2107
|
-
|
|
2108
|
-
${colors.bold(colors.yellow('See also:'))}
|
|
2109
|
-
${colors.cyan('rek rdap <domain>')} RDAP (modern WHOIS replacement)
|
|
2110
|
-
`)
|
|
2111
|
-
.action(async (query, args) => {
|
|
1725
|
+
.option('-r, --raw', 'Show raw WHOIS response')
|
|
1726
|
+
.action(async (query, options) => {
|
|
2112
1727
|
const { whois } = await import('../utils/whois.js');
|
|
2113
|
-
const raw = args.includes('raw');
|
|
2114
1728
|
console.log(colors.gray(`Looking up WHOIS for ${query}...`));
|
|
2115
1729
|
try {
|
|
2116
1730
|
const result = await whois(query);
|
|
2117
|
-
if (raw) {
|
|
1731
|
+
if (options.raw) {
|
|
2118
1732
|
console.log(result.raw);
|
|
2119
1733
|
return;
|
|
2120
1734
|
}
|
|
@@ -2149,33 +1763,8 @@ ${colors.bold('Server:')} ${result.server}
|
|
|
2149
1763
|
});
|
|
2150
1764
|
program
|
|
2151
1765
|
.command('rdap')
|
|
2152
|
-
.description('RDAP lookup (modern WHOIS
|
|
1766
|
+
.description('RDAP lookup (modern WHOIS replacement)')
|
|
2153
1767
|
.argument('<domain>', 'Domain name to lookup')
|
|
2154
|
-
.addHelpText('after', `
|
|
2155
|
-
${colors.bold(colors.blue('What it does:'))}
|
|
2156
|
-
Performs an RDAP (Registration Data Access Protocol) lookup for a domain.
|
|
2157
|
-
RDAP is the modern, standardized replacement for WHOIS that returns
|
|
2158
|
-
structured JSON data instead of unstructured text.
|
|
2159
|
-
|
|
2160
|
-
RDAP provides better data consistency, supports internationalized domain
|
|
2161
|
-
names (IDN), and follows HTTP redirects to find authoritative servers.
|
|
2162
|
-
All major TLDs now support RDAP.
|
|
2163
|
-
|
|
2164
|
-
${colors.bold(colors.yellow('Advantages over WHOIS:'))}
|
|
2165
|
-
- Structured JSON output (machine-readable)
|
|
2166
|
-
- Better internationalization support
|
|
2167
|
-
- Standardized by IETF (RFC 7480-7484)
|
|
2168
|
-
- Bootstrap mechanism for TLD discovery
|
|
2169
|
-
- Rate limiting with proper error codes
|
|
2170
|
-
|
|
2171
|
-
${colors.bold(colors.yellow('Examples:'))}
|
|
2172
|
-
${colors.green('$ rek rdap github.com')} Domain registration info
|
|
2173
|
-
${colors.green('$ rek rdap google.co.uk')} ccTLD domains
|
|
2174
|
-
${colors.green('$ rek rdap cloudflare.com')} Check registrar info
|
|
2175
|
-
|
|
2176
|
-
${colors.bold(colors.yellow('See also:'))}
|
|
2177
|
-
${colors.cyan('rek whois <domain>')} Traditional WHOIS lookup
|
|
2178
|
-
`)
|
|
2179
1768
|
.action(async (domain) => {
|
|
2180
1769
|
const { rdap } = await import('../utils/rdap.js');
|
|
2181
1770
|
const { Client } = await import('../core/client.js');
|
|
@@ -2227,46 +1816,14 @@ ${colors.bold('Status:')} ${result.status?.join(', ') || 'N/A'}
|
|
|
2227
1816
|
});
|
|
2228
1817
|
program
|
|
2229
1818
|
.command('ping')
|
|
2230
|
-
.description('
|
|
1819
|
+
.description('TCP connectivity check to a host')
|
|
2231
1820
|
.argument('<host>', 'Hostname or IP address')
|
|
2232
|
-
.argument('[
|
|
2233
|
-
.
|
|
2234
|
-
|
|
2235
|
-
Tests TCP connectivity to a host and port, measuring connection latency.
|
|
2236
|
-
Unlike ICMP ping, this actually establishes TCP connections, so it works
|
|
2237
|
-
through most firewalls and accurately tests if a service is reachable.
|
|
2238
|
-
|
|
2239
|
-
Shows individual response times and calculates min/avg/max/stddev stats.
|
|
2240
|
-
Perfect for testing if a server is up, measuring network latency, or
|
|
2241
|
-
debugging connectivity issues.
|
|
2242
|
-
|
|
2243
|
-
${colors.bold(colors.yellow('Options:'))}
|
|
2244
|
-
${colors.cyan('[port]')} Port number (default: 443)
|
|
2245
|
-
${colors.cyan('count=<n>')} Number of pings (default: 4)
|
|
2246
|
-
|
|
2247
|
-
${colors.bold(colors.yellow('Examples:'))}
|
|
2248
|
-
${colors.green('$ rek ping google.com')} Test HTTPS (port 443)
|
|
2249
|
-
${colors.green('$ rek ping google.com 80')} Test HTTP (port 80)
|
|
2250
|
-
${colors.green('$ rek ping db.server.com 5432')} Test PostgreSQL port
|
|
2251
|
-
${colors.green('$ rek ping redis.local 6379 count=10')} 10 pings to Redis
|
|
2252
|
-
|
|
2253
|
-
${colors.bold(colors.yellow('Output:'))}
|
|
2254
|
-
Shows response time for each attempt, then summary statistics:
|
|
2255
|
-
- min/avg/max response times
|
|
2256
|
-
- standard deviation
|
|
2257
|
-
- packet loss percentage
|
|
2258
|
-
`)
|
|
2259
|
-
.action(async (host, args) => {
|
|
1821
|
+
.argument('[port]', 'Port number (default: 80 for HTTP, 443 for HTTPS)', '443')
|
|
1822
|
+
.option('-c, --count <n>', 'Number of pings', '4')
|
|
1823
|
+
.action(async (host, port, options) => {
|
|
2260
1824
|
const net = await import('node:net');
|
|
2261
|
-
|
|
2262
|
-
|
|
2263
|
-
for (const arg of args) {
|
|
2264
|
-
if (arg.startsWith('count='))
|
|
2265
|
-
count = parseInt(arg.split('=')[1]);
|
|
2266
|
-
else if (!arg.includes('=') && /^\d+$/.test(arg))
|
|
2267
|
-
port = parseInt(arg);
|
|
2268
|
-
}
|
|
2269
|
-
const portNum = port;
|
|
1825
|
+
const count = parseInt(options.count);
|
|
1826
|
+
const portNum = parseInt(port);
|
|
2270
1827
|
const results = [];
|
|
2271
1828
|
console.log(colors.gray(`Pinging ${host}:${portNum}...`));
|
|
2272
1829
|
console.log('');
|
|
@@ -2316,48 +1873,20 @@ ${colors.bold('Statistics:')}
|
|
|
2316
1873
|
.command('ls')
|
|
2317
1874
|
.description('List files in a remote directory')
|
|
2318
1875
|
.argument('<host>', 'FTP server hostname')
|
|
2319
|
-
.argument('[
|
|
2320
|
-
.
|
|
2321
|
-
|
|
2322
|
-
|
|
2323
|
-
|
|
2324
|
-
|
|
2325
|
-
|
|
2326
|
-
${colors.cyan('implicit')} Use implicit FTPS (port 990)
|
|
2327
|
-
|
|
2328
|
-
${colors.bold(colors.yellow('Examples:'))}
|
|
2329
|
-
${colors.green('$ rek ftp ls ftp.example.com')} ${colors.gray('List root')}
|
|
2330
|
-
${colors.green('$ rek ftp ls ftp.example.com /pub')} ${colors.gray('List /pub directory')}
|
|
2331
|
-
${colors.green('$ rek ftp ls ftp.example.com user=admin pass=secret')} ${colors.gray('With credentials')}
|
|
2332
|
-
`)
|
|
2333
|
-
.action(async (host, args) => {
|
|
1876
|
+
.argument('[path]', 'Remote path to list', '/')
|
|
1877
|
+
.option('-u, --user <username>', 'Username', 'anonymous')
|
|
1878
|
+
.option('-p, --pass <password>', 'Password', 'anonymous@')
|
|
1879
|
+
.option('-P, --port <port>', 'Port number', '21')
|
|
1880
|
+
.option('--secure', 'Use FTPS (explicit TLS)')
|
|
1881
|
+
.option('--implicit', 'Use implicit FTPS (port 990)')
|
|
1882
|
+
.action(async (host, path, options) => {
|
|
2334
1883
|
const { createFTP } = await import('../protocols/ftp.js');
|
|
2335
|
-
|
|
2336
|
-
let user = 'anonymous';
|
|
2337
|
-
let pass = 'anonymous@';
|
|
2338
|
-
let port = 21;
|
|
2339
|
-
let secureOption = false;
|
|
2340
|
-
let implicitOption = false;
|
|
2341
|
-
for (const arg of args) {
|
|
2342
|
-
if (arg.startsWith('user='))
|
|
2343
|
-
user = arg.split('=')[1];
|
|
2344
|
-
else if (arg.startsWith('pass='))
|
|
2345
|
-
pass = arg.split('=')[1];
|
|
2346
|
-
else if (arg.startsWith('port='))
|
|
2347
|
-
port = parseInt(arg.split('=')[1]);
|
|
2348
|
-
else if (arg === 'secure')
|
|
2349
|
-
secureOption = true;
|
|
2350
|
-
else if (arg === 'implicit')
|
|
2351
|
-
implicitOption = true;
|
|
2352
|
-
else if (!arg.includes('='))
|
|
2353
|
-
path = arg;
|
|
2354
|
-
}
|
|
2355
|
-
const secure = implicitOption ? 'implicit' : secureOption ? true : false;
|
|
1884
|
+
const secure = options.implicit ? 'implicit' : options.secure ? true : false;
|
|
2356
1885
|
const client = createFTP({
|
|
2357
1886
|
host,
|
|
2358
|
-
port,
|
|
2359
|
-
user,
|
|
2360
|
-
password: pass,
|
|
1887
|
+
port: parseInt(options.port),
|
|
1888
|
+
user: options.user,
|
|
1889
|
+
password: options.pass,
|
|
2361
1890
|
secure,
|
|
2362
1891
|
});
|
|
2363
1892
|
console.log(colors.gray(`Connecting to ${host}...`));
|
|
@@ -2400,50 +1929,22 @@ ${colors.bold(colors.yellow('Examples:'))}
|
|
|
2400
1929
|
.description('Download a file from FTP server')
|
|
2401
1930
|
.argument('<host>', 'FTP server hostname')
|
|
2402
1931
|
.argument('<remote>', 'Remote file path')
|
|
2403
|
-
.argument('[
|
|
2404
|
-
.
|
|
2405
|
-
|
|
2406
|
-
|
|
2407
|
-
|
|
2408
|
-
|
|
2409
|
-
|
|
2410
|
-
${colors.cyan('implicit')} Use implicit FTPS (port 990)
|
|
2411
|
-
|
|
2412
|
-
${colors.bold(colors.yellow('Examples:'))}
|
|
2413
|
-
${colors.green('$ rek ftp get ftp.example.com /pub/file.zip')} ${colors.gray('Download file')}
|
|
2414
|
-
${colors.green('$ rek ftp get ftp.example.com /pub/file.zip myfile.zip')} ${colors.gray('Save as different name')}
|
|
2415
|
-
${colors.green('$ rek ftp get ftp.example.com /data.csv user=admin pass=secret')} ${colors.gray('With credentials')}
|
|
2416
|
-
`)
|
|
2417
|
-
.action(async (host, remote, args) => {
|
|
1932
|
+
.argument('[local]', 'Local file path (default: same filename)')
|
|
1933
|
+
.option('-u, --user <username>', 'Username', 'anonymous')
|
|
1934
|
+
.option('-p, --pass <password>', 'Password', 'anonymous@')
|
|
1935
|
+
.option('-P, --port <port>', 'Port number', '21')
|
|
1936
|
+
.option('--secure', 'Use FTPS (explicit TLS)')
|
|
1937
|
+
.option('--implicit', 'Use implicit FTPS (port 990)')
|
|
1938
|
+
.action(async (host, remote, local, options) => {
|
|
2418
1939
|
const { createFTP } = await import('../protocols/ftp.js');
|
|
2419
|
-
const
|
|
2420
|
-
|
|
2421
|
-
|
|
2422
|
-
let pass = 'anonymous@';
|
|
2423
|
-
let port = 21;
|
|
2424
|
-
let secureOption = false;
|
|
2425
|
-
let implicitOption = false;
|
|
2426
|
-
for (const arg of args) {
|
|
2427
|
-
if (arg.startsWith('user='))
|
|
2428
|
-
user = arg.split('=')[1];
|
|
2429
|
-
else if (arg.startsWith('pass='))
|
|
2430
|
-
pass = arg.split('=')[1];
|
|
2431
|
-
else if (arg.startsWith('port='))
|
|
2432
|
-
port = parseInt(arg.split('=')[1]);
|
|
2433
|
-
else if (arg === 'secure')
|
|
2434
|
-
secureOption = true;
|
|
2435
|
-
else if (arg === 'implicit')
|
|
2436
|
-
implicitOption = true;
|
|
2437
|
-
else if (!arg.includes('='))
|
|
2438
|
-
local = arg;
|
|
2439
|
-
}
|
|
2440
|
-
const localPath = local || pathMod.basename(remote);
|
|
2441
|
-
const secure = implicitOption ? 'implicit' : secureOption ? true : false;
|
|
1940
|
+
const path = await import('node:path');
|
|
1941
|
+
const localPath = local || path.basename(remote);
|
|
1942
|
+
const secure = options.implicit ? 'implicit' : options.secure ? true : false;
|
|
2442
1943
|
const client = createFTP({
|
|
2443
1944
|
host,
|
|
2444
|
-
port,
|
|
2445
|
-
user,
|
|
2446
|
-
password: pass,
|
|
1945
|
+
port: parseInt(options.port),
|
|
1946
|
+
user: options.user,
|
|
1947
|
+
password: options.pass,
|
|
2447
1948
|
secure,
|
|
2448
1949
|
});
|
|
2449
1950
|
console.log(colors.gray(`Connecting to ${host}...`));
|
|
@@ -2483,50 +1984,22 @@ ${colors.bold(colors.yellow('Examples:'))}
|
|
|
2483
1984
|
.description('Upload a file to FTP server')
|
|
2484
1985
|
.argument('<host>', 'FTP server hostname')
|
|
2485
1986
|
.argument('<local>', 'Local file path')
|
|
2486
|
-
.argument('[
|
|
2487
|
-
.
|
|
2488
|
-
|
|
2489
|
-
|
|
2490
|
-
|
|
2491
|
-
|
|
2492
|
-
|
|
2493
|
-
${colors.cyan('implicit')} Use implicit FTPS (port 990)
|
|
2494
|
-
|
|
2495
|
-
${colors.bold(colors.yellow('Examples:'))}
|
|
2496
|
-
${colors.green('$ rek ftp put ftp.example.com myfile.zip')} ${colors.gray('Upload file')}
|
|
2497
|
-
${colors.green('$ rek ftp put ftp.example.com myfile.zip /uploads/data.zip')} ${colors.gray('Upload with path')}
|
|
2498
|
-
${colors.green('$ rek ftp put ftp.example.com data.csv user=admin pass=secret')} ${colors.gray('With credentials')}
|
|
2499
|
-
`)
|
|
2500
|
-
.action(async (host, local, args) => {
|
|
1987
|
+
.argument('[remote]', 'Remote file path (default: same filename)')
|
|
1988
|
+
.option('-u, --user <username>', 'Username', 'anonymous')
|
|
1989
|
+
.option('-p, --pass <password>', 'Password', 'anonymous@')
|
|
1990
|
+
.option('-P, --port <port>', 'Port number', '21')
|
|
1991
|
+
.option('--secure', 'Use FTPS (explicit TLS)')
|
|
1992
|
+
.option('--implicit', 'Use implicit FTPS (port 990)')
|
|
1993
|
+
.action(async (host, local, remote, options) => {
|
|
2501
1994
|
const { createFTP } = await import('../protocols/ftp.js');
|
|
2502
|
-
const
|
|
2503
|
-
|
|
2504
|
-
|
|
2505
|
-
let pass = 'anonymous@';
|
|
2506
|
-
let port = 21;
|
|
2507
|
-
let secureOption = false;
|
|
2508
|
-
let implicitOption = false;
|
|
2509
|
-
for (const arg of args) {
|
|
2510
|
-
if (arg.startsWith('user='))
|
|
2511
|
-
user = arg.split('=')[1];
|
|
2512
|
-
else if (arg.startsWith('pass='))
|
|
2513
|
-
pass = arg.split('=')[1];
|
|
2514
|
-
else if (arg.startsWith('port='))
|
|
2515
|
-
port = parseInt(arg.split('=')[1]);
|
|
2516
|
-
else if (arg === 'secure')
|
|
2517
|
-
secureOption = true;
|
|
2518
|
-
else if (arg === 'implicit')
|
|
2519
|
-
implicitOption = true;
|
|
2520
|
-
else if (!arg.includes('='))
|
|
2521
|
-
remote = arg;
|
|
2522
|
-
}
|
|
2523
|
-
const remotePath = remote || '/' + pathMod.basename(local);
|
|
2524
|
-
const secure = implicitOption ? 'implicit' : secureOption ? true : false;
|
|
1995
|
+
const path = await import('node:path');
|
|
1996
|
+
const remotePath = remote || '/' + path.basename(local);
|
|
1997
|
+
const secure = options.implicit ? 'implicit' : options.secure ? true : false;
|
|
2525
1998
|
const client = createFTP({
|
|
2526
1999
|
host,
|
|
2527
|
-
port,
|
|
2528
|
-
user,
|
|
2529
|
-
password: pass,
|
|
2000
|
+
port: parseInt(options.port),
|
|
2001
|
+
user: options.user,
|
|
2002
|
+
password: options.pass,
|
|
2530
2003
|
secure,
|
|
2531
2004
|
});
|
|
2532
2005
|
console.log(colors.gray(`Connecting to ${host}...`));
|
|
@@ -2566,44 +2039,19 @@ ${colors.bold(colors.yellow('Examples:'))}
|
|
|
2566
2039
|
.description('Delete a file from FTP server')
|
|
2567
2040
|
.argument('<host>', 'FTP server hostname')
|
|
2568
2041
|
.argument('<path>', 'Remote file path to delete')
|
|
2569
|
-
.
|
|
2570
|
-
.
|
|
2571
|
-
|
|
2572
|
-
|
|
2573
|
-
|
|
2574
|
-
|
|
2575
|
-
${colors.cyan('secure')} Use FTPS (explicit TLS)
|
|
2576
|
-
${colors.cyan('implicit')} Use implicit FTPS (port 990)
|
|
2577
|
-
|
|
2578
|
-
${colors.bold(colors.yellow('Examples:'))}
|
|
2579
|
-
${colors.green('$ rek ftp rm ftp.example.com /uploads/old.zip')} ${colors.gray('Delete file')}
|
|
2580
|
-
${colors.green('$ rek ftp rm ftp.example.com /data.txt user=admin pass=secret')} ${colors.gray('With credentials')}
|
|
2581
|
-
`)
|
|
2582
|
-
.action(async (host, remotePath, args) => {
|
|
2042
|
+
.option('-u, --user <username>', 'Username', 'anonymous')
|
|
2043
|
+
.option('-p, --pass <password>', 'Password', 'anonymous@')
|
|
2044
|
+
.option('-P, --port <port>', 'Port number', '21')
|
|
2045
|
+
.option('--secure', 'Use FTPS (explicit TLS)')
|
|
2046
|
+
.option('--implicit', 'Use implicit FTPS (port 990)')
|
|
2047
|
+
.action(async (host, remotePath, options) => {
|
|
2583
2048
|
const { createFTP } = await import('../protocols/ftp.js');
|
|
2584
|
-
|
|
2585
|
-
let pass = 'anonymous@';
|
|
2586
|
-
let port = 21;
|
|
2587
|
-
let secureOption = false;
|
|
2588
|
-
let implicitOption = false;
|
|
2589
|
-
for (const arg of args) {
|
|
2590
|
-
if (arg.startsWith('user='))
|
|
2591
|
-
user = arg.split('=')[1];
|
|
2592
|
-
else if (arg.startsWith('pass='))
|
|
2593
|
-
pass = arg.split('=')[1];
|
|
2594
|
-
else if (arg.startsWith('port='))
|
|
2595
|
-
port = parseInt(arg.split('=')[1]);
|
|
2596
|
-
else if (arg === 'secure')
|
|
2597
|
-
secureOption = true;
|
|
2598
|
-
else if (arg === 'implicit')
|
|
2599
|
-
implicitOption = true;
|
|
2600
|
-
}
|
|
2601
|
-
const secure = implicitOption ? 'implicit' : secureOption ? true : false;
|
|
2049
|
+
const secure = options.implicit ? 'implicit' : options.secure ? true : false;
|
|
2602
2050
|
const client = createFTP({
|
|
2603
2051
|
host,
|
|
2604
|
-
port,
|
|
2605
|
-
user,
|
|
2606
|
-
password: pass,
|
|
2052
|
+
port: parseInt(options.port),
|
|
2053
|
+
user: options.user,
|
|
2054
|
+
password: options.pass,
|
|
2607
2055
|
secure,
|
|
2608
2056
|
});
|
|
2609
2057
|
console.log(colors.gray(`Connecting to ${host}...`));
|
|
@@ -2634,44 +2082,19 @@ ${colors.bold(colors.yellow('Examples:'))}
|
|
|
2634
2082
|
.description('Create a directory on FTP server')
|
|
2635
2083
|
.argument('<host>', 'FTP server hostname')
|
|
2636
2084
|
.argument('<path>', 'Remote directory path to create')
|
|
2637
|
-
.
|
|
2638
|
-
.
|
|
2639
|
-
|
|
2640
|
-
|
|
2641
|
-
|
|
2642
|
-
|
|
2643
|
-
${colors.cyan('secure')} Use FTPS (explicit TLS)
|
|
2644
|
-
${colors.cyan('implicit')} Use implicit FTPS (port 990)
|
|
2645
|
-
|
|
2646
|
-
${colors.bold(colors.yellow('Examples:'))}
|
|
2647
|
-
${colors.green('$ rek ftp mkdir ftp.example.com /uploads/new-folder')} ${colors.gray('Create directory')}
|
|
2648
|
-
${colors.green('$ rek ftp mkdir ftp.example.com /data user=admin pass=secret')} ${colors.gray('With credentials')}
|
|
2649
|
-
`)
|
|
2650
|
-
.action(async (host, remotePath, args) => {
|
|
2085
|
+
.option('-u, --user <username>', 'Username', 'anonymous')
|
|
2086
|
+
.option('-p, --pass <password>', 'Password', 'anonymous@')
|
|
2087
|
+
.option('-P, --port <port>', 'Port number', '21')
|
|
2088
|
+
.option('--secure', 'Use FTPS (explicit TLS)')
|
|
2089
|
+
.option('--implicit', 'Use implicit FTPS (port 990)')
|
|
2090
|
+
.action(async (host, remotePath, options) => {
|
|
2651
2091
|
const { createFTP } = await import('../protocols/ftp.js');
|
|
2652
|
-
|
|
2653
|
-
let pass = 'anonymous@';
|
|
2654
|
-
let port = 21;
|
|
2655
|
-
let secureOption = false;
|
|
2656
|
-
let implicitOption = false;
|
|
2657
|
-
for (const arg of args) {
|
|
2658
|
-
if (arg.startsWith('user='))
|
|
2659
|
-
user = arg.split('=')[1];
|
|
2660
|
-
else if (arg.startsWith('pass='))
|
|
2661
|
-
pass = arg.split('=')[1];
|
|
2662
|
-
else if (arg.startsWith('port='))
|
|
2663
|
-
port = parseInt(arg.split('=')[1]);
|
|
2664
|
-
else if (arg === 'secure')
|
|
2665
|
-
secureOption = true;
|
|
2666
|
-
else if (arg === 'implicit')
|
|
2667
|
-
implicitOption = true;
|
|
2668
|
-
}
|
|
2669
|
-
const secure = implicitOption ? 'implicit' : secureOption ? true : false;
|
|
2092
|
+
const secure = options.implicit ? 'implicit' : options.secure ? true : false;
|
|
2670
2093
|
const client = createFTP({
|
|
2671
2094
|
host,
|
|
2672
|
-
port,
|
|
2673
|
-
user,
|
|
2674
|
-
password: pass,
|
|
2095
|
+
port: parseInt(options.port),
|
|
2096
|
+
user: options.user,
|
|
2097
|
+
password: options.pass,
|
|
2675
2098
|
secure,
|
|
2676
2099
|
});
|
|
2677
2100
|
console.log(colors.gray(`Connecting to ${host}...`));
|
|
@@ -2701,31 +2124,15 @@ ${colors.bold(colors.yellow('Examples:'))}
|
|
|
2701
2124
|
.command('telnet')
|
|
2702
2125
|
.description('Connect to a Telnet server')
|
|
2703
2126
|
.argument('<host>', 'Hostname or IP address')
|
|
2704
|
-
.argument('[
|
|
2705
|
-
.
|
|
2706
|
-
|
|
2707
|
-
${colors.cyan('[port]')} Port number (default: 23)
|
|
2708
|
-
${colors.cyan('timeout=<ms>')} Connection timeout in ms (default: 30000)
|
|
2709
|
-
|
|
2710
|
-
${colors.bold(colors.yellow('Examples:'))}
|
|
2711
|
-
${colors.green('$ rek telnet mail.example.com 25')} ${colors.gray('Connect to SMTP server')}
|
|
2712
|
-
${colors.green('$ rek telnet host.example.com timeout=60000')} ${colors.gray('With custom timeout')}
|
|
2713
|
-
`)
|
|
2714
|
-
.action(async (host, args) => {
|
|
2127
|
+
.argument('[port]', 'Port number', '23')
|
|
2128
|
+
.option('-t, --timeout <ms>', 'Connection timeout in ms', '30000')
|
|
2129
|
+
.action(async (host, port, options) => {
|
|
2715
2130
|
const { createTelnet } = await import('../protocols/telnet.js');
|
|
2716
|
-
let port = 23;
|
|
2717
|
-
let timeout = 30000;
|
|
2718
|
-
for (const arg of args) {
|
|
2719
|
-
if (arg.startsWith('timeout='))
|
|
2720
|
-
timeout = parseInt(arg.split('=')[1]);
|
|
2721
|
-
else if (!arg.includes('=') && /^\d+$/.test(arg))
|
|
2722
|
-
port = parseInt(arg);
|
|
2723
|
-
}
|
|
2724
2131
|
console.log(colors.gray(`Connecting to ${host}:${port}...`));
|
|
2725
2132
|
const client = createTelnet({
|
|
2726
2133
|
host,
|
|
2727
|
-
port,
|
|
2728
|
-
timeout,
|
|
2134
|
+
port: parseInt(port),
|
|
2135
|
+
timeout: parseInt(options.timeout),
|
|
2729
2136
|
});
|
|
2730
2137
|
try {
|
|
2731
2138
|
await client.connect();
|
|
@@ -2775,37 +2182,9 @@ ${colors.bold(colors.yellow('Examples:'))}
|
|
|
2775
2182
|
});
|
|
2776
2183
|
dns
|
|
2777
2184
|
.command('lookup')
|
|
2778
|
-
.
|
|
2779
|
-
.description('Look up DNS records (A, MX, TXT, etc)')
|
|
2185
|
+
.description('Perform DNS lookup for any record type')
|
|
2780
2186
|
.argument('<domain>', 'Domain name to lookup')
|
|
2781
2187
|
.argument('[type]', 'Record type (A, AAAA, CNAME, MX, NS, TXT, SOA, CAA, SRV, ANY)', 'A')
|
|
2782
|
-
.addHelpText('after', `
|
|
2783
|
-
${colors.bold(colors.blue('What it does:'))}
|
|
2784
|
-
Queries DNS servers to resolve domain records. Returns IP addresses (A/AAAA),
|
|
2785
|
-
mail servers (MX), name servers (NS), text records (TXT), and more.
|
|
2786
|
-
|
|
2787
|
-
Uses your system's configured DNS resolvers. For advanced queries with
|
|
2788
|
-
custom nameservers, use ${colors.cyan('rek dns dig')} instead.
|
|
2789
|
-
|
|
2790
|
-
${colors.bold(colors.yellow('Record Types:'))}
|
|
2791
|
-
A IPv4 address
|
|
2792
|
-
AAAA IPv6 address
|
|
2793
|
-
CNAME Canonical name (alias)
|
|
2794
|
-
MX Mail exchange servers
|
|
2795
|
-
NS Name servers
|
|
2796
|
-
TXT Text records (SPF, DKIM, etc)
|
|
2797
|
-
SOA Start of Authority
|
|
2798
|
-
CAA Certificate Authority Authorization
|
|
2799
|
-
SRV Service location
|
|
2800
|
-
ANY All available records
|
|
2801
|
-
|
|
2802
|
-
${colors.bold(colors.yellow('Examples:'))}
|
|
2803
|
-
${colors.green('$ rek dns lookup google.com')} A records (default)
|
|
2804
|
-
${colors.green('$ rek dns lookup google.com MX')} Mail servers
|
|
2805
|
-
${colors.green('$ rek dns lookup google.com TXT')} Text records
|
|
2806
|
-
${colors.green('$ rek dns lookup google.com ANY')} All records
|
|
2807
|
-
${colors.green('$ rek dns resolve github.com AAAA')} IPv6 addresses
|
|
2808
|
-
`)
|
|
2809
2188
|
.action(async (domain, type) => {
|
|
2810
2189
|
const { dnsLookup } = await import('../utils/dns-toolkit.js');
|
|
2811
2190
|
console.log(colors.gray(`Looking up ${type.toUpperCase()} records for ${domain}...`));
|
|
@@ -3080,54 +2459,27 @@ ${colors.gray('Status:')} ${statusIcon}
|
|
|
3080
2459
|
dns
|
|
3081
2460
|
.command('generate-dmarc')
|
|
3082
2461
|
.description('Generate a DMARC record interactively')
|
|
3083
|
-
.
|
|
3084
|
-
.
|
|
3085
|
-
|
|
3086
|
-
|
|
3087
|
-
|
|
3088
|
-
|
|
3089
|
-
${colors.cyan('rua=<emails>')} Aggregate report email(s), comma-separated
|
|
3090
|
-
${colors.cyan('ruf=<emails>')} Forensic report email(s), comma-separated
|
|
3091
|
-
|
|
3092
|
-
${colors.bold(colors.yellow('Examples:'))}
|
|
3093
|
-
${colors.green('$ rek dns generate-dmarc')} ${colors.gray('Generate with defaults')}
|
|
3094
|
-
${colors.green('$ rek dns generate-dmarc policy=quarantine')} ${colors.gray('Set quarantine policy')}
|
|
3095
|
-
${colors.green('$ rek dns generate-dmarc policy=reject pct=50')} ${colors.gray('Reject 50% of failures')}
|
|
3096
|
-
${colors.green('$ rek dns generate-dmarc rua=admin@example.com')} ${colors.gray('Send reports to email')}
|
|
3097
|
-
`)
|
|
3098
|
-
.action(async (args) => {
|
|
2462
|
+
.option('-p, --policy <policy>', 'Policy: none, quarantine, reject', 'none')
|
|
2463
|
+
.option('--subdomain-policy <policy>', 'Subdomain policy')
|
|
2464
|
+
.option('--pct <percent>', 'Percentage of emails to apply policy', '100')
|
|
2465
|
+
.option('--rua <emails>', 'Aggregate report email(s), comma-separated')
|
|
2466
|
+
.option('--ruf <emails>', 'Forensic report email(s), comma-separated')
|
|
2467
|
+
.action(async (options) => {
|
|
3099
2468
|
const { generateDmarc } = await import('../utils/dns-toolkit.js');
|
|
3100
|
-
let policy = 'none';
|
|
3101
|
-
let subdomainPolicy;
|
|
3102
|
-
let pct = '100';
|
|
3103
|
-
let rua;
|
|
3104
|
-
let ruf;
|
|
3105
|
-
for (const arg of args) {
|
|
3106
|
-
if (arg.startsWith('policy='))
|
|
3107
|
-
policy = arg.split('=')[1];
|
|
3108
|
-
else if (arg.startsWith('subdomain-policy='))
|
|
3109
|
-
subdomainPolicy = arg.split('=')[1];
|
|
3110
|
-
else if (arg.startsWith('pct='))
|
|
3111
|
-
pct = arg.split('=')[1];
|
|
3112
|
-
else if (arg.startsWith('rua='))
|
|
3113
|
-
rua = arg.split('=')[1];
|
|
3114
|
-
else if (arg.startsWith('ruf='))
|
|
3115
|
-
ruf = arg.split('=')[1];
|
|
3116
|
-
}
|
|
3117
2469
|
const dmarcOptions = {
|
|
3118
|
-
policy,
|
|
2470
|
+
policy: options.policy,
|
|
3119
2471
|
};
|
|
3120
|
-
if (subdomainPolicy) {
|
|
3121
|
-
dmarcOptions.subdomainPolicy = subdomainPolicy;
|
|
2472
|
+
if (options.subdomainPolicy) {
|
|
2473
|
+
dmarcOptions.subdomainPolicy = options.subdomainPolicy;
|
|
3122
2474
|
}
|
|
3123
|
-
if (pct && pct !== '100') {
|
|
3124
|
-
dmarcOptions.percentage = parseInt(pct);
|
|
2475
|
+
if (options.pct && options.pct !== '100') {
|
|
2476
|
+
dmarcOptions.percentage = parseInt(options.pct);
|
|
3125
2477
|
}
|
|
3126
|
-
if (rua) {
|
|
3127
|
-
dmarcOptions.aggregateReports = rua.split(',').map((e) => e.trim());
|
|
2478
|
+
if (options.rua) {
|
|
2479
|
+
dmarcOptions.aggregateReports = options.rua.split(',').map((e) => e.trim());
|
|
3128
2480
|
}
|
|
3129
|
-
if (ruf) {
|
|
3130
|
-
dmarcOptions.forensicReports = ruf.split(',').map((e) => e.trim());
|
|
2481
|
+
if (options.ruf) {
|
|
2482
|
+
dmarcOptions.forensicReports = options.ruf.split(',').map((e) => e.trim());
|
|
3131
2483
|
}
|
|
3132
2484
|
const record = generateDmarc(dmarcOptions);
|
|
3133
2485
|
console.log(`
|
|
@@ -3209,68 +2561,22 @@ ${colors.bold(colors.yellow('Record Types:'))}
|
|
|
3209
2561
|
});
|
|
3210
2562
|
program
|
|
3211
2563
|
.command('graphql')
|
|
3212
|
-
.
|
|
3213
|
-
.description('Execute GraphQL queries and mutations')
|
|
2564
|
+
.description('Execute a GraphQL query')
|
|
3214
2565
|
.argument('<url>', 'GraphQL endpoint URL')
|
|
3215
|
-
.
|
|
3216
|
-
.
|
|
3217
|
-
|
|
3218
|
-
|
|
3219
|
-
|
|
3220
|
-
|
|
3221
|
-
|
|
3222
|
-
Results are displayed as formatted JSON. Headers can be added for
|
|
3223
|
-
authentication (Bearer tokens, API keys, etc).
|
|
3224
|
-
|
|
3225
|
-
${colors.bold(colors.yellow('Options:'))}
|
|
3226
|
-
${colors.cyan('query=<query>')} Inline GraphQL query string
|
|
3227
|
-
${colors.cyan('file=<file>')} Load query from .graphql file
|
|
3228
|
-
${colors.cyan('variables=<json>')} Inline variables as JSON
|
|
3229
|
-
${colors.cyan('var-file=<file>')} Load variables from JSON file
|
|
3230
|
-
${colors.cyan('Header:Value')} Add header (can use multiple times)
|
|
3231
|
-
|
|
3232
|
-
${colors.bold(colors.yellow('Examples:'))}
|
|
3233
|
-
${colors.green('$ rek graphql https://api.github.com/graphql query="{ viewer { login } }"')}
|
|
3234
|
-
${colors.gray(' Simple query')}
|
|
3235
|
-
|
|
3236
|
-
${colors.green('$ rek gql https://api.spacex.land/graphql query="{ rockets { name } }"')}
|
|
3237
|
-
${colors.gray(' Using the gql alias')}
|
|
3238
|
-
|
|
3239
|
-
${colors.green('$ rek graphql api.example.com/graphql file=query.graphql variables=\'{"id": 123}\'')}
|
|
3240
|
-
${colors.gray(' Query from file with variables')}
|
|
3241
|
-
|
|
3242
|
-
${colors.green('$ rek graphql api.com/graphql query="query User($id: ID!) { user(id: $id) { name } }" var-file=vars.json')}
|
|
3243
|
-
${colors.gray(' Parameterized query with variable file')}
|
|
3244
|
-
|
|
3245
|
-
${colors.green('$ rek graphql api.com/graphql query="{ me { id } }" Authorization:"Bearer token123"')}
|
|
3246
|
-
${colors.gray(' With authentication header')}
|
|
3247
|
-
`)
|
|
3248
|
-
.action(async (url, args) => {
|
|
2566
|
+
.option('-q, --query <query>', 'GraphQL query string')
|
|
2567
|
+
.option('-f, --file <file>', 'Path to GraphQL query file')
|
|
2568
|
+
.option('-v, --variables <json>', 'Variables as JSON string')
|
|
2569
|
+
.option('--var-file <file>', 'Path to variables JSON file')
|
|
2570
|
+
.option('-H, --header <header>', 'Add header (can be used multiple times)', (val, prev) => [...prev, val], [])
|
|
2571
|
+
.action(async (url, options) => {
|
|
3249
2572
|
const { graphql } = await import('../plugins/graphql.js');
|
|
3250
2573
|
const { createClient } = await import('../core/client.js');
|
|
3251
2574
|
const fs = await import('node:fs/promises');
|
|
3252
|
-
let
|
|
3253
|
-
let queryFile;
|
|
3254
|
-
let variablesStr;
|
|
3255
|
-
let varFile;
|
|
3256
|
-
const headerArgs = [];
|
|
3257
|
-
for (const arg of args) {
|
|
3258
|
-
if (arg.startsWith('query='))
|
|
3259
|
-
queryStr = arg.slice(6);
|
|
3260
|
-
else if (arg.startsWith('file='))
|
|
3261
|
-
queryFile = arg.slice(5);
|
|
3262
|
-
else if (arg.startsWith('variables='))
|
|
3263
|
-
variablesStr = arg.slice(10);
|
|
3264
|
-
else if (arg.startsWith('var-file='))
|
|
3265
|
-
varFile = arg.slice(9);
|
|
3266
|
-
else if (arg.includes(':'))
|
|
3267
|
-
headerArgs.push(arg);
|
|
3268
|
-
}
|
|
3269
|
-
let query = queryStr;
|
|
2575
|
+
let query = options.query;
|
|
3270
2576
|
let variables = {};
|
|
3271
|
-
if (
|
|
2577
|
+
if (options.file) {
|
|
3272
2578
|
try {
|
|
3273
|
-
query = await fs.readFile(
|
|
2579
|
+
query = await fs.readFile(options.file, 'utf-8');
|
|
3274
2580
|
}
|
|
3275
2581
|
catch (err) {
|
|
3276
2582
|
console.error(colors.red(`Failed to read query file: ${err.message}`));
|
|
@@ -3278,22 +2584,22 @@ ${colors.bold(colors.yellow('Examples:'))}
|
|
|
3278
2584
|
}
|
|
3279
2585
|
}
|
|
3280
2586
|
if (!query) {
|
|
3281
|
-
console.error(colors.red('Error: Query is required. Use query
|
|
3282
|
-
console.log(colors.gray('Example: rek graphql https://api.example.com/graphql
|
|
2587
|
+
console.error(colors.red('Error: Query is required. Use --query or --file'));
|
|
2588
|
+
console.log(colors.gray('Example: rek graphql https://api.example.com/graphql -q "query { users { id name } }"'));
|
|
3283
2589
|
process.exit(1);
|
|
3284
2590
|
}
|
|
3285
|
-
if (
|
|
2591
|
+
if (options.variables) {
|
|
3286
2592
|
try {
|
|
3287
|
-
variables = JSON.parse(
|
|
2593
|
+
variables = JSON.parse(options.variables);
|
|
3288
2594
|
}
|
|
3289
2595
|
catch {
|
|
3290
|
-
console.error(colors.red('Invalid JSON in variables
|
|
2596
|
+
console.error(colors.red('Invalid JSON in --variables'));
|
|
3291
2597
|
process.exit(1);
|
|
3292
2598
|
}
|
|
3293
2599
|
}
|
|
3294
|
-
else if (varFile) {
|
|
2600
|
+
else if (options.varFile) {
|
|
3295
2601
|
try {
|
|
3296
|
-
const content = await fs.readFile(varFile, 'utf-8');
|
|
2602
|
+
const content = await fs.readFile(options.varFile, 'utf-8');
|
|
3297
2603
|
variables = JSON.parse(content);
|
|
3298
2604
|
}
|
|
3299
2605
|
catch (err) {
|
|
@@ -3304,7 +2610,7 @@ ${colors.bold(colors.yellow('Examples:'))}
|
|
|
3304
2610
|
const headers = {
|
|
3305
2611
|
'Content-Type': 'application/json',
|
|
3306
2612
|
};
|
|
3307
|
-
for (const h of
|
|
2613
|
+
for (const h of options.header) {
|
|
3308
2614
|
const [key, ...valueParts] = h.split(':');
|
|
3309
2615
|
headers[key.trim()] = valueParts.join(':').trim();
|
|
3310
2616
|
}
|
|
@@ -3468,48 +2774,21 @@ ${colors.bold(colors.yellow('Examples:'))}
|
|
|
3468
2774
|
.command('download')
|
|
3469
2775
|
.description('Download an HLS stream')
|
|
3470
2776
|
.argument('<url>', 'HLS playlist URL')
|
|
3471
|
-
.argument('[
|
|
3472
|
-
.
|
|
3473
|
-
|
|
3474
|
-
|
|
3475
|
-
|
|
3476
|
-
|
|
3477
|
-
${colors.cyan('duration=<seconds>')} Duration for live recording in seconds
|
|
3478
|
-
${colors.cyan('concurrency=<n>')} Concurrent segment downloads (default: 4)
|
|
3479
|
-
|
|
3480
|
-
${colors.bold(colors.yellow('Examples:'))}
|
|
3481
|
-
${colors.green('$ rek hls download https://example.com/stream.m3u8')} ${colors.gray('Download stream')}
|
|
3482
|
-
${colors.green('$ rek hls download https://example.com/stream.m3u8 video.ts')} ${colors.gray('Custom output')}
|
|
3483
|
-
${colors.green('$ rek hls download https://example.com/stream.m3u8 quality=720p')} ${colors.gray('Select quality')}
|
|
3484
|
-
${colors.green('$ rek hls download https://example.com/live.m3u8 live duration=60')} ${colors.gray('Record live stream')}
|
|
3485
|
-
`)
|
|
3486
|
-
.action(async (url, args) => {
|
|
2777
|
+
.argument('[output]', 'Output file path', 'output.ts')
|
|
2778
|
+
.option('-q, --quality <quality>', 'Quality: highest, lowest, or resolution (e.g., 720p)')
|
|
2779
|
+
.option('--live', 'Enable live stream mode')
|
|
2780
|
+
.option('-d, --duration <seconds>', 'Duration for live recording in seconds')
|
|
2781
|
+
.option('-c, --concurrency <n>', 'Concurrent segment downloads', '4')
|
|
2782
|
+
.action(async (url, output, options) => {
|
|
3487
2783
|
const { hls } = await import('../plugins/hls.js');
|
|
3488
2784
|
const { Client } = await import('../core/client.js');
|
|
3489
|
-
let output = 'output.ts';
|
|
3490
|
-
let quality;
|
|
3491
|
-
let live = false;
|
|
3492
|
-
let duration;
|
|
3493
|
-
let concurrency = 4;
|
|
3494
|
-
for (const arg of args) {
|
|
3495
|
-
if (arg.startsWith('quality='))
|
|
3496
|
-
quality = arg.split('=')[1];
|
|
3497
|
-
else if (arg === 'live')
|
|
3498
|
-
live = true;
|
|
3499
|
-
else if (arg.startsWith('duration='))
|
|
3500
|
-
duration = parseInt(arg.split('=')[1]);
|
|
3501
|
-
else if (arg.startsWith('concurrency='))
|
|
3502
|
-
concurrency = parseInt(arg.split('=')[1]);
|
|
3503
|
-
else if (!arg.includes('='))
|
|
3504
|
-
output = arg;
|
|
3505
|
-
}
|
|
3506
2785
|
const client = new Client();
|
|
3507
2786
|
console.log(colors.gray(`Downloading HLS stream from ${url}...`));
|
|
3508
2787
|
console.log(colors.gray(`Output: ${output}`));
|
|
3509
2788
|
console.log('');
|
|
3510
2789
|
try {
|
|
3511
2790
|
const hlsOptions = {
|
|
3512
|
-
concurrency,
|
|
2791
|
+
concurrency: parseInt(options.concurrency),
|
|
3513
2792
|
onProgress: (p) => {
|
|
3514
2793
|
const segs = p.totalSegments
|
|
3515
2794
|
? `${p.downloadedSegments}/${p.totalSegments}`
|
|
@@ -3518,17 +2797,17 @@ ${colors.bold(colors.yellow('Examples:'))}
|
|
|
3518
2797
|
process.stdout.write(`\r ${colors.cyan(segs)} segments | ${colors.cyan(mb + ' MB')} downloaded`);
|
|
3519
2798
|
},
|
|
3520
2799
|
};
|
|
3521
|
-
if (quality) {
|
|
3522
|
-
if (quality === 'highest' || quality === 'lowest') {
|
|
3523
|
-
hlsOptions.quality = quality;
|
|
2800
|
+
if (options.quality) {
|
|
2801
|
+
if (options.quality === 'highest' || options.quality === 'lowest') {
|
|
2802
|
+
hlsOptions.quality = options.quality;
|
|
3524
2803
|
}
|
|
3525
|
-
else if (quality.includes('p')) {
|
|
3526
|
-
hlsOptions.quality = { resolution: quality };
|
|
2804
|
+
else if (options.quality.includes('p')) {
|
|
2805
|
+
hlsOptions.quality = { resolution: options.quality };
|
|
3527
2806
|
}
|
|
3528
2807
|
}
|
|
3529
|
-
if (live) {
|
|
3530
|
-
hlsOptions.live = duration
|
|
3531
|
-
? { duration: duration * 1000 }
|
|
2808
|
+
if (options.live) {
|
|
2809
|
+
hlsOptions.live = options.duration
|
|
2810
|
+
? { duration: parseInt(options.duration) * 1000 }
|
|
3532
2811
|
: true;
|
|
3533
2812
|
}
|
|
3534
2813
|
await hls(client, url, hlsOptions).download(output);
|
|
@@ -3701,23 +2980,19 @@ ${colors.bold(colors.yellow('Examples:'))}
|
|
|
3701
2980
|
.command('info')
|
|
3702
2981
|
.description('Show information about a HAR file')
|
|
3703
2982
|
.argument('<file>', 'HAR file to inspect')
|
|
3704
|
-
.
|
|
2983
|
+
.option('--json', 'Output as JSON')
|
|
3705
2984
|
.addHelpText('after', `
|
|
3706
|
-
${colors.bold(colors.yellow('Options:'))}
|
|
3707
|
-
${colors.cyan('json')} Output as JSON
|
|
3708
|
-
|
|
3709
2985
|
${colors.bold(colors.yellow('Examples:'))}
|
|
3710
2986
|
${colors.green('$ rek har info api.har')}
|
|
3711
|
-
${colors.green('$ rek har info api.har json')}
|
|
2987
|
+
${colors.green('$ rek har info api.har --json')}
|
|
3712
2988
|
`)
|
|
3713
|
-
.action(async (file,
|
|
2989
|
+
.action(async (file, options) => {
|
|
3714
2990
|
const { promises: fsPromises } = await import('node:fs');
|
|
3715
|
-
const jsonOutput = args.includes('json');
|
|
3716
2991
|
try {
|
|
3717
2992
|
const content = await fsPromises.readFile(file, 'utf-8');
|
|
3718
2993
|
const har = JSON.parse(content);
|
|
3719
2994
|
const entries = har.log?.entries || [];
|
|
3720
|
-
if (
|
|
2995
|
+
if (options.json) {
|
|
3721
2996
|
const info = {
|
|
3722
2997
|
version: har.log?.version,
|
|
3723
2998
|
creator: har.log?.creator,
|
|
@@ -3844,66 +3119,28 @@ ${colors.bold(colors.yellow('Examples:'))}
|
|
|
3844
3119
|
const serve = program.command('serve').description('Start mock servers for testing protocols');
|
|
3845
3120
|
serve
|
|
3846
3121
|
.command('http')
|
|
3847
|
-
.description('Start a mock HTTP server
|
|
3848
|
-
.
|
|
3122
|
+
.description('Start a mock HTTP server')
|
|
3123
|
+
.option('-p, --port <number>', 'Port to listen on', '3000')
|
|
3124
|
+
.option('-h, --host <string>', 'Host to bind to', '127.0.0.1')
|
|
3125
|
+
.option('--echo', 'Echo request body back in response')
|
|
3126
|
+
.option('--delay <ms>', 'Add delay to responses (milliseconds)', '0')
|
|
3127
|
+
.option('--cors', 'Enable CORS', true)
|
|
3849
3128
|
.addHelpText('after', `
|
|
3850
|
-
${colors.bold(colors.blue('What it does:'))}
|
|
3851
|
-
Starts a local mock HTTP server for testing HTTP clients, webhooks, or APIs.
|
|
3852
|
-
Provides useful built-in endpoints for testing various HTTP scenarios.
|
|
3853
|
-
|
|
3854
|
-
Supports echo mode (returns the request back), configurable delays for
|
|
3855
|
-
testing timeouts, and CORS for browser-based testing. Perfect for
|
|
3856
|
-
integration tests, webhook development, or API prototyping.
|
|
3857
|
-
|
|
3858
|
-
${colors.bold(colors.yellow('Options:'))}
|
|
3859
|
-
${colors.cyan('port=<number>')} Port to listen on (default: 3000)
|
|
3860
|
-
${colors.cyan('host=<string>')} Host to bind to (default: 127.0.0.1)
|
|
3861
|
-
${colors.cyan('echo')} Echo request body back in response
|
|
3862
|
-
${colors.cyan('delay=<ms>')} Add delay to responses in ms (default: 0)
|
|
3863
|
-
${colors.cyan('cors')} Enable CORS (default: true)
|
|
3864
|
-
|
|
3865
|
-
${colors.bold(colors.yellow('Built-in Endpoints:'))}
|
|
3866
|
-
GET / Health check, returns { ok: true }
|
|
3867
|
-
GET /json Sample JSON response
|
|
3868
|
-
GET /delay/:ms Delayed response
|
|
3869
|
-
POST /echo Echo request body back
|
|
3870
|
-
* /status/:code Return specific HTTP status code
|
|
3871
|
-
GET /headers Return request headers
|
|
3872
|
-
|
|
3873
3129
|
${colors.bold(colors.yellow('Examples:'))}
|
|
3874
|
-
${colors.green('$ rek serve http')} Start on port 3000
|
|
3875
|
-
${colors.green('$ rek serve http
|
|
3876
|
-
${colors.green('$ rek serve http echo')}
|
|
3877
|
-
${colors.green('$ rek serve http delay
|
|
3130
|
+
${colors.green('$ rek serve http')} ${colors.gray('Start on port 3000')}
|
|
3131
|
+
${colors.green('$ rek serve http -p 8080')} ${colors.gray('Start on port 8080')}
|
|
3132
|
+
${colors.green('$ rek serve http --echo')} ${colors.gray('Echo mode')}
|
|
3133
|
+
${colors.green('$ rek serve http --delay 500')} ${colors.gray('Add 500ms delay')}
|
|
3878
3134
|
`)
|
|
3879
|
-
.action(async (
|
|
3880
|
-
let port = 3000;
|
|
3881
|
-
let host = '127.0.0.1';
|
|
3882
|
-
let echo = false;
|
|
3883
|
-
let delay = 0;
|
|
3884
|
-
let cors = true;
|
|
3885
|
-
for (const arg of args) {
|
|
3886
|
-
if (arg.startsWith('port='))
|
|
3887
|
-
port = parseInt(arg.split('=')[1]);
|
|
3888
|
-
else if (arg.startsWith('host='))
|
|
3889
|
-
host = arg.split('=')[1];
|
|
3890
|
-
else if (arg === 'echo')
|
|
3891
|
-
echo = true;
|
|
3892
|
-
else if (arg.startsWith('delay='))
|
|
3893
|
-
delay = parseInt(arg.split('=')[1]);
|
|
3894
|
-
else if (arg === 'cors')
|
|
3895
|
-
cors = true;
|
|
3896
|
-
else if (arg === 'nocors')
|
|
3897
|
-
cors = false;
|
|
3898
|
-
}
|
|
3135
|
+
.action(async (options) => {
|
|
3899
3136
|
const { MockHttpServer } = await import('../testing/mock-http-server.js');
|
|
3900
3137
|
const server = await MockHttpServer.create({
|
|
3901
|
-
port,
|
|
3902
|
-
host,
|
|
3903
|
-
delay,
|
|
3904
|
-
cors,
|
|
3138
|
+
port: parseInt(options.port),
|
|
3139
|
+
host: options.host,
|
|
3140
|
+
delay: parseInt(options.delay),
|
|
3141
|
+
cors: options.cors,
|
|
3905
3142
|
});
|
|
3906
|
-
if (echo) {
|
|
3143
|
+
if (options.echo) {
|
|
3907
3144
|
server.any('/*', (req) => ({
|
|
3908
3145
|
status: 200,
|
|
3909
3146
|
body: {
|
|
@@ -3920,8 +3157,8 @@ ${colors.bold(colors.yellow('Examples:'))}
|
|
|
3920
3157
|
│ ${colors.bold('Recker Mock HTTP Server')} │
|
|
3921
3158
|
├─────────────────────────────────────────────┤
|
|
3922
3159
|
│ URL: ${colors.cyan(server.url.padEnd(37))}│
|
|
3923
|
-
│ Mode: ${colors.yellow((echo ? 'Echo' : 'Default').padEnd(36))}│
|
|
3924
|
-
│ Delay: ${colors.gray((delay + 'ms').padEnd(35))}│
|
|
3160
|
+
│ Mode: ${colors.yellow((options.echo ? 'Echo' : 'Default').padEnd(36))}│
|
|
3161
|
+
│ Delay: ${colors.gray((options.delay + 'ms').padEnd(35))}│
|
|
3925
3162
|
├─────────────────────────────────────────────┤
|
|
3926
3163
|
│ Press ${colors.bold('Ctrl+C')} to stop │
|
|
3927
3164
|
└─────────────────────────────────────────────┘
|
|
@@ -3939,18 +3176,15 @@ ${colors.bold(colors.yellow('Examples:'))}
|
|
|
3939
3176
|
.command('webhook')
|
|
3940
3177
|
.alias('wh')
|
|
3941
3178
|
.description('Start a webhook receiver server')
|
|
3942
|
-
.
|
|
3179
|
+
.option('-p, --port <number>', 'Port to listen on', '3000')
|
|
3180
|
+
.option('-h, --host <string>', 'Host to bind to', '127.0.0.1')
|
|
3181
|
+
.option('-s, --status <code>', 'Response status code (200 or 204)', '204')
|
|
3182
|
+
.option('-q, --quiet', 'Disable logging', false)
|
|
3943
3183
|
.addHelpText('after', `
|
|
3944
|
-
${colors.bold(colors.yellow('Options:'))}
|
|
3945
|
-
${colors.cyan('port=<number>')} Port to listen on (default: 3000)
|
|
3946
|
-
${colors.cyan('host=<string>')} Host to bind to (default: 127.0.0.1)
|
|
3947
|
-
${colors.cyan('status=<code>')} Response status code 200 or 204 (default: 204)
|
|
3948
|
-
${colors.cyan('quiet')} Disable logging
|
|
3949
|
-
|
|
3950
3184
|
${colors.bold(colors.yellow('Examples:'))}
|
|
3951
3185
|
${colors.green('$ rek serve webhook')} ${colors.gray('Start on port 3000')}
|
|
3952
|
-
${colors.green('$ rek serve wh
|
|
3953
|
-
${colors.green('$ rek serve webhook status
|
|
3186
|
+
${colors.green('$ rek serve wh -p 8080')} ${colors.gray('Start on port 8080')}
|
|
3187
|
+
${colors.green('$ rek serve webhook --status 200')} ${colors.gray('Return 200 instead of 204')}
|
|
3954
3188
|
|
|
3955
3189
|
${colors.bold(colors.yellow('Endpoints:'))}
|
|
3956
3190
|
* / ${colors.gray('Receive webhook without ID')}
|
|
@@ -3959,32 +3193,18 @@ ${colors.bold(colors.yellow('Endpoints:'))}
|
|
|
3959
3193
|
${colors.bold(colors.yellow('Methods:'))}
|
|
3960
3194
|
${colors.gray('GET, POST, PUT, PATCH, DELETE, HEAD, OPTIONS')}
|
|
3961
3195
|
`)
|
|
3962
|
-
.action(async (
|
|
3963
|
-
let port = 3000;
|
|
3964
|
-
let host = '127.0.0.1';
|
|
3965
|
-
let statusCode = 204;
|
|
3966
|
-
let quiet = false;
|
|
3967
|
-
for (const arg of args) {
|
|
3968
|
-
if (arg.startsWith('port='))
|
|
3969
|
-
port = parseInt(arg.split('=')[1]);
|
|
3970
|
-
else if (arg.startsWith('host='))
|
|
3971
|
-
host = arg.split('=')[1];
|
|
3972
|
-
else if (arg.startsWith('status='))
|
|
3973
|
-
statusCode = parseInt(arg.split('=')[1]);
|
|
3974
|
-
else if (arg === 'quiet')
|
|
3975
|
-
quiet = true;
|
|
3976
|
-
}
|
|
3196
|
+
.action(async (options) => {
|
|
3977
3197
|
const { createWebhookServer } = await import('../testing/mock-http-server.js');
|
|
3978
|
-
const status =
|
|
3198
|
+
const status = parseInt(options.status);
|
|
3979
3199
|
if (status !== 200 && status !== 204) {
|
|
3980
3200
|
console.error(colors.red('Status must be 200 or 204'));
|
|
3981
3201
|
process.exit(1);
|
|
3982
3202
|
}
|
|
3983
3203
|
const server = await createWebhookServer({
|
|
3984
|
-
port,
|
|
3985
|
-
host,
|
|
3204
|
+
port: parseInt(options.port),
|
|
3205
|
+
host: options.host,
|
|
3986
3206
|
status,
|
|
3987
|
-
log: !quiet,
|
|
3207
|
+
log: !options.quiet,
|
|
3988
3208
|
});
|
|
3989
3209
|
console.log(colors.green(`
|
|
3990
3210
|
┌─────────────────────────────────────────────┐
|
|
@@ -4010,51 +3230,32 @@ ${colors.bold(colors.yellow('Methods:'))}
|
|
|
4010
3230
|
.command('websocket')
|
|
4011
3231
|
.alias('ws')
|
|
4012
3232
|
.description('Start a mock WebSocket server')
|
|
4013
|
-
.
|
|
3233
|
+
.option('-p, --port <number>', 'Port to listen on', '8080')
|
|
3234
|
+
.option('-h, --host <string>', 'Host to bind to', '127.0.0.1')
|
|
3235
|
+
.option('--echo', 'Echo messages back (default: true)', true)
|
|
3236
|
+
.option('--no-echo', 'Disable echo mode')
|
|
3237
|
+
.option('--delay <ms>', 'Add delay to responses (milliseconds)', '0')
|
|
4014
3238
|
.addHelpText('after', `
|
|
4015
|
-
${colors.bold(colors.yellow('Options:'))}
|
|
4016
|
-
${colors.cyan('port=<number>')} Port to listen on (default: 8080)
|
|
4017
|
-
${colors.cyan('host=<string>')} Host to bind to (default: 127.0.0.1)
|
|
4018
|
-
${colors.cyan('echo')} Echo messages back (default)
|
|
4019
|
-
${colors.cyan('noecho')} Disable echo mode
|
|
4020
|
-
${colors.cyan('delay=<ms>')} Add delay to responses in ms (default: 0)
|
|
4021
|
-
|
|
4022
3239
|
${colors.bold(colors.yellow('Examples:'))}
|
|
4023
3240
|
${colors.green('$ rek serve websocket')} ${colors.gray('Start on port 8080')}
|
|
4024
|
-
${colors.green('$ rek serve ws
|
|
4025
|
-
${colors.green('$ rek serve ws
|
|
3241
|
+
${colors.green('$ rek serve ws -p 9000')} ${colors.gray('Start on port 9000')}
|
|
3242
|
+
${colors.green('$ rek serve ws --no-echo')} ${colors.gray('Disable echo')}
|
|
4026
3243
|
`)
|
|
4027
|
-
.action(async (
|
|
4028
|
-
let port = 8080;
|
|
4029
|
-
let host = '127.0.0.1';
|
|
4030
|
-
let echo = true;
|
|
4031
|
-
let delay = 0;
|
|
4032
|
-
for (const arg of args) {
|
|
4033
|
-
if (arg.startsWith('port='))
|
|
4034
|
-
port = parseInt(arg.split('=')[1]);
|
|
4035
|
-
else if (arg.startsWith('host='))
|
|
4036
|
-
host = arg.split('=')[1];
|
|
4037
|
-
else if (arg === 'echo')
|
|
4038
|
-
echo = true;
|
|
4039
|
-
else if (arg === 'noecho')
|
|
4040
|
-
echo = false;
|
|
4041
|
-
else if (arg.startsWith('delay='))
|
|
4042
|
-
delay = parseInt(arg.split('=')[1]);
|
|
4043
|
-
}
|
|
3244
|
+
.action(async (options) => {
|
|
4044
3245
|
const { MockWebSocketServer } = await import('../testing/mock-websocket-server.js');
|
|
4045
3246
|
const server = await MockWebSocketServer.create({
|
|
4046
|
-
port,
|
|
4047
|
-
host,
|
|
4048
|
-
echo,
|
|
4049
|
-
delay,
|
|
3247
|
+
port: parseInt(options.port),
|
|
3248
|
+
host: options.host,
|
|
3249
|
+
echo: options.echo,
|
|
3250
|
+
delay: parseInt(options.delay),
|
|
4050
3251
|
});
|
|
4051
3252
|
console.log(colors.green(`
|
|
4052
3253
|
┌─────────────────────────────────────────────┐
|
|
4053
3254
|
│ ${colors.bold('Recker Mock WebSocket Server')} │
|
|
4054
3255
|
├─────────────────────────────────────────────┤
|
|
4055
3256
|
│ URL: ${colors.cyan(server.url.padEnd(37))}│
|
|
4056
|
-
│ Echo: ${colors.yellow((echo ? 'Enabled' : 'Disabled').padEnd(36))}│
|
|
4057
|
-
│ Delay: ${colors.gray((delay + 'ms').padEnd(35))}│
|
|
3257
|
+
│ Echo: ${colors.yellow((options.echo ? 'Enabled' : 'Disabled').padEnd(36))}│
|
|
3258
|
+
│ Delay: ${colors.gray((options.delay + 'ms').padEnd(35))}│
|
|
4058
3259
|
├─────────────────────────────────────────────┤
|
|
4059
3260
|
│ Press ${colors.bold('Ctrl+C')} to stop │
|
|
4060
3261
|
└─────────────────────────────────────────────┘
|
|
@@ -4078,53 +3279,36 @@ ${colors.bold(colors.yellow('Examples:'))}
|
|
|
4078
3279
|
serve
|
|
4079
3280
|
.command('sse')
|
|
4080
3281
|
.description('Start a mock SSE (Server-Sent Events) server')
|
|
4081
|
-
.
|
|
3282
|
+
.option('-p, --port <number>', 'Port to listen on', '8081')
|
|
3283
|
+
.option('-h, --host <string>', 'Host to bind to', '127.0.0.1')
|
|
3284
|
+
.option('--path <string>', 'SSE endpoint path', '/events')
|
|
3285
|
+
.option('--heartbeat <ms>', 'Send heartbeat every N ms (0 = disabled)', '0')
|
|
4082
3286
|
.addHelpText('after', `
|
|
4083
|
-
${colors.bold(colors.yellow('Options:'))}
|
|
4084
|
-
${colors.cyan('port=<number>')} Port to listen on (default: 8081)
|
|
4085
|
-
${colors.cyan('host=<string>')} Host to bind to (default: 127.0.0.1)
|
|
4086
|
-
${colors.cyan('path=<string>')} SSE endpoint path (default: /events)
|
|
4087
|
-
${colors.cyan('heartbeat=<ms>')} Send heartbeat every N ms (0 = disabled)
|
|
4088
|
-
|
|
4089
3287
|
${colors.bold(colors.yellow('Examples:'))}
|
|
4090
3288
|
${colors.green('$ rek serve sse')} ${colors.gray('Start on port 8081')}
|
|
4091
|
-
${colors.green('$ rek serve sse
|
|
4092
|
-
${colors.green('$ rek serve sse heartbeat
|
|
3289
|
+
${colors.green('$ rek serve sse -p 9000')} ${colors.gray('Start on port 9000')}
|
|
3290
|
+
${colors.green('$ rek serve sse --heartbeat 5000')} ${colors.gray('Send heartbeat every 5s')}
|
|
4093
3291
|
|
|
4094
3292
|
${colors.bold(colors.yellow('Interactive Commands:'))}
|
|
4095
3293
|
Type a message and press Enter to broadcast it to all clients.
|
|
4096
3294
|
`)
|
|
4097
|
-
.action(async (
|
|
4098
|
-
let port = 8081;
|
|
4099
|
-
let host = '127.0.0.1';
|
|
4100
|
-
let path = '/events';
|
|
4101
|
-
let heartbeat = 0;
|
|
4102
|
-
for (const arg of args) {
|
|
4103
|
-
if (arg.startsWith('port='))
|
|
4104
|
-
port = parseInt(arg.split('=')[1]);
|
|
4105
|
-
else if (arg.startsWith('host='))
|
|
4106
|
-
host = arg.split('=')[1];
|
|
4107
|
-
else if (arg.startsWith('path='))
|
|
4108
|
-
path = arg.split('=')[1];
|
|
4109
|
-
else if (arg.startsWith('heartbeat='))
|
|
4110
|
-
heartbeat = parseInt(arg.split('=')[1]);
|
|
4111
|
-
}
|
|
3295
|
+
.action(async (options) => {
|
|
4112
3296
|
const { MockSSEServer } = await import('../testing/mock-sse-server.js');
|
|
4113
3297
|
const readline = await import('node:readline');
|
|
4114
3298
|
const server = await MockSSEServer.create({
|
|
4115
|
-
port,
|
|
4116
|
-
host,
|
|
4117
|
-
path,
|
|
3299
|
+
port: parseInt(options.port),
|
|
3300
|
+
host: options.host,
|
|
3301
|
+
path: options.path,
|
|
4118
3302
|
});
|
|
4119
|
-
if (heartbeat > 0) {
|
|
4120
|
-
server.startPeriodicEvents('heartbeat', heartbeat);
|
|
3303
|
+
if (parseInt(options.heartbeat) > 0) {
|
|
3304
|
+
server.startPeriodicEvents('heartbeat', parseInt(options.heartbeat));
|
|
4121
3305
|
}
|
|
4122
3306
|
console.log(colors.green(`
|
|
4123
3307
|
┌─────────────────────────────────────────────┐
|
|
4124
3308
|
│ ${colors.bold('Recker Mock SSE Server')} │
|
|
4125
3309
|
├─────────────────────────────────────────────┤
|
|
4126
3310
|
│ URL: ${colors.cyan(server.url.padEnd(37))}│
|
|
4127
|
-
│ Heartbeat: ${colors.yellow((heartbeat === 0 ? 'Disabled' : heartbeat + 'ms').padEnd(31))}│
|
|
3311
|
+
│ Heartbeat: ${colors.yellow((options.heartbeat === '0' ? 'Disabled' : options.heartbeat + 'ms').padEnd(31))}│
|
|
4128
3312
|
├─────────────────────────────────────────────┤
|
|
4129
3313
|
│ Type message + Enter to broadcast │
|
|
4130
3314
|
│ Press ${colors.bold('Ctrl+C')} to stop │
|
|
@@ -4156,51 +3340,30 @@ ${colors.bold(colors.yellow('Interactive Commands:'))}
|
|
|
4156
3340
|
serve
|
|
4157
3341
|
.command('hls')
|
|
4158
3342
|
.description('Start a mock HLS streaming server')
|
|
4159
|
-
.
|
|
3343
|
+
.option('-p, --port <number>', 'Port to listen on', '8082')
|
|
3344
|
+
.option('-h, --host <string>', 'Host to bind to', '127.0.0.1')
|
|
3345
|
+
.option('--mode <type>', 'Stream mode: vod, live, event', 'vod')
|
|
3346
|
+
.option('--segments <number>', 'Number of segments (VOD mode)', '10')
|
|
3347
|
+
.option('--duration <seconds>', 'Segment duration in seconds', '6')
|
|
3348
|
+
.option('--qualities <list>', 'Comma-separated quality variants', '720p,480p,360p')
|
|
4160
3349
|
.addHelpText('after', `
|
|
4161
|
-
${colors.bold(colors.yellow('Options:'))}
|
|
4162
|
-
${colors.cyan('port=<number>')} Port to listen on (default: 8082)
|
|
4163
|
-
${colors.cyan('host=<string>')} Host to bind to (default: 127.0.0.1)
|
|
4164
|
-
${colors.cyan('mode=<type>')} Stream mode: vod, live, event (default: vod)
|
|
4165
|
-
${colors.cyan('segments=<n>')} Number of segments for VOD (default: 10)
|
|
4166
|
-
${colors.cyan('duration=<sec>')} Segment duration in seconds (default: 6)
|
|
4167
|
-
${colors.cyan('qualities=<list>')} Comma-separated quality variants (default: 720p,480p,360p)
|
|
4168
|
-
|
|
4169
3350
|
${colors.bold(colors.yellow('Examples:'))}
|
|
4170
3351
|
${colors.green('$ rek serve hls')} ${colors.gray('Start VOD server')}
|
|
4171
|
-
${colors.green('$ rek serve hls mode
|
|
4172
|
-
${colors.green('$ rek serve hls segments
|
|
4173
|
-
${colors.green('$ rek serve hls qualities
|
|
3352
|
+
${colors.green('$ rek serve hls --mode live')} ${colors.gray('Start live stream')}
|
|
3353
|
+
${colors.green('$ rek serve hls --segments 20')} ${colors.gray('VOD with 20 segments')}
|
|
3354
|
+
${colors.green('$ rek serve hls --qualities 1080p,720p,480p')}
|
|
4174
3355
|
|
|
4175
3356
|
${colors.bold(colors.yellow('Endpoints:'))}
|
|
4176
3357
|
${colors.cyan('/master.m3u8')} Master playlist (multi-quality)
|
|
4177
3358
|
${colors.cyan('/playlist.m3u8')} Single quality playlist
|
|
4178
3359
|
${colors.cyan('/<quality>/playlist.m3u8')} Quality-specific playlist
|
|
4179
3360
|
`)
|
|
4180
|
-
.action(async (
|
|
4181
|
-
let port = 8082;
|
|
4182
|
-
let host = '127.0.0.1';
|
|
4183
|
-
let mode = 'vod';
|
|
4184
|
-
let segments = 10;
|
|
4185
|
-
let duration = 6;
|
|
4186
|
-
let qualitiesStr = '720p,480p,360p';
|
|
4187
|
-
for (const arg of args) {
|
|
4188
|
-
if (arg.startsWith('port='))
|
|
4189
|
-
port = parseInt(arg.split('=')[1]);
|
|
4190
|
-
else if (arg.startsWith('host='))
|
|
4191
|
-
host = arg.split('=')[1];
|
|
4192
|
-
else if (arg.startsWith('mode='))
|
|
4193
|
-
mode = arg.split('=')[1];
|
|
4194
|
-
else if (arg.startsWith('segments='))
|
|
4195
|
-
segments = parseInt(arg.split('=')[1]);
|
|
4196
|
-
else if (arg.startsWith('duration='))
|
|
4197
|
-
duration = parseInt(arg.split('=')[1]);
|
|
4198
|
-
else if (arg.startsWith('qualities='))
|
|
4199
|
-
qualitiesStr = arg.split('=')[1];
|
|
4200
|
-
}
|
|
3361
|
+
.action(async (options) => {
|
|
4201
3362
|
const { MockHlsServer } = await import('../testing/mock-hls-server.js');
|
|
4202
3363
|
const http = await import('node:http');
|
|
4203
|
-
const
|
|
3364
|
+
const port = parseInt(options.port);
|
|
3365
|
+
const host = options.host;
|
|
3366
|
+
const qualities = options.qualities.split(',').map(q => q.trim());
|
|
4204
3367
|
const resolutions = ['1920x1080', '1280x720', '854x480', '640x360', '426x240'];
|
|
4205
3368
|
const bandwidths = [5000000, 2500000, 1400000, 800000, 500000];
|
|
4206
3369
|
const variants = qualities.map((name, i) => ({
|
|
@@ -4211,9 +3374,9 @@ ${colors.bold(colors.yellow('Endpoints:'))}
|
|
|
4211
3374
|
const baseUrl = `http://${host}:${port}`;
|
|
4212
3375
|
const hlsServer = await MockHlsServer.create({
|
|
4213
3376
|
baseUrl,
|
|
4214
|
-
mode: mode,
|
|
4215
|
-
segmentCount: segments,
|
|
4216
|
-
segmentDuration: duration,
|
|
3377
|
+
mode: options.mode,
|
|
3378
|
+
segmentCount: parseInt(options.segments),
|
|
3379
|
+
segmentDuration: parseInt(options.duration),
|
|
4217
3380
|
multiQuality: variants.length > 1,
|
|
4218
3381
|
variants: variants.length > 1 ? variants : undefined,
|
|
4219
3382
|
});
|
|
@@ -4239,9 +3402,9 @@ ${colors.bold(colors.yellow('Endpoints:'))}
|
|
|
4239
3402
|
│ ${colors.bold('Recker Mock HLS Server')} │
|
|
4240
3403
|
├─────────────────────────────────────────────┤
|
|
4241
3404
|
│ Master: ${colors.cyan((hlsServer.manifestUrl).padEnd(34))}│
|
|
4242
|
-
│ Mode: ${colors.yellow(mode.padEnd(36))}│
|
|
4243
|
-
│ Segments: ${colors.gray(
|
|
4244
|
-
│ Duration: ${colors.gray((duration + 's').padEnd(32))}│
|
|
3405
|
+
│ Mode: ${colors.yellow(options.mode.padEnd(36))}│
|
|
3406
|
+
│ Segments: ${colors.gray(options.segments.padEnd(32))}│
|
|
3407
|
+
│ Duration: ${colors.gray((options.duration + 's').padEnd(32))}│
|
|
4245
3408
|
│ Qualities: ${colors.cyan(qualities.join(', ').padEnd(31))}│
|
|
4246
3409
|
├─────────────────────────────────────────────┤
|
|
4247
3410
|
│ Press ${colors.bold('Ctrl+C')} to stop │
|
|
@@ -4258,46 +3421,30 @@ ${colors.bold(colors.yellow('Endpoints:'))}
|
|
|
4258
3421
|
serve
|
|
4259
3422
|
.command('udp')
|
|
4260
3423
|
.description('Start a mock UDP server')
|
|
4261
|
-
.
|
|
3424
|
+
.option('-p, --port <number>', 'Port to listen on', '9000')
|
|
3425
|
+
.option('-h, --host <string>', 'Host to bind to', '127.0.0.1')
|
|
3426
|
+
.option('--echo', 'Echo messages back (default: true)', true)
|
|
3427
|
+
.option('--no-echo', 'Disable echo mode')
|
|
4262
3428
|
.addHelpText('after', `
|
|
4263
|
-
${colors.bold(colors.yellow('Options:'))}
|
|
4264
|
-
${colors.cyan('port=<number>')} Port to listen on (default: 9000)
|
|
4265
|
-
${colors.cyan('host=<string>')} Host to bind to (default: 127.0.0.1)
|
|
4266
|
-
${colors.cyan('echo')} Echo messages back (default)
|
|
4267
|
-
${colors.cyan('noecho')} Disable echo mode
|
|
4268
|
-
|
|
4269
3429
|
${colors.bold(colors.yellow('Examples:'))}
|
|
4270
3430
|
${colors.green('$ rek serve udp')} ${colors.gray('Start on port 9000')}
|
|
4271
|
-
${colors.green('$ rek serve udp
|
|
4272
|
-
${colors.green('$ rek serve udp
|
|
3431
|
+
${colors.green('$ rek serve udp -p 5353')} ${colors.gray('Start on port 5353')}
|
|
3432
|
+
${colors.green('$ rek serve udp --no-echo')} ${colors.gray('Disable echo')}
|
|
4273
3433
|
`)
|
|
4274
|
-
.action(async (
|
|
4275
|
-
let port = 9000;
|
|
4276
|
-
let host = '127.0.0.1';
|
|
4277
|
-
let echo = true;
|
|
4278
|
-
for (const arg of args) {
|
|
4279
|
-
if (arg.startsWith('port='))
|
|
4280
|
-
port = parseInt(arg.split('=')[1]);
|
|
4281
|
-
else if (arg.startsWith('host='))
|
|
4282
|
-
host = arg.split('=')[1];
|
|
4283
|
-
else if (arg === 'echo')
|
|
4284
|
-
echo = true;
|
|
4285
|
-
else if (arg === 'noecho')
|
|
4286
|
-
echo = false;
|
|
4287
|
-
}
|
|
3434
|
+
.action(async (options) => {
|
|
4288
3435
|
const { MockUDPServer } = await import('../testing/mock-udp-server.js');
|
|
4289
3436
|
const server = new MockUDPServer({
|
|
4290
|
-
port,
|
|
4291
|
-
host,
|
|
4292
|
-
echo,
|
|
3437
|
+
port: parseInt(options.port),
|
|
3438
|
+
host: options.host,
|
|
3439
|
+
echo: options.echo,
|
|
4293
3440
|
});
|
|
4294
3441
|
await server.start();
|
|
4295
3442
|
console.log(colors.green(`
|
|
4296
3443
|
┌─────────────────────────────────────────────┐
|
|
4297
3444
|
│ ${colors.bold('Recker Mock UDP Server')} │
|
|
4298
3445
|
├─────────────────────────────────────────────┤
|
|
4299
|
-
│ Address: ${colors.cyan(`${host}:${port}`.padEnd(33))}│
|
|
4300
|
-
│ Echo: ${colors.yellow((echo ? 'Enabled' : 'Disabled').padEnd(36))}│
|
|
3446
|
+
│ Address: ${colors.cyan(`${options.host}:${options.port}`.padEnd(33))}│
|
|
3447
|
+
│ Echo: ${colors.yellow((options.echo ? 'Enabled' : 'Disabled').padEnd(36))}│
|
|
4301
3448
|
├─────────────────────────────────────────────┤
|
|
4302
3449
|
│ Press ${colors.bold('Ctrl+C')} to stop │
|
|
4303
3450
|
└─────────────────────────────────────────────┘
|
|
@@ -4315,16 +3462,13 @@ ${colors.bold(colors.yellow('Examples:'))}
|
|
|
4315
3462
|
serve
|
|
4316
3463
|
.command('dns')
|
|
4317
3464
|
.description('Start a mock DNS server')
|
|
4318
|
-
.
|
|
3465
|
+
.option('-p, --port <number>', 'Port to listen on', '5353')
|
|
3466
|
+
.option('-h, --host <string>', 'Host to bind to', '127.0.0.1')
|
|
3467
|
+
.option('--delay <ms>', 'Add delay to responses (milliseconds)', '0')
|
|
4319
3468
|
.addHelpText('after', `
|
|
4320
|
-
${colors.bold(colors.yellow('Options:'))}
|
|
4321
|
-
${colors.cyan('port=<number>')} Port to listen on (default: 5353)
|
|
4322
|
-
${colors.cyan('host=<string>')} Host to bind to (default: 127.0.0.1)
|
|
4323
|
-
${colors.cyan('delay=<ms>')} Add delay to responses in ms (default: 0)
|
|
4324
|
-
|
|
4325
3469
|
${colors.bold(colors.yellow('Examples:'))}
|
|
4326
3470
|
${colors.green('$ rek serve dns')} ${colors.gray('Start on port 5353')}
|
|
4327
|
-
${colors.green('$ rek serve dns
|
|
3471
|
+
${colors.green('$ rek serve dns -p 53')} ${colors.gray('Start on standard port (requires root)')}
|
|
4328
3472
|
${colors.green('$ dig @127.0.0.1 -p 5353 example.com')} ${colors.gray('Test with dig')}
|
|
4329
3473
|
|
|
4330
3474
|
${colors.bold(colors.yellow('Default Records:'))}
|
|
@@ -4332,32 +3476,21 @@ ${colors.bold(colors.yellow('Default Records:'))}
|
|
|
4332
3476
|
${colors.cyan('example.com')} A, AAAA, NS, MX, TXT records
|
|
4333
3477
|
${colors.cyan('test.local')} A: 192.168.1.100
|
|
4334
3478
|
`)
|
|
4335
|
-
.action(async (
|
|
4336
|
-
let port = 5353;
|
|
4337
|
-
let host = '127.0.0.1';
|
|
4338
|
-
let delay = 0;
|
|
4339
|
-
for (const arg of args) {
|
|
4340
|
-
if (arg.startsWith('port='))
|
|
4341
|
-
port = parseInt(arg.split('=')[1]);
|
|
4342
|
-
else if (arg.startsWith('host='))
|
|
4343
|
-
host = arg.split('=')[1];
|
|
4344
|
-
else if (arg.startsWith('delay='))
|
|
4345
|
-
delay = parseInt(arg.split('=')[1]);
|
|
4346
|
-
}
|
|
3479
|
+
.action(async (options) => {
|
|
4347
3480
|
const { MockDnsServer } = await import('../testing/mock-dns-server.js');
|
|
4348
3481
|
const server = await MockDnsServer.create({
|
|
4349
|
-
port,
|
|
4350
|
-
host,
|
|
4351
|
-
delay,
|
|
3482
|
+
port: parseInt(options.port),
|
|
3483
|
+
host: options.host,
|
|
3484
|
+
delay: parseInt(options.delay),
|
|
4352
3485
|
});
|
|
4353
3486
|
console.log(colors.green(`
|
|
4354
3487
|
┌─────────────────────────────────────────────┐
|
|
4355
3488
|
│ ${colors.bold('Recker Mock DNS Server')} │
|
|
4356
3489
|
├─────────────────────────────────────────────┤
|
|
4357
|
-
│ Address: ${colors.cyan(`${host}:${port}`.padEnd(33))}│
|
|
3490
|
+
│ Address: ${colors.cyan(`${options.host}:${options.port}`.padEnd(33))}│
|
|
4358
3491
|
│ Protocol: ${colors.yellow('UDP'.padEnd(32))}│
|
|
4359
3492
|
├─────────────────────────────────────────────┤
|
|
4360
|
-
│ Test: dig @${host} -p ${port} example.com │
|
|
3493
|
+
│ Test: dig @${options.host} -p ${options.port} example.com │
|
|
4361
3494
|
│ Press ${colors.bold('Ctrl+C')} to stop │
|
|
4362
3495
|
└─────────────────────────────────────────────┘
|
|
4363
3496
|
`));
|
|
@@ -4373,13 +3506,10 @@ ${colors.bold(colors.yellow('Default Records:'))}
|
|
|
4373
3506
|
serve
|
|
4374
3507
|
.command('whois')
|
|
4375
3508
|
.description('Start a mock WHOIS server')
|
|
4376
|
-
.
|
|
3509
|
+
.option('-p, --port <number>', 'Port to listen on', '4343')
|
|
3510
|
+
.option('-h, --host <string>', 'Host to bind to', '127.0.0.1')
|
|
3511
|
+
.option('--delay <ms>', 'Add delay to responses (milliseconds)', '0')
|
|
4377
3512
|
.addHelpText('after', `
|
|
4378
|
-
${colors.bold(colors.yellow('Options:'))}
|
|
4379
|
-
${colors.cyan('port=<number>')} Port to listen on (default: 4343)
|
|
4380
|
-
${colors.cyan('host=<string>')} Host to bind to (default: 127.0.0.1)
|
|
4381
|
-
${colors.cyan('delay=<ms>')} Add delay to responses in ms (default: 0)
|
|
4382
|
-
|
|
4383
3513
|
${colors.bold(colors.yellow('Examples:'))}
|
|
4384
3514
|
${colors.green('$ rek serve whois')} ${colors.gray('Start on port 4343')}
|
|
4385
3515
|
${colors.green('$ whois -h 127.0.0.1 -p 4343 example.com')} ${colors.gray('Test with whois')}
|
|
@@ -4389,32 +3519,21 @@ ${colors.bold(colors.yellow('Default Domains:'))}
|
|
|
4389
3519
|
${colors.cyan('google.com')} MarkMonitor registrar
|
|
4390
3520
|
${colors.cyan('test.local')} Test domain
|
|
4391
3521
|
`)
|
|
4392
|
-
.action(async (
|
|
4393
|
-
let port = 4343;
|
|
4394
|
-
let host = '127.0.0.1';
|
|
4395
|
-
let delay = 0;
|
|
4396
|
-
for (const arg of args) {
|
|
4397
|
-
if (arg.startsWith('port='))
|
|
4398
|
-
port = parseInt(arg.split('=')[1]);
|
|
4399
|
-
else if (arg.startsWith('host='))
|
|
4400
|
-
host = arg.split('=')[1];
|
|
4401
|
-
else if (arg.startsWith('delay='))
|
|
4402
|
-
delay = parseInt(arg.split('=')[1]);
|
|
4403
|
-
}
|
|
3522
|
+
.action(async (options) => {
|
|
4404
3523
|
const { MockWhoisServer } = await import('../testing/mock-whois-server.js');
|
|
4405
3524
|
const server = await MockWhoisServer.create({
|
|
4406
|
-
port,
|
|
4407
|
-
host,
|
|
4408
|
-
delay,
|
|
3525
|
+
port: parseInt(options.port),
|
|
3526
|
+
host: options.host,
|
|
3527
|
+
delay: parseInt(options.delay),
|
|
4409
3528
|
});
|
|
4410
3529
|
console.log(colors.green(`
|
|
4411
3530
|
┌─────────────────────────────────────────────┐
|
|
4412
3531
|
│ ${colors.bold('Recker Mock WHOIS Server')} │
|
|
4413
3532
|
├─────────────────────────────────────────────┤
|
|
4414
|
-
│ Address: ${colors.cyan(`${host}:${port}`.padEnd(33))}│
|
|
3533
|
+
│ Address: ${colors.cyan(`${options.host}:${options.port}`.padEnd(33))}│
|
|
4415
3534
|
│ Protocol: ${colors.yellow('TCP'.padEnd(32))}│
|
|
4416
3535
|
├─────────────────────────────────────────────┤
|
|
4417
|
-
│ Test: whois -h ${host} -p ${port} example.com │
|
|
3536
|
+
│ Test: whois -h ${options.host} -p ${options.port} example.com │
|
|
4418
3537
|
│ Press ${colors.bold('Ctrl+C')} to stop │
|
|
4419
3538
|
└─────────────────────────────────────────────┘
|
|
4420
3539
|
`));
|
|
@@ -4430,15 +3549,12 @@ ${colors.bold(colors.yellow('Default Domains:'))}
|
|
|
4430
3549
|
serve
|
|
4431
3550
|
.command('telnet')
|
|
4432
3551
|
.description('Start a mock Telnet server')
|
|
4433
|
-
.
|
|
3552
|
+
.option('-p, --port <number>', 'Port to listen on', '2323')
|
|
3553
|
+
.option('-h, --host <string>', 'Host to bind to', '127.0.0.1')
|
|
3554
|
+
.option('--echo', 'Echo input back (default: true)', true)
|
|
3555
|
+
.option('--no-echo', 'Disable echo mode')
|
|
3556
|
+
.option('--delay <ms>', 'Add delay to responses (milliseconds)', '0')
|
|
4434
3557
|
.addHelpText('after', `
|
|
4435
|
-
${colors.bold(colors.yellow('Options:'))}
|
|
4436
|
-
${colors.cyan('port=<number>')} Port to listen on (default: 2323)
|
|
4437
|
-
${colors.cyan('host=<string>')} Host to bind to (default: 127.0.0.1)
|
|
4438
|
-
${colors.cyan('echo')} Echo input back (default)
|
|
4439
|
-
${colors.cyan('noecho')} Disable echo mode
|
|
4440
|
-
${colors.cyan('delay=<ms>')} Add delay to responses in ms (default: 0)
|
|
4441
|
-
|
|
4442
3558
|
${colors.bold(colors.yellow('Examples:'))}
|
|
4443
3559
|
${colors.green('$ rek serve telnet')} ${colors.gray('Start on port 2323')}
|
|
4444
3560
|
${colors.green('$ telnet localhost 2323')} ${colors.gray('Connect to server')}
|
|
@@ -4451,38 +3567,22 @@ ${colors.bold(colors.yellow('Built-in Commands:'))}
|
|
|
4451
3567
|
${colors.cyan('ping')} Returns "pong"
|
|
4452
3568
|
${colors.cyan('quit')} Disconnect
|
|
4453
3569
|
`)
|
|
4454
|
-
.action(async (
|
|
4455
|
-
let port = 2323;
|
|
4456
|
-
let host = '127.0.0.1';
|
|
4457
|
-
let echo = true;
|
|
4458
|
-
let delay = 0;
|
|
4459
|
-
for (const arg of args) {
|
|
4460
|
-
if (arg.startsWith('port='))
|
|
4461
|
-
port = parseInt(arg.split('=')[1]);
|
|
4462
|
-
else if (arg.startsWith('host='))
|
|
4463
|
-
host = arg.split('=')[1];
|
|
4464
|
-
else if (arg === 'echo')
|
|
4465
|
-
echo = true;
|
|
4466
|
-
else if (arg === 'noecho')
|
|
4467
|
-
echo = false;
|
|
4468
|
-
else if (arg.startsWith('delay='))
|
|
4469
|
-
delay = parseInt(arg.split('=')[1]);
|
|
4470
|
-
}
|
|
3570
|
+
.action(async (options) => {
|
|
4471
3571
|
const { MockTelnetServer } = await import('../testing/mock-telnet-server.js');
|
|
4472
3572
|
const server = await MockTelnetServer.create({
|
|
4473
|
-
port,
|
|
4474
|
-
host,
|
|
4475
|
-
echo,
|
|
4476
|
-
delay,
|
|
3573
|
+
port: parseInt(options.port),
|
|
3574
|
+
host: options.host,
|
|
3575
|
+
echo: options.echo,
|
|
3576
|
+
delay: parseInt(options.delay),
|
|
4477
3577
|
});
|
|
4478
3578
|
console.log(colors.green(`
|
|
4479
3579
|
┌─────────────────────────────────────────────┐
|
|
4480
3580
|
│ ${colors.bold('Recker Mock Telnet Server')} │
|
|
4481
3581
|
├─────────────────────────────────────────────┤
|
|
4482
|
-
│ Address: ${colors.cyan(`${host}:${port}`.padEnd(33))}│
|
|
4483
|
-
│ Echo: ${colors.yellow((echo ? 'Enabled' : 'Disabled').padEnd(36))}│
|
|
3582
|
+
│ Address: ${colors.cyan(`${options.host}:${options.port}`.padEnd(33))}│
|
|
3583
|
+
│ Echo: ${colors.yellow((options.echo ? 'Enabled' : 'Disabled').padEnd(36))}│
|
|
4484
3584
|
├─────────────────────────────────────────────┤
|
|
4485
|
-
│ Connect: telnet ${host} ${port} │
|
|
3585
|
+
│ Connect: telnet ${options.host} ${options.port} │
|
|
4486
3586
|
│ Press ${colors.bold('Ctrl+C')} to stop │
|
|
4487
3587
|
└─────────────────────────────────────────────┘
|
|
4488
3588
|
`));
|
|
@@ -4504,21 +3604,18 @@ ${colors.bold(colors.yellow('Built-in Commands:'))}
|
|
|
4504
3604
|
serve
|
|
4505
3605
|
.command('ftp')
|
|
4506
3606
|
.description('Start a mock FTP server')
|
|
4507
|
-
.
|
|
3607
|
+
.option('-p, --port <number>', 'Port to listen on', '2121')
|
|
3608
|
+
.option('-h, --host <string>', 'Host to bind to', '127.0.0.1')
|
|
3609
|
+
.option('-u, --username <user>', 'Username for auth', 'user')
|
|
3610
|
+
.option('--password <pass>', 'Password for auth', 'pass')
|
|
3611
|
+
.option('--anonymous', 'Allow anonymous login (default: true)', true)
|
|
3612
|
+
.option('--no-anonymous', 'Disable anonymous login')
|
|
3613
|
+
.option('--delay <ms>', 'Add delay to responses (milliseconds)', '0')
|
|
4508
3614
|
.addHelpText('after', `
|
|
4509
|
-
${colors.bold(colors.yellow('Options:'))}
|
|
4510
|
-
${colors.cyan('port=<number>')} Port to listen on (default: 2121)
|
|
4511
|
-
${colors.cyan('host=<string>')} Host to bind to (default: 127.0.0.1)
|
|
4512
|
-
${colors.cyan('username=<user>')} Username for auth (default: user)
|
|
4513
|
-
${colors.cyan('password=<pass>')} Password for auth (default: pass)
|
|
4514
|
-
${colors.cyan('anonymous')} Allow anonymous login (default)
|
|
4515
|
-
${colors.cyan('noanonymous')} Disable anonymous login
|
|
4516
|
-
${colors.cyan('delay=<ms>')} Add delay to responses (default: 0)
|
|
4517
|
-
|
|
4518
3615
|
${colors.bold(colors.yellow('Examples:'))}
|
|
4519
3616
|
${colors.green('$ rek serve ftp')} ${colors.gray('Start on port 2121')}
|
|
4520
3617
|
${colors.green('$ ftp localhost 2121')} ${colors.gray('Connect to server')}
|
|
4521
|
-
${colors.green('$ rek serve ftp
|
|
3618
|
+
${colors.green('$ rek serve ftp --no-anonymous')} ${colors.gray('Require authentication')}
|
|
4522
3619
|
|
|
4523
3620
|
${colors.bold(colors.yellow('Default Files:'))}
|
|
4524
3621
|
${colors.cyan('/welcome.txt')} Welcome message
|
|
@@ -4530,47 +3627,25 @@ ${colors.bold(colors.yellow('Credentials:'))}
|
|
|
4530
3627
|
Username: ${colors.cyan('user')} Password: ${colors.cyan('pass')}
|
|
4531
3628
|
Or use anonymous login with user: ${colors.cyan('anonymous')}
|
|
4532
3629
|
`)
|
|
4533
|
-
.action(async (
|
|
3630
|
+
.action(async (options) => {
|
|
4534
3631
|
const { MockFtpServer } = await import('../testing/mock-ftp-server.js');
|
|
4535
|
-
let port = 2121;
|
|
4536
|
-
let host = '127.0.0.1';
|
|
4537
|
-
let username = 'user';
|
|
4538
|
-
let password = 'pass';
|
|
4539
|
-
let anonymous = true;
|
|
4540
|
-
let delay = 0;
|
|
4541
|
-
for (const arg of args) {
|
|
4542
|
-
if (arg.startsWith('port='))
|
|
4543
|
-
port = parseInt(arg.split('=')[1]);
|
|
4544
|
-
else if (arg.startsWith('host='))
|
|
4545
|
-
host = arg.split('=')[1];
|
|
4546
|
-
else if (arg.startsWith('username='))
|
|
4547
|
-
username = arg.split('=')[1];
|
|
4548
|
-
else if (arg.startsWith('password='))
|
|
4549
|
-
password = arg.split('=')[1];
|
|
4550
|
-
else if (arg === 'anonymous')
|
|
4551
|
-
anonymous = true;
|
|
4552
|
-
else if (arg === 'noanonymous')
|
|
4553
|
-
anonymous = false;
|
|
4554
|
-
else if (arg.startsWith('delay='))
|
|
4555
|
-
delay = parseInt(arg.split('=')[1]);
|
|
4556
|
-
}
|
|
4557
3632
|
const server = await MockFtpServer.create({
|
|
4558
|
-
port,
|
|
4559
|
-
host,
|
|
4560
|
-
username,
|
|
4561
|
-
password,
|
|
4562
|
-
anonymous,
|
|
4563
|
-
delay,
|
|
3633
|
+
port: parseInt(options.port),
|
|
3634
|
+
host: options.host,
|
|
3635
|
+
username: options.username,
|
|
3636
|
+
password: options.password,
|
|
3637
|
+
anonymous: options.anonymous,
|
|
3638
|
+
delay: parseInt(options.delay),
|
|
4564
3639
|
});
|
|
4565
3640
|
console.log(colors.green(`
|
|
4566
3641
|
┌─────────────────────────────────────────────┐
|
|
4567
3642
|
│ ${colors.bold('Recker Mock FTP Server')} │
|
|
4568
3643
|
├─────────────────────────────────────────────┤
|
|
4569
|
-
│ Address: ${colors.cyan(`${host}:${port}`.padEnd(33))}│
|
|
4570
|
-
│ Anonymous: ${colors.yellow((anonymous ? 'Allowed' : 'Disabled').padEnd(31))}│
|
|
4571
|
-
│ User: ${colors.cyan(username.padEnd(36))}│
|
|
3644
|
+
│ Address: ${colors.cyan(`${options.host}:${options.port}`.padEnd(33))}│
|
|
3645
|
+
│ Anonymous: ${colors.yellow((options.anonymous ? 'Allowed' : 'Disabled').padEnd(31))}│
|
|
3646
|
+
│ User: ${colors.cyan(options.username.padEnd(36))}│
|
|
4572
3647
|
├─────────────────────────────────────────────┤
|
|
4573
|
-
│ Connect: ftp ${host} ${port} │
|
|
3648
|
+
│ Connect: ftp ${options.host} ${options.port} │
|
|
4574
3649
|
│ Press ${colors.bold('Ctrl+C')} to stop │
|
|
4575
3650
|
└─────────────────────────────────────────────┘
|
|
4576
3651
|
`));
|
|
@@ -4592,25 +3667,22 @@ ${colors.bold(colors.yellow('Credentials:'))}
|
|
|
4592
3667
|
program
|
|
4593
3668
|
.command('mcp')
|
|
4594
3669
|
.description('Start MCP server for AI agents to access Recker documentation')
|
|
4595
|
-
.
|
|
3670
|
+
.option('-t, --transport <mode>', 'Transport mode: stdio, http, sse', 'stdio')
|
|
3671
|
+
.option('-p, --port <number>', 'Server port (for http/sse modes)', '3100')
|
|
3672
|
+
.option('-d, --docs <path>', 'Path to documentation folder')
|
|
3673
|
+
.option('-T, --tools <paths...>', 'Paths to external tool modules to load')
|
|
3674
|
+
.option('--debug', 'Enable debug logging')
|
|
4596
3675
|
.addHelpText('after', `
|
|
4597
|
-
${colors.bold(colors.yellow('Options:'))}
|
|
4598
|
-
${colors.cyan('transport=<mode>')} Transport mode: stdio, http, sse (default: stdio)
|
|
4599
|
-
${colors.cyan('port=<number>')} Server port for http/sse modes (default: 3100)
|
|
4600
|
-
${colors.cyan('docs=<path>')} Path to documentation folder
|
|
4601
|
-
${colors.cyan('tools=<paths>')} Paths to external tool modules (comma-separated)
|
|
4602
|
-
${colors.cyan('debug')} Enable debug logging
|
|
4603
|
-
|
|
4604
3676
|
${colors.bold(colors.yellow('Transport Modes:'))}
|
|
4605
3677
|
${colors.cyan('stdio')} ${colors.gray('(default)')} For Claude Code and other CLI tools
|
|
4606
3678
|
${colors.cyan('http')} Simple HTTP POST endpoint
|
|
4607
3679
|
${colors.cyan('sse')} HTTP + Server-Sent Events for real-time notifications
|
|
4608
3680
|
|
|
4609
|
-
${colors.bold(colors.yellow('
|
|
4610
|
-
${colors.green('$ rek mcp')}
|
|
4611
|
-
${colors.green('$ rek mcp
|
|
4612
|
-
${colors.green('$ rek mcp
|
|
4613
|
-
${colors.green('$ rek mcp debug')}
|
|
3681
|
+
${colors.bold(colors.yellow('Usage:'))}
|
|
3682
|
+
${colors.green('$ rek mcp')} ${colors.gray('Start in stdio mode (for Claude Code)')}
|
|
3683
|
+
${colors.green('$ rek mcp -t http')} ${colors.gray('Start HTTP server on port 3100')}
|
|
3684
|
+
${colors.green('$ rek mcp -t sse -p 8080')} ${colors.gray('Start SSE server on custom port')}
|
|
3685
|
+
${colors.green('$ rek mcp --debug')} ${colors.gray('Enable debug logging')}
|
|
4614
3686
|
|
|
4615
3687
|
${colors.bold(colors.yellow('Tools provided:'))}
|
|
4616
3688
|
${colors.cyan('search_docs')} Search documentation by keyword
|
|
@@ -4626,31 +3698,15 @@ ${colors.bold(colors.yellow('Claude Code config (~/.claude.json):'))}
|
|
|
4626
3698
|
}
|
|
4627
3699
|
}`)}
|
|
4628
3700
|
`)
|
|
4629
|
-
.action(async (
|
|
3701
|
+
.action(async (options) => {
|
|
4630
3702
|
const { MCPServer } = await import('../mcp/server.js');
|
|
4631
|
-
|
|
4632
|
-
let port = 3100;
|
|
4633
|
-
let docsPath;
|
|
4634
|
-
let debug = false;
|
|
4635
|
-
let toolPaths;
|
|
4636
|
-
for (const arg of args) {
|
|
4637
|
-
if (arg.startsWith('transport='))
|
|
4638
|
-
transport = arg.split('=')[1];
|
|
4639
|
-
else if (arg.startsWith('port='))
|
|
4640
|
-
port = parseInt(arg.split('=')[1]);
|
|
4641
|
-
else if (arg.startsWith('docs='))
|
|
4642
|
-
docsPath = arg.split('=')[1];
|
|
4643
|
-
else if (arg.startsWith('tools='))
|
|
4644
|
-
toolPaths = arg.split('=')[1].split(',');
|
|
4645
|
-
else if (arg === 'debug')
|
|
4646
|
-
debug = true;
|
|
4647
|
-
}
|
|
3703
|
+
const transport = options.transport;
|
|
4648
3704
|
const server = new MCPServer({
|
|
4649
3705
|
transport,
|
|
4650
|
-
port,
|
|
4651
|
-
docsPath,
|
|
4652
|
-
debug,
|
|
4653
|
-
toolPaths,
|
|
3706
|
+
port: parseInt(options.port),
|
|
3707
|
+
docsPath: options.docs,
|
|
3708
|
+
debug: options.debug,
|
|
3709
|
+
toolPaths: options.tools,
|
|
4654
3710
|
});
|
|
4655
3711
|
if (transport === 'stdio') {
|
|
4656
3712
|
await server.start();
|
|
@@ -4669,7 +3725,7 @@ ${colors.bold(colors.yellow('Claude Code config (~/.claude.json):'))}
|
|
|
4669
3725
|
│ ${colors.bold('Recker MCP Server')} │
|
|
4670
3726
|
├─────────────────────────────────────────────┤
|
|
4671
3727
|
│ Transport: ${colors.cyan(transport.padEnd(31))}│
|
|
4672
|
-
│ Endpoint: ${colors.cyan(`http://localhost:${port}`.padEnd(32))}│
|
|
3728
|
+
│ Endpoint: ${colors.cyan(`http://localhost:${options.port}`.padEnd(32))}│
|
|
4673
3729
|
│ Docs indexed: ${colors.yellow(String(server.getDocsCount()).padEnd(28))}│
|
|
4674
3730
|
├─────────────────────────────────────────────┤${endpoints}
|
|
4675
3731
|
├─────────────────────────────────────────────┤
|
|
@@ -5345,39 +4401,21 @@ ${colors.bold(colors.yellow('Examples:'))}
|
|
|
5345
4401
|
});
|
|
5346
4402
|
program
|
|
5347
4403
|
.command('proxy')
|
|
5348
|
-
.description('
|
|
4404
|
+
.description('Make a request through a proxy')
|
|
5349
4405
|
.argument('<proxy>', 'Proxy URL (http://host:port or socks5://host:port)')
|
|
5350
4406
|
.argument('<url>', 'Target URL')
|
|
5351
4407
|
.argument('[args...]', 'Request arguments: method=x key=value key:=json Header:value')
|
|
5352
4408
|
.addHelpText('after', `
|
|
5353
|
-
${colors.bold(colors.
|
|
5354
|
-
|
|
5355
|
-
|
|
5356
|
-
|
|
5357
|
-
|
|
5358
|
-
The proxy URL can include authentication (user:pass@host:port).
|
|
5359
|
-
All standard rek request options (headers, body, method) work normally.
|
|
5360
|
-
|
|
5361
|
-
${colors.bold(colors.yellow('Supported Proxy Types:'))}
|
|
5362
|
-
http://host:port HTTP proxy
|
|
5363
|
-
https://host:port HTTPS proxy
|
|
5364
|
-
socks5://host:port SOCKS5 proxy (Tor, SSH tunnels)
|
|
5365
|
-
|
|
5366
|
-
${colors.bold(colors.yellow('Request Syntax:'))}
|
|
5367
|
-
method=<method> HTTP method (default: GET)
|
|
5368
|
-
key=value String data (form/JSON body)
|
|
5369
|
-
key:=json JSON value (numbers, booleans, objects)
|
|
5370
|
-
Header:value HTTP headers
|
|
4409
|
+
${colors.bold(colors.yellow('Parameters:'))}
|
|
4410
|
+
method=<method> HTTP method (default: GET)
|
|
4411
|
+
key=value String data
|
|
4412
|
+
key:=json JSON data
|
|
4413
|
+
Header:value HTTP headers (Key:Value format)
|
|
5371
4414
|
|
|
5372
4415
|
${colors.bold(colors.yellow('Examples:'))}
|
|
5373
|
-
${colors.green('$ rek proxy http://proxy.example.com:8080 api.com/data')}
|
|
5374
|
-
${colors.
|
|
5375
|
-
|
|
5376
|
-
${colors.green('$ rek proxy socks5://127.0.0.1:9050 api.com/users')}
|
|
5377
|
-
${colors.gray(' Route through Tor (SOCKS5 on port 9050)')}
|
|
5378
|
-
|
|
5379
|
-
${colors.green('$ rek proxy http://user:pass@proxy.com:3128 api.com method=POST name="John"')}
|
|
5380
|
-
${colors.gray(' POST with authentication')}
|
|
4416
|
+
${colors.green('$ rek proxy http://proxy.example.com:8080 https://api.com/data')}
|
|
4417
|
+
${colors.green('$ rek proxy socks5://127.0.0.1:1080 https://api.com/users')}
|
|
4418
|
+
${colors.green('$ rek proxy http://user:pass@proxy.com:3128 api.com/endpoint method=POST data=test')}
|
|
5381
4419
|
`)
|
|
5382
4420
|
.action(async (proxy, url, args) => {
|
|
5383
4421
|
const { createClient } = await import('../core/client.js');
|
|
@@ -5436,13 +4474,6 @@ ${colors.bold(colors.yellow('Examples:'))}
|
|
|
5436
4474
|
process.exit(1);
|
|
5437
4475
|
}
|
|
5438
4476
|
});
|
|
5439
|
-
function applyHelpAfterError(cmd) {
|
|
5440
|
-
cmd.showHelpAfterError(true);
|
|
5441
|
-
for (const subcmd of cmd.commands) {
|
|
5442
|
-
applyHelpAfterError(subcmd);
|
|
5443
|
-
}
|
|
5444
|
-
}
|
|
5445
|
-
applyHelpAfterError(program);
|
|
5446
4477
|
program.parse();
|
|
5447
4478
|
}
|
|
5448
4479
|
main().catch((error) => {
|