Please check the build logs for more information.
See Builds for ideas on how to fix a failed build, or Metadata for how to configure docs.rs builds.
If you believe this is docs.rs' fault, open an issue.
framewatch
Event-driven, change-triggered window capture that emits timestamped screenshots + metadata so an AI coding agent can reconstruct what happened — without a continuous frame stream.
Why
Continuous screen capture floods an agent's context with near-duplicate frames.
On-demand "screenshot now" misses everything in between. framewatch sits in the
middle: it watches one window and only writes a frame to disk at
semantically meaningful moments:
- the window settles after a burst of change (the final, important state),
- a spinner / busy indicator starts or stops animating,
- a volatile region (counter, progress %, log tail) is sampled on a throttle,
- plus the initial frame and any manual trigger.
Everything else is collapsed. Between saved frames an append-only timeline records what happened (how many frames were coalesced, how long the window was busy, which regions changed), so an agent reads a compact, timestamped story and opens only the images that matter.
Detection is heuristic and runs in the hot path with no LLM call. You can
pre-annotate where spinners and volatile values live via the GUI, which makes
detection both faster and more accurate — or set auto_detect_spinners = true
to have a small, compact loading animation detected and collapsed automatically,
no ROI hint required.
What makes it new
The primitives exist; the assembled product did not. No crate combined change-triggered capture + transient-state (spinner/volatile) awareness + timestamped, agent-readable artifacts in one importable package:
| Capability | Closest existing thing | Gap framewatch fills |
|---|---|---|
| Per-window capture, frame-on-change | windows-capture |
Raw frames only; no semantics, dedup, or artifacts |
| Native dirty-rectangle data | dxgi-capture-rs |
Per-monitor, low-level; no spinner/value logic |
| Cross-platform capture | xcap, scap |
No change semantics |
| Perceptual-hash dedup | image_hasher |
A building block, not a pipeline |
Using framewatch from another project or agent? See
docs/AGENT_INTEGRATION.mdfor the exact where/how-to-call contract, anddist/framewatch.jsonfor a machine-readable manifest.
Codex integration
This repository includes both Codex-native discovery layers:
AGENTS.mdgives Codex the project architecture, public-contract rules, and verification commands when it works on Framewatch itself..agents/skills/framewatch/SKILL.mdteaches Codex when to useshot,watch, orrecord, and how to consume the resulting artifacts. Codex discovers the skill automatically in this checkout; invoke it explicitly with$framewatchor let it trigger for desktop UI capture tasks.
To use the skill from another repository, ensure a current framewatch.exe is
on PATH and copy the framewatch skill directory into that repository's
.agents/skills/ directory (or into your user-level ~/.agents/skills/). See
the Codex skill documentation for
skill discovery details.
Install
# CLI (Windows live capture):
# CLI with narrated recording (also enables wgc; requires ffmpeg on PATH):
# As a library (engine + sinks only, no clap/egui):
Platforms. The detection engine is platform-agnostic and compiles/tests everywhere. The live capture backend is Windows-only (Graphics Capture API) and is enabled with the
wgcfeature. macOS/Linux backends are a documented future extension — they just implement the sameCaptureBackendtrait.
Quickstart (CLI)
# 1. See what can be captured
# 2. Pick a window + mark spinner/ignore regions visually
# 3. Or run headless by title
# 4. Or against a saved config
Record & narrate → an LLM package (V4)
Sometimes you don't want a deduped story — you want to show and tell. The
record subcommand (install with --features "wgc record", needs ffmpeg on
PATH) continuously records one window to video while you narrate into the
microphone, then transcribes the narration locally and bundles everything an LLM
needs to act on it. On first use, Framewatch automatically downloads a pinned,
checksum-verified whisper.cpp runtime and base.en model (~150 MiB) into the
user cache; later recordings reuse it:
# Record a window for 60s (or stop early with Ctrl+C) while you talk.
It writes a package directory:
recording.mp4 # the window video (your narration muxed in)
audio.wav # the raw mic narration
transcript.json/.srt # segments with start_ms/end_ms from video start
recording.json # manifest
PROMPT.md # the prompt to hand the model (transcript inline)
README_FOR_AGENT.md
Because every transcript segment is timestamped from the start of the video, a
model can correlate "click this" with the exact on-screen moment — ingesting
recording.mp4 directly or pulling a frame with
ffmpeg -ss <seconds> -i recording.mp4 -frames:v 1 frame.png. See the
recording-package contract.
Transcription uses managed whisper.cpp by default. Use
--no-transcribefor an audio-only package with no model download. Advanced users can override the engine with--transcribe-cmd;{audio}and{output}are substituted and the command writes framewatch transcript JSON or SRT. SetFRAMEWATCH_WHISPER_DIRto change the managed cache parent. The runtime and model are downloaded rather than embedded in the crate, keeping installation small while making the default workflow self-configuring.No microphone? Recording degrades gracefully — it warns and produces a video-only package (no transcript). Pass
--no-audioto opt out of mic capture explicitly.
The agent-consumption contract
A session directory (./.framewatch/<session_id>/) contains:
frames/000000_initial.png
frames/000003_settled.png
timeline.jsonl # one JSON event per line, chronological
session.json # manifest: target, time range, config, ROI hints, counts
README_FOR_AGENT.md # how to read this directory
An agent should: read session.json, stream timeline.jsonl, and open only the
PNGs referenced by kind:"settled" / kind:"busy_end" unless it needs finer
detail. coalesced_frames tells it how much activity each saved image represents.
A whole "run tests" workflow collapses to ~4 timeline entries and 2 images instead of ~75 screenshots:
{"seq":0,"kind":"initial","elapsed_ms":0,"image":"frames/000000_initial.png","note":"Session start."}
{"seq":1,"kind":"busy_start","elapsed_ms":1200,"image":null,"note":"Test runner started (spinner active)."}
{"seq":2,"kind":"busy_end","elapsed_ms":4830,"image":"frames/000002_busy_end.png","coalesced_frames":71,"note":"Spinner stopped after 3.63s; 71 animation frames collapsed."}
{"seq":3,"kind":"settled","elapsed_ms":5180,"image":"frames/000003_settled.png","note":"Settled: test results rendered."}
Embedding
use ;
The Engine is pure: (state, RawFrame, now) -> Vec<CaptureEvent>. It does no
I/O, no capture, and takes its clock by injection — which is why the whole
detection pipeline is unit-tested without a GPU, screen, or Windows.
Cargo features
| Feature | Default | Adds |
|---|---|---|
cli |
✅ | the framewatch binary (clap) |
wgc |
Windows Graphics Capture backend + window enumeration | |
gui |
eframe/egui window picker & ROI editor | |
record |
record subcommand: window video (via ffmpeg) + mic (cpal) → LLM package |
|
jpeg / webp |
extra image encoders | |
llm |
reserved: out-of-band vision-caption sink |
The core library pulls no Windows or GUI deps unless you opt in.
License
Licensed under either of MIT or Apache-2.0 at your option.