bitwrench 2.0.18 → 2.0.20

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 (58) hide show
  1. package/README.md +86 -81
  2. package/dist/bitwrench-bccl.cjs.js +221 -48
  3. package/dist/bitwrench-bccl.cjs.min.js +3 -3
  4. package/dist/bitwrench-bccl.esm.js +221 -48
  5. package/dist/bitwrench-bccl.esm.min.js +3 -3
  6. package/dist/bitwrench-bccl.umd.js +221 -48
  7. package/dist/bitwrench-bccl.umd.min.js +3 -3
  8. package/dist/bitwrench-code-edit.cjs.js +7 -9
  9. package/dist/bitwrench-code-edit.cjs.min.js +5 -7
  10. package/dist/bitwrench-code-edit.es5.js +6 -8
  11. package/dist/bitwrench-code-edit.es5.min.js +5 -7
  12. package/dist/bitwrench-code-edit.esm.js +7 -9
  13. package/dist/bitwrench-code-edit.esm.min.js +5 -7
  14. package/dist/bitwrench-code-edit.umd.js +7 -9
  15. package/dist/bitwrench-code-edit.umd.min.js +5 -7
  16. package/dist/bitwrench-debug.js +268 -0
  17. package/dist/bitwrench-debug.min.js +3 -0
  18. package/dist/bitwrench-lean.cjs.js +250 -1574
  19. package/dist/bitwrench-lean.cjs.min.js +6 -6
  20. package/dist/bitwrench-lean.es5.js +344 -1661
  21. package/dist/bitwrench-lean.es5.min.js +4 -4
  22. package/dist/bitwrench-lean.esm.js +250 -1574
  23. package/dist/bitwrench-lean.esm.min.js +6 -6
  24. package/dist/bitwrench-lean.umd.js +250 -1574
  25. package/dist/bitwrench-lean.umd.min.js +6 -6
  26. package/dist/bitwrench-util-css.cjs.js +1 -1
  27. package/dist/bitwrench-util-css.cjs.min.js +1 -1
  28. package/dist/bitwrench-util-css.es5.js +1 -1
  29. package/dist/bitwrench-util-css.es5.min.js +1 -1
  30. package/dist/bitwrench-util-css.esm.js +1 -1
  31. package/dist/bitwrench-util-css.esm.min.js +1 -1
  32. package/dist/bitwrench-util-css.umd.js +1 -1
  33. package/dist/bitwrench-util-css.umd.min.js +1 -1
  34. package/dist/bitwrench.cjs.js +510 -1660
  35. package/dist/bitwrench.cjs.min.js +7 -7
  36. package/dist/bitwrench.css +80 -33
  37. package/dist/bitwrench.es5.js +569 -1694
  38. package/dist/bitwrench.es5.min.js +5 -5
  39. package/dist/bitwrench.esm.js +510 -1660
  40. package/dist/bitwrench.esm.min.js +7 -7
  41. package/dist/bitwrench.min.css +1 -1
  42. package/dist/bitwrench.umd.js +510 -1660
  43. package/dist/bitwrench.umd.min.js +7 -7
  44. package/dist/builds.json +133 -111
  45. package/dist/bwserve.cjs.js +2 -2
  46. package/dist/bwserve.esm.js +2 -2
  47. package/dist/sri.json +46 -44
  48. package/package.json +5 -3
  49. package/readme.html +86 -75
  50. package/src/bitwrench-bccl-entry.js +3 -4
  51. package/src/bitwrench-bccl.js +217 -43
  52. package/src/bitwrench-code-edit.js +6 -8
  53. package/src/bitwrench-debug.js +245 -0
  54. package/src/bitwrench-styles.js +35 -8
  55. package/src/bitwrench.js +212 -1563
  56. package/src/cli/attach.js +53 -21
  57. package/src/cli/serve.js +179 -3
  58. package/src/version.js +3 -3
package/src/cli/attach.js CHANGED
@@ -70,6 +70,14 @@ Examples:
70
70
  bw> /screenshot body page.png
71
71
  bw> /listen .bw-btn click
72
72
  bw> /mount #app card {"title":"Hello","content":"World"}
