Chrome DevTools panel with terminal and browser context injection for Claude Code
Project description
devpanel
Chrome DevTools panel with an embedded terminal and browser context injection for Claude Code.
What it does
Opens a terminal inside Chrome DevTools. Claude Code runs in that terminal connected to a local MCP server. When you select an element, capture network, or take a screenshot in DevTools, it appears immediately in Claude's conversation as a channel notification — no need to type "look at this," no need to drain on prompt.
Select elements — click Select (or Ctrl+K → Select element), Chrome's native crosshair picker activates. A modal describes the gesture; outside-click cancels. Pick an element, type an annotation in the prompt, repeat. Each selection pushes a channel notification.
Capture network — snapshot recent requests from devtools.network.getHAR(). Full HAR spills to a JSON file the agent can grep. network() MCP tool also supports filters (url, method, status, since).
Screenshot — viewport via the toolbar / screenshot() MCP tool, or element-clipped via Ctrl+K → "Screenshot element…" / screenshot(selector="…"). Element clips use captureBeyondViewport: true so off-screen targets work without scrolling. PNG spills to disk.
Eval JS — js(code) MCP tool runs in the inspected page (sync via inspectedWindow.eval, async via Runtime.evaluate({awaitPromise: true}) when await is present). defer=true returns a task_id immediately and pushes the result via channel. every=N runs on a timer until cancelled. Inside eval, sendChannel(msg) pushes ad-hoc channel notifications.
Console + watchers — console() returns recent log/warn/error entries; errors are also auto-pushed as channels. watch(url_pattern) registers a network observer; matching requests push channel notifications until cancelled.
Page navigation events — auto-pushed as channel notifications when the page navigates outside of a click/type/navigate MCP call.
Two delivery channels:
- MCP channel notifications (primary) —
notifications/claude/channelpushed over Streamable HTTP/SSE the moment context is captured. Claude sees<channel source="devpanel">...</channel>in real time. Routing: browser events → most-recently-active McpSession;defer/everyreplies → originating McpSession only. - UserPromptSubmit hook (fallback) — drains the stack via
curl /stackon every prompt. Works for clients that don't enable channels.
Architecture
Chrome DevTools (DevPanel tab) PTY daemon (Python)
├── Terminal (xterm.js) ──ws ─────────────────── /ws (attach/detach)
├── cdp.ts ── chrome.debugger ─── all CDP work
│ ├── ensureAttached / detach (with onDetach reset)
│ ├── startInspect (Overlay.setInspectMode)
│ ├── captureScreenshot (clip + captureBeyondViewport)
│ ├── captureTree (Accessibility.getFullAXTree)
│ ├── resolveSelector / focusElement / dispatchClick / dispatchType
│ └── settle + networkDelta
├── commands.ts dispatch ────────────────────── POST /stack
│ eval / screenshot / network / console / tree / click / type / navigate
├── actions.svelte.ts (Actions class)
│ toggleSelect / captureScreenshot / captureNetwork / selectAndScreenshot
├── network.onNavigated ───────────────────── POST /channel
├── CommandPalette (Ctrl+K) / SelectionDialog / StackChip / ThemeToggle
└── Service Worker ── NMH-relay only (sendNativeMessage to spawn daemon)
PTY daemon endpoints
├── /ws?tab=N → terminal I/O (per-mount uuid)
├── POST /stack?session= → push context (spills large data to disk)
├── GET /stack?session= → drain (advances watermark) / ?peek=true (raw JSON)
├── POST /channel?session= → ad-hoc channel push
├── POST /mcp?session= → MCP JSON-RPC
├── GET /mcp?session= → SSE stream (channel notifications)
├── DELETE /mcp?session= → terminate MCP session
└── GET /controls → dev endpoint (port, pid, stack size, MCP sessions, uptime)
Claude Code (in PTY terminal)
├── MCP client ──POST/GET──→ /mcp (tools + SSE channel notifications)
└── UserPromptSubmit hook ──→ GET /stack (drain fallback for non-channel clients)
The service worker is reduced to NMH-relay-only — it spawns the daemon via sendNativeMessage and returns the port to the panel. All CDP work runs in the panel via chrome.debugger.
Install
pip install devpanel # or: uv tool install devpanel
devpanel install --extension-id=EXTENSION_ID
Then load the extension in chrome://extensions → Load unpacked → point to the printed path.
The --extension-id is shown on the extensions page after loading. Re-run devpanel install with the correct ID.
Usage
- Open Chrome DevTools on any page
- Click the DevPanel tab
- A terminal opens with your shell (fish/bash/zsh) —
DEVPANEL_PORT,DEVPANEL_SESSION,DEVPANEL_SPILL_DIR, andDEVPANEL_REPLD_SOCKETenv vars are set - Your fish conf.d (see Shell setup below) wraps
claudewith--mcp-config,--settings, and--dangerously-load-development-channelswhen those env vars are present - Run
claude— it connects to the MCP server athttp://127.0.0.1:$DEVPANEL_PORT/mcpand registers for channel notifications - Click Select → pick elements → annotate → channel push fires immediately, Claude sees
<channel source="devpanel">in the conversation - (Fallback) Type a prompt — hook drains any unsent context
Dev
# Extension
cd extension-src
npm install
npm run build # required — hot reload doesn't work with CRXJS service workers
npm run check # svelte-check
npm run lint # prettier + eslint
npm run format # prettier --write
# Daemon (Python)
uv sync --dev # installs aiohttp + repld (for live introspection)
uv run devpanel start # standalone daemon for testing
ruff format src/ # format
ruff check src/ # lint
# Then: curl localhost:PORT/controls, curl POST/GET /stack, /mcp
# Full build (extension + Python wheel)
make build
Live introspection via repld
When the NMH spawns the daemon, it goes through repld so you can inspect daemon state from your main Claude Code session:
# .mcp.json registers repld bridge (gitignored — socket path is per-user)
{"mcpServers": {"repld": {"type": "stdio", "command": "uv",
"args": ["run", "repld", "bridge", "--socket", "/run/user/$UID/devpanel/repld.sock"]}}}
Then in your Claude Code session: mcp__repld__exec("list(_sessions.keys())"), exec("[s.stack for s in _sessions.values()]"), push test channel notifications, etc.
Shell setup
DevPanel does not write any shell config files. The daemon only sets env vars on the PTY: DEVPANEL_PORT, DEVPANEL_SESSION, DEVPANEL_SPILL_DIR, DEVPANEL_REPLD_SOCKET. You configure your dotfiles to use those vars:
~/.config/fish/conf.d/devpanel.fish — guarded function that activates only inside DevPanel PTYs:
if set -q DEVPANEL_PORT
function claude
command claude \
--mcp-config '{"mcpServers":{"devpanel":{"type":"http","url":"http://127.0.0.1:'$DEVPANEL_PORT'/mcp?session='$DEVPANEL_SESSION'"}}}' \
--settings '{"hooks":{"UserPromptSubmit":[{"matcher":"","hooks":[{"type":"command","command":"curl -s http://127.0.0.1:$DEVPANEL_PORT/stack?session=$DEVPANEL_SESSION"}]}]}}' \
--dangerously-load-development-channels server:devpanel \
$argv
end
end
~/.tmux.conf — pass env vars through tmux:
set -ga update-environment " DEVPANEL_PORT DEVPANEL_SESSION DEVPANEL_SPILL_DIR DEVPANEL_REPLD_SOCKET"
The fish function is harmless outside DevPanel (the if set -q guard skips it). The single-quote breaks ('...:'$DEVPANEL_PORT'/...') make fish expand the URL at function-call time; the hook's $DEVPANEL_PORT stays literal so Claude's hook executor expands it at hook-fire time.
How delivery works
Two paths, both wired up by the function above:
MCP channel notifications — when you click Select / Network / Screenshot, the panel POSTs to /stack. The daemon stores the item, then pushes notifications/claude/channel to the most-recently-active McpSession for that PTY. Claude Code receives it instantly via the SSE stream and renders <channel source="devpanel">...</channel> in the conversation. Stale McpSessions (no POST/GET activity for 5 min) are reaped automatically. defer/every replies route to the originating session only.
UserPromptSubmit hook — on every prompt, Claude's hook executor curls GET /stack?session=.... The daemon returns formatted markdown of any pending items and clears the stack. This is the fallback for clients that don't have --dangerously-load-development-channels enabled.
Both fire by default. With channels active, items are typically already in the conversation by the time the hook drains; the hook just sees an empty stack.
Drain format
When the hook fires, the agent sees:
## Browser Context
### Selected elements
- `div.central-textlogo` — this is the logo
- `nav.central-featured` — language links
### Network
- 8 requests, 1 errors → /run/user/1000/devpanel/network-def.json
### Screenshots
- /run/user/1000/devpanel/screenshot-ghi.png
Large data (HAR, screenshots) spills to disk. The agent uses Read/Grep on the file paths. With channel routing live, the hook usually returns empty — the items are already in the conversation.
Project details
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file devpanel-0.4.2.tar.gz.
File metadata
- Download URL: devpanel-0.4.2.tar.gz
- Upload date:
- Size: 380.1 kB
- Tags: Source
- Uploaded using Trusted Publishing? No
- Uploaded via: uv/0.11.7 {"installer":{"name":"uv","version":"0.11.7","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"EndeavourOS","version":null,"id":null,"libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":null}
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
e4b839363aa00f94bac04bd5ddbc6beabfacc38e0b0ddccd30e6e44234794c8c
|
|
| MD5 |
ad2405418a3fb0255da55335b974b62e
|
|
| BLAKE2b-256 |
82fb8d2b52f6f57e4c9a70bf51bbee27a91247bb6d70f43763d146935757574e
|
File details
Details for the file devpanel-0.4.2-py3-none-any.whl.
File metadata
- Download URL: devpanel-0.4.2-py3-none-any.whl
- Upload date:
- Size: 385.9 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? No
- Uploaded via: uv/0.11.7 {"installer":{"name":"uv","version":"0.11.7","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"EndeavourOS","version":null,"id":null,"libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":null}
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
a1dbfd15ea132fcf66a40af0ef09e077eda120fd24cacc084483a56573492890
|
|
| MD5 |
38dae6ab8011de20f4d2546c94f46f00
|
|
| BLAKE2b-256 |
34265e66a5978769df09d100aa040975215d936e362abef857d5787850c9e114
|