@hanzo/browser-extension 1.7.6 → 1.9.31

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 (135) hide show
  1. package/LICENSE +9 -18
  2. package/dist/browser-extension/README.md +1 -1
  3. package/dist/browser-extension/background-firefox.js +4180 -499
  4. package/dist/browser-extension/background.js +6932 -2419
  5. package/dist/browser-extension/browser-control.js +652 -18
  6. package/dist/browser-extension/chrome/background.js +6932 -2419
  7. package/dist/browser-extension/chrome/content-script.js +1642 -123
  8. package/dist/browser-extension/chrome/icon128.png +0 -0
  9. package/dist/browser-extension/chrome/icon16.png +0 -0
  10. package/dist/browser-extension/chrome/icon48.png +0 -0
  11. package/dist/browser-extension/chrome/manifest.json +20 -11
  12. package/dist/browser-extension/chrome/popup.css +352 -110
  13. package/dist/browser-extension/chrome/popup.html +159 -48
  14. package/dist/browser-extension/chrome/popup.js +1418 -46
  15. package/dist/browser-extension/chrome/sidebar.css +772 -156
  16. package/dist/browser-extension/chrome/sidebar.html +268 -85
  17. package/dist/browser-extension/chrome/sidebar.js +25846 -84
  18. package/dist/browser-extension/cli.js +2 -2
  19. package/dist/browser-extension/content-script.js +1642 -123
  20. package/dist/browser-extension/firefox/background.js +4180 -499
  21. package/dist/browser-extension/firefox/content-script.js +1642 -123
  22. package/dist/browser-extension/firefox/icon128.png +0 -0
  23. package/dist/browser-extension/firefox/icon16.png +0 -0
  24. package/dist/browser-extension/firefox/icon48.png +0 -0
  25. package/dist/browser-extension/firefox/manifest.json +24 -13
  26. package/dist/browser-extension/firefox/popup.css +352 -110
  27. package/dist/browser-extension/firefox/popup.html +159 -48
  28. package/dist/browser-extension/firefox/popup.js +1418 -46
  29. package/dist/browser-extension/firefox/sidebar.css +772 -156
  30. package/dist/browser-extension/firefox/sidebar.html +268 -85
  31. package/dist/browser-extension/firefox/sidebar.js +25846 -84
  32. package/dist/browser-extension/icon128.png +0 -0
  33. package/dist/browser-extension/icon16.png +0 -0
  34. package/dist/browser-extension/icon48.png +0 -0
  35. package/dist/browser-extension/manifest.json +20 -11
  36. package/dist/browser-extension/package.json +1 -1
  37. package/dist/browser-extension/popup.js +1418 -46
  38. package/dist/browser-extension/safari/Info.plist +3 -3
  39. package/dist/browser-extension/safari/background.js +6932 -2419
  40. package/dist/browser-extension/safari/content-script.js +1642 -123
  41. package/dist/browser-extension/safari/icon128.png +0 -0
  42. package/dist/browser-extension/safari/icon16.png +0 -0
  43. package/dist/browser-extension/safari/icon48.png +0 -0
  44. package/dist/browser-extension/safari/popup.css +352 -110
  45. package/dist/browser-extension/safari/popup.html +159 -48
  46. package/dist/browser-extension/safari/popup.js +1418 -46
  47. package/dist/browser-extension/safari/sidebar.css +772 -156
  48. package/dist/browser-extension/safari/sidebar.html +268 -85
  49. package/dist/browser-extension/safari/sidebar.js +25846 -84
  50. package/dist/browser-extension/sidebar.js +25846 -84
  51. package/dist/browser-extension/webgpu-ai.js +78 -5
  52. package/images/icon128.png +0 -0
  53. package/images/icon16.png +0 -0
  54. package/images/icon48.png +0 -0
  55. package/manifest.json +7 -2
  56. package/package.json +27 -10
  57. package/src/chat-widget.tsx +96 -0
  58. package/src/gui-primitives.tsx +116 -0
  59. package/src/icon128.png +0 -0
  60. package/src/icon16.png +0 -0
  61. package/src/icon32.png +0 -0
  62. package/src/icon48.png +0 -0
  63. package/src/manifest-firefox.json +23 -12
  64. package/src/manifest.json +19 -10
  65. package/src/package.json +1 -1
  66. package/src/popup.css +352 -110
  67. package/src/popup.html +159 -48
  68. package/src/popup.js +196 -0
  69. package/src/sidebar.css +772 -156
  70. package/src/sidebar.html +268 -85
  71. package/src/sidebar.js +885 -0
  72. package/.github/workflows/docs.yml +0 -44
  73. package/.github/workflows/release.yml +0 -55
  74. package/README.md +0 -126
  75. package/dist/browser-extension/ai-worker.js +0 -30
  76. package/dist/browser-extension/cdp-bridge-server.js +0 -444
  77. package/dist/browser-extension/chrome/icon32.png +0 -0
  78. package/dist/browser-extension/firefox/icon32.png +0 -0
  79. package/dist/browser-extension/hanzo-browser-extension-firefox-1.1.0.zip +0 -0
  80. package/dist/browser-extension/icon32.png +0 -0
  81. package/dist/browser-extension/popup.css +0 -468
  82. package/dist/browser-extension/popup.html +0 -191
  83. package/dist/browser-extension/safari/icon32.png +0 -0
  84. package/dist/browser-extension/sidebar.css +0 -718
  85. package/dist/browser-extension/sidebar.html +0 -184
  86. package/dist/cdp-bridge-server.js +0 -411
  87. package/dist/hanzo-ai-chrome-1.7.2.zip +0 -0
  88. package/dist/hanzo-ai-chrome-1.7.3.zip +0 -0
  89. package/dist/hanzo-ai-firefox-1.7.2.zip +0 -0
  90. package/dist/hanzo-ai-firefox-1.7.3.zip +0 -0
  91. package/dist/safari/Hanzo AI/Hanzo AI.xcodeproj/project.pbxproj +0 -1047
  92. package/dist/safari/Hanzo AI/Hanzo AI.xcodeproj/project.xcworkspace/contents.xcworkspacedata +0 -7
  93. package/dist/safari/Hanzo AI/Shared (App)/Assets.xcassets/AccentColor.colorset/Contents.json +0 -11
  94. package/dist/safari/Hanzo AI/Shared (App)/Assets.xcassets/AppIcon.appiconset/Contents.json +0 -98
  95. package/dist/safari/Hanzo AI/Shared (App)/Assets.xcassets/AppIcon.appiconset/mac-icon-128@1x.png +0 -0
  96. package/dist/safari/Hanzo AI/Shared (App)/Assets.xcassets/AppIcon.appiconset/mac-icon-128@2x.png +0 -0
  97. package/dist/safari/Hanzo AI/Shared (App)/Assets.xcassets/AppIcon.appiconset/mac-icon-16@1x.png +0 -0
  98. package/dist/safari/Hanzo AI/Shared (App)/Assets.xcassets/AppIcon.appiconset/mac-icon-16@2x.png +0 -0
  99. package/dist/safari/Hanzo AI/Shared (App)/Assets.xcassets/AppIcon.appiconset/mac-icon-256@1x.png +0 -0
  100. package/dist/safari/Hanzo AI/Shared (App)/Assets.xcassets/AppIcon.appiconset/mac-icon-256@2x.png +0 -0
  101. package/dist/safari/Hanzo AI/Shared (App)/Assets.xcassets/AppIcon.appiconset/mac-icon-32@1x.png +0 -0
  102. package/dist/safari/Hanzo AI/Shared (App)/Assets.xcassets/AppIcon.appiconset/mac-icon-32@2x.png +0 -0
  103. package/dist/safari/Hanzo AI/Shared (App)/Assets.xcassets/AppIcon.appiconset/mac-icon-512@1x.png +0 -0
  104. package/dist/safari/Hanzo AI/Shared (App)/Assets.xcassets/AppIcon.appiconset/mac-icon-512@2x.png +0 -0
  105. package/dist/safari/Hanzo AI/Shared (App)/Assets.xcassets/AppIcon.appiconset/universal-icon-1024@1x.png +0 -0
  106. package/dist/safari/Hanzo AI/Shared (App)/Assets.xcassets/Contents.json +0 -6
  107. package/dist/safari/Hanzo AI/Shared (App)/Assets.xcassets/LargeIcon.imageset/Contents.json +0 -21
  108. package/dist/safari/Hanzo AI/Shared (App)/Assets.xcassets/LargeIcon.imageset/icon128.png +0 -0
  109. package/dist/safari/Hanzo AI/Shared (App)/Resources/Base.lproj/Main.html +0 -20
  110. package/dist/safari/Hanzo AI/Shared (App)/Resources/Icon.png +0 -0
  111. package/dist/safari/Hanzo AI/Shared (App)/Resources/Script.js +0 -24
  112. package/dist/safari/Hanzo AI/Shared (App)/Resources/Style.css +0 -61
  113. package/dist/safari/Hanzo AI/Shared (App)/ViewController.swift +0 -81
  114. package/dist/safari/Hanzo AI/Shared (Extension)/SafariWebExtensionHandler.swift +0 -42
  115. package/dist/safari/Hanzo AI/iOS (App)/AppDelegate.swift +0 -24
  116. package/dist/safari/Hanzo AI/iOS (App)/Base.lproj/LaunchScreen.storyboard +0 -36
  117. package/dist/safari/Hanzo AI/iOS (App)/Base.lproj/Main.storyboard +0 -38
  118. package/dist/safari/Hanzo AI/iOS (App)/Info.plist +0 -27
  119. package/dist/safari/Hanzo AI/iOS (App)/SceneDelegate.swift +0 -18
  120. package/dist/safari/Hanzo AI/iOS (Extension)/Info.plist +0 -13
  121. package/dist/safari/Hanzo AI/macOS (App)/AppDelegate.swift +0 -21
  122. package/dist/safari/Hanzo AI/macOS (App)/Base.lproj/Main.storyboard +0 -124
  123. package/dist/safari/Hanzo AI/macOS (App)/Info.plist +0 -8
  124. package/dist/safari/Hanzo AI/macOS (Extension)/Info.plist +0 -13
  125. package/docs/_config.yml +0 -13
  126. package/docs/_layouts/default.html +0 -435
  127. package/docs/index.md +0 -54
  128. package/hanzo-ai-chrome-v1.7.6.zip +0 -0
  129. package/hanzo-ai-firefox-v1.7.6.zip +0 -0
  130. package/install-extension.sh +0 -75
  131. package/test/claude-integration.test.ts +0 -251
  132. package/test/server-frameworks-edge-cases.test.ts +0 -145
  133. package/test/server-frameworks.test.ts +0 -395
  134. package/test-cdp-connection.js +0 -140
  135. package/tsconfig.json +0 -19
