builtwith-official-cli 1.0.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/config.js ADDED
@@ -0,0 +1,51 @@
1
+ 'use strict';
2
+
3
+ const fs = require('fs');
4
+ const path = require('path');
5
+ const os = require('os');
6
+ const { ConfigError } = require('./errors');
7
+
8
+ // Load .env silently from CWD
9
+ try {
10
+ require('dotenv').config({ path: path.join(process.cwd(), '.env'), quiet: true });
11
+ } catch (_) {}
12
+
13
+ function loadRcFile(dir) {
14
+ const rcPath = path.join(dir, '.builtwithrc');
15
+ try {
16
+ const raw = fs.readFileSync(rcPath, 'utf8');
17
+ const parsed = JSON.parse(raw);
18
+ return parsed.key || null;
19
+ } catch (_) {
20
+ return null;
21
+ }
22
+ }
23
+
24
+ /**
25
+ * Resolve API key using priority order:
26
+ * 1. --key CLI flag
27
+ * 2. BUILTWITH_API_KEY env var
28
+ * 3. .builtwithrc in CWD
29
+ * 4. .builtwithrc in home dir
30
+ */
31
+ function resolveKey(flagValue) {
32
+ if (flagValue) return flagValue;
33
+ if (process.env.BUILTWITH_API_KEY) return process.env.BUILTWITH_API_KEY;
34
+ const cwdKey = loadRcFile(process.cwd());
35
+ if (cwdKey) return cwdKey;
36
+ const homeKey = loadRcFile(os.homedir());
37
+ if (homeKey) return homeKey;
38
+ return null;
39
+ }
40
+
41
+ function requireKey(flagValue) {
42
+ const key = resolveKey(flagValue);
43
+ if (!key) {
44
+ throw new ConfigError(
45
+ 'No API key found. Provide via --key, BUILTWITH_API_KEY env var, or .builtwithrc file.'
46
+ );
47
+ }
48
+ return key;
49
+ }
50
+
51
+ module.exports = { resolveKey, requireKey };
package/lib/errors.js ADDED
@@ -0,0 +1,52 @@
1
+ 'use strict';
2
+
3
+ const EXIT_CODES = {
4
+ SUCCESS: 0,
5
+ UNEXPECTED: 1,
6
+ AUTH: 2,
7
+ NOT_FOUND: 3,
8
+ RATE_LIMIT: 4,
9
+ API_ERROR: 5,
10
+ NETWORK: 6,
11
+ INVALID_INPUT: 7,
12
+ INTERRUPTED: 8,
13
+ };
14
+
15
+ class BuiltWithError extends Error {
16
+ constructor(message, exitCode) {
17
+ super(message);
18
+ this.name = this.constructor.name;
19
+ this.exitCode = exitCode;
20
+ }
21
+ }
22
+
23
+ class InputError extends BuiltWithError {
24
+ constructor(message) {
25
+ super(message, EXIT_CODES.INVALID_INPUT);
26
+ }
27
+ }
28
+
29
+ class ConfigError extends BuiltWithError {
30
+ constructor(message) {
31
+ super(message, EXIT_CODES.AUTH);
32
+ }
33
+ }
34
+
35
+ class ApiError extends BuiltWithError {
36
+ constructor(message, statusCode) {
37
+ let exitCode = EXIT_CODES.API_ERROR;
38
+ if (statusCode === 401 || statusCode === 403) exitCode = EXIT_CODES.AUTH;
39
+ else if (statusCode === 404) exitCode = EXIT_CODES.NOT_FOUND;
40
+ else if (statusCode === 429) exitCode = EXIT_CODES.RATE_LIMIT;
41
+ super(message, exitCode);
42
+ this.statusCode = statusCode;
43
+ }
44
+ }
45
+
46
+ class NetworkError extends BuiltWithError {
47
+ constructor(message) {
48
+ super(message, EXIT_CODES.NETWORK);
49
+ }
50
+ }
51
+
52
+ module.exports = { EXIT_CODES, BuiltWithError, InputError, ConfigError, ApiError, NetworkError };
package/lib/output.js ADDED
@@ -0,0 +1,100 @@
1
+ 'use strict';
2
+
3
+ const Table = require('cli-table3');
4
+ const { stringify } = require('csv-stringify/sync');
5
+ const chalk = require('chalk');
6
+
7
+ let noColor = false;
8
+
9
+ function setNoColor(val) {
10
+ noColor = val;
11
+ if (val) chalk.level = 0;
12
+ }
13
+
14
+ /**
15
+ * Flatten a nested object into dot-notation key/value pairs (one level deep for tables).
16
+ */
17
+ function flattenOne(obj, prefix = '') {
18
+ const out = {};
19
+ for (const [k, v] of Object.entries(obj)) {
20
+ const key = prefix ? `${prefix}.${k}` : k;
21
+ if (v !== null && typeof v === 'object' && !Array.isArray(v)) {
22
+ out[key] = JSON.stringify(v);
23
+ } else if (Array.isArray(v)) {
24
+ out[key] = JSON.stringify(v);
25
+ } else {
26
+ out[key] = v;
27
+ }
28
+ }
29
+ return out;
30
+ }
31
+
32
+ function toRows(data) {
33
+ if (Array.isArray(data)) return data.map(item =>
34
+ typeof item === 'object' && item !== null ? flattenOne(item) : { value: item }
35
+ );
36
+ if (typeof data === 'object' && data !== null) return [flattenOne(data)];
37
+ return [{ value: data }];
38
+ }
39
+
40
+ function printJson(data) {
41
+ process.stdout.write(JSON.stringify(data, null, 2) + '\n');
42
+ }
43
+
44
+ function printTable(data) {
45
+ const rows = toRows(data);
46
+ if (rows.length === 0) {
47
+ process.stdout.write('(no results)\n');
48
+ return;
49
+ }
50
+ const headers = Object.keys(rows[0]);
51
+ const table = new Table({ head: headers });
52
+ for (const row of rows) {
53
+ table.push(headers.map(h => {
54
+ const v = row[h];
55
+ return v === null || v === undefined ? '' : String(v);
56
+ }));
57
+ }
58
+ process.stdout.write(table.toString() + '\n');
59
+ }
60
+
61
+ function printCsv(data) {
62
+ const rows = toRows(data);
63
+ if (rows.length === 0) {
64
+ process.stdout.write('\n');
65
+ return;
66
+ }
67
+ const headers = Object.keys(rows[0]);
68
+ const output = stringify(
69
+ rows.map(r => headers.map(h => {
70
+ const v = r[h];
71
+ return v === null || v === undefined ? '' : String(v);
72
+ })),
73
+ { header: true, columns: headers }
74
+ );
75
+ process.stdout.write(output);
76
+ }
77
+
78
+ function print(data, opts = {}) {
79
+ const fmt = (opts.format || 'json').toLowerCase();
80
+ if (fmt === 'table') return printTable(data);
81
+ if (fmt === 'csv') return printCsv(data);
82
+ return printJson(data);
83
+ }
84
+
85
+ function error(msg) {
86
+ const prefix = noColor ? '[error]' : chalk.red('[error]');
87
+ process.stderr.write(`${prefix} ${msg}\n`);
88
+ }
89
+
90
+ function warn(msg) {
91
+ const prefix = noColor ? '[warn]' : chalk.yellow('[warn]');
92
+ process.stderr.write(`${prefix} ${msg}\n`);
93
+ }
94
+
95
+ function info(msg) {
96
+ const prefix = noColor ? '[info]' : chalk.cyan('[info]');
97
+ process.stderr.write(`${prefix} ${msg}\n`);
98
+ }
99
+
100
+ module.exports = { print, error, warn, info, setNoColor };
@@ -0,0 +1,53 @@
1
+ 'use strict';
2
+
3
+ const WebSocket = require('ws');
4
+ const { NetworkError } = require('./errors');
5
+
6
+ const WS_URL = 'wss://sync.builtwith.com';
7
+
8
+ /**
9
+ * Connect to the BuiltWith live feed WebSocket.
10
+ * @param {string} key - API key
11
+ * @param {object} opts - { onMessage, onError, onClose, debug }
12
+ * @returns {{ close: Function }} - Object with close() to disconnect
13
+ */
14
+ function connect(key, opts = {}) {
15
+ const { onMessage, onError, onClose, debug } = opts;
16
+
17
+ const url = `${WS_URL}?KEY=${encodeURIComponent(key)}`;
18
+ if (debug) process.stderr.write(`[debug] WS connect: ${WS_URL}?KEY=REDACTED\n`);
19
+
20
+ const ws = new WebSocket(url);
21
+
22
+ ws.on('open', () => {
23
+ if (debug) process.stderr.write('[debug] WS connection opened\n');
24
+ });
25
+
26
+ ws.on('message', (data) => {
27
+ try {
28
+ const parsed = JSON.parse(data.toString());
29
+ if (onMessage) onMessage(parsed);
30
+ } catch (_) {
31
+ // Pass raw data if not JSON
32
+ if (onMessage) onMessage({ raw: data.toString() });
33
+ }
34
+ });
35
+
36
+ ws.on('error', (err) => {
37
+ if (debug) process.stderr.write(`[debug] WS error: ${err.message}\n`);
38
+ if (onError) onError(new NetworkError(`WebSocket error: ${err.message}`));
39
+ });
40
+
41
+ ws.on('close', (code, reason) => {
42
+ if (debug) process.stderr.write(`[debug] WS closed: ${code} ${reason}\n`);
43
+ if (onClose) onClose(code, reason);
44
+ });
45
+
46
+ return {
47
+ close() {
48
+ ws.close();
49
+ },
50
+ };
51
+ }
52
+
53
+ module.exports = { connect };
package/package.json ADDED
@@ -0,0 +1,40 @@
1
+ {
2
+ "name": "builtwith-official-cli",
3
+ "version": "1.0.0",
4
+ "description": "Non-interactive, scriptable CLI for the BuiltWith API",
5
+ "main": "lib/cli.js",
6
+ "bin": {
7
+ "bw": "bin/bw.js",
8
+ "builtwith": "bin/bw.js"
9
+ },
10
+ "scripts": {
11
+ "test": "node --test test/*.test.js"
12
+ },
13
+ "files": [
14
+ "bin/",
15
+ "lib/"
16
+ ],
17
+ "keywords": ["builtwith", "cli", "api"],
18
+ "license": "MIT",
19
+ "repository": {
20
+ "type": "git",
21
+ "url": "https://github.com/builtwith/builtwith-official-cli.git"
22
+ },
23
+ "homepage": "https://github.com/builtwith/builtwith-official-cli#readme",
24
+ "bugs": {
25
+ "url": "https://github.com/builtwith/builtwith-official-cli/issues"
26
+ },
27
+ "engines": {
28
+ "node": ">=18"
29
+ },
30
+ "dependencies": {
31
+ "chalk": "^4.1.2",
32
+ "cli-table3": "^0.6.5",
33
+ "commander": "^14.0.3",
34
+ "csv-stringify": "^6.6.0",
35
+ "dotenv": "^17.3.1",
36
+ "node-fetch": "^2.7.0",
37
+ "ora": "^5.4.1",
38
+ "ws": "^8.19.0"
39
+ }
40
+ }