recker 1.0.15-next.7944604 → 1.0.15-next.c7370be
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 +21 -0
- package/dist/cli/tui/scroll-buffer.d.ts +43 -0
- package/dist/cli/tui/scroll-buffer.d.ts.map +1 -0
- package/dist/cli/tui/scroll-buffer.js +162 -0
- package/dist/cli/tui/search-panel.d.ts +41 -0
- package/dist/cli/tui/search-panel.d.ts.map +1 -0
- package/dist/cli/tui/search-panel.js +419 -0
- package/dist/cli/tui/shell.d.ts +11 -0
- package/dist/cli/tui/shell.d.ts.map +1 -1
- package/dist/cli/tui/shell.js +189 -30
- package/dist/dns/index.d.ts +1 -0
- package/dist/dns/index.d.ts.map +1 -1
- package/dist/dns/index.js +1 -0
- package/dist/dns/propagation.d.ts +19 -0
- package/dist/dns/propagation.d.ts.map +1 -0
- package/dist/dns/propagation.js +129 -0
- package/package.json +1 -1
package/dist/cli/index.js
CHANGED
|
@@ -270,6 +270,27 @@ complete -F _rek_completions rek
|
|
|
270
270
|
const shell = new RekShell();
|
|
271
271
|
shell.start();
|
|
272
272
|
});
|
|
273
|
+
program
|
|
274
|
+
.command('docs [query...]')
|
|
275
|
+
.alias('?')
|
|
276
|
+
.description('Search Recker documentation (opens fullscreen panel)')
|
|
277
|
+
.action(async (queryParts) => {
|
|
278
|
+
const query = queryParts.join(' ').trim();
|
|
279
|
+
const { openSearchPanel } = await import('./tui/search-panel.js');
|
|
280
|
+
await openSearchPanel(query || undefined);
|
|
281
|
+
});
|
|
282
|
+
const dns = program.command('dns').description('DNS tools and diagnostics');
|
|
283
|
+
dns
|
|
284
|
+
.command('propagate')
|
|
285
|
+
.description('Check global DNS propagation across multiple providers')
|
|
286
|
+
.argument('<domain>', 'Domain name to check')
|
|
287
|
+
.argument('[type]', 'Record type (A, AAAA, CNAME, MX, NS, TXT)', 'A')
|
|
288
|
+
.action(async (domain, type) => {
|
|
289
|
+
const { checkPropagation, formatPropagationReport } = await import('../dns/propagation.js');
|
|
290
|
+
console.log(pc.gray(`Checking propagation for ${domain} (${type})...`));
|
|
291
|
+
const results = await checkPropagation(domain, type);
|
|
292
|
+
console.log(formatPropagationReport(results, domain, type));
|
|
293
|
+
});
|
|
273
294
|
const bench = program.command('bench').description('Performance benchmarking tools');
|
|
274
295
|
bench
|
|
275
296
|
.command('load')
|
|
@@ -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
|
+
}
|
package/dist/cli/tui/shell.d.ts
CHANGED
|
@@ -10,6 +10,9 @@ export declare class RekShell {
|
|
|
10
10
|
private initialized;
|
|
11
11
|
private currentDoc;
|
|
12
12
|
private currentDocUrl;
|
|
13
|
+
private scrollBuffer;
|
|
14
|
+
private originalStdoutWrite;
|
|
15
|
+
private inScrollMode;
|
|
13
16
|
constructor();
|
|
14
17
|
private ensureInitialized;
|
|
15
18
|
private getPrompt;
|
|
@@ -17,6 +20,13 @@ export declare class RekShell {
|
|
|
17
20
|
private getRootDomain;
|
|
18
21
|
private completer;
|
|
19
22
|
start(): Promise<void>;
|
|
23
|
+
private setupScrollCapture;
|
|
24
|
+
private cleanupScrollCapture;
|
|
25
|
+
private setupScrollKeyHandler;
|
|
26
|
+
private handleScrollKey;
|
|
27
|
+
private enterScrollMode;
|
|
28
|
+
private exitScrollMode;
|
|
29
|
+
private renderScrollView;
|
|
20
30
|
private prompt;
|
|
21
31
|
private handleCommand;
|
|
22
32
|
private runInteractiveMode;
|
|
@@ -33,6 +43,7 @@ export declare class RekShell {
|
|
|
33
43
|
private runWhois;
|
|
34
44
|
private runTLS;
|
|
35
45
|
private runDNS;
|
|
46
|
+
private runDNSPropagation;
|
|
36
47
|
private runRDAP;
|
|
37
48
|
private runPing;
|
|
38
49
|
private runScrap;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"shell.d.ts","sourceRoot":"","sources":["../../../src/cli/tui/shell.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"shell.d.ts","sourceRoot":"","sources":["../../../src/cli/tui/shell.ts"],"names":[],"mappings":"AAsCA,qBAAa,QAAQ;IACnB,OAAO,CAAC,EAAE,CAAsB;IAChC,OAAO,CAAC,MAAM,CAAM;IACpB,OAAO,CAAC,OAAO,CAAqB;IACpC,OAAO,CAAC,OAAO,CAAc;IAC7B,OAAO,CAAC,YAAY,CAAa;IACjC,OAAO,CAAC,SAAS,CAA2B;IAC5C,OAAO,CAAC,OAAO,CAA8B;IAC7C,OAAO,CAAC,SAAS,CAAkB;IACnC,OAAO,CAAC,WAAW,CAAS;IAC5B,OAAO,CAAC,UAAU,CAA+B;IACjD,OAAO,CAAC,aAAa,CAAc;IACnC,OAAO,CAAC,YAAY,CAAe;IACnC,OAAO,CAAC,mBAAmB,CAA4C;IACvE,OAAO,CAAC,YAAY,CAAkB;;YAgBxB,iBAAiB;IAe/B,OAAO,CAAC,SAAS;IAMjB,OAAO,CAAC,aAAa;IAUrB,OAAO,CAAC,aAAa;IAiBrB,OAAO,CAAC,SAAS;IAcJ,KAAK;IA8ClB,OAAO,CAAC,kBAAkB;IA2B1B,OAAO,CAAC,oBAAoB;IAW5B,OAAO,CAAC,qBAAqB;IA2B7B,OAAO,CAAC,eAAe;IAmEvB,OAAO,CAAC,eAAe;IAmBvB,OAAO,CAAC,cAAc;IA0BtB,OAAO,CAAC,gBAAgB;IA8BxB,OAAO,CAAC,MAAM;YAKA,aAAa;YAoMb,kBAAkB;YAkBlB,SAAS;YAkBT,WAAW;IA0DzB,OAAO,CAAC,SAAS;IAMjB,OAAO,CAAC,UAAU;IAMlB,OAAO,CAAC,WAAW;IASnB,OAAO,CAAC,QAAQ;IAoCV,WAAW,CAAC,QAAQ,CAAC,EAAE,MAAM;IA6CnC,OAAO,CAAC,gBAAgB;IAqBxB,OAAO,CAAC,UAAU;YAeJ,cAAc;YAyEd,QAAQ;YA8GR,MAAM;YA2DN,MAAM;YA2EN,iBAAiB;YAuBjB,OAAO;YA+DP,OAAO;YA0CP,QAAQ;YAoER,SAAS;YAsCT,aAAa;YA8Bb,aAAa;YA+Bb,aAAa;YA6Bb,cAAc;YAkCd,eAAe;YA+Ef,gBAAgB;YAmEhB,YAAY;YAiEZ,mBAAmB;YAsFnB,QAAQ;YA0FR,YAAY;YAoCZ,YAAY;YA6CZ,WAAW;IA6CzB,OAAO,CAAC,UAAU;IA4GlB,OAAO,CAAC,WAAW;YAgFL,eAAe;YAkBf,cAAc;YAgDd,SAAS;YAgBT,UAAU;YAuBV,UAAU;IAwBxB,OAAO,CAAC,aAAa;IAuCrB,OAAO,CAAC,SAAS;CA8ElB"}
|
package/dist/cli/tui/shell.js
CHANGED
|
@@ -12,6 +12,8 @@ import { rdap } from '../../utils/rdap.js';
|
|
|
12
12
|
import { ScrapeDocument } from '../../scrape/document.js';
|
|
13
13
|
import colors from '../../utils/colors.js';
|
|
14
14
|
import { getShellSearch } from './shell-search.js';
|
|
15
|
+
import { openSearchPanel } from './search-panel.js';
|
|
16
|
+
import { ScrollBuffer, parseScrollKey, parseMouseScroll, enableMouseReporting, disableMouseReporting } from './scroll-buffer.js';
|
|
15
17
|
let highlight;
|
|
16
18
|
async function initDependencies() {
|
|
17
19
|
if (!highlight) {
|
|
@@ -36,11 +38,16 @@ export class RekShell {
|
|
|
36
38
|
initialized = false;
|
|
37
39
|
currentDoc = null;
|
|
38
40
|
currentDocUrl = '';
|
|
41
|
+
scrollBuffer;
|
|
42
|
+
originalStdoutWrite = null;
|
|
43
|
+
inScrollMode = false;
|
|
39
44
|
constructor() {
|
|
40
45
|
this.client = createClient({
|
|
41
46
|
baseUrl: 'http://localhost',
|
|
42
|
-
checkHooks: false
|
|
47
|
+
checkHooks: false,
|
|
48
|
+
http2: true
|
|
43
49
|
});
|
|
50
|
+
this.scrollBuffer = new ScrollBuffer({ maxLines: 10000 });
|
|
44
51
|
}
|
|
45
52
|
async ensureInitialized() {
|
|
46
53
|
if (this.initialized)
|
|
@@ -85,7 +92,7 @@ export class RekShell {
|
|
|
85
92
|
const commands = [
|
|
86
93
|
'get', 'post', 'put', 'delete', 'patch', 'head', 'options',
|
|
87
94
|
'ws', 'udp', 'load', 'chat', 'ai',
|
|
88
|
-
'whois', 'tls', 'ssl', 'dns', 'rdap', 'ping',
|
|
95
|
+
'whois', 'tls', 'ssl', 'dns', 'dns:propagate', 'rdap', 'ping',
|
|
89
96
|
'scrap', '$', '$text', '$attr', '$html', '$links', '$images', '$scripts', '$css', '$sourcemaps', '$unmap', '$unmap:view', '$unmap:save', '$beautify', '$beautify:save', '$table',
|
|
90
97
|
'?', 'search', 'suggest', 'example',
|
|
91
98
|
'help', 'clear', 'exit', 'set', 'url', 'vars', 'env'
|
|
@@ -95,9 +102,11 @@ export class RekShell {
|
|
|
95
102
|
}
|
|
96
103
|
async start() {
|
|
97
104
|
await this.ensureInitialized();
|
|
105
|
+
this.setupScrollCapture();
|
|
98
106
|
console.clear();
|
|
99
107
|
console.log(colors.bold(colors.cyan('Rek Console')));
|
|
100
108
|
console.log(colors.gray('Chat with your APIs. Type "help" for magic.'));
|
|
109
|
+
console.log(colors.gray('Page Up/Down or mouse scroll to view history.'));
|
|
101
110
|
console.log(colors.gray('--------------------------------------------\n'));
|
|
102
111
|
this.prompt();
|
|
103
112
|
this.rl.on('line', async (line) => {
|
|
@@ -112,15 +121,159 @@ export class RekShell {
|
|
|
112
121
|
this.rl.close();
|
|
113
122
|
});
|
|
114
123
|
this.rl.on('close', () => {
|
|
124
|
+
this.cleanupScrollCapture();
|
|
115
125
|
console.log(colors.gray('\nSee ya.'));
|
|
116
126
|
process.exit(0);
|
|
117
127
|
});
|
|
128
|
+
process.stdout.on('resize', () => {
|
|
129
|
+
this.scrollBuffer.updateViewport();
|
|
130
|
+
});
|
|
131
|
+
this.setupScrollKeyHandler();
|
|
132
|
+
}
|
|
133
|
+
setupScrollCapture() {
|
|
134
|
+
this.originalStdoutWrite = process.stdout.write.bind(process.stdout);
|
|
135
|
+
const self = this;
|
|
136
|
+
process.stdout.write = function (chunk, encodingOrCallback, callback) {
|
|
137
|
+
const content = typeof chunk === 'string' ? chunk : chunk.toString();
|
|
138
|
+
self.scrollBuffer.write(content);
|
|
139
|
+
if (!self.inScrollMode && self.originalStdoutWrite) {
|
|
140
|
+
return self.originalStdoutWrite(chunk, encodingOrCallback, callback);
|
|
141
|
+
}
|
|
142
|
+
return true;
|
|
143
|
+
};
|
|
144
|
+
}
|
|
145
|
+
cleanupScrollCapture() {
|
|
146
|
+
if (this.originalStdoutWrite) {
|
|
147
|
+
process.stdout.write = this.originalStdoutWrite;
|
|
148
|
+
this.originalStdoutWrite = null;
|
|
149
|
+
}
|
|
150
|
+
disableMouseReporting();
|
|
151
|
+
}
|
|
152
|
+
setupScrollKeyHandler() {
|
|
153
|
+
enableMouseReporting();
|
|
154
|
+
if (process.stdin.isTTY) {
|
|
155
|
+
process.stdin.on('data', (data) => {
|
|
156
|
+
const scrollKey = parseScrollKey(data);
|
|
157
|
+
if (scrollKey) {
|
|
158
|
+
this.handleScrollKey(scrollKey);
|
|
159
|
+
return;
|
|
160
|
+
}
|
|
161
|
+
const mouseScroll = parseMouseScroll(data);
|
|
162
|
+
if (mouseScroll) {
|
|
163
|
+
this.handleScrollKey(mouseScroll);
|
|
164
|
+
return;
|
|
165
|
+
}
|
|
166
|
+
});
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
handleScrollKey(key) {
|
|
170
|
+
let needsRedraw = false;
|
|
171
|
+
switch (key) {
|
|
172
|
+
case 'pageUp':
|
|
173
|
+
if (!this.inScrollMode) {
|
|
174
|
+
this.enterScrollMode();
|
|
175
|
+
}
|
|
176
|
+
needsRedraw = this.scrollBuffer.pageUp();
|
|
177
|
+
break;
|
|
178
|
+
case 'pageDown':
|
|
179
|
+
needsRedraw = this.scrollBuffer.pageDown();
|
|
180
|
+
if (!this.scrollBuffer.isScrolledUp && this.inScrollMode) {
|
|
181
|
+
this.exitScrollMode();
|
|
182
|
+
return;
|
|
183
|
+
}
|
|
184
|
+
break;
|
|
185
|
+
case 'scrollUp':
|
|
186
|
+
if (!this.inScrollMode) {
|
|
187
|
+
this.enterScrollMode();
|
|
188
|
+
}
|
|
189
|
+
needsRedraw = this.scrollBuffer.scrollUp(3);
|
|
190
|
+
break;
|
|
191
|
+
case 'scrollDown':
|
|
192
|
+
needsRedraw = this.scrollBuffer.scrollDown(3);
|
|
193
|
+
if (!this.scrollBuffer.isScrolledUp && this.inScrollMode) {
|
|
194
|
+
this.exitScrollMode();
|
|
195
|
+
return;
|
|
196
|
+
}
|
|
197
|
+
break;
|
|
198
|
+
case 'home':
|
|
199
|
+
if (!this.inScrollMode) {
|
|
200
|
+
this.enterScrollMode();
|
|
201
|
+
}
|
|
202
|
+
this.scrollBuffer.scrollToTop();
|
|
203
|
+
needsRedraw = true;
|
|
204
|
+
break;
|
|
205
|
+
case 'end':
|
|
206
|
+
this.scrollBuffer.scrollToBottom();
|
|
207
|
+
if (this.inScrollMode) {
|
|
208
|
+
this.exitScrollMode();
|
|
209
|
+
return;
|
|
210
|
+
}
|
|
211
|
+
break;
|
|
212
|
+
case 'escape':
|
|
213
|
+
if (this.inScrollMode) {
|
|
214
|
+
this.exitScrollMode();
|
|
215
|
+
return;
|
|
216
|
+
}
|
|
217
|
+
break;
|
|
218
|
+
}
|
|
219
|
+
if (needsRedraw && this.inScrollMode) {
|
|
220
|
+
this.renderScrollView();
|
|
221
|
+
}
|
|
222
|
+
}
|
|
223
|
+
enterScrollMode() {
|
|
224
|
+
if (this.inScrollMode)
|
|
225
|
+
return;
|
|
226
|
+
this.inScrollMode = true;
|
|
227
|
+
this.rl.pause();
|
|
228
|
+
if (this.originalStdoutWrite) {
|
|
229
|
+
this.originalStdoutWrite('\x1b[?25l');
|
|
230
|
+
}
|
|
231
|
+
this.renderScrollView();
|
|
232
|
+
}
|
|
233
|
+
exitScrollMode() {
|
|
234
|
+
if (!this.inScrollMode)
|
|
235
|
+
return;
|
|
236
|
+
this.inScrollMode = false;
|
|
237
|
+
if (this.originalStdoutWrite) {
|
|
238
|
+
this.originalStdoutWrite('\x1b[?25h');
|
|
239
|
+
this.originalStdoutWrite('\x1b[2J\x1b[H');
|
|
240
|
+
}
|
|
241
|
+
const recentLines = this.scrollBuffer.getVisibleLines();
|
|
242
|
+
if (this.originalStdoutWrite) {
|
|
243
|
+
this.originalStdoutWrite(recentLines.join('\n') + '\n');
|
|
244
|
+
}
|
|
245
|
+
this.rl.resume();
|
|
246
|
+
this.prompt();
|
|
247
|
+
}
|
|
248
|
+
renderScrollView() {
|
|
249
|
+
if (!this.originalStdoutWrite)
|
|
250
|
+
return;
|
|
251
|
+
const rows = process.stdout.rows || 24;
|
|
252
|
+
const cols = process.stdout.columns || 80;
|
|
253
|
+
const visibleLines = this.scrollBuffer.getVisibleLines();
|
|
254
|
+
const info = this.scrollBuffer.getScrollInfo();
|
|
255
|
+
this.originalStdoutWrite('\x1b[2J\x1b[H');
|
|
256
|
+
for (let i = 0; i < visibleLines.length && i < rows - 1; i++) {
|
|
257
|
+
const line = visibleLines[i] || '';
|
|
258
|
+
const truncated = line.length > cols ? line.slice(0, cols - 1) + '…' : line;
|
|
259
|
+
this.originalStdoutWrite(truncated + '\n');
|
|
260
|
+
}
|
|
261
|
+
const scrollInfo = this.scrollBuffer.isScrolledUp
|
|
262
|
+
? colors.yellow(`↑ ${this.scrollBuffer.position} lines | ${info.percent}% | `)
|
|
263
|
+
: '';
|
|
264
|
+
const helpText = colors.gray('Page Up/Down • Home/End • Esc to exit');
|
|
265
|
+
const statusBar = `\x1b[${rows};1H\x1b[7m ${scrollInfo}${helpText} \x1b[0m`;
|
|
266
|
+
this.originalStdoutWrite(statusBar);
|
|
118
267
|
}
|
|
119
268
|
prompt() {
|
|
120
269
|
this.rl.setPrompt(this.getPrompt());
|
|
121
270
|
this.rl.prompt();
|
|
122
271
|
}
|
|
123
272
|
async handleCommand(input) {
|
|
273
|
+
if (input.endsWith('?') && !input.startsWith('?') && input.length > 1) {
|
|
274
|
+
await this.runSearch(input.slice(0, -1).trim());
|
|
275
|
+
return;
|
|
276
|
+
}
|
|
124
277
|
if (input.includes('=') && !input.includes(' ') && !input.startsWith('http')) {
|
|
125
278
|
}
|
|
126
279
|
const parts = this.parseLine(input);
|
|
@@ -165,6 +318,9 @@ export class RekShell {
|
|
|
165
318
|
case 'dns':
|
|
166
319
|
await this.runDNS(parts[1]);
|
|
167
320
|
return;
|
|
321
|
+
case 'dns:propagate':
|
|
322
|
+
await this.runDNSPropagation(parts[1], parts[2]);
|
|
323
|
+
return;
|
|
168
324
|
case 'rdap':
|
|
169
325
|
await this.runRDAP(parts[1]);
|
|
170
326
|
return;
|
|
@@ -736,6 +892,27 @@ export class RekShell {
|
|
|
736
892
|
}
|
|
737
893
|
console.log('');
|
|
738
894
|
}
|
|
895
|
+
async runDNSPropagation(domain, type = 'A') {
|
|
896
|
+
if (!domain) {
|
|
897
|
+
domain = this.getBaseDomain() || '';
|
|
898
|
+
if (!domain) {
|
|
899
|
+
console.log(colors.yellow('Usage: dns:propagate <domain> [type]'));
|
|
900
|
+
console.log(colors.gray(' Examples: dns:propagate google.com | dns:propagate github.com TXT'));
|
|
901
|
+
console.log(colors.gray(' Or set a base URL first: url https://example.com'));
|
|
902
|
+
return;
|
|
903
|
+
}
|
|
904
|
+
}
|
|
905
|
+
console.log(colors.gray(`Checking DNS propagation for ${domain} (${type})...`));
|
|
906
|
+
try {
|
|
907
|
+
const { checkPropagation, formatPropagationReport } = await import('../../dns/propagation.js');
|
|
908
|
+
const results = await checkPropagation(domain, type);
|
|
909
|
+
console.log(formatPropagationReport(results, domain, type));
|
|
910
|
+
this.lastResponse = results;
|
|
911
|
+
}
|
|
912
|
+
catch (error) {
|
|
913
|
+
console.error(colors.red(`Propagation check failed: ${error.message}`));
|
|
914
|
+
}
|
|
915
|
+
}
|
|
739
916
|
async runRDAP(domain) {
|
|
740
917
|
if (!domain) {
|
|
741
918
|
domain = this.getRootDomain() || '';
|
|
@@ -1660,38 +1837,14 @@ export class RekShell {
|
|
|
1660
1837
|
console.log('');
|
|
1661
1838
|
}
|
|
1662
1839
|
async runSearch(query) {
|
|
1663
|
-
if (!query.trim()) {
|
|
1664
|
-
console.log(colors.yellow('Usage: ? <query> or search <query>'));
|
|
1665
|
-
console.log(colors.gray(' Examples:'));
|
|
1666
|
-
console.log(colors.gray(' ? retry exponential backoff'));
|
|
1667
|
-
console.log(colors.gray(' search cache configuration'));
|
|
1668
|
-
return;
|
|
1669
|
-
}
|
|
1670
|
-
console.log(colors.gray('Searching documentation...'));
|
|
1671
1840
|
try {
|
|
1672
|
-
|
|
1673
|
-
|
|
1674
|
-
|
|
1675
|
-
console.log(colors.yellow(`No results for: "${query}"`));
|
|
1676
|
-
console.log(colors.gray('Try different keywords like: retry, cache, streaming, websocket, pagination'));
|
|
1677
|
-
return;
|
|
1678
|
-
}
|
|
1679
|
-
console.log(colors.bold(colors.cyan(`\nFound ${results.length} results for "${query}":\n`)));
|
|
1680
|
-
for (let i = 0; i < results.length; i++) {
|
|
1681
|
-
const result = results[i];
|
|
1682
|
-
const score = Math.round(result.score * 100);
|
|
1683
|
-
console.log(` ${colors.green(`${i + 1}.`)} ${colors.bold(result.title)} ${colors.gray(`(${score}%)`)}`);
|
|
1684
|
-
console.log(` ${colors.gray(result.path)}`);
|
|
1685
|
-
if (result.snippet) {
|
|
1686
|
-
const snippet = result.snippet.slice(0, 120).replace(/\n/g, ' ');
|
|
1687
|
-
console.log(` ${colors.gray(snippet)}${result.snippet.length > 120 ? '...' : ''}`);
|
|
1688
|
-
}
|
|
1689
|
-
console.log('');
|
|
1690
|
-
}
|
|
1691
|
-
console.log(colors.gray(' Tip: Use "get_doc <path>" to read full documentation'));
|
|
1841
|
+
this.rl.pause();
|
|
1842
|
+
await openSearchPanel(query.trim() || undefined);
|
|
1843
|
+
this.rl.resume();
|
|
1692
1844
|
}
|
|
1693
1845
|
catch (error) {
|
|
1694
1846
|
console.error(colors.red(`Search failed: ${error.message}`));
|
|
1847
|
+
this.rl.resume();
|
|
1695
1848
|
}
|
|
1696
1849
|
}
|
|
1697
1850
|
async runSuggest(useCase) {
|
|
@@ -1834,6 +1987,12 @@ export class RekShell {
|
|
|
1834
1987
|
${colors.green('suggest <use-case>')} Get implementation suggestions.
|
|
1835
1988
|
${colors.green('example <feature>')} Get code examples for a feature.
|
|
1836
1989
|
|
|
1990
|
+
${colors.bold('Navigation:')}
|
|
1991
|
+
${colors.green('Page Up/Down')} Scroll through command history.
|
|
1992
|
+
${colors.green('Home/End')} Jump to top/bottom of history.
|
|
1993
|
+
${colors.green('Mouse Scroll')} Scroll with mouse wheel.
|
|
1994
|
+
${colors.green('Escape')} Exit scroll mode.
|
|
1995
|
+
|
|
1837
1996
|
${colors.bold('Examples:')}
|
|
1838
1997
|
› url httpbin.org
|
|
1839
1998
|
› get /json
|
package/dist/dns/index.d.ts
CHANGED
|
@@ -2,6 +2,7 @@ import { type DnsSecurityRecords } from '../utils/dns-toolkit.js';
|
|
|
2
2
|
export { createLookupFunction, customDNSLookup } from '../utils/dns.js';
|
|
3
3
|
export { createDoHLookup } from '../utils/doh.js';
|
|
4
4
|
export { getSecurityRecords, type DnsSecurityRecords } from '../utils/dns-toolkit.js';
|
|
5
|
+
export { checkPropagation, type PropagationResult, type DnsRecord, formatPropagationReport } from './propagation.js';
|
|
5
6
|
export type { DNSOptions } from '../types/index.js';
|
|
6
7
|
export type RecordType = 'A' | 'AAAA' | 'CNAME' | 'MX' | 'TXT' | 'NS' | 'SOA' | 'SRV' | 'CAA' | 'PTR';
|
|
7
8
|
export type DoHProvider = 'cloudflare' | 'google' | 'quad9' | 'system';
|
package/dist/dns/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/dns/index.ts"],"names":[],"mappings":"AA4BA,OAAO,EAAgD,KAAK,kBAAkB,EAAE,MAAM,yBAAyB,CAAC;AAGhH,OAAO,EAAE,oBAAoB,EAAE,eAAe,EAAE,MAAM,iBAAiB,CAAC;AACxE,OAAO,EAAE,eAAe,EAAE,MAAM,iBAAiB,CAAC;AAClD,OAAO,EAAE,kBAAkB,EAAE,KAAK,kBAAkB,EAAE,MAAM,yBAAyB,CAAC;AACtF,YAAY,EAAE,UAAU,EAAE,MAAM,mBAAmB,CAAC;AAKpD,MAAM,MAAM,UAAU,GAAG,GAAG,GAAG,MAAM,GAAG,OAAO,GAAG,IAAI,GAAG,KAAK,GAAG,IAAI,GAAG,KAAK,GAAG,KAAK,GAAG,KAAK,GAAG,KAAK,CAAC;AAKtG,MAAM,MAAM,WAAW,GAAG,YAAY,GAAG,QAAQ,GAAG,OAAO,GAAG,QAAQ,CAAC;AAKvE,MAAM,WAAW,gBAAgB;IAK/B,QAAQ,CAAC,EAAE,WAAW,CAAC;IAMvB,OAAO,CAAC,EAAE,MAAM,CAAC;IAMjB,KAAK,CAAC,EAAE,OAAO,CAAC;IAKhB,OAAO,CAAC,EAAE,MAAM,EAAE,CAAC;CACpB;AAqBD,qBAAa,SAAS;IACpB,OAAO,CAAC,OAAO,CAA6B;gBAEhC,OAAO,GAAE,gBAAqB;IAc1C,OAAO,CAAC,GAAG;IASL,OAAO,CAAC,QAAQ,EAAE,MAAM,EAAE,IAAI,GAAE,UAAgB,GAAG,OAAO,CAAC,MAAM,EAAE,CAAC;IAiD1E,QAAQ,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,EAAE,CAAC;IAO7C,QAAQ,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,EAAE,CAAC;IAO7C,SAAS,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC;QAAE,QAAQ,EAAE,MAAM,CAAC;QAAC,QAAQ,EAAE,MAAM,CAAA;KAAE,EAAE,CAAC;IAOxE,UAAU,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,EAAE,CAAC;IAQrD,SAAS,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,EAAE,CAAC;IAO9C,UAAU,CAAC,QAAQ,EAAE,MAAM;IAO3B,UAAU,CAAC,QAAQ,EAAE,MAAM;IAO3B,UAAU,CAAC,QAAQ,EAAE,MAAM;IAO3B,OAAO,CAAC,EAAE,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,EAAE,CAAC;IAOhC,UAAU,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,MAAM,EAAE,OAAO,EAAE,CAAC,CAAC;IA4BhE,kBAAkB,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,kBAAkB,CAAC;CAItE;AA0BD,wBAAgB,SAAS,CAAC,OAAO,CAAC,EAAE,gBAAgB,GAAG,SAAS,CAE/D"}
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/dns/index.ts"],"names":[],"mappings":"AA4BA,OAAO,EAAgD,KAAK,kBAAkB,EAAE,MAAM,yBAAyB,CAAC;AAGhH,OAAO,EAAE,oBAAoB,EAAE,eAAe,EAAE,MAAM,iBAAiB,CAAC;AACxE,OAAO,EAAE,eAAe,EAAE,MAAM,iBAAiB,CAAC;AAClD,OAAO,EAAE,kBAAkB,EAAE,KAAK,kBAAkB,EAAE,MAAM,yBAAyB,CAAC;AACtF,OAAO,EAAE,gBAAgB,EAAE,KAAK,iBAAiB,EAAE,KAAK,SAAS,EAAE,uBAAuB,EAAE,MAAM,kBAAkB,CAAC;AACrH,YAAY,EAAE,UAAU,EAAE,MAAM,mBAAmB,CAAC;AAKpD,MAAM,MAAM,UAAU,GAAG,GAAG,GAAG,MAAM,GAAG,OAAO,GAAG,IAAI,GAAG,KAAK,GAAG,IAAI,GAAG,KAAK,GAAG,KAAK,GAAG,KAAK,GAAG,KAAK,CAAC;AAKtG,MAAM,MAAM,WAAW,GAAG,YAAY,GAAG,QAAQ,GAAG,OAAO,GAAG,QAAQ,CAAC;AAKvE,MAAM,WAAW,gBAAgB;IAK/B,QAAQ,CAAC,EAAE,WAAW,CAAC;IAMvB,OAAO,CAAC,EAAE,MAAM,CAAC;IAMjB,KAAK,CAAC,EAAE,OAAO,CAAC;IAKhB,OAAO,CAAC,EAAE,MAAM,EAAE,CAAC;CACpB;AAqBD,qBAAa,SAAS;IACpB,OAAO,CAAC,OAAO,CAA6B;gBAEhC,OAAO,GAAE,gBAAqB;IAc1C,OAAO,CAAC,GAAG;IASL,OAAO,CAAC,QAAQ,EAAE,MAAM,EAAE,IAAI,GAAE,UAAgB,GAAG,OAAO,CAAC,MAAM,EAAE,CAAC;IAiD1E,QAAQ,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,EAAE,CAAC;IAO7C,QAAQ,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,EAAE,CAAC;IAO7C,SAAS,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC;QAAE,QAAQ,EAAE,MAAM,CAAC;QAAC,QAAQ,EAAE,MAAM,CAAA;KAAE,EAAE,CAAC;IAOxE,UAAU,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,EAAE,CAAC;IAQrD,SAAS,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,EAAE,CAAC;IAO9C,UAAU,CAAC,QAAQ,EAAE,MAAM;IAO3B,UAAU,CAAC,QAAQ,EAAE,MAAM;IAO3B,UAAU,CAAC,QAAQ,EAAE,MAAM;IAO3B,OAAO,CAAC,EAAE,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,EAAE,CAAC;IAOhC,UAAU,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,MAAM,EAAE,OAAO,EAAE,CAAC,CAAC;IA4BhE,kBAAkB,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,kBAAkB,CAAC;CAItE;AA0BD,wBAAgB,SAAS,CAAC,OAAO,CAAC,EAAE,gBAAgB,GAAG,SAAS,CAE/D"}
|
package/dist/dns/index.js
CHANGED
|
@@ -3,6 +3,7 @@ import { getSecurityRecords as getSecurityRecordsUtil } from '../utils/dns-toolk
|
|
|
3
3
|
export { createLookupFunction, customDNSLookup } from '../utils/dns.js';
|
|
4
4
|
export { createDoHLookup } from '../utils/doh.js';
|
|
5
5
|
export { getSecurityRecords } from '../utils/dns-toolkit.js';
|
|
6
|
+
export { checkPropagation, formatPropagationReport } from './propagation.js';
|
|
6
7
|
export class DNSClient {
|
|
7
8
|
options;
|
|
8
9
|
constructor(options = {}) {
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
export interface DnsRecord {
|
|
2
|
+
name: string;
|
|
3
|
+
type: number;
|
|
4
|
+
TTL: number;
|
|
5
|
+
data: string;
|
|
6
|
+
}
|
|
7
|
+
export interface PropagationResult {
|
|
8
|
+
id: string;
|
|
9
|
+
provider: string;
|
|
10
|
+
status: 'ok' | 'error';
|
|
11
|
+
records: string[];
|
|
12
|
+
rawRecords: DnsRecord[];
|
|
13
|
+
latency: number;
|
|
14
|
+
error?: string;
|
|
15
|
+
location?: string;
|
|
16
|
+
}
|
|
17
|
+
export declare function checkPropagation(domain: string, type?: string): Promise<PropagationResult[]>;
|
|
18
|
+
export declare function formatPropagationReport(results: PropagationResult[], domain: string, type: string): string;
|
|
19
|
+
//# sourceMappingURL=propagation.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"propagation.d.ts","sourceRoot":"","sources":["../../src/dns/propagation.ts"],"names":[],"mappings":"AAGA,MAAM,WAAW,SAAS;IACxB,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,CAAC;IACb,GAAG,EAAE,MAAM,CAAC;IACZ,IAAI,EAAE,MAAM,CAAC;CACd;AAED,MAAM,WAAW,iBAAiB;IAChC,EAAE,EAAE,MAAM,CAAC;IACX,QAAQ,EAAE,MAAM,CAAC;IACjB,MAAM,EAAE,IAAI,GAAG,OAAO,CAAC;IACvB,OAAO,EAAE,MAAM,EAAE,CAAC;IAClB,UAAU,EAAE,SAAS,EAAE,CAAC;IACxB,OAAO,EAAE,MAAM,CAAC;IAChB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,QAAQ,CAAC,EAAE,MAAM,CAAC;CACnB;AA8BD,wBAAsB,gBAAgB,CAAC,MAAM,EAAE,MAAM,EAAE,IAAI,GAAE,MAAY,GAAG,OAAO,CAAC,iBAAiB,EAAE,CAAC,CAyEvG;AAED,wBAAgB,uBAAuB,CAAC,OAAO,EAAE,iBAAiB,EAAE,EAAE,MAAM,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,MAAM,CAqD1G"}
|
|
@@ -0,0 +1,129 @@
|
|
|
1
|
+
import { request } from 'undici';
|
|
2
|
+
import pc from '../utils/colors.js';
|
|
3
|
+
const PROVIDERS = [
|
|
4
|
+
{ id: 'google', name: 'Google DNS', url: 'https://dns.google/resolve' },
|
|
5
|
+
{ id: 'cloudflare', name: 'Cloudflare', url: 'https://cloudflare-dns.com/dns-query' },
|
|
6
|
+
{ id: 'quad9', name: 'Quad9', url: 'https://dns.quad9.net:5053/dns-query' },
|
|
7
|
+
{ id: 'alidns', name: 'AliDNS (China)', url: 'https://dns.alidns.com/resolve' },
|
|
8
|
+
];
|
|
9
|
+
const TYPE_MAP = {
|
|
10
|
+
'A': '1',
|
|
11
|
+
'AAAA': '28',
|
|
12
|
+
'CNAME': '5',
|
|
13
|
+
'MX': '15',
|
|
14
|
+
'NS': '2',
|
|
15
|
+
'TXT': '16',
|
|
16
|
+
'PTR': '12',
|
|
17
|
+
'SRV': '33',
|
|
18
|
+
'SOA': '6',
|
|
19
|
+
'CAA': '257'
|
|
20
|
+
};
|
|
21
|
+
const TYPE_ID_MAP = Object.entries(TYPE_MAP).reduce((acc, [k, v]) => {
|
|
22
|
+
acc[Number(v)] = k;
|
|
23
|
+
return acc;
|
|
24
|
+
}, {});
|
|
25
|
+
export async function checkPropagation(domain, type = 'A') {
|
|
26
|
+
const typeId = TYPE_MAP[type.toUpperCase()] || type;
|
|
27
|
+
const queries = PROVIDERS.map(async (provider) => {
|
|
28
|
+
const start = performance.now();
|
|
29
|
+
try {
|
|
30
|
+
const url = new URL(provider.url);
|
|
31
|
+
url.searchParams.set('name', domain);
|
|
32
|
+
url.searchParams.set('type', typeId);
|
|
33
|
+
const { body, statusCode } = await request(url, {
|
|
34
|
+
method: 'GET',
|
|
35
|
+
headers: { 'Accept': 'application/dns-json' }
|
|
36
|
+
});
|
|
37
|
+
if (statusCode !== 200) {
|
|
38
|
+
throw new Error(`HTTP ${statusCode}`);
|
|
39
|
+
}
|
|
40
|
+
const json = await body.json();
|
|
41
|
+
const duration = performance.now() - start;
|
|
42
|
+
if (json.Status !== 0) {
|
|
43
|
+
const statusMap = {
|
|
44
|
+
1: 'FormErr', 2: 'ServFail', 3: 'NXDomain', 4: 'NotImp', 5: 'Refused'
|
|
45
|
+
};
|
|
46
|
+
const errorName = statusMap[json.Status] || `Code ${json.Status}`;
|
|
47
|
+
return {
|
|
48
|
+
id: provider.id,
|
|
49
|
+
provider: provider.name,
|
|
50
|
+
status: 'error',
|
|
51
|
+
records: [],
|
|
52
|
+
rawRecords: [],
|
|
53
|
+
latency: duration,
|
|
54
|
+
error: errorName
|
|
55
|
+
};
|
|
56
|
+
}
|
|
57
|
+
const answers = (json.Answer || []);
|
|
58
|
+
const records = answers.map(r => r.data);
|
|
59
|
+
return {
|
|
60
|
+
id: provider.id,
|
|
61
|
+
provider: provider.name,
|
|
62
|
+
status: 'ok',
|
|
63
|
+
records,
|
|
64
|
+
rawRecords: answers,
|
|
65
|
+
latency: duration
|
|
66
|
+
};
|
|
67
|
+
}
|
|
68
|
+
catch (err) {
|
|
69
|
+
return {
|
|
70
|
+
id: provider.id,
|
|
71
|
+
provider: provider.name,
|
|
72
|
+
status: 'error',
|
|
73
|
+
records: [],
|
|
74
|
+
rawRecords: [],
|
|
75
|
+
latency: performance.now() - start,
|
|
76
|
+
error: err.message
|
|
77
|
+
};
|
|
78
|
+
}
|
|
79
|
+
});
|
|
80
|
+
return Promise.all(queries);
|
|
81
|
+
}
|
|
82
|
+
export function formatPropagationReport(results, domain, type) {
|
|
83
|
+
let output = '';
|
|
84
|
+
output += `
|
|
85
|
+
${pc.bold(pc.cyan('🌍 Global DNS Propagation Check'))}
|
|
86
|
+
`;
|
|
87
|
+
output += `${pc.gray('Domain:')} ${pc.white(domain)} ${pc.gray('Type:')} ${pc.white(type.toUpperCase())}
|
|
88
|
+
|
|
89
|
+
`;
|
|
90
|
+
const consensus = {};
|
|
91
|
+
results.forEach(res => {
|
|
92
|
+
const key = res.status === 'ok' ? res.records.sort().join(', ') : `Error: ${res.error}`;
|
|
93
|
+
consensus[key] = (consensus[key] || 0) + 1;
|
|
94
|
+
const statusIcon = res.status === 'ok' ? pc.green('✔') : pc.red('✖');
|
|
95
|
+
const latencyColor = res.latency < 50 ? pc.green : res.latency < 200 ? pc.yellow : pc.red;
|
|
96
|
+
const latencyText = latencyColor(`${Math.round(res.latency)}ms`.padStart(5));
|
|
97
|
+
const providerName = pc.bold(res.provider.padEnd(15));
|
|
98
|
+
let recordsText = '';
|
|
99
|
+
if (res.status === 'ok') {
|
|
100
|
+
if (res.records.length === 0) {
|
|
101
|
+
recordsText = pc.yellow('(No records)');
|
|
102
|
+
}
|
|
103
|
+
else {
|
|
104
|
+
recordsText = res.records.join(', ');
|
|
105
|
+
if (recordsText.length > 60)
|
|
106
|
+
recordsText = recordsText.substring(0, 57) + '...';
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
else {
|
|
110
|
+
recordsText = pc.red(res.error || 'Unknown Error');
|
|
111
|
+
}
|
|
112
|
+
output += ` ${statusIcon} ${providerName} ${latencyText} ${recordsText}
|
|
113
|
+
`;
|
|
114
|
+
});
|
|
115
|
+
output += '\n';
|
|
116
|
+
const distinctAnswers = Object.keys(consensus);
|
|
117
|
+
if (distinctAnswers.length === 1) {
|
|
118
|
+
if (distinctAnswers[0].startsWith('Error')) {
|
|
119
|
+
output += pc.red('❌ All providers returned error.\n');
|
|
120
|
+
}
|
|
121
|
+
else {
|
|
122
|
+
output += pc.green('✅ All providers returned the same records. Propagation is complete.\n');
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
else if (distinctAnswers.length > 1) {
|
|
126
|
+
output += pc.yellow('⚠ Inconsistent results detected (Propagation in progress or Split-Horizon DNS).\n');
|
|
127
|
+
}
|
|
128
|
+
return output;
|
|
129
|
+
}
|