Install in Claude Code
Copygit clone --depth 1 https://github.com/oratis/LISA /tmp/cloud-screencast && cp -r /tmp/cloud-screencast/.claude/skills/cloud-screencast ~/.claude/skills/cloud-screencastThen start a new Claude Code session; the skill loads automatically.
Definition
SKILL.md
# cloud-screencast — reproducible UI demo videos on a throwaway VM
Records a web UI on a headless cloud box instead of the user's desktop. You get a
pristine app state, a chosen viewport, no personal data in frame, and a scripted
take you can re-run until it's right — none of which is true of a hand-recorded
local screen capture.
> **Cost + lifecycle:** this creates a billable VM. Always tell the user it exists,
> and **delete it** when the recording is downloaded (§8). An e2-standard-4 left
> running is roughly $95/month.
---
## Config
Fill once per project, then run.
```
PROJECT = <gcp-project-id>
ZONE = <zone, e.g. us-central1-a>
VM = <instance-name, e.g. app-record>
APP_REPO = <git url of the app, or an npm package name>
APP_START = <command that serves the UI, e.g. node dist/cli.js serve --web --port 5757>
APP_URL = <local url the browser opens, e.g. http://127.0.0.1:5757/>
SECRETS = <local env file the app needs, e.g. ~/.app/config.env>
```
---
## 1. Pick the geometry first
Everything downstream depends on this, and getting it wrong means re-recording.
Chrome's `--force-device-scale-factor=N` divides the X display into CSS pixels:
```
CSS viewport = Xvfb resolution / DSF
```
Record at **2× the delivery resolution** and downscale in post — supersampling is
what makes small UI text look sharp in an H.264 clip.
| Delivery | Xvfb | DSF | CSS viewport | Notes |
|---|---|---|---|---|
| 1920×1080 | 2880×1620 | 2 | 1440×810 | good default, 16:9 |
| 1920×1080 | 2560×1440 | 2 | 1280×720 | tighter; short UIs get clipped |
| 1080×1080 | 2160×2160 | 2 | 1080×1080 | square, better mobile in-feed |
**Do not just use 1280×720.** A real user's browser window is far taller than 720
CSS px, so a 720-tall viewport clips panels that never clip in real life — and the
clipped part is usually the payoff (the final state, the confirm button). Probe the
actual content height before committing (§4) and go taller if it overflows.
Keep integer DSF values. Fractional scale factors make Chrome's text rendering
noticeably softer.
## 2. Provision
```bash
gcloud compute instances create <VM> --project=<PROJECT> --zone=<ZONE> \
--machine-type=e2-standard-4 \
--image-family=ubuntu-2404-lts-amd64 --image-project=ubuntu-os-cloud \
--boot-disk-size=50GB --boot-disk-type=pd-balanced \
--labels=purpose=demo-recording
```
4 vCPU is the floor — x11grab at 2880×1620/30fps plus Chrome plus the app will
saturate 2 cores. Copy `reference/setup.sh` over and run it: it installs Xvfb,
ffmpeg, Chrome, xdotool, ImageMagick, Node, and — critically — **fonts**.
> **Fonts are the whole ballgame for "does this look right".** Most web UIs specify
> `-apple-system, BlinkMacSystemFont, "SF Pro Text"` and `ui-monospace, "SF Mono",
> Menlo`. None of those exist on Linux, so you get DejaVu fallback and the app looks
> subtly wrong in a way reviewers notice but can't name. Install `fonts-inter`
> (a near-exact SF Pro substitute, and often already in the CSS fallback chain),
> `fonts-jetbrains-mono`, `fonts-noto-color-emoji` (UIs lean on ❤️ 🌙 ★ far more than
> you expect), and `fonts-noto-cjk`. Then `fc-cache -f`.
**`gcloud compute ssh` is flaky under load.** It intermittently dies with
`RemoteDisconnected`. Fall back to plain SSH against the external IP with the key
gcloud already provisioned:
```bash
ssh -i ~/.ssh/google_compute_engine -o StrictHostKeyChecking=no <user>@<EXTERNAL_IP>
```
## 3. Deploy the app + secrets
Prefer building from source at the version you want to show — a published package
often lags the current version.
**Never put secrets in instance metadata or in a command line.** Metadata is
readable by anyone with project viewer, and argv shows up in `ps` and shell
history. Pipe them over SSH's stdin instead:
```bash
grep -E '^(API_KEY|BASE_URL)=' <SECRETS> | \
ssh ... "mkdir -p ~/.app && cat > ~/.app/config.env && chmod 600 ~/.app/config.env"
```
**Snapshot the pristine state before the app ever runs.** First-run flows —
onboarding, setup wizards, a birth ritual — happen exactly once, and you will need
three or four takes to get one good one:
```bash
cp -r ~/.app ~/.app-pristine # BEFORE first launch
# each retake starts with: rm -rf ~/.app && cp -r ~/.app-pristine ~/.app
```
## 4. Bring the stack up
`reference/stack.sh` does Xvfb → app → Chrome, idempotently. Chrome flags that
matter for a clean frame:
```
--kiosk no tabs, no URL bar, no bookmarks
--hide-scrollbars scrollbars read as clutter on video
--force-device-scale-factor see §1
--lang=en-US UIs branch on navigator.language; pin it
--remote-debugging-port=9222 so Playwright can attach
--user-data-dir=/tmp/... fresh profile, no first-run bubbles
--disable-features=Translate,TranslateUI,AutofillServerCommunication,MediaRouter
```
Also `xsetroot -solid '<app-bg-color>'` so any gap looks deliberate, and
`xset -dpms s off s noblank` so the screen never blanks mid-take.
Then **probe before recording** — check the content actually fits:
```js
await page.evaluate(() => ({
viewport: [innerWidth, innerHeight], dpr: devicePixelRatio,
overflow: document.querySelector('<container>').scrollHeight,
clientH: document.querySelector('<container>').clientHeight,
}))
```
If `scrollHeight > clientHeight` on something that shouldn't scroll, go back to §1.
## 5. Drive it
Attach to the running Chrome rather than letting Playwright launch its own — the
browser then survives a script crash, so a failed drive doesn't cost you the whole
recording:
```js
const b = await chromium.connectOverCDP('http://127.0.0.1:9222');
const page = b.contexts()[0].pages()[0];
```
Use `playwright-core` (no bundled browser download). Rules that make takes usable:
- **Print a `MARK <label> <seconds>` timeline.** The edit is driven entirely by
these timestamps; without them you are scrubbing a 6-minute file by hand.
- **Record `-draw_mouse 0`.** A programmatically driven cursor never