UI はローカライズ済みです。スキルの紹介と説明文書は選択した言語へ AI が自動翻訳し、コード・コマンド・パス・専門用語は原文のままです。原作者の文書が優先されます。
スキルを探す
デザイン · SKILL.md

headless-capture

headless-capture

Capture screenshots/recordings of desktop or web apps for visual verification. Works across Linux, macOS, and Windows. Supports Tauri, Electron, native desktop apps (via Xvfb/screencapture/gdigrab), and web apps (via Playwright). Use proactively after UI changes so the human can verify without running the app locally.

Independent GitHub snapshot · Automated classification · Original documentation governs

スキルの説明

GitHub

name: headless-capture description: Capture screenshots/recordings of desktop or web apps for visual verification. Works across Linux, macOS, and Windows. Supports Tauri, Electron, native desktop apps (via Xvfb/screencapture/gdigrab), and web apps (via Playwright). Use proactively after UI changes so the human can verify without running the app locally. triggers:

  • after modifying UI files (*.tsx, *.vue, *.svelte, *.jsx, *.html, *.css, src-tauri/, renderer/)
  • when user asks to verify UI changes visually
  • when user mentions "screenshot", "capture", "recording", or "show me"
  • after implementing a UI feature or fixing a visual bug
  • when working remotely and the human can't see the running app

Headless Capture

Capture screenshots and recordings of desktop or web applications for visual verification. This skill helps remote agents show their UI work to humans who aren't watching the screen live.

This skill is about delivering visual artifacts to a human for verification. It is not an agent-vision feedback loop; for browser automation and self-correction, prefer Playwright/CDP-style tooling directly.

When to use this skill

Proactively suggest captures when:

  • You just modified UI code (React, Vue, Svelte, HTML/CSS, Tauri frontend, Electron renderer)
  • You implemented a visual feature or fixed a UI bug
  • The user asks "what does it look like?" or "can you show me?"
  • You're working in a remote session and the human can't see the app

Don't use this skill when:

  • Pure backend/API changes with no UI impact
  • The user is sitting in front of the same screen (just tell them to look)
  • The app doesn't have a visual interface

Supported platforms and app types

App TypeLinuxmacOSWindows
Web apps (Vite, Next.js, etc.)PlaywrightPlaywrightPlaywright
TauriXvfb + import/ffmpegscreencapture + ffmpegnircmd + ffmpeg
ElectronXvfb + import/ffmpegscreencapture + ffmpegnircmd + ffmpeg
Native GTK/Qt/etc.Xvfb + import/ffmpegscreencapture + ffmpegnircmd + ffmpeg
VS Code extensionXvfb + downloaded VS Codeadapt (Xvfb shape)adapt (Xvfb shape)

Playwright is the preferred path for web apps — it's simpler, cross-platform, and doesn't need a virtual display.

Templates and recipes

App typeTemplateRecipe
Tauri / Electron / GTK / Qt / generic X11 (own binary)templates/capture.mjsrecipes/tauri.md
VS Code extension (hosted by a downloaded VS Code)templates/capture-vscode-extension.mjsrecipes/vscode-extension.md
Web appstemplates/capture.mjs (Playwright path)—

Own-binary apps (Tauri/Electron/GTK/Qt) differ only by values (binary path, window name, env hints, build command) → one generic template. A VS Code extension diverges structurally (download the editor, run in Extension Development Host mode, seed a user-data-dir to auto-open the panel) → its own template. A new variant earns its own template only when it adds lifecycle steps, not when it only changes values.

What you're building

  1. Capture script (scripts/capture.mjs) — unified script that:

    • Detects OS and app type
    • Uses Playwright for web apps, platform-native tools for desktop apps
    • Outputs to artifacts/captures/
  2. SessionStart hook (Linux/Claude Code on the web) — installs toolchain automatically

  3. Wrapper scripts in package.json for easy invocation

  4. .gitignore entry for artifacts

Workflow

1. Detect environment

First, determine OS and app type:

// OS detection
const platform = process.platform; // 'linux', 'darwin', 'win32'

// App type detection - check in order:
// 1. Web app: package.json has vite/next/nuxt/astro/remix OR user specifies URL
// 2. VS Code extension: package.json has engines.vscode + contributes
// 3. Tauri: src-tauri/Cargo.toml exists
// 4. Electron: package.json has "electron" dependency
// 5. Native: ask user for binary path

A VS Code extension has no URL and isn't your own binary — the "app" is a real VS Code (Electron) editor hosting your extension. It needs the dedicated templates/capture-vscode-extension.mjs + recipes/vscode-extension.md, not the generic capture.mjs. If you can't tell the app type, ask the user — don't guess.

2. Choose capture strategy

Web apps (Playwright path):

  • Install: npm install -D playwright @playwright/test
  • No Xvfb needed — Playwright runs headless Chromium
  • Works identically on all platforms
  • Can capture at specific viewport sizes, wait for selectors, etc.

Desktop apps (platform-native path):

