@hanzo/browser-extension 1.7.6 → 1.9.33

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 (144) hide show
  1. package/LICENSE +9 -18
  2. package/dist/browser-extension/content-script.js +1642 -123
  3. package/images/icon128.png +0 -0
  4. package/images/icon16.png +0 -0
  5. package/images/icon48.png +0 -0
  6. package/manifest.json +7 -2
  7. package/package.json +28 -10
  8. package/src/answer/AnswerEngine.tsx +553 -0
  9. package/src/chat-widget.tsx +96 -0
  10. package/src/gui-primitives.tsx +116 -0
  11. package/src/icon128.png +0 -0
  12. package/src/icon16.png +0 -0
  13. package/src/icon32.png +0 -0
  14. package/src/icon48.png +0 -0
  15. package/src/manifest-firefox.json +32 -13
  16. package/src/manifest.json +35 -11
  17. package/src/newtab.css +277 -0
  18. package/src/newtab.html +13 -0
  19. package/src/package.json +1 -1
  20. package/src/popup.css +352 -110
  21. package/src/popup.html +186 -47
  22. package/src/popup.js +196 -0
  23. package/src/sidebar.css +772 -156
  24. package/src/sidebar.html +268 -85
  25. package/src/sidebar.js +885 -0
  26. package/.github/workflows/docs.yml +0 -44
  27. package/.github/workflows/release.yml +0 -55
  28. package/README.md +0 -126
  29. package/dist/browser-extension/README.md +0 -43
  30. package/dist/browser-extension/ai-worker.js +0 -30
  31. package/dist/browser-extension/background-firefox.js +0 -875
  32. package/dist/browser-extension/background.js +0 -2732
  33. package/dist/browser-extension/browser-control.js +0 -683
  34. package/dist/browser-extension/cdp-bridge-server.js +0 -444
  35. package/dist/browser-extension/chrome/ai-worker.js +0 -571
  36. package/dist/browser-extension/chrome/background.js +0 -2732
  37. package/dist/browser-extension/chrome/callback.html +0 -17
  38. package/dist/browser-extension/chrome/content-script.js +0 -1270
  39. package/dist/browser-extension/chrome/icon128.png +0 -0
  40. package/dist/browser-extension/chrome/icon16.png +0 -0
  41. package/dist/browser-extension/chrome/icon32.png +0 -0
  42. package/dist/browser-extension/chrome/icon48.png +0 -0
  43. package/dist/browser-extension/chrome/manifest.json +0 -57
  44. package/dist/browser-extension/chrome/popup.css +0 -519
  45. package/dist/browser-extension/chrome/popup.html +0 -242
  46. package/dist/browser-extension/chrome/popup.js +0 -278
  47. package/dist/browser-extension/chrome/sidebar.css +0 -1176
  48. package/dist/browser-extension/chrome/sidebar.html +0 -280
  49. package/dist/browser-extension/chrome/sidebar.js +0 -779
  50. package/dist/browser-extension/cli.js +0 -233
  51. package/dist/browser-extension/firefox/ai-worker.js +0 -571
  52. package/dist/browser-extension/firefox/background.js +0 -875
  53. package/dist/browser-extension/firefox/callback.html +0 -17
  54. package/dist/browser-extension/firefox/content-script.js +0 -1270
  55. package/dist/browser-extension/firefox/icon128.png +0 -0
  56. package/dist/browser-extension/firefox/icon16.png +0 -0
  57. package/dist/browser-extension/firefox/icon32.png +0 -0
  58. package/dist/browser-extension/firefox/icon48.png +0 -0
  59. package/dist/browser-extension/firefox/manifest.json +0 -66
  60. package/dist/browser-extension/firefox/popup.css +0 -519
  61. package/dist/browser-extension/firefox/popup.html +0 -242
  62. package/dist/browser-extension/firefox/popup.js +0 -278
  63. package/dist/browser-extension/firefox/sidebar.css +0 -1176
  64. package/dist/browser-extension/firefox/sidebar.html +0 -280
  65. package/dist/browser-extension/firefox/sidebar.js +0 -779
  66. package/dist/browser-extension/hanzo-browser-extension-firefox-1.1.0.zip +0 -0
  67. package/dist/browser-extension/icon128.png +0 -0
  68. package/dist/browser-extension/icon16.png +0 -0
  69. package/dist/browser-extension/icon32.png +0 -0
  70. package/dist/browser-extension/icon48.png +0 -0
  71. package/dist/browser-extension/manifest.json +0 -57
  72. package/dist/browser-extension/package.json +0 -41
  73. package/dist/browser-extension/popup.css +0 -468
  74. package/dist/browser-extension/popup.html +0 -191
  75. package/dist/browser-extension/popup.js +0 -278
  76. package/dist/browser-extension/safari/Info.plist +0 -21
  77. package/dist/browser-extension/safari/ai-worker.js +0 -571
  78. package/dist/browser-extension/safari/background.js +0 -2732
  79. package/dist/browser-extension/safari/callback.html +0 -17
  80. package/dist/browser-extension/safari/content-script.js +0 -1270
  81. package/dist/browser-extension/safari/icon128.png +0 -0
  82. package/dist/browser-extension/safari/icon16.png +0 -0
  83. package/dist/browser-extension/safari/icon32.png +0 -0
  84. package/dist/browser-extension/safari/icon48.png +0 -0
  85. package/dist/browser-extension/safari/popup.css +0 -519
  86. package/dist/browser-extension/safari/popup.html +0 -242
  87. package/dist/browser-extension/safari/popup.js +0 -278
  88. package/dist/browser-extension/safari/sidebar.css +0 -1176
  89. package/dist/browser-extension/safari/sidebar.html +0 -280
  90. package/dist/browser-extension/safari/sidebar.js +0 -779
  91. package/dist/browser-extension/sidebar.css +0 -718
  92. package/dist/browser-extension/sidebar.html +0 -184
  93. package/dist/browser-extension/sidebar.js +0 -779
  94. package/dist/browser-extension/webgpu-ai.js +0 -495
  95. package/dist/cdp-bridge-server.js +0 -411
  96. package/dist/hanzo-ai-chrome-1.7.2.zip +0 -0
  97. package/dist/hanzo-ai-chrome-1.7.3.zip +0 -0
  98. package/dist/hanzo-ai-firefox-1.7.2.zip +0 -0
  99. package/dist/hanzo-ai-firefox-1.7.3.zip +0 -0
  100. package/dist/safari/Hanzo AI/Hanzo AI.xcodeproj/project.pbxproj +0 -1047
  101. package/dist/safari/Hanzo AI/Hanzo AI.xcodeproj/project.xcworkspace/contents.xcworkspacedata +0 -7
  102. package/dist/safari/Hanzo AI/Shared (App)/Assets.xcassets/AccentColor.colorset/Contents.json +0 -11
  103. package/dist/safari/Hanzo AI/Shared (App)/Assets.xcassets/AppIcon.appiconset/Contents.json +0 -98
  104. package/dist/safari/Hanzo AI/Shared (App)/Assets.xcassets/AppIcon.appiconset/mac-icon-128@1x.png +0 -0
  105. package/dist/safari/Hanzo AI/Shared (App)/Assets.xcassets/AppIcon.appiconset/mac-icon-128@2x.png +0 -0
  106. package/dist/safari/Hanzo AI/Shared (App)/Assets.xcassets/AppIcon.appiconset/mac-icon-16@1x.png +0 -0
  107. package/dist/safari/Hanzo AI/Shared (App)/Assets.xcassets/AppIcon.appiconset/mac-icon-16@2x.png +0 -0
  108. package/dist/safari/Hanzo AI/Shared (App)/Assets.xcassets/AppIcon.appiconset/mac-icon-256@1x.png +0 -0
  109. package/dist/safari/Hanzo AI/Shared (App)/Assets.xcassets/AppIcon.appiconset/mac-icon-256@2x.png +0 -0
  110. package/dist/safari/Hanzo AI/Shared (App)/Assets.xcassets/AppIcon.appiconset/mac-icon-32@1x.png +0 -0
  111. package/dist/safari/Hanzo AI/Shared (App)/Assets.xcassets/AppIcon.appiconset/mac-icon-32@2x.png +0 -0
  112. package/dist/safari/Hanzo AI/Shared (App)/Assets.xcassets/AppIcon.appiconset/mac-icon-512@1x.png +0 -0
  113. package/dist/safari/Hanzo AI/Shared (App)/Assets.xcassets/AppIcon.appiconset/mac-icon-512@2x.png +0 -0
  114. package/dist/safari/Hanzo AI/Shared (App)/Assets.xcassets/AppIcon.appiconset/universal-icon-1024@1x.png +0 -0
  115. package/dist/safari/Hanzo AI/Shared (App)/Assets.xcassets/Contents.json +0 -6
  116. package/dist/safari/Hanzo AI/Shared (App)/Assets.xcassets/LargeIcon.imageset/Contents.json +0 -21
  117. package/dist/safari/Hanzo AI/Shared (App)/Assets.xcassets/LargeIcon.imageset/icon128.png +0 -0
  118. package/dist/safari/Hanzo AI/Shared (App)/Resources/Base.lproj/Main.html +0 -20
  119. package/dist/safari/Hanzo AI/Shared (App)/Resources/Icon.png +0 -0
  120. package/dist/safari/Hanzo AI/Shared (App)/Resources/Script.js +0 -24
  121. package/dist/safari/Hanzo AI/Shared (App)/Resources/Style.css +0 -61
  122. package/dist/safari/Hanzo AI/Shared (App)/ViewController.swift +0 -81
  123. package/dist/safari/Hanzo AI/Shared (Extension)/SafariWebExtensionHandler.swift +0 -42
  124. package/dist/safari/Hanzo AI/iOS (App)/AppDelegate.swift +0 -24
  125. package/dist/safari/Hanzo AI/iOS (App)/Base.lproj/LaunchScreen.storyboard +0 -36
  126. package/dist/safari/Hanzo AI/iOS (App)/Base.lproj/Main.storyboard +0 -38
  127. package/dist/safari/Hanzo AI/iOS (App)/Info.plist +0 -27
  128. package/dist/safari/Hanzo AI/iOS (App)/SceneDelegate.swift +0 -18
  129. package/dist/safari/Hanzo AI/iOS (Extension)/Info.plist +0 -13
  130. package/dist/safari/Hanzo AI/macOS (App)/AppDelegate.swift +0 -21
  131. package/dist/safari/Hanzo AI/macOS (App)/Base.lproj/Main.storyboard +0 -124
  132. package/dist/safari/Hanzo AI/macOS (App)/Info.plist +0 -8
  133. package/dist/safari/Hanzo AI/macOS (Extension)/Info.plist +0 -13
  134. package/docs/_config.yml +0 -13
  135. package/docs/_layouts/default.html +0 -435
  136. package/docs/index.md +0 -54
  137. package/hanzo-ai-chrome-v1.7.6.zip +0 -0
  138. package/hanzo-ai-firefox-v1.7.6.zip +0 -0
  139. package/install-extension.sh +0 -75
  140. package/test/claude-integration.test.ts +0 -251
  141. package/test/server-frameworks-edge-cases.test.ts +0 -145
  142. package/test/server-frameworks.test.ts +0 -395
  143. package/test-cdp-connection.js +0 -140
  144. package/tsconfig.json +0 -19
