video → frames → LLM.

Turn a video — or an animated GIF, APNG, or WebP — into a timeline of still frames so any LLM canwatchit.

Ships as aClaude Code pluginwith drag-and-drop auto-invoke, native manifests forCursor · Windsurf · Cline · Codex CLI · Gemini CLI, and a standalone CLI with19 pluggable sinks(SQLite, Postgres, S3-compatible, Slack, Discord, Webhook, GraphQL, Notion, Obsidian, IDE attachments, Linear, GitHub Issues, Sentry, Chroma, Qdrant, Pinecone, pgvector, MongoDB, MemPalace).

npm versionNode 22 or later MIT license

peepshow ./demo.mp4scene detection · 6 frames · 0.8s
Container
Duration
Resolution
Codec
Director
Studio

extracted6 framesfedto LLM as imagesreadyfor analysis_

Who it's for

LLMs can read images. Your footage is a sequence.

Whatever's in your video — a bug, a break-in, a lecture, an exploit — peepshow turns it into still frames an LLM already knows how to reason about.

A QA engineer screen-records a flicker. A designer sends a 12-second Loom. A user uploads a.movwith the frame that breaks everything. peepshow turns that clip into scene-aware stills so the model sees the bug frame-by-frame.

# drop a repro into Claude — the hook does the rest
peepshow ./bug-repro.mov --strategy scene --max 12
  • 12 stillsacross the scene changes
  • JSON metadatathe LLM can cite — timestamps, scores, tags
  • Piped through/peepshow:slidesautomatically

one slideshow · any LLM · any video

Why peepshow?

Drag & drop — same UX as images

Drop an.mp4,.mov, or.gifinto the Claude prompt. AUserPromptSubmithook detects the path and auto-invokes/peepshow:slides. No slash command required.

Scene-change detection

Defaults to ffmpeg'sselect='gt(scene,0.3)'filter — catches visually distinct moments, not fixed-fps noise. Falls back to interval sampling for short clips.

19 built-in sinks

Every run can fan out to databases, object stores, vector DBs, issue trackers, chat, and wiki systems. All tested, all shipped on npm.Full list →

Conditional routing

Sinks fire only when the input matches —--when director=Kubrick,--when path=/Volumes/Work/,--when extension=mp4,mov. Turns peepshow into a smart router.

Hardware-accelerated

VideoToolbox on macOS, VAAPI on Linux, D3D11VA on Windows — auto-detected. Prefers native ffmpeg over the bundled static build for extra speed.

Live statusline badge

[PEEPSHOW:decoding:42%]mid-run,[PEEPSHOW:5frm:scene:system]after,[PEEPSHOW|3s]with three auto-sinks armed.

Works with any LLM CLI

Ships native agent manifests forClaude · Cursor · Windsurf · Cline · Codex · Gemini. Integration snippets foraider · llm · Copilot · Continue · Cody · Zedin the docs.

Container metadata

Title, director, producer, show, genre, creation time — every tag inside the video's container flows into the JSON the LLM sees. Ground answers in what the videosays it is, not just what's on screen.

The[PEEPSHOW]statusline badge

A one-line live indicator rendered in the Claude Code statusline — shows whether peepshow is idle, probing ffmpeg, decoding, or done. Also reports which sinks fired, how many frames were extracted, which strategy won.

~/projects$[PEEPSHOW:idle]rotates every ~1.4s · real peepshow states

Rich states out of the box:[PEEPSHOW:decoding:42%]mid-run,[PEEPSHOW:6frm:scene:system|3s]after (six frames, scene-detection won, used native ffmpeg, three auto-sinks armed),[PEEPSHOW:sink:slack:posted]when a sink succeeds. All readable at a glance without leaving the editor.

More things peepshow does that aren't obvious

Auto-sinks that survive sessions

peepshow sinks add obsidianonce — every future run fires it. Config at~/.peepshow/sinks.json. The statusline shows how many are armed:[PEEPSHOW|3s].

Conditional routing via--when

--when director=Kubrick,--when extension=mp4,mov,--when path=/Volumes/Work/. Sinks only fire when the input matches. ANDed within a sink, ORed across values.

Container metadata flows through

Title, director, producer, show, genre, creation time — every tag inside the video's container reaches both the LLMandeach sink's payload.--when key=valuecan match any of them.

Four emit formats

--emit paths(default),--emit json(structured),--emit markdown(human-readable),--emit caveman(token-compressed viacaveman). Pair with any agent pipeline.

ffmpeg selection heuristic

PEEPSHOW_FFMPEGenv → nativeffmpegon PATH → bundledffmpeg-static. System build wins because brew/choco/apt ship with VideoToolbox, NVENC, QSV, VAAPI, D3D11VA. Static build is the zero-config safety net.

Written-your-own sinks