OSDisplayScreenshotRecordingInput
LinuxXvfb (virtual)import -window rootffmpeg -f x11grabxdotool
macOSReal displayscreencapture -xffmpeg -f avfoundationcliclick / AppleScript
WindowsReal displayPowerShell or nircmdffmpeg -f gdigrabnircmd / AutoHotkey

3. Confirm scope with user

Use AskUserQuestion for:

  • App type if ambiguous (web vs desktop, which URL/binary)
  • Build behavior: auto-build when missing vs. fail with hint
  • Capture trigger: one-shot vs. watching for changes

Don't ask about things you can decide yourself.

4. Set up the SessionStart hook (Linux only)

Copy templates/session-start.sh to .claude/hooks/session-start.sh. Register in .claude/settings.json:

{
  "hooks": {
    "SessionStart": [
      {
        "hooks": [
          {
            "type": "command",
            "command": "$CLAUDE_PROJECT_DIR/.claude/hooks/session-start.sh"
          }
        ]
      }
    ]
  }
}

The hook is gated on $CLAUDE_CODE_REMOTE / $CURSOR_AGENT so it only runs in remote agent sessions, not on a local Linux dev machine. It falls back to sudo when the container runs as a non-root user.

Cursor Cloud Agent caveat: Cursor does not run SessionStart hooks. Install the toolchain explicitly there — run session-start.sh directly with CURSOR_AGENT=1, or call it from a build/test step.

For macOS/Windows, document manual install in README instead of a hook.

5. Drop in the capture script

