wildcard-domain-finder-plus 1.0.3 → 1.0.5

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/app.js CHANGED
@@ -1,23 +1,24 @@
1
1
  #!/usr/bin/env node
2
2
 
3
- // wildcard-domain-finder (upgraded)
4
- // - Supports wildcard patterns (* = single char)
5
- // - Optional regex mode
3
+ // wildcard-domain-finder (streaming, old UX, round-robin DNS)
4
+ //
5
+ // - Wildcard patterns (* = single char)
6
6
  // - TLD selection (explicit, all, premium)
7
7
  // - Filtering (tld, length, starts, ends)
8
- // - Sorting (comfirst, tld, length, alpha)
9
- // - Output formats: txt, json, jsonl, csv
10
- // - Caching + resume via JSONL
11
8
  // - Streaming generation (no heap blowups)
12
9
  // - Concurrency + timeout
13
- // - Interactive pause/resume/quit (p/r/q)
10
+ // - Round-robin across many public resolvers (dns2)
11
+ // - Old-style UX: emoji progress, stats, recent found, current domain
12
+ // - Interactive: p = pause, r = resume, q = quit, Ctrl+C = interrupt
14
13
 
15
14
  const fs = require('fs');
16
- const dns = require('dns').promises;
17
15
  const readline = require('readline');
16
+ const { DNSClient } = require('dns2');
18
17
 
18
+ // Character set for wildcard expansion
19
19
  const CHARSET = 'abcdefghijklmnopqrstuvwxyz0123456789';
20
20
 
21
+ // TLD lists
21
22
  const IANA_TLDS = [
22
23
  'com','net','org','edu','gov','mil','int',
23
24
  'ca','us','uk','de','fr','au','jp','cn','io','ai','co','me','tv','cc',
@@ -37,10 +38,35 @@ const IANA_TLDS = [
37
38
 
38
39
  const PREMIUM_TLDS = ['com','net','org','io','ai','co','dev','app','xyz','tech'];
39
40
 
41
+ // Domain validation regex
40
42
  const DOMAIN_REGEX = /^(?=.{1,253}$)(?!.*\.\.)([A-Za-z0-9](?:[A-Za-z0-9-]{0,61}[A-Za-z0-9])?\.)+[A-Za-z]{2,63}$/;
41
43
 
44
+ // Large public resolver pool (round-robin)
45
+ const RESOLVERS = [
46
+ '1.1.1.1','1.0.0.1',
47
+ '8.8.8.8','8.8.4.4',
48
+ '9.9.9.9','149.112.112.112',
49
+ '208.67.222.222','208.67.220.220',
50
+ '185.228.168.9','185.228.169.9',
51
+ '185.228.168.10','185.228.169.11',
52
+ '94.140.14.14','94.140.15.15',
53
+ '156.154.70.1','156.154.71.1',
54
+ '4.2.2.1','4.2.2.2',
55
+ '64.6.64.6','64.6.65.6'
56
+ ];
57
+
58
+ let resolverIndex = 0;
59
+ function nextResolver() {
60
+ const ip = RESOLVERS[resolverIndex];
61
+ resolverIndex = (resolverIndex + 1) % RESOLVERS.length;
62
+ return ip;
63
+ }
64
+
65
+ // Interactive state
42
66
  let paused = false;
43
67
  let quitting = false;
68
+ let currentDomain = '';
69
+ let recentlyFound = []; // last 3 available domains
44
70
 
45
71
  function isValidDomain(domain) {
46
72
  return DOMAIN_REGEX.test(domain);
@@ -69,6 +95,11 @@ function setupInteractiveControls() {
69
95
  process.stdout.write('\nšŸ›‘ Quitting gracefully...\n');
70
96
  }
71
97
  });
98
+
99
+ process.on('SIGINT', () => {
100
+ console.log('\n\nā¹ļø Process interrupted by user (Ctrl+C). Exiting...');
101
+ process.exit(0);
102
+ });
72
103
  }
