bitwrench 2.0.15 → 2.0.16

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.
Files changed (51) hide show
  1. package/README.md +57 -21
  2. package/dist/bitwrench-bccl.cjs.js +3746 -0
  3. package/dist/bitwrench-bccl.cjs.min.js +40 -0
  4. package/dist/bitwrench-bccl.esm.js +3741 -0
  5. package/dist/bitwrench-bccl.esm.min.js +40 -0
  6. package/dist/bitwrench-bccl.umd.js +3752 -0
  7. package/dist/bitwrench-bccl.umd.min.js +40 -0
  8. package/dist/bitwrench-code-edit.cjs.js +57 -7
  9. package/dist/bitwrench-code-edit.cjs.min.js +9 -2
  10. package/dist/bitwrench-code-edit.es5.js +74 -11
  11. package/dist/bitwrench-code-edit.es5.min.js +9 -2
  12. package/dist/bitwrench-code-edit.esm.js +57 -7
  13. package/dist/bitwrench-code-edit.esm.min.js +9 -2
  14. package/dist/bitwrench-code-edit.umd.js +57 -7
  15. package/dist/bitwrench-code-edit.umd.min.js +9 -2
  16. package/dist/bitwrench-lean.cjs.js +413 -17
  17. package/dist/bitwrench-lean.cjs.min.js +7 -7
  18. package/dist/bitwrench-lean.es5.js +428 -16
  19. package/dist/bitwrench-lean.es5.min.js +5 -5
  20. package/dist/bitwrench-lean.esm.js +413 -17
  21. package/dist/bitwrench-lean.esm.min.js +7 -7
  22. package/dist/bitwrench-lean.umd.js +413 -17
  23. package/dist/bitwrench-lean.umd.min.js +7 -7
  24. package/dist/bitwrench.cjs.js +413 -17
  25. package/dist/bitwrench.cjs.min.js +7 -7
  26. package/dist/bitwrench.css +60 -17
  27. package/dist/bitwrench.es5.js +428 -16
  28. package/dist/bitwrench.es5.min.js +6 -6
  29. package/dist/bitwrench.esm.js +413 -17
  30. package/dist/bitwrench.esm.min.js +7 -7
  31. package/dist/bitwrench.min.css +1 -1
  32. package/dist/bitwrench.umd.js +413 -17
  33. package/dist/bitwrench.umd.min.js +7 -7
  34. package/dist/builds.json +168 -80
  35. package/dist/bwserve.cjs.js +646 -0
  36. package/dist/bwserve.esm.js +638 -0
  37. package/dist/sri.json +36 -28
  38. package/package.json +18 -3
  39. package/readme.html +62 -23
  40. package/src/bitwrench-bccl-entry.js +72 -0
  41. package/src/bitwrench-code-edit.js +56 -6
  42. package/src/bitwrench-color-utils.js +5 -6
  43. package/src/bitwrench-styles.js +20 -8
  44. package/src/bitwrench.js +385 -0
  45. package/src/bwserve/client.js +182 -0
  46. package/src/bwserve/index.js +352 -0
  47. package/src/bwserve/shell.js +103 -0
  48. package/src/cli/index.js +36 -15
  49. package/src/cli/serve.js +325 -0
  50. package/src/version.js +3 -3
  51. /package/bin/{bitwrench.js → bwcli.js} +0 -0
