page-harness 0.0.0-stage → 0.1.1

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.
Files changed (90) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +174 -2
  3. package/README.zh-CN.md +175 -0
  4. package/THIRD_PARTY_LICENSES.md +2925 -0
  5. package/dist/cli.js +5123 -0
  6. package/dist/cli.js.map +1 -0
  7. package/dist/client/app/assets/astro-BZ97kZFP.js +1 -0
  8. package/dist/client/app/assets/bash-BiGZg9Om.js +1 -0
  9. package/dist/client/app/assets/c-DWGmR78Y.js +1 -0
  10. package/dist/client/app/assets/c-SSSlmE39.js +1 -0
  11. package/dist/client/app/assets/core-R68uHlmr.js +12 -0
  12. package/dist/client/app/assets/cpp-CJIxL8VH.js +1 -0
  13. package/dist/client/app/assets/cpp-Cts1PyIS.js +1 -0
  14. package/dist/client/app/assets/csharp-dmMxLVLW.js +1 -0
  15. package/dist/client/app/assets/css-BGIxQoYs.js +1 -0
  16. package/dist/client/app/assets/css-DUjzNfgv.js +1 -0
  17. package/dist/client/app/assets/dart-CHZY9Xhz.js +1 -0
  18. package/dist/client/app/assets/diff-woXpYk--.js +1 -0
  19. package/dist/client/app/assets/dockerfile-IyjqRm3v.js +1 -0
  20. package/dist/client/app/assets/engine-javascript-CYgs891E.js +141 -0
  21. package/dist/client/app/assets/github-dark-C-LZuMrd.js +1 -0
  22. package/dist/client/app/assets/github-light-EUqPIrTm.js +1 -0
  23. package/dist/client/app/assets/go-rLFTqkRN.js +1 -0
  24. package/dist/client/app/assets/graphql-DAzSVzvB.js +1 -0
  25. package/dist/client/app/assets/graphql-DEIChqxY.js +1 -0
  26. package/dist/client/app/assets/history-DTQ4t3U_.js +1 -0
  27. package/dist/client/app/assets/history-DfMi5ceT.css +1 -0
  28. package/dist/client/app/assets/html-DSw1lh5G.js +1 -0
  29. package/dist/client/app/assets/html-WQKKblVZ.js +1 -0
  30. package/dist/client/app/assets/html2canvas-Dn6bw578.js +5 -0
  31. package/dist/client/app/assets/index.es-pLGXQA_f.js +5 -0
  32. package/dist/client/app/assets/ini-B5eOa1yu.js +1 -0
  33. package/dist/client/app/assets/inter-cyrillic-ext-wght-normal-BOeWTOD4.woff2 +0 -0
  34. package/dist/client/app/assets/inter-cyrillic-wght-normal-DqGufNeO.woff2 +0 -0
  35. package/dist/client/app/assets/inter-greek-ext-wght-normal-DlzME5K_.woff2 +0 -0
  36. package/dist/client/app/assets/inter-greek-wght-normal-CkhJZR-_.woff2 +0 -0
  37. package/dist/client/app/assets/inter-latin-ext-wght-normal-DO1Apj_S.woff2 +0 -0
  38. package/dist/client/app/assets/inter-latin-wght-normal-Dx4kXJAl.woff2 +0 -0
  39. package/dist/client/app/assets/inter-vietnamese-wght-normal-CBcvBZtf.woff2 +0 -0
  40. package/dist/client/app/assets/java-BtIQNnN0.js +1 -0
  41. package/dist/client/app/assets/java-DxlWrXwe.js +1 -0
  42. package/dist/client/app/assets/javascript-BzbQS41l.js +1 -0
  43. package/dist/client/app/assets/javascript-Yelw-5ZN.js +1 -0
  44. package/dist/client/app/assets/jetbrains-mono-cyrillic-wght-normal-D73BlboJ.woff2 +0 -0
  45. package/dist/client/app/assets/jetbrains-mono-greek-wght-normal-Bw9x6K1M.woff2 +0 -0
  46. package/dist/client/app/assets/jetbrains-mono-latin-ext-wght-normal-DBQx-q_a.woff2 +0 -0
  47. package/dist/client/app/assets/jetbrains-mono-latin-wght-normal-B9CIFXIH.woff2 +0 -0
  48. package/dist/client/app/assets/jetbrains-mono-vietnamese-wght-normal-Bt-aOZkq.woff2 +0 -0
  49. package/dist/client/app/assets/json-B88Oqtu5.js +1 -0
  50. package/dist/client/app/assets/json-CaljABEy.js +1 -0
  51. package/dist/client/app/assets/jsonc-CYpm1nAK.js +1 -0
  52. package/dist/client/app/assets/jspdf.es.min-OsUFBIXu.js +79 -0
  53. package/dist/client/app/assets/jsx-D3KKoC5L.js +1 -0
  54. package/dist/client/app/assets/jsx-hEbJ82HF.js +1 -0
  55. package/dist/client/app/assets/kotlin-DhhofPvG.js +1 -0
  56. package/dist/client/app/assets/less-DVTAwKKz.js +1 -0
  57. package/dist/client/app/assets/markdown-BYOwaDjH.js +1 -0
  58. package/dist/client/app/assets/panel-5OkTcFqg.css +1 -0
  59. package/dist/client/app/assets/panel-dt4wG7is.js +2 -0
  60. package/dist/client/app/assets/php-D7kNSOJl.js +1 -0
  61. package/dist/client/app/assets/postcss-D_Wz94rJ.js +1 -0
  62. package/dist/client/app/assets/powershell-QtSnmXcu.js +1 -0
  63. package/dist/client/app/assets/python-gzcpVVnB.js +1 -0
  64. package/dist/client/app/assets/rolldown-runtime-hePW80VL.js +1 -0
  65. package/dist/client/app/assets/ruby-dXJhvJjh.js +1 -0
  66. package/dist/client/app/assets/rust-DpiIf5JO.js +1 -0
  67. package/dist/client/app/assets/scss-8bKmll0B.js +1 -0
  68. package/dist/client/app/assets/shellscript-CqHdAlCp.js +1 -0
  69. package/dist/client/app/assets/sql-BOsskAmu.js +1 -0
  70. package/dist/client/app/assets/sql-CNu3Or2W.js +1 -0
  71. package/dist/client/app/assets/svelte-CdeT-Ich.js +1 -0
  72. package/dist/client/app/assets/swift-CyEgAFGc.js +1 -0
  73. package/dist/client/app/assets/toml-CcmNWLt0.js +1 -0
  74. package/dist/client/app/assets/tsx-5f3ulCja.js +1 -0
  75. package/dist/client/app/assets/tsx-BOvsFOI6.js +1 -0
  76. package/dist/client/app/assets/typescript-Utq2Cl8c.js +1 -0
  77. package/dist/client/app/assets/typescript-j_1H8WHN.js +1 -0
  78. package/dist/client/app/assets/vue-BqdMUtqy.js +1 -0
  79. package/dist/client/app/assets/ws-B8OO397O.js +71 -0
  80. package/dist/client/app/assets/ws-BhXqMf5F.css +1 -0
  81. package/dist/client/app/assets/xml-CoDIzWF0.js +1 -0
  82. package/dist/client/app/assets/xml-WY0zc4PG.js +1 -0
  83. package/dist/client/app/assets/yaml-CxbubiML.js +1 -0
  84. package/dist/client/app/assets/yaml-DijMHRDA.js +1 -0
  85. package/dist/client/app/history.html +17 -0
  86. package/dist/client/app/panel.html +17 -0
  87. package/dist/client/page/live-overlay.js +61 -0
  88. package/dist/client/page/overlay.js +198 -0
  89. package/dist/client/page/page-control.js +205 -0
  90. package/package.json +73 -4
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Page Harness contributors
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 CHANGED
@@ -1,3 +1,175 @@
1
- # Temporary Holding Version
1
+ # page-harness
2
2
 
