Automatically generate visual demo clips of UI changes in pull requests.
When a PR is opened, GitGlimpse reads the diff, uses an LLM to understand what changed, generates a Playwright interaction script, records a demo, and posts it as a PR comment — all without leaving CI.
PR opened/updated (or /glimpse comment)
│
▼
Diff Analyzer ── identifies changed files and affected routes
│
▼
Script Generator ── LLM reads diff → generates Playwright script
│
▼
Recorder ── Playwright executes script + captures video
│
▼
Publisher ── FFmpeg converts to GIF, posts as PR comment
One-prompt integration: Paste this into your AI agent (Cursor, Copilot, Claude Code, etc.):
Integrate git-glimpse into this repo: https://github.com/DeDuckProject/git-glimpse. Follow the README instructions.See an example integration PR to know what to expect.
Two files are recommended. The first is the main pipeline; the second gives instant 👀 feedback on /glimpse comments without adding noise to PR push checks.
Note: GitHub always reads
issue_commentworkflows from the default branch (main). Edits on feature branches are silently ignored for comment triggers — merge to main first for those changes to take effect. Action and core code changes are fine to test on branches.
.github/workflows/git-glimpse.yml — the main pipeline:
name: GitGlimpse
on:
pull_request:
types: [opened, synchronize]
issue_comment:
types: [created]
jobs:
demo:
runs-on: ubuntu-latest
if: >-
github.event_name == 'pull_request' ||
(github.event_name == 'issue_comment' &&
github.event.issue.pull_request != null &&
contains(github.event.comment.body, '/glimpse'))
permissions:
pull-requests: write
contents: write # required for uploading assets
issues: write # required for comment reactions
steps:
- uses: actions/checkout@v4
with:
fetch-depth: 0
ref: ${{ github.event_name == 'issue_comment' && format('refs/pull/{0}/head', github.event.issue.number) || '' }}
- uses: actions/setup-node@v4
with:
node-version: '20'
- run: npm ci
- name: Install FFmpeg
run: sudo apt-get install -y ffmpeg
- name: Install Playwright Chromium
run: npx playwright install chromium --with-deps
- uses: DeDuckProject/git-glimpse@v1
env:
ANTHROPIC_API_KEY: ${{ secrets.ANTHROPIC_API_KEY }}
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
- name: React with hooray on success
if: github.event_name == 'issue_comment' && success()
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: |
gh api repos/$GITHUB_REPOSITORY/issues/comments/${{ github.event.comment.id }}/reactions \
--method POST --field content=hooray || true
- name: React with confused on failure
if: github.event_name == 'issue_comment' && failure()
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: |
gh api repos/$GITHUB_REPOSITORY/issues/comments/${{ github.event.comment.id }}/reactions \
--method POST --field content=confused || true.github/workflows/git-glimpse-ack.yml — optional but recommended, reacts with 👀 within ~15–30s so the commenter knows their request was received before the heavy pipeline begins:
name: GitGlimpse Acknowledge
on:
issue_comment:
types: [created]
jobs:
ack:
if: >-
github.event.issue.pull_request != null &&
contains(github.event.comment.body, '/glimpse')
runs-on: ubuntu-latest
permissions:
issues: write
steps:
- name: React with eyes
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: |
gh api repos/$GITHUB_REPOSITORY/issues/comments/${{ github.event.comment.id }}/reactions \
--method POST --field content=eyes || trueThe ack workflow is kept separate so it never shows up as a "skipped" check on PR push events — which would add noise for non-developer reviewers.
// git-glimpse.config.ts
import type { GitGlimpseConfig } from '@git-glimpse/core';
export default {
app: {
startCommand: 'npm run dev',
readyWhen: { url: 'http://localhost:3000' },
},
} satisfies GitGlimpseConfig;Tip: Keep the default
automode for your first PR — it runs automatically so you'll see the demo right away. Once you've confirmed everything works, you can switch toon-demandif you prefer. Starting withon-demandmeans no demo on your first PR, so you won't know if the integration is working.
In your repo: Settings → Secrets and variables → Actions
ANTHROPIC_API_KEY— your Anthropic API key
That's it. Open a PR with UI changes and GitGlimpse will record and post a demo.
GitGlimpse supports three trigger modes that control when the pipeline runs.
Runs automatically on every PR push that contains UI-relevant file changes. Skips if no UI files changed and posts a comment explaining why, with instructions to force-run.
export default {
app: { startCommand: 'npm run dev' },
trigger: {
mode: 'auto',
},
} satisfies GitGlimpseConfig;Never runs automatically. Only runs when someone comments /glimpse on a PR.
Best for teams that want explicit control over when recordings are made, or for apps that are expensive to start.
export default {
app: { startCommand: 'npm run dev' },
trigger: {
mode: 'on-demand',
},
} satisfies GitGlimpseConfig;When a PR is pushed, GitGlimpse posts a skip comment:
On-demand mode is enabled. Comment
/glimpseon this PR to generate a demo.
Runs automatically, but only when the UI diff exceeds a line-change threshold. Small tweaks (typos, color values, minor copy edits) are skipped; meaningful visual changes trigger a recording.
export default {
app: { startCommand: 'npm run dev' },
trigger: {
mode: 'smart',
threshold: 20, // min lines changed to trigger (default: 5)
},
} satisfies GitGlimpseConfig;All trigger modes support /glimpse PR comments:
| Comment | Effect |
|---|---|
/glimpse |
Run the pipeline on this PR |
/glimpse --force |
Run even if no UI files changed |
/glimpse --route /products |
Record a specific route regardless of what changed |
The command prefix is configurable via trigger.commentCommand (default: /glimpse).
Note: The
issue_commentevent must be included in your workflow trigger (as shown in the quick start) for comment commands to work. Add the optionalgit-glimpse-ack.ymlworkflow to give commenters immediate 👀 feedback before the heavy pipeline starts.
Installing FFmpeg and Playwright Chromium takes 2–4 minutes. When using on-demand or smart mode, many PR pushes would be skipped anyway. The check-trigger companion action evaluates the trigger decision first, for the cost of a few seconds, so you can gate the heavy installs on the result.
jobs:
demo:
runs-on: ubuntu-latest
if: >-
github.event_name == 'pull_request' ||
(github.event_name == 'issue_comment' &&
github.event.issue.pull_request != null &&
contains(github.event.comment.body, '/glimpse'))
permissions:
pull-requests: write
contents: write
issues: write
steps:
- uses: actions/checkout@v4
with:
fetch-depth: 0
ref: ${{ github.event_name == 'issue_comment' && format('refs/pull/{0}/head', github.event.issue.number) || '' }}
- run: npm ci
# Lightweight check — runs in seconds
- uses: DeDuckProject/git-glimpse/check-trigger@v1
id: check
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
# Gate all heavy steps on the result
- name: Install FFmpeg
if: steps.check.outputs.should-run == 'true'
run: sudo apt-get install -y ffmpeg
- name: Cache Playwright browsers
if: steps.check.outputs.should-run == 'true'
uses: actions/cache@v4
with:
path: ~/.cache/ms-playwright
key: playwright-chromium-${{ hashFiles('**/package-lock.json') }}
restore-keys: playwright-chromium-
- name: Install Playwright Chromium
if: steps.check.outputs.should-run == 'true'
run: npx playwright install chromium --with-deps
- uses: DeDuckProject/git-glimpse@v1
if: steps.check.outputs.should-run == 'true'
env:
ANTHROPIC_API_KEY: ${{ secrets.ANTHROPIC_API_KEY }}
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
- name: React with hooray on success
if: github.event_name == 'issue_comment' && steps.check.outputs.should-run == 'true' && success()
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: |
gh api repos/$GITHUB_REPOSITORY/issues/comments/${{ github.event.comment.id }}/reactions \
--method POST --field content=hooray || true
- name: React with confused on failure
if: github.event_name == 'issue_comment' && failure()
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: |
gh api repos/$GITHUB_REPOSITORY/issues/comments/${{ github.event.comment.id }}/reactions \
--method POST --field content=confused || truecheck-trigger outputs:
| Output | Values |
|---|---|
should-run |
"true" or "false" |
If your app already has deploy previews, skip startCommand — point GitGlimpse at the preview URL instead:
- name: Deploy to Vercel
id: vercel
uses: amondnet/vercel-action@v25
with:
vercel-token: ${{ secrets.VERCEL_TOKEN }}
# ...
- uses: DeDuckProject/git-glimpse@v1
with:
preview-url: ${{ steps.vercel.outputs.preview-url }}
env:
ANTHROPIC_API_KEY: ${{ secrets.ANTHROPIC_API_KEY }}
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}export default {
app: {
previewUrl: 'VERCEL_URL', // env var name — or a literal URL
},
} satisfies GitGlimpseConfig;# Run on your current changes
npx git-glimpse run --diff HEAD~1 --url http://localhost:3000 --open
# Initialize a config file
npx git-glimpse init// git-glimpse.config.ts
import type { GitGlimpseConfig } from '@git-glimpse/core';
export default {
// ── App startup ───────────────────────────────────────────────────────────
app: {
// Option A: GitGlimpse starts your app
startCommand: 'npm run dev',
readyWhen: {
url: 'http://localhost:3000', // poll this URL until it responds
status: 200, // expected status code (default: 200)
timeout: 30000, // ms before giving up (default: 30000)
},
env: { DATABASE_URL: 'sqlite::memory:' }, // extra env vars for startCommand
// Option B: Use an existing deploy preview
// previewUrl: 'VERCEL_URL', // env var name or literal URL
// Hint: extra context injected into every LLM prompt.
// Use this to tell the LLM about authentication, test accounts, app quirks, etc.
hint: 'Use email demo@example.com and password "password" to log in.',
},
// ── Trigger ───────────────────────────────────────────────────────────────
trigger: {
mode: 'auto', // 'auto' | 'on-demand' | 'smart' (default: 'auto')
threshold: 5, // smart mode: min lines changed to trigger (default: 5)
include: ['src/**', 'app/**'], // only count these files as UI-relevant
exclude: ['**/*.test.*', '**/*.spec.*'], // always skip these files
commentCommand: '/glimpse', // PR comment command (default: '/glimpse')
skipComment: true, // post a comment when skipping (default: true)
},
// ── Route map (optional, improves LLM accuracy) ───────────────────────────
// Maps source file globs → URLs where changes are visible.
// Useful for shared components, Liquid templates, Shopify extensions, etc.
routeMap: {
'app/routes/products.$id.tsx': '/products/sample-product',
'src/components/Header.tsx': '/',
'extensions/my-block/**': '/products/sample-product',
},
// ── Setup (optional) ──────────────────────────────────────────────────────
// Shell command to run before recording (e.g. seed the database)
setup: 'node scripts/seed.js',
// ── Recording ─────────────────────────────────────────────────────────────
recording: {
format: 'gif', // 'gif' | 'mp4' | 'webm' (default: 'gif')
maxDuration: 30, // seconds (default: 30)
viewport: { width: 1280, height: 720 },
deviceScaleFactor: 2, // 2 = retina/HiDPI (default: 2)
showMouseClicks: true, // highlight clicks in recording
},
// ── LLM ───────────────────────────────────────────────────────────────────
llm: {
provider: 'anthropic', // 'anthropic' | 'openai'
model: 'claude-sonnet-4-6', // or 'gpt-4o', etc.
},
} satisfies GitGlimpseConfig;| Input | Default | Description |
|---|---|---|
config-path |
git-glimpse.config.ts |
Path to config file |
preview-url |
— | External preview URL (overrides config) |
start-command |
— | App start command (overrides config) |
trigger-mode |
(from config) | Override trigger mode: auto, on-demand, or smart |
format |
gif |
Output format: gif, mp4, webm |
max-duration |
30 |
Max recording duration in seconds |
| Output | Description |
|---|---|
recording-url |
URL of the uploaded recording artifact |
comment-url |
URL of the posted PR comment |
success |
Whether recording succeeded (true/false) |
GitGlimpse automatically maps changed files to URLs using framework conventions:
| Framework | Convention | Example |
|---|---|---|
| Remix | app/routes/products.$id.tsx |
/products/:id |
| Next.js App Router | app/products/[id]/page.tsx |
/products/:id |
| Next.js Pages Router | pages/products/[id].tsx |
/products/:id |
| SvelteKit | src/routes/products/[id]/+page.svelte |
/products/:id |
For files outside these conventions, use routeMap in the config to explicitly map them.
The app.hint field lets you inject free-form context into every LLM prompt. The text is included verbatim under an "App-specific notes" heading, so the model sees it before generating any Playwright script.
Common uses:
- Authentication — tell the LLM which test account to use so it can log in before navigating to the changed page.
- Seed data — describe what records exist in the database so the script can interact with realistic content.
- App quirks — explain non-obvious UI patterns (e.g. a custom modal, a multi-step wizard) so the model handles them correctly.
export default {
app: {
startCommand: 'npm run dev',
// Injected into every prompt sent to the LLM
hint: 'Use email demo@example.com and password "password" to log in before navigating anywhere.',
},
} satisfies GitGlimpseConfig;The hint is applied to both diff-driven scripts and general overview demos, so it is always available regardless of trigger mode.
If the generated Playwright script fails (element not found, timeout, etc.), GitGlimpse:
- Retries up to 2 times, feeding the error back to the LLM for a revised script
- Falls back to static screenshots if all attempts fail
The PR comment is always posted — with a GIF if recording succeeded, or screenshots as a fallback.
- Node.js ≥ 20
- Anthropic API key — set as
ANTHROPIC_API_KEYenvironment variable - FFmpeg — required for GIF/MP4 conversion
- GitHub Actions:
sudo apt-get install -y ffmpeg - macOS:
brew install ffmpeg - Ubuntu/Debian:
sudo apt-get install -y ffmpeg
- GitHub Actions:
- Playwright Chromium —
npx playwright install chromium --with-deps
Disclaimer: The figures below are based on the maintainer's own testing and may not reflect your usage patterns. Token consumption varies significantly by diff size, app complexity, and retry count. Use these numbers as a rough reference only — your costs may be higher or lower.
Each GitGlimpse run makes one or more LLM calls to generate the Playwright interaction script. The table below summarizes observed costs using claude-sonnet-4-6 with default settings.
| Metric | Value |
|---|---|
| Average cost per request | ~$0.015 (1.5¢) |
| 95th percentile | ~$0.035 |
| Peak (max observed) | ~$0.047 |
| Requests per USD | ~66 |
| Sample size | ~82 requests / 246k tokens |
Token breakdown (per request, approximate):
| Token type | Volume | Share of tokens | Share of cost |
|---|---|---|---|
| Input | ~205k total | ~83% | ~50% |
| Output | ~42k total | ~17% | ~50% |
For high-frequency teams, use trigger.mode: 'smart' or 'on-demand' to avoid running the LLM on every push.
pnpm install
pnpm build
pnpm test # unit tests
pnpm run test:integration # Playwright + FFmpeg tests (no API key needed)
pnpm run test:llm # full pipeline with real LLM (requires ANTHROPIC_API_KEY)See CLAUDE.md for repo structure and contributor notes.
Large diffs are automatically truncated before being sent to the LLM to stay within reasonable token budgets:
| Stage | Character limit | Purpose |
|---|---|---|
| Change summarizer | 8 000 chars | Analyzing what changed and suggesting a demo flow |
| Script generator | 10 000 chars | Generating the Playwright interaction script |
When a diff exceeds the limit, it is cut at the threshold and a ... (diff truncated) marker is appended so the LLM knows the input is incomplete. In practice this means very large PRs may produce less accurate scripts — consider using routeMap or app.hint to give the LLM additional context when working with big diffs.
- Single entry point — only one preview URL or start command per run is supported; multiple entry points are planned.
- LLM provider — only Anthropic is supported currently; support for other providers is planned.
- Multiple preview URL support — currently GitGlimpse accepts a single preview URL per run; planned support for specifying multiple starting points so a single PR can record demos across several routes or environments simultaneously.
- GitHub App — a first-party GitHub App to reduce setup friction: no workflow files to copy, no secrets to configure manually, and automatic installation across repos in an organization.
AGPL