@@ -0,0 +1,352 @@
1
+ /**
2
+ * bwserve — Server-driven UI library for bitwrench
3
+ *
4
+ * Programmatic API for building server-push UIs (Streamlit-style).
5
+ * Uses SSE (Server-Sent Events) by default, with WebSocket opt-in.
6
+ * Zero runtime dependencies — only Node.js stdlib (http, fs, path).
7
+ *
8
+ * Usage:
9
+ * import bwserve from 'bitwrench/bwserve';
10
+ * const app = bwserve.create({ port: 7902 });
11
+ * app.page('/', (client) => {
12
+ * client.render('#app', bw.makeCard({ title: 'Hello' }));
13
+ * });
14
+ * app.listen();
15
+ *
16
+ * @module bwserve
17
+ */
18
+
19
+ import { BwServeClient } from './client.js';
20
+ import { generateShell } from './shell.js';
21
+
22
+ // Resolve dist/ paths relative to the package root
23
+ import { fileURLToPath } from 'url';
24
+ import { dirname, resolve, join, extname } from 'path';
25
+ import { createServer } from 'http';
26
+ import { readFileSync, existsSync, statSync } from 'fs';
27
+
28
+ var __dirname = dirname(fileURLToPath(import.meta.url));
29
+ var DIST_DIR = resolve(__dirname, '..', '..', 'dist');
30
+
31
+ // MIME type lookup for static file serving
32
+ var MIME_TYPES = {
33
+ '.html': 'text/html; charset=utf-8',
34
+ '.js': 'application/javascript; charset=utf-8',
35
+ '.css': 'text/css; charset=utf-8',
36
+ '.json': 'application/json; charset=utf-8',
37
+ '.png': 'image/png',
38
+ '.jpg': 'image/jpeg',
39
+ '.jpeg': 'image/jpeg',
40
+ '.gif': 'image/gif',
41
+ '.svg': 'image/svg+xml',
42
+ '.ico': 'image/x-icon',
43
+ '.woff': 'font/woff',
44
+ '.woff2': 'font/woff2',
45
+ '.ttf': 'font/ttf',
46
+ '.map': 'application/json'
47
+ };
48
+
49
+ /**
50
+ * Create a bwserve application.
51
+ *
52
+ * @param {Object} opts - Server options
53
+ * @param {number} [opts.port=7902] - Port to listen on
54
+ * @param {string} [opts.title='bwserve'] - Page title
55
+ * @param {string} [opts.static] - Directory to serve static files from
56
+ * @param {boolean} [opts.injectBitwrench=true] - Auto-inject bitwrench client JS
57
+ * @param {string|Object} [opts.theme] - Theme preset name or config object
58
+ * @returns {BwServeApp} Application instance
59
+ */
60
+ export function create(opts) {
61
+ return new BwServeApp(opts || {});
62
+ }
63
+
64
+ /**
65
+ * BwServeApp — the server application object.
66
+ *
67
+ * Manages pages, client connections, and the HTTP/SSE server.
68
+ */
69
+ class BwServeApp {
70
+ constructor(opts) {
71
+ this.port = opts.port || 7902;
72
+ this.title = opts.title || 'bwserve';
73
+ this.staticDir = opts.static || null;
74
+ this.injectBitwrench = opts.injectBitwrench !== false;
75
+ this.theme = opts.theme || null;
76
+ this.keepAliveInterval = opts.keepAliveInterval || 15000;
77
+ this._pages = new Map();
78
+ this._clients = new Map();
79
+ this._server = null;
80
+ this._clientCounter = 0;
81
+ }
82
+
83
+ /**
84
+ * Register a page handler.
85
+ *
86
+ * @param {string} path - URL path (e.g., '/', '/dashboard')
87
+ * @param {Function} handler - Called with (client: BwServeClient) on connection
88
+ * @returns {BwServeApp} this (for chaining)
89
+ */
90
+ page(path, handler) {
91
+ this._pages.set(path, handler);
92
+ return this;
93
+ }
94
+
95
+ /**
96
+ * Start the HTTP server and begin accepting SSE connections.
97
+ *
98
+ * @param {Function} [callback] - Called when server is listening
99
+ * @returns {Promise<void>}
100
+ */
101
+ listen(callback) {
102
+ var self = this;
103
+
104
+ return new Promise(function(res) {
105
+ self._server = createServer(function(req, rawRes) {
106
+ self._handleRequest(req, rawRes);
107
+ });
108
+
109
+ self._server.listen(self.port, function() {
110
+ if (callback) callback();
111
+ res();
112
+ });
113
+ });
114
+ }
115
+
116
+ /**
117
+ * Stop the server and close all client connections.
118
+ */
119
+ close() {
120
+ var self = this;
121
+ return new Promise(function(res) {
122
+ // Close all SSE streams
123
+ for (var record of self._clients.values()) {
124
+ if (record.client && typeof record.client.close === 'function') {
125
+ record.client.close();
126
+ }
127
+ }
128
+ self._clients.clear();
129
+
130
+ if (self._server) {
131
+ self._server.close(function() {
132
+ self._server = null;
133
+ res();
134
+ });
135
+ } else {
136
+ res();
137
+ }
138
+ });
139
+ }
140
+
141
+ /**
142
+ * Get count of active client connections.
143
+ * @returns {number}
144
+ */
145
+ get clientCount() {
146
+ return this._clients.size;
147
+ }
148
+
149
+ /**
150
+ * Broadcast a protocol message to all connected clients.
151
+ *
152
+ * If msg has a clientId field, send only to that client.
153
+ * Otherwise, broadcast to all.
154
+ *
155
+ * @param {Object} msg - Protocol message (replace, patch, append, remove, batch)
156
+ * @returns {number} Number of clients that received the message
157
+ */
158
+ broadcast(msg) {
159
+ if (msg.clientId) {
160
+ var record = this._clients.get(msg.clientId);
161
+ if (record && record.client) {
162
+ record.client._send(msg);
163
+ return 1;
164
+ }
165
+ return 0;
166
+ }
167
+ var count = 0;
168
+ for (var record of this._clients.values()) {
169
+ if (record.client && !record.client._closed) {
170
+ record.client._send(msg);
171
+ count++;
172
+ }
173
+ }
174
+ return count;
175
+ }
176
+
177
+ /**
178
+ * Internal: route incoming HTTP requests.
179
+ * @private
180
+ */
181
+ _handleRequest(req, res) {
182
+ var url = req.url || '/';
183
+ var method = req.method || 'GET';
184
+
185
+ // Parse URL path (strip query string)
186
+ var path = url.split('?')[0];
187
+
188
+ // /__bw/bitwrench.umd.js — serve bitwrench client library
189
+ if (path === '/__bw/bitwrench.umd.js' && method === 'GET') {
190
+ return this._serveDistFile(res, 'bitwrench.umd.js');
191
+ }
192
+
193
+ // /__bw/bitwrench.umd.min.js — serve minified
194
+ if (path === '/__bw/bitwrench.umd.min.js' && method === 'GET') {
195
+ return this._serveDistFile(res, 'bitwrench.umd.min.js');
196
+ }
197
+
198
+ // /__bw/bitwrench.css — serve bitwrench CSS
199
+ if (path === '/__bw/bitwrench.css' && method === 'GET') {
200
+ return this._serveDistFile(res, 'bitwrench.css');
201
+ }
202
+
203
+ // /__bw/events/:clientId — SSE stream
204
+ if (path.startsWith('/__bw/events/') && method === 'GET') {
205
+ var clientId = path.slice('/__bw/events/'.length);
206
+ return this._handleSSE(req, res, clientId);
207
+ }
208
+
209
+ // /__bw/action/:clientId — action POST
210
+ if (path.startsWith('/__bw/action/') && method === 'POST') {
211
+ var actionClientId = path.slice('/__bw/action/'.length);
212
+ return this._handleAction(req, res, actionClientId);
213
+ }
214
+
215
+ // Registered page routes — serve shell HTML
216
+ if (method === 'GET' && this._pages.has(path)) {
217
+ var clientId2 = 'c' + (++this._clientCounter);
218
+ var shell = generateShell({
219
+ clientId: clientId2,
220
+ title: this.title,
221
+ theme: this.theme,
222
+ injectBitwrench: this.injectBitwrench
223
+ });
224
+ // Store the page path for this client so SSE knows which handler to call
225
+ this._clients.set(clientId2, { pagePath: path, client: null });
226
+ res.writeHead(200, { 'Content-Type': 'text/html; charset=utf-8' });
227
+ res.end(shell);
228
+ return;
229
+ }
230
+
231
+ // Static file serving
232
+ if (method === 'GET' && this.staticDir) {
233
+ var filePath = join(this.staticDir, path);
234
+ if (existsSync(filePath) && statSync(filePath).isFile()) {
235
+ var ext = extname(filePath);
236
+ var mime = MIME_TYPES[ext] || 'application/octet-stream';
237
+ var content = readFileSync(filePath);
238
+ res.writeHead(200, { 'Content-Type': mime });
239
+ res.end(content);
240
+ return;
241
+ }
242
+ }
243
+
244
+ // 404
245
+ res.writeHead(404, { 'Content-Type': 'text/plain' });
246
+ res.end('Not Found');
247
+ }
248
+
249
+ /**
250
+ * Serve a file from the dist/ directory.
251
+ * @private
252
+ */
253
+ _serveDistFile(res, filename) {
254
+ var filePath = join(DIST_DIR, filename);
255
+ if (!existsSync(filePath)) {
256
+ res.writeHead(404, { 'Content-Type': 'text/plain' });
257
+ res.end('Not Found: ' + filename);
258
+ return;
259
+ }
260
+ var ext = extname(filename);
261
+ var mime = MIME_TYPES[ext] || 'application/octet-stream';
262
+ var content = readFileSync(filePath);
263
+ res.writeHead(200, {
264
+ 'Content-Type': mime,
265
+ 'Cache-Control': 'public, max-age=3600'
266
+ });
267
+ res.end(content);
268
+ }
269
+
270
+ /**
271
+ * Handle an SSE connection.
272
+ * @private
273
+ */
274
+ _handleSSE(req, res, clientId) {
275
+ var self = this;
276
+
277
+ // Set SSE headers
278
+ res.writeHead(200, {
279
+ 'Content-Type': 'text/event-stream',
280
+ 'Cache-Control': 'no-cache',
281
+ 'Connection': 'keep-alive',
282
+ 'Access-Control-Allow-Origin': '*'
283
+ });
284
+
285
+ // Create client instance
286
+ var client = new BwServeClient(clientId, res);
287
+
288
+ // Look up the pending client record (set during page serve)
289
+ var pending = self._clients.get(clientId);
290
+ var pagePath = pending ? pending.pagePath : '/';
291
+ self._clients.set(clientId, { pagePath: pagePath, client: client });
292
+
293
+ // Keep-alive: send SSE comment periodically
294
+ var keepAlive = setInterval(function() {
295
+ if (!client._closed) {
296
+ try { res.write(':keepalive\n\n'); } catch (e) { /* ignore */ }
297
+ }
298
+ }, self.keepAliveInterval);
299
+
300
+ // Clean up on disconnect
301
+ req.on('close', function() {
302
+ clearInterval(keepAlive);
303
+ client._closed = true;
304
+ self._clients.delete(clientId);
305
+ });
306
+
307
+ // Call the page handler
308
+ var handler = self._pages.get(pagePath);
309
+ if (handler) {
310
+ try {
311
+ handler(client);
312
+ } catch (e) {
313
+ console.error('[bwserve] Page handler error:', e);
314
+ }
315
+ }
316
+ }
317
+
318
+ /**
319
+ * Handle an action POST from a client.
320
+ * @private
321
+ */
322
+ _handleAction(req, res, clientId) {
323
+ var record = this._clients.get(clientId);
324
+ if (!record || !record.client) {
325
+ res.writeHead(404, { 'Content-Type': 'application/json' });
326
+ res.end(JSON.stringify({ error: 'Unknown client' }));
327
+ return;
328
+ }
329
+
330
+ var body = '';
331
+ req.on('data', function(chunk) {
332
+ body += chunk;
333
+ });
334
+ req.on('end', function() {
335
+ try {
336
+ var data = JSON.parse(body);
337
+ var action = data.action;
338
+ var payload = data.data || data;
339
+ record.client._dispatch(action, payload);
340
+ res.writeHead(200, { 'Content-Type': 'application/json' });
341
+ res.end(JSON.stringify({ ok: true }));
342
+ } catch (e) {
343
+ res.writeHead(400, { 'Content-Type': 'application/json' });
344
+ res.end(JSON.stringify({ error: e.message }));
345
+ }
346
+ });
347
+ }
348
+ }
349
+
350
+ export { BwServeApp, BwServeClient };
351
+
352
+ export default { create, BwServeApp, BwServeClient };
@@ -0,0 +1,103 @@
1
+ /**
2
+ * bwserve shell — generates the HTML page shell served to browsers.
3
+ *
4
+ * The shell is a minimal HTML doc that:
5
+ * - Loads bitwrench UMD + CSS from /__bw/ routes
6
+ * - Calls bw.loadDefaultStyles()
7
+ * - Optionally applies a theme
8
+ * - Creates a #app div
9
+ * - Opens an SSE connection via bw.clientConnect()
10
+ * - Delegates data-bw-action clicks to the server via POST
11
+ *
12
+ * @module bwserve/shell
13
+ */
14
+
15
+ /**
16
+ * Generate the shell HTML page for a bwserve app.
17
+ *
18
+ * @param {Object} opts
19
+ * @param {string} opts.clientId - Unique client ID for this connection
20
+ * @param {string} [opts.title='bwserve'] - Page title
21
+ * @param {string} [opts.theme] - Theme preset name or config
22
+ * @param {boolean} [opts.injectBitwrench=true] - Whether to inject bitwrench scripts
23
+ * @returns {string} Complete HTML document
24
+ */
25
+ export function generateShell(opts) {
26
+ opts = opts || {};
27
+ var clientId = opts.clientId || 'default';
28
+ var title = opts.title || 'bwserve';
29
+ var inject = opts.injectBitwrench !== false;
30
+
31
+ var head = [
32
+ '<!DOCTYPE html>',
33
+ '<html lang="en">',
34
+ '<head>',
35
+ '<meta charset="UTF-8">',
36
+ '<meta name="viewport" content="width=device-width, initial-scale=1.0">',
37
+ '<title>' + title + '</title>'
38
+ ];
39
+
40
+ if (inject) {
41
+ head.push('<script src="/__bw/bitwrench.umd.js"></script>');
42
+ head.push('<link rel="stylesheet" href="/__bw/bitwrench.css">');
43
+ }
44
+
45
+ head.push('</head>');
46
+ head.push('<body>');
47
+ head.push('<div id="app"></div>');
48
+
49
+ var script = [
50
+ '<script>',
51
+ '(function() {',
52
+ ' "use strict";',
53
+ ' bw.loadDefaultStyles();'
54
+ ];
55
+
56
+ if (opts.theme) {
57
+ script.push(' bw.generateTheme("bwserve", ' + JSON.stringify(
58
+ typeof opts.theme === 'string'
59
+ ? { primary: '#006666', secondary: '#333333' }
60
+ : opts.theme
61
+ ) + ');');
62
+ }
63
+
64
+ script.push(' var clientId = ' + JSON.stringify(clientId) + ';');
65
+ script.push(' var conn = bw.clientConnect("/__bw/events/" + clientId, {');
66
+ script.push(' actionUrl: "/__bw/action/" + clientId,');
67
+ script.push(' onStatus: function(s) {');
68
+ script.push(' if (typeof console !== "undefined") console.log("[bwserve] " + s);');
69
+ script.push(' }');
70
+ script.push(' });');
71
+
72
+ // data-bw-action click delegation
73
+ script.push(' document.addEventListener("click", function(e) {');
74
+ script.push(' var el = e.target.closest ? e.target.closest("[data-bw-action]") : null;');
75
+ script.push(' if (!el) return;');
76
+ script.push(' e.preventDefault();');
77
+ script.push(' var actionData = {};');
78
+ script.push(' if (el.getAttribute("data-bw-id")) actionData.bwId = el.getAttribute("data-bw-id");');
79
+ script.push(' var form = el.closest("div") || document;');
80
+ script.push(' var inp = form.querySelector("input[type=text],input:not([type])");');
81
+ script.push(' if (inp) { actionData.inputValue = inp.value; inp.value = ""; }');
82
+ script.push(' conn.sendAction(el.getAttribute("data-bw-action"), actionData);');
83
+ script.push(' });');
84
+
85
+ // Enter key on inputs
86
+ script.push(' document.addEventListener("keydown", function(e) {');
87
+ script.push(' if (e.key === "Enter" && e.target.tagName === "INPUT") {');
88
+ script.push(' var form = e.target.closest("div") || document;');
89
+ script.push(' var btn = form.querySelector("[data-bw-action]");');
90
+ script.push(' if (btn) {');
91
+ script.push(' conn.sendAction(btn.getAttribute("data-bw-action"), { inputValue: e.target.value });');
92
+ script.push(' e.target.value = "";');
93
+ script.push(' }');
94
+ script.push(' }');
95
+ script.push(' });');
96
+
97
+ script.push('})();');
98
+ script.push('</script>');
99
+ script.push('</body>');
100
+ script.push('</html>');
101
+
102
+ return head.concat(script).join('\n');
103
+ }
package/src/cli/index.js CHANGED
@@ -1,21 +1,27 @@
1
1
  /**
2
- * Bitwrench CLI - Main entry point
2
+ * bwcli Main entry point for the bitwrench command-line tool
3
3
  * Arg parsing with util.parseArgs(), help, version, dispatch
4
+ *
5
+ * Subcommands:
6
+ * bwcli <file> [options] Convert a file to styled HTML
7
+ * bwcli serve [dir] [options] Start bwserve dev server
4
8
  */
