domainstorm 0.2.2 → 0.2.4

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.
Files changed (3) hide show
  1. package/README.md +42 -46
  2. package/check-domains.mjs +270 -15
  3. package/package.json +1 -1
package/README.md CHANGED
@@ -19,7 +19,7 @@ It is built for AI and agent products where naming cycles happen fast and every
19
19
  ## Install
20
20
 
21
21
  ```bash
22
- npm i -g domainstorm
22
+ npm i -g domainstorm@latest
23
23
  ```
24
24
 
25
25
  Run instantly without global install:
@@ -28,21 +28,27 @@ Run instantly without global install:
28
28
  npx --yes domainstorm --help
29
29
  ```
30
30
 
31
+ Update to latest:
32
+
33
+ ```bash
34
+ npm update -g domainstorm
35
+ ```
36
+
31
37
  ## Quick Wins
32
38
 
33
39
  Brainstorm + check + return only likely available:
34
40
 
35
41
  ```bash
36
- domainstorm --brainstorm "agent orchestration" "mcp broker" --tld md --server whois.nic.md --only-available --table
42
+ domainstorm --brainstorm "agent orchestration" "mcp broker" --tld md --server whois.nic.md --dns-prefilter --only-available --table
37
43
  ```
38
44
 
39
45
  Check exact domains:
40
46
 
41
47
  ```bash
42
- domainstorm agentmesh.ai agentmesh.com agentmesh.md
48
+ domainstorm broker.md --server whois.nic.md --dns-prefilter
43
49
  ```
44
50
 
45
- Default output is plain English (for example: `broker.md is available.`).
51
+ Default output is plain English (for example: `✅ broker.md is available.`).
46
52
  Use `--table` if you want a structured table view.
47
53
 
48
54
  Check from file and export CSV:
@@ -73,11 +79,29 @@ Compatibility alias:
73
79
  domain-check --help
74
80
  ```
75
81
 
82
+ ## Local Dev Test (npm)
83
+
84
+ Run the current repo version without publishing:
85
+
86
+ ```bash
87
+ npm run check
88
+ npm link
89
+ domainstorm --help
90
+ domainstorm broker.md --table
91
+ ```
92
+
93
+ Remove local link when done:
94
+
95
+ ```bash
96
+ npm unlink -g domainstorm
97
+ ```
98
+
76
99
  ## Example Output
77
100
 
78
101
  ```txt
79
- agentmesh.md likely_available availability_pattern_match low story=Keyword + product framing
80
- agentops.md registered registration_pattern_match low story=Keyword + product framing
102
+ ✅ broker.md is available.
103
+ agentops.md is not available (already registered).
104
+ Summary: ✅ 1 available, ❌ 1 taken, ⚠️ 0 unknown.
81
105
  ```
82
106
 
83
107
  Output columns:
@@ -85,6 +109,7 @@ Output columns:
85
109
  - `domain`
86
110
  - `status` (`registered`, `likely_available`, `unknown`)
87
111
  - `reason`
112
+ - `state` (registry/DNS state when available)
88
113
  - `legal_risk` (`low`, `high_tm_risk`)
89
114
  - `story` (brainstorm narrative tag)
90
115
  - `error`
@@ -94,57 +119,28 @@ Output columns:
94
119
  - `--brainstorm` / `--storm`
95
120
  - `--max-suggestions <n>` (default: `120`)
96
121
  - `--table`
122
+ - `--plain`
97
123
  - `--input <file>`
98
124
  - `--output <file>`
99
125
  - `--tld <tld>` (default: `md`)
100
126
  - `--server <whois-host>`
127
+ - `--dns-prefilter`
101
128
  - `--only-available`
102
129
  - `--concurrency <n>`
103
130
  - `--timeout-ms <n>`
104
131
  - `--raw`
105
132
 
106
- ## Add `NPM_TOKEN` In GitHub (2 Minutes)
107
-
108
- 1. Create an npm Automation token:
109
- `npmjs.com -> Account Settings -> Access Tokens -> Generate New Token -> Automation`
110
- 2. Open your repo:
111
- `github.com/tanishqsh/domain-cli`
112
- 3. Go to:
113
- `Settings -> Secrets and variables -> Actions`
114
- 4. Click:
115
- `New repository secret`
116
- 5. Set:
117
- `Name = NPM_TOKEN`
118
- `Secret = <your npm automation token>`
119
- 6. Save.
120
-
121
- Now GitHub Actions can publish with `.github/workflows/npm-publish.yml`.
122
-
123
- ## GitHub -> npm Auto Publish
124
-
125
- The publish workflow triggers on:
126
-
127
- - GitHub Release published
128
- - Manual `workflow_dispatch`
129
-
130
- Workflow path:
131
-
132
- `/.github/workflows/npm-publish.yml`
133
-
134
- Important:
135
-
136
- - Bump `package.json` version before publishing a new release
137
- - npm will reject already-published versions
138
-
139
- ## Local Publish
140
-
141
- ```bash
142
- npm login
143
- npm publish --access public
144
- ```
145
-
146
133
  ## Notes
