wu-framework 1.1.7 → 1.1.9

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 (95) hide show
  1. package/LICENSE +19 -1
  2. package/README.md +257 -1122
  3. package/dist/wu-framework.cjs.js +3 -1
  4. package/dist/wu-framework.cjs.js.map +1 -0
  5. package/dist/wu-framework.dev.js +9867 -3183
  6. package/dist/wu-framework.dev.js.map +1 -1
  7. package/dist/wu-framework.esm.js +3 -0
  8. package/dist/wu-framework.esm.js.map +1 -0
  9. package/dist/wu-framework.umd.js +3 -1
  10. package/dist/wu-framework.umd.js.map +1 -0
  11. package/integrations/astro/README.md +127 -0
  12. package/integrations/astro/WuApp.astro +63 -0
  13. package/integrations/astro/WuShell.astro +39 -0
  14. package/integrations/astro/index.js +68 -0
  15. package/integrations/astro/package.json +38 -0
  16. package/integrations/astro/types.d.ts +53 -0
  17. package/package.json +96 -72
  18. package/src/adapters/angular/ai.js +30 -0
  19. package/src/adapters/angular/index.d.ts +154 -0
  20. package/src/adapters/angular/index.js +932 -0
  21. package/src/adapters/angular.d.ts +3 -154
  22. package/src/adapters/angular.js +3 -813
  23. package/src/adapters/index.js +35 -24
  24. package/src/adapters/lit/ai.js +20 -0
  25. package/src/adapters/lit/index.d.ts +120 -0
  26. package/src/adapters/lit/index.js +721 -0
  27. package/src/adapters/lit.d.ts +3 -120
  28. package/src/adapters/lit.js +3 -726
  29. package/src/adapters/preact/ai.js +33 -0
  30. package/src/adapters/preact/index.d.ts +108 -0
  31. package/src/adapters/preact/index.js +661 -0
  32. package/src/adapters/preact.d.ts +3 -108
  33. package/src/adapters/preact.js +3 -665
  34. package/src/adapters/react/ai.js +135 -0
  35. package/src/adapters/react/index.d.ts +246 -0
  36. package/src/adapters/react/index.js +694 -0
  37. package/src/adapters/react.d.ts +3 -212
  38. package/src/adapters/react.js +3 -513
  39. package/src/adapters/shared.js +64 -0
  40. package/src/adapters/solid/ai.js +32 -0
  41. package/src/adapters/solid/index.d.ts +101 -0
  42. package/src/adapters/solid/index.js +586 -0
  43. package/src/adapters/solid.d.ts +3 -101
  44. package/src/adapters/solid.js +3 -591
  45. package/src/adapters/svelte/ai.js +31 -0
  46. package/src/adapters/svelte/index.d.ts +166 -0
  47. package/src/adapters/svelte/index.js +798 -0
  48. package/src/adapters/svelte.d.ts +3 -166
  49. package/src/adapters/svelte.js +3 -803
  50. package/src/adapters/vanilla/ai.js +30 -0
  51. package/src/adapters/vanilla/index.d.ts +179 -0
  52. package/src/adapters/vanilla/index.js +785 -0
  53. package/src/adapters/vanilla.d.ts +3 -179
  54. package/src/adapters/vanilla.js +3 -791
  55. package/src/adapters/vue/ai.js +52 -0
  56. package/src/adapters/vue/index.d.ts +299 -0
  57. package/src/adapters/vue/index.js +608 -0
  58. package/src/adapters/vue.d.ts +3 -299
  59. package/src/adapters/vue.js +3 -611
  60. package/src/ai/wu-ai-actions.js +261 -0
  61. package/src/ai/wu-ai-agent.js +546 -0
  62. package/src/ai/wu-ai-browser-primitives.js +354 -0
  63. package/src/ai/wu-ai-browser.js +380 -0
  64. package/src/ai/wu-ai-context.js +332 -0
  65. package/src/ai/wu-ai-conversation.js +613 -0
  66. package/src/ai/wu-ai-orchestrate.js +1021 -0
  67. package/src/ai/wu-ai-permissions.js +381 -0
  68. package/src/ai/wu-ai-provider.js +700 -0
  69. package/src/ai/wu-ai-schema.js +225 -0
  70. package/src/ai/wu-ai-triggers.js +396 -0
  71. package/src/ai/wu-ai.js +804 -0
  72. package/src/core/wu-app.js +50 -8
  73. package/src/core/wu-cache.js +2 -3
  74. package/src/core/wu-core.js +648 -681
  75. package/src/core/wu-html-parser.js +121 -211
  76. package/src/core/wu-iframe-sandbox.js +328 -0
  77. package/src/core/wu-mcp-bridge.js +431 -0
  78. package/src/core/wu-overrides.js +510 -0
  79. package/src/core/wu-plugin.js +4 -1
  80. package/src/core/wu-prefetch.js +414 -0
  81. package/src/core/wu-proxy-sandbox.js +398 -75
  82. package/src/core/wu-sandbox.js +86 -268
  83. package/src/core/wu-script-executor.js +79 -182
  84. package/src/core/wu-snapshot-sandbox.js +149 -106
  85. package/src/core/wu-strategies.js +13 -0
  86. package/src/core/wu-style-bridge.js +23 -23
  87. package/src/index.js +162 -665
  88. package/dist/wu-framework.hex.js +0 -23
  89. package/dist/wu-framework.min.js +0 -1
  90. package/dist/wu-framework.obf.js +0 -1
  91. package/scripts/build-protected.js +0 -366
  92. package/scripts/build.js +0 -212
  93. package/scripts/rollup-plugin-hex.js +0 -143
  94. package/src/core/wu-registry.js +0 -60
  95. package/src/core/wu-sandbox-pool.js +0 -390
