devloop-mcp 0.1.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/LICENSE +21 -0
- package/README.md +205 -0
- package/dist/index.cjs +14911 -0
- package/package.json +72 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Vincent Vella
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,205 @@
|
|
|
1
|
+
<p align="center">
|
|
2
|
+
<img src="assets/wordmark.svg" alt="Devloop" width="280" />
|
|
3
|
+
</p>
|
|
4
|
+
|
|
5
|
+
<p align="center">
|
|
6
|
+
Browser control + dev-server logs on one correlated timeline — for AI agents and humans.
|
|
7
|
+
</p>
|
|
8
|
+
|
|
9
|
+
---
|
|
10
|
+
|
|
11
|
+
A unified dev-loop tool: it drives a **browser** and your **dev server**, pushing both sides into one timestamped buffer so you can correlate a browser console error with the backend stack trace from the same moment. It runs two ways from a shared core:
|
|
12
|
+
|
|
13
|
+
- **Headless (stdio)** — drives Chrome via Puppeteer, served over stdio. The lightweight mode Claude Code spawns per session.
|
|
14
|
+
- **Cockpit (Electron)** — a single desktop window: tabbed browser panes (embedded `WebContentsView`s driven via CDP) with a browser bar (back/forward/reload + address) beside a collapsible side panel that toggles between **logs** and a **repro builder**. Project picker, auto-navigate, pop-out targets. The renderer is React 19 + Tailwind v4 + Radix + lucide-react. Serves the same tools over HTTP.
|
|
15
|
+
|
|
16
|
+
Because every event (browser console/network/page-errors **and** server stdout/stderr) shares one monotonic clock, `get_logs_around` / `repro` return a correlated, cross-source slice of the timeline.
|
|
17
|
+
|
|
18
|
+
## Architecture
|
|
19
|
+
|
|
20
|
+
```
|
|
21
|
+
┌──────────────────────── shared core (src/) ────────────────────────┐
|
|
22
|
+
│ logBuffer · devServer · registry · toolLayer (TOOLS + handleTool) │
|
|
23
|
+
└────────────────────────────────────────────────────────────────────┘
|
|
24
|
+
▲ ▲
|
|
25
|
+
IBrowserController ───┤ ├─── IBrowserManager
|
|
26
|
+
│ │
|
|
27
|
+
Puppeteer (Chrome) ◀──┘ └──▶ Electron webContents (N panes)
|
|
28
|
+
│ │
|
|
29
|
+
stdio ◀── index.ts ──┘ └── cockpit/main.ts ──▶ MCP over HTTP
|
|
30
|
+
(Claude spawns) (long-running app; Claude connects to a URL)
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
The **tool layer is transport- and substrate-agnostic**: it's bound to `{ buffer, browser, devServer }` via `configureTools()` and never knows whether Puppeteer or Electron is behind it, or whether it's talking over stdio or HTTP. The browser sits behind `IBrowserController`; the cockpit's `BrowserManager` implements the extended `IBrowserManager` (multiple panes, delegating to the active one).
|
|
34
|
+
|
|
35
|
+
stdout is reserved for the MCP protocol in stdio mode; all human-facing output goes to stderr.
|
|
36
|
+
|
|
37
|
+
## Install
|
|
38
|
+
|
|
39
|
+
**Headless MCP (stdio)** — no clone needed, register it with Claude Code:
|
|
40
|
+
|
|
41
|
+
```sh
|
|
42
|
+
claude mcp add devloop --scope user -- npx -y devloop-mcp
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
(Published as [`devloop-mcp`](https://www.npmjs.com/package/devloop-mcp) on npm; Puppeteer fetches Chromium on first install.)
|
|
46
|
+
|
|
47
|
+
**Cockpit (desktop app)** — grab the installer for your OS from [Releases](https://github.com/vincentvella/devloop/releases) (`.dmg` / `.exe` / `.AppImage`).
|
|
48
|
+
|
|
49
|
+
**From source** (dev) — requires [bun](https://bun.sh):
|
|
50
|
+
|
|
51
|
+
```sh
|
|
52
|
+
bun install
|
|
53
|
+
bun run app # build + launch the Electron cockpit
|
|
54
|
+
bun run start # or run the stdio MCP directly
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
## Tools (20)
|
|
58
|
+
|
|
59
|
+
**Dev server** — runtime, no per-project registration needed
|
|
60
|
+
- `dev_start({ project?, cmd?, cwd? })` — start a dev server and tee its logs. Specify it three ways: a saved registry `project`; explicit `cmd`+`cwd`; or neither (`cwd` defaults to the server's dir, `cmd` auto-detected from `package.json` scripts: `dev`/`develop`/`web`/`start`/`serve`).
|
|
61
|
+
- `dev_stop()` — stop it. Kills the whole **process group** (so `next dev`/`metro` grandchildren die too).
|
|
62
|
+
- `dev_status()` — running?, plus cmd/cwd/pid.
|
|
63
|
+
|
|
64
|
+
**Browser control** — act on the active pane
|
|
65
|
+
- `browser_navigate({ url })`
|
|
66
|
+
- `browser_screenshot({ fullPage? })` → PNG image
|
|
67
|
+
- `browser_click({ selector })`
|
|
68
|
+
- `browser_type({ selector, text })`
|
|
69
|
+
- `browser_eval({ expression })` — runs in page context (not blocked by CSP)
|
|
70
|
+
|
|
71
|
+
**Logs & correlation**
|
|
72
|
+
- `get_logs({ source?, stream?, grep?, app?, sinceSeq?, limit? })` — unified tail. `source` is `server`|`browser`; `stream` is `stdout`/`stderr`/`console`/`network`/`pageerror`. `app` scopes to one project's logs — it matches a pane's **label** (project name) or id (see `pane_list`) and filters *both* that pane's server and browser logs, regardless of which pane is active. Pass the last `seq` as `sinceSeq` to tail incrementally.
|
|
73
|
+
- `get_logs_around({ ts, windowMs?, source?, app? })` — **the correlation tool**: all events within ±`windowMs` of a timestamp, time-ordered across both sources (optionally scoped to one `app`).
|
|
74
|
+
- Logged **network** events (failures + status ≥ `DEVLOOP_NET_THRESHOLD`) carry the request `postData` and a capped, base64-decoded `responseBody` in their `detail` (Electron substrate).
|
|
75
|
+
- `clear_logs()` — reset before reproducing an issue.
|
|
76
|
+
- `repro({ actions | action, waitFor?, settleMs?, stepSettleMs?, idleMs?, timeoutMs?, continueOnError?, clear? })` — **reproduce-and-correlate**: clears the buffer, performs one action or a **sequence**, waits, and returns everything that happened on both sides across the sequence — with per-step results (`steps[]`), a `byStream` count, and a pre-filtered `errors` list.
|
|
77
|
+
- `actions: [{kind, ...}]` — kinds: `navigate`/`click`/`type`/`eval`/`none`. `action` (singular) = one-step convenience.
|
|
78
|
+
- Waits `stepSettleMs` (default 300) between steps, `settleMs` (default 1000) after the last. `waitFor: "networkidle"` waits until no network activity for `idleMs` (default 500) up to `timeoutMs` (default 10000) — **use it for slow/streaming responses** (Expo's first web bundle takes ~12s). On timeout you still get what landed, with a `waitNote`.
|
|
79
|
+
- `continueOnError` (default false) — otherwise stops at the failing step (`stoppedAtStep`).
|
|
80
|
+
|
|
81
|
+
**Project registry** — saved projects, persisted to `~/.devloop/projects.json`
|
|
82
|
+
- `project_list()` — list saved projects (name, cwd, cmd, url, steps).
|
|
83
|
+
- `project_add({ name, cwd, cmd?, url?, steps? })` — save/replace a project (incl. a saved repro `steps` sequence), so you can `dev_start({ project })` by name.
|
|
84
|
+
- `project_remove({ name })`.
|
|
85
|
+
|
|
86
|
+
**Panes** — multi-target (cockpit only; stdio mode is single-pane and reports so)
|
|
87
|
+
- `pane_list()` — each pane: `{ id, url, active, popped }`. The active pane is what `browser_*`/`repro` target; events are tagged with their pane `id`.
|
|
88
|
+
- `pane_new({ url? })` — open a new pane and make it active.
|
|
89
|
+
- `pane_select({ id })` — make a pane active.
|
|
90
|
+
- `pane_close({ id })`.
|
|
91
|
+
- `pane_pop({ id })` — detach a pane into its own standalone window (side-by-side targets).
|
|
92
|
+
|
|
93
|
+
### Console arguments
|
|
94
|
+
|
|
95
|
+
`console.log(obj)` is captured with arguments resolved to real values (e.g. `[log] user {"id":7}`), not `JSHandle@object`. The Electron substrate renders them synchronously from CDP previews; the Puppeteer substrate uses a *reserve-then-fill* pattern (stamp `seq`/`ts` synchronously at arrival, patch resolved args in afterward) so ordering matches emit order and interleaves correctly with server logs.
|
|
96
|
+
|
|
97
|
+
## Headless mode (stdio)
|
|
98
|
+
|
|
99
|
+
Register **once**, at user scope — works for every project:
|
|
100
|
+
|
|
101
|
+
```sh
|
|
102
|
+
claude mcp add devloop --scope user -- npx -y devloop-mcp
|
|
103
|
+
```
|
|
104
|
+
|
|
105
|
+
Then, in any project: *"dev_start and repro a navigate to /projects"*. `dev_start` defaults `cwd` to the project you're in and auto-detects the command.
|
|
106
|
+
|
|
107
|
+
| Var | Default | Meaning |
|
|
108
|
+
| --- | --- | --- |
|
|
109
|
+
| `DEVLOOP_HEADLESS` | `false` | `"true"` runs Chrome headless; default headful so you can watch. |
|
|
110
|
+
| `DEVLOOP_CHROME_PATH` | _(bundled)_ | Explicit Chrome executable path. |
|
|
111
|
+
| `DEVLOOP_NET_THRESHOLD` | `400` | Log network responses with status >= this (failures always logged). |
|
|
112
|
+
| `DEVLOOP_ACTION_TIMEOUT` | `10000` | Cap (ms) on interactions — a wedged page fails fast instead of hanging. |
|
|
113
|
+
| `DEVLOOP_NAV_TIMEOUT` | `30000` | Cap (ms) on navigations. |
|
|
114
|
+
| `DEVLOOP_LOG_CAPACITY` | `5000` | Max buffered events. |
|
|
115
|
+
| `DEVLOOP_DEV_CMD` / `DEVLOOP_DEV_CWD` | _(none)_ | Optional dev-server auto-start on boot (normally use `dev_start`). |
|
|
116
|
+
| `DEVLOOP_HOME` | `~/.devloop` | Registry location. |
|
|
117
|
+
|
|
118
|
+
## Cockpit mode (Electron)
|
|
119
|
+
|
|
120
|
+
```sh
|
|
121
|
+
bun run app # build + launch the cockpit
|
|
122
|
+
bun run app:selftest # headless integration test (no visible windows)
|
|
123
|
+
```
|
|
124
|
+
|
|
125
|
+
**One window** (React 19 + Tailwind v4 with `@theme` tokens + Radix `Dialog`/`Tooltip` + lucide-react icons), laid out as:
|
|
126
|
+
- **Top bar** — the **pane tabs**, then the active pane's **dev controls** + window toggles:
|
|
127
|
+
- **dev controls** (act on the active pane): a status chip (`● project` green when running, `✗ exited (code N)` red on a non-zero exit, else `dev: stopped`/`not configured`), **▶/⏹** start-stop the dev server, **⟳** restart it (Power), **📷** screenshot the pane into the timeline.
|
|
128
|
+
- `⚙` settings · `⤢` pop out the active pane into its own window.
|
|
129
|
+
- Tabs are auto-named from the project (`package.json` `name`, else folder basename), carry a **green running dot** when that pane's server is up, show `⤢` when popped; **click** to switch (the timeline follows the active pane), **double-click** to rename, `×` to close, `+ pane` to add. Live-updates whether panes change from the UI or from Claude.
|
|
130
|
+
- **Browser bar** (above the pane) — **←/→** back/forward (disabled when there's no history), **⟲** reload, **⤓** hard-reload (ignore cache), and an **address bar** showing the active pane's live URL — it follows link clicks / SPA route changes (the manager listens to `did-navigate`); accepts a bare port (`3000` → `http://localhost:3000`) or any `http(s)://` URL; Enter navigates (`⌘L` focuses).
|
|
131
|
+
- **Browser area** — the active pane: a real Chromium `WebContentsView` driven via CDP. Other panes keep running in the background (their logs keep flowing); the active one is positioned over this region and reflows when you collapse panels or resize.
|
|
132
|
+
- **Settings** (behind `⚙`, collapsed by default so the top bar stays clean) — labeled rows:
|
|
133
|
+
- **project** — dropdown of saved projects; **picking one opens it immediately** (fills cmd/cwd/url + repro steps, then dev-starts + navigates). **💾 save** snapshots the active pane as a project named by its tab label (rename on the tab).
|
|
134
|
+
- **dev** — `cmd` (blank = auto-detect) + **📁 folder** picker + `cwd`. **Auto-saved to the active pane on blur** (and on folder pick) — no separate "apply" button; after that the top-bar **▶** is pre-wired.
|
|
135
|
+
- **Side panel** (collapsible) — a segmented **logs / repro** control:
|
|
136
|
+
- **logs** — the live event list (per-source coloring, timestamps, pane tags, click-to-expand long rows, screenshot thumbnails → Radix-`Dialog` lightbox) under a sticky filter bar: substring filter + chips (`server`/`console`/`network`/`errors`/`repro`), a `↓ latest` pill, and `clear`. **Always scoped to the active pane.**
|
|
137
|
+
- **repro** — the **repro builder** (`+ step` / `run`); results land in the **logs** timeline (it auto-switches there) with per-step ✓/✗ and the correlated error list.
|
|
138
|
+
- Collapse via the `›` in the panel header; re-expand via a small **hover handle** on the right edge. Popping the active pane into its own window **fills the freed space with the timeline**. Drag the divider to resize.
|
|
139
|
+
- **Pop-out window** — `⤢` (right of the URL bar) detaches the active pane into its **own browser window with its own bar** (back/forward/reload/hard-reload/address/screenshot), driving that pane by id; the live URL tracks navigations and `⌘R` reloads the page. Closing it re-docks the pane.
|
|
140
|
+
- **Keyboard:** `⌘L` address bar · `⌘R`/`⌘⇧R` reload/hard-reload · `⌘K` clear · `⌘B` toggle panel · `⌘,` settings · `⌘1–9` switch panes.
|
|
141
|
+
|
|
142
|
+
**Per-pane projects:** each pane has its own dev server and config (cmd/cwd) — so different panes run different projects (on different ports) at once, and the controls act on whichever pane is active.
|
|
143
|
+
|
|
144
|
+
**Auto-navigate:** on dev-start (or opening a project), the cockpit watches that pane's server output and opens the first `http://localhost:PORT` it announces in the pane — no port-typing.
|
|
145
|
+
|
|
146
|
+
**Persistence & restore:** open panes (each pane's URL, project label, and dev config) are saved to `~/.devloop/panes.json` and restored on relaunch; the form state (repro steps + selected project) is saved to `~/.devloop/session.json`. Restore **does not assume a dev server is running** — a pane whose saved URL is a dev (`localhost`) URL comes back as a "press ▶ to start" placeholder (its real URL preserved), and hitting **▶** starts the server and auto-navigates.
|
|
147
|
+
|
|
148
|
+
The cockpit serves the same tools over **MCP-over-HTTP** (stateful sessions). It auto-picks a free port starting at `DEVLOOP_HTTP_PORT` (default 7333) and logs the URL. Point Claude at the running cockpit:
|
|
149
|
+
|
|
150
|
+
```sh
|
|
151
|
+
claude mcp add --transport http devloop-cockpit http://localhost:7333/mcp
|
|
152
|
+
```
|
|
153
|
+
(Only connected while `bun run app` is running.)
|
|
154
|
+
|
|
155
|
+
**Clean teardown:** closing the window (or quit / SIGTERM / SIGINT) tears down everything — the dev-server process group, all browser panes, and the HTTP server — with a hard-exit fallback if graceful quit stalls. And the dev server runs under a **parent-pid watchdog**, so even a crash/SIGKILL of the cockpit can't orphan it (no `next dev` left holding `:3000`).
|
|
156
|
+
|
|
157
|
+
Cockpit-only env: `DEVLOOP_HTTP_PORT` (default 7333), plus the shared `DEVLOOP_NET_THRESHOLD` / `DEVLOOP_ACTION_TIMEOUT` / `DEVLOOP_LOG_CAPACITY` / `DEVLOOP_HOME`.
|
|
158
|
+
|
|
159
|
+
## Project layout
|
|
160
|
+
|
|
161
|
+
```
|
|
162
|
+
src/
|
|
163
|
+
logBuffer.ts source-aware, timestamped ring buffer (+ live onPush)
|
|
164
|
+
devServer.ts runtime dev-server manager (process-group kill) + detectDevCommand
|
|
165
|
+
registry.ts persisted project registry
|
|
166
|
+
browserController.ts IBrowserController + IBrowserManager interfaces
|
|
167
|
+
browser.ts PuppeteerBrowserController (headless/stdio)
|
|
168
|
+
electronBrowser.ts ElectronBrowserController (cockpit; CDP debugger)
|
|
169
|
+
toolLayer.ts TOOLS + handleTool, bound via configureTools(deps)
|
|
170
|
+
index.ts stdio entry (Puppeteer + stdio)
|
|
171
|
+
cockpit/
|
|
172
|
+
main.ts Electron main: windows, BrowserManager, MCP-over-HTTP, lifecycle
|
|
173
|
+
browserManager.ts multi-pane manager (IBrowserManager)
|
|
174
|
+
preload.ts contextBridge IPC surface
|
|
175
|
+
renderer/ React UI — main.tsx (app) + global.d.ts (IPC types) +
|
|
176
|
+
styles.css (Tailwind v4 @theme) + index.html
|
|
177
|
+
build.ts Bun build for main/preload/renderer + Tailwind CLI step
|
|
178
|
+
```
|
|
179
|
+
|
|
180
|
+
## Test
|
|
181
|
+
|
|
182
|
+
```sh
|
|
183
|
+
bun run typecheck
|
|
184
|
+
bun run test-smoke.ts # headless Puppeteer: structured args, networkidle, repro sequence, abort
|
|
185
|
+
bun run app:selftest # headless Electron: substrate→buffer, tool layer, MCP-over-HTTP,
|
|
186
|
+
# renderer IPC, registry, multi-target panes + pop-out, auto-navigate,
|
|
187
|
+
# derived project name, per-pane dev (server-log tagging), app-scoped
|
|
188
|
+
# get_logs, inline repro builder, pane persistence/restore, teardown
|
|
189
|
+
bun run mcp-drive.ts # live smoke test: drives a RUNNING cockpit over its MCP-over-HTTP
|
|
190
|
+
# endpoint (start dev server → auto-navigate → verify the live app via
|
|
191
|
+
# browser_eval → app-scoped get_logs → screenshot). Cockpit must be up.
|
|
192
|
+
```
|
|
193
|
+
|
|
194
|
+
## Gotchas learned in the field
|
|
195
|
+
|
|
196
|
+
- **Port conflicts surface as browser 500s.** Wiring against an Expo app while another held port 8081 produced a browser-side `500`; the *server* logs showed Expo had skipped starting. Pin a free port per app — and a good example of why the unified timeline helps.
|
|
197
|
+
- **`bun run dev` spawns the real server as a grandchild.** Killing the shell orphans `next dev`/`metro`; that's why the dev server is spawned detached and stopped by process group.
|
|
198
|
+
- **Don't pass `CI=1` for interactive use** — it disables Metro watch/HMR.
|
|
199
|
+
|
|
200
|
+
## Where to take it next
|
|
201
|
+
|
|
202
|
+
- **Stdout/network parity for Puppeteer** — the Puppeteer substrate logs method/status/url; bring request/response **bodies** there too (the Electron substrate captures them).
|
|
203
|
+
- **HTTP/SSE for stdio** — a long-lived shared daemon outside the cockpit.
|
|
204
|
+
|
|
205
|
+
_Done: unified browser+server timeline · `get_logs_around` correlation · `repro` one-shot + action sequences (results rendered inline) · `waitFor: networkidle` · structured console args · bounded interaction timeouts · self-healing re-acquire (Puppeteer **and** Electron panes — recover from renderer crash) · **network request/response bodies** (capped, base64-decoded, on logged Electron entries) · project registry (with saved repro steps) · session persistence · single-window Electron cockpit — tabbed panes, collapsible toolbar + timeline, pop-out, project-named tabs · per-pane dev servers, configure-once (auto-saved) · auto-navigate from logs · pane persistence + restore (no "assume running") · **React 19 + Tailwind v4 + Radix + lucide-react** renderer · browser bar (back/forward/reload + live address) · segmented logs/repro panel · **pop-out windows with their own browser chrome** · screenshot → timeline (thumbnail + lightbox) · dev failed-state indicator · project picker (open-on-pick) + folder browse · visual repro builder · MCP-over-HTTP · clean process-group teardown + crash watchdog · Electron security-warning suppression._
|