bitwrench 2.0.24 → 2.0.30
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/README.md +17 -9
- package/dist/bitwrench-bccl.cjs.js +1 -1
- package/dist/bitwrench-bccl.cjs.min.js +1 -1
- package/dist/bitwrench-bccl.cjs.min.js.gz +0 -0
- package/dist/bitwrench-bccl.esm.js +1 -1
- package/dist/bitwrench-bccl.esm.min.js +1 -1
- package/dist/bitwrench-bccl.esm.min.js.gz +0 -0
- package/dist/bitwrench-bccl.umd.js +1 -1
- package/dist/bitwrench-bccl.umd.min.js +1 -1
- package/dist/bitwrench-bccl.umd.min.js.gz +0 -0
- package/dist/bitwrench-code-edit.cjs.js +1 -1
- package/dist/bitwrench-code-edit.cjs.min.js +1 -1
- package/dist/bitwrench-code-edit.es5.js +1 -1
- package/dist/bitwrench-code-edit.es5.min.js +1 -1
- package/dist/bitwrench-code-edit.esm.js +1 -1
- package/dist/bitwrench-code-edit.esm.min.js +1 -1
- package/dist/bitwrench-code-edit.umd.js +1 -1
- package/dist/bitwrench-code-edit.umd.min.js +1 -1
- package/dist/bitwrench-code-edit.umd.min.js.gz +0 -0
- package/dist/bitwrench-debug.js +1 -1
- package/dist/bitwrench-debug.min.js +1 -1
- package/dist/bitwrench-lean.cjs.js +661 -174
- package/dist/bitwrench-lean.cjs.min.js +7 -7
- package/dist/bitwrench-lean.cjs.min.js.gz +0 -0
- package/dist/bitwrench-lean.es5.js +690 -178
- package/dist/bitwrench-lean.es5.min.js +5 -5
- package/dist/bitwrench-lean.es5.min.js.gz +0 -0
- package/dist/bitwrench-lean.esm.js +661 -174
- package/dist/bitwrench-lean.esm.min.js +6 -6
- package/dist/bitwrench-lean.esm.min.js.gz +0 -0
- package/dist/bitwrench-lean.umd.js +661 -174
- package/dist/bitwrench-lean.umd.min.js +7 -7
- package/dist/bitwrench-lean.umd.min.js.gz +0 -0
- package/dist/bitwrench-util-css.cjs.js +1 -1
- package/dist/bitwrench-util-css.cjs.min.js +1 -1
- package/dist/bitwrench-util-css.es5.js +1 -1
- package/dist/bitwrench-util-css.es5.min.js +1 -1
- package/dist/bitwrench-util-css.esm.js +1 -1
- package/dist/bitwrench-util-css.esm.min.js +1 -1
- package/dist/bitwrench-util-css.umd.js +1 -1
- package/dist/bitwrench-util-css.umd.min.js +1 -1
- package/dist/bitwrench-util-css.umd.min.js.gz +0 -0
- package/dist/bitwrench.cjs.js +659 -172
- package/dist/bitwrench.cjs.min.js +6 -6
- package/dist/bitwrench.cjs.min.js.gz +0 -0
- package/dist/bitwrench.css +6 -6
- package/dist/bitwrench.d.ts +666 -0
- package/dist/bitwrench.es5.js +687 -175
- package/dist/bitwrench.es5.min.js +6 -6
- package/dist/bitwrench.es5.min.js.gz +0 -0
- package/dist/bitwrench.esm.js +659 -172
- package/dist/bitwrench.esm.min.js +5 -5
- package/dist/bitwrench.esm.min.js.gz +0 -0
- package/dist/bitwrench.min.css +1 -1
- package/dist/bitwrench.umd.js +659 -172
- package/dist/bitwrench.umd.min.js +6 -6
- package/dist/bitwrench.umd.min.js.gz +0 -0
- package/dist/builds.json +96 -96
- package/dist/bwserve.cjs.js +140 -7
- package/dist/bwserve.esm.js +141 -8
- package/dist/sri.json +46 -46
- package/docs/README.md +5 -3
- package/docs/bitwrench-for-wasm.md +851 -0
- package/docs/bitwrench-mcp.md +1 -1
- package/docs/bitwrench-taco-schema-discussion.md +694 -0
- package/docs/bitwrench_api.md +134 -24
- package/docs/bitwrench_typescript_usage.md +441 -0
- package/docs/component-cheatsheet.md +1 -1
- package/docs/framework-translation-table.md +1 -1
- package/docs/llm-bitwrench-guide.md +34 -6
- package/docs/routing.md +1 -1
- package/docs/state-management.md +27 -3
- package/docs/thinking-in-bitwrench.md +6 -5
- package/docs/tutorial-bwserve.md +1 -1
- package/docs/tutorial-website.md +1 -1
- package/package.json +16 -10
- package/readme.html +29 -14
- package/src/bitwrench-styles.js +17 -17
- package/src/bitwrench.d.ts +666 -0
- package/src/bitwrench.js +638 -150
- package/src/bwserve/bwclient.js +3 -3
- package/src/bwserve/client.js +26 -0
- package/src/bwserve/index.js +110 -3
- package/src/cli/attach.js +7 -5
- package/src/cli/serve.js +53 -9
- package/src/mcp/live.js +3 -1
- package/src/mcp/server.js +7 -7
- package/src/version.js +3 -3
package/src/bwserve/bwclient.js
CHANGED
|
@@ -86,8 +86,8 @@ var BWCLIENT_SOURCE = '(function(bw) {\n'
|
|
|
86
86
|
+ ' // ── Register built-in functions ──\n'
|
|
87
87
|
+ ' _client._registerBuiltins = function() {\n'
|
|
88
88
|
+ ' var builtins = {\n'
|
|
89
|
-
+ ' scrollTo: "function(sel){var el=bw.
|
|
90
|
-
+ ' focus: "function(sel){var el=bw.
|
|
89
|
+
+ ' scrollTo: "function(sel){var el=bw.el(sel);if(el)el.scrollTop=el.scrollHeight;}",\n'
|
|
90
|
+
+ ' focus: "function(sel){var el=bw.el(sel);if(el&&typeof el.focus===\\"function\\")el.focus();}",\n'
|
|
91
91
|
+ ' download: "function(fn,c,m){if(typeof document===\\"undefined\\")return;var b=new Blob([c],{type:m||\\"text/plain\\"});var a=document.createElement(\\"a\\");a.href=URL.createObjectURL(b);a.download=fn;a.click();URL.revokeObjectURL(a.href);}",\n'
|
|
92
92
|
+ ' clipboard: "function(t){if(typeof navigator!==\\"undefined\\"&&navigator.clipboard)navigator.clipboard.writeText(t);}",\n'
|
|
93
93
|
+ ' redirect: "function(u){if(typeof window!==\\"undefined\\")window.location.href=u;}",\n'
|
|
@@ -95,7 +95,7 @@ var BWCLIENT_SOURCE = '(function(bw) {\n'
|
|
|
95
95
|
+ ' _bw_query: "function(opts){if(!bw._bwClient)return;try{var r=new Function(opts.code)();if(r&&typeof r.then===\\"function\\"){r.then(function(v){bw._bwClient.respond(\\"query\\",opts.requestId,v);}).catch(function(e){bw._bwClient.respond(\\"query\\",opts.requestId,null,e.message);});}else{bw._bwClient.respond(\\"query\\",opts.requestId,r);}}catch(e){bw._bwClient.respond(\\"query\\",opts.requestId,null,e.message);}}",\n'
|
|
96
96
|
+ ' _bw_mount: "function(opts){if(!bw._bwClient)return;try{var taco;var f=opts.factory;var n=f.replace(/-([a-z])/g,function(_,c){return c.toUpperCase();});if(bw.BCCL&&bw.BCCL[n]){taco=bw.make(n,opts.props||{});}else if(bw._allowExec){taco=new Function(\\"props\\",f)(opts.props||{});}else{throw new Error(\\"Unknown component and allowExec disabled\\");}bw.DOM(opts.target,taco);bw._bwClient.respond(\\"mount\\",opts.requestId,{mounted:true});}catch(e){bw._bwClient.respond(\\"mount\\",opts.requestId,null,e.message);}}",\n'
|
|
97
97
|
+ ' _bw_screenshot: "function(opts){if(!bw._bwClient)return;var sel=opts.selector||\\"body\\";var el=document.querySelector(sel);if(!el){bw._bwClient.respond(\\"screenshot\\",opts.requestId,null,\\"Element not found: \\"+sel);return;}function _ls(url){return new Promise(function(res,rej){var s=document.createElement(\\"script\\");s.src=url;s.onload=function(){res(window.html2canvas);};s.onerror=function(){rej(new Error(\\"Failed to load html2canvas\\"));};document.head.appendChild(s);});}var p=window.html2canvas?Promise.resolve(window.html2canvas):_ls(opts.captureUrl||\\"/bw/lib/vendor/html2canvas.min.js\\");p.then(function(h2c){return h2c(el,{scale:opts.scale||1,useCORS:true});}).then(function(canvas){var out=canvas;var mw=opts.maxWidth;var mh=opts.maxHeight;if((mw&&canvas.width>mw)||(mh&&canvas.height>mh)){var sw=mw?mw/canvas.width:1;var sh=mh?mh/canvas.height:1;var sc=Math.min(sw,sh);out=document.createElement(\\"canvas\\");out.width=Math.round(canvas.width*sc);out.height=Math.round(canvas.height*sc);out.getContext(\\"2d\\").drawImage(canvas,0,0,out.width,out.height);}var fmt=opts.format===\\"jpeg\\"?\\"image/jpeg\\":\\"image/png\\";var q=opts.format===\\"jpeg\\"?(opts.quality||0.85):undefined;var dataUrl=out.toDataURL(fmt,q);bw._bwClient.respond(\\"screenshot\\",opts.requestId,{data:dataUrl,width:out.width,height:out.height,format:opts.format||\\"png\\"});}).catch(function(err){bw._bwClient.respond(\\"screenshot\\",opts.requestId,null,err.message||String(err));});}",\n'
|
|
98
|
-
+ ' _bw_tree: "function(opts){if(!bw._bwClient)return;var sel=opts.selector||\\"body\\";var depth=opts.depth||3;function walk(el,d){if(!el||d>depth)return null;var info={tag:el.tagName?el.tagName.toLowerCase():\\"#text\\"};if(el.id)info.id=el.id;if(el.className&&typeof el.className===\\"string\\")info.cls=el.className.split(\\" \\").slice(0,5).join(\\" \\");if(el.children&&el.children.length>0&&d<depth){info.children=[];for(var i=0;i<Math.min(el.children.length,20);i++){var c=walk(el.children[i],d+1);if(c)info.children.push(c);}}return info;}
|
|
98
|
+
+ ' _bw_tree: "function(opts){if(!bw._bwClient)return;var sel=opts.selector||\\"body\\";var depth=opts.depth||3;var root=document.querySelector(sel);if(typeof bw.inspect===\\"function\\"&&bw.inspect.length===2){bw._bwClient.respond(\\"query\\",opts.requestId,bw.inspect(root,depth));return;}function walk(el,d){if(!el||d>depth)return null;var info={tag:el.tagName?el.tagName.toLowerCase():\\"#text\\"};if(el.id)info.id=el.id;if(el.className&&typeof el.className===\\"string\\")info.cls=el.className.split(\\" \\").slice(0,5).join(\\" \\");if(el.children&&el.children.length>0&&d<depth){info.children=[];for(var i=0;i<Math.min(el.children.length,20);i++){var c=walk(el.children[i],d+1);if(c)info.children.push(c);}}return info;}bw._bwClient.respond(\\"query\\",opts.requestId,walk(root,0));}",\n'
|
|
99
99
|
+ ' _bw_listen: "function(opts){if(!bw._bwClient)return;if(!bw._bwClient._listeners)bw._bwClient._listeners={};var key=opts.selector+\\":::\\"+opts.event;if(bw._bwClient._listeners[key])return;var fn=function(e){var el=e.target.closest?e.target.closest(opts.selector):null;if(!el)return;bw._bwClient.respond(\\"event\\",null,{event:opts.event,selector:opts.selector,tagName:el.tagName,id:el.id||null,text:(el.textContent||\\"\\").slice(0,100)});};document.addEventListener(opts.event,fn,true);bw._bwClient._listeners[key]={fn:fn,event:opts.event};}",\n'
|
|
100
100
|
+ ' _bw_unlisten: "function(opts){if(!bw._bwClient||!bw._bwClient._listeners)return;var key=opts.selector+\\":::\\"+opts.event;var entry=bw._bwClient._listeners[key];if(!entry)return;document.removeEventListener(entry.event,entry.fn,true);delete bw._bwClient._listeners[key];}"\n'
|
|
101
101
|
+ ' };\n'
|
package/src/bwserve/client.js
CHANGED
|
@@ -263,6 +263,32 @@ export class BwServeClient {
|
|
|
263
263
|
return pend.promise;
|
|
264
264
|
}
|
|
265
265
|
|
|
266
|
+
// ── Inspect ──
|
|
267
|
+
|
|
268
|
+
/**
|
|
269
|
+
* Inspect the DOM tree of the connected client.
|
|
270
|
+
*
|
|
271
|
+
* Calls the `_bw_tree` builtin on the client which delegates to
|
|
272
|
+
* `bw.inspect()` when available, returning a plain-object tree with
|
|
273
|
+
* bitwrench metadata (tag, uuid, type, handles, state, children).
|
|
274
|
+
*
|
|
275
|
+
* @param {string} [selector='body'] - CSS selector of root element
|
|
276
|
+
* @param {Object} [options]
|
|
277
|
+
* @param {number} [options.depth=3] - Max recursion depth
|
|
278
|
+
* @param {number} [options.timeout=10000] - Timeout in ms
|
|
279
|
+
* @returns {Promise<Object|null>} Tree object, or null if element not found
|
|
280
|
+
*/
|
|
281
|
+
inspect(selector, options) {
|
|
282
|
+
var opts = options || {};
|
|
283
|
+
var pend = this._pend(opts.timeout || 10000);
|
|
284
|
+
this.call('_bw_tree', {
|
|
285
|
+
selector: selector || 'body',
|
|
286
|
+
depth: opts.depth || 3,
|
|
287
|
+
requestId: pend.requestId
|
|
288
|
+
});
|
|
289
|
+
return pend.promise;
|
|
290
|
+
}
|
|
291
|
+
|
|
266
292
|
// ── Screenshot ──
|
|
267
293
|
|
|
268
294
|
/**
|
package/src/bwserve/index.js
CHANGED
|
@@ -25,7 +25,7 @@ import { VERSION } from '../version.js';
|
|
|
25
25
|
import { fileURLToPath } from 'url';
|
|
26
26
|
import { dirname, resolve, join, extname } from 'path';
|
|
27
27
|
import { createServer } from 'http';
|
|
28
|
-
import { readFileSync, existsSync, statSync } from 'fs';
|
|
28
|
+
import { readFileSync, readdirSync, existsSync, statSync } from 'fs';
|
|
29
29
|
|
|
30
30
|
var __dirname = dirname(fileURLToPath(import.meta.url));
|
|
31
31
|
|
|
@@ -54,7 +54,21 @@ var MIME_TYPES = {
|
|
|
54
54
|
'.woff': 'font/woff',
|
|
55
55
|
'.woff2': 'font/woff2',
|
|
56
56
|
'.ttf': 'font/ttf',
|
|
57
|
-
'.map': 'application/json'
|
|
57
|
+
'.map': 'application/json',
|
|
58
|
+
'.txt': 'text/plain; charset=utf-8',
|
|
59
|
+
'.xml': 'application/xml; charset=utf-8',
|
|
60
|
+
'.pdf': 'application/pdf',
|
|
61
|
+
'.zip': 'application/zip',
|
|
62
|
+
'.gz': 'application/gzip',
|
|
63
|
+
'.mp3': 'audio/mpeg',
|
|
64
|
+
'.mp4': 'video/mp4',
|
|
65
|
+
'.webm': 'video/webm',
|
|
66
|
+
'.webp': 'image/webp',
|
|
67
|
+
'.avif': 'image/avif',
|
|
68
|
+
'.wasm': 'application/wasm',
|
|
69
|
+
'.csv': 'text/csv; charset=utf-8',
|
|
70
|
+
'.md': 'text/markdown; charset=utf-8',
|
|
71
|
+
'.mjs': 'application/javascript; charset=utf-8'
|
|
58
72
|
};
|
|
59
73
|
|
|
60
74
|
/**
|
|
@@ -67,6 +81,8 @@ var MIME_TYPES = {
|
|
|
67
81
|
* @param {boolean} [opts.injectBitwrench=true] - Auto-inject bitwrench client JS
|
|
68
82
|
* @param {string|Object} [opts.theme] - Theme preset name or config object
|
|
69
83
|
* @param {boolean} [opts.allowScreenshot=false] - Enable client.screenshot() capability
|
|
84
|
+
* @param {boolean} [opts.dirList=true] - Enable directory listings when no index.html
|
|
85
|
+
* @param {string} [opts.host='0.0.0.0'] - Host/address to bind to
|
|
70
86
|
* @returns {BwServeApp} Application instance
|
|
71
87
|
*/
|
|
72
88
|
export function create(opts) {
|
|
@@ -87,6 +103,8 @@ class BwServeApp {
|
|
|
87
103
|
this.theme = opts.theme || null;
|
|
88
104
|
this.allowExec = opts.allowExec || false;
|
|
89
105
|
this.allowScreenshot = opts.allowScreenshot || false;
|
|
106
|
+
this.dirList = opts.dirList !== false;
|
|
107
|
+
this.host = opts.host || '0.0.0.0';
|
|
90
108
|
this.keepAliveInterval = opts.keepAliveInterval || 15000;
|
|
91
109
|
this._pages = new Map();
|
|
92
110
|
this._clients = new Map();
|
|
@@ -120,7 +138,7 @@ class BwServeApp {
|
|
|
120
138
|
self._handleRequest(req, rawRes);
|
|
121
139
|
});
|
|
122
140
|
|
|
123
|
-
self._server.listen(self.port, function() {
|
|
141
|
+
self._server.listen(self.port, self.host, function() {
|
|
124
142
|
if (callback) callback();
|
|
125
143
|
res();
|
|
126
144
|
});
|
|
@@ -284,6 +302,32 @@ class BwServeApp {
|
|
|
284
302
|
res.end(content);
|
|
285
303
|
return;
|
|
286
304
|
}
|
|
305
|
+
// Directory index resolution: /foo/ => /foo/index.html
|
|
306
|
+
if (path.endsWith('/')) {
|
|
307
|
+
var indexPath = join(this.staticDir, path, 'index.html');
|
|
308
|
+
if (existsSync(indexPath) && statSync(indexPath).isFile()) {
|
|
309
|
+
var indexContent = readFileSync(indexPath);
|
|
310
|
+
res.writeHead(200, { 'Content-Type': 'text/html; charset=utf-8' });
|
|
311
|
+
res.end(indexContent);
|
|
312
|
+
return;
|
|
313
|
+
}
|
|
314
|
+
// Directory listing when no index.html
|
|
315
|
+
var dirPath = join(this.staticDir, path);
|
|
316
|
+
if (this.dirList && existsSync(dirPath) && statSync(dirPath).isDirectory()) {
|
|
317
|
+
var listing = this._generateDirListing(path, dirPath);
|
|
318
|
+
res.writeHead(200, { 'Content-Type': 'text/html; charset=utf-8' });
|
|
319
|
+
res.end(listing);
|
|
320
|
+
return;
|
|
321
|
+
}
|
|
322
|
+
}
|
|
323
|
+
// Bare directory without trailing slash: /foo => 301 to /foo/
|
|
324
|
+
if (!path.endsWith('/') && existsSync(filePath) && statSync(filePath).isDirectory()) {
|
|
325
|
+
var qs = url.split('?')[1];
|
|
326
|
+
var location = path + '/' + (qs ? '?' + qs : '');
|
|
327
|
+
res.writeHead(301, { 'Location': location });
|
|
328
|
+
res.end();
|
|
329
|
+
return;
|
|
330
|
+
}
|
|
287
331
|
}
|
|
288
332
|
|
|
289
333
|
// 404
|
|
@@ -453,6 +497,69 @@ class BwServeApp {
|
|
|
453
497
|
});
|
|
454
498
|
res.end(content);
|
|
455
499
|
}
|
|
500
|
+
|
|
501
|
+
/**
|
|
502
|
+
* Generate an HTML directory listing page.
|
|
503
|
+
* @private
|
|
504
|
+
* @param {string} urlPath - URL path (with trailing slash)
|
|
505
|
+
* @param {string} dirPath - Filesystem path to the directory
|
|
506
|
+
* @returns {string} HTML page
|
|
507
|
+
*/
|
|
508
|
+
_generateDirListing(urlPath, dirPath) {
|
|
509
|
+
var entries = readdirSync(dirPath);
|
|
510
|
+
var dirs = [];
|
|
511
|
+
var files = [];
|
|
512
|
+
|
|
513
|
+
for (var i = 0; i < entries.length; i++) {
|
|
514
|
+
var name = entries[i];
|
|
515
|
+
var fullPath = join(dirPath, name);
|
|
516
|
+
try {
|
|
517
|
+
var st = statSync(fullPath);
|
|
518
|
+
if (st.isDirectory()) {
|
|
519
|
+
dirs.push({ name: name + '/', size: '-' });
|
|
520
|
+
} else {
|
|
521
|
+
files.push({ name: name, size: _formatSize(st.size) });
|
|
522
|
+
}
|
|
523
|
+
} catch (e) {
|
|
524
|
+
// Skip entries we cannot stat
|
|
525
|
+
}
|
|
526
|
+
}
|
|
527
|
+
|
|
528
|
+
// Sort alphabetically
|
|
529
|
+
dirs.sort(function(a, b) { return a.name.localeCompare(b.name); });
|
|
530
|
+
files.sort(function(a, b) { return a.name.localeCompare(b.name); });
|
|
531
|
+
|
|
532
|
+
var all = dirs.concat(files);
|
|
533
|
+
|
|
534
|
+
var rows = '';
|
|
535
|
+
// Parent directory link (unless at root)
|
|
536
|
+
if (urlPath !== '/') {
|
|
537
|
+
rows += '<tr><td><a href="../">..</a></td><td>-</td></tr>\n';
|
|
538
|
+
}
|
|
539
|
+
for (var j = 0; j < all.length; j++) {
|
|
540
|
+
var entry = all[j];
|
|
541
|
+
var escaped = entry.name.replace(/&/g, '&').replace(/</g, '<').replace(/>/g, '>').replace(/"/g, '"');
|
|
542
|
+
rows += '<tr><td><a href="' + encodeURIComponent(entry.name.replace(/\/$/, '')) + (entry.size === '-' ? '/' : '') + '">' + escaped + '</a></td><td>' + entry.size + '</td></tr>\n';
|
|
543
|
+
}
|
|
544
|
+
|
|
545
|
+
var escapedPath = urlPath.replace(/&/g, '&').replace(/</g, '<').replace(/>/g, '>');
|
|
546
|
+
return '<!DOCTYPE html>\n<html><head><meta charset="utf-8"><title>Index of ' + escapedPath + '</title>' +
|
|
547
|
+
'<style>body{font-family:monospace;margin:2em}table{border-collapse:collapse}td,th{text-align:left;padding:4px 16px}a{text-decoration:none}a:hover{text-decoration:underline}</style>' +
|
|
548
|
+
'</head><body><h1>Index of ' + escapedPath + '</h1><table><tr><th>Name</th><th>Size</th></tr>\n' +
|
|
549
|
+
rows + '</table></body></html>';
|
|
550
|
+
}
|
|
551
|
+
}
|
|
552
|
+
|
|
553
|
+
/**
|
|
554
|
+
* Format a byte size as a human-readable string.
|
|
555
|
+
* @param {number} bytes
|
|
556
|
+
* @returns {string}
|
|
557
|
+
*/
|
|
558
|
+
function _formatSize(bytes) {
|
|
559
|
+
if (bytes < 1024) return bytes + ' B';
|
|
560
|
+
if (bytes < 1024 * 1024) return (bytes / 1024).toFixed(1) + ' KB';
|
|
561
|
+
if (bytes < 1024 * 1024 * 1024) return (bytes / (1024 * 1024)).toFixed(1) + ' MB';
|
|
562
|
+
return (bytes / (1024 * 1024 * 1024)).toFixed(1) + ' GB';
|
|
456
563
|
}
|
|
457
564
|
|
|
458
565
|
export var version = VERSION;
|
package/src/cli/attach.js
CHANGED
|
@@ -47,7 +47,7 @@ REPL Commands:
|
|
|
47
47
|
<expression> Evaluate JS in the connected browser (e.g., document.title)
|
|
48
48
|
/help Show command reference
|
|
49
49
|
/quit, /q Exit
|
|
50
|
-
/
|
|
50
|
+
/inspect [selector] [depth] Show DOM tree summary (default: body, depth 3)
|
|
51
51
|
/screenshot [sel] [file] Capture screenshot (requires --allow-screenshot)
|
|
52
52
|
/mount <sel> <comp> [json] Mount a BCCL component on the client
|
|
53
53
|
/render <sel> <taco-json> Render a TACO object at selector
|
|
@@ -66,7 +66,7 @@ Examples:
|
|
|
66
66
|
# In the REPL:
|
|
67
67
|
bw> document.title
|
|
68
68
|
bw> bw.$('.bw-card').length
|
|
69
|
-
bw> /
|
|
69
|
+
bw> /inspect #app 2
|
|
70
70
|
bw> /screenshot body page.png
|
|
71
71
|
bw> /listen .bw-btn click
|
|
72
72
|
bw> /mount #app card {"title":"Hello","content":"World"}
|
|
@@ -76,7 +76,7 @@ Examples:
|
|
|
76
76
|
bw> /patch users "Users: 342"
|
|
77
77
|
bw> /patch orders "Orders: 28"
|
|
78
78
|
bw> /mount #app card {"title":"Status","content":"All systems go"}
|
|
79
|
-
bw> /
|
|
79
|
+
bw> /inspect #app 2
|
|
80
80
|
bw> /listen .bw-btn click
|
|
81
81
|
`.trim();
|
|
82
82
|
|
|
@@ -342,7 +342,8 @@ export function handleSlashCommand(line, activeClient, clients, opts, rl) {
|
|
|
342
342
|
rl.prompt();
|
|
343
343
|
break;
|
|
344
344
|
|
|
345
|
-
case '/
|
|
345
|
+
case '/inspect':
|
|
346
|
+
case '/tree': // alias kept for muscle memory
|
|
346
347
|
if (!activeClient) {
|
|
347
348
|
console.log('No client connected.');
|
|
348
349
|
rl.prompt();
|
|
@@ -563,7 +564,8 @@ export function printHelp() {
|
|
|
563
564
|
' /help, /h Show this help',
|
|
564
565
|
' /quit, /q Exit',
|
|
565
566
|
'',
|
|
566
|
-
' /
|
|
567
|
+
' /inspect [sel] [depth] Show DOM tree (default: body, depth 3)',
|
|
568
|
+
' /tree [sel] [depth] Alias for /inspect',
|
|
567
569
|
' /screenshot [sel] [file] Capture screenshot to PNG file',
|
|
568
570
|
' Requires --allow-screenshot flag',
|
|
569
571
|
' /mount <sel> <comp> [json] Mount a BCCL component',
|
package/src/cli/serve.js
CHANGED
|
@@ -30,10 +30,12 @@ Arguments:
|
|
|
30
30
|
Options:
|
|
31
31
|
-p, --port <number> Browser-facing web port (default: 8080)
|
|
32
32
|
-l, --listen <number> Input port for protocol messages (default: 9000)
|
|
33
|
+
-b, --bind <address> Host/address to bind to (default: 0.0.0.0)
|
|
33
34
|
--stdin Read protocol messages from stdin (newline-delimited JSON)
|
|
34
35
|
-t, --theme <name> Theme preset or hex colors ("#pri,#sec")
|
|
35
36
|
--title <string> Page title (default: "bwcli serve")
|
|
36
37
|
--allow-exec Enable exec messages (runs JS in browser, use for dev only)
|
|
38
|
+
--no-dir-list Disable directory listings
|
|
37
39
|
--open Open browser on start
|
|
38
40
|
-v, --verbose Verbose output
|
|
39
41
|
-h, --help Print this help
|
|
@@ -292,10 +294,12 @@ export function runServe(argv, ioOpts) {
|
|
|
292
294
|
options: {
|
|
293
295
|
port: { type: 'string', short: 'p' },
|
|
294
296
|
listen: { type: 'string', short: 'l' },
|
|
297
|
+
bind: { type: 'string', short: 'b' },
|
|
295
298
|
stdin: { type: 'boolean' },
|
|
296
299
|
theme: { type: 'string', short: 't' },
|
|
297
300
|
title: { type: 'string' },
|
|
298
301
|
'allow-exec': { type: 'boolean' },
|
|
302
|
+
'no-dir-list': { type: 'boolean' },
|
|
299
303
|
open: { type: 'boolean' },
|
|
300
304
|
verbose: { type: 'boolean', short: 'v' },
|
|
301
305
|
help: { type: 'boolean', short: 'h' }
|
|
@@ -317,9 +321,11 @@ export function runServe(argv, ioOpts) {
|
|
|
317
321
|
var dir = positionals[0] || '.';
|
|
318
322
|
var webPort = values.port ? parseInt(values.port, 10) : 8080;
|
|
319
323
|
var listenPort = values.listen ? parseInt(values.listen, 10) : 9000;
|
|
324
|
+
var bindAddr = values.bind || '0.0.0.0';
|
|
320
325
|
var useStdin = !!values.stdin;
|
|
321
326
|
var theme = values.theme || null;
|
|
322
327
|
var title = values.title || 'bwcli serve';
|
|
328
|
+
var dirList = !values['no-dir-list'];
|
|
323
329
|
var verbose = !!values.verbose;
|
|
324
330
|
|
|
325
331
|
if (isNaN(webPort) || webPort < 1 || webPort > 65535) {
|
|
@@ -339,9 +345,11 @@ export function runServe(argv, ioOpts) {
|
|
|
339
345
|
dir: dir,
|
|
340
346
|
webPort: webPort,
|
|
341
347
|
listenPort: listenPort,
|
|
348
|
+
bind: bindAddr,
|
|
342
349
|
useStdin: useStdin,
|
|
343
350
|
theme: theme,
|
|
344
351
|
title: title,
|
|
352
|
+
dirList: dirList,
|
|
345
353
|
verbose: verbose,
|
|
346
354
|
open: !!values.open,
|
|
347
355
|
allowExec: !!values['allow-exec']
|
|
@@ -360,9 +368,11 @@ export function runServe(argv, ioOpts) {
|
|
|
360
368
|
function startServer(bwserve, opts) {
|
|
361
369
|
var app = bwserve.create({
|
|
362
370
|
port: opts.webPort,
|
|
371
|
+
host: opts.bind,
|
|
363
372
|
title: opts.title,
|
|
364
373
|
static: opts.dir,
|
|
365
374
|
theme: opts.theme,
|
|
375
|
+
dirList: opts.dirList,
|
|
366
376
|
allowExec: opts.allowExec
|
|
367
377
|
});
|
|
368
378
|
|
|
@@ -381,21 +391,24 @@ function startServer(bwserve, opts) {
|
|
|
381
391
|
// Start web server
|
|
382
392
|
app.listen(function() {
|
|
383
393
|
console.error('bwcli serve v' + VERSION);
|
|
384
|
-
console.error(' Web server: http://localhost:' + opts.webPort);
|
|
394
|
+
console.error(' Web server: http://' + (opts.bind === '0.0.0.0' ? 'localhost' : opts.bind) + ':' + opts.webPort);
|
|
395
|
+
console.error(' Bind: ' + opts.bind);
|
|
385
396
|
console.error(' Static dir: ' + opts.dir);
|
|
386
397
|
if (opts.theme) console.error(' Theme: ' + opts.theme);
|
|
398
|
+
if (opts.dirList === false) console.error(' Dir listing: disabled');
|
|
387
399
|
|
|
388
400
|
if (opts.useStdin) {
|
|
389
401
|
console.error(' Input: stdin (newline-delimited JSON)');
|
|
390
402
|
startStdinReader(app, opts.verbose);
|
|
403
|
+
console.error('');
|
|
404
|
+
console.error('Ready. Send protocol messages to push UI to browsers.');
|
|
391
405
|
} else {
|
|
392
|
-
|
|
393
|
-
|
|
406
|
+
startInputServer(app, opts.listenPort, opts.verbose).then(function() {
|
|
407
|
+
console.error('');
|
|
408
|
+
console.error('Ready. Send protocol messages to push UI to browsers.');
|
|
409
|
+
});
|
|
394
410
|
}
|
|
395
411
|
|
|
396
|
-
console.error('');
|
|
397
|
-
console.error('Ready. Send protocol messages to push UI to browsers.');
|
|
398
|
-
|
|
399
412
|
if (opts.open) {
|
|
400
413
|
import('node:child_process').then(function(cp) {
|
|
401
414
|
var url = 'http://localhost:' + opts.webPort;
|
|
@@ -414,7 +427,40 @@ function startServer(bwserve, opts) {
|
|
|
414
427
|
* Messages with a `type` field (no `command`) are broadcast to all clients.
|
|
415
428
|
*/
|
|
416
429
|
function startInputServer(app, listenPort, verbose) {
|
|
417
|
-
var inputServer =
|
|
430
|
+
var inputServer = _createInputServer(app, verbose);
|
|
431
|
+
|
|
432
|
+
return new Promise(function(resolve) {
|
|
433
|
+
inputServer.on('error', function(err) {
|
|
434
|
+
if (err.code === 'EADDRINUSE') {
|
|
435
|
+
console.error(' Warning: Input port ' + listenPort + ' in use, picking a free port...');
|
|
436
|
+
var retry = _createInputServer(app, verbose);
|
|
437
|
+
retry.on('error', function(err2) {
|
|
438
|
+
console.error(' Warning: Could not bind input server (' + err2.message + '). Continuing without input port.');
|
|
439
|
+
resolve(null);
|
|
440
|
+
});
|
|
441
|
+
retry.listen(0, function() {
|
|
442
|
+
var actualPort = retry.address().port;
|
|
443
|
+
console.error(' Input port: http://localhost:' + actualPort + ' (fallback)');
|
|
444
|
+
resolve(retry);
|
|
445
|
+
});
|
|
446
|
+
} else {
|
|
447
|
+
console.error(' Warning: Input server error (' + err.message + '). Continuing without input port.');
|
|
448
|
+
resolve(null);
|
|
449
|
+
}
|
|
450
|
+
});
|
|
451
|
+
inputServer.listen(listenPort, function() {
|
|
452
|
+
console.error(' Input port: http://localhost:' + listenPort);
|
|
453
|
+
resolve(inputServer);
|
|
454
|
+
});
|
|
455
|
+
});
|
|
456
|
+
}
|
|
457
|
+
|
|
458
|
+
/**
|
|
459
|
+
* Create the input HTTP server (without binding).
|
|
460
|
+
* @private
|
|
461
|
+
*/
|
|
462
|
+
function _createInputServer(app, verbose) {
|
|
463
|
+
return createServer(function(req, res) {
|
|
418
464
|
if (req.method !== 'POST') {
|
|
419
465
|
res.writeHead(405, { 'Content-Type': 'application/json' });
|
|
420
466
|
res.end(JSON.stringify({ error: 'Use POST' }));
|
|
@@ -458,8 +504,6 @@ function startInputServer(app, listenPort, verbose) {
|
|
|
458
504
|
res.end(JSON.stringify({ ok: true, clients: count }));
|
|
459
505
|
});
|
|
460
506
|
});
|
|
461
|
-
|
|
462
|
-
inputServer.listen(listenPort);
|
|
463
507
|
}
|
|
464
508
|
|
|
465
509
|
/**
|
package/src/mcp/live.js
CHANGED
package/src/mcp/server.js
CHANGED
|
@@ -204,13 +204,13 @@ export function run(argv) {
|
|
|
204
204
|
var server = createMcpServer(opts);
|
|
205
205
|
server.listen();
|
|
206
206
|
|
|
207
|
-
// Clean shutdown
|
|
208
|
-
|
|
207
|
+
// Clean shutdown (once -- don't accumulate listeners across calls)
|
|
208
|
+
function onSignal() {
|
|
209
209
|
server.close();
|
|
210
210
|
process.exit(0);
|
|
211
|
-
}
|
|
212
|
-
process.
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
211
|
+
}
|
|
212
|
+
process.once('SIGINT', onSignal);
|
|
213
|
+
process.once('SIGTERM', onSignal);
|
|
214
|
+
|
|
215
|
+
return server;
|
|
216
216
|
}
|
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.
|
|
6
|
+
export const VERSION = '2.0.30';
|
|
7
7
|
export const VERSION_INFO = {
|
|
8
|
-
version: '2.0.
|
|
8
|
+
version: '2.0.30',
|
|
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-
|
|
15
|
+
buildDate: '2026-04-12T07:51:29.111Z'
|
|
16
16
|
};
|