{
"tool": "framewatch",
"version": "0.6.0",
"description": "Event-driven, change-triggered single-window capture that emits timestamped screenshots + metadata for AI agents; plus a `record` mode that bundles a narrated screen recording + timestamped transcript into an LLM package.",
"docs": "docs/AGENT_INTEGRATION.md",
"codex": {
"skill": ".agents/skills/framewatch/SKILL.md",
"invoke": "$framewatch",
"discovery": "Codex discovers this skill automatically in the Framewatch checkout. Copy .agents/skills/framewatch into another repository, or ~/.agents/skills/framewatch for user-wide use. Ensure a current framewatch.exe is on PATH; the skill verifies its version and required subcommand before capture."
},
"sample_session": "dist/sample-session",
"sample_recording": "dist/sample-recording",
"binary": {
"relative_path": "dist/framewatch.exe",
"path_note": "Paths are relative to the repository root. Resolve relative_path against your checkout to get an absolute path.",
"platform": "windows-x86_64",
"features": ["cli", "wgc", "gui", "record"],
"version_command": "framewatch.exe --version"
},
"commands": [
{
"name": "windows",
"summary": "List capturable windows.",
"invoke": "framewatch.exe windows",
"blocking": false,
"stdout": "table with columns: HWND, EXE, TITLE"
},
{
"name": "watch",
"summary": "Capture a window into a session directory until stopped.",
"invoke": "framewatch.exe watch <selector> [--out <dir>] [--settle-ms <n>] [--value-sample-ms <n>] [--config <file>]",
"blocking": true,
"stop": "Ctrl+C, or the target window closes",
"selectors": {
"--title": "case-insensitive substring matched against the window title (literal, not a regex)",
"--exe": "executable basename, e.g. Code.exe (case-insensitive)",
"--hwnd": "exact numeric window handle",
"--pid": "exact: the window owned by this process id (avoids stale-window matches on repeat captures of the same exe)"
},
"options": {
"--out": "output parent directory (default ./.framewatch)",
"--settle-ms": "quiescence ms before 'settled' (default 350)",
"--value-sample-ms": "throttle ms for volatile samples (default 1000)",
"--roi": "crop capture + detection + output to a pixel region X,Y,W,H (clips host chrome); window.rect still reports the full source window",
"--config": "path to a framewatch.toml base config (flags override it)"
},
"lifecycle_options": {
"--wait": "poll/retry up to N seconds for the target window to appear (no launch-order race)",
"--until-settled": "exit after the first settled frame (deterministic one-shot)",
"--duration": "exit after N seconds (time-bounded; also stops an idle window)",
"--frames": "exit after N images have been saved"
},
"one_shot_example": "framewatch.exe watch --title \"My App\" --wait 15 --until-settled --out ./.framewatch",
"stdout_first_line": "framewatch: writing session to <out>/<session_id>"
},
{
"name": "shot",
"summary": "One settled frame to a single chosen file; optionally launch+capture+kill a program. Best for scripted/batch capture.",
"invoke": "framewatch.exe shot (--launch \"<cmd>\" | <selector>) --out-file <path> [--timeout <secs>] [--settle-ms <n>] [--roi X,Y,W,H] [--settle-best-effort]",
"blocking": true,
"selectors": {
"--launch": "spawn this program and capture its window by PID, then kill it on exit (whitespace-split; \"...\" groups an arg)",
"--pid": "capture the window owned by this process id (exact)",
"--title": "case-insensitive substring matched against the window title (literal, not a regex)",
"--exe": "executable basename",
"--hwnd": "exact numeric window handle"
},
"options": {
"--out-file": "exact PNG path to write (required, deterministic)",
"--timeout": "overall budget seconds to wait for window + settle (default 20)",
"--settle-ms": "quiescence ms before 'settled'",
"--roi": "crop region X,Y,W,H",
"--settle-best-effort": "if nothing settles before timeout, write the latest frame anyway"
},
"contract": "On success prints the written path to stdout and exits 0. If no frame settles before --timeout (and --settle-best-effort is unset), writes nothing and exits non-zero (3).",
"example": "framewatch.exe shot --launch \"game.exe --freecam\" --out-file shot.png --timeout 25"
},
{
"name": "record",
"summary": "Continuously record a window to video while narrating into the mic, transcribe locally with automatically provisioned managed Whisper, and write an LLM package (video + timestamped transcript + prompt). Requires the `record` feature and ffmpeg on PATH; first transcription downloads about 150 MiB into the user cache.",
"invoke": "framewatch.exe record <selector|--launch \"<cmd>\"> [--out <dir>] [--duration <secs>] [--fps <n>] [--mic <name>] [--roi X,Y,W,H] [--transcribe-cmd \"<cmd>\" | --no-transcribe | --no-audio]",
"blocking": true,
"stop": "Ctrl+C (mp4 is finalized cleanly), or after --duration",
"selectors": {
"--title": "case-insensitive substring matched against the window title (literal, not a regex)",
"--exe": "executable basename",
"--hwnd": "exact numeric window handle",
"--pid": "exact: the window owned by this process id",
"--launch": "spawn this program and record its window by PID, then kill it on stop"
},
"options": {
"--out": "output parent directory (a per-recording subdir is created)",
"--duration": "auto-stop after N seconds measured from encoder startup, after target resolution and the first frame (otherwise record until Ctrl+C)",
"--fps": "target video frames per second (default 30, clamped 1..=60)",
"--mic": "microphone device name substring (default: system default input)",
"--no-audio": "record video only (no microphone / transcription); also the automatic fallback when no input device is available",
"--roi": "crop the recorded region to a pixel rect X,Y,W,H",
"--wait": "wait up to N seconds for the target window to appear; --launch defaults to 15 seconds when neither this option nor a config wait is set",
"--transcribe-cmd": "override managed Whisper by shelling out to a local transcriber; {audio}/{output} substituted; command emits framewatch JSON or SRT",
"--no-transcribe": "record video + audio only (empty transcript); skips managed Whisper setup/download"
},
"managed_transcription": {
"engine": "whisper.cpp",
"version": "1.9.2",
"model": "base.en",
"first_use_download_bytes_approx": 157000000,
"cache_override_env": "FRAMEWATCH_WHISPER_DIR",
"runtime_sha256": "49dcc16de826f20bd53d44f947a1ae49dfa81f86cad67a64d80820cb192d674a",
"model_sha256": "a03779c86df3323075f5e796cb2ce5029f00ec8869eee3fdfb897afe36c6d002"
},
"stdout": "first line: the package directory; last line: the PROMPT.md path",
"example": "framewatch.exe record --title \"My Game\" --duration 60"
},
{
"name": "transcriber setup",
"summary": "Pre-download, checksum-verify, and validate the managed whisper.cpp runtime and base.en model. Optional because record performs the same setup automatically on first use.",
"invoke": "framewatch.exe transcriber setup",
"blocking": true,
"stdout": "absolute whisper_cli and model paths"
},
{
"name": "gui",
"summary": "Launch the GUI window picker / ROI editor (for humans).",
"invoke": "framewatch.exe gui [--config <file>]",
"blocking": true
}
],
"output": {
"session_dir": "<out_dir>/<session_id>",
"session_id_format": "%Y-%m-%dT%H-%M-%S_<exe-stem>",
"default_out_dir": "./.framewatch",
"files": {
"manifest": "session.json",
"timeline": "timeline.jsonl",
"agent_readme": "README_FOR_AGENT.md",
"frames": "frames/<seq:06>_<kind>.png"
},
"timeline_format": "JSON Lines (one event object per line, chronological)",
"event_kinds": [
"initial",
"transition_start",
"busy_start",
"busy_end",
"value_sample",
"settled",
"manual"
],
"images_saved_for_default": ["initial", "settled", "busy_end", "manual"],
"open_images_for": ["settled", "busy_end"],
"timeline_event_fields": {
"seq": "u64 monotonic event index",
"id": "string 'f' + zero-padded seq",
"kind": "one of event_kinds",
"wall_time": "RFC3339 UTC timestamp",
"elapsed_ms": "u64 ms since session start",
"image": "string path relative to session dir, or null",
"window": "{ title, exe, class, hwnd, rect:[x,y,w,h], dpi, foreground }",
"change": "{ changed_tiles, tile_grid:[cols,rows], area_ratio, bboxes:[[x,y,w,h]], dhash?, hamming_to_prev_emit? }",
"busy": "{ active, regions:[{ label, active }] }",
"timing": "{ since_prev_emit_ms?, active_for_ms?, quiescent_for_ms? }",
"coalesced_frames": "u32 frames collapsed since previous saved image",
"note": "human-readable summary"
}
},
"consume_algorithm": [
"Read session.json for target app, time range, config, and ROI hints.",
"Stream timeline.jsonl line by line (each line is a complete JSON object).",
"Open images only for events with kind == 'settled' or kind == 'busy_end'.",
"Use coalesced_frames to know how much activity each saved image represents.",
"Use busy_start / value_sample notes for timing context without opening images."
],
"recording_output": {
"note": "Produced by the `record` command (see docs section 6 + dist/sample-recording).",
"package_dir": "<out_dir>/<session_id>",
"files": {
"prompt": "PROMPT.md (start here; transcript embedded inline)",
"video": "recording.mp4 (window video; narration muxed in)",
"audio": "audio.wav (raw mic narration)",
"transcript_json": "transcript.json",
"transcript_srt": "transcript.srt",
"manifest": "recording.json",
"agent_readme": "README_FOR_AGENT.md"
},
"transcript_json_shape": "{ language?, duration_ms, segments: [{ start_ms, end_ms, text }] }",
"timestamp_meaning": "segment start_ms/end_ms are ms from the start of recording.mp4 — seek the video to a quoted instruction, e.g. ffmpeg -ss <start_ms/1000> -i recording.mp4 -frames:v 1 frame.png",
"consume_algorithm": [
"Read PROMPT.md — it is self-contained (transcript inline) for text-only models.",
"If you can ingest video, open recording.mp4 and use each segment's start_ms to locate the on-screen moment an instruction refers to.",
"recording.json records the engine used and video/audio metadata."
]
},
"library": {
"crate": "framewatch",
"cargo_dependency": "framewatch = { path = \"../framewatch\", default-features = false } # or version = \"0.1\" once published",
"live_capture_features": ["wgc"],
"entry_points": [
"framewatch::watch(config, sink)",
"framewatch::record_with_duration(config, duration)",
"framewatch::default_backend(&config)",
"framewatch::enumerate_windows()",
"framewatch::Engine::process(&frame, now)",
"framewatch::Config::builder()",
"framewatch::DirectorySink::new(&config)"
]
},
"platform_notes": "Live capture (windows/watch commands and default_backend) requires Windows with the wgc feature. The pure detection engine and sinks compile and run on all platforms."
}