tab-agent 0.2.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 (45) hide show
  1. package/README.md +62 -0
  2. package/bin/tab-agent.js +40 -0
  3. package/cli/detect-extension.js +131 -0
  4. package/cli/setup.js +133 -0
  5. package/cli/start.js +19 -0
  6. package/cli/status.js +70 -0
  7. package/extension/content-script.js +510 -0
  8. package/extension/icons/icon128.png +0 -0
  9. package/extension/icons/icon16.png +0 -0
  10. package/extension/icons/icon48.png +0 -0
  11. package/extension/manifest.json +40 -0
  12. package/extension/popup/popup.html +142 -0
  13. package/extension/popup/popup.js +104 -0
  14. package/extension/service-worker.js +471 -0
  15. package/extension/snapshot.js +194 -0
  16. package/package.json +25 -0
  17. package/relay/install-native-host.sh +57 -0
  18. package/relay/native-host-wrapper.cmd +3 -0
  19. package/relay/native-host-wrapper.sh +29 -0
  20. package/relay/native-host.js +128 -0
  21. package/relay/node_modules/.package-lock.json +29 -0
  22. package/relay/node_modules/ws/LICENSE +20 -0
  23. package/relay/node_modules/ws/README.md +548 -0
  24. package/relay/node_modules/ws/browser.js +8 -0
  25. package/relay/node_modules/ws/index.js +13 -0
  26. package/relay/node_modules/ws/lib/buffer-util.js +131 -0
  27. package/relay/node_modules/ws/lib/constants.js +19 -0
  28. package/relay/node_modules/ws/lib/event-target.js +292 -0
  29. package/relay/node_modules/ws/lib/extension.js +203 -0
  30. package/relay/node_modules/ws/lib/limiter.js +55 -0
  31. package/relay/node_modules/ws/lib/permessage-deflate.js +528 -0
  32. package/relay/node_modules/ws/lib/receiver.js +706 -0
  33. package/relay/node_modules/ws/lib/sender.js +602 -0
  34. package/relay/node_modules/ws/lib/stream.js +161 -0
  35. package/relay/node_modules/ws/lib/subprotocol.js +62 -0
  36. package/relay/node_modules/ws/lib/validation.js +152 -0
  37. package/relay/node_modules/ws/lib/websocket-server.js +554 -0
  38. package/relay/node_modules/ws/lib/websocket.js +1393 -0
  39. package/relay/node_modules/ws/package.json +69 -0
  40. package/relay/node_modules/ws/wrapper.mjs +8 -0
  41. package/relay/package-lock.json +36 -0
  42. package/relay/package.json +12 -0
  43. package/relay/server.js +114 -0
  44. package/skills/claude-code/tab-agent.md +53 -0
  45. package/skills/codex/tab-agent.md +40 -0
