recker 1.0.7 → 1.0.9
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +13 -13
- package/dist/cli/handler.d.ts.map +1 -1
- package/dist/cli/handler.js +7 -2
- package/dist/cli/tui/shell.d.ts +17 -0
- package/dist/cli/tui/shell.d.ts.map +1 -1
- package/dist/cli/tui/shell.js +684 -51
- package/dist/utils/whois.d.ts.map +1 -1
- package/dist/utils/whois.js +17 -2
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
|
|
7
7
|
### The HTTP SDK for the AI Era
|
|
8
8
|
|
|
9
|
-
**
|
|
9
|
+
**Fast as infrastructure demands. AI-ready from the first byte. Observable down to the millisecond. Resilient when everything else fails.**
|
|
10
10
|
|
|
11
11
|
[](https://www.npmjs.com/package/recker)
|
|
12
12
|
[](https://www.npmjs.com/package/recker)
|
|
@@ -102,24 +102,24 @@ console.log(timings);
|
|
|
102
102
|
|
|
103
103
|
**Getting Started**
|
|
104
104
|
- [Installation](./docs/getting-started/installation.md)
|
|
105
|
-
- [Quick Start](./docs/
|
|
106
|
-
- [Client Configuration](./docs/
|
|
105
|
+
- [Quick Start](./docs/http/01-quickstart.md)
|
|
106
|
+
- [Client Configuration](./docs/http/05-configuration.md)
|
|
107
107
|
|
|
108
108
|
**Core Features**
|
|
109
|
-
- [HTTP
|
|
110
|
-
- [Streaming & SSE](./docs/
|
|
111
|
-
- [Retry &
|
|
112
|
-
- [Caching](./docs/
|
|
113
|
-
- [
|
|
109
|
+
- [HTTP Fundamentals](./docs/http/02-fundamentals.md)
|
|
110
|
+
- [Streaming & SSE](./docs/ai/02-streaming.md)
|
|
111
|
+
- [Retry & Resilience](./docs/http/07-resilience.md)
|
|
112
|
+
- [Caching](./docs/http/09-cache.md)
|
|
113
|
+
- [Concurrency](./docs/http/08-concurrency.md)
|
|
114
114
|
|
|
115
115
|
**Integrations**
|
|
116
|
-
- [GraphQL](./docs/
|
|
117
|
-
- [Scraping](./docs/
|
|
118
|
-
- [
|
|
116
|
+
- [GraphQL](./docs/http/13-graphql.md)
|
|
117
|
+
- [Scraping](./docs/http/14-scraping.md)
|
|
118
|
+
- [Plugins](./docs/http/10-plugins.md)
|
|
119
119
|
|
|
120
120
|
**Reference**
|
|
121
|
-
- [API Reference](./docs/api
|
|
122
|
-
- [Troubleshooting](./docs/
|
|
121
|
+
- [API Reference](./docs/reference/01-api.md)
|
|
122
|
+
- [Troubleshooting](./docs/reference/05-troubleshooting.md)
|
|
123
123
|
- [Examples](./docs/examples/README.md)
|
|
124
124
|
|
|
125
125
|
## ❤️ Acknowledgements
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"handler.d.ts","sourceRoot":"","sources":["../../src/cli/handler.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"handler.d.ts","sourceRoot":"","sources":["../../src/cli/handler.ts"],"names":[],"mappings":"AAwBA,UAAU,cAAc;IACtB,MAAM,EAAE,MAAM,CAAC;IACf,GAAG,EAAE,MAAM,CAAC;IACZ,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IAChC,IAAI,CAAC,EAAE,GAAG,CAAC;IACX,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB,YAAY,CAAC,EAAE,GAAG,CAAC;CACpB;AAED,wBAAsB,aAAa,CAAC,OAAO,EAAE,cAAc,iBAqG1D"}
|
package/dist/cli/handler.js
CHANGED
|
@@ -6,8 +6,13 @@ let highlight;
|
|
|
6
6
|
const ora = oraImport;
|
|
7
7
|
async function initDependencies() {
|
|
8
8
|
if (!highlight) {
|
|
9
|
-
|
|
10
|
-
|
|
9
|
+
try {
|
|
10
|
+
const cardinal = await requireOptional('cardinal', 'recker/cli');
|
|
11
|
+
highlight = cardinal.highlight;
|
|
12
|
+
}
|
|
13
|
+
catch {
|
|
14
|
+
highlight = (code) => code;
|
|
15
|
+
}
|
|
11
16
|
}
|
|
12
17
|
}
|
|
13
18
|
export async function handleRequest(options) {
|
package/dist/cli/tui/shell.d.ts
CHANGED
|
@@ -6,9 +6,13 @@ export declare class RekShell {
|
|
|
6
6
|
private lastResponse;
|
|
7
7
|
private variables;
|
|
8
8
|
private initialized;
|
|
9
|
+
private currentDoc;
|
|
10
|
+
private currentDocUrl;
|
|
9
11
|
constructor();
|
|
10
12
|
private ensureInitialized;
|
|
11
13
|
private getPrompt;
|
|
14
|
+
private getBaseDomain;
|
|
15
|
+
private getRootDomain;
|
|
12
16
|
private completer;
|
|
13
17
|
start(): Promise<void>;
|
|
14
18
|
private prompt;
|
|
@@ -22,6 +26,19 @@ export declare class RekShell {
|
|
|
22
26
|
private resolveVariables;
|
|
23
27
|
private resolveUrl;
|
|
24
28
|
private executeRequest;
|
|
29
|
+
private runWhois;
|
|
30
|
+
private runTLS;
|
|
31
|
+
private runDNS;
|
|
32
|
+
private runRDAP;
|
|
33
|
+
private runPing;
|
|
34
|
+
private runScrap;
|
|
35
|
+
private runSelect;
|
|
36
|
+
private runSelectText;
|
|
37
|
+
private runSelectAttr;
|
|
38
|
+
private runSelectHtml;
|
|
39
|
+
private runSelectLinks;
|
|
40
|
+
private runSelectImages;
|
|
41
|
+
private runSelectTable;
|
|
25
42
|
private printHelp;
|
|
26
43
|
}
|
|
27
44
|
//# sourceMappingURL=shell.d.ts.map
|
|
@@ -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":"AAiCA,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,WAAW,CAAS;IAC5B,OAAO,CAAC,UAAU,CAA+B;IACjD,OAAO,CAAC,aAAa,CAAc;;YAWrB,iBAAiB;IAe/B,OAAO,CAAC,SAAS;IAMjB,OAAO,CAAC,aAAa;IAUrB,OAAO,CAAC,aAAa;IAiBrB,OAAO,CAAC,SAAS;IAYJ,KAAK;IA8BlB,OAAO,CAAC,MAAM;YAKA,aAAa;YAwIb,kBAAkB;YAkBlB,SAAS;YAkBT,WAAW;IA0DzB,OAAO,CAAC,SAAS;IAMjB,OAAO,CAAC,UAAU;IAMlB,OAAO,CAAC,WAAW;IASnB,OAAO,CAAC,gBAAgB;IAoBxB,OAAO,CAAC,UAAU;YAeJ,cAAc;YAyEd,QAAQ;YA8GR,MAAM;YA2DN,MAAM;YA2EN,OAAO;YA+DP,OAAO;YA0CP,QAAQ;YAwCR,SAAS;YAsCT,aAAa;YA8Bb,aAAa;YA+Bb,aAAa;YA6Bb,cAAc;YAkCd,eAAe;YAkCf,cAAc;IA8C5B,OAAO,CAAC,SAAS;CAyDlB"}
|
package/dist/cli/tui/shell.js
CHANGED
|
@@ -1,13 +1,24 @@
|
|
|
1
1
|
import readline from 'node:readline';
|
|
2
|
+
import { promises as dns } from 'node:dns';
|
|
2
3
|
import { requireOptional } from '../../utils/optional-require.js';
|
|
3
4
|
import { createClient } from '../../core/client.js';
|
|
4
5
|
import { startInteractiveWebSocket } from './websocket.js';
|
|
5
|
-
import
|
|
6
|
+
import { whois, isDomainAvailable } from '../../utils/whois.js';
|
|
7
|
+
import { inspectTLS } from '../../utils/tls-inspector.js';
|
|
8
|
+
import { getSecurityRecords } from '../../utils/dns-toolkit.js';
|
|
9
|
+
import { rdap } from '../../utils/rdap.js';
|
|
10
|
+
import { ScrapeDocument } from '../../scrape/document.js';
|
|
11
|
+
import colors from '../../utils/colors.js';
|
|
6
12
|
let highlight;
|
|
7
13
|
async function initDependencies() {
|
|
8
14
|
if (!highlight) {
|
|
9
|
-
|
|
10
|
-
|
|
15
|
+
try {
|
|
16
|
+
const cardinal = await requireOptional('cardinal', 'recker/cli');
|
|
17
|
+
highlight = cardinal.highlight;
|
|
18
|
+
}
|
|
19
|
+
catch {
|
|
20
|
+
highlight = (code) => code;
|
|
21
|
+
}
|
|
11
22
|
}
|
|
12
23
|
}
|
|
13
24
|
export class RekShell {
|
|
@@ -18,6 +29,8 @@ export class RekShell {
|
|
|
18
29
|
lastResponse = null;
|
|
19
30
|
variables = {};
|
|
20
31
|
initialized = false;
|
|
32
|
+
currentDoc = null;
|
|
33
|
+
currentDocUrl = '';
|
|
21
34
|
constructor() {
|
|
22
35
|
this.client = createClient({
|
|
23
36
|
baseUrl: 'http://localhost',
|
|
@@ -37,20 +50,49 @@ export class RekShell {
|
|
|
37
50
|
this.initialized = true;
|
|
38
51
|
}
|
|
39
52
|
getPrompt() {
|
|
40
|
-
const base = this.baseUrl ?
|
|
41
|
-
return `${base} ${
|
|
53
|
+
const base = this.baseUrl ? colors.cyan(new URL(this.baseUrl).hostname) : colors.gray('rek');
|
|
54
|
+
return `${base} ${colors.magenta('›')} `;
|
|
55
|
+
}
|
|
56
|
+
getBaseDomain() {
|
|
57
|
+
if (!this.baseUrl)
|
|
58
|
+
return null;
|
|
59
|
+
try {
|
|
60
|
+
return new URL(this.baseUrl).hostname;
|
|
61
|
+
}
|
|
62
|
+
catch {
|
|
63
|
+
return null;
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
getRootDomain() {
|
|
67
|
+
const hostname = this.getBaseDomain();
|
|
68
|
+
if (!hostname)
|
|
69
|
+
return null;
|
|
70
|
+
const parts = hostname.split('.');
|
|
71
|
+
if (parts.length <= 2)
|
|
72
|
+
return hostname;
|
|
73
|
+
const commonSLDs = ['co', 'com', 'net', 'org', 'gov', 'edu', 'ac'];
|
|
74
|
+
if (parts.length >= 3 && commonSLDs.includes(parts[parts.length - 2])) {
|
|
75
|
+
return parts.slice(-3).join('.');
|
|
76
|
+
}
|
|
77
|
+
return parts.slice(-2).join('.');
|
|
42
78
|
}
|
|
43
79
|
completer(line) {
|
|
44
|
-
const commands = [
|
|
80
|
+
const commands = [
|
|
81
|
+
'get', 'post', 'put', 'delete', 'patch', 'head', 'options',
|
|
82
|
+
'ws', 'udp', 'load', 'chat', 'ai',
|
|
83
|
+
'whois', 'tls', 'ssl', 'dns', 'rdap', 'ping',
|
|
84
|
+
'scrap', '$', '$text', '$attr', '$html', '$links', '$images', '$table',
|
|
85
|
+
'help', 'clear', 'exit', 'set', 'url', 'vars'
|
|
86
|
+
];
|
|
45
87
|
const hits = commands.filter((c) => c.startsWith(line));
|
|
46
88
|
return [hits.length ? hits : commands, line];
|
|
47
89
|
}
|
|
48
90
|
async start() {
|
|
49
91
|
await this.ensureInitialized();
|
|
50
92
|
console.clear();
|
|
51
|
-
console.log(
|
|
52
|
-
console.log(
|
|
53
|
-
console.log(
|
|
93
|
+
console.log(colors.bold(colors.cyan('Rek Console')));
|
|
94
|
+
console.log(colors.gray('Chat with your APIs. Type "help" for magic.'));
|
|
95
|
+
console.log(colors.gray('--------------------------------------------\n'));
|
|
54
96
|
this.prompt();
|
|
55
97
|
this.rl.on('line', async (line) => {
|
|
56
98
|
const input = line.trim();
|
|
@@ -60,11 +102,11 @@ export class RekShell {
|
|
|
60
102
|
this.prompt();
|
|
61
103
|
});
|
|
62
104
|
this.rl.on('SIGINT', () => {
|
|
63
|
-
|
|
64
|
-
this.
|
|
105
|
+
console.log('');
|
|
106
|
+
this.rl.close();
|
|
65
107
|
});
|
|
66
108
|
this.rl.on('close', () => {
|
|
67
|
-
console.log(
|
|
109
|
+
console.log(colors.gray('\nSee ya.'));
|
|
68
110
|
process.exit(0);
|
|
69
111
|
});
|
|
70
112
|
}
|
|
@@ -104,6 +146,46 @@ export class RekShell {
|
|
|
104
146
|
case 'chat':
|
|
105
147
|
await this.runAIChat(parts.slice(1));
|
|
106
148
|
return;
|
|
149
|
+
case 'whois':
|
|
150
|
+
await this.runWhois(parts[1]);
|
|
151
|
+
return;
|
|
152
|
+
case 'tls':
|
|
153
|
+
case 'ssl':
|
|
154
|
+
await this.runTLS(parts[1], parts[2] ? parseInt(parts[2]) : 443);
|
|
155
|
+
return;
|
|
156
|
+
case 'dns':
|
|
157
|
+
await this.runDNS(parts[1]);
|
|
158
|
+
return;
|
|
159
|
+
case 'rdap':
|
|
160
|
+
await this.runRDAP(parts[1]);
|
|
161
|
+
return;
|
|
162
|
+
case 'ping':
|
|
163
|
+
await this.runPing(parts[1]);
|
|
164
|
+
return;
|
|
165
|
+
case 'scrap':
|
|
166
|
+
await this.runScrap(parts[1]);
|
|
167
|
+
return;
|
|
168
|
+
case '$':
|
|
169
|
+
await this.runSelect(parts.slice(1).join(' '));
|
|
170
|
+
return;
|
|
171
|
+
case '$text':
|
|
172
|
+
await this.runSelectText(parts.slice(1).join(' '));
|
|
173
|
+
return;
|
|
174
|
+
case '$attr':
|
|
175
|
+
await this.runSelectAttr(parts[1], parts.slice(2).join(' '));
|
|
176
|
+
return;
|
|
177
|
+
case '$html':
|
|
178
|
+
await this.runSelectHtml(parts.slice(1).join(' '));
|
|
179
|
+
return;
|
|
180
|
+
case '$links':
|
|
181
|
+
await this.runSelectLinks(parts[1]);
|
|
182
|
+
return;
|
|
183
|
+
case '$images':
|
|
184
|
+
await this.runSelectImages(parts[1]);
|
|
185
|
+
return;
|
|
186
|
+
case '$table':
|
|
187
|
+
await this.runSelectTable(parts.slice(1).join(' '));
|
|
188
|
+
return;
|
|
107
189
|
}
|
|
108
190
|
const methods = ['get', 'post', 'put', 'delete', 'patch', 'head', 'options'];
|
|
109
191
|
let method = 'GET';
|
|
@@ -121,12 +203,12 @@ export class RekShell {
|
|
|
121
203
|
bodyParts = parts.slice(1);
|
|
122
204
|
}
|
|
123
205
|
else {
|
|
124
|
-
console.log(
|
|
206
|
+
console.log(colors.red(`Unknown command: ${cmd}`));
|
|
125
207
|
return;
|
|
126
208
|
}
|
|
127
209
|
url = this.resolveUrl(url);
|
|
128
210
|
if (!url) {
|
|
129
|
-
console.log(
|
|
211
|
+
console.log(colors.yellow('No URL provided and no Base URL set. Use "url <url>" or provide full URL.'));
|
|
130
212
|
return;
|
|
131
213
|
}
|
|
132
214
|
const body = {};
|
|
@@ -181,7 +263,7 @@ export class RekShell {
|
|
|
181
263
|
let targetUrl = '';
|
|
182
264
|
let users = 50;
|
|
183
265
|
let duration = 300;
|
|
184
|
-
let mode = '
|
|
266
|
+
let mode = 'realistic';
|
|
185
267
|
let http2 = false;
|
|
186
268
|
let rampUp = 5;
|
|
187
269
|
for (const arg of args) {
|
|
@@ -208,7 +290,7 @@ export class RekShell {
|
|
|
208
290
|
}
|
|
209
291
|
targetUrl = this.resolveUrl(targetUrl);
|
|
210
292
|
if (!targetUrl) {
|
|
211
|
-
console.log(
|
|
293
|
+
console.log(colors.yellow('Target URL required. usage: load <url> users=10 duration=10s ramp=5'));
|
|
212
294
|
return;
|
|
213
295
|
}
|
|
214
296
|
const { startLoadDashboard } = await import('./load-dashboard.js');
|
|
@@ -225,7 +307,7 @@ export class RekShell {
|
|
|
225
307
|
});
|
|
226
308
|
}
|
|
227
309
|
catch (e) {
|
|
228
|
-
console.error(
|
|
310
|
+
console.error(colors.red('Load Test Failed: ' + e.message));
|
|
229
311
|
}
|
|
230
312
|
finally {
|
|
231
313
|
process.stdout.write('\x1B[?25h');
|
|
@@ -240,7 +322,7 @@ export class RekShell {
|
|
|
240
322
|
if (!url.startsWith('http'))
|
|
241
323
|
url = `https://${url}`;
|
|
242
324
|
this.baseUrl = url;
|
|
243
|
-
console.log(
|
|
325
|
+
console.log(colors.gray(`Base URL set to: ${colors.cyan(this.baseUrl)}`));
|
|
244
326
|
}
|
|
245
327
|
setVariable(args) {
|
|
246
328
|
const [expr] = args;
|
|
@@ -248,7 +330,7 @@ export class RekShell {
|
|
|
248
330
|
return;
|
|
249
331
|
const [key, val] = expr.split('=');
|
|
250
332
|
this.variables[key] = val;
|
|
251
|
-
console.log(
|
|
333
|
+
console.log(colors.gray(`Variable $${key} set.`));
|
|
252
334
|
}
|
|
253
335
|
resolveVariables(value) {
|
|
254
336
|
if (value.startsWith('$')) {
|
|
@@ -297,18 +379,18 @@ export class RekShell {
|
|
|
297
379
|
const { UDPTransport } = await import('../../transport/udp.js');
|
|
298
380
|
const transport = new UDPTransport(url);
|
|
299
381
|
const msg = Object.keys(body).length ? JSON.stringify(body) : 'ping';
|
|
300
|
-
console.log(
|
|
382
|
+
console.log(colors.gray(`UDP packet -> ${url}`));
|
|
301
383
|
const res = await transport.dispatch({
|
|
302
384
|
url, method: 'GET', headers: new Headers(),
|
|
303
385
|
body: msg, withHeader: () => ({}), withBody: () => ({})
|
|
304
386
|
});
|
|
305
387
|
const text = await res.text();
|
|
306
|
-
console.log(
|
|
388
|
+
console.log(colors.green('✔ Sent/Received'));
|
|
307
389
|
if (text)
|
|
308
390
|
console.log(text);
|
|
309
391
|
return;
|
|
310
392
|
}
|
|
311
|
-
console.log(
|
|
393
|
+
console.log(colors.gray(`${method} ${url}...`));
|
|
312
394
|
try {
|
|
313
395
|
const hasBody = Object.keys(body).length > 0;
|
|
314
396
|
const res = await this.client.request(url, {
|
|
@@ -317,9 +399,9 @@ export class RekShell {
|
|
|
317
399
|
json: hasBody ? body : undefined
|
|
318
400
|
});
|
|
319
401
|
const duration = Math.round(performance.now() - startTime);
|
|
320
|
-
const statusColor = res.ok ?
|
|
321
|
-
console.log(`${statusColor(
|
|
322
|
-
`${
|
|
402
|
+
const statusColor = res.ok ? colors.green : colors.red;
|
|
403
|
+
console.log(`${statusColor(colors.bold(res.status))} ${statusColor(res.statusText)} ` +
|
|
404
|
+
`${colors.gray(`(${duration}ms)`)}`);
|
|
323
405
|
const text = await res.text();
|
|
324
406
|
const isJson = res.headers.get('content-type')?.includes('json');
|
|
325
407
|
if (isJson) {
|
|
@@ -339,43 +421,594 @@ export class RekShell {
|
|
|
339
421
|
}
|
|
340
422
|
}
|
|
341
423
|
catch (error) {
|
|
342
|
-
console.error(
|
|
424
|
+
console.error(colors.red(`Error: ${error.message}`));
|
|
425
|
+
}
|
|
426
|
+
console.log('');
|
|
427
|
+
}
|
|
428
|
+
async runWhois(domain) {
|
|
429
|
+
if (!domain) {
|
|
430
|
+
domain = this.getRootDomain() || '';
|
|
431
|
+
if (!domain) {
|
|
432
|
+
console.log(colors.yellow('Usage: whois <domain>'));
|
|
433
|
+
console.log(colors.gray(' Examples: whois google.com | whois 8.8.8.8'));
|
|
434
|
+
console.log(colors.gray(' Or set a base URL first: url https://example.com'));
|
|
435
|
+
return;
|
|
436
|
+
}
|
|
437
|
+
}
|
|
438
|
+
const maxRetries = 3;
|
|
439
|
+
let lastError = null;
|
|
440
|
+
for (let attempt = 1; attempt <= maxRetries; attempt++) {
|
|
441
|
+
if (attempt === 1) {
|
|
442
|
+
console.log(colors.gray(`Looking up ${domain}...`));
|
|
443
|
+
}
|
|
444
|
+
else {
|
|
445
|
+
console.log(colors.gray(`Retrying (${attempt}/${maxRetries})...`));
|
|
446
|
+
}
|
|
447
|
+
const startTime = performance.now();
|
|
448
|
+
try {
|
|
449
|
+
const result = await whois(domain);
|
|
450
|
+
const duration = Math.round(performance.now() - startTime);
|
|
451
|
+
console.log(colors.green(`✔ WHOIS lookup completed`) + colors.gray(` (${duration}ms)`));
|
|
452
|
+
console.log(colors.gray(`Server: ${result.server}\n`));
|
|
453
|
+
const importantFields = [
|
|
454
|
+
'domain name', 'registrar', 'registrar url',
|
|
455
|
+
'creation date', 'registry expiry date', 'updated date',
|
|
456
|
+
'domain status', 'name server', 'dnssec',
|
|
457
|
+
'organization', 'orgname', 'cidr', 'netname', 'country'
|
|
458
|
+
];
|
|
459
|
+
let foundFields = 0;
|
|
460
|
+
for (const field of importantFields) {
|
|
461
|
+
const value = result.data[field];
|
|
462
|
+
if (value) {
|
|
463
|
+
const displayValue = Array.isArray(value) ? value.join(', ') : value;
|
|
464
|
+
console.log(` ${colors.cyan(field)}: ${displayValue}`);
|
|
465
|
+
foundFields++;
|
|
466
|
+
}
|
|
467
|
+
}
|
|
468
|
+
if (foundFields === 0 && Object.keys(result.data).length > 0) {
|
|
469
|
+
console.log(colors.gray(' (showing all available fields)\n'));
|
|
470
|
+
for (const [key, value] of Object.entries(result.data)) {
|
|
471
|
+
if (value) {
|
|
472
|
+
const displayValue = Array.isArray(value) ? value.join(', ') : String(value);
|
|
473
|
+
console.log(` ${colors.cyan(key)}: ${displayValue}`);
|
|
474
|
+
}
|
|
475
|
+
}
|
|
476
|
+
}
|
|
477
|
+
if (Object.keys(result.data).length === 0 && result.raw) {
|
|
478
|
+
console.log(colors.gray(' (raw response)\n'));
|
|
479
|
+
console.log(colors.white(result.raw.slice(0, 2000)));
|
|
480
|
+
}
|
|
481
|
+
const available = await isDomainAvailable(domain);
|
|
482
|
+
if (available) {
|
|
483
|
+
console.log(colors.green(`\n✓ Domain appears to be available`));
|
|
484
|
+
}
|
|
485
|
+
this.lastResponse = result.data;
|
|
486
|
+
console.log('');
|
|
487
|
+
return;
|
|
488
|
+
}
|
|
489
|
+
catch (error) {
|
|
490
|
+
lastError = error;
|
|
491
|
+
const isRetryable = error.code === 'ECONNRESET' ||
|
|
492
|
+
error.code === 'ECONNREFUSED' ||
|
|
493
|
+
error.code === 'ETIMEDOUT' ||
|
|
494
|
+
error.code === 'ENOTFOUND' ||
|
|
495
|
+
error.message?.includes('timeout') ||
|
|
496
|
+
error.message?.includes('WHOIS query failed');
|
|
497
|
+
if (!isRetryable || attempt === maxRetries) {
|
|
498
|
+
break;
|
|
499
|
+
}
|
|
500
|
+
await new Promise(resolve => setTimeout(resolve, 500 * attempt));
|
|
501
|
+
}
|
|
502
|
+
}
|
|
503
|
+
const errorMsg = lastError?.message || 'Unknown error';
|
|
504
|
+
const errorCode = lastError?.code;
|
|
505
|
+
console.error(colors.red(`WHOIS failed: ${errorMsg}`));
|
|
506
|
+
if (errorCode) {
|
|
507
|
+
console.error(colors.gray(` Error code: ${errorCode}`));
|
|
508
|
+
}
|
|
509
|
+
if (lastError?.suggestions?.length) {
|
|
510
|
+
console.log(colors.yellow(' Suggestions:'));
|
|
511
|
+
for (const suggestion of lastError.suggestions) {
|
|
512
|
+
console.log(colors.gray(` • ${suggestion}`));
|
|
513
|
+
}
|
|
514
|
+
}
|
|
515
|
+
console.log('');
|
|
516
|
+
}
|
|
517
|
+
async runTLS(host, port = 443) {
|
|
518
|
+
if (!host) {
|
|
519
|
+
host = this.getBaseDomain() || '';
|
|
520
|
+
if (!host) {
|
|
521
|
+
console.log(colors.yellow('Usage: tls <host> [port]'));
|
|
522
|
+
console.log(colors.gray(' Examples: tls google.com | tls api.stripe.com 443'));
|
|
523
|
+
console.log(colors.gray(' Or set a base URL first: url https://example.com'));
|
|
524
|
+
return;
|
|
525
|
+
}
|
|
526
|
+
}
|
|
527
|
+
else {
|
|
528
|
+
host = host.replace(/^https?:\/\//, '').split('/')[0];
|
|
529
|
+
}
|
|
530
|
+
console.log(colors.gray(`Inspecting TLS for ${host}:${port}...`));
|
|
531
|
+
const startTime = performance.now();
|
|
532
|
+
try {
|
|
533
|
+
const info = await inspectTLS(host, port);
|
|
534
|
+
const duration = Math.round(performance.now() - startTime);
|
|
535
|
+
const statusIcon = info.valid ? colors.green('✔') : colors.red('✖');
|
|
536
|
+
const statusText = info.valid ? colors.green('Valid') : colors.red('Invalid/Expired');
|
|
537
|
+
console.log(`${statusIcon} Certificate ${statusText}` + colors.gray(` (${duration}ms)\n`));
|
|
538
|
+
console.log(colors.bold(' Certificate:'));
|
|
539
|
+
console.log(` ${colors.cyan('Subject')}: ${info.subject?.CN || info.subject?.O || 'N/A'}`);
|
|
540
|
+
console.log(` ${colors.cyan('Issuer')}: ${info.issuer?.CN || info.issuer?.O || 'N/A'}`);
|
|
541
|
+
console.log(` ${colors.cyan('Valid From')}: ${info.validFrom.toISOString()}`);
|
|
542
|
+
console.log(` ${colors.cyan('Valid To')}: ${info.validTo.toISOString()}`);
|
|
543
|
+
const daysColor = info.daysRemaining < 30 ? colors.red : info.daysRemaining < 90 ? colors.yellow : colors.green;
|
|
544
|
+
console.log(` ${colors.cyan('Days Remaining')}: ${daysColor(String(info.daysRemaining))}`);
|
|
545
|
+
console.log(colors.bold('\n Connection:'));
|
|
546
|
+
console.log(` ${colors.cyan('Protocol')}: ${info.protocol || 'N/A'}`);
|
|
547
|
+
console.log(` ${colors.cyan('Cipher')}: ${info.cipher?.name || 'N/A'}`);
|
|
548
|
+
console.log(` ${colors.cyan('Authorized')}: ${info.authorized ? colors.green('Yes') : colors.red('No')}`);
|
|
549
|
+
if (info.authorizationError) {
|
|
550
|
+
console.log(` ${colors.cyan('Auth Error')}: ${colors.red(String(info.authorizationError))}`);
|
|
551
|
+
}
|
|
552
|
+
console.log(colors.bold('\n Fingerprints:'));
|
|
553
|
+
console.log(` ${colors.cyan('SHA1')}: ${info.fingerprint}`);
|
|
554
|
+
console.log(` ${colors.cyan('SHA256')}: ${info.fingerprint256}`);
|
|
555
|
+
console.log(` ${colors.cyan('Serial')}: ${info.serialNumber}`);
|
|
556
|
+
this.lastResponse = info;
|
|
557
|
+
}
|
|
558
|
+
catch (error) {
|
|
559
|
+
console.error(colors.red(`TLS inspection failed: ${error.message}`));
|
|
560
|
+
}
|
|
561
|
+
console.log('');
|
|
562
|
+
}
|
|
563
|
+
async runDNS(domain) {
|
|
564
|
+
if (!domain) {
|
|
565
|
+
domain = this.getBaseDomain() || '';
|
|
566
|
+
if (!domain) {
|
|
567
|
+
console.log(colors.yellow('Usage: dns <domain>'));
|
|
568
|
+
console.log(colors.gray(' Examples: dns google.com | dns github.com'));
|
|
569
|
+
console.log(colors.gray(' Or set a base URL first: url https://example.com'));
|
|
570
|
+
return;
|
|
571
|
+
}
|
|
572
|
+
}
|
|
573
|
+
console.log(colors.gray(`Resolving DNS for ${domain}...`));
|
|
574
|
+
const startTime = performance.now();
|
|
575
|
+
try {
|
|
576
|
+
const [a, aaaa, mx, ns, txt, security] = await Promise.all([
|
|
577
|
+
dns.resolve4(domain).catch(() => []),
|
|
578
|
+
dns.resolve6(domain).catch(() => []),
|
|
579
|
+
dns.resolveMx(domain).catch(() => []),
|
|
580
|
+
dns.resolveNs(domain).catch(() => []),
|
|
581
|
+
dns.resolveTxt(domain).catch(() => []),
|
|
582
|
+
getSecurityRecords(domain).catch(() => ({}))
|
|
583
|
+
]);
|
|
584
|
+
const duration = Math.round(performance.now() - startTime);
|
|
585
|
+
console.log(colors.green(`✔ DNS resolved`) + colors.gray(` (${duration}ms)\n`));
|
|
586
|
+
if (a.length) {
|
|
587
|
+
console.log(colors.bold(' A Records (IPv4):'));
|
|
588
|
+
a.forEach(ip => console.log(` ${colors.cyan('→')} ${ip}`));
|
|
589
|
+
}
|
|
590
|
+
if (aaaa.length) {
|
|
591
|
+
console.log(colors.bold(' AAAA Records (IPv6):'));
|
|
592
|
+
aaaa.forEach(ip => console.log(` ${colors.cyan('→')} ${ip}`));
|
|
593
|
+
}
|
|
594
|
+
if (ns.length) {
|
|
595
|
+
console.log(colors.bold(' NS Records:'));
|
|
596
|
+
ns.forEach(n => console.log(` ${colors.cyan('→')} ${n}`));
|
|
597
|
+
}
|
|
598
|
+
if (mx.length) {
|
|
599
|
+
console.log(colors.bold(' MX Records:'));
|
|
600
|
+
mx.sort((a, b) => a.priority - b.priority)
|
|
601
|
+
.forEach(m => console.log(` ${colors.cyan(String(m.priority).padStart(3))} ${m.exchange}`));
|
|
602
|
+
}
|
|
603
|
+
const sec = security;
|
|
604
|
+
if (sec.spf?.length) {
|
|
605
|
+
console.log(colors.bold(' SPF:'));
|
|
606
|
+
console.log(` ${colors.gray(sec.spf[0].slice(0, 80))}${sec.spf[0].length > 80 ? '...' : ''}`);
|
|
607
|
+
}
|
|
608
|
+
if (sec.dmarc) {
|
|
609
|
+
console.log(colors.bold(' DMARC:'));
|
|
610
|
+
console.log(` ${colors.gray(sec.dmarc.slice(0, 80))}${sec.dmarc.length > 80 ? '...' : ''}`);
|
|
611
|
+
}
|
|
612
|
+
if (sec.caa?.issue?.length) {
|
|
613
|
+
console.log(colors.bold(' CAA:'));
|
|
614
|
+
sec.caa.issue.forEach((ca) => console.log(` ${colors.cyan('issue')} ${ca}`));
|
|
615
|
+
}
|
|
616
|
+
this.lastResponse = { a, aaaa, mx, ns, txt, security };
|
|
617
|
+
}
|
|
618
|
+
catch (error) {
|
|
619
|
+
console.error(colors.red(`DNS lookup failed: ${error.message}`));
|
|
620
|
+
}
|
|
621
|
+
console.log('');
|
|
622
|
+
}
|
|
623
|
+
async runRDAP(domain) {
|
|
624
|
+
if (!domain) {
|
|
625
|
+
domain = this.getRootDomain() || '';
|
|
626
|
+
if (!domain) {
|
|
627
|
+
console.log(colors.yellow('Usage: rdap <domain>'));
|
|
628
|
+
console.log(colors.gray(' Examples: rdap google.com | rdap 8.8.8.8'));
|
|
629
|
+
console.log(colors.gray(' Or set a base URL first: url https://example.com'));
|
|
630
|
+
return;
|
|
631
|
+
}
|
|
632
|
+
}
|
|
633
|
+
console.log(colors.gray(`RDAP lookup for ${domain}...`));
|
|
634
|
+
const startTime = performance.now();
|
|
635
|
+
try {
|
|
636
|
+
const result = await rdap(this.client, domain);
|
|
637
|
+
const duration = Math.round(performance.now() - startTime);
|
|
638
|
+
console.log(colors.green(`✔ RDAP lookup completed`) + colors.gray(` (${duration}ms)\n`));
|
|
639
|
+
if (result.status?.length) {
|
|
640
|
+
console.log(colors.bold(' Status:'));
|
|
641
|
+
result.status.forEach((s) => console.log(` ${colors.cyan('→')} ${s}`));
|
|
642
|
+
}
|
|
643
|
+
if (result.events?.length) {
|
|
644
|
+
console.log(colors.bold(' Events:'));
|
|
645
|
+
result.events.forEach((e) => {
|
|
646
|
+
const date = new Date(e.eventDate).toISOString().split('T')[0];
|
|
647
|
+
console.log(` ${colors.cyan(e.eventAction.padEnd(15))} ${date}`);
|
|
648
|
+
});
|
|
649
|
+
}
|
|
650
|
+
if (result.entities?.length) {
|
|
651
|
+
console.log(colors.bold(' Entities:'));
|
|
652
|
+
result.entities.forEach((e) => {
|
|
653
|
+
const roles = e.roles?.join(', ') || 'unknown';
|
|
654
|
+
console.log(` ${colors.cyan(roles.padEnd(15))} ${e.handle || 'N/A'}`);
|
|
655
|
+
});
|
|
656
|
+
}
|
|
657
|
+
if (result.handle) {
|
|
658
|
+
console.log(` ${colors.cyan('Handle')}: ${result.handle}`);
|
|
659
|
+
}
|
|
660
|
+
if (result.name) {
|
|
661
|
+
console.log(` ${colors.cyan('Name')}: ${result.name}`);
|
|
662
|
+
}
|
|
663
|
+
if (result.startAddress && result.endAddress) {
|
|
664
|
+
console.log(` ${colors.cyan('Range')}: ${result.startAddress} - ${result.endAddress}`);
|
|
665
|
+
}
|
|
666
|
+
this.lastResponse = result;
|
|
667
|
+
}
|
|
668
|
+
catch (error) {
|
|
669
|
+
console.error(colors.red(`RDAP lookup failed: ${error.message}`));
|
|
670
|
+
console.log(colors.gray(' Tip: RDAP may not be available for all TLDs. Try "whois" instead.'));
|
|
671
|
+
}
|
|
672
|
+
console.log('');
|
|
673
|
+
}
|
|
674
|
+
async runPing(host) {
|
|
675
|
+
if (!host) {
|
|
676
|
+
host = this.getBaseDomain() || '';
|
|
677
|
+
if (!host) {
|
|
678
|
+
console.log(colors.yellow('Usage: ping <host>'));
|
|
679
|
+
console.log(colors.gray(' Or set a base URL first: url https://example.com'));
|
|
680
|
+
return;
|
|
681
|
+
}
|
|
682
|
+
}
|
|
683
|
+
else {
|
|
684
|
+
host = host.replace(/^https?:\/\//, '').split('/')[0];
|
|
685
|
+
}
|
|
686
|
+
console.log(colors.gray(`Pinging ${host}...`));
|
|
687
|
+
try {
|
|
688
|
+
const { connect } = await import('node:net');
|
|
689
|
+
const port = 443;
|
|
690
|
+
const startTime = performance.now();
|
|
691
|
+
await new Promise((resolve, reject) => {
|
|
692
|
+
const socket = connect(port, host, () => {
|
|
693
|
+
const duration = Math.round(performance.now() - startTime);
|
|
694
|
+
console.log(colors.green(`✔ ${host}:${port} is reachable`) + colors.gray(` (${duration}ms)`));
|
|
695
|
+
socket.end();
|
|
696
|
+
resolve();
|
|
697
|
+
});
|
|
698
|
+
socket.on('error', reject);
|
|
699
|
+
socket.setTimeout(5000, () => {
|
|
700
|
+
socket.destroy();
|
|
701
|
+
reject(new Error('Connection timed out'));
|
|
702
|
+
});
|
|
703
|
+
});
|
|
704
|
+
}
|
|
705
|
+
catch (error) {
|
|
706
|
+
console.error(colors.red(`✖ ${host} is unreachable: ${error.message}`));
|
|
707
|
+
}
|
|
708
|
+
console.log('');
|
|
709
|
+
}
|
|
710
|
+
async runScrap(url) {
|
|
711
|
+
if (!url) {
|
|
712
|
+
if (!this.baseUrl) {
|
|
713
|
+
console.log(colors.yellow('Usage: scrap <url>'));
|
|
714
|
+
console.log(colors.gray(' Examples: scrap https://news.ycombinator.com'));
|
|
715
|
+
console.log(colors.gray(' Or set a base URL first: url https://example.com'));
|
|
716
|
+
return;
|
|
717
|
+
}
|
|
718
|
+
url = this.baseUrl;
|
|
719
|
+
}
|
|
720
|
+
else if (!url.startsWith('http')) {
|
|
721
|
+
url = this.baseUrl ? `${this.baseUrl}${url.startsWith('/') ? '' : '/'}${url}` : `https://${url}`;
|
|
722
|
+
}
|
|
723
|
+
console.log(colors.gray(`Fetching ${url}...`));
|
|
724
|
+
const startTime = performance.now();
|
|
725
|
+
try {
|
|
726
|
+
const response = await this.client.get(url);
|
|
727
|
+
const html = await response.text();
|
|
728
|
+
const duration = Math.round(performance.now() - startTime);
|
|
729
|
+
this.currentDoc = await ScrapeDocument.create(html);
|
|
730
|
+
this.currentDocUrl = url;
|
|
731
|
+
const elementCount = this.currentDoc.select('*').length;
|
|
732
|
+
const title = this.currentDoc.selectFirst('title').text() || 'No title';
|
|
733
|
+
console.log(colors.green(`✔ Loaded`) + colors.gray(` (${duration}ms)`));
|
|
734
|
+
console.log(` ${colors.cyan('Title')}: ${title}`);
|
|
735
|
+
console.log(` ${colors.cyan('Elements')}: ${elementCount}`);
|
|
736
|
+
console.log(` ${colors.cyan('Size')}: ${(html.length / 1024).toFixed(1)}kb`);
|
|
737
|
+
console.log(colors.gray('\n Use $ <selector> to query, $text, $attr, $links, $images, $table'));
|
|
738
|
+
}
|
|
739
|
+
catch (error) {
|
|
740
|
+
console.error(colors.red(`Scrape failed: ${error.message}`));
|
|
741
|
+
}
|
|
742
|
+
console.log('');
|
|
743
|
+
}
|
|
744
|
+
async runSelect(selector) {
|
|
745
|
+
if (!this.currentDoc) {
|
|
746
|
+
console.log(colors.yellow('No document loaded. Use "scrap <url>" first.'));
|
|
747
|
+
return;
|
|
748
|
+
}
|
|
749
|
+
if (!selector) {
|
|
750
|
+
console.log(colors.yellow('Usage: $ <selector>'));
|
|
751
|
+
console.log(colors.gray(' Examples: $ h1 | $ .title | $ a[href*="article"]'));
|
|
752
|
+
return;
|
|
753
|
+
}
|
|
754
|
+
try {
|
|
755
|
+
const elements = this.currentDoc.select(selector);
|
|
756
|
+
const count = elements.length;
|
|
757
|
+
console.log(colors.cyan(`Found ${count} element(s)`));
|
|
758
|
+
if (count > 0 && count <= 10) {
|
|
759
|
+
elements.each((el, i) => {
|
|
760
|
+
const text = el.text().slice(0, 80).replace(/\s+/g, ' ').trim();
|
|
761
|
+
console.log(` ${colors.gray(`${i + 1}.`)} ${text}${text.length >= 80 ? '...' : ''}`);
|
|
762
|
+
});
|
|
763
|
+
}
|
|
764
|
+
else if (count > 10) {
|
|
765
|
+
console.log(colors.gray(' (showing first 10)'));
|
|
766
|
+
let shown = 0;
|
|
767
|
+
elements.each((el, i) => {
|
|
768
|
+
if (shown >= 10)
|
|
769
|
+
return;
|
|
770
|
+
const text = el.text().slice(0, 80).replace(/\s+/g, ' ').trim();
|
|
771
|
+
console.log(` ${colors.gray(`${i + 1}.`)} ${text}${text.length >= 80 ? '...' : ''}`);
|
|
772
|
+
shown++;
|
|
773
|
+
});
|
|
774
|
+
}
|
|
775
|
+
this.lastResponse = { count, selector };
|
|
776
|
+
}
|
|
777
|
+
catch (error) {
|
|
778
|
+
console.error(colors.red(`Query failed: ${error.message}`));
|
|
779
|
+
}
|
|
780
|
+
console.log('');
|
|
781
|
+
}
|
|
782
|
+
async runSelectText(selector) {
|
|
783
|
+
if (!this.currentDoc) {
|
|
784
|
+
console.log(colors.yellow('No document loaded. Use "scrap <url>" first.'));
|
|
785
|
+
return;
|
|
786
|
+
}
|
|
787
|
+
if (!selector) {
|
|
788
|
+
console.log(colors.yellow('Usage: $text <selector>'));
|
|
789
|
+
return;
|
|
790
|
+
}
|
|
791
|
+
try {
|
|
792
|
+
const elements = this.currentDoc.select(selector);
|
|
793
|
+
const texts = [];
|
|
794
|
+
elements.each((el, i) => {
|
|
795
|
+
const text = el.text().trim();
|
|
796
|
+
if (text) {
|
|
797
|
+
texts.push(text);
|
|
798
|
+
console.log(`${colors.gray(`${i + 1}.`)} ${text.slice(0, 200)}${text.length > 200 ? '...' : ''}`);
|
|
799
|
+
}
|
|
800
|
+
});
|
|
801
|
+
this.lastResponse = texts;
|
|
802
|
+
console.log(colors.gray(`\n ${texts.length} text item(s) extracted`));
|
|
803
|
+
}
|
|
804
|
+
catch (error) {
|
|
805
|
+
console.error(colors.red(`Query failed: ${error.message}`));
|
|
806
|
+
}
|
|
807
|
+
console.log('');
|
|
808
|
+
}
|
|
809
|
+
async runSelectAttr(attrName, selector) {
|
|
810
|
+
if (!this.currentDoc) {
|
|
811
|
+
console.log(colors.yellow('No document loaded. Use "scrap <url>" first.'));
|
|
812
|
+
return;
|
|
813
|
+
}
|
|
814
|
+
if (!attrName || !selector) {
|
|
815
|
+
console.log(colors.yellow('Usage: $attr <attribute> <selector>'));
|
|
816
|
+
console.log(colors.gray(' Examples: $attr href a | $attr src img'));
|
|
817
|
+
return;
|
|
818
|
+
}
|
|
819
|
+
try {
|
|
820
|
+
const elements = this.currentDoc.select(selector);
|
|
821
|
+
const attrs = [];
|
|
822
|
+
elements.each((el, i) => {
|
|
823
|
+
const value = el.attr(attrName);
|
|
824
|
+
if (value) {
|
|
825
|
+
attrs.push(value);
|
|
826
|
+
console.log(`${colors.gray(`${i + 1}.`)} ${value}`);
|
|
827
|
+
}
|
|
828
|
+
});
|
|
829
|
+
this.lastResponse = attrs;
|
|
830
|
+
console.log(colors.gray(`\n ${attrs.length} attribute(s) extracted`));
|
|
831
|
+
}
|
|
832
|
+
catch (error) {
|
|
833
|
+
console.error(colors.red(`Query failed: ${error.message}`));
|
|
834
|
+
}
|
|
835
|
+
console.log('');
|
|
836
|
+
}
|
|
837
|
+
async runSelectHtml(selector) {
|
|
838
|
+
if (!this.currentDoc) {
|
|
839
|
+
console.log(colors.yellow('No document loaded. Use "scrap <url>" first.'));
|
|
840
|
+
return;
|
|
841
|
+
}
|
|
842
|
+
if (!selector) {
|
|
843
|
+
console.log(colors.yellow('Usage: $html <selector>'));
|
|
844
|
+
return;
|
|
845
|
+
}
|
|
846
|
+
try {
|
|
847
|
+
const element = this.currentDoc.selectFirst(selector);
|
|
848
|
+
const html = element.html();
|
|
849
|
+
if (html) {
|
|
850
|
+
console.log(html.slice(0, 1000));
|
|
851
|
+
if (html.length > 1000) {
|
|
852
|
+
console.log(colors.gray(`\n ... (${html.length} chars total)`));
|
|
853
|
+
}
|
|
854
|
+
this.lastResponse = html;
|
|
855
|
+
}
|
|
856
|
+
else {
|
|
857
|
+
console.log(colors.gray('No element found'));
|
|
858
|
+
}
|
|
859
|
+
}
|
|
860
|
+
catch (error) {
|
|
861
|
+
console.error(colors.red(`Query failed: ${error.message}`));
|
|
862
|
+
}
|
|
863
|
+
console.log('');
|
|
864
|
+
}
|
|
865
|
+
async runSelectLinks(selector) {
|
|
866
|
+
if (!this.currentDoc) {
|
|
867
|
+
console.log(colors.yellow('No document loaded. Use "scrap <url>" first.'));
|
|
868
|
+
return;
|
|
869
|
+
}
|
|
870
|
+
try {
|
|
871
|
+
const linkSelector = selector || 'a[href]';
|
|
872
|
+
const elements = this.currentDoc.select(linkSelector);
|
|
873
|
+
const links = [];
|
|
874
|
+
elements.each((el, i) => {
|
|
875
|
+
const href = el.attr('href');
|
|
876
|
+
const text = el.text().trim().slice(0, 50);
|
|
877
|
+
if (href) {
|
|
878
|
+
links.push({ text, href });
|
|
879
|
+
if (i < 20) {
|
|
880
|
+
console.log(`${colors.gray(`${i + 1}.`)} ${colors.cyan(text || '(no text)')} ${colors.gray('→')} ${href}`);
|
|
881
|
+
}
|
|
882
|
+
}
|
|
883
|
+
});
|
|
884
|
+
if (links.length > 20) {
|
|
885
|
+
console.log(colors.gray(` ... and ${links.length - 20} more links`));
|
|
886
|
+
}
|
|
887
|
+
this.lastResponse = links;
|
|
888
|
+
console.log(colors.gray(`\n ${links.length} link(s) found`));
|
|
889
|
+
}
|
|
890
|
+
catch (error) {
|
|
891
|
+
console.error(colors.red(`Query failed: ${error.message}`));
|
|
892
|
+
}
|
|
893
|
+
console.log('');
|
|
894
|
+
}
|
|
895
|
+
async runSelectImages(selector) {
|
|
896
|
+
if (!this.currentDoc) {
|
|
897
|
+
console.log(colors.yellow('No document loaded. Use "scrap <url>" first.'));
|
|
898
|
+
return;
|
|
899
|
+
}
|
|
900
|
+
try {
|
|
901
|
+
const imgSelector = selector || 'img[src]';
|
|
902
|
+
const elements = this.currentDoc.select(imgSelector);
|
|
903
|
+
const images = [];
|
|
904
|
+
elements.each((el, i) => {
|
|
905
|
+
const src = el.attr('src');
|
|
906
|
+
const alt = el.attr('alt') || '';
|
|
907
|
+
if (src) {
|
|
908
|
+
images.push({ alt, src });
|
|
909
|
+
if (i < 20) {
|
|
910
|
+
console.log(`${colors.gray(`${i + 1}.`)} ${colors.cyan(alt.slice(0, 30) || '(no alt)')} ${colors.gray('→')} ${src.slice(0, 60)}`);
|
|
911
|
+
}
|
|
912
|
+
}
|
|
913
|
+
});
|
|
914
|
+
if (images.length > 20) {
|
|
915
|
+
console.log(colors.gray(` ... and ${images.length - 20} more images`));
|
|
916
|
+
}
|
|
917
|
+
this.lastResponse = images;
|
|
918
|
+
console.log(colors.gray(`\n ${images.length} image(s) found`));
|
|
919
|
+
}
|
|
920
|
+
catch (error) {
|
|
921
|
+
console.error(colors.red(`Query failed: ${error.message}`));
|
|
922
|
+
}
|
|
923
|
+
console.log('');
|
|
924
|
+
}
|
|
925
|
+
async runSelectTable(selector) {
|
|
926
|
+
if (!this.currentDoc) {
|
|
927
|
+
console.log(colors.yellow('No document loaded. Use "scrap <url>" first.'));
|
|
928
|
+
return;
|
|
929
|
+
}
|
|
930
|
+
if (!selector) {
|
|
931
|
+
console.log(colors.yellow('Usage: $table <selector>'));
|
|
932
|
+
console.log(colors.gray(' Examples: $table table | $table .data-table'));
|
|
933
|
+
return;
|
|
934
|
+
}
|
|
935
|
+
try {
|
|
936
|
+
const tables = this.currentDoc.tables(selector);
|
|
937
|
+
if (tables.length === 0) {
|
|
938
|
+
console.log(colors.gray('No tables found'));
|
|
939
|
+
return;
|
|
940
|
+
}
|
|
941
|
+
tables.forEach((table, tableIndex) => {
|
|
942
|
+
console.log(colors.bold(`\nTable ${tableIndex + 1}:`));
|
|
943
|
+
if (table.headers.length > 0) {
|
|
944
|
+
console.log(colors.cyan(' Headers: ') + table.headers.join(' | '));
|
|
945
|
+
}
|
|
946
|
+
console.log(colors.cyan(` Rows: `) + table.rows.length);
|
|
947
|
+
table.rows.slice(0, 5).forEach((row, i) => {
|
|
948
|
+
const rowStr = row.map(cell => cell.slice(0, 20)).join(' | ');
|
|
949
|
+
console.log(` ${colors.gray(`${i + 1}.`)} ${rowStr}`);
|
|
950
|
+
});
|
|
951
|
+
if (table.rows.length > 5) {
|
|
952
|
+
console.log(colors.gray(` ... and ${table.rows.length - 5} more rows`));
|
|
953
|
+
}
|
|
954
|
+
});
|
|
955
|
+
this.lastResponse = tables;
|
|
956
|
+
}
|
|
957
|
+
catch (error) {
|
|
958
|
+
console.error(colors.red(`Query failed: ${error.message}`));
|
|
343
959
|
}
|
|
344
960
|
console.log('');
|
|
345
961
|
}
|
|
346
962
|
printHelp() {
|
|
347
963
|
console.log(`
|
|
348
|
-
${
|
|
964
|
+
${colors.bold(colors.cyan('Rek Console Help'))}
|
|
965
|
+
|
|
966
|
+
${colors.bold('Core Commands:')}
|
|
967
|
+
${colors.green('url <url>')} Set persistent Base URL.
|
|
968
|
+
${colors.green('set <key>=<val>')} Set a session variable.
|
|
969
|
+
${colors.green('vars')} List all session variables.
|
|
970
|
+
${colors.green('clear')} Clear the screen.
|
|
971
|
+
${colors.green('exit')} Exit the console.
|
|
972
|
+
|
|
973
|
+
${colors.bold('HTTP Requests:')}
|
|
974
|
+
${colors.green('<method> <path>')} Execute HTTP request (GET, POST, PUT, DELETE, etc).
|
|
975
|
+
${colors.gray('Params:')} ${colors.white('key=value')} (string) or ${colors.white('key:=value')} (typed).
|
|
976
|
+
${colors.gray('Headers:')} ${colors.white('Key:Value')}
|
|
349
977
|
|
|
350
|
-
${
|
|
351
|
-
${
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
978
|
+
${colors.bold('Advanced Tools:')}
|
|
979
|
+
${colors.green('load <url>')} Run Load Test.
|
|
980
|
+
${colors.gray('Options:')}
|
|
981
|
+
${colors.white('users=50')} ${colors.gray('Concurrent users')}
|
|
982
|
+
${colors.white('duration=300')} ${colors.gray('Duration in seconds')}
|
|
983
|
+
${colors.white('ramp=5')} ${colors.gray('Ramp-up time in seconds')}
|
|
984
|
+
${colors.white('mode=realistic')} ${colors.gray('realistic | throughput | stress')}
|
|
985
|
+
${colors.white('http2=false')} ${colors.gray('Force HTTP/2')}
|
|
356
986
|
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
${
|
|
360
|
-
${pc.gray('Headers:')} ${pc.white('Key:Value')}
|
|
987
|
+
${colors.green('chat <provider>')} Start AI Chat.
|
|
988
|
+
${colors.gray('Providers:')} ${colors.white('openai')}, ${colors.white('anthropic')}
|
|
989
|
+
${colors.gray('Arg:')} ${colors.white('model=...')} (optional)
|
|
361
990
|
|
|
362
|
-
|
|
363
|
-
${
|
|
364
|
-
${pc.gray('Options:')}
|
|
365
|
-
${pc.white('users=50')} ${pc.gray('Concurrent users')}
|
|
366
|
-
${pc.white('duration=300')} ${pc.gray('Duration in seconds')}
|
|
367
|
-
${pc.white('ramp=5')} ${pc.gray('Ramp-up time in seconds')}
|
|
368
|
-
${pc.white('mode=throughput')}${pc.gray('throughput | stress | realistic')}
|
|
369
|
-
${pc.white('http2=false')} ${pc.gray('Force HTTP/2')}
|
|
991
|
+
${colors.green('ws <url>')} Start interactive WebSocket session.
|
|
992
|
+
${colors.green('udp <url>')} Send UDP packet.
|
|
370
993
|
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
994
|
+
${colors.bold('Network Tools:')}
|
|
995
|
+
${colors.green('whois <domain>')} WHOIS lookup (domain or IP).
|
|
996
|
+
${colors.green('tls <host> [port]')} Inspect TLS/SSL certificate.
|
|
997
|
+
${colors.green('dns <domain>')} Full DNS lookup (A, AAAA, MX, NS, SPF, DMARC).
|
|
998
|
+
${colors.green('rdap <domain>')} RDAP lookup (modern WHOIS).
|
|
999
|
+
${colors.green('ping <host>')} Quick TCP connectivity check.
|
|
374
1000
|
|
|
375
|
-
|
|
376
|
-
${
|
|
1001
|
+
${colors.bold('Web Scraping:')}
|
|
1002
|
+
${colors.green('scrap <url>')} Fetch and parse HTML document.
|
|
1003
|
+
${colors.green('$ <selector>')} Query elements (CSS selector).
|
|
1004
|
+
${colors.green('$text <selector>')} Extract text content.
|
|
1005
|
+
${colors.green('$attr <name> <sel>')} Extract attribute values.
|
|
1006
|
+
${colors.green('$html <selector>')} Get inner HTML.
|
|
1007
|
+
${colors.green('$links [selector]')} List all links.
|
|
1008
|
+
${colors.green('$images [selector]')} List all images.
|
|
1009
|
+
${colors.green('$table <selector>')} Extract table as data.
|
|
377
1010
|
|
|
378
|
-
${
|
|
1011
|
+
${colors.bold('Examples:')}
|
|
379
1012
|
› url httpbin.org
|
|
380
1013
|
› get /json
|
|
381
1014
|
› post /post name="Neo" active:=true role:Admin
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"whois.d.ts","sourceRoot":"","sources":["../../src/utils/whois.ts"],"names":[],"mappings":"AAQA,MAAM,WAAW,YAAY;IAK3B,MAAM,CAAC,EAAE,MAAM,CAAC;IAKhB,IAAI,CAAC,EAAE,MAAM,CAAC;IAMd,OAAO,CAAC,EAAE,MAAM,CAAC;IAMjB,MAAM,CAAC,EAAE,OAAO,CAAC;CAClB;AAED,MAAM,WAAW,WAAW;IAI1B,GAAG,EAAE,MAAM,CAAC;IAKZ,KAAK,EAAE,MAAM,CAAC;IAKd,MAAM,EAAE,MAAM,CAAC;IAKf,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,EAAE,CAAC,CAAC;CACzC;
|
|
1
|
+
{"version":3,"file":"whois.d.ts","sourceRoot":"","sources":["../../src/utils/whois.ts"],"names":[],"mappings":"AAQA,MAAM,WAAW,YAAY;IAK3B,MAAM,CAAC,EAAE,MAAM,CAAC;IAKhB,IAAI,CAAC,EAAE,MAAM,CAAC;IAMd,OAAO,CAAC,EAAE,MAAM,CAAC;IAMjB,MAAM,CAAC,EAAE,OAAO,CAAC;CAClB;AAED,MAAM,WAAW,WAAW;IAI1B,GAAG,EAAE,MAAM,CAAC;IAKZ,KAAK,EAAE,MAAM,CAAC;IAKd,MAAM,EAAE,MAAM,CAAC;IAKf,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,EAAE,CAAC,CAAC;CACzC;AAmND,wBAAsB,KAAK,CACzB,KAAK,EAAE,MAAM,EACb,OAAO,GAAE,YAAiB,GACzB,OAAO,CAAC,WAAW,CAAC,CAmCtB;AAMD,wBAAsB,iBAAiB,CACrC,MAAM,EAAE,MAAM,EACd,OAAO,CAAC,EAAE,YAAY,GACrB,OAAO,CAAC,OAAO,CAAC,CAoBlB"}
|
package/dist/utils/whois.js
CHANGED
|
@@ -120,11 +120,26 @@ async function queryWhoisServer(server, query, port, timeout) {
|
|
|
120
120
|
});
|
|
121
121
|
socket.on('error', (error) => {
|
|
122
122
|
clearTimeout(timeoutId);
|
|
123
|
-
|
|
123
|
+
const errorDetail = error.message || error.code || 'Connection failed';
|
|
124
|
+
const err = new ReckerError(`WHOIS query failed: ${errorDetail}`, undefined, undefined, [
|
|
124
125
|
'Verify the WHOIS server is reachable and correct.',
|
|
125
126
|
'Check network/firewall settings blocking WHOIS port 43.',
|
|
126
127
|
'Retry the query; transient network issues can occur.'
|
|
127
|
-
])
|
|
128
|
+
]);
|
|
129
|
+
err.code = error.code;
|
|
130
|
+
reject(err);
|
|
131
|
+
});
|
|
132
|
+
socket.on('close', (hadError) => {
|
|
133
|
+
clearTimeout(timeoutId);
|
|
134
|
+
if (hadError && !response) {
|
|
135
|
+
const err = new ReckerError('WHOIS connection closed unexpectedly', undefined, undefined, [
|
|
136
|
+
'The WHOIS server may be rate limiting requests.',
|
|
137
|
+
'Try again in a few seconds.',
|
|
138
|
+
'Some TLDs have unreliable WHOIS servers.'
|
|
139
|
+
]);
|
|
140
|
+
err.code = 'ECONNRESET';
|
|
141
|
+
reject(err);
|
|
142
|
+
}
|
|
128
143
|
});
|
|
129
144
|
});
|
|
130
145
|
}
|