recker 1.0.29 → 1.0.30
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +28 -1
- package/dist/ai/client-ai.d.ts +41 -0
- package/dist/ai/client-ai.js +391 -0
- package/dist/ai/index.d.ts +2 -0
- package/dist/ai/index.js +2 -0
- package/dist/ai/memory.d.ts +35 -0
- package/dist/ai/memory.js +136 -0
- package/dist/browser/ai/client-ai.d.ts +41 -0
- package/dist/browser/ai/client-ai.js +391 -0
- package/dist/browser/ai/memory.d.ts +35 -0
- package/dist/browser/ai/memory.js +136 -0
- package/dist/browser/core/client.d.ts +6 -1
- package/dist/browser/core/client.js +18 -0
- package/dist/browser/transport/undici.js +11 -2
- package/dist/browser/types/ai-client.d.ts +32 -0
- package/dist/browser/types/ai-client.js +1 -0
- package/dist/browser/types/ai.d.ts +1 -1
- package/dist/cli/index.js +261 -1
- package/dist/cli/tui/scroll-buffer.js +4 -4
- package/dist/cli/tui/shell.d.ts +3 -0
- package/dist/cli/tui/shell.js +166 -19
- package/dist/core/client.d.ts +6 -1
- package/dist/core/client.js +18 -0
- package/dist/mcp/server.js +15 -0
- package/dist/mcp/tools/scrape.d.ts +3 -0
- package/dist/mcp/tools/scrape.js +156 -0
- package/dist/mcp/tools/security.d.ts +3 -0
- package/dist/mcp/tools/security.js +471 -0
- package/dist/mcp/tools/seo.d.ts +3 -0
- package/dist/mcp/tools/seo.js +427 -0
- package/dist/presets/anthropic.d.ts +3 -1
- package/dist/presets/anthropic.js +11 -1
- package/dist/presets/azure-openai.d.ts +3 -1
- package/dist/presets/azure-openai.js +11 -1
- package/dist/presets/cohere.d.ts +3 -1
- package/dist/presets/cohere.js +8 -2
- package/dist/presets/deepseek.d.ts +3 -1
- package/dist/presets/deepseek.js +8 -2
- package/dist/presets/fireworks.d.ts +3 -1
- package/dist/presets/fireworks.js +8 -2
- package/dist/presets/gemini.d.ts +3 -1
- package/dist/presets/gemini.js +8 -1
- package/dist/presets/groq.d.ts +3 -1
- package/dist/presets/groq.js +8 -2
- package/dist/presets/huggingface.d.ts +3 -1
- package/dist/presets/huggingface.js +8 -1
- package/dist/presets/mistral.d.ts +3 -1
- package/dist/presets/mistral.js +8 -2
- package/dist/presets/openai.d.ts +3 -1
- package/dist/presets/openai.js +9 -2
- package/dist/presets/perplexity.d.ts +3 -1
- package/dist/presets/perplexity.js +8 -2
- package/dist/presets/registry.d.ts +4 -0
- package/dist/presets/registry.js +48 -0
- package/dist/presets/replicate.d.ts +3 -1
- package/dist/presets/replicate.js +8 -1
- package/dist/presets/together.d.ts +3 -1
- package/dist/presets/together.js +8 -2
- package/dist/presets/xai.d.ts +3 -1
- package/dist/presets/xai.js +8 -2
- package/dist/scrape/spider.js +1 -1
- package/dist/transport/undici.js +11 -2
- package/dist/types/ai-client.d.ts +32 -0
- package/dist/types/ai-client.js +1 -0
- package/dist/types/ai.d.ts +1 -1
- package/dist/utils/colors.d.ts +2 -0
- package/dist/utils/colors.js +4 -0
- package/package.json +1 -1
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import type { AIResponse, AIStream, ChatMessage, AIProvider } from './ai.js';
|
|
2
|
+
export interface AIMemoryConfig {
|
|
3
|
+
maxPairs?: number;
|
|
4
|
+
systemPrompt?: string;
|
|
5
|
+
}
|
|
6
|
+
export interface PresetAIConfig {
|
|
7
|
+
provider: AIProvider;
|
|
8
|
+
apiKey?: string;
|
|
9
|
+
model: string;
|
|
10
|
+
baseUrl?: string;
|
|
11
|
+
memory?: AIMemoryConfig;
|
|
12
|
+
organization?: string;
|
|
13
|
+
headers?: Record<string, string>;
|
|
14
|
+
resourceName?: string;
|
|
15
|
+
deploymentName?: string;
|
|
16
|
+
apiVersion?: string;
|
|
17
|
+
}
|
|
18
|
+
export interface ClientAI {
|
|
19
|
+
chat(prompt: string): Promise<AIResponse>;
|
|
20
|
+
chatStream(prompt: string): Promise<AIStream>;
|
|
21
|
+
prompt(prompt: string): Promise<AIResponse>;
|
|
22
|
+
promptStream(prompt: string): Promise<AIStream>;
|
|
23
|
+
clearMemory(): void;
|
|
24
|
+
getMemory(): readonly ChatMessage[];
|
|
25
|
+
setMemoryConfig(config: Partial<AIMemoryConfig>): void;
|
|
26
|
+
getMemoryConfig(): AIMemoryConfig;
|
|
27
|
+
readonly provider: AIProvider;
|
|
28
|
+
readonly model: string;
|
|
29
|
+
}
|
|
30
|
+
export interface ClientOptionsWithAI {
|
|
31
|
+
_aiConfig?: PresetAIConfig;
|
|
32
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
export type AIProvider = 'openai' | 'anthropic' | 'google' | 'replicate' | 'huggingface' | 'ollama' | 'custom';
|
|
1
|
+
export type AIProvider = 'openai' | 'anthropic' | 'google' | 'replicate' | 'huggingface' | 'ollama' | 'groq' | 'mistral' | 'cohere' | 'together' | 'perplexity' | 'deepseek' | 'fireworks' | 'xai' | 'azure-openai' | 'cloudflare-workers-ai' | 'custom';
|
|
2
2
|
export type MessageRole = 'system' | 'user' | 'assistant' | 'tool';
|
|
3
3
|
export interface ChatMessage {
|
|
4
4
|
role: MessageRole;
|
package/dist/cli/index.js
CHANGED
|
@@ -330,7 +330,17 @@ complete -F _rek_completions rek
|
|
|
330
330
|
.alias('interactive')
|
|
331
331
|
.alias('repl')
|
|
332
332
|
.description('Start the interactive Rek Shell')
|
|
333
|
-
.
|
|
333
|
+
.option('-e, --env [path]', 'Load .env file (auto-loads from cwd by default)')
|
|
334
|
+
.action(async (options) => {
|
|
335
|
+
if (options.env !== false) {
|
|
336
|
+
try {
|
|
337
|
+
const envPath = typeof options.env === 'string' ? options.env : join(process.cwd(), '.env');
|
|
338
|
+
await fs.access(envPath);
|
|
339
|
+
await loadEnvFile(options.env);
|
|
340
|
+
}
|
|
341
|
+
catch {
|
|
342
|
+
}
|
|
343
|
+
}
|
|
334
344
|
const { RekShell } = await import('./tui/shell.js');
|
|
335
345
|
const shell = new RekShell();
|
|
336
346
|
shell.start();
|
|
@@ -520,6 +530,256 @@ ${colors.gray('Grade:')} ${gradeColor(colors.bold(report.grade))} ${colors.gray
|
|
|
520
530
|
process.exit(1);
|
|
521
531
|
}
|
|
522
532
|
});
|
|
533
|
+
program
|
|
534
|
+
.command('spider')
|
|
535
|
+
.description('Crawl a website following internal links')
|
|
536
|
+
.argument('<url>', 'Starting URL to crawl')
|
|
537
|
+
.option('-d, --depth <n>', 'Maximum depth to crawl', '5')
|
|
538
|
+
.option('-l, --limit <n>', 'Maximum pages to crawl', '100')
|
|
539
|
+
.option('-c, --concurrency <n>', 'Concurrent requests', '5')
|
|
540
|
+
.option('--delay <ms>', 'Delay between requests in ms', '100')
|
|
541
|
+
.option('--format <format>', 'Output format: text (default) or json', 'text')
|
|
542
|
+
.option('-o, --output <file>', 'Write JSON results to file')
|
|
543
|
+
.addHelpText('after', `
|
|
544
|
+
${colors.bold(colors.yellow('Examples:'))}
|
|
545
|
+
${colors.green('$ rek spider example.com')} ${colors.gray('Crawl with defaults')}
|
|
546
|
+
${colors.green('$ rek spider example.com -d 3 -l 50')} ${colors.gray('Depth 3, max 50 pages')}
|
|
547
|
+
${colors.green('$ rek spider example.com --format json')} ${colors.gray('Output as JSON')}
|
|
548
|
+
${colors.green('$ rek spider example.com -o results.json')} ${colors.gray('Save to file')}
|
|
549
|
+
|
|
550
|
+
${colors.bold(colors.yellow('Options:'))}
|
|
551
|
+
${colors.cyan('--depth, -d')} Max link depth to follow (default: 5)
|
|
552
|
+
${colors.cyan('--limit, -l')} Max pages to crawl (default: 100)
|
|
553
|
+
${colors.cyan('--concurrency, -c')} Parallel requests (default: 5)
|
|
554
|
+
${colors.cyan('--delay')} Ms between requests (default: 100)
|
|
555
|
+
`)
|
|
556
|
+
.action(async (url, options) => {
|
|
557
|
+
if (!url.startsWith('http'))
|
|
558
|
+
url = `https://${url}`;
|
|
559
|
+
const isJson = options.format === 'json';
|
|
560
|
+
const maxDepth = parseInt(options.depth || '5', 10);
|
|
561
|
+
const maxPages = parseInt(options.limit || '100', 10);
|
|
562
|
+
const concurrency = parseInt(options.concurrency || '5', 10);
|
|
563
|
+
const delay = parseInt(options.delay || '100', 10);
|
|
564
|
+
const { Spider } = await import('../scrape/spider.js');
|
|
565
|
+
if (!isJson) {
|
|
566
|
+
console.log(colors.gray(`\nCrawling ${url}...`));
|
|
567
|
+
console.log(colors.gray(` Depth: ${maxDepth}, Limit: ${maxPages}, Concurrency: ${concurrency}\n`));
|
|
568
|
+
}
|
|
569
|
+
try {
|
|
570
|
+
const startTime = performance.now();
|
|
571
|
+
const spider = new Spider({
|
|
572
|
+
maxDepth,
|
|
573
|
+
maxPages,
|
|
574
|
+
concurrency,
|
|
575
|
+
delay,
|
|
576
|
+
onProgress: (progress) => {
|
|
577
|
+
if (!isJson) {
|
|
578
|
+
process.stdout.write(`\r${colors.cyan('⟳')} Crawled: ${colors.bold(String(progress.crawled))} | Queued: ${progress.queued} | Current: ${colors.gray(progress.currentUrl.slice(0, 50))}...`);
|
|
579
|
+
}
|
|
580
|
+
},
|
|
581
|
+
});
|
|
582
|
+
const result = await spider.crawl(url);
|
|
583
|
+
const duration = Math.round(performance.now() - startTime);
|
|
584
|
+
if (!isJson) {
|
|
585
|
+
process.stdout.write('\r' + ' '.repeat(100) + '\r');
|
|
586
|
+
}
|
|
587
|
+
if (isJson || options.output) {
|
|
588
|
+
const jsonOutput = {
|
|
589
|
+
startUrl: result.startUrl,
|
|
590
|
+
crawledAt: new Date().toISOString(),
|
|
591
|
+
duration,
|
|
592
|
+
summary: {
|
|
593
|
+
totalPages: result.pages.length,
|
|
594
|
+
successCount: result.pages.filter(p => !p.error).length,
|
|
595
|
+
errorCount: result.errors.length,
|
|
596
|
+
uniqueUrls: result.visited.size,
|
|
597
|
+
},
|
|
598
|
+
pages: result.pages.map(p => ({
|
|
599
|
+
url: p.url,
|
|
600
|
+
status: p.status,
|
|
601
|
+
title: p.title,
|
|
602
|
+
depth: p.depth,
|
|
603
|
+
linksCount: p.links.length,
|
|
604
|
+
duration: p.duration,
|
|
605
|
+
error: p.error,
|
|
606
|
+
})),
|
|
607
|
+
errors: result.errors,
|
|
608
|
+
};
|
|
609
|
+
if (options.output) {
|
|
610
|
+
await fs.writeFile(options.output, JSON.stringify(jsonOutput, null, 2));
|
|
611
|
+
console.log(colors.green(`✓ Results saved to ${options.output}`));
|
|
612
|
+
}
|
|
613
|
+
if (isJson) {
|
|
614
|
+
console.log(JSON.stringify(jsonOutput, null, 2));
|
|
615
|
+
return;
|
|
616
|
+
}
|
|
617
|
+
}
|
|
618
|
+
console.log(`${colors.bold(colors.cyan('🕷️ Spider Crawl Complete'))}`);
|
|
619
|
+
console.log(`${colors.gray('URL:')} ${url}`);
|
|
620
|
+
console.log(`${colors.gray('Duration:')} ${(duration / 1000).toFixed(1)}s`);
|
|
621
|
+
console.log('');
|
|
622
|
+
const successCount = result.pages.filter(p => !p.error).length;
|
|
623
|
+
console.log(`${colors.bold('Summary:')}`);
|
|
624
|
+
console.log(` ${colors.green('✓')} Pages crawled: ${colors.bold(String(successCount))}`);
|
|
625
|
+
console.log(` ${colors.red('✗')} Errors: ${colors.bold(String(result.errors.length))}`);
|
|
626
|
+
console.log(` ${colors.gray('○')} Unique URLs: ${result.visited.size}`);
|
|
627
|
+
console.log('');
|
|
628
|
+
const byDepth = new Map();
|
|
629
|
+
for (const page of result.pages) {
|
|
630
|
+
byDepth.set(page.depth, (byDepth.get(page.depth) || 0) + 1);
|
|
631
|
+
}
|
|
632
|
+
console.log(`${colors.bold('Pages by Depth:')}`);
|
|
633
|
+
for (const [depth, count] of [...byDepth.entries()].sort((a, b) => a[0] - b[0])) {
|
|
634
|
+
console.log(` ${colors.gray(`Depth ${depth}:`)} ${count} pages`);
|
|
635
|
+
}
|
|
636
|
+
console.log('');
|
|
637
|
+
if (result.errors.length > 0) {
|
|
638
|
+
console.log(`${colors.bold(colors.red('Errors:'))}`);
|
|
639
|
+
for (const err of result.errors.slice(0, 10)) {
|
|
640
|
+
const shortUrl = err.url.replace(url, '');
|
|
641
|
+
const statusMatch = err.error.match(/status code (\d+)/);
|
|
642
|
+
const errorMsg = statusMatch ? `HTTP ${statusMatch[1]}` : err.error.slice(0, 50);
|
|
643
|
+
console.log(` ${colors.red('✗')} ${colors.gray(shortUrl || '/')} → ${errorMsg}`);
|
|
644
|
+
}
|
|
645
|
+
if (result.errors.length > 10) {
|
|
646
|
+
console.log(` ${colors.gray(` ... and ${result.errors.length - 10} more`)}`);
|
|
647
|
+
}
|
|
648
|
+
console.log('');
|
|
649
|
+
}
|
|
650
|
+
const successPages = result.pages.filter(p => !p.error).slice(0, 10);
|
|
651
|
+
if (successPages.length > 0) {
|
|
652
|
+
console.log(`${colors.bold('Sample Pages:')}`);
|
|
653
|
+
for (const page of successPages) {
|
|
654
|
+
const shortUrl = page.url.replace(url, '');
|
|
655
|
+
console.log(` ${colors.green('✓')} ${colors.gray((shortUrl || '/').padEnd(40))} ${page.title.slice(0, 40)}`);
|
|
656
|
+
}
|
|
657
|
+
if (result.pages.length > 10) {
|
|
658
|
+
console.log(` ${colors.gray(` ... and ${result.pages.length - 10} more`)}`);
|
|
659
|
+
}
|
|
660
|
+
}
|
|
661
|
+
}
|
|
662
|
+
catch (error) {
|
|
663
|
+
console.error(colors.red(`\nSpider failed: ${error.message}`));
|
|
664
|
+
process.exit(1);
|
|
665
|
+
}
|
|
666
|
+
});
|
|
667
|
+
program
|
|
668
|
+
.command('ai')
|
|
669
|
+
.description('Send a single AI prompt (no memory/context)')
|
|
670
|
+
.argument('<preset>', 'AI preset to use (e.g., @openai, @anthropic, @groq)')
|
|
671
|
+
.argument('<prompt...>', 'The prompt to send')
|
|
672
|
+
.option('-m, --model <model>', 'Override default model')
|
|
673
|
+
.option('-t, --temperature <temp>', 'Temperature (0-1)', '0.7')
|
|
674
|
+
.option('--max-tokens <tokens>', 'Max tokens in response', '2048')
|
|
675
|
+
.option('-w, --wait', 'Wait for full response (disable streaming)')
|
|
676
|
+
.option('-j, --json', 'Output raw JSON response')
|
|
677
|
+
.option('-e, --env [path]', 'Load .env file (auto-loads from cwd if exists)')
|
|
678
|
+
.addHelpText('after', `
|
|
679
|
+
${colors.bold(colors.yellow('Examples:'))}
|
|
680
|
+
${colors.green('$ rek ai @openai "What is the capital of France?"')}
|
|
681
|
+
${colors.green('$ rek ai @anthropic "Explain quantum computing" -m claude-sonnet-4-20250514')}
|
|
682
|
+
${colors.green('$ rek ai @groq "Write a haiku" --wait')}
|
|
683
|
+
${colors.green('$ rek ai @openai "Translate to Spanish: Hello world"')}
|
|
684
|
+
|
|
685
|
+
${colors.bold(colors.yellow('Available AI Presets:'))}
|
|
686
|
+
${colors.cyan('@openai')} OpenAI (GPT-4o, GPT-5.1)
|
|
687
|
+
${colors.cyan('@anthropic')} Anthropic (Claude)
|
|
688
|
+
${colors.cyan('@groq')} Groq (fast inference)
|
|
689
|
+
${colors.cyan('@google')} Google (Gemini)
|
|
690
|
+
${colors.cyan('@xai')} xAI (Grok)
|
|
691
|
+
${colors.cyan('@mistral')} Mistral AI
|
|
692
|
+
${colors.cyan('@cohere')} Cohere
|
|
693
|
+
${colors.cyan('@deepseek')} DeepSeek
|
|
694
|
+
${colors.cyan('@perplexity')} Perplexity
|
|
695
|
+
${colors.cyan('@together')} Together AI
|
|
696
|
+
${colors.cyan('@fireworks')} Fireworks AI
|
|
697
|
+
${colors.cyan('@replicate')} Replicate
|
|
698
|
+
${colors.cyan('@huggingface')} Hugging Face
|
|
699
|
+
|
|
700
|
+
${colors.bold(colors.yellow('Note:'))}
|
|
701
|
+
This command sends a single prompt without conversation memory.
|
|
702
|
+
For chat with memory, use: ${colors.cyan('rek shell')} then ${colors.cyan('@openai Your message')}
|
|
703
|
+
`)
|
|
704
|
+
.action(async (preset, promptParts, options) => {
|
|
705
|
+
if (options.env !== undefined) {
|
|
706
|
+
await loadEnvFile(options.env);
|
|
707
|
+
}
|
|
708
|
+
else {
|
|
709
|
+
try {
|
|
710
|
+
const envPath = join(process.cwd(), '.env');
|
|
711
|
+
await fs.access(envPath);
|
|
712
|
+
await loadEnvFile(true);
|
|
713
|
+
}
|
|
714
|
+
catch {
|
|
715
|
+
}
|
|
716
|
+
}
|
|
717
|
+
let presetName = preset;
|
|
718
|
+
if (presetName.startsWith('@')) {
|
|
719
|
+
presetName = presetName.slice(1);
|
|
720
|
+
}
|
|
721
|
+
const presetConfig = resolvePreset(presetName);
|
|
722
|
+
if (!presetConfig) {
|
|
723
|
+
console.error(colors.red(`Unknown AI preset: @${presetName}`));
|
|
724
|
+
console.log(colors.gray('Available AI presets: @openai, @anthropic, @groq, @google, @xai, @mistral, @cohere'));
|
|
725
|
+
process.exit(1);
|
|
726
|
+
}
|
|
727
|
+
if (!presetConfig._aiConfig) {
|
|
728
|
+
console.error(colors.red(`Preset @${presetName} does not support AI features.`));
|
|
729
|
+
console.log(colors.gray('Use an AI preset like @openai, @anthropic, @groq, etc.'));
|
|
730
|
+
process.exit(1);
|
|
731
|
+
}
|
|
732
|
+
const prompt = promptParts.join(' ');
|
|
733
|
+
if (!prompt.trim()) {
|
|
734
|
+
console.error(colors.red('Error: Prompt is required'));
|
|
735
|
+
process.exit(1);
|
|
736
|
+
}
|
|
737
|
+
try {
|
|
738
|
+
const { createClient } = await import('../core/client.js');
|
|
739
|
+
const client = createClient(presetConfig);
|
|
740
|
+
if (options.model) {
|
|
741
|
+
client.ai.setMemoryConfig({ systemPrompt: undefined });
|
|
742
|
+
client._aiConfig.model = options.model;
|
|
743
|
+
}
|
|
744
|
+
if (!options.json) {
|
|
745
|
+
console.log(colors.gray(`Using @${presetName} (${client._aiConfig.model})...\n`));
|
|
746
|
+
}
|
|
747
|
+
if (options.wait || options.json) {
|
|
748
|
+
const response = await client.ai.prompt(prompt);
|
|
749
|
+
if (options.json) {
|
|
750
|
+
console.log(JSON.stringify({
|
|
751
|
+
content: response.content,
|
|
752
|
+
model: response.model,
|
|
753
|
+
usage: response.usage,
|
|
754
|
+
finishReason: response.finishReason,
|
|
755
|
+
}, null, 2));
|
|
756
|
+
}
|
|
757
|
+
else {
|
|
758
|
+
console.log(response.content);
|
|
759
|
+
if (response.usage) {
|
|
760
|
+
console.log(colors.gray(`\n─────────────────────────────────────────`));
|
|
761
|
+
console.log(colors.gray(`Tokens: ${response.usage.inputTokens} in / ${response.usage.outputTokens} out`));
|
|
762
|
+
}
|
|
763
|
+
}
|
|
764
|
+
}
|
|
765
|
+
else {
|
|
766
|
+
const stream = await client.ai.promptStream(prompt);
|
|
767
|
+
for await (const event of stream) {
|
|
768
|
+
if (event.type === 'text') {
|
|
769
|
+
process.stdout.write(event.content);
|
|
770
|
+
}
|
|
771
|
+
}
|
|
772
|
+
console.log('');
|
|
773
|
+
}
|
|
774
|
+
}
|
|
775
|
+
catch (error) {
|
|
776
|
+
console.error(colors.red(`AI request failed: ${error.message}`));
|
|
777
|
+
if (error.cause) {
|
|
778
|
+
console.error(colors.gray(error.cause.message || error.cause));
|
|
779
|
+
}
|
|
780
|
+
process.exit(1);
|
|
781
|
+
}
|
|
782
|
+
});
|
|
523
783
|
program
|
|
524
784
|
.command('ip')
|
|
525
785
|
.description('Get IP address intelligence using local GeoLite2 database')
|
|
@@ -119,17 +119,17 @@ export class ScrollBuffer extends EventEmitter {
|
|
|
119
119
|
}
|
|
120
120
|
export function parseScrollKey(data) {
|
|
121
121
|
const str = data.toString();
|
|
122
|
-
if (str === '\x1b[5~' || str === '\x1bOy')
|
|
122
|
+
if (str === '\x1b[5~' || str === '\x1bOy' || str === '\x1b[5;5~' || str === '\x1b[5;2~')
|
|
123
123
|
return 'pageUp';
|
|
124
|
-
if (str === '\x1b[6~' || str === '\x1bOs')
|
|
124
|
+
if (str === '\x1b[6~' || str === '\x1bOs' || str === '\x1b[6;5~' || str === '\x1b[6;2~')
|
|
125
125
|
return 'pageDown';
|
|
126
126
|
if (str === '\x1b[1;2A')
|
|
127
127
|
return 'scrollUp';
|
|
128
128
|
if (str === '\x1b[1;2B')
|
|
129
129
|
return 'scrollDown';
|
|
130
|
-
if (str === '\x1b[H' || str === '\x1b[1~' || str === '\x1bOH')
|
|
130
|
+
if (str === '\x1b[H' || str === '\x1b[1~' || str === '\x1bOH' || str === '\x1b[7~')
|
|
131
131
|
return 'home';
|
|
132
|
-
if (str === '\x1b[F' || str === '\x1b[4~' || str === '\x1bOF')
|
|
132
|
+
if (str === '\x1b[F' || str === '\x1b[4~' || str === '\x1bOF' || str === '\x1b[8~')
|
|
133
133
|
return 'end';
|
|
134
134
|
if (str === 'q' || str === 'Q')
|
|
135
135
|
return 'quit';
|
package/dist/cli/tui/shell.d.ts
CHANGED
|
@@ -13,6 +13,7 @@ export declare class RekShell {
|
|
|
13
13
|
private scrollBuffer;
|
|
14
14
|
private originalStdoutWrite;
|
|
15
15
|
private inScrollMode;
|
|
16
|
+
private aiClients;
|
|
16
17
|
constructor();
|
|
17
18
|
private ensureInitialized;
|
|
18
19
|
private getPrompt;
|
|
@@ -31,6 +32,8 @@ export declare class RekShell {
|
|
|
31
32
|
private handleCommand;
|
|
32
33
|
private runInteractiveMode;
|
|
33
34
|
private runAIChat;
|
|
35
|
+
private runAIPresetChat;
|
|
36
|
+
private clearAIMemory;
|
|
34
37
|
private runLoadTest;
|
|
35
38
|
private parseLine;
|
|
36
39
|
private setBaseUrl;
|
package/dist/cli/tui/shell.js
CHANGED
|
@@ -16,6 +16,7 @@ import { getShellSearch } from './shell-search.js';
|
|
|
16
16
|
import { openSearchPanel } from './search-panel.js';
|
|
17
17
|
import { ScrollBuffer, parseScrollKey, parseMouseScroll, disableMouseReporting } from './scroll-buffer.js';
|
|
18
18
|
import { analyzeSeo, SeoSpider } from '../../seo/index.js';
|
|
19
|
+
import { resolvePreset } from '../presets.js';
|
|
19
20
|
let highlight;
|
|
20
21
|
async function initDependencies() {
|
|
21
22
|
if (!highlight) {
|
|
@@ -43,6 +44,7 @@ export class RekShell {
|
|
|
43
44
|
scrollBuffer;
|
|
44
45
|
originalStdoutWrite = null;
|
|
45
46
|
inScrollMode = false;
|
|
47
|
+
aiClients = new Map();
|
|
46
48
|
constructor() {
|
|
47
49
|
this.client = createClient({
|
|
48
50
|
baseUrl: 'http://localhost',
|
|
@@ -94,8 +96,10 @@ export class RekShell {
|
|
|
94
96
|
const commands = [
|
|
95
97
|
'get', 'post', 'put', 'delete', 'patch', 'head', 'options',
|
|
96
98
|
'ws', 'udp', 'load', 'chat', 'ai',
|
|
99
|
+
'@openai', '@anthropic', '@groq', '@google', '@xai', '@mistral', '@cohere', '@deepseek', '@fireworks', '@together', '@perplexity',
|
|
100
|
+
'ai:clear',
|
|
97
101
|
'whois', 'tls', 'ssl', 'security', 'ip', 'dns', 'dns:propagate', 'dns:email', 'rdap', 'ping',
|
|
98
|
-
'scrap', 'spider', '$', '$text', '$attr', '$html', '$links', '$images', '$scripts', '$css', '$sourcemaps', '$unmap', '$unmap:view', '$unmap:save', '$beautify', '$beautify:save', '$table',
|
|
102
|
+
'scrap', 'spider', 'seo', '$', '$text', '$attr', '$html', '$links', '$images', '$scripts', '$css', '$sourcemaps', '$unmap', '$unmap:view', '$unmap:save', '$beautify', '$beautify:save', '$table',
|
|
99
103
|
'?', 'search', 'suggest', 'example',
|
|
100
104
|
'help', 'clear', 'exit', 'set', 'url', 'vars', 'env'
|
|
101
105
|
];
|
|
@@ -173,20 +177,36 @@ export class RekShell {
|
|
|
173
177
|
}
|
|
174
178
|
return true;
|
|
175
179
|
}
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
if (scrollKey
|
|
179
|
-
if (
|
|
180
|
+
try {
|
|
181
|
+
const scrollKey = parseScrollKey(data);
|
|
182
|
+
if (scrollKey) {
|
|
183
|
+
if (scrollKey === 'quit') {
|
|
184
|
+
if (self.inScrollMode) {
|
|
185
|
+
self.exitScrollMode();
|
|
186
|
+
return true;
|
|
187
|
+
}
|
|
188
|
+
return originalEmit(event, ...args);
|
|
189
|
+
}
|
|
190
|
+
self.handleScrollKey(scrollKey);
|
|
191
|
+
return true;
|
|
192
|
+
}
|
|
193
|
+
if (self.inScrollMode) {
|
|
194
|
+
if (str === '\x1b[A') {
|
|
195
|
+
self.handleScrollKey('scrollUp');
|
|
196
|
+
return true;
|
|
197
|
+
}
|
|
198
|
+
if (str === '\x1b[B') {
|
|
199
|
+
self.handleScrollKey('scrollDown');
|
|
200
|
+
return true;
|
|
201
|
+
}
|
|
202
|
+
if (str === '\x1b' || str === '\x1b\x1b') {
|
|
180
203
|
self.exitScrollMode();
|
|
181
204
|
return true;
|
|
182
205
|
}
|
|
183
|
-
return
|
|
206
|
+
return true;
|
|
184
207
|
}
|
|
185
|
-
self.handleScrollKey(scrollKey);
|
|
186
|
-
return true;
|
|
187
208
|
}
|
|
188
|
-
|
|
189
|
-
return true;
|
|
209
|
+
catch {
|
|
190
210
|
}
|
|
191
211
|
}
|
|
192
212
|
return originalEmit(event, ...args);
|
|
@@ -194,6 +214,9 @@ export class RekShell {
|
|
|
194
214
|
}
|
|
195
215
|
}
|
|
196
216
|
handleScrollKey(key) {
|
|
217
|
+
if (!this.originalStdoutWrite) {
|
|
218
|
+
return;
|
|
219
|
+
}
|
|
197
220
|
let needsRedraw = false;
|
|
198
221
|
switch (key) {
|
|
199
222
|
case 'pageUp':
|
|
@@ -250,11 +273,15 @@ export class RekShell {
|
|
|
250
273
|
enterScrollMode() {
|
|
251
274
|
if (this.inScrollMode)
|
|
252
275
|
return;
|
|
276
|
+
if (!this.originalStdoutWrite)
|
|
277
|
+
return;
|
|
253
278
|
this.inScrollMode = true;
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
279
|
+
try {
|
|
280
|
+
this.rl.pause();
|
|
281
|
+
}
|
|
282
|
+
catch {
|
|
257
283
|
}
|
|
284
|
+
this.originalStdoutWrite('\x1b[?25l');
|
|
258
285
|
this.renderScrollView();
|
|
259
286
|
}
|
|
260
287
|
exitScrollMode() {
|
|
@@ -288,7 +315,7 @@ export class RekShell {
|
|
|
288
315
|
const scrollInfo = this.scrollBuffer.isScrolledUp
|
|
289
316
|
? colors.yellow(`↑ ${this.scrollBuffer.position} lines | ${info.percent}% | `)
|
|
290
317
|
: '';
|
|
291
|
-
const helpText = colors.gray('
|
|
318
|
+
const helpText = colors.gray('↑↓/PgUp/PgDn • Home/End • Esc/Q to exit');
|
|
292
319
|
const statusBar = `\x1b[${rows};1H\x1b[7m ${scrollInfo}${helpText} \x1b[0m`;
|
|
293
320
|
this.originalStdoutWrite(statusBar);
|
|
294
321
|
}
|
|
@@ -297,6 +324,21 @@ export class RekShell {
|
|
|
297
324
|
this.rl.prompt();
|
|
298
325
|
}
|
|
299
326
|
async handleCommand(input) {
|
|
327
|
+
if (input.startsWith('@')) {
|
|
328
|
+
const spaceIdx = input.indexOf(' ');
|
|
329
|
+
if (spaceIdx > 1) {
|
|
330
|
+
const presetName = input.slice(1, spaceIdx).toLowerCase();
|
|
331
|
+
const message = input.slice(spaceIdx + 1).trim();
|
|
332
|
+
if (message) {
|
|
333
|
+
await this.runAIPresetChat(presetName, message);
|
|
334
|
+
return;
|
|
335
|
+
}
|
|
336
|
+
}
|
|
337
|
+
console.log(colors.yellow('Usage: @<preset> <message>'));
|
|
338
|
+
console.log(colors.gray('Example: @openai Hello, how are you?'));
|
|
339
|
+
console.log(colors.gray('Available AI presets: openai, anthropic, groq, google, xai, mistral, cohere'));
|
|
340
|
+
return;
|
|
341
|
+
}
|
|
300
342
|
if (input.endsWith('?') && !input.startsWith('?') && input.length > 1) {
|
|
301
343
|
await this.runSearch(input.slice(0, -1).trim());
|
|
302
344
|
return;
|
|
@@ -312,6 +354,9 @@ export class RekShell {
|
|
|
312
354
|
case 'clear':
|
|
313
355
|
console.clear();
|
|
314
356
|
return;
|
|
357
|
+
case 'ai:clear':
|
|
358
|
+
this.clearAIMemory(parts[1]);
|
|
359
|
+
return;
|
|
315
360
|
case 'exit':
|
|
316
361
|
case 'quit':
|
|
317
362
|
this.rl.close();
|
|
@@ -507,6 +552,96 @@ export class RekShell {
|
|
|
507
552
|
await startAIChat(rl, provider, apiKey, model);
|
|
508
553
|
});
|
|
509
554
|
}
|
|
555
|
+
async runAIPresetChat(presetName, message) {
|
|
556
|
+
try {
|
|
557
|
+
let client = this.aiClients.get(presetName);
|
|
558
|
+
if (!client) {
|
|
559
|
+
const presetConfig = resolvePreset(presetName);
|
|
560
|
+
if (!presetConfig) {
|
|
561
|
+
console.log(colors.red(`Unknown AI preset: @${presetName}`));
|
|
562
|
+
console.log(colors.gray('Available AI presets: openai, anthropic, groq, google, xai, mistral, cohere, deepseek, fireworks, together, perplexity'));
|
|
563
|
+
return;
|
|
564
|
+
}
|
|
565
|
+
if (!presetConfig._aiConfig) {
|
|
566
|
+
console.log(colors.red(`Preset @${presetName} does not support AI features.`));
|
|
567
|
+
console.log(colors.gray('Use an AI preset like @openai, @anthropic, @groq, etc.'));
|
|
568
|
+
return;
|
|
569
|
+
}
|
|
570
|
+
client = createClient(presetConfig);
|
|
571
|
+
this.aiClients.set(presetName, client);
|
|
572
|
+
}
|
|
573
|
+
if (!client.hasAI) {
|
|
574
|
+
console.log(colors.red(`Preset @${presetName} does not have AI capabilities.`));
|
|
575
|
+
return;
|
|
576
|
+
}
|
|
577
|
+
const model = client._aiConfig?.model || presetName;
|
|
578
|
+
console.log(colors.gray(`\n${presetName} (${model}) is thinking...`));
|
|
579
|
+
const stream = await client.ai.chatStream(message);
|
|
580
|
+
process.stdout.write('\n');
|
|
581
|
+
for await (const event of stream) {
|
|
582
|
+
if (event.type === 'text') {
|
|
583
|
+
process.stdout.write(colors.orange(event.content));
|
|
584
|
+
}
|
|
585
|
+
else if (event.type === 'error') {
|
|
586
|
+
console.log(colors.red(`\nError: ${event.error}`));
|
|
587
|
+
}
|
|
588
|
+
}
|
|
589
|
+
const memory = client.ai.getMemory();
|
|
590
|
+
const pairs = Math.floor(memory.length / 2);
|
|
591
|
+
console.log(colors.reset(''));
|
|
592
|
+
console.log(colors.gray(`Memory: ${pairs}/12 pairs (${memory.length} messages)`));
|
|
593
|
+
}
|
|
594
|
+
catch (error) {
|
|
595
|
+
if (error.message?.includes('API key')) {
|
|
596
|
+
console.log(colors.red(`\nMissing API key for @${presetName}`));
|
|
597
|
+
const envVarMap = {
|
|
598
|
+
openai: 'OPENAI_API_KEY',
|
|
599
|
+
anthropic: 'ANTHROPIC_API_KEY',
|
|
600
|
+
google: 'GOOGLE_API_KEY',
|
|
601
|
+
groq: 'GROQ_API_KEY',
|
|
602
|
+
xai: 'XAI_API_KEY',
|
|
603
|
+
mistral: 'MISTRAL_API_KEY',
|
|
604
|
+
cohere: 'COHERE_API_KEY',
|
|
605
|
+
deepseek: 'DEEPSEEK_API_KEY',
|
|
606
|
+
fireworks: 'FIREWORKS_API_KEY',
|
|
607
|
+
together: 'TOGETHER_API_KEY',
|
|
608
|
+
perplexity: 'PERPLEXITY_API_KEY',
|
|
609
|
+
};
|
|
610
|
+
const envVar = envVarMap[presetName] || `${presetName.toUpperCase()}_API_KEY`;
|
|
611
|
+
console.log(colors.gray(`Set ${envVar} environment variable to use this preset.`));
|
|
612
|
+
}
|
|
613
|
+
else {
|
|
614
|
+
console.log(colors.red(`\nError: ${error.message || error}`));
|
|
615
|
+
}
|
|
616
|
+
}
|
|
617
|
+
}
|
|
618
|
+
clearAIMemory(presetName) {
|
|
619
|
+
if (presetName) {
|
|
620
|
+
const client = this.aiClients.get(presetName);
|
|
621
|
+
if (client && client.hasAI) {
|
|
622
|
+
client.ai.clearMemory();
|
|
623
|
+
console.log(colors.green(`Cleared AI memory for @${presetName}`));
|
|
624
|
+
}
|
|
625
|
+
else {
|
|
626
|
+
console.log(colors.yellow(`No active AI session for @${presetName}`));
|
|
627
|
+
}
|
|
628
|
+
}
|
|
629
|
+
else {
|
|
630
|
+
let cleared = 0;
|
|
631
|
+
for (const [name, client] of this.aiClients) {
|
|
632
|
+
if (client.hasAI) {
|
|
633
|
+
client.ai.clearMemory();
|
|
634
|
+
cleared++;
|
|
635
|
+
}
|
|
636
|
+
}
|
|
637
|
+
if (cleared > 0) {
|
|
638
|
+
console.log(colors.green(`Cleared AI memory for ${cleared} preset(s)`));
|
|
639
|
+
}
|
|
640
|
+
else {
|
|
641
|
+
console.log(colors.yellow('No active AI sessions to clear'));
|
|
642
|
+
}
|
|
643
|
+
}
|
|
644
|
+
}
|
|
510
645
|
async runLoadTest(args) {
|
|
511
646
|
let targetUrl = '';
|
|
512
647
|
let users = 50;
|
|
@@ -1494,7 +1629,7 @@ ${colors.bold('Network:')}
|
|
|
1494
1629
|
}
|
|
1495
1630
|
async runSpider(args) {
|
|
1496
1631
|
let url = '';
|
|
1497
|
-
let maxDepth =
|
|
1632
|
+
let maxDepth = 5;
|
|
1498
1633
|
let maxPages = 100;
|
|
1499
1634
|
let concurrency = 5;
|
|
1500
1635
|
let seoEnabled = false;
|
|
@@ -1502,7 +1637,7 @@ ${colors.bold('Network:')}
|
|
|
1502
1637
|
for (let i = 0; i < args.length; i++) {
|
|
1503
1638
|
const arg = args[i];
|
|
1504
1639
|
if (arg.startsWith('depth=')) {
|
|
1505
|
-
maxDepth = parseInt(arg.split('=')[1]) ||
|
|
1640
|
+
maxDepth = parseInt(arg.split('=')[1]) || 5;
|
|
1506
1641
|
}
|
|
1507
1642
|
else if (arg.startsWith('limit=')) {
|
|
1508
1643
|
maxPages = parseInt(arg.split('=')[1]) || 100;
|
|
@@ -1524,7 +1659,7 @@ ${colors.bold('Network:')}
|
|
|
1524
1659
|
if (!this.baseUrl) {
|
|
1525
1660
|
console.log(colors.yellow('Usage: spider <url> [options]'));
|
|
1526
1661
|
console.log(colors.gray(' Options:'));
|
|
1527
|
-
console.log(colors.gray(' depth=
|
|
1662
|
+
console.log(colors.gray(' depth=5 Max crawl depth'));
|
|
1528
1663
|
console.log(colors.gray(' limit=100 Max pages to crawl'));
|
|
1529
1664
|
console.log(colors.gray(' concurrency=5 Concurrent requests'));
|
|
1530
1665
|
console.log(colors.gray(' seo Enable SEO analysis'));
|
|
@@ -2656,6 +2791,17 @@ ${colors.bold('Network:')}
|
|
|
2656
2791
|
${colors.green('ws <url>')} Start interactive WebSocket session.
|
|
2657
2792
|
${colors.green('udp <url>')} Send UDP packet.
|
|
2658
2793
|
|
|
2794
|
+
${colors.bold('AI Chat:')}
|
|
2795
|
+
${colors.green('@openai <message>')} Chat with OpenAI (GPT) with memory.
|
|
2796
|
+
${colors.green('@anthropic <msg>')} Chat with Anthropic (Claude) with memory.
|
|
2797
|
+
${colors.green('@groq <message>')} Chat with Groq (fast inference).
|
|
2798
|
+
${colors.green('@google <message>')} Chat with Google (Gemini).
|
|
2799
|
+
${colors.green('@xai <message>')} Chat with xAI (Grok).
|
|
2800
|
+
${colors.green('@mistral <message>')} Chat with Mistral AI.
|
|
2801
|
+
${colors.gray('Memory:')} ${colors.white('12 pairs (24 messages)')} preserved per preset.
|
|
2802
|
+
${colors.gray('Env:')} Set ${colors.white('OPENAI_API_KEY')}, ${colors.white('ANTHROPIC_API_KEY')}, etc.
|
|
2803
|
+
${colors.green('ai:clear [preset]')} Clear AI memory (all or specific preset).
|
|
2804
|
+
|
|
2659
2805
|
${colors.bold('Network Tools:')}
|
|
2660
2806
|
${colors.green('whois <domain>')} WHOIS lookup (domain or IP).
|
|
2661
2807
|
${colors.green('tls <host> [port]')} Inspect TLS/SSL certificate.
|
|
@@ -2687,7 +2833,7 @@ ${colors.bold('Network:')}
|
|
|
2687
2833
|
${colors.bold('Web Crawler:')}
|
|
2688
2834
|
${colors.green('spider <url>')} Crawl website following internal links.
|
|
2689
2835
|
${colors.gray('Options:')}
|
|
2690
|
-
${colors.white('--depth=
|
|
2836
|
+
${colors.white('--depth=5')} ${colors.gray('Maximum depth to crawl')}
|
|
2691
2837
|
${colors.white('--limit=100')} ${colors.gray('Maximum pages to crawl')}
|
|
2692
2838
|
${colors.white('--concurrency=5')} ${colors.gray('Parallel requests')}
|
|
2693
2839
|
|
|
@@ -2707,7 +2853,8 @@ ${colors.bold('Network:')}
|
|
|
2707
2853
|
› get /json
|
|
2708
2854
|
› post /post name="Neo" active:=true role:Admin
|
|
2709
2855
|
› load /heavy-endpoint users=100 mode=stress
|
|
2710
|
-
›
|
|
2856
|
+
› @openai What is the capital of France?
|
|
2857
|
+
› @anthropic Explain quantum computing
|
|
2711
2858
|
› spider example.com depth=2 limit=50
|
|
2712
2859
|
`);
|
|
2713
2860
|
}
|