bitwrench 2.0.17 → 2.0.19

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 (72) hide show
  1. package/README.md +169 -75
  2. package/dist/bitwrench-bccl.cjs.js +228 -55
  3. package/dist/bitwrench-bccl.cjs.min.js +3 -3
  4. package/dist/bitwrench-bccl.esm.js +228 -55
  5. package/dist/bitwrench-bccl.esm.min.js +3 -3
  6. package/dist/bitwrench-bccl.umd.js +228 -55
  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 +1190 -2348
  19. package/dist/bitwrench-lean.cjs.min.js +20 -20
  20. package/dist/bitwrench-lean.es5.js +1285 -2551
  21. package/dist/bitwrench-lean.es5.min.js +18 -18
  22. package/dist/bitwrench-lean.esm.js +1190 -2348
  23. package/dist/bitwrench-lean.esm.min.js +20 -20
  24. package/dist/bitwrench-lean.umd.js +1190 -2348
  25. package/dist/bitwrench-lean.umd.min.js +20 -20
  26. package/dist/bitwrench-util-css.cjs.js +236 -0
  27. package/dist/bitwrench-util-css.cjs.min.js +22 -0
  28. package/dist/bitwrench-util-css.es5.js +414 -0
  29. package/dist/bitwrench-util-css.es5.min.js +21 -0
  30. package/dist/bitwrench-util-css.esm.js +230 -0
  31. package/dist/bitwrench-util-css.esm.min.js +21 -0
  32. package/dist/bitwrench-util-css.umd.js +242 -0
  33. package/dist/bitwrench-util-css.umd.min.js +21 -0
  34. package/dist/bitwrench.cjs.js +1404 -2388
  35. package/dist/bitwrench.cjs.min.js +21 -21
  36. package/dist/bitwrench.css +503 -132
  37. package/dist/bitwrench.es5.js +1588 -2659
  38. package/dist/bitwrench.es5.min.js +19 -19
  39. package/dist/bitwrench.esm.js +1405 -2389
  40. package/dist/bitwrench.esm.min.js +21 -21
  41. package/dist/bitwrench.min.css +1 -1
  42. package/dist/bitwrench.umd.js +1404 -2388
  43. package/dist/bitwrench.umd.min.js +21 -21
  44. package/dist/builds.json +214 -104
  45. package/dist/bwserve.cjs.js +514 -68
  46. package/dist/bwserve.esm.js +513 -69
  47. package/dist/sri.json +46 -36
  48. package/package.json +6 -3
  49. package/readme.html +183 -85
  50. package/src/bitwrench-bccl-entry.js +3 -4
  51. package/src/bitwrench-bccl.js +224 -50
  52. package/src/bitwrench-code-edit.js +6 -8
  53. package/src/bitwrench-color-utils.js +31 -9
  54. package/src/bitwrench-debug.js +245 -0
  55. package/src/bitwrench-esm-entry.js +11 -0
  56. package/src/bitwrench-styles.js +474 -240
  57. package/src/bitwrench-util-css.js +229 -0
  58. package/src/bitwrench.js +689 -2042
  59. package/src/bwserve/attach.js +57 -0
  60. package/src/bwserve/bwclient.js +141 -0
  61. package/src/bwserve/bwshell.js +102 -0
  62. package/src/bwserve/client.js +151 -1
  63. package/src/bwserve/index.js +127 -28
  64. package/src/cli/attach.js +587 -0
  65. package/src/cli/convert.js +2 -5
  66. package/src/cli/index.js +7 -0
  67. package/src/cli/inject.js +1 -1
  68. package/src/cli/serve.js +185 -5
  69. package/src/generate-css.js +11 -4
  70. package/src/vendor/html2canvas.min.js +20 -0
  71. package/src/version.js +3 -3
  72. package/src/bwserve/shell.js +0 -106
