sitedrift 0.1.0 → 0.3.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/AGENTS.md ADDED
@@ -0,0 +1,112 @@
1
+ # sitedrift for agents
2
+
3
+ sitedrift compares a local site with production and keeps a shared review-note
4
+ channel for humans and AI tools. Prefer the packaged MCP server. Use the JSON CLI
5
+ only when the host cannot run MCP.
6
+
7
+ ## Fast path
8
+
9
+ 1. Install once: `npm install --global sitedrift`
10
+ 2. In the project, create `sitedrift.config.json`:
11
+
12
+ ```json
13
+ {
14
+ "dev": "http://localhost:4321",
15
+ "live": "https://example.com",
16
+ "open": true
17
+ }
18
+ ```
19
+
20
+ 3. Start the app: `sitedrift`
21
+ 4. Configure the AI host to run:
22
+
23
+ ```json
24
+ {
25
+ "command": "sitedrift-mcp",
26
+ "args": []
27
+ }
28
+ ```
29
+
30
+ No global install: use `"command": "npx"` and
31
+ `"args": ["-y", "sitedrift", "mcp"]`.
32
+
33
+ Common one-command setup:
34
+
35
+ ```bash
36
+ codex mcp add sitedrift -- sitedrift-mcp
37
+ claude mcp add sitedrift -- sitedrift-mcp
38
+ ```
39
+
40
+ For Cursor and other JSON-configured hosts, put this in the host's MCP
41
+ configuration under `mcpServers`:
42
+
43
+ ```json
44
+ {
45
+ "sitedrift": {
46
+ "command": "sitedrift-mcp",
47
+ "args": []
48
+ }
49
+ }
50
+ ```
51
+
52
+ ## Agent workflow
53
+
54
+ 1. Call `sitedrift_context` first. Do not guess the URLs or active session.
55
+ 2. Inspect the requested route in the viewer or with the host's browser tools.
56
+ 3. Call `sitedrift_notes_list` before adding notes to avoid duplicates.
57
+ 4. Add one concrete finding per `sitedrift_note_add` call. Always provide
58
+ `route`; provide `side` when the issue belongs specifically to DEV or LIVE.
59
+ 5. Re-list notes after code changes. Resolve only findings you verified.
60
+ 6. Remove or clear notes only when the user explicitly requests it.
61
+
62
+ Hosted Cloudflare preview deployments are a different mode: their notes are
63
+ browser-local and intentionally unavailable to MCP. Use browser inspection for
64
+ those URLs. Do not claim that a hosted note was shared with an agent or written
65
+ to the project.
66
+
67
+ ## MCP tools
68
+
69
+ - `sitedrift_context`: active targets, viewer URL, and capabilities.
70
+ - `sitedrift_notes_list`: shared findings.
71
+ - `sitedrift_note_add`: add one actionable finding.
72
+ - `sitedrift_note_resolve`: mark a verified finding complete.
73
+ - `sitedrift_note_reopen`: reopen a regressed or incomplete finding.
74
+ - `sitedrift_note_remove`: permanently remove one finding.
75
+ - `sitedrift_notes_clear`: permanently remove all findings.
76
+ - `sitedrift_setup`: return install, config, HTTPS, and MCP setup instructions.
77
+
78
+ Default port is `4178`. Pass `port` to every tool when the user runs another
79
+ session port.
80
+
81
+ ## CLI fallback
82
+
83
+ ```bash
84
+ sitedrift context
85
+ sitedrift notes list
86
+ sitedrift notes add "CTA differs" --route /pricing --side live --author agent
87
+ sitedrift notes resolve <id>
88
+ ```
89
+
90
+ All CLI output is JSON. The MCP server communicates over stdio and writes no
91
+ logs to stdout.
92
+
93
+ ## HTTPS
94
+
95
+ Loopback HTTP is normally sufficient. When the compared site requires HTTPS:
96
+
97
+ ```bash
98
+ sitedrift --setup-https
99
+ sitedrift --https
100
+ ```
101
+
102
+ ## Security
103
+
104
+ sitedrift accepts loopback hosts only. The control API uses a random bearer
105
+ token stored in `~/.sitedrift/sessions/<port>.json` with mode `0600`. DEV and
106
+ LIVE render on separate origins. Never expose sitedrift through a public proxy.
107
+
108
+ The optional Cloudflare Pages addon is intentionally public-preview safe: it is
109
+ installed only on non-production builds, exposes only `/__sitedrift/*`, permits
110
+ only `GET` and `HEAD`, allowlists one configured live origin, and sandboxes both
111
+ frames without same-origin authority. Production output and existing API
112
+ Functions are unchanged.
package/README.md CHANGED
@@ -5,15 +5,11 @@ that frames your local site and production **side-by-side on the same route**,
5
5
  locked to the same scroll — then overlays them in `difference` mode so the only
