recker 1.0.29 → 1.0.31

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (68) hide show
  1. package/README.md +28 -1
  2. package/dist/ai/client-ai.d.ts +41 -0
  3. package/dist/ai/client-ai.js +391 -0
  4. package/dist/ai/index.d.ts +2 -0
  5. package/dist/ai/index.js +2 -0
  6. package/dist/ai/memory.d.ts +35 -0
  7. package/dist/ai/memory.js +136 -0
  8. package/dist/browser/ai/client-ai.d.ts +41 -0
  9. package/dist/browser/ai/client-ai.js +391 -0
  10. package/dist/browser/ai/memory.d.ts +35 -0
  11. package/dist/browser/ai/memory.js +136 -0
  12. package/dist/browser/core/client.d.ts +6 -1
  13. package/dist/browser/core/client.js +18 -0
  14. package/dist/browser/transport/undici.js +11 -2
  15. package/dist/browser/types/ai-client.d.ts +32 -0
  16. package/dist/browser/types/ai-client.js +1 -0
  17. package/dist/browser/types/ai.d.ts +1 -1
  18. package/dist/cli/index.js +402 -1
  19. package/dist/cli/tui/scroll-buffer.js +4 -4
  20. package/dist/cli/tui/shell.d.ts +3 -0
  21. package/dist/cli/tui/shell.js +166 -19
  22. package/dist/core/client.d.ts +6 -1
  23. package/dist/core/client.js +18 -0
  24. package/dist/mcp/server.js +15 -0
  25. package/dist/mcp/tools/scrape.d.ts +3 -0
  26. package/dist/mcp/tools/scrape.js +156 -0
  27. package/dist/mcp/tools/security.d.ts +3 -0
  28. package/dist/mcp/tools/security.js +471 -0
  29. package/dist/mcp/tools/seo.d.ts +3 -0
  30. package/dist/mcp/tools/seo.js +427 -0
  31. package/dist/presets/anthropic.d.ts +3 -1
  32. package/dist/presets/anthropic.js +11 -1
  33. package/dist/presets/azure-openai.d.ts +3 -1
  34. package/dist/presets/azure-openai.js +11 -1
  35. package/dist/presets/cohere.d.ts +3 -1
  36. package/dist/presets/cohere.js +8 -2
  37. package/dist/presets/deepseek.d.ts +3 -1
  38. package/dist/presets/deepseek.js +8 -2
  39. package/dist/presets/fireworks.d.ts +3 -1
  40. package/dist/presets/fireworks.js +8 -2
  41. package/dist/presets/gemini.d.ts +3 -1
  42. package/dist/presets/gemini.js +8 -1
  43. package/dist/presets/groq.d.ts +3 -1
  44. package/dist/presets/groq.js +8 -2
  45. package/dist/presets/huggingface.d.ts +3 -1
  46. package/dist/presets/huggingface.js +8 -1
  47. package/dist/presets/mistral.d.ts +3 -1
  48. package/dist/presets/mistral.js +8 -2
  49. package/dist/presets/openai.d.ts +3 -1
  50. package/dist/presets/openai.js +9 -2
  51. package/dist/presets/perplexity.d.ts +3 -1
  52. package/dist/presets/perplexity.js +8 -2
  53. package/dist/presets/registry.d.ts +4 -0
  54. package/dist/presets/registry.js +48 -0
  55. package/dist/presets/replicate.d.ts +3 -1
  56. package/dist/presets/replicate.js +8 -1
  57. package/dist/presets/together.d.ts +3 -1
  58. package/dist/presets/together.js +8 -2
  59. package/dist/presets/xai.d.ts +3 -1
  60. package/dist/presets/xai.js +8 -2
  61. package/dist/scrape/spider.js +1 -1
  62. package/dist/transport/undici.js +11 -2
  63. package/dist/types/ai-client.d.ts +32 -0
  64. package/dist/types/ai-client.js +1 -0
  65. package/dist/types/ai.d.ts +1 -1
  66. package/dist/utils/colors.d.ts +2 -0
  67. package/dist/utils/colors.js +4 -0
  68. 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
