bitwrench 2.0.31 → 2.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +210 -125
- package/dist/bitwrench-bccl.cjs.js +349 -188
- package/dist/bitwrench-bccl.cjs.min.js +2 -39
- package/dist/bitwrench-bccl.cjs.min.js.gz +0 -0
- package/dist/bitwrench-bccl.esm.js +349 -188
- package/dist/bitwrench-bccl.esm.min.js +2 -39
- package/dist/bitwrench-bccl.esm.min.js.gz +0 -0
- package/dist/bitwrench-bccl.umd.js +349 -188
- package/dist/bitwrench-bccl.umd.min.js +2 -39
- package/dist/bitwrench-bccl.umd.min.js.gz +0 -0
- package/dist/bitwrench-code-edit.cjs.js +17 -6
- package/dist/bitwrench-code-edit.cjs.min.js +2 -20
- package/dist/bitwrench-code-edit.es5.js +8 -3
- package/dist/bitwrench-code-edit.es5.min.js +2 -19
- package/dist/bitwrench-code-edit.esm.js +17 -6
- package/dist/bitwrench-code-edit.esm.min.js +2 -19
- package/dist/bitwrench-code-edit.umd.js +17 -6
- package/dist/bitwrench-code-edit.umd.min.js +2 -19
- 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 +2492 -1628
- package/dist/bitwrench-lean.cjs.min.js +2 -80
- package/dist/bitwrench-lean.cjs.min.js.gz +0 -0
- package/dist/bitwrench-lean.es5.js +2740 -1838
- package/dist/bitwrench-lean.es5.min.js +2 -49
- package/dist/bitwrench-lean.es5.min.js.gz +0 -0
- package/dist/bitwrench-lean.esm.js +2492 -1628
- package/dist/bitwrench-lean.esm.min.js +2 -80
- package/dist/bitwrench-lean.esm.min.js.gz +0 -0
- package/dist/bitwrench-lean.umd.js +2492 -1628
- package/dist/bitwrench-lean.umd.min.js +2 -80
- package/dist/bitwrench-lean.umd.min.js.gz +0 -0
- package/dist/bitwrench-util-color.cjs.js +251 -0
- package/dist/bitwrench-util-color.cjs.min.js +3 -0
- package/dist/bitwrench-util-color.es5.js +256 -0
- package/dist/bitwrench-util-color.es5.min.js +3 -0
- package/dist/bitwrench-util-color.esm.js +241 -0
- package/dist/bitwrench-util-color.esm.min.js +3 -0
- package/dist/bitwrench-util-color.umd.js +257 -0
- package/dist/bitwrench-util-color.umd.min.js +3 -0
- package/dist/bitwrench-util-css.cjs.js +2 -1
- package/dist/bitwrench-util-css.cjs.min.js +2 -21
- package/dist/bitwrench-util-css.es5.js +2 -1
- package/dist/bitwrench-util-css.es5.min.js +2 -20
- package/dist/bitwrench-util-css.esm.js +2 -1
- package/dist/bitwrench-util-css.esm.min.js +1 -19
- package/dist/bitwrench-util-css.umd.js +2 -1
- package/dist/bitwrench-util-css.umd.min.js +2 -20
- package/dist/bitwrench-util-css.umd.min.js.gz +0 -0
- package/dist/bitwrench.cjs.js +2826 -1801
- package/dist/bitwrench.cjs.min.js +2 -99
- package/dist/bitwrench.cjs.min.js.gz +0 -0
- package/dist/bitwrench.css +403 -479
- package/dist/bitwrench.d.ts +70 -73
- package/dist/bitwrench.es5.js +3106 -2020
- package/dist/bitwrench.es5.min.js +2 -67
- package/dist/bitwrench.es5.min.js.gz +0 -0
- package/dist/bitwrench.esm.js +2826 -1801
- package/dist/bitwrench.esm.min.js +2 -99
- package/dist/bitwrench.esm.min.js.gz +0 -0
- package/dist/bitwrench.min.css +1 -1
- package/dist/bitwrench.umd.js +2826 -1801
- package/dist/bitwrench.umd.min.js +2 -99
- package/dist/bitwrench.umd.min.js.gz +0 -0
- package/dist/builds.json +222 -134
- package/dist/bwserve.cjs.js +308 -298
- package/dist/bwserve.d.ts +157 -0
- package/dist/bwserve.esm.js +309 -299
- package/dist/sri.json +54 -46
- package/docs/README.md +6 -3
- package/docs/app-patterns.md +7 -6
- package/docs/bitwrench-for-wasm.md +53 -54
- package/docs/bitwrench-mcp.md +2 -2
- package/docs/bitwrench-northstar-principles.md +406 -0
- package/docs/bitwrench-taco-schema-discussion.md +2 -2
- package/docs/bitwrench_api.md +191 -106
- package/docs/bitwrench_typescript_usage.md +5 -5
- package/docs/bw-attach.md +29 -75
- package/docs/bwserve.md +200 -168
- package/docs/cli.md +36 -12
- package/docs/component-cheatsheet.md +2 -2
- package/docs/component-library.md +4 -4
- package/docs/component-lifecycle.md +234 -0
- package/docs/drift-lint.md +268 -0
- package/docs/framework-translation-table.md +4 -4
- package/docs/llm-bitwrench-guide.md +60 -50
- package/docs/routing.md +11 -13
- package/docs/state-management.md +110 -109
- package/docs/taco-format.md +13 -14
- package/docs/theming.md +13 -3
- package/docs/thinking-in-bitwrench.md +858 -983
- package/docs/tutorial-bwserve.md +37 -36
- package/docs/tutorial-embedded.md +10 -21
- package/docs/tutorial-website.md +2 -2
- package/package.json +38 -7
- package/readme.html +262 -160
- package/src/bitwrench-bccl-entry.js +2 -2
- package/src/bitwrench-bccl.js +346 -185
- package/src/bitwrench-code-edit.js +16 -5
- package/src/bitwrench-color-utils.js +117 -181
- package/src/bitwrench-file-ops.js +2 -2
- package/src/bitwrench-lean.js +4 -3
- package/src/bitwrench-router.js +5 -2
- package/src/bitwrench-styles.js +420 -504
- package/src/bitwrench-util-color.js +240 -0
- package/src/bitwrench-util-css.js +1 -0
- package/src/bitwrench-utils.js +4 -0
- package/src/bitwrench.d.ts +70 -73
- package/src/bitwrench.h +5 -0
- package/src/bitwrench.js +1939 -933
- package/src/bwserve/attach.js +0 -1
- package/src/bwserve/bwclient.js +172 -32
- package/src/bwserve/bwshell.js +0 -4
- package/src/bwserve/client.js +59 -220
- package/src/bwserve/index.js +78 -42
- package/src/bwserve.d.ts +157 -0
- package/src/bwserve.h +5 -0
- package/src/cli/attach.js +12 -75
- package/src/cli/convert.js +2 -2
- package/src/cli/serve.js +37 -35
- package/src/generate-css.js +1 -1
- package/src/mcp/knowledge.js +4 -4
- package/src/mcp/live.js +21 -13
- package/src/mcp/tools.js +0 -1
- package/src/version.js +3 -7
package/src/bwserve/index.js
CHANGED
|
@@ -9,7 +9,7 @@
|
|
|
9
9
|
* import bwserve from 'bitwrench/bwserve';
|
|
10
10
|
* const app = bwserve.create({ port: 7902 });
|
|
11
11
|
* app.page('/', (client) => {
|
|
12
|
-
* client.
|
|
12
|
+
* client.mount('#app', bw.makeCard({ title: 'Hello' }));
|
|
13
13
|
* });
|
|
14
14
|
* app.listen();
|
|
15
15
|
*
|
|
@@ -23,7 +23,7 @@ import { VERSION } from '../version.js';
|
|
|
23
23
|
|
|
24
24
|
// Resolve dist/ paths relative to the package root
|
|
25
25
|
import { fileURLToPath } from 'url';
|
|
26
|
-
import { dirname, resolve, join, extname } from 'path';
|
|
26
|
+
import { dirname, resolve, join, extname, sep } from 'path';
|
|
27
27
|
import { createServer } from 'http';
|
|
28
28
|
import { readFileSync, readdirSync, existsSync, statSync } from 'fs';
|
|
29
29
|
|
|
@@ -32,9 +32,11 @@ var __dirname = dirname(fileURLToPath(import.meta.url));
|
|
|
32
32
|
// Resolve dist/ — try source layout (src/bwserve/), then npm install layout,
|
|
33
33
|
// then dist/ itself (when running from dist/bwserve.esm.js)
|
|
34
34
|
var DIST_DIR = resolve(__dirname, '..', '..', 'dist');
|
|
35
|
+
/* c8 ignore next 3 -- DIST_DIR fallback at module load; only triggers in npm install layout */
|
|
35
36
|
if (!existsSync(DIST_DIR)) {
|
|
36
37
|
DIST_DIR = resolve(__dirname, '..', 'dist');
|
|
37
38
|
}
|
|
39
|
+
/* c8 ignore next 3 -- DIST_DIR fallback at module load; only triggers when no dist/ exists */
|
|
38
40
|
if (!existsSync(DIST_DIR)) {
|
|
39
41
|
DIST_DIR = __dirname;
|
|
40
42
|
}
|
|
@@ -96,15 +98,14 @@ export function create(opts) {
|
|
|
96
98
|
*/
|
|
97
99
|
class BwServeApp {
|
|
98
100
|
constructor(opts) {
|
|
99
|
-
this.port = opts.port
|
|
101
|
+
this.port = opts.port != null ? opts.port : 7902;
|
|
100
102
|
this.title = opts.title || 'bwserve';
|
|
101
103
|
this.staticDir = opts.static || null;
|
|
102
104
|
this.injectBitwrench = opts.injectBitwrench !== false;
|
|
103
105
|
this.theme = opts.theme || null;
|
|
104
|
-
this.allowExec = opts.allowExec || false;
|
|
105
106
|
this.allowScreenshot = opts.allowScreenshot || false;
|
|
106
107
|
this.dirList = opts.dirList !== false;
|
|
107
|
-
this.host = opts.host || '
|
|
108
|
+
this.host = opts.host || '127.0.0.1';
|
|
108
109
|
this.keepAliveInterval = opts.keepAliveInterval || 15000;
|
|
109
110
|
this._pages = new Map();
|
|
110
111
|
this._clients = new Map();
|
|
@@ -139,6 +140,11 @@ class BwServeApp {
|
|
|
139
140
|
});
|
|
140
141
|
|
|
141
142
|
self._server.listen(self.port, self.host, function() {
|
|
143
|
+
// Update port to the actual bound port (important when port 0 is used)
|
|
144
|
+
var addr = self._server.address();
|
|
145
|
+
if (addr && addr.port) {
|
|
146
|
+
self.port = addr.port;
|
|
147
|
+
}
|
|
142
148
|
if (callback) callback();
|
|
143
149
|
res();
|
|
144
150
|
});
|
|
@@ -197,9 +203,9 @@ class BwServeApp {
|
|
|
197
203
|
return 0;
|
|
198
204
|
}
|
|
199
205
|
var count = 0;
|
|
200
|
-
for (var
|
|
201
|
-
if (
|
|
202
|
-
|
|
206
|
+
for (var rec of this._clients.values()) {
|
|
207
|
+
if (rec.client && !rec.client._closed) {
|
|
208
|
+
rec.client._send(msg);
|
|
203
209
|
count++;
|
|
204
210
|
}
|
|
205
211
|
}
|
|
@@ -218,6 +224,7 @@ class BwServeApp {
|
|
|
218
224
|
var path = url.split('?')[0];
|
|
219
225
|
|
|
220
226
|
// /bw/attach.js — self-contained attach script for remote debugging
|
|
227
|
+
/* c8 ignore next 3 -- covered in isolation; flaky in combined suite due to server state */
|
|
221
228
|
if (path === '/bw/attach.js' && method === 'GET') {
|
|
222
229
|
return this._serveAttachScript(req, res);
|
|
223
230
|
}
|
|
@@ -244,6 +251,7 @@ class BwServeApp {
|
|
|
244
251
|
}
|
|
245
252
|
|
|
246
253
|
// CORS preflight for /bw/return/ (needed for cross-origin attach)
|
|
254
|
+
/* c8 ignore next 9 -- covered in isolation; flaky in combined suite */
|
|
247
255
|
if (method === 'OPTIONS' && path.startsWith('/bw/return/')) {
|
|
248
256
|
res.writeHead(204, {
|
|
249
257
|
'Access-Control-Allow-Origin': '*',
|
|
@@ -258,6 +266,7 @@ class BwServeApp {
|
|
|
258
266
|
if (method === 'POST' && path.startsWith('/bw/return/')) {
|
|
259
267
|
var rest = path.slice('/bw/return/'.length);
|
|
260
268
|
var slash = rest.indexOf('/');
|
|
269
|
+
/* c8 ignore next 4 -- covered in isolation; flaky in combined suite */
|
|
261
270
|
if (slash === -1) {
|
|
262
271
|
res.writeHead(400, { 'Content-Type': 'application/json' });
|
|
263
272
|
res.end(JSON.stringify({ error: 'Invalid return path' }));
|
|
@@ -274,37 +283,30 @@ class BwServeApp {
|
|
|
274
283
|
return this._serveVendorFile(res, vendorFile);
|
|
275
284
|
}
|
|
276
285
|
|
|
277
|
-
//
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
var shell = generateShell({
|
|
281
|
-
clientId: clientId2,
|
|
282
|
-
title: this.title,
|
|
283
|
-
theme: this.theme,
|
|
284
|
-
injectBitwrench: this.injectBitwrench,
|
|
285
|
-
allowExec: this.allowExec
|
|
286
|
-
});
|
|
287
|
-
// Store the page path for this client so SSE knows which handler to call
|
|
288
|
-
this._clients.set(clientId2, { pagePath: path, client: null });
|
|
289
|
-
res.writeHead(200, { 'Content-Type': 'text/html; charset=utf-8' });
|
|
290
|
-
res.end(shell);
|
|
291
|
-
return;
|
|
292
|
-
}
|
|
293
|
-
|
|
294
|
-
// Static file serving
|
|
286
|
+
// Static file serving — takes priority over registered page handlers
|
|
287
|
+
// so that bwserve works as a drop-in static server (like python -m
|
|
288
|
+
// http.server or npx serve) with opt-in bwserve superpowers.
|
|
295
289
|
if (method === 'GET' && this.staticDir) {
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
290
|
+
// Path traversal guard: resolve to absolute and verify containment
|
|
291
|
+
var resolvedBase = resolve(this.staticDir);
|
|
292
|
+
var resolvedPath = resolve(resolvedBase, '.' + path);
|
|
293
|
+
if (resolvedPath !== resolvedBase && !resolvedPath.startsWith(resolvedBase + sep)) {
|
|
294
|
+
res.writeHead(403, { 'Content-Type': 'text/plain' });
|
|
295
|
+
res.end('Forbidden');
|
|
296
|
+
return;
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
if (existsSync(resolvedPath) && statSync(resolvedPath).isFile()) {
|
|
300
|
+
var ext = extname(resolvedPath);
|
|
299
301
|
var mime = MIME_TYPES[ext] || 'application/octet-stream';
|
|
300
|
-
var content = readFileSync(
|
|
302
|
+
var content = readFileSync(resolvedPath);
|
|
301
303
|
res.writeHead(200, { 'Content-Type': mime });
|
|
302
304
|
res.end(content);
|
|
303
305
|
return;
|
|
304
306
|
}
|
|
305
307
|
// Directory index resolution: /foo/ => /foo/index.html
|
|
306
308
|
if (path.endsWith('/')) {
|
|
307
|
-
var indexPath = join(
|
|
309
|
+
var indexPath = join(resolvedPath, 'index.html');
|
|
308
310
|
if (existsSync(indexPath) && statSync(indexPath).isFile()) {
|
|
309
311
|
var indexContent = readFileSync(indexPath);
|
|
310
312
|
res.writeHead(200, { 'Content-Type': 'text/html; charset=utf-8' });
|
|
@@ -312,16 +314,15 @@ class BwServeApp {
|
|
|
312
314
|
return;
|
|
313
315
|
}
|
|
314
316
|
// Directory listing when no index.html
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
var listing = this._generateDirListing(path, dirPath);
|
|
317
|
+
if (this.dirList && existsSync(resolvedPath) && statSync(resolvedPath).isDirectory()) {
|
|
318
|
+
var listing = this._generateDirListing(path, resolvedPath);
|
|
318
319
|
res.writeHead(200, { 'Content-Type': 'text/html; charset=utf-8' });
|
|
319
320
|
res.end(listing);
|
|
320
321
|
return;
|
|
321
322
|
}
|
|
322
323
|
}
|
|
323
324
|
// Bare directory without trailing slash: /foo => 301 to /foo/
|
|
324
|
-
if (!path.endsWith('/') && existsSync(
|
|
325
|
+
if (!path.endsWith('/') && existsSync(resolvedPath) && statSync(resolvedPath).isDirectory()) {
|
|
325
326
|
var qs = url.split('?')[1];
|
|
326
327
|
var location = path + '/' + (qs ? '?' + qs : '');
|
|
327
328
|
res.writeHead(301, { 'Location': location });
|
|
@@ -330,6 +331,23 @@ class BwServeApp {
|
|
|
330
331
|
}
|
|
331
332
|
}
|
|
332
333
|
|
|
334
|
+
// Registered page routes — serve bwserve shell HTML (fallback when no
|
|
335
|
+
// static file matched, e.g. pipe/SSE driven pages)
|
|
336
|
+
if (method === 'GET' && this._pages.has(path)) {
|
|
337
|
+
var clientId2 = 'c' + (++this._clientCounter);
|
|
338
|
+
var shell = generateShell({
|
|
339
|
+
clientId: clientId2,
|
|
340
|
+
title: this.title,
|
|
341
|
+
theme: this.theme,
|
|
342
|
+
injectBitwrench: this.injectBitwrench
|
|
343
|
+
});
|
|
344
|
+
// Store the page path for this client so SSE knows which handler to call
|
|
345
|
+
this._clients.set(clientId2, { pagePath: path, client: null });
|
|
346
|
+
res.writeHead(200, { 'Content-Type': 'text/html; charset=utf-8' });
|
|
347
|
+
res.end(shell);
|
|
348
|
+
return;
|
|
349
|
+
}
|
|
350
|
+
|
|
333
351
|
// 404
|
|
334
352
|
res.writeHead(404, { 'Content-Type': 'text/plain' });
|
|
335
353
|
res.end('Not Found');
|
|
@@ -380,9 +398,13 @@ class BwServeApp {
|
|
|
380
398
|
var pagePath = pending ? pending.pagePath : '/';
|
|
381
399
|
self._clients.set(clientId, { pagePath: pagePath, client: client });
|
|
382
400
|
|
|
401
|
+
// Send the handshake as the very first SSE event
|
|
402
|
+
client._send({ type: 'hello' });
|
|
403
|
+
|
|
383
404
|
// Keep-alive: send SSE comment periodically
|
|
384
405
|
var keepAlive = setInterval(function() {
|
|
385
406
|
if (!client._closed) {
|
|
407
|
+
/* c8 ignore next -- keepalive write failure only on socket close */
|
|
386
408
|
try { res.write(':keepalive\n\n'); } catch (e) { /* ignore */ }
|
|
387
409
|
}
|
|
388
410
|
}, self.keepAliveInterval);
|
|
@@ -394,11 +416,12 @@ class BwServeApp {
|
|
|
394
416
|
self._clients.delete(clientId);
|
|
395
417
|
});
|
|
396
418
|
|
|
397
|
-
// Call the page handler
|
|
419
|
+
// Call the page handler (runs on every connection, including reconnects)
|
|
398
420
|
var handler = self._pages.get(pagePath);
|
|
399
421
|
if (handler) {
|
|
400
422
|
try {
|
|
401
423
|
handler(client);
|
|
424
|
+
/* c8 ignore next 3 -- page handler error catch; requires throwing handler in SSE context */
|
|
402
425
|
} catch (e) {
|
|
403
426
|
console.error('[bwserve] Page handler error:', e);
|
|
404
427
|
}
|
|
@@ -411,8 +434,8 @@ class BwServeApp {
|
|
|
411
434
|
*
|
|
412
435
|
* Routes:
|
|
413
436
|
* action — fire-and-forget action dispatch (no requestId)
|
|
414
|
-
*
|
|
415
|
-
*
|
|
437
|
+
* event — event dispatch from client listeners
|
|
438
|
+
* topic — topic dispatch from client pub/sub
|
|
416
439
|
* screenshot — resolve pending screenshot promise
|
|
417
440
|
*
|
|
418
441
|
* @private
|
|
@@ -430,18 +453,26 @@ class BwServeApp {
|
|
|
430
453
|
req.on('end', function() {
|
|
431
454
|
try {
|
|
432
455
|
var data = JSON.parse(body);
|
|
433
|
-
if (route === '
|
|
456
|
+
if (route === 'topic') {
|
|
457
|
+
// Topic dispatch — forward to the listen handler registered on the client
|
|
458
|
+
var topic = data.topic;
|
|
459
|
+
var topicData = data.data;
|
|
460
|
+
record.client._dispatch('_topic:' + topic, topicData);
|
|
461
|
+
} else if (route === 'action' || route === 'event') {
|
|
434
462
|
// Action/event dispatch (no requestId/pending pattern)
|
|
435
463
|
var action = route === 'event'
|
|
436
464
|
? '_bw_event'
|
|
437
465
|
: (data.result ? data.result.action : data.action);
|
|
466
|
+
/* c8 ignore next 3 -- data.data fallback; covered in isolation */
|
|
438
467
|
var payload = route === 'event'
|
|
439
468
|
? (data.result || data)
|
|
440
469
|
: (data.result ? data.result.data : data.data || data);
|
|
441
470
|
record.client._dispatch(action, payload);
|
|
442
471
|
} else {
|
|
443
|
-
// All other routes: resolve pending promise
|
|
444
|
-
record.client._resolvePending
|
|
472
|
+
// All other routes: resolve pending promise if mechanism exists
|
|
473
|
+
if (record.client._resolvePending) {
|
|
474
|
+
record.client._resolvePending(data.requestId, data);
|
|
475
|
+
}
|
|
445
476
|
}
|
|
446
477
|
res.writeHead(200, { 'Content-Type': 'application/json', 'Access-Control-Allow-Origin': '*' });
|
|
447
478
|
res.end(JSON.stringify({ ok: true }));
|
|
@@ -459,13 +490,18 @@ class BwServeApp {
|
|
|
459
490
|
*/
|
|
460
491
|
_serveAttachScript(req, res) {
|
|
461
492
|
try {
|
|
462
|
-
var
|
|
493
|
+
var h = req.headers || {};
|
|
494
|
+
var proto = (h['x-forwarded-proto'] || 'http');
|
|
495
|
+
var host = h['x-forwarded-host'] || h.host || '';
|
|
496
|
+
var origin = host ? (proto + '://' + host) : '';
|
|
497
|
+
var js = generateAttachScript({ origin: origin });
|
|
463
498
|
res.writeHead(200, {
|
|
464
499
|
'Content-Type': 'application/javascript; charset=utf-8',
|
|
465
500
|
'Access-Control-Allow-Origin': '*',
|
|
466
501
|
'Cache-Control': 'no-cache'
|
|
467
502
|
});
|
|
468
503
|
res.end(js);
|
|
504
|
+
/* c8 ignore next 4 -- generateAttachScript is a pure template; cannot throw */
|
|
469
505
|
} catch (err) {
|
|
470
506
|
res.writeHead(500, { 'Content-Type': 'text/plain' });
|
|
471
507
|
res.end('Error generating attach script: ' + err.message);
|
package/src/bwserve.d.ts
ADDED
|
@@ -0,0 +1,157 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Type declarations for bitwrench/bwserve — server-driven UI over SSE.
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import { IncomingMessage, ServerResponse } from 'http';
|
|
6
|
+
|
|
7
|
+
/** Options for creating a BwServeApp instance. */
|
|
8
|
+
export interface BwServeOptions {
|
|
9
|
+
/** Port to listen on (default: 7902) */
|
|
10
|
+
port?: number;
|
|
11
|
+
/** Page title (default: 'bwserve') */
|
|
12
|
+
title?: string;
|
|
13
|
+
/** Directory path for static file serving */
|
|
14
|
+
static?: string;
|
|
15
|
+
/** Auto-inject bitwrench client JS (default: true) */
|
|
16
|
+
injectBitwrench?: boolean;
|
|
17
|
+
/** Theme preset name or config object */
|
|
18
|
+
theme?: string | Record<string, string>;
|
|
19
|
+
/** Enable client.screenshot() (default: false) */
|
|
20
|
+
allowScreenshot?: boolean;
|
|
21
|
+
/** Enable directory listings (default: true) */
|
|
22
|
+
dirList?: boolean;
|
|
23
|
+
/** Host/address to bind to (default: '127.0.0.1') */
|
|
24
|
+
host?: string;
|
|
25
|
+
/** SSE keep-alive interval in ms (default: 15000) */
|
|
26
|
+
keepAliveInterval?: number;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
/** Options for generateShell(). */
|
|
30
|
+
export interface ShellOptions {
|
|
31
|
+
/** Unique client ID for this connection */
|
|
32
|
+
clientId: string;
|
|
33
|
+
/** Page title (default: 'bwserve') */
|
|
34
|
+
title?: string;
|
|
35
|
+
/** Theme preset name or config object */
|
|
36
|
+
theme?: string | Record<string, string>;
|
|
37
|
+
/** Whether to inject bitwrench scripts (default: true) */
|
|
38
|
+
injectBitwrench?: boolean;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
/** A TACO object — Tag, Attributes, Content, Options. */
|
|
42
|
+
export interface Taco {
|
|
43
|
+
t?: string;
|
|
44
|
+
a?: Record<string, any>;
|
|
45
|
+
c?: string | Taco | (string | Taco)[];
|
|
46
|
+
o?: Record<string, any>;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
/** SSE protocol message. */
|
|
50
|
+
export interface BwServeMessage {
|
|
51
|
+
v?: number;
|
|
52
|
+
type?: string;
|
|
53
|
+
ref?: string;
|
|
54
|
+
taco?: Taco;
|
|
55
|
+
[key: string]: any;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
/** Batch operation entry. */
|
|
59
|
+
export interface BatchOp {
|
|
60
|
+
type: string;
|
|
61
|
+
ref?: string;
|
|
62
|
+
taco?: Taco;
|
|
63
|
+
[key: string]: any;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
/** Server-side client connection — represents one browser tab via SSE. */
|
|
67
|
+
export declare class BwServeClient {
|
|
68
|
+
/** bwserve version string */
|
|
69
|
+
static version: string;
|
|
70
|
+
|
|
71
|
+
/** Unique client identifier */
|
|
72
|
+
id: string;
|
|
73
|
+
|
|
74
|
+
constructor(id: string, res: ServerResponse);
|
|
75
|
+
|
|
76
|
+
/** Mount TACO at selector, replacing contents */
|
|
77
|
+
mount(selector: string, taco: Taco): void;
|
|
78
|
+
|
|
79
|
+
/** @deprecated Use mount() instead */
|
|
80
|
+
render(selector: string, taco: Taco): void;
|
|
81
|
+
|
|
82
|
+
/** Patch element properties without full rebuild */
|
|
83
|
+
patch(ref: string, fields: Record<string, any>): void;
|
|
84
|
+
|
|
85
|
+
/** Append TACO as child of target element */
|
|
86
|
+
append(selector: string, taco: Taco): void;
|
|
87
|
+
|
|
88
|
+
/** Remove element from DOM with cleanup */
|
|
89
|
+
remove(selector: string): void;
|
|
90
|
+
|
|
91
|
+
/** Send array of operations as single batch message */
|
|
92
|
+
batch(ops: BatchOp[]): void;
|
|
93
|
+
|
|
94
|
+
/** Dispatch to component's el.bw[action](data) */
|
|
95
|
+
message(ref: string, action: string, data?: any): void;
|
|
96
|
+
|
|
97
|
+
/** Call registered or built-in client function */
|
|
98
|
+
call(name: string, ...args: any[]): void;
|
|
99
|
+
|
|
100
|
+
/** Subscribe to client topic. Returns this for chaining. */
|
|
101
|
+
listen(topic: string, handler: (data: any) => void): BwServeClient;
|
|
102
|
+
|
|
103
|
+
/** Register handler for client action. Returns this for chaining. */
|
|
104
|
+
on(action: string, handler: (data: any, client: BwServeClient) => void): BwServeClient;
|
|
105
|
+
|
|
106
|
+
/** Close SSE connection */
|
|
107
|
+
close(): void;
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
/** Main bwserve application — HTTP + SSE server for server-driven UI. */
|
|
111
|
+
export declare class BwServeApp {
|
|
112
|
+
port: number;
|
|
113
|
+
title: string;
|
|
114
|
+
staticDir: string | null;
|
|
115
|
+
injectBitwrench: boolean;
|
|
116
|
+
theme: string | Record<string, string> | null;
|
|
117
|
+
allowScreenshot: boolean;
|
|
118
|
+
dirList: boolean;
|
|
119
|
+
host: string;
|
|
120
|
+
keepAliveInterval: number;
|
|
121
|
+
|
|
122
|
+
/** Number of active client connections */
|
|
123
|
+
readonly clientCount: number;
|
|
124
|
+
|
|
125
|
+
constructor(opts?: BwServeOptions);
|
|
126
|
+
|
|
127
|
+
/** Register a page handler. Returns this for chaining. */
|
|
128
|
+
page(path: string, handler: (client: BwServeClient) => void): BwServeApp;
|
|
129
|
+
|
|
130
|
+
/** Start HTTP server. Returns Promise that resolves when listening. */
|
|
131
|
+
listen(callback?: () => void): Promise<void>;
|
|
132
|
+
|
|
133
|
+
/** Stop server and close all client connections. */
|
|
134
|
+
close(): Promise<void>;
|
|
135
|
+
|
|
136
|
+
/** Send protocol message to all clients (or specific client if msg.clientId is set). Returns count of recipients. */
|
|
137
|
+
broadcast(msg: BwServeMessage): number;
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
/** Generate a complete HTML shell document for SSE connection. */
|
|
141
|
+
export declare function generateShell(opts?: ShellOptions): string;
|
|
142
|
+
|
|
143
|
+
/** Create a new BwServeApp instance. */
|
|
144
|
+
export declare function create(opts?: BwServeOptions): BwServeApp;
|
|
145
|
+
|
|
146
|
+
/** bwserve version string */
|
|
147
|
+
export declare const version: string;
|
|
148
|
+
|
|
149
|
+
/** Default export */
|
|
150
|
+
declare const bwserve: {
|
|
151
|
+
create: typeof create;
|
|
152
|
+
version: string;
|
|
153
|
+
BwServeApp: typeof BwServeApp;
|
|
154
|
+
BwServeClient: typeof BwServeClient;
|
|
155
|
+
generateShell: typeof generateShell;
|
|
156
|
+
};
|
|
157
|
+
export default bwserve;
|
package/src/bwserve.h
ADDED
package/src/cli/attach.js
CHANGED
|
@@ -29,8 +29,8 @@ Usage:
|
|
|
29
29
|
Description:
|
|
30
30
|
Starts a bwserve instance and waits for a browser to connect via the
|
|
31
31
|
drop-in attach script. Once connected, you get an interactive REPL
|
|
32
|
-
|
|
33
|
-
|
|
32
|
+
for inspecting the DOM, mounting components, taking screenshots,
|
|
33
|
+
and listening to events — all from your terminal.
|
|
34
34
|
|
|
35
35
|
To connect a page, either:
|
|
36
36
|
1. Add <script src="http://localhost:<port>/bw/attach.js"></script>
|
|
@@ -44,7 +44,6 @@ Options:
|
|
|
44
44
|
-h, --help Print this help
|
|
45
45
|
|
|
46
46
|
REPL Commands:
|
|
47
|
-
<expression> Evaluate JS in the connected browser (e.g., document.title)
|
|
48
47
|
/help Show command reference
|
|
49
48
|
/quit, /q Exit
|
|
50
49
|
/inspect [selector] [depth] Show DOM tree summary (default: body, depth 3)
|
|
@@ -55,7 +54,6 @@ REPL Commands:
|
|
|
55
54
|
/listen <sel> <event> Start listening for DOM events (e.g., /listen button click)
|
|
56
55
|
/unlisten <sel> <event> Stop listening for a previously added listener
|
|
57
56
|
/clients List connected clients
|
|
58
|
-
/exec <code> Execute JS without capturing return value
|
|
59
57
|
|
|
60
58
|
Examples:
|
|
61
59
|
bwcli attach Start on default port 7902
|
|
@@ -64,8 +62,6 @@ Examples:
|
|
|
64
62
|
bwcli attach -v Verbose mode (shows protocol)
|
|
65
63
|
|
|
66
64
|
# In the REPL:
|
|
67
|
-
bw> document.title
|
|
68
|
-
bw> bw.$('.bw-card').length
|
|
69
65
|
bw> /inspect #app 2
|
|
70
66
|
bw> /screenshot body page.png
|
|
71
67
|
bw> /listen .bw-btn click
|
|
@@ -80,21 +76,7 @@ Examples:
|
|
|
80
76
|
bw> /listen .bw-btn click
|
|
81
77
|
`.trim();
|
|
82
78
|
|
|
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
|
-
}
|
|
79
|
+
// v2.1: wrapExpression and bare JS eval removed — no code-over-wire.
|
|
98
80
|
|
|
99
81
|
/**
|
|
100
82
|
* Run the attach subcommand.
|
|
@@ -160,7 +142,6 @@ export function startAttach(bwserve, opts) {
|
|
|
160
142
|
var app = bwserve.create({
|
|
161
143
|
port: opts.port,
|
|
162
144
|
title: 'bwcli attach',
|
|
163
|
-
allowExec: true,
|
|
164
145
|
allowScreenshot: opts.allowScreenshot
|
|
165
146
|
});
|
|
166
147
|
|
|
@@ -266,33 +247,9 @@ export function startAttach(bwserve, opts) {
|
|
|
266
247
|
return;
|
|
267
248
|
}
|
|
268
249
|
|
|
269
|
-
//
|
|
270
|
-
|
|
271
|
-
|
|
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
|
-
});
|
|
250
|
+
// Non-slash input: show hint (no more bare JS eval)
|
|
251
|
+
console.log('Unknown input. Type /help for available commands.');
|
|
252
|
+
safePrompt();
|
|
296
253
|
});
|
|
297
254
|
|
|
298
255
|
rl.on('close', function() {
|
|
@@ -417,13 +374,13 @@ export function handleSlashCommand(line, activeClient, clients, opts, rl) {
|
|
|
417
374
|
break;
|
|
418
375
|
}
|
|
419
376
|
}
|
|
420
|
-
activeClient.
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
console.error('[error] ' + err.message);
|
|
425
|
-
rl.prompt();
|
|
377
|
+
activeClient.call('_bw_mount', {
|
|
378
|
+
target: mountSel,
|
|
379
|
+
factory: mountComp,
|
|
380
|
+
props: mountProps
|
|
426
381
|
});
|
|
382
|
+
console.log('Mounted ' + mountComp + ' at ' + mountSel);
|
|
383
|
+
rl.prompt();
|
|
427
384
|
break;
|
|
428
385
|
|
|
429
386
|
case '/render':
|
|
@@ -508,22 +465,6 @@ export function handleSlashCommand(line, activeClient, clients, opts, rl) {
|
|
|
508
465
|
rl.prompt();
|
|
509
466
|
break;
|
|
510
467
|
|
|
511
|
-
case '/exec':
|
|
512
|
-
if (!activeClient) {
|
|
513
|
-
console.log('No client connected.');
|
|
514
|
-
rl.prompt();
|
|
515
|
-
break;
|
|
516
|
-
}
|
|
517
|
-
if (parts.length < 2) {
|
|
518
|
-
console.log('Usage: /exec <code>');
|
|
519
|
-
rl.prompt();
|
|
520
|
-
break;
|
|
521
|
-
}
|
|
522
|
-
activeClient.exec(parts.slice(1).join(' '));
|
|
523
|
-
console.log('Executed.');
|
|
524
|
-
rl.prompt();
|
|
525
|
-
break;
|
|
526
|
-
|
|
527
468
|
default:
|
|
528
469
|
console.log('Unknown command: ' + cmd + '. Type /help for available commands.');
|
|
529
470
|
rl.prompt();
|
|
@@ -558,9 +499,6 @@ export function printHelp() {
|
|
|
558
499
|
'',
|
|
559
500
|
'bwcli attach — REPL Commands',
|
|
560
501
|
'',
|
|
561
|
-
' <expression> Evaluate JS in the browser and print result',
|
|
562
|
-
' Examples: document.title, bw.$(".card").length',
|
|
563
|
-
'',
|
|
564
502
|
' /help, /h Show this help',
|
|
565
503
|
' /quit, /q Exit',
|
|
566
504
|
'',
|
|
@@ -577,7 +515,6 @@ export function printHelp() {
|
|
|
577
515
|
' /listen <sel> <event> Listen for DOM events (prints inline)',
|
|
578
516
|
' Example: /listen button click',
|
|
579
517
|
' /unlisten <sel> <event> Remove a listener',
|
|
580
|
-
' /exec <code> Execute JS without capturing return value',
|
|
581
518
|
' /clients List connected clients',
|
|
582
519
|
'',
|
|
583
520
|
' Workflow — build and push a component:',
|
package/src/cli/convert.js
CHANGED
|
@@ -7,7 +7,7 @@ import { readFileSync, writeFileSync, mkdirSync } from 'node:fs';
|
|
|
7
7
|
import { resolve, extname, basename, dirname } from 'node:path';
|
|
8
8
|
import quikdown from '../vendor/quikdown.js';
|
|
9
9
|
import bw from '../bitwrench.js';
|
|
10
|
-
import {
|
|
10
|
+
import { THEME_PRESETS } from '../bitwrench-styles.js';
|
|
11
11
|
import { getInjectionHead, getInjectionBodyEnd } from './inject.js';
|
|
12
12
|
import { makePageLayout } from './layout-default.js';
|
|
13
13
|
|
|
@@ -110,7 +110,7 @@ export function convertFile(inputPath, flags = {}) {
|
|
|
110
110
|
const raw = readFileSync(absInput, 'utf8');
|
|
111
111
|
const ext = extname(absInput).toLowerCase();
|
|
112
112
|
|
|
113
|
-
let bodyHTML
|
|
113
|
+
let bodyHTML;
|
|
114
114
|
let autoTitle = null;
|
|
115
115
|
|
|
116
116
|
// Process based on file extension
|