nordic-data 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.
Files changed (4) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +160 -0
  3. package/bin/cli.js +378 -0
  4. package/package.json +48 -0
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Nordic Data
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 ADDED
@@ -0,0 +1,160 @@
1
+ # nordic-data
2
+
3
+ Every Norwegian company as one API, from your terminal.
4
+
5
+ ```sh
6
+ npx nordic-data search equinor
7
+ npx nordic-data lookup 923609016
8
+ npx nordic-data contacts 923609016
9
+ npx nordic-data finances 923609016
10
+ ```
11
+
12
+ ## Install
13
+
14
+ ```sh
15
+ npm install -g nordic-data
16
+ # or just use npx
17
+ npx nordic-data --help
18
+ ```
19
+
20
+ ## Free tier
21
+
22
+ 5,000 requests per month with an API key. Without a key, the CLI uses the public widget tier (4 full snapshots per IP per 24 hours). Get a key:
23
+
24
+ ```sh
25
+ npx nordic-data signup
26
+ # or visit https://nordicdata.cloud/?signup=free
27
+ ```
28
+
29
+ Set the key as an environment variable:
30
+
31
+ ```sh
32
+ export NORDIC_DATA_KEY=nrd_live_...
33
+ ```
34
+
35
+ You can also pass `--key <key>` per invocation.
36
+
37
+ ## Commands
38
+
39
+ | Command | What it does |
40
+ |---|---|
41
+ | `search <query>` | Search Norwegian companies by name or fragment |
42
+ | `lookup <orgnr>` | Full snapshot for one organisation number (registry + officers + finances + sanctions + contacts) |
43
+ | `contacts <orgnr>` | Emails, phones, and named executives |
44
+ | `board <orgnr>` | Current board + leadership |
45
+ | `finances <orgnr>` | Latest financial summary (revenue, operating profit, equity, ratios) |
46
+ | `procurement <orgnr>` | Doffin public-sector contract aggregates |
47
+ | `grants <orgnr>` | EU R&D grants (Horizon Europe, EIC) |
48
+ | `sanctions <orgnr>` | Sanctions screening (EU, UN, OFAC) hits |
49
+ | `shareholders <orgnr>` | Aksjonærregisteret aggregates |
50
+ | `mcp` | Show MCP setup snippet for Claude Desktop or Cursor |
51
+ | `signup` | Open the free-tier signup in your browser |
52
+
53
+ ## Examples
54
+
55
+ Find a company:
56
+
57
+ ```sh
58
+ $ npx nordic-data search equinor
59
+ 8 result(s) for "equinor"
60
+ 923609016 EQUINOR ASA · STAVANGER
61
+ 959733600 EQUINOR PENSJON · STAVANGER
62
+ ...
63
+ ```
64
+
65
+ Get the full picture (verified live against api.nordicdata.cloud):
66
+
67
+ ```sh
68
+ $ npx nordic-data lookup 923609016
69
+ EQUINOR ASA (923609016)
70
+ Status active
71
+ Founded 1995-03-12
72
+ Legal form ASA (Allmennaksjeselskap)
73
+ NACE 06.100 Utvinning av råolje
74
+ Address Forusbeen 50
75
+ City 4035 STAVANGER
76
+ Employees 21376
77
+ VAT reg. yes
78
+ Website www.equinor.com
79
+ Phone +47 406 37 334
80
+ Email apost@equinor.com
81
+
82
+ Key personnel
83
+ Chief Executive Officer Anders Opedal
84
+ Chief Financial Officer Torgrim Reitan
85
+ Chairman of the Board Jon Erik Reinhardsen
86
+ ...
87
+ ```
88
+
89
+ Financials:
90
+
91
+ ```sh
92
+ $ npx nordic-data finances 923609016
93
+ Financials (FY2024) for EQUINOR ASA
94
+ Revenue USD 72.54B
95
+ Operating profit USD 10.35B
96
+ Net result USD 8.14B
97
+ Total assets USD 109.15B
98
+ Equity USD 41.09B
99
+ Equity ratio 37.6%
100
+ Net margin 11.2%
101
+ ```
102
+
103
+ JSON output for scripting:
104
+
105
+ ```sh
106
+ $ npx nordic-data lookup 923609016 --json | jq .identity.name
107
+ "EQUINOR ASA"
108
+ ```
109
+
110
+ Sanctions screening:
111
+
112
+ ```sh
113
+ $ npx nordic-data sanctions 923609016
114
+ Sanctions screening for EQUINOR ASA
115
+ ● Officer hits: 1 (of 15 screened)
116
+ ```
117
+
118
+ ## MCP setup for Claude Desktop / Cursor
119
+
120
+ ```sh
121
+ $ npx nordic-data mcp
122
+ ```
123
+
124
+ prints the JSON snippet to drop into your MCP client config. Or visit our listings:
125
+
126
+ - [Smithery](https://smithery.ai/servers/sofia-jameson-20/Nordic-Data)
127
+ - [mcp.so](https://mcp.so/server/nordic-data)
128
+ - [PulseMCP](https://www.pulsemcp.com/servers/nordic-data)
129
+
130
+ ## What data is in Nordic Data?
131
+
132
+ Every Norwegian company joined on the organisation number:
133
+
134
+ - **Brønnøysundregistrene** — name, address, NACE, status, board, signatories (real-time, < 5 min lag)
135
+ - **Aksjonærregisteret** — shareholders with recursive UBO chain (annual snapshot)
136
+ - **Doffin** — public-sector procurement filings (live)
137
+ - **EU R&D grants** — Horizon Europe, EIC, joined to Norwegian recipients
138
+ - **Sanctions** — EU, UN, OFAC, screened by org and by officer
139
+ - **Enriched contacts** — 4-layer pipeline lifts contact-fill rate from 23% to 81% on the top 5,000 companies. [How it works](https://nordicdata.cloud/blog/four-layer-contact-enrichment).
140
+ - **Financial summaries** — revenue, operating profit, equity, ratios — last 5 reported years
141
+
142
+ ## Pricing
143
+
144
+ Free tier: 5,000 requests / month. Paid tiers from €29/mo (25,000 req) to €499/mo (500,000 req). [Full pricing](https://nordicdata.cloud/#pricing).
145
+
146
+ ## Comparison vs other vendors
147
+
148
+ We publish an honest benchmark page comparing Nordic Data against OpenCorporates, BvD/Orbis, Bisnode, Proff, Strise, and Sumsub: [https://nordicdata.cloud/coverage](https://nordicdata.cloud/coverage).
149
+
150
+ ## License
151
+
152
+ MIT — see LICENSE.
153
+
154
+ ## Links
155
+
156
+ - Web: https://nordicdata.cloud
157
+ - Docs: https://nordicdata.cloud/docs
158
+ - Blog: https://nordicdata.cloud/blog
159
+ - Smithery (MCP): https://smithery.ai/servers/sofia-jameson-20/Nordic-Data
160
+ - Issues: support@nordicdata.cloud
package/bin/cli.js ADDED
@@ -0,0 +1,378 @@
1
+ #!/usr/bin/env node
2
+ /* eslint-disable no-console */
3
+ // Nordic Data CLI — every Norwegian company as one API
4
+ // MIT License — Nordic Data <support@nordicdata.cloud>
5
+ //
6
+ // Endpoints used (verified live, 2026-05):
7
+ // GET /_/look?q=<query> Public name/orgnr search, anonymous
8
+ // GET /_/look/:orgnr Public full snapshot, 4 free per IP/24h
9
+ // GET /companies/:orgnr* Authenticated tier (X-API-Key)
10
+
11
+ const API_BASE = process.env.NORDIC_DATA_API || 'https://api.nordicdata.cloud';
12
+ const API_KEY = process.env.NORDIC_DATA_KEY || '';
13
+
14
+ const COLORS = {
15
+ reset: '\x1b[0m',
16
+ dim: '\x1b[2m',
17
+ bold: '\x1b[1m',
18
+ cyan: '\x1b[36m',
19
+ green: '\x1b[32m',
20
+ red: '\x1b[31m',
21
+ yellow: '\x1b[33m',
22
+ orange: '\x1b[38;5;208m',
23
+ };
24
+ const isTTY = process.stdout.isTTY;
25
+ const c = (color, text) => (isTTY ? `${COLORS[color]}${text}${COLORS.reset}` : text);
26
+
27
+ const HELP = `${c('bold', 'nordic-data')} ${c('dim', '— every Norwegian company as one API')}
28
+
29
+ ${c('bold', 'USAGE')}
30
+ nordic-data <command> [args]
31
+
32
+ ${c('bold', 'COMMANDS')}
33
+ search <query> Search companies by name or org number
34
+ lookup <orgnr> Full snapshot of one company
35
+ contacts <orgnr> Emails, phones, and named executives
36
+ board <orgnr> Board + leadership
37
+ finances <orgnr> Latest financial summary
38
+ procurement <orgnr> Public-sector contract aggregates (Doffin)
39
+ grants <orgnr> EU R&D grants (Horizon, EIC)
40
+ sanctions <orgnr> Sanctions screening (EU/UN/OFAC) hits
41
+ shareholders <orgnr> Aksjonærregisteret aggregates
42
+ mcp Show MCP setup snippet for Claude Desktop / Cursor
43
+ signup Open the free-tier signup page in your browser
44
+ --help, -h Show this help
45
+ --version, -v Show version
46
+
47
+ ${c('bold', 'FLAGS')}
48
+ --json Output raw JSON instead of formatted
49
+ --key <api-key> API key (or set NORDIC_DATA_KEY env var)
50
+ --no-color Disable ANSI colors
51
+
52
+ ${c('bold', 'EXAMPLES')}
53
+ ${c('dim', '# Look up Equinor (no key — uses the public widget tier)')}
54
+ nordic-data search equinor
55
+ nordic-data lookup 923609016
56
+ nordic-data contacts 923609016
57
+
58
+ ${c('dim', '# Use your API key for higher limits')}
59
+ export NORDIC_DATA_KEY=nrd_live_...
60
+ nordic-data lookup 923609016 --json | jq
61
+
62
+ ${c('dim', '# Show MCP config for Claude Desktop')}
63
+ nordic-data mcp
64
+
65
+ ${c('bold', 'FREE TIER')}
66
+ ${c('orange', '5,000 requests per month, no card.')} Get a key at
67
+ ${c('cyan', 'https://nordicdata.cloud/?signup=free')}
68
+ ${c('dim', 'Without a key, this CLI uses the public widget tier (4 lookups/IP/24h).')}
69
+ `;
70
+
71
+ const args = process.argv.slice(2);
72
+ let useJson = false;
73
+ let useColor = isTTY;
74
+ const cleanArgs = [];
75
+ for (let i = 0; i < args.length; i++) {
76
+ const a = args[i];
77
+ if (a === '--json') useJson = true;
78
+ else if (a === '--no-color') useColor = false;
79
+ else if (a === '--key') { process.env.NORDIC_DATA_KEY = args[++i] || ''; }
80
+ else if (a === '--help' || a === '-h') { console.log(HELP); process.exit(0); }
81
+ else if (a === '--version' || a === '-v') {
82
+ const { version } = require('../package.json');
83
+ console.log(version);
84
+ process.exit(0);
85
+ } else cleanArgs.push(a);
86
+ }
87
+ if (cleanArgs.length === 0) { console.log(HELP); process.exit(0); }
88
+
89
+ const [cmd, ...rest] = cleanArgs;
90
+
91
+ // ── API helpers ────────────────────────────────────────────
92
+
93
+ async function publicRequest(path) {
94
+ const headers = {
95
+ 'User-Agent': `nordic-data-cli/${require('../package.json').version}`,
96
+ Origin: 'https://nordicdata.cloud',
97
+ };
98
+ const key = API_KEY || process.env.NORDIC_DATA_KEY;
99
+ if (key) headers['X-API-Key'] = key;
100
+
101
+ const res = await fetch(`${API_BASE}${path}`, { headers });
102
+ if (!res.ok) {
103
+ if (res.status === 429) die('Rate limited. Free tier is 4 lookups per IP per 24h. Set NORDIC_DATA_KEY=... for higher limits.');
104
+ if (res.status === 401) die('Auth required. Set NORDIC_DATA_KEY=... or use --key. Sign up at https://nordicdata.cloud/?signup=free');
105
+ if (res.status === 404) die('Not found.');
106
+ die(`API error ${res.status}: ${await res.text()}`);
107
+ }
108
+ return res.json();
109
+ }
110
+
111
+ function die(msg) {
112
+ console.error(c('red', '✗ ') + msg);
113
+ process.exit(1);
114
+ }
115
+
116
+ function pretty(label, value) {
117
+ if (value == null || value === '' || value === 0) return;
118
+ const v = typeof value === 'string' ? value : JSON.stringify(value);
119
+ console.log(` ${c('dim', label.padEnd(16))} ${v}`);
120
+ }
121
+
122
+ function header(title) {
123
+ console.log('');
124
+ console.log(c('bold', title));
125
+ }
126
+
127
+ // Normalise nested response paths used across commands.
128
+ function id(snapshot) { return snapshot.identity || {}; }
129
+ function pd(snapshot) { return (snapshot.public_details && snapshot.public_details.contact_details) || {}; }
130
+ function offs(snapshot) { return (snapshot.public_details && snapshot.public_details.top_officers) || []; }
131
+
132
+ // ── Commands ───────────────────────────────────────────────
133
+
134
+ async function search(query) {
135
+ if (!query) die('Usage: nordic-data search <query>');
136
+ const data = await publicRequest(`/_/look?q=${encodeURIComponent(query)}`);
137
+ if (useJson) return console.log(JSON.stringify(data, null, 2));
138
+ const rs = data.results || [];
139
+ if (rs.length === 0) return console.log(c('dim', 'No results.'));
140
+ header(`${rs.length} result(s) for "${query}"`);
141
+ for (const r of rs.slice(0, 25)) {
142
+ const city = (r.business_address && r.business_address.city) || '';
143
+ const status = r.status === 'active' ? '' : c('dim', ` · ${r.status}`);
144
+ console.log(` ${c('orange', r.orgnr)} ${r.name}${city ? c('dim', ` · ${city}`) : ''}${status}`);
145
+ }
146
+ if (rs.length > 25) console.log(c('dim', ` ...and ${rs.length - 25} more`));
147
+ console.log('');
148
+ console.log(c('dim', 'nordic-data lookup <orgnr> to see the full snapshot.'));
149
+ }
150
+
151
+ async function lookup(orgnr) {
152
+ if (!orgnr) die('Usage: nordic-data lookup <orgnr>');
153
+ const snap = await publicRequest(`/_/look/${orgnr}`);
154
+ if (useJson) return console.log(JSON.stringify(snap, null, 2));
155
+
156
+ const i = id(snap);
157
+ const ba = i.business_address || {};
158
+ header(`${i.name || 'Unknown'} ${c('dim', `(${snap.orgnr})`)}`);
159
+ pretty('Status', i.status);
160
+ if (snap.status && snap.status.is_bankrupt) pretty('Bankruptcy', c('red', 'Active konkurs'));
161
+ pretty('Founded', i.registered);
162
+ pretty('Legal form', i.legal_form && `${i.legal_form.code} (${i.legal_form.description})`);
163
+ if (i.nace && i.nace[0]) pretty('NACE', `${i.nace[0].code} ${i.nace[0].description}`);
164
+ pretty('Address', ba.street);
165
+ pretty('City', `${ba.postal_code || ''} ${ba.city || ''}`.trim());
166
+ pretty('Employees', i.employees);
167
+ pretty('VAT reg.', i.in_vat_registry != null ? (i.in_vat_registry ? 'yes' : 'no') : null);
168
+ pretty('Website', i.website);
169
+
170
+ const contact = pd(snap);
171
+ const phones = contact.phones || [];
172
+ const emails = contact.emails || [];
173
+ if (phones.length) pretty('Phone', phones[0]);
174
+ if (emails.length) pretty('Email', emails[0]);
175
+
176
+ const nc = contact.named_contacts || [];
177
+ if (nc.length) {
178
+ header('Key personnel');
179
+ for (const p of nc.slice(0, 10)) {
180
+ const extras = [p.email, p.phone].filter(Boolean).join(' · ');
181
+ console.log(` ${c('orange', (p.role || '').slice(0, 32).padEnd(32))} ${c('bold', p.name)}${extras ? c('dim', ` · ${extras}`) : ''}`);
182
+ }
183
+ }
184
+
185
+ const sanc = snap.sanctions || {};
186
+ if (sanc.company_hits || sanc.officer_hits) {
187
+ header('Sanctions');
188
+ if (sanc.company_hits) console.log(c('red', ` ● Company hits: ${sanc.company_hits}`));
189
+ if (sanc.officer_hits) console.log(c('yellow', ` ● Officer hits: ${sanc.officer_hits}`));
190
+ }
191
+
192
+ console.log('');
193
+ console.log(c('dim', 'Open in browser: ') + c('cyan', `https://nordicdata.cloud/company/${snap.orgnr}`));
194
+ }
195
+
196
+ async function contacts(orgnr) {
197
+ if (!orgnr) die('Usage: nordic-data contacts <orgnr>');
198
+ const snap = await publicRequest(`/_/look/${orgnr}`);
199
+ if (useJson) return console.log(JSON.stringify(snap.public_details && snap.public_details.contact_details, null, 2));
200
+ const contact = pd(snap);
201
+ header(`Contacts for ${id(snap).name || orgnr}`);
202
+ const labels = contact.labels || {};
203
+ if ((contact.emails || []).length) {
204
+ console.log(c('bold', '\n Emails:'));
205
+ for (const e of contact.emails) {
206
+ const label = labels[`email:${e}`] || '';
207
+ console.log(` ${e}${label ? c('dim', ` · ${label}`) : ''}`);
208
+ }
209
+ }
210
+ if ((contact.phones || []).length) {
211
+ console.log(c('bold', '\n Phones:'));
212
+ for (const p of contact.phones) {
213
+ const label = labels[`phone:${p}`] || '';
214
+ console.log(` ${p}${label ? c('dim', ` · ${label}`) : ''}`);
215
+ }
216
+ }
217
+ if ((contact.named_contacts || []).length) {
218
+ console.log(c('bold', '\n Named contacts:'));
219
+ for (const n of contact.named_contacts) {
220
+ console.log(` ${c('orange', n.role || '')} ${c('bold', n.name)}${n.email ? c('dim', ` · ${n.email}`) : ''}${n.phone ? c('dim', ` · ${n.phone}`) : ''}`);
221
+ }
222
+ }
223
+ }
224
+
225
+ async function board(orgnr) {
226
+ if (!orgnr) die('Usage: nordic-data board <orgnr>');
227
+ const snap = await publicRequest(`/_/look/${orgnr}`);
228
+ const officers = offs(snap);
229
+ if (useJson) return console.log(JSON.stringify(officers, null, 2));
230
+ header(`Officers for ${id(snap).name || orgnr}`);
231
+ if (!officers.length) return console.log(c('dim', ' No officers in public snapshot.'));
232
+ for (const o of officers) {
233
+ const cat = o.category === 'styre' ? c('cyan', '[styre] ') : o.category === 'ledelse' ? c('orange', '[ledelse] ') : c('dim', `[${o.category}]`.padEnd(12));
234
+ console.log(` ${cat} ${(o.role_description || '').padEnd(22)} ${c('bold', o.name)}`);
235
+ }
236
+ }
237
+
238
+ async function finances(orgnr) {
239
+ if (!orgnr) die('Usage: nordic-data finances <orgnr>');
240
+ const snap = await publicRequest(`/_/look/${orgnr}`);
241
+ const a = snap.accounts || {};
242
+ if (useJson) return console.log(JSON.stringify(a, null, 2));
243
+ header(`Financials ${a.fiscal_year ? `(FY${a.fiscal_year})` : ''} for ${id(snap).name || orgnr}`);
244
+ const is_ = a.income_statement || {};
245
+ const bs = a.balance_sheet || {};
246
+ const r = a.ratios || {};
247
+ pretty('Revenue', fmt(is_.revenue, a.currency));
248
+ pretty('Operating profit', fmt(is_.operating_profit, a.currency));
249
+ pretty('Net result', fmt(is_.net_result, a.currency));
250
+ pretty('Total assets', fmt(bs.total_assets, a.currency));
251
+ pretty('Equity', fmt(bs.equity, a.currency));
252
+ pretty('Equity ratio', r.equity_ratio != null && (r.equity_ratio * 100).toFixed(1) + '%');
253
+ pretty('Net margin', r.net_margin != null && (r.net_margin * 100).toFixed(1) + '%');
254
+ }
255
+
256
+ async function procurement(orgnr) {
257
+ if (!orgnr) die('Usage: nordic-data procurement <orgnr>');
258
+ const snap = await publicRequest(`/_/look/${orgnr}`);
259
+ const p = snap.procurement || {};
260
+ if (useJson) return console.log(JSON.stringify(p, null, 2));
261
+ header(`Doffin procurement aggregates for ${id(snap).name || orgnr}`);
262
+ pretty('Tenders as buyer', `${p.tenders_as_buyer_24m || 0} (24m)`);
263
+ pretty('Contracts won', `${p.contracts_won_24m || 0} (24m)`);
264
+ pretty('Contract value', p.contract_wins_value_24m && `NOK ${p.contract_wins_value_24m.toLocaleString('nb-NO')} (24m)`);
265
+ const top = (snap.public_details && snap.public_details.top_contract_wins) || [];
266
+ if (top.length) {
267
+ console.log(c('bold', '\n Top contract wins:'));
268
+ for (const w of top.slice(0, 10)) {
269
+ console.log(` ${c('orange', w.awarded_at || '')} ${w.buyer || ''} ${c('dim', '·')} ${w.value ? 'NOK ' + Number(w.value).toLocaleString('nb-NO') : ''}`);
270
+ if (w.title) console.log(` ${c('dim', w.title)}`);
271
+ }
272
+ }
273
+ }
274
+
275
+ async function grants(orgnr) {
276
+ if (!orgnr) die('Usage: nordic-data grants <orgnr>');
277
+ const snap = await publicRequest(`/_/look/${orgnr}`);
278
+ const g = snap.eu_funding || {};
279
+ if (useJson) return console.log(JSON.stringify(g, null, 2));
280
+ header(`EU funding for ${id(snap).name || orgnr}`);
281
+ pretty('Horizon grants', g.horizon_grants);
282
+ pretty('Coordinator grants', g.coordinator_grants);
283
+ pretty('Total EC contrib', g.total_ec_contribution_eur && `EUR ${Math.round(g.total_ec_contribution_eur).toLocaleString('nb-NO')}`);
284
+ const top = (snap.public_details && snap.public_details.top_eu_grants) || [];
285
+ if (top.length) {
286
+ console.log(c('bold', '\n Top grants:'));
287
+ for (const t of top.slice(0, 10)) {
288
+ console.log(` ${c('orange', t.programme || '')} ${(t.topic || '').slice(0, 60)}`);
289
+ }
290
+ }
291
+ }
292
+
293
+ async function sanctions(orgnr) {
294
+ if (!orgnr) die('Usage: nordic-data sanctions <orgnr>');
295
+ const snap = await publicRequest(`/_/look/${orgnr}`);
296
+ const s = snap.sanctions || {};
297
+ if (useJson) return console.log(JSON.stringify(s, null, 2));
298
+ header(`Sanctions screening for ${id(snap).name || orgnr}`);
299
+ if (!s.company_hits && !s.officer_hits) {
300
+ console.log(c('green', ' ✓ No sanctions hits.'));
301
+ pretty('Officers screened', s.officers_screened);
302
+ return;
303
+ }
304
+ if (s.company_hits) console.log(c('red', ` ● Company hits: ${s.company_hits}`));
305
+ if (s.officer_hits) console.log(c('yellow', ` ● Officer hits: ${s.officer_hits} (of ${s.officers_screened || '?'} screened)`));
306
+ if (s.company_top_match) pretty('Top match', s.company_top_match);
307
+ }
308
+
309
+ async function shareholders(orgnr) {
310
+ if (!orgnr) die('Usage: nordic-data shareholders <orgnr>');
311
+ const snap = await publicRequest(`/_/look/${orgnr}`);
312
+ const sh = snap.shareholders || {};
313
+ if (useJson) return console.log(JSON.stringify(sh, null, 2));
314
+ header(`Aksjonærregisteret summary for ${id(snap).name || orgnr}`);
315
+ pretty('Fiscal year', sh.fiscal_year);
316
+ pretty('Shareholders', sh.count);
317
+ pretty('Total shares', sh.total_shares && sh.total_shares.toLocaleString('nb-NO'));
318
+ console.log(c('dim', '\n Full UBO chain available via authenticated /companies/:orgnr/ownership.'));
319
+ }
320
+
321
+ function mcp() {
322
+ console.log(`${c('bold', 'MCP setup for Claude Desktop / Cursor')}\n`);
323
+ console.log(c('dim', '# Add to your MCP client config (e.g. claude_desktop_config.json):'));
324
+ const snippet = {
325
+ mcpServers: {
326
+ 'nordic-data': {
327
+ url: 'https://api.nordicdata.cloud/mcp',
328
+ headers: { Authorization: 'Bearer YOUR_NORDIC_DATA_KEY' },
329
+ },
330
+ },
331
+ };
332
+ console.log(JSON.stringify(snippet, null, 2));
333
+ console.log('');
334
+ console.log(c('dim', 'Get a free key (5,000 req/mo) at ') + c('cyan', 'https://nordicdata.cloud/?signup=free'));
335
+ console.log(c('dim', 'Listed on Smithery: ') + c('cyan', 'https://smithery.ai/servers/sofia-jameson-20/Nordic-Data'));
336
+ console.log(c('dim', 'Listed on mcp.so: ') + c('cyan', 'https://mcp.so/server/nordic-data'));
337
+ }
338
+
339
+ function signup() {
340
+ const url = 'https://nordicdata.cloud/?signup=free';
341
+ console.log(`Opening ${c('cyan', url)} in your browser...`);
342
+ const { exec } = require('child_process');
343
+ const cmds = { darwin: `open "${url}"`, win32: `start "" "${url}"`, linux: `xdg-open "${url}"` };
344
+ const c2 = cmds[process.platform];
345
+ if (c2) exec(c2);
346
+ }
347
+
348
+ function fmt(n, currency) {
349
+ if (n == null) return null;
350
+ const cur = currency || 'NOK';
351
+ if (Math.abs(n) >= 1e9) return `${cur} ${(n / 1e9).toFixed(2)}B`;
352
+ if (Math.abs(n) >= 1e6) return `${cur} ${(n / 1e6).toFixed(1)}M`;
353
+ return `${cur} ${n.toLocaleString('nb-NO')}`;
354
+ }
355
+
356
+ // ── Dispatch ───────────────────────────────────────────────
357
+
358
+ (async () => {
359
+ try {
360
+ switch (cmd) {
361
+ case 'search': await search(rest.join(' ')); break;
362
+ case 'lookup': await lookup(rest[0]); break;
363
+ case 'contacts': await contacts(rest[0]); break;
364
+ case 'board': await board(rest[0]); break;
365
+ case 'finances': await finances(rest[0]); break;
366
+ case 'procurement': await procurement(rest[0]); break;
367
+ case 'grants': await grants(rest[0]); break;
368
+ case 'sanctions': await sanctions(rest[0]); break;
369
+ case 'shareholders': await shareholders(rest[0]); break;
370
+ case 'mcp': mcp(); break;
371
+ case 'signup': signup(); break;
372
+ default:
373
+ die(`Unknown command: ${cmd}\n\nRun ${c('bold', 'nordic-data --help')} for usage.`);
374
+ }
375
+ } catch (err) {
376
+ die(err.message || String(err));
377
+ }
378
+ })();
package/package.json ADDED
@@ -0,0 +1,48 @@
1
+ {
2
+ "name": "nordic-data",
3
+ "version": "0.1.0",
4
+ "description": "CLI for Nordic Data — every Norwegian company as one API. Look up companies, board members, owners, sanctions, procurement, EU grants, and financial summaries from your terminal.",
5
+ "bin": {
6
+ "nordic-data": "./bin/cli.js"
7
+ },
8
+ "files": [
9
+ "bin/",
10
+ "README.md",
11
+ "LICENSE"
12
+ ],
13
+ "keywords": [
14
+ "norway",
15
+ "norwegian",
16
+ "company",
17
+ "data",
18
+ "brreg",
19
+ "bronnoysund",
20
+ "aksjonaerregisteret",
21
+ "doffin",
22
+ "kyb",
23
+ "aml",
24
+ "sanctions",
25
+ "cli",
26
+ "mcp",
27
+ "nordic-data"
28
+ ],
29
+ "homepage": "https://nordicdata.cloud",
30
+ "bugs": {
31
+ "url": "https://github.com/Aengus406/nordic-data-cli/issues",
32
+ "email": "support@nordicdata.cloud"
33
+ },
34
+ "repository": {
35
+ "type": "git",
36
+ "url": "git+https://github.com/Aengus406/nordic-data-cli.git"
37
+ },
38
+ "license": "MIT",
39
+ "author": "Nordic Data <support@nordicdata.cloud> (https://nordicdata.cloud)",
40
+ "engines": {
41
+ "node": ">=18"
42
+ },
43
+ "scripts": {
44
+ "test": "node --test test/*.test.mjs",
45
+ "prepublishOnly": "node bin/cli.js --version"
46
+ },
47
+ "dependencies": {}
48
+ }