Pwa Debug Layer — independently scanned and version-tracked by SaferSkills.
SaferSkills independently audited Pwa Debug Layer (Plugin) and scored it 45/100 (orange). The audit ran 55 deterministic rules across Security, Supply Chain, Maintenance, Transparency, and Community; it found 2 high-severity and 0 lower-severity findings. The full rule-by-rule trace and per-finding evidence are below. Free, methodology-open.
Findings & checks · 2 flagged
A base64 string of 128+ characters appears in a documentation file. Encoded prompt injection hides the hostile instruction in base64 — invisible to keyword filters — and relies on the agent's ability to decode it at runtime. There is no normal authoring reason to embed a multi-hundred-byte base64 blob in skill docs.
*.sig, SIGNATURES) outside the documentation.A base64 string of 128+ characters appears in a documentation file. Encoded prompt injection hides the hostile instruction in base64 — invisible to keyword filters — and relies on the agent's ability to decode it at runtime. There is no normal authoring reason to embed a multi-hundred-byte base64 blob in skill docs.
*.sig, SIGNATURES) outside the documentation.Every scanned point with the score it earned and what moved between them.
First recorded scan — no prior version to compare against.
The primary manifest — the file an agent reads to learn what this artifact does.
An AI-native debugging layer for PWAs and modern web apps. It lets an AI agent (e.g. Claude Code via MCP) see and act on your live, logged-in browser the way a developer with full DevTools open would — DOM, console, network, framework state, store state, service workers, caches, and direct interaction — as structured data the model consumes natively.
It's built for the questions developers actually search for and that Chrome DevTools makes you assemble by hand: why won't my service worker update? why is my cache stale? why won't my PWA install? why does this component have the wrong state? — answered by an agent reading the runtime directly, against your real browser profile (extensions, auth, and all), not a sterile automated tab.
The PWA failures developers actually search for, read straight from your live runtime:
All read from your real, logged-in profile — service-worker, cache, and extension state included — which chrome-devtools-mcp's sterile automated Chrome can't see.
The goal is to eliminate the "user is the AI's eyes and hands" loop. Today, debugging a PWA with AI usually means the human copy/pastes DOM snippets, describes console errors, screenshots UI state, and hand-executes clicks. This project replaces that with direct, structured access.
Status: working on Linux. The full MCP→IPC→native-host→service-worker→page-world round-trip is live, and a broad debugging surface is shipped: - Capture — console / network / error / DOM-mutation / lifecycle, with persistent ring buffers + disk spill. - Framework introspection — React, Vue, Svelte, and Solid (component/element trees, state, find-by-text/role). - Store introspection — Redux, Zustand, Pinia, and Jotai (read, subscribe, dispatch). - Interaction + touch gestures — click, fill, submit, hover, focus/blur, select, key/type, drag, scroll, swipe, tap, double-tap, long-press, pinch. - Library-popup capture/replay — WalletConnect / SDK modals: record, replay, tail, failure correlation. - Replay & source maps — rrwebsession_record/session_replay,source_map_resolve. - Browser launcher — one-callpdl_launch_browserwithchrome-devtools-mcpcoexistence. - PWA Runtime Diagnostics — service-worker lifecycle + versions, CacheStorage contents + age, installability gaps, a live capability matrix, IndexedDB/web-storage inspection, update-propagation / version-skew analysis, and a one-shot runtime-state snapshot.
>
Verified on Linux (the full suite live-tested against a real PWA). macOS/Windows code paths are implemented with unit coverage but still need real-machine retest (help wanted). Firefox is not supported (it doesn't speak CDP).
>
⚠️ macOS / Windows users, beware: these platforms have never been run on real hardware — only unit-tested with injected fakes. Expect rough edges (browser detection, profile paths, native-messaging registration, system-default resolution). Please open an issue with the failing command and its output — bug reports from real macOS/Windows machines are the single most useful contribution right now. See Help wanted for the specific things to try.
chrome-devtools-mcpGoogle's chrome-devtools-mcp gives an AI Chrome DevTools Protocol access (DOM, console, network, screenshots). That covers a lot.
pwa-debug-layer is complementary — it targets the things CDP can't reach:
chrome-devtools-mcp spawns a fresh automated Chrome — which shows "controlled by automated test software," blocks extension loading, and has none of your auth/session state, so authenticated apps are hard to debug (the extensions/auth gap, #265). pwa-debug-layer's default existing mode attaches to your normal profile, with its real cookies, extensions, and service-worker state — exactly the real-profile SW/extension visibility people keep asking Google for (#1173, #96).__REACT_DEVTOOLS_GLOBAL_HOOK__, __vue_app__, _vnode, etc.). CDP can't see these.initScript-on-next-nav.The two are designed to coexist: install both, the AI uses each for what it does best, with zero tool-surface duplication.
┌──────────────────┐ MCP (stdio) ┌──────────────────────────┐
│ Claude Code │ ◄───────────► │ Native Messaging Host │
│ (or any MCP │ │ - MCP server │
│ client) │ │ - Ring buffers │
└──────────────────┘ │ - Replay/snapshot store │
└────────────┬─────────────┘
│ Native Messaging
│ (JSON over stdio)
▼
┌──────────────────────────┐
│ Extension Service Worker│
│ - chrome.debugger (CDP) │
│ - Tab/router │
└────────────┬─────────────┘
│
┌────────────────────────┼────────────────────────┐
▼ ▼ ▼
┌────────────────────┐ ┌────────────────────┐ ┌────────────────────┐
│ Content Script │ │ Page-World Script │ │ DevTools Panel │
│ (isolated world) │ │ (MAIN world) │ │ (planned) │
│ - DOM observe │ │ - React/Vue hooks │ │ - Human inspector │
│ - Action exec │ │ - fetch/XHR patch │ │ of AI session │
│ - Bridge to SW │ │ - Bus/RxJS taps │ │ │
└────────────────────┘ └────────────────────┘ └────────────────────┘
└────────── live page (the PWA being debugged) ─────────┘Three components, one installable unit:
Each does what only it can. See docs/PLAN.md for the full design.
Chromium-family only, sideloaded. Tested against:
.deb / .rpm).deb)macOS Application Support paths and Windows HKCU-registry registration are implemented and have unit-test coverage; the manual round-trip retest currently runs on Linux.
If you installed your browser via snap (e.g. snap install chromium on Ubuntu), it will not work with pwa-debug-layer.
Why: snap's home interface allows the browser to read files in $HOME but blocks exec of any binary whose resolved path crosses a hidden directory (~/.nvm/..., ~/.config/...). The native messaging host launcher and the node binary it invokes both live under hidden paths in a normal install, so spawn fails with Permission denied and the service worker reports Native host has exited. There is no fix on the extension/host side that doesn't require copying ~125 MB of node into a non-hidden install dir per registration; not worth the install bloat for a setup most distros let you avoid.
What to do: install your Chromium-family browser from a native package source instead:
apt install chromium from the universe repo if you've enabled the non-snap source, or apt install brave-browser / microsoft-edge-stable from their respective .deb repos. The Chromium tarball from chromium.org also works.dnf install chromium is non-snap by default.pacman -S chromium.Flatpak browsers are detected and have a manifest written, but flatpak confinement may also block exec — if it fails, run flatpak override --user --filesystem=host <app-id> and retry.
git clone https://github.com/<your-fork>/pwa-debug-layer
cd pwa-debug-layer
pnpm install
pnpm build # builds packages/host/dist/main.js and packages/extension/dist/
pnpm test # full workspace unit suite (shared + host + extension)For Claude Code, add to your .mcp.json (project-scoped) or ~/.claude/mcp.json (global):
{
"mcpServers": {
"pwa-debug": {
"type": "stdio",
"command": "node",
"args": ["/absolute/path/to/pwa-debug-layer/packages/host/dist/main.js"]
}
}
}Restart Claude Code so it picks up the server.
chrome://extensions (or brave://extensions, etc.) in your browser.packages/extension/dist/.[pwa-debug/sw] id=<id> on every boot.The host registration is AI-driven. Six MCP tools are exposed for setup; Claude calls them via a guided flow:
Set up pwa-debug. The extension ID is `<your-id>` (or omit the ID and Claude can fetch it from the SW console if you also have `chrome-devtools-mcp` installed.)
Claude will:
host_status to see what's already registered.host_register_extension(id) — this writes a per-browser native-messaging manifest into every detected install (Chromium-family native, macOS Application Support, Windows HKCU registry), and drops a launcher script with an absolute node path (so the host spawns under sandboxed PATH environments).chrome://extensions.host_status again to confirm the manifest is on disk and the SW is connecting.When the round-trip works you'll see in the SW console (Inspect views: service worker on the extension card):
[pwa-debug/sw] connected to host
[pwa-debug/sw] pong …
[pwa-debug/sw] hello … (5s after connect — host-pushed message proving bidirectional flow)chrome-devtools-mcp coexistencepwa-debug and chrome-devtools-mcp are two separate MCP servers that share one browser. pwa-debug launches (or attaches to) a Chromium browser with a live remote-debugging port; chrome-devtools-mcp attaches to that same port over CDP. No proxying, no version coupling.
Register both with your client. For Claude Code:
# pwa-debug (this project) — adjust the path to your checkout
claude mcp add pwa-debug --scope user -- node /absolute/path/to/pwa-debug-layer/packages/host/dist/main.js
# chrome-devtools-mcp (optional but recommended) — runs via npx, no global install
# The --browserUrl port MUST match the port pwa-debug launches on (launch.defaultPort, default 9222).
claude mcp add chrome-devtools --scope user -- npx -y chrome-devtools-mcp@latest --browserUrl http://127.0.0.1:9222pwa-debug as a Claude Code pluginThe repo ships a .claude-plugin/ manifest whose MCP server runs the published npm package (npx -y @aryanduntley/pwa-debug@latest), so the plugin installs with no clone and no build — and picks up updates with `/reload-plugins`, no full restart:
# Add the marketplace straight from GitHub (no checkout needed)
/plugin marketplace add aryanduntley/pwa-debug-layer
# Install + enable the pwa-debug plugin
/plugin install pwa-debug@pwa-debug
# Bring the MCP server up with no restart:
/reload-pluginsNo build needed. The plugin's MCP entry isnpx -y @aryanduntley/pwa-debug@latest, which fetches the prebuilt host (and its bundled extension) from npm. Working on a local clone instead? Skip the plugin and register your own build directly:claude mcp add pwa-debug --scope user -- node /absolute/path/to/pwa-debug-layer/packages/host/dist/main.js.
>
`chrome-devtools-mcp` is still separate. The plugin declares only thepwa-debughost —chrome-devtools-mcpstays the optionalclaude mcp add chrome-devtools …above (no version coupling, no owning its launch). The bundled `chrome-devtools-coexistence` skill walks you through registering it; with a plugin install, its "make the tools appear" step is/reload-plugins.
Port must match.chrome-devtools-mcp's--browserUrlhas to point at the exact portpwa-debugopens — thelaunch.defaultPortsetting (default9222), or the active port from a currentpdl_launch_browser. Rather than hand-write this, let Claude call `pdl_register_chrome_devtools`, which runs theclaude mcp addabove for you pinned to the right port (and `pdl_check_setup` flags a registration that is unpinned — no--browserUrl, so it spawns its own isolated browser — or pointed at the wrong port).
Mind the (re)connect. A registration added or changed viaclaude mcp addonly takes effect when the server (re)connects. Fastest path: open `/mcp` and reconnectchrome-devtools— this reloads it from the current registration (verified to pick up a changed--browserUrl) with no full restart and no context loss. If the client won't load it that way, fully restart Claude Code as a fallback. If instead you installchrome-devtools-mcpas a plugin, run `/reload-plugins`. The bundled `chrome-devtools-coexistence` skill walks Claude through all paths and, for the full-restart fallback, hands you a context note to paste back afterward.
The host has noinstall/servesubcommand —dist/main.jsauto-detects its mode: launched by Chrome (argv starts withchrome-extension://) it runs as the native-messaging host; launched by your MCP client it runs as the MCP server.
Then let Claude drive setup and launch:
{ ok, gaps[], recommendations[] }: whether chrome-devtools-mcp is registered (read from the claude CLI) and pinned with a `--browserUrl` at the right debug port — it flags both an unpinned registration (which would spawn its own isolated browser instead of attaching to yours) and one pointed at the wrong port; the host manifest is installed (including per active sandbox profile); the extension dist is present; and an extension ID is registered. Follow its next_steps to close any gap.chrome-devtools-mcp registration for you, pinned to the active/launch.defaultPort port (idempotent; re-points it if it's unpinned or on the wrong port). (Mutates your MCP config — Claude will ask first; a restart/reconnect follows, see above.)~/Downloads/pwa-debug-extension (or a target you pass) with chrome://extensions "Load unpacked" instructions. (On most browsers a sandbox mode preloads the extension so you can skip this; on branded Google Chrome 142+ preload is impossible — this is the manual install the sandbox launch will point you to.)browserUrl to hand to chrome-devtools-mcp. Always launch first: call this before any chrome-devtools-mcp tool, so the browser + debug port exist for it to attach to. Follow its next_steps — they carry the brand/version-specific guidance (preloaded vs. manual Load unpacked, or the modern-Chromium port caveat).The validated one-profile recipe (what these tools converge on, verified live on Brave 148):
pdl_launch_browser({ browser: "brave", mode: "sandbox-persistent" })
→ dedicated profile at ~/.pwa-debug/profiles/brave with:
• the native-messaging manifest auto-written into the profile (so the extension connects), and
• the pwa-debug extension preloaded via --load-extension (Brave honors it), and
• --remote-debugging-port live (custom profile dir, so Chromium 136+ opens it).
pdl_register_chrome_devtools() → pins chrome-devtools-mcp at that port
restart / reconnect chrome-devtools-mcp → it attaches to the SAME browserNow chrome-devtools-mcp (CDP) and pwa-debug (extension → page-world) drive the same tab simultaneously — independent, non-contending channels (the extension uses page-world injection, not chrome.debugger, so there's no CDP-attacher contention).
pdl_launch_browser takes mode (default existing), browser (defaults to your system-default Chromium browser), and port (default 9222).
| Mode | Profile | When to use |
|---|---|---|
| `existing` (default) | Your normal browser profile | Debugging your real browsing session with pwa-debug (your real cookies, extensions, SW/cache state). Degrades gracefully: (a) debug port already live → attaches; (b) browser running without a debug port → opens a new window in the existing session (your extension tools work, chrome-devtools-mcp does not); (c) browser not running → spawns fresh with your profile. Modern-Chromium caveat (136+): Chromium refuses --remote-debugging-port on the default profile, so on a current browser existing mode gives you pwa-debug but not a CDP port — the launch reports this (attached:false, no false browserUrl) and steers you to sandbox-persistent for chrome-devtools-mcp. |
| `sandbox-persistent` | ~/.pwa-debug/profiles/<browser>/ (persists across restarts) | The canonical both-tools profile — a dedicated profile beside your main browser, with its own --user-data-dir (so the debug port works on Chromium 136+). The launcher auto-writes the native-messaging manifest into the profile and, on most browsers, preloads the pwa-debug extension via --load-extension. Branded-Google-Chrome 142+ caveat: Chrome removed --load-extension, so there the launch brings up the debug port (for chrome-devtools-mcp) but not the extension — it returns a one-time manual Load unpacked walkthrough (persists in this profile), or steers you to a non-Google Chromium where preload just works. |
| `sandbox-temp` | a fresh mktemp dir (removed on host shutdown) | One-off / CI / clean-state runs. Same as sandbox-persistent, but throwaway — so the Chrome-142+ manual-load step won't persist usefully here; prefer a non-Google Chromium for temp runs that need the extension. |
Extension preload depends on the browser brand + version (the launcher detects it from --version and adapts):
| Browser | --load-extension (sandbox preload) |
|---|---|
| Brave, Chromium, Edge, Opera, Vivaldi, Chrome for Testing | ✅ works on current versions |
| Google Chrome ≤ 136 | ✅ works |
| Google Chrome 137–141 | ✅ works — launcher adds --disable-features=DisableLoadExtensionCommandLineSwitch |
| Google Chrome ≥ 142 | ❌ removed — launcher omits the dead flag and guides a one-time manual Load unpacked (or use Brave/Chromium) |
Recommended coexistence setup: sandbox-persistent on a non-Google Chromium (e.g. Brave or Chromium). That single profile gets the extension preloaded and a live debug port, so pwa-debug and chrome-devtools-mcp drive the same browser with zero manual steps.
Chromium-family only (Firefox doesn't speak CDP). Linux is first-class. macOS/Windows binary detection, profile/user-data-dir paths, and system-default detection (macOS LaunchServices, Windows UserChoice registry) are all implemented with unit coverage, but live verification on real macOS/Windows machines is still needed — see [Help wanted](#help-wanted-macos--windows-verification).
| Browser | PATH names probed | Standard Linux binary | Linux profile dir (existing mode) |
|---|---|---|---|
| Chrome | google-chrome, google-chrome-stable | /opt/google/chrome/chrome, /usr/bin/google-chrome* | ~/.config/google-chrome |
| Chromium | chromium, chromium-browser | /usr/bin/chromium*, /snap/bin/chromium | ~/.config/chromium |
| Edge | microsoft-edge, microsoft-edge-stable | /opt/microsoft/msedge/msedge | ~/.config/microsoft-edge |
| Brave | brave-browser, brave | /opt/brave.com/brave/brave-browser | ~/.config/BraveSoftware/Brave-Browser |
| Vivaldi | vivaldi, vivaldi-stable | /opt/vivaldi/vivaldi | ~/.config/vivaldi |
| Opera | opera | /usr/bin/opera, /opt/opera/opera | ~/.config/opera |
xdg-settings get default-web-browser, macOS via LaunchServices (defaults read … LSHandlers), Windows via the HKCU UrlAssociations\http\UserChoice ProgId. The macOS/Windows paths are implemented + unit-tested but not yet exercised on a real machine.9222 (the chrome-devtools-mcp convention); override it without passing port each time via the launch.defaultPort setting (settings.set)./snap/bin/…) in existing mode, the launcher now resolves its confined profile (~/snap/<snap>/common/<cfg>) instead of ~/.config — but note snap browsers still can't run the native-messaging host (see snap section), so this only matters if/when that confinement is lifted.session_ping reports page_blocks_scripts.flatpak override --user --filesystem=host <app-id> and retry.Development happens on Linux, so the macOS and Windows code paths are written and unit-tested with injected fakes, but never run on a real machine. If you're on macOS or Windows, trying these and reporting back (open an issue with the output) is the single most useful contribution right now:
macOS
pdl_check_setup / pdl_install_extension — does the extension resolve and copy, and do the chrome://extensions instructions work?/Applications/*.app/Contents/MacOS/….defaults read com.apple.LaunchServices/com.apple.launchservices.secure LSHandlers — does the parser pick the right browser? (Paste the raw output if it doesn't.)pdl_launch_browser mode existing (profile under ~/Library/Application Support/…) and both sandbox modes.Windows
%PROGRAMFILES% / %LOCALAPPDATA%.reg query "HKCU\Software\Microsoft\Windows\Shell\Associations\UrlAssociations\http\UserChoice" /v ProgId — does the ProgId map to the right browser?pdl_launch_browser (profile under %LOCALAPPDATA%\…\User Data) and the HKCU native-messaging registration.Any OS
pdl_browser_status after restarting the host — launches now persist to launches.json, so previously-launched browsers should still be listed (with a fresh liveness re-probe). Confirm the list survives a restart and that closed browsers show as not-live.The launcher never kills your running browser and sandbox modes use throwaway/dedicated profiles, so this is low-risk to try.
When something isn't working, walk this ladder — each step localizes the failure:
pageWorldError table below.)--remote-debugging-port only at process start — if the browser was already running without it, pdl_launch_browser opens a new window (sub-state b) but can't add the port to the live process (attached:false). (2) On Chromium 136+, the port is refused on the default profile entirely, even on a fresh start. Either way: use mode: sandbox-persistent (a dedicated profile where the port works) for chrome-devtools-mcp.--load-extension, so the sandbox can't auto-preload the extension — the launch says so and gives you a one-time chrome://extensions → Developer mode → Load unpacked walkthrough (it persists in that dedicated profile). Or relaunch with browser: "brave" / "chromium", where preload works automatically. (chrome-devtools-mcp still works either way — the debug port is live.)--browserUrl). Run pdl_register_chrome_devtools (or re-add with --browserUrl http://127.0.0.1:<port>) and restart/reconnect it; pdl_check_setup flags this..mcp.json / client MCP config is read at startup — restart your MCP client (or /mcp-reconnect that server) after adding pwa-debug or chrome-devtools.chrome://extensions detaches content scripts from already-open tabs. Hard-refresh the page tab (Ctrl+Shift+R); the SW also auto-reinjects on the next session_ping (look for pageWorldSelfHealed: true). Sandbox modes avoid this entirely (extension preloaded before tabs open).session_ping returns pageWorld: null with a typed pageWorldErrorsession_ping reports failure modes as typed codes in pageWorldError (machine-readable) plus the original chrome-runtime string in pageWorldErrorMessage (for logs). The MCP next_steps[] field carries imperative, code-specific guidance — AI clients should relay it verbatim. Tabs that simply predated the extension reload (the most common dev-loop friction) are auto-recovered by the SW via chrome.scripting.executeScript; when that succeeds, pageWorld is populated and pageWorldSelfHealed: true appears alongside it. The table below is the canonical mapping (single source of truth: NEXT_STEPS_BY_CODE in packages/host/src/mcp/tools/session_ping.ts).
pageWorldError | What it means | What to do |
|---|---|---|
(absent) with pageWorldSelfHealed: true | The static content script was missing on the active tab; the SW silently re-injected content-script.js + page-world.js and retried. No action needed. | Informational only. |
cs_not_attached_refresh_tab | Auto-recovery was attempted but did not stick (page rejected the injection or reloaded mid-flight). | Hard-refresh the page tab (Ctrl+Shift+R) and retry. If it repeats, reload the extension at chrome://extensions then hard-refresh. |
page_blocks_scripts | A content blocker is rejecting the script (Brave Shields, uBlock Origin, AdGuard, or similar). Site CSP is also possible. | Brave: click the lion icon → set Shields Down for the site → refresh → retry. uBlock Origin / similar: disable for this site → refresh → retry. If neither, the site's own CSP is blocking and pwa-debug cannot bypass it. |
page_world_blocked | The content script attached but the MAIN-world page-world bridge cannot be reached — the site's Content-Security-Policy blocks the inline script tag. | Site-level restriction; cannot bypass. Console + network capture may still work via the content-script side, but live page-world reads (state, evaluate) will not. |
restricted_url | The active tab is on a URL browsers do not allow extensions to touch (chrome://, chromewebstore.google.com, about:, devtools://, file://, view-source:, etc.). | Switch focus to a regular http(s) tab of the PWA, then retry. |
no_active_tab | No active http(s) tab is focused (DevTools window or extension popup may be focused instead). | Focus a regular browser tab and retry. |
cs_inject_failed | The auto-recovery chrome.scripting.executeScript itself failed. The extension cannot reach this tab. | Reload the extension at chrome://extensions and hard-refresh the page (Ctrl+Shift+R). If it persists, the URL may be one the browser blocks all extensions from — check the address bar. |
To confirm the content script attached after a successful round-trip, open the page tab's DevTools (F12 on the page itself, not the SW console) and look for [pwa-debug/cs] attached at <url> in the Console.
Every tool returns a structured response of the form { ok, data, error?, next_steps[] }. The next_steps array encodes the rules of engagement for the AI — what to call next based on the actual response shape — mirroring the AIMFP return_statements pattern. The canonical list lives in packages/host/src/mcp/tools/index.ts.
| Tool | Purpose |
|---|---|
host_status | Install/liveness state: registered IDs, manifest paths, launcher path, active connections. Cheap, idempotent. Always call first. |
host_register_extension(id) / host_unregister_extension(id) | Add/remove an extension ID across per-browser manifests + launcher script. Idempotent. |
host_list_registrations / host_reset | Read registered IDs; destructive cleanup to re-bootstrap. |
session_ping | Full MCP → IPC → NMH → SW → page-world round-trip with typed pageWorldError codes + self-heal. |
pdl_check_setup | Diagnose setup → { ok, gaps[], recommendations[] } (CDP reachable, manifest installed, extension present, ID registered). |
pdl_install_extension({ target? }) | Copy the extension to a folder for unpacked install. |
| Tool | Purpose |
|---|---|
pdl_launch_browser({ browser?, port?, mode? }) | Launch/attach a Chromium browser with a live debug port. mode: existing (default), sandbox-persistent, sandbox-temp. Returns browserUrl for chrome-devtools-mcp. |
pdl_browser_status | Managed launches (browser, profile mode, port, pid) with live debug-port re-probe + extension SW heartbeat. |
| Tool | Purpose |
|---|---|
console_tail / network_tail / error_tail | Cursor-paginated, filterable tails of the persistent capture ring buffers (memory + disk spill). |
recent_events | Recent captured events across kinds for quick verification. |
evaluate | Evaluate an expression in the page world. |
session_record / session_replay | rrweb session recording + cursor-paginated replay. |
source_map_resolve | Resolve generated stack frames to original src/…:line:col. |
settings_list_schema / settings_get / settings_set | Read the typed settings schema; get/set values (allowlist, capture filters, disk-spill, etc.). |
The namesake suite — read the debugged PWA's service-worker, cache, installability, storage, and update state. All page-world reads against your real profile; CDP / chrome-devtools-mcp can't surface these.
| Tool | Purpose |
|---|---|
sw_status | Service-worker registrations: installing/waiting/active versions, updateViaCache, controller, and whether an update is stuck waiting (the #1 "why isn't my update showing"). |
sw_lifecycle_tail | Cursor-paginated stream of SW lifecycle events (updatefound / statechange / controllerchange). |
cache_list / cache_inspect / cache_match | CacheStorage caches + per-entry { url, status, content-type, size, ageSeconds, cache-control }, and which cache serves a given URL — the core of stale-cache debugging. |
pwa_status | Display mode / standalone, controller, permissions, and a live capability matrix (Push / Background Sync / Periodic Sync / Badging / File System Access / Window Controls Overlay). |
pwa_installability | Discover + parse the manifest and run installability checks → structured { supported, gaps[], fixes[] } with per-gap remediation. |
storage_get | localStorage / sessionStorage snapshot (capped). |
idb_list / idb_query | IndexedDB databases + object-store schema, then a read-only capped slice of records — inspect IndexedDB live. |
pwa_update_analyze | Correlates SW status + cache ages + recent 404s into a verdict: waiting-update-on-active-client, cached-HTML-older-than-JS version skew, chunk 404s — why are some users on old code? |
pwa_snapshot | One capped runtime-state blob (SW + store + web storage + IndexedDB + cache names) for deterministic bug-repro / hand-off. |
Read via each framework's own model — things CDP can't see. Tree/state where the framework persists it; element-level find everywhere.
| Tool family | Purpose |
|---|---|
react_tree / react_get_state / react_find_by_text / react_find_by_role | React fiber-tree introspection + props/state/hooks by stable id + component lookup. |
vue_tree / vue_get_state / vue_find_by_text / vue_find_by_role | Vue 3 ComponentInternalInstance tree + reactive state + lookup (parity with React). |
svelte_components / svelte_find_by_text / svelte_find_by_role | Svelte component discovery + __svelte_meta source locations + element lookup (no instance state — Svelte exposes none). |
solid_detect / solid_find_by_text / solid_find_by_role | Solid detection + element-level find (no persisted tree/state without @solid-devtools). |
| Tool family | Purpose |
|---|---|
redux_get_state / redux_subscribe / redux_tail / redux_dispatch | Redux read, change-delta subscribe/tail, JSONPath-lite slice, (opt-in) dispatch. |
store_get_state / store_subscribe / store_tail / store_dispatch | Framework-agnostic adapter covering Zustand / Pinia / Jotai through one contract (read / subscribe / dispatch by name). |
Framework-agnostic native-event sequences dispatched so React/Vue/etc. delegated synthetic-event systems fire. Each targets a unified locator (selector / role / text / framework stable-id).
| Tool | Purpose |
|---|---|
pdl_click / pdl_dblclick / pdl_hover | Pointer interactions. |
pdl_fill / pdl_submit / pdl_select_option / pdl_uncheck | Form interactions. |
pdl_focus / pdl_blur / pdl_key_press / pdl_type_sequence | Focus + keyboard. |
pdl_drag / pdl_scroll / pdl_swipe / pdl_tap / pdl_double_tap / pdl_long_press / pdl_pinch | Touch/gesture primitives (direction/distance/duration/steps model — no hand-built coordinates). |
| Tool | Purpose |
|---|---|
popup_tail | Tail the library-popup event stream (WalletConnect / RainbowKit / SDK modals, incl. shadow-root + nested). |
popup_record / popup_replay | Intent-driven recording of a popup's full primary+nested stream (immune to ring-buffer eviction) + flat/primary/tree replay. |
popup_failures | Correlate a popup's auth/connect failure with the console errors + failed requests during its open window. |
console_tail / network_tail.evaluate.popup_record / popup_replay / popup_tail / popup_failures for WalletConnect & SDK modals (incl. shadow-root + nested).session_record / session_replay; source_map_resolve for stack frames.pdl_launch_browser (existing + sandbox-persistent + sandbox-temp), pdl_check_setup, pdl_browser_status, pdl_close_browser, pdl_install_extension, pdl_register_chrome_devtools, and chrome-devtools-mcp coexistence.sw_status + sw_lifecycle_tail: installing/waiting/active versions, updateViaCache, skipWaiting/claim/controllerchange); CacheStorage inspection (cache_list / cache_inspect / cache_match with size/age + match-by-URL); pwa_status (display mode, controller, permissions, live capability matrix — Push/Background Sync/Periodic Sync/Badging/File System Access/Window Controls Overlay); installability diagnostics (pwa_installability → structured {gaps[], fixes[]}); IndexedDB/web-storage inspection (idb_list / idb_query / storage_get); update-propagation / version-skew analysis (pwa_update_analyze); and a one-shot runtime-state snapshot (pwa_snapshot). Closes the stale-cache / "SW won't update" / install-failure / version-skew pain cluster against your real profile.pdl_install_extension hands you the path + steps) — so every user knows exactly what they're running and why. Disabling Chrome Developer mode auto-disables it.winreg) wrapped in thin functional adapters with injection points for tests.pwa-debug-layer/
├── packages/
│ ├── host/ Native-messaging host + MCP server (Node, ESM, rollup-bundled)
│ ├── extension/ WebExtension (MV3) — service worker, content script, page-world
│ └── shared/ Cross-package types
├── docs/
│ ├── PLAN.md Full design doc (architecture, capability matrix, phased plan)
│ └── goals.txt
├── examples/ (future: test PWAs)
└── reference/ Read-only reference checkoutsThis is a personal project under active redesign. PRs welcome but please open an issue first to discuss scope — the architecture is still settling. The FP / no-OOP discipline applies to all contributions; see CLAUDE.md for the full rules.
MIT — see LICENSE.
~30 seconds. Free. No account. Every finding cites a rule and a line of evidence.