@@ -0,0 +1,431 @@
1
+ /**
2
+ * WU-MCP Bridge (Browser Side)
3
+ *
4
+ * Connects to the wu-mcp-server via WebSocket and executes
5
+ * commands using wu.* APIs. This is the "eyes and hands" of
6
+ * the MCP server inside the browser.
7
+ *
8
+ * Security:
9
+ * - Optional auth token sent on first message (handshake)
10
+ * - All state/event/mount operations check wu.ai permissions
11
+ * - Mutating operations emit audit events
12
+ * - Read-only operations (status, list_apps, snapshot, console, network) are unrestricted
13
+ *
14
+ * @example
15
+ * // Connect with auth token
16
+ * wu.mcp.connect('ws://localhost:19100', { token: 'my-secret' });
17
+ *
18
+ * // Connect without auth (development only)
19
+ * wu.mcp.connect();
20
+ */
21
+
22
+ import {
23
+ ensureInterceptors,
24
+ networkLog,
25
+ consoleLog,
26
+ captureScreenshot,
27
+ buildA11yTree,
28
+ clickElement,
29
+ typeIntoElement,
30
+ getFilteredNetwork,
31
+ getFilteredConsole,
32
+ } from '../ai/wu-ai-browser-primitives.js';
33
+
34
+ /**
35
+ * Create the MCP bridge for a Wu instance.
36
+ *
37
+ * @param {object} wu - The Wu Framework instance (window.wu)
38
+ * @returns {object} Bridge API: { connect, disconnect, isConnected }
39
+ */
40
+ export function createMcpBridge(wu) {
41
+ let ws = null;
42
+ let reconnectTimer = null;
43
+ let reconnectAttempts = 0;
44
+ let authenticated = false;
45
+ let authToken = null;
46
+ const MAX_RECONNECT_ATTEMPTS = 10;
47
+ const RECONNECT_DELAY = 2000;
48
+
49
+ // Event log for wu_list_events
50
+ const eventLog = [];
51
+ const MAX_EVENT_LOG = 200;
52
+
53
+ // Capture events for history
54
+ if (wu.eventBus) {
55
+ wu.eventBus.on('*', (event) => {
56
+ eventLog.push({
57
+ name: event.name,
58
+ data: event.data,
59
+ timestamp: event.timestamp || Date.now(),
60
+ source: event.source || 'unknown',
61
+ });
62
+ if (eventLog.length > MAX_EVENT_LOG) eventLog.shift();
63
+ });
64
+ }
65
+
66
+ // Install shared interceptors (idempotent — safe if wu-ai-browser already did it)
67
+ ensureInterceptors();
68
+
69
+ // ── Permission helpers ──
70
+
71
+ /**
72
+ * Check a permission flag via wu.ai.permissions if available.
73
+ * Falls back to deny if wu.ai is not initialized.
74
+ */
75
+ function _checkPermission(perm) {
76
+ if (wu.ai && wu.ai.permissions) {
77
+ return wu.ai.permissions.check(perm);
78
+ }
79
+ // If AI module not initialized, deny write operations, allow reads
80
+ const readPerms = ['readStore', 'executeActions'];
81
+ return readPerms.includes(perm);
82
+ }
83
+
84
+ /**
85
+ * Emit an audit event for bridge operations.
86
+ */
87
+ function _audit(operation, params, result) {
88
+ if (wu.eventBus) {
89
+ wu.eventBus.emit('mcp:bridge:operation', {
90
+ operation,
91
+ params,
92
+ result: result?.error ? { error: result.error } : { success: true },
93
+ timestamp: Date.now(),
94
+ }, { appName: 'wu-mcp-bridge' });
95
+ }
96
+ }
97
+
98
+ // ── Command handlers ──
99
+
100
+ const handlers = {
101
+ // ── Read-only operations (no permission gates) ──
102
+
103
+ status() {
104
+ return {
105
+ connected: true,
106
+ framework: 'wu-framework',
107
+ apps: _getAppList(),
108
+ storeKeys: wu.store ? Object.keys(wu.store.get('') || {}) : [],
109
+ actionsCount: wu.ai ? wu.ai.tools().length : 0,
110
+ eventLogSize: eventLog.length,
111
+ };
112
+ },
113
+
114
+ list_apps() {
115
+ return _getAppList();
116
+ },
117
+
118
+ list_events({ limit = 20 }) {
119
+ return eventLog.slice(-limit);
120
+ },
121
+
122
+ list_actions() {
123
+ if (!wu.ai) return { actions: [], note: 'wu.ai not initialized' };
124
+ const tools = wu.ai.tools();
125
+ return { actions: tools, count: tools.length };
126
+ },
127
+
128
+ snapshot({ appName }) {
129
+ try {
130
+ const target = appName
131
+ ? document.querySelector(`[data-wu-app="${appName}"]`) || document.querySelector(`#wu-app-${appName}`)
132
+ : document.body;
133
+
134
+ if (!target) return { error: `App "${appName}" not found in DOM` };
135
+
136
+ return {
137
+ app: appName || '(page)',
138
+ snapshot: buildA11yTree(target, 0, 5),
139
+ timestamp: Date.now(),
140
+ };
141
+ } catch (err) {
142
+ return { error: err.message };
143
+ }
144
+ },
145
+
146
+ console({ level = 'all', limit = 50 }) {
147
+ return getFilteredConsole(level, limit);
148
+ },
149
+
150
+ async screenshot({ selector, quality = 0.8 }) {
151
+ const result = await captureScreenshot(selector, quality);
152
+ if (!result.error) result.timestamp = Date.now();
153
+ return result;
154
+ },
155
+
156
+ network({ method, status, limit = 50 }) {
157
+ return getFilteredNetwork(method, status, limit);
158
+ },
159
+
160
+ // ── Permission-gated operations ──
161
+
162
+ get_state({ path }) {
163
+ if (!wu.store) return { error: 'wu.store not available' };
164
+ if (!_checkPermission('readStore')) {
165
+ return { error: 'Permission denied: readStore is disabled' };
166
+ }
167
+ const value = wu.store.get(path || '');
168
+ return { path: path || '(root)', value };
169
+ },
170
+
171
+ set_state({ path, value }) {
172
+ if (!wu.store) return { error: 'wu.store not available' };
173
+ if (!path) return { error: 'path is required' };
174
+ if (!_checkPermission('writeStore')) {
175
+ _audit('set_state', { path }, { error: 'Permission denied' });
176
+ return { error: 'Permission denied: writeStore is disabled' };
177
+ }
178
+ wu.store.set(path, value);
179
+ _audit('set_state', { path, value }, { success: true });
180
+ return { path, value, updated: true };
181
+ },
182
+
183
+ emit_event({ event, data }) {
184
+ if (!wu.eventBus) return { error: 'wu.eventBus not available' };
185
+ if (!event) return { error: 'event name is required' };
186
+ if (!_checkPermission('emitEvents')) {
187
+ _audit('emit_event', { event }, { error: 'Permission denied' });
188
+ return { error: 'Permission denied: emitEvents is disabled' };
189
+ }
190
+ wu.eventBus.emit(event, data, { appName: 'wu-mcp-bridge' });
191
+ _audit('emit_event', { event, data }, { success: true });
192
+ return { emitted: event, data };
193
+ },
194
+
195
+ navigate({ route }) {
196
+ if (!route) return { error: 'Route is required' };
197
+ if (!_checkPermission('emitEvents')) {
198
+ _audit('navigate', { route }, { error: 'Permission denied: emitEvents' });
199
+ return { error: 'Permission denied: emitEvents is disabled' };
200
+ }
201
+ if (wu.eventBus) {
202
+ wu.eventBus.emit('shell:navigate', { route }, { appName: 'wu-mcp-bridge' });
203
+ }
204
+ if (wu.store && _checkPermission('writeStore')) {
205
+ wu.store.set('currentPath', route);
206
+ }
207
+ _audit('navigate', { route }, { success: true });
208
+ return { navigated: route };
209
+ },
210
+
211
+ mount_app({ appName, container }) {
212
+ if (!appName) return { error: 'appName is required' };
213
+ if (!_checkPermission('modifyDOM')) {
214
+ _audit('mount_app', { appName }, { error: 'Permission denied' });
215
+ return { error: 'Permission denied: modifyDOM is disabled' };
216
+ }
217
+ try {
218
+ if (wu.mount) {
219
+ wu.mount(appName, container);
220
+ _audit('mount_app', { appName, container }, { success: true });
221
+ return { mounted: appName, container };
222
+ }
223
+ return { error: 'wu.mount not available' };
224
+ } catch (err) {
225
+ return { error: err.message };
226
+ }
227
+ },
228
+
229
+ unmount_app({ appName }) {
230
+ if (!appName) return { error: 'appName is required' };
231
+ if (!_checkPermission('modifyDOM')) {
232
+ _audit('unmount_app', { appName }, { error: 'Permission denied' });
233
+ return { error: 'Permission denied: modifyDOM is disabled' };
234
+ }
235
+ try {
236
+ if (wu.unmount) {
237
+ wu.unmount(appName);
238
+ _audit('unmount_app', { appName }, { success: true });
239
+ return { unmounted: appName };
240
+ }
241
+ return { error: 'wu.unmount not available' };
242
+ } catch (err) {
243
+ return { error: err.message };
244
+ }
245
+ },
246
+
247
+ click({ selector, text }) {
248
+ if (!_checkPermission('modifyDOM')) {
249
+ return { error: 'Permission denied: modifyDOM is disabled' };
250
+ }
251
+ const result = clickElement(selector, text);
252
+ _audit('click', { selector, text }, result);
253
+ return result;
254
+ },
255
+
256
+ type({ selector, text, clear = false, submit = false }) {
257
+ if (!_checkPermission('modifyDOM')) {
258
+ return { error: 'Permission denied: modifyDOM is disabled' };
259
+ }
260
+ const result = typeIntoElement(selector, text, { clear, submit });
261
+ _audit('type', { selector, textLength: text?.length }, result);
262
+ return result;
263
+ },
264
+
265
+ async execute_action({ action, params }) {
266
+ if (!wu.ai) return { error: 'wu.ai not available' };
267
+ if (!action) return { error: 'action name is required' };
268
+
269
+ try {
270
+ // Execute through public API (respects permissions, validation, audit)
271
+ const result = await wu.ai.execute(action, params || {});
272
+ return { action, ...result };
273
+ } catch (err) {
274
+ return { error: err.message };
275
+ }
276
+ },
277
+ };
278
+
279
+ // ── WebSocket connection ──
280
+
281
+ function connect(url = 'ws://localhost:19100', options = {}) {
282
+ if (ws && ws.readyState <= 1) {
283
+ console.warn('[wu-mcp-bridge] Already connected or connecting');
284
+ return;
285
+ }
286
+
287
+ authToken = options.token || null;
288
+ authenticated = !authToken; // No token = auto-authenticated (dev mode)
289
+
290
+ try {
291
+ ws = new WebSocket(url);
292
+
293
+ ws.onopen = () => {
294
+ console.log('[wu-mcp-bridge] Connected to wu-mcp-server');
295
+ reconnectAttempts = 0;
296
+
297
+ // Send auth handshake if token provided
298
+ if (authToken) {
299
+ ws.send(JSON.stringify({
300
+ type: 'auth',
301
+ token: authToken,
302
+ }));
303
+ }
304
+ };
305
+
306
+ ws.onmessage = async (event) => {
307
+ try {
308
+ const msg = JSON.parse(event.data);
309
+
310
+ // Handle auth response
311
+ if (msg.type === 'auth_result') {
312
+ authenticated = msg.success === true;
313
+ if (!authenticated) {
314
+ console.error('[wu-mcp-bridge] Authentication failed:', msg.reason || 'Invalid token');
315
+ disconnect();
316
+ } else {
317
+ console.log('[wu-mcp-bridge] Authenticated successfully');
318
+ }
319
+ return;
320
+ }
321
+
322
+ // Reject commands if not authenticated
323
+ if (!authenticated) {
324
+ if (msg.id) {
325
+ _respond(msg.id, null, 'Not authenticated. Send auth token first.');
326
+ }
327
+ return;
328
+ }
329
+
330
+ const { id, command, params } = msg;
331
+
332
+ if (!id || !command) {
333
+ console.warn('[wu-mcp-bridge] Invalid message:', msg);
334
+ return;
335
+ }
336
+
337
+ const handler = handlers[command];
338
+ if (!handler) {
339
+ _respond(id, null, `Unknown command: ${command}`);
340
+ return;
341
+ }
342
+
343
+ try {
344
+ const result = await handler(params || {});
345
+ _respond(id, result);
346
+ } catch (err) {
347
+ _respond(id, null, err.message);
348
+ }
349
+ } catch (err) {
350
+ console.error('[wu-mcp-bridge] Failed to handle message:', err);
351
+ }
352
+ };
353
+
354
+ ws.onclose = () => {
355
+ console.log('[wu-mcp-bridge] Disconnected');
356
+ ws = null;
357
+ authenticated = false;
358
+ _scheduleReconnect(url, options);
359
+ };
360
+
361
+ ws.onerror = () => {
362
+ // onclose will fire after this
363
+ };
364
+ } catch (err) {
365
+ console.error('[wu-mcp-bridge] Connection failed:', err.message);
366
+ _scheduleReconnect(url, options);
367
+ }
368
+ }
369
+
370
+ function disconnect() {
371
+ if (reconnectTimer) {
372
+ clearTimeout(reconnectTimer);
373
+ reconnectTimer = null;
374
+ }
375
+ reconnectAttempts = MAX_RECONNECT_ATTEMPTS; // prevent reconnect
376
+ if (ws) {
377
+ ws.close();
378
+ ws = null;
379
+ }
380
+ authenticated = false;
381
+ }
382
+
383
+ function isConnected() {
384
+ return ws !== null && ws.readyState === 1 && authenticated;
385
+ }
386
+
387
+ // ── Private helpers ──
388
+
389
+ function _respond(id, result, error) {
390
+ if (!ws || ws.readyState !== 1) return;
391
+ const msg = error ? { id, error } : { id, result };
392
+ ws.send(JSON.stringify(msg));
393
+ }
394
+
395
+ function _scheduleReconnect(url, options) {
396
+ if (reconnectAttempts >= MAX_RECONNECT_ATTEMPTS) return;
397
+ reconnectAttempts++;
398
+ const delay = RECONNECT_DELAY * Math.min(reconnectAttempts, 5);
399
+ reconnectTimer = setTimeout(() => connect(url, options), delay);
400
+ }
401
+
402
+ function _getAppList() {
403
+ const apps = [];
404
+
405
+ if (wu._apps) {
406
+ for (const [name, app] of Object.entries(wu._apps)) {
407
+ apps.push({
408
+ name,
409
+ mounted: app.mounted || app.isMounted || false,
410
+ url: app.url || app.info?.url || '',
411
+ status: app.status || app.info?.status || 'unknown',
412
+ });
413
+ }
414
+ }
415
+
416
+ // Fallback: scan DOM for wu-app elements
417
+ if (apps.length === 0) {
418
+ document.querySelectorAll('[data-wu-app]').forEach((el) => {
419
+ apps.push({
420
+ name: el.getAttribute('data-wu-app'),
421
+ mounted: true,
422
+ container: `#${el.id || '(no-id)'}`,
423
+ });
424
+ });
425
+ }
426
+
427
+ return apps;
428
+ }
429
+
430
+ return { connect, disconnect, isConnected };
431
+ }