nameable 0.0.0-stage → 0.1.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/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Min-An Yang (minanyang)
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md CHANGED
@@ -1,3 +1,212 @@
1
- # Temporary Holding Version
1
+ <div align="center">
2
2
 
3
- This version is a temporary placeholder for this package. An operational version to replace this has been submitted for review and is awaiting a staged release.
3
+ # Nameable
4
+
5
+ Find out if a name is free everywhere your project will ship, and don't take a 404 for a yes.
6
+
7
+ [![npm](https://img.shields.io/npm/v/nameable)](https://www.npmjs.com/package/nameable) [![License: MIT](https://img.shields.io/badge/license-MIT-green)](LICENSE) [![Claude Code plugin](https://img.shields.io/badge/Claude_Code-plugin-D97757)](https://code.claude.com/docs/en/plugins)
8
+
9
+ **English** · [繁體中文](README.zh-TW.md)
10
+
11
+ </div>
12
+
13
+ Nameable checks a name on package registries, GitHub and domains, shows the evidence behind every answer, and
14
+ says how sure each answer is. It runs as a CLI, as an MCP server for AI agents, and as a Claude Code plugin that also
15
+ looks for prior art and recommends which name to keep.
16
+
17
+ ## Why
18
+
19
+ Most availability checks ask each registry whether the name exists and read "not found" as free. Not found only
20
+ means nobody holds that exact name. The registry may still refuse it:
21
+
22
+ | Name | Registry | Not found? | What the registry does |
23
+ | -------------- | --------- | ---------- | ----------------------------------------------------- |
24
+ | `f1ask` | PyPI | yes | Refuses it: too similar to `flask` |
25
+ | `first-one` | npm | yes | Refuses it: same as `firstone` without punctuation |
26
+ | `golang` | Homebrew | yes | Already taken: an alias of `go` |
27
+ | `rust` | crates.io | yes | Refuses it: reserved by crates.io |
28
+ | `gh` | crates.io | yes | Refuses it until 2027: the crate was deleted recently |
29
+ | `xn--ls8h.dev` | domains | yes | Cannot be registered: it is an emoji in punycode |
30
+
31
+ Nameable applies each registry's own rules, taken from its source code, and checks the lists those rules need:
32
+ similar and reserved names, aliases and old names, recently deleted names, unpublished packages. When a rule cannot
33
+ be checked, the answer says "probably" and why, instead of "available". A failed request is `unknown`, never free.
34
+
35
+ ## Use it from the terminal
36
+
37
+ Requires Node.js 22 or later.
38
+
39
+ ```sh
40
+ npx nameable nameable --tlds com,dev,sh
41
+ ```
42
+
43
+ ```
44
+ Checking "nameable" on 8 channels
45
+
46
+ ✗ npm taken Find out if a name is free everywhere it matters for your p…
47
+ ✓ PyPI available
48
+ ✓ crates.io available
49
+ ✓ Homebrew available no homebrew-core formula or cask
50
+ ✓ GitHub user/org probably available suspended, deleted or reserved accounts also look free; con…
51
+ ✗ .com taken registered 1999-02-13, expires 2027-02-13
52
+ ✓ .dev probably available not registered; premium or reserved names can still be refu…
53
+ ✓ .sh probably available not registered (whois); premium or reserved names can still…
54
+
55
+ npm https://www.npmjs.com/package/nameable
56
+ .com https://lookup.icann.org/en/lookup?name=nameable.com
57
+
58
+ 6 available · 2 taken
59
+ ```
60
+
61
+ Several names show a grid to compare them:
62
+
63
+ ```sh
64
+ npx nameable twinsy dupefind fclones --only crates,homebrew,github,domain --tlds com,dev,sh
65
+ ```
66
+
67
+ ```
68
+ twinsy dupefind fclones
69
+ crates.io ✓ ✓ ✗
70
+ Homebrew ✓ ✓ ✗
71
+ GitHub user/org ✗ ✓* ✓*
72
+ .com ✗ ✗ ✓*
73
+ .dev ✓* ✓* ✓*
74
+ .sh ✓* ✓* ✓*
75
+ free 4/6 5/6 4/6
76
+ ```
77
+
78
+ | Option | |
79
+ | --------------- | -------------------------------------------------------------------------------------------------------------------- |
80
+ | `--only <ids>` | Channels to check: `npm`, `pypi`, `crates`, `homebrew`, `github`, `domain` (every TLD in `--tlds`) or `domain:<tld>` |
81
+ | `--tlds <list>` | Domains to check, any TLD (default `com,dev`). TLDs without RDAP, such as `.io`, `.sh` and `.de`, go through whois |
82
+ | `--json` | Print JSON; also the default when the output is not a terminal |
83
+ | `--refresh` | Download the registry name lists again |
84
+ | `--clear-cache` | Delete the cached name lists |
85
+
86
+ GitHub checks use `GITHUB_TOKEN`, `GH_TOKEN` or `gh auth token` when one is available, for a higher rate limit.
87
+
88
+ ## Use it with AI agents
89
+
90
+ ### Claude Code plugin
91
+
92
+ ```
93
+ /plugin marketplace add minanyang/claude-plugins
94
+ /plugin install nameable@minanyang
95
+ ```
96
+
97
+ Then ask for a name, or run `/nameable:check <names>`. The skill picks the channels that matter for your project,
98
+ checks them, looks for projects that already do the same thing, and gives a verdict for each candidate: use it, use it
99
+ with changes (a scope, another TLD, another GitHub owner), or drop it.
100
+
101
+ ### MCP server
102
+
103
+ `npx -y nameable mcp` runs the same checks as an MCP server over stdio. It has one read-only tool, `check_names`
104
+ (`names`, and optionally `channels`, `tlds` and `refresh`), which returns the [JSON below](#json) as structured content.
105
+
106
+ | Client | Add it with |
107
+ | ----------- | ---------------------------------------------------------------------------------------------------------------------------------------------- |
108
+ | Claude Code | `claude mcp add nameable -- npx -y nameable mcp` |
109
+ | Codex | `codex mcp add nameable -- npx -y nameable mcp` |
110
+ | Gemini CLI | Add to `~/.gemini/settings.json`: `{ "mcpServers": { "nameable": { "command": "npx", "args": ["-y", "nameable", "mcp"] } } }` |
111
+ | VS Code | `code --add-mcp '{"name":"nameable","type":"stdio","command":"npx","args":["-y","nameable","mcp"]}'` |
112
+ | Cursor | Add to `.cursor/mcp.json` or `~/.cursor/mcp.json`: `{ "mcpServers": { "nameable": { "command": "npx", "args": ["-y", "nameable", "mcp"] } } }` |
113
+
114
+ ## What the answers mean
115
+
116
+ | Status | Meaning |
117
+ | ------------- | ------------------------------------------------------------------------------------------------------------------------------ |
118
+ | `available` | Nobody holds it and the registry would accept it |
119
+ | `reclaimable` | It was held but released (an unpublished npm package); anyone can take it again |
120
+ | `taken` | Someone holds it; `url` shows who |
121
+ | `unavailable` | Nobody holds it exactly, but the registry would refuse it: invalid, reserved, too similar, or blocked for now after a deletion |
122
+ | `unknown` | No trustworthy answer: a network error, a rate limit, an unrecognized reply. Never treat it as free |
123
+
124
+ An `available` answer carries a certainty. **Confirmed** means every rule of that registry that public data allows
125
+ was checked. **Probable** (`*` in the grid) means one could not be, and `note` says which: GitHub hides suspended and
126
+ reserved accounts, npm's similar-name rule cannot be checked for every spelling, registries can refuse premium or
127
+ reserved domains, or a name list could not be downloaded. Two PyPI rules rest on data PyPI does not publish, so even a
128
+ confirmed PyPI answer cannot cover them: names its admins prohibited, and names similar to a quarantined project.
129
+
130
+ | Channel | Checked beyond "not found" |
131
+ | --------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
132
+ | npm | npm's name rules and Node.js core modules, names equal without punctuation, unpublished packages and the 24-hour block after an unpublish; a missing scoped package may be private, so it is probable |
133
+ | PyPI | PEP 508 names, standard library names, similar names (`f1ask` = `flask`), projects with no releases or in quarantine |
134
+ | crates.io | Cargo's name rules, reserved names, recently deleted names, `-` and `_` as one |
135
+ | Homebrew | Formula name rules, aliases and old names of formulae and casks, names homebrew-core disallows |
136
+ | GitHub | Users and organizations; hidden accounts make a free answer probable |
137
+ | Domains | Any TLD through RDAP or whois, internationalized names, labels registries cannot accept |
138
+
139
+ ### Name lists
140
+
141
+ PyPI, Homebrew and crates.io checks compare against registry-wide lists: every PyPI project, every Homebrew alias and
142
+ old name, and the reserved and recently deleted names in crates.io's daily database dump (read by byte range, under
143
+ 1 MB of a 2 GB archive). They are downloaded the first time a check needs them, about 10 MB in all, and at most once a
144
+ day after that. They are kept compressed (about 8.5 MB) in `~/Library/Caches/nameable` on macOS,
145
+ `$XDG_CACHE_HOME/nameable` on Linux, `%LOCALAPPDATA%\nameable\Cache` on Windows, or `NAMEABLE_CACHE_DIR`.
146
+
147
+ ### JSON
148
+
149
+ ```json
150
+ {
151
+ "schema": 1,
152
+ "reports": [
153
+ {
154
+ "name": "nameable",
155
+ "results": [
156
+ {
157
+ "channel": "npm",
158
+ "label": "npm",
159
+ "status": "taken",
160
+ "url": "https://www.npmjs.com/package/nameable",
161
+ "note": "..."
162
+ },
163
+ {
164
+ "channel": "domain:dev",
165
+ "label": ".dev",
166
+ "status": "available",
167
+ "certainty": "probable",
168
+ "url": "https://lookup.icann.org/en/lookup?name=nameable.dev",
169
+ "note": "..."
170
+ }
171
+ ]
172
+ }
173
+ ]
174
+ }
175
+ ```
176
+
177
+ Exit codes: `0` every channel is available or reclaimable for every name · `1` taken or unavailable somewhere ·
178
+ `2` nothing taken, but some channels could not be checked · `64` usage error.
179
+
180
+ ## Planned channels
181
+
182
+ Nameable checks npm, PyPI, crates.io, Homebrew, GitHub users and organizations, and domains today. Each channel below
183
+ was tested before it was listed: a request about a name gets an answer that tells an existing name apart from a missing
184
+ one.
185
+
186
+ | Group | Channels |
187
+ | ------------------ | ----------------------------------------------------------------------------------------- |
188
+ | AI agents | MCP Registry, Smithery, the Claude Code plugin marketplace, Cursor, Gemini CLI extensions |
189
+ | Package registries | Packagist, RubyGems, pub.dev, NuGet, Docker Hub |
190
+ | Editor extensions | VS Code Marketplace, Open VSX, JetBrains Marketplace |
191
+ | Social handles | X, YouTube, TikTok |
192
+
193
+ Not every channel has an official API. Where Nameable reads a public page or an undocumented endpoint, the site can
194
+ change it or block requests at any time: a free answer there is probable at most, and a blocked request is `unknown`.
195
+ The same goes for registries that keep part of their rules private, such as reserved names and typo checks.
196
+
197
+ These will only show whether something with the same name exists, never that a name is free:
198
+
199
+ | Channel | Why |
200
+ | ------------------------------------- | -------------------------------------------------------------- |
201
+ | Apple App Store, Google Play | App names are not unique, and an unreleased app cannot be seen |
202
+ | Trademarks: USPTO (US), TIPO (Taiwan) | A matching mark is a risk to weigh, not a yes or no |
203
+
204
+ When a channel Nameable does not check matters for your project, the Claude Code skill lists it as not checked, so you
205
+ can look yourself.
206
+
207
+ ## License
208
+
209
+ Nameable is released under the [MIT License](LICENSE).
210
+
211
+ The name Nameable refers to this project; please don't use it as the name of a fork or derivative work — saying a
212
+ fork is based on Nameable is fine.
package/dist/cli.mjs ADDED
@@ -0,0 +1,324 @@
1
+ #!/usr/bin/env node
2
+ import { a as defaultCacheDir, i as clearStore, n as channelIds, o as fileStore, r as checkMany, s as selectChannels, t as DEFAULT_TLDS } from "./dist-kIDsX2Ie.mjs";
3
+ import { createRequire } from "node:module";
4
+ import { execFile } from "node:child_process";
5
+ import { parseArgs, promisify, styleText } from "node:util";
6
+ //#region src/render.ts
7
+ const LOOK = {
8
+ available: {
9
+ mark: "✓",
10
+ style: "green"
11
+ },
12
+ reclaimable: {
13
+ mark: "↺",
14
+ style: "cyan"
15
+ },
16
+ taken: {
17
+ mark: "✗",
18
+ style: "red"
19
+ },
20
+ unavailable: {
21
+ mark: "⊘",
22
+ style: "red"
23
+ },
24
+ unknown: {
25
+ mark: "?",
26
+ style: "yellow"
27
+ }
28
+ };
29
+ const ORDER = [
30
+ "available",
31
+ "reclaimable",
32
+ "taken",
33
+ "unavailable",
34
+ "unknown"
35
+ ];
36
+ const STATUS_WIDTH = 18;
37
+ const probable = (r) => r.status === "available" && r.certainty === "probable";
38
+ const free = (r) => r.status === "available" || r.status === "reclaimable";
39
+ const WIDE = /[\u1100-\u115f\u2e80-\u303e\u3041-\u33ff\u3400-\u4dbf\u4e00-\u9fff\ua000-\ua4cf\uac00-\ud7a3\uf900-\ufaff\ufe30-\ufe4f\uff00-\uff60\uffe0-\uffe6\u{20000}-\u{3fffd}]|\p{Extended_Pictographic}/u;
40
+ const segmenter = new Intl.Segmenter();
41
+ const graphemes = (text) => Array.from(segmenter.segment(text), (s) => s.segment);
42
+ const graphemeWidth = (g) => WIDE.test(g) ? 2 : /^\p{M}+$/u.test(g) ? 0 : 1;
43
+ const displayWidth = (text) => graphemes(text).reduce((sum, g) => sum + graphemeWidth(g), 0);
44
+ /**
45
+ * Text from registries (descriptions, names) with control characters replaced by spaces, so it cannot
46
+ * move the cursor, rewrite a row or break the in-place redraw with a newline.
47
+ */
48
+ const printable = (text) => text.replace(/[\u0000-\u001f\u007f-\u009f]+/g, " ");
49
+ const pad = (text, width) => text + " ".repeat(Math.max(0, width - displayWidth(text)));
50
+ const labelWidth = (channels) => Math.max(...channels.map((c) => displayWidth(c.label)));
51
+ function clip(text, max) {
52
+ if (max <= 0) return "";
53
+ if (displayWidth(text) <= max) return text;
54
+ let out = "";
55
+ let used = 0;
56
+ for (const g of graphemes(text)) {
57
+ if (used + graphemeWidth(g) > max - 1) break;
58
+ out += g;
59
+ used += graphemeWidth(g);
60
+ }
61
+ return `${out}…`;
62
+ }
63
+ function createRenderer(out, color) {
64
+ const paint = (style, text) => color ? styleText(style, text, { validateStream: false }) : text;
65
+ const columns = () => out.columns || 80;
66
+ const cellWidth = (channels, names) => Math.max(4, Math.min(Math.max(...names.map(displayWidth)), columns() - labelWidth(channels) - 6)) + 2;
67
+ /** Prints `rows` once and returns a function that rewrites the whole block in place. Rows must never wrap. */
68
+ function block(rows) {
69
+ const draw = (redraw) => out.write(`${redraw ? `\x1b[${rows.length}A` : ""}${rows.map((r) => `\r\x1b[2K${r}\n`).join("")}`);
70
+ draw(false);
71
+ return () => draw(true);
72
+ }
73
+ /** One row, clipped to the terminal so it never wraps. */
74
+ function line(result, width) {
75
+ const look = LOOK[result.status];
76
+ const status = probable(result) ? "probably available" : result.status;
77
+ const detail = printable(result.note ?? "");
78
+ const used = 4 + width + 1 + STATUS_WIDTH + 1;
79
+ return ` ${paint(look.style, look.mark)} ${pad(result.label, width)} ${paint(look.style, status.padEnd(STATUS_WIDTH))} ${paint("dim", clip(detail, columns() - used - 1))}`;
80
+ }
81
+ /** One name: a row per channel with its status and evidence, in a stable order. */
82
+ function liveTable(channels) {
83
+ const width = labelWidth(channels);
84
+ const rows = channels.map((c) => ` ${paint("dim", "·")} ${pad(c.label, width)} ${paint("dim", "checking…")}`);
85
+ const redraw = block(rows);
86
+ return (_name, result) => {
87
+ rows[channels.findIndex((c) => c.id === result.channel)] = line(result, width);
88
+ redraw();
89
+ };
90
+ }
91
+ /** How many names fit across the terminal in the grid. */
92
+ function gridFits(channels, names) {
93
+ return Math.max(1, Math.floor((columns() - labelWidth(channels) - 4) / cellWidth(channels, names)));
94
+ }
95
+ /** Several names: channels down, names across, one mark per cell. */
96
+ function liveGrid(channels, names) {
97
+ const width = labelWidth(channels);
98
+ const cell = cellWidth(channels, names);
99
+ const shown = names.slice(0, gridFits(channels, names));
100
+ const cells = channels.map(() => shown.map(() => ({
101
+ text: paint("dim", "·"),
102
+ plain: 1
103
+ })));
104
+ const row = (i) => ` ${pad(channels[i].label, width)} ${cells[i].map((c) => c.text + " ".repeat(cell - c.plain)).join("")}`;
105
+ const rows = [` ${" ".repeat(width)} ${shown.map((n) => pad(clip(printable(n), cell - 2), cell)).join("")}`, ...channels.map((_, i) => row(i))];
106
+ const redraw = block(rows);
107
+ return (name, result) => {
108
+ const col = shown.indexOf(name);
109
+ const i = channels.findIndex((c) => c.id === result.channel);
110
+ if (col < 0 || i < 0) return;
111
+ const look = LOOK[result.status];
112
+ const mark = `${look.mark}${probable(result) ? "*" : ""}`;
113
+ cells[i][col] = {
114
+ text: paint(look.style, mark),
115
+ plain: mark.length
116
+ };
117
+ rows[i + 1] = row(i);
118
+ redraw();
119
+ };
120
+ }
121
+ function gridFooter(reports, channels) {
122
+ const names = reports.map((r) => r.name);
123
+ const shown = gridFits(channels, names);
124
+ const cell = cellWidth(channels, names);
125
+ const counts = reports.slice(0, shown).map((r) => `${r.results.filter(free).length}/${r.results.length}`.padEnd(cell)).join("");
126
+ const legend = ORDER.map((s) => `${paint(LOOK[s].style, LOOK[s].mark)} ${s}`).join(" ");
127
+ const hidden = reports.length - shown;
128
+ return [
129
+ ` ${"free".padEnd(labelWidth(channels))} ${counts}`,
130
+ "",
131
+ ` ${legend} ${paint("dim", "* probably")}`,
132
+ ...hidden > 0 ? [` ${paint("yellow", `${hidden} more did not fit; use --json`)}`] : [],
133
+ ` ${paint("dim", "Check one name at a time for the details behind each mark.")}`
134
+ ].join("\n");
135
+ }
136
+ /** Full, unclipped links for every channel that is not free, printed once the table is final. */
137
+ function evidence(results) {
138
+ const linked = results.filter((r) => !free(r) && r.url);
139
+ if (!linked.length) return "";
140
+ const width = Math.max(...linked.map((r) => displayWidth(r.label)));
141
+ return `\n${linked.map((r) => ` ${pad(r.label, width)} ${paint("dim", printable(r.url))}`).join("\n")}\n`;
142
+ }
143
+ function summary(results) {
144
+ const counts = /* @__PURE__ */ new Map();
145
+ for (const r of results) counts.set(r.status, (counts.get(r.status) ?? 0) + 1);
146
+ return ORDER.filter((s) => counts.has(s)).map((s) => paint(LOOK[s].style, `${counts.get(s)} ${s}`)).join(paint("dim", " · "));
147
+ }
148
+ return {
149
+ liveTable,
150
+ liveGrid,
151
+ gridFooter,
152
+ evidence,
153
+ summary
154
+ };
155
+ }
156
+ //#endregion
157
+ //#region src/run.ts
158
+ const { version } = createRequire(import.meta.url)("../package.json");
159
+ const HELP = `nameable ${version}
160
+ Find out if a name is free everywhere it matters for your project.
161
+
162
+ Usage
163
+ nameable <name>... [options]
164
+ nameable check <name>... [options]
165
+ nameable mcp
166
+
167
+ One name shows each channel with its evidence; several names show a comparison grid.
168
+ \`mcp\` serves the same checks to AI agents as an MCP server over stdio.
169
+
170
+ Options
171
+ --only <ids> Comma-separated channels: ${channelIds().join(", ")}
172
+ --tlds <list> Domains to check (default: ${DEFAULT_TLDS.join(",")})
173
+ --json Print JSON (default when stdout is not a terminal)
174
+ --refresh Download the registry name lists even if the cached copy is fresh
175
+ --clear-cache Delete the cached name lists and exit
176
+ -h, --help Show this help
177
+ -v, --version Show the version
178
+
179
+ Exit codes
180
+ 0 every channel is available or reclaimable for every name
181
+ 1 taken or unavailable somewhere
182
+ 2 nothing taken, but some channels could not be checked
183
+ 64 usage error
184
+
185
+ GitHub checks use GITHUB_TOKEN, GH_TOKEN or \`gh auth token\` when available.
186
+ PyPI, Homebrew and crates.io checks download registry-wide name lists (about 10 MB in all) at most
187
+ once a day and keep them in the cache directory (NAMEABLE_CACHE_DIR to change it).
188
+ `;
189
+ var UsageError = class extends Error {};
190
+ function exitCode(reports) {
191
+ const results = reports.flatMap((r) => r.results);
192
+ if (results.some((r) => r.status === "taken" || r.status === "unavailable")) return 1;
193
+ if (results.some((r) => r.status === "unknown")) return 2;
194
+ return 0;
195
+ }
196
+ const split = (s) => s?.split(",").map((x) => x.trim()).filter(Boolean);
197
+ function parse(argv) {
198
+ let parsed;
199
+ try {
200
+ parsed = parseArgs({
201
+ args: argv,
202
+ allowPositionals: true,
203
+ options: {
204
+ only: { type: "string" },
205
+ tlds: { type: "string" },
206
+ json: { type: "boolean" },
207
+ refresh: { type: "boolean" },
208
+ "clear-cache": { type: "boolean" },
209
+ help: {
210
+ type: "boolean",
211
+ short: "h"
212
+ },
213
+ version: {
214
+ type: "boolean",
215
+ short: "v"
216
+ }
217
+ }
218
+ });
219
+ } catch (err) {
220
+ throw new UsageError(err.message);
221
+ }
222
+ const { values, positionals } = parsed;
223
+ const names = positionals[0] === "check" && positionals.length > 1 ? positionals.slice(1) : positionals;
224
+ const mcp = positionals.length === 1 && positionals[0] === "mcp";
225
+ return {
226
+ values,
227
+ names: [...new Set(names)],
228
+ mcp
229
+ };
230
+ }
231
+ async function run(argv, io) {
232
+ let values;
233
+ let names;
234
+ let mcp;
235
+ let channels;
236
+ try {
237
+ ({values, names, mcp} = parse(argv));
238
+ if (values.help) return io.stdout.write(HELP), 0;
239
+ if (values.version) return io.stdout.write(`${version}\n`), 0;
240
+ if (mcp) {
241
+ if (Object.values(values).some((v) => v !== void 0)) throw new UsageError("`mcp` takes no options; the agent chooses them in each call");
242
+ const { serveMcp } = await import("./mcp-BO8l8n9u.mjs");
243
+ const { githubToken, fetch, whois, cacheDir, mcpTransport } = io;
244
+ serveMcp({
245
+ version,
246
+ githubToken,
247
+ fetch,
248
+ whois,
249
+ cacheDir
250
+ }, mcpTransport);
251
+ return 0;
252
+ }
253
+ if (values["clear-cache"]) {
254
+ if (io.cacheDir) await clearStore(io.cacheDir);
255
+ return io.stdout.write(`Cleared ${io.cacheDir ?? "nothing (no cache directory)"}\n`), 0;
256
+ }
257
+ if (!names.length) throw new UsageError("missing <name>");
258
+ if (names.some((n) => !n.trim())) throw new UsageError("names cannot be empty");
259
+ for (const flag of ["only", "tlds"]) if (values[flag] !== void 0 && !split(values[flag])?.length) throw new UsageError(`--${flag} needs at least one value`);
260
+ try {
261
+ channels = selectChannels({
262
+ only: split(values.only),
263
+ tlds: split(values.tlds)
264
+ });
265
+ } catch (err) {
266
+ throw new UsageError(err.message);
267
+ }
268
+ } catch (err) {
269
+ if (!(err instanceof UsageError)) throw err;
270
+ io.stderr.write(`nameable: ${err.message}\nRun \`nameable --help\` for usage.\n`);
271
+ return 64;
272
+ }
273
+ const json = values.json || !io.interactive;
274
+ const render = createRenderer(io.stdout, io.color);
275
+ const single = names.length === 1;
276
+ if (!json) {
277
+ const what = single ? `"${printable(names[0])}"` : `${names.length} names`;
278
+ io.stdout.write(`\nChecking ${what} on ${channels.length} channels\n\n`);
279
+ }
280
+ const reports = await checkMany(names, channels, {
281
+ fetch: io.fetch,
282
+ whois: io.whois,
283
+ githubToken: channels.some((c) => c.id === "github") ? await io.githubToken?.() : void 0,
284
+ store: io.cacheDir ? fileStore(io.cacheDir) : void 0,
285
+ refresh: values.refresh,
286
+ onResult: json ? void 0 : single ? render.liveTable(channels) : render.liveGrid(channels, names)
287
+ });
288
+ if (json) io.stdout.write(`${JSON.stringify({
289
+ schema: 1,
290
+ reports
291
+ }, null, 2)}\n`);
292
+ else if (single) io.stdout.write(`${render.evidence(reports[0].results)}\n ${render.summary(reports[0].results)}\n\n`);
293
+ else io.stdout.write(`${render.gridFooter(reports, channels)}\n\n`);
294
+ return exitCode(reports);
295
+ }
296
+ //#endregion
297
+ //#region src/cli.ts
298
+ /** Token for the GitHub API's higher rate limit; checks still run without one. */
299
+ async function githubToken() {
300
+ const env = process.env.GITHUB_TOKEN || process.env.GH_TOKEN;
301
+ if (env) return env;
302
+ try {
303
+ const { stdout } = await promisify(execFile)("gh", ["auth", "token"], { timeout: 3e3 });
304
+ return stdout.trim() || void 0;
305
+ } catch {
306
+ return;
307
+ }
308
+ }
309
+ process.stdout.on("error", (err) => {
310
+ if (err.code === "EPIPE") process.exit(process.exitCode ?? 0);
311
+ throw err;
312
+ });
313
+ process.exitCode = await run(process.argv.slice(2), {
314
+ stdout: process.stdout,
315
+ stderr: process.stderr,
316
+ interactive: process.stdout.isTTY === true,
317
+ color: process.stdout.isTTY === true && process.stdout.hasColors(),
318
+ githubToken,
319
+ cacheDir: defaultCacheDir()
320
+ });
321
+ //#endregion
322
+ export {};
323
+
324
+ //# sourceMappingURL=cli.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"cli.mjs","names":[],"sources":["../src/render.ts","../src/run.ts","../src/cli.ts"],"sourcesContent":["import { styleText } from \"node:util\";\nimport type { Channel, CheckResult, Report, Status } from \"@nameable/core\";\n\ntype Style = Parameters<typeof styleText>[0];\n\nexport type Output = { write(text: string): unknown; columns?: number };\n\nconst LOOK: Record<Status, { mark: string; style: Style }> = {\n available: { mark: \"✓\", style: \"green\" },\n reclaimable: { mark: \"↺\", style: \"cyan\" },\n taken: { mark: \"✗\", style: \"red\" },\n unavailable: { mark: \"⊘\", style: \"red\" },\n unknown: { mark: \"?\", style: \"yellow\" },\n};\nconst ORDER: Status[] = [\"available\", \"reclaimable\", \"taken\", \"unavailable\", \"unknown\"];\nconst STATUS_WIDTH = 18;\n\nconst probable = (r: CheckResult) => r.status === \"available\" && r.certainty === \"probable\";\nconst free = (r: CheckResult) => r.status === \"available\" || r.status === \"reclaimable\";\n// Terminal columns, not string length: East Asian wide characters and emoji take two columns,\n// combining marks none. Close to wcwidth for the text this tool prints (names, labels, notes).\nconst WIDE =\n /[\\u1100-\\u115f\\u2e80-\\u303e\\u3041-\\u33ff\\u3400-\\u4dbf\\u4e00-\\u9fff\\ua000-\\ua4cf\\uac00-\\ud7a3\\uf900-\\ufaff\\ufe30-\\ufe4f\\uff00-\\uff60\\uffe0-\\uffe6\\u{20000}-\\u{3fffd}]|\\p{Extended_Pictographic}/u;\nconst segmenter = new Intl.Segmenter();\nconst graphemes = (text: string) => Array.from(segmenter.segment(text), (s) => s.segment);\nconst graphemeWidth = (g: string) => (WIDE.test(g) ? 2 : /^\\p{M}+$/u.test(g) ? 0 : 1);\nexport const displayWidth = (text: string): number =>\n graphemes(text).reduce((sum, g) => sum + graphemeWidth(g), 0);\n/**\n * Text from registries (descriptions, names) with control characters replaced by spaces, so it cannot\n * move the cursor, rewrite a row or break the in-place redraw with a newline.\n */\nexport const printable = (text: string): string =>\n // oxlint-disable-next-line no-control-regex\n text.replace(/[\\u0000-\\u001f\\u007f-\\u009f]+/g, \" \");\nconst pad = (text: string, width: number) =>\n text + \" \".repeat(Math.max(0, width - displayWidth(text)));\n\nconst labelWidth = (channels: Channel[]) => Math.max(...channels.map((c) => displayWidth(c.label)));\n\nfunction clip(text: string, max: number): string {\n if (max <= 0) return \"\";\n if (displayWidth(text) <= max) return text;\n let out = \"\";\n let used = 0;\n for (const g of graphemes(text)) {\n if (used + graphemeWidth(g) > max - 1) break;\n out += g;\n used += graphemeWidth(g);\n }\n return `${out}…`;\n}\n\nexport function createRenderer(out: Output, color: boolean) {\n const paint = (style: Style, text: string) =>\n color ? styleText(style, text, { validateStream: false }) : text;\n const columns = () => out.columns || 80;\n // A grid column fits its longest name, but never so wide that one column overflows the terminal.\n const cellWidth = (channels: Channel[], names: string[]) =>\n Math.max(\n 4,\n Math.min(Math.max(...names.map(displayWidth)), columns() - labelWidth(channels) - 6),\n ) + 2;\n\n /** Prints `rows` once and returns a function that rewrites the whole block in place. Rows must never wrap. */\n function block(rows: string[]) {\n const draw = (redraw: boolean) =>\n out.write(\n `${redraw ? `\\x1b[${rows.length}A` : \"\"}${rows.map((r) => `\\r\\x1b[2K${r}\\n`).join(\"\")}`,\n );\n draw(false);\n return () => draw(true);\n }\n\n /** One row, clipped to the terminal so it never wraps. */\n function line(result: CheckResult, width: number): string {\n const look = LOOK[result.status];\n const status = probable(result) ? \"probably available\" : result.status;\n // Links are listed in full under the table (see `evidence`), so the row keeps its room for the note.\n const detail = printable(result.note ?? \"\");\n const used = 2 + 1 + 1 + width + 1 + STATUS_WIDTH + 1;\n return ` ${paint(look.style, look.mark)} ${pad(result.label, width)} ${paint(look.style, status.padEnd(STATUS_WIDTH))} ${paint(\"dim\", clip(detail, columns() - used - 1))}`;\n }\n\n /** One name: a row per channel with its status and evidence, in a stable order. */\n function liveTable(channels: Channel[]) {\n const width = labelWidth(channels);\n const rows = channels.map(\n (c) => ` ${paint(\"dim\", \"·\")} ${pad(c.label, width)} ${paint(\"dim\", \"checking…\")}`,\n );\n const redraw = block(rows);\n return (_name: string, result: CheckResult) => {\n rows[channels.findIndex((c) => c.id === result.channel)] = line(result, width);\n redraw();\n };\n }\n\n /** How many names fit across the terminal in the grid. */\n function gridFits(channels: Channel[], names: string[]): number {\n return Math.max(\n 1,\n Math.floor((columns() - labelWidth(channels) - 4) / cellWidth(channels, names)),\n );\n }\n\n /** Several names: channels down, names across, one mark per cell. */\n function liveGrid(channels: Channel[], names: string[]) {\n const width = labelWidth(channels);\n const cell = cellWidth(channels, names);\n const shown = names.slice(0, gridFits(channels, names));\n // `plain` is the visible width, since color codes take no space on screen.\n const cells = channels.map(() => shown.map(() => ({ text: paint(\"dim\", \"·\"), plain: 1 })));\n const row = (i: number) =>\n ` ${pad(channels[i]!.label, width)} ${cells[i]!.map((c) => c.text + \" \".repeat(cell - c.plain)).join(\"\")}`;\n const rows = [\n ` ${\" \".repeat(width)} ${shown.map((n) => pad(clip(printable(n), cell - 2), cell)).join(\"\")}`,\n ...channels.map((_, i) => row(i)),\n ];\n const redraw = block(rows);\n return (name: string, result: CheckResult) => {\n const col = shown.indexOf(name);\n const i = channels.findIndex((c) => c.id === result.channel);\n if (col < 0 || i < 0) return;\n const look = LOOK[result.status];\n const mark = `${look.mark}${probable(result) ? \"*\" : \"\"}`;\n cells[i]![col] = { text: paint(look.style, mark), plain: mark.length };\n rows[i + 1] = row(i);\n redraw();\n };\n }\n\n function gridFooter(reports: Report[], channels: Channel[]): string {\n const names = reports.map((r) => r.name);\n const shown = gridFits(channels, names);\n const cell = cellWidth(channels, names);\n const counts = reports\n .slice(0, shown)\n .map((r) => `${r.results.filter(free).length}/${r.results.length}`.padEnd(cell))\n .join(\"\");\n const legend = ORDER.map((s) => `${paint(LOOK[s].style, LOOK[s].mark)} ${s}`).join(\" \");\n const hidden = reports.length - shown;\n return [\n ` ${\"free\".padEnd(labelWidth(channels))} ${counts}`,\n \"\",\n ` ${legend} ${paint(\"dim\", \"* probably\")}`,\n ...(hidden > 0 ? [` ${paint(\"yellow\", `${hidden} more did not fit; use --json`)}`] : []),\n ` ${paint(\"dim\", \"Check one name at a time for the details behind each mark.\")}`,\n ].join(\"\\n\");\n }\n\n /** Full, unclipped links for every channel that is not free, printed once the table is final. */\n function evidence(results: CheckResult[]): string {\n const linked = results.filter((r) => !free(r) && r.url);\n if (!linked.length) return \"\";\n const width = Math.max(...linked.map((r) => displayWidth(r.label)));\n return `\\n${linked.map((r) => ` ${pad(r.label, width)} ${paint(\"dim\", printable(r.url!))}`).join(\"\\n\")}\\n`;\n }\n\n function summary(results: CheckResult[]): string {\n const counts = new Map<Status, number>();\n for (const r of results) counts.set(r.status, (counts.get(r.status) ?? 0) + 1);\n return ORDER.filter((s) => counts.has(s))\n .map((s) => paint(LOOK[s].style, `${counts.get(s)} ${s}`))\n .join(paint(\"dim\", \" · \"));\n }\n\n return { liveTable, liveGrid, gridFooter, evidence, summary };\n}\n","import { createRequire } from \"node:module\";\nimport { parseArgs } from \"node:util\";\nimport type { Transport } from \"@modelcontextprotocol/server\";\nimport {\n channelIds,\n checkMany,\n clearStore,\n DEFAULT_TLDS,\n fileStore,\n selectChannels,\n type Channel,\n type Fetch,\n type Report,\n type Whois,\n} from \"@nameable/core\";\nimport { createRenderer, printable, type Output } from \"./render.js\";\n\nconst { version } = createRequire(import.meta.url)(\"../package.json\") as { version: string };\n\nconst HELP = `nameable ${version}\nFind out if a name is free everywhere it matters for your project.\n\nUsage\n nameable <name>... [options]\n nameable check <name>... [options]\n nameable mcp\n\n One name shows each channel with its evidence; several names show a comparison grid.\n \\`mcp\\` serves the same checks to AI agents as an MCP server over stdio.\n\nOptions\n --only <ids> Comma-separated channels: ${channelIds().join(\", \")}\n --tlds <list> Domains to check (default: ${DEFAULT_TLDS.join(\",\")})\n --json Print JSON (default when stdout is not a terminal)\n --refresh Download the registry name lists even if the cached copy is fresh\n --clear-cache Delete the cached name lists and exit\n -h, --help Show this help\n -v, --version Show the version\n\nExit codes\n 0 every channel is available or reclaimable for every name\n 1 taken or unavailable somewhere\n 2 nothing taken, but some channels could not be checked\n 64 usage error\n\nGitHub checks use GITHUB_TOKEN, GH_TOKEN or \\`gh auth token\\` when available.\nPyPI, Homebrew and crates.io checks download registry-wide name lists (about 10 MB in all) at most\nonce a day and keep them in the cache directory (NAMEABLE_CACHE_DIR to change it).\n`;\n\nexport type Io = {\n stdout: Output;\n stderr: { write(text: string): unknown };\n /** A terminal that can show the live table; otherwise JSON is printed. */\n interactive: boolean;\n color: boolean;\n githubToken?: () => Promise<string | undefined>;\n fetch?: Fetch;\n whois?: Whois;\n /** Where name lists are cached; without one they are downloaded on every run. */\n cacheDir?: string;\n /** Where `nameable mcp` talks MCP; the process's stdin and stdout by default. */\n mcpTransport?: Transport;\n};\n\nclass UsageError extends Error {}\n\nfunction exitCode(reports: Report[]): number {\n const results = reports.flatMap((r) => r.results);\n if (results.some((r) => r.status === \"taken\" || r.status === \"unavailable\")) return 1;\n if (results.some((r) => r.status === \"unknown\")) return 2;\n return 0;\n}\n\nconst split = (s?: string) =>\n s\n ?.split(\",\")\n .map((x) => x.trim())\n .filter(Boolean);\n\nfunction parse(argv: string[]) {\n let parsed;\n try {\n parsed = parseArgs({\n args: argv,\n allowPositionals: true,\n options: {\n only: { type: \"string\" },\n tlds: { type: \"string\" },\n json: { type: \"boolean\" },\n refresh: { type: \"boolean\" },\n \"clear-cache\": { type: \"boolean\" },\n help: { type: \"boolean\", short: \"h\" },\n version: { type: \"boolean\", short: \"v\" },\n },\n });\n } catch (err) {\n throw new UsageError((err as Error).message);\n }\n const { values, positionals } = parsed;\n // `check` is the subcommand only when names follow it; `nameable check` checks the name \"check\".\n const names =\n positionals[0] === \"check\" && positionals.length > 1 ? positionals.slice(1) : positionals;\n // `mcp` alone starts the server; with names after it, as with `check`, it is a name to check.\n const mcp = positionals.length === 1 && positionals[0] === \"mcp\";\n return { values, names: [...new Set(names)], mcp };\n}\n\nexport async function run(argv: string[], io: Io): Promise<number> {\n let values;\n let names: string[];\n let mcp: boolean;\n let channels: Channel[];\n try {\n ({ values, names, mcp } = parse(argv));\n if (values.help) return (io.stdout.write(HELP), 0);\n if (values.version) return (io.stdout.write(`${version}\\n`), 0);\n if (mcp) {\n // The agent picks channels and TLDs per call, so options here would silently do nothing.\n if (Object.values(values).some((v) => v !== undefined)) {\n throw new UsageError(\"`mcp` takes no options; the agent chooses them in each call\");\n }\n // Loaded only here, so checking names from the terminal does not pay for the MCP SDK.\n const { serveMcp } = await import(\"./mcp.js\");\n const { githubToken, fetch, whois, cacheDir, mcpTransport } = io;\n serveMcp({ version, githubToken, fetch, whois, cacheDir }, mcpTransport);\n return 0;\n }\n if (values[\"clear-cache\"]) {\n if (io.cacheDir) await clearStore(io.cacheDir);\n return (io.stdout.write(`Cleared ${io.cacheDir ?? \"nothing (no cache directory)\"}\\n`), 0);\n }\n if (!names.length) throw new UsageError(\"missing <name>\");\n if (names.some((n) => !n.trim())) throw new UsageError(\"names cannot be empty\");\n for (const flag of [\"only\", \"tlds\"] as const) {\n if (values[flag] !== undefined && !split(values[flag])?.length) {\n throw new UsageError(`--${flag} needs at least one value`);\n }\n }\n try {\n channels = selectChannels({ only: split(values.only), tlds: split(values.tlds) });\n } catch (err) {\n throw new UsageError((err as Error).message);\n }\n } catch (err) {\n if (!(err instanceof UsageError)) throw err;\n io.stderr.write(`nameable: ${err.message}\\nRun \\`nameable --help\\` for usage.\\n`);\n return 64;\n }\n\n const json = values.json || !io.interactive;\n const render = createRenderer(io.stdout, io.color);\n const single = names.length === 1;\n if (!json) {\n const what = single ? `\"${printable(names[0]!)}\"` : `${names.length} names`;\n io.stdout.write(`\\nChecking ${what} on ${channels.length} channels\\n\\n`);\n }\n const reports = await checkMany(names, channels, {\n fetch: io.fetch,\n whois: io.whois,\n // Asking `gh` for a token can take a moment, so only when GitHub is checked.\n githubToken: channels.some((c) => c.id === \"github\") ? await io.githubToken?.() : undefined,\n store: io.cacheDir ? fileStore(io.cacheDir) : undefined,\n refresh: values.refresh,\n onResult: json\n ? undefined\n : single\n ? render.liveTable(channels)\n : render.liveGrid(channels, names),\n });\n\n if (json) io.stdout.write(`${JSON.stringify({ schema: 1, reports }, null, 2)}\\n`);\n else if (single)\n io.stdout.write(\n `${render.evidence(reports[0]!.results)}\\n ${render.summary(reports[0]!.results)}\\n\\n`,\n );\n else io.stdout.write(`${render.gridFooter(reports, channels)}\\n\\n`);\n return exitCode(reports);\n}\n","#!/usr/bin/env node\nimport { execFile } from \"node:child_process\";\nimport { promisify } from \"node:util\";\nimport { defaultCacheDir } from \"@nameable/core\";\nimport { run } from \"./run.js\";\n\n/** Token for the GitHub API's higher rate limit; checks still run without one. */\nasync function githubToken(): Promise<string | undefined> {\n const env = process.env.GITHUB_TOKEN || process.env.GH_TOKEN;\n if (env) return env;\n try {\n const { stdout } = await promisify(execFile)(\"gh\", [\"auth\", \"token\"], { timeout: 3000 });\n return stdout.trim() || undefined;\n } catch {\n return undefined;\n }\n}\n\n// A closed pipe (`nameable foo | head`) is the reader's choice, not a crash.\nprocess.stdout.on(\"error\", (err: NodeJS.ErrnoException) => {\n if (err.code === \"EPIPE\") process.exit(process.exitCode ?? 0);\n throw err;\n});\n\nprocess.exitCode = await run(process.argv.slice(2), {\n stdout: process.stdout,\n stderr: process.stderr,\n interactive: process.stdout.isTTY === true,\n // hasColors() honors NO_COLOR and FORCE_COLOR.\n color: process.stdout.isTTY === true && process.stdout.hasColors(),\n githubToken,\n cacheDir: defaultCacheDir(),\n});\n"],"mappings":"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"}