- .action(async () => {
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,397 @@ ${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
+ .argument('[args...]', 'Options: depth=N limit=N concurrency=N seo output=file.json')
538
+ .addHelpText('after', `
539
+ ${colors.bold(colors.yellow('Examples:'))}
540
+ ${colors.green('$ rek spider example.com')} ${colors.gray('Crawl with defaults')}
541
+ ${colors.green('$ rek spider example.com depth=3 limit=50')} ${colors.gray('Depth 3, max 50 pages')}
542
+ ${colors.green('$ rek spider example.com seo')} ${colors.gray('Crawl + SEO analysis')}
543
+ ${colors.green('$ rek spider example.com seo output=report.json')} ${colors.gray('SEO with JSON export')}
544
+
545
+ ${colors.bold(colors.yellow('Options:'))}
546
+ ${colors.cyan('depth=N')} Max link depth to follow (default: 5)
547
+ ${colors.cyan('limit=N')} Max pages to crawl (default: 100)
548
+ ${colors.cyan('concurrency=N')} Parallel requests (default: 5)
549
+ ${colors.cyan('seo')} Enable SEO analysis mode
550
+ ${colors.cyan('output=file.json')} Save JSON report to file
551
+ `)
552
+ .action(async (url, args) => {
553
+ let maxDepth = 5;
554
+ let maxPages = 100;
555
+ let concurrency = 5;
556
+ let seoEnabled = false;
557
+ let outputFile = '';
558
+ for (const arg of args) {
559
+ if (arg.startsWith('depth=')) {
560
+ maxDepth = parseInt(arg.split('=')[1]) || 5;
561
+ }
562
+ else if (arg.startsWith('limit=')) {
563
+ maxPages = parseInt(arg.split('=')[1]) || 100;
564
+ }
565
+ else if (arg.startsWith('concurrency=')) {
566
+ concurrency = parseInt(arg.split('=')[1]) || 5;
567
+ }
568
+ else if (arg === 'seo') {
569
+ seoEnabled = true;
570
+ }
571
+ else if (arg.startsWith('output=')) {
572
+ outputFile = arg.split('=')[1] || '';
573
+ }
574
+ }
575
+ if (!url.startsWith('http'))
576
+ url = `https://${url}`;
577
+ const modeLabel = seoEnabled ? colors.magenta(' + SEO') : '';
578
+ console.log(colors.cyan(`\nSpider starting: ${url}`));
579
+ console.log(colors.gray(` Depth: ${maxDepth} | Limit: ${maxPages} | Concurrency: ${concurrency}${modeLabel}`));
580
+ if (outputFile) {
581
+ console.log(colors.gray(` Output: ${outputFile}`));
582
+ }
583
+ console.log('');
584
+ try {
585
+ if (seoEnabled) {
586
+ const { SeoSpider } = await import('../seo/index.js');
587
+ const seoSpider = new SeoSpider({
588
+ maxDepth,
589
+ maxPages,
590
+ concurrency,
591
+ sameDomain: true,
592
+ delay: 100,
593
+ seo: true,
594
+ output: outputFile || undefined,
595
+ onProgress: (progress) => {
596
+ process.stdout.write(`\r${colors.gray(' Crawling:')} ${colors.cyan(progress.crawled.toString())} pages | ${colors.gray('Queue:')} ${progress.queued} | ${colors.gray('Depth:')} ${progress.depth} `);
597
+ },
598
+ });
599
+ const result = await seoSpider.crawl(url);
600
+ process.stdout.write('\r' + ' '.repeat(80) + '\r');
601
+ console.log(colors.green(`\n✔ SEO Spider complete`) + colors.gray(` (${(result.duration / 1000).toFixed(1)}s)`));
602
+ console.log(` ${colors.cyan('Pages crawled')}: ${result.pages.length}`);
603
+ console.log(` ${colors.cyan('Unique URLs')}: ${result.visited.size}`);
604
+ console.log(` ${colors.cyan('Avg SEO Score')}: ${result.summary.avgScore}/100`);
605
+ const responseTimes = result.pages.filter(p => p.duration > 0).map(p => p.duration);
606
+ const avgResponseTime = responseTimes.length > 0
607
+ ? Math.round(responseTimes.reduce((a, b) => a + b, 0) / responseTimes.length)
608
+ : 0;
609
+ const minResponseTime = responseTimes.length > 0 ? Math.min(...responseTimes) : 0;
610
+ const maxResponseTime = responseTimes.length > 0 ? Math.max(...responseTimes) : 0;
611
+ const reqPerSec = result.duration > 0 ? (result.pages.length / (result.duration / 1000)).toFixed(1) : '0';
612
+ const statusCounts = new Map();
613
+ for (const page of result.pages) {
614
+ const status = page.status || 0;
615
+ statusCounts.set(status, (statusCounts.get(status) || 0) + 1);
616
+ }
617
+ let totalInternalLinks = 0;
618
+ let totalExternalLinks = 0;
619
+ let totalImages = 0;
620
+ let imagesWithoutAlt = 0;
621
+ let pagesWithoutTitle = 0;
622
+ let pagesWithoutDescription = 0;
623
+ for (const page of result.pages) {
624
+ if (page.seoReport) {
625
+ totalInternalLinks += page.seoReport.links?.internal || 0;
626
+ totalExternalLinks += page.seoReport.links?.external || 0;
627
+ totalImages += page.seoReport.images?.total || 0;
628
+ imagesWithoutAlt += page.seoReport.images?.withoutAlt || 0;
629
+ if (!page.seoReport.title?.text)
630
+ pagesWithoutTitle++;
631
+ if (!page.seoReport.metaDescription?.text)
632
+ pagesWithoutDescription++;
633
+ }
634
+ }
635
+ console.log(colors.bold('\n Performance:'));
636
+ console.log(` ${colors.gray('Avg Response:')} ${avgResponseTime}ms`);
637
+ console.log(` ${colors.gray('Min/Max:')} ${minResponseTime}ms / ${maxResponseTime}ms`);
638
+ console.log(` ${colors.gray('Throughput:')} ${reqPerSec} req/s`);
639
+ console.log(colors.bold('\n HTTP Status:'));
640
+ const sortedStatuses = Array.from(statusCounts.entries()).sort((a, b) => b[1] - a[1]);
641
+ for (const [status, count] of sortedStatuses.slice(0, 5)) {
642
+ const statusLabel = status === 0 ? 'Error' : status.toString();
643
+ const statusColor = status >= 400 || status === 0 ? colors.red :
644
+ status >= 300 ? colors.yellow : colors.green;
645
+ const pct = ((count / result.pages.length) * 100).toFixed(0);
646
+ console.log(` ${statusColor(statusLabel.padEnd(5))} ${count.toString().padStart(3)} (${pct}%)`);
647
+ }
648
+ console.log(colors.bold('\n Content:'));
649
+ console.log(` ${colors.gray('Internal links:')} ${totalInternalLinks.toLocaleString()}`);
650
+ console.log(` ${colors.gray('External links:')} ${totalExternalLinks.toLocaleString()}`);
651
+ console.log(` ${colors.gray('Images:')} ${totalImages.toLocaleString()} (${imagesWithoutAlt} missing alt)`);
652
+ console.log(` ${colors.gray('Missing title:')} ${pagesWithoutTitle}`);
653
+ console.log(` ${colors.gray('Missing desc:')} ${pagesWithoutDescription}`);
654
+ console.log(colors.bold('\n SEO Summary:'));
655
+ const { summary } = result;
656
+ console.log(` ${colors.red('✗')} Pages with errors: ${summary.pagesWithErrors}`);
657
+ console.log(` ${colors.yellow('⚠')} Pages with warnings: ${summary.pagesWithWarnings}`);
658
+ console.log(` ${colors.magenta('⚐')} Duplicate titles: ${summary.duplicateTitles}`);
659
+ console.log(` ${colors.magenta('⚐')} Duplicate descriptions:${summary.duplicateDescriptions}`);
660
+ console.log(` ${colors.magenta('⚐')} Duplicate H1s: ${summary.duplicateH1s}`);
661
+ console.log(` ${colors.gray('○')} Orphan pages: ${summary.orphanPages}`);
662
+ if (result.siteWideIssues.length > 0) {
663
+ console.log(colors.bold('\n Site-Wide Issues:'));
664
+ for (const issue of result.siteWideIssues.slice(0, 10)) {
665
+ const icon = issue.severity === 'error' ? colors.red('✗') :
666
+ issue.severity === 'warning' ? colors.yellow('⚠') : colors.gray('○');
667
+ console.log(` ${icon} ${issue.message}`);
668
+ if (issue.value) {
669
+ const truncatedValue = issue.value.length > 50 ? issue.value.slice(0, 47) + '...' : issue.value;
670
+ console.log(` ${colors.gray(`"${truncatedValue}"`)}`);
671
+ }
672
+ const uniquePaths = [...new Set(issue.affectedUrls.map(u => new URL(u).pathname))];
673
+ if (uniquePaths.length <= 3) {
674
+ for (const path of uniquePaths) {
675
+ console.log(` ${colors.gray('→')} ${path}`);
676
+ }
677
+ }
678
+ else {
679
+ console.log(` ${colors.gray(`→ ${uniquePaths.length} pages affected`)}`);
680
+ }
681
+ }
682
+ if (result.siteWideIssues.length > 10) {
683
+ console.log(colors.gray(` ... and ${result.siteWideIssues.length - 10} more issues`));
684
+ }
685
+ }
686
+ const pagesWithScores = result.pages
687
+ .filter(p => p.seoReport)
688
+ .sort((a, b) => (a.seoReport?.score || 0) - (b.seoReport?.score || 0));
689
+ const seenPaths = new Set();
690
+ const uniquePages = pagesWithScores.filter(page => {
691
+ const path = new URL(page.url).pathname;
692
+ if (seenPaths.has(path))
693
+ return false;
694
+ seenPaths.add(path);
695
+ return true;
696
+ });
697
+ if (uniquePages.length > 0) {
698
+ console.log(colors.bold('\n Pages by SEO Score:'));
699
+ const worstPages = uniquePages.slice(0, 5);
700
+ for (const page of worstPages) {
701
+ const score = page.seoReport?.score || 0;
702
+ const grade = page.seoReport?.grade || '?';
703
+ const path = new URL(page.url).pathname;
704
+ const scoreColor = score >= 80 ? colors.green : score >= 60 ? colors.yellow : colors.red;
705
+ console.log(` ${scoreColor(`${score.toString().padStart(3)}`)} ${colors.gray(`[${grade}]`)} ${path.slice(0, 50)}`);
706
+ }
707
+ if (uniquePages.length > 5) {
708
+ console.log(colors.gray(` ... and ${uniquePages.length - 5} more pages`));
709
+ }
710
+ }
711
+ if (outputFile) {
712
+ console.log(colors.green(`\n Report saved to: ${outputFile}`));
713
+ }
714
+ }
715
+ else {
716
+ const { Spider } = await import('../scrape/spider.js');
717
+ const spider = new Spider({
718
+ maxDepth,
719
+ maxPages,
720
+ concurrency,
721
+ sameDomain: true,
722
+ delay: 100,
723
+ onProgress: (progress) => {
724
+ process.stdout.write(`\r${colors.gray(' Crawling:')} ${colors.cyan(progress.crawled.toString())} pages | ${colors.gray('Queue:')} ${progress.queued} | ${colors.gray('Depth:')} ${progress.depth} `);
725
+ },
726
+ });
727
+ const result = await spider.crawl(url);
728
+ process.stdout.write('\r' + ' '.repeat(80) + '\r');
729
+ console.log(colors.green(`\n✔ Spider complete`) + colors.gray(` (${(result.duration / 1000).toFixed(1)}s)`));
730
+ console.log(` ${colors.cyan('Pages crawled')}: ${result.pages.length}`);
731
+ console.log(` ${colors.cyan('Unique URLs')}: ${result.visited.size}`);
732
+ console.log(` ${colors.cyan('Errors')}: ${result.errors.length}`);
733
+ const byDepth = new Map();
734
+ for (const page of result.pages) {
735
+ byDepth.set(page.depth, (byDepth.get(page.depth) || 0) + 1);
736
+ }
737
+ console.log(colors.bold('\n Pages by depth:'));
738
+ for (const [depth, count] of Array.from(byDepth.entries()).sort((a, b) => a[0] - b[0])) {
739
+ const bar = '█'.repeat(Math.min(count, 40));
740
+ console.log(` ${colors.gray(`d${depth}:`)} ${bar} ${count}`);
741
+ }
742
+ const topPages = [...result.pages]
743
+ .filter(p => !p.error)
744
+ .sort((a, b) => b.links.length - a.links.length)
745
+ .slice(0, 10);
746
+ if (topPages.length > 0) {
747
+ console.log(colors.bold('\n Top pages by outgoing links:'));
748
+ for (const page of topPages) {
749
+ const title = page.title.slice(0, 40) || new URL(page.url).pathname;
750
+ console.log(` ${colors.cyan(page.links.length.toString().padStart(3))} ${title}`);
751
+ }
752
+ }
753
+ const formatError = (error) => {
754
+ const statusMatch = error.match(/status code (\d{3})/i);
755
+ if (statusMatch) {
756
+ return `HTTP ${statusMatch[1]}`;
757
+ }
758
+ return error.length > 50 ? error.slice(0, 47) + '...' : error;
759
+ };
760
+ if (result.errors.length > 0 && result.errors.length <= 10) {
761
+ console.log(colors.bold('\n Errors:'));
762
+ for (const err of result.errors) {
763
+ const path = new URL(err.url).pathname;
764
+ console.log(` ${colors.red('✗')} ${path.padEnd(30)} → ${formatError(err.error)}`);
765
+ }
766
+ }
767
+ else if (result.errors.length > 10) {
768
+ console.log(colors.bold('\n Errors:'));
769
+ for (const err of result.errors.slice(0, 5)) {
770
+ const path = new URL(err.url).pathname;
771
+ console.log(` ${colors.red('✗')} ${path.padEnd(30)} → ${formatError(err.error)}`);
772
+ }
773
+ console.log(colors.gray(` ... and ${result.errors.length - 5} more errors`));
774
+ }
775
+ if (outputFile) {
776
+ const jsonOutput = {
777
+ startUrl: result.startUrl,
778
+ crawledAt: new Date().toISOString(),
779
+ duration: result.duration,
780
+ summary: {
781
+ totalPages: result.pages.length,
782
+ successCount: result.pages.filter(p => !p.error).length,
783
+ errorCount: result.errors.length,
784
+ uniqueUrls: result.visited.size,
785
+ },
786
+ pages: result.pages.map(p => ({
787
+ url: p.url,
788
+ status: p.status,
789
+ title: p.title,
790
+ depth: p.depth,
791
+ linksCount: p.links.length,
792
+ duration: p.duration,
793
+ error: p.error,
794
+ })),
795
+ errors: result.errors,
796
+ };
797
+ await fs.writeFile(outputFile, JSON.stringify(jsonOutput, null, 2));
798
+ console.log(colors.green(`\n Report saved to: ${outputFile}`));
799
+ }
800
+ }
801
+ console.log('');
802
+ }
803
+ catch (error) {
804
+ console.error(colors.red(`\nSpider failed: ${error.message}`));
805
+ process.exit(1);
806
+ }
807
+ });
808
+ program
809
+ .command('ai')
810
+ .description('Send a single AI prompt (no memory/context)')
811
+ .argument('<preset>', 'AI preset to use (e.g., @openai, @anthropic, @groq)')
812
+ .argument('<prompt...>', 'The prompt to send')
813
+ .option('-m, --model <model>', 'Override default model')
814
+ .option('-t, --temperature <temp>', 'Temperature (0-1)', '0.7')
815
+ .option('--max-tokens <tokens>', 'Max tokens in response', '2048')
816
+ .option('-w, --wait', 'Wait for full response (disable streaming)')
817
+ .option('-j, --json', 'Output raw JSON response')
818
+ .option('-e, --env [path]', 'Load .env file (auto-loads from cwd if exists)')
819
+ .addHelpText('after', `
820
+ ${colors.bold(colors.yellow('Examples:'))}
821
+ ${colors.green('$ rek ai @openai "What is the capital of France?"')}
822
+ ${colors.green('$ rek ai @anthropic "Explain quantum computing" -m claude-sonnet-4-20250514')}
823
+ ${colors.green('$ rek ai @groq "Write a haiku" --wait')}
824
+ ${colors.green('$ rek ai @openai "Translate to Spanish: Hello world"')}
825
+
826
+ ${colors.bold(colors.yellow('Available AI Presets:'))}
827
+ ${colors.cyan('@openai')} OpenAI (GPT-4o, GPT-5.1)
828
+ ${colors.cyan('@anthropic')} Anthropic (Claude)
829
+ ${colors.cyan('@groq')} Groq (fast inference)
830
+ ${colors.cyan('@google')} Google (Gemini)
831
+ ${colors.cyan('@xai')} xAI (Grok)
832
+ ${colors.cyan('@mistral')} Mistral AI
833
+ ${colors.cyan('@cohere')} Cohere
834
+ ${colors.cyan('@deepseek')} DeepSeek
835
+ ${colors.cyan('@perplexity')} Perplexity
836
+ ${colors.cyan('@together')} Together AI
837
+ ${colors.cyan('@fireworks')} Fireworks AI
838
+ ${colors.cyan('@replicate')} Replicate
839
+ ${colors.cyan('@huggingface')} Hugging Face
840
+
841
+ ${colors.bold(colors.yellow('Note:'))}
842
+ This command sends a single prompt without conversation memory.
843
+ For chat with memory, use: ${colors.cyan('rek shell')} then ${colors.cyan('@openai Your message')}
844
+ `)
845
+ .action(async (preset, promptParts, options) => {
846
+ if (options.env !== undefined) {
847
+ await loadEnvFile(options.env);
848
+ }
849
+ else {
850
+ try {
851
+ const envPath = join(process.cwd(), '.env');
852
+ await fs.access(envPath);
853
+ await loadEnvFile(true);
854
+ }
855
+ catch {
856
+ }
857
+ }
858
+ let presetName = preset;
859
+ if (presetName.startsWith('@')) {
860
+ presetName = presetName.slice(1);
861
+ }
862
+ const presetConfig = resolvePreset(presetName);
863
+ if (!presetConfig) {
864
+ console.error(colors.red(`Unknown AI preset: @${presetName}`));
865
+ console.log(colors.gray('Available AI presets: @openai, @anthropic, @groq, @google, @xai, @mistral, @cohere'));
866
+ process.exit(1);
867
+ }
868
+ if (!presetConfig._aiConfig) {
869
+ console.error(colors.red(`Preset @${presetName} does not support AI features.`));
870
+ console.log(colors.gray('Use an AI preset like @openai, @anthropic, @groq, etc.'));
871
+ process.exit(1);
872
+ }
873
+ const prompt = promptParts.join(' ');
874
+ if (!prompt.trim()) {
875
+ console.error(colors.red('Error: Prompt is required'));
876
+ process.exit(1);
877
+ }
878
+ try {
879
+ const { createClient } = await import('../core/client.js');
880
+ const client = createClient(presetConfig);
881
+ if (options.model) {
882
+ client.ai.setMemoryConfig({ systemPrompt: undefined });
883
+ client._aiConfig.model = options.model;
884
+ }
885
+ if (!options.json) {
886
+ console.log(colors.gray(`Using @${presetName} (${client._aiConfig.model})...\n`));
887
+ }
888
+ if (options.wait || options.json) {
889
+ const response = await client.ai.prompt(prompt);
890
+ if (options.json) {
891
+ console.log(JSON.stringify({
892
+ content: response.content,
893
+ model: response.model,
894
+ usage: response.usage,
895
+ finishReason: response.finishReason,
896
+ }, null, 2));
897
+ }
898
+ else {
899
+ console.log(response.content);
900
+ if (response.usage) {
901
+ console.log(colors.gray(`\n─────────────────────────────────────────`));
902
+ console.log(colors.gray(`Tokens: ${response.usage.inputTokens} in / ${response.usage.outputTokens} out`));
903
+ }
904
+ }
905
+ }
906
+ else {
907
+ const stream = await client.ai.promptStream(prompt);
908
+ for await (const event of stream) {
909
+ if (event.type === 'text') {
910
+ process.stdout.write(event.content);
911
+ }
912
+ }
913
+ console.log('');
914
+ }
915
+ }
916
+ catch (error) {
917
+ console.error(colors.red(`AI request failed: ${error.message}`));
918
+ if (error.cause) {
919
+ console.error(colors.gray(error.cause.message || error.cause));
920
+ }
921
+ process.exit(1);
922
+ }
923
+ });
523
924
  program
524
925
  .command('ip')
525
926
  .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';
@@ -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;