147
134
 
148
135
  - WHOIS formats vary by registry; treat `likely_available` as a pre-check, not final registrar checkout.
149
136
  - Registries can rate-limit bulk lookups; retry `unknown` rows after cooldown.
137
+ - Domainstorm includes registry adapters for `.md`, `.com`, `.org`, and `.ai` to improve state parsing.
138
+ - Domainstorm auto-throttles WHOIS to concurrency `2` across TLDs (override with `--concurrency`).
139
+ - For `.md`, `Domain state` is parsed explicitly (`Inactive`, `OK`, `OK Delegated`, and no-match states).
140
+ - `--dns-prefilter` runs DNS first and marks resolving domains as taken before WHOIS (faster and lighter on WHOIS quota).
150
141
  - Install `whois` locally (`brew install whois` on macOS).
142
+
143
+ ## Maintainers
144
+
145
+ Maintainer-only release and CI publishing instructions are in:
146
+ `MAINTAINERS.md`
package/check-domains.mjs CHANGED
@@ -5,9 +5,11 @@ import path from "node:path";
5
5
  import { spawn } from "node:child_process";
6
6
 
7
7
  const DEFAULT_CONCURRENCY = 6;
8
+ const SAFE_WHOIS_CONCURRENCY = 2;
8
9
  const DEFAULT_TIMEOUT_MS = 12000;
9
10
  const DEFAULT_TLD = "md";
10
11
  const DEFAULT_MAX_SUGGESTIONS = 120;
