domainstorm 0.2.6 → 0.3.1

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
@@ -78,6 +76,18 @@ Compatibility alias:
78
76
  domain-check --help
79
77
  ```
80
78
 
79
+ ## Zero Install — Just `npx`
80
+
81
+ Agents don't need to install anything globally. Domainstorm works out of the box with `npx`:
82
+
83
+ ```bash
84
+ npx domainstorm --brainstorm "agent runtime" --tld com --only-available --table
85
+ ```
86
+
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.
88
+
89
+ Works with Claude Code, Codex, OpenClaw, Cursor, and any agent that can run shell commands.
90
+
81
91
  ## Example Output
82
92
 
83
93
  ```txt
@@ -112,6 +122,24 @@ Output columns:
112
122
  - `--timeout-ms <n>`
113
123
  - `--raw`
114
124
 
125
+ ## OpenClaw / AI Agent Skill
126
+
127
+ Domainstorm ships as an [OpenClaw](https://openclaw.ai) skill. Install it with a single `npx` command — no global install needed:
128
+
129
+ ```bash
130
+ npx domainstorm --install-skill
131
+ ```
132
+
133
+ This launches an interactive installer that lets you pick where to install the skill (Claude Code, OpenClaw, Cursor, or a custom path). Once installed, your agent will automatically use Domainstorm when you ask it to brainstorm or check domains.
134
+
135
+ You can also print the skill file to stdout for manual setup:
136
+
137
+ ```bash
138
+ npx domainstorm --skill
139
+ ```
140
+
141
+ Works with any agent that supports OpenClaw skills — Claude, Codex, Cursor, and others.
142
+
115
143
  ## Notes
116
144
 
117
145
  - 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.6",
3
+ "version": "0.3.1",
4
4
  "description": "Brainstorm and check domain names in one command.",
5
5
  "repository": {
6
6
  "type": "git",