aipeek 0.2.7 → 0.2.9
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/README.md +92 -18
- package/dist/{chunk-STYCUT23.cjs → chunk-7ALIH3JX.cjs} +622 -46
- package/dist/{chunk-37VLLZIU.js → chunk-7NJSWR7E.js} +621 -45
- package/dist/index.cjs +2 -2
- package/dist/index.d.cts +43 -0
- package/dist/index.d.ts +43 -0
- package/dist/index.js +1 -1
- package/dist/plugin.cjs +2 -2
- package/dist/plugin.js +1 -1
- package/package.json +3 -1
- package/src/babel/line-profiler.ts +190 -0
- package/src/client/client-patch.ts +332 -2
- package/src/client/client.ts +259 -44
- package/src/core/action.ts +199 -22
- package/src/core/compact.ts +2 -0
- package/src/core/detail.ts +3 -1
- package/src/core/diff.ts +55 -1
- package/src/core/emit.ts +14 -2
- package/src/core/perf.ts +250 -0
- package/src/core/types.ts +73 -0
- package/src/core/util.ts +115 -0
- package/src/server/plugin.ts +463 -52
package/README.md
CHANGED
|
@@ -1,10 +1,12 @@
|
|
|
1
1
|
# aipeek
|
|
2
2
|
|
|
3
|
-
Gives AI a peek into — and a hand on — your running browser app. Reads the UI tree (React fiber), semantic DOM, console, network, errors, and store state; drives the page (click
|
|
3
|
+
Gives AI a peek into — and a hand on — your running browser app. Reads the UI tree (React fiber), semantic DOM, console, network, errors, and store state; drives the page (click, fill, press, wait, drag/drop, clipboard, screenshot) and profiles it. All over plain-text HTTP on your Vite dev server — zero resident context cost, unlike a browser MCP whose tool schemas sit in the model's context whether used or not.
|
|
4
4
|
|
|
5
5
|
**10× faster end-to-end.** What you feel is wall-clock from prompt to done — model thinking, round-trips, all of it. Screenshot agents (Playwright + vision) pay 2–5s of pixel-parsing *every step*; aipeek reads semantic text (instant) and batches a whole interaction into one round-trip with `/chain` — the model thinks once, not N times.
|
|
6
6
|
|
|
7
|
-
It lives **inside** the open page (injected client + HMR channel), so it reads React/store internals a DOM-only driver can't, and acts on the current tab with no separate browser process. It does **not** open browsers, navigate, run headless
|
|
7
|
+
It lives **inside** the open page (injected client + HMR channel), so it reads React/store internals a DOM-only driver can't, and acts on the current tab with no separate browser process. It does **not** open browsers, navigate, or run headless — it's the dev inner loop, not E2E. For that, use Playwright. (It *can* fire trusted OS-level pointer events via `realclick` when a synthetic click won't do — see Actions.)
|
|
8
|
+
|
|
9
|
+
**Self-healing.** The transport rides the Vite HMR socket; the injected client re-announces itself on connect and polls a process-level `BOOT_ID` over HTTP, so a full dev-server restart re-handshakes automatically instead of stranding the page until a human hits ⌘R.
|
|
8
10
|
|
|
9
11
|
## Install
|
|
10
12
|
|
|
@@ -29,21 +31,31 @@ export default defineConfig({
|
|
|
29
31
|
|
|
30
32
|
## API Endpoints
|
|
31
33
|
|
|
32
|
-
All endpoints are available on your Vite dev server
|
|
34
|
+
All endpoints are available on your Vite dev server. Reads are listed cheapest-first.
|
|
33
35
|
|
|
34
36
|
| Endpoint | Description |
|
|
35
37
|
|----------|-------------|
|
|
36
|
-
| `GET /__aipeek/screen` | **State-machine projection** — `{view, modal, focus, knobs}`. Start here. |
|
|
37
|
-
| `GET /__aipeek` |
|
|
38
|
-
| `GET /__aipeek
|
|
38
|
+
| `GET /__aipeek/screen` | **State-machine projection** — `{view, modal, focus, knobs, domain}`. Each read prints a `token: tN`. Start here. |
|
|
39
|
+
| `GET /__aipeek/screen?since=tN` | **Delta** — only what moved since token `tN` (view/modal/focus + new errors/failed requests), `(no state change)` if nothing did. The cheap "what changed after I acted" read. |
|
|
40
|
+
| `GET /__aipeek` | Summary of all sections (UI, console, network, errors, state); healthy sections fold to one line, issues expand |
|
|
41
|
+
| `GET /__aipeek?full` | Full dump — UI tree + console + network + errors + state |
|
|
42
|
+
| `GET /__aipeek/{section}` | Detail for a section (`ui`, `console`, `network`, `errors`, `state`, `profile`) |
|
|
39
43
|
| `GET /__aipeek/{section}/{index}` | Detail for a specific item in a section |
|
|
40
44
|
| `GET /__aipeek/{section}?full` | Full detail (no truncation) |
|
|
41
45
|
| `GET /__aipeek/dom[?scope=Name\|?sel=css]` | Semantic DOM — UI as text (see below) |
|
|
42
46
|
| `GET /__aipeek/query?sel=css` | Read-side twin of `sel=`: a selector's live `count` + each match's `text`/`visible`/`attrs` (role, `data-state`, `aria-*`/`data-*`, value, disabled). Per-element assertions without `/eval`. |
|
|
47
|
+
| `GET /__aipeek/check` | Pass/fail health check (no console errors / no uncaught errors / no failed requests / UI rendered). `417` when any assertion fails. Use after a code change. |
|
|
48
|
+
| `GET /__aipeek/console` · `/network` · `/errors` · `/state` | Section shortcuts (same as `/{section}`) |
|
|
49
|
+
| `GET /__aipeek/profile` | Performance profiler — which component/function is burning frames. `/profile/reset` clears the window; `/profile/diff` gives a baseline→after IMPROVED/REGRESSED verdict |
|
|
43
50
|
| `GET /__aipeek/{action}?...` | Drive the page (see Actions) |
|
|
51
|
+
| `GET /__aipeek/tabs` | List live tabs (id, visible/background, title) for `?tab=` addressing |
|
|
52
|
+
| `GET /__aipeek/timeline` | Interleaved action stream across **all** tabs in time order — who clicked what, and the resulting UI change |
|
|
44
53
|
| `POST /__aipeek/chain` | Run a JSON array of actions in one round-trip (see Actions) |
|
|
45
54
|
| `GET\|POST /__aipeek/eval` | Run arbitrary JS in the page (`?code=` or POST body); returns the result. Escape hatch for what typed endpoints can't do — for count/text/state/attr checks reach for `/query` first. |
|
|
46
55
|
|
|
56
|
+
**Secret redaction.** Password inputs and API-key/token fields render as `‹redacted N chars›`
|
|
57
|
+
across `/dom`, `/query`, and `/screen` — the length stays visible, the value doesn't.
|
|
58
|
+
|
|
47
59
|
### Perception layers — UI as text, not pixels
|
|
48
60
|
|
|
49
61
|
For a model, the UI's optimal representation is its **semantics**, not rendered pixels.
|
|
@@ -52,8 +64,10 @@ information is already textual in the DOM. aipeek exposes four layers, cheapest
|
|
|
52
64
|
|
|
53
65
|
- **`/screen`** — state-machine projection. The whole UI collapsed to what a human reads
|
|
54
66
|
off a washing-machine panel: `view` (which area), `modal` (is something covering it),
|
|
55
|
-
`focus`,
|
|
56
|
-
and when a modal is open only its subtree counts)
|
|
67
|
+
`focus`, `knobs` (the few *reachable* controls now — repeated rows fold to `source ×N`,
|
|
68
|
+
and when a modal is open only its subtree counts), and `domain` (the app's own state
|
|
69
|
+
variables, if it sets `window.__AIPEEK_SCREEN__`). A handful of lines. *Start here.*
|
|
70
|
+
Append `?full` for untruncated output; pass `?since=tN` for just the delta.
|
|
57
71
|
- **`/ui`** — React component tree. Full structure. Deep-dive when `/screen` isn't enough.
|
|
58
72
|
- **`/dom`** — semantic DOM: `tag·role·semantic-class·data-*·state` per element, with
|
|
59
73
|
Tailwind/atomic noise stripped and each line tagged with its **source location**
|
|
@@ -79,43 +93,103 @@ as the component boundary. Each line's `@File.tsx:line` then tells you exactly w
|
|
|
79
93
|
| Endpoint | Params | Effect |
|
|
80
94
|
|----------|--------|--------|
|
|
81
95
|
| `/click` | `sel=` (CSS) or `text=` (visible text) | dispatch a real click |
|
|
82
|
-
| `/fill` | `sel=`/`text=` + `value=` | set value on input/textarea/select; **contenteditable** via `execCommand` |
|
|
96
|
+
| `/fill` | `sel=`/`text=` + `value=` | set value on input/textarea/select via React's native value setter (fires onChange on **controlled** inputs); **contenteditable** via `execCommand` |
|
|
83
97
|
| `/press` | `key=` (e.g. `Enter`, `Control+a`) | keydown/keyup on the focused element |
|
|
84
|
-
| `/wait` | `text=`/`sel=`, `timeout=` (ms, default 5000) | poll until it appears; 504 on timeout |
|
|
98
|
+
| `/wait` | `text=`/`sel=`, `timeout=` (ms, default 5000), `gone=1` | poll until it appears (or, with `gone=1`, disappears); 504 on timeout |
|
|
99
|
+
| `/realclick` | `sel=`/`text=`, `button=left\|right` | **trusted** OS-level click via the extension's CDP channel — for popups/context-menus a synthetic click can't open. Electron fires it in-process |
|
|
100
|
+
| `/scrollIntoView` | `sel=`/`text=` | scroll a target into view (off-screen virtualized rows) |
|
|
101
|
+
| `/drag` | `sel=` + `to=` | synthetic pointer drag, source → destination (steps past dnd-kit's activation distance) |
|
|
102
|
+
| `/drop` | `sel=` + `files=a.png,b.pdf` | fire a file-drop (`DataTransfer`) on a target (synthetic Files trigger handlers, no byte content) |
|
|
103
|
+
| `/clipboard` | `mode=read\|write`, `value=` | seed or read the clipboard (needs the tab focused) |
|
|
85
104
|
| `/screenshot` | `sel=`, `out=` | DOM→PNG into `.aipeek/`; skips cross-origin/broken images |
|
|
86
|
-
| `POST /chain` | JSON array of
|
|
105
|
+
| `POST /chain` | JSON array of steps | run in sequence, settle between steps, stop on first failure |
|
|
106
|
+
|
|
107
|
+
`click`/`fill`/`press` **settle the DOM and append `--- changed ---`** — only the state-machine
|
|
108
|
+
transition this action caused (`view: a → b`, `modal: opened X`, `focus: …`) plus any new
|
|
109
|
+
errors/failed requests, not a fresh snapshot. `(no state change)` means nothing moved. You read
|
|
110
|
+
the delta and drill into `/ui` or `/dom` for detail only if you need it. On a target miss,
|
|
111
|
+
`/click` and `/fill` return the reachable clickable elements (clipped to the open modal's
|
|
112
|
+
subtree) so you can re-target.
|
|
87
113
|
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
114
|
+
They also append a **`--- recent actions ---` timeline** — the semantic page actions in order
|
|
115
|
+
(`T`=trusted human / `S`=synthetic aipeek), each with its resulting UI change, your own action
|
|
116
|
+
bracketed by `你当前的行为` dividers. If the user manipulates the page concurrently (closes a
|
|
117
|
+
dialog you opened), their action shows up in your next response — conflict surfaces automatically.
|
|
91
118
|
|
|
92
119
|
A CSS `sel=` with non-ASCII or quotes/brackets must be URL-encoded, or the query parser
|
|
93
120
|
mangles it: `curl -G .../click --data-urlencode 'sel=button[title="知识库"]'`.
|
|
94
121
|
|
|
95
|
-
**
|
|
122
|
+
**Multiple tabs.** Every read/drive command takes `?tab=<id>` to address one tab — including
|
|
123
|
+
a **background** one (drive the Chat tab while the user reads a different tab; synthetic events
|
|
124
|
+
and Electron `sendInputEvent` don't need foreground). `GET /tabs` lists the live ids. One tab
|
|
125
|
+
open → omit `?tab=`, it just works. Several open + no `?tab=` → the command returns `409` + the
|
|
126
|
+
tab list instead of randomly hitting one; pick an id and retry with `?tab=`. `GET /timeline`
|
|
127
|
+
interleaves every tab's actions in time order, so an A/B comparison (drive A, watch B react) is
|
|
128
|
+
one read.
|
|
129
|
+
|
|
130
|
+
**Federation across dev servers.** When several dev servers run at once (a micro-frontend, a
|
|
131
|
+
separate front/back, a teammate's machine), any command takes `?host=<host:port>` to reach a
|
|
132
|
+
*sibling* aipeek. The plugin you curl reverse-proxies the request server-side (no browser, no
|
|
133
|
+
CORS): `…/screen?host=localhost:5174` reads the app on :5174; combine with `?tab=` to point at
|
|
134
|
+
one tab over there. No registry — you name the peer, so list its tabs with `/tabs?host=…` first.
|
|
135
|
+
|
|
136
|
+
**Trusted input.** A control tagged `{needs-trusted?}` in `/screen` or `/dom` opens a popup
|
|
137
|
+
(`aria-haspopup`) a synthetic click may not trigger — use `/realclick` on it. Right-click menus
|
|
138
|
+
carry no DOM marker; reach for `/realclick` with `button=right` there. (Requires the browser
|
|
139
|
+
extension for a plain tab; Electron fires trusted events in-process.)
|
|
140
|
+
|
|
141
|
+
**Chain** packs a whole interaction into one round-trip. An `assert` step is a mid-chain judge —
|
|
142
|
+
`{type:"assert", screen, equals}` checks an app domain variable (from `window.__AIPEEK_SCREEN__`),
|
|
143
|
+
or `{type:"assert", sel, equals}` an element's text — and stops the chain with `asserted X=="Y",
|
|
144
|
+
actual "Z"` on mismatch:
|
|
96
145
|
|
|
97
146
|
```bash
|
|
98
147
|
curl -X POST localhost:5195/__aipeek/chain -d '[
|
|
99
148
|
{"type":"click","sel":"button[title=\"知识库\"]"},
|
|
100
149
|
{"type":"wait","text":"Done"},
|
|
101
150
|
{"type":"fill","sel":"textarea","value":"hi"},
|
|
151
|
+
{"type":"assert","screen":"streaming","equals":"false"},
|
|
102
152
|
{"type":"press","key":"Enter"}
|
|
103
153
|
]'
|
|
104
154
|
```
|
|
105
155
|
|
|
156
|
+
**Connection diagnostics.** When a read can't get an answer, aipeek never returns a bare "no
|
|
157
|
+
tab" — it distinguishes, and tells you the fix: *tab connected but the handler hung* (check
|
|
158
|
+
`/console`), *tab closed / mid self-heal* (retry), *pages connected via HMR but the client
|
|
159
|
+
didn't load* (wrong vite server / `vite preview` / production build — check the port), or *no
|
|
160
|
+
browser pointed here at all* (open the app). `/profile` on a backgrounded tab says so too —
|
|
161
|
+
the browser throttles rAF for hidden tabs, so bring it foreground ~2s; it's the only read that
|
|
162
|
+
needs foreground.
|
|
163
|
+
|
|
106
164
|
## CLI
|
|
107
165
|
|
|
166
|
+
A thin wrapper over the HTTP endpoints, with colored `check` output:
|
|
167
|
+
|
|
108
168
|
```bash
|
|
109
|
-
npx aipeek #
|
|
110
|
-
npx aipeek
|
|
169
|
+
npx aipeek # full summary (localhost:5173)
|
|
170
|
+
npx aipeek check # health check (pass/fail)
|
|
171
|
+
npx aipeek console # console logs
|
|
172
|
+
npx aipeek network/0 # detail for one network request
|
|
173
|
+
npx aipeek errors/1 --full # untruncated error detail
|
|
174
|
+
npx aipeek --port=5195 # custom port
|
|
111
175
|
```
|
|
112
176
|
|
|
113
177
|
## Store Registration (optional)
|
|
114
178
|
|
|
115
|
-
Register MobX/other stores
|
|
179
|
+
Register MobX/other stores so their snapshots appear in the `state` section:
|
|
116
180
|
|
|
117
181
|
```ts
|
|
118
182
|
window.__AIPEEK_STORES__ = { myStore, anotherStore }
|
|
119
183
|
```
|
|
120
184
|
|
|
121
185
|
State is snapshotted on demand (depth-limited, bounded) and included in the `<state>` section.
|
|
186
|
+
|
|
187
|
+
## Domain projection (optional)
|
|
188
|
+
|
|
189
|
+
Let aipeek read your app's own state machine — the variables show up in `/screen`'s `domain`
|
|
190
|
+
block, in every `--- changed ---` diff, and are assertable in a chain (`{type:"assert", screen,
|
|
191
|
+
equals}`). Opt in by exposing a snapshot function:
|
|
192
|
+
|
|
193
|
+
```ts
|
|
194
|
+
window.__AIPEEK_SCREEN__ = () => ({ view, streaming, modal: openDialog })
|
|
195
|
+
```
|