3
- This version is a temporary placeholder for this package. An operational version to replace this has been submitted for review and is awaiting a staged release.
3
+ [中文](./README.zh-CN.md)
4
+
5
+ Point it at any running frontend and the page gets a **Pick / Artifacts / Chat** dock, driven by the coding agent CLI already on your machine (Claude Code, Codex or Cursor Agent). No sandbox, no accounts, no changes to your app.
6
+
7
+ ```bash
8
+ npx page-harness http://localhost:5173 --project ./my-app --agent claude
9
+ # open http://127.0.0.1:17990 — it is your app, plus the dock in the bottom-right corner
10
+ ```
11
+
12
+ ## What you get
13
+
14
+ - **Pick** — click an element on the page and choose what to do with it: **Quote in chat** (selector, text and outer HTML go with your next message; Shift+click quotes directly), **Comment**, **Edit** (no-source sites) or **Live candidates**. Live: choose an element, describe the change and the agent writes 2–4 candidates straight into your source; you flip between them on the page, compare side by side, tune parameters, refine one, then accept or discard.
15
+ - **Chat** — a draggable, resizable chat window. Streaming replies with Markdown and syntax highlighting, collapsible thinking, tool cards, changed-file lists, image/file attachments (paste, drop or pick), a per-chat queue you can reorder, edit or withdraw, Stop / Retry, multiple chats, per-chat agent and model.
16
+ - **Page control** (off by default) — let the agent read the page and click, type, select and scroll on it. A banner shows when it is on; Stop takes it back at any time.
17
+ - **Artifacts** — the agent writes reports, mockups, JSON, images and other files through MCP. Preview them in tabs with version history: Markdown, sandboxed HTML (desktop / mobile widths), structured JSON, code, images; copy, download, export to PNG or PDF.
18
+ - **Notes** — comment on or edit elements in place, threaded replies, resolve and per-page approval; teammates on your network can join as reviewers with `--share`. Works on sites you have no source for.
19
+ - **Questions** — the agent can ask you multiple-choice questions or a short form and wait for the answer.
20
+ - **History** — every chat is an append-only event log on disk. Browse, search and export it at `http://127.0.0.1:17991/__ph/history`, or from the chat switcher.
21
+
22
+ ## Requirements
23
+
24
+ - Node.js 20.11 or newer.
25
+ - At least one agent CLI on `PATH`, signed in:
26
+ - [Claude Code](https://docs.anthropic.com/en/docs/claude-code) (`claude`)
27
+ - [Codex](https://github.com/openai/codex) (`codex`)
28
+ - [Cursor Agent](https://docs.cursor.com/en/cli/overview) (`cursor-agent`)
29
+
30
+ page-harness detects which ones are installed and shows them in the banner. Use `--claude-path`, `--codex-path` or `--cursor-path` for non-standard locations.
31
+
32
+ ## Usage
33
+
34
+ ```text
35
+ page-harness <frontend-url> [options]
36
+
37
+ --project <dir> Project the agent works in (default: current directory)
38
+ --agent <id> claude | codex | cursor (default: first one installed)
39
+ --model <name> Default model for new chats
40
+ --port <n> App port: the proxied frontend (default 17990)
41
+ --control-port <n> Panel / API port (default: app port + 1)
42
+ --host <addr> Listen address (default 127.0.0.1)
43
+ --allow-host <name> Extra Host header to accept (repeatable), e.g. a LAN name
44
+ --keep-host Forward the browser's Host header to the target
45
+ --open Open the browser
46
+ --live-guard-hook Install a git pre-commit hook that rejects Live candidate markers
47
+ --review / --source Force review mode (no source) or source mode; default is by host
48
+ --share Let teammates on your network join as reviewers (listens on 0.0.0.0)
49
+ --verbose / --quiet
50
+ ```
51
+
52
+ `<frontend-url>` can be a bare port (`3000`), `host:port` or a full URL. Dev-server HMR (Vite, webpack, Next.js…) keeps working through the proxy, including WebSockets.
53
+
54
+ ## How it works
55
+
56
+ ```text
57
+ browser ──► :17990 app port reverse proxy to your dev server; injects /__ph/overlay.js into HTML
58
+ └─► :17991 control port panel UI, history page, JSON API, WebSocket, MCP endpoint
59
+ │
60
+ └─ spawns the agent CLI per turn (claude -p / codex app-server / cursor-agent -p)
61
+ with a local MCP server for page_*, artifacts, questions and Live
62
+ ```
63
+
64
+ - The overlay lives in a Shadow DOM so its styles and your app's styles do not leak into each other. The chat and artifacts windows are iframes from the control port and talk to the page over a `MessagePort` handshake.
65
+ - Each chat keeps the agent's session id, so follow-up messages resume the same agent session. Messages sent while a turn runs are queued per chat.
66
+ - Agents get a short system prompt describing the page context, the MCP tools and the Live protocol. Nothing is written to your project except `.page-harness/` (ignored by git through its own `.gitignore`) and, during Live, temporary candidate markup.
67
+
68
+ ### MCP tools
69
+
70
+ | Tool | Purpose |
71
+ | --- | --- |
72
+ | `page_state`, `page_click`, `page_input`, `page_select`, `page_scroll` | Read and operate the page (only while the user allows page control) |
73
+ | `write_artifact`, `read_artifact`, `list_artifacts`, `set_artifact_preview` | Versioned artifacts in `.page-harness/artifacts` |
74
+ | `ask_question`, `ask_form` | Ask the user and block until they answer (30 minutes) |
75
+ | `live_update` | Report Live candidates as ready / done / failed |
76
+ | `live_patch_variants` | Review mode only: Live candidates as sets of page changes |
77
+ | `page_note`, `list_review`, `reply_review`, `resolve_review`, `remove_review` | Pin notes on the page, read people's feedback, answer and resolve it |
78
+ | `page_patch` | Review mode only: temporary text, CSS or image change on an element |
79
+
80
+ ### Live candidates
81
+
82
+ For a Live request the agent edits your source so that the element is wrapped like this:
83
+
84
+ ```html
85
+ <div data-ph-live="L1a2b3c4" style="display:contents">
86
+ <h1 data-ph-variant="0" hidden>original</h1>
87
+ <h1 data-ph-variant="1" data-ph-variant-label="Bold">…</h1>
88
+ <h1 data-ph-variant="2" data-ph-variant-label="Soft" hidden
89
+ data-ph-params='[{"name":"gap","type":"range","min":0,"max":40,"unit":"px"}]'>…</h1>
90
+ </div>
91
+ ```
92
+
93
+ Your dev server reloads, the overlay finds the wrapper and takes over showing and hiding variants (parameters are applied as `--ph-<name>` CSS variables on the wrapper). Accepting asks the agent to keep the chosen variant and remove the wrapper; discarding restores the original. After every Live or file-editing turn page-harness scans the working tree (`git grep`) and warns if markers were left behind; `--live-guard-hook` also blocks commits that contain them.
94
+
95
+ ### Review mode (no source)
96
+
97
+ When the target is a public site (anything that is not localhost, a private IP or a LAN name) there is no source to edit, so page-harness starts in review mode. Force it either way with `--review` / `--source`.
98
+
99
+ - The agent is told not to touch files or build mockups. `write_artifact` only accepts reports (`.md`, `.txt`, `.json`, `.csv`, images); HTML/SVG mockups are refused.
100
+ - Findings go on the page instead: `page_note` pins a numbered comment (issue / suggestion / info) to an element, and `page_patch` changes its text, CSS or image (`src` / `alt`) so you see the suggestion in place. There is no HTML patching.
101
+ - Both tools check the selector against your open page first (no page-control permission needed), so a wrong selector comes back to the agent instead of silently pinning nothing.
102
+ - People can do the same by hand: **Pick** an element, then **Comment** to leave a note, or **Edit** to change its text, color, background, font size and weight, padding, radius or image with a live preview and saves it as a change.
103
+ - **Live** works here too, as page changes: the agent renders 2–4 candidates with `live_patch_variants`, each a set of text / CSS / image changes on up to 12 elements. Switch, peek at the original and refine them on the page; accepting keeps the chosen candidate as change items in Notes, discarding puts the page back. There is no side-by-side view, no parameters and no Insert, since those need the source.
104
+
105
+ ### Notes, threads and approvals
106
+
107
+ Notes work in both modes; hand edits and `page_patch` only without source.
108
+
109
+ - Notes belong to the page they were made on (path without hash, trailing slash or tracking parameters). **This page** shows the current page; **All** groups every page, and clicking a note on another page goes there and opens it. The dock badge counts open notes on the current page and shows a dot when other pages have some.
110
+ - Each pin opens a thread: author, replies, Resolve / Reopen, compare a change with the original, quote the element into the chat (owner) and delete (author or owner).
111
+ - **Approve** / **Request changes** records a verdict per page and person.
112
+ - Pins are re-attached with a fingerprint (tag, id, stable classes, key attributes, text and its context) when the selector breaks after a re-render or a redesign. If nothing fits well enough the note is shown as *not found on the page* instead of being pinned to the wrong element. Changes are re-applied after reloads and re-renders; nothing is sent to the site.
113
+ - The agent sees people's notes with `list_review`, answers with `reply_review` and closes them with `resolve_review` plus a summary. It can only remove its own notes.
114
+ - **Export** writes `page-review.md` (notes, before → after text, CSS, replies, approvals) to Artifacts.
115
+
116
+ ### Team review (`--share`)
117
+
118
+ ```bash
119
+ npx page-harness https://example.com --share
120
+ # Share http://192.168.1.20:17991/__ph/join?t=…
121
+ ```
122
+
123
+ Send the Share link (also in Settings → Team) to teammates on the same network. Opening it gives them a reviewer session for that browser; they enter a display name once and then see the page with everyone's notes and changes in real time. Reviewers can comment, edit (review mode), reply, resolve and approve. They cannot run or see the agent, chats, history, uploads or page control. Other machines without the link get `403`.
124
+
125
+ ## Security model
126
+
127
+ There is no login: it is a local developer tool and binds to `127.0.0.1` by default. Internally every surface is locked down:
128
+
129
+ - **Two origins.** Your app (and any script on it) runs on the app port; the panel, API and agent endpoints are on the control port, so page scripts cannot read them.
130
+ - **Owner and reviewers.** Without `--share` only this machine can connect and everything behaves as one user. With `--share`, the owner session is only issued to loopback requests; other machines need the invite link, which sets signed (HMAC) reviewer cookies for the panel and the app port; without them both ports answer `403`. Reviewers are limited to the review actions on the WebSocket and to reading artifacts; everything else returns `403`. Name the owner in Settings → Team.
131
+ - **Session cookie.** The panel gets an `HttpOnly`, `SameSite=Strict` cookie scoped to `/__ph`; the cookie is stripped before requests are forwarded to your dev server.
132
+ - **API.** Reads require a same-origin `Sec-Fetch-Site`; writes require a matching `Origin` plus a custom `X-PH` header. The WebSocket checks the cookie and `Origin`.
133
+ - **MCP.** Every agent turn gets its own bearer token (passed through an environment variable or a private temp file, never on the command line) that is revoked when the turn ends. Browser requests to the MCP endpoint are rejected.
134
+ - **Host allow-list.** Requests with unexpected `Host` headers get `421` (DNS rebinding). Add names with `--allow-host` if you expose it on a LAN.
135
+ - **Framing.** The panel may only be framed by the app origin; the history page cannot be framed at all.
136
+ - **Artifacts.** HTML and SVG artifacts are served with a `sandbox` CSP and shown in iframes without `allow-same-origin`.
137
+ - **Page control** is opt-in per browser, shown with a banner, and password values are never sent to the agent.
138
+
139
+ Agents run with their CLI's non-interactive permissions (e.g. Claude Code `--permission-mode bypassPermissions`, Codex with approvals off, Cursor Agent `--force`) inside `--project`. Only point page-harness at projects you would let the agent edit.
140
+
141
+ ## Data
142
+
143
+ Everything lives in `<project>/.page-harness/`:
144
+
145
+ ```text
146
+ chats/<id>/meta.json title, agent, model, session id
147
+ chats/<id>/events.jsonl append-only event log (prompts, deltas, tools, results)
148
+ artifacts/<name>/v<n> artifact versions + index.json
149
+ uploads/ attachments
150
+ live.json Live sessions
151
+ review.json notes, page changes, replies and approvals
152
+ team.json invite token, signing key and reviewer names (mode 600)
153
+ ```
154
+
155
+ Delete the folder to start over.
156
+
157
+ ## Development
158
+
159
+ ```bash
160
+ npm install
161
+ npm run dev # rebuild client bundles and the CLI on change
162
+ node dist/cli.js http://localhost:5173 --project ../some-app
163
+
164
+ npm run lint
165
+ npm run typecheck
166
+ npm test # unit + server integration tests (scripted agent)
167
+ npx playwright install chromium
168
+ npm run build && npm run test:e2e # browser tests against a fixture app
169
+ ```
170
+
171
+ `PH_FAKE_AGENT=1` adds a scripted `fake` agent used by the tests; it drives the real MCP endpoint, so the whole pipeline is exercised without an LLM.
172
+
173
+ ## License
174
+
175
+ [MIT](./LICENSE). Bundled third-party code is listed in [THIRD_PARTY_LICENSES.md](./THIRD_PARTY_LICENSES.md).
@@ -0,0 +1,175 @@
1
+ # page-harness
2
+
3
+ [English](./README.md)
4
+
5
+ 给任意正在运行的前端地址,页面上就多出 **取点 / 产物 / 对话** 底栏,由你本机已有的编码 Agent CLI(Claude Code、Codex、Cursor Agent)驱动。不需要沙箱、不需要账号、不用改你的项目代码。
6
+
7
+ ```bash
8
+ npx page-harness http://localhost:5173 --project ./my-app --agent claude
9
+ # 打开 http://127.0.0.1:17990 —— 页面就是你的应用,右下角多出底栏
10
+ ```
11
+
12
+ ## 功能
13
+
14
+ - **取点(Pick)**:点选页面元素后选择要做什么:**引用到对话**(选择器、文本、outerHTML 附到下一条消息;Shift+点击直接引用)、**评论**、**编辑**(没有源码的站点)或 **Live 候选**。Live:选中元素、描述改动,Agent 直接在源码里写出 2–4 个候选;你在页面上轮换、并排对比、调参数、细化某一个,最后采用或放弃。
15
+ - **对话(Chat)**:可拖动、可缩放的对话窗。流式回复,Markdown 与代码高亮,可折叠的思考过程,工具卡片,改动文件列表,图片/文件附件(粘贴、拖入、选择),每个会话独立的排队(可拖动排序、编辑、撤回),停止 / 重试,多会话,每个会话可选 Agent 与模型。
16
+ - **页面控制**(默认关闭):允许 Agent 读取页面并点击、输入、选择、滚动。开启时页面顶部有横幅,随时可以停止。
17
+ - **产物(Artifacts)**:Agent 通过 MCP 写报告、原型、JSON、图片等文件。标签页预览并保留版本历史:Markdown、沙箱 HTML(桌面 / 手机宽度)、结构化 JSON、代码、图片;可复制、下载、导出 PNG / PDF。
18
+ - **意见**:在元素上评论或直接修改、讨论串回复、解决与按页面审批;加 `--share` 后同一网络的同事可以作为评审者加入。没有源码的站点也能用。
19
+ - **提问**:Agent 可以向你发选择题或小表单,等你回答后继续。
20
+ - **聊天记录**:每个会话都是磁盘上只追加的事件日志。在 `http://127.0.0.1:17991/__ph/history` 或会话切换菜单里浏览、搜索、导出。
21
+
22
+ ## 环境要求
23
+
24
+ - Node.js 20.11 及以上。
25
+ - `PATH` 上至少有一个已登录的 Agent CLI:
26
+ - [Claude Code](https://docs.anthropic.com/en/docs/claude-code)(`claude`)
27
+ - [Codex](https://github.com/openai/codex)(`codex`)
28
+ - [Cursor Agent](https://docs.cursor.com/en/cli/overview)(`cursor-agent`)
29
+
30
+ 启动时会自动检测已安装的 CLI 并在终端里列出。不在默认位置时用 `--claude-path` / `--codex-path` / `--cursor-path` 指定。
31
+
32
+ ## 用法
33
+
34
+ ```text
35
+ page-harness <前端地址> [选项]
36
+
37
+ --project <dir> Agent 工作的项目目录(默认当前目录)
38
+ --agent <id> claude | codex | cursor(默认第一个已安装的)
39
+ --model <name> 新会话的默认模型
40
+ --port <n> 应用端口:代理后的前端(默认 17990)
41
+ --control-port <n> 面板 / API 端口(默认应用端口 + 1)
42
+ --host <addr> 监听地址(默认 127.0.0.1)
43
+ --allow-host <name> 额外允许的 Host 头(可重复),比如局域网域名
44
+ --keep-host 把浏览器的 Host 头原样转发给目标
45
+ --open 自动打开浏览器
46
+ --live-guard-hook 安装 git pre-commit 钩子,拒绝提交 Live 候选标记
47
+ --review / --source 强制评审模式(无源码)或源码模式;默认按域名判断
48
+ --share 允许同一网络的同事以评审者身份加入(监听 0.0.0.0)
49
+ --verbose / --quiet
50
+ ```
51
+
52
+ `<前端地址>` 可以是端口(`3000`)、`host:port` 或完整 URL。开发服务器的热更新(Vite、webpack、Next.js 等)通过代理照常工作,包括 WebSocket。
53
+
54
+ ## 原理
55
+
56
+ ```text
57
+ 浏览器 ──► :17990 应用端口 反向代理到你的开发服务器,在 HTML 里注入 /__ph/overlay.js
58
+ └─► :17991 控制端口 面板 UI、聊天记录页、JSON API、WebSocket、MCP
59
+ │
60
+ └─ 每一轮对话启动一次 Agent CLI(claude -p / codex app-server / cursor-agent -p),
61
+ 并接入本地 MCP:page_*、产物、提问、Live
62
+ ```
63
+
64
+ - 底栏在 Shadow DOM 里,和你的样式互不影响。对话窗、产物窗是来自控制端口的 iframe,通过 `MessagePort` 握手和页面通信。
65
+ - 每个会话记住 Agent 的 session id,后续消息续接同一个 Agent 会话;一轮还在跑时发的消息进入该会话的队列。
66
+ - Agent 会收到一段简短的系统提示,说明页面上下文、MCP 工具和 Live 协议。除了 `.page-harness/`(自带 `.gitignore`)和 Live 期间的临时候选标记外,不会往你的项目里写东西。
67
+
68
+ ### MCP 工具
69
+
70
+ | 工具 | 用途 |
71
+ | --- | --- |
72
+ | `page_state`、`page_click`、`page_input`、`page_select`、`page_scroll` | 读取与操作页面(仅在用户允许页面控制时可用) |
73
+ | `write_artifact`、`read_artifact`、`list_artifacts`、`set_artifact_preview` | 带版本的产物,存放在 `.page-harness/artifacts` |
74
+ | `ask_question`、`ask_form` | 向用户提问并等待回答(最长 30 分钟) |
75
+ | `live_update` | 上报 Live 候选的 ready / done / failed |
76
+ | `live_patch_variants` | 仅评审模式:以一组页面修改的形式给出 Live 候选 |
77
+ | `page_note`、`list_review`、`reply_review`、`resolve_review`、`remove_review` | 在页面上钉意见,读取大家的反馈,回复并标记解决 |
78
+ | `page_patch` | 仅评审模式:对元素做临时的文字、CSS 或图片修改 |
79
+
80
+ ### Live 候选
81
+
82
+ 收到 Live 请求时,Agent 在源码里把目标元素包成这样:
83
+
84
+ ```html
85
+ <div data-ph-live="L1a2b3c4" style="display:contents">
86
+ <h1 data-ph-variant="0" hidden>原版</h1>
87
+ <h1 data-ph-variant="1" data-ph-variant-label="Bold">…</h1>
88
+ <h1 data-ph-variant="2" data-ph-variant-label="Soft" hidden
89
+ data-ph-params='[{"name":"gap","type":"range","min":0,"max":40,"unit":"px"}]'>…</h1>
90
+ </div>
91
+ ```
92
+
93
+ 开发服务器热更新后,底栏找到这个包裹层并接管各候选的显示与隐藏(参数以 `--ph-<name>` CSS 变量写在包裹层上)。采用时让 Agent 只保留选中的候选并去掉包裹层;放弃则还原原版。每次 Live 或改了文件的回合结束后,会用 `git grep` 扫描工作区,发现遗留标记就提醒;`--live-guard-hook` 还会拦住含标记的提交。
94
+
95
+ ### 评审模式(没有源码)
96
+
97
+ 目标是公网站点(不是 localhost、内网 IP 或局域网主机名)时,手里没有源码可改,page-harness 会以评审模式启动;也可以用 `--review` / `--source` 强制指定。
98
+
99
+ - Agent 会被告知不要改文件、不要做原型。`write_artifact` 只接受报告类文件(`.md`、`.txt`、`.json`、`.csv`、图片),HTML/SVG 原型会被拒绝。
100
+ - 结论直接落在页面上:`page_note` 在元素上钉一条带编号的意见(问题 / 建议 / 说明);`page_patch` 修改元素的文字、CSS 或图片(`src` / `alt`),原地看到效果。不支持改 HTML 结构。
101
+ - 两个工具都会先拿选择器到你打开的页面上核对(不需要页面控制权限),选择器写错会直接反馈给 Agent,而不是钉了个空。
102
+ - 人也可以直接动手:用「取点」选中元素,点「评论」留言,或点「编辑」实时预览地修改文字、颜色、背景、字号、字重、内边距、圆角或图片,保存为一条修改。
103
+ - **Live** 在这里也能用,候选是页面修改:Agent 用 `live_patch_variants` 给出 2–4 个候选,每个候选是最多 12 个元素上的文字 / CSS / 图片修改。在页面上切换、看原版、细化;采用后选中的候选存为「意见」里的修改条目,放弃则还原页面。没有并排对比、参数调节和插入,这些需要源码。
104
+
105
+ ### 意见、讨论与审批
106
+
107
+ 意见在两种模式下都能用;手动编辑和 `page_patch` 只在没有源码时可用。
108
+
109
+ - 意见属于创建它的页面(路径,忽略 hash、末尾斜杠和统计参数)。「本页」只看当前页面;「全部」按页面分组,点其他页面的条目会跳过去并打开它。底栏角标只计当前页面未解决的意见,其他页面有意见时显示小圆点。
110
+ - 每个图钉打开一个讨论串:作者、回复、解决 / 重新打开、修改与原版对比、引用到对话(所有者)、删除(作者或所有者)。
111
+ - 「通过」/「需修改」按页面、按人记录审批结论。
112
+ - 页面重新渲染或改版导致选择器失效时,用指纹(标签、id、稳定的 class、关键属性、文字及上下文)重新定位。没有足够匹配的元素时显示「页面上找不到了」,而不是钉到错误的元素上。刷新和重新渲染后修改会自动重新套上,不会向站点发送任何东西。
113
+ - Agent 用 `list_review` 看到大家的意见,用 `reply_review` 回复,用 `resolve_review` 附上说明后关闭;它只能删除自己的条目。
114
+ - 「导出」把 `page-review.md`(意见、文字前后对比、CSS、回复、审批)写入产物。
115
+
116
+ ### 团队评审(`--share`)
117
+
118
+ ```bash
119
+ npx page-harness https://example.com --share
120
+ # Share http://192.168.1.20:17991/__ph/join?t=…
121
+ ```
122
+
123
+ 把 Share 链接(设置 → 团队里也有)发给同一网络的同事。打开后这个浏览器获得评审者身份,填一次显示名,就能实时看到页面上所有人的意见和修改。评审者可以评论、编辑(评审模式)、回复、解决、审批;不能使用或看到 Agent、对话、聊天记录、上传和页面控制。没有链接的其他机器一律 `403`。
124
+
125
+ ## 安全模型
126
+
127
+ 用户使用上不需要登录:这是本地开发工具,默认只监听 `127.0.0.1`。内部各个入口都有鉴权:
128
+
129
+ - **两个源。** 你的应用(以及其中任何脚本)在应用端口;面板、API、Agent 接口在控制端口,页面脚本读不到。
130
+ - **所有者与评审者。** 不加 `--share` 时只有本机能连,一切按单用户处理。加了 `--share` 后,所有者会话只发给本机回环请求;其他机器必须通过邀请链接拿到带 HMAC 签名的评审者 Cookie(面板和应用端口各一个),没有就两个端口都返回 `403`。评审者在 WebSocket 上只能做评审操作,API 只能读产物,其余一律 `403`。所有者名字在设置 → 团队里修改。
131
+ - **会话 Cookie。** 面板拿到 `HttpOnly`、`SameSite=Strict`、作用于 `/__ph` 的 Cookie;转发到你的开发服务器前会去掉它。
132
+ - **API。** 读请求要求同源的 `Sec-Fetch-Site`;写请求要求匹配的 `Origin` 加自定义 `X-PH` 头。WebSocket 校验 Cookie 和 `Origin`。
133
+ - **MCP。** 每一轮 Agent 调用都发一个独立的 Bearer token(通过环境变量或私有临时文件传递,不出现在命令行),回合结束即作废。来自浏览器的 MCP 请求一律拒绝。
134
+ - **Host 白名单。** `Host` 头不符的请求返回 `421`(防 DNS rebinding)。在局域网使用时用 `--allow-host` 添加。
135
+ - **嵌入限制。** 面板只允许被应用源嵌入;聊天记录页不允许被任何页面嵌入。
136
+ - **产物。** HTML / SVG 产物带 `sandbox` CSP 返回,并放在不含 `allow-same-origin` 的 iframe 里展示。
137
+ - **页面控制**需要在浏览器里手动开启,开启时有横幅提示,密码框的值不会发给 Agent。
138
+
139
+ Agent 以各自 CLI 的非交互权限在 `--project` 里运行(例如 Claude Code `--permission-mode bypassPermissions`、Codex 关闭审批、Cursor Agent `--force`)。只对你愿意让 Agent 修改的项目使用。
140
+
141
+ ## 数据
142
+
143
+ 全部保存在 `<project>/.page-harness/`:
144
+
145
+ ```text
146
+ chats/<id>/meta.json 标题、Agent、模型、session id
147
+ chats/<id>/events.jsonl 只追加的事件日志(提问、增量、工具、结果)
148
+ artifacts/<name>/v<n> 产物各版本 + index.json
149
+ uploads/ 附件
150
+ live.json Live 会话
151
+ review.json 意见、页面修改、回复与审批
152
+ team.json 邀请 token、签名密钥和评审者名字(权限 600)
153
+ ```
154
+
155
+ 删除该目录即可重置。
156
+
157
+ ## 开发
158
+
159
+ ```bash
160
+ npm install
161
+ npm run dev # 监听并重建前端脚本与 CLI
162
+ node dist/cli.js http://localhost:5173 --project ../some-app
163
+
164
+ npm run lint
165
+ npm run typecheck
166
+ npm test # 单元测试 + 服务端集成测试(脚本化 Agent)
167
+ npx playwright install chromium
168
+ npm run build && npm run test:e2e # 基于示例应用的浏览器测试
169
+ ```
170
+
171
+ 设置 `PH_FAKE_AGENT=1` 会多出一个脚本化的 `fake` Agent,测试用它走真实的 MCP 接口,不需要大模型就能覆盖整条链路。
172
+
173
+ ## 许可证
174
+
175
+ [MIT](./LICENSE)。打包进来的第三方代码见 [THIRD_PARTY_LICENSES.md](./THIRD_PARTY_LICENSES.md)。