recker 1.0.14 → 1.0.15-next.0dab95d

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 (79) hide show
  1. package/README.md +78 -100
  2. package/dist/ai/client.d.ts +2 -3
  3. package/dist/ai/client.d.ts.map +1 -1
  4. package/dist/ai/client.js +5 -6
  5. package/dist/ai/index.d.ts +1 -1
  6. package/dist/ai/index.d.ts.map +1 -1
  7. package/dist/ai/index.js +1 -1
  8. package/dist/bench/generator.d.ts.map +1 -1
  9. package/dist/bench/generator.js +2 -1
  10. package/dist/bench/stats.d.ts +15 -1
  11. package/dist/bench/stats.d.ts.map +1 -1
  12. package/dist/bench/stats.js +84 -5
  13. package/dist/cli/index.js +21 -0
  14. package/dist/cli/tui/ai-chat.js +2 -2
  15. package/dist/cli/tui/load-dashboard.d.ts.map +1 -1
  16. package/dist/cli/tui/load-dashboard.js +62 -8
  17. package/dist/cli/tui/scroll-buffer.d.ts +43 -0
  18. package/dist/cli/tui/scroll-buffer.d.ts.map +1 -0
  19. package/dist/cli/tui/scroll-buffer.js +162 -0
  20. package/dist/cli/tui/search-panel.d.ts +41 -0
  21. package/dist/cli/tui/search-panel.d.ts.map +1 -0
  22. package/dist/cli/tui/search-panel.js +419 -0
  23. package/dist/cli/tui/shell.d.ts +11 -0
  24. package/dist/cli/tui/shell.d.ts.map +1 -1
  25. package/dist/cli/tui/shell.js +242 -46
  26. package/dist/contract/index.js +3 -2
  27. package/dist/dns/index.d.ts +36 -0
  28. package/dist/dns/index.d.ts.map +1 -0
  29. package/dist/dns/index.js +125 -0
  30. package/dist/dns/propagation.d.ts +19 -0
  31. package/dist/dns/propagation.d.ts.map +1 -0
  32. package/dist/dns/propagation.js +129 -0
  33. package/dist/index.d.ts +2 -0
  34. package/dist/index.d.ts.map +1 -1
  35. package/dist/index.js +2 -0
  36. package/dist/mcp/embeddings-loader.d.ts +18 -0
  37. package/dist/mcp/embeddings-loader.d.ts.map +1 -0
  38. package/dist/mcp/embeddings-loader.js +152 -0
  39. package/dist/mcp/index.d.ts +1 -0
  40. package/dist/mcp/index.d.ts.map +1 -1
  41. package/dist/mcp/index.js +1 -0
  42. package/dist/mcp/search/hybrid-search.d.ts.map +1 -1
  43. package/dist/mcp/search/hybrid-search.js +7 -21
  44. package/dist/mcp/server.d.ts +2 -0
  45. package/dist/mcp/server.d.ts.map +1 -1
  46. package/dist/mcp/server.js +8 -1
  47. package/dist/recker.d.ts +47 -0
  48. package/dist/recker.d.ts.map +1 -0
  49. package/dist/recker.js +99 -0
  50. package/dist/transport/base-udp.d.ts.map +1 -1
  51. package/dist/transport/base-udp.js +1 -0
  52. package/dist/transport/udp-response.d.ts +2 -2
  53. package/dist/transport/udp-response.d.ts.map +1 -1
  54. package/dist/transport/udp-response.js +2 -2
  55. package/dist/transport/udp.d.ts +4 -3
  56. package/dist/transport/udp.d.ts.map +1 -1
  57. package/dist/transport/udp.js +16 -7
  58. package/dist/types/udp.d.ts +1 -0
  59. package/dist/types/udp.d.ts.map +1 -1
  60. package/dist/udp/index.d.ts +2 -2
  61. package/dist/udp/index.d.ts.map +1 -1
  62. package/dist/udp/index.js +2 -2
  63. package/dist/utils/colors.d.ts +16 -0
  64. package/dist/utils/colors.d.ts.map +1 -1
  65. package/dist/utils/colors.js +16 -0
  66. package/dist/utils/tls-inspector.d.ts +6 -0
  67. package/dist/utils/tls-inspector.d.ts.map +1 -1
  68. package/dist/utils/tls-inspector.js +35 -1
  69. package/dist/utils/whois.d.ts +18 -0
  70. package/dist/utils/whois.d.ts.map +1 -1
  71. package/dist/utils/whois.js +63 -0
  72. package/dist/webrtc/index.d.ts +80 -0
  73. package/dist/webrtc/index.d.ts.map +1 -0
  74. package/dist/webrtc/index.js +311 -0
  75. package/dist/websocket/client.d.ts +1 -1
  76. package/dist/websocket/client.d.ts.map +1 -1
  77. package/dist/websocket/client.js +1 -1
  78. package/package.json +2 -2
  79. package/dist/mcp/data/embeddings.json +0 -1