@@ -0,0 +1,587 @@
1
+ /**
2
+ * bwcli attach — Interactive remote debugging for bitwrench pages.
3
+ *
4
+ * Starts a bwserve instance and waits for a browser page to connect
5
+ * via the drop-in attach script. Once connected, provides a REPL
6
+ * for evaluating JS, inspecting the DOM tree, taking screenshots,
7
+ * and listening to events in real time.
8
+ *
9
+ * Usage:
10
+ * bwcli attach [options]
11
+ *
12
+ * This is bitwrench's answer to Playwright/Chrome DevTools — a
13
+ * built-in inspector that speaks the bwserve protocol.
14
+ *
15
+ * @module cli/attach
16
+ */
17
+
18
+ import { parseArgs } from 'node:util';
19
+ import { createInterface } from 'node:readline';
20
+ import { writeFileSync } from 'node:fs';
21
+ import { VERSION } from '../version.js';
22
+
23
+ var ATTACH_USAGE = `
24
+ bwcli attach v${VERSION} — Remote debugging REPL for bitwrench pages
25
+
26
+ Usage:
27
+ bwcli attach [options]
28
+
29
+ Description:
30
+ Starts a bwserve instance and waits for a browser to connect via the
31
+ drop-in attach script. Once connected, you get an interactive REPL
32
+ where you can evaluate JS expressions, inspect the DOM, take
33
+ screenshots, and listen to events — all from your terminal.
34
+
35
+ To connect a page, either:
36
+ 1. Add <script src="http://localhost:<port>/bw/attach.js"></script>
37
+ 2. Paste the script URL into devtools console:
38
+ var s=document.createElement('script');s.src='http://localhost:<port>/bw/attach.js';document.head.appendChild(s);
39
+
40
+ Options:
41
+ -p, --port <number> Server port (default: 7902)
42
+ --allow-screenshot Enable /screenshot command (loads html2canvas in browser)
43
+ -v, --verbose Verbose output (show protocol details)
44
+ -h, --help Print this help
45
+
46
+ REPL Commands:
47
+ <expression> Evaluate JS in the connected browser (e.g., document.title)
48
+ /help Show command reference
49
+ /quit, /q Exit
50
+ /tree [selector] [depth] Show DOM tree summary (default: body, depth 3)
51
+ /screenshot [sel] [file] Capture screenshot (requires --allow-screenshot)
52
+ /mount <sel> <comp> [json] Mount a BCCL component on the client
53
+ /render <sel> <taco-json> Render a TACO object at selector
54
+ /patch <id> <content> Patch an element's text content
55
+ /listen <sel> <event> Start listening for DOM events (e.g., /listen button click)
56
+ /unlisten <sel> <event> Stop listening for a previously added listener
57
+ /clients List connected clients
58
+ /exec <code> Execute JS without capturing return value
59
+
60
+ Examples:
61
+ bwcli attach Start on default port 7902
62
+ bwcli attach --port 3000 Use custom port
63
+ bwcli attach --allow-screenshot Enable screenshot support
64
+ bwcli attach -v Verbose mode (shows protocol)
65
+
66
+ # In the REPL:
67
+ bw> document.title
68
+ bw> bw.$('.bw-card').length
69
+ bw> /tree #app 2
70
+ bw> /screenshot body page.png
71
+ bw> /listen .bw-btn click
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
81
+ `.trim();
82
+
83
+ /**
84
+ * Wrap a JS expression for client.query().
85
+ * Statements (var, let, const, if, for, etc.) are sent as-is.
86
+ * Expressions are wrapped in return() so the result comes back.
87
+ *
88
+ * @param {string} code - User input
89
+ * @returns {string} Code suitable for new Function()
90
+ */
91
+ export function wrapExpression(code) {
92
+ code = code.trim();
93
+ if (/^(var |let |const |if |for |while |function |try |switch |throw |class |\{)/.test(code)) {
94
+ return code;
95
+ }
96
+ return 'return (' + code + ')';
97
+ }
98
+
99
+ /**
100
+ * Run the attach subcommand.
101
+ * @param {string[]} argv - arguments after "attach"
102
+ * @param {object} [ioOpts] - optional input/output streams for testing
103
+ */
104
+ export function runAttach(argv, ioOpts) {
105
+ var values;
106
+
107
+ try {
108
+ var result = parseArgs({
109
+ args: argv,
110
+ strict: true,
111
+ allowPositionals: false,
112
+ options: {
113
+ port: { type: 'string', short: 'p' },
114
+ 'allow-screenshot': { type: 'boolean' },
115
+ verbose: { type: 'boolean', short: 'v' },
116
+ help: { type: 'boolean', short: 'h' }
117
+ }
118
+ });
119
+ values = result.values;
120
+ } catch (err) {
121
+ console.error('Error: ' + err.message);
122
+ console.error('Run "bwcli attach --help" for usage.');
123
+ process.exit(1);
124
+ }
125
+
126
+ if (values.help) {
127
+ console.log(ATTACH_USAGE);
128
+ return;
129
+ }
130
+
131
+ var port = values.port ? parseInt(values.port, 10) : 7902;
132
+ var allowScreenshot = !!values['allow-screenshot'];
133
+ var verbose = !!values.verbose;
134
+
135
+ if (isNaN(port) || port < 1 || port > 65535) {
136
+ console.error('Error: --port must be a number between 1 and 65535.');
137
+ process.exit(1);
138
+ }
139
+
140
+ // Dynamic import of bwserve
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 });
145
+ }).catch(function(err) {
146
+ console.error('Failed to load bwserve: ' + err.message);
147
+ process.exit(1);
148
+ });
149
+
150
+ return importPromise;
151
+ }
152
+
153
+ /**
154
+ * Start the attach server and REPL.
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
158
+ */
159
+ export function startAttach(bwserve, opts) {
160
+ var app = bwserve.create({
161
+ port: opts.port,
162
+ title: 'bwcli attach',
163
+ allowExec: true,
164
+ allowScreenshot: opts.allowScreenshot
165
+ });
166
+
167
+ // Track the active client (most recent connection)
168
+ var activeClient = null;
169
+ var clients = new Map();
170
+
171
+ // Register a catch-all page handler for attach connections.
172
+ // Attach clients don't hit a registered page — they connect via SSE directly.
173
+ // We need to handle SSE connections from attach clients by intercepting _handleSSE.
174
+ // Since attach clients generate their own clientId (att_...), we store them when they connect.
175
+
176
+ // Monkey-patch _handleSSE to accept attach clients (those without a pending page record)
177
+ var origHandleSSE = app._handleSSE.bind(app);
178
+ app._handleSSE = function(req, res, clientId) {
179
+ // If no pending record exists, this is an attach client
180
+ if (!app._clients.has(clientId)) {
181
+ app._clients.set(clientId, { pagePath: '/_attach', client: null });
182
+ }
183
+ origHandleSSE(req, res, clientId);
184
+
185
+ // After original handler runs, grab the client
186
+ var record = app._clients.get(clientId);
187
+ if (record && record.client) {
188
+ var client = record.client;
189
+ client._allowScreenshot = opts.allowScreenshot;
190
+ clients.set(clientId, client);
191
+
192
+ // Set as active client
193
+ activeClient = client;
194
+
195
+ // Print connection message
196
+ process.stdout.write('\r\x1b[K');
197
+ console.log('[connected] client ' + clientId);
198
+ safePrompt(true);
199
+
200
+ // Listen for events from _bw_listen
201
+ client.on('_bw_event', function(data) {
202
+ process.stdout.write('\r\x1b[K');
203
+ console.log('[event] ' + data.event + ' on ' + data.selector +
204
+ ' \u2192 ' + data.tagName + (data.id ? '#' + data.id : '') +
205
+ (data.text ? ' "' + data.text.slice(0, 50) + '"' : ''));
206
+ safePrompt(true);
207
+ });
208
+
209
+ // Handle disconnect
210
+ req.on('close', function() {
211
+ clients.delete(clientId);
212
+ if (activeClient === client) {
213
+ // Switch to another client or null
214
+ activeClient = clients.size > 0 ? clients.values().next().value : null;
215
+ }
216
+ process.stdout.write('\r\x1b[K');
217
+ console.log('[disconnected] client ' + clientId);
218
+ if (activeClient) {
219
+ console.log('[active] client ' + activeClient.id);
220
+ } else {
221
+ console.log('Waiting for connection...');
222
+ }
223
+ safePrompt(true);
224
+ });
225
+ }
226
+ };
227
+
228
+ // Start the server
229
+ app.listen(function() {
230
+ var url = 'http://localhost:' + opts.port;
231
+ console.log('bwcli attach v' + VERSION);
232
+ console.log(' Server: ' + url);
233
+ console.log(' Drop-in: <script src="' + url + '/bw/attach.js"></script>');
234
+ if (opts.allowScreenshot) {
235
+ console.log(' Screenshot: enabled');
236
+ }
237
+ console.log('');
238
+ console.log('Waiting for connection...');
239
+ console.log('Type /help for commands, /quit to exit.');
240
+ console.log('');
241
+ safePrompt();
242
+ });
243
+
244
+ // Create readline REPL
245
+ var rl = createInterface({
246
+ input: opts.input || process.stdin,
247
+ output: opts.output || process.stdout,
248
+ prompt: 'bw> '
249
+ });
250
+ var rlClosed = false;
251
+
252
+ function safePrompt(preserveCursor) {
253
+ if (!rlClosed) rl.prompt(preserveCursor);
254
+ }
255
+
256
+ rl.on('line', function(line) {
257
+ line = line.trim();
258
+ if (!line) {
259
+ safePrompt();
260
+ return;
261
+ }
262
+
263
+ // Slash commands
264
+ if (line.charAt(0) === '/') {
265
+ handleSlashCommand(line, activeClient, clients, opts, rl);
266
+ return;
267
+ }
268
+
269
+ // JS expression — requires active client
270
+ if (!activeClient) {
271
+ console.log('No client connected. Add the attach script to a page first.');
272
+ safePrompt();
273
+ return;
274
+ }
275
+
276
+ var code = wrapExpression(line);
277
+ if (opts.verbose) {
278
+ console.log('[query] ' + code);
279
+ }
280
+
281
+ activeClient.query(code, { timeout: 10000 }).then(function(result) {
282
+ if (result !== undefined && result !== null) {
283
+ try {
284
+ console.log(typeof result === 'string' ? result : JSON.stringify(result, null, 2));
285
+ } catch (e) {
286
+ console.log(String(result));
287
+ }
288
+ } else {
289
+ console.log('undefined');
290
+ }
291
+ safePrompt();
292
+ }).catch(function(err) {
293
+ console.error('[error] ' + err.message);
294
+ safePrompt();
295
+ });
296
+ });
297
+
298
+ rl.on('close', function() {
299
+ rlClosed = true;
300
+ console.log('\nExiting.');
301
+ app.close().then(function() {
302
+ process.exit(0);
303
+ });
304
+ });
305
+
306
+ return { rl: rl, app: app };
307
+ }
308
+
309
+ /**
310
+ * Handle slash commands in the REPL.
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
316
+ */
317
+ export function handleSlashCommand(line, activeClient, clients, opts, rl) {
318
+ var parts = line.split(/\s+/);
319
+ var cmd = parts[0].toLowerCase();
320
+
321
+ switch (cmd) {
322
+ case '/help':
323
+ case '/h':
324
+ printHelp();
325
+ rl.prompt();
326
+ break;
327
+
328
+ case '/quit':
329
+ case '/q':
330
+ rl.close();
331
+ break;
332
+
333
+ case '/clients':
334
+ if (clients.size === 0) {
335
+ console.log('No clients connected.');
336
+ } else {
337
+ for (var [id, c] of clients) {
338
+ var marker = c === activeClient ? ' (active)' : '';
339
+ console.log(' ' + id + marker + (c._closed ? ' [closed]' : ''));
340
+ }
341
+ }
342
+ rl.prompt();
343
+ break;
344
+
345
+ case '/tree':
346
+ if (!activeClient) {
347
+ console.log('No client connected.');
348
+ rl.prompt();
349
+ break;
350
+ }
351
+ var treeSel = parts[1] || 'body';
352
+ var treeDepth = parts[2] ? parseInt(parts[2], 10) : 3;
353
+ var treePend = activeClient._pend(10000);
354
+ activeClient.call('_bw_tree', {
355
+ selector: treeSel,
356
+ depth: treeDepth,
357
+ requestId: treePend.requestId
358
+ });
359
+ treePend.promise.then(function(result) {
360
+ if (!result) {
361
+ console.log('(no element found for "' + treeSel + '")');
362
+ } else {
363
+ printTree(result, 0);
364
+ }
365
+ rl.prompt();
366
+ }).catch(function(err) {
367
+ console.error('[error] ' + err.message);
368
+ rl.prompt();
369
+ });
370
+ break;
371
+
372
+ case '/screenshot':
373
+ if (!activeClient) {
374
+ console.log('No client connected.');
375
+ rl.prompt();
376
+ break;
377
+ }
378
+ if (!opts.allowScreenshot) {
379
+ console.log('Screenshot not enabled. Restart with --allow-screenshot.');
380
+ rl.prompt();
381
+ break;
382
+ }
383
+ var ssSel = parts[1] || 'body';
384
+ var ssFile = parts[2] || 'screenshot-' + Date.now() + '.png';
385
+ console.log('Capturing ' + ssSel + ' ...');
386
+ activeClient.screenshot(ssSel, { timeout: 15000 }).then(function(result) {
387
+ writeFileSync(ssFile, result.data);
388
+ console.log('Saved: ' + ssFile + ' (' + result.width + 'x' + result.height + ', ' + result.data.length + ' bytes)');
389
+ rl.prompt();
390
+ }).catch(function(err) {
391
+ console.error('[error] ' + err.message);
392
+ rl.prompt();
393
+ });
394
+ break;
395
+
396
+ case '/mount':
397
+ if (!activeClient) {
398
+ console.log('No client connected.');
399
+ rl.prompt();
400
+ break;
401
+ }
402
+ if (parts.length < 3) {
403
+ console.log('Usage: /mount <selector> <component> [json-props]');
404
+ rl.prompt();
405
+ break;
406
+ }
407
+ var mountSel = parts[1];
408
+ var mountComp = parts[2];
409
+ var mountProps = {};
410
+ if (parts[3]) {
411
+ try {
412
+ mountProps = JSON.parse(parts.slice(3).join(' '));
413
+ } catch (e) {
414
+ console.error('Invalid JSON props: ' + e.message);
415
+ rl.prompt();
416
+ break;
417
+ }
418
+ }
419
+ activeClient.mount(mountSel, mountComp, mountProps, { timeout: 10000 }).then(function() {
420
+ console.log('Mounted ' + mountComp + ' at ' + mountSel);
421
+ rl.prompt();
422
+ }).catch(function(err) {
423
+ console.error('[error] ' + err.message);
424
+ rl.prompt();
425
+ });
426
+ break;
427
+
428
+ case '/render':
429
+ if (!activeClient) {
430
+ console.log('No client connected.');
431
+ rl.prompt();
432
+ break;
433
+ }
434
+ if (parts.length < 3) {
435
+ console.log('Usage: /render <selector> <taco-json>');
436
+ rl.prompt();
437
+ break;
438
+ }
439
+ var renderSel = parts[1];
440
+ var renderTaco;
441
+ try {
442
+ renderTaco = JSON.parse(parts.slice(2).join(' '));
443
+ } catch (e) {
444
+ console.error('Invalid TACO JSON: ' + e.message);
445
+ rl.prompt();
446
+ break;
447
+ }
448
+ activeClient.render(renderSel, renderTaco);
449
+ console.log('Rendered at ' + renderSel);
450
+ rl.prompt();
451
+ break;
452
+
453
+ case '/patch':
454
+ if (!activeClient) {
455
+ console.log('No client connected.');
456
+ rl.prompt();
457
+ break;
458
+ }
459
+ if (parts.length < 3) {
460
+ console.log('Usage: /patch <id> <content>');
461
+ rl.prompt();
462
+ break;
463
+ }
464
+ var patchId = parts[1];
465
+ var patchContent = parts.slice(2).join(' ');
466
+ activeClient.patch(patchId, patchContent);
467
+ console.log('Patched ' + patchId);
468
+ rl.prompt();
469
+ break;
470
+
471
+ case '/listen':
472
+ if (!activeClient) {
473
+ console.log('No client connected.');
474
+ rl.prompt();
475
+ break;
476
+ }
477
+ if (parts.length < 3) {
478
+ console.log('Usage: /listen <selector> <event>');
479
+ console.log('Examples: /listen button click, /listen .card mouseover');
480
+ rl.prompt();
481
+ break;
482
+ }
483
+ activeClient.call('_bw_listen', {
484
+ selector: parts[1],
485
+ event: parts[2]
486
+ });
487
+ console.log('Listening for ' + parts[2] + ' on ' + parts[1]);
488
+ rl.prompt();
489
+ break;
490
+
491
+ case '/unlisten':
492
+ if (!activeClient) {
493
+ console.log('No client connected.');
494
+ rl.prompt();
495
+ break;
496
+ }
497
+ if (parts.length < 3) {
498
+ console.log('Usage: /unlisten <selector> <event>');
499
+ rl.prompt();
500
+ break;
501
+ }
502
+ activeClient.call('_bw_unlisten', {
503
+ selector: parts[1],
504
+ event: parts[2]
505
+ });
506
+ console.log('Stopped listening for ' + parts[2] + ' on ' + parts[1]);
507
+ rl.prompt();
508
+ break;
509
+
510
+ case '/exec':
511
+ if (!activeClient) {
512
+ console.log('No client connected.');
513
+ rl.prompt();
514
+ break;
515
+ }
516
+ if (parts.length < 2) {
517
+ console.log('Usage: /exec <code>');
518
+ rl.prompt();
519
+ break;
520
+ }
521
+ activeClient.exec(parts.slice(1).join(' '));
522
+ console.log('Executed.');
523
+ rl.prompt();
524
+ break;
525
+
526
+ default:
527
+ console.log('Unknown command: ' + cmd + '. Type /help for available commands.');
528
+ rl.prompt();
529
+ break;
530
+ }
531
+ }
532
+
533
+ /**
534
+ * Pretty-print a DOM tree from _bw_tree.
535
+ * @param {object} node - Tree node with tag, id, cls, children
536
+ * @param {number} indent - Current indentation level
537
+ */
538
+ export function printTree(node, indent) {
539
+ if (!node) return;
540
+ var prefix = ' '.repeat(indent);
541
+ var label = node.tag || '?';
542
+ if (node.id) label += '#' + node.id;
543
+ if (node.cls) label += '.' + node.cls.split(' ').join('.');
544
+ console.log(prefix + label);
545
+ if (node.children) {
546
+ for (var i = 0; i < node.children.length; i++) {
547
+ printTree(node.children[i], indent + 1);
548
+ }
549
+ }
550
+ }
551
+
552
+ /**
553
+ * Print the REPL help reference.
554
+ */
555
+ export function printHelp() {
556
+ console.log([
557
+ '',
558
+ 'bwcli attach — REPL Commands',
559
+ '',
560
+ ' <expression> Evaluate JS in the browser and print result',
561
+ ' Examples: document.title, bw.$(".card").length',
562
+ '',
563
+ ' /help, /h Show this help',
564
+ ' /quit, /q Exit',
565
+ '',
566
+ ' /tree [sel] [depth] Show DOM tree (default: body, depth 3)',
567
+ ' /screenshot [sel] [file] Capture screenshot to PNG file',
568
+ ' Requires --allow-screenshot flag',
569
+ ' /mount <sel> <comp> [json] Mount a BCCL component',
570
+ ' Example: /mount #app card {"title":"Hello"}',
571
+ ' /render <sel> <taco> Render TACO JSON at selector',
572
+ ' Example: /render #app {"t":"h1","c":"Hi"}',
573
+ ' /patch <id> <content> Update element text content',
574
+ ' Example: /patch counter 42',
575
+ ' /listen <sel> <event> Listen for DOM events (prints inline)',
576
+ ' Example: /listen button click',
577
+ ' /unlisten <sel> <event> Remove a listener',
578
+ ' /exec <code> Execute JS without capturing return value',
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"}',
585
+ ''
586
+ ].join('\n'));
587
+ }
@@ -67,7 +67,7 @@ function resolveInjectionMode(flags) {
67
67
  /**
68
68
  * Resolve theme config from --theme flag value
69
69
  * @param {string} themeValue - Preset name or hex colors ("primary,secondary" or "primary,secondary,tertiary")
70
- * @returns {Object} Config for bw.generateTheme
70
+ * @returns {Object} Config for bw.makeStyles
71
71
  */
72
72
  function resolveTheme(themeValue) {
73
73
  if (!themeValue) return null;
@@ -165,10 +165,7 @@ export function convertFile(inputPath, flags = {}) {
165
165
  if (flags.theme) {
166
166
  const themeConfig = resolveTheme(flags.theme);
167
167
  if (themeConfig) {
168
- const result = bw.generateTheme('', {
169
- ...themeConfig,
170
- inject: false
171
- });
168
+ const result = bw.makeStyles(themeConfig);
172
169
  css += '\n' + result.css;
173
170
  }
174
171
  }
package/src/cli/index.js CHANGED
@@ -11,6 +11,7 @@ import { parseArgs } from 'node:util';
11
11
  import { VERSION } from '../version.js';
12
12
  import { convertFile } from './convert.js';
13
13
  import { runServe } from './serve.js';
14
+ import { runAttach } from './attach.js';
14
15
 
15
16
  const USAGE = `
16
17
  bwcli v${VERSION} — bitwrench command-line tool
@@ -18,6 +19,7 @@ bwcli v${VERSION} — bitwrench command-line tool
18
19
  Usage:
19
20
  bwcli <file> [options] Convert a file to styled HTML
20
21
  bwcli serve [dir] [options] Start bwserve development server
22
+ bwcli attach [options] Start attach server for remote debugging
21
23
  bwcli --version Print version
22
24
  bwcli --help Print this help
23
25
 
@@ -52,6 +54,8 @@ Examples:
52
54
  bwcli doc.md --theme "#336699,#cc6633" Custom theme colors
53
55
  bwcli serve Serve current directory on port 7902
54
56
  bwcli serve ./site --port 8080 Serve ./site on port 8080
57
+ bwcli attach Start remote debugging REPL on port 7902
58
+ bwcli attach --port 3000 Attach on custom port
55
59
  `.trim();
56
60
 
57
61
  /**
@@ -60,6 +64,9 @@ Examples:
60
64
  */
61
65
  export function run(argv) {
62
66
  // Check for subcommand before parseArgs (subcommands have different options)
67
+ if (argv.length > 0 && argv[0] === 'attach') {
68
+ return runAttach(argv.slice(1));
69
+ }
63
70
  if (argv.length > 0 && argv[0] === 'serve') {
64
71
  return runServe(argv.slice(1));
65
72
  }
package/src/cli/inject.js CHANGED
@@ -48,7 +48,7 @@ export function getInjectionHead(mode) {
48
48
  */
49
49
  export function getInjectionBodyEnd(mode) {
50
50
  if (mode === 'standalone' || mode === 'cdn') {
51
- return `<script>if(typeof bw!=='undefined'){bw.loadDefaultStyles();}</script>`;
51
+ return `<script>if(typeof bw!=='undefined'){bw.loadStyles();}</script>`;
52
52
  }
53
53
  // mode === 'none'
54
54
  return '';