5
9
 
6
10
  import { parseArgs } from 'node:util';
7
11
  import { VERSION } from '../version.js';
8
12
  import { convertFile } from './convert.js';
13
+ import { runServe } from './serve.js';
9
14
 
10
15
  const USAGE = `
11
- bitwrench v${VERSION} — Document converter & static site generator
16
+ bwcli v${VERSION} — bitwrench command-line tool
12
17
 
13
18
  Usage:
14
- bitwrench <file> [options] Convert a file to styled HTML
15
- bitwrench --version Print version
16
- bitwrench --help Print this help
19
+ bwcli <file> [options] Convert a file to styled HTML
20
+ bwcli serve [dir] [options] Start bwserve development server
21
+ bwcli --version Print version
22
+ bwcli --help Print this help
17
23
 
18
- Options:
24
+ Convert options:
19
25
  -o, --output <file> Output file path (default: input with .html extension)
20
26
  -c, --css <file> Include external CSS file
21
27
  -t, --theme <name> Theme preset (ocean, sunset, forest, slate) or hex colors ("#pri,#sec")
@@ -26,16 +32,26 @@ Options:
26
32
  -f, --favicon <path> Favicon path or URL
27
33
  --highlight Include highlight.js for syntax highlighting
28
34
  -v, --verbose Verbose output
35
+
36
+ Serve options:
37
+ -p, --port <number> Port to listen on (default: 7902)
38
+ -t, --theme <name> Theme preset or hex colors
39
+ --open Open browser on start
40
+ -v, --verbose Verbose output
41
+
42
+ General:
29
43
  -h, --help Print this help
30
44
  --version Print version
31
45
 
32
46
  Examples:
33
- bitwrench README.md Convert README.md to README.html
34
- bitwrench README.md -o index.html Specify output file
35
- bitwrench README.md -o out.html --theme ocean Apply ocean theme
36
- bitwrench README.md -o out.html --standalone Self-contained offline HTML
37
- bitwrench README.md -o out.html --highlight With syntax highlighting
38
- bitwrench doc.md --theme "#336699,#cc6633" Custom theme colors
47
+ bwcli README.md Convert README.md to README.html
48
+ bwcli README.md -o index.html Specify output file
49
+ bwcli README.md -o out.html --theme ocean Apply ocean theme
50
+ bwcli README.md -o out.html --standalone Self-contained offline HTML
51
+ bwcli README.md -o out.html --highlight With syntax highlighting
52
+ bwcli doc.md --theme "#336699,#cc6633" Custom theme colors
53
+ bwcli serve Serve current directory on port 7902
54
+ bwcli serve ./site --port 8080 Serve ./site on port 8080
39
55
  `.trim();
