@profullstack/nichedb 0.32.0 → 0.34.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.
Files changed (2) hide show
  1. package/package.json +1 -1
  2. package/src/index.js +147 -1
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@profullstack/nichedb",
3
- "version": "0.32.0",
3
+ "version": "0.34.0",
4
4
  "description": "CLI and MCP bridge for NicheDB: browse collections, manage sources and feeds, search items, from any deployment",
5
5
  "type": "module",
6
6
  "main": "src/index.js",
package/src/index.js CHANGED
@@ -15,7 +15,7 @@ import { homedir, tmpdir } from 'node:os';
15
15
  import { join } from 'node:path';
16
16
  import { createInterface } from 'node:readline';
17
17
 
18
- export const VERSION = '0.32.0';
18
+ export const VERSION = '0.34.0';
19
19
  const GEO_FLAGS = ['lat', 'long', 'radius', 'unit', 'bbox', 'sort', 'offset'];
20
20
  const DEFAULT_API = process.env.NICHEDB_API ?? 'https://nichedb.dev';
21
21
  const CONFIG_DIR = join(process.env.XDG_CONFIG_HOME ?? join(homedir(), '.config'), 'nichedb');
@@ -189,6 +189,40 @@ export const COMMANDS = [
189
189
  'Population from the world to the ZIP code: an area, its measures and its largest children.',
190
190
  options: ['--level country|state|city|zip', '--limit', '--offset', '--json'],
191
191
  },
192
+ {
193
+ name: 'tlds',
194
+ usage: 'tlds [<query>] [--registrar <slug>] [--type <t>] [--max-renew <n>] [--trap] [--sort …]',
195
+ summary:
196
+ "Top-level domains with each one's cheapest register, renew and transfer price, renewal first.",
197
+ options: [
198
+ '--registrar <porkbun|dynadot|cloudflare|ovh>',
199
+ '--type <generic|country-code|…>',
200
+ '--max-renew <n>',
201
+ '--max-register <n>',
202
+ '--trap (renews at 2x the first year or more)',
203
+ '--idn / --no-idn',
204
+ '--status <delegated|removed|all>',
205
+ '--sort <renew|register|transfer|ratio|tld|registrars|first_seen>',
206
+ '--order <asc|desc>',
207
+ '--limit',
208
+ '--csv',
209
+ '--json',
210
+ ],
211
+ },
212
+ {
213
+ name: 'tld',
214
+ usage: 'tld <tld> | tld changes',
215
+ summary:
216
+ 'One top-level domain: registry, RDAP server and every registrar price; or what changed in the root.',
217
+ options: ['--json'],
218
+ },
219
+ {
220
+ name: 'check',
221
+ usage: 'check <name> [--tlds com,dev,io]',
222
+ summary:
223
+ 'Is a domain name registered? Asks the registry over RDAP; a bare word is tried under each ending.',
224
+ options: ['--tlds <list>', '--json'],
225
+ },
192
226
  {
193
227
  name: 'profiles',
194
228
  usage: 'profiles [<query>] [--since <iso>] [--mine]',
@@ -805,6 +839,118 @@ export async function run(
805
839
  }
806
840
  return 0;
807
841
  }