6
6
  things that light up are the pixels that actually changed.
7
7
 
8
- ```
9
- ┌─────────────── sitedrift ───────────────┐
10
- │ DEV ▸ /pricing 200 LIVE ▸ /pricing 200 │
11
- │ ┌───────────┐ │ ┌───────────┐ Overlay ▸ Diff │
12
- │ $19 │ │ $29 │ ≠ meta │
13
- │ │ [Start…] │ │ │ [Get…] │ ✓ author notes │
14
- │ └───────────┘ │ └───────────┘ │
15
- └──────────────────────────────────────────┘
16
- ```
8
+ <p align="center">
9
+ <img src="docs/images/sitedrift-split.jpg" alt="sitedrift comparing a redesigned local product page with production" width="100%">
10
+ </p>
11
+
12
+ <p align="center"><strong>Same route. Same scroll. Every change visible.</strong></p>
17
13
 
18
14
  ---
19
15
 
@@ -39,6 +35,108 @@ npm i -g sitedrift
39
35
  sitedrift /pricing -d http://localhost:4321 -l https://example.com -o
40
36
  ```
41
37
 
38
+ For a project you use repeatedly, add `sitedrift.config.json`:
39
+
40
+ ```json
41
+ {
42
+ "dev": "http://localhost:4321",
43
+ "live": "https://example.com",
44
+ "author": "joe",
45
+ "open": true
46
+ }
47
+ ```
48
+
49
+ Configuration precedence is **flag > environment > project file > default**.
50
+ The file is discovered from the current directory upward; `--config <file>`
51
+ selects one explicitly.
52
+
53
+ ### HTTPS
54
+
55
+ The default (`http://127.0.0.1`) just works — loopback is a browser "secure
56
+ context", so you usually need nothing. When you do want HTTPS:
57
+
58
+ ```bash
59
+ sitedrift --setup-https # one-time: generate + trust a local cert
60
+ sitedrift --https # serve over HTTPS from then on
61
+ ```
62
+
63
+ `--setup-https` uses [mkcert](https://github.com/FiloSottile/mkcert) if it's
64
+ installed — that gives a **locally-trusted cert with zero browser warnings**. If
65
+ mkcert isn't found it falls back to an `openssl` self-signed cert and prints the
66
+ one command to trust it on your OS. Already have a cert? Skip all of this and
67
+ pass `--cert <file> --key <file>`.
68
+
69
+ ### Cloudflare preview deployments
70
+
71
+ Turn every non-production Cloudflare Pages deployment into a compact sitedrift
72
+ review URL. The deployment opens its own preview in DEV Solo mode and can switch
73
+ to Split, Overlay, or Diff against the configured production site.
74
+
75
+ Install sitedrift and run the wrapper after your static build:
76
+
77
+ ```json
78
+ {
79
+ "scripts": {
80
+ "build": "astro build && sitedrift cloudflare --dir dist --live https://example.com"
81
+ },
82
+ "devDependencies": {
83
+ "sitedrift": "^0.3.0"
84
+ }
85
+ }
86
+ ```
87
+
88
+ Add one scoped Pages Function:
89
+
90
+ ```ts
91
+ // functions/__sitedrift/[[path]].ts
92
+ export { onRequest } from 'sitedrift/cloudflare';
93
+ ```
94
+
95
+ That is the entire integration. On Cloudflare Pages, the wrapper activates only
96
+ when `CF_PAGES=1` and `CF_PAGES_BRANCH` is not `main`. Production builds are
97
+ left unchanged. Use `--production-branch <name>` when production is another
98
+ branch.
99
+
100
+ Hosted proxies are read-only (`GET`/`HEAD`), sandboxed without same-origin
101
+ authority, and fixed to the configured live origin. Review notes stay in that
102
+ browser's `localStorage`; they are not sent to an API, shared with agents, or
103
+ written to disk. Existing application Functions keep their original routes.
104
+
105
+ ---
106
+
107
+ ## See the whole review loop
108
+
109
+ The included example compares a fictional Northstar release candidate against
110
+ production. It uses realistic release drift: primary CTA, metric values, and a
111
+ release badge change while the underlying layout stays aligned.
112
+
113
+ <table>
114
+ <tr>
115
+ <td width="50%">
116
+ <img src="docs/images/sitedrift-diff.jpg" alt="Difference overlay showing only changed pixels">
117
+ <br><strong>Pixel difference</strong><br>
118
+ Overlay both pages and light up only what changed.
119
+ </td>
120
+ <td width="50%">
121
+ <img src="docs/images/sitedrift-collaboration.jpg" alt="Review drawer with notes from a human and an AI agent">
122
+ <br><strong>Human + AI review channel</strong><br>
123
+ Share route-specific findings through the viewer, CLI, or MCP.
124
+ </td>
125
+ </tr>
126
+ </table>
127
+
128
+ <p align="center">
129
+ <img src="docs/images/sitedrift-mobile.jpg" alt="sitedrift Solo mode on a narrow mobile viewport" width="360">
130
+ <br><strong>Focused mobile review</strong><br>
131
+ Narrow screens default to Solo; Swap flips between DEV and LIVE.
132
+ </p>
133
+
134
+ Rebuild these screenshots from the deterministic local showcase:
135
+
136
+ ```bash
137
+ npm run docs:screenshots
138
+ ```
139
+
42
140
  ---
43
141
 
44
142
  ## What it does
@@ -59,7 +157,10 @@ sitedrift /pricing -d http://localhost:4321 -l https://example.com -o
59
157
  file the viewer polls every 4s, so a teammate or an AI coding session can leave
60
158
  notes that appear live. Click a note to jump to its route, copy a per-note
61
159
  deep link, dock or float the drawer, and **Send to vault** or export Markdown.
62
- - **No dependencies.** Node standard library only.
160
+ - **No runtime dependencies.** Node standard library only.
161
+ - **Deploy-preview mode for Cloudflare Pages.** Preview branches can carry the
162
+ compact comparison toolbar without changing production output or application
163
+ API routes.
63
164
 
64
165
  ### Keyboard
65
166
 
@@ -82,7 +183,6 @@ Shortcuts work whether focus is in the viewer chrome or inside either pane.
82
183
  ## Options
83
184
 
84
185
  Every option is a CLI flag, and also reads a `SITEDRIFT_<NAME>` env var.
85
- Precedence is **flag > env > default**.
86
186
 
87
187
  | Flag | Env | Default | Purpose |
88
188
  |---|---|---|---|
@@ -91,36 +191,85 @@ Precedence is **flag > env > default**.
91
191
  | `-p, --port <n>` | `SITEDRIFT_PORT` | `4178` | Listen port. |
92
192
  | `--host <addr>` | `SITEDRIFT_HOST` | `127.0.0.1` | Bind address. |
93
193
  | `-o, --open` | — | off | Open the viewer in your browser. |
94
- | `--http` | — | | Force plain HTTP (ignore `--cert`/`--key`). |
95
- | `--cert <file>` / `--key <file>` | `SITEDRIFT_CERT` / `_KEY` | | If both set, serve over HTTPS. |
194
+ | `--https` | — | off | Serve HTTPS with an auto cert (mkcert if present, else openssl). |
195
+ | `--setup-https` | | | One-time: generate + trust a local cert, then exit. |
196
+ | `--http` | — | — | Force plain HTTP (the default; overrides `--https`). |
197
+ | `--cert <file>` / `--key <file>` | `SITEDRIFT_CERT` / `_KEY` | — | Bring your own cert; if both set, serve over HTTPS. |
96
198
  | `--notes <file>` | `SITEDRIFT_NOTES` | `$TMPDIR/sitedrift-notes.json` | Shared review-notes file. |
97
199
  | `--brand <text>` | `SITEDRIFT_BRAND` | — | Strip `\| <text>` from titles in pane headers. |
98
200
  | `--author <name>` | `SITEDRIFT_AUTHOR` | `you` | Byline for notes added in the viewer. |
99
201
  | `--vault <dir>` | `SITEDRIFT_VAULT` | — | Enable **Send to vault** (writes the review markdown here). |
202
+ | `--config <file>` | — | discovered | Read project configuration from JSON. |
100
203
 
101
204
  A positional `[path]` (e.g. `sitedrift /pricing`) sets the initial route.
102
205
  `-h, --help` and `-v, --version` do what you'd expect.
103
206
 
207
+ ### AI and automation
208
+
209
+ The running process writes a private mode-`0600` descriptor under
210
+ `~/.sitedrift/sessions/`. The npm package includes a zero-dependency stdio MCP
211
+ server and an `AGENTS.md` operating guide.
212
+
213
+ Configure an MCP host with:
214
+
215
+ ```json
216
+ {
217
+ "command": "sitedrift-mcp",
218
+ "args": []
219
+ }
220
+ ```
221
+
222
+ Codex and Claude Code can register it directly:
223
+
224
+ ```bash
225
+ codex mcp add sitedrift -- sitedrift-mcp
226
+ claude mcp add sitedrift -- sitedrift-mcp
227
+ ```
228
+
229
+ Without a global install:
230
+
231
+ ```json
232
+ {
233
+ "command": "npx",
234
+ "args": ["-y", "sitedrift", "mcp"]
235
+ }
236
+ ```
237
+
238
+ Agents call `sitedrift_context` first, then use the note tools to share findings
239
+ with the user. The server also exposes `sitedrift://guide`, a `review_route`
240
+ prompt, and `sitedrift_setup` for install/config/HTTPS guidance.
241
+
242
+ For hosts without MCP, use the JSON CLI instead of scraping the viewer or
243
+ constructing authenticated requests:
244
+
245
+ ```bash
246
+ sitedrift context
247
+ sitedrift notes list
248
+ sitedrift notes add "CTA differs" --route /pricing --side live --author codex
249
+ sitedrift notes resolve <id>
250
+ sitedrift notes reopen <id>
251
+ sitedrift notes remove <id>
252
+ sitedrift notes clear
253
+ ```
254
+
255
+ Commands print JSON and return non-zero on failure. They work over HTTP or local
256
+ HTTPS without an agent-specific SDK.
257
+
104
258
  ### HTTP endpoints
105
259
 
106
260
  | Route | Purpose |
107
261
  |---|---|
108
262
  | `GET /` | The viewer. |
109
263
  | `GET /health` | `{ dev, live, version }`. |
110
- | `GET /notes` · `POST /notes` | Read / mutate notes (`op: add\|remove\|toggle\|clear`). |
264
+ | `GET /api/v1/session` | Authenticated session context and capabilities. |
265
+ | `GET /api/v1/notes` · `POST /api/v1/notes` | Authenticated note list / operations. |
111
266
  | `GET /notes.md` | Notes as a Markdown checklist. |
112
- | `POST /notes/save` | Write the notes markdown into `--vault`. |
267
+ | `POST /api/v1/notes/save` | Write notes markdown into `--vault`. |
113
268
  | `GET /icon.svg` | The app mark / favicon. |
114
- | `GET /__dev/*` · `GET /__live/*` | Proxied origins. |
115
-
116
- `POST /notes` requires `Content-Type: application/json`, so a cross-origin page
117
- can't forge a no-preflight write. Add a note from anywhere:
269
+ | frame ports: `GET /__dev/*` · `GET /__live/*` | Isolated proxied origins. |
118
270
 
119
- ```bash
120
- curl -X POST localhost:4178/notes -H 'content-type: application/json' \
121
- -d '{"op":"add","text":"H1 on /about is larger on LIVE",
122
- "author":"claude","route":"/about","side":"live"}'
123
- ```
271
+ The control API requires the per-session bearer token. The CLI commands above
272
+ are the supported machine interface.
124
273
 
125
274
  Most viewer state (route, layout, scroll mode, focus) is mirrored into the URL
126
275
  query string, so a link reproduces the exact view.
@@ -131,12 +280,26 @@ query string, so a link reproduces the exact view.
131
280
 
132
281
  The proxy strips `Content-Security-Policy`, `X-Frame-Options`, and the
133
282
  Cross-Origin-{Embedder,Opener,Resource}-Policy headers so production can be
134
- framed next to dev. That is safe **only on loopback**:
283
+ framed next to dev.
135
284
 
136
- - It binds to `127.0.0.1` unless you override `--host`. **Do not** bind it to a
137
- public interface or put it behind a public proxy.
285
+ - Only loopback hosts are accepted, and Host headers are validated.
286
+ - The viewer/control API uses `--port`; DEV and LIVE frames use `--port + 1`
287
+ and `--port + 2`, so neither page can inspect the other.
288
+ - Framed pages communicate through a narrow `postMessage` bridge and cannot
289
+ access the viewer DOM, bearer token, notes API, or vault endpoint.
138
290
  - Treat the notes file as plaintext shared scratch space.
139
291
 
292
+ ## Development
293
+
294
+ ```bash
295
+ npm test
296
+ npm run test:e2e:visual
297
+ npm run test:e2e:visual:update # intentionally accept visual changes
298
+ ```
299
+
300
+ The visual suite uses deterministic origins and checked-in Chromium baselines
301
+ for desktop split, narrow Solo, difference overlay, and the notes drawer.
302
+
140
303
  ## Limitations
141
304
 
142
305
  - **URL rewriting is regex-based**, tuned for static sites (e.g. Astro builds).