browserzap 0.6.1 → 0.7.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.
- package/CHANGELOG.md +46 -0
- package/README.md +31 -19
- package/THIRD_PARTY_NOTICES.md +3 -3
- package/bridge-server/extension-id.js +64 -1
- package/bridge-server/extension-version.js +65 -0
- package/bridge-server/feedback.js +12 -5
- package/bridge-server/index.js +36 -24
- package/bridge-server/limits.js +1 -1
- package/bridge-server/mcp-server.js +79 -4
- package/bridge-server/native-host.js +22 -3
- package/bridge-server/probe.js +56 -1
- package/bridge-server/server.js +11 -4
- package/bridge-server/setup.js +84 -25
- package/bridge-server/tools.js +28 -1
- package/package.json +4 -5
- package/extension/background.js +0 -1
- package/extension/engine.js +0 -1
- package/extension/fonts/inter-latin-wght-normal.woff2 +0 -0
- package/extension/icons/icon128.png +0 -0
- package/extension/icons/icon16.png +0 -0
- package/extension/icons/icon32.png +0 -0
- package/extension/icons/icon48.png +0 -0
- package/extension/logo.svg +0 -7
- package/extension/manifest.json +0 -47
- package/extension/sidepanel.css +0 -2
- package/extension/sidepanel.html +0 -138
- package/extension/sidepanel.js +0 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,51 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 0.7.0 — public launch
|
|
4
|
+
|
|
5
|
+
BrowserZap is public: free, and installed from the Chrome Web Store.
|
|
6
|
+
|
|
7
|
+
- **The extension comes from the Chrome Web Store:**
|
|
8
|
+
<https://chromewebstore.google.com/detail/bbnmhaonicbmljhbahimpgfafbjoijpe>. Chrome keeps it updated. There is no
|
|
9
|
+
folder to load and no Developer mode.
|
|
10
|
+
- **The npm package no longer includes the extension.** `npm install -g browserzap` brings the bridge, the native
|
|
11
|
+
messaging helper and the MCP server — not `extension/`. `browserzap setup` prints the store link when the extension
|
|
12
|
+
is missing, and `--extension` (a developer option) now says so instead of installing nothing.
|
|
13
|
+
- **The helper accepts only the store extension by default.** `browserzap setup --dev` adds a developer's unpacked
|
|
14
|
+
build to the allowed ids and remembers the choice; a plain `setup` goes back to store-only and says so.
|
|
15
|
+
- **`browserzap doctor` works on an npm install** with no bundled extension: it names the store id and passes.
|
|
16
|
+
- **The bridge knows which extension it is talking to.** `hello` carries the extension's version, `browser_status`
|
|
17
|
+
shows it, and a store build older than the bridge gets a notice — never a refused connection. Chrome updates the
|
|
18
|
+
extension on its own; to update now, open `chrome://extensions` and press **Update**.
|
|
19
|
+
- **A new side panel, built to be understood on first sight** (Preact, still compiled locally with Tailwind):
|
|
20
|
+
- **Chat** shows what the agent does in this tab as a conversation, and a first-visit guide in three steps with
|
|
21
|
+
prompts to copy. **Workflows** holds saved tasks. The Comment tab is gone: **Point at page** at the bottom of the
|
|
22
|
+
panel does the same.
|
|
23
|
+
- **Notes reach the agent sooner.** Every tool result now tells the agent that notes are waiting until it calls
|
|
24
|
+
`feedback_get` (it used to be `browser_status` only).
|
|
25
|
+
- **Take over / Hand back** replaces the Collab/AI/User switch. Approvals and "Your turn" hand-offs sit at the
|
|
26
|
+
bottom, always in reach; a decision waiting in another tab says so with a **Show** button.
|
|
27
|
+
- A half-typed hand-off note is no longer wiped while the agent works, and the conversation stops jumping to the
|
|
28
|
+
bottom while you read further up.
|
|
29
|
+
- **Give your agent the tab you are on.** **Let the agent use this tab** in that tab's panel: the agent's next request
|
|
30
|
+
works there instead of opening a new tab, and an agent already working sees it in `browser_list_tabs` (role
|
|
31
|
+
`shared-available`) and can `browser_switch_tab` to it. An agent never takes a tab nobody shared. A shared tab stays
|
|
32
|
+
yours: when the session ends it is kept, not closed.
|
|
33
|
+
- **Chrome's "BrowserZap started debugging this browser" bar no longer flickers between steps.** BrowserZap now lets go
|
|
34
|
+
of a tab after 5 minutes without an agent action (was 60 s), and still at once when the session ends, when you take
|
|
35
|
+
over, hand off or close the panel.
|
|
36
|
+
- **The panel says "Connecting…" while Chrome wakes BrowserZap up**, instead of "offline".
|
|
37
|
+
- **The side panel belongs to one tab.** It opens where you click the toolbar icon (and is available on the agent's
|
|
38
|
+
tabs) and hides when you switch to another tab. The toolbar badge reads **ON** only on tabs BrowserZap controls, or
|
|
39
|
+
the number of decisions waiting there.
|
|
40
|
+
- **Closing the side panel hands the tab back.** Comment mode ends (it used to come back on every reload), a recording
|
|
41
|
+
is dropped, and an agent working there lets go of the tab: it leaves the group, stays open as it is, its overlays go
|
|
42
|
+
and the debugger detaches. The agent's next page action is told the person took the tab back.
|
|
43
|
+
- **An existing upload is detected** (#65): `file_upload` reports the file already on the input instead of asking for
|
|
44
|
+
a second approval, and tells the agent to ask before replacing it.
|
|
45
|
+
- **Comment screenshots show context** (#66): each element crop now includes the surrounding page instead of a tight
|
|
46
|
+
cut-out.
|
|
47
|
+
- No new tools and no new permission; the protocol stays 4, and approvals, hand-off and Stop stay free.
|
|
48
|
+
|
|
3
49
|
## 0.6.1 (2026-10-30)
|
|
4
50
|
|
|
5
51
|
The changes from three real-Chrome runs in the owner's own browser (docs/REVIEW_LATER.md, K-7…K-10). Checked in the
|
package/README.md
CHANGED
|
@@ -28,26 +28,30 @@ browser, no copied cookies and no new login: the agent works in your real tab, a
|
|
|
28
28
|
|
|
29
29
|
## Install
|
|
30
30
|
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
31
|
+
1. **Add the extension from the Chrome Web Store:**
|
|
32
|
+
<https://chromewebstore.google.com/detail/bbnmhaonicbmljhbahimpgfafbjoijpe>. Chrome keeps it updated for you.
|
|
33
|
+
|
|
34
|
+
2. **Install the companion program** (the bridge and the MCP server; it needs Node.js 22 or newer):
|
|
35
|
+
|
|
36
|
+
```cmd
|
|
37
|
+
npm install -g browserzap
|
|
38
|
+
```
|
|
35
39
|
|
|
36
|
-
|
|
40
|
+
3. **Run setup once:**
|
|
41
|
+
|
|
42
|
+
```cmd
|
|
43
|
+
browserzap setup
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
`browserzap setup` does two things, and is safe to run again:
|
|
37
47
|
|
|
38
48
|
1. Registers a small helper with Chrome and Edge for your Windows user only (no administrator rights), so the
|
|
39
|
-
extension connects by itself. There is no token to copy.
|
|
40
|
-
2.
|
|
41
|
-
3. Finds Claude Code, Codex CLI and Cursor, shows the exact entry it would add for BrowserZap, and adds it only after
|
|
49
|
+
extension connects by itself and can start the companion program. There is no token to copy.
|
|
50
|
+
2. Finds Claude Code, Codex CLI and Cursor, shows the exact entry it would add for BrowserZap, and adds it only after
|
|
42
51
|
you answer `y`.
|
|
43
52
|
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
- **From the Chrome Web Store** (in review; this link works once it is published):
|
|
47
|
-
<https://chromewebstore.google.com/detail/bbnmhaonicbmljhbahimpgfafbjoijpe>. Chrome keeps it updated.
|
|
48
|
-
- **Until then, load it unpacked** (Chrome does not allow this step to be automated):
|
|
49
|
-
1. Open `chrome://extensions` and turn on **Developer mode**.
|
|
50
|
-
2. Click **Load unpacked** and choose the folder `setup` printed.
|
|
53
|
+
The npm package ships the bridge, the native helper and the MCP server — **not** the extension. If the extension is
|
|
54
|
+
not installed yet, `setup` prints the store link.
|
|
51
55
|
|
|
52
56
|
Click the BrowserZap icon to open the side panel. It turns green when it is connected. Check everything with:
|
|
53
57
|
|
|
@@ -58,6 +62,13 @@ browserzap doctor
|
|
|
58
62
|
Using Claude Code inside WSL? BrowserZap stays on Windows; in WSL run
|
|
59
63
|
`claude mcp add browserzap -- cmd.exe /c browserzap` (details: <https://browserzap.com/docs/platforms>).
|
|
60
64
|
|
|
65
|
+
### Developers
|
|
66
|
+
|
|
67
|
+
From a source checkout, `npm run build` still writes `extension/`, which you can load with **Load unpacked** in
|
|
68
|
+
`chrome://extensions` (turn on **Developer mode**). Run `browserzap setup --dev` so the helper accepts that build;
|
|
69
|
+
`setup` without `--dev` goes back to the store extension only. From a checkout without a global install, the same
|
|
70
|
+
command is `node bridge-server/index.js setup --dev`.
|
|
71
|
+
|
|
61
72
|
## Use it
|
|
62
73
|
|
|
63
74
|
Restart your MCP client and ask for something in the browser, for example: *"Open example.com and tell me what the
|
|
@@ -88,7 +99,7 @@ Start with `browserzap doctor`: every failed line says what to do.
|
|
|
88
99
|
| Doctor: *a bridge with a different pairing secret holds 127.0.0.1:9334* | `browserzap restart-bridge`, then **Reconnect** in the side panel |
|
|
89
100
|
| Doctor: *Launcher finds Node and the helper* fails | Node moved (a version switch with fnm, Volta or nvm): run `browserzap setup` again |
|
|
90
101
|
| Doctor: *Not blocked by Chrome policy* fails | Your computer is managed; ask IT to allow the helper `com.browserzap.bridge` and the extension |
|
|
91
|
-
| Chrome says *Disable developer mode extensions* on start-up |
|
|
102
|
+
| Chrome says *Disable developer mode extensions* on start-up | Only with a developer's unpacked copy: choose **Keep**. The Chrome Web Store install never shows it |
|
|
92
103
|
| A bar says *"BrowserZap" started debugging this browser* | Expected while an agent can act; it goes away when the agent ends or after a minute idle |
|
|
93
104
|
| Your agent does not list BrowserZap's tools | Restart the agent app after `browserzap setup`. To add it by hand, `browserzap --help` prints the exact MCP configuration |
|
|
94
105
|
| A step stops with *"needs you"* | The page asked for a password, a code or an approval. Do it, press **Done**, and the agent continues |
|
|
@@ -104,7 +115,7 @@ More: <https://browserzap.com/docs/troubleshooting>.
|
|
|
104
115
|
| `browserzap doctor` | Checks the helper registration, the launcher, Chrome policy, the extension id and the connection |
|
|
105
116
|
| `browserzap restart-bridge` | Replaces a bridge that will not pair (another install's secret or an old version) |
|
|
106
117
|
| `browserzap run <workflow> --params rows.csv` | Runs a saved workflow without an agent (one row on the free plan) |
|
|
107
|
-
| `browserzap uninstall` | Removes the helper registration and
|
|
118
|
+
| `browserzap uninstall` | Removes the helper registration (and a developer's unpacked copy); keeps your data |
|
|
108
119
|
| `browserzap uninstall --purge` | Also removes your BrowserZap data folder (cannot be undone) |
|
|
109
120
|
|
|
110
121
|
Environment variables (allowed sites, profile, upload folder, retention, …): <https://browserzap.com/docs/configuration>.
|
|
@@ -116,8 +127,9 @@ npm install -g browserzap@latest
|
|
|
116
127
|
browserzap setup
|
|
117
128
|
```
|
|
118
129
|
|
|
119
|
-
|
|
120
|
-
|
|
130
|
+
Chrome updates the extension from the Chrome Web Store by itself. Your workflows, run reports and settings are kept.
|
|
131
|
+
If the extension is older than the companion program, the agent's status result says so: Chrome retries on its own, or
|
|
132
|
+
open `chrome://extensions` and press **Update**.
|
|
121
133
|
|
|
122
134
|
## Free and Pro
|
|
123
135
|
|
package/THIRD_PARTY_NOTICES.md
CHANGED
|
@@ -78,6 +78,7 @@ No copyleft, source-available-restricted or non-commercial licences are present
|
|
|
78
78
|
| Package | Licence | Purpose |
|
|
79
79
|
|---|---|---|
|
|
80
80
|
| `ws` | MIT | WebSocket transport for the local bridge |
|
|
81
|
+
| `preact` | MIT | Side-panel UI, bundled into the extension's `sidepanel.js` (Copyright (c) 2015-present Jason Miller) |
|
|
81
82
|
|
|
82
83
|
### Build-time only (not redistributed)
|
|
83
84
|
|
|
@@ -85,9 +86,8 @@ No copyleft, source-available-restricted or non-commercial licences are present
|
|
|
85
86
|
`copy-webpack-plugin`, `@types/chrome` and their transitive dependencies — including all
|
|
86
87
|
packages listed above as Apache-2.0, BSD and CC-BY-4.0.
|
|
87
88
|
|
|
88
|
-
The extension bundle emitted by webpack contains first-party source
|
|
89
|
-
|
|
90
|
-
`./src/...`).
|
|
89
|
+
The extension bundle emitted by webpack contains first-party source, the webpack runtime and,
|
|
90
|
+
since 0.7.0, `preact` in the side panel's `sidepanel.js` (MIT; listed above).
|
|
91
91
|
|
|
92
92
|
---
|
|
93
93
|
|
|
@@ -10,7 +10,8 @@
|
|
|
10
10
|
* The Chrome Web Store signs its own build and does not accept a manifest
|
|
11
11
|
* `key`, so the store build may get a different id. Fill in
|
|
12
12
|
* STORE_EXTENSION_ID from the developer dashboard after the first upload; the
|
|
13
|
-
* native host then lets both builds in
|
|
13
|
+
* native host then lets both builds in, once `setup --dev` allows the unpacked
|
|
14
|
+
* one (docs/RELEASE.md §3b).
|
|
14
15
|
*
|
|
15
16
|
* Everything that has to name the extension — the native messaging host
|
|
16
17
|
* manifest's allowed_origins, `browserzap doctor`, the tests — reads the id
|
|
@@ -18,11 +19,20 @@
|
|
|
18
19
|
* changed key fails `doctor` and the tests instead of silently breaking native
|
|
19
20
|
* messaging.
|
|
20
21
|
*
|
|
22
|
+
* The store build is the only build a user has (D3), so the store id is the
|
|
23
|
+
* only one allowed by default. `browserzap setup --dev` saves
|
|
24
|
+
* `{ "allowDevExtension": true }` in the config dir (next to the pairing
|
|
25
|
+
* secret, user-only permissions) and the unpacked id is allowed on top of it.
|
|
26
|
+
* Chrome starts the native host itself, so an env var in the user's shell can
|
|
27
|
+
* never carry that choice (F2): the file can.
|
|
28
|
+
*
|
|
21
29
|
* Derived from the manifest key in src/manifest.json. Chrome computes:
|
|
22
30
|
* id = SHA-256(SPKI DER public key), first 16 bytes, each hex nibble 0-f → a-p
|
|
23
31
|
*/
|
|
24
32
|
|
|
25
33
|
const crypto = require('crypto');
|
|
34
|
+
const fs = require('fs');
|
|
35
|
+
const path = require('path');
|
|
26
36
|
|
|
27
37
|
/** chrome-extension://gienkaoaimdjmcjdejjanmbkpfakmkno/ */
|
|
28
38
|
const EXTENSION_ID = 'gienkaoaimdjmcjdejjanmbkpfakmkno';
|
|
@@ -36,6 +46,9 @@ const ALLOWED_EXTENSION_IDS = Object.freeze([EXTENSION_ID, STORE_EXTENSION_ID].f
|
|
|
36
46
|
/** Name Chrome uses for the native messaging host; also the registry key's leaf. */
|
|
37
47
|
const HOST_NAME = 'com.browserzap.bridge';
|
|
38
48
|
|
|
49
|
+
/** Written by `browserzap setup --dev` in the config dir; its absence means store only. */
|
|
50
|
+
const DEV_FLAG_FILENAME = 'allow-dev-extension';
|
|
51
|
+
|
|
39
52
|
function extensionOrigin(id = EXTENSION_ID) {
|
|
40
53
|
return `chrome-extension://${id}/`;
|
|
41
54
|
}
|
|
@@ -45,6 +58,52 @@ function allowedOrigins(ids = ALLOWED_EXTENSION_IDS) {
|
|
|
45
58
|
return ids.map((id) => extensionOrigin(id));
|
|
46
59
|
}
|
|
47
60
|
|
|
61
|
+
/**
|
|
62
|
+
* Whether the saved dev flag in `dir` allows the unpacked developer build.
|
|
63
|
+
*
|
|
64
|
+
* Fail closed: a missing, unreadable or malformed file means store only (D4).
|
|
65
|
+
*/
|
|
66
|
+
function devExtensionAllowed(dir) {
|
|
67
|
+
if (typeof dir !== 'string' || !dir) return false;
|
|
68
|
+
try {
|
|
69
|
+
const saved = JSON.parse(fs.readFileSync(path.join(dir, DEV_FLAG_FILENAME), 'utf8'));
|
|
70
|
+
return saved?.allowDevExtension === true;
|
|
71
|
+
} catch {
|
|
72
|
+
return false;
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
/**
|
|
77
|
+
* Save (or clear) the dev flag. `setup` calls this before it writes the host
|
|
78
|
+
* manifest, so the manifest and the host's own check read the same answer.
|
|
79
|
+
*/
|
|
80
|
+
function setDevExtensionAllowed(dir, allowed) {
|
|
81
|
+
if (typeof dir !== 'string' || !dir) return false;
|
|
82
|
+
const file = path.join(dir, DEV_FLAG_FILENAME);
|
|
83
|
+
if (!allowed) {
|
|
84
|
+
try { fs.rmSync(file, { force: true }); } catch { /* already gone */ }
|
|
85
|
+
return false;
|
|
86
|
+
}
|
|
87
|
+
fs.mkdirSync(dir, { recursive: true, mode: 0o700 });
|
|
88
|
+
const tmp = `${file}.${process.pid}.tmp`;
|
|
89
|
+
fs.writeFileSync(tmp, '{"allowDevExtension":true}\n', { mode: 0o600 });
|
|
90
|
+
fs.renameSync(tmp, file);
|
|
91
|
+
return true;
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
/**
|
|
95
|
+
* Every extension id the native host may let in: the store id, plus the
|
|
96
|
+
* unpacked id when `setup --dev` saved the flag.
|
|
97
|
+
*
|
|
98
|
+
* `dir` defaults to the config dir, resolved lazily so importing this module
|
|
99
|
+
* never reads the filesystem or triggers the legacy-folder check.
|
|
100
|
+
*/
|
|
101
|
+
function allowedExtensionIds({ dir } = {}) {
|
|
102
|
+
const configDir = dir === undefined ? require('./secret').configDir() : dir;
|
|
103
|
+
const ids = devExtensionAllowed(configDir) ? [EXTENSION_ID, STORE_EXTENSION_ID] : [STORE_EXTENSION_ID];
|
|
104
|
+
return ids.filter(Boolean);
|
|
105
|
+
}
|
|
106
|
+
|
|
48
107
|
/**
|
|
49
108
|
* Chrome's extension id for a manifest `key` value (base64 SPKI DER).
|
|
50
109
|
* Returns null when the key is missing or is not a valid public key.
|
|
@@ -72,7 +131,11 @@ module.exports = {
|
|
|
72
131
|
STORE_EXTENSION_ID,
|
|
73
132
|
ALLOWED_EXTENSION_IDS,
|
|
74
133
|
HOST_NAME,
|
|
134
|
+
DEV_FLAG_FILENAME,
|
|
75
135
|
extensionOrigin,
|
|
76
136
|
allowedOrigins,
|
|
137
|
+
devExtensionAllowed,
|
|
138
|
+
setDevExtensionAllowed,
|
|
139
|
+
allowedExtensionIds,
|
|
77
140
|
extensionIdFromKey,
|
|
78
141
|
};
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
/**
|
|
3
|
+
* The extension version the bridge expects (WP5, F4).
|
|
4
|
+
*
|
|
5
|
+
* The Chrome Web Store ships the extension and updates it on its own schedule,
|
|
6
|
+
* so after the 0.7.0 launch the store build will often lag the npm bridge by a
|
|
7
|
+
* review cycle. The extension's `hello` carries `extensionVersion` (an additive
|
|
8
|
+
* field; the protocol stays 4), the bridge stores it per profile, and
|
|
9
|
+
* `browser_status` says plainly when the connected build is older than this
|
|
10
|
+
* bridge expects.
|
|
11
|
+
*
|
|
12
|
+
* Nothing here ever refuses a connection or a call: a missing version means an
|
|
13
|
+
* older build (≤ 0.6.1), which is exactly the case the notice is for.
|
|
14
|
+
*/
|
|
15
|
+
|
|
16
|
+
/** The oldest extension build this bridge describes as current. */
|
|
17
|
+
const MIN_EXTENSION_VERSION = '0.7.0';
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* One numeric part per dot-separated segment. Anything that is not a version at
|
|
21
|
+
* all counts as 0, so a short, long or odd string still compares instead of
|
|
22
|
+
* throwing.
|
|
23
|
+
*/
|
|
24
|
+
function versionParts(version) {
|
|
25
|
+
let text = '';
|
|
26
|
+
if (typeof version === 'string') text = version.trim();
|
|
27
|
+
else if (typeof version === 'number' && Number.isFinite(version)) text = String(version);
|
|
28
|
+
if (!text) return [0];
|
|
29
|
+
return text.split('.').map((segment) => {
|
|
30
|
+
const match = /^\d+/.exec(segment.trim());
|
|
31
|
+
return match ? Number(match[0]) : 0;
|
|
32
|
+
});
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* Numeric dot comparison: 0.7.0 > 0.6.1, 0.7 > 0.7.0, 0.7.0.1 > 0.7.0.
|
|
37
|
+
* Returns -1 when `a` is older, 0 when equal, 1 when newer.
|
|
38
|
+
*/
|
|
39
|
+
function compareVersions(a, b) {
|
|
40
|
+
const left = versionParts(a);
|
|
41
|
+
const right = versionParts(b);
|
|
42
|
+
const length = Math.max(left.length, right.length);
|
|
43
|
+
for (let i = 0; i < length; i++) {
|
|
44
|
+
const x = left[i] ?? 0;
|
|
45
|
+
const y = right[i] ?? 0;
|
|
46
|
+
if (x !== y) return x < y ? -1 : 1;
|
|
47
|
+
}
|
|
48
|
+
return 0;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
/**
|
|
52
|
+
* True only for a real version string that is older than `minimum`. A missing
|
|
53
|
+
* or non-string version answers false: "unknown" is not the same as "old", and
|
|
54
|
+
* the caller decides what to say about it.
|
|
55
|
+
*/
|
|
56
|
+
function isOlderThan(version, minimum) {
|
|
57
|
+
if (typeof version !== 'string' || !version.trim()) return false;
|
|
58
|
+
return compareVersions(version, minimum) < 0;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
module.exports = {
|
|
62
|
+
MIN_EXTENSION_VERSION,
|
|
63
|
+
compareVersions,
|
|
64
|
+
isOlderThan,
|
|
65
|
+
};
|
|
@@ -96,10 +96,14 @@ function componentOf(c) {
|
|
|
96
96
|
};
|
|
97
97
|
}
|
|
98
98
|
|
|
99
|
-
function pageOf(p) {
|
|
99
|
+
function pageOf(p, { optional = false } = {}) {
|
|
100
100
|
const page = p && typeof p === 'object' ? p : {};
|
|
101
101
|
const url = str(page.url, 2000);
|
|
102
|
-
if (!url || !/^https?:\/\//i.test(url))
|
|
102
|
+
if (!url || !/^https?:\/\//i.test(url)) {
|
|
103
|
+
// A message typed in the side panel may come from a tab with no web page (chrome://, a new tab).
|
|
104
|
+
if (optional) return null;
|
|
105
|
+
throw new FeedbackError('Every comment needs the http(s) page it was made on.');
|
|
106
|
+
}
|
|
103
107
|
return {
|
|
104
108
|
url,
|
|
105
109
|
title: str(page.title, 200),
|
|
@@ -140,11 +144,14 @@ function saveBatch(batch, { now = new Date() } = {}) {
|
|
|
140
144
|
const n = Number.isInteger(c.n) && c.n > 0 && c.n < 1000 ? c.n : index + 1;
|
|
141
145
|
const shot = imageData(c.screenshot, `The screenshot of comment ${n}`);
|
|
142
146
|
const attachments = Array.isArray(c.attachments) ? c.attachments.slice(0, MAX_ATTACHMENTS) : [];
|
|
147
|
+
// kind "message": typed in the side panel's message box, not pinned to an element.
|
|
148
|
+
const message = c.kind === 'message';
|
|
143
149
|
const entry = {
|
|
144
150
|
n,
|
|
151
|
+
kind: message ? 'message' : 'comment',
|
|
145
152
|
text,
|
|
146
|
-
page: pageOf(c.page),
|
|
147
|
-
element: elementOf(c.info),
|
|
153
|
+
page: pageOf(c.page, { optional: message }),
|
|
154
|
+
element: message ? null : elementOf(c.info),
|
|
148
155
|
component: componentOf(c.component),
|
|
149
156
|
screenshot: null,
|
|
150
157
|
attachments: [],
|
|
@@ -199,7 +206,7 @@ function list({ includeDelivered = false } = {}) {
|
|
|
199
206
|
id: b.id,
|
|
200
207
|
createdAt: b.createdAt,
|
|
201
208
|
count: b.comments.length,
|
|
202
|
-
pages: [...new Set(b.comments.map((c) => c.page
|
|
209
|
+
pages: [...new Set(b.comments.map((c) => c.page?.url).filter(Boolean))].slice(0, 5),
|
|
203
210
|
delivered: b.delivered,
|
|
204
211
|
}));
|
|
205
212
|
}
|
package/bridge-server/index.js
CHANGED
|
@@ -4,9 +4,10 @@
|
|
|
4
4
|
* BrowserZap — entry point.
|
|
5
5
|
*
|
|
6
6
|
* When used as an MCP server (the primary use case), this starts the MCP
|
|
7
|
-
* server, which embeds the WebSocket bridge. The Chrome extension
|
|
8
|
-
*
|
|
9
|
-
*
|
|
7
|
+
* server, which embeds the WebSocket bridge. The Chrome extension comes from
|
|
8
|
+
* the Chrome Web Store and updates itself; a developer working from a source
|
|
9
|
+
* checkout can pass --extension to copy the built build under the user's home
|
|
10
|
+
* directory and print the path for a "Load unpacked" install.
|
|
10
11
|
*
|
|
11
12
|
* Derived from Open Browser Control (MIT, Copyright (c) 2026 smankoo) at commit
|
|
12
13
|
* 4e6c262. BrowserZap drops the Firefox target and rebrands.
|
|
@@ -17,7 +18,7 @@
|
|
|
17
18
|
* Standalone:
|
|
18
19
|
* node bridge-server/index.js # MCP mode (default)
|
|
19
20
|
* node bridge-server/index.js --bridge # standalone WebSocket bridge
|
|
20
|
-
* node bridge-server/index.js --extension #
|
|
21
|
+
* node bridge-server/index.js --extension # copy + print the built extension (developer)
|
|
21
22
|
* node bridge-server/index.js --uninstall # remove the installed extension copy
|
|
22
23
|
* node bridge-server/index.js --print-token # print the pairing token
|
|
23
24
|
*/
|
|
@@ -28,6 +29,7 @@ const os = require('os');
|
|
|
28
29
|
|
|
29
30
|
const { getOrCreateSecret, secretPath, configDir } = require('./secret');
|
|
30
31
|
const { DEFAULT_PORT } = require('./server');
|
|
32
|
+
const { STORE_URL } = require('./setup');
|
|
31
33
|
|
|
32
34
|
const args = process.argv.slice(2);
|
|
33
35
|
|
|
@@ -82,18 +84,29 @@ function syncDir(src, dest) {
|
|
|
82
84
|
prune(dest, dest);
|
|
83
85
|
}
|
|
84
86
|
|
|
87
|
+
/**
|
|
88
|
+
* The extension build inside this package, or null.
|
|
89
|
+
*
|
|
90
|
+
* npm ships the bridge only (D3), so an installed copy has neither folder and
|
|
91
|
+
* nothing to copy. A source checkout has extension/ after `npm run build`, or
|
|
92
|
+
* dist/chrome when webpack output is all that exists yet.
|
|
93
|
+
*/
|
|
94
|
+
function builtExtensionDir() {
|
|
95
|
+
for (const candidate of [path.join(__dirname, '..', 'extension'), path.join(__dirname, '..', 'dist', 'chrome')]) {
|
|
96
|
+
if (fs.existsSync(path.join(candidate, 'manifest.json'))) return candidate;
|
|
97
|
+
}
|
|
98
|
+
return null;
|
|
99
|
+
}
|
|
100
|
+
|
|
85
101
|
function installExtension({ quiet = false } = {}) {
|
|
86
102
|
const say = quiet ? () => {} : log;
|
|
87
|
-
const bundled =
|
|
88
|
-
|
|
89
|
-
if (!
|
|
90
|
-
//
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
return true;
|
|
95
|
-
}
|
|
96
|
-
say('Pre-built extension not found. Run "npm run build" first.');
|
|
103
|
+
const bundled = builtExtensionDir();
|
|
104
|
+
|
|
105
|
+
if (!bundled) {
|
|
106
|
+
// The store is the install path for a user; this flag is a developer tool.
|
|
107
|
+
process.stdout.write('The extension is not part of the npm package. Install it from the Chrome Web Store:\n');
|
|
108
|
+
process.stdout.write(` ${STORE_URL}\n`);
|
|
109
|
+
process.stdout.write('(Chrome keeps it updated. "browserzap --extension" only works from a source checkout.)\n');
|
|
97
110
|
return false;
|
|
98
111
|
}
|
|
99
112
|
|
|
@@ -119,8 +132,9 @@ function installExtension({ quiet = false } = {}) {
|
|
|
119
132
|
say(` 4. Select: ${EXTENSION_HOME}`);
|
|
120
133
|
say('');
|
|
121
134
|
say('One-time setup (registers the native messaging host, so the extension');
|
|
122
|
-
say('connects automatically with no token to paste
|
|
123
|
-
say('
|
|
135
|
+
say('connects automatically with no token to paste; --dev lets this unpacked');
|
|
136
|
+
say('build in, which the store build does not need):');
|
|
137
|
+
say(' browserzap setup --dev');
|
|
124
138
|
say('');
|
|
125
139
|
return true;
|
|
126
140
|
}
|
|
@@ -277,13 +291,10 @@ if (require('./setup').insideWsl() && !process.env.BROWSERZAP_ALLOW_LINUX) {
|
|
|
277
291
|
const SUBCOMMANDS = new Set(['setup', 'doctor', 'uninstall', 'run', 'restart-bridge']);
|
|
278
292
|
|
|
279
293
|
if (command === 'setup') {
|
|
280
|
-
//
|
|
281
|
-
//
|
|
282
|
-
//
|
|
283
|
-
const
|
|
284
|
-
const extensionPath = checkout
|
|
285
|
-
? path.join(__dirname, '..', 'extension')
|
|
286
|
-
: installExtension({ quiet: true }) ? EXTENSION_HOME : null;
|
|
294
|
+
// A source checkout has a built extension to load unpacked; an npm install has
|
|
295
|
+
// none, and the store build is the extension there (D3). Nothing is copied:
|
|
296
|
+
// setup only names the path a developer already has.
|
|
297
|
+
const extensionPath = builtExtensionDir();
|
|
287
298
|
require('./setup').setupCommand(args.slice(1), { extensionPath })
|
|
288
299
|
.then((code) => process.exit(code))
|
|
289
300
|
.catch((err) => {
|
|
@@ -493,6 +504,7 @@ browserzap — let an AI agent act on the browser tab you already have open.
|
|
|
493
504
|
Usage:
|
|
494
505
|
browserzap setup One-time setup: register the native messaging host and
|
|
495
506
|
optionally add the MCP server to detected clients
|
|
507
|
+
browserzap setup --dev Allow the unpacked developer build to connect (store-only otherwise)
|
|
496
508
|
browserzap doctor Check the host manifest, registry, extension id and bridge
|
|
497
509
|
browserzap restart-bridge Replace a bridge that will not pair (another secret or version)
|
|
498
510
|
browserzap uninstall [--purge] Remove the host registration and installed extension copy
|
|
@@ -501,7 +513,7 @@ Usage:
|
|
|
501
513
|
more than one). Exit code 0 when every run completed
|
|
502
514
|
browserzap Start the MCP server (default)
|
|
503
515
|
browserzap --bridge Start the standalone WebSocket bridge
|
|
504
|
-
browserzap --extension
|
|
516
|
+
browserzap --extension Developer: copy the built extension to the install folder (source checkout only)
|
|
505
517
|
browserzap --uninstall Remove the installed extension copy (keeps your data)
|
|
506
518
|
browserzap --uninstall --purge
|
|
507
519
|
Also remove the config folder (pairing token,
|
package/bridge-server/limits.js
CHANGED
|
@@ -36,7 +36,7 @@ const LIMITS = Object.freeze({
|
|
|
36
36
|
/** Longest per-step pacing a person may set on a saved workflow step. */
|
|
37
37
|
MAX_STEP_PACING_MS: 30000,
|
|
38
38
|
/** A tab with no agent action for this long is detached, so Chrome's "debugging" bar goes away (D-35). */
|
|
39
|
-
DEBUGGER_IDLE_MS:
|
|
39
|
+
DEBUGGER_IDLE_MS: 300000,
|
|
40
40
|
/** Tabs a session may have while it works (main + helpers); one more closes the least recently used helper (D-38). */
|
|
41
41
|
MAX_SESSION_TABS: 5,
|
|
42
42
|
/** Tabs a session keeps once it has been idle for TAB_TRIM_IDLE_MS. */
|