recker 1.0.8 → 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/dist/cli/tui/shell.d.ts +12 -0
- package/dist/cli/tui/shell.d.ts.map +1 -1
- package/dist/cli/tui/shell.js +533 -148
- package/dist/utils/whois.d.ts.map +1 -1
- package/dist/utils/whois.js +17 -2
- package/package.json +1 -1
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;
|
|
@@ -27,6 +31,14 @@ export declare class RekShell {
|
|
|
27
31
|
private runDNS;
|
|
28
32
|
private runRDAP;
|
|
29
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;
|
|
30
42
|
private printHelp;
|
|
31
43
|
}
|
|
32
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
|
@@ -7,7 +7,8 @@ import { whois, isDomainAvailable } from '../../utils/whois.js';
|
|
|
7
7
|
import { inspectTLS } from '../../utils/tls-inspector.js';
|
|
8
8
|
import { getSecurityRecords } from '../../utils/dns-toolkit.js';
|
|
9
9
|
import { rdap } from '../../utils/rdap.js';
|
|
10
|
-
import
|
|
10
|
+
import { ScrapeDocument } from '../../scrape/document.js';
|
|
11
|
+
import colors from '../../utils/colors.js';
|
|
11
12
|
let highlight;
|
|
12
13
|
async function initDependencies() {
|
|
13
14
|
if (!highlight) {
|
|
@@ -28,6 +29,8 @@ export class RekShell {
|
|
|
28
29
|
lastResponse = null;
|
|
29
30
|
variables = {};
|
|
30
31
|
initialized = false;
|
|
32
|
+
currentDoc = null;
|
|
33
|
+
currentDocUrl = '';
|
|
31
34
|
constructor() {
|
|
32
35
|
this.client = createClient({
|
|
33
36
|
baseUrl: 'http://localhost',
|
|
@@ -47,14 +50,38 @@ export class RekShell {
|
|
|
47
50
|
this.initialized = true;
|
|
48
51
|
}
|
|
49
52
|
getPrompt() {
|
|
50
|
-
const base = this.baseUrl ?
|
|
51
|
-
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('.');
|
|
52
78
|
}
|
|
53
79
|
completer(line) {
|
|
54
80
|
const commands = [
|
|
55
81
|
'get', 'post', 'put', 'delete', 'patch', 'head', 'options',
|
|
56
82
|
'ws', 'udp', 'load', 'chat', 'ai',
|
|
57
83
|
'whois', 'tls', 'ssl', 'dns', 'rdap', 'ping',
|
|
84
|
+
'scrap', '$', '$text', '$attr', '$html', '$links', '$images', '$table',
|
|
58
85
|
'help', 'clear', 'exit', 'set', 'url', 'vars'
|
|
59
86
|
];
|
|
60
87
|
const hits = commands.filter((c) => c.startsWith(line));
|
|
@@ -63,9 +90,9 @@ export class RekShell {
|
|
|
63
90
|
async start() {
|
|
64
91
|
await this.ensureInitialized();
|
|
65
92
|
console.clear();
|
|
66
|
-
console.log(
|
|
67
|
-
console.log(
|
|
68
|
-
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'));
|
|
69
96
|
this.prompt();
|
|
70
97
|
this.rl.on('line', async (line) => {
|
|
71
98
|
const input = line.trim();
|
|
@@ -75,11 +102,11 @@ export class RekShell {
|
|
|
75
102
|
this.prompt();
|
|
76
103
|
});
|
|
77
104
|
this.rl.on('SIGINT', () => {
|
|
78
|
-
|
|
79
|
-
this.
|
|
105
|
+
console.log('');
|
|
106
|
+
this.rl.close();
|
|
80
107
|
});
|
|
81
108
|
this.rl.on('close', () => {
|
|
82
|
-
console.log(
|
|
109
|
+
console.log(colors.gray('\nSee ya.'));
|
|
83
110
|
process.exit(0);
|
|
84
111
|
});
|
|
85
112
|
}
|
|
@@ -135,6 +162,30 @@ export class RekShell {
|
|
|
135
162
|
case 'ping':
|
|
136
163
|
await this.runPing(parts[1]);
|
|
137
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;
|
|
138
189
|
}
|
|
139
190
|
const methods = ['get', 'post', 'put', 'delete', 'patch', 'head', 'options'];
|
|
140
191
|
let method = 'GET';
|
|
@@ -152,12 +203,12 @@ export class RekShell {
|
|
|
152
203
|
bodyParts = parts.slice(1);
|
|
153
204
|
}
|
|
154
205
|
else {
|
|
155
|
-
console.log(
|
|
206
|
+
console.log(colors.red(`Unknown command: ${cmd}`));
|
|
156
207
|
return;
|
|
157
208
|
}
|
|
158
209
|
url = this.resolveUrl(url);
|
|
159
210
|
if (!url) {
|
|
160
|
-
console.log(
|
|
211
|
+
console.log(colors.yellow('No URL provided and no Base URL set. Use "url <url>" or provide full URL.'));
|
|
161
212
|
return;
|
|
162
213
|
}
|
|
163
214
|
const body = {};
|
|
@@ -212,7 +263,7 @@ export class RekShell {
|
|
|
212
263
|
let targetUrl = '';
|
|
213
264
|
let users = 50;
|
|
214
265
|
let duration = 300;
|
|
215
|
-
let mode = '
|
|
266
|
+
let mode = 'realistic';
|
|
216
267
|
let http2 = false;
|
|
217
268
|
let rampUp = 5;
|
|
218
269
|
for (const arg of args) {
|
|
@@ -239,7 +290,7 @@ export class RekShell {
|
|
|
239
290
|
}
|
|
240
291
|
targetUrl = this.resolveUrl(targetUrl);
|
|
241
292
|
if (!targetUrl) {
|
|
242
|
-
console.log(
|
|
293
|
+
console.log(colors.yellow('Target URL required. usage: load <url> users=10 duration=10s ramp=5'));
|
|
243
294
|
return;
|
|
244
295
|
}
|
|
245
296
|
const { startLoadDashboard } = await import('./load-dashboard.js');
|
|
@@ -256,7 +307,7 @@ export class RekShell {
|
|
|
256
307
|
});
|
|
257
308
|
}
|
|
258
309
|
catch (e) {
|
|
259
|
-
console.error(
|
|
310
|
+
console.error(colors.red('Load Test Failed: ' + e.message));
|
|
260
311
|
}
|
|
261
312
|
finally {
|
|
262
313
|
process.stdout.write('\x1B[?25h');
|
|
@@ -271,7 +322,7 @@ export class RekShell {
|
|
|
271
322
|
if (!url.startsWith('http'))
|
|
272
323
|
url = `https://${url}`;
|
|
273
324
|
this.baseUrl = url;
|
|
274
|
-
console.log(
|
|
325
|
+
console.log(colors.gray(`Base URL set to: ${colors.cyan(this.baseUrl)}`));
|
|
275
326
|
}
|
|
276
327
|
setVariable(args) {
|
|
277
328
|
const [expr] = args;
|
|
@@ -279,7 +330,7 @@ export class RekShell {
|
|
|
279
330
|
return;
|
|
280
331
|
const [key, val] = expr.split('=');
|
|
281
332
|
this.variables[key] = val;
|
|
282
|
-
console.log(
|
|
333
|
+
console.log(colors.gray(`Variable $${key} set.`));
|
|
283
334
|
}
|
|
284
335
|
resolveVariables(value) {
|
|
285
336
|
if (value.startsWith('$')) {
|
|
@@ -328,18 +379,18 @@ export class RekShell {
|
|
|
328
379
|
const { UDPTransport } = await import('../../transport/udp.js');
|
|
329
380
|
const transport = new UDPTransport(url);
|
|
330
381
|
const msg = Object.keys(body).length ? JSON.stringify(body) : 'ping';
|
|
331
|
-
console.log(
|
|
382
|
+
console.log(colors.gray(`UDP packet -> ${url}`));
|
|
332
383
|
const res = await transport.dispatch({
|
|
333
384
|
url, method: 'GET', headers: new Headers(),
|
|
334
385
|
body: msg, withHeader: () => ({}), withBody: () => ({})
|
|
335
386
|
});
|
|
336
387
|
const text = await res.text();
|
|
337
|
-
console.log(
|
|
388
|
+
console.log(colors.green('✔ Sent/Received'));
|
|
338
389
|
if (text)
|
|
339
390
|
console.log(text);
|
|
340
391
|
return;
|
|
341
392
|
}
|
|
342
|
-
console.log(
|
|
393
|
+
console.log(colors.gray(`${method} ${url}...`));
|
|
343
394
|
try {
|
|
344
395
|
const hasBody = Object.keys(body).length > 0;
|
|
345
396
|
const res = await this.client.request(url, {
|
|
@@ -348,9 +399,9 @@ export class RekShell {
|
|
|
348
399
|
json: hasBody ? body : undefined
|
|
349
400
|
});
|
|
350
401
|
const duration = Math.round(performance.now() - startTime);
|
|
351
|
-
const statusColor = res.ok ?
|
|
352
|
-
console.log(`${statusColor(
|
|
353
|
-
`${
|
|
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)`)}`);
|
|
354
405
|
const text = await res.text();
|
|
355
406
|
const isJson = res.headers.get('content-type')?.includes('json');
|
|
356
407
|
if (isJson) {
|
|
@@ -370,94 +421,156 @@ export class RekShell {
|
|
|
370
421
|
}
|
|
371
422
|
}
|
|
372
423
|
catch (error) {
|
|
373
|
-
console.error(
|
|
424
|
+
console.error(colors.red(`Error: ${error.message}`));
|
|
374
425
|
}
|
|
375
426
|
console.log('');
|
|
376
427
|
}
|
|
377
428
|
async runWhois(domain) {
|
|
378
429
|
if (!domain) {
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
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
|
+
}
|
|
382
437
|
}
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
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`));
|
|
401
484
|
}
|
|
485
|
+
this.lastResponse = result.data;
|
|
486
|
+
console.log('');
|
|
487
|
+
return;
|
|
402
488
|
}
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
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));
|
|
406
501
|
}
|
|
407
|
-
this.lastResponse = result.data;
|
|
408
502
|
}
|
|
409
|
-
|
|
410
|
-
|
|
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
|
+
}
|
|
411
514
|
}
|
|
412
515
|
console.log('');
|
|
413
516
|
}
|
|
414
517
|
async runTLS(host, port = 443) {
|
|
415
518
|
if (!host) {
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
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];
|
|
419
529
|
}
|
|
420
|
-
|
|
421
|
-
console.log(pc.gray(`Inspecting TLS for ${host}:${port}...`));
|
|
530
|
+
console.log(colors.gray(`Inspecting TLS for ${host}:${port}...`));
|
|
422
531
|
const startTime = performance.now();
|
|
423
532
|
try {
|
|
424
533
|
const info = await inspectTLS(host, port);
|
|
425
534
|
const duration = Math.round(performance.now() - startTime);
|
|
426
|
-
const statusIcon = info.valid ?
|
|
427
|
-
const statusText = info.valid ?
|
|
428
|
-
console.log(`${statusIcon} Certificate ${statusText}` +
|
|
429
|
-
console.log(
|
|
430
|
-
console.log(` ${
|
|
431
|
-
console.log(` ${
|
|
432
|
-
console.log(` ${
|
|
433
|
-
console.log(` ${
|
|
434
|
-
const daysColor = info.daysRemaining < 30 ?
|
|
435
|
-
console.log(` ${
|
|
436
|
-
console.log(
|
|
437
|
-
console.log(` ${
|
|
438
|
-
console.log(` ${
|
|
439
|
-
console.log(` ${
|
|
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')}`);
|
|
440
549
|
if (info.authorizationError) {
|
|
441
|
-
console.log(` ${
|
|
550
|
+
console.log(` ${colors.cyan('Auth Error')}: ${colors.red(String(info.authorizationError))}`);
|
|
442
551
|
}
|
|
443
|
-
console.log(
|
|
444
|
-
console.log(` ${
|
|
445
|
-
console.log(` ${
|
|
446
|
-
console.log(` ${
|
|
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}`);
|
|
447
556
|
this.lastResponse = info;
|
|
448
557
|
}
|
|
449
558
|
catch (error) {
|
|
450
|
-
console.error(
|
|
559
|
+
console.error(colors.red(`TLS inspection failed: ${error.message}`));
|
|
451
560
|
}
|
|
452
561
|
console.log('');
|
|
453
562
|
}
|
|
454
563
|
async runDNS(domain) {
|
|
455
564
|
if (!domain) {
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
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
|
+
}
|
|
459
572
|
}
|
|
460
|
-
console.log(
|
|
573
|
+
console.log(colors.gray(`Resolving DNS for ${domain}...`));
|
|
461
574
|
const startTime = performance.now();
|
|
462
575
|
try {
|
|
463
576
|
const [a, aaaa, mx, ns, txt, security] = await Promise.all([
|
|
@@ -469,98 +582,108 @@ export class RekShell {
|
|
|
469
582
|
getSecurityRecords(domain).catch(() => ({}))
|
|
470
583
|
]);
|
|
471
584
|
const duration = Math.round(performance.now() - startTime);
|
|
472
|
-
console.log(
|
|
585
|
+
console.log(colors.green(`✔ DNS resolved`) + colors.gray(` (${duration}ms)\n`));
|
|
473
586
|
if (a.length) {
|
|
474
|
-
console.log(
|
|
475
|
-
a.forEach(ip => console.log(` ${
|
|
587
|
+
console.log(colors.bold(' A Records (IPv4):'));
|
|
588
|
+
a.forEach(ip => console.log(` ${colors.cyan('→')} ${ip}`));
|
|
476
589
|
}
|
|
477
590
|
if (aaaa.length) {
|
|
478
|
-
console.log(
|
|
479
|
-
aaaa.forEach(ip => console.log(` ${
|
|
591
|
+
console.log(colors.bold(' AAAA Records (IPv6):'));
|
|
592
|
+
aaaa.forEach(ip => console.log(` ${colors.cyan('→')} ${ip}`));
|
|
480
593
|
}
|
|
481
594
|
if (ns.length) {
|
|
482
|
-
console.log(
|
|
483
|
-
ns.forEach(n => console.log(` ${
|
|
595
|
+
console.log(colors.bold(' NS Records:'));
|
|
596
|
+
ns.forEach(n => console.log(` ${colors.cyan('→')} ${n}`));
|
|
484
597
|
}
|
|
485
598
|
if (mx.length) {
|
|
486
|
-
console.log(
|
|
599
|
+
console.log(colors.bold(' MX Records:'));
|
|
487
600
|
mx.sort((a, b) => a.priority - b.priority)
|
|
488
|
-
.forEach(m => console.log(` ${
|
|
601
|
+
.forEach(m => console.log(` ${colors.cyan(String(m.priority).padStart(3))} ${m.exchange}`));
|
|
489
602
|
}
|
|
490
603
|
const sec = security;
|
|
491
604
|
if (sec.spf?.length) {
|
|
492
|
-
console.log(
|
|
493
|
-
console.log(` ${
|
|
605
|
+
console.log(colors.bold(' SPF:'));
|
|
606
|
+
console.log(` ${colors.gray(sec.spf[0].slice(0, 80))}${sec.spf[0].length > 80 ? '...' : ''}`);
|
|
494
607
|
}
|
|
495
608
|
if (sec.dmarc) {
|
|
496
|
-
console.log(
|
|
497
|
-
console.log(` ${
|
|
609
|
+
console.log(colors.bold(' DMARC:'));
|
|
610
|
+
console.log(` ${colors.gray(sec.dmarc.slice(0, 80))}${sec.dmarc.length > 80 ? '...' : ''}`);
|
|
498
611
|
}
|
|
499
612
|
if (sec.caa?.issue?.length) {
|
|
500
|
-
console.log(
|
|
501
|
-
sec.caa.issue.forEach((ca) => console.log(` ${
|
|
613
|
+
console.log(colors.bold(' CAA:'));
|
|
614
|
+
sec.caa.issue.forEach((ca) => console.log(` ${colors.cyan('issue')} ${ca}`));
|
|
502
615
|
}
|
|
503
616
|
this.lastResponse = { a, aaaa, mx, ns, txt, security };
|
|
504
617
|
}
|
|
505
618
|
catch (error) {
|
|
506
|
-
console.error(
|
|
619
|
+
console.error(colors.red(`DNS lookup failed: ${error.message}`));
|
|
507
620
|
}
|
|
508
621
|
console.log('');
|
|
509
622
|
}
|
|
510
623
|
async runRDAP(domain) {
|
|
511
624
|
if (!domain) {
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
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
|
+
}
|
|
515
632
|
}
|
|
516
|
-
console.log(
|
|
633
|
+
console.log(colors.gray(`RDAP lookup for ${domain}...`));
|
|
517
634
|
const startTime = performance.now();
|
|
518
635
|
try {
|
|
519
636
|
const result = await rdap(this.client, domain);
|
|
520
637
|
const duration = Math.round(performance.now() - startTime);
|
|
521
|
-
console.log(
|
|
638
|
+
console.log(colors.green(`✔ RDAP lookup completed`) + colors.gray(` (${duration}ms)\n`));
|
|
522
639
|
if (result.status?.length) {
|
|
523
|
-
console.log(
|
|
524
|
-
result.status.forEach((s) => console.log(` ${
|
|
640
|
+
console.log(colors.bold(' Status:'));
|
|
641
|
+
result.status.forEach((s) => console.log(` ${colors.cyan('→')} ${s}`));
|
|
525
642
|
}
|
|
526
643
|
if (result.events?.length) {
|
|
527
|
-
console.log(
|
|
644
|
+
console.log(colors.bold(' Events:'));
|
|
528
645
|
result.events.forEach((e) => {
|
|
529
646
|
const date = new Date(e.eventDate).toISOString().split('T')[0];
|
|
530
|
-
console.log(` ${
|
|
647
|
+
console.log(` ${colors.cyan(e.eventAction.padEnd(15))} ${date}`);
|
|
531
648
|
});
|
|
532
649
|
}
|
|
533
650
|
if (result.entities?.length) {
|
|
534
|
-
console.log(
|
|
651
|
+
console.log(colors.bold(' Entities:'));
|
|
535
652
|
result.entities.forEach((e) => {
|
|
536
653
|
const roles = e.roles?.join(', ') || 'unknown';
|
|
537
|
-
console.log(` ${
|
|
654
|
+
console.log(` ${colors.cyan(roles.padEnd(15))} ${e.handle || 'N/A'}`);
|
|
538
655
|
});
|
|
539
656
|
}
|
|
540
657
|
if (result.handle) {
|
|
541
|
-
console.log(` ${
|
|
658
|
+
console.log(` ${colors.cyan('Handle')}: ${result.handle}`);
|
|
542
659
|
}
|
|
543
660
|
if (result.name) {
|
|
544
|
-
console.log(` ${
|
|
661
|
+
console.log(` ${colors.cyan('Name')}: ${result.name}`);
|
|
545
662
|
}
|
|
546
663
|
if (result.startAddress && result.endAddress) {
|
|
547
|
-
console.log(` ${
|
|
664
|
+
console.log(` ${colors.cyan('Range')}: ${result.startAddress} - ${result.endAddress}`);
|
|
548
665
|
}
|
|
549
666
|
this.lastResponse = result;
|
|
550
667
|
}
|
|
551
668
|
catch (error) {
|
|
552
|
-
console.error(
|
|
553
|
-
console.log(
|
|
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.'));
|
|
554
671
|
}
|
|
555
672
|
console.log('');
|
|
556
673
|
}
|
|
557
674
|
async runPing(host) {
|
|
558
675
|
if (!host) {
|
|
559
|
-
|
|
560
|
-
|
|
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
|
+
}
|
|
561
682
|
}
|
|
562
|
-
|
|
563
|
-
|
|
683
|
+
else {
|
|
684
|
+
host = host.replace(/^https?:\/\//, '').split('/')[0];
|
|
685
|
+
}
|
|
686
|
+
console.log(colors.gray(`Pinging ${host}...`));
|
|
564
687
|
try {
|
|
565
688
|
const { connect } = await import('node:net');
|
|
566
689
|
const port = 443;
|
|
@@ -568,7 +691,7 @@ export class RekShell {
|
|
|
568
691
|
await new Promise((resolve, reject) => {
|
|
569
692
|
const socket = connect(port, host, () => {
|
|
570
693
|
const duration = Math.round(performance.now() - startTime);
|
|
571
|
-
console.log(
|
|
694
|
+
console.log(colors.green(`✔ ${host}:${port} is reachable`) + colors.gray(` (${duration}ms)`));
|
|
572
695
|
socket.end();
|
|
573
696
|
resolve();
|
|
574
697
|
});
|
|
@@ -580,50 +703,312 @@ export class RekShell {
|
|
|
580
703
|
});
|
|
581
704
|
}
|
|
582
705
|
catch (error) {
|
|
583
|
-
console.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}`));
|
|
584
959
|
}
|
|
585
960
|
console.log('');
|
|
586
961
|
}
|
|
587
962
|
printHelp() {
|
|
588
963
|
console.log(`
|
|
589
|
-
${
|
|
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.
|
|
590
972
|
|
|
591
|
-
${
|
|
592
|
-
${
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
${pc.green('clear')} Clear the screen.
|
|
596
|
-
${pc.green('exit')} Exit the console.
|
|
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')}
|
|
597
977
|
|
|
598
|
-
${
|
|
599
|
-
${
|
|
600
|
-
${
|
|
601
|
-
${
|
|
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')}
|
|
602
986
|
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
${
|
|
606
|
-
${pc.white('users=50')} ${pc.gray('Concurrent users')}
|
|
607
|
-
${pc.white('duration=300')} ${pc.gray('Duration in seconds')}
|
|
608
|
-
${pc.white('ramp=5')} ${pc.gray('Ramp-up time in seconds')}
|
|
609
|
-
${pc.white('mode=throughput')}${pc.gray('throughput | stress | realistic')}
|
|
610
|
-
${pc.white('http2=false')} ${pc.gray('Force HTTP/2')}
|
|
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)
|
|
611
990
|
|
|
612
|
-
${
|
|
613
|
-
|
|
614
|
-
${pc.gray('Arg:')} ${pc.white('model=...')} (optional)
|
|
991
|
+
${colors.green('ws <url>')} Start interactive WebSocket session.
|
|
992
|
+
${colors.green('udp <url>')} Send UDP packet.
|
|
615
993
|
|
|
616
|
-
|
|
617
|
-
${
|
|
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.
|
|
618
1000
|
|
|
619
|
-
${
|
|
620
|
-
${
|
|
621
|
-
${
|
|
622
|
-
${
|
|
623
|
-
${
|
|
624
|
-
${
|
|
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.
|
|
625
1010
|
|
|
626
|
-
${
|
|
1011
|
+
${colors.bold('Examples:')}
|
|
627
1012
|
› url httpbin.org
|
|
628
1013
|
› get /json
|
|
629
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
|
}
|