@@ -1,875 +0,0 @@
1
- "use strict";
2
- (() => {
3
- // src/auth.ts
4
- function generateRandomString(length) {
5
- const array = new Uint8Array(length);
6
- crypto.getRandomValues(array);
7
- return Array.from(array, (b) => b.toString(36).padStart(2, "0")).join("").slice(0, length);
8
- }
9
- async function sha256(plain) {
10
- const encoder = new TextEncoder();
11
- return crypto.subtle.digest("SHA-256", encoder.encode(plain));
12
- }
13
- function base64UrlEncode(buffer) {
14
- const bytes = new Uint8Array(buffer);
15
- let binary = "";
16
- for (const byte of bytes) {
17
- binary += String.fromCharCode(byte);
18
- }
19
- return btoa(binary).replace(/\+/g, "-").replace(/\//g, "_").replace(/=+$/, "");
20
- }
21
- async function generatePkceChallenge() {
22
- const codeVerifier = generateRandomString(64);
23
- const hash = await sha256(codeVerifier);
24
- const codeChallenge = base64UrlEncode(hash);
25
- return { codeVerifier, codeChallenge };
26
- }
27
- function generateState() {
28
- return generateRandomString(32);
29
- }
30
- var IAM_LOGIN = "https://hanzo.id";
31
- var IAM_API = "https://iam.hanzo.ai";
32
- var CLIENT_ID = "app-hanzo";
33
- var SCOPES = "openid profile email";
34
- function getRedirectUri() {
35
- return "https://hanzo.ai/callback";
36
- }
37
- var STORAGE_KEYS = {
38
- accessToken: "hanzo_iam_access_token",
39
- refreshToken: "hanzo_iam_refresh_token",
40
- idToken: "hanzo_iam_id_token",
41
- expiresAt: "hanzo_iam_expires_at",
42
- user: "hanzo_iam_user"
43
- };
44
- async function storeTokens(tokens) {
45
- const data = {
46
- [STORAGE_KEYS.accessToken]: tokens.access_token
47
- };
48
- if (tokens.refresh_token) data[STORAGE_KEYS.refreshToken] = tokens.refresh_token;
49
- if (tokens.id_token) data[STORAGE_KEYS.idToken] = tokens.id_token;
50
- if (tokens.expires_in) {
51
- data[STORAGE_KEYS.expiresAt] = Date.now() + tokens.expires_in * 1e3;
52
- }
53
- return chrome.storage.local.set(data);
54
- }
55
- async function getStoredTokens() {
56
- return new Promise((resolve) => {
57
- chrome.storage.local.get(
58
- [STORAGE_KEYS.accessToken, STORAGE_KEYS.refreshToken, STORAGE_KEYS.expiresAt],
59
- (result) => {
60
- resolve({
61
- accessToken: result[STORAGE_KEYS.accessToken] || null,
62
- refreshToken: result[STORAGE_KEYS.refreshToken] || null,
63
- expiresAt: result[STORAGE_KEYS.expiresAt] || null
64
- });
65
- }
66
- );
67
- });
68
- }
69
- async function login() {
70
- const { codeVerifier, codeChallenge } = await generatePkceChallenge();
71
- const state = generateState();
72
- const redirectUri = getRedirectUri();
73
- const authorizeUrl = new URL(`${IAM_LOGIN}/login/oauth/authorize`);
74
- authorizeUrl.searchParams.set("client_id", CLIENT_ID);
75
- authorizeUrl.searchParams.set("response_type", "code");
76
- authorizeUrl.searchParams.set("redirect_uri", redirectUri);
77
- authorizeUrl.searchParams.set("code_challenge", codeChallenge);
78
- authorizeUrl.searchParams.set("code_challenge_method", "S256");
79
- authorizeUrl.searchParams.set("scope", SCOPES);
80
- authorizeUrl.searchParams.set("state", state);
81
- const callbackUrl = await openAuthTab(authorizeUrl.toString(), redirectUri);
82
- const url = new URL(callbackUrl);
83
- const returnedState = url.searchParams.get("state");
84
- if (returnedState !== state) throw new Error("State mismatch \u2014 possible CSRF");
85
- const code = url.searchParams.get("code");
86
- const directToken = url.searchParams.get("access_token");
87
- if (directToken) {
88
- const tokens = {
89
- access_token: directToken,
90
- refresh_token: url.searchParams.get("refresh_token") || void 0,
91
- token_type: "Bearer"
92
- };
93
- await storeTokens(tokens);
94
- } else if (code) {
95
- const tokenResponse = await fetch(`${IAM_API}/api/login/oauth/access_token`, {
96
- method: "POST",
97
- headers: { "Content-Type": "application/json" },
98
- body: JSON.stringify({
99
- grant_type: "authorization_code",
100
- client_id: CLIENT_ID,
101
- code,
102
- redirect_uri: redirectUri,
103
- code_verifier: codeVerifier
104
- })
105
- });
106
- if (!tokenResponse.ok) {
107
- const errText = await tokenResponse.text();
108
- throw new Error(`Token exchange failed: ${errText}`);
109
- }
110
- const tokens = await tokenResponse.json();
111
- await storeTokens(tokens);
112
- } else {
113
- const error = url.searchParams.get("error_description") || url.searchParams.get("error") || "No authorization code or token";
114
- throw new Error(error);
115
- }
116
- const { accessToken } = await getStoredTokens();
117
- if (!accessToken) throw new Error("Login succeeded but no token was stored");
118
- const user = await fetchUserInfo(accessToken);
119
- await chrome.storage.local.set({ [STORAGE_KEYS.user]: user });
120
- return user;
121
- }
122
- function openAuthTab(authorizeUrl, redirectUriPrefix) {
123
- return new Promise((resolve, reject) => {
124
- let authTabId;
125
- const timeout = setTimeout(() => {
126
- cleanup();
127
- reject(new Error("Login timed out"));
128
- }, 3e5);
129
- function cleanup() {
130
- clearTimeout(timeout);
131
- chrome.tabs.onUpdated.removeListener(onTabUpdated);
132
- chrome.tabs.onRemoved.removeListener(onTabRemoved);
133
- }
134
- function onTabUpdated(tabId, changeInfo) {
135
- if (tabId !== authTabId || !changeInfo.url) return;
136
- if (changeInfo.url.startsWith(redirectUriPrefix)) {
137
- const callbackUrl = changeInfo.url;
138
- cleanup();
139
- chrome.tabs.remove(tabId).catch(() => {
140
- });
141
- resolve(callbackUrl);
142
- }
143
- }
144
- function onTabRemoved(tabId) {
145
- if (tabId !== authTabId) return;
146
- cleanup();
147
- reject(new Error("Login cancelled"));
148
- }
149
- chrome.tabs.onUpdated.addListener(onTabUpdated);
150
- chrome.tabs.onRemoved.addListener(onTabRemoved);
151
- chrome.tabs.create({ url: authorizeUrl }, (tab) => {
152
- if (chrome.runtime.lastError || !tab?.id) {
153
- cleanup();
154
- reject(new Error(chrome.runtime.lastError?.message || "Failed to open login tab"));
155
- return;
156
- }
157
- authTabId = tab.id;
158
- });
159
- });
160
- }
161
- async function logout() {
162
- await chrome.storage.local.remove(Object.values(STORAGE_KEYS));
163
- }
164
- async function getValidAccessToken() {
165
- const { accessToken, refreshToken, expiresAt } = await getStoredTokens();
166
- if (!accessToken) return null;
167
- if (expiresAt && Date.now() < expiresAt - 6e4) {
168
- return accessToken;
169
- }
170
- if (refreshToken) {
171
- try {
172
- const refreshed = await refreshAccessToken(refreshToken);
173
- return refreshed;
174
- } catch {
175
- await logout();
176
- return null;
177
- }
178
- }
179
- return accessToken;
180
- }
181
- async function refreshAccessToken(refreshToken) {
182
- const response = await fetch(`${IAM_API}/api/login/oauth/access_token`, {
183
- method: "POST",
184
- headers: { "Content-Type": "application/json" },
185
- body: JSON.stringify({
186
- grant_type: "refresh_token",
187
- client_id: CLIENT_ID,
188
- refresh_token: refreshToken
189
- })
190
- });
191
- if (!response.ok) throw new Error("Token refresh failed");
192
- const tokens = await response.json();
193
- await storeTokens(tokens);
194
- return tokens.access_token;
195
- }
196
- async function fetchUserInfo(accessToken) {
197
- const response = await fetch(`${IAM_API}/api/userinfo`, {
198
- headers: { Authorization: `Bearer ${accessToken}` }
199
- });
200
- if (!response.ok) throw new Error("Failed to fetch user info");
201
- return response.json();
202
- }
203
- async function getUserInfo() {
204
- return new Promise((resolve) => {
205
- chrome.storage.local.get([STORAGE_KEYS.user], (result) => {
206
- resolve(result[STORAGE_KEYS.user] || null);
207
- });
208
- });
209
- }
210
- async function isAuthenticated() {
211
- const { accessToken } = await getStoredTokens();
212
- return !!accessToken;
213
- }
214
- async function getAuthStatus() {
215
- const [authenticated, user] = await Promise.all([
216
- isAuthenticated(),
217
- getUserInfo()
218
- ]);
219
- return { authenticated, user };
220
- }
221
-
222
- // src/chat-client.ts
223
- var API_BASE = "https://api.hanzo.ai";
224
- async function listModels(token) {
225
- const response = await fetch(`${API_BASE}/v1/models`, {
226
- headers: { Authorization: `Bearer ${token}` }
227
- });
228
- if (!response.ok) {
229
- throw new Error(`Failed to list models: ${response.status}`);
230
- }
231
- const data = await response.json();
232
- return data.data || data.models || data || [];
233
- }
234
- async function chatCompletion(token, options) {
235
- const response = await fetch(`${API_BASE}/v1/chat/completions`, {
236
- method: "POST",
237
- headers: {
238
- "Content-Type": "application/json",
239
- Authorization: `Bearer ${token}`
240
- },
241
- body: JSON.stringify(options)
242
- });
243
- if (!response.ok) {
244
- const text = await response.text();
245
- throw new Error(`Chat API error ${response.status}: ${text}`);
246
- }
247
- const data = await response.json();
248
- return data.choices?.[0]?.message?.content || "";
249
- }
250
-
251
- // src/background-firefox.ts
252
- var controlSession = {
253
- active: false,
254
- tabId: null,
255
- task: null,
256
- startedAt: null
257
- };
258
- var HanzoFirefoxExtension = class {
259
- constructor() {
260
- this.wsConnection = null;
261
- this.cdpPort = 9223;
262
- this.reconnectTimeout = 3e3;
263
- console.log("[Hanzo] Firefox extension initializing...");
264
- this.loadPort().then(() => this.connect());
265
- }
266
- async loadPort() {
267
- try {
268
- const result = await browser.storage.local.get(["cdpPort"]);
269
- if (result.cdpPort) this.cdpPort = result.cdpPort;
270
- } catch {
271
- }
272
- }
273
- connect() {
274
- const url = `ws://localhost:${this.cdpPort}/cdp`;
275
- console.log(`[Hanzo] Connecting to ${url}`);
276
- this.wsConnection = new WebSocket(url);
277
- this.wsConnection.onopen = () => {
278
- console.log("[Hanzo] WebSocket CONNECTED");
279
- this.register();
280
- };
281
- this.wsConnection.onmessage = async (event) => {
282
- try {
283
- const message = JSON.parse(event.data);
284
- console.log("[Hanzo] Received:", message.method || "response");
285
- const response = await this.handleCommand(message);
286
- this.send(response);
287
- } catch (e) {
288
- console.error("[Hanzo] Message handler error:", e);
289
- this.send({ id: 0, error: { code: -1, message: String(e) } });
290
- }
291
- };
292
- this.wsConnection.onerror = () => {
293
- };
294
- this.wsConnection.onclose = () => {
295
- console.log("[Hanzo] Disconnected, reconnecting...");
296
- setTimeout(() => this.connect(), this.reconnectTimeout);
297
- };
298
- }
299
- isConnected() {
300
- return this.wsConnection?.readyState === WebSocket.OPEN;
301
- }
302
- sendToWs(data) {
303
- if (this.wsConnection?.readyState === WebSocket.OPEN) {
304
- this.wsConnection.send(JSON.stringify(data));
305
- }
306
- }
307
- register() {
308
- this.sendToWs({
309
- type: "register",
310
- role: "cdp-provider",
311
- capabilities: [
312
- "navigate",
313
- "screenshot",
314
- "click",
315
- "type",
316
- "evaluate",
317
- "tabs",
318
- "fill",
319
- "reload",
320
- "goBack",
321
- "goForward"
322
- ]
323
- });
324
- }
325
- send(data) {
326
- this.sendToWs(data);
327
- }
328
- async handleCommand(message) {
329
- const { id, method, params = {} } = message;
330
- try {
331
- const result = await this.executeMethod(method, params);
332
- return { id, result };
333
- } catch (e) {
334
- return { id, error: { code: -1, message: String(e) } };
335
- }
336
- }
337
- executeScriptWithTimeout(tabId, code, timeoutMs = 1e4) {
338
- return Promise.race([
339
- browser.tabs.executeScript(tabId, { code }),
340
- new Promise(
341
- (_, reject) => setTimeout(() => reject(new Error("executeScript timeout")), timeoutMs)
342
- )
343
- ]);
344
- }
345
- async executeMethod(method, params) {
346
- const [activeTab] = await browser.tabs.query({
347
- active: true,
348
- currentWindow: true
349
- });
350
- switch (method) {
351
- case "Target.getTargets": {
352
- const allTabs = await browser.tabs.query({});
353
- return {
354
- targetInfos: allTabs.map((tab) => ({
355
- targetId: `tab-${tab.id}`,
356
- type: "page",
357
- title: tab.title || "",
358
- url: tab.url || ""
359
- }))
360
- };
361
- }
362
- case "hanzo.url": {
363
- if (activeTab) return { result: { value: activeTab.url || "" } };
364
- throw new Error("No active tab");
365
- }
366
- case "hanzo.title": {
367
- if (activeTab) return { result: { value: activeTab.title || "" } };
368
- throw new Error("No active tab");
369
- }
370
- case "hanzo.tabInfo": {
371
- if (activeTab) {
372
- return {
373
- result: {
374
- value: {
375
- id: activeTab.id,
376
- url: activeTab.url,
377
- title: activeTab.title,
378
- status: activeTab.status,
379
- favIconUrl: activeTab.favIconUrl
380
- }
381
- }
382
- };
383
- }
384
- throw new Error("No active tab");
385
- }
386
- case "Page.navigate": {
387
- if (activeTab?.id) {
388
- this.notifyOverlay(activeTab.id, "ai.control.status", { text: `Navigating to ${params.url}` });
389
- await browser.tabs.update(activeTab.id, { url: params.url });
390
- return { frameId: "main" };
391
- }
392
- throw new Error("No active tab");
393
- }
394
- case "Page.reload": {
395
- if (activeTab?.id) {
396
- await browser.tabs.reload(activeTab.id);
397
- return { success: true };
398
- }
399
- throw new Error("No active tab");
400
- }
401
- case "Page.goBack": {
402
- if (activeTab?.id) {
403
- await browser.tabs.goBack(activeTab.id);
404
- return { success: true };
405
- }
406
- throw new Error("No active tab");
407
- }
408
- case "Page.goForward": {
409
- if (activeTab?.id) {
410
- await browser.tabs.goForward(activeTab.id);
411
- return { success: true };
412
- }
413
- throw new Error("No active tab");
414
- }
415
- case "Runtime.evaluate": {
416
- if (activeTab?.id) {
417
- const expression = params.expression;
418
- const timeout = params.timeout || 1e4;
419
- const results = await this.executeScriptWithTimeout(activeTab.id, expression, timeout);
420
- return { result: { value: results[0] } };
421
- }
422
- throw new Error("No active tab");
423
- }
424
- case "Page.captureScreenshot":
425
- case "hanzo.screenshot": {
426
- const format = params.format || "jpeg";
427
- const quality = params.quality || (format === "jpeg" ? 80 : void 0);
428
- const opts = { format };
429
- if (quality !== void 0) opts.quality = quality;
430
- const dataUrl = await browser.tabs.captureVisibleTab(opts);
431
- const base64 = dataUrl.replace(/^data:image\/\w+;base64,/, "");
432
- return { data: base64 };
433
- }
434
- case "hanzo.click": {
435
- if (activeTab?.id && params.selector) {
436
- const selector = this.escapeSelector(params.selector);
437
- this.notifyOverlay(activeTab.id, "ai.control.highlight", { selector: params.selector });
438
- this.notifyOverlay(activeTab.id, "ai.control.status", { text: `Clicking ${params.selector}` });
439
- await this.executeScriptWithTimeout(
440
- activeTab.id,
441
- `document.querySelector('${selector}')?.click()`
442
- );
443
- return { success: true };
444
- }
445
- throw new Error("No active tab or selector");
446
- }
447
- case "hanzo.fill": {
448
- if (activeTab?.id && params.selector) {
449
- const selector = this.escapeSelector(params.selector);
450
- const value = this.escapeValue(params.value || "");
451
- this.notifyOverlay(activeTab.id, "ai.control.highlight", { selector: params.selector });
452
- this.notifyOverlay(activeTab.id, "ai.control.status", { text: `Filling ${params.selector}` });
453
- await this.executeScriptWithTimeout(activeTab.id, `
454
- var el = document.querySelector('${selector}');
455
- if (el) {
456
- el.value = '${value}';
457
- el.dispatchEvent(new Event('input', {bubbles: true}));
458
- el.dispatchEvent(new Event('change', {bubbles: true}));
459
- }
460
- `);
461
- return { success: true };
462
- }
463
- throw new Error("No active tab or selector");
464
- }
465
- case "hanzo.control.start": {
466
- const targetTabId = await this.resolveControlTabId(params.tabId);
467
- if (targetTabId) {
468
- await this.startControlSession(targetTabId, params.task);
469
- return { success: true };
470
- }
471
- throw new Error("No active tab");
472
- }
473
- case "hanzo.control.stop": {
474
- await this.stopControlSession();
475
- return { success: true };
476
- }
477
- case "hanzo.type": {
478
- if (activeTab?.id && params.text) {
479
- const text = this.escapeValue(params.text);
480
- await this.executeScriptWithTimeout(activeTab.id, `
481
- var el = document.activeElement;
482
- if (el && (el.tagName === 'INPUT' || el.tagName === 'TEXTAREA')) {
483
- el.value += '${text}';
484
- el.dispatchEvent(new Event('input', {bubbles: true}));
485
- }
486
- `);
487
- return { success: true };
488
- }
489
- throw new Error("No active tab or text");
490
- }
491
- case "Browser.getVersion": {
492
- return {
493
- protocolVersion: "1.3",
494
- product: "Hanzo Firefox Extension",
495
- userAgent: navigator.userAgent
496
- };
497
- }
498
- case "hanzo.getTabs":
499
- case "hanzo.listTabs": {
500
- const tabs = await browser.tabs.query({});
501
- return {
502
- tabs: tabs.map((tab) => ({
503
- id: tab.id,
504
- url: tab.url,
505
- title: tab.title,
506
- active: tab.active
507
- }))
508
- };
509
- }
510
- default:
511
- console.warn("[Hanzo] Unknown method:", method);
512
- return { error: `Unknown method: ${method}` };
513
- }
514
- }
515
- notifyOverlay(tabId, action, data) {
516
- browser.tabs.sendMessage(tabId, { action, ...data }).catch(() => {
517
- });
518
- }
519
- async getActiveTabId() {
520
- const [tab] = await browser.tabs.query({ active: true, currentWindow: true });
521
- return tab?.id ?? null;
522
- }
523
- async resolveControlTabId(tabId) {
524
- if (typeof tabId === "number") return tabId;
525
- if (controlSession.active && controlSession.tabId !== null) return controlSession.tabId;
526
- return this.getActiveTabId();
527
- }
528
- async startControlSession(tabId, task) {
529
- if (controlSession.active && controlSession.tabId !== null && controlSession.tabId !== tabId) {
530
- this.notifyOverlay(controlSession.tabId, "ai.control.stop", {});
531
- }
532
- controlSession.active = true;
533
- controlSession.tabId = tabId;
534
- controlSession.task = task || "AI is controlling this page";
535
- controlSession.startedAt = Date.now();
536
- this.notifyOverlay(tabId, "ai.control.start", { task: controlSession.task });
537
- }
538
- async stopControlSession() {
539
- if (controlSession.active && controlSession.tabId !== null) {
540
- this.notifyOverlay(controlSession.tabId, "ai.control.stop", {});
541
- }
542
- controlSession.active = false;
543
- controlSession.tabId = null;
544
- controlSession.task = null;
545
- controlSession.startedAt = null;
546
- }
547
- escapeSelector(selector) {
548
- return selector.replace(/'/g, "\\'");
549
- }
550
- escapeValue(value) {
551
- return value.replace(/\\/g, "\\\\").replace(/'/g, "\\'").replace(/\n/g, "\\n");
552
- }
553
- };
554
- async function getRagConfig() {
555
- const result = await browser.storage.local.get([
556
- "hanzo_rag_endpoint",
557
- "hanzo_rag_api_key",
558
- "hanzo_rag_kb",
559
- "hanzo_rag_top_k",
560
- "hanzo_rag_include_tab_context"
561
- ]);
562
- const topKParsed = Number.parseInt(String(result.hanzo_rag_top_k ?? 5), 10);
563
- return {
564
- endpoint: String(result.hanzo_rag_endpoint || "").trim(),
565
- apiKey: String(result.hanzo_rag_api_key || "").trim(),
566
- knowledgeBase: String(result.hanzo_rag_kb || "").trim(),
567
- topK: Number.isFinite(topKParsed) ? Math.max(1, Math.min(topKParsed, 20)) : 5,
568
- includeTabContext: result.hanzo_rag_include_tab_context !== false
569
- };
570
- }
571
- function normalizeRagSnippets(raw) {
572
- if (!raw) return [];
573
- const candidates = [];
574
- if (Array.isArray(raw)) candidates.push(...raw);
575
- if (Array.isArray(raw?.snippets)) candidates.push(...raw.snippets);
576
- if (Array.isArray(raw?.documents)) candidates.push(...raw.documents);
577
- if (Array.isArray(raw?.items)) candidates.push(...raw.items);
578
- if (Array.isArray(raw?.results)) candidates.push(...raw.results);
579
- if (!candidates.length && typeof raw?.content === "string") {
580
- candidates.push({ content: raw.content, source: raw.source || "rag-endpoint" });
581
- }
582
- return candidates.map((item) => {
583
- const content = String(item?.content ?? item?.text ?? item?.snippet ?? item?.body ?? "").trim();
584
- if (!content) return null;
585
- return {
586
- content,
587
- title: item?.title ? String(item.title) : void 0,
588
- source: item?.source ? String(item.source) : void 0,
589
- score: typeof item?.score === "number" ? item.score : void 0,
590
- url: item?.url ? String(item.url) : void 0
591
- };
592
- }).filter((item) => !!item).slice(0, 20);
593
- }
594
- async function queryRagEndpoint(params) {
595
- if (!params.endpoint) return [];
596
- const headers = { "Content-Type": "application/json" };
597
- if (params.apiKey) headers.Authorization = `Bearer ${params.apiKey}`;
598
- const response = await fetch(params.endpoint, {
599
- method: "POST",
600
- headers,
601
- body: JSON.stringify({
602
- query: params.query,
603
- top_k: params.topK,
604
- knowledge_base: params.knowledgeBase || void 0,
605
- page_context: params.includeTabContext ? params.pageContext : void 0,
606
- source: "hanzo-browser-extension-firefox"
607
- })
608
- });
609
- if (!response.ok) {
610
- const text = await response.text();
611
- throw new Error(`RAG endpoint error ${response.status}: ${text || "Unknown error"}`);
612
- }
613
- const raw = await response.json();
614
- return normalizeRagSnippets(raw).map((snippet) => ({
615
- ...snippet,
616
- source: snippet.source || "rag-endpoint"
617
- }));
618
- }
619
- browser.runtime.onMessage.addListener((request, sender, sendResponse) => {
620
- (async () => {
621
- switch (request.action) {
622
- // --- Auth (shared module — same code as Chrome) ---
623
- case "auth.login":
624
- try {
625
- const user = await login();
626
- sendResponse({ success: true, user });
627
- } catch (e) {
628
- sendResponse({ success: false, error: e.message });
629
- }
630
- break;
631
- case "auth.logout":
632
- try {
633
- await logout();
634
- sendResponse({ success: true });
635
- } catch (e) {
636
- sendResponse({ success: false, error: e.message });
637
- }
638
- break;
639
- case "auth.status":
640
- try {
641
- const status = await getAuthStatus();
642
- sendResponse({ success: true, ...status });
643
- } catch (e) {
644
- sendResponse({ success: false, error: e.message });
645
- }
646
- break;
647
- case "auth.getToken":
648
- try {
649
- const token = await getValidAccessToken();
650
- sendResponse({ success: !!token, token });
651
- } catch (e) {
652
- sendResponse({ success: false, error: e.message });
653
- }
654
- break;
655
- // --- Cloud Chat (shared module) ---
656
- case "chat.listModels":
657
- try {
658
- const token = await getValidAccessToken();
659
- if (!token) {
660
- sendResponse({ success: false, error: "Not authenticated" });
661
- break;
662
- }
663
- const models = await listModels(token);
664
- sendResponse({ success: true, models });
665
- } catch (e) {
666
- sendResponse({ success: false, error: e.message });
667
- }
668
- break;
669
- case "chat.complete":
670
- try {
671
- const token = await getValidAccessToken();
672
- if (!token) {
673
- sendResponse({ success: false, error: "Not authenticated" });
674
- break;
675
- }
676
- const model = String(request.model || "gpt-4o");
677
- const messagesInput = Array.isArray(request.messages) ? request.messages : [];
678
- const messages = messagesInput.filter((m) => m && typeof m.content === "string" && typeof m.role === "string").map((m) => ({
679
- role: m.role === "system" || m.role === "assistant" ? m.role : "user",
680
- content: String(m.content)
681
- })).slice(-24);
682
- if (!messages.length) {
683
- sendResponse({ success: false, error: "messages are required" });
684
- break;
685
- }
686
- const content = await chatCompletion(token, {
687
- model,
688
- messages,
689
- temperature: typeof request.temperature === "number" ? request.temperature : void 0,
690
- max_tokens: typeof request.max_tokens === "number" ? request.max_tokens : void 0
691
- });
692
- sendResponse({ success: true, content });
693
- } catch (e) {
694
- sendResponse({ success: false, error: e.message });
695
- }
696
- break;
697
- // --- ZAP Protocol (not available on Firefox — return proper status) ---
698
- case "zap.status":
699
- sendResponse({ success: true, zap: { connected: false, mcps: [], extensionId: "firefox" } });
700
- break;
701
- case "zap.discover":
702
- sendResponse({ success: true, mcps: [] });
703
- break;
704
- case "zap.listTools":
705
- sendResponse({ success: true, tools: [] });
706
- break;
707
- case "zap.connect":
708
- case "zap.callTool":
709
- sendResponse({ success: false, error: "ZAP protocol not available on Firefox yet" });
710
- break;
711
- // --- Element selection (click-to-code) ---
712
- case "elementSelected":
713
- if (hanzoExtension.isConnected()) {
714
- hanzoExtension.sendToWs({
715
- type: "elementSelected",
716
- data: request.data
717
- });
718
- }
719
- sendResponse({ success: true });
720
- break;
721
- // --- Tab Filesystem ---
722
- case "listTabFS": {
723
- const allTabs = await browser.tabs.query({});
724
- sendResponse({
725
- success: true,
726
- tabs: allTabs.map((tab) => ({
727
- id: tab.id,
728
- url: tab.url,
729
- title: tab.title,
730
- active: tab.active
731
- }))
732
- });
733
- break;
734
- }
735
- case "readTabFS":
736
- sendResponse({ success: false, error: "Tab filesystem read not available on Firefox" });
737
- break;
738
- case "writeTabFS":
739
- sendResponse({ success: false, error: "Tab filesystem write not available on Firefox" });
740
- break;
741
- // --- WebGPU / Local AI ---
742
- case "checkWebGPU": {
743
- try {
744
- if (!navigator.gpu) {
745
- sendResponse({ available: false });
746
- break;
747
- }
748
- const adapter = await navigator.gpu.requestAdapter();
749
- sendResponse({
750
- available: !!adapter,
751
- adapter: adapter?.name || "Unknown GPU",
752
- models: []
753
- });
754
- } catch {
755
- sendResponse({ available: false });
756
- }
757
- break;
758
- }
759
- case "runLocalAI":
760
- sendResponse({ success: false, error: "Local AI not available on Firefox" });
761
- break;
762
- case "listAgents":
763
- sendResponse({ success: true, agents: [] });
764
- break;
765
- case "stopAgent":
766
- sendResponse({ success: false, error: "Agent workers not available on Firefox" });
767
- break;
768
- case "launchAIWorker":
769
- sendResponse({ success: false, error: "AI workers not available on Firefox" });
770
- break;
771
- // --- RAG ---
772
- case "rag.query": {
773
- try {
774
- const config = await getRagConfig();
775
- const topKInput = Number.parseInt(String(request.topK ?? config.topK), 10);
776
- const params = {
777
- endpoint: String(request.endpoint ?? config.endpoint ?? "").trim(),
778
- apiKey: String(request.apiKey ?? config.apiKey ?? "").trim(),
779
- query: String(request.query || "").trim(),
780
- topK: Number.isFinite(topKInput) ? Math.max(1, Math.min(topKInput, 20)) : 5,
781
- knowledgeBase: String(request.knowledgeBase ?? config.knowledgeBase ?? "").trim(),
782
- includeTabContext: request.includeTabContext !== void 0 ? !!request.includeTabContext : config.includeTabContext,
783
- pageContext: request.pageContext && typeof request.pageContext === "object" ? {
784
- url: request.pageContext.url ? String(request.pageContext.url) : void 0,
785
- title: request.pageContext.title ? String(request.pageContext.title) : void 0
786
- } : void 0
787
- };
788
- if (!params.query) {
789
- sendResponse({ success: false, error: "Query is required" });
790
- break;
791
- }
792
- if (!params.endpoint) {
793
- sendResponse({ success: true, source: "none", snippets: [] });
794
- break;
795
- }
796
- const snippets = await queryRagEndpoint(params);
797
- sendResponse({ success: true, source: snippets.length ? "rag-endpoint" : "none", snippets });
798
- } catch (e) {
799
- sendResponse({ success: false, error: e.message });
800
- }
801
- break;
802
- }
803
- // --- Bridge status ---
804
- case "bridge.status":
805
- sendResponse({
806
- success: true,
807
- connected: hanzoExtension.isConnected(),
808
- browsers: hanzoExtension.isConnected() ? ["firefox"] : []
809
- });
810
- break;
811
- // --- Config ---
812
- case "config.save":
813
- sendResponse({ success: true });
814
- break;
815
- // --- In-page overlay (forwarded to content script) ---
816
- case "page.overlay.toggle":
817
- case "page.overlay.show":
818
- case "page.overlay.hide":
819
- case "page.overlay.status": {
820
- try {
821
- const tabId = typeof request.tabId === "number" ? request.tabId : sender.tab?.id ?? await hanzoExtension.getActiveTabId();
822
- if (tabId === null) {
823
- sendResponse({ success: false, error: "No target tab" });
824
- break;
825
- }
826
- const response = await browser.tabs.sendMessage(tabId, { action: request.action }).catch(() => null);
827
- sendResponse({ success: true, tabId, ...response || {} });
828
- } catch (e) {
829
- sendResponse({ success: false, error: e.message });
830
- }
831
- break;
832
- }
833
- // --- AI Control Overlay ---
834
- case "ai.control.start":
835
- case "ai.control.cursor":
836
- case "ai.control.highlight":
837
- case "ai.control.status": {
838
- const targetTabId = request.tabId || controlSession.tabId || await hanzoExtension.getActiveTabId();
839
- if (!targetTabId) {
840
- sendResponse({ success: false, error: "No target tab found" });
841
- break;
842
- }
843
- if (request.action !== "ai.control.start" && controlSession.active && controlSession.tabId && targetTabId !== controlSession.tabId) {
844
- sendResponse({ success: false, error: `Control locked to tab ${controlSession.tabId}` });
845
- break;
846
- }
847
- if (request.action === "ai.control.start") {
848
- await hanzoExtension.startControlSession(targetTabId, request.task);
849
- } else {
850
- browser.tabs.sendMessage(targetTabId, request).catch(() => {
851
- });
852
- }
853
- sendResponse({ success: true, tabId: targetTabId });
854
- break;
855
- }
856
- case "ai.control.stop":
857
- case "ai.control.cancel":
858
- await hanzoExtension.stopControlSession();
859
- sendResponse({ success: true });
860
- break;
861
- default:
862
- sendResponse({ success: false, error: `Unknown action: ${request.action}` });
863
- break;
864
- }
865
- })();
866
- return true;
867
- });
868
- var hanzoExtension = new HanzoFirefoxExtension();
869
- browser.tabs.onRemoved.addListener((tabId) => {
870
- if (controlSession.active && controlSession.tabId === tabId) {
871
- void hanzoExtension.stopControlSession();
872
- }
873
- });
874
- console.log("[Hanzo] Firefox background script loaded");
875
- })();