Web / own-binary apps (Tauri/Electron/GTK/Qt) — copy templates/capture.mjs to scripts/capture.mjs and customize (see recipes/tauri.md for the own-binary swap-ins):

  • APP_TYPE: 'web', 'tauri', 'electron', or 'native'
  • WEB_URL: dev server URL for web apps (e.g., http://localhost:5173)
  • APP_BINARY: path to desktop app binary
  • WINDOW_NAME: for desktop app window detection
  • Build command in ensureBinary() for desktop apps

On Linux the desktop path defaults to 1920x1080, resizes the window to fill the virtual screen (no window manager under Xvfb to maximize it), and captures the window region rather than the whole screen — so the screen background doesn't bleed in as a black strip. It falls back to root capture when xdotool is absent.

VS Code extension — copy templates/capture-vscode-extension.mjs to scripts/capture-vscode.mjs and fill in EXTENSION_MAIN, BUILD_CMD, AUTO_OPEN_SETTINGS, and (optionally) the tour coords. See recipes/vscode-extension.md. If you only changed the extension's webview content (not the editor chrome), prefer Playwright against the webview HTML or over CDP — it's faster and more reliable than full-editor capture.

6. Add wrapper scripts

{
  "scripts": {
    "capture": "node scripts/capture.mjs screenshot",
    "capture:record": "node scripts/capture.mjs record",
    "capture:web": "node scripts/capture.mjs screenshot --web",
    "capture:web:record": "node scripts/capture.mjs record --web"
  }
}

7. Ignore output directory

Add to .gitignore:

artifacts/captures/

8. Validate end-to-end

For web apps:

  1. Start dev server: npm run dev
  2. Run: npm run capture:web
  3. Verify PNG shows the app, not a blank page

For desktop apps:

  1. Build the app if needed — captures only matter if they show the real app, not a placeholder.
  2. Run: npm run capture
  3. Inspect the output; don't just trust the exit code:
    • Tiny PNG (~hundreds of bytes, 1-bit grayscale) → window appeared but hadn't painted. Bump --settle.
    • Black/empty frame → check WINDOW_NAME matches, or the app isn't launching on the capture display.
    • Recording shows nothing changing → app may be off-display, or (during a tour) xdotool coords are hitting title-bar chrome instead of the UI.
  4. Run a short recording too if you'll deliver video.

9. Attach capture to user

Getting the artifact in front of the human proves the loop works end to end.

  • Claude Code on the web: call SendUserFile with the PNG/MP4 and a one-line caption. Paths under artifacts/captures/ work even though they are gitignored.
  • Cursor Cloud Agent / other harnesses: use the Read tool on the artifact path — but only after copying out of the gitignored artifacts/captures/ (see below). PNG for stills; MP4 for recordings (inlines reliably). Avoid GIF in chat — it often hangs while loading.

If the artifact is broken (blank/wrong) after the obvious fixes, send what you have anyway with a note on what went wrong rather than reporting success.

Cursor Cloud Agent: copy before Read

Capture scripts write to artifacts/captures/, which is gitignored. That's fine for generation, but do not Read or reference a gitignored path directly — Cursor chat shows "Waiting for upload…" forever. Copy it to the harness's public artifact dir first, then Read that path:

mkdir -p /opt/cursor/artifacts/screenshots
cp artifacts/captures/my-shot.png /opt/cursor/artifacts/screenshots/
# then Read /opt/cursor/artifacts/screenshots/my-shot.png

Don't reference artifacts/captures/… in markdown image syntax — those URLs aren't reachable from chat. Optional: bake the copy step into the capture script via a PUBLIC_ARTIFACT_DIR env var pointing at the public dir.

10. Document in README/AGENTS.md

Add a section explaining:

  • How to run captures
  • Env knobs available
  • Platform-specific setup (macOS: brew install ffmpeg cliclick, Windows: choco install ffmpeg nircmd)

Proactive usage

After modifying UI files, proactively offer to capture:

I've updated the login form styling. Would you like me to capture a screenshot so you can verify the changes?

If the user has previously accepted captures in the session, just do it:

I've updated the button colors. Here's a screenshot of the result:
[attach screenshot]

Platform-specific notes

Linux (Claude Code on the web)

  • SessionStart hook handles all deps
  • Xvfb creates a virtual display — no real monitor needed
  • Default display :99, override with CAPTURE_DISPLAY

macOS

  • Uses real display — works even without a logged-in GUI session if you enable screen recording permissions
  • Install: brew install ffmpeg cliclick
  • screencapture -x for silent screenshots
  • For GUI-less sessions (SSH), need to enable headless screen capture (complex, prefer Playwright for web apps)

Windows

  • Uses real display
  • Install: choco install ffmpeg nircmd or winget install ffmpeg
  • PowerShell Add-Type can capture screen without external tools
  • For headless/CI, use virtual display drivers or prefer Playwright

All platforms (Playwright path)

  • npm install -D playwright
  • npx playwright install chromium
  • Works headless everywhere, no platform-specific setup

Mouse-driven tours (optional, Linux/Xvfb)

The default record mode grabs a settled, idle window — no input during the clip. Validate that first; only reach for tours if the user wants navigation in the deliverable (clicking tabs, opening menus, etc.).

  • Start ffmpeg -f x11grab, then drive clicks with xdotool during the grab.
  • Pin the window first (xdotool windowmove <id> 0 0) and calibrate Y: mousemove --window coords are relative to the decorated window, not the webview. Low Y often lands on the title bar / app logo, so the tour looks frozen on one view. Take a calibration screenshot before scripting clicks.
  • Mis-clicks on the title bar or resize edges can drag the window off-screen — keep tours session-local; don't commit machine-specific coordinate scripts to the repo.
  • Robust long-term alternative: switch views from inside the app (by element id / a test hook), or use CDP / a Tauri MCP plugin instead of pixel coordinates.

Common issues

Cursor chat stuck on "Waiting for upload…":

  • You attached a gitignored path (artifacts/captures/…) or used markdown ![](artifacts/…). Copy to /opt/cursor/artifacts/screenshots/ and Read that path instead (see step 9).

Blank captures (desktop apps):

  • Window exists but hasn't painted yet
  • Increase --settle time (default 3s)
  • For Tauri, ensure WebKit env hints are set

Playwright can't connect:

  • Dev server not running — start it first
  • Wrong URL — check port number
  • Page not ready — add waitForSelector or increase timeout

Permission denied (macOS):

  • System Preferences → Security & Privacy → Screen Recording
  • Add Terminal/iTerm/your IDE

Display collision (Linux):

  • Another Xvfb on :99
  • Set CAPTURE_DISPLAY=:98 or another number

Recording shows nothing:

  • App launched off-screen or on wrong display
  • Check DISPLAY env var matches Xvfb

xdotool coords on decorated windows (Linux tours):

  • Low Y hits title-bar chrome, not the app — the tour looks "stuck" on one view. Calibrate from a screenshot.
  • Bad edge clicks can slide the window off-frame on Xvfb.

Window-name mismatch (Linux):

  • Window titles vary by app and locale. If --name finds nothing, the script falls back to capturing the root display, which is fine.

Tauri build deps (Linux):

  • If cargo build fails with gdk-3.0 not found or similar, install: libgtk-3-dev libwebkit2gtk-4.1-dev libayatana-appindicator3-dev librsvg2-dev libcurl4-openssl-dev libssl-dev pkg-config.
  • The SessionStart hook intentionally does not pull these in (heavy, only needed when building) — flag them in your docs.

EGL / DRI3 warnings (Linux):

  • Harmless under Xvfb — neither is available. Don't chase them.

Lingering processes:

  • Trap SIGINT/SIGTERM in the capture script and kill the app + Xvfb in cleanup, otherwise repeat runs collide on the display.

apt-get without root:

  • The SessionStart hook assumes root or passwordless sudo in the remote container. If neither is available, install manually: apt-get install -y ffmpeg imagemagick xdotool x11-utils xvfb.

Templates and recipes

  • templates/capture.mjs — unified capture script (web + own-binary desktop)
  • templates/capture-vscode-extension.mjs — VS Code extension capture (downloads VS Code, EDH mode)
  • templates/session-start.sh — Linux SessionStart hook
  • recipes/tauri.md — own-binary (Tauri/Electron/GTK/Qt) swap-ins
  • recipes/vscode-extension.md — VS Code extension lifecycle + calibration
保存したスキル