domainstorm 0.2.5 → 0.3.0

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
@@ -2,12 +2,10 @@
2
2
 
3
3
  [![npm version](https://img.shields.io/npm/v/domainstorm)](https://www.npmjs.com/package/domainstorm)
4
4
  [![npm downloads](https://img.shields.io/npm/dm/domainstorm)](https://www.npmjs.com/package/domainstorm)
5
- [![CI](https://img.shields.io/github/actions/workflow/status/tanishqsh/domain-cli/npm-publish.yml)](https://github.com/tanishqsh/domain-cli/actions/workflows/npm-publish.yml)
6
5
 
7
6
  Domainstorm is the fastest way to go from "we need a name" to "these are the domains we can buy now."
8
7
 
9
8
  It is built for AI and agent products where naming cycles happen fast and every good domain disappears quickly.
10
-
11
9
  ## Why Domainstorm
12
10
 
13
11
  - One command for ideation and availability checks
@@ -70,7 +68,6 @@ Use Domainstorm when your coding/research agent needs to propose names and valid
70
68
  - Deterministic CLI output
71
69
  - Works with shell pipelines
72
70
  - No external AI API keys required
73
- - Easy to trigger in CI on release or branch workflows
74
71
  - No seed ideas? Ask your agent of choice (Codex, Claude, etc.) for seed phrases and pass them to `--brainstorm`
75
72
 
76
73
  Compatibility alias:
@@ -79,22 +76,17 @@ Compatibility alias:
79
76
  domain-check --help
80
77
  ```
81
78
 
82
- ## Local Dev Test (npm)
79
+ ## Zero Install Just `npx`
83
80
 
84
- Run the current repo version without publishing:
81
+ Agents don't need to install anything globally. Domainstorm works out of the box with `npx`:
85
82
 
86
83
  ```bash
87
- npm run check
88
- npm link
89
- domainstorm --help
90
- domainstorm broker.md --table
84
+ npx domainstorm --brainstorm "agent runtime" --tld com --only-available --table
91
85
  ```
92
86
 
93
- Remove local link when done:
87
+ No setup, no config, no API keys. One command and the agent has domain availability data. This makes it trivial to add to any agent's tool chain — just shell out to `npx domainstorm` and parse the output.
94
88
 
95
- ```bash
96
- npm unlink -g domainstorm
97
- ```
89
+ Works with Claude Code, Codex, OpenClaw, Cursor, and any agent that can run shell commands.
98
90
 
99
91
  ## Example Output
100
92
 
@@ -130,6 +122,19 @@ Output columns:
130
122
  - `--timeout-ms <n>`
131
123
  - `--raw`
132
124
 
125
+ ## OpenClaw / AI Agent Skill
126
+
127
+ Domainstorm ships as an [OpenClaw](https://openclaw.ai) skill. Install it and any OpenClaw-powered agent will automatically use Domainstorm when you ask it to brainstorm or check domains.
128
+
129
+ ```bash
130
+ # Install the skill (from ClaHub or local)
131
+ openclaw skill install domainstorm
132
+ ```
133
+
134
+ Or drop the [`SKILL.md`](https://github.com/tanishqsh/domain-cli/blob/main/skill/SKILL.md) into your `~/.openclaw/workspace/skills/domainstorm/` directory manually.
135
+
136
+ Works with any agent that supports OpenClaw skills — Claude, Codex, Cursor, and others.
137
+
133
138
  ## Notes
134
139
 
135
140
  - WHOIS formats vary by registry; treat `likely_available` as a pre-check, not final registrar checkout.
package/check-domains.mjs CHANGED
@@ -101,6 +101,228 @@ const TRADEMARK_KEYWORDS = [
101
101
  "spacex",
102
102
  ];
103
103
 
104
+ import { createInterface } from "node:readline";
105
+ import { homedir } from "node:os";
106
+
107
+ const SKILL_DIRS = [
108
+ { name: "OpenClaw", path: path.join(homedir(), ".openclaw", "workspace", "skills") },
109
+ { name: "OpenClaw (global)", path: path.join(homedir(), ".openclaw", "skills") },
110
+ { name: "Claude Code", path: path.join(homedir(), ".claude", "skills") },
111
+ { name: "Cursor", path: path.join(homedir(), ".cursor", "skills") },
112
+ { name: "Codex", path: path.join(homedir(), ".codex", "skills") },
113
+ { name: "Local ./skills", path: path.resolve("skills") },
114
+ ];
115
+
116
+ const DIM = "\x1b[2m";
117
+ const RESET = "\x1b[0m";
118
+ const BOLD = "\x1b[1m";
119
+ const GREEN = "\x1b[32m";
120
+ const CYAN = "\x1b[36m";
121
+ const YELLOW = "\x1b[33m";
122
+ const WHITE = "\x1b[37m";
123
+ const HIDE_CURSOR = "\x1b[?25l";
124
+ const SHOW_CURSOR = "\x1b[?25h";
125
+ const CLEAR_LINE = "\x1b[2K";
126
+ const UP = (n) => `\x1b[${n}A`;
127
+
128
+ function multiSelect(items) {
129
+ return new Promise((resolve) => {
130
+ const selected = new Set();
131
+ let cursor = 0;
132
+ const stdin = process.stdin;
133
+ const out = process.stdout;
134
+
135
+ // Pre-select detected items
136
+ items.forEach((item, i) => {
137
+ if (item.exists) selected.add(i);
138
+ });
139
+
140
+ function render() {
141
+ let output = "";
142
+ for (let i = 0; i < items.length; i++) {
143
+ const item = items[i];
144
+ const isSelected = selected.has(i);
145
+ const isCursor = i === cursor;
146
+ const checkbox = isSelected ? `${GREEN}◉${RESET}` : `${DIM}○${RESET}`;
147
+ const pointer = isCursor ? `${CYAN}❯${RESET} ` : " ";
148
+ const label = isCursor ? `${WHITE}${BOLD}${item.name}${RESET}` : `${WHITE}${item.name}${RESET}`;
149
+ const pathStr = `${DIM}${item.path}${RESET}`;
150
+ const badge = item.exists ? ` ${GREEN}● found${RESET}` : "";
151
+ output += `${CLEAR_LINE}${pointer}${checkbox} ${label} ${pathStr}${badge}\n`;
152
+ }
153
+ output += `${CLEAR_LINE}\n`;
154
+ output += `${CLEAR_LINE}${DIM}↑↓ navigate ⎵ select ↵ confirm a toggle all q cancel${RESET}`;
155
+ out.write(output);
156
+ }
157
+
158
+ function clearRender() {
159
+ const totalLines = items.length + 2;
160
+ out.write(UP(totalLines) + "\r");
161
+ }
162
+
163
+ out.write(`\n${BOLD}📦 Where do you want to install?${RESET}\n\n`);
164
+ out.write(HIDE_CURSOR);
165
+ render();
166
+
167
+ stdin.setRawMode(true);
168
+ stdin.resume();
169
+ stdin.setEncoding("utf8");
170
+
171
+ function cleanup() {
172
+ stdin.setRawMode(false);
173
+ stdin.pause();
174
+ stdin.removeListener("data", onKey);
175
+ out.write(SHOW_CURSOR + "\n");
176
+ }
177
+
178
+ function onKey(key) {
179
+ // Ctrl+C
180
+ if (key === "\x03") {
181
+ cleanup();
182
+ process.exit(0);
183
+ }
184
+ // q / Escape
185
+ if (key === "q" || key === "\x1b" && key.length === 1) {
186
+ cleanup();
187
+ resolve([]);
188
+ return;
189
+ }
190
+ // Enter
191
+ if (key === "\r" || key === "\n") {
192
+ cleanup();
193
+ resolve(items.filter((_, i) => selected.has(i)));
194
+ return;
195
+ }
196
+ // Space
197
+ if (key === " ") {
198
+ if (selected.has(cursor)) {
199
+ selected.delete(cursor);
200
+ } else {
201
+ selected.add(cursor);
202
+ }
203
+ }
204
+ // a — toggle all
205
+ if (key === "a") {
206
+ if (selected.size === items.length) {
207
+ selected.clear();
208
+ } else {
209
+ items.forEach((_, i) => selected.add(i));
210
+ }
211
+ }
212
+ // Up arrow
213
+ if (key === "\x1b[A" || key === "k") {
214
+ cursor = (cursor - 1 + items.length) % items.length;
215
+ }
216
+ // Down arrow
217
+ if (key === "\x1b[B" || key === "j") {
218
+ cursor = (cursor + 1) % items.length;
219
+ }
220
+ clearRender();
221
+ render();
222
+ }
223
+
224
+ stdin.on("data", onKey);
225
+ });
226
+ }
227
+
228
+ async function installSkill() {
229
+ const scriptDir = path.dirname(new URL(import.meta.url).pathname);
230
+ const skillSrc = path.join(scriptDir, "SKILL.md");
231
+
232
+ try {
233
+ await fs.access(skillSrc);
234
+ } catch {
235
+ console.error("Error: SKILL.md not found in domainstorm package. Try updating: npm i -g domainstorm@latest");
236
+ process.exit(1);
237
+ }
238
+
239
+ console.log(`\n${BOLD}${CYAN}domainstorm${RESET} ${DIM}— skill installer${RESET}\n`);
240
+
241
+ const items = [];
242
+ for (const dir of SKILL_DIRS) {
243
+ let exists = false;
244
+ try {
245
+ await fs.access(dir.path);
246
+ exists = true;
247
+ } catch {}
248
+ items.push({ ...dir, exists });
249
+ }
250
+
251
+ let selections;
252
+
253
+ if (process.stdin.isTTY) {
254
+ selections = await multiSelect(items);
255
+ } else {
256
+ // Fallback for non-TTY (piped input, CI, agents)
257
+ console.log(`${BOLD}📦 Where do you want to install?${RESET}\n`);
258
+ items.forEach((item, i) => {
259
+ const badge = item.exists ? ` ${GREEN}● found${RESET}` : "";
260
+ console.log(` ${i + 1}. ${item.name} ${DIM}${item.path}${RESET}${badge}`);
261
+ });
262
+ console.log(` ${items.length + 1}. Enter custom path\n`);
263
+
264
+ const rl = createInterface({ input: process.stdin, output: process.stdout });
265
+ const answer = await new Promise((resolve) => {
266
+ rl.question(`Select (1-${items.length + 1}, comma-separated): `, resolve);
267
+ });
268
+ rl.close();
269
+
270
+ const indices = answer.split(",").map((s) => Number(s.trim()) - 1).filter((i) => i >= 0 && i < items.length);
271
+ selections = indices.map((i) => items[i]);
272
+ }
273
+
274
+ if (selections.length === 0) {
275
+ console.log(`${DIM}No directories selected. Exiting.${RESET}`);
276
+ process.exit(0);
277
+ }
278
+
279
+ console.log("");
280
+ const skillContent = await fs.readFile(skillSrc, "utf8");
281
+
282
+ const frames = ["⠋", "⠙", "⠹", "⠸", "⠼", "⠴", "⠦", "⠧", "⠇", "⠏"];
283
+ const out = process.stdout;
284
+
285
+ for (const item of selections) {
286
+ const destDir = path.join(item.path, "domainstorm");
287
+ const destFile = path.join(destDir, "SKILL.md");
288
+
289
+ // Spinner
290
+ let frameIdx = 0;
291
+ const spinner = setInterval(() => {
292
+ out.write(`\r${CLEAR_LINE} ${CYAN}${frames[frameIdx]}${RESET} Installing to ${BOLD}${item.name}${RESET}${DIM}...${RESET}`);
293
+ frameIdx = (frameIdx + 1) % frames.length;
294
+ }, 80);
295
+
296
+ try {
297
+ await fs.mkdir(destDir, { recursive: true });
298
+ await fs.writeFile(destFile, skillContent, "utf8");
299
+ // Small delay so spinner is visible
300
+ await new Promise((r) => setTimeout(r, 400));
301
+ clearInterval(spinner);
302
+ out.write(`\r${CLEAR_LINE} ${GREEN}✓${RESET} ${BOLD}${item.name}${RESET} ${DIM}→ ${destFile}${RESET}\n`);
303
+ } catch (err) {
304
+ clearInterval(spinner);
305
+ out.write(`\r${CLEAR_LINE} ${YELLOW}✗${RESET} ${BOLD}${item.name}${RESET} ${DIM}— ${err.message}${RESET}\n`);
306
+ }
307
+ }
308
+
309
+ console.log("");
310
+ console.log(` ${GREEN}${BOLD}✨ Done!${RESET} Your agent can now discover domainstorm as a skill.`);
311
+ console.log(` ${DIM}Restart your agent or start a new session to pick it up.${RESET}\n`);
312
+ }
313
+
314
+ async function printSkill() {
315
+ const scriptDir = path.dirname(new URL(import.meta.url).pathname);
316
+ const skillSrc = path.join(scriptDir, "SKILL.md");
317
+ try {
318
+ const content = await fs.readFile(skillSrc, "utf8");
319
+ console.log(content);
320
+ } catch {
321
+ console.error("Error: SKILL.md not found in domainstorm package.");
322
+ process.exit(1);
323
+ }
324
+ }
325
+
104
326
  function printUsage() {
105
327
  console.log(
106
328
  [
@@ -123,6 +345,8 @@ function printUsage() {
123
345
  " --plain Emit machine-friendly TSV output",
124
346
  " --only-available Print only likely available domains",
125
347
  " --raw Include WHOIS snippet in console output",
348
+ " --install-skill Install SKILL.md to your agent's skill directory",
349
+ " --skill Print SKILL.md to stdout",
126
350
  " --help Show this help",
127
351
  "",
128
352
  "Notes:",
@@ -135,7 +359,7 @@ function printUsage() {
135
359
  );
136
360
  }
137
361
 
138
- function parseArgs(argv) {
362
+ async function parseArgs(argv) {
139
363
  const options = {
140
364
  input: null,
141
365
  output: null,
@@ -201,6 +425,12 @@ function parseArgs(argv) {
201
425
  case "--raw":
202
426
  options.raw = true;
203
427
  break;
428
+ case "--install-skill":
429
+ await installSkill();
430
+ process.exit(0);
431
+ case "--skill":
432
+ await printSkill();
433
+ process.exit(0);
204
434
  case "--help":
205
435
  case "-h":
206
436
  printUsage();
@@ -894,7 +1124,7 @@ function shouldAutoThrottleWhois(domains) {
894
1124
  }
895
1125
 
896
1126
  async function main() {
897
- const options = parseArgs(process.argv.slice(2));
1127
+ const options = await parseArgs(process.argv.slice(2));
898
1128
  let rawLabels = await loadLabels(options);
899
1129
  let storyByLabel = new Map();
900
1130
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "domainstorm",
3
- "version": "0.2.5",
3
+ "version": "0.3.0",
4
4
  "description": "Brainstorm and check domain names in one command.",
5
5
  "repository": {
6
6
  "type": "git",