nordic-data 0.1.0 → 0.4.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/README.md +130 -115
- package/bin/cli.js +655 -17
- package/package.json +24 -7
package/README.md
CHANGED
|
@@ -1,160 +1,175 @@
|
|
|
1
|
-
#
|
|
1
|
+
# Nordic Data
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
> Norwegian, Swedish, and US business intelligence in one terminal — plus a hosted MCP server for AI agents.
|
|
4
4
|
|
|
5
|
-
```
|
|
6
|
-
npx nordic-data
|
|
7
|
-
npx nordic-data
|
|
8
|
-
npx nordic-data
|
|
9
|
-
npx nordic-data
|
|
5
|
+
```bash
|
|
6
|
+
npx nordic-data lookup 923609016 # full snapshot of Equinor
|
|
7
|
+
npx nordic-data list elektriker --city Stavanger --employees-min 5
|
|
8
|
+
npx nordic-data nl "Norwegian fintech startups under 20 employees"
|
|
9
|
+
npx nordic-data kyb 918274758 # full due-diligence pack
|
|
10
|
+
npx nordic-data verify-invoice 923609016 DE89370400440532013000
|
|
11
|
+
npx nordic-data us-search Microsoft
|
|
10
12
|
```
|
|
11
13
|
|
|
14
|
+
**Get a free key:** [nordicdata.cloud](https://nordicdata.cloud) — 500 requests/month, no card
|
|
15
|
+
**Docs:** [nordicdata.cloud/docs](https://nordicdata.cloud/docs)
|
|
16
|
+
|
|
17
|
+
---
|
|
18
|
+
|
|
12
19
|
## Install
|
|
13
20
|
|
|
14
|
-
```
|
|
21
|
+
```bash
|
|
22
|
+
npx nordic-data --help # zero install
|
|
23
|
+
# or
|
|
15
24
|
npm install -g nordic-data
|
|
16
|
-
|
|
17
|
-
npx nordic-data --help
|
|
25
|
+
nordic-data --help
|
|
18
26
|
```
|
|
19
27
|
|
|
20
|
-
##
|
|
28
|
+
## Configure your API key
|
|
21
29
|
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
```sh
|
|
25
|
-
npx nordic-data signup
|
|
26
|
-
# or visit https://nordicdata.cloud/?signup=free
|
|
30
|
+
```bash
|
|
31
|
+
export NORDIC_DATA_KEY=nrd_live_… # or pass --key on each call
|
|
27
32
|
```
|
|
28
33
|
|
|
29
|
-
|
|
34
|
+
Most commands work without a key on the public widget tier (4 lookups per IP per 24h). Discovery, KYB, AI, and US commands all require a key.
|
|
30
35
|
|
|
31
|
-
|
|
32
|
-
export NORDIC_DATA_KEY=nrd_live_...
|
|
33
|
-
```
|
|
34
|
-
|
|
35
|
-
You can also pass `--key <key>` per invocation.
|
|
36
|
+
---
|
|
36
37
|
|
|
37
38
|
## Commands
|
|
38
39
|
|
|
40
|
+
### Core lookup (Norway + Sweden)
|
|
39
41
|
| Command | What it does |
|
|
40
42
|
|---|---|
|
|
41
|
-
| `search <query>` | Search
|
|
42
|
-
| `lookup <orgnr>` | Full snapshot
|
|
43
|
+
| `search <query>` | Search companies by name or org number |
|
|
44
|
+
| `lookup <orgnr>` | Full snapshot of one company (NO 9-digit + SE 10-digit auto-routes) |
|
|
43
45
|
| `contacts <orgnr>` | Emails, phones, and named executives |
|
|
44
|
-
| `board <orgnr>` |
|
|
45
|
-
| `finances <orgnr>` | Latest financial summary
|
|
46
|
-
| `procurement <orgnr>` |
|
|
47
|
-
| `grants <orgnr>` | EU R&D
|
|
48
|
-
| `sanctions <orgnr>` | Sanctions screening (
|
|
49
|
-
| `shareholders <orgnr>` |
|
|
50
|
-
| `
|
|
51
|
-
|
|
46
|
+
| `board <orgnr>` | Board + leadership |
|
|
47
|
+
| `finances <orgnr>` | Latest financial summary |
|
|
48
|
+
| `procurement <orgnr>` | Public-sector contract aggregates |
|
|
49
|
+
| `grants <orgnr>` | EU R&D grant participations |
|
|
50
|
+
| `sanctions <orgnr>` | Sanctions screening (OFAC + EU) on company + officers |
|
|
51
|
+
| `shareholders <orgnr>` | Shareholder cap-table |
|
|
52
|
+
| `contacts-se <orgnr>` | Sweden: identity + AI-enriched contacts |
|
|
53
|
+
|
|
54
|
+
### Discovery (v0.4.0)
|
|
55
|
+
| Command | What it does |
|
|
56
|
+
|---|---|
|
|
57
|
+
| `find <q>` | Universal resolver: name / orgnr / domain / email → company |
|
|
58
|
+
| `list <profession>` | Filter NO companies by industry (× `--city` × `--employees-min`) |
|
|
59
|
+
| `nl "<question>"` | Natural-language discovery — LLM parses to filter spec |
|
|
60
|
+
| `near <lat> <lng>` | Geo-radius search (`--radius` km, `--profession` <slug>) |
|
|
61
|
+
| `similar <orgnr>` | Lookalike companies by industry + geo + size |
|
|
62
|
+
| `trades <slug>` | Vertical shortcut: elektriker, rorlegger, snekker, tannlege, etc. |
|
|
63
|
+
| `decision-makers <orgnr>` | CEO + board + named contacts in one call |
|
|
64
|
+
| `resolve <type> <value>` | email / domain / phone / iban / vat / lei / address / person → entity |
|
|
65
|
+
|
|
66
|
+
### KYB & Compliance & AI (v0.4.0)
|
|
67
|
+
| Command | What it does |
|
|
68
|
+
|---|---|
|
|
69
|
+
| `kyb <orgnr>` | One-call due-diligence pack (identity + sanctions + officers + signals) |
|
|
70
|
+
| `diff <orgnr>` | Typed changelog of recent changes (`--since YYYY-MM-DD`) |
|
|
71
|
+
| `verify-invoice <o> <iban>` | Invoice fraud check via IBAN country-mismatch heuristic |
|
|
72
|
+
| `explain <orgnr>` | AI 3-sentence narrative |
|
|
73
|
+
| `ask <orgnr> "<q>"` | Q&A grounded in the company's data |
|
|
74
|
+
| `pitch <orgnr>` | Sales talking-points referencing concrete facts |
|
|
75
|
+
|
|
76
|
+
### United States (v0.4.0 — SEC EDGAR + USA Spending)
|
|
77
|
+
| Command | What it does |
|
|
78
|
+
|---|---|
|
|
79
|
+
| `us-search <query>` | Search ~10K US public companies by name / ticker |
|
|
80
|
+
| `us-lookup <q>` | Resolve a US public company by CIK, ticker, or name |
|
|
81
|
+
| `us-filings <cik>` | Recent SEC filings (`--type 10-K`) with direct EDGAR URLs |
|
|
82
|
+
| `us-kyb <cik>` | US KYB pack: identity + filings + sanctions + federal contracts |
|
|
52
83
|
|
|
53
|
-
|
|
84
|
+
### Meta
|
|
85
|
+
| Command | What it does |
|
|
86
|
+
|---|---|
|
|
87
|
+
| `mcp` | Print MCP config snippet for Claude Desktop / Cursor |
|
|
88
|
+
| `signup` | Open the free-tier signup in your browser |
|
|
89
|
+
| `--help`, `-h` | Help (per-command help with `<cmd> --help`) |
|
|
90
|
+
| `--version`, `-v` | Show version |
|
|
54
91
|
|
|
55
|
-
|
|
92
|
+
### Flags
|
|
93
|
+
- `--json` — output raw JSON instead of formatted
|
|
94
|
+
- `--key <api-key>` — API key (or set `NORDIC_DATA_KEY` env var)
|
|
95
|
+
- `--no-color` — disable ANSI colors
|
|
56
96
|
|
|
57
|
-
|
|
58
|
-
$ npx nordic-data search equinor
|
|
59
|
-
8 result(s) for "equinor"
|
|
60
|
-
923609016 EQUINOR ASA · STAVANGER
|
|
61
|
-
959733600 EQUINOR PENSJON · STAVANGER
|
|
62
|
-
...
|
|
63
|
-
```
|
|
97
|
+
---
|
|
64
98
|
|
|
65
|
-
|
|
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
|
-
```
|
|
99
|
+
## MCP server (for AI agents)
|
|
88
100
|
|
|
89
|
-
|
|
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
|
-
```
|
|
101
|
+
The same backend is also a [Model Context Protocol](https://modelcontextprotocol.io) server with **69 tools** exposed natively for AI agents — no SDK glue required.
|
|
102
102
|
|
|
103
|
-
|
|
103
|
+
### Hosted endpoint
|
|
104
104
|
|
|
105
|
-
```
|
|
106
|
-
|
|
107
|
-
|
|
105
|
+
```
|
|
106
|
+
URL: https://api.nordicdata.cloud/mcp
|
|
107
|
+
Header: X-API-Key: <your key>
|
|
108
|
+
Transport: Streamable HTTP
|
|
108
109
|
```
|
|
109
110
|
|
|
110
|
-
|
|
111
|
+
### Claude Desktop / Cursor config
|
|
111
112
|
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
113
|
+
Edit `claude_desktop_config.json` (or `~/.cursor/mcp.json`):
|
|
114
|
+
|
|
115
|
+
```json
|
|
116
|
+
{
|
|
117
|
+
"mcpServers": {
|
|
118
|
+
"nordic-data": {
|
|
119
|
+
"url": "https://api.nordicdata.cloud/mcp",
|
|
120
|
+
"headers": { "X-API-Key": "YOUR_API_KEY_HERE" }
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
}
|
|
116
124
|
```
|
|
117
125
|
|
|
118
|
-
|
|
126
|
+
Restart the client — all 69 tools appear in the MCP picker.
|
|
119
127
|
|
|
120
|
-
|
|
121
|
-
$ npx nordic-data mcp
|
|
122
|
-
```
|
|
128
|
+
### Smithery one-click install
|
|
123
129
|
|
|
124
|
-
|
|
130
|
+
[smithery.ai/server/sofia-jameson-20/Nordic-Data](https://smithery.ai/server/sofia-jameson-20/Nordic-Data)
|
|
125
131
|
|
|
126
|
-
|
|
127
|
-
- [mcp.so](https://mcp.so/server/nordic-data)
|
|
128
|
-
- [PulseMCP](https://www.pulsemcp.com/servers/nordic-data)
|
|
132
|
+
---
|
|
129
133
|
|
|
130
|
-
## What
|
|
134
|
+
## What's covered
|
|
131
135
|
|
|
132
|
-
|
|
136
|
+
- **Norwegian company registry** — full company data + complete shareholder cap tables (3M+ positions across 396K companies)
|
|
137
|
+
- **Swedish company data** — identity (~1.6M companies) + AI-enriched contacts (verified emails, direct phones, named executives)
|
|
138
|
+
- **Danish + Finnish company data** — identity + basic enrichment
|
|
139
|
+
- **United States public companies** — SEC EDGAR (~10K listed entities, full identity + filings) + USA Spending (federal contracts since 2008)
|
|
140
|
+
- **Officer & ownership network (Norway)** — board memberships, shortest-path queries, full role history
|
|
141
|
+
- **Public procurement (5 Nordic countries)** — EU-tier (above-threshold) + Norway-tier (below-EU-threshold municipal/county tenders)
|
|
142
|
+
- **Sanctions / AML** — OFAC SDN + EU consolidated lists, with strict-surname disambiguation to suppress first-name-only false positives
|
|
143
|
+
- **R&D grants** — Horizon Europe participations per recipient
|
|
144
|
+
- **Tech intelligence** — find companies using a specific technology stack
|
|
145
|
+
- **News** — Norwegian-language news mentioning a company
|
|
146
|
+
- **AI / NL** — natural-language search, Q&A, talking-points, narrative for any covered company
|
|
133
147
|
|
|
134
|
-
|
|
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
|
|
148
|
+
## Example prompts (via the MCP server in Claude/Cursor)
|
|
141
149
|
|
|
142
|
-
|
|
150
|
+
- *"Find Norwegian electricians in Stavanger with 5+ employees and verified contact emails."*
|
|
151
|
+
- *"Pull the latest accounts and shareholders for orgnr 923609016."*
|
|
152
|
+
- *"Give me a full KYB pack on this counterparty before we onboard them."*
|
|
153
|
+
- *"Verify this invoice — orgnr 923609016 asking us to pay German IBAN DE89370400440532013000. Is that suspicious?"*
|
|
154
|
+
- *"Which Norwegian municipalities tendered snow-clearing contracts under 5M NOK with deadlines in the next 30 days?"*
|
|
155
|
+
- *"Resolve ceo@equinor.com to a company."*
|
|
156
|
+
- *"Find 20 lookalike companies for Cognite for our prospect list."*
|
|
157
|
+
- *"Look up Tesla's most recent 10-K filing."*
|
|
143
158
|
|
|
144
|
-
|
|
159
|
+
## Pricing
|
|
145
160
|
|
|
146
|
-
|
|
161
|
+
- **Free** — 500 requests/month, no card required
|
|
162
|
+
- **Paid plans** from €49/mo
|
|
147
163
|
|
|
148
|
-
|
|
164
|
+
See [nordicdata.cloud](https://nordicdata.cloud).
|
|
149
165
|
|
|
150
|
-
##
|
|
166
|
+
## Support
|
|
151
167
|
|
|
152
|
-
|
|
168
|
+
- Email: [hello@nordicdata.cloud](mailto:hello@nordicdata.cloud)
|
|
169
|
+
- Docs: [nordicdata.cloud/docs](https://nordicdata.cloud/docs)
|
|
170
|
+
- LLM-friendly site index: [nordicdata.cloud/llms.txt](https://nordicdata.cloud/llms.txt)
|
|
171
|
+
- OpenAPI spec: [api.nordicdata.cloud/openapi.json](https://api.nordicdata.cloud/openapi.json)
|
|
153
172
|
|
|
154
|
-
##
|
|
173
|
+
## License
|
|
155
174
|
|
|
156
|
-
|
|
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
|
|
175
|
+
MIT — see `LICENSE`.
|
package/bin/cli.js
CHANGED
|
@@ -28,17 +28,45 @@ const HELP = `${c('bold', 'nordic-data')} ${c('dim', '— every Norwegian compan
|
|
|
28
28
|
|
|
29
29
|
${c('bold', 'USAGE')}
|
|
30
30
|
nordic-data <command> [args]
|
|
31
|
+
nordic-data <command> --help ${c('dim', '# per-command help with examples')}
|
|
31
32
|
|
|
32
|
-
${c('bold', 'COMMANDS')}
|
|
33
|
+
${c('bold', 'COMMANDS — core lookup (NO + SE)')}
|
|
33
34
|
search <query> Search companies by name or org number
|
|
34
35
|
lookup <orgnr> Full snapshot of one company
|
|
35
36
|
contacts <orgnr> Emails, phones, and named executives
|
|
36
37
|
board <orgnr> Board + leadership
|
|
37
38
|
finances <orgnr> Latest financial summary
|
|
38
|
-
procurement <orgnr> Public-sector contract aggregates (
|
|
39
|
-
grants <orgnr> EU R&D
|
|
39
|
+
procurement <orgnr> Public-sector contract aggregates (NO + EU)
|
|
40
|
+
grants <orgnr> EU R&D grant participations
|
|
40
41
|
sanctions <orgnr> Sanctions screening (EU/UN/OFAC) hits
|
|
41
|
-
shareholders <orgnr>
|
|
42
|
+
shareholders <orgnr> Shareholder graph aggregates (Norway)
|
|
43
|
+
contacts-se <orgnr> Sweden: identity + AI-enriched contacts (10-digit orgnr)
|
|
44
|
+
|
|
45
|
+
${c('bold', 'COMMANDS — discovery (v0.4.0)')}
|
|
46
|
+
find <q> Universal resolver: name / orgnr / domain / email → company
|
|
47
|
+
list <profession> Filter NO companies by industry (× --city × --employees-min)
|
|
48
|
+
nl "<question>" Natural-language discovery (LLM parses → filter spec → results)
|
|
49
|
+
near <lat> <lng> Geo-radius search (--radius km, --profession <slug>)
|
|
50
|
+
similar <orgnr> Lookalike companies by industry + geo + size
|
|
51
|
+
trades <slug> Vertical shortcut: elektriker, rorlegger, snekker, tannlege, etc.
|
|
52
|
+
decision-makers <orgnr> CEO + board + named contacts in one call
|
|
53
|
+
resolve <type> <value> email / domain / phone / iban / vat / lei / address / person → entity
|
|
54
|
+
|
|
55
|
+
${c('bold', 'COMMANDS — KYB / Compliance / AI')}
|
|
56
|
+
kyb <orgnr> One-call due-diligence pack (identity + sanctions + officers + signals)
|
|
57
|
+
diff <orgnr> Typed changelog of recent changes (--since YYYY-MM-DD)
|
|
58
|
+
explain <orgnr> AI-generated 3-sentence narrative about the company
|
|
59
|
+
ask <orgnr> "<question>" Q&A grounded in the company's data
|
|
60
|
+
pitch <orgnr> Sales talking-points (referencing concrete facts)
|
|
61
|
+
verify-invoice <o> <iban> Invoice fraud check via IBAN country-mismatch heuristic
|
|
62
|
+
|
|
63
|
+
${c('bold', 'COMMANDS — United States (SEC EDGAR + USA Spending)')}
|
|
64
|
+
us-search <query> Search US public companies by name / ticker
|
|
65
|
+
us-lookup <q> Resolve a US public company by CIK, ticker, or name
|
|
66
|
+
us-filings <cik> Recent SEC filings (--type 10-K to filter)
|
|
67
|
+
us-kyb <cik> US KYB pack (identity + filings + sanctions + federal contracts)
|
|
68
|
+
|
|
69
|
+
${c('bold', 'COMMANDS — meta')}
|
|
42
70
|
mcp Show MCP setup snippet for Claude Desktop / Cursor
|
|
43
71
|
signup Open the free-tier signup page in your browser
|
|
44
72
|
--help, -h Show this help
|
|
@@ -50,12 +78,39 @@ ${c('bold', 'FLAGS')}
|
|
|
50
78
|
--no-color Disable ANSI colors
|
|
51
79
|
|
|
52
80
|
${c('bold', 'EXAMPLES')}
|
|
53
|
-
${c('dim', '#
|
|
81
|
+
${c('dim', '# Core lookup')}
|
|
54
82
|
nordic-data search equinor
|
|
55
83
|
nordic-data lookup 923609016
|
|
56
84
|
nordic-data contacts 923609016
|
|
57
|
-
|
|
58
|
-
|
|
85
|
+
nordic-data contacts-se 5566370985 ${c('dim', '# Sweden (10-digit orgnr)')}
|
|
86
|
+
|
|
87
|
+
${c('dim', '# Discovery — find a list of companies')}
|
|
88
|
+
nordic-data list elektriker --city Stavanger --employees-min 5
|
|
89
|
+
nordic-data nl "dentists in Bergen with 10+ employees"
|
|
90
|
+
nordic-data trades snekker --city Tromsø
|
|
91
|
+
nordic-data near 58.97 5.73 --radius 5 --profession rorlegger
|
|
92
|
+
|
|
93
|
+
${c('dim', '# Universal resolver')}
|
|
94
|
+
nordic-data find Cognite ${c('dim', '# name → company')}
|
|
95
|
+
nordic-data resolve email ceo@equinor.com ${c('dim', '# email → company')}
|
|
96
|
+
nordic-data resolve domain cognite.com ${c('dim', '# domain → company')}
|
|
97
|
+
|
|
98
|
+
${c('dim', '# KYB / Compliance')}
|
|
99
|
+
nordic-data kyb 923609016
|
|
100
|
+
nordic-data verify-invoice 923609016 DE89370400440532013000
|
|
101
|
+
nordic-data diff 923609016 --since 2026-01-01
|
|
102
|
+
|
|
103
|
+
${c('dim', '# AI / NL')}
|
|
104
|
+
nordic-data explain 923609016
|
|
105
|
+
nordic-data ask 923609016 "who chairs the board"
|
|
106
|
+
nordic-data pitch 923609016
|
|
107
|
+
|
|
108
|
+
${c('dim', '# United States (SEC EDGAR + USA Spending)')}
|
|
109
|
+
nordic-data us-search Microsoft
|
|
110
|
+
nordic-data us-kyb 0000789019
|
|
111
|
+
nordic-data us-filings 0000320193 --type 10-K
|
|
112
|
+
|
|
113
|
+
${c('dim', '# Use your API key for full access (most v0.4.0 commands require a key)')}
|
|
59
114
|
export NORDIC_DATA_KEY=nrd_live_...
|
|
60
115
|
nordic-data lookup 923609016 --json | jq
|
|
61
116
|
|
|
@@ -63,27 +118,246 @@ ${c('bold', 'EXAMPLES')}
|
|
|
63
118
|
nordic-data mcp
|
|
64
119
|
|
|
65
120
|
${c('bold', 'FREE TIER')}
|
|
66
|
-
${c('orange', '
|
|
121
|
+
${c('orange', '500 requests per month, no card.')} Get a key at
|
|
67
122
|
${c('cyan', 'https://nordicdata.cloud/?signup=free')}
|
|
68
123
|
${c('dim', 'Without a key, this CLI uses the public widget tier (4 lookups/IP/24h).')}
|
|
69
124
|
`;
|
|
70
125
|
|
|
126
|
+
// Per-command help text. Shown when user runs `nordic-data <cmd> --help`.
|
|
127
|
+
const COMMAND_HELP = {
|
|
128
|
+
search: `${c('bold', 'nordic-data search')} ${c('dim', '<query> [--json]')}
|
|
129
|
+
|
|
130
|
+
Search Norwegian companies by name or organisation number.
|
|
131
|
+
|
|
132
|
+
${c('bold', 'EXAMPLES')}
|
|
133
|
+
${c('dim', '# Fuzzy name search')}
|
|
134
|
+
nordic-data search equinor
|
|
135
|
+
|
|
136
|
+
${c('dim', '# JSON for scripting')}
|
|
137
|
+
nordic-data search "telenor" --json | jq '.results[0].orgnr'
|
|
138
|
+
|
|
139
|
+
${c('dim', '# Lookup by orgnr also works as a single-result search')}
|
|
140
|
+
nordic-data search 923609016
|
|
141
|
+
`,
|
|
142
|
+
lookup: `${c('bold', 'nordic-data lookup')} ${c('dim', '<orgnr> [--json]')}
|
|
143
|
+
|
|
144
|
+
Full snapshot of one company. Includes identity, address, key personnel,
|
|
145
|
+
contacts, and a sanctions hit count.
|
|
146
|
+
|
|
147
|
+
${c('bold', 'ORGNR FORMATS')}
|
|
148
|
+
9 digits ${c('dim', '— Norway (e.g. 923609016)')}
|
|
149
|
+
10 digits ${c('dim', '— Sweden (e.g. 5566370985 or 556637-0985) — auto-routes to Sweden command')}
|
|
150
|
+
|
|
151
|
+
${c('bold', 'EXAMPLES')}
|
|
152
|
+
${c('dim', '# Norway')}
|
|
153
|
+
nordic-data lookup 923609016
|
|
154
|
+
|
|
155
|
+
${c('dim', '# Sweden (10-digit orgnr — auto-detected)')}
|
|
156
|
+
nordic-data lookup 5566370985
|
|
157
|
+
|
|
158
|
+
${c('dim', '# Raw JSON')}
|
|
159
|
+
nordic-data lookup 923609016 --json | jq .identity.name
|
|
160
|
+
`,
|
|
161
|
+
contacts: `${c('bold', 'nordic-data contacts')} ${c('dim', '<orgnr> [--json]')}
|
|
162
|
+
|
|
163
|
+
Verified emails, phones, and named executives for a Norwegian company.
|
|
164
|
+
Cached 30 days. Empty when no public contact info is available.
|
|
165
|
+
|
|
166
|
+
${c('bold', 'EXAMPLES')}
|
|
167
|
+
nordic-data contacts 923609016
|
|
168
|
+
nordic-data contacts 923609016 --json
|
|
169
|
+
`,
|
|
170
|
+
'contacts-se': `${c('bold', 'nordic-data contacts-se')} ${c('dim', '<orgnr> [--json]')}
|
|
171
|
+
|
|
172
|
+
Sweden: identity + AI-enriched contacts (verified emails, phones, named executives).
|
|
173
|
+
|
|
174
|
+
${c('bold', 'EXAMPLES')}
|
|
175
|
+
nordic-data contacts-se 5566370985
|
|
176
|
+
nordic-data contacts-se 556637-0985 ${c('dim', '# dash is accepted')}
|
|
177
|
+
`,
|
|
178
|
+
board: `${c('bold', 'nordic-data board')} ${c('dim', '<orgnr> [--json]')}
|
|
179
|
+
|
|
180
|
+
Current board + leadership for a Norwegian company. Shows role category
|
|
181
|
+
(styre / ledelse / other) and full role description.
|
|
182
|
+
|
|
183
|
+
${c('bold', 'EXAMPLES')}
|
|
184
|
+
nordic-data board 923609016
|
|
185
|
+
`,
|
|
186
|
+
finances: `${c('bold', 'nordic-data finances')} ${c('dim', '<orgnr> [--json]')}
|
|
187
|
+
|
|
188
|
+
Latest annual accounts for a Norwegian company: revenue, operating profit,
|
|
189
|
+
net result, balance sheet totals, equity, and computed ratios.
|
|
190
|
+
|
|
191
|
+
${c('bold', 'EXAMPLES')}
|
|
192
|
+
nordic-data finances 923609016
|
|
193
|
+
nordic-data finances 923609016 --json | jq .ratios
|
|
194
|
+
`,
|
|
195
|
+
procurement: `${c('bold', 'nordic-data procurement')} ${c('dim', '<orgnr> [--json]')}
|
|
196
|
+
|
|
197
|
+
Public-sector contract aggregates for a Norwegian company — count of
|
|
198
|
+
contracts won, estimated total value, top buyers.
|
|
199
|
+
|
|
200
|
+
${c('bold', 'EXAMPLES')}
|
|
201
|
+
nordic-data procurement 923609016
|
|
202
|
+
`,
|
|
203
|
+
grants: `${c('bold', 'nordic-data grants')} ${c('dim', '<orgnr> [--json]')}
|
|
204
|
+
|
|
205
|
+
EU R&D grant participations for a Norwegian company. Returns each grant
|
|
206
|
+
with role (coordinator/participant), EU contribution, project budget.
|
|
207
|
+
|
|
208
|
+
${c('bold', 'EXAMPLES')}
|
|
209
|
+
nordic-data grants 923609016
|
|
210
|
+
`,
|
|
211
|
+
sanctions: `${c('bold', 'nordic-data sanctions')} ${c('dim', '<orgnr> [--json]')}
|
|
212
|
+
|
|
213
|
+
Sanctions / AML / KYC check. Screens the company AND its officers against
|
|
214
|
+
international watchlists (OFAC SDN; EU + UN forthcoming). Auto-fetches
|
|
215
|
+
officers if not yet cached — one call gives you the full picture.
|
|
216
|
+
|
|
217
|
+
${c('bold', 'EXAMPLES')}
|
|
218
|
+
nordic-data sanctions 923609016
|
|
219
|
+
`,
|
|
220
|
+
shareholders: `${c('bold', 'nordic-data shareholders')} ${c('dim', '<orgnr> [--json]')}
|
|
221
|
+
|
|
222
|
+
Shareholder cap table for a Norwegian AS — ownership %, share count, identity
|
|
223
|
+
of each holder.
|
|
224
|
+
|
|
225
|
+
${c('bold', 'EXAMPLES')}
|
|
226
|
+
nordic-data shareholders 923609016
|
|
227
|
+
`,
|
|
228
|
+
mcp: `${c('bold', 'nordic-data mcp')}
|
|
229
|
+
|
|
230
|
+
Print the MCP server config snippet to drop into Claude Desktop / Cursor.
|
|
231
|
+
Set NORDIC_DATA_KEY first to embed your key in the snippet.
|
|
232
|
+
|
|
233
|
+
${c('bold', 'EXAMPLES')}
|
|
234
|
+
${c('dim', '# Print the snippet')}
|
|
235
|
+
nordic-data mcp
|
|
236
|
+
|
|
237
|
+
${c('dim', '# Wire your key in the printed snippet')}
|
|
238
|
+
export NORDIC_DATA_KEY=nrd_live_...
|
|
239
|
+
nordic-data mcp
|
|
240
|
+
`,
|
|
241
|
+
signup: `${c('bold', 'nordic-data signup')}
|
|
242
|
+
|
|
243
|
+
Open the free-tier signup page in your browser. 500 requests/month, no card.
|
|
244
|
+
`,
|
|
245
|
+
// v0.4.0 — Discovery + KYB + AI/NL + US
|
|
246
|
+
find: `${c('bold', 'nordic-data find')} ${c('dim', '<query>')}
|
|
247
|
+
Universal resolver. Pass anything (name, orgnr, domain, email); get the best company match plus alternates.
|
|
248
|
+
nordic-data find Cognite
|
|
249
|
+
nordic-data find 918274758
|
|
250
|
+
nordic-data find cognite.com
|
|
251
|
+
nordic-data find ceo@equinor.com
|
|
252
|
+
`,
|
|
253
|
+
list: `${c('bold', 'nordic-data list')} ${c('dim', '<profession> [--city <city>] [--employees-min N] [--employees-max N] [--limit N]')}
|
|
254
|
+
Filter Norwegian companies by plain-text profession (auto-mapped to NACE) + city + size.
|
|
255
|
+
nordic-data list elektriker --city Stavanger --employees-min 5
|
|
256
|
+
nordic-data list tannlege --city Bergen
|
|
257
|
+
nordic-data list snekker --city Tromsø --limit 20
|
|
258
|
+
`,
|
|
259
|
+
nl: `${c('bold', 'nordic-data nl')} ${c('dim', '"<natural-language query>"')}
|
|
260
|
+
LLM parses your sentence into a filter spec and runs the search. Returns the parsed filter plus matching companies.
|
|
261
|
+
nordic-data nl "electricians in Stavanger with 5+ employees"
|
|
262
|
+
nordic-data nl "Norwegian fintech startups under 20 employees"
|
|
263
|
+
`,
|
|
264
|
+
kyb: `${c('bold', 'nordic-data kyb')} ${c('dim', '<orgnr>')}
|
|
265
|
+
One-call due-diligence bundle: identity + officers + sanctions (company + all officers) + bankruptcy + sister companies + recent changes + shell-likelihood signals.
|
|
266
|
+
nordic-data kyb 923609016
|
|
267
|
+
`,
|
|
268
|
+
explain: `${c('bold', 'nordic-data explain')} ${c('dim', '<orgnr>')}
|
|
269
|
+
AI-generated 3-sentence narrative: who the company is, who runs it, where they're going. Uses only structured registry data — no invented facts.
|
|
270
|
+
nordic-data explain 923609016
|
|
271
|
+
`,
|
|
272
|
+
ask: `${c('bold', 'nordic-data ask')} ${c('dim', '<orgnr> "<question>"')}
|
|
273
|
+
Q&A grounded in the company's registry + contact + contract data. Answers "Not available in the data provided" when the answer isn't in the data.
|
|
274
|
+
nordic-data ask 923609016 "who chairs the board"
|
|
275
|
+
nordic-data ask 923609016 "how many employees and where are they based"
|
|
276
|
+
`,
|
|
277
|
+
pitch: `${c('bold', 'nordic-data pitch')} ${c('dim', '<orgnr>')}
|
|
278
|
+
Sales talking points (4-5 numbered items) referencing concrete facts: recent contract wins, new execs, growth signals.
|
|
279
|
+
nordic-data pitch 923609016
|
|
280
|
+
`,
|
|
281
|
+
'verify-invoice': `${c('bold', 'nordic-data verify-invoice')} ${c('dim', '<orgnr> <iban>')}
|
|
282
|
+
Invoice fraud heuristic via IBAN-country-mismatch check. If a supposed Norwegian supplier asks you to pay a German IBAN, that's a classic CEO-fraud pattern.
|
|
283
|
+
nordic-data verify-invoice 923609016 DE89370400440532013000
|
|
284
|
+
nordic-data verify-invoice 923609016 NO9386011117947
|
|
285
|
+
`,
|
|
286
|
+
resolve: `${c('bold', 'nordic-data resolve')} ${c('dim', '<type> <value>')}
|
|
287
|
+
Resolvers — anything → entity. Types: email, domain, phone, iban, vat, lei, address, person, wikipedia.
|
|
288
|
+
nordic-data resolve email ceo@equinor.com
|
|
289
|
+
nordic-data resolve domain cognite.com
|
|
290
|
+
nordic-data resolve phone "+47 519 90 000"
|
|
291
|
+
nordic-data resolve iban DE89370400440532013000
|
|
292
|
+
`,
|
|
293
|
+
near: `${c('bold', 'nordic-data near')} ${c('dim', '<lat> <lng> [--radius 5] [--profession <slug>] [--limit N]')}
|
|
294
|
+
Geo-radius search around a coordinate. Optional industry filter.
|
|
295
|
+
nordic-data near 58.97 5.73 --radius 5 --profession rorlegger
|
|
296
|
+
`,
|
|
297
|
+
similar: `${c('bold', 'nordic-data similar')} ${c('dim', '<orgnr>')}
|
|
298
|
+
Find up to 20 lookalike companies (same industry + country, prioritised by same-city and closest employee count).
|
|
299
|
+
nordic-data similar 918274758
|
|
300
|
+
`,
|
|
301
|
+
'decision-makers': `${c('bold', 'nordic-data decision-makers')} ${c('dim', '<orgnr>')}
|
|
302
|
+
Synthesised list of key roles (CEO, CFO, chair, board) plus role-labelled named contacts.
|
|
303
|
+
nordic-data decision-makers 923609016
|
|
304
|
+
`,
|
|
305
|
+
trades: `${c('bold', 'nordic-data trades')} ${c('dim', '<slug> [--city <city>] [--limit N]')}
|
|
306
|
+
Vertical shortcut for common Norwegian trades. Slugs include: elektriker, rorlegger, snekker, tomrer, maler, murer, tannlege, lege, advokat, frisor, restaurant, kafe, etc.
|
|
307
|
+
nordic-data trades elektriker --city Oslo
|
|
308
|
+
`,
|
|
309
|
+
diff: `${c('bold', 'nordic-data diff')} ${c('dim', '<orgnr> [--since YYYY-MM-DD]')}
|
|
310
|
+
Typed changelog: board changes, ownership transfers, bankruptcy events etc. since a given date (default 30 days ago).
|
|
311
|
+
nordic-data diff 923609016 --since 2026-01-01
|
|
312
|
+
`,
|
|
313
|
+
'us-search': `${c('bold', 'nordic-data us-search')} ${c('dim', '<query>')}
|
|
314
|
+
Search ~10,000 US public companies (SEC EDGAR) by name or ticker.
|
|
315
|
+
nordic-data us-search Microsoft
|
|
316
|
+
nordic-data us-search AAPL
|
|
317
|
+
`,
|
|
318
|
+
'us-lookup': `${c('bold', 'nordic-data us-lookup')} ${c('dim', '<query>')}
|
|
319
|
+
Resolve a US public company by CIK, ticker, or name. Returns SEC identity (SIC, state, city, address).
|
|
320
|
+
nordic-data us-lookup Tesla
|
|
321
|
+
nordic-data us-lookup 0000789019
|
|
322
|
+
`,
|
|
323
|
+
'us-filings': `${c('bold', 'nordic-data us-filings')} ${c('dim', '<cik> [--type 10-K]')}
|
|
324
|
+
Recent SEC filings for a US public company with direct EDGAR URLs.
|
|
325
|
+
nordic-data us-filings 0000320193 --type 10-K
|
|
326
|
+
`,
|
|
327
|
+
'us-kyb': `${c('bold', 'nordic-data us-kyb')} ${c('dim', '<cik>')}
|
|
328
|
+
US KYB pack: SEC identity + recent filings + sanctions screen + recent federal contracts (USA Spending).
|
|
329
|
+
nordic-data us-kyb 0000789019
|
|
330
|
+
`,
|
|
331
|
+
};
|
|
332
|
+
|
|
71
333
|
const args = process.argv.slice(2);
|
|
72
334
|
let useJson = false;
|
|
73
335
|
let useColor = isTTY;
|
|
336
|
+
let wantsHelp = false;
|
|
74
337
|
const cleanArgs = [];
|
|
75
338
|
for (let i = 0; i < args.length; i++) {
|
|
76
339
|
const a = args[i];
|
|
77
340
|
if (a === '--json') useJson = true;
|
|
78
341
|
else if (a === '--no-color') useColor = false;
|
|
79
342
|
else if (a === '--key') { process.env.NORDIC_DATA_KEY = args[++i] || ''; }
|
|
80
|
-
else if (a === '--help' || a === '-h')
|
|
343
|
+
else if (a === '--help' || a === '-h') wantsHelp = true;
|
|
81
344
|
else if (a === '--version' || a === '-v') {
|
|
82
345
|
const { version } = require('../package.json');
|
|
83
346
|
console.log(version);
|
|
84
347
|
process.exit(0);
|
|
85
348
|
} else cleanArgs.push(a);
|
|
86
349
|
}
|
|
350
|
+
|
|
351
|
+
// Resolve per-command help: `nordic-data <cmd> --help` shows command-specific help.
|
|
352
|
+
// `nordic-data --help` (no command) shows the global help.
|
|
353
|
+
if (wantsHelp) {
|
|
354
|
+
if (cleanArgs.length > 0 && COMMAND_HELP[cleanArgs[0]]) {
|
|
355
|
+
console.log(COMMAND_HELP[cleanArgs[0]]);
|
|
356
|
+
} else {
|
|
357
|
+
console.log(HELP);
|
|
358
|
+
}
|
|
359
|
+
process.exit(0);
|
|
360
|
+
}
|
|
87
361
|
if (cleanArgs.length === 0) { console.log(HELP); process.exit(0); }
|
|
88
362
|
|
|
89
363
|
const [cmd, ...rest] = cleanArgs;
|
|
@@ -100,10 +374,26 @@ async function publicRequest(path) {
|
|
|
100
374
|
|
|
101
375
|
const res = await fetch(`${API_BASE}${path}`, { headers });
|
|
102
376
|
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
|
|
105
|
-
|
|
106
|
-
|
|
377
|
+
if (res.status === 429) die('Rate limited. Free tier is 4 lookups per IP per 24h. Set NORDIC_DATA_KEY=... for higher limits.\n Sign up at https://nordicdata.cloud/?signup=free (500 requests/month, no card).');
|
|
378
|
+
if (res.status === 401) die('Auth required. Set NORDIC_DATA_KEY=... or pass --key <key>.\n Sign up at https://nordicdata.cloud/?signup=free (500 requests/month, no card).');
|
|
379
|
+
// Try to parse a structured JSON error and surface a useful message + hint.
|
|
380
|
+
let body = null;
|
|
381
|
+
try { body = await res.json(); } catch {}
|
|
382
|
+
const errCode = body && body.error;
|
|
383
|
+
const errMsg = body && body.message;
|
|
384
|
+
if (res.status === 404) {
|
|
385
|
+
if (errCode === 'not_found' && body.orgnr) {
|
|
386
|
+
die(`Company ${body.orgnr} not found in the official register.\n ${c('dim', 'If you do not know the orgnr, try:')} nordic-data search <name>`);
|
|
387
|
+
}
|
|
388
|
+
die(`Not found.\n ${c('dim', 'Tip:')} nordic-data search <name> ${c('dim', 'to find the orgnr first.')}`);
|
|
389
|
+
}
|
|
390
|
+
if (res.status === 400 && errCode === 'invalid_orgnr') {
|
|
391
|
+
die(`Invalid Norwegian organisation number — must be 9 digits.\n ${c('dim', 'Tip:')} nordic-data search <name> ${c('dim', 'to find the orgnr.')}`);
|
|
392
|
+
}
|
|
393
|
+
if (res.status === 400 && errMsg) die(errMsg);
|
|
394
|
+
if (res.status === 402) die(`Payment required. ${errMsg || 'Plan does not include this endpoint.'}\n See plans: https://nordicdata.cloud/#pricing`);
|
|
395
|
+
if (res.status >= 500) die(`Server error (${res.status}). Try again in a moment. If it persists, email support@nordicdata.cloud.`);
|
|
396
|
+
die(`API error ${res.status}: ${errMsg || (body && JSON.stringify(body)) || 'unknown error'}`);
|
|
107
397
|
}
|
|
108
398
|
return res.json();
|
|
109
399
|
}
|
|
@@ -149,7 +439,16 @@ async function search(query) {
|
|
|
149
439
|
}
|
|
150
440
|
|
|
151
441
|
async function lookup(orgnr) {
|
|
152
|
-
if (!orgnr) die('Usage: nordic-data lookup <orgnr
|
|
442
|
+
if (!orgnr) die('Usage: nordic-data lookup <orgnr>\n Norway: 9-digit orgnr. Sweden: 10-digit orgnr (with or without dash).');
|
|
443
|
+
// Auto-route Swedish orgnrs (10 digits, with or without dash) to the SE flow.
|
|
444
|
+
const cleaned = String(orgnr).replace(/[-\s]/g, '');
|
|
445
|
+
if (/^\d{10}$/.test(cleaned)) {
|
|
446
|
+
console.log(c('dim', `(10-digit orgnr detected — routing to Sweden command)\n`));
|
|
447
|
+
return contactsSe(orgnr);
|
|
448
|
+
}
|
|
449
|
+
if (!/^\d{9}$/.test(cleaned)) {
|
|
450
|
+
die(`Invalid orgnr "${orgnr}".\n Norway uses 9 digits (e.g. 923609016).\n Sweden uses 10 digits (e.g. 5566370985 or 556637-0985).\n ${c('dim', 'Tip:')} nordic-data search <name> ${c('dim', 'to find it.')}`);
|
|
451
|
+
}
|
|
153
452
|
const snap = await publicRequest(`/_/look/${orgnr}`);
|
|
154
453
|
if (useJson) return console.log(JSON.stringify(snap, null, 2));
|
|
155
454
|
|
|
@@ -258,7 +557,7 @@ async function procurement(orgnr) {
|
|
|
258
557
|
const snap = await publicRequest(`/_/look/${orgnr}`);
|
|
259
558
|
const p = snap.procurement || {};
|
|
260
559
|
if (useJson) return console.log(JSON.stringify(p, null, 2));
|
|
261
|
-
header(`
|
|
560
|
+
header(`Public procurement aggregates for ${id(snap).name || orgnr}`);
|
|
262
561
|
pretty('Tenders as buyer', `${p.tenders_as_buyer_24m || 0} (24m)`);
|
|
263
562
|
pretty('Contracts won', `${p.contracts_won_24m || 0} (24m)`);
|
|
264
563
|
pretty('Contract value', p.contract_wins_value_24m && `NOK ${p.contract_wins_value_24m.toLocaleString('nb-NO')} (24m)`);
|
|
@@ -311,13 +610,40 @@ async function shareholders(orgnr) {
|
|
|
311
610
|
const snap = await publicRequest(`/_/look/${orgnr}`);
|
|
312
611
|
const sh = snap.shareholders || {};
|
|
313
612
|
if (useJson) return console.log(JSON.stringify(sh, null, 2));
|
|
314
|
-
header(`
|
|
613
|
+
header(`Shareholder summary for ${id(snap).name || orgnr}`);
|
|
315
614
|
pretty('Fiscal year', sh.fiscal_year);
|
|
316
615
|
pretty('Shareholders', sh.count);
|
|
317
616
|
pretty('Total shares', sh.total_shares && sh.total_shares.toLocaleString('nb-NO'));
|
|
318
617
|
console.log(c('dim', '\n Full UBO chain available via authenticated /companies/:orgnr/ownership.'));
|
|
319
618
|
}
|
|
320
619
|
|
|
620
|
+
|
|
621
|
+
async function contactsSe(orgnr) {
|
|
622
|
+
if (!orgnr) die('Usage: nordic-data contacts-se <orgnr> (Swedish 10-digit organisation number)');
|
|
623
|
+
const cleaned = String(orgnr).replace(/[-\s]/g, '');
|
|
624
|
+
if (!/^\d{10}$/.test(cleaned)) die('Swedish orgnr must be 10 digits (with or without dash).');
|
|
625
|
+
const data = await publicRequest(`/companies/se/${cleaned}/contact`);
|
|
626
|
+
if (useJson) return console.log(JSON.stringify(data, null, 2));
|
|
627
|
+
header(`${data.name || 'Swedish company'} ${c('dim', `(${data.orgnr_formatted || cleaned})`)}`);
|
|
628
|
+
pretty('VAT number', data.vat_number);
|
|
629
|
+
pretty('Address', data.address);
|
|
630
|
+
const emails = data.emails || [];
|
|
631
|
+
const phones = data.phones || [];
|
|
632
|
+
if (phones.length) pretty('Phone', phones[0]);
|
|
633
|
+
if (emails.length) pretty('Email', emails[0]);
|
|
634
|
+
const nc = data.named_contacts || [];
|
|
635
|
+
if (nc.length) {
|
|
636
|
+
header('Named contacts');
|
|
637
|
+
for (const p of nc.slice(0, 10)) {
|
|
638
|
+
const extras = [p.email, p.phone].filter(Boolean).join(' · ');
|
|
639
|
+
console.log(` ${c('orange', (p.role || '').slice(0, 32).padEnd(32))} ${c('bold', p.name)}${extras ? c('dim', ` · ${extras}`) : ''}`);
|
|
640
|
+
}
|
|
641
|
+
}
|
|
642
|
+
if (data.cached) console.log(c('dim', `\n (cached, fetched ${data.fetched_at || ''})`));
|
|
643
|
+
console.log('');
|
|
644
|
+
console.log(c('dim', 'Sweden contact data uses your monthly contact-enrichment credits.'));
|
|
645
|
+
}
|
|
646
|
+
|
|
321
647
|
function mcp() {
|
|
322
648
|
console.log(`${c('bold', 'MCP setup for Claude Desktop / Cursor')}\n`);
|
|
323
649
|
console.log(c('dim', '# Add to your MCP client config (e.g. claude_desktop_config.json):'));
|
|
@@ -331,7 +657,7 @@ function mcp() {
|
|
|
331
657
|
};
|
|
332
658
|
console.log(JSON.stringify(snippet, null, 2));
|
|
333
659
|
console.log('');
|
|
334
|
-
console.log(c('dim', 'Get a free key (
|
|
660
|
+
console.log(c('dim', 'Get a free key (500 req/mo) at ') + c('cyan', 'https://nordicdata.cloud/?signup=free'));
|
|
335
661
|
console.log(c('dim', 'Listed on Smithery: ') + c('cyan', 'https://smithery.ai/servers/sofia-jameson-20/Nordic-Data'));
|
|
336
662
|
console.log(c('dim', 'Listed on mcp.so: ') + c('cyan', 'https://mcp.so/server/nordic-data'));
|
|
337
663
|
}
|
|
@@ -353,6 +679,296 @@ function fmt(n, currency) {
|
|
|
353
679
|
return `${cur} ${n.toLocaleString('nb-NO')}`;
|
|
354
680
|
}
|
|
355
681
|
|
|
682
|
+
// ── v0.4.0 — Discovery + KYB + AI/NL + US ─────────────────
|
|
683
|
+
// Helper to parse --flag value args from a sub-array.
|
|
684
|
+
function flag(arr, name, def) {
|
|
685
|
+
const i = arr.indexOf(`--${name}`);
|
|
686
|
+
if (i < 0 || i === arr.length - 1) return def;
|
|
687
|
+
return arr[i + 1];
|
|
688
|
+
}
|
|
689
|
+
function bool(arr, name) { return arr.includes(`--${name}`); }
|
|
690
|
+
|
|
691
|
+
// Find: universal lookup (resolves name / orgnr / domain / email)
|
|
692
|
+
async function find(q) {
|
|
693
|
+
if (!q) die('Usage: nordic-data find <query>\n Pass anything — name, orgnr, domain, or email.');
|
|
694
|
+
const data = await publicRequest(`/companies/lookup?q=${encodeURIComponent(q)}`);
|
|
695
|
+
if (useJson) return console.log(JSON.stringify(data, null, 2));
|
|
696
|
+
if (!data.resolved) {
|
|
697
|
+
console.log(c('dim', `No match for "${q}".`));
|
|
698
|
+
if (data.note) console.log(c('dim', ' ' + data.note));
|
|
699
|
+
return;
|
|
700
|
+
}
|
|
701
|
+
const r = data.resolved;
|
|
702
|
+
header(`${r.name || 'Match'}${r.orgnr ? c('dim', ` (${r.orgnr})`) : ''}`);
|
|
703
|
+
pretty('Resolved by', data.resolved_by);
|
|
704
|
+
pretty('Country', r.country || (r.business_address && r.business_address.country_code) || '');
|
|
705
|
+
pretty('Status', r.status || '');
|
|
706
|
+
pretty('City', (r.business_address && r.business_address.city) || r.city || '');
|
|
707
|
+
if (r.nace && r.nace[0]) pretty('NACE', `${r.nace[0].code} ${r.nace[0].description || ''}`);
|
|
708
|
+
pretty('Employees', r.employees);
|
|
709
|
+
pretty('Website', r.website);
|
|
710
|
+
if (data.alternates && data.alternates.length) {
|
|
711
|
+
console.log(c('dim', `\n ${data.alternates.length} alternate match(es):`));
|
|
712
|
+
for (const a of data.alternates.slice(0, 5)) console.log(` ${c('dim', a.orgnr || '')} ${a.name}`);
|
|
713
|
+
}
|
|
714
|
+
}
|
|
715
|
+
|
|
716
|
+
// List: filter Norwegian companies by industry × city × size
|
|
717
|
+
async function list(args) {
|
|
718
|
+
const profession = args[0];
|
|
719
|
+
if (!profession) die('Usage: nordic-data list <profession> [--city <city>] [--employees-min N] [--employees-max N] [--limit N]\n Examples:\n nordic-data list elektriker --city Stavanger --employees-min 5\n nordic-data list tannlege --city Bergen\n nordic-data list snekker --city Tromsø');
|
|
720
|
+
const params = new URLSearchParams({ profession });
|
|
721
|
+
if (flag(args, 'city')) params.set('city', flag(args, 'city'));
|
|
722
|
+
if (flag(args, 'employees-min')) params.set('min_employees', flag(args, 'employees-min'));
|
|
723
|
+
if (flag(args, 'employees-max')) params.set('max_employees', flag(args, 'employees-max'));
|
|
724
|
+
if (flag(args, 'limit')) params.set('limit', flag(args, 'limit'));
|
|
725
|
+
if (bool(args, 'active')) params.set('active', 'true');
|
|
726
|
+
const data = await publicRequest(`/companies/no/list?${params}`);
|
|
727
|
+
if (useJson) return console.log(JSON.stringify(data, null, 2));
|
|
728
|
+
header(`${data.count} ${profession}${data.filter && data.filter.city ? ` in ${data.filter.city}` : ''}`);
|
|
729
|
+
for (const r of (data.results || []).slice(0, 50)) {
|
|
730
|
+
const city = (r.business_address && r.business_address.city) || '';
|
|
731
|
+
const emp = r.employees != null ? c('dim', ` · ${r.employees} emp`) : '';
|
|
732
|
+
console.log(` ${c('orange', r.orgnr)} ${r.name}${city ? c('dim', ` · ${city}`) : ''}${emp}`);
|
|
733
|
+
}
|
|
734
|
+
}
|
|
735
|
+
|
|
736
|
+
// NL: natural-language discovery
|
|
737
|
+
async function nl(query) {
|
|
738
|
+
if (!query) die('Usage: nordic-data nl "<natural language query>"\n Examples:\n nordic-data nl "electricians in Stavanger with 5+ employees"\n nordic-data nl "dentists in Bergen"');
|
|
739
|
+
const data = await publicRequest(`/companies/nl-search?q=${encodeURIComponent(query)}`);
|
|
740
|
+
if (useJson) return console.log(JSON.stringify(data, null, 2));
|
|
741
|
+
if (data.filter_spec) {
|
|
742
|
+
console.log(c('dim', `Interpreted as: ${JSON.stringify(data.filter_spec)}`));
|
|
743
|
+
}
|
|
744
|
+
header(`${data.count} match(es)`);
|
|
745
|
+
for (const r of (data.results || []).slice(0, 50)) {
|
|
746
|
+
const city = (r.business_address && r.business_address.city) || '';
|
|
747
|
+
const emp = r.employees != null ? c('dim', ` · ${r.employees} emp`) : '';
|
|
748
|
+
console.log(` ${c('orange', r.orgnr)} ${r.name}${city ? c('dim', ` · ${city}`) : ''}${emp}`);
|
|
749
|
+
}
|
|
750
|
+
}
|
|
751
|
+
|
|
752
|
+
// KYB: one-call due-diligence bundle
|
|
753
|
+
async function kyb(orgnr) {
|
|
754
|
+
if (!orgnr) die('Usage: nordic-data kyb <orgnr>\n Returns: identity + officers + sanctions on company AND officers + sister companies + recent changes + shell-likelihood.');
|
|
755
|
+
const data = await publicRequest(`/companies/${orgnr}/kyb-pack`);
|
|
756
|
+
if (useJson) return console.log(JSON.stringify(data, null, 2));
|
|
757
|
+
const i = data.identity || {};
|
|
758
|
+
header(`KYB pack — ${i.name || orgnr}`);
|
|
759
|
+
pretty('Orgnr', data.orgnr);
|
|
760
|
+
pretty('Status', i.status);
|
|
761
|
+
pretty('Employees', i.employees);
|
|
762
|
+
pretty('Generated', data.generated_at);
|
|
763
|
+
if (data.officers && data.officers.all) {
|
|
764
|
+
console.log(c('bold', `\n Officers (${data.officers.all.length}):`));
|
|
765
|
+
for (const o of data.officers.all.slice(0, 10)) {
|
|
766
|
+
console.log(` ${c('orange', (o.role || '').padEnd(20))} ${c('bold', (o.person && o.person.name) || o.name || '')}`);
|
|
767
|
+
}
|
|
768
|
+
}
|
|
769
|
+
const sa = data.sanctions || {};
|
|
770
|
+
console.log(c('bold', '\n Sanctions:'));
|
|
771
|
+
console.log(` Company matches: ${(sa.company_matches || []).length}`);
|
|
772
|
+
const om = (sa.officer_matches || []).filter(x => (x.matches || []).length).length;
|
|
773
|
+
console.log(` Officers with hits: ${om}`);
|
|
774
|
+
if (data.sister_companies && data.sister_companies.length) pretty('Sister cos', data.sister_companies.length);
|
|
775
|
+
if (data.recent_changes && data.recent_changes.length) pretty('Recent changes', data.recent_changes.length);
|
|
776
|
+
const sh = data.shell_signals || {};
|
|
777
|
+
const shell = [sh.no_employees && 'no employees', sh.fresh && 'fresh<1y', sh.no_website && 'no website'].filter(Boolean).join(', ');
|
|
778
|
+
if (shell) pretty('Shell signals', c('yellow', shell));
|
|
779
|
+
}
|
|
780
|
+
|
|
781
|
+
// Explain: AI 3-sentence narrative
|
|
782
|
+
async function explain(orgnr) {
|
|
783
|
+
if (!orgnr) die('Usage: nordic-data explain <orgnr>');
|
|
784
|
+
const data = await publicRequest(`/companies/${orgnr}/explain`);
|
|
785
|
+
if (useJson) return console.log(JSON.stringify(data, null, 2));
|
|
786
|
+
header(`Narrative for ${orgnr}`);
|
|
787
|
+
console.log(` ${data.narrative || c('dim', '(no narrative)')}`);
|
|
788
|
+
}
|
|
789
|
+
|
|
790
|
+
// Ask: Q&A about a company
|
|
791
|
+
async function ask(args) {
|
|
792
|
+
const orgnr = args[0];
|
|
793
|
+
const question = args.slice(1).join(' ');
|
|
794
|
+
if (!orgnr || !question) die('Usage: nordic-data ask <orgnr> <question>\n Example: nordic-data ask 923609016 "who chairs the board"');
|
|
795
|
+
const data = await publicRequest(`/companies/${orgnr}/q?question=${encodeURIComponent(question)}`);
|
|
796
|
+
if (useJson) return console.log(JSON.stringify(data, null, 2));
|
|
797
|
+
header(`Q: ${question}`);
|
|
798
|
+
console.log(` ${data.answer || c('dim', '(no answer)')}`);
|
|
799
|
+
}
|
|
800
|
+
|
|
801
|
+
// Pitch: sales talking points
|
|
802
|
+
async function pitch(orgnr) {
|
|
803
|
+
if (!orgnr) die('Usage: nordic-data pitch <orgnr>\n Returns 4-5 sales talking points referencing concrete facts.');
|
|
804
|
+
const data = await publicRequest(`/companies/${orgnr}/talking-points`);
|
|
805
|
+
if (useJson) return console.log(JSON.stringify(data, null, 2));
|
|
806
|
+
header(`Sales talking points for ${orgnr}`);
|
|
807
|
+
console.log(data.talking_points || c('dim', '(none)'));
|
|
808
|
+
}
|
|
809
|
+
|
|
810
|
+
// Verify invoice
|
|
811
|
+
async function verifyInvoice(args) {
|
|
812
|
+
const orgnr = args[0];
|
|
813
|
+
const iban = args[1];
|
|
814
|
+
if (!orgnr || !iban) die('Usage: nordic-data verify-invoice <orgnr> <iban>\n Flags fraud risk if IBAN country mismatches the company’s registered country.');
|
|
815
|
+
const data = await publicRequest(`/verify/invoice?orgnr=${encodeURIComponent(orgnr)}&iban=${encodeURIComponent(iban)}`);
|
|
816
|
+
if (useJson) return console.log(JSON.stringify(data, null, 2));
|
|
817
|
+
header(`Invoice check — orgnr ${orgnr} → IBAN ${iban}`);
|
|
818
|
+
const risk = data.risk === 'high' ? c('red', 'HIGH') : data.risk === 'low' ? c('green', 'LOW') : data.risk;
|
|
819
|
+
pretty('Risk', risk);
|
|
820
|
+
pretty('IBAN country', data.iban_country);
|
|
821
|
+
pretty('Company country', data.company_country);
|
|
822
|
+
console.log('\n ' + c('dim', data.reason || ''));
|
|
823
|
+
}
|
|
824
|
+
|
|
825
|
+
// Universal /resolve/* — type one of: email, domain, phone, iban, vat, lei, address, person, wikipedia
|
|
826
|
+
async function resolve(args) {
|
|
827
|
+
const type = args[0];
|
|
828
|
+
const value = args[1];
|
|
829
|
+
const validTypes = ['email','domain','phone','iban','vat','lei','address','person','wikipedia'];
|
|
830
|
+
if (!type || !validTypes.includes(type)) die(`Usage: nordic-data resolve <type> <value>\n Type: ${validTypes.join(' | ')}\n Examples:\n nordic-data resolve email ceo@equinor.com\n nordic-data resolve domain cognite.com\n nordic-data resolve phone "+47 519 90 000"\n nordic-data resolve iban DE89370400440532013000`);
|
|
831
|
+
if (!value) die(`Usage: nordic-data resolve ${type} <value>`);
|
|
832
|
+
const param = type === 'email' ? 'address' : type === 'iban' ? 'iban' : type === 'vat' ? 'vat' : type === 'lei' ? 'lei' : type === 'wikipedia' ? 'title' : type === 'address' ? 'street' : type === 'person' ? 'name' : type === 'phone' ? 'number' : 'domain';
|
|
833
|
+
const data = await publicRequest(`/resolve/${type}?${param}=${encodeURIComponent(value)}`);
|
|
834
|
+
if (useJson) return console.log(JSON.stringify(data, null, 2));
|
|
835
|
+
console.log(JSON.stringify(data, null, 2));
|
|
836
|
+
}
|
|
837
|
+
|
|
838
|
+
// Near: geo-radius search
|
|
839
|
+
async function near(args) {
|
|
840
|
+
const lat = args[0];
|
|
841
|
+
const lng = args[1];
|
|
842
|
+
if (!lat || !lng) die('Usage: nordic-data near <lat> <lng> [--radius 5] [--profession <profession>] [--limit N]');
|
|
843
|
+
const params = new URLSearchParams({ lat, lng });
|
|
844
|
+
params.set('radius_km', flag(args, 'radius', '5'));
|
|
845
|
+
if (flag(args, 'profession')) params.set('profession', flag(args, 'profession'));
|
|
846
|
+
if (flag(args, 'limit')) params.set('limit', flag(args, 'limit'));
|
|
847
|
+
const data = await publicRequest(`/companies/near?${params}`);
|
|
848
|
+
if (useJson) return console.log(JSON.stringify(data, null, 2));
|
|
849
|
+
header(`${data.count} companies within ${params.get('radius_km')} km of ${lat},${lng}`);
|
|
850
|
+
for (const r of (data.results || []).slice(0, 50)) {
|
|
851
|
+
console.log(` ${c('orange', r.orgnr)} ${r.name}${c('dim', ` · ${r.distance_km} km`)}`);
|
|
852
|
+
}
|
|
853
|
+
}
|
|
854
|
+
|
|
855
|
+
// Similar companies
|
|
856
|
+
async function similar(orgnr) {
|
|
857
|
+
if (!orgnr) die('Usage: nordic-data similar <orgnr>\n Returns 20 lookalike companies based on industry + geo + size.');
|
|
858
|
+
const data = await publicRequest(`/companies/similar/${orgnr}?limit=20`);
|
|
859
|
+
if (useJson) return console.log(JSON.stringify(data, null, 2));
|
|
860
|
+
header(`Similar to ${orgnr}`);
|
|
861
|
+
for (const r of (data.similar || []).slice(0, 30)) {
|
|
862
|
+
console.log(` ${c('orange', r.orgnr)} ${r.name}${r.city ? c('dim', ` · ${r.city}`) : ''}${r.employees ? c('dim', ` · ${r.employees} emp`) : ''}`);
|
|
863
|
+
}
|
|
864
|
+
}
|
|
865
|
+
|
|
866
|
+
// Decision makers
|
|
867
|
+
async function decisionMakers(orgnr) {
|
|
868
|
+
if (!orgnr) die('Usage: nordic-data decision-makers <orgnr>');
|
|
869
|
+
const data = await publicRequest(`/companies/${orgnr}/decision-makers`);
|
|
870
|
+
if (useJson) return console.log(JSON.stringify(data, null, 2));
|
|
871
|
+
header(`Decision-makers for ${orgnr}`);
|
|
872
|
+
for (const o of (data.key_officers || [])) {
|
|
873
|
+
console.log(` ${c('orange', (o.role || '').padEnd(24))} ${c('bold', (o.person && o.person.name) || '')}`);
|
|
874
|
+
}
|
|
875
|
+
if ((data.named_contacts || []).length) {
|
|
876
|
+
console.log(c('dim', '\n Named contacts:'));
|
|
877
|
+
for (const n of data.named_contacts) {
|
|
878
|
+
console.log(` ${c('orange', n.role)} ${c('bold', n.name)}${n.email ? c('dim', ` · ${n.email}`) : ''}`);
|
|
879
|
+
}
|
|
880
|
+
}
|
|
881
|
+
}
|
|
882
|
+
|
|
883
|
+
// Trades vertical shortcut
|
|
884
|
+
async function trades(args) {
|
|
885
|
+
const slug = args[0];
|
|
886
|
+
if (!slug) die('Usage: nordic-data trades <slug> [--city <city>]\n Examples: nordic-data trades elektriker --city Oslo\n nordic-data trades rorlegger --city Stavanger\n nordic-data trades snekker --city Tromsø');
|
|
887
|
+
const params = new URLSearchParams();
|
|
888
|
+
if (flag(args, 'city')) params.set('city', flag(args, 'city'));
|
|
889
|
+
if (flag(args, 'limit')) params.set('limit', flag(args, 'limit'));
|
|
890
|
+
const data = await publicRequest(`/trades/${slug}/directory?${params}`);
|
|
891
|
+
if (useJson) return console.log(JSON.stringify(data, null, 2));
|
|
892
|
+
header(`${data.count} ${slug}${flag(args, 'city') ? ` in ${flag(args, 'city')}` : ''}`);
|
|
893
|
+
for (const r of (data.results || []).slice(0, 50)) {
|
|
894
|
+
const city = (r.business_address && r.business_address.city) || '';
|
|
895
|
+
console.log(` ${c('orange', r.orgnr)} ${r.name}${city ? c('dim', ` · ${city}`) : ''}`);
|
|
896
|
+
}
|
|
897
|
+
}
|
|
898
|
+
|
|
899
|
+
// Diff: typed changelog
|
|
900
|
+
async function diff(args) {
|
|
901
|
+
const orgnr = args[0];
|
|
902
|
+
if (!orgnr) die('Usage: nordic-data diff <orgnr> [--since YYYY-MM-DD]');
|
|
903
|
+
const params = new URLSearchParams();
|
|
904
|
+
if (flag(args, 'since')) params.set('since', flag(args, 'since'));
|
|
905
|
+
const data = await publicRequest(`/companies/${orgnr}/diff?${params}`);
|
|
906
|
+
if (useJson) return console.log(JSON.stringify(data, null, 2));
|
|
907
|
+
header(`Changes for ${orgnr} since ${data.since}`);
|
|
908
|
+
for (const ch of (data.changes || []).slice(0, 50)) {
|
|
909
|
+
console.log(` ${c('orange', ch.change_date || '')} ${ch.change_type || ''}`);
|
|
910
|
+
}
|
|
911
|
+
if (!data.changes || !data.changes.length) console.log(c('dim', ' (no changes)'));
|
|
912
|
+
}
|
|
913
|
+
|
|
914
|
+
// US: search public companies
|
|
915
|
+
async function usSearch(query) {
|
|
916
|
+
if (!query) die('Usage: nordic-data us-search <query>');
|
|
917
|
+
const data = await publicRequest(`/companies/us/search?q=${encodeURIComponent(query)}`);
|
|
918
|
+
if (useJson) return console.log(JSON.stringify(data, null, 2));
|
|
919
|
+
header(`${data.count} US public company matches for "${query}"`);
|
|
920
|
+
for (const r of (data.results || []).slice(0, 25)) {
|
|
921
|
+
console.log(` ${c('orange', r.cik)} ${r.name}${r.ticker ? c('dim', ` · ${r.ticker}`) : ''}`);
|
|
922
|
+
}
|
|
923
|
+
}
|
|
924
|
+
|
|
925
|
+
async function usLookup(query) {
|
|
926
|
+
if (!query) die('Usage: nordic-data us-lookup <query>\n Resolves a US public company by CIK, ticker, or name.');
|
|
927
|
+
const data = await publicRequest(`/companies/us/lookup?q=${encodeURIComponent(query)}`);
|
|
928
|
+
if (useJson) return console.log(JSON.stringify(data, null, 2));
|
|
929
|
+
if (!data.resolved) return console.log(c('dim', data.note || 'No match.'));
|
|
930
|
+
const r = data.resolved;
|
|
931
|
+
header(`${r.name} ${c('dim', `(CIK ${r.cik}${r.tickers && r.tickers[0] ? ' · ' + r.tickers[0] : ''})`)}`);
|
|
932
|
+
pretty('SIC', `${r.sic || ''} ${r.sic_description || ''}`.trim());
|
|
933
|
+
pretty('State', r.state);
|
|
934
|
+
pretty('City', r.city);
|
|
935
|
+
pretty('Address', r.address);
|
|
936
|
+
}
|
|
937
|
+
|
|
938
|
+
async function usFilings(args) {
|
|
939
|
+
const cik = args[0];
|
|
940
|
+
if (!cik) die('Usage: nordic-data us-filings <cik> [--type 10-K]');
|
|
941
|
+
const params = new URLSearchParams();
|
|
942
|
+
if (flag(args, 'type')) params.set('type', flag(args, 'type'));
|
|
943
|
+
const data = await publicRequest(`/companies/us/${cik}/filings?${params}`);
|
|
944
|
+
if (useJson) return console.log(JSON.stringify(data, null, 2));
|
|
945
|
+
header(`${data.company} — ${data.count} filing(s)`);
|
|
946
|
+
for (const f of (data.filings || []).slice(0, 25)) {
|
|
947
|
+
console.log(` ${c('orange', f.date.padEnd(11))} ${c('bold', f.form.padEnd(8))} ${c('dim', f.url)}`);
|
|
948
|
+
}
|
|
949
|
+
}
|
|
950
|
+
|
|
951
|
+
async function usKyb(cik) {
|
|
952
|
+
if (!cik) die('Usage: nordic-data us-kyb <cik>');
|
|
953
|
+
const data = await publicRequest(`/companies/us/${cik}/kyb-pack`);
|
|
954
|
+
if (useJson) return console.log(JSON.stringify(data, null, 2));
|
|
955
|
+
const i = data.identity || {};
|
|
956
|
+
header(`US KYB pack — ${i.name || cik}`);
|
|
957
|
+
pretty('CIK', i.cik);
|
|
958
|
+
pretty('SIC', `${i.sic || ''} ${i.sic_description || ''}`.trim());
|
|
959
|
+
pretty('State', i.state);
|
|
960
|
+
pretty('City', i.city);
|
|
961
|
+
if (data.recent_filings && data.recent_filings.length) {
|
|
962
|
+
console.log(c('bold', '\n Recent filings:'));
|
|
963
|
+
for (const f of data.recent_filings) console.log(` ${c('orange', f.date)} ${f.form}`);
|
|
964
|
+
}
|
|
965
|
+
const sa = data.sanctions || {};
|
|
966
|
+
pretty('Sanctions matches', (sa.company_matches || []).length);
|
|
967
|
+
if (data.recent_federal_contracts && data.recent_federal_contracts.length) {
|
|
968
|
+
pretty('Recent federal contracts', data.recent_federal_contracts.length);
|
|
969
|
+
}
|
|
970
|
+
}
|
|
971
|
+
|
|
356
972
|
// ── Dispatch ───────────────────────────────────────────────
|
|
357
973
|
|
|
358
974
|
(async () => {
|
|
@@ -367,6 +983,28 @@ function fmt(n, currency) {
|
|
|
367
983
|
case 'grants': await grants(rest[0]); break;
|
|
368
984
|
case 'sanctions': await sanctions(rest[0]); break;
|
|
369
985
|
case 'shareholders': await shareholders(rest[0]); break;
|
|
986
|
+
case 'contacts-se': await contactsSe(rest[0]); break;
|
|
987
|
+
// v0.4.0 — Discovery, KYB, AI/NL, US
|
|
988
|
+
case 'find': await find(rest.join(' ')); break;
|
|
989
|
+
case 'list': await list(rest); break;
|
|
990
|
+
case 'nl': await nl(rest.join(' ')); break;
|
|
991
|
+
case 'kyb': await kyb(rest[0]); break;
|
|
992
|
+
case 'explain': await explain(rest[0]); break;
|
|
993
|
+
case 'ask': await ask(rest); break;
|
|
994
|
+
case 'pitch': await pitch(rest[0]); break;
|
|
995
|
+
case 'talking-points': await pitch(rest[0]); break;
|
|
996
|
+
case 'verify-invoice': await verifyInvoice(rest); break;
|
|
997
|
+
case 'resolve': await resolve(rest); break;
|
|
998
|
+
case 'near': await near(rest); break;
|
|
999
|
+
case 'similar': await similar(rest[0]); break;
|
|
1000
|
+
case 'decision-makers': await decisionMakers(rest[0]); break;
|
|
1001
|
+
case 'trades': await trades(rest); break;
|
|
1002
|
+
case 'diff': await diff(rest); break;
|
|
1003
|
+
case 'us-search': await usSearch(rest.join(' ')); break;
|
|
1004
|
+
case 'us-lookup': await usLookup(rest.join(' ')); break;
|
|
1005
|
+
case 'us-filings': await usFilings(rest); break;
|
|
1006
|
+
case 'us-kyb': await usKyb(rest[0]); break;
|
|
1007
|
+
// Meta
|
|
370
1008
|
case 'mcp': mcp(); break;
|
|
371
1009
|
case 'signup': signup(); break;
|
|
372
1010
|
default:
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "nordic-data",
|
|
3
|
-
"version": "0.
|
|
4
|
-
"description": "CLI for Nordic Data
|
|
3
|
+
"version": "0.4.0",
|
|
4
|
+
"description": "CLI for Nordic Data \u2014 Norwegian, Swedish, and US business intelligence in one terminal. Discovery (filter by industry \u00d7 city \u00d7 size, natural-language search, geo-radius, similar-companies, trades verticals), universal resolvers (email / domain / phone / IBAN / VAT / LEI \u2192 company), KYB / compliance (one-call due-diligence pack, typed changelog, invoice-fraud heuristic), AI / NL (explain, Q&A, sales talking-points, risk narrative), United States coverage (SEC EDGAR + USA Spending federal contracts), plus the original Nordic core: identity, accounts, officers, sanctions, public procurement, R&D grants, shareholders, AI-enriched contacts.",
|
|
5
5
|
"bin": {
|
|
6
6
|
"nordic-data": "./bin/cli.js"
|
|
7
7
|
},
|
|
@@ -13,17 +13,34 @@
|
|
|
13
13
|
"keywords": [
|
|
14
14
|
"norway",
|
|
15
15
|
"norwegian",
|
|
16
|
+
"sweden",
|
|
17
|
+
"swedish",
|
|
18
|
+
"nordic",
|
|
19
|
+
"united-states",
|
|
20
|
+
"us",
|
|
21
|
+
"sec-edgar",
|
|
16
22
|
"company",
|
|
17
23
|
"data",
|
|
18
|
-
"
|
|
19
|
-
"bronnoysund",
|
|
20
|
-
"aksjonaerregisteret",
|
|
21
|
-
"doffin",
|
|
24
|
+
"cli",
|
|
22
25
|
"kyb",
|
|
26
|
+
"kyc",
|
|
23
27
|
"aml",
|
|
24
28
|
"sanctions",
|
|
25
|
-
"
|
|
29
|
+
"ofac",
|
|
30
|
+
"procurement",
|
|
31
|
+
"tenders",
|
|
32
|
+
"lookup",
|
|
33
|
+
"resolver",
|
|
34
|
+
"enrichment",
|
|
35
|
+
"discovery",
|
|
36
|
+
"natural-language",
|
|
26
37
|
"mcp",
|
|
38
|
+
"model-context-protocol",
|
|
39
|
+
"agent",
|
|
40
|
+
"ai",
|
|
41
|
+
"fraud",
|
|
42
|
+
"invoice-fraud",
|
|
43
|
+
"due-diligence",
|
|
27
44
|
"nordic-data"
|
|
28
45
|
],
|
|
29
46
|
"homepage": "https://nordicdata.cloud",
|