73
+
74
+ Workflow — build a dashboard from your terminal:
75
+ bw> /render #app {"t":"div","c":[{"t":"h2","c":"Dashboard"},{"t":"div","a":{"id":"stats"},"c":[{"t":"span","a":{"id":"users"},"c":"Users: 0"},{"t":"span","a":{"id":"orders"},"c":"Orders: 0"}]}]}
76
+ bw> /patch users "Users: 342"
77
+ bw> /patch orders "Orders: 28"
78
+ bw> /mount #app card {"title":"Status","content":"All systems go"}
79
+ bw> /tree #app 2
80
+ bw> /listen .bw-btn click
73
81
  `.trim();
74
82
 
75
83
  /**
@@ -91,8 +99,9 @@ export function wrapExpression(code) {
91
99
  /**
92
100
  * Run the attach subcommand.
93
101
  * @param {string[]} argv - arguments after "attach"
102
+ * @param {object} [ioOpts] - optional input/output streams for testing
94
103
  */
95
- export function runAttach(argv) {
104
+ export function runAttach(argv, ioOpts) {
96
105
  var values;
97
106
 
98
107
  try {
@@ -129,19 +138,25 @@ export function runAttach(argv) {
129
138
  }
130
139
 
131
140
  // Dynamic import of bwserve
132
- import('../bwserve/index.js').then(function(bwserve) {
133
- startAttach(bwserve, { port, allowScreenshot, verbose });
141
+ var io = ioOpts || {};
142
+ var importPath = io._importPath || '../bwserve/index.js';
143
+ var importPromise = import(importPath).then(function(bwserve) {
144
+ return startAttach(bwserve, { port: port, allowScreenshot: allowScreenshot, verbose: verbose, input: io.input, output: io.output });
134
145
  }).catch(function(err) {
135
146
  console.error('Failed to load bwserve: ' + err.message);
136
147
  process.exit(1);
137
148
  });
149
+
150
+ return importPromise;
138
151
  }
139
152
 
140
153
  /**
141
154
  * Start the attach server and REPL.
142
- * @private
155
+ * @param {object} bwserve - The bwserve module (or mock)
156
+ * @param {object} opts - { port, allowScreenshot, verbose, input }
157
+ * @returns {{ rl: object, app: object }} readline interface and app for testing
143
158
  */
144
- function startAttach(bwserve, opts) {
159
+ export function startAttach(bwserve, opts) {
145
160
  var app = bwserve.create({
146
161
  port: opts.port,
147
162
  title: 'bwcli attach',
@@ -180,7 +195,7 @@ function startAttach(bwserve, opts) {
180
195
  // Print connection message
181
196
  process.stdout.write('\r\x1b[K');
182
197
  console.log('[connected] client ' + clientId);
183
- rl.prompt(true);
198
+ safePrompt(true);
184
199
 
185
200
  // Listen for events from _bw_listen
186
201
  client.on('_bw_event', function(data) {
@@ -188,7 +203,7 @@ function startAttach(bwserve, opts) {
188
203
  console.log('[event] ' + data.event + ' on ' + data.selector +
189
204
  ' \u2192 ' + data.tagName + (data.id ? '#' + data.id : '') +
190
205
  (data.text ? ' "' + data.text.slice(0, 50) + '"' : ''));
191
- rl.prompt(true);
206
+ safePrompt(true);
192
207
  });
193
208
 
194
209
  // Handle disconnect
@@ -205,7 +220,7 @@ function startAttach(bwserve, opts) {
205
220
  } else {
206
221
  console.log('Waiting for connection...');
207
222
  }
208
- rl.prompt(true);
223
+ safePrompt(true);
209
224
  });
210
225
  }
211
226
  };
@@ -223,20 +238,25 @@ function startAttach(bwserve, opts) {
223
238
  console.log('Waiting for connection...');
224
239
  console.log('Type /help for commands, /quit to exit.');
225
240
  console.log('');
226
- rl.prompt();
241
+ safePrompt();
227
242
  });
228
243
 
229
244
  // Create readline REPL
230
245
  var rl = createInterface({
231
- input: process.stdin,
232
- output: process.stdout,
246
+ input: opts.input || process.stdin,
247
+ output: opts.output || process.stdout,
233
248
  prompt: 'bw> '
234
249
  });
250
+ var rlClosed = false;
251
+
252
+ function safePrompt(preserveCursor) {
253
+ if (!rlClosed) rl.prompt(preserveCursor);
254
+ }
235
255
 
236
256
  rl.on('line', function(line) {
237
257
  line = line.trim();
238
258
  if (!line) {
239
- rl.prompt();
259
+ safePrompt();
240
260
  return;
241
261
  }
242
262
 
@@ -249,7 +269,7 @@ function startAttach(bwserve, opts) {
249
269
  // JS expression — requires active client
250
270
  if (!activeClient) {
251
271
  console.log('No client connected. Add the attach script to a page first.');
252
- rl.prompt();
272
+ safePrompt();
253
273
  return;
254
274
  }
255
275
 
@@ -268,26 +288,33 @@ function startAttach(bwserve, opts) {
268
288
  } else {
269
289
  console.log('undefined');
270
290
  }
271
- rl.prompt();
291
+ safePrompt();
272
292
  }).catch(function(err) {
273
293
  console.error('[error] ' + err.message);
274
- rl.prompt();
294
+ safePrompt();
275
295
  });
276
296
  });
277
297
 
278
298
  rl.on('close', function() {
299
+ rlClosed = true;
279
300
  console.log('\nExiting.');
280
301
  app.close().then(function() {
281
302
  process.exit(0);
282
303
  });
283
304
  });
305
+
306
+ return { rl: rl, app: app };
284
307
  }
285
308
 
286
309
  /**
287
310
  * Handle slash commands in the REPL.
288
- * @private
311
+ * @param {string} line - The full command line (e.g., "/tree #app 2")
312
+ * @param {object|null} activeClient - The active BwServeClient, or null
313
+ * @param {Map} clients - Map of clientId -> client
314
+ * @param {object} opts - { allowScreenshot, verbose }
315
+ * @param {object} rl - readline interface with prompt() method
289
316
  */
290
- function handleSlashCommand(line, activeClient, clients, opts, rl) {
317
+ export function handleSlashCommand(line, activeClient, clients, opts, rl) {
291
318
  var parts = line.split(/\s+/);
292
319
  var cmd = parts[0].toLowerCase();
293
320
 
@@ -505,9 +532,10 @@ function handleSlashCommand(line, activeClient, clients, opts, rl) {
505
532
 
506
533
  /**
507
534
  * Pretty-print a DOM tree from _bw_tree.
508
- * @private
535
+ * @param {object} node - Tree node with tag, id, cls, children
536
+ * @param {number} indent - Current indentation level
509
537
  */
510
- function printTree(node, indent) {
538
+ export function printTree(node, indent) {
511
539
  if (!node) return;
512
540
  var prefix = ' '.repeat(indent);
513
541
  var label = node.tag || '?';
@@ -523,9 +551,8 @@ function printTree(node, indent) {
523
551
 
524
552
  /**
525
553
  * Print the REPL help reference.
526
- * @private
527
554
  */
528
- function printHelp() {
555
+ export function printHelp() {
529
556
  console.log([
530
557
  '',
531
558
  'bwcli attach — REPL Commands',
@@ -550,6 +577,11 @@ function printHelp() {
550
577
  ' /unlisten <sel> <event> Remove a listener',
551
578
  ' /exec <code> Execute JS without capturing return value',
552
579
  ' /clients List connected clients',
580
+ '',
581
+ ' Workflow — build and push a component:',
582
+ ' /render #app {"t":"div","c":[{"t":"h2","c":"Hello"},{"t":"span","a":{"id":"msg"},"c":"..."}]}',
583
+ ' /patch msg "Component pushed!"',
584
+ ' /mount #app card {"title":"Status","content":"OK"}',
553
585
  ''
554
586
  ].join('\n'));
555
587
  }
package/src/cli/serve.js CHANGED
@@ -5,6 +5,10 @@
5
5
  * via an input HTTP port or stdin. Broadcasts messages to all connected
6
6
  * browser tabs.
7
7
  *
8
+ * The input HTTP port also supports interactive commands: POST a JSON
9
+ * object with a `command` field and receive the result as the HTTP
10
+ * response. This is the programmatic equivalent of bwcli attach's REPL.
11
+ *
8
12
  * Usage:
9
13
  * bwcli serve [dir] [--port N] [--listen N] [--stdin] [--theme name]
10
14
  */
@@ -40,6 +44,8 @@ Examples:
40
44
  bwcli serve --stdin Read from pipe instead of input port
41
45
  sensor-reader | bwcli serve --stdin Pipe sensor data to browser
42
46
  curl -X POST :9000 -d '{"type":"replace","target":"#app","node":{"t":"h1","c":"Hi"}}'
47
+ curl -X POST :9000 -d '{"command":"query","code":"document.title"}'
48
+ curl -X POST :9000 -d '{"command":"clients"}'
43
49
  `.trim();
