rpc-agent 0.0.0-stage → 0.4.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.
Files changed (43) hide show
  1. package/CHANGELOG.md +157 -0
  2. package/LICENSE +21 -0
  3. package/NOTICE +16 -0
  4. package/README.md +471 -2
  5. package/assets/bezels/imac.svg +63 -0
  6. package/assets/bezels/ipad.svg +27 -0
  7. package/assets/bezels/iphone-11-pro.svg +35 -0
  8. package/assets/bezels/iphone-12-mini.svg +26 -0
  9. package/assets/bezels/iphone-12-pro-max.svg +26 -0
  10. package/assets/bezels/iphone-17-pro.svg +172 -0
  11. package/assets/bezels/iphone-se.svg +37 -0
  12. package/assets/bezels/laptop.svg +25 -0
  13. package/assets/bezels/macbook-air.svg +235 -0
  14. package/assets/bezels/macbook-pro-13.svg +63 -0
  15. package/assets/bezels/macbook-pro-14.svg +115 -0
  16. package/assets/bezels/macbook-pro.svg +32 -0
  17. package/assets/bezels/macbook.svg +25 -0
  18. package/assets/bezels/monitor.svg +57 -0
  19. package/assets/bezels/phone-classic.svg +17 -0
  20. package/assets/bezels/phone-home.svg +12 -0
  21. package/assets/bezels/phone-island.svg +130 -0
  22. package/assets/bezels/phone-notch.svg +26 -0
  23. package/assets/bezels/surface-laptop.svg +84 -0
  24. package/assets/bezels/tablet.svg +25 -0
  25. package/assets/bezels/windows-laptop.svg +84 -0
  26. package/dist/args.js +308 -0
  27. package/dist/auth.js +416 -0
  28. package/dist/bezel.js +74 -0
  29. package/dist/browser.js +82 -0
  30. package/dist/capture.js +274 -0
  31. package/dist/cli.js +255 -0
  32. package/dist/composite.js +115 -0
  33. package/dist/devices.js +407 -0
  34. package/dist/errors.js +115 -0
  35. package/dist/help.js +160 -0
  36. package/dist/login.js +150 -0
  37. package/dist/mcp.js +312 -0
  38. package/dist/options.js +51 -0
  39. package/dist/png.js +183 -0
  40. package/dist/slices.js +59 -0
  41. package/dist/term.js +66 -0
  42. package/dist/ui.js +599 -0
  43. package/package.json +68 -4