842
+ case 'tlds': {
843
+ const qs = new URLSearchParams();
844
+ const term = rest.join(' ');
845
+ if (term) qs.set('q', term);
846
+ for (const [flag, param] of [
847
+ ['registrar', 'registrar'],
848
+ ['type', 'type'],
849
+ ['maxRenew', 'max_renew'],
850
+ ['maxRegister', 'max_register'],
851
+ ['status', 'status'],
852
+ ['sort', 'sort'],
853
+ ['order', 'order'],
854
+ ['limit', 'limit'],
855
+ ['offset', 'offset'],
856
+ ['manager', 'manager'],
857
+ ['band', 'band'],
858
+ ])
859
+ if (flags[flag] !== undefined && flags[flag] !== true) qs.set(param, flags[flag]);
860
+ if (flags.trap) qs.set('trap', '1');
861
+ if (flags.idn) qs.set('idn', '1');
862
+ if (flags.noIdn) qs.set('idn', '0');
863
+ if (flags.csv) {
864
+ qs.set('format', 'csv');
865
+ const res = await fetchImpl(`${api}/api/v1/tlds?${qs}`);
866
+ out((await res.text()).trimEnd());
867
+ return 0;
868
+ }
869
+ const r = await client.get(`/api/v1/tlds?${qs}`);
870
+ if (json) {
871
+ out(JSON.stringify(r, null, 2));
872
+ return 0;
873
+ }
874
+ const m = (p) =>
875
+ p ? `${p.amount.toFixed(2)} ${p.currency === 'USD' ? '' : p.currency}`.trim() : '-';
876
+ out(
877
+ `${pad('TLD', 22)} ${pad('REGISTER', 10)} ${pad('RENEW', 10)} ${pad('x', 5)} ${pad('TRANSFER', 10)} ${pad('CHEAPEST RENEWAL AT', 22)} REGISTRY`,
878
+ );
879
+ for (const t of r.tlds) {
880
+ out(
881
+ `${pad(`.${t.unicode ?? t.tld}`, 22)} ${pad(m(t.best?.register), 10)} ${pad(m(t.best?.renew), 10)} ${pad(t.renewal_ratio ? `${t.renewal_ratio}x` : '', 5)} ${pad(m(t.best?.transfer), 10)} ${pad(t.best?.renew?.registrar_name ?? '', 22)} ${t.manager ?? ''}`,
882
+ );
883
+ }
884
+ out(`(${r.tlds.length} of ${r.total}${r.compare_currency ? '; cheapest in USD' : ''})`);
885
+ return 0;
886
+ }
887
+ case 'tld': {
888
+ const [arg] = rest;
889
+ if (!arg) throw new Error('tld <tld> | tld changes');
890
+ if (arg === 'changes') {
891
+ const r = await client.get('/api/v1/tlds/changes');
892
+ if (json) {
893
+ out(JSON.stringify(r.changes, null, 2));
894
+ return 0;
895
+ }
896
+ for (const c of r.changes)
897
+ out(
898
+ `${when(c.at)} ${pad(c.change, 8)} .${pad(c.unicode ?? c.tld, 24)} ${c.list_version ?? ''}`,
899
+ );
900
+ if (!r.changes.length) out('(no changes since tracking began)');
901
+ return 0;
902
+ }
903
+ const t = await client.get(`/api/v1/tlds/${encodeURIComponent(arg.replace(/^\./, ''))}`);
904
+ if (json) {
905
+ out(JSON.stringify(t, null, 2));
906
+ return 0;
907
+ }
908
+ out(`.${t.unicode ?? t.tld}${t.unicode ? ` (${t.tld})` : ''} ${t.status} ${t.type ?? ''}`);
909
+ out(`registry ${t.manager ?? '-'}`);
910
+ out(`rdap ${t.rdap ?? 'none published'}`);
911
+ out('');
912
+ out(
913
+ `${pad('REGISTRAR', 24)} ${pad('CUR', 4)} ${pad('REGISTER', 10)} ${pad('RENEW', 10)} ${pad('TRANSFER', 10)} RESTORE`,
914
+ );
915
+ const f = (v) => (v === null || v === undefined ? '-' : Number(v).toFixed(2));
916
+ for (const p of t.prices)
917
+ out(
918
+ `${pad(p.registrar_name, 24)} ${pad(p.currency, 4)} ${pad(f(p.register), 10)} ${pad(f(p.renew), 10)} ${pad(f(p.transfer), 10)} ${f(p.restore)}`,
919
+ );
920
+ if (!t.prices.length) out('(no registrar read here sells it)');
921
+ out(`\n${t.page}`);
922
+ return 0;
923
+ }
924
+ case 'check': {
925
+ const name = rest.join(',');
926
+ if (!name) throw new Error('check <name> [--tlds com,dev,io]');
927
+ const qs = new URLSearchParams({ name });
928
+ if (flags.tlds && flags.tlds !== true) qs.set('tlds', flags.tlds);
929
+ const r = await client.get(`/api/v1/tlds/check?${qs}`);
930
+ if (json) {
931
+ out(JSON.stringify(r, null, 2));
932
+ return 0;
933
+ }
934
+ const word = {
935
+ registered: 'taken',
936
+ not_registered: 'not registered',
937
+ unknown: 'unknown',
938
+ invalid: 'invalid',
939
+ };
940
+ for (const x of r.results) {
941
+ const detail =
942
+ x.status === 'registered'
943
+ ? `${x.registrar ?? ''}${x.expires ? `, expires ${String(x.expires).slice(0, 10)}` : ''}`
944
+ : x.status === 'not_registered'
945
+ ? x.cheapest?.register
946
+ ? `from ${x.cheapest.register.amount.toFixed(2)} at ${x.cheapest.register.registrar_name}, renews ${x.cheapest.renew?.amount?.toFixed(2) ?? '?'}`
947
+ : ''
948
+ : (x.reason ?? '');
949
+ out(`${pad(x.name, 32)} ${pad(word[x.status] ?? x.status, 15)} ${detail}`);
950
+ }
951
+ if (r.truncated) out('(only the first names were checked)');
952
+ return 0;
953
+ }
808
954
  case 'profiles': {
809
955
  const term = rest.join(' ');
810
956
  const qs = new URLSearchParams();