builtwith-official-cli 1.0.0 → 1.2.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/lib/cli.js CHANGED
@@ -18,7 +18,9 @@ function run() {
18
18
  .option('--no-color', 'Disable color on stderr')
19
19
  .option('--dry-run', 'Print request URL (key masked) and exit')
20
20
  .option('--debug', 'Print HTTP metadata to stderr')
21
- .option('--quiet', 'Suppress spinner/info stderr output');
21
+ .option('--quiet', 'Suppress spinner/info stderr output')
22
+ .option('--params <json>', 'Extra API params as JSON object (merged after command params; KEY is protected)')
23
+ .option('--fields <fields>', 'Comma-separated dot-notation fields to include in output (e.g. "Results,Paths.0")');
22
24
 
23
25
  // Register all command modules
24
26
  require('./commands/domain')(program);
@@ -33,9 +35,11 @@ function run() {
33
35
  require('./commands/trends')(program);
34
36
  require('./commands/products')(program);
35
37
  require('./commands/trust')(program);
38
+ require('./commands/vector')(program);
36
39
  require('./commands/account')(program);
37
40
  require('./commands/live')(program);
38
41
  require('./commands/mcp')(program);
42
+ require('./commands/schema')(program);
39
43
 
40
44
  program.parseAsync(process.argv).catch(err => {
41
45
  if (err.code === 'commander.helpDisplayed' || err.code === 'commander.version') {
package/lib/client.js CHANGED
@@ -2,6 +2,26 @@
2
2
 
3
3
  const fetch = require('node-fetch');
4
4
  const { ApiError, NetworkError } = require('./errors');
5
+ const output = require('./output');
6
+
7
+ const INJECTION_PATTERNS = [
8
+ /ignore\s+(previous|above|all)\s+(instructions?|prompts?)/i,
9
+ /system\s*prompt/i,
10
+ /\[INST\]/,
11
+ /<\|im_start\|>/,
12
+ ];
13
+
14
+ function scanForInjection(data) {
15
+ if (typeof data === 'string') {
16
+ for (const p of INJECTION_PATTERNS) {
17
+ if (p.test(data)) output.warn(`Possible prompt injection in response: "${data.slice(0, 80)}"`);
18
+ }
19
+ } else if (Array.isArray(data)) {
20
+ for (const item of data) scanForInjection(item);
21
+ } else if (data !== null && typeof data === 'object') {
22
+ for (const v of Object.values(data)) scanForInjection(v);
23
+ }
24
+ }
5
25
 
6
26
  const BASE_URLS = {
7
27
  domain: 'https://api.builtwith.com/v22/api.json',
@@ -16,6 +36,7 @@ const BASE_URLS = {
16
36
  trends: 'https://api.builtwith.com/trends/v6/api.json',
17
37
  products: 'https://api.builtwith.com/productv1/api.json',
18
38
  trust: 'https://api.builtwith.com/trustv1/api.json',
39
+ vector: 'https://api.builtwith.com/vector/v1/api.json',
19
40
  whoami: 'https://api.builtwith.com/whoamiv1/api.json',
20
41
  usage: 'https://api.builtwith.com/usagev2/api.json',
21
42
  };
@@ -95,7 +116,8 @@ async function request(endpoint, params, opts = {}) {
95
116
  throw new ApiError(msg, isAuth ? 403 : 400);
96
117
  }
97
118
 
119
+ scanForInjection(body);
98
120
  return body;
99
121
  }
100
122
 
101
- module.exports = { buildUrl, maskKey, request, BASE_URLS };
123
+ module.exports = { buildUrl, maskKey, request, scanForInjection, BASE_URLS };
@@ -1,11 +1,21 @@
1
1
  'use strict';
2
2
 
3
- const { Command } = require('commander');
4
3
  const { requireKey } = require('../config');
5
4
  const { request } = require('../client');
5
+ const { InputError } = require('../errors');
6
6
  const output = require('../output');
7
7
  const ora = require('ora');
8
8
 
9
+ function mergeParams(params, rawJson) {
10
+ if (!rawJson) return;
11
+ let extra;
12
+ try { extra = JSON.parse(rawJson); } catch (_) { throw new InputError('--params must be valid JSON'); }
13
+ if (typeof extra !== 'object' || extra === null || Array.isArray(extra)) throw new InputError('--params must be a JSON object');
14
+ const savedKey = params.KEY;
15
+ Object.assign(params, extra);
16
+ params.KEY = savedKey;
17
+ }
18
+
9
19
  module.exports = function registerAccount(program) {
10
20
  const account = program.command('account').description('Account information');
11
21
 
@@ -16,10 +26,12 @@ module.exports = function registerAccount(program) {
16
26
  const opts = program.opts();
17
27
  if (opts.noColor) output.setNoColor(true);
18
28
  const key = requireKey(opts.key);
29
+ const params = { KEY: key };
30
+ mergeParams(params, opts.params);
19
31
  const spinner = opts.quiet ? null : ora({ text: 'Fetching account...', stream: process.stderr }).start();
20
32
  try {
21
- const data = await request('whoami', { KEY: key }, { dryRun: opts.dryRun, debug: opts.debug, spinner });
22
- output.print(data, { format: opts.format });
33
+ const data = await request('whoami', params, { dryRun: opts.dryRun, debug: opts.debug, spinner });
34
+ output.print(data, { format: opts.format, fields: opts.fields });
23
35
  } catch (err) {
24
36
  if (spinner) spinner.stop();
25
37
  throw err;
@@ -33,10 +45,12 @@ module.exports = function registerAccount(program) {
33
45
  const opts = program.opts();
34
46
  if (opts.noColor) output.setNoColor(true);
35
47
  const key = requireKey(opts.key);
48
+ const params = { KEY: key };
49
+ mergeParams(params, opts.params);
36
50
  const spinner = opts.quiet ? null : ora({ text: 'Fetching usage...', stream: process.stderr }).start();
37
51
  try {
38
- const data = await request('usage', { KEY: key }, { dryRun: opts.dryRun, debug: opts.debug, spinner });
39
- output.print(data, { format: opts.format });
52
+ const data = await request('usage', params, { dryRun: opts.dryRun, debug: opts.debug, spinner });
53
+ output.print(data, { format: opts.format, fields: opts.fields });
40
54
  } catch (err) {
41
55
  if (spinner) spinner.stop();
42
56
  throw err;
@@ -2,6 +2,8 @@
2
2
 
3
3
  const { requireKey } = require('../config');
4
4
  const { request } = require('../client');
5
+ const { InputError } = require('../errors');
6
+ const { validateInput } = require('../validate');
5
7
  const output = require('../output');
6
8
  const ora = require('ora');
7
9
 
@@ -15,11 +17,22 @@ module.exports = function registerCompany(program) {
15
17
  const opts = program.opts();
16
18
  if (opts.noColor) output.setNoColor(true);
17
19
  const key = requireKey(opts.key);
20
+ validateInput(name, 'name');
18
21
  const params = { KEY: key, COMPANY: name };
22
+
23
+ if (opts.params) {
24
+ let extra;
25
+ try { extra = JSON.parse(opts.params); } catch (_) { throw new InputError('--params must be valid JSON'); }
26
+ if (typeof extra !== 'object' || extra === null || Array.isArray(extra)) throw new InputError('--params must be a JSON object');
27
+ const savedKey = params.KEY;
28
+ Object.assign(params, extra);
29
+ params.KEY = savedKey;
30
+ }
31
+
19
32
  const spinner = opts.quiet ? null : ora({ text: `Finding company "${name}"...`, stream: process.stderr }).start();
20
33
  try {
21
34
  const data = await request('company', params, { dryRun: opts.dryRun, debug: opts.debug, spinner });
22
- output.print(data, { format: opts.format });
35
+ output.print(data, { format: opts.format, fields: opts.fields });
23
36
  } catch (err) {
24
37
  if (spinner) spinner.stop();
25
38
  throw err;
@@ -3,6 +3,7 @@
3
3
  const { requireKey } = require('../config');
4
4
  const { request } = require('../client');
5
5
  const { InputError } = require('../errors');
6
+ const { validateInput, validateDateRange } = require('../validate');
6
7
  const output = require('../output');
7
8
  const ora = require('ora');
8
9
 
@@ -21,8 +22,10 @@ module.exports = function registerDomain(program) {
21
22
  .action(async (domainArg, cmdOpts) => {
22
23
  const opts = program.opts();
23
24
  if (opts.noColor) output.setNoColor(true);
24
- if (!domainArg) throw new InputError('Domain argument is required');
25
25
  const key = requireKey(opts.key);
26
+ validateInput(domainArg, 'domain');
27
+ if (cmdOpts.fdrange) validateDateRange(cmdOpts.fdrange);
28
+ if (cmdOpts.ldrange) validateDateRange(cmdOpts.ldrange);
26
29
 
27
30
  const params = { KEY: key, LOOKUP: domainArg };
28
31
  // Boolean flags: true → '' so URL gets ?NOPII= which BuiltWith treats as present
@@ -33,10 +36,19 @@ module.exports = function registerDomain(program) {
33
36
  if (cmdOpts.fdrange) params.FDRANGE = cmdOpts.fdrange;
34
37
  if (cmdOpts.ldrange) params.LDRANGE = cmdOpts.ldrange;
35
38
 
39
+ if (opts.params) {
40
+ let extra;
41
+ try { extra = JSON.parse(opts.params); } catch (_) { throw new InputError('--params must be valid JSON'); }
42
+ if (typeof extra !== 'object' || extra === null || Array.isArray(extra)) throw new InputError('--params must be a JSON object');
43
+ const savedKey = params.KEY;
44
+ Object.assign(params, extra);
45
+ params.KEY = savedKey;
46
+ }
47
+
36
48
  const spinner = opts.quiet ? null : ora({ text: `Looking up ${domainArg}...`, stream: process.stderr }).start();
37
49
  try {
38
50
  const data = await request('domain', params, { dryRun: opts.dryRun, debug: opts.debug, spinner });
39
- output.print(data, { format: opts.format });
51
+ output.print(data, { format: opts.format, fields: opts.fields });
40
52
  } catch (err) {
41
53
  if (spinner) spinner.stop();
42
54
  throw err;
@@ -2,6 +2,8 @@
2
2
 
3
3
  const { requireKey } = require('../config');
4
4
  const { request } = require('../client');
5
+ const { InputError } = require('../errors');
6
+ const { validateInput } = require('../validate');
5
7
  const output = require('../output');
6
8
  const ora = require('ora');
7
9
 
@@ -15,11 +17,22 @@ module.exports = function registerFree(program) {
15
17
  const opts = program.opts();
16
18
  if (opts.noColor) output.setNoColor(true);
17
19
  const key = requireKey(opts.key);
20
+ validateInput(domainArg, 'domain');
18
21
  const params = { KEY: key, LOOKUP: domainArg };
22
+
23
+ if (opts.params) {
24
+ let extra;
25
+ try { extra = JSON.parse(opts.params); } catch (_) { throw new InputError('--params must be valid JSON'); }
26
+ if (typeof extra !== 'object' || extra === null || Array.isArray(extra)) throw new InputError('--params must be a JSON object');
27
+ const savedKey = params.KEY;
28
+ Object.assign(params, extra);
29
+ params.KEY = savedKey;
30
+ }
31
+
19
32
  const spinner = opts.quiet ? null : ora({ text: `Fetching free data for ${domainArg}...`, stream: process.stderr }).start();
20
33
  try {
21
34
  const data = await request('free', params, { dryRun: opts.dryRun, debug: opts.debug, spinner });
22
- output.print(data, { format: opts.format });
35
+ output.print(data, { format: opts.format, fields: opts.fields });
23
36
  } catch (err) {
24
37
  if (spinner) spinner.stop();
25
38
  throw err;
@@ -2,6 +2,8 @@
2
2
 
3
3
  const { requireKey } = require('../config');
4
4
  const { request } = require('../client');
5
+ const { InputError } = require('../errors');
6
+ const { validateInput } = require('../validate');
5
7
  const output = require('../output');
6
8
  const ora = require('ora');
7
9
 
@@ -15,11 +17,22 @@ module.exports = function registerKeywords(program) {
15
17
  const opts = program.opts();
16
18
  if (opts.noColor) output.setNoColor(true);
17
19
  const key = requireKey(opts.key);
20
+ validateInput(domainArg, 'domain');
18
21
  const params = { KEY: key, LOOKUP: domainArg };
22
+
23
+ if (opts.params) {
24
+ let extra;
25
+ try { extra = JSON.parse(opts.params); } catch (_) { throw new InputError('--params must be valid JSON'); }
26
+ if (typeof extra !== 'object' || extra === null || Array.isArray(extra)) throw new InputError('--params must be a JSON object');
27
+ const savedKey = params.KEY;
28
+ Object.assign(params, extra);
29
+ params.KEY = savedKey;
30
+ }
31
+
19
32
  const spinner = opts.quiet ? null : ora({ text: `Fetching keywords for ${domainArg}...`, stream: process.stderr }).start();
20
33
  try {
21
34
  const data = await request('keywords', params, { dryRun: opts.dryRun, debug: opts.debug, spinner });
22
- output.print(data, { format: opts.format });
35
+ output.print(data, { format: opts.format, fields: opts.fields });
23
36
  } catch (err) {
24
37
  if (spinner) spinner.stop();
25
38
  throw err;
@@ -2,6 +2,8 @@
2
2
 
3
3
  const { requireKey } = require('../config');
4
4
  const { request } = require('../client');
5
+ const { InputError } = require('../errors');
6
+ const { validateInput, validatePosInt } = require('../validate');
5
7
  const output = require('../output');
6
8
  const ora = require('ora');
7
9
 
@@ -17,11 +19,24 @@ module.exports = function registerLists(program) {
17
19
  const opts = program.opts();
18
20
  if (opts.noColor) output.setNoColor(true);
19
21
  const key = requireKey(opts.key);
20
- const params = { KEY: key, TECH: tech, OFFSET: cmdOpts.offset, LIMIT: cmdOpts.limit };
22
+ validateInput(tech, 'tech');
23
+ const offset = validatePosInt(cmdOpts.offset, 'offset');
24
+ const limit = validatePosInt(cmdOpts.limit, 'limit');
25
+ const params = { KEY: key, TECH: tech, OFFSET: offset, LIMIT: limit };
26
+
27
+ if (opts.params) {
28
+ let extra;
29
+ try { extra = JSON.parse(opts.params); } catch (_) { throw new InputError('--params must be valid JSON'); }
30
+ if (typeof extra !== 'object' || extra === null || Array.isArray(extra)) throw new InputError('--params must be a JSON object');
31
+ const savedKey = params.KEY;
32
+ Object.assign(params, extra);
33
+ params.KEY = savedKey;
34
+ }
35
+
21
36
  const spinner = opts.quiet ? null : ora({ text: `Fetching list for ${tech}...`, stream: process.stderr }).start();
22
37
  try {
23
38
  const data = await request('lists', params, { dryRun: opts.dryRun, debug: opts.debug, spinner });
24
- output.print(data, { format: opts.format });
39
+ output.print(data, { format: opts.format, fields: opts.fields });
25
40
  } catch (err) {
26
41
  if (spinner) spinner.stop();
27
42
  throw err;
@@ -2,6 +2,8 @@
2
2
 
3
3
  const { requireKey } = require('../config');
4
4
  const { request } = require('../client');
5
+ const { InputError } = require('../errors');
6
+ const { validateInput, validatePosInt } = require('../validate');
5
7
  const output = require('../output');
6
8
  const ora = require('ora');
7
9
 
@@ -17,11 +19,24 @@ module.exports = function registerProducts(program) {
17
19
  const opts = program.opts();
18
20
  if (opts.noColor) output.setNoColor(true);
19
21
  const key = requireKey(opts.key);
20
- const params = { KEY: key, QUERY: query, PAGE: cmdOpts.page, LIMIT: cmdOpts.limit };
22
+ validateInput(query, 'query');
23
+ const page = validatePosInt(cmdOpts.page, 'page');
24
+ const limit = validatePosInt(cmdOpts.limit, 'limit');
25
+ const params = { KEY: key, QUERY: query, PAGE: page, LIMIT: limit };
26
+
27
+ if (opts.params) {
28
+ let extra;
29
+ try { extra = JSON.parse(opts.params); } catch (_) { throw new InputError('--params must be valid JSON'); }
30
+ if (typeof extra !== 'object' || extra === null || Array.isArray(extra)) throw new InputError('--params must be a JSON object');
31
+ const savedKey = params.KEY;
32
+ Object.assign(params, extra);
33
+ params.KEY = savedKey;
34
+ }
35
+
21
36
  const spinner = opts.quiet ? null : ora({ text: `Searching products for "${query}"...`, stream: process.stderr }).start();
22
37
  try {
23
38
  const data = await request('products', params, { dryRun: opts.dryRun, debug: opts.debug, spinner });
24
- output.print(data, { format: opts.format });
39
+ output.print(data, { format: opts.format, fields: opts.fields });
25
40
  } catch (err) {
26
41
  if (spinner) spinner.stop();
27
42
  throw err;
@@ -2,6 +2,8 @@
2
2
 
3
3
  const { requireKey } = require('../config');
4
4
  const { request } = require('../client');
5
+ const { InputError } = require('../errors');
6
+ const { validateInput } = require('../validate');
5
7
  const output = require('../output');
6
8
  const ora = require('ora');
7
9
 
@@ -15,11 +17,22 @@ module.exports = function registerRecommendations(program) {
15
17
  const opts = program.opts();
16
18
  if (opts.noColor) output.setNoColor(true);
17
19
  const key = requireKey(opts.key);
20
+ validateInput(domainArg, 'domain');
18
21
  const params = { KEY: key, LOOKUP: domainArg };
22
+
23
+ if (opts.params) {
24
+ let extra;
25
+ try { extra = JSON.parse(opts.params); } catch (_) { throw new InputError('--params must be valid JSON'); }
26
+ if (typeof extra !== 'object' || extra === null || Array.isArray(extra)) throw new InputError('--params must be a JSON object');
27
+ const savedKey = params.KEY;
28
+ Object.assign(params, extra);
29
+ params.KEY = savedKey;
30
+ }
31
+
19
32
  const spinner = opts.quiet ? null : ora({ text: `Fetching recommendations for ${domainArg}...`, stream: process.stderr }).start();
20
33
  try {
21
34
  const data = await request('recommendations', params, { dryRun: opts.dryRun, debug: opts.debug, spinner });
22
- output.print(data, { format: opts.format });
35
+ output.print(data, { format: opts.format, fields: opts.fields });
23
36
  } catch (err) {
24
37
  if (spinner) spinner.stop();
25
38
  throw err;
@@ -2,6 +2,8 @@
2
2
 
3
3
  const { requireKey } = require('../config');
4
4
  const { request } = require('../client');
5
+ const { InputError } = require('../errors');
6
+ const { validateInput } = require('../validate');
5
7
  const output = require('../output');
6
8
  const ora = require('ora');
7
9
 
@@ -15,11 +17,22 @@ module.exports = function registerRedirects(program) {
15
17
  const opts = program.opts();
16
18
  if (opts.noColor) output.setNoColor(true);
17
19
  const key = requireKey(opts.key);
20
+ validateInput(domainArg, 'domain');
18
21
  const params = { KEY: key, LOOKUP: domainArg };
22
+
23
+ if (opts.params) {
24
+ let extra;
25
+ try { extra = JSON.parse(opts.params); } catch (_) { throw new InputError('--params must be valid JSON'); }
26
+ if (typeof extra !== 'object' || extra === null || Array.isArray(extra)) throw new InputError('--params must be a JSON object');
27
+ const savedKey = params.KEY;
28
+ Object.assign(params, extra);
29
+ params.KEY = savedKey;
30
+ }
31
+
19
32
  const spinner = opts.quiet ? null : ora({ text: `Fetching redirects for ${domainArg}...`, stream: process.stderr }).start();
20
33
  try {
21
34
  const data = await request('redirects', params, { dryRun: opts.dryRun, debug: opts.debug, spinner });
22
- output.print(data, { format: opts.format });
35
+ output.print(data, { format: opts.format, fields: opts.fields });
23
36
  } catch (err) {
24
37
  if (spinner) spinner.stop();
25
38
  throw err;
@@ -2,6 +2,8 @@
2
2
 
3
3
  const { requireKey } = require('../config');
4
4
  const { request } = require('../client');
5
+ const { InputError } = require('../errors');
6
+ const { validateInput } = require('../validate');
5
7
  const output = require('../output');
6
8
  const ora = require('ora');
7
9
 
@@ -15,11 +17,22 @@ module.exports = function registerRelationships(program) {
15
17
  const opts = program.opts();
16
18
  if (opts.noColor) output.setNoColor(true);
17
19
  const key = requireKey(opts.key);
20
+ validateInput(domainArg, 'domain');
18
21
  const params = { KEY: key, LOOKUP: domainArg };
22
+
23
+ if (opts.params) {
24
+ let extra;
25
+ try { extra = JSON.parse(opts.params); } catch (_) { throw new InputError('--params must be valid JSON'); }
26
+ if (typeof extra !== 'object' || extra === null || Array.isArray(extra)) throw new InputError('--params must be a JSON object');
27
+ const savedKey = params.KEY;
28
+ Object.assign(params, extra);
29
+ params.KEY = savedKey;
30
+ }
31
+
19
32
  const spinner = opts.quiet ? null : ora({ text: `Fetching relationships for ${domainArg}...`, stream: process.stderr }).start();
20
33
  try {
21
34
  const data = await request('relationships', params, { dryRun: opts.dryRun, debug: opts.debug, spinner });
22
- output.print(data, { format: opts.format });
35
+ output.print(data, { format: opts.format, fields: opts.fields });
23
36
  } catch (err) {
24
37
  if (spinner) spinner.stop();
25
38
  throw err;
@@ -0,0 +1,50 @@
1
+ 'use strict';
2
+
3
+ module.exports = function registerSchema(program) {
4
+ program
5
+ .command('schema')
6
+ .description('Output CLI schema as JSON for agent introspection')
7
+ .option('--command <name>', 'Filter output to one command (e.g. "domain lookup")')
8
+ .action(function(cmdOpts) {
9
+ function serializeOption(opt) {
10
+ return { flags: opt.flags, description: opt.description || '', default: opt.defaultValue ?? null };
11
+ }
12
+ function serializeArg(arg) {
13
+ return { name: arg._name || arg.name(), required: arg.required };
14
+ }
15
+ function serializeCommand(cmd, prefix) {
16
+ const name = prefix ? `${prefix} ${cmd.name()}` : cmd.name();
17
+ const result = {};
18
+ if (cmd.commands && cmd.commands.length > 0) {
19
+ for (const sub of cmd.commands) Object.assign(result, serializeCommand(sub, name));
20
+ } else {
21
+ result[name] = {
22
+ description: cmd.description() || '',
23
+ arguments: (cmd.registeredArguments || []).map(serializeArg),
24
+ options: (cmd.options || []).map(serializeOption),
25
+ };
26
+ }
27
+ return result;
28
+ }
29
+
30
+ const schema = {
31
+ version: program.version(),
32
+ globalOptions: program.options.map(serializeOption),
33
+ commands: {},
34
+ };
35
+ for (const cmd of program.commands) {
36
+ if (cmd.name() === 'schema' || cmd.name() === 'help') continue;
37
+ Object.assign(schema.commands, serializeCommand(cmd, ''));
38
+ }
39
+
40
+ if (cmdOpts.command) {
41
+ if (!schema.commands[cmdOpts.command]) {
42
+ process.stderr.write(`[error] Unknown command: ${cmdOpts.command}\n`);
43
+ process.exit(7);
44
+ }
45
+ schema.commands = { [cmdOpts.command]: schema.commands[cmdOpts.command] };
46
+ }
47
+
48
+ process.stdout.write(JSON.stringify(schema, null, 2) + '\n');
49
+ });
50
+ };
@@ -2,6 +2,8 @@
2
2
 
3
3
  const { requireKey } = require('../config');
4
4
  const { request } = require('../client');
5
+ const { InputError } = require('../errors');
6
+ const { validateInput } = require('../validate');
5
7
  const output = require('../output');
6
8
  const ora = require('ora');
7
9
 
@@ -15,11 +17,22 @@ module.exports = function registerTags(program) {
15
17
  const opts = program.opts();
16
18
  if (opts.noColor) output.setNoColor(true);
17
19
  const key = requireKey(opts.key);
20
+ validateInput(lookup, 'lookup');
18
21
  const params = { KEY: key, LOOKUP: lookup };
22
+
23
+ if (opts.params) {
24
+ let extra;
25
+ try { extra = JSON.parse(opts.params); } catch (_) { throw new InputError('--params must be valid JSON'); }
26
+ if (typeof extra !== 'object' || extra === null || Array.isArray(extra)) throw new InputError('--params must be a JSON object');
27
+ const savedKey = params.KEY;
28
+ Object.assign(params, extra);
29
+ params.KEY = savedKey;
30
+ }
31
+
19
32
  const spinner = opts.quiet ? null : ora({ text: `Fetching tags for ${lookup}...`, stream: process.stderr }).start();
20
33
  try {
21
34
  const data = await request('tags', params, { dryRun: opts.dryRun, debug: opts.debug, spinner });
22
- output.print(data, { format: opts.format });
35
+ output.print(data, { format: opts.format, fields: opts.fields });
23
36
  } catch (err) {
24
37
  if (spinner) spinner.stop();
25
38
  throw err;
@@ -2,6 +2,8 @@
2
2
 
3
3
  const { requireKey } = require('../config');
4
4
  const { request } = require('../client');
5
+ const { InputError } = require('../errors');
6
+ const { validateInput } = require('../validate');
5
7
  const output = require('../output');
6
8
  const ora = require('ora');
7
9
 
@@ -15,11 +17,22 @@ module.exports = function registerTrends(program) {
15
17
  const opts = program.opts();
16
18
  if (opts.noColor) output.setNoColor(true);
17
19
  const key = requireKey(opts.key);
20
+ validateInput(tech, 'tech');
18
21
  const params = { KEY: key, TECH: tech };
22
+
23
+ if (opts.params) {
24
+ let extra;
25
+ try { extra = JSON.parse(opts.params); } catch (_) { throw new InputError('--params must be valid JSON'); }
26
+ if (typeof extra !== 'object' || extra === null || Array.isArray(extra)) throw new InputError('--params must be a JSON object');
27
+ const savedKey = params.KEY;
28
+ Object.assign(params, extra);
29
+ params.KEY = savedKey;
30
+ }
31
+
19
32
  const spinner = opts.quiet ? null : ora({ text: `Fetching trends for ${tech}...`, stream: process.stderr }).start();
20
33
  try {
21
34
  const data = await request('trends', params, { dryRun: opts.dryRun, debug: opts.debug, spinner });
22
- output.print(data, { format: opts.format });
35
+ output.print(data, { format: opts.format, fields: opts.fields });
23
36
  } catch (err) {
24
37
  if (spinner) spinner.stop();
25
38
  throw err;
@@ -2,6 +2,8 @@
2
2
 
3
3
  const { requireKey } = require('../config');
4
4
  const { request } = require('../client');
5
+ const { InputError } = require('../errors');
6
+ const { validateInput } = require('../validate');
5
7
  const output = require('../output');
6
8
  const ora = require('ora');
7
9
 
@@ -15,11 +17,22 @@ module.exports = function registerTrust(program) {
15
17
  const opts = program.opts();
16
18
  if (opts.noColor) output.setNoColor(true);
17
19
  const key = requireKey(opts.key);
20
+ validateInput(domainArg, 'domain');
18
21
  const params = { KEY: key, LOOKUP: domainArg };
22
+
23
+ if (opts.params) {
24
+ let extra;
25
+ try { extra = JSON.parse(opts.params); } catch (_) { throw new InputError('--params must be valid JSON'); }
26
+ if (typeof extra !== 'object' || extra === null || Array.isArray(extra)) throw new InputError('--params must be a JSON object');
27
+ const savedKey = params.KEY;
28
+ Object.assign(params, extra);
29
+ params.KEY = savedKey;
30
+ }
31
+
19
32
  const spinner = opts.quiet ? null : ora({ text: `Fetching trust score for ${domainArg}...`, stream: process.stderr }).start();
20
33
  try {
21
34
  const data = await request('trust', params, { dryRun: opts.dryRun, debug: opts.debug, spinner });
22
- output.print(data, { format: opts.format });
35
+ output.print(data, { format: opts.format, fields: opts.fields });
23
36
  } catch (err) {
24
37
  if (spinner) spinner.stop();
25
38
  throw err;
@@ -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/lib/output.js CHANGED
@@ -75,11 +75,31 @@ function printCsv(data) {
75
75
  process.stdout.write(output);
76
76
  }
77
77
 
78
+ function extractPath(obj, dotPath) {
79
+ return dotPath.split('.').reduce((cur, key) => (cur !== null && cur !== undefined ? cur[key] : undefined), obj);
80
+ }
81
+
82
+ function filterOne(item, paths) {
83
+ if (item === null || typeof item !== 'object') return { value: item };
84
+ const out = {};
85
+ for (const p of paths) out[p] = extractPath(item, p);
86
+ return out;
87
+ }
88
+
89
+ function filterFields(data, fieldsStr) {
90
+ if (!fieldsStr) return data;
91
+ const paths = fieldsStr.split(',').map(s => s.trim()).filter(Boolean);
92
+ if (paths.length === 0) return data;
93
+ if (Array.isArray(data)) return data.map(item => filterOne(item, paths));
94
+ return filterOne(data, paths);
95
+ }
96
+
78
97
  function print(data, opts = {}) {
98
+ const d = opts.fields ? filterFields(data, opts.fields) : data;
79
99
  const fmt = (opts.format || 'json').toLowerCase();
80
- if (fmt === 'table') return printTable(data);
81
- if (fmt === 'csv') return printCsv(data);
82
- return printJson(data);
100
+ if (fmt === 'table') return printTable(d);
101
+ if (fmt === 'csv') return printCsv(d);
102
+ return printJson(d);
83
103
  }
84
104
 
85
105
  function error(msg) {
@@ -97,4 +117,4 @@ function info(msg) {
97
117
  process.stderr.write(`${prefix} ${msg}\n`);
98
118
  }
99
119
 
100
- module.exports = { print, error, warn, info, setNoColor };
120
+ module.exports = { print, error, warn, info, setNoColor, filterFields };
@@ -0,0 +1,35 @@
1
+ 'use strict';
2
+
3
+ const { InputError } = require('./errors');
4
+
5
+ function validateInput(value, label) {
6
+ if (value === null || value === undefined || String(value).trim() === '') {
7
+ throw new InputError(`${label} is required and must not be empty`);
8
+ }
9
+ if (/[\x00-\x1f\x7f]/.test(value)) {
10
+ throw new InputError(`${label} must not contain control characters`);
11
+ }
12
+ if (/(\.\.\/|\.\.\\)/.test(value)) {
13
+ throw new InputError(`${label} contains invalid path traversal sequence`);
14
+ }
15
+ if (/[?&]/.test(value)) {
16
+ throw new InputError(`${label} must not contain query string characters`);
17
+ }
18
+ return value;
19
+ }
20
+
21
+ function validateDateRange(value) {
22
+ if (!/^\d{8}-\d{8}$/.test(value)) {
23
+ throw new InputError(`Date range must be in YYYYMMDD-YYYYMMDD format, got: ${value}`);
24
+ }
25
+ return value;
26
+ }
27
+
28
+ function validatePosInt(value, label) {
29
+ if (!/^\d+$/.test(String(value))) {
30
+ throw new InputError(`${label} must be a non-negative integer, got: ${value}`);
31
+ }
32
+ return parseInt(value, 10);
33
+ }
34
+
35
+ module.exports = { validateInput, validateDateRange, validatePosInt };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "builtwith-official-cli",
3
- "version": "1.0.0",
3
+ "version": "1.2.0",
4
4
  "description": "Non-interactive, scriptable CLI for the BuiltWith API",
5
5
  "main": "lib/cli.js",
6
6
  "bin": {