package/CHANGELOG.md ADDED
@@ -0,0 +1,157 @@
1
+ # Changelog
2
+
3
+ ## 0.4.0
4
+
5
+ Authenticated captures: screenshot logged-in pages without a hand-written
6
+ "sign in, then screenshot" script.
7
+
8
+ ### Added
9
+
10
+ - `rpc login <url>` opens a browser window; sign in, press Enter (or close
11
+ the window) and the storage state is saved for that origin to
12
+ `~/.config/rpc-agent/sessions/<origin>.json` (file 0600, directory 0700).
13
+ `--save <file>` writes it elsewhere.
14
+ - `rpc login <url> --form`: headless email + password sign-in for CI and
15
+ agents, from `RPC_LOGIN_EMAIL` / `RPC_LOGIN_PASSWORD` (or `--email` /
16
+ `--password`), with `--email-selector`, `--password-selector` and
17
+ `--submit-selector`.
18
+ - `rpc shot` uses the saved session for the target origin automatically.
19
+ `--no-session` opts out. `rpc sessions [list]` and
20
+ `rpc sessions clear [origin]` manage them.
21
+ - `rpc shot --cookie "name=value[; Domain=…; Path=…]"`, `--header
22
+ "Name: value"` (both repeatable), `--storage-state <file>` and
23
+ `--allow-origin <origin>`.
24
+ - A capture redirected to a login page (`/login`, `/sign-in`, `/auth`, …)
25
+ fails with code `AUTH_REQUIRED` and the hint ``Session missing or
26
+ expired: run `rpc login <origin>` `` instead of screenshotting the sign-in
27
+ form. Sessions whose cookies have all expired aren't loaded.
28
+ - MCP `screenshot` takes `cookies`, `headers`, `storageState`, `useSession`
29
+ and `allowOrigins`. New `list_sessions` tool.
30
+ - `rpc install --headed` also downloads full Chromium, for the
31
+ `rpc login` window. Without it, `rpc login` uses an installed Google
32
+ Chrome if there is one.
33
+
34
+ ### Security
35
+
36
+ - Headers and cookies go to the target origin only (plus any
37
+ `--allow-origin`). Headers are injected per request through the CDP Fetch
38
+ domain, so a redirect to another host doesn't carry them. Saved sessions
39
+ and storage-state files load only the target origin's cookies and
40
+ localStorage.
41
+ - Cookie, header and localStorage values are never printed: output shows
42
+ names with `•••`, and console errors and failure messages are scrubbed.
43
+ - `--password` prints a warning; use the environment variable.
44
+
45
+ ### Changed
46
+
47
+ - A page that redirects to a login URL now fails that device with
48
+ `AUTH_REQUIRED` (exit code 1) even when no auth was given. In 0.3 it
49
+ screenshotted the sign-in page and reported success.
50
+
51
+ ### Unchanged
52
+
53
+ - With no auth in play, plain output, `--json` output and MCP results are
54
+ the 0.3 ones. With auth, they gain an `auth:` line / an `auth` field.
55
+ Existing MCP params and result fields are unchanged.
56
+
57
+ ## 0.3.1
58
+
59
+ Published to npm as `rpc-agent`.
60
+
61
+ ### Added
62
+
63
+ - Install with `npx rpc-agent …` or `npm i -g rpc-agent`. MCP setup for
64
+ Claude Code is `claude mcp add --scope user rpc -- npx -y rpc-agent mcp`;
65
+ Cursor uses `"command": "npx", "args": ["-y", "rpc-agent", "mcp"]`.
66
+ - Package metadata (repository, homepage, bugs, keywords, author) and a
67
+ `prepublishOnly` check that typechecks, builds and runs the tests.
68
+
69
+ ### Changed
70
+
71
+ - The missing-Chromium message now says `npx rpc-agent install` (or
72
+ `rpc install`) and names the Playwright version, instead of pointing at
73
+ `npx playwright install chromium` inside the package folder, which under
74
+ npx is a cache directory and could pull a different Playwright.
75
+ - `rpc install` run through npx no longer shows Playwright's "install your
76
+ project's dependencies" warning box, which didn't apply.
77
+ - `rpc install` downloads only Chromium's headless shell (`--only-shell`),
78
+ which is all rpc launches: about 210 MB on disk instead of about 580 MB.
79
+ - Requires Node 20.12 or newer (what `@clack/prompts` needs).
80
+ - Chromium is still never downloaded on install: run `rpc install` once.
81
+
82
+ ## 0.3.0
83
+
84
+ A terminal UI modelled on the `skills` CLI (skills.sh). Nothing changes for
85
+ agents, scripts or CI.
86
+
87
+ ### Added
88
+
89
+ - `rpc` with no arguments, in a terminal, is an interactive capture: URL
90
+ (defaults to a running dev server on localhost), a grouped device picker,
91
+ bezels / full page / dark mode / reduced motion, a summary and a confirm.
92
+ It ends with the equivalent `rpc shot` command.
93
+ - `rpc shot` in a terminal: `RPC` banner, a capture summary, a spinner per
94
+ device that resolves to status and time, a results table (status, time,
95
+ console errors), the files written with pixel and file sizes, failures
96
+ grouped with their hint, and `Done! Open with: open .rpc-shots`.
97
+ - `--open` opens the output folder when the capture finishes.
98
+ - `rpc devices` in a terminal: aligned, coloured by category, with each
99
+ device's aliases. `rpc doctor` in a terminal: a checklist with the fix in
100
+ a box.
101
+ - Per-command help: `rpc help shot`, `rpc shot --help` (also devices,
102
+ doctor, install, mcp). `rpc --help` in a terminal has sections and
103
+ examples.
104
+ - `docs/cli-design.md`: what rpc borrows from the `skills` CLI and when the
105
+ rich output is used.
106
+
107
+ ### Unchanged
108
+
109
+ - Output to pipes, files and CI (`CI` set, `TERM=dumb`) is the 0.2 output.
110
+ `--json` output, exit codes and the MCP server are untouched; `rpc mcp`
111
+ and `--json` don't load the UI code. `NO_COLOR` turns colour off and
112
+ `FORCE_COLOR` turns it on for the plain output.
113
+
114
+ ### Dependencies
115
+
116
+ - `@clack/prompts` and `picocolors`.
117
+
118
+ ## 0.2.0
119
+
120
+ From the first real use: an agent reviewing a deployed site on iphone-15,
121
+ ipad and desktop-1440 with bezels and full page.
122
+
123
+ ### Added
124
+
125
+ - Full pages of any height. Pages taller than Chromium's 16,384 px capture
126
+ limit are taken as clipped full-page segments and stitched in Node (no
127
+ native dependencies). Sticky headers and fixed elements appear once. New
128
+ `maxHeight` / `--max-height` cap (60,000 CSS px), reported as `clipped`,
129
+ with `capturedHeight` and `segments` in the result.
130
+ - Full-page review in one MCP call. With `fullPage`, `screenshot` returns the
131
+ framed first screen and then the whole page as labelled, viewport-tall JPEG
132
+ slices (`iphone-15 · slice 2/7 · y=852–1704 css px`). New params
133
+ `maxSlices` (10), `saveSlices` and `sliceWidth` (768). When slices are cut,
134
+ the result says which range was skipped, and every slice is saved to disk
135
+ with its path listed. CLI: `--slices`, `--slice-width`.
136
+ - Actionable errors. Navigation failures come back per device as `error`,
137
+ `code` (`ERR_NAME_NOT_RESOLVED`, `TIMEOUT`, ...) and `hint`, covering
138
+ sandbox / network allowlists, dev servers not running, proxies, TLS and
139
+ timeouts. MCP results lead with the failures in plain text.
140
+ - `reducedMotion` / `--reduced-motion` emulates
141
+ `prefers-reduced-motion: reduce`.
142
+ - Missing-browser check. If Playwright's Chromium isn't installed, the CLI and
143
+ the MCP tool say so with the fix (`npx playwright install chromium`, or
144
+ `rpc install`) instead of Playwright's raw error; the MCP server keeps
145
+ running. New `rpc doctor` and `rpc install` commands.
146
+
147
+ ### Changed
148
+
149
+ - The warm-up scroll covers the whole capped height (up to 100 steps) rather
150
+ than stopping at 30,000 px.
151
+ - `pnpm smoke:mcp` takes `--full-page`, `--devices`, `--max-slices`, `--out`.
152
+
153
+ ## 0.1.0
154
+
155
+ First version: CLI and stdio MCP server, 49 devices and bezels from
156
+ Responsive Point Check, Playwright Chromium capture, bezel compositing in a
157
+ Playwright page.
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Akintola Tope
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/NOTICE ADDED
@@ -0,0 +1,16 @@
1
+ rpc-agent
2
+ Derived from responsive-point-check (https://github.com/akintola4/responsive-point-check),
3
+ MIT License, Copyright (c) 2026 Akintola Tope.
4
+
5
+ Device catalog (src/devices.ts) and bezel layout logic (src/bezel.ts) are ported
6
+ from lib/devices.ts and lib/bezel-asset.ts in that repository.
7
+
8
+ Bezel artwork (assets/bezels/*.svg) is copied unchanged from public/bezels/ in
9
+ that repository and distributed under its MIT license. The SVGs carry no author
10
+ or license metadata of their own; their element ids indicate Figma exports, but
11
+ the original Figma source is not recorded in the upstream repo. Confirm the
12
+ artwork's origin before any public release.
13
+
14
+ The artwork depicts hardware made by Apple, Google, Samsung and Microsoft.
15
+ Device names and product designs are trademarks of their respective owners.
16
+ This project is not affiliated with or endorsed by any of them.
package/README.md CHANGED
@@ -1,3 +1,472 @@
1
- # Temporary Holding Version
1
+ # rpc-agent
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
+ <img src="https://raw.githubusercontent.com/akintola4/rpc-agent/main/docs/media/banner.png" width="100%" alt="rpc-agent: CLI + MCP server · device screenshots for AI agents · Claude Code, Codex, Cursor. A line drawing of a laptop, tablet and phone with a terminal in front running rpc shot; the Claude Code and Cursor marks and a terminal-prompt glyph for Codex sit beside it, with lines into the prompt.">
4
+
5
+ <picture>
6
+ <source media="(prefers-color-scheme: dark)" srcset="https://raw.githubusercontent.com/akintola4/rpc-agent/main/docs/media/hero-dark.png">
7
+ <img alt="A site captured by rpc on an iPhone 15, an iPad Air 11 and a 1440 px desktop, each framed in its device bezel" src="https://raw.githubusercontent.com/akintola4/rpc-agent/main/docs/media/hero-light.png">
8
+ </picture>
9
+
10
+ Screenshot a URL at real device viewports, optionally framed in a device
11
+ bezel, so an AI coding agent can look at the UI it just built. It's a CLI and
12
+ an MCP server in one package, with Playwright (Chromium) doing the capture.
13
+
14
+ The device catalog (49 phones, tablets, laptops and monitors) and the SVG
15
+ bezels come from the [Responsive Point Check](https://github.com/akintola4/responsive-point-check)
16
+ Chrome extension. See `NOTICE` for where the artwork comes from.
17
+
18
+ ## Install
19
+
20
+ Requires Node 20.12 or newer. Run it without installing:
21
+
22
+ ```sh
23
+ npx rpc-agent install # first run: download headless Chromium (~210 MB on disk)
24
+ npx rpc-agent shot localhost:3000 -d iphone-15,desktop-1440 --bezels
25
+ ```
26
+
27
+ Or install it globally to get the `rpc` command:
28
+
29
+ ```sh
30
+ npm i -g rpc-agent
31
+ rpc install # first run: download Chromium
32
+ rpc doctor # checks Chromium is installed and launches
33
+ rpc shot https://example.com --bezels
34
+ ```
35
+
36
+ Chromium is not downloaded when the package installs. `rpc install` (or
37
+ `npx rpc-agent install`) fetches it with rpc's own Playwright version, so
38
+ the browser always matches the one rpc launches. It installs only
39
+ Chromium's headless shell (plus Playwright's small ffmpeg), not the full
40
+ Chrome for Testing build, since captures never open a visible window
41
+ (`rpc install --headed` adds it for `rpc login`). Until you run it, every
42
+ command says Chromium is missing and prints that command, instead of
43
+ failing with Playwright's own error. Browsers go to Playwright's usual
44
+ cache, so other Playwright projects on the same version share them.
45
+
46
+ ### From source
47
+
48
+ ```sh
49
+ git clone https://github.com/akintola4/rpc-agent.git
50
+ cd rpc-agent
51
+ pnpm install && pnpm build
52
+ node dist/cli.js install
53
+ pnpm link --global # optional: puts this build's `rpc` on your PATH
54
+ ```
55
+
56
+ Without the link, run `node /absolute/path/to/rpc-agent/dist/cli.js` where
57
+ these docs say `rpc`, or `pnpm rpc` from inside the repo.
58
+
59
+ ## CLI
60
+
61
+ ![rpc run with no arguments: the banner, prompts for the URL, devices and bezels, a spinner per device, a results table and the list of files written, then the three framed screenshots](https://raw.githubusercontent.com/akintola4/rpc-agent/main/docs/media/demo.gif)
62
+
63
+ ```sh
64
+ rpc # interactive: pick a URL, devices and options
65
+ rpc shot <url> [options]
66
+ rpc login <url> [--form] # sign in once; later shots of that origin use the session
67
+ rpc sessions [list|clear [origin]]
68
+ rpc devices [--json]
69
+ rpc doctor [--json] # is Chromium installed, and does it launch?
70
+ rpc install [--headed] # download Chromium with rpc's own Playwright version
71
+ rpc mcp
72
+ rpc help <command> # options and examples for one command
73
+ ```
74
+
75
+ Run `rpc` with no arguments in a terminal and it asks for the URL
76
+ (defaulting to a dev server on localhost:3000, 5173, 4321 or 8080 if one is
77
+ running), lets you tick devices from a grouped list (iPhone 15 Pro, iPad Air
78
+ 11" and Desktop 1440 are preselected) and pick bezels, full page, dark mode
79
+ or reduced motion. It shows a summary, asks before capturing, and prints the
80
+ equivalent `rpc shot` command at the end so you can re-run it.
81
+
82
+ `rpc shot` in a terminal shows each device's progress, a results table and
83
+ the files it wrote:
84
+
85
+ <img alt="rpc shot in a terminal: a capture summary, one line per device with HTTP status and time, a results table, and the list of files written" src="https://raw.githubusercontent.com/akintola4/rpc-agent/main/docs/media/terminal-shot.png" width="720">
86
+
87
+ | Option | Meaning |
88
+ | --- | --- |
89
+ | `-d, --devices <ids>` | Comma-separated device ids or aliases. Default `iphone-15-pro,desktop-1440` |
90
+ | `-b, --bezels` | Also write a copy framed in the device bezel |
91
+ | `-f, --full-page` | Capture the full scroll height, however tall (up to `--max-height`) |
92
+ | `--slices` | With `--full-page`, also write viewport-tall JPEG review slices |
93
+ | `--slice-width <px>` | Maximum slice width. Default 768 |
94
+ | `--max-height <px>` | Full-page cap in CSS px. Default 60000 |
95
+ | `--reduced-motion` | Emulate `prefers-reduced-motion: reduce` |
96
+ | `-o, --out <dir>` | Output directory. Default `./.rpc-shots` |
97
+ | `--wait-for <selector>` | Wait for a visible element before capturing |
98
+ | `--wait <ms>` | Extra settle delay after load |
99
+ | `--color-scheme <s>` | `light`, `dark` or `no-preference` |
100
+ | `--timeout <ms>` | Navigation timeout. Default 30000 |
101
+ | `--no-emulate` | Viewport and DPR only: no mobile user agent or touch |
102
+ | `--json` | Print the result as JSON |
103
+ | `--open` | Open the output folder when done (`open`, `xdg-open` or `explorer`) |
104
+ | `--cookie <c>` | `name=value[; Domain=…; Path=…]`, repeatable. See [Authenticated pages](#authenticated-pages) |
105
+ | `--header <h>` | `Name: value`, repeatable, sent to the target origin only |
106
+ | `--storage-state <file>` | Load a Playwright storageState JSON |
107
+ | `--no-session` | Don't use the session saved by `rpc login` |
108
+ | `--allow-origin <o>` | Also send headers and loaded cookies to this origin, repeatable |
109
+
110
+ The URL can be a full URL, a bare local host (`localhost:3000` becomes
111
+ `http://`), a bare domain (`example.com` becomes `https://`) or a path to a
112
+ local `.html` file.
113
+
114
+ ```sh
115
+ rpc shot localhost:3000 -d iphone-15,ipad,desktop-1440 --bezels
116
+ rpc shot https://example.com/pricing --full-page --slices --color-scheme dark
117
+ rpc shot localhost:5173 -d pixel --wait-for '[data-ready]' --json
118
+ ```
119
+
120
+ Output files are named `<host>[-<path>]-<device>.png`, with `-framed.png`
121
+ for bezel copies and `-slice-NN.jpg` for slices, for example
122
+ `.rpc-shots/localhost-3000-iphone-15.png`. Re-running overwrites them, so the
123
+ paths stay the same between runs.
124
+
125
+ Aliases: `iphone`, `mobile`, `phone` (iphone-15-pro), `android`, `pixel`
126
+ (pixel-8), `galaxy` (galaxy-s24), `ipad`, `tablet` (ipad-air-11), `macbook`
127
+ (macbook-air-13), `laptop` (laptop-1280), `desktop` (desktop-1440), `imac`
128
+ (imac-24). Run `rpc devices` for the full list.
129
+
130
+ ### What a capture does
131
+
132
+ Each device gets a fresh browser context with its CSS viewport, device pixel
133
+ ratio and, for phones and tablets, a mobile user agent plus touch. The page
134
+ loads, then rpc waits for network idle (up to 10 s, so dev servers with HMR
135
+ sockets don't hang it), web fonts and pending images. CSS animations,
136
+ transitions and Web Animations are finished before the shot.
137
+
138
+ With `--full-page`, rpc first scrolls through the page one viewport at a time
139
+ so lazy images and IntersectionObserver content load, then scrolls back to
140
+ the top. A page that fits in one Chromium capture (16,384 device px) is taken
141
+ in one pass. A taller one is taken as clipped segments of about 8,000 device
142
+ px and stitched into one PNG in Node, with no height limit apart from
143
+ `--max-height` (60,000 CSS px by default; taller pages are cut there and
144
+ marked `clipped`). Both paths render the page as if the viewport were the
145
+ whole document, so sticky headers and fixed elements appear once, at the top,
146
+ and `100vh` sections keep their real height.
147
+
148
+ Bezels frame the above-the-fold viewport. With `--bezels --full-page` you get
149
+ both: the full page raw, and the first screen framed.
150
+
151
+ ### Terminal, pipes and CI
152
+
153
+ The banner, spinners, prompts and boxes only appear when stdout is an
154
+ interactive terminal. When output goes to a pipe or a file, when `CI` is
155
+ set, when `TERM=dumb`, and always with `--json`, rpc prints the same plain
156
+ lines as 0.2 (`ok …`, `x …` plus `hint:`), so agents and scripts that read
157
+ them see no change. `NO_COLOR` turns colour off; `FORCE_COLOR=1` adds colour
158
+ to the plain lines. `rpc mcp` never prints anything but MCP messages.
159
+
160
+ ### When a capture fails
161
+
162
+ Each device fails on its own, with an `error`, a `code` and usually a `hint`.
163
+ In a terminal, devices that fail the same way share one hint:
164
+
165
+ <img alt="rpc shot against a host that doesn't resolve: both devices fail with ERR_NAME_NOT_RESOLVED and one hint explains what to check" src="https://raw.githubusercontent.com/akintola4/rpc-agent/main/docs/media/terminal-error.png" width="720">
166
+
167
+ Plain output (pipes, agents, CI):
168
+
169
+ ```
170
+ x iphone-15 (393x852 @3x): Could not resolve host example.dev (net::ERR_NAME_NOT_RESOLVED)
171
+ hint: Check the URL for typos. If the host works in a normal browser, this process may be
172
+ blocked by a sandbox or network allowlist (e.g. the Claude Code sandbox: add the domain to
173
+ the allowed hosts, or run outside the sandbox). ...
174
+ ```
175
+
176
+ Covered: DNS failures, refused connections (on localhost: "nothing is
177
+ listening on port N"), proxy, tunnel and offline errors, TLS errors (https
178
+ against a plain-http dev server), missing files, redirect loops, navigation
179
+ and `--wait-for` timeouts.
180
+
181
+ ## Authenticated pages
182
+
183
+ Dashboards, billing and settings pages sit behind a login. rpc can carry a
184
+ session so you don't need a hand-written "sign in, then screenshot" script.
185
+
186
+ ### Sign in once with `rpc login`
187
+
188
+ ```sh
189
+ rpc login localhost:3000/sign-in
190
+ rpc shot localhost:3000/app/settings/billing -d iphone-15,desktop-1440 --bezels
191
+ ```
192
+
193
+ `rpc login` opens a browser window at the URL. Sign in as you normally
194
+ would (SSO and 2FA work, it's a real browser), then press Enter in the
195
+ terminal or close the window. rpc saves the browser's storage state
196
+ (cookies and localStorage) to
197
+ `~/.config/rpc-agent/sessions/<origin>.json`, for example
198
+ `http-localhost-3000.json`. From then on, `rpc shot` on that origin uses it
199
+ automatically, and the output says so:
200
+
201
+ ```
202
+ auth: session http://localhost:3000 (saved 2026-10-29 22:55)
203
+ ok iphone-15 (393x852 @3x)
204
+ ```
205
+
206
+ The window needs full Chromium, which `rpc install` doesn't download (it
207
+ fetches only the headless shell). Run `rpc install --headed` once, or have
208
+ Google Chrome installed; rpc falls back to it.
209
+
210
+ `--save <file>` writes the storage state to a file of your choice instead;
211
+ use it with `rpc shot --storage-state <file>`.
212
+
213
+ ```sh
214
+ rpc sessions # origins, cookie counts, saved and expiry times
215
+ rpc sessions clear localhost:3000 # forget one origin
216
+ rpc sessions clear # forget all
217
+ rpc shot localhost:3000/app --no-session # capture as a signed-out visitor
218
+ ```
219
+
220
+ ### When the session is missing or expired
221
+
222
+ If the page redirects to a login URL (a path segment like `/login`,
223
+ `/sign-in`, `/signin` or `/auth`, different from the one you asked for),
224
+ rpc doesn't screenshot the sign-in form. The device fails with code
225
+ `AUTH_REQUIRED`:
226
+
227
+ ```
228
+ x iphone-15 (393x852 @3x): Redirected to a login page (http://localhost:3000/sign-in) instead of http://localhost:3000/app/settings/billing
229
+ hint: Session missing or expired: run `rpc login http://localhost:3000`
230
+ ```
231
+
232
+ A saved session whose cookies have all passed their expiry isn't loaded,
233
+ and gives the same hint.
234
+
235
+ ### CI and agents: `rpc login --form`
236
+
237
+ Without a person at the keyboard, `--form` signs in headlessly through a
238
+ standard email + password form:
239
+
240
+ ```sh
241
+ export RPC_LOGIN_EMAIL=ci@example.test
242
+ export RPC_LOGIN_PASSWORD=... # from your CI secret store
243
+ rpc login https://staging.example.com/sign-in --form
244
+ rpc shot https://staging.example.com/app/billing --json
245
+ ```
246
+
247
+ It loads the page, waits for it to settle (so a client-side form has
248
+ hydrated), fills the first match of `--email-selector` (default
249
+ `input[type=email]`, then `autocomplete=username`, `name=email`,
250
+ `name=username`, `#email`) and `--password-selector` (default
251
+ `input[type=password]`), clicks `--submit-selector` (default
252
+ `button[type=submit], input[type=submit]`, or presses Enter if there is no
253
+ button), and waits for the URL to leave the login page. If it doesn't
254
+ within `--timeout`, it fails and quotes any alert the page shows ("Invalid
255
+ email or password"). `--email` works as a flag too; see the security notes
256
+ before using `--password`.
257
+
258
+ ### Cookies and headers
259
+
260
+ ```sh
261
+ rpc shot localhost:3000/app --cookie "session=$SESSION"
262
+ rpc shot app.example.com/admin --cookie "sid=$SID; Domain=example.com; Path=/"
263
+ rpc shot api.example.com/docs --header "Authorization: Bearer $TOKEN"
264
+ rpc shot localhost:3000/app --storage-state auth.json
265
+ ```
266
+
267
+ - `--cookie` is host-only on the target unless you give a `Domain`, which
268
+ must match the target host (or an `--allow-origin` host). `Secure`,
269
+ `HttpOnly` and `SameSite` are accepted too.
270
+ - `--header` is sent only on requests to the target origin. Images,
271
+ scripts and API calls to other origins don't get it, and neither does a
272
+ redirect to another host (every redirect hop is checked).
273
+ - `--storage-state` and saved sessions load only the target origin's
274
+ cookies and localStorage, even if the file holds others (an SSO domain,
275
+ analytics).
276
+ - `--allow-origin https://api.example.com` widens all of the above to
277
+ another origin, for an app that calls a separate API host.
278
+ - A saved session is used unless `--no-session` or `--storage-state` is
279
+ given. Cookies and headers are added on top of it.
280
+
281
+ ### Security notes
282
+
283
+ - **Values are never printed.** Plain, rich and `--json` output and MCP
284
+ results show cookie and header names with `•••`. Console errors and
285
+ failure messages are scrubbed of every value rpc injected. The `--json`
286
+ result gains an `auth` field, for example
287
+ `{"cookies":[{"name":"sid","value":"•••","domain":"localhost","path":"/"}]}`.
288
+ - **Session files are credentials.** They are written with mode 0600 in a
289
+ 0700 directory (`$XDG_CONFIG_HOME/rpc-agent/sessions` if set;
290
+ `RPC_SESSIONS_DIR` overrides the location). Anyone who can read one can
291
+ act as you on that site until it expires. `rpc sessions clear` deletes
292
+ them. Don't commit a `--save` file.
293
+ - **Keep passwords out of shell history.** Prefer `RPC_LOGIN_PASSWORD` from
294
+ a secret store over `--password`, which lands in your shell history and
295
+ is visible to other users in the process list; rpc warns when you use it.
296
+ The same goes for tokens in `--cookie` and `--header`: pass them from an
297
+ environment variable (`--header "Authorization: Bearer $TOKEN"`), not
298
+ literally.
299
+ - **Use a test account.** Sign in as a user whose data you're happy for
300
+ screenshots (and the agent reading them) to see.
301
+
302
+ ## MCP server
303
+
304
+ `rpc mcp` runs an MCP server on stdio with three tools.
305
+
306
+ ### `screenshot`
307
+
308
+ | Param | Default | |
309
+ | --- | --- | --- |
310
+ | `url` | required | http(s), file, bare host or local path |
311
+ | `devices` | `["iphone-15-pro", "desktop-1440"]` | ids or aliases |
312
+ | `bezels` | false | framed copy of the first screen |
313
+ | `fullPage` | false | whole page, stitched past 16,384 px |
314
+ | `maxSlices` | 10 | full page: slices returned inline per device |
315
+ | `saveSlices` | auto | full page: `true` always writes slice JPEGs, `false` never; by default they are written only when some were cut by `maxSlices` |
316
+ | `sliceWidth` | 768 | full page: max slice width in px (raise it for small text on desktop) |
317
+ | `maxHeight` | 60000 | full page: CSS px cap |
318
+ | `reducedMotion` | false | emulate `prefers-reduced-motion: reduce` |
319
+ | `waitFor`, `waitMs`, `colorScheme`, `outDir`, `timeoutMs` | | as in the CLI |
320
+ | `inline` | true | return images, not just paths |
321
+ | `cookies` | | `[{ name, value, domain?, path? }]`, set on the target origin |
322
+ | `headers` | | `{ "Authorization": "Bearer …" }`, sent to the target origin only |
323
+ | `storageState` | | path to a Playwright storageState JSON |
324
+ | `useSession` | true when one exists | use the session saved by `rpc login` for the origin |
325
+ | `allowOrigins` | | extra origins that also get the headers and cookies |
326
+
327
+ The result starts with a plain-text block: one line per device (or the error
328
+ and hint), followed by the JSON summary (file paths, pixel sizes,
329
+ `scrollHeight`, `capturedHeight`, `segments`, `clipped`, `slices`, HTTP status,
330
+ final URL, title, console errors, `error`/`code`/`hint`).
331
+
332
+ Then the images:
333
+
334
+ - Without `fullPage`: one JPEG preview per device (the framed shot when
335
+ `bezels`), at most 1024 px on the long side.
336
+ - With `fullPage`: the framed first screen when `bezels`, labelled
337
+ `iphone-15 · first screen in bezel`, then the whole page as top-to-bottom
338
+ slices, each one viewport tall and labelled
339
+ `iphone-15 · slice 2/7 · y=852–1704 css px`. A three-device review of a
340
+ typical marketing page is roughly 0.5 MB of JPEG.
341
+
342
+ <picture>
343
+ <source media="(prefers-color-scheme: dark)" srcset="https://raw.githubusercontent.com/akintola4/rpc-agent/main/docs/media/mcp-slices-dark.png">
344
+ <img alt="The first five labelled full-page slices returned for an iPhone 15 capture" src="https://raw.githubusercontent.com/akintola4/rpc-agent/main/docs/media/mcp-slices-light.png">
345
+ </picture>
346
+
347
+ When a page has more slices than `maxSlices`, the text says which ones were
348
+ skipped (`slices 11-26 (y=8520-21656 css px) skipped inline`), all slices are
349
+ written to disk and the summary gives their paths and the full PNG, so the
350
+ agent can Read the rest without slicing anything itself.
351
+
352
+ When auth is in play the text starts with an `auth:` line naming the
353
+ session, cookies and headers (values masked), and the JSON summary gains an
354
+ `auth` field. A capture that lands on a login page fails with
355
+ `AUTH_REQUIRED` and the `rpc login` hint. Signing in is something the user
356
+ does in a terminal (`rpc login`); the agent then just calls `screenshot`.
357
+
358
+ ### `list_devices`
359
+
360
+ Takes an optional `category` (`apple-phone`, `android-phone`, `tablet`,
361
+ `laptop`) and returns ids, sizes and aliases.
362
+
363
+ ### `list_sessions`
364
+
365
+ Lists the origins with a saved session: cookie count, when it was saved and
366
+ whether it has expired. Never returns values.
367
+
368
+ Files go to `outDir`, which is resolved against the server's working
369
+ directory. Claude Code and Cursor start the server in the project root; in
370
+ Codex, set `cwd` on the server to choose the directory.
371
+
372
+ ### Claude Code
373
+
374
+ ```sh
375
+ claude mcp add --scope user rpc -- npx -y rpc-agent mcp
376
+ ```
377
+
378
+ Drop `--scope user` to add it to the current project only. Run
379
+ `npx rpc-agent install` once first so the server has a browser. If
380
+ `rpc-agent` is installed globally, `claude mcp add --scope user rpc -- rpc
381
+ mcp` skips the npx lookup on each start.
382
+
383
+ From a clone, point it at the build instead (the path must be absolute,
384
+ and Claude Code needs a restart or `/mcp` reconnect after rebuilding):
385
+
386
+ ```sh
387
+ claude mcp add --scope user rpc -- node "$(pwd)/dist/cli.js" mcp
388
+ ```
389
+
390
+ If you use the Claude Code sandbox, pages on hosts outside its network
391
+ allowlist fail with `ERR_NAME_NOT_RESOLVED` or a proxy error; the hint says
392
+ so. Add the domain to the allowlist.
393
+
394
+ ### Cursor
395
+
396
+ `~/.cursor/mcp.json` (global) or `.cursor/mcp.json` (per project):
397
+
398
+ ```json
399
+ {
400
+ "mcpServers": {
401
+ "rpc": {
402
+ "command": "npx",
403
+ "args": ["-y", "rpc-agent", "mcp"]
404
+ }
405
+ }
406
+ }
407
+ ```
408
+
409
+ From a clone, use `"command": "node"` and
410
+ `"args": ["/absolute/path/to/rpc-agent/dist/cli.js", "mcp"]`.
411
+
412
+ ### Codex
413
+
414
+ ```sh
415
+ codex mcp add rpc -- npx -y rpc-agent mcp
416
+ ```
417
+
418
+ Or add it to `~/.codex/config.toml` (or `.codex/config.toml` in a trusted
419
+ project). The Codex CLI and IDE extension read the same file:
420
+
421
+ ```toml
422
+ [mcp_servers.rpc]
423
+ command = "npx"
424
+ args = ["-y", "rpc-agent", "mcp"]
425
+ ```
426
+
427
+ From a clone, use `command = "node"` and
428
+ `args = ["/absolute/path/to/rpc-agent/dist/cli.js", "mcp"]`. `/mcp` in the
429
+ Codex TUI lists the servers it started.
430
+
431
+ ## Development
432
+
433
+ ```sh
434
+ pnpm test # vitest: unit tests plus e2e captures of local fixtures
435
+ pnpm typecheck
436
+ pnpm build
437
+ pnpm smoke:mcp [url] [--full-page] [--devices a,b] [--max-slices n]
438
+ pnpm media # regenerate the README images and GIF (needs ffmpeg)
439
+ ```
440
+
441
+ The e2e tests serve `test/fixtures` locally. `tall.html` is 21,656 CSS px
442
+ (64,968 px at DPR 3): the test checks every row of the stitched PNG against
443
+ the known layout across all seams, that the sticky header and a fixed badge
444
+ appear once, that the IntersectionObserver section rendered, and the slice
445
+ count and labels, including through the MCP server. The missing-browser tests
446
+ point `PLAYWRIGHT_BROWSERS_PATH` at an empty temp dir.
447
+
448
+ The README media are built from real captures by `scripts/media/build.ts`;
449
+ `docs/media.md` explains how. The CLI's design notes, and what it borrows
450
+ from the `skills` CLI, are in `docs/cli-design.md`.
451
+
452
+ ## Limitations
453
+
454
+ - Chromium only, portrait only. There is no landscape mode, and none of RPC's
455
+ fake browser chrome bars.
456
+ - Scroll-driven animations are captured in their scroll-top state. Entrance
457
+ animations that run once are usually fine after the warm-up scroll; ones
458
+ that reverse when scrolled back (GSAP `toggleActions` with reverse, scrubbed
459
+ timelines) may show their initial state. `reducedMotion` helps on sites that
460
+ respect it.
461
+ - Very tall stitched PNGs are big files; review them through the slices.
462
+ - The bezel geometry is RPC's, unchanged. On kind-default phones the page
463
+ covers the dynamic island or notch, and generic monitors are drawn edge to
464
+ edge with only the stand showing.
465
+ - No scripted interaction before capture (beyond `rpc login --form`).
466
+ - Login-page detection goes by the URL path. An app that shows its sign-in
467
+ form at the requested URL without redirecting is captured as is.
468
+ - Every MCP call launches a fresh Chromium, which adds about a second.
469
+
470
+ ## License
471
+
472
+ MIT. Bezel artwork and device data come from Responsive Point Check; see `NOTICE`.