---
name: tmpvid
description: Record a visual demo of your current branch or feature and attach it to a GitHub pull request. Use when asked to demo your work, show a feature working, record the app or browser, attach a video or gif to a PR, or share visual proof of a change. Covers capturing the recording, trimming it, and embedding it so it plays inline in the PR. Requires TMPVID_TOKEN env var (minted once by a human at tmpvid.com).
---

# tmpvid — record a demo of your branch, embed it in the PR

The job: produce a short visual recording of the thing you just built or fixed,
and put it in the pull request so reviewers **see** it working instead of
reading claims. GitHub's API has no way to attach media to a PR, and
externally-hosted **video never plays inline** in GitHub markdown (only images
do, via camo). So the pattern is: **record → trim → GIF for inline play + full
video for the click-through → embed the GIF linked to the video**. Everything
auto-expires in 7 days (30 max).

## 1. What a good demo looks like

Before touching tools, decide the slice worth showing:

- **One flow, 15–30 seconds.** "Clicking checkout applies the discount" — not a
  tour of the app. Reviewers scrub past anything longer.
- **Start clean, end on the proof.** Begin at the page before the action,
  finish on the state that demonstrates the change (success banner, new UI,
  fixed behavior).
- **No dead time.** Cut logins, page loads, and typing — either drive the app
  fast programmatically or trim the footage after.

If the change has no visual surface (pure backend, refactor), a demo may be the
wrong artifact — say so instead of recording a terminal.

## 2. Check the token

You need `TMPVID_TOKEN` in the environment. If it's missing, stop and tell the human:

> Mint an agent token at https://tmpvid.com/dashboard (one-time), then set
> `TMPVID_TOKEN` in this environment.

Do NOT attempt to register or mint a token yourself — there is no agent-side
registration.

## 3. Capture the recording

Use whatever recorder fits the situation; you need an mp4/webm at the end.

**Web app — Playwright** is the usual tool. Run the app, then drive the flow
with video recording on:

```js
const context = await browser.newContext({ recordVideo: { dir: "videos/" } });
const page = await context.newPage();
// ...drive the flow fast: page.goto(), page.click(), expect() the proof state...
await context.close(); // video is finalized on close → videos/*.webm
```

Tips: set a deterministic viewport (e.g. 1280×720), prefer `getByRole` clicks
over real-time typing (instant input = no dead footage), and pre-seed auth
(state/storage) instead of recording a login.

**No browser involved?** Record whatever shows the change — a terminal session
(asciinema → gif via `agg`), a simulator, a screenshot sequence. If nothing
moves, take **one good screenshot** of the result and skip to §5 with it — a
static image embeds identically to a GIF.

## 4. Trim and convert to GIF

Raw recordings are almost always too long and too large. Trim to the
interesting slice, then convert:

```bash
# trim: start 2s in, keep 18s (adjust to your footage — see §1)
ffmpeg -i recording.webm -ss 2 -t 18 -c copy trimmed.webm

# convert to GIF, 960px wide, 12fps
ffmpeg -i trimmed.webm -vf "fps=12,scale=960:-1:flags=lanczos,split[s0][s1];[s0]palettegen=max_colors=128[p];[s1][p]paletteuse=dither=bayer" -loop 0 demo.gif
```

**The GIF must be under 5MB** (GitHub's image proxy refuses larger, and tmpvid
rejects images over 5MB anyway). If it's bigger, drop fps to 8, then scale to
640, then shorten `-t`. Keep the trimmed video file too — it's the
click-through target and may be up to 25MB.

**No ffmpeg?** Fall back to a PNG screenshot as the inline image (§3).

## 5. Upload

Raw binary body, metadata in headers. Response is JSON. Upload both files: the
GIF and the video.

```bash
curl -sS -T demo.gif \
  -H "Authorization: Bearer $TMPVID_TOKEN" \
  -H "X-Tmpvid-Title: checkout flow test" \
  -H "X-Tmpvid-Repo: owner/repo" \
  -H "X-Tmpvid-Pr: 123" \
  https://tmpvid.com/v1/recordings
```

Response (fields shown are the complete shape; you mainly need `url` and
`page_url`):

```json
{
  "id": "…",
  "url": "https://tmpvid.dev/v/….gif",
  "page_url": "https://tmpvid.com/v/…",
  "kind": "gif",
  "size_bytes": 123456,
  "status": "live",
  "views": 0,
  "repo": "owner/repo",
  "pr": 123,
  "title": "checkout flow test",
  "created_at": "…",
  "expires_at": "…",
  "deduplicated": false
}
```

Metadata constraints (validated, 400 on violation): title ≤200 chars, repo must
look like `owner/name`, pr an integer 1–999999. Control/bidi/zero-width
characters are rejected everywhere.

## 6. Embed in the PR

Use the **GIF's `url`** as the image and the **video's `url`** (the raw
tmpvid.dev link) as the link target:

```markdown
[![checkout flow test](https://tmpvid.dev/v/<gif-id>.gif)](https://tmpvid.dev/v/<video-id>.mp4)
```

Reviewers see the GIF playing inline; clicking opens the raw video URL, which
browsers play directly in the built-in player (no page, no sign-in). If you
only made a screenshot, use it as the image and the video's `url` as the link
target.

The `page_url` field (tmpvid.com/v/<id>) is the **owner-only** provenance
page — agent, repo/PR, views. It exists for your human's dashboard and for
abuse moderation; anyone else gets a private-recording notice. Don't send
reviewers there.

## 7. Errors

Failures return `{"error": {"code": "…", "message": "…"}}` (plus `retry_after`
seconds on 429). Handle:

- `unauthorized` (401) — token missing/invalid. Check `TMPVID_TOKEN` is set and complete. Do NOT tell the human to re-mint (that's `token_revoked`).
- `token_revoked` (401) — tell the human to re-mint at tmpvid.com/dashboard.
- `empty_body` / `invalid_metadata` (400) — fix the request; see constraints above.
- `invalid_body` / `invalid_expiry` (400, PATCH/DELETE) — malformed JSON, past date, or beyond the 30-day cap. The cap is measured **from the recording's `created_at`**, not from now — compute `created_at + 30d`, not `now + 30d`.
- `hash_blocked` (403) — these exact bytes are banned. Do not retry the same bytes.
- `not_found` (404) — no such id (or not yours).
- `already_expired` (409, PATCH/DELETE) — the recording is no longer live; expiry is one-way.
- `too_large` (413) — video >25MB or image >5MB; shrink and retry.
- `unsupported_media_type` (415) — not mp4/webm/gif/png/jpg; check the file.
- `quota_exceeded` (429) — daily cap hit; wait `retry_after` seconds (UTC midnight).
- `commit_failed` / `upload_failed` (500) — infrastructure failure, safe to retry (identical bytes dedupe cleanly).
- `deduplicated: true` in a 200 — the same bytes were already uploaded; use the returned URLs, don't re-upload.

## Reference

- Check status: `curl -sS -H "Authorization: Bearer $TMPVID_TOKEN" https://tmpvid.com/v1/recordings/<id>` — trust `expires_at`, not `status` (a row can read `"live"` just past expiry; expiry is enforced at request time).
- Extend expiry (≤ created_at + 30 days): `curl -X PATCH -H "Authorization: Bearer $TMPVID_TOKEN" -H "Content-Type: application/json" -d '{"expires_at":"<iso8601>"}' https://tmpvid.com/v1/recordings/<id>`
- Delete early: `curl -X DELETE -H "Authorization: Bearer $TMPVID_TOKEN" https://tmpvid.com/v1/recordings/<id>`
