reactor-devtools
Driving a running Reactor app via `mur devtools` — screenshot, inspect visual tree, click/type/scroll, read hook state. Use when diagnosing visible bugs (layout, contrast) or verifying a UI change landed.
Install
mkdir -p .claude/skills/reactor-devtools && curl -L -o skill.zip "https://agentskills.codes/api/skills/download/16581" && unzip -o skill.zip -d .claude/skills/reactor-devtools && rm skill.zipInstalls to .claude/skills/reactor-devtools
Activation
This is the description your AI agent reads to decide when to run this skill — the better it matches your request, the more reliably it fires.
Driving a running Reactor app via `mur devtools` — screenshot, inspect visual tree, click/type/scroll, read hook state. Use when diagnosing visible bugs (layout, contrast) or verifying a UI change landed.About this skill
Reactor Devtools — CLI-driven UI automation
The Reactor devtools let you look at a running debug-build app the way a
user does (screenshots, rendered text, layout bounds) and drive it the way
a user does (click, type, toggle, scroll). Always prefer the mur devtools
CLI — every action composes with shell pipes and jq, each invocation is
a complete audit record, and no MCP client setup is needed.
Under the hood the CLI talks JSON-RPC to a loopback HTTP endpoint the app
exposes. The MCP endpoint is still available at http://127.0.0.1:PORT/mcp
as a parity escape hatch, but reach for it only when the CLI can't express
what you need (structured args the CLI flattens, or another MCP client
that's already wired up).
Loopback-only — DEBUG builds only, never ship it. Auth is a per-launch
bearer token written into the lockfile; the CLI applies it transparently
during lockfile discovery, so you don't see it. Don't pass --endpoint
for tool calls — without the token the server returns 401. --endpoint
is only useful when you're crafting the Authorization: Bearer … header
yourself (curl, custom client).
Getting mur on your PATH
mur resolves from PATH in both modes:
- Skill kit (deployed):
install-skill-kit.ps1(shipped with the kit zip) prepends<install>/bin/<arch>to your user PATH. - Cloned repo (selfhost):
dotnet buildofsrc/Reactor.Cliautomatically mirrors the output to<repo>/bin/<arch>/(architecture is determined fromRuntimeIdentifier). Add that directory to your PATH once.
If mur --version doesn't resolve, neither path was set up; commands below assume it does.
Attaching to a running app
The app author enables devtools capability in the app project, usually only for Debug builds:
<ItemGroup Condition="'$(Configuration)' == 'Debug'">
<RuntimeHostConfigurationOption Include="Reactor.DevtoolsSupport"
Value="true" Trim="true" />
</ItemGroup>
ReactorApp.Run takes no devtools: or preview: argument.
Prefer attaching to an app that's already running. Any devtools-enabled
app writes a lockfile to %TEMP%/reactor-devtools/ on first render, and
mur devtools <verb> discovers it automatically — no port, no config,
no parsing stdout.
mur devtools session list --pretty # confirm a session is up
Exit 0 with a row means you're good to go; every other verb will find it. Exit 4 means no live session — you need to launch one yourself (next section).
Launching an app yourself
Two launch modes, from simplest to most featured:
Plain dotnet run (the default)
dotnet run --project path/to/App.csproj -- --devtools run \
> /tmp/app-stdout.log 2>&1 &
This is what you want in almost every AI session: spawn the app, drive it
with mur devtools <verb>, call mur devtools shutdown when you're done.
No supervisor machinery, no pinned port — the CLI finds the session via
the lockfile regardless.
mur devtools <project> — the supervisor
Reach for this only when you need a stable MCP endpoint across
reload cycles:
mur devtools path/to/App.csproj --mcp-port 54931
The supervisor pins the port (reload-proof) and catches the child's exit
code 42 to rebuild and relaunch. Useful if you've wired an external MCP
client to http://127.0.0.1:54931/mcp and want it to survive code
edits. Overkill for one-shot automation.
Discovering the tool surface
mur devtools call tools/list --pretty # names + input schemas
mur devtools call tools/list | jq '.tools[].name'
mur devtools --help lists every named verb with one-line descriptions.
CLI verb catalog
Each verb attaches to the running session via lockfile discovery; no flags
needed when only one session is active. Pass --pretty to any verb for
indented JSON.
| Verb | What it does |
|---|---|
version | Build tag + pid + port — confirm the app is the one you expect. |
components | Class names of every Component subclass; marks which is mounted. |
switch <name> | Swap the root component by class name. Invalidates all node ids. |
reload [--component N] | Rebuild + relaunch via the supervisor sentinel. Old ids dead. |
shutdown | Close the app cleanly (supervisor exits 0). Releases the build output file lock. |
windows | Active window ids, titles, bounds, currently-mounted component. |
windows.list | Spec 036 §10. Per-window id, key, title, DIP size, DPI, state, isMain. Use this when you care about DPI / DIP size or the key column for UseOpenWindow-keyed lookups. |
windows.activate <id> | Activate (focus) a window. Returns { ok, id }. |
windows.close <id> | Close a window. Honors UseClosingGuard / Closing subscribers — returns { ok: false, cancelled: true, id } when a guard vetoed the close. |
windows.open <Component> [--title T] [--width W] [--height H] [--key K] | Open a new top-level window mounting an allowlisted Component. The component name is gated by the same allowlist as switchComponent; rejected names return unknown-component with the available list. |
tree [--selector S] [--window W] [--view summary|full] | Dump the visual tree as JSON. full adds layout/automation/visual fields. |
screenshot [--selector S] [--out path] | PNG of the window (or selector-cropped region). --out path.png writes to file; --out - streams bytes to stdout. |
click <selector> | UIA click. Prefers Invoke → Toggle → SelectionItem; returns via. |
invoke / toggle / select | Direct UIA pattern access. select <container> <item> auto-expands ComboBoxes. |
type <selector> <text> [--clear] | Set text on a value-bearing control. |
focus <selector> | Programmatic focus on a Control. |
scroll <selector> [--by H%,V%] [--to <item-selector>] | Scroll by percentage deltas 0–100 (not pixels), or scroll an item into view. |
expand <selector> / collapse <selector> | ExpandCollapse pattern (ComboBox popup, TreeViewItem, Expander). |
wait <selector> [--text X | --text-matches RE | --visible | --count N] [--timeout MS] | Poll a predicate until satisfied or timeout. |
state [--selector S] | Dump every hook value (useState/useReducer/etc.) across mounted components. |
logs [--tail N] [--since SEQ] [--filter RE] [--source stdout|stderr|debug|trace] [--follow] | Drain captured Debug.WriteLine / Trace.WriteLine / Console.Out / Console.Error. Ring buffer installed at --devtools run startup — late-attaching agents still see startup output. --follow long-polls until Ctrl+C. |
fire <Component>.<event> [--args JSON] | Call a NAMED METHOD on a live component by reflection. Inline lambdas aren't reachable. |
properties <selector> [--name PropName] | Read dependency properties on an element. Omit --name to enumerate all DPs with values, types, and local-vs-default status. Supports attached properties via Grid.Row syntax. |
set-property <selector> <name> <value> | Set a dependency property. Value is parsed from string (Thickness, CornerRadius, Brush hex #RRGGBB, enums, bool, double, int). |
resources [--selector S] [--scope element|window|app] [--filter RE] | Browse ResourceDictionary entries. Walks element → ancestor elements → window → app (including MergedDictionaries and ThemeDictionaries). |
set-resource <key> <value> [--scope app|window|element] [--selector S] | Set or add a XAML resource. Reports whether the write replaced an existing entry or created a new shadowing entry. |
styles <selector> | Inspect the explicitly-assigned Style: TargetType, Setters (property + value), BasedOn chain. Returns hasStyle: false when only a default/theme style is active. |
ancestors <selector> | Walk the visual tree upward — returns type, name, and automationId for each ancestor up to the root. |
call <tool|method> [--args JSON] | Generic JSON-RPC passthrough — parity escape hatch. |
Reference-graph overlay
The references tool (Spec 057) returns the app's reactive reference
graph — descriptor.Reference/.ReferenceList, the binding.Reference
bridge, and modifier edges like .LabeledBy/.DescribedBy/.FlowsTo/
.XYFocus* — as {from, to, label, slot, kind, resolved} edges keyed to
the same node ids tree uses, plus diagnostics for cycles and
unresolved (perpetually-null) references. Reach it through the
generic passthrough:
mur devtools call references --pretty
mur devtools call references --args '{"selector":"#login-form"}' # scope to a subtree
Use it to confirm an accessibility relationship (LabeledBy/DescribedBy)
or an XYFocus* ring actually resolved to the control you expect, or to
spot a reference that never resolved. Cycles are a supported topology and
are reported informationally, not as errors. The same overlay also backs
the References toggle in the VS Code live-preview panel.
Session management
mur devtools session list # JSONL, one live session per line
mur devtools session list --pretty # human table
mur devtools session clean # GC stale lockfiles
mur devtools session clean --dry-run # show what would be removed
Shared flags (before any verb)
--endpoint <url>— skip lockfile discovery and talk to this endpoint. Drops the bearer token — the CLI has no--tokenflag, so verbs hit the endpoint unauthenticated and get 401. Only useful withcurlplus a hand-builtAuthorizationheader, not formur devtools <verb>calls.--pretty— indent JSON output.--auto— loopback port scan (slow; use only when lockfile discovery fails).
Exit codes
| Code | Meaning |
|---|---|
| 0 | Success. |
| 1 | Usage error (unknown flag, missing argument). |
| 2 | Transport error (endpoint unreachable, timeout). |
| 3 | Another devtools session is already active for this project. |
| 4 | No live devtools session found. |
| 5 | Tool returne |
Content truncated.