builtwith-official-cli 1.5.2 → 1.5.5
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 +18 -1
- package/lib/cli.js +1 -0
- package/lib/client.js +1 -0
- package/lib/commands/change.js +35 -0
- package/lib/commands/lists.js +52 -2
- package/lib/commands/mcp.js +42 -2
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -5,6 +5,7 @@
|
|
|
5
5
|
```bash
|
|
6
6
|
bw domain lookup shopify.com --format table
|
|
7
7
|
bw domain lookup shopify.com --nopii | jq '.Results[0].Technologies[].Name'
|
|
8
|
+
bw change lookup shopify.com --since "last month"
|
|
8
9
|
bw live feed --duration 60 > events.ndjson
|
|
9
10
|
bw mcp # start MCP server for Claude Desktop, VS Code, etc.
|
|
10
11
|
```
|
|
@@ -86,17 +87,32 @@ bw domain lookup shopify.com --nopii --liveonly | jq '.Results[0].Technologies[]
|
|
|
86
87
|
bw domain lookup shopify.com --fdrange 20240101-20241231
|
|
87
88
|
```
|
|
88
89
|
|
|
90
|
+
### 🔄 Change
|
|
91
|
+
|
|
92
|
+
```bash
|
|
93
|
+
bw change lookup <domain[,domain2]> [--since <date>]
|
|
94
|
+
```
|
|
95
|
+
|
|
96
|
+
```bash
|
|
97
|
+
bw change lookup shopify.com
|
|
98
|
+
bw change lookup shopify.com,builtwith.com --since "last month"
|
|
99
|
+
```
|
|
100
|
+
|
|
89
101
|
### 📋 Lists
|
|
90
102
|
|
|
91
103
|
```bash
|
|
92
|
-
bw lists tech <tech> [--offset <n>] [--limit <n>]
|
|
104
|
+
bw lists tech <tech> [--other-techs <names>] [--country <codes>] [--since <date>] [--revenue <filter>] [--spend <filter>] [--offset <n>] [--limit <n>]
|
|
93
105
|
```
|
|
94
106
|
|
|
95
107
|
```bash
|
|
96
108
|
bw lists tech WordPress
|
|
97
109
|
bw lists tech Shopify --limit 50 --offset 100
|
|
110
|
+
bw lists tech Google-Analytics --other-techs Meta-Pixel
|
|
111
|
+
bw lists tech Shopify --revenue "100000|GT" --spend "100|GTE" --country US
|
|
98
112
|
```
|
|
99
113
|
|
|
114
|
+
Lists numeric filters use `number|operator`, where operator is `EQ`, `LT`, `LTE`, `GT`, or `GTE`. Supported attribute filters include `--spend`, `--revenue`, `--sku`, `--followers`, `--employees`, `--sitemap`, `--page-rank`, `--bw-rank`, `--tranco`, `--majestic`, `--bws`, `--ecat`, `--aim`, `--aio`, `--air`, and `--aiv`.
|
|
115
|
+
|
|
100
116
|
### 🔗 Relationships
|
|
101
117
|
|
|
102
118
|
```bash
|
|
@@ -387,6 +403,7 @@ If your API key isn't in an env var or `.builtwithrc`, pass it inline:
|
|
|
387
403
|
| Tool | Description |
|
|
388
404
|
|---|---|
|
|
389
405
|
| `domain_lookup` | 🌐 Technology stack for a domain (supports `nopii`, `liveonly`, date ranges) |
|
|
406
|
+
| `change_lookup` | 🔄 Technology additions and removals for one or more domains |
|
|
390
407
|
| `lists_tech` | 📋 Domains currently using a technology |
|
|
391
408
|
| `relationships_lookup` | 🔗 Related domains (shared infra, ownership) |
|
|
392
409
|
| `free_lookup` | 🆓 Free-tier category counts for a domain |
|
package/lib/cli.js
CHANGED
|
@@ -24,6 +24,7 @@ function run() {
|
|
|
24
24
|
|
|
25
25
|
// Register all command modules
|
|
26
26
|
require('./commands/domain')(program);
|
|
27
|
+
require('./commands/change')(program);
|
|
27
28
|
require('./commands/lists')(program);
|
|
28
29
|
require('./commands/relationships')(program);
|
|
29
30
|
require('./commands/free')(program);
|
package/lib/client.js
CHANGED
|
@@ -25,6 +25,7 @@ function scanForInjection(data) {
|
|
|
25
25
|
|
|
26
26
|
const BASE_URLS = {
|
|
27
27
|
domain: 'https://api.builtwith.com/v22/api.json',
|
|
28
|
+
change: 'https://api.builtwith.com/change1/api.json',
|
|
28
29
|
lists: 'https://api.builtwith.com/lists12/api.json',
|
|
29
30
|
relationships: 'https://api.builtwith.com/rv4/api.json',
|
|
30
31
|
free: 'https://api.builtwith.com/free1/api.json',
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const { requireKey } = require('../config');
|
|
4
|
+
const { request } = require('../client');
|
|
5
|
+
const { validateInput } = require('../validate');
|
|
6
|
+
const output = require('../output');
|
|
7
|
+
const ora = require('ora');
|
|
8
|
+
|
|
9
|
+
module.exports = function registerChange(program) {
|
|
10
|
+
const change = program.command('change').description('Technology additions and removals');
|
|
11
|
+
|
|
12
|
+
change
|
|
13
|
+
.command('lookup <domains>')
|
|
14
|
+
.description('Get technology additions and removals for one or more comma-separated domains')
|
|
15
|
+
.option('--since <date>', 'Natural language date window (default: 3 months, e.g. "last month")')
|
|
16
|
+
.action(async (domainsArg, cmdOpts) => {
|
|
17
|
+
const opts = program.opts();
|
|
18
|
+
if (opts.noColor) output.setNoColor(true);
|
|
19
|
+
const key = requireKey(opts.key);
|
|
20
|
+
validateInput(domainsArg, 'domains');
|
|
21
|
+
if (cmdOpts.since) validateInput(cmdOpts.since, 'since');
|
|
22
|
+
|
|
23
|
+
const params = { KEY: key, LOOKUP: domainsArg };
|
|
24
|
+
if (cmdOpts.since) params.SINCE = cmdOpts.since;
|
|
25
|
+
|
|
26
|
+
const spinner = opts.quiet ? null : ora({ text: `Fetching changes for ${domainsArg}...`, stream: process.stderr }).start();
|
|
27
|
+
try {
|
|
28
|
+
const data = await request('change', params, { dryRun: opts.dryRun, debug: opts.debug, spinner });
|
|
29
|
+
output.print(data, { format: opts.format, fields: opts.fields });
|
|
30
|
+
} catch (err) {
|
|
31
|
+
if (spinner) spinner.stop();
|
|
32
|
+
throw err;
|
|
33
|
+
}
|
|
34
|
+
});
|
|
35
|
+
};
|
package/lib/commands/lists.js
CHANGED
|
@@ -7,15 +7,45 @@ const { validateInput, validatePosInt } = require('../validate');
|
|
|
7
7
|
const output = require('../output');
|
|
8
8
|
const ora = require('ora');
|
|
9
9
|
|
|
10
|
+
const ATTRIBUTE_FILTERS = [
|
|
11
|
+
['spend', 'SPEND', 'Monthly technology spend filter, e.g. 100|GT'],
|
|
12
|
+
['revenue', 'REVENUE', 'Estimated ecommerce sales revenue filter, e.g. 100000|GT'],
|
|
13
|
+
['sku', 'SKU', 'Product count filter, e.g. 1000|GTE'],
|
|
14
|
+
['followers', 'FOLLOWERS', 'Followers filter, e.g. 5000|GTE'],
|
|
15
|
+
['employees', 'EMPLOYEES', 'Employee count filter, e.g. 50|GTE'],
|
|
16
|
+
['sitemap', 'SITEMAP', 'Sitemap URL count filter, e.g. 100|GT'],
|
|
17
|
+
['pageRank', 'PAGERANK', 'Page rank filter, e.g. 1000000|LT'],
|
|
18
|
+
['bwRank', 'BWRANK', 'BuiltWith rank filter, e.g. 50000|LTE'],
|
|
19
|
+
['tranco', 'TRANCO', 'Tranco rank filter, e.g. 100000|LTE'],
|
|
20
|
+
['majestic', 'MAJESTIC', 'Majestic rank filter, e.g. 100000|LTE'],
|
|
21
|
+
['bws', 'BWS', 'BuiltWith score filter, e.g. 50|GTE'],
|
|
22
|
+
['ecat', 'ECAT', 'Ecommerce category id filter, e.g. 123|EQ'],
|
|
23
|
+
['aim', 'AIM', 'AI maturity filter, e.g. 50|GTE'],
|
|
24
|
+
['aio', 'AIO', 'AI openness filter, e.g. 50|GTE'],
|
|
25
|
+
['air', 'AIR', 'AI readiness filter, e.g. 50|GTE'],
|
|
26
|
+
['aiv', 'AIV', 'AI visibility filter, e.g. 50|GTE'],
|
|
27
|
+
];
|
|
28
|
+
|
|
10
29
|
module.exports = function registerLists(program) {
|
|
11
30
|
const lists = program.command('lists').description('Technology lists');
|
|
12
31
|
|
|
13
|
-
lists
|
|
32
|
+
const techCommand = lists
|
|
14
33
|
.command('tech <tech>')
|
|
15
34
|
.description('Get list of sites using a technology')
|
|
35
|
+
.option('--other-techs <names>', 'Comma-separated additional required technologies')
|
|
36
|
+
.option('--country <codes>', 'Comma-separated country filters, e.g. US,CA')
|
|
37
|
+
.option('--since <date>', 'Date or relative time filter, e.g. "30 days ago"')
|
|
16
38
|
.option('--offset <n>', 'Result offset', '0')
|
|
17
39
|
.option('--limit <n>', 'Max results', '20')
|
|
18
|
-
.
|
|
40
|
+
.option('--meta', 'Include metadata')
|
|
41
|
+
.option('--all', 'Include historical sites');
|
|
42
|
+
|
|
43
|
+
for (const [, apiName, description] of ATTRIBUTE_FILTERS) {
|
|
44
|
+
const optionName = apiName.toLowerCase().replace('pagerank', 'page-rank').replace('bwrank', 'bw-rank');
|
|
45
|
+
techCommand.option(`--${optionName} <filter>`, description);
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
techCommand.action(async (tech, cmdOpts) => {
|
|
19
49
|
const opts = program.opts();
|
|
20
50
|
if (opts.noColor) output.setNoColor(true);
|
|
21
51
|
const key = requireKey(opts.key);
|
|
@@ -23,6 +53,26 @@ module.exports = function registerLists(program) {
|
|
|
23
53
|
const offset = validatePosInt(cmdOpts.offset, 'offset');
|
|
24
54
|
const limit = validatePosInt(cmdOpts.limit, 'limit');
|
|
25
55
|
const params = { KEY: key, TECH: tech, OFFSET: offset, LIMIT: limit };
|
|
56
|
+
if (cmdOpts.otherTechs) {
|
|
57
|
+
validateInput(cmdOpts.otherTechs, 'otherTechs');
|
|
58
|
+
params.OTHERTECHS = cmdOpts.otherTechs;
|
|
59
|
+
}
|
|
60
|
+
if (cmdOpts.country) {
|
|
61
|
+
validateInput(cmdOpts.country, 'country');
|
|
62
|
+
params.COUNTRY = cmdOpts.country;
|
|
63
|
+
}
|
|
64
|
+
if (cmdOpts.since) {
|
|
65
|
+
validateInput(cmdOpts.since, 'since');
|
|
66
|
+
params.SINCE = cmdOpts.since;
|
|
67
|
+
}
|
|
68
|
+
if (cmdOpts.meta) params.META = 'yes';
|
|
69
|
+
if (cmdOpts.all) params.ALL = 'yes';
|
|
70
|
+
for (const [optionKey, apiName] of ATTRIBUTE_FILTERS) {
|
|
71
|
+
if (cmdOpts[optionKey]) {
|
|
72
|
+
validateInput(cmdOpts[optionKey], optionKey);
|
|
73
|
+
params[apiName] = cmdOpts[optionKey];
|
|
74
|
+
}
|
|
75
|
+
}
|
|
26
76
|
|
|
27
77
|
if (opts.params) {
|
|
28
78
|
let extra;
|
package/lib/commands/mcp.js
CHANGED
|
@@ -32,6 +32,18 @@ const TOOLS = [
|
|
|
32
32
|
required: ['domain'],
|
|
33
33
|
},
|
|
34
34
|
},
|
|
35
|
+
{
|
|
36
|
+
name: 'change_lookup',
|
|
37
|
+
description: 'Get technology additions and removals for one or more domains using the BuiltWith Change API.',
|
|
38
|
+
inputSchema: {
|
|
39
|
+
type: 'object',
|
|
40
|
+
properties: {
|
|
41
|
+
domains: { type: 'string', description: 'Domain or comma-separated domains to look up' },
|
|
42
|
+
since: { type: 'string', description: 'Natural language date window, e.g. "last month"' },
|
|
43
|
+
},
|
|
44
|
+
required: ['domains'],
|
|
45
|
+
},
|
|
46
|
+
},
|
|
35
47
|
{
|
|
36
48
|
name: 'lists_tech',
|
|
37
49
|
description: 'Get a list of domains currently using a specific technology.',
|
|
@@ -39,6 +51,17 @@ const TOOLS = [
|
|
|
39
51
|
type: 'object',
|
|
40
52
|
properties: {
|
|
41
53
|
tech: { type: 'string', description: 'Technology name (e.g. "WordPress", "Shopify")' },
|
|
54
|
+
otherTechs: { type: 'string', description: 'Comma-separated additional required technologies, max 16' },
|
|
55
|
+
country: { type: 'string', description: 'Comma-separated country filters, e.g. US,CA' },
|
|
56
|
+
since: { type: 'string', description: 'Date or relative time filter, e.g. "30 days ago"' },
|
|
57
|
+
spend: { type: 'string', description: 'Monthly technology spend filter, e.g. 100|GT' },
|
|
58
|
+
revenue: { type: 'string', description: 'Estimated ecommerce sales revenue filter, e.g. 100000|GT' },
|
|
59
|
+
employees: { type: 'string', description: 'Employee count filter, e.g. 50|GTE' },
|
|
60
|
+
filters: {
|
|
61
|
+
type: 'object',
|
|
62
|
+
description: 'Additional Lists API attribute filters keyed by API parameter name, e.g. {"SKU":"1000|GTE","AIM":"50|GTE"}',
|
|
63
|
+
additionalProperties: { type: 'string' },
|
|
64
|
+
},
|
|
42
65
|
offset: { type: 'number', description: 'Result offset for pagination (default 0)' },
|
|
43
66
|
limit: { type: 'number', description: 'Number of results (default 20)' },
|
|
44
67
|
},
|
|
@@ -191,8 +214,25 @@ async function callTool(name, args, key, debug) {
|
|
|
191
214
|
if (args.ldrange) params.LDRANGE = args.ldrange;
|
|
192
215
|
return request('domain', params, opts);
|
|
193
216
|
}
|
|
194
|
-
case '
|
|
195
|
-
|
|
217
|
+
case 'change_lookup': {
|
|
218
|
+
const params = { KEY: key, LOOKUP: args.domains };
|
|
219
|
+
if (args.since) params.SINCE = args.since;
|
|
220
|
+
return request('change', params, opts);
|
|
221
|
+
}
|
|
222
|
+
case 'lists_tech': {
|
|
223
|
+
const params = { KEY: key, TECH: args.tech, OTHERTECHS: args.otherTechs, OFFSET: args.offset || 0, LIMIT: args.limit || 20 };
|
|
224
|
+
if (args.country) params.COUNTRY = args.country;
|
|
225
|
+
if (args.since) params.SINCE = args.since;
|
|
226
|
+
if (args.spend) params.SPEND = args.spend;
|
|
227
|
+
if (args.revenue) params.REVENUE = args.revenue;
|
|
228
|
+
if (args.employees) params.EMPLOYEES = args.employees;
|
|
229
|
+
if (args.filters && typeof args.filters === 'object') {
|
|
230
|
+
for (const [filterKey, filterValue] of Object.entries(args.filters)) {
|
|
231
|
+
params[filterKey.toUpperCase()] = filterValue;
|
|
232
|
+
}
|
|
233
|
+
}
|
|
234
|
+
return request('lists', params, opts);
|
|
235
|
+
}
|
|
196
236
|
case 'relationships_lookup':
|
|
197
237
|
return request('relationships', { KEY: key, LOOKUP: args.domain }, opts);
|
|
198
238
|
case 'free_lookup':
|