What it does
[Zoom](https://zoom.us) is the meeting platform; [Zoom Team Chat](https://developers.zoom.us/docs/api/chat/) is its persistent messaging layer. This sink posts a markdown-flavoured summary of each peepshow run — title, frame count, duration, resolution, codec, and a truncated transcript preview — to a recipient. The recipient is either a user (DM, by email via `ZOOM_TO_USER`) or a channel (group post, by channel JID via `ZOOM_TO_CHANNEL`); the two are mutually exclusive. Authentication is a Bearer access token typically minted by Zoom's [Server-to-Server OAuth flow](https://developers.zoom.us/docs/internal-apps/s2s-oauth/). The Cloud Recording API only exposes *retrieval* of Zoom-hosted recordings — there is no public upload surface for arbitrary media, so chat messaging is the practical integration that works against any Zoom account today.
When to reach for it
- DM a teammate when an agent finishes processing a video (release video, customer-call recap, sprint demo)
- Post every peepshow run into a `#video-pipeline` Zoom channel as a paper-trail
- Bolt a video-summary stage onto an existing Zoom workflow without writing a Zoom Marketplace app from scratch
Install
npm i -g peepshowUse it
ZOOM_ACCESS_TOKEN="$(./refresh-zoom-token.sh)" \
ZOOM_TO_USER="alice@example.com" \
peepshow ./standup.mp4 --sink zoomMake it automatic
Register the sink once — every run fires it afterward. Scope by --when so it only runs for matching videos.
peepshow sinks add zoom
peepshow sinks add zoom --when extension=mp4,mov
peepshow sinks add zoom --when path=/Volumes/Work/Configuration
ZOOM_ACCESS_TOKENOAuth2 access token (Bearer header). Refresh before each invocation — tokens expire in 1 hour. requiredZOOM_TO_USERRecipient email for a user DM. Mutually exclusive with `ZOOM_TO_CHANNEL`.ZOOM_TO_CHANNELChannel JID for a channel post. Mutually exclusive with `ZOOM_TO_USER`.ZOOM_TITLE_PREFIXPrefix prepended to the message title. Default `peepshow`.ZOOM_API_URLOverride the API base URL. Default `https://api.zoom.us`.ZOOM_TRANSCRIPT_MAXMax transcript characters included in the message. Default 2000.PEEPSHOW_FRAME_BASE_URLWhen set, the first frame URL is appended as a link below the summary.
Use with an LLM agent
Every peepshow sink reads its config from env vars and receives a single JSON payload on stdin. An LLM agent (Claude Code, Cursor, Windsurf, Gemini, Codex) can drive the Zoom sink automatically when three things are true:
- the env vars below are exported in the agent's shell (or a project
.envit can load), - the
peepshowCLI is onPATH— install withnpm i -g peepshow, - a peepshow auto-sink is registered for the run (optional but recommended — makes invocation zero-argument).
1. Set the environment
# Add to ~/.zshrc, ~/.bashrc, or a project .env the agent can load
export ZOOM_ACCESS_TOKEN="..."2. Register as an auto-sink
peepshow sinks add zoom
peepshow sinks add zoom --when extension=mp4,mov3. Example LLM session
You → drop a
.movinto Claude Code.Claude → auto-invokes
/peepshow:slides ./clip.mov. peepshow extracts frames + audio, theZoomsink forwards the run to the configured channel. Claude replies with a summary and a link to the created record.
The transcript snippet is posted alongside the frames as a secondary message.
Write your own
A sink is any executable that reads the --emit json payload on stdin. Shell, Node, Python, Go — the spec's in docs/PLUGINS.md. Register persistent ones with peepshow sinks add-cmd 'your-command'.