73
104
 
74
105
  // ---------- CLI PARSING ----------
@@ -77,28 +108,19 @@ function parseArgs() {
77
108
  const args = process.argv.slice(2);
78
109
  const opts = {
79
110
  pattern: null,
80
- regex: null,
81
111
  tlds: null,
82
112
  allTlds: false,
83
113
  premiumTlds: false,
84
114
  filters: [],
85
- sort: null,
86
- format: 'txt',
87
115
  output: 'available_domains.txt',
88
- concurrency: 10,
89
- timeout: 5000,
90
- resume: false,
91
- cacheFile: 'checked_domains.jsonl',
92
- useCache: true,
93
- maxLength: 4
116
+ concurrency: 50,
117
+ timeout: 3000
94
118
  };
95
119
 
96
120
  for (let i = 0; i < args.length; i++) {
97
121
  const a = args[i];
98
122
  if (a === '-d' || a === '--domain') {
99
123
  opts.pattern = args[++i];
100
- } else if (a === '-r' || a === '--regex') {
101
- opts.regex = args[++i];
102
124
  } else if (a === '-t' || a === '--tlds') {
103
125
  const v = args[++i];
104
126
  if (v === 'all') opts.allTlds = true;
@@ -106,26 +128,12 @@ function parseArgs() {
106
128
  else opts.tlds = v.split(',').map(s => s.trim().toLowerCase()).filter(Boolean);
107
129
  } else if (a === '-f' || a === '--filter') {
108
130
  opts.filters.push(args[++i]);
109
- } else if (a === '-s' || a === '--sort') {
110
- opts.sort = args[++i]; // comfirst | tld | length | alpha
111
- } else if (a === '-F' || a === '--format') {
112
- opts.format = args[++i].toLowerCase(); // txt | json | jsonl | csv
113
131
  } else if (a === '-o' || a === '--output') {
114
132
  opts.output = args[++i];
115
133
  } else if (a === '-c' || a === '--concurrency') {
116
- opts.concurrency = parseInt(args[++i], 10) || 10;
134
+ opts.concurrency = parseInt(args[++i], 10) || opts.concurrency;
117
135
  } else if (a === '-T' || a === '--timeout') {
118
- opts.timeout = parseInt(args[++i], 10) || 5000;
119
- } else if (a === '-R' || a === '--resume') {
120
- opts.resume = true;
121
- } else if (a === '--no-resume') {
122
- opts.resume = false;
123
- } else if (a === '-C' || a === '--cache') {
124
- opts.cacheFile = args[++i];
125
- } else if (a === '--no-cache') {
126
- opts.useCache = false;
127
- } else if (a === '--max-length') {
128
- opts.maxLength = parseInt(args[++i], 10) || 4;
136
+ opts.timeout = parseInt(args[++i], 10) || opts.timeout;
129
137
  } else if (a === '-h' || a === '--help') {
130
138
  printHelp();
131
139
  process.exit(0);
@@ -137,18 +145,15 @@ function parseArgs() {
137
145
 
138
146
  function printHelp() {
139
147
  console.log(`
140
- Wildcard Domain Finder (upgraded)
148
+ šŸ” Wildcard Domain Finder (streaming, old-style UX, round-robin DNS)
141
149
 
142
150
  Usage:
143
- wildcard-domain-finder [options]
151
+ node app.js [options]
144
152
 
145
153
  Domain Input:
146
154
  -d, --domain <pattern> Wildcard pattern (* = single char)
147
- -r, --regex <regex> Regex pattern for full domain
148
- -t, --tlds <list> Comma-separated TLDs (e.g. com,net,io)
149
- --tlds all Use all known TLDs
150
- --tlds premium Use premium TLD list
151
- --max-length <n> Max label length for regex mode (default: 4)
155
+ -t, --tlds <list|all|premium>
156
+ e.g. com,net,io | all | premium
152
157
 
153
158
  Filtering:
154
159
  -f, --filter <rule> Filter results:
@@ -159,44 +164,29 @@ Filtering:
159
164
  starts:go
160
165
  ends:ai
161
166
 
162
- Sorting:
163
- -s, --sort <mode> Sort results:
164
- comfirst (.com first)
165
- tld group by TLD
166
- length shortest first
167
- alpha alphabetical
168
-
169
167
  Output:
170
- -F, --format <fmt> txt | json | jsonl | csv
171
- -o, --output <file> Output file path
168
+ -o, --output <file> Output file path (txt, one domain per line)
172
169
 
173
170
  Performance:
174
- -c, --concurrency <n> DNS concurrency (default: 10)
175
- -T, --timeout <ms> DNS timeout (default: 5000)
176
-
177
- Resume / Cache:
178
- -R, --resume Resume from cache (skip already checked)
179
- --no-resume Ignore cache
180
- -C, --cache <file> Cache file (default: checked_domains.jsonl)
181
- --no-cache Disable caching
171
+ -c, --concurrency <n> DNS concurrency (default: 50)
172
+ -T, --timeout <ms> DNS timeout (default: 3000)
182
173
 
183
174
  Interactive Controls:
184
- p Pause
185
- r Resume
186
- q Quit gracefully
175
+ p Pause
176
+ r Resume
177
+ q Quit gracefully
178
+ Ctrl+C Interrupt immediately
187
179
  `);
188
180
  }
189
181
 
190
- // ---------- FILTERS & SORTING ----------
182
+ // ---------- FILTERS ----------
191
183
 
192
184
  function parseFilterRule(rule) {
193
- // tld:com,io
194
185
  if (rule.startsWith('tld:')) {
195
186
  const list = rule.slice(4).split(',').map(s => s.trim().toLowerCase()).filter(Boolean);
196
187
  return { type: 'tld', list };
197
188
  }
198
189
 
199
- // length<=3, length>=2
200
190
  if (rule.startsWith('length<=')) {
201
191
  const n = parseInt(rule.slice('length<='.length), 10);
202
192
  return { type: 'lengthMax', value: n };
@@ -206,13 +196,11 @@ function parseFilterRule(rule) {
206
196
  return { type: 'lengthMin', value: n };
207
197
  }
208
198
 
209
- // starts:go
210
199
  if (rule.startsWith('starts:')) {
211
200
  const v = rule.slice('starts:'.length).toLowerCase();
212
201
  return { type: 'starts', value: v };
213
202
  }
214
203
 
215
- // ends:ai
216
204
  if (rule.startsWith('ends:')) {
217
205
  const v = rule.slice('ends:'.length).toLowerCase();
218
206
  return { type: 'ends', value: v };
@@ -254,43 +242,9 @@ function passesFilters(domain, filters) {
254
242
  return true;
255
243
  }
256
244
 
257
- function sortResults(results, mode) {
258
- if (!mode) return results;
259
-
260
- if (mode === 'comfirst') {
261
- return results.sort((a, b) => {
262
- const aCom = a.tld === 'com' ? 0 : 1;
263
- const bCom = b.tld === 'com' ? 0 : 1;
264
- if (aCom !== bCom) return aCom - bCom;
265
- return a.domain.localeCompare(b.domain);
266
- });
267
- }
268
-
269
- if (mode === 'tld') {
270
- return results.sort((a, b) => {
271
- if (a.tld !== b.tld) return a.tld.localeCompare(b.tld);
272
- return a.domain.localeCompare(b.domain);
273
- });
274
- }
275
-
276
- if (mode === 'length') {
277
- return results.sort((a, b) => {
278
- if (a.name.length !== b.name.length) return a.name.length - b.name.length;
279
- return a.domain.localeCompare(b.domain);
280
- });
281
- }
282
-
283
- if (mode === 'alpha') {
284
- return results.sort((a, b) => a.domain.localeCompare(b.domain));
285
- }
286
-
287
- return results;
288
- }
289
-
290
- // ---------- PATTERN / REGEX GENERATION ----------
245
+ // ---------- PATTERN GENERATION ----------
291
246
 
292
247
  function* expandWildcardPattern(pattern) {
293
- // * = single char from CHARSET
294
248
  function* helper(index, prefix) {
295
249
  if (index === pattern.length) {
296
250
  yield prefix;
@@ -310,7 +264,7 @@ function* expandWildcardPattern(pattern) {
310
264
 
311
265
  function* expandPatternWithTlds(pattern, tlds) {
312
266
  if (pattern.endsWith('.*')) {
313
- const core = pattern.slice(0, -2); // keep trailing dot
267
+ const core = pattern.slice(0, -2);
314
268
  for (const base of expandWildcardPattern(core)) {
315
269
  for (const tld of tlds) {
316
270
  yield base + tld;
@@ -321,116 +275,83 @@ function* expandPatternWithTlds(pattern, tlds) {
321
275
  }
322
276
  }
323
277
 
324
- function* generateAllDomainsForRegex(maxLength, tlds) {
325
- function* build(prefix, depth) {
326
- if (depth === 0) {
327
- for (const tld of tlds) {
328
- yield prefix + '.' + tld;
329
- }
330
- return;
331
- }
332
- for (const c of CHARSET) {
333
- yield* build(prefix + c, depth - 1);
334
- }
335
- }
336
-
337
- for (let len = 1; len <= maxLength; len++) {
338
- yield* build('', len);
339
- }
340
- }
341
-
342
- // ---------- DNS + CACHE ----------
278
+ // ---------- DNS (round-robin resolvers via dns2) ----------
343
279
 
344
280
  async function checkDomain(domain, timeoutMs) {
281
+ const resolver = nextResolver();
282
+ const client = DNSClient({
283
+ dns: resolver,
284
+ port: 53,
285
+ recursive: true
286
+ });
287
+
345
288
  const timeout = new Promise((_, reject) =>
346
289
  setTimeout(() => reject(new Error('TIMEOUT')), timeoutMs)
347
290
  );
348
291
 
349
292
  try {
350
- await Promise.race([
351
- dns.resolveAny(domain),
293
+ const res = await Promise.race([
294
+ client.resolve(domain, 'A'),
352
295
  timeout
353
296
  ]);
354
- return { domain, available: false, error: null };
355
- } catch (err) {
356
- if (err.code === 'ENOTFOUND' || err.code === 'ENODATA') {
297
+ const answers = res && res.answers ? res.answers : [];
298
+ if (answers.length === 0) {
357
299
  return { domain, available: true, error: null };
358
300
  }
359
- if (err.message === 'TIMEOUT') {
360
- return { domain, available: null, error: 'timeout' };
361
- }
362
- return { domain, available: null, error: err.code || err.message };
363
- }
364
- }
365
-
366
- function loadCache(cacheFile) {
367
- const map = new Map();
368
- if (!fs.existsSync(cacheFile)) return map;
369
- const lines = fs.readFileSync(cacheFile, 'utf8').split('\n');
370
- for (const line of lines) {
371
- if (!line.trim()) continue;
372
- try {
373
- const obj = JSON.parse(line);
374
- if (obj.domain) map.set(obj.domain, obj);
375
- } catch {
376
- // ignore bad lines
377
- }
301
+ return { domain, available: false, error: null };
302
+ } catch (err) {
303
+ const msg = err && err.message ? err.message : String(err);
304
+ return { domain, available: true, error: msg };
378
305
  }
379
- return map;
380
- }
381
-
382
- function appendToCache(cacheFile, obj) {
383
- fs.appendFileSync(cacheFile, JSON.stringify(obj) + '\n');
384
306
  }
385
307
 
386
- // ---------- OUTPUT ----------
387
-
388
- function writeOutput(results, opts) {
389
- const out = opts.output;
390
- const fmt = opts.format;
391
-
392
- if (fmt === 'txt') {
393
- const lines = results.map(r => r.domain);
394
- fs.writeFileSync(out, lines.join('\n') + '\n', 'utf8');
395
- console.log(`āœ… Saved ${results.length} domains to ${out} (txt).`);
396
- return;
397
- }
398
-
399
- if (fmt === 'json') {
400
- fs.writeFileSync(out, JSON.stringify(results, null, 2), 'utf8');
401
- console.log(`āœ… Saved ${results.length} domains to ${out} (json).`);
402
- return;
403
- }
404
-
405
- if (fmt === 'jsonl') {
406
- const lines = results.map(r => JSON.stringify(r));
407
- fs.writeFileSync(out, lines.join('\n') + '\n', 'utf8');
408
- console.log(`āœ… Saved ${results.length} domains to ${out} (jsonl).`);
409
- return;
410
- }
411
-
412
- if (fmt === 'csv') {
413
- const header = 'domain,tld,name,available,checkedAt,error\n';
414
- const rows = results.map(r =>
415
- `${r.domain},${r.tld},${r.name},${r.available},${r.checkedAt},${r.error || ''}`
416
- );
417
- fs.writeFileSync(out, header + rows.join('\n') + '\n', 'utf8');
418
- console.log(`āœ… Saved ${results.length} domains to ${out} (csv).`);
419
- return;
308
+ // ---------- PROGRESS DISPLAY (old-style UX) ----------
309
+
310
+ function displayProgress(stats) {
311
+ const elapsed = (Date.now() - stats.startTime) / 1000;
312
+ const rate = stats.checked > 0 ? stats.checked / elapsed : 0;
313
+ const percentage = stats.totalCandidates > 0
314
+ ? (stats.checked / stats.totalCandidates) * 100
315
+ : 0;
316
+
317
+ const barWidth = 40;
318
+ const filledWidth = Math.round((percentage / 100) * barWidth);
319
+ const emptyWidth = barWidth - filledWidth;
320
+ const progressBar = 'ā–ˆ'.repeat(filledWidth) + 'ā–‘'.repeat(emptyWidth);
321
+
322
+ console.clear();
323
+ console.log('šŸ” Wildcard Domain Finder - Live Progress');
324
+ console.log('='.repeat(70));
325
+ console.log(`Progress: [${progressBar}] ${percentage.toFixed(1)}%`);
326
+ console.log(`Status: ${stats.checked.toLocaleString()}/${stats.totalCandidates.toLocaleString()} domains checked`);
327
+ console.log('');
328
+ console.log('šŸ“Š Statistics:');
329
+ console.log(` āœ… Available: ${stats.available.toLocaleString()}`);
330
+ console.log(` āŒ Errors: ${stats.errors.toLocaleString()}`);
331
+ console.log(` ā±ļø Elapsed: ${elapsed.toFixed(1)}s`);
332
+ console.log(` šŸš€ Rate: ${rate.toFixed(1)} domains/sec`);
333
+ console.log('');
334
+ if (recentlyFound.length > 0) {
335
+ console.log('šŸŽÆ Recently Found Available Domains:');
336
+ recentlyFound.forEach((domain, index) => {
337
+ const icon = index === 0 ? 'šŸ†•' : index === 1 ? 'šŸ“Œ' : 'šŸ“‹';
338
+ console.log(` ${icon} ${domain}`);
339
+ });
340
+ } else {
341
+ console.log('šŸ” No available domains found yet...');
420
342
  }
421
-
422
- // fallback
423
- const lines = results.map(r => r.domain);
424
- fs.writeFileSync(out, lines.join('\n') + '\n', 'utf8');
425
- console.log(`āœ… Saved ${results.length} domains to ${out} (txt fallback).`);
343
+ console.log('');
344
+ console.log(`šŸ”„ Currently checking: ${currentDomain || 'Initializing...'}`);
345
+ console.log('='.repeat(70));
346
+ console.log('šŸ’” Keys: p = pause, r = resume, q = quit, Ctrl+C = interrupt');
426
347
  }
427
348
 
428
- // ---------- MAIN RUN ----------
349
+ // ---------- MAIN RUN (STREAMING) ----------
429
350
 
430
351
  async function run() {
431
352
  const opts = parseArgs();
432
353
 
433
- if (!opts.pattern && !opts.regex) {
354
+ if (!opts.pattern) {
434
355
  printHelp();
435
356
  process.exit(1);
436
357
  }
@@ -443,85 +364,66 @@ async function run() {
443
364
  else if (opts.tlds && opts.tlds.length) tlds = opts.tlds;
444
365
  else tlds = ['com'];
445
366
 
446
- let regex = null;
447
- if (opts.regex) {
448
- try {
449
- regex = new RegExp(opts.regex);
450
- } catch (err) {
451
- console.error('Invalid regex:', err.message);
452
- process.exit(1);
453
- }
454
- }
455
-
456
- const cache = opts.useCache ? loadCache(opts.cacheFile) : new Map();
457
-
458
367
  console.log(`šŸš€ Starting domain search`);
459
- if (opts.pattern) console.log(` Pattern: ${opts.pattern}`);
460
- if (regex) console.log(` Regex: ${opts.regex}`);
368
+ console.log(` Pattern: ${opts.pattern}`);
461
369
  console.log(` TLDs: ${tlds.join(', ')}`);
462
370
  console.log(` Concurrency: ${opts.concurrency}, Timeout: ${opts.timeout}ms`);
463
- console.log(` Output: ${opts.output} (${opts.format})`);
464
- if (opts.useCache) console.log(` Cache: ${opts.cacheFile} (${cache.size} entries loaded)`);
371
+ console.log(` Output: ${opts.output}`);
372
+ console.log('\nšŸ’” Keys: p = pause, r = resume, q = quit, Ctrl+C = interrupt\n');
465
373
 
466
374
  setupInteractiveControls();
467
375
 
468
- const iterator = regex
469
- ? generateAllDomainsForRegex(opts.maxLength, tlds)
470
- : expandPatternWithTlds(opts.pattern, tlds);
376
+ const iterator = expandPatternWithTlds(opts.pattern, tlds);
377
+ const outStream = fs.createWriteStream(opts.output, { flags: 'w' });
378
+
379
+ const stats = {
380
+ totalCandidates: 0,
381
+ checked: 0,
382
+ available: 0,
383
+ errors: 0,
384
+ startTime: Date.now()
385
+ };
471
386
 
472
- const availableResults = [];
473
- const tasks = [];
474
387
  let active = 0;
475
- let checked = 0;
476
- let totalCandidates = 0;
477
- const startTime = Date.now();
388
+ const tasks = [];
389
+ let lastProgressUpdate = 0;
390
+ let done = false;
478
391
 
479
392
  async function scheduleNext() {
480
- if (quitting) return;
481
- while (!paused && active < opts.concurrency) {
482
- const next = iterator.next();
483
- if (next.done) break;
484
- const domain = next.value;
485
- totalCandidates++;
393
+ if (quitting || done) return;
394
+ while (!paused && active < opts.concurrency && !done) {
395
+ const { value: domain, done: isDone } = iterator.next();
396
+ if (isDone) {
397
+ done = true;
398
+ break;
399
+ }
400
+
401
+ stats.totalCandidates++;
486
402
 
487
403
  if (!isValidDomain(domain)) continue;
488
404
  if (!passesFilters(domain, filters)) continue;
489
405
 
490
- if (opts.useCache && cache.has(domain)) {
491
- continue;
492
- }
493
-
494
406
  active++;
407
+ currentDomain = domain;
408
+
495
409
  const task = (async () => {
496
410
  const res = await checkDomain(domain, opts.timeout);
497
- checked++;
498
-
499
- const [name, tld] = (() => {
500
- const parts = domain.split('.');
501
- const tld = parts.pop().toLowerCase();
502
- const name = parts.join('.').toLowerCase();
503
- return [name, tld];
504
- })();
505
-
506
- const record = {
507
- domain,
508
- name,
509
- tld,
510
- available: res.available,
511
- checkedAt: new Date().toISOString(),
512
- error: res.error
513
- };
514
-
515
- if (opts.useCache) {
516
- cache.set(domain, record);
517
- appendToCache(opts.cacheFile, record);
411
+ stats.checked++;
412
+
413
+ if (res.available) {
414
+ stats.available++;
415
+ outStream.write(domain + '\n');
416
+
417
+ recentlyFound.unshift(domain);
418
+ if (recentlyFound.length > 3) recentlyFound.pop();
419
+ } else if (res.error) {
420
+ stats.errors++;
518
421
  }
519
422
 
520
- if (res.available === true) {
521
- availableResults.push(record);
522
- process.stdout.write(
523
- `\rChecked: ${checked.toLocaleString()} | Available: ${availableResults.length.toLocaleString()} `
524
- );
423
+ const now = Date.now();
424
+ if (now - lastProgressUpdate > 500 || (done && active === 0)) {
425
+ lastProgressUpdate = now;
426
+ displayProgress(stats);
525
427
  }
526
428
  })().finally(() => {
527
429
  active--;
@@ -531,28 +433,41 @@ async function run() {
531
433
  }
532
434
  }
533
435
 
534
- while (true) {
535
- if (quitting) break;
436
+ while (!done || active > 0) {
437
+ if (quitting && active === 0) break;
536
438
  if (!paused) {
537
439
  await scheduleNext();
538
440
  }
539
- if (active === 0 && iterator.next().done) {
540
- break;
541
- }
542
- await sleep(100);
441
+ await sleep(50);
543
442
  }
544
443
 
545
444
  await Promise.all(tasks);
546
-
547
- const duration = ((Date.now() - startTime) / 1000).toFixed(1);
548
- console.log(`\nā± Done in ${duration}s. Checked ${checked.toLocaleString()} domains.`);
549
- console.log(`āœ… Available: ${availableResults.length.toLocaleString()}`);
550
-
551
- const sorted = sortResults(availableResults, opts.sort);
552
- writeOutput(sorted, opts);
445
+ outStream.end();
446
+
447
+ const totalTime = (Date.now() - stats.startTime) / 1000;
448
+ console.clear();
449
+ console.log('šŸŽ‰ Domain Search Completed');
450
+ console.log('='.repeat(60));
451
+ console.log(`šŸ“Š Total candidates generated: ${stats.totalCandidates.toLocaleString()}`);
452
+ console.log(`šŸ“Š Total domains checked: ${stats.checked.toLocaleString()}`);
453
+ console.log(`āœ… Available domains found: ${stats.available.toLocaleString()}`);
454
+ console.log(`āŒ Errors encountered: ${stats.errors.toLocaleString()}`);
455
+ console.log(`ā±ļø Total time: ${totalTime.toFixed(1)}s`);
456
+ console.log(`šŸš€ Average rate: ${(stats.checked / totalTime).toFixed(1)} domains/sec`);
457
+ console.log(`šŸ“ Results saved to: ${opts.output}`);
458
+ if (stats.available > 0) {
459
+ console.log('');
460
+ console.log('šŸŽÆ Recently Found Available Domains:');
461
+ recentlyFound.forEach((d, i) => {
462
+ console.log(` ${i + 1}. ${d}`);
463
+ });
464
+ } else {
465
+ console.log('');
466
+ console.log('šŸ˜” No available domains found with this pattern.');
467
+ }
468
+ console.log('='.repeat(60));
553
469
  }
554
470
 
555
471
  run().catch(err => {
556
472
  console.error('Fatal error:', err);
557
- process.exit(1);
558
- });
473
+ process