@bubstack/moe-glass 0.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.
Files changed (62) hide show
  1. package/README.md +29 -0
  2. package/agents/browser-user.md +105 -0
  3. package/dist/LICENSE +25 -0
  4. package/dist/index.d.ts +9 -0
  5. package/dist/index.d.ts.map +1 -0
  6. package/dist/index.js +22517 -0
  7. package/dist/index.js.map +1 -0
  8. package/dist/payload.d.ts +214 -0
  9. package/dist/payload.d.ts.map +1 -0
  10. package/dist/payload.js +325 -0
  11. package/dist/payload.js.map +1 -0
  12. package/package.json +59 -0
  13. package/skills/browsing/COMMANDLINE-USAGE.md +595 -0
  14. package/skills/browsing/EXAMPLES.md +717 -0
  15. package/skills/browsing/README.md +55 -0
  16. package/skills/browsing/SKILL.md +478 -0
  17. package/skills/browsing/chrome-ws +1021 -0
  18. package/skills/browsing/chrome-ws-lib.js +461 -0
  19. package/skills/browsing/host-override.js +98 -0
  20. package/skills/browsing/lib/browser-bridge.js +175 -0
  21. package/skills/browsing/lib/browser-session.js +137 -0
  22. package/skills/browsing/lib/capture.js +499 -0
  23. package/skills/browsing/lib/cdp-router.js +72 -0
  24. package/skills/browsing/lib/cdp-utils.js +18 -0
  25. package/skills/browsing/lib/chrome-launcher-helpers.js +374 -0
  26. package/skills/browsing/lib/chrome-process.js +464 -0
  27. package/skills/browsing/lib/console-logging.js +70 -0
  28. package/skills/browsing/lib/cookies.js +17 -0
  29. package/skills/browsing/lib/dialogs-render.js +154 -0
  30. package/skills/browsing/lib/dialogs-router.js +117 -0
  31. package/skills/browsing/lib/dialogs.js +254 -0
  32. package/skills/browsing/lib/element-selector.js +91 -0
  33. package/skills/browsing/lib/evaluation.js +85 -0
  34. package/skills/browsing/lib/extraction.js +55 -0
  35. package/skills/browsing/lib/file-upload.js +56 -0
  36. package/skills/browsing/lib/html-diff.js +122 -0
  37. package/skills/browsing/lib/key-definitions.js +149 -0
  38. package/skills/browsing/lib/keyboard-input.js +288 -0
  39. package/skills/browsing/lib/mouse.js +423 -0
  40. package/skills/browsing/lib/navigation.js +272 -0
  41. package/skills/browsing/lib/page-scripts/dom-summary.js +31 -0
  42. package/skills/browsing/lib/page-scripts/markdown.js +85 -0
  43. package/skills/browsing/lib/page-scripts/permission-shim.js +80 -0
  44. package/skills/browsing/lib/page-session.js +106 -0
  45. package/skills/browsing/lib/profile-lock.js +179 -0
  46. package/skills/browsing/lib/screenshot.js +171 -0
  47. package/skills/browsing/lib/select-option.js +99 -0
  48. package/skills/browsing/lib/session-state.js +66 -0
  49. package/skills/browsing/lib/tabs.js +144 -0
  50. package/skills/browsing/lib/viewport.js +103 -0
  51. package/skills/browsing/lib/websocket-client.js +162 -0
  52. package/skills/browsing/package.json +11 -0
  53. package/skills/browsing/test-chrome-args.js +81 -0
  54. package/skills/browsing/test-cookies.js +21 -0
  55. package/skills/browsing/test-e2e.sh +51 -0
  56. package/skills/browsing/test-extract.sh +17 -0
  57. package/skills/browsing/test-interact.sh +11 -0
  58. package/skills/browsing/test-navigate.sh +9 -0
  59. package/skills/browsing/test-raw.sh +8 -0
  60. package/skills/browsing/test-tabs.sh +15 -0
  61. package/skills/browsing/test-viewport.js +27 -0
  62. package/skills/browsing/test-wait.sh +9 -0