12
+ const MD_WHOIS_SERVER = "whois.nic.md";
11
13
  const AVAILABILITY_PATTERNS = [
12
14
  "no match for",
13
15
  "not found",
@@ -112,8 +114,9 @@ function printUsage() {
112
114
  " --output <file> Optional CSV output path",
113
115
  " --tld <tld> Default TLD for bare labels (default: md)",
114
116
  " --server <host> Optional WHOIS server (ex: whois.nic.md)",
115
- " --concurrency <n> Parallel WHOIS requests (default: 6)",
117
+ " --concurrency <n> Parallel WHOIS requests (default: 6, auto-throttled to 2 unless set)",
116
118
  " --timeout-ms <n> WHOIS timeout per domain (default: 12000)",
119
+ " --dns-prefilter Run DNS pre-check (A/AAAA/CNAME); skip WHOIS for resolving domains",
117
120
  " --brainstorm Generate domain ideas from seeds, then check availability",
118
121
  " --max-suggestions <n> Max brainstormed candidates (default: 120)",
119
122
  " --table Render results in a readable terminal table",
@@ -124,6 +127,7 @@ function printUsage() {
124
127
  "",
125
128
  "Notes:",
126
129
  " - Labels without a dot are auto-converted to <label>.<tld>",
130
+ " - WHOIS is auto-throttled to concurrency=2 unless you set --concurrency",
127
131
  " - Brainstorm mode uses your seed words only (no hardcoded keyword packs)",
128
132
  " - No seed ideas? Ask your agent (Codex, Claude, etc.) and pass them to --brainstorm",
129
133
  " - WHOIS-based checks are heuristic; always confirm at registrar checkout",
@@ -136,9 +140,11 @@ function parseArgs(argv) {
136
140
  input: null,
137
141
  output: null,
138
142
  concurrency: DEFAULT_CONCURRENCY,
143
+ concurrencyExplicit: false,
139
144
  timeoutMs: DEFAULT_TIMEOUT_MS,
140
145
  tld: DEFAULT_TLD,
141
146
  server: null,
147
+ dnsPrefilter: false,
142
148
  brainstorm: false,
143
149
  maxSuggestions: DEFAULT_MAX_SUGGESTIONS,
144
150
  table: false,
@@ -162,6 +168,7 @@ function parseArgs(argv) {
162
168
  case "--concurrency":
163
169
  case "-c":
164
170
  options.concurrency = Number.parseInt(argv[++i], 10);
171
+ options.concurrencyExplicit = true;
165
172
  break;
166
173
  case "--tld":
167
174
  options.tld = argv[++i];
@@ -172,6 +179,9 @@ function parseArgs(argv) {
172
179
  case "--timeout-ms":
173
180
  options.timeoutMs = Number.parseInt(argv[++i], 10);
174
181
  break;
182
+ case "--dns-prefilter":
183
+ options.dnsPrefilter = true;
184
+ break;
175
185
  case "--brainstorm":
176
186
  case "--storm":
177
187
  options.brainstorm = true;
@@ -409,7 +419,98 @@ function brainstormCandidates(rawSeeds, maxSuggestions) {
409
419
  };
410
420
  }
411
421
 
412
- function classifyWhois(raw) {
422
+ function isMdDomain(domain) {
423
+ return domain.endsWith(".md");
424
+ }
425
+
426
+ function getDomainTld(domain) {
427
+ const idx = domain.lastIndexOf(".");
428
+ if (idx < 0 || idx === domain.length - 1) {
429
+ return "";
430
+ }
431
+ return domain.slice(idx + 1).toLowerCase();
432
+ }
433
+
434
+ function shouldUseMdRules(domain, server) {
435
+ if (isMdDomain(domain)) {
436
+ return true;
437
+ }
438
+ if (!server) {
439
+ return false;
440
+ }
441
+ return server === MD_WHOIS_SERVER || server.endsWith(".nic.md") || server.endsWith(".md");
442
+ }
443
+
444
+ function classifyMdWhois(raw) {
445
+ const text = raw.toLowerCase();
446
+ if (
447
+ text.includes("no entries found [ no match for ]") ||
448
+ (text.includes("no entries found") && text.includes("no match for"))
449
+ ) {
450
+ return {
451
+ status: "likely_available",
452
+ reason: "md_no_match",
453
+ state: "No match",
454
+ };
455
+ }
456
+
457
+ const stateMatch = raw.match(/domain state:\s*([^\r\n]+)/i);
458
+ if (!stateMatch) {
459
+ return null;
460
+ }
461
+
462
+ const state = stateMatch[1].trim();
463
+ const lower = state.toLowerCase();
464
+ if (lower.includes("inactive")) {
465
+ return { status: "registered", reason: "md_state_inactive", state };
466
+ }
467
+ if (lower.includes("ok delegated")) {
468
+ return { status: "registered", reason: "md_state_ok_delegated", state };
469
+ }
470
+ if (lower === "ok" || lower.startsWith("ok ")) {
471
+ return { status: "registered", reason: "md_state_ok", state };
472
+ }
473
+ return { status: "registered", reason: "md_state_registered", state };
474
+ }
475
+
476
+ function cleanDomainStatus(value) {
477
+ return value
478
+ .replace(/\s+https?:\/\/\S+$/i, "")
479
+ .replace(/\s+\(https?:\/\/\S+\)$/i, "")
480
+ .trim();
481
+ }
482
+
483
+ function classifyComOrgAiWhois(raw, domain) {
484
+ const tld = getDomainTld(domain);
485
+ if (!["com", "org", "ai"].includes(tld)) {
486
+ return null;
487
+ }
488
+
489
+ const text = raw.toLowerCase();
490
+ if (tld === "com" && text.includes('no match for domain "')) {
491
+ return { status: "likely_available", reason: "com_no_match", state: "No match" };
492
+ }
493
+ if ((tld === "org" || tld === "ai") && text.includes("domain not found")) {
494
+ return { status: "likely_available", reason: `${tld}_domain_not_found`, state: "No match" };
495
+ }
496
+
497
+ const statusMatch = raw.match(/Domain Status:\s*([^\r\n]+)/i);
498
+ if (statusMatch) {
499
+ return {
500
+ status: "registered",
501
+ reason: `${tld}_domain_status`,
502
+ state: cleanDomainStatus(statusMatch[1]),
503
+ };
504
+ }
505
+
506
+ if (/Domain Name:\s*[^\r\n]+/i.test(raw) || /Registry Domain ID:\s*[^\r\n]+/i.test(raw)) {
507
+ return { status: "registered", reason: `${tld}_registered`, state: "" };
508
+ }
509
+
510
+ return null;
511
+ }
512
+
513
+ function classifyWhois(raw, domain, server) {
413
514
  const text = raw.toLowerCase();
414
515
  if (!text.trim()) {
415
516
  return { status: "unknown", reason: "empty_whois_response" };
@@ -420,6 +521,16 @@ function classifyWhois(raw) {
420
521
  if (RATE_LIMIT_PATTERNS.some((p) => text.includes(p))) {
421
522
  return { status: "unknown", reason: "rate_limited_or_blocked" };
422
523
  }
524
+ if (shouldUseMdRules(domain, server)) {
525
+ const md = classifyMdWhois(raw);
526
+ if (md) {
527
+ return md;
528
+ }
529
+ }
530
+ const common = classifyComOrgAiWhois(raw, domain);
531
+ if (common) {
532
+ return common;
533
+ }
423
534
  if (AVAILABILITY_PATTERNS.some((p) => text.includes(p))) {
424
535
  return { status: "likely_available", reason: "availability_pattern_match" };
425
536
  }
@@ -429,6 +540,75 @@ function classifyWhois(raw) {
429
540
  return { status: "unknown", reason: "unrecognized_whois_format" };
430
541
  }
431
542
 
543
+ function runDigType(domain, type, timeoutMs) {
544
+ return new Promise((resolve) => {
545
+ const child = spawn("dig", ["+short", domain, type], {
546
+ stdio: ["ignore", "pipe", "pipe"],
547
+ });
548
+
549
+ let stdout = "";
550
+ let stderr = "";
551
+ let done = false;
552
+ const timer = setTimeout(() => {
553
+ if (done) {
554
+ return;
555
+ }
556
+ done = true;
557
+ child.kill("SIGKILL");
558
+ resolve({ values: [], error: `dig ${type} timeout` });
559
+ }, Math.min(timeoutMs, 5000));
560
+
561
+ child.stdout.on("data", (chunk) => {
562
+ stdout += chunk.toString();
563
+ });
564
+ child.stderr.on("data", (chunk) => {
565
+ stderr += chunk.toString();
566
+ });
567
+ child.on("error", (err) => {
568
+ if (done) {
569
+ return;
570
+ }
571
+ done = true;
572
+ clearTimeout(timer);
573
+ resolve({ values: [], error: err.message });
574
+ });
575
+ child.on("close", () => {
576
+ if (done) {
577
+ return;
578
+ }
579
+ done = true;
580
+ clearTimeout(timer);
581
+ const values = stdout
582
+ .split(/\r?\n/)
583
+ .map((line) => line.trim())
584
+ .filter(Boolean);
585
+ resolve({ values, error: stderr.trim() || null });
586
+ });
587
+ });
588
+ }
589
+
590
+ async function runDnsPrefilter(domain, timeoutMs) {
591
+ const a = await runDigType(domain, "A", timeoutMs);
592
+ if (a.values.length > 0) {
593
+ return { domain, resolves: true, recordType: "A", values: a.values, error: null };
594
+ }
595
+ const aaaa = await runDigType(domain, "AAAA", timeoutMs);
596
+ if (aaaa.values.length > 0) {
597
+ return { domain, resolves: true, recordType: "AAAA", values: aaaa.values, error: null };
598
+ }
599
+ const cname = await runDigType(domain, "CNAME", timeoutMs);
600
+ if (cname.values.length > 0) {
601
+ return { domain, resolves: true, recordType: "CNAME", values: cname.values, error: null };
602
+ }
603
+ return {
604
+ domain,
605
+ resolves: false,
606
+ recordType: "",
607
+ values: [],
608
+ error: a.error || aaaa.error || cname.error || null,
609
+ };
610
+ }
611
+
432
612
  function runWhois(domain, timeoutMs, server) {
433
613
  return new Promise((resolve) => {
434
614
  const args = server ? ["-h", server, domain] : [domain];
@@ -483,12 +663,13 @@ function runWhois(domain, timeoutMs, server) {
483
663
  done = true;
484
664
  clearTimeout(timer);
485
665
  const merged = [stdout, stderr].filter(Boolean).join("\n");
486
- const classification = classifyWhois(merged);
666
+ const classification = classifyWhois(merged, domain, server);
487
667
  const stderrText = stderr.trim();
488
668
  resolve({
489
669
  domain,
490
670
  status: classification.status,
491
671
  reason: classification.reason,
672
+ state: classification.state ?? "",
492
673
  raw: merged.trim(),
493
674
  error: classification.reason === "whois_lookup_failed" ? stderrText || "WHOIS lookup failed" : null,
494
675
  });
@@ -528,7 +709,7 @@ async function maybeWriteCsv(results, outputPath) {
528
709
  if (!outputPath) {
529
710
  return;
530
711
  }
531
- const header = ["domain", "status", "reason", "legal_risk", "story", "error"];
712
+ const header = ["domain", "status", "reason", "state", "legal_risk", "story", "error"];
532
713
  const lines = [header.join(",")];
533
714
  for (const row of results) {
534
715
  lines.push(
@@ -536,6 +717,7 @@ async function maybeWriteCsv(results, outputPath) {
536
717
  row.domain,
537
718
  row.status,
538
719
  row.reason,
720
+ row.state ?? "",
539
721
  row.legalRisk,
540
722
  row.story ?? "",
541
723
  row.error ?? "",
@@ -570,22 +752,37 @@ function snippet(raw) {
570
752
  function verdictForStatus(status) {
571
753
  switch (status) {
572
754
  case "likely_available":
573
- return "AVAILABLE";
755
+ return "AVAILABLE";
574
756
  case "registered":
575
- return "TAKEN";
757
+ return "TAKEN";
576
758
  default:
577
- return "UNKNOWN";
759
+ return "⚠️ UNKNOWN";
578
760
  }
579
761
  }
580
762
 
581
763
  function sentenceForRow(row) {
582
764
  if (row.status === "likely_available") {
583
- return `${row.domain} is available.`;
765
+ return `✅ ${row.domain} is available.`;
584
766
  }
585
767
  if (row.status === "registered") {
586
- return `${row.domain} is not available (already registered).`;
768
+ if (row.reason === "dns_resolves_prefilter") {
769
+ return `❌ ${row.domain} is not available (DNS resolves).`;
770
+ }
771
+ if (row.reason === "md_state_inactive") {
772
+ return `❌ ${row.domain} is not available (registered, inactive).`;
773
+ }
774
+ if (row.reason === "md_state_ok") {
775
+ return `❌ ${row.domain} is not available (registered, state: OK).`;
776
+ }
777
+ if (row.reason === "md_state_ok_delegated") {
778
+ return `❌ ${row.domain} is not available (registered, state: OK Delegated).`;
779
+ }
780
+ if (row.state) {
781
+ return `❌ ${row.domain} is not available (registered, state: ${row.state}).`;
782
+ }
783
+ return `❌ ${row.domain} is not available (already registered).`;
587
784
  }
588
- return `${row.domain} availability is unknown (whois could not confirm).`;
785
+ return `⚠️ ${row.domain} availability is unknown (whois could not confirm).`;
589
786
  }
590
787
 
591
788
  function toDisplayRow(row, options) {
@@ -593,6 +790,7 @@ function toDisplayRow(row, options) {
593
790
  verdict: verdictForStatus(row.status),
594
791
  domain: row.domain,
595
792
  whois: row.status,
793
+ state: row.state ?? "",
596
794
  reason: row.reason,
597
795
  risk: row.legalRisk,
598
796
  story: row.story ?? "",
@@ -614,12 +812,14 @@ function truncateCell(value, maxWidth) {
614
812
 
615
813
  function printTable(rows, options) {
616
814
  const displayRows = rows.map((row) => toDisplayRow(row, options));
815
+ const hasState = displayRows.some((row) => row.state);
617
816
  const hasStory = displayRows.some((row) => row.story);
618
817
  const hasError = displayRows.some((row) => row.error);
619
818
  const columns = [
620
- { key: "verdict", label: "Verdict", maxWidth: 10 },
819
+ { key: "verdict", label: "Verdict", maxWidth: 14 },
621
820
  { key: "domain", label: "Domain", maxWidth: 44 },
622
821
  { key: "whois", label: "Whois", maxWidth: 18 },
822
+ ...(hasState ? [{ key: "state", label: "State", maxWidth: 22 }] : []),
623
823
  { key: "reason", label: "Reason", maxWidth: 34 },
624
824
  { key: "risk", label: "Risk", maxWidth: 14 },
625
825
  ...(hasStory ? [{ key: "story", label: "Story", maxWidth: 36 }] : []),
@@ -686,6 +886,13 @@ function printNarrativeResults(rows, options) {
686
886
  }
687
887
  }
688
888
 
889
+ function shouldAutoThrottleWhois(domains) {
890
+ if (domains.length === 0) {
891
+ return false;
892
+ }
893
+ return true;
894
+ }
895
+
689
896
  async function main() {
690
897
  const options = parseArgs(process.argv.slice(2));
691
898
  let rawLabels = await loadLabels(options);
@@ -720,11 +927,56 @@ async function main() {
720
927
  throw new Error("No valid domains found after normalization.");
721
928
  }
722
929
 
930
+ const effectiveConcurrency =
931
+ !options.concurrencyExplicit && shouldAutoThrottleWhois(domains)
932
+ ? Math.min(options.concurrency, SAFE_WHOIS_CONCURRENCY)
933
+ : options.concurrency;
934
+
935
+ if (effectiveConcurrency !== options.concurrency) {
936
+ console.error(
937
+ `Auto-throttle: using concurrency=${effectiveConcurrency} for WHOIS (override with --concurrency).`,
938
+ );
939
+ }
940
+
723
941
  const serverLabel = options.server ? `server=${options.server}` : "server=auto";
724
- console.error(`Checking ${domains.length} domain(s) (${serverLabel}) with concurrency=${options.concurrency}`);
942
+ console.error(`Checking ${domains.length} domain(s) (${serverLabel}) with concurrency=${effectiveConcurrency}`);
943
+
944
+ const dnsPrefilterResults = [];
945
+ let whoisTargets = domains;
946
+ if (options.dnsPrefilter) {
947
+ const dnsConcurrency = options.concurrencyExplicit ? options.concurrency : Math.min(20, domains.length || 1);
948
+ const dnsChecks = await mapLimit(domains, dnsConcurrency, async (domain) =>
949
+ runDnsPrefilter(domain, options.timeoutMs),
950
+ );
951
+
952
+ const dnsTaken = dnsChecks.filter((item) => item.resolves);
953
+ if (dnsTaken.length > 0) {
954
+ console.error(`DNS prefilter: ${dnsTaken.length} domain(s) resolve; marked as taken without WHOIS.`);
955
+ }
956
+
957
+ for (const item of dnsTaken) {
958
+ dnsPrefilterResults.push({
959
+ domain: item.domain,
960
+ status: "registered",
961
+ reason: "dns_resolves_prefilter",
962
+ state: `DNS ${item.recordType}`,
963
+ raw: "",
964
+ error: null,
965
+ });
966
+ }
967
+ whoisTargets = dnsChecks.filter((item) => !item.resolves).map((item) => item.domain);
968
+ }
725
969
 
726
- const checked = await mapLimit(domains, options.concurrency, async (domain) => {
727
- const result = await runWhois(domain, options.timeoutMs, options.server);
970
+ const whoisResults = await mapLimit(whoisTargets, effectiveConcurrency, async (domain) =>
971
+ runWhois(domain, options.timeoutMs, options.server),
972
+ );
973
+
974
+ const mergedByDomain = new Map();
975
+ for (const result of [...dnsPrefilterResults, ...whoisResults]) {
976
+ mergedByDomain.set(result.domain, result);
977
+ }
978
+ const checked = domains.map((domain) => {
979
+ const result = mergedByDomain.get(domain);
728
980
  const label = domain.slice(0, domain.lastIndexOf("."));
729
981
  return {
730
982
  ...result,
@@ -740,6 +992,9 @@ async function main() {
740
992
  if (options.plain) {
741
993
  for (const row of filtered) {
742
994
  let line = `${row.domain}\t${row.status}\t${row.reason}\t${row.legalRisk}`;
995
+ if (row.state) {
996
+ line += `\tstate=${row.state}`;
997
+ }
743
998
  if (row.story) {
744
999
  line += `\tstory=${row.story}`;
745
1000
  }
@@ -760,7 +1015,7 @@ async function main() {
760
1015
  await maybeWriteCsv(checked, options.output);
761
1016
 
762
1017
  const counts = summarize(checked);
763
- console.error(`Summary: ${counts.likelyAvailable} available, ${counts.registered} taken, ${counts.unknown} unknown.`);
1018
+ console.error(`Summary: ${counts.likelyAvailable} available, ${counts.registered} taken, ⚠️ ${counts.unknown} unknown.`);
764
1019
  if (options.output) {
765
1020
  console.error(`CSV written: ${path.resolve(options.output)}`);
766
1021
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "domainstorm",
3
- "version": "0.2.2",
3
+ "version": "0.2.4",
4
4
  "description": "Brainstorm and check domain names in one command.",
5
5
  "repository": {
6
6
  "type": "git",