44
50
 
45
51
  /**
@@ -131,11 +137,151 @@ function parseRelaxedJSON(str) {
131
137
  return JSON.parse(out.join(''));
132
138
  }
133
139
 
140
+ // Required fields per command (async commands also listed)
141
+ var _COMMAND_REQUIRED = {
142
+ query: ['code'],
143
+ screenshot: [],
144
+ tree: [],
145
+ mount: ['selector', 'factory'],
146
+ exec: ['code'],
147
+ render: ['selector', 'taco'],
148
+ patch: ['id'],
149
+ listen: ['selector', 'event'],
150
+ unlisten: ['selector', 'event'],
151
+ clients: []
152
+ };
153
+
154
+ // Commands that return a result via promise
155
+ var _ASYNC_COMMANDS = { query: 1, screenshot: 1, tree: 1, mount: 1 };
156
+
157
+ /**
158
+ * Handle an interactive command from the listen port.
159
+ *
160
+ * @param {Object} msg - Parsed message with `command` field
161
+ * @param {Object} app - BwServeApp instance (has _clients Map)
162
+ * @param {boolean} verbose - Log details to stderr
163
+ * @returns {Promise<Object>} Response object ({ok, result, clientId} or {error})
164
+ */
165
+ function handleCommand(msg, app, verbose) {
166
+ var cmd = msg.command;
167
+
168
+ // clients: no client needed
169
+ if (cmd === 'clients') {
170
+ var ids = [];
171
+ for (var entry of app._clients) {
172
+ if (entry[1] && entry[1].client) ids.push(entry[0]);
173
+ }
174
+ return Promise.resolve({ ok: true, clients: ids });
175
+ }
176
+
177
+ // Validate command name
178
+ if (!_COMMAND_REQUIRED.hasOwnProperty(cmd)) {
179
+ return Promise.resolve({ error: 'Unknown command: ' + cmd });
180
+ }
181
+
182
+ // Validate required fields
183
+ var required = _COMMAND_REQUIRED[cmd];
184
+ for (var k = 0; k < required.length; k++) {
185
+ if (msg[required[k]] === undefined) {
186
+ return Promise.resolve({ error: 'Missing required field: ' + required[k] });
187
+ }
188
+ }
189
+
190
+ // Select client
191
+ var client = null;
192
+ var clientId = null;
193
+
194
+ if (msg.clientId) {
195
+ var record = app._clients.get(msg.clientId);
196
+ if (record && record.client) {
197
+ client = record.client;
198
+ clientId = msg.clientId;
199
+ } else {
200
+ return Promise.resolve({ error: 'Client not found: ' + msg.clientId });
201
+ }
202
+ } else {
203
+ // Pick first connected client
204
+ for (var pair of app._clients) {
205
+ if (pair[1] && pair[1].client && !pair[1].client._closed) {
206
+ client = pair[1].client;
207
+ clientId = pair[0];
208
+ break;
209
+ }
210
+ }
211
+ }
212
+
213
+ if (!client) {
214
+ return Promise.resolve({ error: 'No clients connected' });
215
+ }
216
+
217
+ var timeout = msg.timeout;
218
+
219
+ if (verbose) {
220
+ console.error('[command] ' + cmd + ' -> client ' + clientId);
221
+ }
222
+
223
+ // Dispatch
224
+ try {
225
+ switch (cmd) {
226
+ case 'query':
227
+ return client.query(msg.code, { timeout: timeout || 5000 }).then(function(result) {
228
+ return { ok: true, result: result, clientId: clientId };
229
+ });
230
+
231
+ case 'screenshot':
232
+ return client.screenshot(msg.selector || 'body', { timeout: timeout || 10000 }).then(function(result) {
233
+ // Convert Buffer to base64 for JSON response
234
+ var data = result && result.data ? result.data.toString('base64') : null;
235
+ return { ok: true, result: { data: data, width: result.width, height: result.height, format: result.format }, clientId: clientId };
236
+ });
237
+
238
+ case 'tree':
239
+ var pend = client._pend(timeout || 10000);
240
+ client.call('_bw_tree', {
241
+ selector: msg.selector || 'body',
242
+ depth: msg.depth || 3,
243
+ requestId: pend.requestId
244
+ });
245
+ return pend.promise.then(function(result) {
246
+ return { ok: true, result: result, clientId: clientId };
247
+ });
248
+
249
+ case 'mount':
250
+ return client.mount(msg.selector, msg.factory, msg.props || {}, { timeout: timeout || 10000 }).then(function(result) {
251
+ return { ok: true, result: result, clientId: clientId };
252
+ });
253
+
254
+ case 'exec':
255
+ client.exec(msg.code);
256
+ return Promise.resolve({ ok: true, clientId: clientId });
257
+
258
+ case 'render':
259
+ client.render(msg.selector, msg.taco);
260
+ return Promise.resolve({ ok: true, clientId: clientId });
261
+
262
+ case 'patch':
263
+ client.patch(msg.id, msg.content, msg.attr);
264
+ return Promise.resolve({ ok: true, clientId: clientId });
265
+
266
+ case 'listen':
267
+ client.call('_bw_listen', { selector: msg.selector, event: msg.event });
268
+ return Promise.resolve({ ok: true, clientId: clientId });
269
+
270
+ case 'unlisten':
271
+ client.call('_bw_unlisten', { selector: msg.selector, event: msg.event });
272
+ return Promise.resolve({ ok: true, clientId: clientId });
273
+ }
274
+ } catch (err) {
275
+ return Promise.resolve({ error: err.message });
276
+ }
277
+ }
278
+
134
279
  /**
135
280
  * Run the serve subcommand.
136
281
  * @param {string[]} argv - arguments after "serve"
282
+ * @param {object} [ioOpts] - optional overrides for testing
137
283
  */
