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 CHANGED
@@ -6,7 +6,7 @@
6
6
 
7
7
  ### The HTTP SDK for the AI Era
8
8
 
9
- **Stop fetching. Start orchestrating.**
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
  [![npm version](https://img.shields.io/npm/v/recker.svg?style=flat-square&color=F5A623)](https://www.npmjs.com/package/recker)
12
12
  [![npm downloads](https://img.shields.io/npm/dm/recker.svg?style=flat-square&color=34C759)](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/getting-started/quickstart.md)
106
- - [Client Configuration](./docs/guides/client-config.md)
105
+ - [Quick Start](./docs/http/01-quickstart.md)
106
+ - [Client Configuration](./docs/http/05-configuration.md)
107
107
 
108
108
  **Core Features**
109
- - [HTTP Methods (19)](./docs/guides/http-methods.md)
110
- - [Streaming & SSE](./docs/guides/streaming.md)
111
- - [Retry & Backoff](./docs/guides/advanced/retry.md)
112
- - [Caching](./docs/guides/caching.md)
113
- - [Pagination](./docs/guides/pagination.md)
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/guides/graphql.md)
117
- - [Scraping](./docs/guides/scraping.md)
118
- - [Hooks](./docs/guides/hooks.md)
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/README.md)
122
- - [Troubleshooting](./docs/guides/troubleshooting.md)
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":"AAmBA,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"}
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"}
@@ -6,8 +6,13 @@ let highlight;
6
6
  const ora = oraImport;
7
7
  async function initDependencies() {
8
8
  if (!highlight) {
9
- const cardinal = await requireOptional('cardinal', 'recker/cli');
10
- highlight = cardinal.highlight;
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) {
@@ -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":"AAsBA,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;IAMJ,KAAK;IA8BlB,OAAO,CAAC,MAAM;YAKA,aAAa;YAgGb,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;IAyE5B,OAAO,CAAC,SAAS;CAwClB"}
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"}
@@ -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 pc from '../../utils/colors.js';
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
- const cardinal = await requireOptional('cardinal', 'recker/cli');
10
- highlight = cardinal.highlight;
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 ? pc.cyan(new URL(this.baseUrl).hostname) : pc.gray('rek');
41
- 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('.');
42
78
  }
43
79
  completer(line) {
44
- const commands = ['get', 'post', 'put', 'delete', 'patch', 'head', 'options', 'ws', 'udp', 'load', 'chat', 'ai', 'help', 'clear', 'exit', 'set', 'url'];
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(pc.bold(pc.cyan('Rek Console')));
52
- console.log(pc.gray('Chat with your APIs. Type "help" for magic.'));
53
- 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'));
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
- readline.clearLine(process.stdout, 0);
64
- this.prompt();
105
+ console.log('');
106
+ this.rl.close();
65
107
  });
66
108
  this.rl.on('close', () => {
67
- console.log(pc.gray('\nSee ya.'));
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(pc.red(`Unknown command: ${cmd}`));
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(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.'));
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 = 'throughput';
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(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'));
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(pc.red('Load Test Failed: ' + e.message));
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(pc.gray(`Base URL set to: ${pc.cyan(this.baseUrl)}`));
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(pc.gray(`Variable $${key} set.`));
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(pc.gray(`UDP packet -> ${url}`));
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(pc.green('✔ Sent/Received'));
388
+ console.log(colors.green('✔ Sent/Received'));
307
389
  if (text)
308
390
  console.log(text);
309
391
  return;
310
392
  }
311
- console.log(pc.gray(`${method} ${url}...`));
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 ? pc.green : pc.red;
321
- console.log(`${statusColor(pc.bold(res.status))} ${statusColor(res.statusText)} ` +
322
- `${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)`)}`);
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(pc.red(`Error: ${error.message}`));
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
- ${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.
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
- ${pc.bold('Core Commands:')}
351
- ${pc.green('url <url>')} Set persistent Base URL.
352
- ${pc.green('set <key>=<val>')} Set a session variable.
353
- ${pc.green('vars')} List all session variables.
354
- ${pc.green('clear')} Clear the screen.
355
- ${pc.green('exit')} Exit the console.
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
- ${pc.bold('HTTP Requests:')}
358
- ${pc.green('<method> <path>')} Execute HTTP request (GET, POST, PUT, DELETE, etc).
359
- ${pc.gray('Params:')} ${pc.white('key=value')} (string) or ${pc.white('key:=value')} (typed).
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
- ${pc.bold('Advanced Tools:')}
363
- ${pc.green('load <url>')} Run Load Test.
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
- ${pc.green('chat <provider>')} Start AI Chat.
372
- ${pc.gray('Providers:')} ${pc.white('openai')}, ${pc.white('anthropic')}
373
- ${pc.gray('Arg:')} ${pc.white('model=...')} (optional)
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
- ${pc.green('ws <url>')} Start interactive WebSocket session.
376
- ${pc.green('udp <url>')} Send UDP packet.
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
- ${pc.bold('Examples:')}
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;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.7",
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",