builtwith-official-cli 1.1.0 → 1.5.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 CHANGED
@@ -170,6 +170,32 @@ bw products search "running shoes" --page 2 --limit 50
170
170
  bw trust lookup <domain>
171
171
  ```
172
172
 
173
+ ### 🔎 Vector Search
174
+
175
+ ```bash
176
+ bw vector search <query> [--limit <n>]
177
+ ```
178
+
179
+ ```bash
180
+ bw vector search "react framework"
181
+ bw vector search "ecommerce platform" --limit 20
182
+ ```
183
+
184
+ ### 💳 Payment
185
+
186
+ Manage API credits autonomously.
187
+
188
+ ```bash
189
+ bw payment balance # current credit balance
190
+ bw payment config # limits, pricing, monthly usage
191
+ bw payment purchase <credits> # purchase credits (minimum 2000)
192
+ ```
193
+
194
+ ```bash
195
+ bw payment balance
196
+ bw payment purchase 2000
197
+ ```
198
+
173
199
  ### 👤 Account
174
200
 
175
201
  ```bash
@@ -359,6 +385,10 @@ If your API key isn't in an env var or `.builtwithrc`, pass it inline:
359
385
  | `trends_tech` | 📈 Historical adoption trend for a technology |
360
386
  | `products_search` | 🛍️ Search ecommerce products across indexed stores |
361
387
  | `trust_lookup` | 🛡️ Trust/quality score for a domain |
388
+ | `vector_search` | 🔎 Semantic search across technologies and categories |
389
+ | `payment_balance` | 💳 Get current Agent Payment API credit balance |
390
+ | `payment_config` | ⚙️ Retrieve payment limits and pricing configuration |
391
+ | `payment_purchase` | 🛒 Purchase API credits (minimum 2000) |
362
392
  | `account_whoami` | 👤 Authenticated account identity |
363
393
  | `account_usage` | 📊 API usage statistics |
364
394
 
