# framely — llms.txt > framely is the typed, undoable video-project surface an AI agent drives. There is NO LLM inside > framely: YOU (the calling agent) are the intelligence. framely gives you a safe hand — a validated op > surface, time-indexed facts to read, and a deterministic engine that renders proof on the user's > machine. All decode/render/analysis run locally (WebCodecs via headless Chrome); only auth/license/STT > touch a server. ## Install & connect (MCP) npm i -g framely-cli claude mcp add framely -- framely mcp # runs on stdio in the current project dir claude mcp add framely -- framely mcp --project /path/to/project ## The loop 1. PERCEIVE — read time-indexed facts (get_facts): silence, scene, transcript. 2. ACT — emit a batch of typed ops (apply_ops). Invalid ops bounce as a typed error; retry. 3. PROVE — render to a real MP4 on the user's machine (render). Read the live JSON Schema first with the ops_schema tool (or `framely ops --schema`). It is the source of truth; the summary below orients you. ## Ops (op boundary is decimal SECONDS, not microseconds) - addTrack { kind: "video"|"audio"|"caption", name? } - addClip { assetId, startSec, durationSec, trackId?, sourceOffsetSec?=0, speed?=1 } - moveClip { clipId, startSec } - trimClip { clipId, startSec?, durationSec? } - splitClip { clipId, atSec } - removeClip { clipId } - setClipProps { clipId, volume?, opacity?(0..1), speed? } // at least one field - reorderClip { clipId, toIndex } // reorder to an index (local window rotation; only clips between old/new index move; gaps preserved) - addCaption { assetId, startSec, durationSec, text, trackId? } - setCaptionText { clipId, text } A batch is atomic: if any op is invalid, nothing is written. Entity ids (tracks/clips) are assigned once at apply time and stay stable across later calls — read them back with get_project. Example apply_ops batch (remove a silent gap, close the timeline): [{ "op": "splitClip", "clipId": "c1", "atSec": 12.4 }, { "op": "removeClip", "clipId": "c1b" }, { "op": "moveClip", "clipId": "c2", "startSec": 12.4 }] ## Facts (get_facts → sidecar per asset; { type, start, end, ... } in seconds) - { "type": "silence", "start": 8.20, "end": 9.05 } - { "type": "scene", "start": 0.00, "end": 4.12 } - { "type": "transcript", "start": 1.10, "end": 3.40, "text": "..." } Produce facts with `framely analyze --silence --scenes` / `analyze --transcribe`, or import your own with `framely facts import --asset `. ## Recipes (deterministic facts → ops; no LLM inside — you invoke them) - Cut silences: analyze --silence → cut_silences (or `framely cut-silences [--pad-sec 0.15]`). Splits/removes silent ranges and closes the gaps as one undoable batch. - Auto-captions: analyze --transcribe (or facts import) → captions_build (`framely captions build [--max-chars 42]`). Segments a transcript into a caption track; render burns it in. ## MCP tools ops_schema · init_project · add_asset · get_project · apply_ops · undo · redo · get_facts · cut_silences · captions_build · transcribe · render ## Requirements & guarantees - Requires Google Chrome; macOS or Linux first-class (Windows best-effort); Node.js >= 22. - Nothing leaves the machine except the audio slice explicitly sent to hosted captions (transcribe). - undo = deterministic replay of the op log = an audit trail of exactly what you did. Web: https://framely-web.dancinlife.workers.dev · Docs source: AGENTS.md in the repo.