@@ -1,44 +0,0 @@
1
- name: Deploy Docs
2
-
3
- on:
4
- push:
5
- branches: [main]
6
- workflow_dispatch:
7
-
8
- permissions:
9
- contents: read
10
- pages: write
11
- id-token: write
12
-
13
- concurrency:
14
- group: pages
15
- cancel-in-progress: false
16
-
17
- jobs:
18
- build:
19
- runs-on: ubuntu-latest
20
- steps:
21
- - uses: actions/checkout@v4
22
-
23
- - name: Setup Pages
24
- uses: actions/configure-pages@v4
25
-
26
- - name: Build with Jekyll
27
- uses: actions/jekyll-build-pages@v1
28
- with:
29
- source: ./docs
30
- destination: ./_site
31
-
32
- - name: Upload artifact
33
- uses: actions/upload-pages-artifact@v3
34
-
35
- deploy:
36
- environment:
37
- name: github-pages
38
- url: ${{ steps.deployment.outputs.page_url }}
39
- runs-on: ubuntu-latest
40
- needs: build
41
- steps:
42
- - name: Deploy to GitHub Pages
43
- id: deployment
44
- uses: actions/deploy-pages@v4
@@ -1,55 +0,0 @@
1
- name: Release
2
-
3
- on:
4
- push:
5
- tags:
6
- - 'v*'
7
-
8
- permissions:
9
- contents: write
10
-
11
- jobs:
12
- build:
13
- runs-on: ubuntu-latest
14
- steps:
15
- - uses: actions/checkout@v4
16
-
17
- - name: Setup Node.js
18
- uses: actions/setup-node@v4
19
- with:
20
- node-version: '20'
21
- cache: 'npm'
22
-
23
- - name: Install dependencies
24
- run: npm ci || npm install
25
-
26
- - name: Build extension
27
- run: npm run build
28
-
29
- - name: Get version
30
- id: version
31
- run: echo "version=${GITHUB_REF#refs/tags/}" >> $GITHUB_OUTPUT
32
-
33
- - name: Package Chrome extension
34
- run: |
35
- cd dist
36
- zip -r ../hanzo-browser-extension-chrome-${{ steps.version.outputs.version }}.zip .
37
-
38
- - name: Package Firefox extension
39
- run: |
40
- # Copy Firefox manifest if exists
41
- if [ -f src/manifest-firefox.json ]; then
42
- cp src/manifest-firefox.json dist/manifest.json
43
- fi
44
- cd dist
45
- zip -r ../hanzo-browser-extension-firefox-${{ steps.version.outputs.version }}.zip .
46
-
47
- - name: Create GitHub Release
48
- uses: softprops/action-gh-release@v2
49
- with:
50
- files: |
51
- hanzo-browser-extension-chrome-*.zip
52
- hanzo-browser-extension-firefox-*.zip
53
- generate_release_notes: true
54
- draft: false
55
- prerelease: ${{ contains(github.ref, 'alpha') || contains(github.ref, 'beta') || contains(github.ref, 'rc') }}
package/README.md DELETED
@@ -1,126 +0,0 @@
1
- # Hanzo AI Browser Extension
2
-
3
- AI-powered browser extension for Chrome, Firefox, Edge, and Safari.
4
-
5
- [![Chrome](https://img.shields.io/badge/Chrome-4285F4?style=for-the-badge&logo=googlechrome&logoColor=white)](https://chrome.google.com/webstore)
6
- [![Firefox](https://img.shields.io/badge/Firefox-FF7139?style=for-the-badge&logo=firefox&logoColor=white)](https://addons.mozilla.org)
7
- [![Edge](https://img.shields.io/badge/Edge-0078D7?style=for-the-badge&logo=microsoftedge&logoColor=white)](https://microsoftedge.microsoft.com/addons)
8
- [![Safari](https://img.shields.io/badge/Safari-000000?style=for-the-badge&logo=safari&logoColor=white)](https://apps.apple.com)
9
-
10
- ## Features
11
-
12
- - **AI Chat Sidebar**: Chat with Claude, GPT-4, Gemini, and more
13
- - **Page Analysis**: Summarize, explain, and query any webpage
14
- - **Selection Actions**: Right-click menu for AI operations on selected text
15
- - **MCP Integration**: Access 260+ MCP tools from your browser
16
- - **Cloud Sync**: Sync conversations and settings via cloud.hanzo.ai
17
-
18
- ## Installation
19
-
20
- ### Chrome / Edge / Brave
21
-
22
- 1. Download the latest release from [Releases](https://github.com/hanzoai/browser-extension/releases)
23
- 2. Open `chrome://extensions` (or `edge://extensions`)
24
- 3. Enable "Developer mode"
25
- 4. Click "Load unpacked" and select the `dist` folder
26
-
27
- ### Firefox
28
-
29
- 1. Download the latest release
30
- 2. Open `about:debugging`
31
- 3. Click "This Firefox" → "Load Temporary Add-on"
32
- 4. Select `manifest.json`
33
-
34
- ### Safari
35
-
36
- 1. Enable developer mode in Safari preferences
37
- 2. Download the macOS app version from [Releases](https://github.com/hanzoai/browser-extension/releases)
38
- 3. Open the app and enable the extension in Safari preferences
39
-
40
- ## Development
41
-
42
- ```bash
43
- # Install dependencies
44
- npm install
45
-
46
- # Build extension
47
- npm run build
48
-
49
- # Watch mode for development
50
- npm run watch
51
-
52
- # Run tests
53
- npm test
54
- ```
55
-
56
- ## Building for Production
57
-
58
- ```bash
59
- # Build for all browsers
60
- npm run build
61
-
62
- # The dist/ folder contains the extension ready for loading
63
- ```
64
-
65
- ## Project Structure
66
-
67
- ```
68
- browser-extension/
69
- ├── src/
70
- │ ├── background/ # Service worker / background script
71
- │ ├── content/ # Content scripts injected into pages
72
- │ ├── popup/ # Extension popup UI
73
- │ ├── sidebar/ # AI chat sidebar
74
- │ └── shared/ # Shared utilities
75
- ├── dist/ # Built extension
76
- ├── manifest.json # Extension manifest (MV3)
77
- ├── package.json
78
- └── tsconfig.json
79
- ```
80
-
81
- ## Configuration
82
-
83
- ### API Keys
84
-
85
- The extension uses your Hanzo AI account for API access. Login at:
86
- - [cloud.hanzo.ai](https://cloud.hanzo.ai) - Cloud dashboard
87
- - [iam.hanzo.ai](https://iam.hanzo.ai) - API key management
88
-
89
- Or set environment variables for direct provider access:
90
- - `ANTHROPIC_API_KEY` - Claude models
91
- - `OPENAI_API_KEY` - GPT models
92
- - `GOOGLE_API_KEY` - Gemini models
93
-
94
- ### Permissions
95
-
96
- The extension requests minimal permissions:
97
- - `activeTab` - Access to current tab when activated
98
- - `storage` - Store settings and conversations locally
99
- - `contextMenus` - Right-click menu integration
100
-
101
- ## Architecture
102
-
103
- ```
104
- ┌─────────────────┐ ┌──────────────────┐ ┌─────────────────┐
105
- │ Content Script │ ◄──► │ Background │ ◄──► │ Hanzo API │
106
- │ (Page Context) │ │ (Service Worker) │ │ (api.hanzo.ai) │
107
- └─────────────────┘ └──────────────────┘ └─────────────────┘
108
- │ │
109
- │ │
110
- ▼ ▼
111
- ┌─────────────────┐ ┌──────────────────┐
112
- │ Popup / Sidebar │ │ MCP Bridge │
113
- │ (React UI) │ │ (WebSocket) │
114
- └─────────────────┘ └──────────────────┘
115
- ```
116
-
117
- ## Related Projects
118
-
119
- - [ide-extension](https://github.com/hanzoai/ide-extension) - VS Code and JetBrains plugins
120
- - [hanzo-mcp](https://github.com/hanzoai/mcp) - Model Context Protocol tools
121
- - [hanzo.vim](https://github.com/hanzoai/hanzo.vim) - Vim/Neovim plugin
122
- - [hanzo.el](https://github.com/hanzoai/hanzo.el) - Emacs package
123
-
124
- ## License
125
-
126
- MIT - See [LICENSE](LICENSE)
@@ -1,30 +0,0 @@
1
- // AI Worker stub for browser extension
2
- // This is a Web Worker that handles AI inference tasks per-tab
3
-
4
- self.onmessage = function(event) {
5
- const { type, tabId, modelName } = event.data;
6
-
7
- switch (type) {
8
- case 'init':
9
- console.log(`[AI Worker] Initialized for tab ${tabId} with model ${modelName}`);
10
- self.postMessage({ type: 'ready', tabId });
11
- break;
12
-
13
- case 'screenshot':
14
- // Process screenshot for AI analysis (stub)
15
- self.postMessage({ type: 'analysis', result: 'Screenshot received' });
16
- break;
17
-
18
- case 'contentMessage':
19
- // Handle content script messages (stub)
20
- self.postMessage({ type: 'contentResponse', response: { received: true } });
21
- break;
22
-
23
- case 'broadcast':
24
- // Handle broadcast messages (stub)
25
- break;
26
-
27
- default:
28
- console.warn('[AI Worker] Unknown message type:', type);
29
- }
30
- };
@@ -1,444 +0,0 @@
1
- #!/usr/bin/env node
2
- "use strict";
3
- var __create = Object.create;
4
- var __defProp = Object.defineProperty;
5
- var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
6
- var __getOwnPropNames = Object.getOwnPropertyNames;
7
- var __getProtoOf = Object.getPrototypeOf;
8
- var __hasOwnProp = Object.prototype.hasOwnProperty;
9
- var __export = (target, all) => {
10
- for (var name in all)
11
- __defProp(target, name, { get: all[name], enumerable: true });
12
- };
13
- var __copyProps = (to, from, except, desc) => {
14
- if (from && typeof from === "object" || typeof from === "function") {
15
- for (let key of __getOwnPropNames(from))
16
- if (!__hasOwnProp.call(to, key) && key !== except)
17
- __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
18
- }
19
- return to;
20
- };
21
- var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
22
- // If the importer is in node compatibility mode or this is not an ESM
23
- // file that has been converted to a CommonJS file using a Babel-
24
- // compatible transform (i.e. "__esModule" has not been set), then set
25
- // "default" to the CommonJS "module.exports" for node compatibility.
26
- isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
27
- mod
28
- ));
29
- var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
30
-
31
- // src/cdp-bridge-server.ts
32
- var cdp_bridge_server_exports = {};
33
- __export(cdp_bridge_server_exports, {
34
- CDPBridgeServer: () => CDPBridgeServer
35
- });
36
- module.exports = __toCommonJS(cdp_bridge_server_exports);
37
- var import_ws = require("ws");
38
- var readline = __toESM(require("readline"));
39
- var fs = __toESM(require("fs"));
40
- var path = __toESM(require("path"));
41
- var CDPBridgeServer = class {
42
- constructor(port = 9223) {
43
- this.clients = /* @__PURE__ */ new Map();
44
- this.pendingCommands = /* @__PURE__ */ new Map();
45
- this.commandId = 0;
46
- this.httpPort = port;
47
- this.wss = new import_ws.WebSocketServer({ port, path: "/cdp" });
48
- this.wss.on("connection", (ws) => {
49
- console.log("[hanzo.browser] Extension connected");
50
- ws.on("message", (data) => {
51
- try {
52
- const message = JSON.parse(data.toString());
53
- this.handleMessage(ws, message);
54
- } catch (error) {
55
- console.error("[hanzo.browser] Error:", error.message);
56
- }
57
- });
58
- ws.on("close", () => {
59
- console.log("[hanzo.browser] Extension disconnected");
60
- this.clients.delete(ws);
61
- });
62
- ws.on("error", (error) => {
63
- console.error("[hanzo.browser] WebSocket error:", error);
64
- });
65
- });
66
- console.log(`[hanzo.browser] Server listening on ws://localhost:${port}/cdp`);
67
- }
68
- handleMessage(ws, message) {
69
- var _a;
70
- if (message.type === "register") {
71
- this.clients.set(ws, {
72
- ws,
73
- capabilities: message.capabilities || [],
74
- browser: message.browser || "unknown"
75
- });
76
- console.log(`[hanzo.browser] Registered ${message.browser || "unknown"}:`, (_a = message.capabilities) == null ? void 0 : _a.join(", "));
77
- return;
78
- }
79
- if (message.type === "config") {
80
- this.saveConfig(message.key, message.value);
81
- return;
82
- }
83
- if (message.id !== void 0) {
84
- const pending = this.pendingCommands.get(message.id);
85
- if (pending) {
86
- if (message.error) {
87
- pending.reject(new Error(message.error.message));
88
- } else {
89
- pending.resolve(message.result);
90
- }
91
- this.pendingCommands.delete(message.id);
92
- }
93
- return;
94
- }
95
- if (message.type === "event") {
96
- console.log(`[hanzo.browser] Event: ${message.method}`);
97
- }
98
- }
99
- /** Save a config value to ~/.hanzo/extension/config.json */
100
- saveConfig(key, value) {
101
- const configDir = path.join(
102
- process.env.HOME || process.env.USERPROFILE || ".",
103
- ".hanzo",
104
- "extension"
105
- );
106
- const configPath = path.join(configDir, "config.json");
107
- try {
108
- fs.mkdirSync(configDir, { recursive: true });
109
- let config = {};
110
- if (fs.existsSync(configPath)) {
111
- config = JSON.parse(fs.readFileSync(configPath, "utf-8"));
112
- }
113
- config[key] = value;
114
- fs.writeFileSync(configPath, JSON.stringify(config, null, 2) + "\n");
115
- console.log(`[hanzo.browser] Config saved: ${key} = ${JSON.stringify(value)}`);
116
- } catch (error) {
117
- console.error(`[hanzo.browser] Failed to save config: ${error.message}`);
118
- }
119
- }
120
- /** Get connected browser names */
121
- getConnectedBrowsers() {
122
- return Array.from(this.clients.values()).map((c) => c.browser || "unknown").filter((b) => b !== "unknown");
123
- }
124
- async sendRaw(method, params) {
125
- const client = Array.from(this.clients.values())[0];
126
- if (!client) {
127
- throw new Error("No browser extension connected");
128
- }
129
- const id = ++this.commandId;
130
- return new Promise((resolve, reject) => {
131
- this.pendingCommands.set(id, { resolve, reject });
132
- client.ws.send(JSON.stringify({ id, method, params }));
133
- setTimeout(() => {
134
- if (this.pendingCommands.has(id)) {
135
- this.pendingCommands.delete(id);
136
- reject(new Error("Command timeout"));
137
- }
138
- }, 3e4);
139
- });
140
- }
141
- /**
142
- * Unified hanzo.browser interface - matches mcp__hanzo__browser pattern
143
- */
144
- async browser(params) {
145
- const { action, ...rest } = params;
146
- switch (action) {
147
- // Navigation
148
- case "navigate":
149
- return this.sendRaw("Page.navigate", { url: rest.url });
150
- case "navigate_back":
151
- case "go_back":
152
- return this.sendRaw("Page.goBack");
153
- case "navigate_forward":
154
- case "go_forward":
155
- return this.sendRaw("Page.goForward");
156
- case "reload":
157
- return this.sendRaw("Page.reload");
158
- case "url":
159
- return this.sendRaw("hanzo.url");
160
- case "title":
161
- return this.sendRaw("hanzo.title");
162
- case "tab_info":
163
- return this.sendRaw("hanzo.tabInfo");
164
- case "content":
165
- return this.sendRaw("Runtime.evaluate", {
166
- expression: "document.documentElement.outerHTML",
167
- returnByValue: true
168
- });
169
- // Screenshots
170
- case "screenshot":
171
- const screenshot = await this.sendRaw("hanzo.screenshot", {
172
- format: rest.format || "png",
173
- fullPage: rest.fullPage
174
- });
175
- if (rest.filename && screenshot.data) {
176
- const buffer = Buffer.from(screenshot.data, "base64");
177
- fs.writeFileSync(rest.filename, buffer);
178
- return { saved: rest.filename, bytes: buffer.length };
179
- }
180
- return screenshot;
181
- case "snapshot":
182
- return this.sendRaw("Accessibility.getFullAXTree");
183
- // Input - Click
184
- case "click":
185
- return this.sendRaw("hanzo.click", {
186
- selector: rest.selector || rest.ref
187
- });
188
- case "dblclick":
189
- case "double_click":
190
- return this.sendRaw("hanzo.dblclick", {
191
- selector: rest.selector || rest.ref
192
- });
193
- case "hover":
194
- return this.sendRaw("hanzo.hover", {
195
- selector: rest.selector || rest.ref
196
- });
197
- // Input - Type
198
- case "type":
199
- return this.sendRaw("hanzo.type", {
200
- selector: rest.selector || rest.ref,
201
- text: rest.text
202
- });
203
- case "fill":
204
- return this.sendRaw("hanzo.fill", {
205
- selector: rest.selector || rest.ref,
206
- value: rest.value || rest.text
207
- });
208
- case "clear":
209
- return this.sendRaw("hanzo.clear", {
210
- selector: rest.selector || rest.ref
211
- });
212
- case "press_key":
213
- case "press":
214
- return this.sendRaw("Input.dispatchKeyEvent", {
215
- type: "keyDown",
216
- key: rest.key
217
- });
218
- // Forms
219
- case "select_option":
220
- return this.sendRaw("hanzo.select", {
221
- selector: rest.selector || rest.ref,
222
- value: rest.value
223
- });
224
- case "check":
225
- return this.sendRaw("hanzo.check", {
226
- selector: rest.selector || rest.ref
227
- });
228
- case "uncheck":
229
- return this.sendRaw("hanzo.uncheck", {
230
- selector: rest.selector || rest.ref
231
- });
232
- // Evaluate
233
- case "evaluate":
234
- return this.sendRaw("Runtime.evaluate", {
235
- expression: rest.code || rest.function || rest.expression,
236
- returnByValue: true
237
- });
238
- // Wait
239
- case "wait":
240
- await new Promise((resolve) => setTimeout(resolve, (rest.timeout || 1) * 1e3));
241
- return { waited: rest.timeout || 1 };
242
- case "wait_for_load":
243
- return this.sendRaw("Page.waitForLoadState", {
244
- state: rest.state || "load"
245
- });
246
- // Tabs
247
- case "tabs":
248
- case "list_tabs":
249
- return this.sendRaw("Target.getTargets");
250
- case "new_tab":
251
- return this.sendRaw("Target.createTarget", { url: rest.url || "about:blank" });
252
- case "close_tab":
253
- return this.sendRaw("Target.closeTarget", { targetId: rest.tabId });
254
- case "select_tab":
255
- return this.sendRaw("Target.activateTarget", { targetId: rest.tabId });
256
- // Console/Network
257
- case "console_messages":
258
- case "console":
259
- return this.sendRaw("Console.getMessages");
260
- case "network_requests":
261
- return this.sendRaw("Network.getResponseBodies");
262
- // Status
263
- case "status":
264
- return {
265
- connected: this.clients.size > 0,
266
- clients: this.clients.size,
267
- port: this.httpPort
268
- };
269
- default:
270
- return this.sendRaw(action, rest);
271
- }
272
- }
273
- isConnected() {
274
- return this.clients.size > 0;
275
- }
276
- close() {
277
- this.wss.close();
278
- }
279
- };
280
- async function startJSONRPCServer(bridgeServer) {
281
- const http = require("http");
282
- const httpServer = http.createServer(async (req, res) => {
283
- if (req.method === "POST") {
284
- let body = "";
285
- req.on("data", (chunk) => body += chunk);
286
- req.on("end", async () => {
287
- try {
288
- const request = JSON.parse(body);
289
- const result = await bridgeServer.browser(request.params || request);
290
- res.writeHead(200, { "Content-Type": "application/json" });
291
- res.end(JSON.stringify({ result }));
292
- } catch (error) {
293
- res.writeHead(500, { "Content-Type": "application/json" });
294
- res.end(JSON.stringify({ error: error.message }));
295
- }
296
- });
297
- } else {
298
- res.writeHead(200, { "Content-Type": "application/json" });
299
- res.end(JSON.stringify({
300
- service: "hanzo.browser",
301
- connected: bridgeServer.isConnected(),
302
- browsers: bridgeServer.getConnectedBrowsers(),
303
- actions: [
304
- "navigate",
305
- "navigate_back",
306
- "reload",
307
- "url",
308
- "title",
309
- "content",
310
- "screenshot",
311
- "snapshot",
312
- "click",
313
- "dblclick",
314
- "hover",
315
- "type",
316
- "fill",
317
- "clear",
318
- "press_key",
319
- "select_option",
320
- "check",
321
- "uncheck",
322
- "evaluate",
323
- "wait",
324
- "wait_for_load",
325
- "tabs",
326
- "new_tab",
327
- "close_tab",
328
- "select_tab",
329
- "console",
330
- "network_requests",
331
- "takeover.start",
332
- "takeover.end",
333
- "takeover.cursor",
334
- "status"
335
- ]
336
- }));
337
- }
338
- });
339
- httpServer.listen(9224, () => {
340
- console.log("[hanzo.browser] HTTP API at http://localhost:9224");
341
- });
342
- }
343
- async function main() {
344
- const port = parseInt(process.env.CDP_PORT || "9223");
345
- const server = new CDPBridgeServer(port);
346
- startJSONRPCServer(server);
347
- console.log("\n[hanzo.browser] Commands:");
348
- console.log(" navigate <url> - Go to URL");
349
- console.log(" screenshot [file] - Capture screen");
350
- console.log(" click <selector> - Click element");
351
- console.log(" type <sel> <text> - Type into element");
352
- console.log(" eval <js> - Run JavaScript");
353
- console.log(" tabs - List all tabs");
354
- console.log(" status - Connection status");
355
- console.log(" quit - Exit\n");
356
- const rl = readline.createInterface({
357
- input: process.stdin,
358
- output: process.stdout
359
- });
360
- const prompt = () => {
361
- rl.question("hanzo.browser> ", async (line) => {
362
- var _a, _b, _c, _d;
363
- const parts = line.trim().split(" ");
364
- const action = parts[0];
365
- const args = parts.slice(1);
366
- if (!action) {
367
- prompt();
368
- return;
369
- }
370
- try {
371
- let result;
372
- switch (action) {
373
- case "quit":
374
- case "exit":
375
- server.close();
376
- rl.close();
377
- process.exit(0);
378
- case "navigate":
379
- result = await server.browser({ action: "navigate", url: args[0] });
380
- console.log("Navigated to:", args[0]);
381
- break;
382
- case "screenshot":
383
- result = await server.browser({
384
- action: "screenshot",
385
- filename: args[0] || `screenshot-${Date.now()}.png`
386
- });
387
- console.log("Screenshot:", result);
388
- break;
389
- case "click":
390
- result = await server.browser({ action: "click", selector: args[0] });
391
- console.log("Clicked:", args[0]);
392
- break;
393
- case "type":
394
- result = await server.browser({
395
- action: "type",
396
- selector: args[0],
397
- text: args.slice(1).join(" ")
398
- });
399
- console.log("Typed into:", args[0]);
400
- break;
401
- case "eval":
402
- result = await server.browser({
403
- action: "evaluate",
404
- expression: args.join(" ")
405
- });
406
- console.log("Result:", (_a = result == null ? void 0 : result.result) == null ? void 0 : _a.value);
407
- break;
408
- case "tabs":
409
- result = await server.browser({ action: "tabs" });
410
- console.log("Tabs:", (_b = result == null ? void 0 : result.targetInfos) == null ? void 0 : _b.map((t) => t.title).join(", "));
411
- break;
412
- case "status":
413
- result = await server.browser({ action: "status" });
414
- console.log("Status:", result);
415
- break;
416
- case "url":
417
- result = await server.browser({ action: "url" });
418
- console.log("URL:", (_c = result == null ? void 0 : result.result) == null ? void 0 : _c.value);
419
- break;
420
- case "title":
421
- result = await server.browser({ action: "title" });
422
- console.log("Title:", (_d = result == null ? void 0 : result.result) == null ? void 0 : _d.value);
423
- break;
424
- default:
425
- result = await server.browser({ action, ...Object.fromEntries(
426
- args.map((a, i) => [i === 0 ? "selector" : `arg${i}`, a])
427
- ) });
428
- console.log("Result:", result);
429
- }
430
- } catch (error) {
431
- console.error("Error:", error.message);
432
- }
433
- prompt();
434
- });
435
- };
436
- prompt();
437
- }
438
- if (require.main === module) {
439
- main();
440
- }
441
- // Annotate the CommonJS export names for ESM import in node:
442
- 0 && (module.exports = {
443
- CDPBridgeServer
444
- });