138
- export function runServe(argv) {
284
+ export function runServe(argv, ioOpts) {
139
285
  var values, positionals;
140
286
 
141
287
  try {
@@ -186,7 +332,9 @@ export function runServe(argv) {
186
332
  }
187
333
 
188
334
  // Dynamic import of bwserve to avoid loading it at parse time
189
- import('../../src/bwserve/index.js').then(function(bwserve) {
335
+ var io = ioOpts || {};
336
+ var importPath = io._importPath || '../../src/bwserve/index.js';
337
+ var importPromise = import(importPath).then(function(bwserve) {
190
338
  startServer(bwserve, {
191
339
  dir: dir,
192
340
  webPort: webPort,
@@ -202,6 +350,8 @@ export function runServe(argv) {
202
350
  console.error('Failed to load bwserve: ' + err.message);
203
351
  process.exit(1);
204
352
  });
353
+
354
+ return importPromise;
205
355
  }
206
356
 
207
357
  /**
@@ -257,7 +407,11 @@ function startServer(bwserve, opts) {
257
407
  }
258
408
 
259
409
  /**
260
- * Start the input HTTP server for receiving protocol messages.
410
+ * Start the input HTTP server for receiving protocol messages and interactive commands.
411
+ *
412
+ * Messages with a `command` field are treated as interactive commands --
413
+ * routed to a specific client and the result returned in the HTTP response.
414
+ * Messages with a `type` field (no `command`) are broadcast to all clients.
261
415
  */
262
416
  function startInputServer(app, listenPort, verbose) {
263
417
  var inputServer = createServer(function(req, res) {
@@ -277,6 +431,25 @@ function startInputServer(app, listenPort, verbose) {
277
431
  return;
278
432
  }
279
433
 
434
+ // Interactive command path
435
+ if (msg.command) {
436
+ handleCommand(msg, app, verbose).then(function(result) {
437
+ var status = result.error ? 400 : 200;
438
+ // Unknown command and client-not-found get 400; timeout also 400
439
+ res.writeHead(status, { 'Content-Type': 'application/json' });
440
+ res.end(JSON.stringify(result));
441
+ }).catch(function(err) {
442
+ var errMsg = err && err.message ? err.message : String(err);
443
+ if (verbose) {
444
+ console.error('[command] error: ' + errMsg);
445
+ }
446
+ res.writeHead(400, { 'Content-Type': 'application/json' });
447
+ res.end(JSON.stringify({ error: errMsg }));
448
+ });
449
+ return;
450
+ }
451
+
452
+ // Broadcast path (existing behavior)
280
453
  var count = app.broadcast(msg);
281
454
  if (verbose) {
282
455
  console.error('[input] ' + msg.type + ' -> ' + count + ' client(s)');
@@ -327,3 +500,6 @@ function startStdinReader(app, verbose) {
327
500
  if (verbose) console.error('[stdin] Input stream closed. Server stays running.');
328
501
  });
329
502
  }
503
+
504
+ // Export private functions for testability
505
+ export { parseMessage, parseRelaxedJSON, startServer, startInputServer, startStdinReader, handleCommand };
package/src/version.js CHANGED
@@ -3,14 +3,14 @@
3
3
  * DO NOT EDIT DIRECTLY - Use npm run generate-version
4
4
  */
5
5
 
6
- export const VERSION = '2.0.18';
6
+ export const VERSION = '2.0.20';
7
7
  export const VERSION_INFO = {
8
- version: '2.0.18',
8
+ version: '2.0.20',
9
9
  name: 'bitwrench',
10
10
  description: 'A library for javascript UI functions.',
11
11
  license: 'BSD-2-Clause',
12
12
  homepage: 'https://deftio.github.com/bitwrench/pages',
13
13
  repository: 'git+https://github.com/deftio/bitwrench.git',
14
14
  author: 'manu a. chatterjee <deftio@deftio.com> (https://deftio.com/)',
15
- buildDate: '2026-03-17T00:50:09.505Z'
15
+ buildDate: '2026-03-23T05:19:31.951Z'
16
16
  };