Skip to main content

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 JSjs(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 + watchersconsole() 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/channel pushed 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/every replies → originating McpSession only.
  • UserPromptSubmit hook (fallback) — drains the stack via curl /stack on 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

  1. Open Chrome DevTools on any page
  2. Click the DevPanel tab
  3. A terminal opens with your shell (fish/bash/zsh) — DEVPANEL_PORT, DEVPANEL_SESSION, DEVPANEL_SPILL_DIR, and DEVPANEL_REPLD_SOCKET env vars are set
  4. Your fish conf.d (see Shell setup below) wraps claude with --mcp-config, --settings, and --dangerously-load-development-channels when those env vars are present
  5. Run claude — it connects to the MCP server at http://127.0.0.1:$DEVPANEL_PORT/mcp and registers for channel notifications
  6. Click Select → pick elements → annotate → channel push fires immediately, Claude sees <channel source="devpanel"> in the conversation
  7. (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

devpanel-0.4.2.tar.gz (380.1 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

devpanel-0.4.2-py3-none-any.whl (385.9 kB view details)

Uploaded Python 3

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

Hashes for devpanel-0.4.2.tar.gz
Algorithm Hash digest
SHA256 e4b839363aa00f94bac04bd5ddbc6beabfacc38e0b0ddccd30e6e44234794c8c
MD5 ad2405418a3fb0255da55335b974b62e
BLAKE2b-256 82fb8d2b52f6f57e4c9a70bf51bbee27a91247bb6d70f43763d146935757574e

See more details on using hashes here.

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

Hashes for devpanel-0.4.2-py3-none-any.whl
Algorithm Hash digest
SHA256 a1dbfd15ea132fcf66a40af0ef09e077eda120fd24cacc084483a56573492890
MD5 38dae6ab8011de20f4d2546c94f46f00
BLAKE2b-256 34265e66a5978769df09d100aa040975215d936e362abef857d5787850c9e114

See more details on using hashes here.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page