@@ -0,0 +1,43 @@
1
+ import { EventEmitter } from 'node:events';
2
+ export interface ScrollBufferOptions {
3
+ maxLines?: number;
4
+ viewportHeight?: number;
5
+ }
6
+ export declare class ScrollBuffer extends EventEmitter {
7
+ private lines;
8
+ private scrollOffset;
9
+ private maxLines;
10
+ private viewportHeight;
11
+ private isScrollMode;
12
+ private originalWrite;
13
+ private pendingOutput;
14
+ constructor(options?: ScrollBufferOptions);
15
+ write(content: string): void;
16
+ flush(): void;
17
+ get lineCount(): number;
18
+ get position(): number;
19
+ get isScrolledUp(): boolean;
20
+ scrollUp(lines?: number): boolean;
21
+ scrollDown(lines?: number): boolean;
22
+ pageUp(): boolean;
23
+ pageDown(): boolean;
24
+ scrollToTop(): void;
25
+ scrollToBottom(): void;
26
+ getVisibleLines(): string[];
27
+ render(): string;
28
+ updateViewport(height?: number): void;
29
+ enterScrollMode(): void;
30
+ exitScrollMode(): void;
31
+ get inScrollMode(): boolean;
32
+ clear(): void;
33
+ getScrollInfo(): {
34
+ current: number;
35
+ total: number;
36
+ percent: number;
37
+ };
38
+ }
39
+ export declare function parseScrollKey(data: Buffer): 'pageUp' | 'pageDown' | 'scrollUp' | 'scrollDown' | 'home' | 'end' | 'escape' | null;
40
+ export declare function parseMouseScroll(data: Buffer): 'scrollUp' | 'scrollDown' | null;
41
+ export declare function enableMouseReporting(): void;
42
+ export declare function disableMouseReporting(): void;
43
+ //# sourceMappingURL=scroll-buffer.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"scroll-buffer.d.ts","sourceRoot":"","sources":["../../../src/cli/tui/scroll-buffer.ts"],"names":[],"mappings":"AAKA,OAAO,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAE3C,MAAM,WAAW,mBAAmB;IAClC,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,cAAc,CAAC,EAAE,MAAM,CAAC;CACzB;AAED,qBAAa,YAAa,SAAQ,YAAY;IAC5C,OAAO,CAAC,KAAK,CAAgB;IAC7B,OAAO,CAAC,YAAY,CAAa;IACjC,OAAO,CAAC,QAAQ,CAAS;IACzB,OAAO,CAAC,cAAc,CAAS;IAC/B,OAAO,CAAC,YAAY,CAAkB;IACtC,OAAO,CAAC,aAAa,CAA4C;IACjE,OAAO,CAAC,aAAa,CAAc;gBAEvB,OAAO,GAAE,mBAAwB;IAS7C,KAAK,CAAC,OAAO,EAAE,MAAM,GAAG,IAAI;IAuB5B,KAAK,IAAI,IAAI;IAUb,IAAI,SAAS,IAAI,MAAM,CAEtB;IAKD,IAAI,QAAQ,IAAI,MAAM,CAErB;IAKD,IAAI,YAAY,IAAI,OAAO,CAE1B;IAKD,QAAQ,CAAC,KAAK,GAAE,MAAU,GAAG,OAAO;IAcpC,UAAU,CAAC,KAAK,GAAE,MAAU,GAAG,OAAO;IAatC,MAAM,IAAI,OAAO;IAOjB,QAAQ,IAAI,OAAO;IAOnB,WAAW,IAAI,IAAI;IAOnB,cAAc,IAAI,IAAI;IAOtB,eAAe,IAAI,MAAM,EAAE;IAS3B,MAAM,IAAI,MAAM;IAsBhB,cAAc,CAAC,MAAM,CAAC,EAAE,MAAM,GAAG,IAAI;IAOrC,eAAe,IAAI,IAAI;IAcvB,cAAc,IAAI,IAAI;IAWtB,IAAI,YAAY,IAAI,OAAO,CAE1B;IAKD,KAAK,IAAI,IAAI;IASb,aAAa,IAAI;QAAE,OAAO,EAAE,MAAM,CAAC;QAAC,KAAK,EAAE,MAAM,CAAC;QAAC,OAAO,EAAE,MAAM,CAAA;KAAE;CAOrE;AAKD,wBAAgB,cAAc,CAAC,IAAI,EAAE,MAAM,GAAG,QAAQ,GAAG,UAAU,GAAG,UAAU,GAAG,YAAY,GAAG,MAAM,GAAG,KAAK,GAAG,QAAQ,GAAG,IAAI,CAyBjI;AAOD,wBAAgB,gBAAgB,CAAC,IAAI,EAAE,MAAM,GAAG,UAAU,GAAG,YAAY,GAAG,IAAI,CAqB/E;AAKD,wBAAgB,oBAAoB,IAAI,IAAI,CAK3C;AAKD,wBAAgB,qBAAqB,IAAI,IAAI,CAE5C"}
@@ -0,0 +1,162 @@
1
+ import { EventEmitter } from 'node:events';
2
+ export class ScrollBuffer extends EventEmitter {
3
+ lines = [];
4
+ scrollOffset = 0;
5
+ maxLines;
6
+ viewportHeight;
7
+ isScrollMode = false;
8
+ originalWrite = null;
9
+ pendingOutput = '';
10
+ constructor(options = {}) {
11
+ super();
12
+ this.maxLines = options.maxLines || 10000;
13
+ this.viewportHeight = options.viewportHeight || (process.stdout.rows || 24) - 2;
14
+ }
15
+ write(content) {
16
+ const parts = (this.pendingOutput + content).split('\n');
17
+ this.pendingOutput = parts.pop() || '';
18
+ for (const part of parts) {
19
+ this.lines.push(part);
20
+ }
21
+ if (this.lines.length > this.maxLines) {
22
+ this.lines = this.lines.slice(-this.maxLines);
23
+ }
24
+ if (this.scrollOffset === 0 && !this.isScrollMode) {
25
+ this.emit('output', content);
26
+ }
27
+ }
28
+ flush() {
29
+ if (this.pendingOutput) {
30
+ this.lines.push(this.pendingOutput);
31
+ this.pendingOutput = '';
32
+ }
33
+ }
34
+ get lineCount() {
35
+ return this.lines.length;
36
+ }
37
+ get position() {
38
+ return this.scrollOffset;
39
+ }
40
+ get isScrolledUp() {
41
+ return this.scrollOffset > 0;
42
+ }
43
+ scrollUp(lines = 1) {
44
+ const maxScroll = Math.max(0, this.lines.length - this.viewportHeight);
45
+ const newOffset = Math.min(this.scrollOffset + lines, maxScroll);
46
+ if (newOffset !== this.scrollOffset) {
47
+ this.scrollOffset = newOffset;
48
+ return true;
49
+ }
50
+ return false;
51
+ }
52
+ scrollDown(lines = 1) {
53
+ const newOffset = Math.max(0, this.scrollOffset - lines);
54
+ if (newOffset !== this.scrollOffset) {
55
+ this.scrollOffset = newOffset;
56
+ return true;
57
+ }
58
+ return false;
59
+ }
60
+ pageUp() {
61
+ return this.scrollUp(this.viewportHeight - 1);
62
+ }
63
+ pageDown() {
64
+ return this.scrollDown(this.viewportHeight - 1);
65
+ }
66
+ scrollToTop() {
67
+ this.scrollOffset = Math.max(0, this.lines.length - this.viewportHeight);
68
+ }
69
+ scrollToBottom() {
70
+ this.scrollOffset = 0;
71
+ }
72
+ getVisibleLines() {
73
+ const endIndex = this.lines.length - this.scrollOffset;
74
+ const startIndex = Math.max(0, endIndex - this.viewportHeight);
75
+ return this.lines.slice(startIndex, endIndex);
76
+ }
77
+ render() {
78
+ const visibleLines = this.getVisibleLines();
79
+ let output = '';
80
+ output += '\x1b[2J\x1b[H';
81
+ output += visibleLines.join('\n');
82
+ if (this.scrollOffset > 0) {
83
+ const indicator = `\x1b[7m ↑ ${this.scrollOffset} lines above | Page Down to scroll ↓ \x1b[0m`;
84
+ output += `\n${indicator}`;
85
+ }
86
+ return output;
87
+ }
88
+ updateViewport(height) {
89
+ this.viewportHeight = height || (process.stdout.rows || 24) - 2;
90
+ }
91
+ enterScrollMode() {
92
+ if (this.isScrollMode)
93
+ return;
94
+ this.isScrollMode = true;
95
+ this.flush();
96
+ this.emit('scrollModeStart');
97
+ }
98
+ exitScrollMode() {
99
+ if (!this.isScrollMode)
100
+ return;
101
+ this.isScrollMode = false;
102
+ this.scrollToBottom();
103
+ this.emit('scrollModeEnd');
104
+ }
105
+ get inScrollMode() {
106
+ return this.isScrollMode;
107
+ }
108
+ clear() {
109
+ this.lines = [];
110
+ this.scrollOffset = 0;
111
+ this.pendingOutput = '';
112
+ }
113
+ getScrollInfo() {
114
+ const total = this.lines.length;
115
+ const current = total - this.scrollOffset;
116
+ const percent = total > 0 ? Math.round((current / total) * 100) : 100;
117
+ return { current, total, percent };
118
+ }
119
+ }
120
+ export function parseScrollKey(data) {
121
+ const str = data.toString();
122
+ if (str === '\x1b[5~' || str === '\x1bOy')
123
+ return 'pageUp';
124
+ if (str === '\x1b[6~' || str === '\x1bOs')
125
+ return 'pageDown';
126
+ if (str === '\x1b[1;2A')
127
+ return 'scrollUp';
128
+ if (str === '\x1b[1;2B')
129
+ return 'scrollDown';
130
+ if (str === '\x1b[H' || str === '\x1b[1~' || str === '\x1bOH')
131
+ return 'home';
132
+ if (str === '\x1b[F' || str === '\x1b[4~' || str === '\x1bOF')
133
+ return 'end';
134
+ if (str === '\x1b' || str === '\x1b\x1b')
135
+ return 'escape';
136
+ return null;
137
+ }
138
+ export function parseMouseScroll(data) {
139
+ const str = data.toString();
140
+ const sgrMatch = str.match(/\x1b\[<(\d+);(\d+);(\d+)[Mm]/);
141
+ if (sgrMatch) {
142
+ const button = parseInt(sgrMatch[1], 10);
143
+ if (button === 64)
144
+ return 'scrollUp';
145
+ if (button === 65)
146
+ return 'scrollDown';
147
+ }
148
+ if (data.length >= 6 && data[0] === 0x1b && data[1] === 0x5b && data[2] === 0x4d) {
149
+ const button = data[3];
150
+ if (button === 96 || button === 0x60)
151
+ return 'scrollUp';
152
+ if (button === 97 || button === 0x61)
153
+ return 'scrollDown';
154
+ }
155
+ return null;
156
+ }
157
+ export function enableMouseReporting() {
158
+ process.stdout.write('\x1b[?1000h\x1b[?1006h');
159
+ }
160
+ export function disableMouseReporting() {
161
+ process.stdout.write('\x1b[?1000l\x1b[?1006l');
162
+ }
@@ -0,0 +1,41 @@
1
+ interface SearchPanelOptions {
2
+ initialQuery?: string;
3
+ docsPath?: string;
4
+ }
5
+ export declare class SearchPanel {
6
+ private rl;
7
+ private state;
8
+ private docsPath;
9
+ private running;
10
+ private termWidth;
11
+ private termHeight;
12
+ private leftPanelWidth;
13
+ private rightPanelWidth;
14
+ private contentHeight;
15
+ private previewContent;
16
+ constructor(options?: SearchPanelOptions);
17
+ private findDocsPath;
18
+ open(): Promise<void>;
19
+ close(): void;
20
+ private updateDimensions;
21
+ private handleKeyInput;
22
+ private navigateUp;
23
+ private navigateDown;
24
+ private scrollPreview;
25
+ private performSearch;
26
+ private loadPreview;
27
+ private formatMarkdown;
28
+ private wordWrap;
29
+ private stripAnsi;
30
+ private render;
31
+ private renderHeader;
32
+ private renderSearchInput;
33
+ private renderDivider;
34
+ private renderContent;
35
+ private renderLeftPanelLine;
36
+ private renderRightPanelLine;
37
+ private renderFooter;
38
+ }
39
+ export declare function openSearchPanel(query?: string): Promise<void>;
40
+ export {};
41
+ //# sourceMappingURL=search-panel.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"search-panel.d.ts","sourceRoot":"","sources":["../../../src/cli/tui/search-panel.ts"],"names":[],"mappings":"AAyCA,UAAU,kBAAkB;IAC1B,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,EAAE,MAAM,CAAC;CACnB;AAeD,qBAAa,WAAW;IACtB,OAAO,CAAC,EAAE,CAA0B;IACpC,OAAO,CAAC,KAAK,CAAa;IAC1B,OAAO,CAAC,QAAQ,CAAS;IACzB,OAAO,CAAC,OAAO,CAAS;IACxB,OAAO,CAAC,SAAS,CAAM;IACvB,OAAO,CAAC,UAAU,CAAM;IACxB,OAAO,CAAC,cAAc,CAAK;IAC3B,OAAO,CAAC,eAAe,CAAK;IAC5B,OAAO,CAAC,aAAa,CAAK;IAC1B,OAAO,CAAC,cAAc,CAAgB;gBAE1B,OAAO,GAAE,kBAAuB;IAa5C,OAAO,CAAC,YAAY;IAed,IAAI,IAAI,OAAO,CAAC,IAAI,CAAC;IAiD3B,KAAK,IAAI,IAAI;IAsBb,OAAO,CAAC,gBAAgB;YAYV,cAAc;IA8C5B,OAAO,CAAC,UAAU;IAclB,OAAO,CAAC,YAAY;IAepB,OAAO,CAAC,aAAa;YAKP,aAAa;IAgC3B,OAAO,CAAC,WAAW;IAyBnB,OAAO,CAAC,cAAc;IAyCtB,OAAO,CAAC,QAAQ;IAuBhB,OAAO,CAAC,SAAS;IAIjB,OAAO,CAAC,MAAM;IA0Bd,OAAO,CAAC,YAAY;IAapB,OAAO,CAAC,iBAAiB;IAiBzB,OAAO,CAAC,aAAa;IAcrB,OAAO,CAAC,aAAa;IAoBrB,OAAO,CAAC,mBAAmB;IAiC3B,OAAO,CAAC,oBAAoB;IA8B5B,OAAO,CAAC,YAAY;CA0BrB;AAKD,wBAAsB,eAAe,CAAC,KAAK,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAGnE"}
@@ -0,0 +1,419 @@
1
+ import { createInterface } from 'readline';
2
+ import { getShellSearch } from './shell-search.js';
3
+ import { readFileSync, existsSync } from 'fs';
4
+ import { join, dirname } from 'path';
5
+ import { fileURLToPath } from 'url';
6
+ import colors from '../../utils/colors.js';
7
+ const ESC = '\x1b';
8
+ const CLEAR_SCREEN = `${ESC}[2J`;
9
+ const CURSOR_HOME = `${ESC}[H`;
10
+ const CURSOR_HIDE = `${ESC}[?25l`;
11
+ const CURSOR_SHOW = `${ESC}[?25h`;
12
+ const CLEAR_LINE = `${ESC}[2K`;
13
+ const BOX = {
14
+ topLeft: '┌',
15
+ topRight: '┐',
16
+ bottomLeft: '└',
17
+ bottomRight: '┘',
18
+ horizontal: '─',
19
+ vertical: '│',
20
+ verticalRight: '├',
21
+ verticalLeft: '┤',
22
+ horizontalDown: '┬',
23
+ horizontalUp: '┴',
24
+ cross: '┼',
25
+ };
26
+ export class SearchPanel {
27
+ rl = null;
28
+ state;
29
+ docsPath;
30
+ running = false;
31
+ termWidth = 80;
32
+ termHeight = 24;
33
+ leftPanelWidth = 0;
34
+ rightPanelWidth = 0;
35
+ contentHeight = 0;
36
+ previewContent = [];
37
+ constructor(options = {}) {
38
+ this.state = {
39
+ query: options.initialQuery || '',
40
+ results: [],
41
+ selectedIndex: 0,
42
+ scrollOffset: 0,
43
+ previewScrollOffset: 0,
44
+ isSearching: false,
45
+ error: null,
46
+ };
47
+ this.docsPath = options.docsPath || this.findDocsPath();
48
+ }
49
+ findDocsPath() {
50
+ const candidates = [
51
+ join(process.cwd(), 'docs'),
52
+ join(dirname(fileURLToPath(import.meta.url)), '../../../docs'),
53
+ join(dirname(fileURLToPath(import.meta.url)), '../../../../docs'),
54
+ ];
55
+ for (const p of candidates) {
56
+ if (existsSync(p))
57
+ return p;
58
+ }
59
+ return candidates[0];
60
+ }
61
+ async open() {
62
+ this.running = true;
63
+ this.updateDimensions();
64
+ this.rl = createInterface({
65
+ input: process.stdin,
66
+ output: process.stdout,
67
+ terminal: true,
68
+ });
69
+ if (process.stdin.isTTY) {
70
+ process.stdin.setRawMode(true);
71
+ }
72
+ process.stdout.on('resize', () => {
73
+ this.updateDimensions();
74
+ this.render();
75
+ });
76
+ process.stdin.on('data', this.handleKeyInput.bind(this));
77
+ if (this.state.query) {
78
+ await this.performSearch();
79
+ }
80
+ this.render();
81
+ return new Promise((resolve) => {
82
+ const checkInterval = setInterval(() => {
83
+ if (!this.running) {
84
+ clearInterval(checkInterval);
85
+ resolve();
86
+ }
87
+ }, 100);
88
+ });
89
+ }
90
+ close() {
91
+ this.running = false;
92
+ if (process.stdin.isTTY) {
93
+ process.stdin.setRawMode(false);
94
+ }
95
+ process.stdout.write(CURSOR_SHOW);
96
+ process.stdout.write(CLEAR_SCREEN);
97
+ process.stdout.write(CURSOR_HOME);
98
+ if (this.rl) {
99
+ this.rl.close();
100
+ this.rl = null;
101
+ }
102
+ process.stdin.removeAllListeners('data');
103
+ process.stdout.removeAllListeners('resize');
104
+ }
105
+ updateDimensions() {
106
+ this.termWidth = process.stdout.columns || 80;
107
+ this.termHeight = process.stdout.rows || 24;
108
+ this.leftPanelWidth = Math.max(30, Math.floor(this.termWidth * 0.4));
109
+ this.rightPanelWidth = this.termWidth - this.leftPanelWidth - 1;
110
+ this.contentHeight = this.termHeight - 6;
111
+ }
112
+ async handleKeyInput(data) {
113
+ const key = data.toString();
114
+ if (key === '\x1b' || key === '\x03') {
115
+ this.close();
116
+ return;
117
+ }
118
+ if (key === '\x1b[A') {
119
+ this.navigateUp();
120
+ }
121
+ else if (key === '\x1b[B') {
122
+ this.navigateDown();
123
+ }
124
+ else if (key === '\x1b[C') {
125
+ this.scrollPreview(5);
126
+ }
127
+ else if (key === '\x1b[D') {
128
+ this.scrollPreview(-5);
129
+ }
130
+ else if (key === '\x1b[5~') {
131
+ this.scrollPreview(-this.contentHeight);
132
+ }
133
+ else if (key === '\x1b[6~') {
134
+ this.scrollPreview(this.contentHeight);
135
+ }
136
+ else if (key === '\r' || key === '\n') {
137
+ }
138
+ else if (key === '\x7f' || key === '\b') {
139
+ if (this.state.query.length > 0) {
140
+ this.state.query = this.state.query.slice(0, -1);
141
+ await this.performSearch();
142
+ }
143
+ }
144
+ else if (key.length === 1 && key >= ' ') {
145
+ this.state.query += key;
146
+ await this.performSearch();
147
+ }
148
+ this.render();
149
+ }
150
+ navigateUp() {
151
+ if (this.state.selectedIndex > 0) {
152
+ this.state.selectedIndex--;
153
+ this.state.previewScrollOffset = 0;
154
+ if (this.state.selectedIndex < this.state.scrollOffset) {
155
+ this.state.scrollOffset = this.state.selectedIndex;
156
+ }
157
+ this.loadPreview();
158
+ }
159
+ }
160
+ navigateDown() {
161
+ if (this.state.selectedIndex < this.state.results.length - 1) {
162
+ this.state.selectedIndex++;
163
+ this.state.previewScrollOffset = 0;
164
+ const visibleItems = this.contentHeight - 2;
165
+ if (this.state.selectedIndex >= this.state.scrollOffset + visibleItems) {
166
+ this.state.scrollOffset = this.state.selectedIndex - visibleItems + 1;
167
+ }
168
+ this.loadPreview();
169
+ }
170
+ }
171
+ scrollPreview(delta) {
172
+ const maxScroll = Math.max(0, this.previewContent.length - this.contentHeight + 2);
173
+ this.state.previewScrollOffset = Math.max(0, Math.min(maxScroll, this.state.previewScrollOffset + delta));
174
+ }
175
+ async performSearch() {
176
+ if (!this.state.query.trim()) {
177
+ this.state.results = [];
178
+ this.state.selectedIndex = 0;
179
+ this.state.scrollOffset = 0;
180
+ this.previewContent = [];
181
+ return;
182
+ }
183
+ this.state.isSearching = true;
184
+ this.state.error = null;
185
+ try {
186
+ const search = getShellSearch();
187
+ this.state.results = await search.search(this.state.query, { limit: 20 });
188
+ this.state.selectedIndex = 0;
189
+ this.state.scrollOffset = 0;
190
+ this.state.previewScrollOffset = 0;
191
+ if (this.state.results.length > 0) {
192
+ this.loadPreview();
193
+ }
194
+ else {
195
+ this.previewContent = [];
196
+ }
197
+ }
198
+ catch (error) {
199
+ this.state.error = error.message;
200
+ this.state.results = [];
201
+ }
202
+ finally {
203
+ this.state.isSearching = false;
204
+ }
205
+ }
206
+ loadPreview() {
207
+ const result = this.state.results[this.state.selectedIndex];
208
+ if (!result) {
209
+ this.previewContent = [];
210
+ return;
211
+ }
212
+ const fullPath = join(this.docsPath, result.path);
213
+ if (existsSync(fullPath)) {
214
+ try {
215
+ const content = readFileSync(fullPath, 'utf-8');
216
+ this.previewContent = this.formatMarkdown(content);
217
+ }
218
+ catch {
219
+ this.previewContent = this.formatMarkdown(result.content || result.snippet || 'No preview available');
220
+ }
221
+ }
222
+ else if (result.content) {
223
+ this.previewContent = this.formatMarkdown(result.content);
224
+ }
225
+ else if (result.snippet) {
226
+ this.previewContent = this.formatMarkdown(result.snippet);
227
+ }
228
+ else {
229
+ this.previewContent = ['No preview available'];
230
+ }
231
+ }
232
+ formatMarkdown(text) {
233
+ const lines = [];
234
+ const width = this.rightPanelWidth - 4;
235
+ for (const rawLine of text.split('\n')) {
236
+ let line = rawLine;
237
+ if (line.startsWith('# ')) {
238
+ line = colors.bold(colors.cyan(line.substring(2)));
239
+ }
240
+ else if (line.startsWith('## ')) {
241
+ line = colors.bold(colors.blue(line.substring(3)));
242
+ }
243
+ else if (line.startsWith('### ')) {
244
+ line = colors.bold(colors.green(line.substring(4)));
245
+ }
246
+ else if (line.startsWith('#### ')) {
247
+ line = colors.bold(line.substring(5));
248
+ }
249
+ else if (line.startsWith('```')) {
250
+ line = colors.gray(line);
251
+ }
252
+ else if (line.includes('`')) {
253
+ line = line.replace(/`([^`]+)`/g, (_, code) => colors.cyan(code));
254
+ }
255
+ if (line.includes('**')) {
256
+ line = line.replace(/\*\*([^*]+)\*\*/g, (_, text) => colors.bold(text));
257
+ }
258
+ const wrapped = this.wordWrap(line, width);
259
+ lines.push(...wrapped);
260
+ }
261
+ return lines;
262
+ }
263
+ wordWrap(text, width) {
264
+ if (this.stripAnsi(text).length <= width) {
265
+ return [text];
266
+ }
267
+ const words = text.split(' ');
268
+ const lines = [];
269
+ let currentLine = '';
270
+ for (const word of words) {
271
+ const testLine = currentLine ? `${currentLine} ${word}` : word;
272
+ if (this.stripAnsi(testLine).length <= width) {
273
+ currentLine = testLine;
274
+ }
275
+ else {
276
+ if (currentLine)
277
+ lines.push(currentLine);
278
+ currentLine = word;
279
+ }
280
+ }
281
+ if (currentLine)
282
+ lines.push(currentLine);
283
+ return lines;
284
+ }
285
+ stripAnsi(text) {
286
+ return text.replace(/\x1b\[[0-9;]*m/g, '');
287
+ }
288
+ render() {
289
+ const output = [];
290
+ output.push(CURSOR_HIDE);
291
+ output.push(CURSOR_HOME);
292
+ output.push(this.renderHeader());
293
+ output.push(this.renderSearchInput());
294
+ output.push(this.renderDivider());
295
+ output.push(...this.renderContent());
296
+ output.push(this.renderFooter());
297
+ process.stdout.write(output.join(''));
298
+ }
299
+ renderHeader() {
300
+ const title = ' Documentation Search ';
301
+ const padding = Math.max(0, Math.floor((this.termWidth - title.length) / 2));
302
+ const line = BOX.horizontal.repeat(this.termWidth);
303
+ return (colors.cyan(BOX.topLeft + line.slice(0, padding - 1)) +
304
+ colors.bold(colors.cyan(title)) +
305
+ colors.cyan(line.slice(padding + title.length - 1) + BOX.topRight) +
306
+ '\n');
307
+ }
308
+ renderSearchInput() {
309
+ const prefix = colors.cyan(BOX.vertical) + ' ' + colors.yellow('Search: ');
310
+ const query = this.state.query;
311
+ const cursor = colors.cyan('_');
312
+ const status = this.state.isSearching
313
+ ? colors.gray(' (searching...)')
314
+ : this.state.results.length > 0
315
+ ? colors.gray(` (${this.state.results.length} results)`)
316
+ : '';
317
+ const contentWidth = this.termWidth - 4;
318
+ const inputLine = query + cursor + status;
319
+ const padding = Math.max(0, contentWidth - this.stripAnsi(inputLine).length);
320
+ return prefix + inputLine + ' '.repeat(padding) + colors.cyan(BOX.vertical) + '\n';
321
+ }
322
+ renderDivider() {
323
+ const leftPart = BOX.horizontal.repeat(this.leftPanelWidth - 1);
324
+ const rightPart = BOX.horizontal.repeat(this.rightPanelWidth);
325
+ return (colors.cyan(BOX.verticalRight) +
326
+ colors.cyan(leftPart) +
327
+ colors.cyan(BOX.horizontalDown) +
328
+ colors.cyan(rightPart) +
329
+ colors.cyan(BOX.verticalLeft) +
330
+ '\n');
331
+ }
332
+ renderContent() {
333
+ const lines = [];
334
+ for (let i = 0; i < this.contentHeight; i++) {
335
+ const leftContent = this.renderLeftPanelLine(i);
336
+ const rightContent = this.renderRightPanelLine(i);
337
+ lines.push(colors.cyan(BOX.vertical) +
338
+ leftContent +
339
+ colors.cyan(BOX.vertical) +
340
+ rightContent +
341
+ colors.cyan(BOX.vertical) +
342
+ '\n');
343
+ }
344
+ return lines;
345
+ }
346
+ renderLeftPanelLine(lineIndex) {
347
+ const width = this.leftPanelWidth - 2;
348
+ const resultIndex = this.state.scrollOffset + lineIndex;
349
+ if (resultIndex >= this.state.results.length) {
350
+ return ' '.repeat(width);
351
+ }
352
+ const result = this.state.results[resultIndex];
353
+ const isSelected = resultIndex === this.state.selectedIndex;
354
+ const score = Math.round(result.score * 100);
355
+ const indexStr = ` ${resultIndex + 1}. `;
356
+ const scoreStr = ` (${score}%)`;
357
+ const maxTitleLen = width - indexStr.length - scoreStr.length - 1;
358
+ let title = result.title;
359
+ if (title.length > maxTitleLen) {
360
+ title = title.slice(0, maxTitleLen - 1) + '…';
361
+ }
362
+ let line = indexStr + title + scoreStr;
363
+ const padding = width - this.stripAnsi(line).length;
364
+ line += ' '.repeat(Math.max(0, padding));
365
+ if (isSelected) {
366
+ return colors.bgBlue(colors.white(colors.bold(line)));
367
+ }
368
+ return colors.white(indexStr) + colors.white(title) + colors.gray(scoreStr) + ' '.repeat(Math.max(0, padding));
369
+ }
370
+ renderRightPanelLine(lineIndex) {
371
+ const width = this.rightPanelWidth - 1;
372
+ const contentIndex = this.state.previewScrollOffset + lineIndex;
373
+ if (this.state.results.length === 0) {
374
+ if (lineIndex === Math.floor(this.contentHeight / 2) - 1) {
375
+ const msg = this.state.query ? 'No results found' : 'Type to search documentation';
376
+ const padding = Math.floor((width - msg.length) / 2);
377
+ return ' '.repeat(padding) + colors.gray(msg) + ' '.repeat(width - padding - msg.length);
378
+ }
379
+ return ' '.repeat(width);
380
+ }
381
+ if (contentIndex >= this.previewContent.length) {
382
+ return ' '.repeat(width);
383
+ }
384
+ let line = ' ' + this.previewContent[contentIndex];
385
+ const visibleLen = this.stripAnsi(line).length;
386
+ if (visibleLen > width) {
387
+ line = line.slice(0, width - 1) + '…';
388
+ }
389
+ else {
390
+ line += ' '.repeat(width - visibleLen);
391
+ }
392
+ return line;
393
+ }
394
+ renderFooter() {
395
+ const bottomLine = BOX.horizontal.repeat(this.termWidth - 2);
396
+ const helpText = ' ↑↓ Navigate ←→ Scroll preview ESC Close ';
397
+ const pathText = this.state.results[this.state.selectedIndex]
398
+ ? ` ${this.state.results[this.state.selectedIndex].path} `
399
+ : '';
400
+ const footerLine = colors.cyan(BOX.bottomLeft) +
401
+ colors.cyan(bottomLine) +
402
+ colors.cyan(BOX.bottomRight) +
403
+ '\n' +
404
+ colors.gray(helpText) +
405
+ ' '.repeat(Math.max(0, this.termWidth - helpText.length - pathText.length)) +
406
+ colors.cyan(pathText);
407
+ return (colors.cyan(BOX.verticalRight) +
408
+ colors.cyan(BOX.horizontal.repeat(this.leftPanelWidth - 1)) +
409
+ colors.cyan(BOX.horizontalUp) +
410
+ colors.cyan(BOX.horizontal.repeat(this.rightPanelWidth)) +
411
+ colors.cyan(BOX.verticalLeft) +
412
+ '\n' +
413
+ footerLine);
414
+ }
415
+ }
416
+ export async function openSearchPanel(query) {
417
+ const panel = new SearchPanel({ initialQuery: query });
418
+ await panel.open();
419
+ }