@profullstack/nichedb 0.36.2 → 0.39.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 (3) hide show
  1. package/README.md +1 -1
  2. package/package.json +1 -1
  3. package/src/index.js +123 -1
package/README.md CHANGED
@@ -3,7 +3,7 @@
3
3
  The CLI for a [NicheDB](https://github.com/profullstack/niche-db) deployment, and an MCP server over stdio.
4
4
 
5
5
  ```sh
6
- npm install -g @profullstack/nichedb
6
+ curl -fsSL https://nichedb.dev/install.sh | sh # or: npm install -g @profullstack/nichedb
7
7
  nichedb login --api https://nichedb.dev # paste a key from /settings
8
8
  nichedb collections
9
9
  nichedb feeds --collection packages
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@profullstack/nichedb",
3
- "version": "0.36.2",
3
+ "version": "0.39.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.36.0';
18
+ export const VERSION = '0.39.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');
@@ -223,6 +223,33 @@ export const COMMANDS = [
223
223
  'Is a domain name registered? Asks the registry over RDAP; a bare word is tried under each ending.',
224
224
  options: ['--tlds <list>', '--json'],
225
225
  },
226
+ {
227
+ name: 'business',
228
+ usage: 'business <name> [--domain <d>] [--state <st>] [--number <n>] [--ein <ein>]',
229
+ summary:
230
+ "Is a business on anybody's list? OFAC sanctions, LEI and SEC registrations, state registry, federal exclusions and its domain's reputation, in one standing report.",
231
+ options: [
232
+ '--domain <domain>',
233
+ '--state <two letters>',
234
+ '--number <entity number>',
235
+ '--ein <ein>',
236
+ '--json',
237
+ ],
238
+ },
239
+ {
240
+ name: 'reputation',
241
+ usage: 'reputation <ip-or-domain>',
242
+ summary:
243
+ 'Is an IP or a domain on a blocklist? nichedb /traffic (human-reviewed bans), abuse.ch malware feeds and SURBL in one answer. Exits 1 when listed.',
244
+ options: ['--json'],
245
+ },
246
+ {
247
+ name: 'traffic',
248
+ usage: 'traffic [<ip>] [--days <n>] [--category <c>] [--txt]',
249
+ summary:
250
+ 'The human-reviewed /traffic IP blocklist: newest approvals, one address, or --txt for a fail2ban/nftables-ready list. Exits 1 when an address is listed.',
251
+ options: ['--days <n>', '--category <c>', '--limit <n>', '--offset <n>', '--txt', '--json'],
252
+ },
226
253
  {
227
254
  name: 'profiles',
228
255
  usage:
@@ -984,6 +1011,101 @@ export async function run(
984
1011
  if (r.truncated) out('(only the first names were checked)');
985
1012
  return 0;
986
1013
  }
1014
+ case 'business': {
1015
+ const qs = new URLSearchParams();
1016
+ const name = rest.join(' ');
1017
+ if (name) qs.set('name', name);
1018
+ for (const k of ['domain', 'state', 'number', 'ein'])
1019
+ if (flags[k] && flags[k] !== true) qs.set(k, String(flags[k]));
1020
+ if (![...qs.keys()].length)
1021
+ throw new Error('business <name> [--domain <d>] [--state <st>] [--number <n>]');
1022
+ const r = await client.get(`/api/v1/business?${qs}`);
1023
+ if (json) {
1024
+ out(JSON.stringify(r, null, 2));
1025
+ return 0;
1026
+ }
1027
+ out(`standing: ${r.standing}`);
1028
+ for (const f of r.findings) out(` ${pad(f.severity, 8)} ${f.text}`);
1029
+ if (r.notChecked.length) out(` not checked: ${r.notChecked.join('; ')}`);
1030
+ const s = r.sections;
1031
+ out('');
1032
+ out(
1033
+ `${pad('sanctions', 16)} ${s.sanctions.status === 'checked' ? (s.sanctions.hits.length ? s.sanctions.hits.map((h) => `${h.match}: ${h.name}`).join('; ') : 'not listed') : s.sanctions.status}`,
1034
+ );
1035
+ out(
1036
+ `${pad('lei', 16)} ${s.gleif.status === 'checked' ? s.gleif.records.map((x) => `${x.name} (${x.registrationStatus})`).join('; ') || 'none' : s.gleif.status}`,
1037
+ );
1038
+ out(
1039
+ `${pad('sec', 16)} ${s.edgar.status === 'checked' ? s.edgar.filers.map((x) => `${x.name} CIK ${x.cik}`).join('; ') || 'never filed' : s.edgar.status}`,
1040
+ );
1041
+ out(
1042
+ `${pad('registry', 16)} ${s.opencorporates.status === 'checked' ? s.opencorporates.companies.map((x) => `${x.name} ${x.status}`).join('; ') || 'no record' : s.opencorporates.status}`,
1043
+ );
1044
+ out(
1045
+ `${pad('exclusions', 16)} ${s.sam.status === 'checked' ? s.sam.exclusions.map((x) => x.name).join('; ') || 'none' : s.sam.status}`,
1046
+ );
1047
+ if (s.domain.status !== 'skipped') {
1048
+ const reg = s.domain.registration;
1049
+ out(
1050
+ `${pad('domain', 16)} ${s.domain.domain}${reg ? ` ${reg.status}${reg.registrar ? ` at ${reg.registrar}` : ''}` : ''}`,
1051
+ );
1052
+ for (const l of s.domain.lists ?? [])
1053
+ out(
1054
+ ` ${pad(l.list, 22)} ${pad(l.subject, 22)} ${l.status}${l.reasons?.length ? ` ${l.reasons.map((x) => x.label).join(', ')}` : ''}`,
1055
+ );
1056
+ if (s.domain.status === 'unconfigured')
1057
+ out(
1058
+ ` reputation lists not checked here: ${s.domain.selfChecks.map((x) => x.url).join(' ')}`,
1059
+ );
1060
+ }
1061
+ out('');
1062
+ for (const l of r.lookUpYourself) out(`${pad(l.name, 44)} ${l.url}`);
1063
+ return 0;
1064
+ }
1065
+ case 'reputation': {
1066
+ const indicator = rest[0];
1067
+ if (!indicator) throw new Error('reputation <ip-or-domain>');
1068
+ const r = await client.get(`/api/v1/reputation/${encodeURIComponent(indicator)}`);
1069
+ if (json) {
1070
+ out(JSON.stringify(r, null, 2));
1071
+ return 0;
1072
+ }
1073
+ out(`${r.query}: ${r.listed ? `LISTED, score ${r.score}/100` : 'not listed'}`);
1074
+ for (const reason of r.reasons) out(` ${reason}`);
1075
+ if (r.note) out(` ${r.note}`);
1076
+ out(` checked: ${r.checked.map((c) => c.name).join('; ')}`);
1077
+ out(` deeper: ${r.deeper.url}`);
1078
+ return r.listed ? 1 : 0;
1079
+ }
1080
+ case 'traffic': {
1081
+ const qs = new URLSearchParams();
1082
+ for (const k of ['days', 'category', 'limit', 'offset'])
1083
+ if (flags[k] && flags[k] !== true) qs.set(k, String(flags[k]));
1084
+ if (rest[0]) {
1085
+ const r = await client.get(`/api/v1/traffic/ip/${encodeURIComponent(rest[0])}`);
1086
+ if (json) out(JSON.stringify(r, null, 2));
1087
+ else if (!r.listed) out(`${r.ip}: not on /traffic`);
1088
+ else
1089
+ out(
1090
+ `${r.ip}: listed (${r.category}), ${r.servers} servers, ${r.blocks} bans, last seen ${String(r.last_seen).slice(0, 10)}`,
1091
+ );
1092
+ return r.listed ? 1 : 0;
1093
+ }
1094
+ if (flags.txt) {
1095
+ out(await client.text(`/traffic/blocklist.txt${qs.size ? `?${qs}` : ''}`));
1096
+ return 0;
1097
+ }
1098
+ const r = await client.get(`/api/v1/traffic${qs.size ? `?${qs}` : ''}`);
1099
+ if (json) {
1100
+ out(JSON.stringify(r, null, 2));
1101
+ return 0;
1102
+ }
1103
+ for (const x of r.addresses ?? r.rows ?? [])
1104
+ out(
1105
+ `${pad(x.ip, 40)} ${pad(x.category ?? 'other', 12)} ${String(x.last_seen ?? '').slice(0, 10)}`,
1106
+ );
1107
+ return 0;
1108
+ }
987
1109
  case 'profiles': {
988
1110
  const term = rest.join(' ');
989
1111
  const qs = new URLSearchParams();