Releases
Current · v0.9.4
User-facing changes to peepshow, newest first — new sinks, new features, fixes, and opt-in behaviour changes. This page is generated from the changelog shipped with the npm package, so it tracks the release it describes. Internal tooling and build churn are omitted. Full notes for each tag, including install commands, live on GitHub Releases.
Unreleased
Added — agent distribution surfaces + llms.txt
Audited peepshow's agent-facing surfaces against a comparable project and
closed the gaps. Each of these is a file some agent keys off; a missing one
doesn't error, the agent simply never learns peepshow exists.
.codex-plugin/plugin.json— Codex plugin manifest..codex/hooks.jsonannounced peepshow to a running session; this makes it installable..cursor-plugin/plugin.json— same split for Cursor, where.cursor/rules/teaches but doesn't install.plugin.jsonat the repo root — the cross-agentagent-plugins.orgconvention.opencode.json— points OpenCode at the sameskills/tree. Nomcpblock: peepshow is CLI-first and ships no MCP server, and claiming one would be untrue..agents/skills/{slides,sink}/SKILL.md— mirrors of the Claude skills, so.agents-aware agents find the skills and not just the marketplace entry. A test asserts they stay byte-identical toskills/..codexignoreat the root and in the plugin dir.
The .agents marketplace entry never resolved. It pointed at
./plugins/peepshow, which did not exist — the scanner resolves that path from
the repo root while the real directory was nested at
.agents/plugins/plugins/peepshow. The entry was silently skipped
("local plugin manifest not found") and the repo's trust score was 0.0/100
even though the overall scan passed. The directory now sits at the root with
its own manifest, README, LICENSE and SECURITY.md.
Measured against the pinned scanner: score 85 → 87, trust 0 → 72.37,
verify_pass false → true, nothing skipped. Switching the entry to a url
source — which is what some projects do — was tried and rejected: it scores 76,
below the 80 the listing gate requires.
Added — llms.txt
peepshow.dev/llms.txt was a 404. The site has ~130 generated pages, so there
was no machine-readable index for the thing whose whole audience is LLMs. Now
generated from the same catalogues as the pages — all 95 sinks by category,
every agent, model, how-to and comparison — so it can't drift.
Added — "powered with peepshow" badge + star history
docs/media/powered-with-peepshow-{light,dark}.svg, with copy-paste markdown in the README for anyone shipping something built on peepshow. Plain SVG in the peepshow accent, no tracking.- A star history chart in the README.
Added — --version
peepshow --version/-Vprints the version and exits 0. It was missing, so the obvious liveness check reportedunknown flag: --versionwith exit 2 — which is exactly what the reporter of issue #3 ran to check whether the CLI could start.-vis deliberately not an alias: it is conventionally verbose, and claiming it would be a trap to undo later.
v0.9.3 — 2026-10-06
npm i -g peepshow@0.9.3
Fixed — peepshow was unusable on native Windows
Reported from outside in issue #3:
peepshow.CMD --version exited 1 with `The system cannot find the path
specified.before the CLI started, whilenode dist/cli.js` worked fine on
the same install. Two independent faults, each sufficient on its own, and
neither visible from a macOS or Linux CI run.
- Every
bin/launcher was a#!/bin/shscript. npm and pnpm generate the Windows.CMDshim from the target's shebang, so all 93 commands shimmed toSET "_prog=/bin/sh"— a path that does not exist on Windows without Git Bash. All launchers are nowbin/<name>.mjswith#!/usr/bin/env node, which shims toSET "_prog=node". Verified by generating real shims with npm's owncmd-shimfor both shebangs and diffing the interpreter each selects. - 93 entry points would have silently no-opped anyway. Each sink decided whether to run via `
import.meta.url ===file://${process.argv[1]}, which is false on Windows (file:///C:/…vsC:\…) and false through symlinks (/tmpvs/private/tmp). A false result means "I'm being imported", so the process would have exited 0 having done nothing. This is the same bug fixed forcli.ts` in 0.4.1; the fix never propagated. Now
Also in this area:
- Launchers spawn the bundle rather than importing it, so
argv[1]stays on the bundle and the guard above still works.bin/_launch.mjsis the shared helper: Node ≥ 22 check with the same exit 3 and message as before, nvm fallback when the running Node is too old, argv/stdio pass-through, exit code propagation, and signal forwarding so Ctrl-C reaches ffmpeg. peepshow-sink-algolia,-otterand-typesensenow actually install. They shipped launchers that were never registered inpackage.json bin, sonpm i -gnever put them onPATH. 93 commands → 96.- The slack and discord launchers keep their shell semantics exactly: the service-specific URL variable is aliased onto
PEEPSHOW_WEBHOOK_URLonly when the generic one is unset or empty, which a bare??would not reproduce. tests can import the helper under type-checked lint.
Verified by packing the tarball, installing it into a clean directory, and
running through node_modules/.bin/ — the symlink layer where the 0.4.1 bug
lived — including a real extraction end to end.
extensions, executable bits, bin-map parity, and the guard's behaviour through
symlinks and percent-encoded paths. They run on every platform, because that
is the only way a Mac notices a Windows packaging bug.
Changed — published docs and security policy
SECURITY.md— supported versions, private reporting via GitHub advisories, and an explicit account of what peepshow executes: ffmpeg resolution order and thePEEPSHOW_FFMPEGoverride,yt-dlp, sinks being arbitrary executables driven by~/.peepshow/sinks.json, andservebinding loopback by default.- Placeholder credentials in the sink docs are now
"<your-value>". Eleven docs used values shaped closely enough to real credentials ("patXXXXXXXX","abcd-efgh-ijkl-mnop","tk_abc123") that secret scanners flagged the documentation itself. Where the value is a real command the quotes are simply gone, since an assignment does not need them. - Both skills gained
name:frontmatter.
v0.9.2 — 2026-09-16
npm i -g peepshow@0.9.2
Added — peepshow is a real Gemini CLI extension
Previously Gemini CLI support was a four-field manifest and a context file
that imported AGENTS.md. The manifest had also drifted to 0.1.2 while the
package was on 0.9.1, so gemini extensions update had nothing to compare
against.
- Two native slash commands, registered by the extension itself:
commands/peepshow/slides.toml→/peepshow:slides <video>andcommands/peepshow/sink.toml→/peepshow:sink <intent>. Gemini CLI derives the namespace from the directory, so the commands match the Claude Code skill names exactly. Both shell out to thepeepshowbinary — no MCP server, no daemon. /peepshow:slidesmirrorsskills/slides/SKILL.md: run the extraction, read every frame in order viaread_many_files, read the transcript, answer, then annotate the run soreport.htmlstops sayingno-analysis./peepshow:sinkmirrorsskills/sink/SKILL.md, including--whenconditions and the rule that it must re-list after every mutation.commands/now ships in both distribution paths —package.jsonfiles[]gemini extensions install https://github.com/t0mtaylor/peepshowactually clones.gemini-extension.jsonand.claude-plugin/plugin.jsonversions are now pinned topackage.json, with tests asserting it so they can't drift again.
Verified against Gemini CLI 0.60.0: the extension loads as
✓ peepshow (0.9.2) with its context file resolved, and both command files
parse as valid TOML v1 to the documented command names.
Site: /agents/gemini/ rewritten around the real install flow with a new
"Slash commands" section (the agent-page template gained an optional
commands block), and Gemini CLI promoted on the homepage feature slide and
agent table.
Fixed — YouTube input was broken end to end
Three separate faults, each of which alone blocked every yt-dlp input
(local paths were never affected).
node:child_process with require() inside defaultSpawn(). Under
--experimental-strip-types the loader can't classify a .ts file that
contains both require and top-level await, so it refused the module on
evaluation — before the YouTube path was reached. The same require
survived into the esbuild ESM bundle, where dist/cli.js threw
Dynamic require of "node:child_process" is not supported. Replaced with a
static import; node:child_process is a builtin, so the lazy-load was
buying nothing. Test seams still inject through deps.spawn.
- Format selector could never match.
-f best[height<=720]…asks for a single file carrying both video and audio, and YouTube no longer publishes progressive formats for most uploads — everything is DASH, video-only plus audio-only. Every such video died withRequested format is not available. Nowbv[height<=720]+ba/b[height<=720]/bv[height<=720]/b, with peepshow's own resolved ffmpeg passed via--ffmpeg-locationso the merge works without an ffmpeg on the user's PATH. Theprotocol!^=m3u8filter is gone — it removed valid fallbacks on HLS-only videos, and yt-dlp's default sort already prefers https. - Failures were opaque. yt-dlp's stderr was piped and then never read — so a failure surfaced as a bare
yt-dlp failed (exit 1), and a chatty child could deadlock on a full pipe buffer. stderr is now drained, its tail quoted in the error, and a 403 (the classic stale-yt-dlp symptom) gets an explicityt-dlp -Uhint. Dropped--no-warnings: yt-dlp's own "outdated version" line now reaches the user viaResolvedInput.warningseven when the download succeeds.
already flagged the original call, but it was silenced inline, which is how
it shipped.
v0.9.0 — 2026-05-30
npm i -g peepshow@0.9.0
Added — Granola sink (now 78 sinks)
peepshow-sink-granola— write a per-run markdown bundle into a Granola import directory. Granola's public API (https://public-api.granola.ai/v1) is read-only at the time of writing — onlyGET /v1/notes,GET /v1/notes/:id,GET /v1/foldersexist. The community wrapper atgetprobo/reverse-engineering-granola-apiis archived and was also read-only. So this sink takes the practical file-drop path: it writes<importDir>/<run-id>/note.md+ aframes/subdir, formatted for clipboard / drag-drop import into the Granola desktop app. Markdown shape: H1 title,## Meeting detailsbullets,## Container tags, timestamped## Transcriptsegments, inline frame embeds. Env:GRANOLA_IMPORT_DIR(default~/Documents/Granola Imports),GRANOLA_TITLE_PREFIX,GRANOLA_COPY_FRAMES, plusGRANOLA_API_KEYreserved for when the upstream public API gains write endpoints. env parsing, title fallbacks, duration / timestamp formatters, bundle layout, transcript segment rendering, DI-injected fs failures, and a tempdir round-trip.- Catalogue + landing pages + brand logo updated (
wikicategory). Sink-count assertions bumped from 77 → 78 in
v0.8.1 — 2026-05-01
npm i -g peepshow@0.8.1
Added — sink replay (closes the v0.8.0 sink-log loop)
peepshow sinks log [--limit N] [--status ok|failed|skipped]— print recent sink interaction-log entries from~/.peepshow/sink-log.ndjson. Default 20 newest, status-filterable.peepshow sinks replay <logId>— re-pipe a past invocation. Resolves the SinkSpec by id (falls back to kind+value+when), reads the run's manifest fromoutputDir, rebuilds the payload, and callsrunSinkswith the same env override +whenclause that fired originally. Appends a new log entry stamped withreplayOf: <originalId>so retries are auditable.POST /api/sinks/replay/:logId— same flow over HTTP. Returns{ ok, originalId, newEntry, status, exitCode, durationMs }. 404 on missing logId, 422 on missing manifest / spec.SinkReplayErrorwith codeslog_entry_not_found,manifest_missing,sink_spec_not_found,outputDir_missing. happy-path payload reconstruction (verifies the manifest's outputDir + strategy land in stdin).
v0.8.0 — 2026-04-28
npm i -g peepshow@0.8.0
Added — peepshow serve (phase 2 of the report system)
peepshow serve— local HTTP server for the run history. Purenode:http, no new runtime deps. Routes:GET /— homepage with newest-first run table (server-rendered).GET /runs/:runId— per-run report.html (same shell as the standalone file).GET /runs/:runId/manifest.json— raw manifest.GET /runs/:runId/frames/:idx— streamed frames with ETag.GET /runs/:runId/audio.m4a— streamed audio when present.GET /api/runs.json— cursor-paginated JSON.GET /sinks+/api/sinksGET/POST/DELETE — auto-sink management UI + JSON API.POST /runs/:runId/annotate— pipe LLM analysis from any HTTP client (no CLI needed).GET /healthz— version + run count.- CLI flags:
--port/--host/--token/--open. Defaults seed from saved config (serve.port,serve.host,serve.autoOpen); env vars (PEEPSHOW_SERVE_PORT,PEEPSHOW_SERVE_HOST) override. - Security model: loopback (
127.0.0.1) by default with no auth. Non-loopback bind requires--token <hex>— auto-generated when omitted; pass via?token=…query orX-Peepshow-Tokenheader. peepshow config initextended: wizard now prompts forserve.port,serve.host,serve.autoOpen. New keys settable viapeepshow config set serve.* <value>.- Caller attribution — every served request is tagged with a client / session / agent triple, sourced from the
X-Peepshow-Client/-Session/-Agentheaders when set, with User-Agent sniffing as fallback (claude-code, copilot, cursor, codex, browser, curl, …). Records land in~/.peepshow/serve-access.ndjson(override viaPEEPSHOW_ACCESS_LOG). Surfaced at: GET /access— HTML page with per-caller summary cards + the recent request log.GET /api/access.json—{ records, summary }JSON.- Manifest
invoker— extract runs now readPEEPSHOW_CLIENT/PEEPSHOW_SESSION/PEEPSHOW_AGENTfrom env (withCLAUDE_SESSION_IDas a session fallback) and write the triple intomanifest.json. The runs index gainsinvokerClient/invokerSession/invokerAgentfields, and the homepage shows a "caller" column so a sharedpeepshow servecan attribute each run back to the Claude Code / Copilot / Cursor / Codex session that triggered it. Schema stays at v1 —invokeris a new optional field. - Slides skill (
/peepshow:slides) now exportsPEEPSHOW_CLIENT=claude-code PEEPSHOW_SESSION=$CLAUDE_SESSION_IDbefore invoking the CLI so the manifest captures session attribution automatically. - Inline SVG logo: report.html template + the server's homepage/sinks pages render the same peepshow mark as the marketing site.
Site
- New /serve/ landing page: command + flag table, full route matrix, security model, config keys, programmatic-use examples. Linked from the primary nav (icon position between Report and FAQ) + footer Learn column on every page.
- New /use-cases/ landing page: hub linking the six intended deployment targets — Claude Code plugin, Cursor / Windsurf / Cline agents, generic CLI shell, Electron desktop AI client, server-side AI portal pre-processor,
peepshow servedashboard. - /privacy/ extended: v0.8.0 telemetry summary + opt-out switches surfaced on the marketing site, with a link out to the new
docs/PRIVACY.md.
Telemetry & privacy
- Telemetry + analytics are on by default in v0.8.0. Opt out via
peepshow config set telemetry off,PEEPSHOW_TELEMETRY=0,PEEPSHOW_ANALYTICS=0,DO_NOT_TRACK=1, or by refusing the consent banner onpeepshow servepages. Full details, every switch, and the exact payload in [docs/PRIVACY.md](./docs/PRIVACY.md). - New README section
Where peepshow fitscovering the Electron desktop and server-side AI portal patterns, with snippets.
Deferred to v0.8.1
- SQLite mirror for >10K runs (
--sqliteflag). ndjson scales fine to ~10K so this stays opt-in. - Marketing video for
peepshow serve. Site landing page + README carry the launch; video ships in the next polish patch.
v0.7.6 — 2026-04-27
npm i -g peepshow@0.7.6
Changed
- Slimmer npm install footprint — published
package.jsonno longer carries development-only metadata. Same runtime, smaller install.
v0.7.5 — 2026-04-27
npm i -g peepshow@0.7.5
Changed
- Slimmer npm tarball — dropped a few unused metadata files from the package. Same runtime, smaller install footprint.
- README now points to the GitHub Releases page (github.com/t0mtaylor/peepshow/releases) as the canonical user-facing changelog. Per-version notes live there with the new flags, env vars, manifest fields, and default-behaviour shifts that affect users.
v0.7.4 — 2026-04-27
npm i -g peepshow@0.7.4
Added — Perceptual frame dedup
--dedup on|auto|off(defaulton) — every extraction now runs an 8×8 dHash post-pass and drops frames whose hamming distance to the previous keeper is ≤ threshold. Catches the case ffmpeg's scene-detect misses (low-motion talking heads, slideshow recordings, security-cam loops) and stops fps-mode fallback from flooding the LLM with near-identical thumbnails. Single ffmpeg invocation hashes all frames in one pass — adds ~50–100ms for 40 frames on M1, well below typical extraction cost.autoruns only for fps fallback (since scene mode already filters);oncovers both modes;offdisables.--no-dedupis a shortcut for--dedup off.--dedup-distance N(default5, range 0..64) — hamming-distance threshold. 0 means only bit-identical hashes drop; higher = looser dedup. 5 is the sweet spot: tight enough to catch lighting flicker / duplicate slideshow frames, loose enough to keep legit minor scene changes.- Manifest schema (additive, still v1) —
extraction.framesDeduped+extraction.dedupDistancefields surface what dedup did per run, so reports + sinks can show the count. - Soft-fail by design — if hashing throws (rare; only when ffmpeg can't decode our own JPEGs), extraction falls through to pruneToMax with the original frame list. Dedup is a quality pass, not a correctness gate.
Tests
- 28 new unit tests across
dHashFromBytes,hamming,selectKeepers,shouldDedup,computeDHashes(with stub spawn),applyDedup(real fs + collision-safe two-phase rename).extraction.*keys. addition.
Why we shipped this now
External feedback: *"Keyframes + transcript is a clean approach — most
'video understanding' demos cheat by relying purely on transcript.
Does it dedupe similar frames or just sample at fixed intervals?"*
Honest pre-fix answer was "scene mode kind of, fps fallback no."
Honest post-fix answer is "yes, perceptual dedup default-on for both
modes." Same pitch, no caveat.
Added — --gpu auto is now duration-aware
- Short clips (<15s) auto-skip hwaccel under
--gpu autobecause videotoolbox / vaapi / d3d11va init overhead (~200-500ms) exceeds per-frame decode savings. Real measurements on the hero reel videos showed CPU 3-4× faster than GPU on 10-30s clips. The heuristic fixes this without changing theautodefault — long/HD clips still get hardware decode. - Dual-axis threshold:
<15striggers CPU regardless of size;15-30salso triggers CPU when height < 720p.>30salways uses the platform default. Both cutoffs are env-configurable:PEEPSHOW_GPU_MIN_SECONDS(default 15),PEEPSHOW_GPU_MIN_HEIGHT(default 720). - Explicit
--gpu <backend>bypasses the heuristic — power users who want to force GPU just name the backend (--gpu videotoolbox,--gpu cuda, etc.).--gpu offstill forces CPU. height × env override combination.
Added — Motion signal + adaptive density (high-motion answer)
extraction.motionSignalAvg+extraction.motionSignalLevel— average pairwise hamming distance across kept dHashes, classified aslow(<6) ·medium(<15) ·high(15+). Free byproduct of dedup — the hashes were already computed. Surfaced inmanifest.jsonso the consuming LLM can adjust its narration ("rapid motion segment...") and so reports can show "high motion · zero dups" rather than just "0 dropped".- High-motion stderr hint — when motion is high, dedup dropped 0 (every frame distinct), and
framesEmitted ≤ 8, peepshow prints a one-line tip suggesting--fps 2or a higher--max. Silent underPEEPSHOW_NO_HINTS=1. --adaptive on|off(defaulton) — second-pass extraction. When dedup drops 0 frames AND motion is high ANDframesEmitted < maxFrames * 0.6, peepshow re-extracts at higher fps to take advantage of the visual variety. Targets ~80% of--maxon the second pass. Recursion is guarded — second pass forcesadaptive: "off".--no-adaptiveshortcuts disable. Verified live: synth high-motion clip went from 6 frames to 14 (2.3×) under the same--maxbudget.
Fixed — Scene-mode dedup was silently skipping
- Scene extraction used
-frame_pts 1which named frames by their PTS (frame_0160.jpg,frame_0307.jpg…) rather than sequential. Dedup's ffmpeg pass globbedframe_%04d.jpgand found nothing on those runs, silently caught the failure, and returneddedupDistance: null. After this fix scene mode names sequentially again, dedup runs on every strategy, and the motion signal populates regardless of which path fired.
Tests
- 8 new pure tests for
averageAdjacentHamming+classifyMotionin - 8 new pure tests for
shouldRunAdaptiveDensitycovering every decision branch.extraction.*keys.
v0.7.3 — 2026-04-26
npm i -g peepshow@0.7.3
Added
--describe <provider>— vision-LLM per-frame captions, sister of--transcribe. Providers:anthropic/openai/groq(defaultoff). Captions land inmanifest.analysis.perFrame[]and surface in the report's "LLM analysis" section. API key envs:ANTHROPIC_API_KEY,OPENAI_API_KEY,GROQ_API_KEY. Soft-fails on missing key / HTTP errors — never blocks the extract. Per-framepeepshow runs prune --keep N— caps the index at the N newest entries in addition to the existing dead-outputDir cleanup. 4 new test cases covering --keep N, --keep=0, oversize N, invalid values.
Changed
- README hero GIF regenerated from the now-extended
docs/demo.mp4so it shows the full drop → process → ask → report flow (not the original 28s clip without the report outro). ~1.5MB at 8fps / 560w / 64 colors. - Critical CSS trim — dropped decorative selectors that paint below the fold (
.hero-floater,.hero-video,.header-frame,.reveal,.counter). Down from 17.6KB to 13.7KB minified. Soft-target bumped to 14KB to silence the warning at the new realistic baseline. docs/INTEGRATIONS.mdgets the report-outro GIF embed under the top-level "Annotate the report" section so every agent's per-tool integration sees the close-the-loop animation in context.
v0.7.2 — 2026-04-26
npm i -g peepshow@0.7.2
Added — peepshow config + first-run wizard + cross-platform browser opener
~/.peepshow/config.json— per-machine user prefs (override viaPEEPSHOW_CONFIG_FILE). schemaVersion 1, additive-friendly.peepshow config init— interactive first-run wizard for three answers: writereport.htmlon every run, auto-open in browser, preferred browser. Stderr hint shown once when peepshow runs in a TTY without a config file.peepshow config list / get / set / reset— scriptable surface for managing prefs without re-running the wizard.peepshow config export [path]/import <path>— copy prefs between machines or back up before a reset. Strict schemaVersion check on import.- Cross-platform browser opener —
report.browserresolves todefault | chrome | firefox | safari | edge | brave | arc. macOS usesopen -a "<App>", Linux uses the browser binary, Windows usesstart <alias>. Env override viaPEEPSHOW_BROWSER=chrome.--report-open(orPEEPSHOW_REPORT_OPEN=1) launches via the resolved browser. - Video on /sinks/ hub — was missing; now mirrors the homepage demo so the page lede has something visual.
Tests
schemaVersion validation, browser resolution per platform, env
overrides).
- 17 cases for the
peepshow configsubcommand path (list/get/set/reset/init TTY guard/export/import).
Docs
- README "Reports & runs" section grows a "Preferences" sub-block with the config table + import/export examples + env overrides.
docs/REPORT.mdadds a "User preferences (peepshow config)" section with the full key reference./report/landing page (site/report/index.html) gets a preferences card matching the docs.docs/LAUNCH-POSTS.mdrefreshed with the 0.7 wave additions (HTML reports + analysis loop + runs subcommand + config).
v0.7.1 — 2026-04-26
npm i -g peepshow@0.7.1
Added
- Shared "report + annotate" outro segment — new
report.htmlopening (Summary + LLM analysis + Sinks fan-out) followed by thepeepshow report annotatecommand in a terminal pill. The script then concats the segment to every per-agent demo (site/assets/agents/<slug>/demo.mp4) and the homepage main demo (site/assets/demo/demo.mp4) so users see the new report flow regardless of which page they land on. docs/report-outro.gif— 720px GIF version (~830KB) of the outro segment, embedded inREADME.md(after the Reports & runs closing-the-loop snippet) anddocs/REPORT.mdso readers scrolling the docs see the close-the-loop animation.
Fixed
/report/landing page now uses the shared iconified primary nav matching the homepage / sinks / agents pages (was using the legacy text-only nav cloned from/transcription/).
v0.7.0 — 2026-04-26
npm i -g peepshow@0.7.0
Added — HTML reports + run history + LLM analysis loop
report.htmlper run — self-contained HTML dashboard written next to the frames. Inlined CSS + JS, opens offline. Sections: header badges, video preview, summary, LLM analysis (when attached), frames grid w/ lightbox + keyboard nav, transcript with click-to-seek, sink fan-out with status badges + filter, stats, raw manifest tree (collapsible JSON viewer). All on by default — opt out with--no-report/PEEPSHOW_NO_REPORT=1.manifest.jsonper run — locked-shape JSON record (schema extraction + frames + transcript + sink fan-out + host info. Optionalanalysisfield for LLM summaries. Opt out via--no-manifest/PEEPSHOW_NO_MANIFEST=1.- Append-only ndjson run history at
~/.peepshow/runs/index.ndjson(override viaPEEPSHOW_RUNS_INDEX). One line per run; POSIX-atomic under PIPE_BUF. Opt out with--no-index/PEEPSHOW_NO_INDEX=1. peepshow report annotate <dir>subcommand — close the loop when peepshow runs inside an LLM workflow. The agent that read the frames pipes a JSON summary back on stdin; peepshow merges it intomanifest.analysisatomically and re-rendersreport.html. Every supported agent integration (skills, cursor / windsurf / cline / codex / gemini / AGENTS.md) ships with the annotate instruction wired in.peepshow runs list / show / prune / clear— inspect the run history from the CLI. Phase-1 surface that the futurepeepshow servewill reuse.peepshow report <dir>— re-renderreport.htmlfrom an existingmanifest.json(e.g. after--no-reportor after a template upgrade).
Changed
--transcribe whisper-cppis now the default (was off). Auto-skips with a hint when the binary isn't on PATH. Opt out per-run with--no-transcribeor globally viaPEEPSHOW_TRANSCRIBE=off.SinkResultshape extended — addsname,status("ok" | "failed" | "skipped"),skipReason?,when?.okkept as a back-compat shorthand forstatus === "ok".runSinksnow accepts the richerSinkSpecLikeso--whenclauses thread through to the report.
v0.6.2 — 2026-04-25
npm i -g peepshow@0.6.2
Changed
- Sink count is now derived from the catalogue at build time. A new
95token inbuild-site.mjsreadssinksCatalogue().lengthand substitutes it into every visible reference (meta description, OG card, hero copy, FAQ entry, footer CTAs). No more "51 sinks" stuck on a page after the catalogue grew. Author-facing docs (README, SUBMISSION-PLAN, SINKS-ROADMAP, LAUNCH-POSTS, OG default description) refreshed to 71 with the current per-category breakdown. - README + CHANGELOG dropped the "User-facing changes to peepshow LLM…" preamble — added noise on the public README + npm page without conveying anything readers couldn't infer from the actual entries.
- Site demo caption now flows below the video on mobile (was covering ~25% of the frame).
v0.6.1 — 2026-04-25
npm i -g peepshow@0.6.1
Added — 3 macOS-only sinks (workflow + wiki)
The remaining AppleScript / x-callback-url backlog:
- apple-reminders (workflow) — create a Reminders.app todo via
osascript. Optional due date set N hours from now. Plain-text body with run summary + transcript snippet. - things (workflow) — Cultured Code Things 3. Builds a
things:///add?...URL and dispatches viaopen. Title / notes / tags / list / when (today, tomorrow, ISO-date, …). - bear (wiki) — Bear notes app.
bear://x-callback-url/create(or/add-textwithBEAR_NOTE_ID) viaopen -g. Markdown body with frame links + transcript. Auto-truncates near Bear's ~32 KB URL ceiling.
All three skip cleanly with exit 0 on non-darwin unless the
matching *_ALLOW_NON_DARWIN=1 is set.
Fixed
- README demo GIF path is stable again. The 0.4.2 hash-rotation scheme (
docs/demo.<hash>.gif) was breaking the README onmainevery regen — old commits + cached READMEs + npm tarballs kept pointing at hashes that no longer existed in the repo. Reverted to a stabledocs/demo.giffilename overwritten in place. GitHub's ETag handles cache busting on byte change.
Tests
all passing.
v0.6.0 — 2026-04-24
npm i -g peepshow@0.6.0
Second sinks wave. Ten more (58 → 68) covering the rest of the
feasible backlog in docs/SINKS-MISSING.md.
Added — 10 new sinks
Workflow / agent tooling
- cody — drop frames + manifest + transcript into
.cody/context/peepshow/for Sourcegraph Cody. Copy / symlink / hardlink modes (EXDEV fallback). - openai-files — pre-upload frames via the OpenAI Files API (
purpose=visiondefault) for Custom GPTs / Projects / Assistants file-search. Manifest file ties peepshow frames to OpenAI file IDs. - shortcuts — invoke a named macOS Shortcut via the
shortcutsCLI. Payload mode (pipe the whole JSON) or frames mode (one call per frame path). - zapier — branded Catch Hook URL wrapper. Flat body with
tag_<key>hoisting so Zapier steps destructure by position. Optional HMAC-SHA256 signing. 429/5xx exponential retry.
Chat / messaging
- whatsapp — WhatsApp Cloud API: upload frames via
/media, send one summary text + one image message per frame via/messages. Flags template-message + 24-hour-window caveats in the doc. - imessage — macOS Messages.app via
osascript. Sends a text summary + up to N frame attachments to a buddy or group.
Wiki / notes
- apple-notes — macOS Notes.app via
osascript. Creates an HTML note with metadata, linked frames, and timestamped transcript segments. Configurable folder + account.
Observability
- grafana-oncall — Formatted-webhook integration POST for alert groups. Region-aware endpoint, configurable state, deep-link to the upstream frames folder.
- honeycomb —
/1/events/<dataset>ingest with dotted keys (video.tags.director,audio.transcript_segment_count, etc) — matches Honeycomb's wide-event model. - new-relic — Events API POST with camelCase attributes (
videoTagDirector,framesEmitted) to match the platform's attribute-naming rules. US + EU collectors.
All six macOS-specific sinks (apple-notes, imessage, shortcuts,
raycast) skip cleanly with exit 0 on non-darwin unless an explicit
*_ALLOW_NON_DARWIN=1 override is set.
Tests
Changed
package.jsondescription advertises 68 sinks and lists every new adapter.
Docs
docs/SINKS-MISSING.mdrefreshed: only 3 real gaps remain (Apple Reminders / Things 3 / Bear, Make/n8n/Activepieces/Node-RED, Roam when their API settles).- Per-sink demo videos + GIFs generated for the 10 new sinks.
v0.5.0 — 2026-04-24
npm i -g peepshow@0.5.0
The sinks wave. 7 new sinks (51 → 58), storage-mode toggle for the
database-shaped sinks, and per-sink launch videos + GIFs across
every site + doc page.
Added — 7 new sinks
- height (issue trackers) — Height REST API; task create with attachments + list targeting + title override.
- opsgenie (observability) — Opsgenie /v2/alerts; region-aware (us/eu), priority clamp (P1..P5), tag slugification.
- firebase-storage (storage) — Firebase REST upload on top of GCS; accepts a pre-issued token OR signs + exchanges a service-account JWT — no SDK needed.
- aider (workflow) — append markdown block to
.aider.chat.history.md(or drop standalone viaAIDER_ROOT). Transcript included when whisper ran. - continue (workflow) — drop frames + manifest.json + transcript.md into
.continue/context/peepshow/. Copy / symlink / hardlink modes (hardlink EXDEV-falls-back-to-copy silently). - raycast (workflow) — macOS-only; writes a manifest + a companion "Show last peepshow run" Raycast script you install once. Skips cleanly on non-darwin unless
RAYCAST_SCRIPT_DIRis overridden. - pipedream (workflow) — POST to a Pipedream source URL. Optional HMAC-SHA256 signing. 429/5xx exponential-backoff retry up to 3×.
Added — sink storage modes
Database-shaped sinks (sqlite, postgres, mongodb) now support two
record layouts, chosen per-sink or globally:
multi(default) — the historical normalised layout: runs row + frame rows + tag rows.single— one row / document per run carrying the entireSinkPayload(frames + audio + transcript) as a JSON blob (TEXT on SQLite, JSONB with GIN index on Postgres, native subdoc on Mongo). A few fields (duration, frames count) are promoted for cheap filtering without JSON parsing.
Precedence: PEEPSHOW_<SINK>_STORAGE_MODE env > PEEPSHOW_SINK_STORAGE_MODE
global env > a PEEPSHOW_SINK_CONFIG_FILE JSON file > the sink's own
fallback. Switching modes on an existing database is safe — the two
layouts live in parallel tables and nothing is migrated.
Full reference + query examples: [docs/SINK-STORAGE-MODES.md](./docs/SINK-STORAGE-MODES.md).
Added — per-sink launch videos + GIFs
Every sink page (/sinks/<slug>/) and every agent page
(/agents/<slug>/) now carries a branded 14-second demo video (drop
→ process → sink/agent action → outro). Sink videos fade to the
"peepshow · <Sink>" outro so loops don't snap. Same MP4 is available
as a GIF under docs/sink-gifs/<slug>.gif for the GitHub / npm
README surfaces. All 64 videos generated from a single template —
Changed
package.jsondescription advertises 58 sinks and the new workflow adapters (Aider · Continue · Raycast · Pipedream).- README embeds a content-hashed
docs/demo.<hash>.gifso browser caches rotate automatically on every regen.
v0.4.2 — 2026-04-24
npm i -g peepshow@0.4.2
Changed
- README now embeds the demo GIF right under the opening hook, so the npm + GitHub README show peepshow actually working instead of walls of text. The GIF is shipped inside the tarball, so npmjs.com renders it without hitting the GitHub CDN.
Added
- 51 per-sink demo videos at
www.peepshow.dev/sinks/<slug>/— each shows the archetype-specific storage + retrieval animation in the sink's brand accent colour. Purely docs/site, no runtime impact.
v0.4.1 — 2026-04-24
npm i -g peepshow@0.4.1
Emergency hotfix over 0.4.0. Three bugs stopped the published package from
running at all when installed via npm; none of them showed up in local dev
because everything in this repo runs via real paths under /Users/....
Fixed
- Bin wrappers now work through
node_modules/.binsymlinks. The 53 wrapper scripts (peepshow+ 52 sink entries) resolved their own location withdirname "$0", which returnsnode_modules/.binwhen invoked through npm's symlink — and the sibling_find-node.shhelper isn't there. Calling./node_modules/.bin/peepshow(or anypeepshow-sink-*) from a fresh install aborted with_find-node.sh: No such file or directory. Wrappers now resolve symlinks first so the helper is always found. - CLI no longer silently exits on macOS
/tmppaths. TheinvokedDirectlyguard comparedimport.meta.urltoprocess.argv[1]as strings, but macOS resolves/tmp→/private/tmponimport.meta.urlwhileargv[1]keeps the logical path. The compare always failed and the CLI returned 0 without doing anything. Both sides now go throughrealpathSyncbefore comparison. - whisper.cpp transcripts populate again. The audio extraction step emits AAC-in-m4a, but whisper.cpp only accepts 16 kHz mono PCM WAV.
runWhisperCppnow decodes the extracted audio via ffmpeg before invoking whisper-cli (matching the pattern the site reels builder already used). Previously every run reportedskippedReason: "error: unable to parse whisper-cpp json"and no segments.
v0.4.0 — 2026-04-24
npm i -g peepshow@0.4.0
The big expansion. Thirty-two new built-in sinks (19 → 51), audio-track
extraction, and a use-case finder on the website. Nothing removed, nothing
renamed — drop-in upgrade.
Added — 32 new sinks
Chat & messaging
mattermost— Mattermost Incoming Webhook (Slack-compatible card).rocketchat— Rocket.Chat Incoming Webhook (Slack-compatible attachments).zulip— Post a markdown message to a Zulip stream or private message.matrix— Upload to any Matrix homeserver (/_matrix/media/v3/upload) +m.imageevents per frame.telegram— Telegram Bot APIsendMessage+sendMediaGroupalbums.msteams— Microsoft Teams Incoming Webhook with Adaptive Card payload.
Issue trackers
asana— Create a task (or attach to one) + upload each frame.clickup— Create a ClickUp task (or attach to one) +/task/{id}/attachment.shortcut— Create a Shortcut story (or attach) +/api/v3/fileslink.trello— Create a Trello card (or attach) + per-frame attachments.jira— Create a Jira issue (or attach) with ADF body + attachments.
Observability & analytics
pagerduty— Fire a PagerDuty Events API v2triggerevent.event-track— Unified sink for Mixpanel / Amplitude / Segment viaEVENT_TRACK_PROVIDER=mixpanel|amplitude|segment.plausible— Custom event to a Plausible Analytics site via/api/event.posthog— PostHogcaptureevent with run metadata.datadog— Datadog/api/v1/events— attaches a peepshow run to the event stream.
Vector + AI memory
milvus— Upsert one row per frame into a Milvus collection (zero-vector placeholder for external embedding).letta— Post a run summary as a message to a Letta (formerly MemGPT) agent.zep— Append a run to a Zep memory session.mem0— Write a run into Mem0 long-term user memory.weaviate— Batch-insert frames as objects into a Weaviate class.
Whiteboards
miro— Upload each frame to a Miro board as an image item on a grid.figma— Post a single file comment summarising the run on a Figma file.
Wiki & notes
outline— Create a markdown doc per run in Outline.confluence— Create a Confluence Cloud page per run (storage-format body).logseq— Write a markdown page into a Logseq graph with frame embeds underassets/.
Object + file storage
gcs— Upload frames +manifest.jsonto a Google Cloud Storage bucket.azure-blob— Upload frames +manifest.jsonto an Azure Blob container (connection string or account+key auth).supabase— Upload frames +manifest.jsonto a Supabase Storage bucket.dropbox— Upload frames +manifest.jsonto a Dropbox folder.gdrive— Upload frames +manifest.jsonto a Google Drive folder.box— Create a per-run Box folder + upload frames + manifest.
Added — audio extraction + transcription
When a video input has an audio stream (MP4 / MOV / WebM / MKV), peepshow
now runs a second ffmpeg pass that:
- Extracts a compact mono 16 kHz AAC
audio.m4anext to the frames. - Probes loudness peak (
astats) + silence ratio (silencedetect). - Emits an
audiofield on the JSON payload with codec / duration / channels / sample rate / bitrate / peak / silence ratio.
On top of that, peepshow **auto-enables transcription when
whisper.cpp is on
PATH** — no flag, no API key, nothing leaves the machine. The
base.en model downloads once to ~/.peepshow/whisper-models/ and is
reused forever after. Explicit --no-transcribe or
PEEPSHOW_TRANSCRIBE=off skips the step.
Five cloud / custom providers are selectable in one flag:
--transcribe openai(needsOPENAI_API_KEY)--transcribe groq(needsGROQ_API_KEY)--transcribe deepgram(needsDEEPGRAM_API_KEY)--transcribe assemblyai(needsASSEMBLYAI_API_KEY)--transcribe custom(needsPEEPSHOW_TRANSCRIBE_CMD)
Each cloud provider only runs when its API key env var is set — without
it, peepshow logs a one-line note and keeps the rest of the run intact.
The transcript lands on audio.transcript with the same shape across
every provider (provider, model, language, durationSeconds,
segments: [{start, end, text}], text), so downstream sinks see the
spoken words regardless of which engine produced them.
Full setup, provider matrix, and privacy notes:
Animated GIF, APNG, and animated WebP inputs skip cleanly — those
formats can't carry audio. Opt out entirely with --no-audio or
PEEPSHOW_AUDIO_ENCODER=off.
Added — website use-case finder
peepshow.dev/sinks/find/ — pick
what you want to do with the frames (search · alert · archive · memory
· whiteboard · analytics · compliance · LLM pipeline · review · workflow)
and where your stack lives (cloud · self-hosted · LLM · local). The
finder ranks matching sinks and hands you the CLI command to wire each
one up.
The homepage #sinks section gets a mini-finder too. The /sinks/
index gets live text + chip filters. All three surfaces share a
hash-bang URL (#!use=memory,search&deploy=self-hosted) so a
selection is one-click-copyable between them.
Changed
package.jsondescriptionnow reflects the 51-sink catalogue.--emit jsonpayload gainsaudioat the top level. All existing fields are unchanged; downstream sinks that ignore unknown fields are unaffected.
v0.3.x — earlier releases
npm i -g peepshow@0.3.x
Baseline that this release builds on: 19 built-in sinks (SQLite ·
Postgres · pgvector · S3-compatible · Webhook · Slack · Discord ·
GraphQL · Notion · Obsidian · IDE attachments · MemPalace · Linear ·
GitHub Issues · Sentry · Chroma · Qdrant · Pinecone · MongoDB) plus
the core frame-extraction CLI, auto-sink persistence
(~/.peepshow/sinks.json), conditional sinks via --when, and agent
manifests for Claude Code, Cursor, Windsurf, Cline, Codex CLI,
Gemini CLI + the generic AGENTS.md convention.
See the git log for fine-grained history; this changelog only tracks
user-facing releases from 0.4.0 onward.
Install the current release with npm i -g peepshow, or pin a version with npm i -g peepshow@0.9.4.