package/lib/cli.js CHANGED
@@ -35,10 +35,14 @@ function run() {
35
35
  require('./commands/trends')(program);
36
36
  require('./commands/products')(program);
37
37
  require('./commands/trust')(program);
38
+ require('./commands/vector')(program);
39
+ require('./commands/keyword-search')(program);
40
+ require('./commands/payment')(program);
38
41
  require('./commands/account')(program);
39
42
  require('./commands/live')(program);
40
43
  require('./commands/mcp')(program);
41
44
  require('./commands/schema')(program);
45
+ require('./commands/auth')(program);
42
46
 
43
47
  program.parseAsync(process.argv).catch(err => {
44
48
  if (err.code === 'commander.helpDisplayed' || err.code === 'commander.version') {
package/lib/client.js CHANGED
@@ -36,8 +36,15 @@ const BASE_URLS = {
36
36
  trends: 'https://api.builtwith.com/trends/v6/api.json',
37
37
  products: 'https://api.builtwith.com/productv1/api.json',
38
38
  trust: 'https://api.builtwith.com/trustv1/api.json',
39
+ vector: 'https://api.builtwith.com/vector/v1/api.json',
40
+ 'keyword-search': 'https://api.builtwith.com/kws1/api.json',
39
41
  whoami: 'https://api.builtwith.com/whoamiv1/api.json',
40
42
  usage: 'https://api.builtwith.com/usagev2/api.json',
43
+ 'payment-balance': 'https://payments.builtwith.com/v1/billing/api-discovery',
44
+ 'payment-config': 'https://payments.builtwith.com/v1/billing/api-configuration',
45
+ 'payment-purchase': 'https://payments.builtwith.com/v1/billing/api-purchase',
46
+ 'agent-auth-start': 'https://api.builtwith.com/agent-auth/start',
47
+ 'agent-auth-token': 'https://api.builtwith.com/agent-auth/token',
41
48
  };
42
49
 
43
50
  /**
@@ -61,7 +68,7 @@ function maskKey(url, key) {
61
68
  }
62
69
 
63
70
  async function request(endpoint, params, opts = {}) {
64
- const { dryRun, debug, spinner } = opts;
71
+ const { dryRun, debug, spinner, method = 'GET', body } = opts;
65
72
  const url = buildUrl(endpoint, params);
66
73
 
67
74
  if (dryRun) {
@@ -71,17 +78,24 @@ async function request(endpoint, params, opts = {}) {
71
78
  }
72
79
 
73
80
  if (debug) {
74
- process.stderr.write(`[debug] GET ${maskKey(url, params.KEY || params.key)}\n`);
81
+ process.stderr.write(`[debug] ${method} ${maskKey(url, params.KEY || params.key)}\n`);
75
82
  }
76
83
 
77
84
  if (spinner) spinner.start();
78
85
 
86
+ const fetchOpts = {
87
+ method,
88
+ headers: { 'User-Agent': 'builtwith-cli/1.0.0' },
89
+ timeout: 30000,
90
+ };
91
+ if (body !== undefined) {
92
+ fetchOpts.body = JSON.stringify(body);
93
+ fetchOpts.headers['Content-Type'] = 'application/json';
94
+ }
95
+
79
96
  let res;
80
97
  try {
81
- res = await fetch(url, {
82
- headers: { 'User-Agent': 'builtwith-cli/1.0.0' },
83
- timeout: 30000,
84
- });
98
+ res = await fetch(url, fetchOpts);
85
99
  } catch (err) {
86
100
  if (spinner) spinner.stop();
87
101
  throw new NetworkError(`Network request failed: ${err.message}`);
@@ -93,30 +107,30 @@ async function request(endpoint, params, opts = {}) {
93
107
 
94
108
  if (spinner) spinner.stop();
95
109
 
96
- let body;
110
+ let resBody;
97
111
  try {
98
- body = await res.json();
112
+ resBody = await res.json();
99
113
  } catch (_) {
100
114
  throw new ApiError(`Invalid JSON response (HTTP ${res.status})`, res.status);
101
115
  }
102
116
 
103
117
  if (!res.ok) {
104
- const msg = body && body.Errors
105
- ? body.Errors.map(e => e.Message || e).join('; ')
118
+ const msg = resBody && resBody.Errors
119
+ ? resBody.Errors.map(e => e.Message || e).join('; ')
106
120
  : `HTTP ${res.status} ${res.statusText}`;
107
121
  throw new ApiError(msg, res.status);
108
122
  }
109
123
 
110
124
  // BuiltWith API sometimes returns error info in the body with HTTP 200
111
- if (body && body.Errors && body.Errors.length > 0) {
112
- const msg = body.Errors.map(e => e.Message || e).join('; ');
125
+ if (resBody && resBody.Errors && resBody.Errors.length > 0) {
126
+ const msg = resBody.Errors.map(e => e.Message || e).join('; ');
113
127
  // Treat auth-related messages as auth errors
114
128
  const isAuth = /key|auth|unauthori/i.test(msg);
115
129
  throw new ApiError(msg, isAuth ? 403 : 400);
116
130
  }
117
131
 
118
- scanForInjection(body);
119
- return body;
132
+ scanForInjection(resBody);
133
+ return resBody;
120
134
  }
121
135
 
122
136
  module.exports = { buildUrl, maskKey, request, scanForInjection, BASE_URLS };
@@ -0,0 +1,81 @@
1
+ 'use strict';
2
+
3
+ const fetch = require('node-fetch');
4
+ const output = require('../output');
5
+
6
+ const AUTH_START_URL = 'https://api.builtwith.com/agent-auth/start';
7
+ const AUTH_TOKEN_URL = 'https://api.builtwith.com/agent-auth/token';
8
+ const POLL_INTERVAL_MS = 5000;
9
+ const TIMEOUT_MS = 5 * 60 * 1000;
10
+
11
+ module.exports = function registerAuth(program) {
12
+ const auth = program.command('auth').description('Agent Device-Code Authorization – obtain a temporary API token');
13
+
14
+ auth
15
+ .command('login')
16
+ .description('Authorize this agent to use the BuiltWith API via browser approval')
17
+ .action(async () => {
18
+ const opts = program.opts();
19
+ if (opts.noColor) output.setNoColor(true);
20
+
21
+ // Step 1: start the device-code flow
22
+ let startRes;
23
+ try {
24
+ const res = await fetch(AUTH_START_URL, {
25
+ method: 'POST',
26
+ headers: { 'Content-Type': 'application/json' },
27
+ body: '{}',
28
+ });
29
+ startRes = await res.json();
30
+ } catch (err) {
31
+ output.error(`Failed to start authorization: ${err.message}`);
32
+ process.exit(1);
33
+ }
34
+
35
+ const { device_code, verification_uri } = startRes;
36
+ if (!device_code || !verification_uri) {
37
+ output.error('Unexpected response from authorization server.');
38
+ process.exit(1);
39
+ }
40
+
41
+ // Step 2: prompt user to open the browser
42
+ process.stderr.write(`\nOpen this URL in your browser to authorize access:\n\n ${verification_uri}\n\nWaiting for approval`);
43
+
44
+ // Step 3: poll every 5 seconds
45
+ const deadline = Date.now() + TIMEOUT_MS;
46
+ while (Date.now() < deadline) {
47
+ await new Promise(r => setTimeout(r, POLL_INTERVAL_MS));
48
+ process.stderr.write('.');
49
+
50
+ let tokenRes;
51
+ try {
52
+ const res = await fetch(AUTH_TOKEN_URL, {
53
+ method: 'POST',
54
+ headers: { 'Content-Type': 'application/json' },
55
+ body: JSON.stringify({ device_code }),
56
+ });
57
+ tokenRes = await res.json();
58
+ } catch (err) {
59
+ output.error(`\nPolling error: ${err.message}`);
60
+ process.exit(1);
61
+ }
62
+
63
+ if (tokenRes.status === 'approved' && tokenRes.access_token) {
64
+ process.stderr.write('\n\n');
65
+ output.print({ access_token: tokenRes.access_token, message: 'Authorization approved. Use this token as your BW_API_KEY.' }, { format: opts.format });
66
+ return;
67
+ }
68
+
69
+ if (tokenRes.status === 'denied') {
70
+ process.stderr.write('\n');
71
+ output.error('Authorization was denied by the user.');
72
+ process.exit(1);
73
+ }
74
+ // status === 'pending' — keep polling
75
+ }
76
+
77
+ process.stderr.write('\n');
78
+ output.error('Authorization timed out after 5 minutes.');
79
+ process.exit(1);
80
+ });
81
+ };
@@ -0,0 +1,32 @@
1
+ 'use strict';
2
+
3
+ const { requireKey } = require('../config');
4
+ const { request } = require('../client');
5
+ const output = require('../output');
6
+ const ora = require('ora');
7
+
8
+ module.exports = function registerKeywordSearch(program) {
9
+ const kws = program.command('keyword-search').description('Find websites containing a specific keyword');
10
+
11
+ kws
12
+ .command('search <keyword>')
13
+ .description('Search for websites by keyword')
14
+ .option('--limit <n>', 'Results per page (16-1000)', parseInt)
15
+ .option('--offset <offset>', 'Pagination offset (NextOffset from previous response)')
16
+ .action(async (keywordArg, cmdOpts) => {
17
+ const opts = program.opts();
18
+ if (opts.noColor) output.setNoColor(true);
19
+ const key = requireKey(opts.key);
20
+ const params = { KEY: key, KEYWORD: keywordArg };
21
+ if (cmdOpts.limit) params.LIMIT = cmdOpts.limit;
22
+ if (cmdOpts.offset) params.OFFSET = cmdOpts.offset;
23
+ const spinner = opts.quiet ? null : ora({ text: `Searching for "${keywordArg}"...`, stream: process.stderr }).start();
24
+ try {
25
+ const data = await request('keyword-search', params, { dryRun: opts.dryRun, debug: opts.debug, spinner });
26
+ output.print(data, { format: opts.format });
27
+ } catch (err) {
28
+ if (spinner) spinner.stop();
29
+ throw err;
30
+ }
31
+ });
32
+ };
@@ -0,0 +1,67 @@
1
+ 'use strict';
2
+
3
+ const { requireKey } = require('../config');
4
+ const { request } = require('../client');
5
+ const { InputError } = require('../errors');
6
+ const output = require('../output');
7
+ const ora = require('ora');
8
+
9
+ module.exports = function registerPayment(program) {
10
+ const payment = program.command('payment').description('Agent Payment API – manage and purchase API credits');
11
+
12
+ payment
13
+ .command('balance')
14
+ .description('Get current credit balance')
15
+ .action(async () => {
16
+ const opts = program.opts();
17
+ if (opts.noColor) output.setNoColor(true);
18
+ const key = requireKey(opts.key);
19
+ const params = { KEY: key };
20
+ const spinner = opts.quiet ? null : ora({ text: 'Fetching credit balance...', stream: process.stderr }).start();
21
+ try {
22
+ const data = await request('payment-balance', params, { dryRun: opts.dryRun, debug: opts.debug, spinner });
23
+ output.print(data, { format: opts.format, fields: opts.fields });
24
+ } catch (err) {
25
+ if (spinner) spinner.stop();
26
+ throw err;
27
+ }
28
+ });
29
+
30
+ payment
31
+ .command('config')
32
+ .description('Retrieve payment configuration (limits, pricing)')
33
+ .action(async () => {
34
+ const opts = program.opts();
35
+ if (opts.noColor) output.setNoColor(true);
36
+ const key = requireKey(opts.key);
37
+ const params = { KEY: key };
38
+ const spinner = opts.quiet ? null : ora({ text: 'Fetching payment config...', stream: process.stderr }).start();
39
+ try {
40
+ const data = await request('payment-config', params, { dryRun: opts.dryRun, debug: opts.debug, spinner });
41
+ output.print(data, { format: opts.format, fields: opts.fields });
42
+ } catch (err) {
43
+ if (spinner) spinner.stop();
44
+ throw err;
45
+ }
46
+ });
47
+
48
+ payment
49
+ .command('purchase <credits>')
50
+ .description('Purchase API credits (minimum 2000)')
51
+ .action(async (creditsArg) => {
52
+ const opts = program.opts();
53
+ if (opts.noColor) output.setNoColor(true);
54
+ const key = requireKey(opts.key);
55
+ const credits = parseInt(creditsArg, 10);
56
+ if (isNaN(credits) || credits < 2000) throw new InputError('credits must be an integer of at least 2000');
57
+ const params = { KEY: key };
58
+ const spinner = opts.quiet ? null : ora({ text: `Purchasing ${credits} credits...`, stream: process.stderr }).start();
59
+ try {
60
+ const data = await request('payment-purchase', params, { dryRun: opts.dryRun, debug: opts.debug, spinner, method: 'POST', body: { credits } });
61
+ output.print(data, { format: opts.format, fields: opts.fields });
62
+ } catch (err) {
63
+ if (spinner) spinner.stop();
64
+ throw err;
65
+ }
66
+ });
67
+ };
@@ -0,0 +1,30 @@
1
+ 'use strict';
2
+
3
+ const { requireKey } = require('../config');
4
+ const { request } = require('../client');
5
+ const output = require('../output');
6
+ const ora = require('ora');
7
+
8
+ module.exports = function registerVector(program) {
9
+ const vector = program.command('vector').description('Vector search for technologies and categories');
10
+
11
+ vector
12
+ .command('search <query>')
13
+ .description('Search technologies and categories by text using semantic similarity')
14
+ .option('--limit <n>', 'Number of results (default 10, max 100)', parseInt)
15
+ .action(async (queryArg, cmdOpts) => {
16
+ const opts = program.opts();
17
+ if (opts.noColor) output.setNoColor(true);
18
+ const key = requireKey(opts.key);
19
+ const params = { KEY: key, QUERY: queryArg };
20
+ if (cmdOpts.limit) params.LIMIT = cmdOpts.limit;
21
+ const spinner = opts.quiet ? null : ora({ text: `Searching for "${queryArg}"...`, stream: process.stderr }).start();
22
+ try {
23
+ const data = await request('vector', params, { dryRun: opts.dryRun, debug: opts.debug, spinner });
24
+ output.print(data, { format: opts.format });
25
+ } catch (err) {
26
+ if (spinner) spinner.stop();
27
+ throw err;
28
+ }
29
+ });
30
+ };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "builtwith-official-cli",
3
- "version": "1.1.0",
3
+ "version": "1.5.0",
4
4
  "description": "Non-interactive, scriptable CLI for the BuiltWith API",
5
5
  "main": "lib/cli.js",
6
6
  "bin": {