@jobshimo/browser-link 0.36.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.
Files changed (69) hide show
  1. package/dist/agent-instructions/content.js +5 -349
  2. package/dist/agent-instructions/content.js.map +1 -1
  3. package/dist/bridge/server.d.ts +5 -0
  4. package/dist/bridge/server.js +1 -1
  5. package/dist/bridge/server.js.map +1 -1
  6. package/dist/cdp/flow.d.ts +44 -3
  7. package/dist/cdp/flow.js +65 -19
  8. package/dist/cdp/flow.js.map +1 -1
  9. package/dist/cdp/inpage/builders.d.ts +110 -24
  10. package/dist/cdp/inpage/builders.js +203 -53
  11. package/dist/cdp/inpage/builders.js.map +1 -1
  12. package/dist/cdp/inpage/deep-query.d.ts +1 -1
  13. package/dist/cdp/inpage/deep-query.js +12 -9
  14. package/dist/cdp/inpage/deep-query.js.map +1 -1
  15. package/dist/cdp/inpage/observer.d.ts +36 -7
  16. package/dist/cdp/inpage/observer.js +242 -16
  17. package/dist/cdp/inpage/observer.js.map +1 -1
  18. package/dist/cdp/transport.js +96 -9
  19. package/dist/cdp/transport.js.map +1 -1
  20. package/dist/commands/about.js +4 -2
  21. package/dist/commands/about.js.map +1 -1
  22. package/dist/extension/background.js +97 -13
  23. package/dist/extension/background.js.map +1 -1
  24. package/dist/extension/flow.d.ts +44 -3
  25. package/dist/extension/flow.js +65 -19
  26. package/dist/extension/flow.js.map +1 -1
  27. package/dist/extension/inpage/builders.d.ts +110 -24
  28. package/dist/extension/inpage/builders.js +203 -53
  29. package/dist/extension/inpage/builders.js.map +1 -1
  30. package/dist/extension/inpage/deep-query.d.ts +1 -1
  31. package/dist/extension/inpage/deep-query.js +12 -9
  32. package/dist/extension/inpage/deep-query.js.map +1 -1
  33. package/dist/extension/inpage/observer.d.ts +36 -7
  34. package/dist/extension/inpage/observer.js +242 -16
  35. package/dist/extension/inpage/observer.js.map +1 -1
  36. package/dist/extension/manifest.json +1 -1
  37. package/dist/manual/core.d.ts +20 -0
  38. package/dist/manual/core.js +71 -0
  39. package/dist/manual/core.js.map +1 -0
  40. package/dist/manual/render.d.ts +22 -0
  41. package/dist/manual/render.js +56 -0
  42. package/dist/manual/render.js.map +1 -0
  43. package/dist/manual/status.d.ts +23 -0
  44. package/dist/manual/status.js +52 -0
  45. package/dist/manual/status.js.map +1 -0
  46. package/dist/manual/tool.d.ts +20 -0
  47. package/dist/manual/tool.js +106 -0
  48. package/dist/manual/tool.js.map +1 -0
  49. package/dist/manual/topics.d.ts +18 -0
  50. package/dist/manual/topics.js +60 -0
  51. package/dist/manual/topics.js.map +1 -0
  52. package/dist/map/queries.d.ts +2 -0
  53. package/dist/map/queries.js +23 -1
  54. package/dist/map/queries.js.map +1 -1
  55. package/dist/map/tools.js +26 -4
  56. package/dist/map/tools.js.map +1 -1
  57. package/dist/permissions.js +6 -0
  58. package/dist/permissions.js.map +1 -1
  59. package/dist/server.js +11 -14
  60. package/dist/server.js.map +1 -1
  61. package/dist/tools/browser-definitions.js +113 -23
  62. package/dist/tools/browser-definitions.js.map +1 -1
  63. package/dist/tools/browser-dispatch.d.ts +24 -26
  64. package/dist/tools/browser-dispatch.js +169 -14
  65. package/dist/tools/browser-dispatch.js.map +1 -1
  66. package/dist/tools/server-instructions.d.ts +24 -13
  67. package/dist/tools/server-instructions.js +25 -303
  68. package/dist/tools/server-instructions.js.map +1 -1
  69. package/package.json +1 -1
