@btraut/browser-bridge 0.14.0 → 0.15.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 +30 -0
- package/README.md +27 -2
- package/dist/api.js +893 -166
- package/dist/api.js.map +4 -4
- package/dist/index.js +349 -115
- package/dist/index.js.map +4 -4
- package/extension/dist/background.js +468 -18
- package/extension/dist/background.js.map +4 -4
- package/extension/dist/content.js +71 -8
- package/extension/dist/content.js.map +2 -2
- package/extension/dist/permissions-request-ui.js +111 -0
- package/extension/dist/permissions-request-ui.js.map +7 -0
- package/extension/manifest.json +2 -2
- package/package.json +1 -1
- package/skills/browser-bridge/SKILL.md +9 -0
- package/skills/browser-bridge/skill.json +1 -1
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
(() => {
|
|
3
|
+
// packages/extension/src/permissions-request-ui.ts
|
|
4
|
+
var PORT_NAME = "permissions_request_prompt";
|
|
5
|
+
var byId = (id) => {
|
|
6
|
+
const el = document.getElementById(id);
|
|
7
|
+
if (!el) {
|
|
8
|
+
throw new Error(`Missing element: ${id}`);
|
|
9
|
+
}
|
|
10
|
+
return el;
|
|
11
|
+
};
|
|
12
|
+
var escapeHtml = (raw) => raw.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">");
|
|
13
|
+
var setApproveDisabled = (disabled) => {
|
|
14
|
+
byId("bb-approve").disabled = disabled;
|
|
15
|
+
};
|
|
16
|
+
var setAllDisabled = (disabled) => {
|
|
17
|
+
byId("bb-approve").disabled = disabled;
|
|
18
|
+
byId("bb-deny").disabled = disabled;
|
|
19
|
+
const acknowledge = byId("bb-acknowledge");
|
|
20
|
+
if (acknowledge) {
|
|
21
|
+
acknowledge.disabled = disabled;
|
|
22
|
+
}
|
|
23
|
+
};
|
|
24
|
+
var describeRequest = (kind, site, mode, source) => {
|
|
25
|
+
const sourceLabel = source ? `${source.toUpperCase()} requested:` : "Request:";
|
|
26
|
+
switch (kind) {
|
|
27
|
+
case "allow_site":
|
|
28
|
+
return {
|
|
29
|
+
title: "Approve site access",
|
|
30
|
+
summary: `${sourceLabel} allow Browser Bridge actions on <span class="bb-inline-code">${escapeHtml(
|
|
31
|
+
site
|
|
32
|
+
)}</span>.`
|
|
33
|
+
};
|
|
34
|
+
case "revoke_site":
|
|
35
|
+
return {
|
|
36
|
+
title: "Approve site revoke",
|
|
37
|
+
summary: `${sourceLabel} revoke Browser Bridge actions on <span class="bb-inline-code">${escapeHtml(
|
|
38
|
+
site
|
|
39
|
+
)}</span>.`
|
|
40
|
+
};
|
|
41
|
+
case "set_mode":
|
|
42
|
+
return {
|
|
43
|
+
title: mode === "bypass" ? "Approve bypass mode" : "Approve granular mode",
|
|
44
|
+
summary: `${sourceLabel} switch Browser Bridge to <span class="bb-inline-code">${escapeHtml(
|
|
45
|
+
mode
|
|
46
|
+
)}</span> mode.`
|
|
47
|
+
};
|
|
48
|
+
default:
|
|
49
|
+
return {
|
|
50
|
+
title: "Approve permissions change",
|
|
51
|
+
summary: `${sourceLabel} update Browser Bridge permissions.`
|
|
52
|
+
};
|
|
53
|
+
}
|
|
54
|
+
};
|
|
55
|
+
var main = () => {
|
|
56
|
+
const qs = new URLSearchParams(window.location.search);
|
|
57
|
+
const requestId = qs.get("requestId") ?? "";
|
|
58
|
+
const kind = qs.get("kind") ?? "";
|
|
59
|
+
const site = qs.get("site") ?? "";
|
|
60
|
+
const mode = qs.get("mode") ?? "";
|
|
61
|
+
const source = qs.get("source") ?? "";
|
|
62
|
+
const warning = qs.get("warning") ?? "";
|
|
63
|
+
const requireAcknowledge = qs.get("requireAcknowledge") === "1";
|
|
64
|
+
const title = byId("bb-title");
|
|
65
|
+
const summary = byId("bb-summary");
|
|
66
|
+
const details = byId("bb-details");
|
|
67
|
+
const warningSection = byId("bb-warning");
|
|
68
|
+
const warningText = byId("bb-warning-text");
|
|
69
|
+
const acknowledgeWrap = byId("bb-acknowledge-wrap");
|
|
70
|
+
const acknowledge = byId("bb-acknowledge");
|
|
71
|
+
if (!requestId || !kind) {
|
|
72
|
+
title.textContent = "Invalid request";
|
|
73
|
+
summary.textContent = "Close this window and retry.";
|
|
74
|
+
details.textContent = "";
|
|
75
|
+
setAllDisabled(true);
|
|
76
|
+
return;
|
|
77
|
+
}
|
|
78
|
+
const copy = describeRequest(kind, site, mode, source);
|
|
79
|
+
title.textContent = copy.title;
|
|
80
|
+
summary.innerHTML = copy.summary;
|
|
81
|
+
details.textContent = site || mode || "";
|
|
82
|
+
if (warning) {
|
|
83
|
+
warningSection.hidden = false;
|
|
84
|
+
warningText.textContent = warning;
|
|
85
|
+
}
|
|
86
|
+
if (requireAcknowledge) {
|
|
87
|
+
acknowledgeWrap.hidden = false;
|
|
88
|
+
setApproveDisabled(true);
|
|
89
|
+
acknowledge.addEventListener("change", () => {
|
|
90
|
+
setApproveDisabled(!acknowledge.checked);
|
|
91
|
+
});
|
|
92
|
+
}
|
|
93
|
+
const port = chrome.runtime.connect({ name: PORT_NAME });
|
|
94
|
+
const sendDecision = (decision) => {
|
|
95
|
+
setAllDisabled(true);
|
|
96
|
+
try {
|
|
97
|
+
port.postMessage({
|
|
98
|
+
type: "decision",
|
|
99
|
+
requestId,
|
|
100
|
+
decision
|
|
101
|
+
});
|
|
102
|
+
} finally {
|
|
103
|
+
window.setTimeout(() => window.close(), 50);
|
|
104
|
+
}
|
|
105
|
+
};
|
|
106
|
+
byId("bb-approve").addEventListener("click", () => sendDecision("approve"));
|
|
107
|
+
byId("bb-deny").addEventListener("click", () => sendDecision("deny"));
|
|
108
|
+
};
|
|
109
|
+
main();
|
|
110
|
+
})();
|
|
111
|
+
//# sourceMappingURL=permissions-request-ui.js.map
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 3,
|
|
3
|
+
"sources": ["../src/permissions-request-ui.ts"],
|
|
4
|
+
"sourcesContent": ["type Decision = 'approve' | 'deny';\n\nconst PORT_NAME = 'permissions_request_prompt';\n\nconst byId = (id: string): HTMLElement => {\n const el = document.getElementById(id);\n if (!el) {\n throw new Error(`Missing element: ${id}`);\n }\n return el;\n};\n\nconst escapeHtml = (raw: string): string =>\n raw.replace(/&/g, '&').replace(/</g, '<').replace(/>/g, '>');\n\nconst setApproveDisabled = (disabled: boolean): void => {\n (byId('bb-approve') as HTMLButtonElement).disabled = disabled;\n};\n\nconst setAllDisabled = (disabled: boolean): void => {\n (byId('bb-approve') as HTMLButtonElement).disabled = disabled;\n (byId('bb-deny') as HTMLButtonElement).disabled = disabled;\n const acknowledge = byId('bb-acknowledge') as HTMLInputElement | null;\n if (acknowledge) {\n acknowledge.disabled = disabled;\n }\n};\n\nconst describeRequest = (\n kind: string,\n site: string,\n mode: string,\n source: string\n): { title: string; summary: string } => {\n const sourceLabel = source\n ? `${source.toUpperCase()} requested:`\n : 'Request:';\n switch (kind) {\n case 'allow_site':\n return {\n title: 'Approve site access',\n summary: `${sourceLabel} allow Browser Bridge actions on <span class=\"bb-inline-code\">${escapeHtml(\n site\n )}</span>.`,\n };\n case 'revoke_site':\n return {\n title: 'Approve site revoke',\n summary: `${sourceLabel} revoke Browser Bridge actions on <span class=\"bb-inline-code\">${escapeHtml(\n site\n )}</span>.`,\n };\n case 'set_mode':\n return {\n title:\n mode === 'bypass' ? 'Approve bypass mode' : 'Approve granular mode',\n summary: `${sourceLabel} switch Browser Bridge to <span class=\"bb-inline-code\">${escapeHtml(\n mode\n )}</span> mode.`,\n };\n default:\n return {\n title: 'Approve permissions change',\n summary: `${sourceLabel} update Browser Bridge permissions.`,\n };\n }\n};\n\nconst main = (): void => {\n const qs = new URLSearchParams(window.location.search);\n const requestId = qs.get('requestId') ?? '';\n const kind = qs.get('kind') ?? '';\n const site = qs.get('site') ?? '';\n const mode = qs.get('mode') ?? '';\n const source = qs.get('source') ?? '';\n const warning = qs.get('warning') ?? '';\n const requireAcknowledge = qs.get('requireAcknowledge') === '1';\n\n const title = byId('bb-title');\n const summary = byId('bb-summary');\n const details = byId('bb-details');\n const warningSection = byId('bb-warning');\n const warningText = byId('bb-warning-text');\n const acknowledgeWrap = byId('bb-acknowledge-wrap');\n const acknowledge = byId('bb-acknowledge') as HTMLInputElement;\n\n if (!requestId || !kind) {\n title.textContent = 'Invalid request';\n summary.textContent = 'Close this window and retry.';\n details.textContent = '';\n setAllDisabled(true);\n return;\n }\n\n const copy = describeRequest(kind, site, mode, source);\n title.textContent = copy.title;\n summary.innerHTML = copy.summary;\n details.textContent = site || mode || '';\n\n if (warning) {\n warningSection.hidden = false;\n warningText.textContent = warning;\n }\n\n if (requireAcknowledge) {\n acknowledgeWrap.hidden = false;\n setApproveDisabled(true);\n acknowledge.addEventListener('change', () => {\n setApproveDisabled(!acknowledge.checked);\n });\n }\n\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n const port = (chrome as any).runtime.connect({ name: PORT_NAME });\n\n const sendDecision = (decision: Decision): void => {\n setAllDisabled(true);\n try {\n port.postMessage({\n type: 'decision',\n requestId,\n decision,\n });\n } finally {\n window.setTimeout(() => window.close(), 50);\n }\n };\n\n byId('bb-approve').addEventListener('click', () => sendDecision('approve'));\n byId('bb-deny').addEventListener('click', () => sendDecision('deny'));\n};\n\nmain();\n\nexport {};\n"],
|
|
5
|
+
"mappings": "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",
|
|
6
|
+
"names": []
|
|
7
|
+
}
|
package/extension/manifest.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"manifest_version": 3,
|
|
3
3
|
"name": "Browser Bridge",
|
|
4
|
-
"version": "0.
|
|
4
|
+
"version": "0.15.0",
|
|
5
5
|
"description": "Control Chrome for Browser Bridge: drive tabs and inspect pages for coding agents.",
|
|
6
6
|
"icons": {
|
|
7
7
|
"16": "assets/icons/icon-16.png",
|
|
@@ -25,7 +25,7 @@
|
|
|
25
25
|
"page": "options.html",
|
|
26
26
|
"open_in_tab": true
|
|
27
27
|
},
|
|
28
|
-
"permissions": ["tabs", "storage", "webNavigation", "debugger"],
|
|
28
|
+
"permissions": ["tabs", "storage", "webNavigation", "debugger", "scripting"],
|
|
29
29
|
"host_permissions": ["http://*/*", "https://*/*", "<all_urls>"],
|
|
30
30
|
"web_accessible_resources": [
|
|
31
31
|
{
|
package/package.json
CHANGED
|
@@ -101,6 +101,7 @@ Note: MCP still requires `browser-bridge` to be on PATH, since the client invoke
|
|
|
101
101
|
## Tool Groups (MCP)
|
|
102
102
|
|
|
103
103
|
- `session.*` - Session lifecycle
|
|
104
|
+
- `permissions.*` - Allowlist/mode inspection plus approval-gated permission-change requests
|
|
104
105
|
- `drive.*` - Navigation and input
|
|
105
106
|
- `inspect.*` - DOM snapshots and evaluation
|
|
106
107
|
- `artifacts.*` - Screenshots
|
|
@@ -139,6 +140,14 @@ Manage approvals (and bypass mode):
|
|
|
139
140
|
- Review/revoke sites under **Approved sites**.
|
|
140
141
|
- Switch **Permission mode** to **Bypass (dangerous)** to skip prompts/allowlist entirely.
|
|
141
142
|
- Restricted URLs (for example `chrome://` and `file://`) are still blocked.
|
|
143
|
+
- CLI and MCP can also inspect or request permission changes:
|
|
144
|
+
- CLI reads: `browser-bridge permissions list`, `browser-bridge permissions mode`, `browser-bridge permissions pending`
|
|
145
|
+
- CLI requests: `browser-bridge permissions allow-site --site example.com`, `revoke-site --site example.com`, `set-mode --mode granular|bypass`
|
|
146
|
+
- MCP equivalents: `permissions.list`, `permissions.get_mode`, `permissions.list_pending_requests`, and `permissions.request_*`
|
|
147
|
+
- External permission-change requests are still human-gated:
|
|
148
|
+
- Request tools/commands open a dedicated Chrome approval prompt.
|
|
149
|
+
- A request timeout returns `status: "timed_out"`; if the prompt stays open, a later human approval still applies the change.
|
|
150
|
+
- Nothing is silently mutated from CLI or MCP.
|
|
142
151
|
|
|
143
152
|
### Error Handling (Structured Envelopes)
|
|
144
153
|
|