@jobshimo/browser-link 0.37.0 → 0.38.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/dist/agent-instructions/content.js +5 -360
- package/dist/agent-instructions/content.js.map +1 -1
- package/dist/bridge/server.d.ts +5 -0
- package/dist/bridge/server.js +1 -1
- package/dist/bridge/server.js.map +1 -1
- package/dist/cdp/flow.d.ts +8 -0
- package/dist/cdp/flow.js +11 -0
- package/dist/cdp/flow.js.map +1 -1
- package/dist/cdp/inpage/builders.d.ts +7 -4
- package/dist/cdp/inpage/builders.js +4 -1
- package/dist/cdp/inpage/builders.js.map +1 -1
- package/dist/cdp/inpage/deep-query.d.ts +1 -1
- package/dist/cdp/inpage/deep-query.js +12 -9
- package/dist/cdp/inpage/deep-query.js.map +1 -1
- package/dist/cdp/transport.js +7 -1
- package/dist/cdp/transport.js.map +1 -1
- package/dist/commands/about.js +4 -2
- package/dist/commands/about.js.map +1 -1
- package/dist/extension/background.js +7 -1
- package/dist/extension/background.js.map +1 -1
- package/dist/extension/flow.d.ts +8 -0
- package/dist/extension/flow.js +11 -0
- package/dist/extension/flow.js.map +1 -1
- package/dist/extension/inpage/builders.d.ts +7 -4
- package/dist/extension/inpage/builders.js +4 -1
- package/dist/extension/inpage/builders.js.map +1 -1
- package/dist/extension/inpage/deep-query.d.ts +1 -1
- package/dist/extension/inpage/deep-query.js +12 -9
- package/dist/extension/inpage/deep-query.js.map +1 -1
- package/dist/extension/manifest.json +1 -1
- package/dist/manual/core.d.ts +20 -0
- package/dist/manual/core.js +71 -0
- package/dist/manual/core.js.map +1 -0
- package/dist/manual/render.d.ts +22 -0
- package/dist/manual/render.js +56 -0
- package/dist/manual/render.js.map +1 -0
- package/dist/manual/status.d.ts +23 -0
- package/dist/manual/status.js +52 -0
- package/dist/manual/status.js.map +1 -0
- package/dist/manual/tool.d.ts +20 -0
- package/dist/manual/tool.js +106 -0
- package/dist/manual/tool.js.map +1 -0
- package/dist/manual/topics.d.ts +18 -0
- package/dist/manual/topics.js +60 -0
- package/dist/manual/topics.js.map +1 -0
- package/dist/permissions.js +6 -0
- package/dist/permissions.js.map +1 -1
- package/dist/server.js +11 -14
- package/dist/server.js.map +1 -1
- package/dist/tools/browser-definitions.js +34 -6
- package/dist/tools/browser-definitions.js.map +1 -1
- package/dist/tools/browser-dispatch.d.ts +5 -0
- package/dist/tools/browser-dispatch.js +17 -0
- package/dist/tools/browser-dispatch.js.map +1 -1
- package/dist/tools/server-instructions.d.ts +24 -13
- package/dist/tools/server-instructions.js +25 -314
- package/dist/tools/server-instructions.js.map +1 -1
- package/package.json +1 -1
|
@@ -1,320 +1,31 @@
|
|
|
1
1
|
/**
|
|
2
|
-
*
|
|
3
|
-
*
|
|
4
|
-
* tool
|
|
5
|
-
* drift between the schema and the human-facing documentation.
|
|
2
|
+
* The MCP `initialize.instructions` payload. The text itself lives in the
|
|
3
|
+
* agent manual (`../manual/`): the hand-written core first, then one section
|
|
4
|
+
* per tool rendered from the `doc` block each tool definition carries.
|
|
6
5
|
*
|
|
7
|
-
* `SERVER_INSTRUCTIONS` is computed once at module load
|
|
8
|
-
* plain string so the existing consumers (`server.ts`,
|
|
9
|
-
* keep working
|
|
6
|
+
* `SERVER_INSTRUCTIONS` is computed once at module load (no notice) and kept
|
|
7
|
+
* as a plain string so the existing consumers (`server.ts`,
|
|
8
|
+
* `bridge/server.ts`) keep working; `buildServerInstructions` renders it on
|
|
9
|
+
* demand, optionally with a one-line notice placed before the core.
|
|
10
10
|
*/
|
|
11
|
-
import {
|
|
12
|
-
import {
|
|
13
|
-
/**
|
|
14
|
-
*
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
explicitly connected through the companion extension, and ships a
|
|
18
|
-
persistent UI map backed by a local SQLite DB. The data dir resolves
|
|
19
|
-
per-OS via env-paths ($XDG_DATA_HOME/browser-link on Linux,
|
|
20
|
-
~/Library/Application Support/browser-link on macOS, %APPDATA%/browser-link
|
|
21
|
-
on Windows). Override with $BROWSER_LINK_DATA_DIR. The map is private
|
|
22
|
-
and per-machine; never persisted in any repo.
|
|
23
|
-
|
|
24
|
-
## Reflex protocol (ALWAYS ACTIVE)
|
|
25
|
-
|
|
26
|
-
Tools you have here see only Chrome tabs the user explicitly connected
|
|
27
|
-
through the companion extension. Never reason about state you cannot
|
|
28
|
-
see — call \`browser.list_tabs\` BEFORE answering when the user mentions
|
|
29
|
-
a UI element, a web app, a URL, a broken layout, "the page", or asks
|
|
30
|
-
"does X work". Take a \`browser.snapshot\` before suggesting any UI code
|
|
31
|
-
change. If a call returns "Tab not connected", call \`browser.events\`
|
|
32
|
-
to find the new tab_id before retrying. After a non-trivial flow worked
|
|
33
|
-
end-to-end, persist UI structure (NEVER domain data) with
|
|
34
|
-
\`browser.map.save\`. When \`list_tabs\` shows a \`map\` hint on a tab
|
|
35
|
-
(\`{ app_key, entries, flows, mechanisms, pitfalls, inline? }\`): if
|
|
36
|
-
\`inline\` is present the small map is already in the hint — read its
|
|
37
|
-
pitfalls first and skip the round trip; otherwise call
|
|
38
|
-
\`browser.map.recall\` BEFORE snapshotting — the map already has
|
|
39
|
-
selectors, gotchas, flow recipes, mechanisms or pitfalls for that origin,
|
|
40
|
-
so recall first instead of rediscovering them from scratch. When list_tabs is empty ([]) a second text block "hint: ..."
|
|
41
|
-
follows the result — read it: it says whether to wait for the extension's
|
|
42
|
-
auto-reconnect (up to 10 minutes after a drop) or ask the user to press
|
|
43
|
-
Connect.
|
|
44
|
-
|
|
45
|
-
## Sharing tabs with other agents (multi-agent mode)
|
|
46
|
-
|
|
47
|
-
Several MCP clients may share one bridge. To stop two agents fighting
|
|
48
|
-
over the same Chrome tab, a cooperative claim layer is in place:
|
|
49
|
-
|
|
50
|
-
- Action tools (\`browser.click\`, \`browser.type\`, \`browser.navigate\`,
|
|
51
|
-
\`browser.evaluate\`) auto-claim a free tab on first use. If another
|
|
52
|
-
agent holds the tab, they return an error naming the owner — do NOT
|
|
53
|
-
retry blindly; ask the user whose tab it should be, or use a
|
|
54
|
-
different tab.
|
|
55
|
-
- Read tools (\`browser.snapshot\`, \`browser.state\`, \`browser.console\`,
|
|
56
|
-
\`browser.network\`, \`browser.network_body\`, \`browser.events\`,
|
|
57
|
-
\`browser.ping\`) ignore claims.
|
|
58
|
-
- Use \`browser.claim_tab\` with a stable \`label\` ("claude-code",
|
|
59
|
-
"opencode") to reserve a tab before a multi-step flow; release with
|
|
60
|
-
\`browser.release_tab\` (or let the inactivity TTL handle it).
|
|
61
|
-
|
|
62
|
-
The label is display only — security relies on the IPC session id
|
|
63
|
-
(kernel-vetted), not on what an agent calls itself. When you get a
|
|
64
|
-
claim-conflict error: do NOT spin-retry. Either work on a different tab
|
|
65
|
-
from \`list_tabs\`, or surface the conflict to the user.
|
|
66
|
-
|
|
67
|
-
## cdp-direct tabs (optional, off by default, human-gated)
|
|
68
|
-
|
|
69
|
-
\`browser.list_tabs\` may show tabs whose \`tab_id\` starts with \`cdp:\` and
|
|
70
|
-
carry \`transport: "cdp"\` — the server reached them directly over Chrome's
|
|
71
|
-
remote-debugging protocol instead of through the extension. This ONLY
|
|
72
|
-
happens when the user has BOTH enabled cdp-direct AND granted a live,
|
|
73
|
-
time-boxed permission — you cannot request, enable, or grant this
|
|
74
|
-
yourself. Every \`browser.*\` tool works on a \`cdp:\` tab exactly like on an
|
|
75
|
-
extension tab, EXCEPT \`browser.drag\` (standalone and as a
|
|
76
|
-
\`browser.flow\` drag step), \`browser.console\`,
|
|
77
|
-
\`browser.network\`, \`browser.network_body\`, \`browser.canvas_screenshot\`,
|
|
78
|
-
\`browser.dialog_respond\`, \`browser.set_permission\`,
|
|
79
|
-
\`browser.wait_for_tab\` and \`browser.download\` by \`selector\`, which are
|
|
80
|
-
not implemented for this transport yet — those calls fail with a clear
|
|
81
|
-
error naming the extension as the fallback (\`browser.download\` by \`url\`
|
|
82
|
-
does work there).
|
|
83
|
-
If any tool call on a \`cdp:\` tab fails with "cdp-direct is disabled" or
|
|
84
|
-
"cdp-direct requires an active grant", that is the two-step permission gate
|
|
85
|
-
talking, not a bug — relay the exact suggested command
|
|
86
|
-
(\`browser-link config set cdp-direct.enabled true\` or
|
|
87
|
-
\`browser-link cdp allow\`) to the user; there is no way around it from here.
|
|
88
|
-
|
|
89
|
-
## Identifying the app
|
|
90
|
-
|
|
91
|
-
- \`origin\` = scheme://host:port of the tab.
|
|
92
|
-
- \`app_key\` distinguishes apps that share an origin over time. On
|
|
93
|
-
first save you may omit it; it will be derived from the page title
|
|
94
|
-
(slugified). Use \`browser.map.rename_app\` if that initial guess is
|
|
95
|
-
poor.
|
|
96
|
-
|
|
97
|
-
The live snapshot is always the source of truth. The persistent map is
|
|
98
|
-
a cache of navigation, not a substitute.
|
|
99
|
-
|
|
100
|
-
## Replaying a saved flow recipe
|
|
101
|
-
|
|
102
|
-
\`browser.map.save\` can persist named flow recipes (\`flows: [{ name,
|
|
103
|
-
description?, steps }]\`) alongside selectors/gotchas — \`steps\` follows
|
|
104
|
-
the EXACT \`browser.flow\` step grammar and is validated with the same
|
|
105
|
-
rules. \`browser.map.recall\` returns the app's saved flows
|
|
106
|
-
(\`name\`, \`description\`, \`steps\`, \`use_count\`). The pattern:
|
|
107
|
-
recall → ADAPT the steps (substitute placeholder text like
|
|
108
|
-
\`type text "<QUERY>"\` for the real value the user asked for) →
|
|
109
|
-
\`browser.flow\` with the adapted steps. Never replay a saved flow
|
|
110
|
-
verbatim when it contains a placeholder — substitute first. This is the
|
|
111
|
-
SAME privacy rule as every other map entry: flows store UI STRUCTURE
|
|
112
|
-
only, never domain data (real names, IDs, dates, message content) —
|
|
113
|
-
placeholders exist precisely so a flow can be saved without ever writing
|
|
114
|
-
that data to disk.
|
|
115
|
-
|
|
116
|
-
## Saving mechanisms and pitfalls
|
|
117
|
-
|
|
118
|
-
After a non-trivial discovery, save more than selectors.
|
|
119
|
-
\`browser.map.save\` also takes \`mechanisms: [{ key, description }]\` (how
|
|
120
|
-
the app behaves underneath — "a GET of an item URL marks it complete
|
|
121
|
-
server-side", "progress % is client-rendered, not in the fetched HTML")
|
|
122
|
-
and \`pitfalls: [{ key, description, symptom? }]\` (what does NOT work —
|
|
123
|
-
"click on the next link returns ok but does not navigate (onmouseup
|
|
124
|
-
handler)"). \`browser.map.recall\` returns \`pitfalls\` first, then
|
|
125
|
-
\`mechanisms\`. A pitfall is the most expensive thing to rediscover, so
|
|
126
|
-
write them down. Same privacy rule as the rest of the map: behaviour,
|
|
127
|
-
never content — no IDs, names, dates or real values.
|
|
128
|
-
|
|
129
|
-
## Token-efficient patterns (READ THIS BEFORE REACHING FOR EVALUATE)
|
|
130
|
-
|
|
131
|
-
\`browser.evaluate\` is the all-purpose escape hatch. Reach for it only
|
|
132
|
-
when no dedicated tool fits — every dedicated tool below is cheaper in
|
|
133
|
-
tokens AND more reliable than a hand-rolled expression.
|
|
134
|
-
|
|
135
|
-
- **Any multi-step interaction** (find a target, act on it, wait, act
|
|
136
|
-
again) → \`browser.flow({ steps: [...] })\` is now the DEFAULT, not a
|
|
137
|
-
fallback. A sequence like find → click → wait_for → type → press used
|
|
138
|
-
to cost 5-10+ separate MCP round trips — each one a full LLM inference
|
|
139
|
-
— and now costs ONE \`browser.flow\` call (two if a step fails and you
|
|
140
|
-
need a corrective follow-up). A \`find\` step's resolved selector
|
|
141
|
-
becomes the implicit target for the very next click/type/press step
|
|
142
|
-
that omits \`selector\`, so you never have to copy a selector from one
|
|
143
|
-
call into the next by hand. Repeated drag-and-drop (card-matching
|
|
144
|
-
exercises, sortable lists) batches the same way: N \`drag\` steps —
|
|
145
|
-
same params as \`browser.drag\`, endpoints always explicit, drag stays
|
|
146
|
-
OUT of the implicit-target chain — in ONE flow instead of N
|
|
147
|
-
\`browser.drag\` calls (extension tabs only; a drag step on a \`cdp:\`
|
|
148
|
-
tab fails at that step). Flow is strictly sequential and fails fast
|
|
149
|
-
on the first bad step, returning a focused \`recovery_snapshot\` so you
|
|
150
|
-
do not need a follow-up \`browser.snapshot\` to see where it stopped.
|
|
151
|
-
Reach for individual click/type/press/find/wait_for/drag calls only
|
|
152
|
-
when you need to branch on an intermediate result.
|
|
153
|
-
- **Finding an element by visible text** → call \`browser.find\` (not
|
|
154
|
-
\`browser.evaluate\` with a textContent grep). \`find\` covers
|
|
155
|
-
\`<div onclick>\` and other "no testid" markup that a naive
|
|
156
|
-
\`querySelectorAll('button')\` MISSES SILENTLY, applies visibility +
|
|
157
|
-
ARIA checks consistently, returns a stable selector + coords, and on
|
|
158
|
-
multi-match returns up to 5 candidates with snippets for
|
|
159
|
-
disambiguation. The hand-rolled version forgets at least one of
|
|
160
|
-
these every time.
|
|
161
|
-
- **Trimming snapshot size** → pass filters to \`browser.snapshot\`:
|
|
162
|
-
\`within_selector\` restricts the scan to a subtree, \`only_interactive\`
|
|
163
|
-
skips headings + visible text, \`exclude:["nav","footer"]\` drops
|
|
164
|
-
repeated landmarks, \`max_interactive\` overrides the cap of 120.
|
|
165
|
-
Cuts token cost on the most-called tool; not hackeable from the
|
|
166
|
-
client side (the filtering happens in-page).
|
|
167
|
-
- **Looking at the same view again** → take ONE full \`browser.snapshot\` per
|
|
168
|
-
view, keep its \`snapshot_id\`, and pass it as \`since\` for every later
|
|
169
|
-
look: \`browser.snapshot({ since })\` returns only \`added\` / \`removed\` /
|
|
170
|
-
\`changed\` entries instead of the whole page. On
|
|
171
|
-
\`since_status: "doc-changed"\` (or \`evicted\`, \`observer-unavailable\`) you
|
|
172
|
-
already got the full snapshot back: keep its new \`snapshot_id\`. A changed
|
|
173
|
-
neighbour can be listed because its selector changed — use the current
|
|
174
|
-
one.
|
|
175
|
-
- **Acting on an element you just located** → prefer its \`ref\` (like \`k3x9ab:e12\`)
|
|
176
|
-
from the last \`snapshot\`/\`find\` of the CURRENT document: \`click\`,
|
|
177
|
-
\`type\`, \`press\`, \`find\` and \`wait_for\` take \`ref\` instead of
|
|
178
|
-
\`selector\`. A replaced node is re-found by role + accessible name +
|
|
179
|
-
tag path and the result says \`reresolved: true\`; identical rows tie
|
|
180
|
-
and are refused. On \`ref-stale\` (node gone, or ref from another
|
|
181
|
-
document) take a fresh \`browser.snapshot\` — never retry the same
|
|
182
|
-
ref. Refs die with the document: never save one to the map.
|
|
183
|
-
- **Saving a structural selector to the map** → a \`find\` single match
|
|
184
|
-
carries \`semantic_key\`. When the selector is structural
|
|
185
|
-
(\`:nth-of-type\`), save that \`semantic_key\` in the payload next to it,
|
|
186
|
-
and use \`browser.find({ key })\` when recall returns
|
|
187
|
-
\`prefer: "semantic_key"\`. Never save a key for an \`ambiguous\` entry.
|
|
188
|
-
- **Quick orientation without a full snapshot** → \`browser.state\`. Returns
|
|
189
|
-
\`{ url, title, focused?, dialogs?, scroll?, viewport }\` — cheaper than
|
|
190
|
-
\`browser.snapshot\` when you only need to know where you are (is a
|
|
191
|
-
dialog open, what has focus, did the page scroll) before deciding
|
|
192
|
-
whether a full snapshot is even needed.
|
|
193
|
-
- **\`browser.find\` returned \`not-found\`** → read \`near_misses\` (up to 3
|
|
194
|
-
ranked candidates) before giving up or re-snapshotting blind. When
|
|
195
|
-
\`error\` names a role mismatch (text exists, wrong role), drop \`role\`
|
|
196
|
-
or match the role the closest near-miss actually has.
|
|
197
|
-
- **Reading N values** → ONE \`browser.evaluate\` that returns an
|
|
198
|
-
object: \`(() => ({ a: document.querySelector('#a').value, b: ... }))()\`.
|
|
199
|
-
Never call \`browser.evaluate\` N times in a row to read N values.
|
|
200
|
-
- **Scrolling** → \`browser.evaluate\` with \`pane.scrollTop = N\` or
|
|
201
|
-
\`el.scrollIntoView({block:'center'})\`. No dedicated tool needed.
|
|
202
|
-
- **Special keys (Enter / Tab / Escape / arrow keys / shortcuts)** →
|
|
203
|
-
\`browser.press\`, NOT \`browser.evaluate\` with a synthetic
|
|
204
|
-
\`KeyboardEvent\`. \`el.dispatchEvent(new KeyboardEvent(...))\` produces
|
|
205
|
-
\`isTrusted:false\` events that rich text editors, autocompletes, and
|
|
206
|
-
non-DOM runtimes (Qt-WASM, WebGL) silently ignore. \`browser.press\`
|
|
207
|
-
dispatches a real CDP \`Input.dispatchKeyEvent\` sequence
|
|
208
|
-
(\`isTrusted:true\`) — pass \`key\` ("Enter", "ArrowUp", a single
|
|
209
|
-
character, …) and optional \`modifiers\` for shortcuts (Ctrl+A, Cmd+S).
|
|
210
|
-
For TYPING text into an input, use \`browser.type\` — it goes through
|
|
211
|
-
the native setter so controlled components update their state.
|
|
212
|
-
\`dispatchEvent\` on \`value\` does NOT.
|
|
213
|
-
- **Waiting for the result of an action** → pass \`settle_ms\` to
|
|
214
|
-
\`browser.click\` / \`.type\` / \`.press\` (default 150, max 2000)
|
|
215
|
-
instead of a follow-up \`browser.wait_for\` + \`browser.snapshot\`. The
|
|
216
|
-
action waits for the page to go quiet — no DOM mutations for
|
|
217
|
-
\`settle_ms\` — and returns a \`settle\` object
|
|
218
|
-
(\`{ settled, duration_ms, mutation_count, url_changed?, focus_moved?,
|
|
219
|
-
changes, observer? }\`). Settle proves QUIET, not EFFECT, but it
|
|
220
|
-
measures from a mark taken BEFORE the action: \`settle.changes\`
|
|
221
|
-
(records, targets, kinds, roots, descriptors) is what the action
|
|
222
|
-
caused, and \`mutation_count: 0\` with \`changes\` present means no DOM
|
|
223
|
-
change in the quiet window. \`changes: null\` with
|
|
224
|
-
\`observer: "<reason>"\` means the observer was not available for that
|
|
225
|
-
action. A reaction that starts after the quiet window is still
|
|
226
|
-
invisible: reach for \`expect\` or \`wait_for\` when you need a
|
|
227
|
-
SPECIFIC condition (a particular selector, network request, or
|
|
228
|
-
expression), not just "did anything change".
|
|
229
|
-
- **Counting on a specific effect of an action** (navigation, a dialog
|
|
230
|
-
opening, a result appearing) → pass \`expect\` to \`browser.click\` /
|
|
231
|
-
\`.type\` / \`.press\` (or the matching \`browser.flow\` step):
|
|
232
|
-
\`url_changed\`, \`selector_appears\`, \`selector_disappears\`,
|
|
233
|
-
\`text_appears\`, \`timeout_ms\`. A no-op action then fails right
|
|
234
|
-
there with \`expectation not met: ...\` instead of reading as success.
|
|
235
|
-
\`expect\` asserts a transition: appears/disappears conditions are
|
|
236
|
-
sampled before the action, and one that already held beforehand fails
|
|
237
|
-
immediately.
|
|
238
|
-
- **Paging through \`browser.events\`** → keep \`lastId = result.latest_id\`
|
|
239
|
-
in your working notes and pass it as \`since_id\` on the next call.
|
|
240
|
-
That is one variable. The server does not maintain per-agent cursors.
|
|
241
|
-
|
|
242
|
-
## Seeing a page: browser.screenshot
|
|
243
|
-
|
|
244
|
-
To SEE a page, use \`browser.screenshot\`: \`mode: "viewport"\` (default),
|
|
245
|
-
\`"full_page"\` (the whole document in one image, no scrolling) or \`"element"\`
|
|
246
|
-
(with \`selector\`). It returns a real image plus metadata, scales long pages
|
|
247
|
-
down to \`max_dimension\` (default 8000 px) and caps the result at ~8 MB. It is
|
|
248
|
-
a read tool and works on \`cdp:\` tabs too. Images cost tokens: prefer
|
|
249
|
-
"element" or JPEG when full fidelity is not needed.
|
|
250
|
-
|
|
251
|
-
## Pages rendered to a canvas (Qt-WASM, WebGL, DOMless UIs)
|
|
252
|
-
|
|
253
|
-
Some pages render their entire UI to a single \`<canvas>\` element — Victron
|
|
254
|
-
VRM Remote Console, Venus OS, Felgo apps, WebGL games, custom rendering
|
|
255
|
-
engines. On those pages \`browser.snapshot\` returns an empty
|
|
256
|
-
\`interactive\` list and \`browser.find\` finds nothing, because the visible
|
|
257
|
-
UI lives inside the canvas's pixel buffer, not in the DOM.
|
|
258
|
-
|
|
259
|
-
When you see that mismatch (clear UI on screen, nothing in snapshot),
|
|
260
|
-
reach for \`browser.canvas_screenshot\`. It returns the canvas as PNG/JPEG
|
|
261
|
-
base64 so you can SEE the page as a vision input. The finder walks
|
|
262
|
-
nested Shadow DOM roots automatically — Qt-WASM hides its canvas behind
|
|
263
|
-
two layers of \`attachShadow\` and a no-selector call still finds it.
|
|
264
|
-
|
|
265
|
-
Acting on a canvas page is NOT yet possible from this server.
|
|
266
|
-
Qt-WASM and similar runtimes only accept \`isTrusted: true\` input from
|
|
267
|
-
the browser kernel; \`browser.click\` / \`browser.type\` / synthetic
|
|
268
|
-
\`dispatchEvent\` go straight through without effect. Take screenshots,
|
|
269
|
-
reason about what is on screen, ask the user to act when interaction
|
|
270
|
-
is needed.
|
|
271
|
-
|
|
272
|
-
## Downloads — never click a download button directly
|
|
273
|
-
|
|
274
|
-
A plain \`browser.click\` on a Download / Export button can open Chrome's
|
|
275
|
-
native "Save as" dialog (when the user enabled "Ask where to save each
|
|
276
|
-
file"). Nothing in CDP or the page can press that dialog, so the agent is
|
|
277
|
-
stuck. Use \`browser.download({ tab_id, selector })\` instead: it captures the
|
|
278
|
-
download the click starts, cancels it before Chrome can prompt, fetches the
|
|
279
|
-
file in the page with the page's credentials and writes it into the
|
|
280
|
-
human-configured downloads directory. With a known URL, pass \`url\`. The
|
|
281
|
-
result always says what happened — \`state: "complete" | "interrupted" |
|
|
282
|
-
"timeout"\` with \`path\`, \`bytes\`, \`mime\` — so you never have to guess.`;
|
|
283
|
-
/** Render a single tool's documentation as a markdown section. Skips
|
|
284
|
-
* tools without a `doc` block (the structured shape is opt-in for now
|
|
285
|
-
* so future contributors can stage the migration tool-by-tool). */
|
|
286
|
-
function renderTool(def) {
|
|
287
|
-
if (!def.doc)
|
|
288
|
-
return null;
|
|
289
|
-
const { purpose, when_to_use, gotchas, example } = def.doc;
|
|
290
|
-
const lines = [`### ${def.name}`, '', purpose, '', '**When to use:**'];
|
|
291
|
-
for (const w of when_to_use)
|
|
292
|
-
lines.push(`- ${w}`);
|
|
293
|
-
if (gotchas && gotchas.length > 0) {
|
|
294
|
-
lines.push('', '**Gotchas:**');
|
|
295
|
-
for (const g of gotchas)
|
|
296
|
-
lines.push(`- ${g}`);
|
|
297
|
-
}
|
|
298
|
-
if (example !== undefined && example !== '') {
|
|
299
|
-
lines.push('', '**Example:**', '```', example, '```');
|
|
300
|
-
}
|
|
301
|
-
return lines.join('\n');
|
|
11
|
+
import { renderServerInstructions } from '../manual/render.js';
|
|
12
|
+
import { detectInstructionsStatus, instructionsNotice, } from '../manual/status.js';
|
|
13
|
+
/** Render the instructions text; `opts.notice`, when set, becomes its first
|
|
14
|
+
* line. */
|
|
15
|
+
export function buildServerInstructions(opts = {}) {
|
|
16
|
+
return renderServerInstructions(opts);
|
|
302
17
|
}
|
|
303
|
-
/**
|
|
304
|
-
*
|
|
305
|
-
* the string after a hot-reload or runtime tool registration. */
|
|
306
|
-
export function buildServerInstructions() {
|
|
307
|
-
const all = [...BROWSER_TOOL_DEFINITIONS, ...MAP_TOOL_DEFINITIONS];
|
|
308
|
-
const sections = [PREAMBLE, '', '## Tools'];
|
|
309
|
-
for (const def of all) {
|
|
310
|
-
const rendered = renderTool(def);
|
|
311
|
-
if (rendered) {
|
|
312
|
-
sections.push('', rendered);
|
|
313
|
-
}
|
|
314
|
-
}
|
|
315
|
-
return sections.join('\n');
|
|
316
|
-
}
|
|
317
|
-
/** Usage protocol pushed to the MCP client on `initialize`. Plain string,
|
|
318
|
-
* generated from the per-tool `doc` blocks at module load. */
|
|
18
|
+
/** Usage protocol pushed to the MCP client on `initialize`, without a
|
|
19
|
+
* notice, rendered at module load. */
|
|
319
20
|
export const SERVER_INSTRUCTIONS = buildServerInstructions();
|
|
21
|
+
/** The instructions the primary serves, built at start: detects each
|
|
22
|
+
* client's block (read-only) and, when one is outdated or missing, puts the
|
|
23
|
+
* notice first so the agent sees it. Returns the statuses too so the caller
|
|
24
|
+
* can log them. `installers` defaults to the real clients; tests inject
|
|
25
|
+
* stubs. */
|
|
26
|
+
export function buildStartInstructions(installers) {
|
|
27
|
+
const statuses = detectInstructionsStatus(installers);
|
|
28
|
+
const notice = instructionsNotice(statuses);
|
|
29
|
+
return { statuses, text: notice ? buildServerInstructions({ notice }) : SERVER_INSTRUCTIONS };
|
|
30
|
+
}
|
|
320
31
|
//# sourceMappingURL=server-instructions.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"server-instructions.js","sourceRoot":"","sources":["../../src/tools/server-instructions.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAEH,OAAO,EAAE,wBAAwB,EAAE,MAAM,
|
|
1
|
+
{"version":3,"file":"server-instructions.js","sourceRoot":"","sources":["../../src/tools/server-instructions.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAEH,OAAO,EAAE,wBAAwB,EAAE,MAAM,qBAAqB,CAAC;AAE/D,OAAO,EACL,wBAAwB,EACxB,kBAAkB,GAEnB,MAAM,qBAAqB,CAAC;AAG7B;WACW;AACX,MAAM,UAAU,uBAAuB,CAAC,OAAoB,EAAE;IAC5D,OAAO,wBAAwB,CAAC,IAAI,CAAC,CAAC;AACxC,CAAC;AAED;sCACsC;AACtC,MAAM,CAAC,MAAM,mBAAmB,GAAG,uBAAuB,EAAE,CAAC;AAE7D;;;;YAIY;AACZ,MAAM,UAAU,sBAAsB,CAAC,UAA6C;IAIlF,MAAM,QAAQ,GAAG,wBAAwB,CAAC,UAAU,CAAC,CAAC;IACtD,MAAM,MAAM,GAAG,kBAAkB,CAAC,QAAQ,CAAC,CAAC;IAC5C,OAAO,EAAE,QAAQ,EAAE,IAAI,EAAE,MAAM,CAAC,CAAC,CAAC,uBAAuB,CAAC,EAAE,MAAM,EAAE,CAAC,CAAC,CAAC,CAAC,mBAAmB,EAAE,CAAC;AAChG,CAAC"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@jobshimo/browser-link",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.38.0",
|
|
4
4
|
"description": "MCP server that bridges Claude Code, OpenCode, GitHub Copilot CLI and other MCP clients to a Chrome tab. Per-tool permissions, multi-agent mode (multiple MCP clients sharing one bridge), persistent UI map across sessions.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"mcp",
|