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.
@@ -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":"AAgCA,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;;YAWd,iBAAiB;IAe/B,OAAO,CAAC,SAAS;IAKjB,OAAO,CAAC,SAAS;IAWJ,KAAK;IA8BlB,OAAO,CAAC,MAAM;YAKA,aAAa;YAgHb,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;YA8CR,MAAM;YAuDN,MAAM;YAuEN,OAAO;YA2DP,OAAO;IAoCrB,OAAO,CAAC,SAAS;CA+ClB"}
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"}
@@ -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 pc from '../../utils/colors.js';
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 ? pc.cyan(new URL(this.baseUrl).hostname) : pc.gray('rek');
51
- return `${base} ${pc.magenta('›')} `;
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(pc.bold(pc.cyan('Rek Console')));
67
- console.log(pc.gray('Chat with your APIs. Type "help" for magic.'));
68
- console.log(pc.gray('--------------------------------------------\n'));
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
- readline.clearLine(process.stdout, 0);
79
- this.prompt();
105
+ console.log('');
106
+ this.rl.close();
80
107
  });
81
108
  this.rl.on('close', () => {
82
- console.log(pc.gray('\nSee ya.'));
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(pc.red(`Unknown command: ${cmd}`));
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(pc.yellow('No URL provided and no Base URL set. Use "url <url>" or provide full URL.'));
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 = 'throughput';
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(pc.yellow('Target URL required. usage: load <url> users=10 duration=10s ramp=5'));
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(pc.red('Load Test Failed: ' + e.message));
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(pc.gray(`Base URL set to: ${pc.cyan(this.baseUrl)}`));
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(pc.gray(`Variable $${key} set.`));
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(pc.gray(`UDP packet -> ${url}`));
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(pc.green('✔ Sent/Received'));
388
+ console.log(colors.green('✔ Sent/Received'));
338
389
  if (text)
339
390
  console.log(text);
340
391
  return;
341
392
  }
342
- console.log(pc.gray(`${method} ${url}...`));
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 ? pc.green : pc.red;
352
- console.log(`${statusColor(pc.bold(res.status))} ${statusColor(res.statusText)} ` +
353
- `${pc.gray(`(${duration}ms)`)}`);
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(pc.red(`Error: ${error.message}`));
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
- console.log(pc.yellow('Usage: whois <domain>'));
380
- console.log(pc.gray(' Examples: whois google.com | whois 8.8.8.8'));
381
- return;
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
- console.log(pc.gray(`Looking up ${domain}...`));
384
- const startTime = performance.now();
385
- try {
386
- const result = await whois(domain);
387
- const duration = Math.round(performance.now() - startTime);
388
- console.log(pc.green(`✔ WHOIS lookup completed`) + pc.gray(` (${duration}ms)`));
389
- console.log(pc.gray(`Server: ${result.server}\n`));
390
- const importantFields = [
391
- 'domain name', 'registrar', 'registrar url',
392
- 'creation date', 'registry expiry date', 'updated date',
393
- 'domain status', 'name server', 'dnssec',
394
- 'organization', 'orgname', 'cidr', 'netname', 'country'
395
- ];
396
- for (const field of importantFields) {
397
- const value = result.data[field];
398
- if (value) {
399
- const displayValue = Array.isArray(value) ? value.join(', ') : value;
400
- console.log(` ${pc.cyan(field)}: ${displayValue}`);
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
- const available = await isDomainAvailable(domain);
404
- if (available) {
405
- console.log(pc.green(`\n✓ Domain appears to be available`));
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
- catch (error) {
410
- console.error(pc.red(`WHOIS failed: ${error.message}`));
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
- console.log(pc.yellow('Usage: tls <host> [port]'));
417
- console.log(pc.gray(' Examples: tls google.com | tls api.stripe.com 443'));
418
- return;
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
- host = host.replace(/^https?:\/\//, '').split('/')[0];
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 ? pc.green('✔') : pc.red('✖');
427
- const statusText = info.valid ? pc.green('Valid') : pc.red('Invalid/Expired');
428
- console.log(`${statusIcon} Certificate ${statusText}` + pc.gray(` (${duration}ms)\n`));
429
- console.log(pc.bold(' Certificate:'));
430
- console.log(` ${pc.cyan('Subject')}: ${info.subject?.CN || info.subject?.O || 'N/A'}`);
431
- console.log(` ${pc.cyan('Issuer')}: ${info.issuer?.CN || info.issuer?.O || 'N/A'}`);
432
- console.log(` ${pc.cyan('Valid From')}: ${info.validFrom.toISOString()}`);
433
- console.log(` ${pc.cyan('Valid To')}: ${info.validTo.toISOString()}`);
434
- const daysColor = info.daysRemaining < 30 ? pc.red : info.daysRemaining < 90 ? pc.yellow : pc.green;
435
- console.log(` ${pc.cyan('Days Remaining')}: ${daysColor(String(info.daysRemaining))}`);
436
- console.log(pc.bold('\n Connection:'));
437
- console.log(` ${pc.cyan('Protocol')}: ${info.protocol || 'N/A'}`);
438
- console.log(` ${pc.cyan('Cipher')}: ${info.cipher?.name || 'N/A'}`);
439
- console.log(` ${pc.cyan('Authorized')}: ${info.authorized ? pc.green('Yes') : pc.red('No')}`);
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(` ${pc.cyan('Auth Error')}: ${pc.red(String(info.authorizationError))}`);
550
+ console.log(` ${colors.cyan('Auth Error')}: ${colors.red(String(info.authorizationError))}`);
442
551
  }
443
- console.log(pc.bold('\n Fingerprints:'));
444
- console.log(` ${pc.cyan('SHA1')}: ${info.fingerprint}`);
445
- console.log(` ${pc.cyan('SHA256')}: ${info.fingerprint256}`);
446
- console.log(` ${pc.cyan('Serial')}: ${info.serialNumber}`);
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(pc.red(`TLS inspection failed: ${error.message}`));
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
- console.log(pc.yellow('Usage: dns <domain>'));
457
- console.log(pc.gray(' Examples: dns google.com | dns github.com'));
458
- return;
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(pc.gray(`Resolving DNS for ${domain}...`));
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(pc.green(`✔ DNS resolved`) + pc.gray(` (${duration}ms)\n`));
585
+ console.log(colors.green(`✔ DNS resolved`) + colors.gray(` (${duration}ms)\n`));
473
586
  if (a.length) {
474
- console.log(pc.bold(' A Records (IPv4):'));
475
- a.forEach(ip => console.log(` ${pc.cyan('→')} ${ip}`));
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(pc.bold(' AAAA Records (IPv6):'));
479
- aaaa.forEach(ip => console.log(` ${pc.cyan('→')} ${ip}`));
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(pc.bold(' NS Records:'));
483
- ns.forEach(n => console.log(` ${pc.cyan('→')} ${n}`));
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(pc.bold(' MX Records:'));
599
+ console.log(colors.bold(' MX Records:'));
487
600
  mx.sort((a, b) => a.priority - b.priority)
488
- .forEach(m => console.log(` ${pc.cyan(String(m.priority).padStart(3))} ${m.exchange}`));
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(pc.bold(' SPF:'));
493
- console.log(` ${pc.gray(sec.spf[0].slice(0, 80))}${sec.spf[0].length > 80 ? '...' : ''}`);
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(pc.bold(' DMARC:'));
497
- console.log(` ${pc.gray(sec.dmarc.slice(0, 80))}${sec.dmarc.length > 80 ? '...' : ''}`);
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(pc.bold(' CAA:'));
501
- sec.caa.issue.forEach((ca) => console.log(` ${pc.cyan('issue')} ${ca}`));
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(pc.red(`DNS lookup failed: ${error.message}`));
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
- console.log(pc.yellow('Usage: rdap <domain>'));
513
- console.log(pc.gray(' Examples: rdap google.com | rdap 8.8.8.8'));
514
- return;
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(pc.gray(`RDAP lookup for ${domain}...`));
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(pc.green(`✔ RDAP lookup completed`) + pc.gray(` (${duration}ms)\n`));
638
+ console.log(colors.green(`✔ RDAP lookup completed`) + colors.gray(` (${duration}ms)\n`));
522
639
  if (result.status?.length) {
523
- console.log(pc.bold(' Status:'));
524
- result.status.forEach((s) => console.log(` ${pc.cyan('→')} ${s}`));
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(pc.bold(' Events:'));
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(` ${pc.cyan(e.eventAction.padEnd(15))} ${date}`);
647
+ console.log(` ${colors.cyan(e.eventAction.padEnd(15))} ${date}`);
531
648
  });
532
649
  }
533
650
  if (result.entities?.length) {
534
- console.log(pc.bold(' Entities:'));
651
+ console.log(colors.bold(' Entities:'));
535
652
  result.entities.forEach((e) => {
536
653
  const roles = e.roles?.join(', ') || 'unknown';
537
- console.log(` ${pc.cyan(roles.padEnd(15))} ${e.handle || 'N/A'}`);
654
+ console.log(` ${colors.cyan(roles.padEnd(15))} ${e.handle || 'N/A'}`);
538
655
  });
539
656
  }
540
657
  if (result.handle) {
541
- console.log(` ${pc.cyan('Handle')}: ${result.handle}`);
658
+ console.log(` ${colors.cyan('Handle')}: ${result.handle}`);
542
659
  }
543
660
  if (result.name) {
544
- console.log(` ${pc.cyan('Name')}: ${result.name}`);
661
+ console.log(` ${colors.cyan('Name')}: ${result.name}`);
545
662
  }
546
663
  if (result.startAddress && result.endAddress) {
547
- console.log(` ${pc.cyan('Range')}: ${result.startAddress} - ${result.endAddress}`);
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(pc.red(`RDAP lookup failed: ${error.message}`));
553
- console.log(pc.gray(' Tip: RDAP may not be available for all TLDs. Try "whois" instead.'));
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
- console.log(pc.yellow('Usage: ping <host>'));
560
- return;
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
- host = host.replace(/^https?:\/\//, '').split('/')[0];
563
- console.log(pc.gray(`Pinging ${host}...`));
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(pc.green(`✔ ${host}:${port} is reachable`) + pc.gray(` (${duration}ms)`));
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(pc.red(`✖ ${host} is unreachable: ${error.message}`));
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
- ${pc.bold(pc.cyan('Rek Console Help'))}
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
- ${pc.bold('Core Commands:')}
592
- ${pc.green('url <url>')} Set persistent Base URL.
593
- ${pc.green('set <key>=<val>')} Set a session variable.
594
- ${pc.green('vars')} List all session variables.
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
- ${pc.bold('HTTP Requests:')}
599
- ${pc.green('<method> <path>')} Execute HTTP request (GET, POST, PUT, DELETE, etc).
600
- ${pc.gray('Params:')} ${pc.white('key=value')} (string) or ${pc.white('key:=value')} (typed).
601
- ${pc.gray('Headers:')} ${pc.white('Key:Value')}
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
- ${pc.bold('Advanced Tools:')}
604
- ${pc.green('load <url>')} Run Load Test.
605
- ${pc.gray('Options:')}
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
- ${pc.green('chat <provider>')} Start AI Chat.
613
- ${pc.gray('Providers:')} ${pc.white('openai')}, ${pc.white('anthropic')}
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
- ${pc.green('ws <url>')} Start interactive WebSocket session.
617
- ${pc.green('udp <url>')} Send UDP packet.
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
- ${pc.bold('Network Tools:')}
620
- ${pc.green('whois <domain>')} WHOIS lookup (domain or IP).
621
- ${pc.green('tls <host> [port]')} Inspect TLS/SSL certificate.
622
- ${pc.green('dns <domain>')} Full DNS lookup (A, AAAA, MX, NS, SPF, DMARC).
623
- ${pc.green('rdap <domain>')} RDAP lookup (modern WHOIS).
624
- ${pc.green('ping <host>')} Quick TCP connectivity check.
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
- ${pc.bold('Examples:')}
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;AA8LD,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"}
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"}
@@ -120,11 +120,26 @@ async function queryWhoisServer(server, query, port, timeout) {
120
120
  });
121
121
  socket.on('error', (error) => {
122
122
  clearTimeout(timeoutId);
123
- reject(new ReckerError(`WHOIS query failed: ${error.message}`, undefined, undefined, [
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
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "recker",
3
- "version": "1.0.8",
3
+ "version": "1.0.9",
4
4
  "description": "AI & DevX focused HTTP client for Node.js 18+",
5
5
  "main": "./dist/index.js",
6
6
  "types": "./dist/index.d.ts",