scenescout 1.1.0 โ†’ 1.3.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 CHANGED
@@ -1,5 +1,34 @@
1
1
  # scenescout
2
2
 
3
+ ## 1.3.0
4
+
5
+ ### Minor Changes
6
+
7
+ - a7c978a: `scenescout install` now puts the `scenescout` command on your PATH. Until now neither an `npx` run nor a source checkout left it there, so `scenescout status`, `scenescout doctor` and the other commands the tool itself tells you to run answered "command not found".
8
+
9
+ Run through `npx`, install does `npm install -g` of the version you ran. From a checkout it does `npm link`, so the command always runs what you last built, and From a checkout it runs `npm link`, taking the name over from any other copy the way install already takes over the MCP registration. Run through `npx`, a command that is already there is left alone. On Windows the step prints the command to run by hand. If npm refuses, the step prints the command to run by hand and does not fail the setup. `--no-command` skips it.
10
+ - 195cb59: Watch a run live. `scout_attach` now returns a `Live view:` address, which the agent passes on to you, and `scenescout watch <project>` opens the same page from a terminal. It shows one card per session: the tool it is running, how long it has been there, the page it is on, a thumbnail of that page, a rolling feed of what it just did (each action, its target and how it turned out, read from the same action log a finding's repro trace uses), and a live stream you can switch on per session or for all of them. Opening a card's close-up shows a longer stretch of that feed beside the session's brief: the task the agent gave it at `scout_attach {task}`, and the goal of the journey it is on right now. Actions of one journey share a tint in the feed, and pointing at a group shows the goal those actions served. The Report button shows the run's report as it stands, rendered from the current state without writing it, so it can be read while the run is still going. It works for headless runs, and a session whose call is still running past its own tool's watchdog budget is marked as stuck.
11
+
12
+ `status.json` now describes every session instead of the last one to write, and `scenescout status` prints a line for each.
13
+
14
+ The live view is served on `127.0.0.1` only, behind a per-process token, answers GET and nothing else, and writes no frame to disk ([ADR 7](docs/adr/0007-the-live-view-is-local-read-only-and-leaves-nothing-behind.md)). A stream runs only while someone is watching it. Set `SCENESCOUT_LIVE=off` to keep the engine from opening the port. The engine now also shuts down, closing its browsers and removing the token file, when its client closes the connection instead of sending a signal.
15
+
16
+ ## 1.2.0
17
+
18
+ ### Minor Changes
19
+
20
+ - 2932164: `scenescout install --browsers <list>` chooses what to download: `chromium` (the default, unchanged), `chromium-headless-shell` for the smallest working setup, `firefox`, `webkit`, or `all`. `scout_attach` takes a `browser` option, and `SCENESCOUT_BROWSER` sets the default. In Firefox and WebKit the engine keeps service workers from registering, because a request issued by one cannot be intercepted there and would pass the write policy. The missing-browser message and `doctor` now name the build that is actually missing, and the sizes quoted are the sizes on disk.
21
+
22
+ Pages are no longer given shared workers unless the mode is `destructive`. A request a shared worker sends cannot be intercepted in any browser, and a `DELETE` sent from one reached the server in read-only mode.
23
+
24
+ Hover no longer reports text that was already on the page and only moved to a new line, and the keyboard focus audit uses Option+Tab in WebKit on macOS, where plain Tab skips buttons and links.
25
+ - ad0f31d: `scenescout install --client <list>` registers the server with clients other than Claude Code: `cursor`, `vscode`, `codex`, `gemini`, `copilot` and `windsurf`. Clients that have a command for adding a server are registered through it; Cursor and Windsurf get an entry added to their JSON server list, with every other entry kept and an unreadable file left untouched. For VS Code, a `code` command that belongs to another editor is not used. The skill is installed only when `claude-code` is among the clients.
26
+ - 5504c63: The testing method now reaches every MCP client, not only Claude Code. A new `scout_playbook` tool returns it, the server's instructions tell an agent to call that tool before its first attach, and an `explore` prompt loads the method together with the target URL for clients that list server prompts as commands. It is the same text Claude Code loads as a skill, read from the same file.
27
+
28
+ ### Patch Changes
29
+
30
+ - d0aa6aa: The package description, keywords and README now present SceneScout as a tool for any MCP client, with Claude Code as one of them. `doctor --engine` ends with what to ask an agent instead of a Claude Code command.
31
+
3
32
  ## 1.1.0
4
33
 
5
34
  ### Minor Changes
package/README.md CHANGED
@@ -2,7 +2,9 @@
2
2
 
3
3
  # ๐Ÿ”ญ SceneScout
4
4
 
5
- **Exploratory UI testing, driven by an AI agent.**
5
+ **Exploratory UI testing, driven by the AI agent you already use.**
6
+
7
+ Works with Claude Code ยท Cursor ยท VS Code (Copilot) ยท Codex CLI ยท Gemini CLI ยท Copilot CLI ยท Windsurf ยท any [MCP](https://modelcontextprotocol.io) client
6
8
 
7
9
  [![test](https://github.com/brunoboto96/SceneScout/actions/workflows/test.yml/badge.svg)](https://github.com/brunoboto96/SceneScout/actions/workflows/test.yml)
8
10
  [![npm](https://img.shields.io/npm/v/scenescout.svg)](https://www.npmjs.com/package/scenescout)
@@ -14,11 +16,11 @@
14
16
 
15
17
  </div>
16
18
 
17
- SceneScout is an [MCP](https://modelcontextprotocol.io) server that hands an agent a *structured view* of a running web app โ€” every element, its geometry, and a set of always-on correctness oracles โ€” and lets the agent explore it like a curious user. Claude Code is the brain; SceneScout is the hands, eyes, and memory.
19
+ SceneScout is an [MCP](https://modelcontextprotocol.io) server that hands an agent a *structured view* of a running web app โ€” every element, its geometry, and a set of always-on correctness oracles โ€” and lets the agent explore it like a curious user. Your coding agent is the brain; SceneScout is the hands, eyes, and memory. Any MCP client can drive it, and the testing method comes with the server, so the agent knows how to use the tools wherever it runs.
18
20
 
19
21
  ```
20
22
  โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” MCP (stdio) โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”
21
- โ”‚ Claude Code + skill โ”‚ โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ–ถ โ”‚ SceneScout engine โ”‚
23
+ โ”‚ Your coding agent โ”‚ โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ–ถ โ”‚ SceneScout engine โ”‚
22
24
  โ”‚ (intent, judgment, โ”‚ โ—€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ โ”‚ Playwright ยท oracles ยท memory โ”‚
23
25
  โ”‚ your subscription) โ”‚ tool results โ”‚ findings ยท report โ€” no LLM โ”‚
24
26
  โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜
@@ -33,7 +35,7 @@ This is a real run against the small demo app bundled in this repository. The ap
33
35
 
34
36
  <p align="center"><img src="examples/screenshots/dashboard-annotated.png" alt="The demo app's dashboard with two defects outlined in red: 1, a yellow badge covering the All orders button; 2, the weekly chart image failing to load" width="760" /></p>
35
37
 
36
- **The broken chart is the demo app's bug, not this page's** โ€” it is one of the ten findings SceneScout filed, next to the badge sitting on a button. The red callouts were added for this README; the [unmarked screenshots](examples/screenshots/) are the ones the engine took.
38
+ **The broken chart is the demo app's bug, not this page's** โ€” it is one of the twelve findings SceneScout filed, next to the badge sitting on a button. The red callouts were added for this README; the [unmarked screenshots](examples/screenshots/) are the ones the engine took.
37
39
 
38
40
  An excerpt of the report it wrote โ€” [read the whole thing](examples/report.md):
39
41
 
@@ -44,13 +46,16 @@ An excerpt of the report it wrote โ€” [read the whole thing](examples/report.md)
44
46
  > **๐Ÿ”ด [HIGH] Filtering orders by Archived fails, and the page shows an empty table instead of an error**
45
47
  > Evidence: `GET /api/orders?status=archived โ†’ HTTP 500`
46
48
  >
49
+ > **๐Ÿ”ด [HIGH] A clerk can approve an order by calling the endpoint the page hides from them**
50
+ > Evidence: `POST /api/orders/1037/approve 200 as clerk; POST /api/orders/1038/reject 403 as clerk` โ€” the button was hidden, the server did not agree.
51
+ >
47
52
  > **๐ŸŸ  [MEDIUM] The "New: bulk import" badge sits on top of the All orders button** *(callout 1)*
48
53
  > Evidence: `"All orders" overlaps "New: bulk import" (81%)` โ€” measured from layout boxes, no screenshot needed.
49
54
  >
50
55
  > **๐ŸŸก [LOW] The dashboard chart image is missing** *(callout 2)*
51
56
  > Evidence: `GET /img/weekly-chart.png โ†’ HTTP 404`
52
57
  >
53
- > **Gap ledger โ€” what was NOT tested:** 4/7 visited routes never design-audited ยท single-role run, so permission boundaries are untested
58
+ > **Gap ledger โ€” what was NOT tested:** 9/12 visited routes never design-audited ยท single-role run, so permission boundaries are untested
54
59
 
55
60
  Every finding comes with a repro trace and a Playwright regression-test skeleton. To try it yourself, clone this repository, run `npm run demo:serve`, then `/scenescout --url http://127.0.0.1:4173` โ€” see [demo-app/](demo-app/). Its README lists every seeded defect and which oracle catches it.
56
61
 
@@ -58,7 +63,7 @@ Every finding comes with a repro trace and a Playwright regression-test skeleton
58
63
 
59
64
  ## โœจ Why it's different
60
65
 
61
- - ๐Ÿง  **Claude is the brain โ€” no API key.** The engine contains no LLM. Exploration runs on your Claude Code subscription; SceneScout just gives it deterministic tools.
66
+ - ๐Ÿง  **Your agent is the brain โ€” no API key.** The engine contains no LLM. Exploration runs on the agent and subscription you already have (Claude Code, Cursor, Copilot, Codex, Gemini CLI and others); SceneScout just gives it deterministic tools and the method for using them.
62
67
  - ๐Ÿ“ **Structured scene, not pixels.** The agent reads element lists *with layout geometry*, not screenshots. Overlap and off-screen bugs are computed from boxes โ€” deterministic, no vision guessing. Images that failed to load are read from the DOM too. (Screenshots exist only for pixel-native residue like a canvas or a rendering glitch.)
63
68
  - ๐Ÿ›ก๏ธ **Read-only by default, enforced on the wire.** Destructive actions are blocked at the network layer, not by asking the model nicely. Opt into writes only against disposable data.
64
69
  - โœ… **Completion is a contract, not a vibe.** The engine knows the app's routes and *refuses* to file an "extensive" report while any known route is unvisited, unexercised, or un-audited. "Explored a bit and stopped" is structurally impossible.
@@ -80,7 +85,7 @@ SceneScout needs only a URL. Give it the source code as well and it gets noticea
80
85
  | **What a finding looks like** | the symptom, **plus** the file behind it and a suggested fix | the symptom, a repro trace, and a regression-test skeleton |
81
86
  | **Typical target** | `localhost` while you build | staging, a preview deploy, a client's site |
82
87
 
83
- **Why the codebase helps.** The agent driving SceneScout is Claude Code, which can already read your repository. With the source at hand it knows the app's static routes before opening the browser, so coverage is measured against the real app instead of whatever happened to be linked. It can also check a suspicion against the code before reporting it: "there is no way to export this table" is a much stronger finding once the agent has confirmed no export handler exists. And when something breaks it can open the component or handler responsible and tell you *where* and *how* to fix it โ€” "the save button does nothing" becomes "`OrderForm` swallows the rejected promise in `onSubmit`; surface the error and re-enable the button".
88
+ **Why the codebase helps.** The agent driving SceneScout is a coding agent, which can already read your repository. With the source at hand it knows the app's static routes before opening the browser, so coverage is measured against the real app instead of whatever happened to be linked. It can also check a suspicion against the code before reporting it: "there is no way to export this table" is a much stronger finding once the agent has confirmed no export handler exists. And when something breaks it can open the component or handler responsible and tell you *where* and *how* to fix it โ€” "the save button does nothing" becomes "`OrderForm` swallows the rejected promise in `onSubmit`; surface the error and re-enable the button".
84
89
 
85
90
  **Why it still works without it.** Everything SceneScout *observes* comes from the running page โ€” elements, layout geometry, console and network errors, design-audit scores, task-ease measurements โ€” and none of that needs source code. Point it at a URL you are allowed to test and it behaves like a thorough QA tester: it explores, reproduces, and files findings with evidence.
86
91
 
@@ -104,7 +109,7 @@ SceneScout needs only a URL. Give it the source code as well and it gets noticea
104
109
  | | |
105
110
  |---|---|
106
111
  | **Node** | โ‰ฅ 20 |
107
- | **An MCP client** | [Claude Code](https://claude.ai/code) is the first-class one (it loads the skill); [others work too](#-other-mcp-clients) |
112
+ | **An MCP client** | Claude Code, Cursor, VS Code with Copilot, Codex CLI, Gemini CLI, GitHub Copilot CLI, Windsurf, or [any other](#-other-mcp-clients) |
108
113
  | **A web app to test** | SceneScout tests a *live* app: start yours locally first (e.g. `npm run dev`, `make dev-up`), or have the URL of a deployed one you're allowed to test |
109
114
 
110
115
  ### 1๏ธโƒฃ Install
@@ -112,9 +117,12 @@ SceneScout needs only a URL. Give it the source code as well and it gets noticea
112
117
  It is on npm. Nothing to clone:
113
118
 
114
119
  ```bash
115
- npx -y scenescout install # skill + Chromium (~150 MB, one-time) + registers the server with Claude Code
120
+ npx -y scenescout install # Claude Code: skill + server + Chromium (one-time download)
121
+ npx -y scenescout install --client cursor # or: vscode, codex, gemini, copilot, windsurf (comma-separated for several)
116
122
  ```
117
123
 
124
+ Either way it downloads the browser and registers the server with the client you named. Claude Code also gets the method as a skill; every other client receives the same method from the server. [What each client gets](#-other-mcp-clients).
125
+
118
126
  **Prefer a Claude Code plugin?** The skill and the server arrive together:
119
127
 
120
128
  ```
@@ -124,16 +132,17 @@ npx -y scenescout install # skill + Chromium (~150 MB, one-time) + register
124
132
 
125
133
  Then download the browser once with `npx -y scenescout install --browser-only`. The command becomes `/scenescout:scenescout`. A plugin's skill comes from this repository and its server from the latest npm release, so right after a release lands here the two can differ for a short while; `/plugin marketplace update scenescout-marketplace` brings the skill up to date.
126
134
 
127
- **Another MCP client?** Run `npx -y scenescout install --browser-only` and [add the server to its config](#-other-mcp-clients).
135
+ **A client that is not in that list?** Run `npx -y scenescout install --browser-only` and [add the server to its config by hand](#-other-mcp-clients).
128
136
 
129
137
  <details>
130
138
  <summary>What <code>install</code> actually does</summary>
131
139
 
132
140
  1. puts the `/scenescout` skill into `~/.claude/skills/` (or `$CLAUDE_CONFIG_DIR/skills/`) โ€” a `scenescout` folder it didn't create is moved aside to a `.backup-โ€ฆ` copy, never deleted,
133
- 2. downloads the Chromium build SceneScout drives (skipped if you already have it),
134
- 3. registers the MCP server with Claude Code at user scope. Run through `npx`, the launcher is `npx -y scenescout serve`, with the absolute path of `npx` where one sits beside node, so it works under nvm/fnm. From a clone or a global install it is the absolute node path plus that install's `dist/mcp-server.js`.
141
+ 2. downloads the browser SceneScout drives (skipped if you already have it). By default that is Chromium, as two builds: the full browser for headed runs and the headless shell every other run uses. [Choose something else](#-choosing-browsers) with `--browsers`,
142
+ 3. registers the MCP server with Claude Code at user scope. Run through `npx`, the launcher is `npx -y scenescout serve`, with the absolute path of `npx` where one sits beside node, so it works under nvm/fnm. From a clone or a global install it is the absolute node path plus that install's `dist/mcp-server.js`,
143
+ 4. puts the `scenescout` command on your PATH, so `scenescout status`, `scenescout watch` and `scenescout doctor` work from any terminal. Run through `npx`, that is `npm install -g` of the version you just ran; from a clone it is `npm link`, so the command always runs what you last built. If npm refuses (a system-wide node usually needs `sudo` for this), the step prints the command to run by hand and the rest of the setup still counts as done: `npx -y scenescout <command>` works without it.
135
144
 
136
- Re-run it any time: after moving the folder or switching node versions it refreshes the stored paths. It exits non-zero if any step failed, so it is safe to chain. Opt out of a step with `--no-register` or `--skip-browser`.
145
+ Re-run it any time: after moving the folder or switching node versions it refreshes the stored paths. It exits non-zero if a step the tool depends on failed, so it is safe to chain. Opt out of a step with `--no-register`, `--skip-browser` or `--no-command`.
137
146
 
138
147
  If `claude` isn't on the PATH of the shell you ran it from, it prints the registration command instead of running it:
139
148
 
@@ -146,8 +155,8 @@ claude mcp add --scope user scenescout -- npx -y scenescout serve
146
155
  ### 2๏ธโƒฃ Check it
147
156
 
148
157
  ```bash
149
- npx -y scenescout doctor # everything, for the default install
150
- npx -y scenescout doctor --engine # plugin install or another MCP client: node + build + browser only
158
+ npx -y scenescout doctor --engine # any client: node + build + browser
159
+ npx -y scenescout doctor # Claude Code: the above, plus the skill and the registration
151
160
  ```
152
161
 
153
162
  Every line should be a โœ“. Anything that isn't prints the exact command that fixes it. Then **start a fresh session** in your client so it picks up the new tools.
@@ -156,18 +165,50 @@ Every line should be a โœ“. Anything that isn't prints the exact command that fi
156
165
 
157
166
  No app handy? Clone this repository and run `npm run demo:serve`: the [demo app](demo-app/) starts on `http://127.0.0.1:4173`.
158
167
 
159
- From Claude Code, inside the project you want to test (or, for a [remote URL](#-two-ways-to-use-it), any folder):
168
+ Open your agent inside the project you want to test (or, for a [remote URL](#-two-ways-to-use-it), any folder) and ask:
169
+
170
+ ```
171
+ Use SceneScout to test http://localhost:3000 at medium level
172
+ ```
173
+
174
+ In Claude Code the skill gives you a command with flags for the same thing:
160
175
 
161
176
  ```
162
177
  /scenescout --level medium --url http://localhost:3000 --role qa
163
178
  ```
164
179
 
165
- The skill scans the project (if there is one), attaches read-only, explores, and writes findings to `.scenescout/report.md`. That's it.
180
+ The agent scans the project (if there is one), attaches read-only, explores, and writes findings to `.scenescout/report.md`. That's it.
166
181
 
167
182
  **Common flags** โ€” `--level minimal|medium|extensive` ยท `--url <app>` ยท `--role <name\|path>` (a Playwright storage-state to explore as: a name found by the scan, or a path to the JSON file) ยท `--observe` / `--safe-write` / `--allow-destructive`.
168
183
 
169
184
  ---
170
185
 
186
+ ## ๐Ÿ“บ Watching a run live
187
+
188
+ When a session attaches, the engine starts a small live view and hands the agent its address on a `Live view:` line, which the agent passes on to you. From a terminal, `scenescout watch` opens the same page. There is one card per session:
189
+
190
+ <p align="center"><img src="examples/screenshots/live-view.png" alt="The live view during a run of six parallel agents against the demo app: one card per session, each with its role, the tool it is running and for how long, the page it is on, a thumbnail, and a feed of the actions it just took" width="880" /></p>
191
+
192
+ - **What it is doing:** the tool it is running and for how long, the page it is on, and a thumbnail of that page. This works for headless runs too, which have no window to look at.
193
+ - **What it just did:** a rolling feed of its actions, each with its target and how it turned out, with failures in red. It is the same trail a finding's repro trace uses. The engine never sees the agent's reasoning, so this is what the session *did*, not what it thought.
194
+ - **Stuck, not slow:** a call still running past its own tool's watchdog budget turns the card red, so a wedged session is visible without asking. A crawl legitimately runs for minutes; it is judged against the crawl's budget, not a click's.
195
+ - **Live stream:** switch it on for one card, or for all of them. Click a thumbnail for a close-up.
196
+ - **The report, as it stands:** the Report button in the top bar shows the same document `scout_report` writes at the end, rendered from the run's current state, so findings can be read while the agents are still working.
197
+ - **What it is for:** the close-up puts the feed beside the session's brief: the task the agent gave it when it attached (`scout_attach {task}`), and the goal of the journey it is on right now (`scout_journey`). Actions of one journey share a tint in the feed; point at a group and the brief shows the goal those actions served.
198
+
199
+ <p align="center"><img src="examples/screenshots/live-view-closeup.png" alt="A close-up of one session: its page streaming live, the feed of its recent actions grouped by journey, and beside it the task and current objective" width="880" /></p>
200
+
201
+ <p align="center"><img src="examples/screenshots/live-view-report.png" alt="The report opened from the live view's top bar while the run is still going: summary table, gap ledger and the findings filed so far" width="880" /></p>
202
+
203
+ The view is served on `127.0.0.1` only, behind a token that changes every time the engine starts. It answers `GET` and nothing else, so a viewer can watch a run but not act in it, and no frame is ever written to disk ([ADR 7](docs/adr/0007-the-live-view-is-local-read-only-and-leaves-nothing-behind.md)). A stream runs only while someone is watching it. `SCENESCOUT_LIVE=off` keeps the port closed.
204
+
205
+ **Try it with parallel agents.** The demo app has three roles and several separate areas, so a run can be split between agents. Start it with `npm run demo:serve`, then ask your agent to explore it with several agents in parallel, one role and one area each. The pictures above come from a run of six. Two things keep a parallel run efficient:
206
+
207
+ - **Each agent opens its own session when it starts and closes it when it is done.** An agent waiting for its turn then holds no browser. Opening every session up front leaves browsers idling while the machine runs out of memory for the agents that are working.
208
+ - **Run about as many agents at once as your machine has cores, less two.** Each one drives a real browser.
209
+
210
+ ---
211
+
171
212
  ## ๐Ÿ”„ How a run works
172
213
 
173
214
  One curiosity loop, repeated โ€” breadth first, then judgment where it matters:
@@ -192,11 +233,11 @@ Snapshots are cheap: re-snapshotting a route returns only *what changed*, with s
192
233
 
193
234
  ## ๐Ÿงฐ The toolbox
194
235
 
195
- 24 deterministic tools. The agent picks; you rarely call these by hand.
236
+ 25 deterministic tools. The agent picks; you rarely call these by hand.
196
237
 
197
238
  | Phase | Tools | What they do |
198
239
  |---|---|---|
199
- | **Set up** | `scout_scan` `scout_attach` `scout_session` | Discover routes; launch a browser in a write-mode; keep several authenticated roles alive at once |
240
+ | **Set up** | `scout_playbook` `scout_scan` `scout_attach` `scout_session` | Hand the testing method to an agent that has no skill loaded; discover routes; launch a browser in a write-mode; keep several authenticated roles alive at once |
200
241
  | **Explore** | `scout_crawl` `scout_coverage` | Sweep every route in one call; ask what's still untested |
201
242
  | **Look** | `scout_snapshot` `scout_hover` `scout_screenshot` | Read the structured scene (diffed); reveal tooltips/hover cards; capture pixels only when needed |
202
243
  | **Act** | `scout_click` `scout_type` `scout_select` `scout_upload` `scout_press` `scout_scroll` `scout_navigate` `scout_back` `scout_run_plan` | Drive the UI like a user; `scout_run_plan` batches a whole mechanical sequence into one call |
@@ -263,7 +304,7 @@ Run `npx -y scenescout doctor` first โ€” it checks every setup item below (every
263
304
  | The `scout_*` tools don't appear | The MCP server isn't registered, or points at an old path. `npx -y scenescout install` re-registers it; `claude mcp list` should show `scenescout` as connected. |
264
305
  | *"Executable not found in $PATH"* | The server was registered with a bare `node`. `npx -y scenescout install` registers an absolute path. |
265
306
  | Installed as a plugin, and the tools fail with *"Executable not found in $PATH: npx"* | A plugin starts the server with a bare `npx`, which Claude Code can only find if it was launched from an environment that has Node on its `PATH`. Under nvm or fnm that means starting Claude Code from a terminal, not from a dock or launcher. Or use `npx -y scenescout install` instead, which registers the absolute path of `npx`. |
266
- | *"Executable doesn't exist โ€ฆ chromium"* | The browser download was skipped or failed. `npx playwright install chromium` (on Linux add `--with-deps`). |
307
+ | *"โ€ฆ build has not been downloaded yet"* on attach | The browser download was skipped or failed, or the run asked for a browser you did not install. Run the command the message names, for example `npx -y scenescout install --browser-only --browsers firefox`. On Linux, system libraries may be missing too: `npx playwright install --with-deps chromium`. |
267
308
  | Tools broke after moving the folder or changing node version | The registration stores absolute paths. `npx -y scenescout install` refreshes them. |
268
309
  | Attach fails or every route lands on the login page | Your app isn't running at `--url`, or the `--role` storage state has expired โ€” regenerate it the way your project's Playwright setup does. |
269
310
 
@@ -275,17 +316,69 @@ Run `npx -y scenescout doctor` first โ€” it checks every setup item below (every
275
316
  ### ๐Ÿงน Uninstall
276
317
 
277
318
  ```bash
319
+ # Claude Code
278
320
  claude mcp remove --scope user scenescout
279
321
  rm -rf ~/.claude/skills/scenescout
322
+ # Codex / Gemini / Copilot CLI
323
+ codex mcp remove scenescout # likewise: gemini mcp remove โ€ฆ, copilot mcp remove โ€ฆ
280
324
  ```
281
325
 
326
+ For Cursor, Windsurf and VS Code, delete the `scenescout` entry from the client's MCP server list.
327
+
282
328
  Nothing else is installed: `npx` runs the package from npm's cache. Per-project memory lives in each tested project's `.scenescout/` folder; delete it there if you want it gone.
283
329
 
284
330
  ---
285
331
 
332
+ ## ๐ŸŒ Choosing browsers
333
+
334
+ `install` downloads Chromium and nothing else unless you ask. `--browsers` takes one name, a comma-separated list, or `all`:
335
+
336
+ | `--browsers` | What is downloaded | About, on disk |
337
+ |---|---|---|
338
+ | `chromium` *(default)* | the full browser and the headless shell | 550 MB |
339
+ | `chromium-headless-shell` | the headless shell only: every run works except `headed` | 200 MB |
340
+ | `firefox` | Firefox | 270 MB |
341
+ | `webkit` | WebKit, the engine behind Safari | 290 MB |
342
+ | `all` | Chromium, Firefox and WebKit | 1.1 GB |
343
+
344
+ ```bash
345
+ npx -y scenescout install --browsers chromium-headless-shell # the smallest working setup
346
+ npx -y scenescout install --browser-only --browsers firefox,webkit # add two more later
347
+ ```
348
+
349
+ Sizes vary by platform. The builds go to Playwright's shared cache, so a build another tool already fetched is not downloaded again.
350
+
351
+ To drive another browser, pass `browser` when attaching (`scout_attach { browser: "firefox" }`), or set `SCENESCOUT_BROWSER=webkit` in the server's environment to change the default. `scenescout doctor` checks the browser named by that variable in the shell it runs from, so check another one with `SCENESCOUT_BROWSER=webkit scenescout doctor`. Two things differ outside Chromium:
352
+
353
+ - **Service workers are not allowed to register** in Firefox and WebKit. The write policy works by intercepting requests, and only Chromium lets a request issued by a service worker be intercepted. An app that depends on its worker may behave differently there.
354
+ - **A Firefox or WebKit left behind by a crash is not cleaned up** on the next start the way a leftover Chromium is.
355
+
356
+ In every browser, pages are not given shared workers unless the mode is `destructive`: a request a shared worker sends cannot be intercepted anywhere, so the app is made to do that work on the page, where the policy sees it.
357
+
286
358
  ## ๐Ÿ”Œ Other MCP clients
287
359
 
288
- The engine is a plain MCP server over stdio, so any client can drive it. The Claude Code route is the one this project tests. The entries below follow each client's own documentation for a local stdio server and have not been run by the maintainer; if one is out of date, a correction is welcome (say which client version you checked). The server entry is always the same command โ€” `npx -y scenescout serve` โ€” only the config file differs. Download the browser once with `npx -y scenescout install --browser-only`.
360
+ The engine is a plain MCP server over stdio, so any client can drive it, and the testing method reaches the agent through the server itself (see the end of this section). `install` can register it for you:
361
+
362
+ ```bash
363
+ npx -y scenescout install --client cursor # one client
364
+ npx -y scenescout install --client vscode,codex # several; add claude-code to keep that one too
365
+ ```
366
+
367
+ | `--client` | How it is registered |
368
+ |---|---|
369
+ | `claude-code` *(default)* | `claude mcp add`, plus the skill |
370
+ | `cursor` | adds an entry to `~/.cursor/mcp.json`, keeping the others |
371
+ | `vscode` | VS Code's own `code --add-mcp`. A `code` command that belongs to another editor is not used |
372
+ | `codex` | `codex mcp add` |
373
+ | `gemini` | `gemini mcp add --scope user` |
374
+ | `copilot` | `copilot mcp add` (GitHub Copilot CLI) |
375
+ | `windsurf` | adds an entry to `~/.codeium/windsurf/mcp_config.json`, keeping the others |
376
+
377
+ A config file that is not valid JSON is left untouched, and the entry to add by hand is printed instead; a config that is a link into a dotfiles repository is written through the link. When a client that is registered through its own command is not installed, `install` says so and prints the command to run later. Cursor and Windsurf are files, so their entry is written whether or not the editor is installed yet. On Windows, a client installed through npm is a `.cmd` shim that `install` cannot start; it prints the command for you to run instead. Then restart the client and ask its agent: *"Use SceneScout to test http://localhost:3000"*.
378
+
379
+ What has been checked: registering through each command above was run against Codex CLI, Gemini CLI, GitHub Copilot CLI and VS Code, and Cursor's command line agent read the entry `install` wrote, connected and listed the tools. The Windsurf path follows its documentation. A full test session has been run in Claude Code, with and without the skill. If a client behaves differently for you, a correction is welcome (say which client version you checked).
380
+
381
+ To register by hand instead, the server entry is always the same command, `npx -y scenescout serve`:
289
382
 
290
383
  <details>
291
384
  <summary><strong>Cursor</strong> โ€” <code>~/.cursor/mcp.json</code> (or <code>.cursor/mcp.json</code> in a project)</summary>
@@ -383,13 +476,19 @@ Most clients accept the same `mcpServers` JSON shape shown for Cursor.
383
476
 
384
477
  </details>
385
478
 
386
- **The skill is what makes it good.** The tools are only hands and eyes; [`skills/scenescout/SKILL.md`](skills/scenescout/SKILL.md) is the method โ€” what to look at first, when to stop, what counts as a finding. Claude Code loads it as a skill. In another client, give the agent that file as its instructions (a rule, a custom mode, or pasted into the first message).
479
+ **The method travels with the server.** The tools are only hands and eyes; [`skills/scenescout/SKILL.md`](skills/scenescout/SKILL.md) is the method: what to look at first, when to stop, what counts as a finding. Claude Code loads it as a skill. Every other client gets the same text from the server, with nothing to copy:
480
+
481
+ - the server's instructions tell the agent to call `scout_playbook` before its first attach, and that tool returns the method,
482
+ - clients that list server prompts as commands also get an `explore` prompt, which loads the method and takes an optional URL, level and focus.
483
+
484
+ So in any client, a first message like *"Use SceneScout to test http://localhost:3000"* is enough. If an agent starts clicking without having called `scout_playbook`, tell it to call that first; how closely a model follows server instructions varies by client.
387
485
 
388
486
  The CLI is also useful on its own:
389
487
 
390
488
  ```bash
391
489
  npx -y scenescout scan <path> # project discovery: framework, routes, saved logins
392
- npx -y scenescout status <path> # what a running engine is doing right now
490
+ npx -y scenescout status <path> # what every session of a running engine is doing right now
491
+ npx -y scenescout watch <path> # the same, live in your browser, with each session's page
393
492
  ```
394
493
 
395
494
  ---
@@ -398,7 +497,7 @@ npx -y scenescout status <path> # what a running engine is doing right now
398
497
 
399
498
  ```
400
499
  src/
401
- mcp-server.ts the 24 tools + per-session dispatch
500
+ mcp-server.ts the 25 tools + per-session dispatch
402
501
  scan.ts project discovery (framework, routes, auth)
403
502
  cli.ts scan ยท serve ยท install ยท doctor ยท status
404
503
  installer.ts setup logic (skill link, MCP registration, diagnostics)
@@ -417,7 +516,7 @@ src/
417
516
  โ€ฆ collector ยท dispatch ยท fixtures ยท authloss ยท reaper
418
517
  scripts/ the 12 test suites (smoke/ holds the real-browser ones)
419
518
  test-app/ fixtures for the real-browser smoke tests
420
- skills/scenescout/ the Claude Code skill (SKILL.md)
519
+ skills/scenescout/ the testing method (SKILL.md): a skill in Claude Code, served by the server everywhere else
421
520
  docs/adr/ why it's built this way
422
521
  ```
423
522
 
@@ -451,7 +550,7 @@ npm test # build + 12 suites: scan, oracle, policy, fixture, dispatch,
451
550
  npm run demo # regenerate examples/ from the demo app
452
551
  ```
453
552
 
454
- Contributing? Start with [VISION.md](VISION.md) (what is in scope) and [CONTRIBUTING.md](CONTRIBUTING.md) (how changes land), then see [CLAUDE.md](CLAUDE.md) for the house rules โ€” chiefly: bug fixes need a regression test at the cheapest layer that can fail, keep the repo project-agnostic (ADR 6), and `npm test` must pass.
553
+ Contributing? Start with [VISION.md](VISION.md) (what is in scope) and [CONTRIBUTING.md](CONTRIBUTING.md) (how changes land), then see [AGENTS.md](AGENTS.md) for the house rules โ€” chiefly: bug fixes need a regression test at the cheapest layer that can fail, keep the repo project-agnostic (ADR 6), and `npm test` must pass.
455
554
 
456
555
  ## ๐Ÿ” Security
457
556
 
@@ -477,6 +576,6 @@ Found a way past the write policy, or another security problem? Please report it
477
576
  - **A trustworthy gap ledger.** Entries must be actionable (a search box or wizard sub-step isn't "form filled but never submitted"); API/download URLs never enter the route contract.
478
577
  - **Honest reporting.** Shared chrome counted once, stale scores marked, role matrix compares only roles that actually attempted a route.
479
578
  - **Cross-run written knowledge.** `scout_note` curates `.scenescout/ASSUMPTIONS.md` โ€” app model, personas, constraints, risks โ€” in prose.
480
- - **Daemon-grade robustness.** Per-tool watchdogs, orphaned-browser reaping, bounded teardown, live status via `scenescout status <project>`.
579
+ - **Daemon-grade robustness.** Per-tool watchdogs, orphaned-browser reaping, bounded teardown, live status via `scenescout status <project>`, and a live view of every session's page: the agent gives you its address when it attaches, or run `scenescout watch <project>` (loopback only, read-only, nothing written to disk: [ADR 7](docs/adr/0007-the-live-view-is-local-read-only-and-leaves-nothing-behind.md)).
481
580
 
482
581
  </details>
@@ -0,0 +1,196 @@
1
+ /**
2
+ * Which browsers SceneScout can drive, which builds `scenescout install`
3
+ * downloads for them, and which build a given launch needs.
4
+ *
5
+ * Kept free of Playwright so the rules can be table-tested: the CLI and the
6
+ * engine pass in the paths Playwright reports and get decisions back.
7
+ */
8
+ import fs from "node:fs";
9
+ import path from "node:path";
10
+ /** A browser the engine can launch. */
11
+ export const BROWSER_ENGINES = ["chromium", "firefox", "webkit"];
12
+ /**
13
+ * A build Playwright downloads. Chromium comes as two: the full browser, which
14
+ * a headed run opens, and the headless shell, which every headless run uses.
15
+ * Installing "chromium" brings both, which is what install has always done.
16
+ */
17
+ export const INSTALL_TARGETS = ["chromium", "chromium-headless-shell", "firefox", "webkit"];
18
+ export const DEFAULT_ENGINE = "chromium";
19
+ /** Environment variable naming the browser an attach uses when it does not name one. */
20
+ export const DEFAULT_ENGINE_ENV = "SCENESCOUT_BROWSER";
21
+ /** Approximate size on disk, so the install output can say what it is about to fetch. */
22
+ export const APPROX_DISK_MB = {
23
+ chromium: 550,
24
+ "chromium-headless-shell": 200,
25
+ firefox: 270,
26
+ webkit: 290,
27
+ };
28
+ export function isBrowserEngine(value) {
29
+ return BROWSER_ENGINES.includes(value);
30
+ }
31
+ /** The engine an attach uses when the caller names none: the environment's choice, else Chromium. */
32
+ export function defaultEngine(env) {
33
+ const raw = env[DEFAULT_ENGINE_ENV]?.trim().toLowerCase();
34
+ if (!raw)
35
+ return DEFAULT_ENGINE;
36
+ if (!isBrowserEngine(raw)) {
37
+ throw new Error(`${DEFAULT_ENGINE_ENV}="${env[DEFAULT_ENGINE_ENV]}" is not a browser SceneScout can drive. Use one of: ${BROWSER_ENGINES.join(", ")}.`);
38
+ }
39
+ return raw;
40
+ }
41
+ /**
42
+ * Read the value of `--browsers`. Absent means what install has always done.
43
+ * `all` is every engine; a comma-separated list picks several. "chromium"
44
+ * already includes the headless shell, so naming both is the same as naming it.
45
+ */
46
+ export function parseBrowserSelection(value) {
47
+ if (value === undefined)
48
+ return { targets: ["chromium"] };
49
+ const names = value
50
+ .split(",")
51
+ .map((s) => s.trim().toLowerCase())
52
+ .filter(Boolean);
53
+ const choices = `${INSTALL_TARGETS.join(", ")}, all`;
54
+ if (names.length === 0)
55
+ return { error: `--browsers needs a value. Choose from: ${choices}.` };
56
+ const picked = new Set();
57
+ for (const name of names) {
58
+ if (name === "all") {
59
+ for (const t of ["chromium", "firefox", "webkit"])
60
+ picked.add(t);
61
+ }
62
+ else if (INSTALL_TARGETS.includes(name)) {
63
+ picked.add(name);
64
+ }
65
+ else {
66
+ return { error: `"${name}" is not a browser SceneScout can install. Choose from: ${choices}.` };
67
+ }
68
+ }
69
+ if (picked.has("chromium"))
70
+ picked.delete("chromium-headless-shell");
71
+ return { targets: INSTALL_TARGETS.filter((t) => picked.has(t)) };
72
+ }
73
+ /** The arguments for Playwright's own installer. Target names are Playwright's names. */
74
+ export function playwrightInstallArgs(targets) {
75
+ return ["install", ...targets];
76
+ }
77
+ /** The build a launch needs on disk. */
78
+ export function launchTarget(engine, headed) {
79
+ if (engine !== "chromium")
80
+ return engine;
81
+ return headed ? "chromium" : "chromium-headless-shell";
82
+ }
83
+ /** The engine each build belongs to. */
84
+ export function engineOf(target) {
85
+ return target === "chromium-headless-shell" ? "chromium" : target;
86
+ }
87
+ /**
88
+ * Where the headless shell lives, worked out from the full browser's path.
89
+ * Playwright exposes no path for the shell, but it keeps the two side by side
90
+ * under one revision: `chromium-1234/โ€ฆ` next to `chromium_headless_shell-1234/`.
91
+ * Null when the path does not have that shape.
92
+ */
93
+ export function headlessShellDir(chromiumExecutable) {
94
+ if (!chromiumExecutable)
95
+ return null;
96
+ const parts = chromiumExecutable.split(/[\\/]/);
97
+ // The LAST such segment: a cache kept under a directory that happens to be
98
+ // named the same way must not be mistaken for the build directory.
99
+ let at = -1;
100
+ for (let i = parts.length - 1; i >= 0 && at < 0; i--)
101
+ if (/^chromium-\d+$/.test(parts[i]))
102
+ at = i;
103
+ if (at < 0)
104
+ return null;
105
+ const sep = chromiumExecutable.includes("\\") && !chromiumExecutable.includes("/") ? "\\" : "/";
106
+ return [...parts.slice(0, at), parts[at].replace(/^chromium-/, "chromium_headless_shell-")].join(sep);
107
+ }
108
+ /**
109
+ * Which builds are on disk. `executables` is what Playwright reports for each
110
+ * engine; a path Playwright names is not proof the file is there. The shell is
111
+ * counted only once Playwright has marked its download complete.
112
+ */
113
+ export function browserPresence(executables, exists = fs.existsSync) {
114
+ const at = (p) => ({ installed: !!p && exists(p), path: p });
115
+ const shellDir = headlessShellDir(executables.chromium);
116
+ const shellMarker = shellDir ? path.join(shellDir, "INSTALLATION_COMPLETE") : null;
117
+ const chromium = at(executables.chromium);
118
+ return {
119
+ chromium: { installed: chromium.installed && !!shellMarker && exists(shellMarker), path: chromium.path },
120
+ "chromium-headless-shell": { installed: !!shellMarker && exists(shellMarker), path: shellDir },
121
+ firefox: at(executables.firefox),
122
+ webkit: at(executables.webkit),
123
+ };
124
+ }
125
+ /** The command that downloads one build, for the way this copy of SceneScout was installed. */
126
+ export function installCommandFor(target, fromCheckout) {
127
+ const flags = `--browser-only --browsers ${target}`;
128
+ return fromCheckout ? `node dist/cli.js install ${flags}` : `npx -y scenescout install ${flags}`;
129
+ }
130
+ /**
131
+ * Whether pages may register service workers in this browser.
132
+ *
133
+ * The write policy works by intercepting requests. Only Chromium lets the
134
+ * driver intercept a request a service worker issues; in Firefox and WebKit
135
+ * such a request goes straight to the network, so an app that syncs its writes
136
+ * from a worker would send a DELETE through read-only mode with nothing
137
+ * logged. Blocking registration there makes the page send those requests
138
+ * itself, where the policy sees them.
139
+ */
140
+ export function serviceWorkerPolicy(engine) {
141
+ return engine === "chromium" ? "allow" : "block";
142
+ }
143
+ /**
144
+ * How the live view gets frames of a page.
145
+ *
146
+ * Chromium can push a frame on every repaint through the DevTools protocol,
147
+ * which costs nothing while the page sits still. Firefox and WebKit have no
148
+ * such channel in the driver, so a stream there is a screenshot taken on a
149
+ * timer for as long as somebody is watching.
150
+ */
151
+ export function screencastSupport(engine) {
152
+ return engine === "chromium" ? "cdp" : "poll";
153
+ }
154
+ /**
155
+ * The key that moves keyboard focus to the next control, links and buttons
156
+ * included. WebKit on macOS follows Safari: plain Tab stops only at text
157
+ * fields, and Option+Tab stops everywhere. A focus audit that pressed Tab
158
+ * there would walk past every button and report nothing.
159
+ */
160
+ export function focusAdvanceKey(engine, platform) {
161
+ return engine === "webkit" && platform === "darwin" ? "Alt+Tab" : "Tab";
162
+ }
163
+ /**
164
+ * Run in every page before its own scripts: takes shared workers away.
165
+ *
166
+ * A request issued by a shared worker cannot be intercepted in any browser, so
167
+ * a write sent from one passes the policy unseen and unlogged. Removing the
168
+ * constructor makes feature detection fail, and an app then does that work on
169
+ * the page, where the policy sees it. Not applied in `destructive` mode, where
170
+ * the policy blocks nothing and the person has opted in to everything.
171
+ */
172
+ export const REMOVE_SHARED_WORKER_SCRIPT = `(() => {
173
+ try { delete globalThis.SharedWorker; } catch {}
174
+ if ("SharedWorker" in globalThis) {
175
+ try { Object.defineProperty(globalThis, "SharedWorker", { value: undefined, configurable: true, writable: true }); } catch {}
176
+ }
177
+ })()`;
178
+ /** Whether pages may use shared workers in this write mode. */
179
+ export function sharedWorkersAllowed(mode) {
180
+ return mode === "destructive";
181
+ }
182
+ /**
183
+ * What install says when the browsers it was asked for do not include the one
184
+ * a default attach launches, and that one is not on disk either. Without it,
185
+ * `install --browsers firefox` on a fresh machine ends in "ready" and the first
186
+ * attach fails. Null when there is nothing to say.
187
+ */
188
+ export function defaultAttachNote(opts) {
189
+ if (opts.defaultInstalled || opts.selected.length === 0)
190
+ return null;
191
+ if (opts.selected.some((t) => engineOf(t) === opts.defaultEngine))
192
+ return null;
193
+ const engine = engineOf(opts.selected[0]);
194
+ return (`an attach drives ${opts.defaultEngine} unless told otherwise, and that build is not installed. ` +
195
+ `Pass browser: "${engine}" when attaching, or set ${DEFAULT_ENGINE_ENV}=${engine} in the MCP server's environment.`);
196
+ }