@@ -1,17 +1,28 @@
1
1
  /**
2
- * Build the MCP `initialize.instructions` payload from the structured `doc`
3
- * blocks each tool definition carries. Keeping the per-tool copy beside the
4
- * tool definition (in `browser-definitions.ts` and `map/tools.ts`) prevents
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 and exported as a
8
- * plain string so the existing consumers (`server.ts`, `bridge/server.ts`)
9
- * keep working without changes.
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
- /** Generate the SERVER_INSTRUCTIONS string from the tool definitions.
12
- * Exported for tests and for any future caller that needs to re-render
13
- * the string after a hot-reload or runtime tool registration. */
14
- export declare function buildServerInstructions(): string;
15
- /** Usage protocol pushed to the MCP client on `initialize`. Plain string,
16
- * generated from the per-tool `doc` blocks at module load. */
11
+ import type { CoreOptions } from '../manual/core.js';
12
+ import { type ClientInstructionsStatus } from '../manual/status.js';
13
+ import type { InstructionsInstaller } from '../agent-instructions/index.js';
14
+ /** Render the instructions text; `opts.notice`, when set, becomes its first
15
+ * line. */
16
+ export declare function buildServerInstructions(opts?: CoreOptions): string;
17
+ /** Usage protocol pushed to the MCP client on `initialize`, without a
18
+ * notice, rendered at module load. */
17
19
  export declare const SERVER_INSTRUCTIONS: string;
