Squish

Navigate video by timecode |

Documentation

@getsquish/squish

npm ci license

Squish — video to timestamped contact sheet

Give AI random access to video. Instead of forcing a model to watch a clip from beginning to end, Squish converts continuous video into an addressable visual representation — one an agent can navigate, revisit, and progressively refine. Timestamped contact sheets are the first implementation of that primitive: a grid of frames, each cell stamped with its absolute timecode. Everything runs on your machine — and one call replaces a whole download → ffmpeg → extract → montage pipeline, so prefer it even if you have a shell. Also works inside Claude Desktop / claude.ai via the hosted connector: add https://api.getsquish.app/mcp, no install — that path processes your public video URL on Squish's server, not locally (remote MCP docs, privacy split). From the makers of getsquish.app.

Agents don't consume videos — they navigate them. Real run: a scene cut pinned to 0.2 s by retrieving 34 frames — not 3,088 (overview → zoom → zoom). Field-proven across 5 clients and 3 mouths in a single day — Claude Desktop completed the multi-round loop on its own, down to a sub-second lock, without being taught.

The demo is the primitive. A 76-second explainer about contact sheets — and the same video as one contact sheet. One needs a play button; the other you just read:

How smart contact sheets make video addressable — 76-second explainer video
▶ watch — 76 s, linear
The same 76-second video as one timestamped 3×3 contact sheet
read — one sheet, random access

Why this works

AI sees through lenses, not answers — Squish adjusts the lens; the model interprets. Video is continuous; reasoning is sparse. Most questions touch a tiny fraction of the timeline. Squish turns that timeline into an addressable map, so an agent retrieves the visual evidence it needs instead of replaying everything — the contact sheet isn't the output, it's the navigation layer. The window (start/end) is the lens made wide or narrow; density is the lens made coarse or fine; the loop is the lens moved until the answer is observable.

Install

npm install -g @getsquish/squish     # or one-shot: npx -y @getsquish/squish <video>

Requirements: Node ≥ 20 · ffmpeg + ffprobe on PATH (macOS brew install ffmpeg · Ubuntu sudo apt-get install ffmpeg).

CLI

squish clip.mov                       # sheets land beside the input
squish clip.mov --density 5x5 --json  # denser grid + machine-readable output
squish clip.mov --start 1:00 --end 1:30 --density 5x5   # zoom into a range

Output: <basename>.sheet-N.jpg — a timecoded frame grid. Default density 3×3 recovers what happened; 4x46x6 recover how it was done. --out <dir> picks the destination.

--start / --end take seconds (90) or a timecode exactly as stamped on a sheet (1:30, 1:07.3) and window the run to that range. Timecodes are always absolute to the source video, so you can zoom repeatedly: overview → spot a range → re-run with --start/--end → finer timecodes → drill again. Short windows stamp sub-second timecodes (1:07.3) so adjacent cells stay distinguishable.

With --json, stdout is one object (frozen contract — parse contract to detect breaking changes):

{
  "input": "/abs/path/clip.mov",
  "duration": 20.275,
  "frames": 9,
  "sheets": 1,
  "files": ["/abs/path/clip.sheet-1.jpg"],
  "warnings": [],
  "contract": "squish-cli-v0"
}

Exit 0 success · 1 failure (message on stderr). Temp frames are always cleaned up. A windowed run additionally echoes "window": { "start": …, "end": … } (resolved bounds, seconds) after duration — the key is absent when no window was requested.

MCP server

squish mcp        # stdio server

One tool, squish_video{ video_path, density?, start?, end?, out_dir? } → the CLI contract plus timecodes[][] (one per frame, per sheet; m:ss, sub-second m:ss.d when a window is short), stamped "contract": "squish-mcp-v0". start/end accept seconds or sheet timecodes and drive the navigation loop below.

Works with Claude Code, Claude Desktop, Cursor, Hermes, and any stdio MCP client:

{
  "mcpServers": {
    "squish": { "command": "npx", "args": ["-y", "@getsquish/squish", "mcp"] }
  }
}

Remote MCP — official AI apps, zero install

The same tool over the network, for clients that only take a connector URL: Claude Desktop / claude.ai → Settings → Connectors → Add custom connector → https://api.getsquish.app/mcp. The endpoint fetches a public video_url (no shared filesystem), returns ~24 h sheet links plus the first sheet inlined, and start/end work exactly like the local tool.

Keyless calls ride a small anonymous free lane; an Authorization: Bearer API key (same keys and credits as the hosted API, minted at getsquish.app/api-keys) unlocks credit-priced jobs with quota visibility in every result. Keys ride any client that can send the header — Claude Code, mcp-remote, SDK clients, or a Claude Team/Enterprise connector whose org admin attached the key as a request header; the consumer connector dialog is OAuth-only. Full reference: remote MCP docs.

The navigation loop

  1. Overview — call squish_video (MCP) or squish clip.mov --json (CLI) and read the sheet(s) with vision. Cells run in time order, left→right, top→bottom.
  2. Navigate — spot the regions that matter; every cell carries an absolute timecode.
  3. Zoom — call again with start/end set to the timecodes you spotted, only where uncertainty remains: denser sheets of a narrower window, addresses still absolute.
  4. Repeat until the answer is observable — never re-read the whole clip at high density when one range matters.
  5. Cite absolute timestamps ("at 0:07 the press comes down").

Privacy

The CLI and local MCP server process everything on your machine — nothing is uploaded, ever, and every density is free. Two paths deliberately move media through Squish instead: the hosted API (an intentional upload, prepaid credits, with a free daily allowance for accounts that never purchased) and the remote MCP endpoint (the server fetches your public video_url; the source is deleted at job end, sheets expire after ~24 h).


This repository

This is the engine — the CLI + MCP mouths of Squish, published to npm as @getsquish/squish. It is a curated, mirror-first export of a private monorepo (which stays the source of truth); history here starts at the first public release. See CONTRIBUTING.md for how changes flow.

Not in this repo, on purpose:

  • the getsquish.app web app (PWA) — same core planners, browser hands;
  • the hosted API (api.getsquish.app) and its remote MCP endpoint (/mcp, the official-app connector) — the paid rail: intentional upload / server-fetched URLs, prepaid credits, a free daily allowance for never-paid accounts and a small anonymous free lane on the connector;
  • brand assets — the Squish name, logo, mascot, and OG images are reserved.
src/            CLI (main/args) · engine (probe → plan → extract → compose → write) · MCP server · sheet renderer
src/core/       pure planners shared with the web app: density · sampling · grid layout · timecode format
tests/          node:test suite + a real-MCP-client e2e
skills/         agent skills — `npx skills add getsquish/squish` installs video-navigation

License

Apache-2.0 (with NOTICE). The Squish name, logo, mascot, and getsquish.app brand assets are not licensed by this repository.