40
56
 
41
57
  /**
@@ -43,6 +59,11 @@ Examples:
43
59
  * @param {string[]} argv - process.argv.slice(2)
44
60
  */
45
61
  export function run(argv) {
62
+ // Check for subcommand before parseArgs (subcommands have different options)
63
+ if (argv.length > 0 && argv[0] === 'serve') {
64
+ return runServe(argv.slice(1));
65
+ }
66
+
46
67
  let values, positionals;
47
68
 
48
69
  try {
@@ -69,13 +90,13 @@ export function run(argv) {
69
90
  positionals = result.positionals;
70
91
  } catch (err) {
71
92
  console.error(`Error: ${err.message}`);
72
- console.error('Run "bitwrench --help" for usage.');
93
+ console.error('Run "bwcli --help" for usage.');
73
94
  process.exit(1);
74
95
  }
75
96
 
76
97
  // --version
77
98
  if (values.version) {
78
- console.log(`bitwrench v${VERSION}`);
99
+ console.log(`bwcli v${VERSION}`);
79
100
  return;
80
101
  }
81
102
 
@@ -88,7 +109,7 @@ export function run(argv) {
88
109
  // No positional args → error
89
110
  if (positionals.length === 0) {
90
111
  console.error('Error: No input file specified.');
91
- console.error('Run "bitwrench --help" for usage.');
112
+ console.error('Run "bwcli --help" for usage.');
92
113
  process.exit(1);
93
114
  }
94
115