@@ -0,0 +1,69 @@
1
+ {
2
+ "name": "ws",
3
+ "version": "8.19.0",
4
+ "description": "Simple to use, blazing fast and thoroughly tested websocket client and server for Node.js",
5
+ "keywords": [
6
+ "HyBi",
7
+ "Push",
8
+ "RFC-6455",
9
+ "WebSocket",
10
+ "WebSockets",
11
+ "real-time"
12
+ ],
13
+ "homepage": "https://github.com/websockets/ws",
14
+ "bugs": "https://github.com/websockets/ws/issues",
15
+ "repository": {
16
+ "type": "git",
17
+ "url": "git+https://github.com/websockets/ws.git"
18
+ },
19
+ "author": "Einar Otto Stangvik <einaros@gmail.com> (http://2x.io)",
20
+ "license": "MIT",
21
+ "main": "index.js",
22
+ "exports": {
23
+ ".": {
24
+ "browser": "./browser.js",
25
+ "import": "./wrapper.mjs",
26
+ "require": "./index.js"
27
+ },
28
+ "./package.json": "./package.json"
29
+ },
30
+ "browser": "browser.js",
31
+ "engines": {
32
+ "node": ">=10.0.0"
33
+ },
34
+ "files": [
35
+ "browser.js",
36
+ "index.js",
37
+ "lib/*.js",
38
+ "wrapper.mjs"
39
+ ],
40
+ "scripts": {
41
+ "test": "nyc --reporter=lcov --reporter=text mocha --throw-deprecation test/*.test.js",
42
+ "integration": "mocha --throw-deprecation test/*.integration.js",
43
+ "lint": "eslint . && prettier --check --ignore-path .gitignore \"**/*.{json,md,yaml,yml}\""
44
+ },
45
+ "peerDependencies": {
46
+ "bufferutil": "^4.0.1",
47
+ "utf-8-validate": ">=5.0.2"
48
+ },
49
+ "peerDependenciesMeta": {
50
+ "bufferutil": {
51
+ "optional": true
52
+ },
53
+ "utf-8-validate": {
54
+ "optional": true
55
+ }
56
+ },
57
+ "devDependencies": {
58
+ "benchmark": "^2.1.4",
59
+ "bufferutil": "^4.0.1",
60
+ "eslint": "^9.0.0",
61
+ "eslint-config-prettier": "^10.0.1",
62
+ "eslint-plugin-prettier": "^5.0.0",
63
+ "globals": "^16.0.0",
64
+ "mocha": "^8.4.0",
65
+ "nyc": "^15.0.0",
66
+ "prettier": "^3.0.0",
67
+ "utf-8-validate": "^6.0.0"
68
+ }
69
+ }
@@ -0,0 +1,8 @@
1
+ import createWebSocketStream from './lib/stream.js';
2
+ import Receiver from './lib/receiver.js';
3
+ import Sender from './lib/sender.js';
4
+ import WebSocket from './lib/websocket.js';
5
+ import WebSocketServer from './lib/websocket-server.js';
6
+
7
+ export { createWebSocketStream, Receiver, Sender, WebSocket, WebSocketServer };
8
+ export default WebSocket;
@@ -0,0 +1,36 @@
1
+ {
2
+ "name": "tab-agent-relay",
3
+ "version": "0.1.0",
4
+ "lockfileVersion": 3,
5
+ "requires": true,
6
+ "packages": {
7
+ "": {
8
+ "name": "tab-agent-relay",
9
+ "version": "0.1.0",
10
+ "dependencies": {
11
+ "ws": "^8.16.0"
12
+ }
13
+ },
14
+ "node_modules/ws": {
15
+ "version": "8.19.0",
16
+ "resolved": "https://registry.npmjs.org/ws/-/ws-8.19.0.tgz",
17
+ "integrity": "sha512-blAT2mjOEIi0ZzruJfIhb3nps74PRWTCz1IjglWEEpQl5XS/UNama6u2/rjFkDDouqr4L67ry+1aGIALViWjDg==",
18
+ "license": "MIT",
19
+ "engines": {
20
+ "node": ">=10.0.0"
21
+ },
22
+ "peerDependencies": {
23
+ "bufferutil": "^4.0.1",
24
+ "utf-8-validate": ">=5.0.2"
25
+ },
26
+ "peerDependenciesMeta": {
27
+ "bufferutil": {
28
+ "optional": true
29
+ },
30
+ "utf-8-validate": {
31
+ "optional": true
32
+ }
33
+ }
34
+ }
35
+ }
36
+ }
@@ -0,0 +1,12 @@
1
+ {
2
+ "name": "tab-agent-relay",
3
+ "version": "0.1.0",
4
+ "description": "WebSocket relay for Tab Agent Chrome extension",
5
+ "main": "server.js",
6
+ "scripts": {
7
+ "start": "node server.js"
8
+ },
9
+ "dependencies": {
10
+ "ws": "^8.16.0"
11
+ }
12
+ }
@@ -0,0 +1,114 @@
1
+ // relay/server.js
2
+ const WebSocket = require('ws');
3
+ const http = require('http');
4
+
5
+ const PORT = process.env.PORT || 9876;
6
+
7
+ const httpServer = http.createServer((req, res) => {
8
+ if (req.url === '/health') {
9
+ res.writeHead(200, { 'Content-Type': 'application/json' });
10
+ res.end(JSON.stringify({ ok: true, clients: wss.clients.size }));
11
+ } else {
12
+ res.writeHead(404);
13
+ res.end('Not found');
14
+ }
15
+ });
16
+
17
+ const wss = new WebSocket.Server({ server: httpServer });
18
+
19
+ // Store extension connection
20
+ let extensionConnection = null;
21
+ const pendingRequests = new Map();
22
+
23
+ function safeParse(data, label) {
24
+ try {
25
+ return JSON.parse(data);
26
+ } catch (error) {
27
+ console.warn(`Invalid JSON from ${label}:`, error);
28
+ return null;
29
+ }
30
+ }
31
+
32
+ function failPendingRequests(reason) {
33
+ for (const [id, pending] of pendingRequests.entries()) {
34
+ try {
35
+ pending.ws.send(JSON.stringify({ id: pending.clientId, ok: false, error: reason }));
36
+ } catch (error) {
37
+ console.warn('Failed to notify pending request:', error);
38
+ }
39
+ pendingRequests.delete(id);
40
+ }
41
+ }
42
+
43
+ wss.on('connection', (ws, req) => {
44
+ const isExtension = req.headers['x-client-type'] === 'extension';
45
+
46
+ if (isExtension) {
47
+ console.log('Extension connected');
48
+ extensionConnection = ws;
49
+
50
+ ws.on('message', (data) => {
51
+ const message = safeParse(data, 'extension');
52
+ if (!message || typeof message.id === 'undefined') {
53
+ return;
54
+ }
55
+ const { id, ...response } = message;
56
+
57
+ const pending = pendingRequests.get(id);
58
+ if (pending) {
59
+ pending.ws.send(JSON.stringify({ id: pending.clientId, ...response }));
60
+ pendingRequests.delete(id);
61
+ }
62
+ });
63
+
64
+ ws.on('close', () => {
65
+ console.log('Extension disconnected');
66
+ extensionConnection = null;
67
+ failPendingRequests('Extension disconnected');
68
+ });
69
+
70
+ } else {
71
+ console.log('Skill client connected');
72
+
73
+ ws.on('message', async (data) => {
74
+ const message = safeParse(data, 'client');
75
+ if (!message || typeof message.id === 'undefined') {
76
+ return;
77
+ }
78
+ const { id, ...command } = message;
79
+
80
+ console.log(`Command: ${command.action}`, command);
81
+
82
+ if (!extensionConnection) {
83
+ ws.send(JSON.stringify({ id, ok: false, error: 'Extension not connected' }));
84
+ return;
85
+ }
86
+
87
+ const internalId = Date.now() + Math.random();
88
+ pendingRequests.set(internalId, { ws, clientId: id });
89
+
90
+ extensionConnection.send(JSON.stringify({ id: internalId, ...command }));
91
+ });
92
+
93
+ ws.on('close', () => {
94
+ console.log('Skill client disconnected');
95
+ for (const [id, pending] of pendingRequests.entries()) {
96
+ if (pending.ws === ws) {
97
+ pendingRequests.delete(id);
98
+ }
99
+ }
100
+ });
101
+ }
102
+ });
103
+
104
+ httpServer.listen(PORT, () => {
105
+ console.log(`Tab Agent Relay running on ws://localhost:${PORT}`);
106
+ console.log(`Health check: http://localhost:${PORT}/health`);
107
+ });
108
+
109
+ process.on('SIGINT', () => {
110
+ console.log('\nShutting down...');
111
+ wss.close();
112
+ httpServer.close();
113
+ process.exit(0);
114
+ });
@@ -0,0 +1,53 @@
1
+ ---
2
+ name: tab-agent
3
+ description: Browser control via WebSocket - snapshot, click, type, fill, evaluate, screenshot
4
+ ---
5
+
6
+ # Tab Agent
7
+
8
+ WebSocket `ws://localhost:9876`. User activates tabs via extension icon (green = active).
9
+
10
+ ## Before First Command
11
+
12
+ ```bash
13
+ curl -s http://localhost:9876/health || (npx tab-agent start &)
14
+ sleep 2
15
+ ```
16
+
17
+ ## Commands
18
+
19
+ ```json
20
+ {"id": 1, "action": "tabs"} // list active tabs
21
+ {"id": 2, "action": "snapshot", "tabId": ID} // get page with refs [e1], [e2]...
22
+ {"id": 3, "action": "screenshot", "tabId": ID} // viewport screenshot
23
+ {"id": 4, "action": "screenshot", "tabId": ID, "fullPage": true} // full page screenshot
24
+ {"id": 5, "action": "click", "tabId": ID, "ref": "e1"} // click element
25
+ {"id": 6, "action": "fill", "tabId": ID, "ref": "e1", "value": "text"} // fill input
26
+ {"id": 7, "action": "type", "tabId": ID, "ref": "e1", "text": "hello"} // type text
27
+ {"id": 8, "action": "type", "tabId": ID, "ref": "e1", "text": "query", "submit": true} // type and Enter
28
+ {"id": 9, "action": "press", "tabId": ID, "key": "Enter"} // press key
29
+ {"id": 10, "action": "scroll", "tabId": ID, "direction": "down", "amount": 500}
30
+ {"id": 11, "action": "scrollintoview", "tabId": ID, "ref": "e1"} // scroll element visible
31
+ {"id": 12, "action": "navigate", "tabId": ID, "url": "https://..."}
32
+ {"id": 13, "action": "wait", "tabId": ID, "text": "Loading complete"} // wait for text
33
+ {"id": 14, "action": "wait", "tabId": ID, "selector": ".results", "timeout": 5000} // wait for element
34
+ {"id": 15, "action": "evaluate", "tabId": ID, "script": "document.title"} // run JavaScript
35
+ {"id": 16, "action": "batchfill", "tabId": ID, "fields": [{"ref": "e1", "value": "a"}, {"ref": "e2", "value": "b"}]}
36
+ {"id": 17, "action": "dialog", "tabId": ID, "accept": true} // handle alert/confirm
37
+ ```
38
+
39
+ ## Usage
40
+
41
+ 1. `tabs` -> get tabId of active tab
42
+ 2. `snapshot` -> read page, get element refs [e1], [e2]...
43
+ 3. `click`/`fill`/`type` using refs
44
+ 4. If snapshot incomplete (complex page) -> `screenshot` and analyze visually
45
+
46
+ ## Notes
47
+
48
+ - Screenshot returns `{"screenshot": "data:image/png;base64,..."}` - analyze directly
49
+ - Snapshot refs reset on each call - always snapshot before interacting
50
+ - Keys: Enter, Escape, Tab, Backspace, ArrowUp/Down/Left/Right
51
+ - `type` with `submit: true` presses Enter after typing (for search boxes)
52
+ - `evaluate` runs in page context - can access page variables/functions
53
+ - `dialog` handles alert/confirm/prompt - debugger bar appears when attached
@@ -0,0 +1,40 @@
1
+ ---
2
+ name: tab-agent
3
+ description: Browser control via WebSocket
4
+ ---
5
+
6
+ # Tab Agent
7
+
8
+ `ws://localhost:9876` - User activates tabs via extension (green = active)
9
+
10
+ ## Start Relay
11
+
12
+ ```bash
13
+ curl -s http://localhost:9876/health || (npx tab-agent start &)
14
+ ```
15
+
16
+ ## Commands
17
+
18
+ ```
19
+ tabs -> list active tabs
20
+ snapshot tabId -> page with refs [e1], [e2]...
21
+ screenshot tabId -> viewport screenshot (base64 PNG)
22
+ screenshot tabId fullPage=true -> full page screenshot
23
+ click tabId ref -> click element
24
+ fill tabId ref value -> fill input
25
+ type tabId ref text -> type text
26
+ type tabId ref text submit=true -> type and press Enter
27
+ press tabId key -> Enter/Escape/Tab/Arrow*
28
+ scroll tabId direction amount -> scroll page
29
+ scrollintoview tabId ref -> scroll element visible
30
+ navigate tabId url -> go to URL
31
+ wait tabId text="..." -> wait for text
32
+ wait tabId selector="..." timeout=ms -> wait for element
33
+ evaluate tabId script="..." -> run JavaScript
34
+ batchfill tabId fields=[...] -> fill multiple fields
35
+ dialog tabId accept=true -> handle alert/confirm
36
+ ```
37
+
38
+ ## Flow
39
+
40
+ `tabs` -> `snapshot` -> `click`/`fill` -> repeat. Use `screenshot` if snapshot incomplete.