20
+ /** The instructions the primary serves, built at start: detects each
21
+ * client's block (read-only) and, when one is outdated or missing, puts the
22
+ * notice first so the agent sees it. Returns the statuses too so the caller
23
+ * can log them. `installers` defaults to the real clients; tests inject
24
+ * stubs. */
25
+ export declare function buildStartInstructions(installers?: readonly InstructionsInstaller[]): {
26
+ statuses: ClientInstructionsStatus[];
27
+ text: string;
28
+ };
@@ -1,309 +1,31 @@
1
1
  /**
2
- * Build the MCP `initialize.instructions` payload from the structured `doc`
3
- * blocks each tool definition carries. Keeping the per-tool copy beside the
4
- * tool definition (in `browser-definitions.ts` and `map/tools.ts`) prevents
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 and exported as a
8
- * plain string so the existing consumers (`server.ts`, `bridge/server.ts`)
9
- * keep working without changes.
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 { BROWSER_TOOL_DEFINITIONS } from './browser-definitions.js';
12
- import { MAP_TOOL_DEFINITIONS } from '../map/tools.js';
13
- /** Stable preamble — explains what the bridge is, where state lives, and
14
- * what the agent must NOT do. Lives here (not in a tool doc) because it is
15
- * cross-cutting context, not tied to any one tool. */
16
- const PREAMBLE = `browser-link bridges Claude Code to the Chrome tabs the user has
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. \`ref\` (\`e<N>\`) on entries is an element handle valid only for the
175
- \`doc_id\` that issued it; for now it is informational, not an action
176
- target.
177
- - **Quick orientation without a full snapshot** → \`browser.state\`. Returns
178
- \`{ url, title, focused?, dialogs?, scroll?, viewport }\` — cheaper than
179
- \`browser.snapshot\` when you only need to know where you are (is a
180
- dialog open, what has focus, did the page scroll) before deciding
181
- whether a full snapshot is even needed.
182
- - **\`browser.find\` returned \`not-found\`** → read \`near_misses\` (up to 3
183
- ranked candidates) before giving up or re-snapshotting blind. When
184
- \`error\` names a role mismatch (text exists, wrong role), drop \`role\`
185
- or match the role the closest near-miss actually has.
186
- - **Reading N values** → ONE \`browser.evaluate\` that returns an
187
- object: \`(() => ({ a: document.querySelector('#a').value, b: ... }))()\`.
188
- Never call \`browser.evaluate\` N times in a row to read N values.
189
- - **Scrolling** → \`browser.evaluate\` with \`pane.scrollTop = N\` or
190
- \`el.scrollIntoView({block:'center'})\`. No dedicated tool needed.
191
- - **Special keys (Enter / Tab / Escape / arrow keys / shortcuts)** →
192
- \`browser.press\`, NOT \`browser.evaluate\` with a synthetic
193
- \`KeyboardEvent\`. \`el.dispatchEvent(new KeyboardEvent(...))\` produces
194
- \`isTrusted:false\` events that rich text editors, autocompletes, and
195
- non-DOM runtimes (Qt-WASM, WebGL) silently ignore. \`browser.press\`
196
- dispatches a real CDP \`Input.dispatchKeyEvent\` sequence
197
- (\`isTrusted:true\`) — pass \`key\` ("Enter", "ArrowUp", a single
198
- character, …) and optional \`modifiers\` for shortcuts (Ctrl+A, Cmd+S).
199
- For TYPING text into an input, use \`browser.type\` — it goes through
200
- the native setter so controlled components update their state.
201
- \`dispatchEvent\` on \`value\` does NOT.
202
- - **Waiting for the result of an action** → pass \`settle_ms\` to
203
- \`browser.click\` / \`.type\` / \`.press\` (default 150, max 2000)
204
- instead of a follow-up \`browser.wait_for\` + \`browser.snapshot\`. The
205
- action waits for the page to go quiet — no DOM mutations for
206
- \`settle_ms\` — and returns a \`settle\` object
207
- (\`{ settled, duration_ms, mutation_count, url_changed?, focus_moved?,
208
- changes, observer? }\`). Settle proves QUIET, not EFFECT, but it
209
- measures from a mark taken BEFORE the action: \`settle.changes\`
210
- (records, targets, kinds, roots, descriptors) is what the action
211
- caused, and \`mutation_count: 0\` with \`changes\` present means no DOM
212
- change in the quiet window. \`changes: null\` with
213
- \`observer: "<reason>"\` means the observer was not available for that
214
- action. A reaction that starts after the quiet window is still
215
- invisible: reach for \`expect\` or \`wait_for\` when you need a
216
- SPECIFIC condition (a particular selector, network request, or
217
- expression), not just "did anything change".
218
- - **Counting on a specific effect of an action** (navigation, a dialog
219
- opening, a result appearing) → pass \`expect\` to \`browser.click\` /
220
- \`.type\` / \`.press\` (or the matching \`browser.flow\` step):
221
- \`url_changed\`, \`selector_appears\`, \`selector_disappears\`,
222
- \`text_appears\`, \`timeout_ms\`. A no-op action then fails right
223
- there with \`expectation not met: ...\` instead of reading as success.
224
- \`expect\` asserts a transition: appears/disappears conditions are
225
- sampled before the action, and one that already held beforehand fails
226
- immediately.
227
- - **Paging through \`browser.events\`** → keep \`lastId = result.latest_id\`
228
- in your working notes and pass it as \`since_id\` on the next call.
229
- That is one variable. The server does not maintain per-agent cursors.
230
-
231
- ## Seeing a page: browser.screenshot
232
-
233
- To SEE a page, use \`browser.screenshot\`: \`mode: "viewport"\` (default),
234
- \`"full_page"\` (the whole document in one image, no scrolling) or \`"element"\`
235
- (with \`selector\`). It returns a real image plus metadata, scales long pages
236
- down to \`max_dimension\` (default 8000 px) and caps the result at ~8 MB. It is
237
- a read tool and works on \`cdp:\` tabs too. Images cost tokens: prefer
238
- "element" or JPEG when full fidelity is not needed.
239
-
240
- ## Pages rendered to a canvas (Qt-WASM, WebGL, DOMless UIs)
241
-
242
- Some pages render their entire UI to a single \`<canvas>\` element — Victron
243
- VRM Remote Console, Venus OS, Felgo apps, WebGL games, custom rendering
244
- engines. On those pages \`browser.snapshot\` returns an empty
245
- \`interactive\` list and \`browser.find\` finds nothing, because the visible
246
- UI lives inside the canvas's pixel buffer, not in the DOM.
247
-
248
- When you see that mismatch (clear UI on screen, nothing in snapshot),
249
- reach for \`browser.canvas_screenshot\`. It returns the canvas as PNG/JPEG
250
- base64 so you can SEE the page as a vision input. The finder walks
251
- nested Shadow DOM roots automatically — Qt-WASM hides its canvas behind
252
- two layers of \`attachShadow\` and a no-selector call still finds it.
253
-
254
- Acting on a canvas page is NOT yet possible from this server.
255
- Qt-WASM and similar runtimes only accept \`isTrusted: true\` input from
256
- the browser kernel; \`browser.click\` / \`browser.type\` / synthetic
257
- \`dispatchEvent\` go straight through without effect. Take screenshots,
258
- reason about what is on screen, ask the user to act when interaction
259
- is needed.
260
-
261
- ## Downloads — never click a download button directly
262
-
263
- A plain \`browser.click\` on a Download / Export button can open Chrome's
264
- native "Save as" dialog (when the user enabled "Ask where to save each
265
- file"). Nothing in CDP or the page can press that dialog, so the agent is
266
- stuck. Use \`browser.download({ tab_id, selector })\` instead: it captures the
267
- download the click starts, cancels it before Chrome can prompt, fetches the
268
- file in the page with the page's credentials and writes it into the
269
- human-configured downloads directory. With a known URL, pass \`url\`. The
270
- result always says what happened — \`state: "complete" | "interrupted" |
271
- "timeout"\` with \`path\`, \`bytes\`, \`mime\` — so you never have to guess.`;
272
- /** Render a single tool's documentation as a markdown section. Skips
273
- * tools without a `doc` block (the structured shape is opt-in for now
274
- * so future contributors can stage the migration tool-by-tool). */
275
- function renderTool(def) {
276
- if (!def.doc)
277
- return null;
278
- const { purpose, when_to_use, gotchas, example } = def.doc;
279
- const lines = [`### ${def.name}`, '', purpose, '', '**When to use:**'];
280
- for (const w of when_to_use)
281
- lines.push(`- ${w}`);
282
- if (gotchas && gotchas.length > 0) {
283
- lines.push('', '**Gotchas:**');
284
- for (const g of gotchas)
285
- lines.push(`- ${g}`);
286
- }
287
- if (example !== undefined && example !== '') {
288
- lines.push('', '**Example:**', '```', example, '```');
289
- }
290
- 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);
291
17
  }
