domainstorm 0.2.1 → 0.2.2

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
@@ -33,7 +33,7 @@ npx --yes domainstorm --help
33
33
  Brainstorm + check + return only likely available:
34
34
 
35
35
  ```bash
36
- domainstorm --brainstorm "agent orchestration" "mcp broker" --tld md --server whois.nic.md --only-available
36
+ domainstorm --brainstorm "agent orchestration" "mcp broker" --tld md --server whois.nic.md --only-available --table
37
37
  ```
38
38
 
39
39
  Check exact domains:
@@ -42,12 +42,21 @@ Check exact domains:
42
42
  domainstorm agentmesh.ai agentmesh.com agentmesh.md
43
43
  ```
44
44
 
45
+ Default output is plain English (for example: `broker.md is available.`).
46
+ Use `--table` if you want a structured table view.
47
+
45
48
  Check from file and export CSV:
46
49
 
47
50
  ```bash
48
51
  domainstorm --input domains.txt --output /tmp/domainstorm.csv
49
52
  ```
50
53
 
54
+ Try immediately with the bundled candidate list:
55
+
56
+ ```bash
57
+ domainstorm --input agent-candidates.txt --tld md --server whois.nic.md --only-available --table
58
+ ```
59
+
51
60
  ## Built For Agent Workflows
52
61
 
53
62
  Use Domainstorm when your coding/research agent needs to propose names and validate them in the same run.
@@ -56,6 +65,7 @@ Use Domainstorm when your coding/research agent needs to propose names and valid
56
65
  - Works with shell pipelines
57
66
  - No external AI API keys required
58
67
  - Easy to trigger in CI on release or branch workflows
68
+ - No seed ideas? Ask your agent of choice (Codex, Claude, etc.) for seed phrases and pass them to `--brainstorm`
59
69
 
60
70
  Compatibility alias:
61
71
 
@@ -83,6 +93,7 @@ Output columns:
83
93
 
84
94
  - `--brainstorm` / `--storm`
85
95
  - `--max-suggestions <n>` (default: `120`)
96
+ - `--table`
86
97
  - `--input <file>`
87
98
  - `--output <file>`
88
99
  - `--tld <tld>` (default: `md`)
package/check-domains.mjs CHANGED
@@ -75,59 +75,6 @@ const STOP_WORDS = new Set([
75
75
  "agents",
76
76
  ]);
77
77
 
78
- const DEFAULT_STORM_SEEDS = [
79
- "agent",
80
- "mcp",
81
- "ai",
82
- "broker",
83
- "router",
84
- "runtime",
85
- "ops",
86
- "stack",
87
- "forge",
88
- "mesh",
89
- "pilot",
90
- "flow",
91
- ];
92
-
93
- const STORM_SUFFIXES = [
94
- "storm",
95
- "ops",
96
- "hub",
97
- "mesh",
98
- "forge",
99
- "stack",
100
- "pilot",
101
- "route",
102
- "router",
103
- "broker",
104
- "runtime",
105
- "engine",
106
- "works",
107
- "cloud",
108
- "grid",
109
- "terminal",
110
- "dock",
111
- "labs",
112
- "studio",
113
- ];
114
-
115
- const STORM_PREFIXES = [
116
- "agent",
117
- "ai",
118
- "mcp",
119
- "task",
120
- "flow",
121
- "auto",
122
- "tool",
123
- "prompt",
124
- "context",
125
- "trace",
126
- "guard",
127
- "secure",
128
- "fleet",
129
- ];
130
-
131
78
  // Small, non-authoritative heuristic list for quick legal-risk triage.
132
79
  const TRADEMARK_KEYWORDS = [
133
80
  "openai",
@@ -169,12 +116,16 @@ function printUsage() {
169
116
  " --timeout-ms <n> WHOIS timeout per domain (default: 12000)",
170
117
  " --brainstorm Generate domain ideas from seeds, then check availability",
171
118
  " --max-suggestions <n> Max brainstormed candidates (default: 120)",
119
+ " --table Render results in a readable terminal table",
120
+ " --plain Emit machine-friendly TSV output",
172
121
  " --only-available Print only likely available domains",
173
122
  " --raw Include WHOIS snippet in console output",
174
123
  " --help Show this help",
175
124
  "",
176
125
  "Notes:",
177
126
  " - Labels without a dot are auto-converted to <label>.<tld>",
127
+ " - Brainstorm mode uses your seed words only (no hardcoded keyword packs)",
128
+ " - No seed ideas? Ask your agent (Codex, Claude, etc.) and pass them to --brainstorm",
178
129
  " - WHOIS-based checks are heuristic; always confirm at registrar checkout",
179
130
  ].join("\n"),
180
131
  );
@@ -190,6 +141,8 @@ function parseArgs(argv) {
190
141
  server: null,
191
142
  brainstorm: false,
192
143
  maxSuggestions: DEFAULT_MAX_SUGGESTIONS,
144
+ table: false,
145
+ plain: false,
193
146
  onlyAvailable: false,
194
147
  raw: false,
195
148
  labels: [],
@@ -226,6 +179,12 @@ function parseArgs(argv) {
226
179
  case "--max-suggestions":
227
180
  options.maxSuggestions = Number.parseInt(argv[++i], 10);
228
181
  break;
182
+ case "--table":
183
+ options.table = true;
184
+ break;
185
+ case "--plain":
186
+ options.plain = true;
187
+ break;
229
188
  case "--only-available":
230
189
  options.onlyAvailable = true;
231
190
  break;
@@ -249,7 +208,9 @@ function parseArgs(argv) {
249
208
  }
250
209
 
251
210
  if (!options.input && options.labels.length === 0 && !options.brainstorm) {
252
- throw new Error("Provide --input <file>, one/more labels/domains, or use --brainstorm.");
211
+ throw new Error(
212
+ "Provide --input <file>, one/more labels/domains, or use --brainstorm. Tip: ask your agent (Codex, Claude, etc.) for seed phrases.",
213
+ );
253
214
  }
254
215
  if (!Number.isInteger(options.concurrency) || options.concurrency < 1 || options.concurrency > 50) {
255
216
  throw new Error("--concurrency must be an integer between 1 and 50.");
@@ -349,7 +310,12 @@ function tokenizeSeeds(rawValues) {
349
310
 
350
311
  function brainstormCandidates(rawSeeds, maxSuggestions) {
351
312
  const seedTokens = tokenizeSeeds(rawSeeds);
352
- const seedPool = Array.from(new Set([...seedTokens, ...DEFAULT_STORM_SEEDS])).slice(0, 24);
313
+ if (seedTokens.length === 0) {
314
+ throw new Error(
315
+ "Brainstorm mode needs seed words. Ask your agent of choice (Codex, Claude, etc.) for naming seeds, then run: --brainstorm \"seed one\" \"seed two\"",
316
+ );
317
+ }
318
+ const seedPool = Array.from(new Set(seedTokens)).slice(0, 24);
353
319
  const scored = new Map();
354
320
 
355
321
  function add(label, story, bonus = 0) {
@@ -368,24 +334,67 @@ function brainstormCandidates(rawSeeds, maxSuggestions) {
368
334
  }
369
335
  }
370
336
 
337
+ function shortForm(token) {
338
+ if (token.length < 5) {
339
+ return token;
340
+ }
341
+ const withoutVowels = token[0] + token.slice(1).replace(/[aeiou]/g, "");
342
+ if (withoutVowels.length >= 3 && withoutVowels.length < token.length) {
343
+ return withoutVowels;
344
+ }
345
+ return token;
346
+ }
347
+
348
+ const formsBySeed = new Map();
371
349
  for (const seed of seedPool) {
372
- add(seed, "Keyword brand");
373
- for (const suffix of STORM_SUFFIXES) {
374
- add(`${seed}${suffix}`, "Keyword + product framing", seed.length < 8 ? 25 : 10);
350
+ const forms = new Set([seed, shortForm(seed)]);
351
+ if (seed.length > 8) {
352
+ forms.add(seed.slice(0, 7));
375
353
  }
376
- for (const prefix of STORM_PREFIXES) {
377
- add(`${prefix}${seed}`, "Platform + keyword framing", 10);
354
+ formsBySeed.set(seed, Array.from(forms));
355
+ }
356
+
357
+ for (const seed of seedPool) {
358
+ for (const form of formsBySeed.get(seed)) {
359
+ add(form, form === seed ? "Seed keyword" : "Compressed variant", form === seed ? 18 : 10);
378
360
  }
379
361
  }
380
362
 
381
363
  for (let i = 0; i < seedPool.length; i += 1) {
382
- for (let j = i + 1; j < seedPool.length; j += 1) {
383
- const a = seedPool[i];
384
- const b = seedPool[j];
385
- add(`${a}${b}`, "Two-keyword compound", 20);
386
- add(`${b}${a}`, "Two-keyword compound", 20);
387
- add(`${a}${b}hq`, "Company-style compound", 10);
388
- add(`${a}${b}labs`, "Innovation-style compound", 8);
364
+ for (let j = 0; j < seedPool.length; j += 1) {
365
+ if (i === j) {
366
+ continue;
367
+ }
368
+ const aForms = formsBySeed.get(seedPool[i]);
369
+ const bForms = formsBySeed.get(seedPool[j]);
370
+ for (const a of aForms) {
371
+ for (const b of bForms) {
372
+ add(`${a}${b}`, "Two-seed compound", 20);
373
+ }
374
+ }
375
+ }
376
+ }
377
+
378
+ for (let i = 0; i < seedPool.length; i += 1) {
379
+ for (let j = 0; j < seedPool.length; j += 1) {
380
+ for (let k = 0; k < seedPool.length; k += 1) {
381
+ if (i === j || j === k || i === k) {
382
+ continue;
383
+ }
384
+ const a = formsBySeed.get(seedPool[i])[0];
385
+ const b = formsBySeed.get(seedPool[j])[0];
386
+ const c = formsBySeed.get(seedPool[k])[0];
387
+ add(`${a}${b}${c}`, "Three-seed compound", 10);
388
+ }
389
+ }
390
+ }
391
+
392
+ const acronym = seedPool.map((token) => token[0]).join("");
393
+ if (acronym.length >= 3) {
394
+ add(acronym, "Seed acronym", 16);
395
+ for (const seed of seedPool) {
396
+ add(`${acronym}${seed}`, "Acronym + seed", 12);
397
+ add(`${seed}${acronym}`, "Seed + acronym", 12);
389
398
  }
390
399
  }
391
400
 
@@ -396,7 +405,7 @@ function brainstormCandidates(rawSeeds, maxSuggestions) {
396
405
  return {
397
406
  labels: sorted.map((item) => item.label),
398
407
  storyByLabel: new Map(sorted.map((item) => [item.label, item.story])),
399
- usedSeeds: seedTokens.length > 0 ? Array.from(new Set(seedTokens)) : DEFAULT_STORM_SEEDS,
408
+ usedSeeds: seedPool,
400
409
  };
401
410
  }
402
411
 
@@ -558,6 +567,125 @@ function snippet(raw) {
558
567
  return raw.replace(/\s+/g, " ").slice(0, 120);
559
568
  }
560
569
 
570
+ function verdictForStatus(status) {
571
+ switch (status) {
572
+ case "likely_available":
573
+ return "AVAILABLE";
574
+ case "registered":
575
+ return "TAKEN";
576
+ default:
577
+ return "UNKNOWN";
578
+ }
579
+ }
580
+
581
+ function sentenceForRow(row) {
582
+ if (row.status === "likely_available") {
583
+ return `${row.domain} is available.`;
584
+ }
585
+ if (row.status === "registered") {
586
+ return `${row.domain} is not available (already registered).`;
587
+ }
588
+ return `${row.domain} availability is unknown (whois could not confirm).`;
589
+ }
590
+
591
+ function toDisplayRow(row, options) {
592
+ return {
593
+ verdict: verdictForStatus(row.status),
594
+ domain: row.domain,
595
+ whois: row.status,
596
+ reason: row.reason,
597
+ risk: row.legalRisk,
598
+ story: row.story ?? "",
599
+ error: row.error ?? "",
600
+ raw: options.raw ? snippet(row.raw) : "",
601
+ };
602
+ }
603
+
604
+ function truncateCell(value, maxWidth) {
605
+ const text = String(value ?? "");
606
+ if (text.length <= maxWidth) {
607
+ return text;
608
+ }
609
+ if (maxWidth <= 1) {
610
+ return text.slice(0, maxWidth);
611
+ }
612
+ return `${text.slice(0, maxWidth - 1)}…`;
613
+ }
614
+
615
+ function printTable(rows, options) {
616
+ const displayRows = rows.map((row) => toDisplayRow(row, options));
617
+ const hasStory = displayRows.some((row) => row.story);
618
+ const hasError = displayRows.some((row) => row.error);
619
+ const columns = [
620
+ { key: "verdict", label: "Verdict", maxWidth: 10 },
621
+ { key: "domain", label: "Domain", maxWidth: 44 },
622
+ { key: "whois", label: "Whois", maxWidth: 18 },
623
+ { key: "reason", label: "Reason", maxWidth: 34 },
624
+ { key: "risk", label: "Risk", maxWidth: 14 },
625
+ ...(hasStory ? [{ key: "story", label: "Story", maxWidth: 36 }] : []),
626
+ ...(hasError ? [{ key: "error", label: "Error", maxWidth: 44 }] : []),
627
+ ...(options.raw ? [{ key: "raw", label: "Raw", maxWidth: 44 }] : []),
628
+ ];
629
+
630
+ const widths = new Map();
631
+ for (const col of columns) {
632
+ const maxValueLen = displayRows.reduce((max, row) => Math.max(max, String(row[col.key] ?? "").length), 0);
633
+ widths.set(col.key, Math.min(Math.max(col.label.length, maxValueLen), col.maxWidth));
634
+ }
635
+
636
+ const formatCell = (value, width) => truncateCell(value, width).padEnd(width, " ");
637
+ const header = columns.map((col) => formatCell(col.label, widths.get(col.key))).join(" | ");
638
+ const separator = columns.map((col) => "-".repeat(widths.get(col.key))).join("-+-");
639
+ console.log(header);
640
+ console.log(separator);
641
+ for (const row of displayRows) {
642
+ const line = columns.map((col) => formatCell(row[col.key], widths.get(col.key))).join(" | ");
643
+ console.log(line);
644
+ }
645
+ }
646
+
647
+ function printDecoratedResults(rows, options) {
648
+ const title = " DOMAINSTORM RESULTS ";
649
+ const border = "=".repeat(Math.max(68, title.length + 8));
650
+ console.log(border);
651
+ console.log(title);
652
+ console.log(border);
653
+ if (rows.length === 0) {
654
+ console.log("No domains matched your filters.");
655
+ return;
656
+ }
657
+ printTable(rows, options);
658
+ if (rows.length === 1) {
659
+ const row = rows[0];
660
+ console.log("");
661
+ console.log(`Result: ${row.domain} is ${verdictForStatus(row.status)}.`);
662
+ }
663
+ }
664
+
665
+ function printNarrativeResults(rows, options) {
666
+ if (rows.length === 0) {
667
+ console.log("No domains matched your filters.");
668
+ return;
669
+ }
670
+
671
+ for (const row of rows) {
672
+ console.log(sentenceForRow(row));
673
+ if (row.status === "unknown" || row.error) {
674
+ let detail = ` reason: ${row.reason}`;
675
+ if (row.error) {
676
+ detail += ` | error: ${row.error}`;
677
+ }
678
+ console.log(detail);
679
+ }
680
+ if (row.story) {
681
+ console.log(` story: ${row.story}`);
682
+ }
683
+ if (options.raw) {
684
+ console.log(` raw: ${snippet(row.raw)}`);
685
+ }
686
+ }
687
+ }
688
+
561
689
  async function main() {
562
690
  const options = parseArgs(process.argv.slice(2));
563
691
  let rawLabels = await loadLabels(options);
@@ -609,26 +737,30 @@ async function main() {
609
737
  ? checked.filter((row) => row.status === "likely_available")
610
738
  : checked;
611
739
 
612
- for (const row of filtered) {
613
- let line = `${row.domain}\t${row.status}\t${row.reason}\t${row.legalRisk}`;
614
- if (row.story) {
615
- line += `\tstory=${row.story}`;
616
- }
617
- if (row.error) {
618
- line += `\terror=${row.error}`;
619
- }
620
- if (options.raw) {
621
- line += `\t${snippet(row.raw)}`;
740
+ if (options.plain) {
741
+ for (const row of filtered) {
742
+ let line = `${row.domain}\t${row.status}\t${row.reason}\t${row.legalRisk}`;
743
+ if (row.story) {
744
+ line += `\tstory=${row.story}`;
745
+ }
746
+ if (row.error) {
747
+ line += `\terror=${row.error}`;
748
+ }
749
+ if (options.raw) {
750
+ line += `\t${snippet(row.raw)}`;
751
+ }
752
+ console.log(line);
622
753
  }
623
- console.log(line);
754
+ } else if (options.table) {
755
+ printDecoratedResults(filtered, options);
756
+ } else {
757
+ printNarrativeResults(filtered, options);
624
758
  }
625
759
 
626
760
  await maybeWriteCsv(checked, options.output);
627
761
 
628
762
  const counts = summarize(checked);
629
- console.error(
630
- `Summary: likely_available=${counts.likelyAvailable}, registered=${counts.registered}, unknown=${counts.unknown}`,
631
- );
763
+ console.error(`Summary: ${counts.likelyAvailable} available, ${counts.registered} taken, ${counts.unknown} unknown.`);
632
764
  if (options.output) {
633
765
  console.error(`CSV written: ${path.resolve(options.output)}`);
634
766
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "domainstorm",
3
- "version": "0.2.1",
3
+ "version": "0.2.2",
4
4
  "description": "Brainstorm and check domain names in one command.",
5
5
  "repository": {
6
6
  "type": "git",