namedaemon 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.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: 9b70e727222e403e62dbe9a828c528a12f9c5d1cbf3d38de3da63771aad893ab
4
+ data.tar.gz: b655fe57923a4f5943347eae91560d0c06347514451cb7f49db1754a5384a10f
5
+ SHA512:
6
+ metadata.gz: 80c4992228f3184dbf0f4d3bb5f3dac5620b17862d04b03eb35362326dbc8aa45043bd1bd38c749838bc29b634c9aeb73b78b7b9a391da9ebc6d37a86430f137
7
+ data.tar.gz: cb71947d098f6e396781f8a5d69359d1e53274cf426fffa973d016748c4f9241171ea6ebdae72cf7f958999c82f2950311186ffbc16b9785dd8983b117ed3267
data/CHANGELOG.md ADDED
@@ -0,0 +1,6 @@
1
+ # Changelog
2
+
3
+ ## 0.1.0
4
+
5
+ - First release: `NameDaemon::Client` and the `namedaemon` command for the public NameDaemon REST layer. Standard library only.
6
+ - Optional `api_key:` (or `NAMEDAEMON_API_KEY`) sends a Pro+ or Business key as `X-ND-API-Key`.
data/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Krasen Slavov
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.
data/README.md ADDED
@@ -0,0 +1,84 @@
1
+ # namedaemon (Ruby)
2
+
3
+ Ruby client and command-line tool for [NameDaemon](https://namedaemon.com), a free set of .com domain tools: availability,
4
+ brandability scores, name ideas, extension costs, domain health and more. No API key needed, standard library only.
5
+
6
+ ```
7
+ gem install namedaemon
8
+ ```
9
+
10
+ Needs Ruby 3.0 or newer. With Bundler: `gem "namedaemon"`.
11
+
12
+ ## Command line
13
+
14
+ ```
15
+ namedaemon rate example
16
+ namedaemon ideas nova 10
17
+ namedaemon check nova novax mybrand
18
+ namedaemon tld nova com io ai
19
+ namedaemon tco io
20
+ namedaemon health example
21
+ namedaemon --json appraise example
22
+ ```
23
+
24
+ `--json` prints the raw response.
25
+
26
+ ## Library
27
+
28
+ ```ruby
29
+ require "namedaemon"
30
+
31
+ nd = NameDaemon::Client.new
32
+
33
+ r = nd.rate("example")
34
+ puts "#{r["score"]} #{r["verdict"]}" # 95.5 Excellent
35
+
36
+ ideas = nd.ideas("nova", limit: 10)["ideas"]
37
+ results = nd.check(%w[nova novax])["results"]
38
+ cost = nd.tco("io", years: 5)
39
+ health = nd.domain_health("example")
40
+ ```
41
+
42
+ | Method | What it returns |
43
+ |---|---|
44
+ | `rate(name)` | brandability score, verdict, breakdown, tips |
45
+ | `ideas(keyword, limit: nil)` | two-word .com ideas built around a keyword |
46
+ | `check(names, verify: 0)` | zone status of up to 50 names, optional live checks |
47
+ | `verify(names)` | live availability of up to 10 names |
48
+ | `tld_check(label, tlds)` | one label across up to 12 extensions |
49
+ | `tco(tld, **options)` | registration, renewal and total cost by registrar |
50
+ | `hacks(word: ...)` | domain hacks such as `hack.er` |
51
+ | `names(seeds, vibe: ..., count: ...)` | brand-name generator |
52
+ | `domain_health(name)` | registration, DNS, email and blocklist checks |
53
+ | `appraise(name, deep: false)` | strength report (no price) |
54
+ | `handles(name)` | username availability on social platforms |
55
+ | `web3(name)`, `screen(name)`, `hosting`, `trend(keyword)` | ENS/Solana names, name screening, hosting share, keyword trend |
56
+ | `get(path, **params)`, `post(path, body)` | any other public route under `/wp-json/nd/v1/` |
57
+
58
+ Every method returns the parsed JSON of the matching REST route (a Hash).
59
+
60
+ ### Errors and rate limits
61
+
62
+ The public API is free and shared, so it limits each visitor to roughly 20 requests a minute. Heavier routes count for more.
63
+ When the limit is hit the client raises `NameDaemon::RateLimitError` (with `retry_after` in seconds, 60 by default); pass
64
+ `retries: 1` to wait and retry automatically. Other failures raise `NameDaemon::Error` with `status`, `code` and the server's message.
65
+
66
+ ```ruby
67
+ nd = NameDaemon::Client.new(timeout: 20, retries: 1)
68
+ begin
69
+ nd.rate("example")
70
+ rescue NameDaemon::RateLimitError => e
71
+ puts e.retry_after
72
+ end
73
+ ```
74
+
75
+ Please keep usage reasonable. For bulk work, see the paid plan at https://namedaemon.com.
76
+
77
+ ## Links
78
+
79
+ - Website and free tools: https://namedaemon.com
80
+ - Issues: https://github.com/namedaemon/namedaemon-ruby/issues
81
+ - Other languages: `npm install namedaemon`, `pip install namedaemon`, `composer require namedaemon/namedaemon`
82
+ - Developer page: https://namedaemon.com/developers/
83
+
84
+ MIT license.
data/exe/namedaemon ADDED
@@ -0,0 +1,70 @@
1
+ #!/usr/bin/env ruby
2
+ # Command line: namedaemon rate example
3
+ require "json"
4
+ require_relative "../lib/namedaemon"
5
+
6
+ USAGE = <<~TEXT.freeze
7
+ usage: namedaemon [--json] COMMAND ARGS
8
+ rate NAME brandability score for one name
9
+ ideas KEYWORD [LIMIT] two-word .com ideas around a keyword
10
+ check NAME... status of up to 50 names
11
+ verify NAME... live availability of up to 10 names
12
+ tld LABEL TLD... one label across several extensions
13
+ tco TLD cost of ownership for an extension
14
+ health NAME DNS, email and registration health
15
+ appraise NAME strength report (no price)
16
+ handles NAME username availability on social platforms
17
+ TEXT
18
+
19
+ def pretty(cmd, r)
20
+ case cmd
21
+ when "rate"
22
+ line = "#{r["name"]}: #{r["score"].nil? ? "no score" : r["score"]} #{r["verdict"]}".strip
23
+ tips = r["tips"] || []
24
+ tips.empty? ? line : "#{line}\n- #{tips.join("\n- ")}"
25
+ when "ideas"
26
+ r["ideas"].map { |i| "#{i["domain"]}.com #{i["score"]}" }.join("\n")
27
+ when "check", "verify", "tld"
28
+ r["results"].map { |x| "#{x["domain"]} #{x["status"]}#{x["est_drop"].to_s.empty? ? "" : " (drops #{x["est_drop"]})"}" }.join("\n")
29
+ else
30
+ JSON.pretty_generate(r)
31
+ end
32
+ end
33
+
34
+ args = ARGV.dup
35
+ json = !args.delete("--json").nil?
36
+ if args.delete("--version")
37
+ puts "namedaemon #{NameDaemon::VERSION}"
38
+ exit 0
39
+ end
40
+ cmd = args.shift
41
+ if cmd.nil? || %w[-h --help help].include?(cmd) || args.empty?
42
+ warn USAGE
43
+ exit(cmd.nil? || args.empty? ? 2 : 0)
44
+ end
45
+
46
+ nd = NameDaemon::Client.new
47
+ begin
48
+ res =
49
+ case cmd
50
+ when "rate" then nd.rate(args[0])
51
+ when "ideas" then nd.ideas(args[0], limit: args[1]&.to_i)
52
+ when "check" then nd.check(args)
53
+ when "verify" then nd.verify(args)
54
+ when "tld" then nd.tld_check(args[0], args[1..])
55
+ when "tco" then nd.tco(args[0])
56
+ when "health" then nd.domain_health(args[0])
57
+ when "appraise" then nd.appraise(args[0])
58
+ when "handles" then nd.handles(args[0])
59
+ else
60
+ warn USAGE
61
+ exit 2
62
+ end
63
+ rescue NameDaemon::RateLimitError => e
64
+ warn "Rate limit reached. Try again in about #{e.retry_after} seconds."
65
+ exit 1
66
+ rescue NameDaemon::Error => e
67
+ warn "Error#{e.status.zero? ? "" : " #{e.status}"}: #{e.message}"
68
+ exit 1
69
+ end
70
+ puts json ? JSON.pretty_generate(res) : pretty(cmd, res)
@@ -0,0 +1,187 @@
1
+ require "json"
2
+ require "net/http"
3
+ require "uri"
4
+ require_relative "errors"
5
+ require_relative "version"
6
+
7
+ module NameDaemon
8
+ DEFAULT_BASE_URL = "https://namedaemon.com/wp-json/nd/v1".freeze
9
+
10
+ # Client for the public NameDaemon REST layer. Standard library only (Net::HTTP).
11
+ #
12
+ # nd = NameDaemon::Client.new
13
+ # nd.rate("example")["score"] # => 95.5
14
+ class Client
15
+ attr_reader :base_url, :timeout, :retries, :user_agent
16
+
17
+ # transport: optional callable (method, uri, headers, body, timeout) => [status, headers, body] with lower-case header names.
18
+ def initialize(base_url: DEFAULT_BASE_URL, timeout: 20, retries: 0, user_agent: nil, api_key: nil, transport: nil, sleeper: nil)
19
+ @base_url = base_url.sub(%r{/+\z}, "")
20
+ @timeout = timeout
21
+ @retries = retries
22
+ @user_agent = user_agent || "namedaemon-rb/#{VERSION}"
23
+ # A Pro+ or Business API key (X-ND-API-Key); defaults to the NAMEDAEMON_API_KEY environment variable.
24
+ @api_key = api_key || ENV.fetch("NAMEDAEMON_API_KEY", "")
25
+ @transport = transport || method(:net_http)
26
+ @sleeper = sleeper || ->(seconds) { sleep(seconds) }
27
+ end
28
+
29
+ # Any public GET route, e.g. nd.get("trend", keyword: "ai").
30
+ def get(path, **params)
31
+ request("GET", path, params: params)
32
+ end
33
+
34
+ # Any public POST route with a JSON body.
35
+ def post(path, body)
36
+ request("POST", path, body: body)
37
+ end
38
+
39
+ # Brandability score and breakdown for one name (4-24 letters).
40
+ def rate(name)
41
+ get("rate", name: name)
42
+ end
43
+
44
+ # Two-word .com ideas built around a keyword (3-14 letters).
45
+ def ideas(q, limit: nil)
46
+ get("ideas", q: q, limit: limit)
47
+ end
48
+
49
+ # Live availability of up to 10 names.
50
+ def verify(domains)
51
+ post("verify", { domains: Array(domains) })
52
+ end
53
+
54
+ # Zone status of up to 50 names, with optional live checks (0-10).
55
+ def check(domains, verify: 0)
56
+ post("check", { domains: Array(domains), verify: verify })
57
+ end
58
+
59
+ # One label across up to 12 extensions.
60
+ def tld_check(label, tlds)
61
+ get("tld-check", label: label, tlds: tlds)
62
+ end
63
+
64
+ # Cost of ownership for an extension, or a ranking with q / scope / sort / limit.
65
+ def tco(tld = nil, **options)
66
+ get("tco", tld: tld, **options)
67
+ end
68
+
69
+ # Domain hacks (for example hack.er). Pass word: or keyword:, and mode:.
70
+ def hacks(**options)
71
+ get("hacks", **options)
72
+ end
73
+
74
+ # Brand-name generator: seeds plus vibe, min_len, max_len, count, variation, hide_taken.
75
+ def names(seeds, **options)
76
+ get("names", seeds: seeds, **options)
77
+ end
78
+
79
+ # Registration, DNS, email and blocklist checks for a .com label.
80
+ def domain_health(name)
81
+ get("domain-health", name: name)
82
+ end
83
+
84
+ # Strength report for a name (no price).
85
+ def appraise(name, deep: false)
86
+ get("appraise", name: name, deep: deep ? true : nil)
87
+ end
88
+
89
+ # Username availability on social platforms.
90
+ def handles(name)
91
+ get("handles", name: name)
92
+ end
93
+
94
+ # ENS and Solana name availability.
95
+ def web3(name)
96
+ get("web3", name: name)
97
+ end
98
+
99
+ # Name screening: unwanted meanings in other languages.
100
+ def screen(name)
101
+ get("screen", name: name)
102
+ end
103
+
104
+ # Hosting provider ranking (mode: top, provider or domain).
105
+ def hosting(mode: nil, q: nil)
106
+ get("hosting", mode: mode, q: q)
107
+ end
108
+
109
+ # Daily size history for a keyword in the .com zone.
110
+ def trend(keyword)
111
+ get("trend", keyword: keyword)
112
+ end
113
+
114
+ private
115
+
116
+ # Drops nil and empty values; booleans become "true" / "false" and lists become comma-separated.
117
+ def clean(params)
118
+ params.each_with_object({}) do |(key, value), out|
119
+ next if value.nil? || value == ""
120
+
121
+ value = value.join(",") if value.is_a?(Array)
122
+ out[key.to_s] = value.to_s
123
+ end
124
+ end
125
+
126
+ def request(verb, path, params: {}, body: nil)
127
+ uri = URI("#{@base_url}/#{path.to_s.sub(%r{\A/+}, "")}")
128
+ query = URI.encode_www_form(clean(params))
129
+ uri.query = query unless query.empty?
130
+ headers = { "accept" => "application/json", "user-agent" => @user_agent }
131
+ headers["x-nd-api-key"] = @api_key unless @api_key.to_s.empty?
132
+ payload = nil
133
+ unless body.nil?
134
+ payload = JSON.generate(body)
135
+ headers["content-type"] = "application/json"
136
+ end
137
+
138
+ attempt = 0
139
+ loop do
140
+ begin
141
+ status, response_headers, raw = @transport.call(verb, uri, headers, payload, @timeout)
142
+ rescue Error
143
+ raise
144
+ rescue StandardError => e
145
+ raise Error, "Network error: #{e.message}"
146
+ end
147
+
148
+ parsed = begin
149
+ raw.to_s.empty? ? nil : JSON.parse(raw)
150
+ rescue JSON::ParserError
151
+ nil
152
+ end
153
+
154
+ if status.between?(200, 299)
155
+ raise Error.new("Response was not JSON", status: status) if parsed.nil?
156
+
157
+ return parsed
158
+ end
159
+
160
+ message = (parsed.is_a?(Hash) && parsed["message"]) || "HTTP #{status}"
161
+ code = parsed.is_a?(Hash) ? parsed["code"].to_s : ""
162
+ if status == 429
163
+ retry_after = response_headers["retry-after"].to_i
164
+ retry_after = 60 if retry_after <= 0
165
+ if attempt < @retries
166
+ attempt += 1
167
+ @sleeper.call([retry_after, 60].min)
168
+ next
169
+ end
170
+ raise RateLimitError.new(message, retry_after: retry_after, code: code, body: parsed)
171
+ end
172
+ raise Error.new(message, status: status, code: code, body: parsed)
173
+ end
174
+ end
175
+
176
+ def net_http(verb, uri, headers, body, timeout)
177
+ klass = verb == "POST" ? Net::HTTP::Post : Net::HTTP::Get
178
+ req = klass.new(uri)
179
+ headers.each { |k, v| req[k] = v }
180
+ req.body = body if body
181
+ res = Net::HTTP.start(uri.host, uri.port, use_ssl: uri.scheme == "https", open_timeout: timeout, read_timeout: timeout) do |http|
182
+ http.request(req)
183
+ end
184
+ [res.code.to_i, res.each_header.to_h.transform_keys(&:downcase), res.body.to_s]
185
+ end
186
+ end
187
+ end
@@ -0,0 +1,23 @@
1
+ module NameDaemon
2
+ # Any failed request. +status+ is 0 for network errors.
3
+ class Error < StandardError
4
+ attr_reader :status, :code, :body
5
+
6
+ def initialize(message, status: 0, code: "", body: nil)
7
+ super(message)
8
+ @status = status
9
+ @code = code
10
+ @body = body
11
+ end
12
+ end
13
+
14
+ # HTTP 429: the shared limit was hit. +retry_after+ is in seconds.
15
+ class RateLimitError < Error
16
+ attr_reader :retry_after
17
+
18
+ def initialize(message, retry_after: 60, code: "", body: nil)
19
+ super(message, status: 429, code: code, body: body)
20
+ @retry_after = retry_after
21
+ end
22
+ end
23
+ end
@@ -0,0 +1,3 @@
1
+ module NameDaemon
2
+ VERSION = "0.1.0"
3
+ end
data/lib/namedaemon.rb ADDED
@@ -0,0 +1,3 @@
1
+ require_relative "namedaemon/version"
2
+ require_relative "namedaemon/errors"
3
+ require_relative "namedaemon/client"
metadata ADDED
@@ -0,0 +1,59 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: namedaemon
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ platform: ruby
6
+ authors:
7
+ - Krasen Slavov
8
+ autorequire:
9
+ bindir: exe
10
+ cert_chain: []
11
+ date: 2026-10-20 00:00:00.000000000 Z
12
+ dependencies: []
13
+ description: 'Client for the public NameDaemon REST layer (https://namedaemon.com):
14
+ domain availability, brandability scores, two-word name ideas, extension costs,
15
+ domain health and more. Standard library only, no API key needed.'
16
+ email:
17
+ executables:
18
+ - namedaemon
19
+ extensions: []
20
+ extra_rdoc_files: []
21
+ files:
22
+ - CHANGELOG.md
23
+ - LICENSE
24
+ - README.md
25
+ - exe/namedaemon
26
+ - lib/namedaemon.rb
27
+ - lib/namedaemon/client.rb
28
+ - lib/namedaemon/errors.rb
29
+ - lib/namedaemon/version.rb
30
+ homepage: https://namedaemon.com/developers/
31
+ licenses:
32
+ - MIT
33
+ metadata:
34
+ homepage_uri: https://namedaemon.com/developers/
35
+ source_code_uri: https://github.com/namedaemon/namedaemon-ruby
36
+ bug_tracker_uri: https://github.com/namedaemon/namedaemon-ruby/issues
37
+ changelog_uri: https://github.com/namedaemon/namedaemon-ruby/blob/main/CHANGELOG.md
38
+ rubygems_mfa_required: 'true'
39
+ post_install_message:
40
+ rdoc_options: []
41
+ require_paths:
42
+ - lib
43
+ required_ruby_version: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - ">="
46
+ - !ruby/object:Gem::Version
47
+ version: '3.0'
48
+ required_rubygems_version: !ruby/object:Gem::Requirement
49
+ requirements:
50
+ - - ">="
51
+ - !ruby/object:Gem::Version
52
+ version: '0'
53
+ requirements: []
54
+ rubygems_version: 3.5.22
55
+ signing_key:
56
+ specification_version: 4
57
+ summary: 'Ruby client and CLI for NameDaemon: .com domain availability, brandability
58
+ scores, name ideas and domain health.'
59
+ test_files: []