A sink is any executable that reads the--emit jsonpayload on stdin. Shell, Node, Python, Go, Rust — doesn't matter. Register persistent ones withpeepshow sinks add-cmd 'your-cmd'.

Compressor auto-detect

Installcavemanon PATH — peepshow picks it up and auto-compresses output unless you've explicitly set--emit. Opt out withPEEPSHOW_AUTO_COMPRESS=0.

GIF, APNG, animated WebP

Same pipeline, different container. Meme-length loops and multi-frame screenshots all flow through the same scene-detect → prune → sink pipeline.

Install

Two steps. Runtime from npm, plugin from GitHub.

1. Runtime from npm

npm i -g peepshow            # adds peepshow + all peepshow-sink-* bins to PATH
npx peepshow ./video.mp4     # one-shot, no install

Optional native ffmpeg for faster hardware decoding:

brew install ffmpeg          # macOS
choco install ffmpeg-full    # Windows
sudo apt install ffmpeg      # Debian / Ubuntu

Skip entirely — peepshow bundlesffmpeg-staticas a fallback.

2. Plugin for Claude Code

claude plugin marketplace add t0mtaylor/peepshow
claude plugin install peepshow@peepshow-marketplace

Restartclaude— the/peepshow:slidesskill is live, and the drag-and-drop hook fires on every prompt.

Use anywhere

peepshow ./bug.mov                              # paths + human-readable stats
peepshow ./demo.mp4 --emit json | jq            # structured
peepshow ./loop.gif --emit caveman              # token-compressed
peepshow ./keynote.mp4 --sink folder:/shared    # fan out

19built-in sinks

Every extract can fan out to any number of destinations. A sink reads the JSON payload on stdin, does anything. Conditional matchers (--when) mean each sink only fires when the input fits.

Browse the full sink catalogue →

Auto-sinks — configure once, fire forever

peepshow sinks add folder:/Volumes/Shared/peepshow
peepshow sinks add postgres
peepshow sinks add-cmd 'node ~/scripts/obsidian-sync.js'
peepshow sinks list                                # see what's active

peepshow ./any-video.mp4                           # all three fire
peepshow ./one-off.mp4 --no-auto-sinks             # skip for this run

Conditional routing —--when

peepshow sinks add folder:/Volumes/Family --when extension=mp4,mov
peepshow sinks add postgres --when path=/Volumes/Work/
peepshow sinks add folder:/Cinema/Kubrick --when director=Kubrick --when genre=Thriller
peepshow sinks add-cmd 'node x.js' --when filename='*vacation*'

Agent support

Native manifests for every major LLM CLI. Install peepshow once; every agent picks it up.

AgentManifestInstall
Claude Code.claude-plugin/+skills/+hooks/claude plugin marketplace add t0mtaylor/peepshow
Cursor.cursor/rules/peepshow.mdcpicked up when peepshow is installed into the project
Windsurf.windsurf/rules/peepshow.mdsame
Cline.clinerules/peepshow.mdsame
Codex CLI.codex/hooks.json+.codex/config.tomlSessionStart hook announces peepshow; invoke via Bash
Gemini CLIgemini-extension.json+GEMINI.mdpoint Gemini at the repo as a custom command
Codex agents / Zed AIAGENTS.mdconvention-based pickup
Generic agents registry.agents/plugins/marketplace.jsonnpx skills add t0mtaylor/peepshow(once supported)

Deep-dive each agent integration →

Plus documented integration snippets forCopilot CLI · aider · llm · Continue · Cody · Zed AI · Perplexity · Ollamaindocs/INTEGRATIONS.md.

FAQ

Does it work without ffmpeg installed?
Yes —npm i peepshowpulls inffmpeg-staticas a fallback. Native ffmpeg (via brew / choco / apt) is preferred for hardware decoding but optional.
What happens when I drop a video into Claude Code?
AUserPromptSubmithook spots the video path, injects a reminder into Claude's context, and Claude auto-invokes/peepshow:slides <path>. Frames are extracted, read as images, and Claude answers — without you typing the slash command.
Are static images handled too?
No — static JPG / PNG / WebP are already readable by every LLM. peepshow only runs for things withmultiple frames across time: video and animated images.
Where does the runtime code come from?
npm. Thepublic GitHub repoships only manifests, hooks, and docs — no compiled code. That keeps the source trusted (versioned on npm with integrity hashes) and the GitHub surface clean.
Can I write my own sink?
Any executable that reads the--emit jsonpayload on stdin is a valid sink — bash, Node, Python, Go, Rust, whatever. Register persistent ones viapeepshow sinks add-cmd 'your command'. Seethe sink spec.
How does it pair with caveman mode?
peepshow ... --emit cavemanprints an ultra-terse one-line summary + paths, designed for token-compressed LLM setups likeJuliusBrussee/caveman. Saves ~70% of peepshow's preamble tokens.