292
- /** Generate the SERVER_INSTRUCTIONS string from the tool definitions.
293
- * Exported for tests and for any future caller that needs to re-render
294
- * the string after a hot-reload or runtime tool registration. */
295
- export function buildServerInstructions() {
296
- const all = [...BROWSER_TOOL_DEFINITIONS, ...MAP_TOOL_DEFINITIONS];
297
- const sections = [PREAMBLE, '', '## Tools'];
298
- for (const def of all) {
299
- const rendered = renderTool(def);
300
- if (rendered) {
301
- sections.push('', rendered);
302
- }
303
- }
304
- return sections.join('\n');
305
- }
306
- /** Usage protocol pushed to the MCP client on `initialize`. Plain string,
307
- * 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. */
308
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
+ }
309
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,0BAA0B,CAAC;AACpE,OAAO,EAAE,oBAAoB,EAAE,MAAM,iBAAiB,CAAC;AAGvD;;sDAEsD;AACtD,MAAM,QAAQ,GAAG;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;6EA+P4D,CAAC;AAE9E;;mEAEmE;AACnE,SAAS,UAAU,CAAC,GAAmB;IACrC,IAAI,CAAC,GAAG,CAAC,GAAG;QAAE,OAAO,IAAI,CAAC;IAC1B,MAAM,EAAE,OAAO,EAAE,WAAW,EAAE,OAAO,EAAE,OAAO,EAAE,GAAG,GAAG,CAAC,GAAG,CAAC;IAC3D,MAAM,KAAK,GAAa,CAAC,OAAO,GAAG,CAAC,IAAI,EAAE,EAAE,EAAE,EAAE,OAAO,EAAE,EAAE,EAAE,kBAAkB,CAAC,CAAC;IACjF,KAAK,MAAM,CAAC,IAAI,WAAW;QAAE,KAAK,CAAC,IAAI,CAAC,KAAK,CAAC,EAAE,CAAC,CAAC;IAClD,IAAI,OAAO,IAAI,OAAO,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;QAClC,KAAK,CAAC,IAAI,CAAC,EAAE,EAAE,cAAc,CAAC,CAAC;QAC/B,KAAK,MAAM,CAAC,IAAI,OAAO;YAAE,KAAK,CAAC,IAAI,CAAC,KAAK,CAAC,EAAE,CAAC,CAAC;IAChD,CAAC;IACD,IAAI,OAAO,KAAK,SAAS,IAAI,OAAO,KAAK,EAAE,EAAE,CAAC;QAC5C,KAAK,CAAC,IAAI,CAAC,EAAE,EAAE,cAAc,EAAE,KAAK,EAAE,OAAO,EAAE,KAAK,CAAC,CAAC;IACxD,CAAC;IACD,OAAO,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;AAC1B,CAAC;AAED;;iEAEiE;AACjE,MAAM,UAAU,uBAAuB;IACrC,MAAM,GAAG,GAAqB,CAAC,GAAG,wBAAwB,EAAE,GAAG,oBAAoB,CAAC,CAAC;IACrF,MAAM,QAAQ,GAAa,CAAC,QAAQ,EAAE,EAAE,EAAE,UAAU,CAAC,CAAC;IACtD,KAAK,MAAM,GAAG,IAAI,GAAG,EAAE,CAAC;QACtB,MAAM,QAAQ,GAAG,UAAU,CAAC,GAAG,CAAC,CAAC;QACjC,IAAI,QAAQ,EAAE,CAAC;YACb,QAAQ,CAAC,IAAI,CAAC,EAAE,EAAE,QAAQ,CAAC,CAAC;QAC9B,CAAC;IACH,CAAC;IACD,OAAO,QAAQ,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;AAC7B,CAAC;AAED;8DAC8D;AAC9D,MAAM,CAAC,MAAM,mBAAmB,GAAG,uBAAuB,EAAE,CAAC"}
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.36.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",