@@ -0,0 +1,175 @@
1
+ 'use strict';
2
+
3
+ const { createCdpRouter } = require('./cdp-router');
4
+ const { attachPageSession, buildPageSessionFromAttached } = require('./page-session');
5
+
6
+ /**
7
+ * attachBrowserBridge({browser, host, port, rewriteWsUrl}) — consumer-facing
8
+ * bridge over the browser-session.
9
+ *
10
+ * Exposes:
11
+ * targets.list() — synchronous snapshot of current targets
12
+ * targets.onCreated(handler) — register listener; returns unsub fn
13
+ * targets.onDestroyed(handler)
14
+ * targets.waitForNew(predicate, {timeoutMs})
15
+ * createBrowserContext({proxyServer?}) -> {browserContextId, createPage, dispose}
16
+ * attachPageSession(targetId) — page session over the browser-WS via flatten mode
17
+ *
18
+ * host/port/rewriteWsUrl are needed by createBrowserContext.createPage to construct
19
+ * per-page WS URLs for callers that still want one (the bridge itself never uses
20
+ * them — page sessions ride the browser-WS).
21
+ */
22
+ async function attachBrowserBridge({ browser, host, port, rewriteWsUrl, autoAttach = false, onPageSession = null }) {
23
+ // The cdp-router sits between browser-session and bridge consumers.
24
+ // Page-session-tagged messages dispatch to the right session; root-session
25
+ // events (Target.*, etc.) fire root listeners. Command responses without
26
+ // sessionId stay correlated by browser-session.js's pendingRequests
27
+ // (single source of truth for root-session correlation).
28
+ const router = createCdpRouter({ browser });
29
+
30
+ const targetMap = new Map(); // targetId -> targetInfo
31
+ const onCreatedFns = new Set();
32
+ const onDestroyedFns = new Set();
33
+
34
+ router.getRootListeners().add((msg) => {
35
+ if (msg.method === 'Target.targetCreated') {
36
+ const t = msg.params.targetInfo;
37
+ targetMap.set(t.targetId, t);
38
+ for (const fn of onCreatedFns) {
39
+ try { fn(t); } catch (e) { console.error('targets onCreated handler threw:', e); }
40
+ }
41
+ } else if (msg.method === 'Target.targetInfoChanged') {
42
+ const t = msg.params.targetInfo;
43
+ targetMap.set(t.targetId, t);
44
+ } else if (msg.method === 'Target.targetDestroyed') {
45
+ const t = targetMap.get(msg.params.targetId);
46
+ targetMap.delete(msg.params.targetId);
47
+ if (t) {
48
+ for (const fn of onDestroyedFns) {
49
+ try { fn(t); } catch (e) { console.error('targets onDestroyed handler threw:', e); }
50
+ }
51
+ }
52
+ }
53
+ });
54
+
55
+ // Handle auto-attached targets (popups, child frames, etc.) when autoAttach is on.
56
+ // Chrome emits this with an already-allocated sessionId — no Target.attachToTarget needed.
57
+ //
58
+ // Only act on targets that are paused (waitingForDebugger: true). Existing tabs
59
+ // that Chrome retrospectively reports via attachedToTarget (waitingForDebugger: false)
60
+ // are not paused and will be set up through the normal getPageSession/attachPageSession
61
+ // path. Installing a page session here for those targets would register the same
62
+ // sessionId twice in the cdp-router, causing duplicate-id protocol errors.
63
+ router.getRootListeners().add(async (msg) => {
64
+ if (msg.method !== 'Target.attachedToTarget') return;
65
+ const { sessionId, targetInfo, waitingForDebugger } = msg.params;
66
+
67
+ if (!waitingForDebugger) return;
68
+
69
+ const ps = buildPageSessionFromAttached({ browser, router, sessionId, targetId: targetInfo.targetId });
70
+
71
+ // Only run the onPageSession hook for page-type targets. Non-page targets
72
+ // (service_worker, background_page, etc.) don't support the Page CDP domain
73
+ // and would cause 'Page.enable' wasn't found errors in the hook.
74
+ if (onPageSession && targetInfo.type === 'page') {
75
+ try { await onPageSession(ps); }
76
+ catch (e) { console.error('onPageSession hook threw:', e); }
77
+ }
78
+
79
+ // Resume the paused target AFTER the hook so any shims (e.g. dialog) are
80
+ // installed before the page's scripts run.
81
+ try { await ps.send('Runtime.runIfWaitingForDebugger', {}); }
82
+ catch (e) { console.error('Runtime.runIfWaitingForDebugger failed:', e); }
83
+ });
84
+
85
+ // Subscribe — replays existing targets as targetCreated events.
86
+ await browser.send('Target.setDiscoverTargets', { discover: true });
87
+
88
+ if (autoAttach) {
89
+ await browser.send('Target.setAutoAttach', {
90
+ autoAttach: true,
91
+ waitForDebuggerOnStart: true,
92
+ flatten: true,
93
+ });
94
+ }
95
+
96
+ function list() { return Array.from(targetMap.values()); }
97
+ function onCreated(fn) { onCreatedFns.add(fn); return () => onCreatedFns.delete(fn); }
98
+ function onDestroyed(fn) { onDestroyedFns.add(fn); return () => onDestroyedFns.delete(fn); }
99
+
100
+ function waitForNew(predicate, { timeoutMs = 15000 } = {}) {
101
+ return new Promise((resolve, reject) => {
102
+ let unsub = null;
103
+ const timeout = setTimeout(() => {
104
+ if (unsub) unsub();
105
+ reject(new Error(`waitForNew: timed out after ${timeoutMs}ms`));
106
+ }, timeoutMs);
107
+ unsub = onCreated((t) => {
108
+ let match;
109
+ try { match = predicate(t); }
110
+ catch (e) {
111
+ clearTimeout(timeout);
112
+ if (unsub) unsub();
113
+ reject(e);
114
+ return;
115
+ }
116
+ if (match) {
117
+ clearTimeout(timeout);
118
+ if (unsub) unsub();
119
+ resolve(t);
120
+ }
121
+ });
122
+ });
123
+ }
124
+
125
+ /**
126
+ * createBrowserContext({proxyServer?}) — creates a Chrome BrowserContext.
127
+ * Returns {browserContextId, createPage, dispose}.
128
+ *
129
+ * createPage(url) calls Target.createTarget({url, browserContextId}) and
130
+ * constructs a tab-shape-compatible page handle whose webSocketDebuggerUrl
131
+ * is run through rewriteWsUrl.
132
+ *
133
+ * dispose() is atomic — Chrome tears down cookies/storage/IDB/SW for the
134
+ * context in one call.
135
+ */
136
+ async function createBrowserContext(opts = {}) {
137
+ const params = {};
138
+ if (opts.proxyServer) params.proxyServer = opts.proxyServer;
139
+ const { browserContextId } = await browser.send('Target.createBrowserContext', params);
140
+ let disposed = false;
141
+
142
+ async function createPage(url = 'about:blank') {
143
+ if (disposed) throw new Error('BrowserContext disposed');
144
+ const { targetId } = await browser.send('Target.createTarget', { url, browserContextId });
145
+ const rawWsUrl = `ws://${host}:${port}/devtools/page/${targetId}`;
146
+ return {
147
+ id: targetId, targetId,
148
+ webSocketDebuggerUrl: rewriteWsUrl(rawWsUrl, host, port),
149
+ type: 'page', url, browserContextId,
150
+ };
151
+ }
152
+
153
+ async function dispose() {
154
+ if (disposed) return;
155
+ disposed = true;
156
+ try { await browser.send('Target.disposeBrowserContext', { browserContextId }); }
157
+ catch (e) { console.warn('BrowserContext.dispose() failed:', e && e.message); }
158
+ }
159
+
160
+ return { browserContextId, createPage, dispose };
161
+ }
162
+
163
+ async function attachPage(targetId) {
164
+ return attachPageSession({ browser, router }, targetId);
165
+ }
166
+
167
+ return {
168
+ targets: { list, onCreated, onDestroyed, waitForNew },
169
+ createBrowserContext,
170
+ attachPageSession: attachPage,
171
+ router,
172
+ };
173
+ }
174
+
175
+ module.exports = { attachBrowserBridge };
@@ -0,0 +1,137 @@
1
+ 'use strict';
2
+
3
+ const { WebSocketClient: DefaultWebSocketClient } = require('./websocket-client');
4
+
5
+ /**
6
+ * createBrowserSession({host, port, rewriteWsUrl, chromeHttp, WebSocketClient?}) -> bridge handle.
7
+ *
8
+ * Owns the one root WebSocket to /devtools/browser/<id>. Page-action commands ride
9
+ * per-page sessions (attached via Target.attachToTarget({flatten:true})) and envelope
10
+ * messages with a sessionId via sendRaw — the page session manages its own pendingRequests
11
+ * (in the cdp-router). browser-session correlates ROOT-session command responses only.
12
+ *
13
+ * Returned API:
14
+ * send(method, params?, {timeoutMs?}) -> Promise<result> // root command
15
+ * onEvent(handler) -> unsub fn
16
+ * close() -> Promise<void>
17
+ * isConnected() -> boolean
18
+ * sendRaw(json) -> void
19
+ */
20
+ function createBrowserSession({ host, port, rewriteWsUrl, chromeHttp, WebSocketClient = DefaultWebSocketClient }) {
21
+ let ws = null;
22
+ const pendingRequests = new Map(); // id -> {resolve, reject, timeout}
23
+ let messageIdCounter = 1;
24
+ const eventListeners = new Set();
25
+ let connectPromise = null;
26
+ let closed = false;
27
+
28
+ async function ensureConnected() {
29
+ if (ws && ws.isConnected()) return;
30
+ if (connectPromise) { await connectPromise; return; }
31
+ connectPromise = (async () => {
32
+ try {
33
+ const versionInfo = await chromeHttp('/json/version');
34
+ if (!versionInfo || !versionInfo.webSocketDebuggerUrl) {
35
+ throw new Error('chromeHttp(/json/version) returned no webSocketDebuggerUrl');
36
+ }
37
+ const url = rewriteWsUrl(versionInfo.webSocketDebuggerUrl, host, port);
38
+ const next = new WebSocketClient(url);
39
+ next.on('message', (raw) => {
40
+ let data;
41
+ try { data = JSON.parse(raw); } catch (e) {
42
+ console.error('browser-session: bad JSON from CDP:', e);
43
+ return;
44
+ }
45
+ // Correlate ROOT-session command responses (id without sessionId). Page-session
46
+ // responses carry {id, result, sessionId} and fall through to event listeners
47
+ // for the cdp-router to dispatch.
48
+ if (data.id !== undefined && data.sessionId === undefined) {
49
+ const pending = pendingRequests.get(data.id);
50
+ if (pending) {
51
+ clearTimeout(pending.timeout);
52
+ pendingRequests.delete(data.id);
53
+ if (data.error) {
54
+ pending.reject(new Error(data.error.message || JSON.stringify(data.error)));
55
+ } else {
56
+ pending.resolve(data.result);
57
+ }
58
+ return;
59
+ }
60
+ }
61
+ for (const fn of eventListeners) {
62
+ try { fn(data); } catch (e) { console.error('browser-session listener threw:', e); }
63
+ }
64
+ });
65
+ next.on('close', () => {
66
+ for (const [, p] of pendingRequests) {
67
+ clearTimeout(p.timeout);
68
+ p.reject(new Error('Browser session WS closed'));
69
+ }
70
+ pendingRequests.clear();
71
+ });
72
+ await next.connect();
73
+ // Assign ws only after a successful connect so concurrent callers that hit the
74
+ // `ws && ws.isConnected()` early-return don't see a half-initialized socket.
75
+ ws = next;
76
+ } catch (e) {
77
+ // Allow retry after a transient failure (network blip, Chrome not yet ready, etc.).
78
+ // We do NOT null on success — leaving the resolved promise in place makes subsequent
79
+ // ensureConnected() awaits a no-op.
80
+ connectPromise = null;
81
+ throw e;
82
+ }
83
+ })();
84
+ await connectPromise;
85
+ }
86
+
87
+ async function send(method, params = {}, { timeoutMs = 10000 } = {}) {
88
+ if (closed) throw new Error('Browser session closed');
89
+ await ensureConnected();
90
+ if (closed) throw new Error('Browser session closed');
91
+ const id = messageIdCounter++;
92
+ return new Promise((resolve, reject) => {
93
+ const timeout = setTimeout(() => {
94
+ pendingRequests.delete(id);
95
+ reject(new Error(`Browser session timeout: ${method}`));
96
+ }, timeoutMs);
97
+ pendingRequests.set(id, { resolve, reject, timeout });
98
+ try {
99
+ ws.send(JSON.stringify({ id, method, params }));
100
+ } catch (e) {
101
+ clearTimeout(timeout);
102
+ pendingRequests.delete(id);
103
+ reject(e);
104
+ }
105
+ });
106
+ }
107
+
108
+ function onEvent(handler) {
109
+ eventListeners.add(handler);
110
+ return () => eventListeners.delete(handler);
111
+ }
112
+
113
+ async function close() {
114
+ closed = true;
115
+ if (ws) { ws.close(); ws = null; }
116
+ for (const [, p] of pendingRequests) {
117
+ clearTimeout(p.timeout);
118
+ p.reject(new Error('Browser session closed'));
119
+ }
120
+ pendingRequests.clear();
121
+ eventListeners.clear();
122
+ }
123
+
124
+ function isConnected() { return ws !== null && ws.isConnected(); }
125
+
126
+ function sendRaw(json) {
127
+ if (closed) throw new Error('Browser session closed');
128
+ if (!ws || !ws.isConnected()) {
129
+ throw new Error('Browser WS not connected (call send() first to lazy-open)');
130
+ }
131
+ ws.send(json);
132
+ }
133
+
134
+ return { send, onEvent, close, isConnected, sendRaw };
135
+ }
136
+
137
+ module.exports = { createBrowserSession };