@miraiclip/assistant 0.1.2 → 0.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/types.ts","../src/openai.ts","../src/http.ts","../src/limits.ts","../src/state.ts","../src/strict.ts","../src/tools.ts","../src/fork.ts","../src/run.ts","../src/testing.ts"],"sourcesContent":["/**\n * The vendor-neutral model contract. An adapter turns these neutral messages\n * and tools into one vendor's API and back; nothing else in the package knows\n * which vendor is on the other end.\n */\n\n/** A tool call the model made. `arguments` is already parsed JSON. */\nexport interface ToolCall {\n id: string;\n name: string;\n arguments: Record<string, unknown>;\n}\n\nexport type ChatMessage =\n | { role: \"system\"; content: string }\n | { role: \"user\"; content: string }\n | { role: \"assistant\"; content: string; toolCalls?: ToolCall[] }\n | { role: \"tool\"; toolCallId: string; name: string; content: string };\n\n/** A tool as the model sees it: name, description and a JSON Schema for its input. */\nexport interface ToolSpec {\n name: string;\n description: string;\n inputSchema: Record<string, unknown>;\n}\n\nexport interface ChatRequest {\n messages: ChatMessage[];\n tools?: ToolSpec[];\n /** Model id, when the adapter serves several. */\n model?: string;\n maxOutputTokens?: number;\n /** Vendor-specific request fields, passed through untouched (e.g. `{ reasoning_effort: \"low\" }`). */\n params?: Record<string, unknown>;\n}\n\nexport interface ChatUsage {\n inputTokens?: number;\n outputTokens?: number;\n}\n\nexport interface ChatResponse {\n content: string;\n toolCalls: ToolCall[];\n /** Why the model stopped. */\n finish: \"stop\" | \"tool_calls\" | \"length\" | \"content_filter\" | \"other\";\n model?: string;\n usage?: ChatUsage;\n}\n\nexport interface ChatOptions {\n signal?: AbortSignal;\n /** Streaming text, as it arrives (adapters that can't stream call it once). */\n onText?: (delta: string) => void;\n}\n\nexport interface ChatModelInfo {\n /** Adapter id, e.g. \"openai\". Alphanumeric with - or _. */\n id: string;\n label: string;\n /** Models this adapter serves; the first is the default. */\n models?: { id: string; label?: string }[];\n}\n\nexport interface ChatModel extends ChatModelInfo {\n complete(request: ChatRequest, options?: ChatOptions): Promise<ChatResponse>;\n}\n\n/** An error from a model vendor, carrying its HTTP status when there was one. */\nexport class ChatModelError extends Error {\n constructor(\n readonly provider: string,\n message: string,\n readonly status?: number,\n ) {\n super(`${provider}: ${message}`);\n this.name = \"ChatModelError\";\n }\n}\n\nexport type FetchLike = (url: string, init?: RequestInit) => Promise<Response>;\n\nexport function defaultFetch(): FetchLike {\n if (typeof fetch !== \"function\") throw new Error(\"No global fetch: pass `fetch` explicitly\");\n return (url, init) => fetch(url, init);\n}\n\n/** Check an adapter's shape once, at registration. */\nexport function defineChatModel<M extends ChatModel>(model: M): M {\n if (!model.id || !/^[a-z0-9][a-z0-9_-]*$/i.test(model.id)) {\n throw new Error(`Chat model id must be alphanumeric with - or _ (got \"${model.id}\")`);\n }\n if (typeof model.complete !== \"function\") throw new Error(`Chat model \"${model.id}\" has no complete()`);\n return model;\n}\n","/**\n * OpenAI adapter, over the Chat Completions API with function calling.\n *\n * Chat Completions is also what most other servers speak (Azure OpenAI,\n * OpenRouter, Groq, Together, vLLM, Ollama, LM Studio…), so `baseUrl` points\n * this one adapter at any of them.\n */\nimport {\n ChatModelError,\n defaultFetch,\n defineChatModel,\n type ChatMessage,\n type ChatModel,\n type ChatRequest,\n type ChatResponse,\n type FetchLike,\n type ToolCall,\n} from \"./types.js\";\n\nexport interface OpenAIChatModelOptions {\n /** Model id, e.g. \"gpt-5.4-mini\". Required: model names change faster than this package. */\n model: string;\n /** Other models the app may pick per request (`ChatRequest.model`). */\n models?: { id: string; label?: string }[];\n /** API key. Omit for local servers that don't need one. Keep it server-side (see `createChatHandler`). */\n apiKey?: string;\n /** Default https://api.openai.com/v1. Any OpenAI-compatible base URL works. */\n baseUrl?: string;\n organization?: string;\n project?: string;\n headers?: Record<string, string>;\n /** Default request fields merged into every call (e.g. `{ reasoning_effort: \"low\" }`). */\n params?: Record<string, unknown>;\n /** Stream responses (default true). */\n stream?: boolean;\n /**\n * Retries on rate limits (429) and server errors (500/502/503/504), waiting\n * as long as the server asks (Retry-After, or \"try again in 1.2s\"), else\n * backing off 1 s, 2 s, 4 s… (capped at 30 s). Default 4; 0 disables.\n */\n maxRetries?: number;\n /** Adapter id / label (default \"openai\" / \"OpenAI\"), e.g. \"ollama\" when pointing elsewhere. */\n id?: string;\n label?: string;\n fetch?: FetchLike;\n}\n\ntype OpenAIMessage =\n | { role: \"system\" | \"user\"; content: string }\n | { role: \"assistant\"; content: string | null; tool_calls?: { id: string; type: \"function\"; function: { name: string; arguments: string } }[] }\n | { role: \"tool\"; tool_call_id: string; content: string };\n\n/** Neutral messages → Chat Completions messages. */\nexport function toOpenAIMessages(messages: readonly ChatMessage[]): OpenAIMessage[] {\n return messages.map((m): OpenAIMessage => {\n if (m.role === \"assistant\") {\n const out: OpenAIMessage = { role: \"assistant\", content: m.content || null };\n if (m.toolCalls?.length) {\n out.tool_calls = m.toolCalls.map((c) => ({ id: c.id, type: \"function\", function: { name: c.name, arguments: JSON.stringify(c.arguments) } }));\n }\n return out;\n }\n if (m.role === \"tool\") return { role: \"tool\", tool_call_id: m.toolCallId, content: m.content };\n return { role: m.role, content: m.content };\n });\n}\n\n/** Parse a tool call's JSON arguments; unparseable JSON is reported to the model as a tool error. */\nexport function parseToolArguments(raw: string): Record<string, unknown> {\n if (!raw.trim()) return {};\n try {\n const value = JSON.parse(raw) as unknown;\n return value && typeof value === \"object\" && !Array.isArray(value) ? (value as Record<string, unknown>) : { __invalidArguments: raw };\n } catch {\n return { __invalidArguments: raw };\n }\n}\n\nconst RETRYABLE = new Set([429, 500, 502, 503, 504]);\n\n/** How long the server asked us to wait (Retry-After header, or \"try again in 1.2s / 350ms\"), else exponential backoff. */\nexport function retryDelayMs(res: Pick<Response, \"headers\">, message: string, attempt: number): number {\n const header = Number(res.headers.get(\"retry-after-ms\") ?? NaN) || Number(res.headers.get(\"retry-after\") ?? NaN) * 1000;\n const m = /try again in ([\\d.]+)\\s*(ms|s)/i.exec(message);\n const asked = header || (m ? Number(m[1]) * (m[2]!.toLowerCase() === \"ms\" ? 1 : 1000) : 0);\n const backoff = Math.min(30_000, 1000 * 2 ** attempt);\n // A little extra and some jitter, so parallel callers don't all retry at the same instant.\n return Math.round(Math.min(60_000, Math.max(asked + 250, asked ? 0 : backoff)) * (1 + Math.random() * 0.25));\n}\n\nfunction sleep(ms: number, signal?: AbortSignal): Promise<void> {\n return new Promise((resolve, reject) => {\n if (signal?.aborted) return reject(signal.reason ?? new Error(\"aborted\"));\n const t = setTimeout(resolve, ms);\n signal?.addEventListener(\"abort\", () => (clearTimeout(t), reject(signal.reason ?? new Error(\"aborted\"))), { once: true });\n });\n}\n\nconst FINISH: Record<string, ChatResponse[\"finish\"]> = {\n stop: \"stop\",\n tool_calls: \"tool_calls\",\n function_call: \"tool_calls\",\n length: \"length\",\n content_filter: \"content_filter\",\n};\n\nexport function openAIChatModel(options: OpenAIChatModelOptions): ChatModel {\n const base = (options.baseUrl ?? \"https://api.openai.com/v1\").replace(/\\/+$/, \"\");\n const id = options.id ?? \"openai\";\n const doFetch = options.fetch ?? defaultFetch();\n const stream = options.stream ?? true;\n const models = [{ id: options.model }, ...(options.models ?? []).filter((m) => m.id !== options.model)];\n\n return defineChatModel({\n id,\n label: options.label ?? \"OpenAI\",\n models,\n async complete(request: ChatRequest, opts = {}): Promise<ChatResponse> {\n const body: Record<string, unknown> = {\n model: request.model ?? options.model,\n messages: toOpenAIMessages(request.messages),\n ...(request.tools?.length\n ? { tools: request.tools.map((t) => ({ type: \"function\", function: { name: t.name, description: t.description, parameters: t.inputSchema } })) }\n : {}),\n ...(request.maxOutputTokens ? { max_completion_tokens: request.maxOutputTokens } : {}),\n ...(stream ? { stream: true, stream_options: { include_usage: true } } : {}),\n ...options.params,\n ...request.params,\n };\n const headers: Record<string, string> = { \"Content-Type\": \"application/json\", ...options.headers };\n if (options.apiKey) headers[\"Authorization\"] = `Bearer ${options.apiKey}`;\n if (options.organization) headers[\"OpenAI-Organization\"] = options.organization;\n if (options.project) headers[\"OpenAI-Project\"] = options.project;\n\n const maxRetries = options.maxRetries ?? 4;\n let res: Response;\n for (let attempt = 0; ; attempt++) {\n res = await doFetch(`${base}/chat/completions`, {\n method: \"POST\",\n headers,\n body: JSON.stringify(body),\n ...(opts.signal ? { signal: opts.signal } : {}),\n });\n if (res.ok) break;\n const err = (await res.json().catch(() => null)) as { error?: { message?: string } } | null;\n const message = err?.error?.message ?? `HTTP ${res.status}`;\n // A 429 for an exhausted quota or no credits won't clear by waiting.\n const permanent = /quota|credits|billing/i.test(message);\n if (attempt >= maxRetries || !RETRYABLE.has(res.status) || permanent) throw new ChatModelError(id, message, res.status);\n await sleep(retryDelayMs(res, message, attempt), opts.signal);\n }\n if (!stream || !res.body || !(res.headers.get(\"content-type\") ?? \"\").includes(\"event-stream\")) {\n return fromCompletion(await res!.json(), opts.onText);\n }\n return readStream(res!.body!, opts.onText);\n },\n });\n}\n\ninterface CompletionJson {\n model?: string;\n choices?: { message?: { content?: string | null; tool_calls?: { id: string; function: { name: string; arguments: string } }[] }; finish_reason?: string }[];\n usage?: { prompt_tokens?: number; completion_tokens?: number };\n}\n\nfunction fromCompletion(json: CompletionJson, onText?: (delta: string) => void): ChatResponse {\n const choice = json.choices?.[0];\n const content = choice?.message?.content ?? \"\";\n if (content) onText?.(content);\n const out: ChatResponse = {\n content,\n toolCalls: (choice?.message?.tool_calls ?? []).map((c) => ({ id: c.id, name: c.function.name, arguments: parseToolArguments(c.function.arguments) })),\n finish: FINISH[choice?.finish_reason ?? \"\"] ?? \"other\",\n };\n if (json.model) out.model = json.model;\n if (json.usage) out.usage = usage(json.usage);\n return out;\n}\n\nconst usage = (u: { prompt_tokens?: number; completion_tokens?: number }) => ({\n ...(u.prompt_tokens !== undefined ? { inputTokens: u.prompt_tokens } : {}),\n ...(u.completion_tokens !== undefined ? { outputTokens: u.completion_tokens } : {}),\n});\n\ninterface ChunkJson {\n model?: string;\n choices?: {\n delta?: { content?: string | null; tool_calls?: { index: number; id?: string; function?: { name?: string; arguments?: string } }[] };\n finish_reason?: string | null;\n }[];\n usage?: { prompt_tokens?: number; completion_tokens?: number } | null;\n}\n\n/** Server-sent events → one response; text deltas stream to `onText`, tool-call fragments are joined by index. */\nasync function readStream(body: ReadableStream<Uint8Array>, onText?: (delta: string) => void): Promise<ChatResponse> {\n const reader = body.getReader();\n const decoder = new TextDecoder();\n let buffer = \"\";\n let content = \"\";\n let finish: ChatResponse[\"finish\"] = \"other\";\n let model: string | undefined;\n let use: ChatResponse[\"usage\"];\n const calls = new Map<number, { id: string; name: string; args: string }>();\n\n const handle = (data: string) => {\n if (data === \"[DONE]\") return;\n let chunk: ChunkJson;\n try {\n chunk = JSON.parse(data) as ChunkJson;\n } catch {\n return;\n }\n if (chunk.model) model = chunk.model;\n if (chunk.usage) use = usage(chunk.usage);\n for (const choice of chunk.choices ?? []) {\n const delta = choice.delta;\n if (delta?.content) {\n content += delta.content;\n onText?.(delta.content);\n }\n for (const tc of delta?.tool_calls ?? []) {\n const call = calls.get(tc.index) ?? { id: \"\", name: \"\", args: \"\" };\n if (tc.id) call.id = tc.id;\n if (tc.function?.name) call.name += tc.function.name;\n if (tc.function?.arguments) call.args += tc.function.arguments;\n calls.set(tc.index, call);\n }\n if (choice.finish_reason) finish = FINISH[choice.finish_reason] ?? \"other\";\n }\n };\n\n for (;;) {\n const { done, value } = await reader.read();\n if (value) buffer += decoder.decode(value, { stream: true });\n let nl: number;\n while ((nl = buffer.indexOf(\"\\n\")) >= 0) {\n const line = buffer.slice(0, nl).replace(/\\r$/, \"\");\n buffer = buffer.slice(nl + 1);\n if (line.startsWith(\"data:\")) handle(line.slice(5).trim());\n }\n if (done) break;\n }\n if (buffer.startsWith(\"data:\")) handle(buffer.slice(5).trim());\n\n const toolCalls: ToolCall[] = [...calls.entries()]\n .sort(([a], [b]) => a - b)\n .map(([index, c]) => ({ id: c.id || `call_${index}`, name: c.name, arguments: parseToolArguments(c.args) }));\n const out: ChatResponse = { content, toolCalls, finish };\n if (model) out.model = model;\n if (use) out.usage = use;\n return out;\n}\n","/**\n * Keys stay on the server: run the real model adapter in your backend behind\n * `createChatHandler`, and give the browser `remoteChatModel(endpoint)` — a\n * proxy with the same `ChatModel` shape. The agent loop and the tools run in\n * the browser, next to the project; only model calls cross the network.\n *\n * GET {base}/ → { id, label, models }\n * POST {base}/chat { request: ChatRequest } →\n * application/x-ndjson, one JSON object per line:\n * { \"type\": \"text\", \"delta\": \"…\" } (as it streams)\n * { \"type\": \"done\", \"response\": ChatResponse }\n * { \"type\": \"error\", \"error\": \"…\", \"status\": 401 }\n * errors before streaming → { error } with 400 / 404 / 413, or the vendor's status\n *\n * Fetch-API handler (`Request` → `Response`): mounts in Node 18+, edge\n * runtimes, Next, Hono, Express (via an adapter).\n */\nimport { ChatModelError, defaultFetch, type ChatModel, type ChatRequest, type ChatResponse, type FetchLike } from \"./types.js\";\n\nexport interface ChatHandlerOptions {\n /** Path prefix the handler is mounted at, e.g. \"/api/assistant\". */\n basePath?: string;\n /** Reject request bodies larger than this (default 2 MB). */\n maxBodyBytes?: number;\n /** Return a Response to refuse a request (auth, rate limits); undefined to allow. */\n authorize?: (request: Request) => Promise<Response | undefined> | Response | undefined;\n /** Adjust each request server-side (pin a model, cap tokens, add instructions). */\n prepare?: (request: ChatRequest) => ChatRequest;\n}\n\nexport function createChatHandler(model: ChatModel, options: ChatHandlerOptions = {}) {\n const base = (options.basePath ?? \"\").replace(/\\/+$/, \"\");\n const maxBody = options.maxBodyBytes ?? 2_000_000;\n\n return async function handle(request: Request): Promise<Response | null> {\n const path = new URL(request.url).pathname;\n if (base && path !== base && !path.startsWith(`${base}/`)) return null;\n const rest = path.slice(base.length).replace(/\\/+$/, \"\");\n\n const refused = await options.authorize?.(request);\n if (refused) return refused;\n\n if (rest === \"\" && request.method === \"GET\") {\n return json(200, { id: model.id, label: model.label, models: model.models ?? [] });\n }\n if (rest !== \"/chat\" || request.method !== \"POST\") return json(404, { error: \"not found\" });\n\n const text = await request.text();\n if (text.length > maxBody) return json(413, { error: \"request too large\" });\n let chat: ChatRequest;\n try {\n chat = (JSON.parse(text) as { request: ChatRequest }).request;\n } catch {\n return json(400, { error: \"body must be JSON { request }\" });\n }\n if (!chat || !Array.isArray(chat.messages)) return json(400, { error: \"request.messages is required\" });\n if (options.prepare) chat = options.prepare(chat);\n\n const encoder = new TextEncoder();\n const body = new ReadableStream<Uint8Array>({\n async start(controller) {\n const send = (value: unknown) => controller.enqueue(encoder.encode(`${JSON.stringify(value)}\\n`));\n try {\n const response = await model.complete(chat, { signal: request.signal, onText: (delta) => send({ type: \"text\", delta }) });\n send({ type: \"done\", response });\n } catch (err) {\n const status = (err as { status?: number })?.status;\n send({ type: \"error\", error: (err as Error)?.message ?? String(err), ...(status ? { status } : {}) });\n } finally {\n controller.close();\n }\n },\n });\n return new Response(body, { status: 200, headers: { \"content-type\": \"application/x-ndjson\", \"cache-control\": \"no-store\" } });\n };\n}\n\nconst json = (status: number, body: unknown) => new Response(JSON.stringify(body), { status, headers: { \"content-type\": \"application/json\" } });\n\nexport interface RemoteChatModelOptions {\n fetch?: FetchLike;\n headers?: Record<string, string>;\n /** Skip the GET for model info (id/label/models default to \"remote\"). */\n info?: { id: string; label: string; models?: { id: string; label?: string }[] };\n}\n\n/** The model a backend serves, as a browser-side `ChatModel`. */\nexport async function remoteChatModel(endpoint: string, options: RemoteChatModelOptions = {}): Promise<ChatModel> {\n const doFetch = options.fetch ?? defaultFetch();\n const root = endpoint.replace(/\\/+$/, \"\");\n let info = options.info;\n if (!info) {\n const res = await doFetch(`${root}/`, { headers: { Accept: \"application/json\", ...options.headers } });\n if (!res.ok) throw new ChatModelError(\"remote\", `HTTP ${res.status}`, res.status);\n info = (await res.json()) as NonNullable<RemoteChatModelOptions[\"info\"]>;\n }\n const { id, label, models } = info;\n\n return {\n id,\n label,\n ...(models ? { models } : {}),\n async complete(request, opts = {}): Promise<ChatResponse> {\n const res = await doFetch(`${root}/chat`, {\n method: \"POST\",\n headers: { \"Content-Type\": \"application/json\", Accept: \"application/x-ndjson\", ...options.headers },\n body: JSON.stringify({ request }),\n ...(opts.signal ? { signal: opts.signal } : {}),\n });\n if (!res.ok || !res.body) {\n const err = (await res.json().catch(() => null)) as { error?: string } | null;\n throw new ChatModelError(id, err?.error ?? `HTTP ${res.status}`, res.status);\n }\n const reader = res.body.getReader();\n const decoder = new TextDecoder();\n let buffer = \"\";\n let final: ChatResponse | undefined;\n const handle = (line: string) => {\n if (!line.trim()) return;\n const event = JSON.parse(line) as { type: string; delta?: string; response?: ChatResponse; error?: string; status?: number };\n if (event.type === \"text\" && event.delta) opts.onText?.(event.delta);\n else if (event.type === \"done\") final = event.response;\n else if (event.type === \"error\") throw new ChatModelError(id, event.error ?? \"failed\", event.status);\n };\n for (;;) {\n const { done, value } = await reader.read();\n if (value) buffer += decoder.decode(value, { stream: true });\n let nl: number;\n while ((nl = buffer.indexOf(\"\\n\")) >= 0) {\n handle(buffer.slice(0, nl));\n buffer = buffer.slice(nl + 1);\n }\n if (done) break;\n }\n handle(buffer);\n if (!final) throw new ChatModelError(id, \"the server closed the stream without a response\");\n return final;\n },\n };\n}\n","import type { ChatModel, ChatRequest } from \"./types.js\";\n\nexport interface RateLimitOptions {\n /** Tokens per rolling minute across every caller of this model (input + output). */\n tokensPerMinute?: number;\n /** Requests per rolling minute. */\n requestsPerMinute?: number;\n /** Token estimate for a request before it's sent (default: JSON length ÷ 4, plus room for the reply). */\n estimate?: (request: ChatRequest) => number;\n}\n\n/**\n * Wrap a model so calls wait for room under your provider's per-minute\n * limits instead of failing with 429s — for batch jobs (evals, bulk edits)\n * and for a server sharing one key. Each call reserves an estimate, which is\n * replaced by the real usage when the response reports it.\n */\nexport function rateLimitedChatModel(model: ChatModel, options: RateLimitOptions): ChatModel {\n const tpm = options.tokensPerMinute ?? Infinity;\n const rpm = options.requestsPerMinute ?? Infinity;\n const estimate = options.estimate ?? ((r: ChatRequest) => Math.ceil(JSON.stringify(r).length / 4) + (r.maxOutputTokens ?? 1000));\n const window: { at: number; tokens: number }[] = [];\n let gate: Promise<void> = Promise.resolve();\n\n const prune = (now: number) => {\n while (window.length && now - window[0]!.at >= 60_000) window.shift();\n };\n const used = () => window.reduce((n, e) => n + e.tokens, 0);\n\n /** Wait (in arrival order) until this request fits, then reserve it. */\n const reserve = (tokens: number, signal?: AbortSignal) => {\n const want = Math.min(tokens, tpm); // a single request bigger than the limit still goes, alone\n const turn = gate.then(async () => {\n for (;;) {\n if (signal?.aborted) throw signal.reason ?? new Error(\"aborted\");\n const now = Date.now();\n prune(now);\n if (window.length < rpm && used() + want <= tpm) break;\n const oldest = window[0];\n await new Promise((r) => setTimeout(r, oldest ? Math.max(50, 60_000 - (now - oldest.at)) : 50));\n }\n const entry = { at: Date.now(), tokens: want };\n window.push(entry);\n return entry;\n });\n gate = turn.then(\n () => undefined,\n () => undefined,\n );\n return turn;\n };\n\n return {\n ...model,\n async complete(request, opts = {}) {\n const entry = await reserve(estimate(request), opts.signal);\n const response = await model.complete(request, opts);\n const real = (response.usage?.inputTokens ?? 0) + (response.usage?.outputTokens ?? 0);\n if (real > 0) entry.tokens = real;\n return response;\n },\n };\n}\n","import { animationPreset, describeAnimation, describeProject, findCuts, keyframeTimeUs, readAnimation, type AnimationRecipe, type Clip, type ProjectDocument } from \"@miraiclip/core\";\n\n/**\n * The project as the model sees it: core's `describeProject`, plus what a\n * model needs to make RELATIVE edits (\"bigger\", \"louder\", \"keep the\n * entrance\") — track names, every clip's position, size and opacity, text\n * styling, volume, effects and its animation recipe.\n */\nexport function describeForAssistant(doc: ProjectDocument): string {\n const lines = [\n describeProject(doc),\n \"\",\n \"track names: \" +\n doc.trackOrder\n .map((id) => {\n const t = doc.tracks[id] as { name?: string; hidden?: boolean; muted?: boolean; solo?: boolean; locked?: boolean } | undefined;\n const flags = [t?.hidden && \"hidden\", t?.muted && \"muted\", t?.solo && \"solo\", t?.locked && \"locked\"].filter(Boolean);\n return `${id} = \"${t?.name ?? id}\"${flags.length ? ` (${flags.join(\", \")})` : \"\"}`;\n })\n .join(\", \") +\n \" (track flags muted / solo / locked / hidden: track/set-property)\",\n ];\n const cuts = findCuts(doc);\n lines.push(\n cuts.length\n ? `cuts (where one clip ends and the next starts on the same track; transitions go here): ${cuts.map((c) => `${n(c.atUs / 1e6)}s ${c.fromClipId}→${c.toClipId}${c.transition ? ` (${c.transition.kind})` : \"\"}`).join(\", \")}`\n : \"cuts: none\",\n );\n const clips = Object.values(doc.clips).sort((a, b) => a.trackId.localeCompare(b.trackId) || a.startUs - b.startUs);\n if (clips.length) {\n lines.push(\"clip details (transform x/y: 0.5 = center; scale 1 = full size; times in seconds):\");\n for (const clip of clips) lines.push(`- ${clip.id}: ${details(clip)}`);\n }\n return lines.join(\"\\n\");\n}\n\nconst n = (v: number) => Number(v.toFixed(3));\n\nfunction details(clip: Clip): string {\n const parts: string[] = [`${clip.kind} on ${clip.trackId}`, `${n(clip.startUs / 1e6)}–${n((clip.startUs + clip.durationUs) / 1e6)}s`];\n if (clip.kind !== \"audio\") {\n const t = clip.transform;\n parts.push(`x ${n(t.x)} y ${n(t.y)} scale ${n(t.scale)}${t.rotation ? ` rotation ${n(t.rotation)}°` : \"\"} opacity ${n(t.opacity)}`);\n }\n if (clip.kind === \"text\") {\n const c = clip as Clip & { fontSizePx: number; color: string; fontFamily: string; fontWeight?: number; textAlign?: string };\n parts.push(`font ${c.fontFamily} ${c.fontSizePx}px${c.fontWeight ? ` weight ${c.fontWeight}` : \"\"} color ${c.color}${c.textAlign ? ` align ${c.textAlign}` : \"\"}`);\n }\n if (clip.kind === \"caption\") {\n const c = clip as Clip & { words: { text: string }[]; style: { preset: string; color: string; highlightColor: string; backgroundColor?: string } };\n parts.push(\n `words \"${c.words.map((w) => w.text).join(\" \").slice(0, 80)}\" · style preset ${c.style.preset} color ${c.style.color} highlight ${c.style.highlightColor} box ${c.style.backgroundColor ?? \"none\"} (style.backgroundColor)`,\n );\n }\n if (clip.kind === \"html\") {\n const c = clip as Clip & { template: string; params: Record<string, unknown> };\n parts.push(`params ${JSON.stringify(c.params)} (template placeholders: ${htmlPlaceholders(c.template).join(\", \") || \"none\"})`);\n }\n if (\"volume\" in clip) {\n const c = clip as Clip & { volume: number; fadeInUs?: number; fadeOutUs?: number; trimStartUs: number };\n parts.push(`volume ${n(c.volume)}${c.fadeInUs ? ` fade-in ${n(c.fadeInUs / 1e6)}s` : \"\"}${c.fadeOutUs ? ` fade-out ${n(c.fadeOutUs / 1e6)}s` : \"\"}`);\n parts.push(`source from ${n(c.trimStartUs / 1e6)}s`);\n }\n if (clip.effects?.length) parts.push(`effects ${clip.effects.map((e) => `${e.kind}#${e.id}${e.enabled ? \"\" : \" (off)\"}`).join(\", \")}`);\n const volumeKeys = clip.animations?.volume ?? [];\n if (volumeKeys.length) parts.push(`volume keyframes ${volumeKeys.map((k) => `${n((clip.startUs + keyframeTimeUs(k, clip.durationUs)) / 1e6)}s=${n(k.value)}`).join(\", \")}`);\n const visual = Object.entries(clip.animations ?? {}).some(([prop, list]) => prop !== \"volume\" && list && list.length);\n if (visual) {\n const read = readClipAnimation(clip);\n parts.push(read.custom ? `custom keyframes (not presets): ${describeKeyframes(clip)}` : `animation ${describeAnimation(read.recipe)}`);\n }\n return parts.join(\" · \");\n}\n\n/** The `{{name}}` placeholders an html clip's template substitutes from its params. */\nexport function htmlPlaceholders(template: string): string[] {\n return [...new Set([...template.matchAll(/\\{\\{\\s*([\\w.-]+)\\s*\\}\\}/g)].map((m) => m[1]!))];\n}\n\n/**\n * core's `readAnimation`, stricter: a \"stale\" reading (In / Out plus a loop\n * whose spacing a trim broke) is only trusted when the loop's properties have\n * enough keyframes to be a loop. Two keyframes are someone's own animation,\n * and rewriting them as presets would destroy it.\n */\nexport function readClipAnimation(clip: Clip): { recipe: AnimationRecipe; custom: boolean } {\n const read = readAnimation(clip);\n if (read.custom) return { recipe: {}, custom: true };\n if (read.stale && read.recipe.loop) {\n const preset = animationPreset(read.recipe.loop.preset);\n const props = Object.keys(preset?.build(clip.transform, 1_000_000, \"linear\") ?? {}) as (keyof NonNullable<Clip[\"animations\"]>)[];\n const count = props.reduce((sum, prop) => sum + (clip.animations?.[prop]?.length ?? 0), 0);\n if (count < 4) return { recipe: {}, custom: true };\n }\n return { recipe: read.recipe, custom: false };\n}\n\n/** Visual keyframes per property, at timeline seconds: \"opacity 0.5s 0 → 1s 1; scale …\". */\nexport function describeKeyframes(clip: Clip): string {\n return Object.entries(clip.animations ?? {})\n .filter(([prop, list]) => prop !== \"volume\" && list?.length)\n .map(([prop, list]) => `${prop} ${list!.map((k) => `${n((clip.startUs + keyframeTimeUs(k, clip.durationUs)) / 1e6)}s ${n(k.value)}`).join(\" → \")}`)\n .join(\"; \");\n}\n","/**\n * Catch fields a command would silently ignore. Core's schemas strip unknown\n * keys, so `clip/move { at: … }` or `track/set-property { property, value }`\n * validate fine and change nothing — and a model then reports an edit that\n * never happened. Checking payload keys against the command's JSON Schema\n * turns those into errors the model can fix.\n */\ntype Schema = {\n type?: string;\n properties?: Record<string, Schema>;\n required?: string[];\n anyOf?: Schema[];\n oneOf?: Schema[];\n const?: unknown;\n additionalProperties?: boolean | Schema;\n};\n\n/** Object schemas a value may match (unions flattened). */\nfunction branches(schema: Schema): Schema[] {\n const alts = schema.anyOf ?? schema.oneOf;\n return alts ? alts.flatMap(branches) : [schema];\n}\n\n/** `path` entries for keys the schema doesn't declare (one level into nested objects). */\nfunction unknownIn(schema: Schema, value: Record<string, unknown>, prefix = \"\"): string[] {\n const props = schema.properties;\n if (!props || (schema.additionalProperties !== undefined && schema.additionalProperties !== false)) return [];\n const out: string[] = [];\n for (const [key, v] of Object.entries(value)) {\n const sub = props[key];\n if (!sub) out.push(prefix + key);\n else if (!prefix && v && typeof v === \"object\" && !Array.isArray(v)) {\n const objectBranch = branches(sub).find((b) => b.properties);\n if (objectBranch) out.push(...unknownIn(objectBranch, v as Record<string, unknown>, `${key}.`));\n }\n }\n return out;\n}\n\nexport interface UnknownFields {\n unknown: string[];\n /** The fields the command does take (for the error message). */\n allowed: string[];\n}\n\n/** Unknown payload fields for a command, judged against the best-matching schema branch. */\nexport function unknownFields(schema: unknown, payload: unknown): UnknownFields | null {\n if (!schema || !payload || typeof payload !== \"object\" || Array.isArray(payload)) return null;\n const value = payload as Record<string, unknown>;\n let candidates = branches(schema as Schema).filter((b) => b.properties);\n if (candidates.length === 0) return null;\n // A discriminated union: prefer the branch whose `kind` constant matches.\n const byKind = candidates.filter((b) => b.properties!.kind?.const !== undefined && b.properties!.kind.const === value.kind);\n if (byKind.length) candidates = byKind;\n let best: UnknownFields | null = null;\n for (const b of candidates) {\n const unknown = unknownIn(b, value);\n if (!best || unknown.length < best.unknown.length) best = { unknown, allowed: Object.keys(b.properties!) };\n }\n return best && best.unknown.length ? best : null;\n}\n","import {\n ANIMATION_EASINGS,\n ANIMATION_PRESETS,\n animationCommands,\n applyCommands,\n commandCatalog,\n builtinTransitionParamSchemas,\n describeAnimation,\n EFFECT_CATALOG,\n findCuts,\n fitAnimation,\n toToolDefinitions,\n tryDispatch,\n type AnimationEasing,\n type AnimationRecipe,\n type AnimationSlot,\n type Command,\n type Cut,\n type Project,\n} from \"@miraiclip/core\";\nimport { describeForAssistant, describeKeyframes, htmlPlaceholders, readClipAnimation } from \"./state.js\";\nimport { unknownFields } from \"./strict.js\";\nimport type { ToolSpec } from \"./types.js\";\n\n/** What a tool's `run` gets. `project` is the turn's working copy. */\nexport interface ToolContext {\n project: Project;\n signal?: AbortSignal;\n}\n\n/** A tool's answer. `changes` are human-readable lines for the app's \"what changed\" list. */\nexport type ToolOutput =\n | { ok: true; result?: unknown; changes?: string[] }\n | { ok: false; error: unknown };\n\nexport interface AssistantTool extends ToolSpec {\n run(input: Record<string, unknown>, ctx: ToolContext): Promise<ToolOutput> | ToolOutput;\n}\n\nexport function defineTool(tool: AssistantTool): AssistantTool {\n if (!/^[a-zA-Z0-9_-]{1,64}$/.test(tool.name)) throw new Error(`Tool name must match ^[a-zA-Z0-9_-]{1,64}$ (got \"${tool.name}\")`);\n return tool;\n}\n\n/**\n * Adopt tools that come as definitions plus one runner — e.g.\n * `@miraiclip/audio-sources`' `audioToolDefinitions` + `runAudioTool`.\n * Accepts `{ name, description, input_schema }` or OpenAI function tools.\n */\nexport function toolsFromDefinitions(\n definitions: readonly unknown[],\n run: (name: string, input: Record<string, unknown>, ctx: ToolContext) => Promise<{ ok: boolean; result?: unknown; error?: unknown }>,\n options: { changes?: (name: string, input: Record<string, unknown>, result: unknown) => string[] | undefined } = {},\n): AssistantTool[] {\n return definitions.map((d) => {\n const def = d as { name?: string; description?: string; input_schema?: Record<string, unknown>; function?: { name: string; description?: string; parameters?: Record<string, unknown> } };\n const name = def.function?.name ?? def.name!;\n return defineTool({\n name,\n description: def.function?.description ?? def.description ?? \"\",\n inputSchema: def.function?.parameters ?? def.input_schema ?? { type: \"object\", properties: {} },\n async run(input, ctx) {\n const out = await run(name, input, ctx);\n if (!out.ok) return { ok: false, error: out.error ?? \"failed\" };\n const changes = options.changes?.(name, input, out.result);\n return { ok: true, result: out.result, ...(changes?.length ? { changes } : {}) };\n },\n });\n });\n}\n\nconst S = 1_000_000;\n/** Slide presets are named by motion; models do better naming the edge, so these map edges to presets. */\nconst SLIDE_IN: Record<string, string> = { \"from-right\": \"left\", \"from-left\": \"right\", \"from-bottom\": \"up\", \"from-top\": \"down\" };\nconst SLIDE_OUT: Record<string, string> = { \"to-left\": \"left\", \"to-right\": \"right\", \"to-top\": \"up\", \"to-bottom\": \"down\" };\nfunction slideAliases(input: Record<string, unknown>): Record<string, unknown> {\n const out: Record<string, unknown> = {};\n if (typeof input.in === \"string\" && SLIDE_IN[input.in]) out.in = SLIDE_IN[input.in];\n if (typeof input.out === \"string\" && SLIDE_OUT[input.out]) out.out = SLIDE_OUT[input.out];\n return out;\n}\n/** The html template set_background uses: one `{{color}}` fill. */\nexport const BACKGROUND_TEMPLATE = '<div style=\"width:100%;height:100%;background:{{color}}\"></div>';\nconst seconds = (us: number) => `${Math.round(us / 100_000) / 10}s`;\nconst n3 = (v: number) => Number(v.toFixed(3));\nconst toUs = (s: unknown) => (typeof s === \"number\" && Number.isFinite(s) ? Math.round(s * S) : undefined);\n\n/* ---------- describing commands for people ---------- */\n\nconst PHRASES: Record<string, (p: Record<string, unknown>) => string> = {\n \"clip/add\": (p) =>\n p.kind === \"text\" ? `Added text \"${String(p.text ?? \"\").slice(0, 40)}\" at ${seconds(Number(p.startUs))}` : `Added a ${p.kind} clip at ${seconds(Number(p.startUs))}`,\n \"clip/remove\": (p) => `Removed clip ${p.clipId}`,\n \"clip/move\": (p) => `Moved clip ${p.clipId}${p.startUs !== undefined ? ` to ${seconds(Number(p.startUs))}` : \"\"}`,\n \"clip/trim\": (p) => `Trimmed clip ${p.clipId}`,\n \"clip/split\": (p) => `Split clip ${p.clipId} at ${seconds(Number(p.atUs))}`,\n \"clip/duplicate\": (p) => `Duplicated clip ${p.clipId}`,\n \"clip/set-property\": (p) => `Changed ${Object.keys(p).filter((k) => k !== \"clipId\").join(\", \")} of clip ${p.clipId}`,\n \"track/add\": (p) => `Added a ${p.kind} track`,\n \"track/remove\": (p) => `Removed track ${p.id}`,\n \"track/set-property\": (p) => `Changed track ${p.trackId}: ${Object.keys(p).filter((k) => k !== \"trackId\").join(\", \")}`,\n \"effect/add\": (p) => `Added ${p.kind} to clip ${p.clipId}`,\n \"effect/update\": (p) => `Adjusted an effect on clip ${p.clipId}`,\n \"effect/remove\": (p) => `Removed an effect from clip ${p.clipId}`,\n \"transition/add\": (p) => `Added a ${p.kind} transition`,\n \"transition/remove\": () => \"Removed a transition\",\n \"project/set-settings\": () => \"Changed the project settings\",\n};\n\n/** One line per command, for a \"what changed\" list (keyframe edits fold into one line per clip). */\nexport function describeCommands(commands: readonly Command[]): string[] {\n const lines: string[] = [];\n const keyframed = new Set<string>();\n for (const c of commands) {\n const p = (c.payload ?? {}) as Record<string, unknown>;\n if (c.type.startsWith(\"keyframe/\")) {\n const id = String(p.clipId);\n if (!keyframed.has(id)) lines.push(`Changed the animation of clip ${id}`);\n keyframed.add(id);\n continue;\n }\n const phrase = PHRASES[c.type];\n lines.push(phrase ? phrase(p) : `${c.type}`);\n }\n return lines;\n}\n\n/* ---------- the editor tools ---------- */\n\nconst US_NOTE = \"Times in commands are integer MICROSECONDS (1 s = 1000000).\";\n\nexport interface EditorToolsOptions {\n /** Transition kinds to offer (default: the built-ins). Add your registered custom kinds. */\n transitionKinds?: string[];\n /** The command catalog to offer in apply_commands (default: the built-ins). Pass `project.commandCatalog()` when you register custom commands. */\n commandCatalog?: Record<string, unknown>;\n}\n\n/**\n * The tools that edit a Miraiclip project: read state, look up a command's\n * schema, apply command batches, and two high-level tools (transitions,\n * animation presets) that use the same logic as the editor's panels.\n */\nexport function editorTools(options: EditorToolsOptions = {}): AssistantTool[] {\n const transitionKinds = options.transitionKinds ?? Object.keys(builtinTransitionParamSchemas);\n const presetIds = (slot: AnimationSlot) => ANIMATION_PRESETS.filter((p) => p.slot === slot).map((p) => p.id.split(\":\")[1]!);\n\n return [\n defineTool({\n name: \"get_state\",\n description: `The project as it is now (after your edits so far): settings, assets, tracks with clips (ids, kinds, time ranges), transitions, and each clip's position, size, opacity, text style, volume, effects and animation. ${US_NOTE}`,\n inputSchema: {\n type: \"object\",\n properties: { includeJson: { type: \"boolean\", description: \"Also return the full document JSON (verbose; only for exact field values).\" } },\n additionalProperties: false,\n },\n run(input, { project }) {\n const summary = describeForAssistant(project.toJSON());\n return { ok: true, result: input.includeJson ? { summary, document: project.toJSON() } : summary };\n },\n }),\n\n defineTool({\n name: \"get_command_schema\",\n description: \"The JSON Schema of one editing command's payload. Look it up before using a command in apply_commands for the first time.\",\n inputSchema: {\n type: \"object\",\n properties: { type: { type: \"string\", description: 'Command type, e.g. \"clip/add\".' } },\n required: [\"type\"],\n additionalProperties: false,\n },\n run(input, { project }) {\n const catalog = project.commandCatalog();\n const schema = catalog[String(input.type)];\n return schema ? { ok: true, result: schema } : { ok: false, error: { error: `unknown command \"${input.type}\"`, validTypes: Object.keys(catalog).sort() } };\n },\n }),\n\n defineTool({\n name: \"apply_commands\",\n description:\n \"Apply editing commands as one all-or-nothing batch. On failure nothing changes and the error names the failing index and why (invalid payload issues, or the engine's rejection reason) — fix it and retry. \" +\n `Give new clips, tracks and assets your own readable ids. ${US_NOTE}`,\n inputSchema: applyCommandsSchema(options.commandCatalog ?? commandCatalog()),\n run(input, { project }) {\n const commands = input.commands as Command[] | undefined;\n if (!Array.isArray(commands) || commands.length === 0) return { ok: false, error: \"commands must be a non-empty array of { type, payload }\" };\n return applyChecked(project, commands);\n },\n }),\n\n defineTool({\n name: \"add_transition\",\n description:\n \"Add (or replace, or remove) a transition on cuts — where one clip ends exactly where the next starts on the same video track. Pick the cut by its two clip ids, by a time near it, or all cuts. \" +\n \"A transition overlaps both clips around the cut, so its length is capped by the spare footage there; cuts with none are skipped and reported. \" +\n 'When a video clip directly follows another on its track, \"fade into / dissolve to / slide in / wipe to\" that clip means a transition on the cut before it, not an animation. ' +\n 'Slide / wipe direction is the direction of motion: \"from the left\" = right.',\n inputSchema: {\n type: \"object\",\n properties: {\n kind: { type: \"string\", enum: [...transitionKinds, \"none\"], description: '\"none\" removes the transition.' },\n fromClipId: { type: \"string\", description: \"The clip ending at the cut.\" },\n toClipId: { type: \"string\", description: \"The clip starting at the cut.\" },\n nearSeconds: { type: \"number\", description: \"Pick the cut closest to this time instead.\" },\n allCuts: { type: \"boolean\", description: \"Apply to every cut.\" },\n durationSeconds: { type: \"number\", minimum: 0.1, maximum: 3, description: \"Default 0.6.\" },\n direction: { type: \"string\", enum: [\"left\", \"right\", \"up\", \"down\"], description: \"wipe / slide only.\" },\n },\n required: [\"kind\"],\n additionalProperties: false,\n },\n run(input, { project }) {\n const cuts = findCuts(project.getState().doc);\n if (cuts.length === 0) return { ok: false, error: \"There are no cuts: transitions need two clips touching on the same video track.\" };\n let targets: Cut[];\n if (input.allCuts === true) targets = cuts;\n else if (typeof input.fromClipId === \"string\" || typeof input.toClipId === \"string\") {\n targets = cuts.filter((c) => (!input.fromClipId || c.fromClipId === input.fromClipId) && (!input.toClipId || c.toClipId === input.toClipId));\n if (targets.length === 0) return { ok: false, error: { error: noCutReason(project, input.fromClipId, input.toClipId), cuts: cuts.map(cutInfo) } };\n } else {\n const at = toUs(input.nearSeconds) ?? project.getState().playheadUs;\n const nearest = cuts.reduce((a, b) => (Math.abs(b.atUs - at) < Math.abs(a.atUs - at) ? b : a));\n if (input.nearSeconds !== undefined && Math.abs(nearest.atUs - at) > 1.5 * S) {\n return {\n ok: false,\n error: { error: `There is no cut near ${seconds(at)} (the nearest is at ${seconds(nearest.atUs)}). Transitions only go on cuts; nothing was added. Use one of these cuts, or tell the user there isn't one there.`, cuts: cuts.map(cutInfo) },\n };\n }\n targets = [nearest];\n }\n if (targets.length === 0) return { ok: false, error: { error: \"no cut matches\", cuts: cuts.map(cutInfo) } };\n const kind = String(input.kind);\n const wanted = toUs(input.durationSeconds) ?? 600_000;\n const commands: Command[] = [];\n const changes: string[] = [];\n const skipped: unknown[] = [];\n for (const cut of targets) {\n if (cut.transition) commands.push({ type: \"transition/remove\", payload: { transitionId: cut.transition.id } });\n if (kind === \"none\") {\n if (cut.transition) changes.push(`Removed the transition at ${seconds(cut.atUs)}`);\n continue;\n }\n const durationUs = Math.min(wanted, cut.maxTransitionUs);\n if (durationUs < 100_000) {\n skipped.push({ ...cutInfo(cut), reason: `no spare footage (${cut.short === \"from\" ? \"the first clip ends where its media ends\" : cut.short === \"to\" ? \"the second clip starts at its media's start\" : \"both clips use all their media\"})` });\n if (cut.transition) commands.pop();\n continue;\n }\n const params = input.direction && (kind === \"wipe\" || kind === \"slide\") ? { direction: input.direction } : undefined;\n commands.push({ type: \"transition/add\", payload: { kind, fromClipId: cut.fromClipId, toClipId: cut.toClipId, durationUs, ...(params ? { params } : {}) } });\n changes.push(`${cut.transition ? \"Replaced the transition with\" : \"Added\"} ${kind} at ${seconds(cut.atUs)} (${seconds(durationUs)})`);\n }\n if (commands.length) {\n const result = applyCommands(project, commands);\n if (!result.ok) return { ok: false, error: result };\n }\n return { ok: true, result: { changed: changes.length, skipped }, changes };\n },\n }),\n\n defineTool({\n name: \"animate_clip\",\n description:\n \"Give a visual clip (video, image, text) an entrance (in), a loop while on screen, and/or an exit (out), as keyframes. Slots you leave out keep their current animation; \\\"none\\\" removes one; reset:true removes all. \" +\n `In/out presets: ${presetIds(\"in\").join(\", \")}. Loops: ${presetIds(\"loop\").join(\", \")}. Exits stay on the clip's end through trims. ` +\n 'Slides: give the edge, in: \"from-right\" (enters from the right edge, moving left), \"from-left\", \"from-top\", \"from-bottom\"; out: \"to-left\", \"to-right\", \"to-top\", \"to-bottom\". ' +\n \"For text, logos and overlays; between two video clips prefer add_transition. The clip's current animation is in get_state; pass only the slots to change. \" +\n \"Fading the whole VIDEO in at the start and out at the end is two calls: in on the FIRST clip of the main video track, out on the LAST one (an out on the first clip would fade to black at its cut).\",\n inputSchema: {\n type: \"object\",\n properties: {\n clipId: { type: \"string\" },\n in: { type: \"string\", enum: [...presetIds(\"in\"), ...Object.keys(SLIDE_IN), \"none\"], description: 'For slides, prefer \"from-left\" / \"from-right\" / \"from-top\" / \"from-bottom\": the edge the clip enters from.' },\n loop: { type: \"string\", enum: [...presetIds(\"loop\"), \"none\"] },\n out: { type: \"string\", enum: [...presetIds(\"out\"), ...Object.keys(SLIDE_OUT), \"none\"], description: 'For slides, prefer \"to-left\" / \"to-right\" / \"to-top\" / \"to-bottom\": the edge the clip leaves by.' },\n inSeconds: { type: \"number\", minimum: 0.2, maximum: 3, description: \"Default 0.6.\" },\n outSeconds: { type: \"number\", minimum: 0.2, maximum: 3, description: \"Default 0.6.\" },\n easing: { type: \"string\", enum: ANIMATION_EASINGS.map((e) => e.id), description: \"Default smooth.\" },\n reset: { type: \"boolean\" },\n },\n required: [\"clipId\"],\n additionalProperties: false,\n },\n run(input, { project }) {\n input = { ...input, ...slideAliases(input) };\n const clip = project.getState().doc.clips[String(input.clipId)];\n if (!clip) return { ok: false, error: `no clip \"${input.clipId}\"` };\n if (clip.kind === \"audio\") return { ok: false, error: \"audio clips have no picture to animate (use volume or fades)\" };\n const reading = readClipAnimation(clip);\n if (reading.custom && input.reset !== true) {\n return {\n ok: false,\n error: `${clip.id} has its own keyframes (${describeKeyframes(clip)}), not presets; presets would overwrite them. To remove one property's animation, apply_commands keyframe/clear { clipId, property }. To edit them, use set_keyframes. To replace them all with presets, pass reset: true. Nothing was changed.`,\n };\n }\n const current = input.reset === true ? {} : reading.recipe;\n const next: AnimationRecipe = { ...current };\n const easing = (typeof input.easing === \"string\" ? input.easing : \"smooth\") as AnimationEasing;\n for (const slot of [\"in\", \"loop\", \"out\"] as const) {\n const name = input[slot];\n if (name === undefined) continue;\n if (name === \"none\") {\n delete next[slot];\n continue;\n }\n const durationUs = slot === \"loop\" ? 0 : toUs(input[slot === \"in\" ? \"inSeconds\" : \"outSeconds\"]) ?? current[slot]?.durationUs ?? 600_000;\n next[slot] = { preset: `${slot}:${name}`, durationUs, easing: slot === \"loop\" ? \"linear\" : easing };\n }\n const recipe = fitAnimation(next, clip.durationUs);\n const result = applyCommands(project, animationCommands(clip, recipe));\n if (!result.ok) return { ok: false, error: result };\n const summary = describeAnimation(recipe) || \"no animation\";\n const atCut = input.in && input.in !== \"none\" && clip.kind === \"video\" ? findCuts(project.getState().doc).find((c) => c.toClipId === clip.id) : undefined;\n return {\n ok: true,\n result: {\n clipId: clip.id,\n animation: summary,\n ...(atCut ? { note: `${clip.id} starts at a cut right after ${atCut.fromClipId}. If the user meant going from ${atCut.fromClipId} into ${clip.id} (\"fade into\", \"slide in\", \"dissolve to\"), that is add_transition on this cut: undo this entrance with in:\"none\" and add the transition.` } : {}),\n },\n changes: [`${clip.id}: ${summary}`],\n };\n },\n }),\n\n defineTool({\n name: \"add_effect\",\n description:\n \"Add a visual effect to one or more clips (video, image, text). Kinds, with their params (min–max, default): \" +\n EFFECT_CATALOG.map((e) => `${e.kind} (${e.label}${e.params.length ? `: ${e.params.map((p) => (p.type === \"number\" ? `${p.key} ${p.min}–${p.max}, ${p.default}` : `${p.key} color`)).join(\"; \")}` : \"\"})`).join(\", \") +\n \". Omitted params take their defaults. Black & white is mono; brightness, contrast, saturation and hue are colorAdjust params (-1–1, 0 = unchanged).\",\n inputSchema: {\n type: \"object\",\n properties: {\n clipIds: { type: \"array\", items: { type: \"string\" }, minItems: 1 },\n kind: { type: \"string\", enum: EFFECT_CATALOG.map((e) => e.kind) },\n params: { type: \"object\", description: \"Effect params by key; see the list above.\" },\n },\n required: [\"clipIds\", \"kind\"],\n additionalProperties: false,\n },\n run(input, { project }) {\n const ids = (input.clipIds as string[]) ?? [];\n const doc = project.getState().doc;\n const missing = ids.filter((id) => !doc.clips[id]);\n if (missing.length) return { ok: false, error: `no clip ${missing.map((m) => `\"${m}\"`).join(\", \")}` };\n const audio = ids.filter((id) => doc.clips[id]!.kind === \"audio\");\n if (audio.length) return { ok: false, error: `audio clips have no picture: ${audio.join(\", \")}` };\n const params = input.params && typeof input.params === \"object\" ? (input.params as Record<string, unknown>) : undefined;\n const out = applyChecked(project, ids.map((clipId) => ({ type: \"effect/add\", payload: { clipId, kind: input.kind, ...(params ? { params } : {}) } })));\n if (!out.ok) return out;\n const label = EFFECT_CATALOG.find((e) => e.kind === input.kind)?.label ?? String(input.kind);\n return { ...out, changes: [`Added ${label} to ${ids.join(\", \")}`] };\n },\n }),\n\n defineTool({\n name: \"remove_effects\",\n description: \"Delete effects from clips: one kind, or every effect when kind is omitted. To turn effects off but keep them, use set_effects_enabled.\",\n inputSchema: {\n type: \"object\",\n properties: {\n clipIds: { type: \"array\", items: { type: \"string\" }, minItems: 1 },\n kind: { type: \"string\", description: \"Only effects of this kind (default: all).\" },\n },\n required: [\"clipIds\"],\n additionalProperties: false,\n },\n run(input, { project }) {\n const doc = project.getState().doc;\n const commands: Command[] = [];\n for (const id of (input.clipIds as string[]) ?? []) {\n const clip = doc.clips[id];\n if (!clip) return { ok: false, error: `no clip \"${id}\"` };\n for (const e of clip.effects ?? []) if (!input.kind || e.kind === input.kind) commands.push({ type: \"effect/remove\", payload: { clipId: id, effectId: e.id } });\n }\n if (!commands.length) return { ok: true, result: { removed: 0, note: \"those clips had no matching effects\" } };\n const out = applyChecked(project, commands);\n return out.ok ? { ...out, result: { removed: commands.length }, changes: [`Removed ${commands.length} effect${commands.length > 1 ? \"s\" : \"\"}`] } : out;\n },\n }),\n\n defineTool({\n name: \"trim_clip\",\n description:\n \"Trim a clip like an editor's edge drag, in seconds. removeFromStart cuts that much off the beginning (the clip's end stays put; for video/audio the source moves on too). \" +\n \"removeFromEnd cuts off the end. setDuration sets the length, keeping the start. startSeconds / endSeconds put the clip's edges at those timeline times (e.g. 0 and the composition length to cover the whole video; a video/audio clip's source shifts with its start edge). \" +\n \"ripple:true shifts later clips on the same track to close (or open) the gap.\",\n inputSchema: {\n type: \"object\",\n properties: {\n clipId: { type: \"string\" },\n removeFromStart: { type: \"number\", exclusiveMinimum: 0 },\n removeFromEnd: { type: \"number\", exclusiveMinimum: 0 },\n setDuration: { type: \"number\", exclusiveMinimum: 0 },\n startSeconds: { type: \"number\", minimum: 0, description: \"Timeline time for the clip's start edge.\" },\n endSeconds: { type: \"number\", exclusiveMinimum: 0, description: \"Timeline time for the clip's end edge.\" },\n ripple: { type: \"boolean\" },\n },\n required: [\"clipId\"],\n additionalProperties: false,\n },\n run(input, { project }) {\n const doc = project.getState().doc;\n const clip = doc.clips[String(input.clipId)];\n if (!clip) return { ok: false, error: `no clip \"${input.clipId}\"` };\n const head = toUs(input.removeFromStart) ?? 0;\n const tail = toUs(input.removeFromEnd) ?? 0;\n const ripple = input.ripple === true;\n const startAt = toUs(input.startSeconds);\n const endAt = toUs(input.endSeconds);\n if ((startAt !== undefined || endAt !== undefined) && (head || tail)) return { ok: false, error: \"use either startSeconds / endSeconds or removeFromStart / removeFromEnd, not both\" };\n let duration = clip.durationUs - head - tail;\n const set = toUs(input.setDuration);\n if (set) duration = set;\n // Without ripple, cutting the head keeps the clip's end in place; with ripple the clip keeps its start and later clips move up.\n let start = ripple ? clip.startUs : clip.startUs + head;\n let sourceShift = head;\n if (startAt !== undefined) {\n start = startAt;\n sourceShift = startAt - clip.startUs;\n if (endAt === undefined && !set) duration = clip.startUs + clip.durationUs - startAt;\n }\n if (endAt !== undefined) duration = endAt - start;\n if (duration <= 0) return { ok: false, error: `that leaves the clip ${seconds(duration)} long; it is ${seconds(clip.durationUs)} (${seconds(clip.startUs)}–${seconds(clip.startUs + clip.durationUs)})` };\n const trimmable = \"trimStartUs\" in clip;\n if (trimmable && (clip as { trimStartUs: number }).trimStartUs + sourceShift < 0) {\n return { ok: false, error: `${clip.id}'s media has only ${seconds((clip as { trimStartUs: number }).trimStartUs)} before its current start, so its start edge can move back at most that far` };\n }\n const commands: Command[] = [\n {\n type: \"clip/trim\",\n payload: {\n clipId: clip.id,\n startUs: start,\n durationUs: duration,\n ...(trimmable && sourceShift ? { trimStartUs: (clip as { trimStartUs: number }).trimStartUs + sourceShift } : {}),\n },\n },\n ];\n if (ripple) {\n const oldEnd = clip.startUs + clip.durationUs;\n const delta = start + duration - oldEnd;\n const later = Object.values(doc.clips)\n .filter((c) => c.trackId === clip.trackId && c.id !== clip.id && c.startUs >= oldEnd)\n .sort((x, y) => (delta < 0 ? x.startUs - y.startUs : y.startUs - x.startUs));\n for (const c of later) commands.push({ type: \"clip/move\", payload: { clipId: c.id, startUs: Math.max(0, c.startUs + delta) } });\n }\n const out = applyChecked(project, commands);\n if (!out.ok) return out;\n const after = project.getState().doc.clips[clip.id]!;\n return { ...out, changes: [`Trimmed ${clip.id} to ${seconds(after.startUs)}–${seconds(after.startUs + after.durationUs)}`] };\n },\n }),\n\n defineTool({\n name: \"set_effects_enabled\",\n description: \"Turn effects off (or back on) without deleting them, keeping their settings: one kind, or every effect when kind is omitted. To delete effects use remove_effects.\",\n inputSchema: {\n type: \"object\",\n properties: {\n clipIds: { type: \"array\", items: { type: \"string\" }, minItems: 1 },\n kind: { type: \"string\", description: \"Only effects of this kind (default: all).\" },\n enabled: { type: \"boolean\" },\n },\n required: [\"clipIds\", \"enabled\"],\n additionalProperties: false,\n },\n run(input, { project }) {\n const doc = project.getState().doc;\n const enabled = input.enabled === true;\n const commands: Command[] = [];\n for (const id of (input.clipIds as string[]) ?? []) {\n const clip = doc.clips[id];\n if (!clip) return { ok: false, error: `no clip \"${id}\"` };\n for (const e of clip.effects ?? []) {\n if ((!input.kind || e.kind === input.kind) && e.enabled !== enabled) commands.push({ type: \"effect/update\", payload: { clipId: id, effectId: e.id, enabled } });\n }\n }\n if (!commands.length) return { ok: true, result: { changed: 0, note: `no matching effects that are ${enabled ? \"off\" : \"on\"}` } };\n const out = applyChecked(project, commands);\n return out.ok ? { ...out, result: { changed: commands.length }, changes: [`Turned ${enabled ? \"on\" : \"off\"} ${commands.length} effect${commands.length > 1 ? \"s\" : \"\"}`] } : out;\n },\n }),\n\n defineTool({\n name: \"set_keyframes\",\n description:\n \"Animate one property of a clip so it CHANGES over time, with keyframes at TIMELINE seconds (the same times as get_state). Replaces that property's existing keyframes (including a preset's) unless keep:true. Between points the value moves smoothly; before the first and after the last it holds. \" +\n \"Properties: x, y (0–1, 0.5 = center), scale (1 = full size), rotation (degrees; 360 = one full turn, so a static 360 looks like 0), opacity (0–1), volume (0–1, audio/video). \" +\n \"Not for constant values (\\\"rotate 45°\\\", \\\"music at 30%\\\", \\\"louder\\\": set those with clip/set-property) or for removing an animation (keyframe/clear). Use it for motion and timed changes the presets don't cover: a spin (rotation 0 → 360 over the clip), a pan, ducking music (volume: normal → low just before a range, low → normal just after), or fading audio between two times.\",\n inputSchema: {\n type: \"object\",\n properties: {\n clipId: { type: \"string\" },\n property: { type: \"string\", enum: [\"x\", \"y\", \"scale\", \"rotation\", \"opacity\", \"volume\"] },\n points: {\n type: \"array\",\n minItems: 1,\n items: {\n type: \"object\",\n properties: { atSeconds: { type: \"number\", minimum: 0 }, value: { type: \"number\" } },\n required: [\"atSeconds\", \"value\"],\n additionalProperties: false,\n },\n },\n easing: { type: \"string\", enum: [\"linear\", \"easeIn\", \"easeOut\", \"easeInOut\", \"hold\"], description: \"Curve between points (default linear).\" },\n keep: { type: \"boolean\", description: \"Keep the property's other keyframes (default false: replace them).\" },\n },\n required: [\"clipId\", \"property\", \"points\"],\n additionalProperties: false,\n },\n run(input, { project }) {\n const clip = project.getState().doc.clips[String(input.clipId)];\n if (!clip) return { ok: false, error: `no clip \"${input.clipId}\"` };\n const property = String(input.property);\n if (property === \"volume\" && !(\"volume\" in clip)) return { ok: false, error: `${clip.id} is a ${clip.kind} clip and has no volume` };\n if (property !== \"volume\" && clip.kind === \"audio\") return { ok: false, error: \"audio clips only animate volume\" };\n const end = clip.startUs + clip.durationUs;\n const points = (input.points as { atSeconds: number; value: number }[]).map((p) => ({ atUs: toUs(p.atSeconds)!, value: p.value }));\n const outside = points.filter((p) => p.atUs < clip.startUs - 1000 || p.atUs > end + 1000);\n if (outside.length) return { ok: false, error: `${clip.id} runs ${seconds(clip.startUs)}–${seconds(end)}; points must be inside that range (got ${outside.map((p) => seconds(p.atUs)).join(\", \")})` };\n if (input.keep !== true && new Set(points.map((p) => p.value)).size < 2) {\n const how = property === \"volume\" ? \"clip/set-property { volume }\" : `clip/set-property { transform: { ${property} } }`;\n return {\n ok: false,\n error: `Keyframes are for a value that CHANGES over time; ${points.length === 1 ? \"one point\" : \"these points\"} hold${points.length === 1 ? \"s\" : \"\"} a single value. For a constant ${property}, set it directly with apply_commands ${how}. To remove an animation, apply keyframe/clear { clipId, property }. Nothing was changed.`,\n };\n }\n const commands: Command[] = [];\n if (input.keep !== true && (clip.animations?.[property as \"x\"]?.length ?? 0) > 0) commands.push({ type: \"keyframe/clear\", payload: { clipId: clip.id, property } });\n const easing = typeof input.easing === \"string\" ? input.easing : \"linear\";\n for (const p of points) {\n commands.push({ type: \"keyframe/set\", payload: { clipId: clip.id, property, timeUs: Math.min(clip.durationUs, Math.max(0, p.atUs - clip.startUs)), value: p.value, easing } });\n }\n const out = applyChecked(project, commands);\n if (!out.ok) return out;\n const list = points.map((p) => `${seconds(p.atUs)} ${n3(p.value)}`).join(\" → \");\n return { ...out, changes: [`${clip.id}: ${property} ${list}`] };\n },\n }),\n\n defineTool({\n name: \"set_background\",\n description:\n \"A solid color background behind everything (an html clip on a Background track at the very bottom). Adds one, recolors or re-times the existing one, or removes it with color \\\"none\\\". \" +\n \"Covers the whole video unless fromSeconds / toSeconds say otherwise. Only visible where nothing opaque is on top (behind scaled-down, faded or transparent clips, or where there's no footage).\",\n inputSchema: {\n type: \"object\",\n properties: {\n color: { type: \"string\", description: 'CSS color, e.g. \"#1E3A8A\", or \"none\" to remove the background.' },\n fromSeconds: { type: \"number\", minimum: 0 },\n toSeconds: { type: \"number\", exclusiveMinimum: 0 },\n },\n required: [\"color\"],\n additionalProperties: false,\n },\n run(input, { project }) {\n const doc = project.getState().doc;\n const color = String(input.color).trim();\n const existing = Object.values(doc.clips).find(\n (c) => c.kind === \"html\" && ((c as { template?: string }).template === BACKGROUND_TEMPLATE || doc.tracks[c.trackId]?.name === \"Background\"),\n );\n if (color.toLowerCase() === \"none\") {\n if (!existing) return { ok: true, result: { note: \"there is no background to remove\" } };\n const commands: Command[] = [{ type: \"clip/remove\", payload: { clipId: existing.id } }];\n if (!Object.values(doc.clips).some((c) => c.trackId === existing.trackId && c.id !== existing.id)) commands.push({ type: \"track/remove\", payload: { id: existing.trackId } });\n const out = applyChecked(project, commands);\n return out.ok ? { ...out, changes: [\"Removed the background\"] } : out;\n }\n let endUs = 0;\n for (const c of Object.values(doc.clips)) if (c.id !== existing?.id) endUs = Math.max(endUs, c.startUs + c.durationUs);\n const startUs = toUs(input.fromSeconds) ?? (existing && input.toSeconds === undefined ? existing.startUs : 0);\n const stopUs = toUs(input.toSeconds) ?? (existing && input.fromSeconds === undefined ? existing.startUs + existing.durationUs : endUs || 10 * S);\n if (stopUs <= startUs) return { ok: false, error: `toSeconds must be after fromSeconds (got ${seconds(startUs)}–${seconds(stopUs)})` };\n const commands: Command[] = [];\n if (existing) {\n const params = existing as { template: string; params: Record<string, unknown> };\n const key = htmlPlaceholders(params.template).find((k) => /colou?r|background|bg/i.test(k)) ?? \"color\";\n if (params.params[key] !== color) commands.push({ type: \"clip/set-property\", payload: { clipId: existing.id, params: { [key]: color } } });\n if (existing.startUs !== startUs || existing.durationUs !== stopUs - startUs) commands.push({ type: \"clip/trim\", payload: { clipId: existing.id, startUs, durationUs: stopUs - startUs } });\n if (!commands.length) return { ok: true, result: { note: \"the background already looks like that\" } };\n } else {\n const trackId = doc.tracks.background ? `background-${Object.keys(doc.tracks).length}` : \"background\";\n commands.push({ type: \"track/add\", payload: { id: trackId, kind: \"video\", name: \"Background\", index: 0 } });\n commands.push({ type: \"clip/add\", payload: { kind: \"html\", id: `${trackId}-clip`, trackId, startUs, durationUs: stopUs - startUs, template: BACKGROUND_TEMPLATE, params: { color } } });\n }\n const out = applyChecked(project, commands);\n if (!out.ok) return out;\n return { ...out, changes: [`${existing ? \"Changed the background to\" : \"Added a background:\"} ${color}, ${seconds(startUs)}–${seconds(stopUs)}`] };\n },\n }),\n\n defineTool({\n name: \"close_gaps\",\n description: \"Close the empty gaps on a track so its clips run back to back, keeping their order (the first clip stays where it is, or starts at 0 with fromZero).\",\n inputSchema: {\n type: \"object\",\n properties: { trackId: { type: \"string\" }, fromZero: { type: \"boolean\" } },\n required: [\"trackId\"],\n additionalProperties: false,\n },\n run(input, { project }) {\n const doc = project.getState().doc;\n if (!doc.tracks[String(input.trackId)]) return { ok: false, error: `no track \"${input.trackId}\"` };\n const clips = Object.values(doc.clips)\n .filter((c) => c.trackId === input.trackId)\n .sort((a, b) => a.startUs - b.startUs);\n let cursor = input.fromZero === true ? 0 : clips[0]?.startUs ?? 0;\n const commands: Command[] = [];\n for (const c of clips) {\n if (c.startUs !== cursor) commands.push({ type: \"clip/move\", payload: { clipId: c.id, startUs: cursor } });\n cursor += c.durationUs;\n }\n if (!commands.length) return { ok: true, result: { moved: 0, note: \"no gaps on that track\" } };\n const out = applyChecked(project, commands);\n return out.ok ? { ...out, changes: [`Closed ${commands.length} gap${commands.length > 1 ? \"s\" : \"\"} on ${input.trackId}`] } : out;\n },\n }),\n ];\n}\n\n/* ---------- apply_commands, checked ---------- */\n\nclass Abort extends Error {}\n\n/**\n * Apply a batch as one all-or-nothing step, with two checks core doesn't do:\n * fields a command would silently ignore are errors, and commands that\n * changed nothing are reported, so the model never claims an edit that didn't\n * happen.\n */\nexport function applyChecked(project: Project, commands: Command[]): ToolOutput {\n const catalog = project.commandCatalog();\n const nothing = commands.length > 1 ? \"Nothing in this batch was applied (not even the commands before it): resend the whole corrected batch.\" : \"Nothing was applied.\";\n const templates = new Map<string, string>();\n for (const [index, command] of commands.entries()) {\n if (!catalog[command?.type]) {\n const looksLikeTool = /^[a-z]+(_[a-z]+)+$/.test(String(command?.type));\n return {\n ok: false,\n error: {\n failedIndex: index,\n kind: \"unknown-command\",\n commandType: command?.type,\n error: `\"${command?.type}\" is not a command${looksLikeTool ? \": it is a tool. Call it as its own tool call, not inside apply_commands\" : \"\"}. ${nothing}`,\n validTypes: Object.keys(catalog).sort(),\n },\n };\n }\n const htmlError = checkHtmlParams(project, command, templates);\n if (htmlError) return { ok: false, error: { failedIndex: index, commandType: command.type, error: `${htmlError} ${nothing}` } };\n const extra = unknownFields(catalog[command?.type], command?.payload);\n if (extra) {\n return {\n ok: false,\n error: {\n failedIndex: index,\n commandType: command.type,\n error: `unknown field${extra.unknown.length > 1 ? \"s\" : \"\"} ${extra.unknown.map((f) => `\"${f}\"`).join(\", \")}: ${command.type} would ignore ${extra.unknown.length > 1 ? \"them\" : \"it\"}. Its fields are: ${extra.allowed.join(\", \")}. ${nothing}`,\n },\n };\n }\n }\n const unchanged: number[] = [];\n let failure: unknown;\n try {\n project.transaction(() => {\n commands.forEach((command, index) => {\n const before = project.getState().doc;\n const result = tryDispatch(project, command);\n if (!result.ok) {\n const hint = rejectionHint(result.error as { code?: string; commandType?: string }, command);\n const skipped = commands.length > 1 ? { notApplied: describeCommands(commands.filter((_, i) => i !== index)) } : {};\n failure = { failedIndex: index, ...result.error, ...(hint ? { hint } : {}), note: nothing, ...skipped };\n throw new Abort();\n }\n if (project.getState().doc === before) unchanged.push(index);\n });\n });\n } catch (err) {\n if (err instanceof Abort) return { ok: false, error: failure };\n throw err;\n }\n const changed = commands.filter((_, i) => !unchanged.includes(i));\n return {\n ok: true,\n result: {\n applied: changed.length,\n ...(unchanged.length\n ? { unchanged: unchanged.map((i) => ({ index: i, type: commands[i]!.type, note: \"changed nothing: the project already had these values. Check the field names and values.\" })) }\n : {}),\n },\n ...(changed.length ? { changes: describeCommands(changed) } : {}),\n };\n}\n\n/** html clips substitute `{{name}}` placeholders: params with any other name change nothing on screen. */\nfunction checkHtmlParams(project: Project, command: Command, templates: Map<string, string>): string | undefined {\n const p = (command.payload ?? {}) as Record<string, unknown>;\n let template: string | undefined;\n if (command.type === \"clip/add\" && p.kind === \"html\" && typeof p.template === \"string\") {\n template = p.template;\n if (typeof p.id === \"string\") templates.set(p.id, template);\n } else if (command.type === \"clip/set-property\" && p.params && typeof p.params === \"object\") {\n const clip = project.getState().doc.clips[String(p.clipId)] as { kind?: string; template?: string } | undefined;\n template = templates.get(String(p.clipId)) ?? (clip?.kind === \"html\" ? clip.template : undefined);\n }\n if (template === undefined || !p.params || typeof p.params !== \"object\") return undefined;\n const names = htmlPlaceholders(template);\n const unknown = Object.keys(p.params).filter((k) => !names.includes(k));\n if (!unknown.length) return undefined;\n return `html params ${unknown.map((k) => `\"${k}\"`).join(\", \")} match no {{placeholder}} in the template, so they would change nothing. Its placeholders: ${names.join(\", \") || \"none\"}.`;\n}\n\n/** What to do instead, for rejections models commonly hit. */\nfunction rejectionHint(error: { code?: string }, _command: Command): string | undefined {\n switch (error.code) {\n case \"no-audio\":\n return \"fadeInUs / fadeOutUs fade audio only. To fade a text, image or video picture in or out, call animate_clip (in / out: fade).\";\n case \"asset-not-found\":\n return \"Clips can only use assets already in the project (see get_state). Audio found with search_audio is added with add_audio, which imports it.\";\n case \"not-caption\":\n case \"not-html\":\n case \"not-text\":\n return \"That field belongs to another clip kind: text clips take color / font fields, caption clips take style, html clips take params (named by the template's {{placeholders}}, shown in get_state).\";\n default:\n return undefined;\n }\n}\n\n/** Why two clips have no cut between them, so the model reports it instead of picking another cut. */\nfunction noCutReason(project: Project, fromId: unknown, toId: unknown): string {\n const doc = project.getState().doc;\n const a = typeof fromId === \"string\" ? doc.clips[fromId] : undefined;\n const b = typeof toId === \"string\" ? doc.clips[toId] : undefined;\n const tail = \"Nothing was added. Don't substitute a different cut: tell the user, and offer an animation (animate_clip) instead if that fits.\";\n for (const [given, clip] of [[fromId, a], [toId, b]] as const) if (typeof given === \"string\" && !clip) return `There is no clip \"${given}\". ${tail}`;\n if (a && b && a.trackId !== b.trackId) return `${a.id} is on track ${a.trackId} and ${b.id} on track ${b.trackId}: a transition joins two clips that touch on the SAME track, so these two have no cut between them. ${tail}`;\n if (a && b) return `${a.id} and ${b.id} don't touch (${a.id} ends at ${seconds(a.startUs + a.durationUs)}, ${b.id} starts at ${seconds(b.startUs)}). ${tail}`;\n return `No cut ${a ? `starts after ${a.id}` : `leads into ${b!.id}`} (it isn't directly after/before another clip on its track). ${tail}`;\n}\n\nfunction cutInfo(c: Cut) {\n return { fromClipId: c.fromClipId, toClipId: c.toClipId, atSeconds: c.atUs / S, maxSeconds: c.maxTransitionUs / S, ...(c.transition ? { transition: c.transition.kind } : {}) };\n}\n\n/** apply_commands' input schema: `type` is an enum of the catalog's commands. */\nexport function applyCommandsSchema(catalog: Record<string, unknown>): Record<string, unknown> {\n const [dispatch] = toToolDefinitions(catalog, { mode: \"dispatch\" }) as { input_schema: Record<string, unknown> }[];\n return {\n type: \"object\",\n properties: {\n commands: { type: \"array\", minItems: 1, items: dispatch!.input_schema, description: \"Commands applied in order, as one batch.\" },\n },\n required: [\"commands\"],\n additionalProperties: false,\n };\n}\n","import { createProject, type Command, type Project } from \"@miraiclip/core\";\n\n/**\n * A working copy of a project that records every command that sticks.\n *\n * The assistant edits the copy while the model thinks (async, many steps);\n * the real project only changes when the turn commits, by replaying the\n * recorded commands in one transaction — one undo step, and nothing half-done\n * if the request fails. Commands inside a transaction that rolls back are\n * dropped, exactly as the copy drops their effects.\n *\n * Replay must produce the same ids the model saw, so commands whose ids are\n * optional get explicit ones before they run.\n */\nexport interface RecordingProject extends Project {\n /** Commands that changed the copy, in order. */\n readonly recorded: readonly Command[];\n}\n\nexport interface ForkOptions {\n /** Build the copy (default: `createProject(doc)`). Register custom commands here. */\n createFork?: (doc: ReturnType<Project[\"toJSON\"]>) => Project;\n /** Give commands explicit ids (default handles the built-ins with optional ids). */\n ensureIds?: (command: Command) => Command;\n}\n\nlet seq = 0;\nconst genId = (prefix: string) => `${prefix}-${Date.now().toString(36)}${(++seq).toString(36)}${Math.random().toString(36).slice(2, 6)}`;\n\n/** Built-in commands that would otherwise get a random id inside the engine. */\nexport function withExplicitIds(command: Command): Command {\n const p = (command.payload ?? {}) as Record<string, unknown>;\n const set = (field: string, prefix: string) =>\n p[field] === undefined ? { ...command, payload: { ...p, [field]: genId(prefix) } } : command;\n switch (command.type) {\n case \"clip/split\":\n case \"clip/duplicate\":\n return set(\"newClipId\", \"clip\");\n case \"effect/add\":\n return set(\"effectId\", \"fx\");\n case \"transition/add\":\n return set(\"id\", \"tr\");\n default:\n return command;\n }\n}\n\nexport function forkProject(project: Project, options: ForkOptions = {}): RecordingProject {\n const fork = (options.createFork ?? ((doc) => createProject(doc)))(project.toJSON());\n const state = project.getState();\n fork.setPlayhead(state.playheadUs);\n fork.setSelection([...state.selection]);\n const ensure = options.ensureIds ?? withExplicitIds;\n const recorded: Command[] = [];\n let depth = 0;\n let buffer: Command[] = [];\n\n return {\n ...fork,\n get recorded() {\n return recorded;\n },\n dispatch(command) {\n const explicit = ensure(command as Command);\n const before = fork.getState().doc;\n fork.dispatch(explicit);\n if (fork.getState().doc === before) return; // no-op: nothing to replay\n if (depth > 0) buffer.push(explicit);\n else recorded.push(explicit);\n },\n transaction(fn, label) {\n if (depth > 0) {\n fn();\n return;\n }\n depth = 1;\n buffer = [];\n try {\n fork.transaction(fn, label);\n recorded.push(...buffer);\n } finally {\n depth = 0;\n buffer = [];\n }\n },\n };\n}\n\nexport type CommitResult = { ok: true; applied: number } | { ok: false; error: string };\n\n/** Replay a fork's commands onto the real project as one undoable step. */\nexport function commitFork(project: Project, fork: RecordingProject, label?: string): CommitResult {\n if (fork.recorded.length === 0) return { ok: true, applied: 0 };\n try {\n project.transaction(() => {\n for (const command of fork.recorded) project.dispatch(command);\n }, label);\n return { ok: true, applied: fork.recorded.length };\n } catch (err) {\n return {\n ok: false,\n error: `The project changed while the assistant was working, so its edit no longer applies (${(err as Error)?.message ?? err}). Ask again.`,\n };\n }\n}\n","import type { Command, Project, ProjectDocument } from \"@miraiclip/core\";\nimport { describeForAssistant } from \"./state.js\";\nimport { commitFork, forkProject, type CommitResult, type ForkOptions } from \"./fork.js\";\nimport { editorTools, type AssistantTool } from \"./tools.js\";\nimport type { ChatMessage, ChatModel, ChatUsage, ToolCall } from \"./types.js\";\n\nexport interface AssistantOptions {\n model: ChatModel;\n /** Default `editorTools()`. Add your own (audio, captions, stock media…). */\n tools?: AssistantTool[];\n /** Extra system instructions (brand voice, house rules). */\n instructions?: string;\n /** Model round-trips per request (default 12). */\n maxSteps?: number;\n /** Model id when the adapter serves several. */\n modelId?: string;\n maxOutputTokens?: number;\n /** Vendor request fields, passed through. */\n params?: Record<string, unknown>;\n fork?: ForkOptions;\n}\n\nexport type AssistantEvent =\n | { type: \"text\"; delta: string }\n | { type: \"tool-start\"; call: ToolCall }\n | { type: \"tool-end\"; call: ToolCall; ok: boolean; changes?: string[]; error?: string }\n | { type: \"step\"; index: number };\n\nexport interface TurnOptions {\n /** Earlier messages of this conversation (`turn.messages` from the last turn). */\n history?: readonly ChatMessage[];\n /** What the user is looking at, e.g. \"Selected: clip t-1. Playhead: 4.2s.\" */\n context?: string;\n /** \"auto\" (default) commits when the request finishes; \"review\" waits for `turn.apply()`. */\n apply?: \"auto\" | \"review\";\n signal?: AbortSignal;\n onEvent?: (event: AssistantEvent) => void;\n /** History label for the undo step (default \"Assistant: <request>\"). */\n label?: string;\n}\n\nexport interface AssistantTurn {\n /** The model's final answer to the user. */\n reply: string;\n /** Human-readable list of what the turn changed (empty when nothing). */\n changes: string[];\n /** The commands the turn makes, in order (replayed onto the project on apply). */\n commands: readonly Command[];\n /**\n * applied: committed as one undo step. review: waiting for apply()/discard().\n * no-changes: nothing to apply. failed / canceled: the project wasn't touched.\n */\n status: \"applied\" | \"review\" | \"no-changes\" | \"failed\" | \"canceled\";\n error?: string;\n /** The project as the turn left it (for previews in review mode). */\n preview: ProjectDocument;\n /** The conversation so far, without the system prompt: pass back as `history`. */\n messages: ChatMessage[];\n usage: Required<ChatUsage>;\n /** Commit a reviewed turn (no-op once applied). */\n apply(): CommitResult;\n discard(): void;\n}\n\nexport interface Assistant {\n readonly tools: readonly AssistantTool[];\n run(project: Project, request: string, options?: TurnOptions): Promise<AssistantTurn>;\n}\n\nconst MAX_TOOL_RESULT = 20_000;\nconst MAX_OLD_TOOL_RESULT = 2_000;\n\nexport const DEFAULT_INSTRUCTIONS = `You are the editing assistant inside a video editor. You change the user's project by calling tools; every change you make lands as ONE undo step when you finish, so act confidently.\n\nHow to work:\n- The current project is summarized below. Clip, track and asset ids there are what tools expect. Call get_state to re-read it after big changes.\n- Prefer the high-level tools when they fit (add_transition, animate_clip, add_effect, set_effects_enabled, set_background, trim_clip, set_keyframes, close_gaps, and any audio or media tools). Call tools directly; apply_commands takes only editing commands like \"clip/add\", never tool names. Use apply_commands for everything else, batching related commands; look up a command's payload with get_command_schema the first time you use it, and use only the fields it lists.\n- Fading a picture (text, image, logo, video) in or out is animate_clip (in / out: fade). fadeInUs / fadeOutUs are audio fades. \"Fade the video in at the start / out at the end\" means the first clip's entrance and the last clip's exit.\n- Constant values are set directly with clip/set-property: rotation (\"rotate 45°\", \"tilt\"), volume (\"lower to 30%\", \"louder\"), position, size, opacity. Only values that CHANGE over a time range (\"duck the music between 10 and 15 s\", \"fade to silence between 18 and 20 s\", \"spin one full turn\", \"pan across\") are keyframes: set_keyframes, with times in timeline seconds. Remove an animation with keyframe/clear.\n- \"Cuts\" are the ones listed in the project summary; use their times (e.g. one sound per cut).\n- \"Add N more seconds of footage\" adds a new clip (clip/add with trimStartUs for the source point) after the last one; it does not stretch an existing clip.\n- Caption looks (box behind the text, outline, shadow, colors, word preset) are the caption clip's style (clip/set-property { style }).\n- \"The whole video\" means from 0 to the composition length (trim_clip startSeconds / endSeconds).\n- A solid color background behind everything is set_background (add, recolor, remove). Other html clips are recolored through the params their template uses (shown in the clip details). A new track goes on top unless track/add gets index 0 (the bottom).\n- \"Replace X with Y\" removes X and adds Y in the same turn, whichever tool adds Y (audio tools take replaceClipId, which does both).\n- Solo / mute / lock / hide a track with track/set-property (solo, muted, locked, hidden). \"Solo X\" is solo: true on X, not muting the others.\n- Relative requests (\"bigger\", \"louder\", \"a bit brighter\", \"twice as big\") start from the current values in the clip details below: make a visible change in the asked direction (about 25% for \"bigger\"/\"smaller\", ×2 for \"twice\").\n- When something is selected, \"this\", \"it\", \"the clip\" and any request that names no target mean ONLY the selection, even with words like \"at the end\" (the end of that clip). Edit nothing else. \"Here\" / \"now\" / \"this point\" mean the playhead. \"Clips\" without a kind mean the video clips on the main video track.\n- \"Again\" / \"also\" / \"another\" mean keep the original and add a copy (clip/duplicate, or clip/add with the same asset); never move the original. \"Instead\" means move or replace.\n- Removing a clip leaves a gap; when asked to close it, also run close_gaps (or trim_clip with ripple).\n- Commands use integer microseconds (1 second = 1000000). High-level tools take seconds.\n- Positions (transform x, y) are fractions of the frame: 0.5, 0.5 is the center.\n- Only reference media that exists in the project or that a tool returned. Never invent file URLs.\n- If a tool fails, read the error, fix the input and retry. A failed apply_commands batch applied NONE of its commands: resend all of them, corrected. If something can't be done, say why.\n- If the request is ambiguous in a way that matters, ask one short question instead of guessing.\n\nWhen you're done, reply in one to three short sentences: what you changed, in plain words (times in seconds, names over ids). Describe only edits your tools reported as applied; if a tool says something changed nothing or failed, fix it or say so. No markdown headings.`;\n\nexport function createAssistant(options: AssistantOptions): Assistant {\n const tools = options.tools ?? editorTools();\n const byName = new Map(tools.map((t) => [t.name, t]));\n const specs = tools.map(({ name, description, inputSchema }) => ({ name, description, inputSchema }));\n const maxSteps = options.maxSteps ?? 12;\n\n return {\n tools,\n async run(project, request, turn = {}) {\n const fork = forkProject(project, options.fork);\n const emit = turn.onEvent ?? (() => {});\n const usage = { inputTokens: 0, outputTokens: 0 };\n const changes: string[] = [];\n const history = trimHistory(turn.history ?? []);\n const added: ChatMessage[] = [{ role: \"user\", content: request }];\n const system = [\n DEFAULT_INSTRUCTIONS,\n options.instructions?.trim(),\n `Current project:\\n${describeForAssistant(fork.toJSON())}`,\n turn.context?.trim() ? `What the user is looking at: ${turn.context.trim()}` : undefined,\n selectionNote(fork.toJSON(), fork.getState().selection),\n ]\n .filter(Boolean)\n .join(\"\\n\\n\");\n\n let reply = \"\";\n let status: AssistantTurn[\"status\"] = \"no-changes\";\n let error: string | undefined;\n\n try {\n for (let step = 0; ; step++) {\n if (step >= maxSteps) {\n reply = (reply ? `${reply}\\n\\n` : \"\") + `I stopped after ${maxSteps} steps; what I finished is applied. Ask me to continue if there's more.`;\n break;\n }\n emit({ type: \"step\", index: step });\n const response = await options.model.complete(\n {\n messages: [{ role: \"system\", content: system }, ...history, ...added],\n tools: specs,\n ...(options.modelId ? { model: options.modelId } : {}),\n ...(options.maxOutputTokens ? { maxOutputTokens: options.maxOutputTokens } : {}),\n ...(options.params ? { params: options.params } : {}),\n },\n { ...(turn.signal ? { signal: turn.signal } : {}), onText: (delta) => emit({ type: \"text\", delta }) },\n );\n usage.inputTokens += response.usage?.inputTokens ?? 0;\n usage.outputTokens += response.usage?.outputTokens ?? 0;\n added.push({ role: \"assistant\", content: response.content, ...(response.toolCalls.length ? { toolCalls: response.toolCalls } : {}) });\n if (response.toolCalls.length === 0) {\n reply = response.content.trim();\n break;\n }\n for (const call of response.toolCalls) {\n if (turn.signal?.aborted) throw abortError();\n emit({ type: \"tool-start\", call });\n const out = await runTool(byName.get(call.name), call, fork, turn.signal);\n if (out.ok && out.changes?.length) changes.push(...out.changes);\n emit({\n type: \"tool-end\",\n call,\n ok: out.ok,\n ...(out.ok && out.changes?.length ? { changes: out.changes } : {}),\n ...(!out.ok ? { error: short(out.error) } : {}),\n });\n added.push({ role: \"tool\", toolCallId: call.id, name: call.name, content: cap(out.ok ? out.result ?? { ok: true } : { ok: false, error: out.error }, MAX_TOOL_RESULT) });\n }\n }\n status = fork.recorded.length === 0 ? \"no-changes\" : turn.apply === \"review\" ? \"review\" : \"applied\";\n } catch (err) {\n status = turn.signal?.aborted || (err as Error)?.name === \"AbortError\" ? \"canceled\" : \"failed\";\n error = (err as Error)?.message ?? String(err);\n }\n\n let committed = false;\n let discarded = false;\n const label = turn.label ?? `Assistant: ${request.length > 40 ? `${request.slice(0, 39)}…` : request}`;\n const apply = (): CommitResult => {\n if (committed) return { ok: true, applied: 0 };\n if (discarded) return { ok: false, error: \"This edit was discarded.\" };\n if (status === \"failed\" || status === \"canceled\") return { ok: false, error: error ?? status };\n const result = commitFork(project, fork, label);\n if (result.ok) committed = true;\n return result;\n };\n if (status === \"applied\") {\n const result = apply();\n if (!result.ok) {\n status = \"failed\";\n error = result.error;\n }\n }\n\n return {\n reply,\n changes,\n commands: fork.recorded,\n status,\n ...(error !== undefined ? { error } : {}),\n preview: fork.toJSON(),\n messages: [...history, ...added],\n usage,\n apply,\n discard() {\n if (!committed) discarded = true;\n },\n };\n },\n };\n}\n\nasync function runTool(tool: AssistantTool | undefined, call: ToolCall, project: Project, signal?: AbortSignal) {\n if (!tool) return { ok: false as const, error: `unknown tool \"${call.name}\"` };\n if (\"__invalidArguments\" in call.arguments) return { ok: false as const, error: \"the tool arguments weren't valid JSON; send a JSON object\" };\n try {\n return await tool.run(call.arguments, { project, ...(signal ? { signal } : {}) });\n } catch (err) {\n if ((err as Error)?.name === \"AbortError\") throw err;\n return { ok: false as const, error: (err as Error)?.message ?? String(err) };\n }\n}\n\nfunction cap(value: unknown, max: number): string {\n const text = typeof value === \"string\" ? value : JSON.stringify(value);\n return text.length > max ? `${text.slice(0, max)}… [truncated ${text.length - max} chars]` : text;\n}\n\nconst short = (e: unknown) => {\n const text = typeof e === \"string\" ? e : JSON.stringify(e);\n return text.length > 300 ? `${text.slice(0, 299)}…` : text;\n};\n\n/**\n * Earlier turns keep their shape but not their bulky tool output. A turn cut\n * short (canceled, failed) can leave tool calls without results, which\n * vendors reject: those get a \"not run\" result.\n */\nfunction trimHistory(history: readonly ChatMessage[]): ChatMessage[] {\n const answered = new Set(history.flatMap((m) => (m.role === \"tool\" ? [m.toolCallId] : [])));\n const out: ChatMessage[] = [];\n for (const m of history) {\n if (m.role === \"system\") continue;\n if (m.role === \"tool\") {\n out.push(m.content.length > MAX_OLD_TOOL_RESULT ? { ...m, content: `${m.content.slice(0, MAX_OLD_TOOL_RESULT)}… [truncated]` } : m);\n continue;\n }\n out.push(m);\n if (m.role === \"assistant\") {\n for (const call of m.toolCalls ?? []) {\n if (!answered.has(call.id)) out.push({ role: \"tool\", toolCallId: call.id, name: call.name, content: '{\"ok\":false,\"error\":\"not run: the request was interrupted\"}' });\n }\n }\n }\n return out;\n}\n\nfunction abortError() {\n const err = new Error(\"canceled\");\n err.name = \"AbortError\";\n return err;\n}\n\n/** The selection, stated as a scope rule right before the request (models drift to \"all clips\" otherwise). */\nfunction selectionNote(doc: ProjectDocument, selection: readonly string[]): string | undefined {\n const clips = selection.map((id) => doc.clips[id]).filter((c): c is NonNullable<typeof c> => !!c);\n if (!clips.length) return undefined;\n const list = clips.map((c) => `${c.id} (${c.kind}, ${Number((c.startUs / 1e6).toFixed(2))}–${Number(((c.startUs + c.durationUs) / 1e6).toFixed(2))}s)`).join(\", \");\n return `Selected: ${list}. In this request, \"this\", \"it\", \"the clip\" and anything that names no target mean ONLY ${clips.map((c) => c.id).join(\", \")}. Leave every other clip alone.`;\n}\n","import { defineChatModel, type ChatModel, type ChatRequest, type ChatResponse, type ToolCall } from \"./types.js\";\n\nexport type ScriptStep =\n | string\n | { text?: string; toolCalls?: (Omit<ToolCall, \"id\"> & { id?: string })[] }\n | ((request: ChatRequest, index: number) => ScriptStep);\n\n/**\n * A model that answers from a script: for tests, demos and offline\n * development. Each `complete` call takes the next step; a string is a final\n * text answer, an object can call tools. Requests are kept in `requests`.\n */\nexport function scriptedChatModel(steps: ScriptStep[], options: { id?: string } = {}): ChatModel & { requests: ChatRequest[] } {\n const requests: ChatRequest[] = [];\n let i = 0;\n const model = defineChatModel({\n id: options.id ?? \"scripted\",\n label: \"Scripted model\",\n async complete(request: ChatRequest, opts: { onText?: (delta: string) => void } = {}): Promise<ChatResponse> {\n requests.push(structuredClone(request));\n let step: ScriptStep = steps[Math.min(i, steps.length - 1)] ?? \"Done.\";\n while (typeof step === \"function\") step = step(request, i);\n i++;\n const s = typeof step === \"string\" ? { text: step } : step;\n if (s.text) opts.onText?.(s.text);\n const toolCalls = (s.toolCalls ?? []).map((c, n) => ({ id: c.id ?? `call_${i}_${n}`, name: c.name, arguments: c.arguments }));\n return { content: s.text ?? \"\", toolCalls, finish: toolCalls.length ? \"tool_calls\" : \"stop\", usage: { inputTokens: 10, outputTokens: 5 } };\n },\n });\n return Object.assign(model, { requests });\n}\n"],"mappings":"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","names":["json","n","describeAnimation","findCuts","findCuts","describeAnimation","commands","out","usage","n"]}
1
+ {"version":3,"sources":["../src/types.ts","../src/wire.ts","../src/openai.ts","../src/anthropic.ts","../src/gemini.ts","../src/providers.ts","../src/http.ts","../src/limits.ts","../src/state.ts","../src/strict.ts","../src/markup.ts","../src/tools.ts","../src/fork.ts","../src/skills.ts","../src/run.ts","../src/skills.generated.ts","../src/testing.ts"],"sourcesContent":["/**\r\n * The vendor-neutral model contract. An adapter turns these neutral messages\r\n * and tools into one vendor's API and back; nothing else in the package knows\r\n * which vendor is on the other end.\r\n */\r\n\r\n/** A tool call the model made. `arguments` is already parsed JSON. */\r\nexport interface ToolCall {\r\n id: string;\r\n name: string;\r\n arguments: Record<string, unknown>;\r\n /**\r\n * Opaque data the adapter that produced the call needs back when the\r\n * conversation is replayed (Gemini thought signatures, Anthropic thinking\r\n * blocks). Keep it with the call; other adapters ignore it.\r\n */\r\n meta?: Record<string, unknown>;\r\n}\r\n\r\n/** An image the model looks at (a rendered preview a tool returns): base64 bytes and their type. */\r\nexport interface ChatImage {\r\n mimeType: string;\r\n /** Base64, no `data:` prefix. */\r\n data: string;\r\n}\r\n\r\nexport type ChatMessage =\r\n | { role: \"system\"; content: string }\r\n | { role: \"user\"; content: string }\r\n | { role: \"assistant\"; content: string; toolCalls?: ToolCall[] }\r\n /** `images`: pictures that come with the result (e.g. rendered frames), for models that can see. */\r\n | { role: \"tool\"; toolCallId: string; name: string; content: string; images?: ChatImage[] };\r\n\r\n/** A tool as the model sees it: name, description and a JSON Schema for its input. */\r\nexport interface ToolSpec {\r\n name: string;\r\n description: string;\r\n inputSchema: Record<string, unknown>;\r\n}\r\n\r\nexport interface ChatRequest {\r\n messages: ChatMessage[];\r\n tools?: ToolSpec[];\r\n /** Model id, when the adapter serves several. */\r\n model?: string;\r\n maxOutputTokens?: number;\r\n /** Vendor-specific request fields, passed through untouched (e.g. `{ reasoning_effort: \"low\" }`). */\r\n params?: Record<string, unknown>;\r\n}\r\n\r\nexport interface ChatUsage {\r\n inputTokens?: number;\r\n outputTokens?: number;\r\n}\r\n\r\nexport interface ChatResponse {\r\n content: string;\r\n toolCalls: ToolCall[];\r\n /** Why the model stopped. */\r\n finish: \"stop\" | \"tool_calls\" | \"length\" | \"content_filter\" | \"other\";\r\n model?: string;\r\n usage?: ChatUsage;\r\n}\r\n\r\nexport interface ChatOptions {\r\n signal?: AbortSignal;\r\n /** Streaming text, as it arrives (adapters that can't stream call it once). */\r\n onText?: (delta: string) => void;\r\n}\r\n\r\nexport interface ModelEntry {\r\n id: string;\r\n label?: string;\r\n}\r\n\r\nexport interface ChatModelInfo {\r\n /** Adapter id, e.g. \"openai\". Alphanumeric with - or _. */\r\n id: string;\r\n label: string;\r\n /** Models this adapter serves; the first is the default. */\r\n models?: ModelEntry[];\r\n /** For a model that routes to several providers (`routedChatModel`): which ones. */\r\n providers?: { id: string; label: string }[];\r\n}\r\n\r\nexport interface ChatModel extends ChatModelInfo {\r\n complete(request: ChatRequest, options?: ChatOptions): Promise<ChatResponse>;\r\n /** Every model the account behind this adapter can use, live from the vendor (for model pickers). */\r\n listModels?(options?: { signal?: AbortSignal }): Promise<ModelEntry[]>;\r\n}\r\n\r\n/** An error from a model vendor, carrying its HTTP status when there was one. */\r\nexport class ChatModelError extends Error {\r\n constructor(\r\n readonly provider: string,\r\n message: string,\r\n readonly status?: number,\r\n ) {\r\n super(`${provider}: ${message}`);\r\n this.name = \"ChatModelError\";\r\n }\r\n}\r\n\r\nexport type FetchLike = (url: string, init?: RequestInit) => Promise<Response>;\r\n\r\nexport function defaultFetch(): FetchLike {\r\n if (typeof fetch !== \"function\") throw new Error(\"No global fetch: pass `fetch` explicitly\");\r\n return (url, init) => fetch(url, init);\r\n}\r\n\r\n/** Check an adapter's shape once, at registration. */\r\nexport function defineChatModel<M extends ChatModel>(model: M): M {\r\n if (!model.id || !/^[a-z0-9][a-z0-9_-]*$/i.test(model.id)) {\r\n throw new Error(`Chat model id must be alphanumeric with - or _ (got \"${model.id}\")`);\r\n }\r\n if (typeof model.complete !== \"function\") throw new Error(`Chat model \"${model.id}\" has no complete()`);\r\n return model;\r\n}\r\n","/**\r\n * Plumbing the vendor adapters share: POST with retries on rate limits and\r\n * server errors, server-sent-event reading, and tool-argument parsing.\r\n */\r\nimport { ChatModelError, type FetchLike } from \"./types.js\";\r\n\r\n/** Parse a tool call's JSON arguments; unparseable JSON is reported to the model as a tool error. */\r\nexport function parseToolArguments(raw: string): Record<string, unknown> {\r\n if (!raw.trim()) return {};\r\n try {\r\n const value = JSON.parse(raw) as unknown;\r\n return value && typeof value === \"object\" && !Array.isArray(value) ? (value as Record<string, unknown>) : { __invalidArguments: raw };\r\n } catch {\r\n return { __invalidArguments: raw };\r\n }\r\n}\r\n\r\n/** How long the server asked us to wait (Retry-After header, or \"try again in 1.2s / 350ms\"), else exponential backoff. */\r\nexport function retryDelayMs(res: Pick<Response, \"headers\">, message: string, attempt: number): number {\r\n const header = Number(res.headers.get(\"retry-after-ms\") ?? NaN) || Number(res.headers.get(\"retry-after\") ?? NaN) * 1000;\r\n const m = /try again in ([\\d.]+)\\s*(ms|s)/i.exec(message);\r\n const asked = header || (m ? Number(m[1]) * (m[2]!.toLowerCase() === \"ms\" ? 1 : 1000) : 0);\r\n const backoff = Math.min(30_000, 1000 * 2 ** attempt);\r\n // A little extra and some jitter, so parallel callers don't all retry at the same instant.\r\n return Math.round(Math.min(60_000, Math.max(asked + 250, asked ? 0 : backoff)) * (1 + Math.random() * 0.25));\r\n}\r\n\r\nexport function sleep(ms: number, signal?: AbortSignal): Promise<void> {\r\n return new Promise((resolve, reject) => {\r\n if (signal?.aborted) return reject(signal.reason ?? new Error(\"aborted\"));\r\n const t = setTimeout(resolve, ms);\r\n signal?.addEventListener(\"abort\", () => (clearTimeout(t), reject(signal.reason ?? new Error(\"aborted\"))), { once: true });\r\n });\r\n}\r\n\r\n/** 529 is Anthropic's \"overloaded\". */\r\nconst RETRYABLE = new Set([429, 500, 502, 503, 504, 529]);\r\n\r\n/** The message out of the error bodies vendors send ({ error: { message } }, { error: \"…\" }, { message }). */\r\nexport function errorMessage(body: unknown, status: number): string {\r\n const b = body as { error?: { message?: string } | string; message?: string } | null;\r\n const msg = typeof b?.error === \"string\" ? b.error : (b?.error?.message ?? b?.message);\r\n return msg || `HTTP ${status}`;\r\n}\r\n\r\n/**\r\n * Send a request, retrying 429s and 5xx (waiting as long as the server asks,\r\n * else 1 s, 2 s, 4 s… capped at 30 s). A 429 for an exhausted quota, missing\r\n * credits or billing won't clear by waiting, so it fails at once.\r\n */\r\nexport async function sendWithRetry(\r\n provider: string,\r\n doFetch: FetchLike,\r\n url: string,\r\n init: RequestInit,\r\n options: { maxRetries?: number; signal?: AbortSignal } = {},\r\n): Promise<Response> {\r\n const maxRetries = options.maxRetries ?? 4;\r\n for (let attempt = 0; ; attempt++) {\r\n let res: Response;\r\n try {\r\n res = await doFetch(url, { ...init, ...(options.signal ? { signal: options.signal } : {}) });\r\n } catch (err) {\r\n if ((err as Error)?.name === \"AbortError\" || options.signal?.aborted) throw err;\r\n throw new ChatModelError(provider, `request failed: ${(err as Error)?.message ?? err}`);\r\n }\r\n if (res.ok) return res;\r\n const message = errorMessage(await res.json().catch(() => null), res.status);\r\n const permanent = /quota|credit|billing/i.test(message);\r\n if (attempt >= maxRetries || !RETRYABLE.has(res.status) || permanent) throw new ChatModelError(provider, message, res.status);\r\n await sleep(retryDelayMs(res, message, attempt), options.signal);\r\n }\r\n}\r\n\r\n/** GET a JSON document, as a ChatModelError on failure. */\r\nexport async function getJson(provider: string, doFetch: FetchLike, url: string, headers: Record<string, string>, signal?: AbortSignal): Promise<unknown> {\r\n const res = await sendWithRetry(provider, doFetch, url, { method: \"GET\", headers }, { maxRetries: 1, ...(signal ? { signal } : {}) });\r\n return res.json();\r\n}\r\n\r\n/**\r\n * Server-sent events: calls `onData` with each `data:` line's payload. Every\r\n * vendor here sends one JSON object per data line, and handling lines one by\r\n * one also copes with servers that skip the blank line between events.\r\n */\r\nexport async function readSse(body: ReadableStream<Uint8Array>, onData: (data: string) => void): Promise<void> {\r\n const reader = body.getReader();\r\n const decoder = new TextDecoder();\r\n let buffer = \"\";\r\n const line = (raw: string) => {\r\n const l = raw.replace(/\\r$/, \"\");\r\n if (l.startsWith(\"data:\")) onData(l.slice(5).trim());\r\n };\r\n for (;;) {\r\n const { done, value } = await reader.read();\r\n if (value) buffer += decoder.decode(value, { stream: true });\r\n let nl: number;\r\n while ((nl = buffer.indexOf(\"\\n\")) >= 0) {\r\n line(buffer.slice(0, nl));\r\n buffer = buffer.slice(nl + 1);\r\n }\r\n if (done) break;\r\n }\r\n if (buffer) line(buffer);\r\n}\r\n\r\n/** True when a response is an event stream we should read incrementally. */\r\nexport const isEventStream = (res: Response) => !!res.body && (res.headers.get(\"content-type\") ?? \"\").includes(\"event-stream\");\r\n\r\nexport const trimSlash = (url: string) => url.replace(/\\/+$/, \"\");\r\n\r\n/** The adapter's model list: the default first (keeping its label if `others` has one), then the rest. */\r\nexport function modelList(model: string, others: readonly { id: string; label?: string }[] = []): { id: string; label?: string }[] {\r\n return [others.find((m) => m.id === model) ?? { id: model }, ...others.filter((m) => m.id !== model)];\r\n}\r\n","/**\r\n * OpenAI adapter, over the Chat Completions API with function calling.\r\n *\r\n * Chat Completions is also what most other servers speak (Azure OpenAI,\r\n * OpenRouter, Groq, Together, vLLM, Ollama, LM Studio…), so `baseUrl` points\r\n * this one adapter at any of them.\r\n */\r\nimport {\r\n defaultFetch,\r\n defineChatModel,\r\n type ChatMessage,\r\n type ChatModel,\r\n type ChatRequest,\r\n type ChatResponse,\r\n type FetchLike,\r\n type ToolCall,\r\n} from \"./types.js\";\r\nimport { getJson, isEventStream, modelList, parseToolArguments, readSse, sendWithRetry, trimSlash } from \"./wire.js\";\r\n\r\nexport { parseToolArguments, retryDelayMs } from \"./wire.js\";\r\n\r\nexport interface OpenAIChatModelOptions {\r\n /** Model id, e.g. \"gpt-5.4-mini\". Required: model names change faster than this package. */\r\n model: string;\r\n /** Other models the app may pick per request (`ChatRequest.model`). */\r\n models?: { id: string; label?: string }[];\r\n /** API key. Omit for local servers that don't need one. Keep it server-side (see `createChatHandler`). */\r\n apiKey?: string | undefined;\r\n /** Default https://api.openai.com/v1. Any OpenAI-compatible base URL works. */\r\n baseUrl?: string;\r\n organization?: string;\r\n project?: string;\r\n headers?: Record<string, string>;\r\n /** Default request fields merged into every call (e.g. `{ reasoning_effort: \"low\" }`). */\r\n params?: Record<string, unknown>;\r\n /** Stream responses (default true). */\r\n stream?: boolean;\r\n /**\r\n * Retries on rate limits (429) and server errors (500/502/503/504), waiting\r\n * as long as the server asks (Retry-After, or \"try again in 1.2s\"), else\r\n * backing off 1 s, 2 s, 4 s… (capped at 30 s). Default 4; 0 disables.\r\n */\r\n maxRetries?: number;\r\n /**\r\n * The request field for the output cap: \"max_completion_tokens\" (default;\r\n * OpenAI) or \"max_tokens\" (what most other compatible servers read).\r\n */\r\n maxTokensField?: \"max_completion_tokens\" | \"max_tokens\";\r\n /** Ask for token usage at the end of a stream (default true; turn off for servers that reject `stream_options`). */\r\n streamUsage?: boolean;\r\n /** Adapter id / label (default \"openai\" / \"OpenAI\"), e.g. \"ollama\" when pointing elsewhere. */\r\n id?: string;\r\n label?: string;\r\n fetch?: FetchLike;\r\n}\r\n\r\ntype OpenAIPart = { type: \"text\"; text: string } | { type: \"image_url\"; image_url: { url: string } };\r\ntype OpenAIMessage =\r\n | { role: \"system\" | \"user\"; content: string }\r\n | { role: \"user\"; content: OpenAIPart[] }\r\n | { role: \"assistant\"; content: string | null; tool_calls?: { id: string; type: \"function\"; function: { name: string; arguments: string } }[] }\r\n | { role: \"tool\"; tool_call_id: string; content: string };\r\n\r\n/** Neutral messages → Chat Completions messages. */\r\nexport function toOpenAIMessages(messages: readonly ChatMessage[]): OpenAIMessage[] {\r\n // Tool messages can't carry images in Chat Completions: the pictures from a\r\n // run of tool results follow it as one user message.\r\n const out: OpenAIMessage[] = [];\r\n let pending: OpenAIPart[] = [];\r\n const flush = () => {\r\n if (!pending.length) return;\r\n out.push({ role: \"user\", content: [{ type: \"text\", text: \"Images returned by the tool calls above:\" }, ...pending] });\r\n pending = [];\r\n };\r\n for (const m of messages) {\r\n if (m.role !== \"tool\") flush();\r\n out.push(one(m));\r\n if (m.role === \"tool\" && m.images?.length) {\r\n pending.push({ type: \"text\", text: `From ${m.name} (${m.toolCallId}):` }, ...m.images.map((i): OpenAIPart => ({ type: \"image_url\", image_url: { url: `data:${i.mimeType};base64,${i.data}` } })));\r\n }\r\n }\r\n flush();\r\n return out;\r\n}\r\n\r\nfunction one(m: ChatMessage): OpenAIMessage {\r\n {\r\n if (m.role === \"assistant\") {\r\n const out: OpenAIMessage = { role: \"assistant\", content: m.content || null };\r\n if (m.toolCalls?.length) {\r\n out.tool_calls = m.toolCalls.map((c) => ({ id: c.id, type: \"function\", function: { name: c.name, arguments: JSON.stringify(c.arguments) } }));\r\n }\r\n return out;\r\n }\r\n if (m.role === \"tool\") return { role: \"tool\", tool_call_id: m.toolCallId, content: m.content };\r\n return { role: m.role, content: m.content };\r\n }\r\n}\r\n\r\nconst FINISH: Record<string, ChatResponse[\"finish\"]> = {\r\n stop: \"stop\",\r\n tool_calls: \"tool_calls\",\r\n function_call: \"tool_calls\",\r\n length: \"length\",\r\n content_filter: \"content_filter\",\r\n};\r\n\r\nexport function openAIChatModel(options: OpenAIChatModelOptions): ChatModel {\r\n const base = trimSlash(options.baseUrl ?? \"https://api.openai.com/v1\");\r\n const id = options.id ?? \"openai\";\r\n const doFetch = options.fetch ?? defaultFetch();\r\n const stream = options.stream ?? true;\r\n const models = modelList(options.model, options.models);\r\n const maxTokensField = options.maxTokensField ?? \"max_completion_tokens\";\r\n\r\n const headers = (): Record<string, string> => {\r\n const h: Record<string, string> = { \"Content-Type\": \"application/json\", ...options.headers };\r\n if (options.apiKey) h[\"Authorization\"] = `Bearer ${options.apiKey}`;\r\n if (options.organization) h[\"OpenAI-Organization\"] = options.organization;\r\n if (options.project) h[\"OpenAI-Project\"] = options.project;\r\n return h;\r\n };\r\n\r\n return defineChatModel({\r\n id,\r\n label: options.label ?? \"OpenAI\",\r\n models,\r\n async listModels(opts = {}) {\r\n const json = (await getJson(id, doFetch, `${base}/models`, headers(), opts.signal)) as { data?: { id: string }[] };\r\n return (json.data ?? []).map((m) => ({ id: m.id })).sort((a, b) => a.id.localeCompare(b.id));\r\n },\r\n async complete(request: ChatRequest, opts = {}): Promise<ChatResponse> {\r\n const body: Record<string, unknown> = {\r\n model: request.model ?? options.model,\r\n messages: toOpenAIMessages(request.messages),\r\n ...(request.tools?.length\r\n ? { tools: request.tools.map((t) => ({ type: \"function\", function: { name: t.name, description: t.description, parameters: t.inputSchema } })) }\r\n : {}),\r\n ...(request.maxOutputTokens ? { [maxTokensField]: request.maxOutputTokens } : {}),\r\n ...(stream ? { stream: true, ...(options.streamUsage === false ? {} : { stream_options: { include_usage: true } }) } : {}),\r\n ...options.params,\r\n ...request.params,\r\n };\r\n const res = await sendWithRetry(\r\n id,\r\n doFetch,\r\n `${base}/chat/completions`,\r\n { method: \"POST\", headers: headers(), body: JSON.stringify(body) },\r\n { ...(options.maxRetries !== undefined ? { maxRetries: options.maxRetries } : {}), ...(opts.signal ? { signal: opts.signal } : {}) },\r\n );\r\n if (!stream || !isEventStream(res)) return fromCompletion(await res.json(), opts.onText);\r\n return readStream(res.body!, opts.onText);\r\n },\r\n });\r\n}\r\n\r\ninterface CompletionJson {\r\n model?: string;\r\n choices?: { message?: { content?: string | null; tool_calls?: { id: string; function: { name: string; arguments: string } }[] }; finish_reason?: string }[];\r\n usage?: { prompt_tokens?: number; completion_tokens?: number };\r\n}\r\n\r\nfunction fromCompletion(json: CompletionJson, onText?: (delta: string) => void): ChatResponse {\r\n const choice = json.choices?.[0];\r\n const content = choice?.message?.content ?? \"\";\r\n if (content) onText?.(content);\r\n const out: ChatResponse = {\r\n content,\r\n toolCalls: (choice?.message?.tool_calls ?? []).map((c) => ({ id: c.id, name: c.function.name, arguments: parseToolArguments(c.function.arguments) })),\r\n finish: FINISH[choice?.finish_reason ?? \"\"] ?? \"other\",\r\n };\r\n if (json.model) out.model = json.model;\r\n if (json.usage) out.usage = usage(json.usage);\r\n return out;\r\n}\r\n\r\nconst usage = (u: { prompt_tokens?: number; completion_tokens?: number }) => ({\r\n ...(u.prompt_tokens !== undefined ? { inputTokens: u.prompt_tokens } : {}),\r\n ...(u.completion_tokens !== undefined ? { outputTokens: u.completion_tokens } : {}),\r\n});\r\n\r\ninterface ChunkJson {\r\n model?: string;\r\n choices?: {\r\n delta?: { content?: string | null; tool_calls?: { index: number; id?: string; function?: { name?: string; arguments?: string } }[] };\r\n finish_reason?: string | null;\r\n }[];\r\n usage?: { prompt_tokens?: number; completion_tokens?: number } | null;\r\n}\r\n\r\n/** Server-sent events → one response; text deltas stream to `onText`, tool-call fragments are joined by index. */\r\nasync function readStream(body: ReadableStream<Uint8Array>, onText?: (delta: string) => void): Promise<ChatResponse> {\r\n let content = \"\";\r\n let finish: ChatResponse[\"finish\"] = \"other\";\r\n let model: string | undefined;\r\n let use: ChatResponse[\"usage\"];\r\n const calls = new Map<number, { id: string; name: string; args: string }>();\r\n\r\n const handle = (data: string) => {\r\n if (data === \"[DONE]\") return;\r\n let chunk: ChunkJson;\r\n try {\r\n chunk = JSON.parse(data) as ChunkJson;\r\n } catch {\r\n return;\r\n }\r\n if (chunk.model) model = chunk.model;\r\n if (chunk.usage) use = usage(chunk.usage);\r\n for (const choice of chunk.choices ?? []) {\r\n const delta = choice.delta;\r\n if (delta?.content) {\r\n content += delta.content;\r\n onText?.(delta.content);\r\n }\r\n for (const tc of delta?.tool_calls ?? []) {\r\n const call = calls.get(tc.index) ?? { id: \"\", name: \"\", args: \"\" };\r\n if (tc.id) call.id = tc.id;\r\n if (tc.function?.name) call.name += tc.function.name;\r\n if (tc.function?.arguments) call.args += tc.function.arguments;\r\n calls.set(tc.index, call);\r\n }\r\n if (choice.finish_reason) finish = FINISH[choice.finish_reason] ?? \"other\";\r\n }\r\n };\r\n\r\n await readSse(body, handle);\r\n\r\n const toolCalls: ToolCall[] = [...calls.entries()]\r\n .sort(([a], [b]) => a - b)\r\n .map(([index, c]) => ({ id: c.id || `call_${index}`, name: c.name, arguments: parseToolArguments(c.args) }));\r\n const out: ChatResponse = { content, toolCalls, finish };\r\n if (model) out.model = model;\r\n if (use) out.usage = use;\r\n return out;\r\n}\r\n","/**\r\n * Anthropic adapter, over the Messages API with tool use.\r\n *\r\n * System messages become the top-level `system` prompt; tool results are\r\n * grouped into one user turn, as the API expects. When extended thinking is\r\n * on (`params: { thinking: … }`), the thinking blocks of a tool-using turn\r\n * ride on the first tool call's `meta` and are sent back on the next request,\r\n * which the API requires.\r\n */\r\nimport {\r\n ChatModelError,\r\n defaultFetch,\r\n defineChatModel,\r\n type ChatMessage,\r\n type ChatModel,\r\n type ChatRequest,\r\n type ChatResponse,\r\n type FetchLike,\r\n type ToolCall,\r\n} from \"./types.js\";\r\nimport { getJson, isEventStream, modelList, parseToolArguments, readSse, sendWithRetry, trimSlash } from \"./wire.js\";\r\n\r\nexport interface AnthropicChatModelOptions {\r\n /** Model id, e.g. \"claude-sonnet-5-5\". Required: model names change faster than this package. */\r\n model: string;\r\n /** Other models the app may pick per request (`ChatRequest.model`). */\r\n models?: { id: string; label?: string }[];\r\n /** API key. Keep it server-side (see `createChatHandler`) unless it's the user's own key in their own browser (`browser`). */\r\n apiKey?: string | undefined;\r\n /** Default https://api.anthropic.com (requests go to `{baseUrl}/v1/messages`). */\r\n baseUrl?: string;\r\n /** `anthropic-version` header (default \"2023-06-01\"). */\r\n version?: string;\r\n /** `anthropic-beta` flags, joined with commas. */\r\n betas?: string[];\r\n headers?: Record<string, string>;\r\n /** Default request fields merged into every call (e.g. `{ thinking: { type: \"enabled\", budget_tokens: 4000 } }`). */\r\n params?: Record<string, unknown>;\r\n /** The API requires an output cap; used when a request sets none (default 8192). */\r\n maxOutputTokens?: number;\r\n /** Stream responses (default true). */\r\n stream?: boolean;\r\n /** Retries on 429, 5xx and 529 (overloaded), as the server asks. Default 4; 0 disables. */\r\n maxRetries?: number;\r\n /**\r\n * Calling from a browser with the user's own key: sends the header the API\r\n * needs to allow cross-origin requests. Never ship your own key this way.\r\n */\r\n browser?: boolean;\r\n /** Adapter id / label (default \"anthropic\" / \"Anthropic\"). */\r\n id?: string;\r\n label?: string;\r\n fetch?: FetchLike;\r\n}\r\n\r\ntype ImageBlock = { type: \"image\"; source: { type: \"base64\"; media_type: string; data: string } };\r\ntype Block =\r\n | { type: \"text\"; text: string }\r\n | { type: \"tool_use\"; id: string; name: string; input: Record<string, unknown> }\r\n | { type: \"tool_result\"; tool_use_id: string; content: string | ({ type: \"text\"; text: string } | ImageBlock)[] }\r\n | { type: \"thinking\"; thinking: string; signature: string }\r\n | { type: \"redacted_thinking\"; data: string };\r\n\r\nexport type AnthropicMessage = { role: \"user\" | \"assistant\"; content: Block[] };\r\n\r\n/** Neutral messages → `{ system, messages }` for the Messages API. */\r\nexport function toAnthropicMessages(messages: readonly ChatMessage[]): { system: string; messages: AnthropicMessage[] } {\r\n const system: string[] = [];\r\n const out: AnthropicMessage[] = [];\r\n const push = (role: AnthropicMessage[\"role\"], blocks: Block[]) => {\r\n if (!blocks.length) return;\r\n const last = out[out.length - 1];\r\n // The API wants user/assistant turns to alternate: fold same-role neighbours together.\r\n if (last && last.role === role) last.content.push(...blocks);\r\n else out.push({ role, content: blocks });\r\n };\r\n for (const m of messages) {\r\n if (m.role === \"system\") system.push(m.content);\r\n else if (m.role === \"user\") push(\"user\", m.content.trim() ? [{ type: \"text\", text: m.content }] : []);\r\n else if (m.role === \"tool\") {\r\n const content = m.images?.length\r\n ? [{ type: \"text\" as const, text: m.content }, ...m.images.map((i): ImageBlock => ({ type: \"image\", source: { type: \"base64\", media_type: i.mimeType, data: i.data } }))]\r\n : m.content;\r\n push(\"user\", [{ type: \"tool_result\", tool_use_id: m.toolCallId, content }]);\r\n }\r\n else {\r\n const thinking = (m.toolCalls?.[0]?.meta?.anthropicThinking as Block[] | undefined) ?? [];\r\n push(\"assistant\", [\r\n ...thinking,\r\n ...(m.content.trim() ? [{ type: \"text\" as const, text: m.content }] : []),\r\n ...(m.toolCalls ?? []).map((c): Block => ({ type: \"tool_use\", id: c.id, name: c.name, input: c.arguments })),\r\n ]);\r\n }\r\n }\r\n return { system: system.join(\"\\n\\n\"), messages: out };\r\n}\r\n\r\nconst FINISH: Record<string, ChatResponse[\"finish\"]> = {\r\n end_turn: \"stop\",\r\n stop_sequence: \"stop\",\r\n tool_use: \"tool_calls\",\r\n max_tokens: \"length\",\r\n model_context_window_exceeded: \"length\",\r\n refusal: \"content_filter\",\r\n};\r\n\r\ninterface Usage {\r\n input_tokens?: number;\r\n output_tokens?: number;\r\n cache_creation_input_tokens?: number | null;\r\n cache_read_input_tokens?: number | null;\r\n}\r\n\r\nconst usageOf = (u: Usage) => {\r\n const input = (u.input_tokens ?? 0) + (u.cache_creation_input_tokens ?? 0) + (u.cache_read_input_tokens ?? 0);\r\n return {\r\n ...(u.input_tokens !== undefined ? { inputTokens: input } : {}),\r\n ...(u.output_tokens !== undefined ? { outputTokens: u.output_tokens } : {}),\r\n };\r\n};\r\n\r\n/** Content blocks → the neutral response (thinking blocks go on the first tool call). */\r\nfunction fromBlocks(blocks: Block[], stopReason: string | undefined, model: string | undefined, usage: Usage | undefined): ChatResponse {\r\n const thinking = blocks.filter((b) => b.type === \"thinking\" || b.type === \"redacted_thinking\");\r\n const toolCalls: ToolCall[] = blocks\r\n .filter((b): b is Extract<Block, { type: \"tool_use\" }> => b.type === \"tool_use\")\r\n .map((b, i) => ({ id: b.id, name: b.name, arguments: b.input ?? {}, ...(i === 0 && thinking.length ? { meta: { anthropicThinking: thinking } } : {}) }));\r\n const out: ChatResponse = {\r\n content: blocks.map((b) => (b.type === \"text\" ? b.text : \"\")).join(\"\"),\r\n toolCalls,\r\n finish: FINISH[stopReason ?? \"\"] ?? \"other\",\r\n };\r\n if (model) out.model = model;\r\n if (usage) out.usage = usageOf(usage);\r\n return out;\r\n}\r\n\r\nexport function anthropicChatModel(options: AnthropicChatModelOptions): ChatModel {\r\n const base = trimSlash(options.baseUrl ?? \"https://api.anthropic.com\");\r\n const id = options.id ?? \"anthropic\";\r\n const doFetch = options.fetch ?? defaultFetch();\r\n const stream = options.stream ?? true;\r\n const models = modelList(options.model, options.models);\r\n\r\n const headers = (): Record<string, string> => {\r\n const h: Record<string, string> = { \"Content-Type\": \"application/json\", \"anthropic-version\": options.version ?? \"2023-06-01\", ...options.headers };\r\n if (options.apiKey) h[\"x-api-key\"] = options.apiKey;\r\n if (options.betas?.length) h[\"anthropic-beta\"] = options.betas.join(\",\");\r\n if (options.browser) h[\"anthropic-dangerous-direct-browser-access\"] = \"true\";\r\n return h;\r\n };\r\n\r\n return defineChatModel({\r\n id,\r\n label: options.label ?? \"Anthropic\",\r\n models,\r\n async listModels(opts = {}) {\r\n const json = (await getJson(id, doFetch, `${base}/v1/models?limit=1000`, headers(), opts.signal)) as { data?: { id: string; display_name?: string }[] };\r\n return (json.data ?? []).map((m) => ({ id: m.id, ...(m.display_name ? { label: m.display_name } : {}) }));\r\n },\r\n async complete(request: ChatRequest, opts = {}): Promise<ChatResponse> {\r\n const { system, messages } = toAnthropicMessages(request.messages);\r\n const body: Record<string, unknown> = {\r\n model: request.model ?? options.model,\r\n max_tokens: request.maxOutputTokens ?? options.maxOutputTokens ?? 8192,\r\n messages,\r\n ...(system ? { system } : {}),\r\n ...(request.tools?.length ? { tools: request.tools.map((t) => ({ name: t.name, description: t.description, input_schema: t.inputSchema })) } : {}),\r\n ...(stream ? { stream: true } : {}),\r\n ...options.params,\r\n ...request.params,\r\n };\r\n const res = await sendWithRetry(\r\n id,\r\n doFetch,\r\n `${base}/v1/messages`,\r\n { method: \"POST\", headers: headers(), body: JSON.stringify(body) },\r\n { ...(options.maxRetries !== undefined ? { maxRetries: options.maxRetries } : {}), ...(opts.signal ? { signal: opts.signal } : {}) },\r\n );\r\n if (!stream || !isEventStream(res)) {\r\n const json = (await res.json()) as { content?: Block[]; stop_reason?: string; model?: string; usage?: Usage };\r\n const out = fromBlocks(json.content ?? [], json.stop_reason, json.model, json.usage);\r\n if (out.content) opts.onText?.(out.content);\r\n return out;\r\n }\r\n return readStream(id, res.body!, opts.onText);\r\n },\r\n });\r\n}\r\n\r\ninterface StreamEvent {\r\n type: string;\r\n index?: number;\r\n message?: { model?: string; usage?: Usage };\r\n content_block?: { type: string; id?: string; name?: string; text?: string; thinking?: string; signature?: string; data?: string };\r\n delta?: { type?: string; text?: string; partial_json?: string; thinking?: string; signature?: string; stop_reason?: string };\r\n usage?: Usage;\r\n error?: { message?: string; type?: string };\r\n}\r\n\r\n/** Message stream events → one response; text deltas go to `onText`, tool input JSON is joined per block. */\r\nasync function readStream(id: string, body: ReadableStream<Uint8Array>, onText?: (delta: string) => void): Promise<ChatResponse> {\r\n const blocks = new Map<number, { type: string; id?: string; name?: string; text: string; json: string; signature: string; data?: string }>();\r\n let model: string | undefined;\r\n let stopReason: string | undefined;\r\n const usage: Usage = {};\r\n let failure: string | undefined;\r\n\r\n await readSse(body, (data) => {\r\n let event: StreamEvent;\r\n try {\r\n event = JSON.parse(data) as StreamEvent;\r\n } catch {\r\n return;\r\n }\r\n switch (event.type) {\r\n case \"message_start\":\r\n if (event.message?.model) model = event.message.model;\r\n Object.assign(usage, event.message?.usage ?? {});\r\n break;\r\n case \"content_block_start\": {\r\n const b = event.content_block!;\r\n blocks.set(event.index ?? blocks.size, {\r\n type: b.type,\r\n ...(b.id ? { id: b.id } : {}),\r\n ...(b.name ? { name: b.name } : {}),\r\n text: b.text ?? b.thinking ?? \"\",\r\n json: \"\",\r\n signature: b.signature ?? \"\",\r\n ...(b.data ? { data: b.data } : {}),\r\n });\r\n if (b.type === \"text\" && b.text) onText?.(b.text);\r\n break;\r\n }\r\n case \"content_block_delta\": {\r\n const block = blocks.get(event.index ?? 0);\r\n const d = event.delta ?? {};\r\n if (!block) break;\r\n if (d.type === \"text_delta\" && d.text) {\r\n block.text += d.text;\r\n onText?.(d.text);\r\n } else if (d.type === \"input_json_delta\" && d.partial_json) block.json += d.partial_json;\r\n else if (d.type === \"thinking_delta\" && d.thinking) block.text += d.thinking;\r\n else if (d.type === \"signature_delta\" && d.signature) block.signature += d.signature;\r\n break;\r\n }\r\n case \"message_delta\":\r\n if (event.delta?.stop_reason) stopReason = event.delta.stop_reason;\r\n Object.assign(usage, Object.fromEntries(Object.entries(event.usage ?? {}).filter(([, v]) => v !== null && v !== undefined)));\r\n break;\r\n case \"error\":\r\n failure = event.error?.message ?? event.error?.type ?? \"stream error\";\r\n break;\r\n }\r\n });\r\n if (failure) throw new ChatModelError(id, failure);\r\n\r\n const ordered = [...blocks.entries()].sort(([a], [b]) => a - b).map(([, b]) => b);\r\n const out: Block[] = ordered.flatMap((b): Block[] => {\r\n if (b.type === \"text\") return [{ type: \"text\", text: b.text }];\r\n if (b.type === \"tool_use\") return [{ type: \"tool_use\", id: b.id ?? \"\", name: b.name ?? \"\", input: parseToolArguments(b.json) }];\r\n if (b.type === \"thinking\") return [{ type: \"thinking\", thinking: b.text, signature: b.signature }];\r\n if (b.type === \"redacted_thinking\") return [{ type: \"redacted_thinking\", data: b.data ?? \"\" }];\r\n return [];\r\n });\r\n return fromBlocks(out, stopReason, model, Object.keys(usage).length ? usage : undefined);\r\n}\r\n","/**\r\n * Google Gemini adapter, over the Gemini API's `generateContent` with\r\n * function calling.\r\n *\r\n * Tools are sent as full JSON Schema (`parametersJsonSchema`), so the same\r\n * schemas the other adapters use work unchanged. Gemini's thought signatures\r\n * (required when thinking models call tools) ride on each tool call's `meta`\r\n * and are sent back exactly as received.\r\n */\r\nimport {\r\n defaultFetch,\r\n defineChatModel,\r\n type ChatMessage,\r\n type ChatModel,\r\n type ChatRequest,\r\n type ChatResponse,\r\n type FetchLike,\r\n type ToolCall,\r\n} from \"./types.js\";\r\nimport { getJson, isEventStream, modelList, readSse, sendWithRetry, trimSlash } from \"./wire.js\";\r\n\r\nexport interface GeminiChatModelOptions {\r\n /** Model id, e.g. \"gemini-3.8-flash\". Required: model names change faster than this package. */\r\n model: string;\r\n /** Other models the app may pick per request (`ChatRequest.model`). */\r\n models?: { id: string; label?: string }[];\r\n /** Gemini API key. Keep it server-side (see `createChatHandler`) unless it's the user's own key in their own browser. */\r\n apiKey?: string | undefined;\r\n /** Default https://generativelanguage.googleapis.com/v1beta. */\r\n baseUrl?: string;\r\n headers?: Record<string, string>;\r\n /** Default request fields merged into every call; `generationConfig` is merged key by key (e.g. `{ generationConfig: { temperature: 0.2 } }`). */\r\n params?: Record<string, unknown>;\r\n /** Stream responses (default true). */\r\n stream?: boolean;\r\n /** Retries on 429 and 5xx, as the server asks. Default 4; 0 disables. */\r\n maxRetries?: number;\r\n /** Adapter id / label (default \"gemini\" / \"Google Gemini\"). */\r\n id?: string;\r\n label?: string;\r\n fetch?: FetchLike;\r\n}\r\n\r\ntype Part = {\r\n text?: string;\r\n thought?: boolean;\r\n thoughtSignature?: string;\r\n functionCall?: { id?: string; name: string; args?: Record<string, unknown> };\r\n functionResponse?: { id?: string; name: string; response: Record<string, unknown> };\r\n inlineData?: { mimeType: string; data: string };\r\n};\r\nexport type GeminiContent = { role: \"user\" | \"model\"; parts: Part[] };\r\n\r\n/** Gemini's documented value for replaying a tool call it didn't sign (e.g. one another model made). */\r\nconst UNSIGNED = \"skip_thought_signature_validator\";\r\n\r\ninterface GeminiMeta {\r\n /** The call id Gemini assigned, when it assigned one. */\r\n id?: string;\r\n signature?: string;\r\n}\r\n\r\nconst metaOf = (call: ToolCall): GeminiMeta => (call.meta?.gemini as GeminiMeta | undefined) ?? {};\r\n\r\n/** A tool result as the object `functionResponse.response` must be. */\r\nfunction responseObject(content: string): Record<string, unknown> {\r\n try {\r\n const value = JSON.parse(content) as unknown;\r\n if (value && typeof value === \"object\" && !Array.isArray(value)) return value as Record<string, unknown>;\r\n return { result: value };\r\n } catch {\r\n return { result: content };\r\n }\r\n}\r\n\r\n/** Neutral messages → `{ systemInstruction, contents }` for `generateContent`. */\r\nexport function toGeminiContents(messages: readonly ChatMessage[]): { system: string; contents: GeminiContent[] } {\r\n const system: string[] = [];\r\n const contents: GeminiContent[] = [];\r\n const callIds = new Map<string, string | undefined>(); // our tool-call id → Gemini's id (if it gave one)\r\n const push = (role: GeminiContent[\"role\"], parts: Part[]) => {\r\n if (!parts.length) return;\r\n const last = contents[contents.length - 1];\r\n if (last && last.role === role) last.parts.push(...parts);\r\n else contents.push({ role, parts });\r\n };\r\n // Pictures from a run of tool results go after all its function responses, in the same turn.\r\n let pending: Part[] = [];\r\n const flush = () => {\r\n if (pending.length) push(\"user\", [{ text: \"Images returned by the tool calls above:\" }, ...pending]);\r\n pending = [];\r\n };\r\n for (const m of messages) {\r\n if (m.role !== \"tool\") flush();\r\n if (m.role === \"system\") system.push(m.content);\r\n else if (m.role === \"user\") push(\"user\", m.content ? [{ text: m.content }] : []);\r\n else if (m.role === \"tool\") {\r\n const id = callIds.get(m.toolCallId);\r\n push(\"user\", [{ functionResponse: { ...(id ? { id } : {}), name: m.name, response: responseObject(m.content) } }]);\r\n if (m.images?.length) pending.push({ text: `From ${m.name}:` }, ...m.images.map((i): Part => ({ inlineData: { mimeType: i.mimeType, data: i.data } })));\r\n } else {\r\n const calls = m.toolCalls ?? [];\r\n const signed = calls.some((c) => metaOf(c).signature);\r\n push(\"model\", [\r\n ...(m.content ? [{ text: m.content }] : []),\r\n ...calls.map((c, i): Part => {\r\n const meta = metaOf(c);\r\n callIds.set(c.id, meta.id);\r\n const signature = meta.signature ?? (i === 0 && !signed ? UNSIGNED : undefined);\r\n return { functionCall: { ...(meta.id ? { id: meta.id } : {}), name: c.name, args: c.arguments }, ...(signature ? { thoughtSignature: signature } : {}) };\r\n }),\r\n ]);\r\n }\r\n }\r\n flush();\r\n return { system: system.join(\"\\n\\n\"), contents };\r\n}\r\n\r\nconst FINISH: Record<string, ChatResponse[\"finish\"]> = {\r\n STOP: \"stop\",\r\n MAX_TOKENS: \"length\",\r\n SAFETY: \"content_filter\",\r\n RECITATION: \"content_filter\",\r\n BLOCKLIST: \"content_filter\",\r\n PROHIBITED_CONTENT: \"content_filter\",\r\n SPII: \"content_filter\",\r\n IMAGE_SAFETY: \"content_filter\",\r\n};\r\n\r\ninterface ResponseJson {\r\n candidates?: { content?: { parts?: Part[] }; finishReason?: string }[];\r\n promptFeedback?: { blockReason?: string };\r\n usageMetadata?: { promptTokenCount?: number; candidatesTokenCount?: number; thoughtsTokenCount?: number };\r\n modelVersion?: string;\r\n}\r\n\r\n/** Collects parts across one response or many stream chunks. */\r\nfunction collector(onText?: (delta: string) => void) {\r\n let content = \"\";\r\n let finishReason: string | undefined;\r\n let blocked = false;\r\n let model: string | undefined;\r\n let usage: ChatResponse[\"usage\"];\r\n const toolCalls: ToolCall[] = [];\r\n const batch = Math.random().toString(36).slice(2, 10);\r\n return {\r\n add(json: ResponseJson) {\r\n if (json.modelVersion) model = json.modelVersion;\r\n if (json.promptFeedback?.blockReason) blocked = true;\r\n const u = json.usageMetadata;\r\n if (u) {\r\n usage = {\r\n ...(u.promptTokenCount !== undefined ? { inputTokens: u.promptTokenCount } : {}),\r\n ...(u.candidatesTokenCount !== undefined || u.thoughtsTokenCount !== undefined ? { outputTokens: (u.candidatesTokenCount ?? 0) + (u.thoughtsTokenCount ?? 0) } : {}),\r\n };\r\n }\r\n const candidate = json.candidates?.[0];\r\n if (candidate?.finishReason) finishReason = candidate.finishReason;\r\n for (const part of candidate?.content?.parts ?? []) {\r\n if (part.functionCall) {\r\n const gemini: GeminiMeta = { ...(part.functionCall.id ? { id: part.functionCall.id } : {}), ...(part.thoughtSignature ? { signature: part.thoughtSignature } : {}) };\r\n toolCalls.push({\r\n // Gemini often gives no id; ours must still be unique across the conversation.\r\n id: part.functionCall.id ?? `call_${batch}_${toolCalls.length}`,\r\n name: part.functionCall.name,\r\n arguments: part.functionCall.args ?? {},\r\n ...(Object.keys(gemini).length ? { meta: { gemini } } : {}),\r\n });\r\n } else if (part.text && !part.thought) {\r\n content += part.text;\r\n onText?.(part.text);\r\n }\r\n }\r\n },\r\n result(): ChatResponse {\r\n const finish: ChatResponse[\"finish\"] = blocked\r\n ? \"content_filter\"\r\n : toolCalls.length && (finishReason === \"STOP\" || finishReason === undefined)\r\n ? \"tool_calls\"\r\n : (FINISH[finishReason ?? \"\"] ?? \"other\");\r\n return { content, toolCalls, finish, ...(model ? { model } : {}), ...(usage ? { usage } : {}) };\r\n },\r\n };\r\n}\r\n\r\nconst bareModel = (model: string) => model.replace(/^models\\//, \"\");\r\n\r\nexport function geminiChatModel(options: GeminiChatModelOptions): ChatModel {\r\n const base = trimSlash(options.baseUrl ?? \"https://generativelanguage.googleapis.com/v1beta\");\r\n const id = options.id ?? \"gemini\";\r\n const doFetch = options.fetch ?? defaultFetch();\r\n const stream = options.stream ?? true;\r\n const models = modelList(bareModel(options.model), options.models?.map((m) => ({ ...m, id: bareModel(m.id) })));\r\n\r\n const headers = (): Record<string, string> => ({\r\n \"Content-Type\": \"application/json\",\r\n ...(options.apiKey ? { \"x-goog-api-key\": options.apiKey } : {}),\r\n ...options.headers,\r\n });\r\n\r\n return defineChatModel({\r\n id,\r\n label: options.label ?? \"Google Gemini\",\r\n models,\r\n async listModels(opts = {}) {\r\n const json = (await getJson(id, doFetch, `${base}/models?pageSize=1000`, headers(), opts.signal)) as {\r\n models?: { name: string; displayName?: string; supportedGenerationMethods?: string[] }[];\r\n };\r\n return (json.models ?? [])\r\n .filter((m) => !m.supportedGenerationMethods || m.supportedGenerationMethods.includes(\"generateContent\"))\r\n .map((m) => ({ id: bareModel(m.name), ...(m.displayName ? { label: m.displayName } : {}) }));\r\n },\r\n async complete(request: ChatRequest, opts = {}): Promise<ChatResponse> {\r\n const { system, contents } = toGeminiContents(request.messages);\r\n const params = { ...options.params, ...request.params };\r\n const generationConfig = {\r\n ...(request.maxOutputTokens ? { maxOutputTokens: request.maxOutputTokens } : {}),\r\n ...(options.params?.generationConfig as object | undefined),\r\n ...(request.params?.generationConfig as object | undefined),\r\n };\r\n const body: Record<string, unknown> = {\r\n ...params,\r\n contents,\r\n ...(system ? { systemInstruction: { parts: [{ text: system }] } } : {}),\r\n ...(request.tools?.length\r\n ? { tools: [{ functionDeclarations: request.tools.map((t) => ({ name: t.name, description: t.description, parametersJsonSchema: t.inputSchema })) }] }\r\n : {}),\r\n ...(Object.keys(generationConfig).length ? { generationConfig } : {}),\r\n };\r\n const model = bareModel(request.model ?? options.model);\r\n const url = `${base}/models/${encodeURIComponent(model)}:${stream ? \"streamGenerateContent?alt=sse\" : \"generateContent\"}`;\r\n const res = await sendWithRetry(\r\n id,\r\n doFetch,\r\n url,\r\n { method: \"POST\", headers: headers(), body: JSON.stringify(body) },\r\n { ...(options.maxRetries !== undefined ? { maxRetries: options.maxRetries } : {}), ...(opts.signal ? { signal: opts.signal } : {}) },\r\n );\r\n const collect = collector(opts.onText);\r\n if (!stream || !isEventStream(res)) collect.add((await res.json()) as ResponseJson);\r\n else {\r\n await readSse(res.body!, (data) => {\r\n try {\r\n collect.add(JSON.parse(data) as ResponseJson);\r\n } catch {\r\n /* keep-alive or partial line */\r\n }\r\n });\r\n }\r\n const out = collect.result();\r\n out.model ??= model;\r\n return out;\r\n },\r\n });\r\n}\r\n","/**\r\n * Every model provider the assistant can talk to, behind one factory.\r\n *\r\n * Three wire formats cover them all: OpenAI Chat Completions (OpenAI and the\r\n * many servers that copy it: OpenRouter, Groq, Mistral, DeepSeek, xAI,\r\n * Together, Ollama, LM Studio, your own), Anthropic Messages and Google\r\n * Gemini. `createChatModel({ provider, apiKey, model })` picks the adapter;\r\n * `chatModelsFromEnv` builds one for each provider whose key is set;\r\n * `routedChatModel` serves several behind one model, addressed as\r\n * \"provider/model\".\r\n */\r\nimport { anthropicChatModel } from \"./anthropic.js\";\r\nimport { geminiChatModel } from \"./gemini.js\";\r\nimport { openAIChatModel } from \"./openai.js\";\r\nimport { ChatModelError, defineChatModel, type ChatModel, type FetchLike, type ModelEntry } from \"./types.js\";\r\n\r\nexport type ProviderApi = \"openai\" | \"anthropic\" | \"gemini\";\r\n\r\nexport interface AiProviderInfo {\r\n /** Id used in configs, env names and \"provider/model\" references. */\r\n id: string;\r\n label: string;\r\n /** Which wire format it speaks. */\r\n api: ProviderApi;\r\n /** API root; `openai-compatible` has none (you give it). */\r\n baseUrl?: string;\r\n /** Whether calls need an API key (local servers don't). */\r\n needsKey: boolean;\r\n /** Runs on the user's machine (Ollama, LM Studio): reachable from that machine only. */\r\n local?: boolean;\r\n /** Where a user creates a key. */\r\n keyUrl?: string;\r\n /** Env var prefix: `{PREFIX}_API_KEY`, `{PREFIX}_MODEL`, `{PREFIX}_BASE_URL`, `{PREFIX}_MODELS`. */\r\n env: string;\r\n /** Used when no model is named. Providers without one need a model picked (env or UI). */\r\n defaultModel?: string;\r\n /** A few good picks to show before the live list loads. */\r\n models?: ModelEntry[];\r\n /** Accepts calls straight from a browser (CORS) with the user's own key. */\r\n browser?: boolean;\r\n /** OpenAI-format servers: the output-cap field and whether `stream_options` is accepted. */\r\n maxTokensField?: \"max_completion_tokens\" | \"max_tokens\";\r\n streamUsage?: boolean;\r\n}\r\n\r\n/**\r\n * The built-in provider catalog. Model ids are suggestions as of release; the\r\n * live list comes from each provider (`ChatModel.listModels`).\r\n */\r\nexport const AI_PROVIDERS: readonly AiProviderInfo[] = [\r\n {\r\n id: \"openai\",\r\n label: \"OpenAI\",\r\n api: \"openai\",\r\n baseUrl: \"https://api.openai.com/v1\",\r\n needsKey: true,\r\n keyUrl: \"https://platform.openai.com/api-keys\",\r\n env: \"OPENAI\",\r\n defaultModel: \"gpt-5.4-mini\",\r\n models: [{ id: \"gpt-5.4-mini\" }, { id: \"gpt-6-luna\" }, { id: \"gpt-6.1-sol\" }],\r\n browser: true,\r\n },\r\n {\r\n id: \"anthropic\",\r\n label: \"Anthropic\",\r\n api: \"anthropic\",\r\n baseUrl: \"https://api.anthropic.com\",\r\n needsKey: true,\r\n keyUrl: \"https://console.anthropic.com/settings/keys\",\r\n env: \"ANTHROPIC\",\r\n defaultModel: \"claude-sonnet-5-5\",\r\n models: [\r\n { id: \"claude-sonnet-5-5\", label: \"Claude Sonnet 5.5\" },\r\n { id: \"claude-haiku-5-5\", label: \"Claude Haiku 5.5\" },\r\n { id: \"claude-opus-5-5\", label: \"Claude Opus 5.5\" },\r\n ],\r\n browser: true,\r\n },\r\n {\r\n id: \"gemini\",\r\n label: \"Google Gemini\",\r\n api: \"gemini\",\r\n baseUrl: \"https://generativelanguage.googleapis.com/v1beta\",\r\n needsKey: true,\r\n keyUrl: \"https://aistudio.google.com/apikey\",\r\n env: \"GEMINI\",\r\n defaultModel: \"gemini-3.8-flash\",\r\n models: [\r\n { id: \"gemini-3.8-flash\", label: \"Gemini 3.8 Flash\" },\r\n { id: \"gemini-3.5-flash-lite\", label: \"Gemini 3.5 Flash-Lite\" },\r\n { id: \"gemini-3.1-pro-preview\", label: \"Gemini 3.1 Pro (preview)\" },\r\n ],\r\n browser: true,\r\n },\r\n {\r\n id: \"openrouter\",\r\n label: \"OpenRouter\",\r\n api: \"openai\",\r\n baseUrl: \"https://openrouter.ai/api/v1\",\r\n needsKey: true,\r\n keyUrl: \"https://openrouter.ai/keys\",\r\n env: \"OPENROUTER\",\r\n defaultModel: \"openrouter/auto\",\r\n models: [{ id: \"openrouter/auto\", label: \"Auto (OpenRouter picks)\" }],\r\n browser: true,\r\n maxTokensField: \"max_tokens\",\r\n },\r\n { id: \"groq\", label: \"Groq\", api: \"openai\", baseUrl: \"https://api.groq.com/openai/v1\", needsKey: true, keyUrl: \"https://console.groq.com/keys\", env: \"GROQ\", maxTokensField: \"max_tokens\" },\r\n {\r\n id: \"mistral\",\r\n label: \"Mistral\",\r\n api: \"openai\",\r\n baseUrl: \"https://api.mistral.ai/v1\",\r\n needsKey: true,\r\n keyUrl: \"https://console.mistral.ai/api-keys\",\r\n env: \"MISTRAL\",\r\n defaultModel: \"mistral-medium-latest\",\r\n maxTokensField: \"max_tokens\",\r\n streamUsage: false,\r\n },\r\n {\r\n id: \"deepseek\",\r\n label: \"DeepSeek\",\r\n api: \"openai\",\r\n baseUrl: \"https://api.deepseek.com/v1\",\r\n needsKey: true,\r\n keyUrl: \"https://platform.deepseek.com/api_keys\",\r\n env: \"DEEPSEEK\",\r\n defaultModel: \"deepseek-chat\",\r\n maxTokensField: \"max_tokens\",\r\n },\r\n { id: \"xai\", label: \"xAI\", api: \"openai\", baseUrl: \"https://api.x.ai/v1\", needsKey: true, keyUrl: \"https://console.x.ai\", env: \"XAI\", maxTokensField: \"max_tokens\" },\r\n {\r\n id: \"together\",\r\n label: \"Together AI\",\r\n api: \"openai\",\r\n baseUrl: \"https://api.together.xyz/v1\",\r\n needsKey: true,\r\n keyUrl: \"https://api.together.ai/settings/api-keys\",\r\n env: \"TOGETHER\",\r\n maxTokensField: \"max_tokens\",\r\n },\r\n { id: \"ollama\", label: \"Ollama (local)\", api: \"openai\", baseUrl: \"http://localhost:11434/v1\", needsKey: false, local: true, env: \"OLLAMA\", maxTokensField: \"max_tokens\" },\r\n { id: \"lmstudio\", label: \"LM Studio (local)\", api: \"openai\", baseUrl: \"http://localhost:1234/v1\", needsKey: false, local: true, env: \"LMSTUDIO\", maxTokensField: \"max_tokens\" },\r\n { id: \"openai-compatible\", label: \"OpenAI-compatible\", api: \"openai\", needsKey: false, env: \"OPENAI_COMPATIBLE\", maxTokensField: \"max_tokens\" },\r\n];\r\n\r\n/** A provider from the catalog, or undefined. */\r\nexport function aiProvider(id: string): AiProviderInfo | undefined {\r\n return AI_PROVIDERS.find((p) => p.id === id);\r\n}\r\n\r\nexport interface CreateChatModelOptions {\r\n /** A catalog id (`AI_PROVIDERS`): \"openai\", \"anthropic\", \"gemini\", \"openrouter\", \"ollama\", \"openai-compatible\", … */\r\n provider: string;\r\n apiKey?: string | undefined;\r\n /** Model id (default: the provider's `defaultModel`; required for providers without one). */\r\n model?: string | undefined;\r\n /** Other models to offer (default: the catalog's suggestions). */\r\n models?: ModelEntry[];\r\n /** Override the API root (required for \"openai-compatible\"). */\r\n baseUrl?: string | undefined;\r\n headers?: Record<string, string>;\r\n /** Default request fields for every call (vendor-specific). */\r\n params?: Record<string, unknown>;\r\n stream?: boolean;\r\n maxRetries?: number;\r\n /** Calling from the browser with the user's own key (sets what the vendor needs for CORS). */\r\n browser?: boolean;\r\n /** Adapter id / label (default: the provider's). */\r\n id?: string;\r\n label?: string;\r\n fetch?: FetchLike;\r\n}\r\n\r\n/** One `ChatModel` for any provider in the catalog. */\r\nexport function createChatModel(options: CreateChatModelOptions): ChatModel {\r\n const info = aiProvider(options.provider);\r\n if (!info) throw new Error(`Unknown AI provider \"${options.provider}\". Known: ${AI_PROVIDERS.map((p) => p.id).join(\", \")}`);\r\n const model = options.model || info.defaultModel;\r\n if (!model) throw new ChatModelError(info.id, `pick a model (${info.label} has no default; set ${info.env}_MODEL or choose one)`, 400);\r\n if (info.needsKey && !options.apiKey) throw new ChatModelError(info.id, `needs an API key${info.keyUrl ? ` (create one at ${info.keyUrl})` : \"\"}`, 401);\r\n const baseUrl = options.baseUrl || info.baseUrl;\r\n if (!baseUrl) throw new ChatModelError(info.id, \"needs a base URL (the server's /v1 address)\", 400);\r\n\r\n const common = {\r\n model,\r\n models: options.models ?? info.models ?? [],\r\n baseUrl,\r\n id: options.id ?? info.id,\r\n label: options.label ?? info.label,\r\n ...(options.apiKey ? { apiKey: options.apiKey } : {}),\r\n ...(options.headers ? { headers: options.headers } : {}),\r\n ...(options.params ? { params: options.params } : {}),\r\n ...(options.stream !== undefined ? { stream: options.stream } : {}),\r\n ...(options.maxRetries !== undefined ? { maxRetries: options.maxRetries } : {}),\r\n ...(options.fetch ? { fetch: options.fetch } : {}),\r\n };\r\n switch (info.api) {\r\n case \"anthropic\":\r\n return anthropicChatModel({ ...common, ...(options.browser ? { browser: true } : {}) });\r\n case \"gemini\":\r\n return geminiChatModel(common);\r\n case \"openai\":\r\n return openAIChatModel({\r\n ...common,\r\n ...(info.maxTokensField ? { maxTokensField: info.maxTokensField } : {}),\r\n ...(info.streamUsage !== undefined ? { streamUsage: info.streamUsage } : {}),\r\n });\r\n }\r\n}\r\n\r\nexport interface ChatModelsFromEnvOptions {\r\n /** Only these providers (default: all). */\r\n providers?: string[];\r\n /** Told about providers that have a key but couldn't be set up (e.g. no model). */\r\n onSkip?: (provider: string, reason: string) => void;\r\n fetch?: FetchLike;\r\n}\r\n\r\n/**\r\n * A model for every provider configured in `env` (`process.env`, Vite's\r\n * `loadEnv`, a Worker's bindings):\r\n *\r\n * {PREFIX}_API_KEY enables a hosted provider (GEMINI also reads GOOGLE_API_KEY)\r\n * {PREFIX}_MODEL its default model (required where the catalog has none)\r\n * {PREFIX}_MODELS more models to offer, comma-separated\r\n * {PREFIX}_BASE_URL another API root (a proxy, a gateway, a regional endpoint)\r\n *\r\n * Local servers (OLLAMA, LMSTUDIO) are enabled by their _MODEL or _BASE_URL.\r\n * OPENAI_BASE_URL alone keeps its old meaning: an OpenAI-compatible server\r\n * (label OPENAI_LABEL), key optional. Order follows the catalog, so the\r\n * first entry makes a sensible default.\r\n */\r\nexport function chatModelsFromEnv(env: Record<string, string | undefined>, options: ChatModelsFromEnvOptions = {}): ChatModel[] {\r\n const out: ChatModel[] = [];\r\n const wanted = options.providers ? new Set(options.providers) : null;\r\n for (const info of AI_PROVIDERS) {\r\n if (wanted && !wanted.has(info.id)) continue;\r\n const p = info.env;\r\n let apiKey = env[`${p}_API_KEY`] || (info.id === \"gemini\" ? env.GOOGLE_API_KEY : undefined);\r\n let baseUrl = env[`${p}_BASE_URL`];\r\n let label: string | undefined;\r\n if (info.id === \"openai\" && env.OPENAI_BASE_URL) continue; // served as openai-compatible below\r\n if (info.id === \"openai-compatible\" && !baseUrl && env.OPENAI_BASE_URL) {\r\n baseUrl = env.OPENAI_BASE_URL;\r\n apiKey ||= env.OPENAI_API_KEY;\r\n label = env.OPENAI_LABEL || undefined;\r\n }\r\n const model = env[`${p}_MODEL`] || (info.id === \"openai-compatible\" ? env.OPENAI_MODEL : undefined);\r\n const enabled = info.needsKey ? !!apiKey : info.local ? !!(model || baseUrl) : !!baseUrl;\r\n if (!enabled) continue;\r\n const models = (env[`${p}_MODELS`] || (info.id === \"openai-compatible\" && env.OPENAI_BASE_URL ? env.OPENAI_MODELS : \"\") || \"\")\r\n .split(\",\")\r\n .map((m) => m.trim())\r\n .filter(Boolean)\r\n .map((m) => ({ id: m }));\r\n try {\r\n out.push(\r\n createChatModel({\r\n provider: info.id,\r\n ...(apiKey ? { apiKey } : {}),\r\n ...(model ? { model } : {}),\r\n ...(models.length ? { models } : {}),\r\n ...(baseUrl ? { baseUrl } : {}),\r\n ...(label ? { label } : {}),\r\n ...(options.fetch ? { fetch: options.fetch } : {}),\r\n }),\r\n );\r\n } catch (err) {\r\n options.onSkip?.(info.id, (err as Error).message);\r\n }\r\n }\r\n return out;\r\n}\r\n\r\n/** \"anthropic/claude-sonnet-5-5\" → { provider: \"anthropic\", model: \"claude-sonnet-5-5\" } when the prefix is a known id. */\r\nexport function splitModelRef(ref: string, providerIds: Iterable<string>): { provider?: string; model: string } {\r\n const slash = ref.indexOf(\"/\");\r\n if (slash > 0) {\r\n const provider = ref.slice(0, slash);\r\n for (const id of providerIds) if (id === provider) return { provider, model: ref.slice(slash + 1) };\r\n }\r\n return { model: ref };\r\n}\r\n\r\nexport interface RoutedChatModelOptions {\r\n id?: string;\r\n label?: string;\r\n /** Adapter id used when a request names no provider (default: the first). */\r\n default?: string;\r\n}\r\n\r\n/**\r\n * Several providers as one `ChatModel`. Its models are \"provider/model\"\r\n * (`anthropic/claude-sonnet-5-5`, `openrouter/meta-llama/…`); a request's\r\n * `model` picks the adapter and the model. A bare model id goes to the\r\n * adapter that lists it, else to the default adapter; no model → the default\r\n * adapter's default model.\r\n */\r\nexport function routedChatModel(models: readonly ChatModel[], options: RoutedChatModelOptions = {}): ChatModel {\r\n if (!models.length) throw new Error(\"routedChatModel needs at least one model\");\r\n const byId = new Map(models.map((m) => [m.id, m]));\r\n if (byId.size !== models.length) throw new Error(\"routedChatModel: adapter ids must be unique\");\r\n const fallback = (options.default && byId.get(options.default)) || models[0]!;\r\n const ordered = [fallback, ...models.filter((m) => m !== fallback)];\r\n const prefixed = (m: ChatModel, list: ModelEntry[]) => list.map((e) => ({ id: `${m.id}/${e.id}`, label: `${m.label} · ${e.label ?? e.id}` }));\r\n\r\n const resolve = (ref: string | undefined): { adapter: ChatModel; model?: string } => {\r\n if (!ref) return { adapter: fallback };\r\n const { provider, model } = splitModelRef(ref, byId.keys());\r\n if (provider) return { adapter: byId.get(provider)!, model };\r\n const lister = ordered.find((m) => m.models?.some((e) => e.id === ref));\r\n return { adapter: lister ?? fallback, model: ref };\r\n };\r\n\r\n return defineChatModel({\r\n id: options.id ?? \"router\",\r\n label: options.label ?? (models.length === 1 ? fallback.label : \"AI models\"),\r\n models: ordered.flatMap((m) => prefixed(m, m.models ?? [])),\r\n providers: ordered.map((m) => ({ id: m.id, label: m.label })),\r\n async complete(request, opts) {\r\n const { adapter, model } = resolve(request.model);\r\n const { model: _drop, ...rest } = request;\r\n return adapter.complete(model ? { ...rest, model } : rest, opts);\r\n },\r\n async listModels(opts) {\r\n const lists = await Promise.allSettled(ordered.map(async (m) => prefixed(m, m.listModels ? await m.listModels(opts) : (m.models ?? []))));\r\n return lists.flatMap((r, i) => (r.status === \"fulfilled\" ? r.value : prefixed(ordered[i]!, ordered[i]!.models ?? [])));\r\n },\r\n });\r\n}\r\n","/**\r\n * Keys stay on the server: run the real model adapter in your backend behind\r\n * `createChatHandler`, and give the browser `remoteChatModel(endpoint)` — a\r\n * proxy with the same `ChatModel` shape. The agent loop and the tools run in\r\n * the browser, next to the project; only model calls cross the network.\r\n *\r\n * GET {base}/ → { id, label, models, providers?, userKeys? }\r\n * GET {base}/models → { models } (live from the vendor when it can list them)\r\n * POST {base}/chat { request: ChatRequest } →\r\n * application/x-ndjson, one JSON object per line:\r\n * { \"type\": \"text\", \"delta\": \"…\" } (as it streams)\r\n * { \"type\": \"done\", \"response\": ChatResponse }\r\n * { \"type\": \"error\", \"error\": \"…\", \"status\": 401 }\r\n * errors before streaming → { error } with 400 / 401 / 403 / 404 / 413, or the vendor's status\r\n *\r\n * Bring your own key (`userKeys`): a request may carry the user's own\r\n * provider and key in headers (`x-ai-provider`, `x-ai-key`, optionally\r\n * `x-ai-base-url`). The handler builds a model for that one request and\r\n * forgets the key — it's never stored or logged. Use it for plans where\r\n * customers pay their AI vendor directly.\r\n *\r\n * Fetch-API handler (`Request` → `Response`): mounts in Node 18+, edge\r\n * runtimes, Next, Hono, Express (via an adapter).\r\n */\r\nimport { AI_PROVIDERS, aiProvider, createChatModel, splitModelRef, type AiProviderInfo, type CreateChatModelOptions } from \"./providers.js\";\r\nimport { ChatModelError, defaultFetch, type ChatModel, type ChatRequest, type ChatResponse, type FetchLike, type ModelEntry } from \"./types.js\";\r\n\r\n/** Header names a request uses to bring the user's own key. */\r\nexport const USER_KEY_HEADERS = { provider: \"x-ai-provider\", key: \"x-ai-key\", baseUrl: \"x-ai-base-url\" } as const;\r\n\r\n/** The user's own provider settings, as the browser sends them. */\r\nexport interface UserCredentials {\r\n provider: string;\r\n apiKey?: string;\r\n /** Only honored when the server allows it (`userKeys.allowBaseUrl`). */\r\n baseUrl?: string;\r\n}\r\n\r\nexport interface UserKeyOptions {\r\n /** Providers users may bring a key for (default: every hosted provider in the catalog). */\r\n providers?: string[];\r\n /**\r\n * Let users send their own API root (OpenAI-compatible servers, gateways).\r\n * Off by default: your server would call whatever URL a user sends.\r\n */\r\n allowBaseUrl?: boolean;\r\n /** Refuse requests without the user's own key, even when the server has a model. */\r\n required?: boolean;\r\n /** Build the model for a user's key (default `createChatModel`): add headers, pin params, wrap with limits. */\r\n create?: (options: CreateChatModelOptions, request: Request) => ChatModel;\r\n fetch?: FetchLike;\r\n}\r\n\r\nexport interface ChatHandlerOptions {\r\n /** Path prefix the handler is mounted at, e.g. \"/api/assistant\". */\r\n basePath?: string;\r\n /** Reject request bodies larger than this (default 2 MB). */\r\n maxBodyBytes?: number;\r\n /** Return a Response to refuse a request (auth, rate limits, plan checks); undefined to allow. */\r\n authorize?: (request: Request) => Promise<Response | undefined> | Response | undefined;\r\n /** Adjust each request server-side (pin a model, cap tokens, add instructions). */\r\n prepare?: (request: ChatRequest) => ChatRequest;\r\n /** Accept the user's own provider key per request (see the module note). Off by default. */\r\n userKeys?: boolean | UserKeyOptions;\r\n}\r\n\r\n/** What `GET {base}/` tells the browser about bring-your-own-key. */\r\nexport interface UserKeyInfo {\r\n providers: Pick<AiProviderInfo, \"id\" | \"label\" | \"needsKey\" | \"keyUrl\" | \"defaultModel\" | \"models\">[];\r\n allowBaseUrl: boolean;\r\n required: boolean;\r\n}\r\n\r\nconst hostedProviders = () => AI_PROVIDERS.filter((p) => !p.local && p.baseUrl).map((p) => p.id);\r\n\r\n/**\r\n * @param model The server's own model (its keys, from env or config), or\r\n * `null` when every user brings their own key (`userKeys` must be on).\r\n */\r\nexport function createChatHandler(model: ChatModel | null, options: ChatHandlerOptions = {}) {\r\n const base = (options.basePath ?? \"\").replace(/\\/+$/, \"\");\r\n const maxBody = options.maxBodyBytes ?? 2_000_000;\r\n const byok: UserKeyOptions | null = options.userKeys ? (options.userKeys === true ? {} : options.userKeys) : null;\r\n if (!model && !byok) throw new Error(\"createChatHandler needs a model, or userKeys so users can bring their own\");\r\n const allowed = new Set(byok?.providers ?? hostedProviders());\r\n if (byok?.allowBaseUrl) allowed.add(\"openai-compatible\");\r\n const userKeyInfo: UserKeyInfo | undefined = byok\r\n ? {\r\n providers: [...allowed].flatMap((id) => {\r\n const p = aiProvider(id);\r\n return p ? [{ id: p.id, label: p.label, needsKey: p.needsKey, ...(p.keyUrl ? { keyUrl: p.keyUrl } : {}), ...(p.defaultModel ? { defaultModel: p.defaultModel } : {}), ...(p.models ? { models: p.models } : {}) }] : [];\r\n }),\r\n allowBaseUrl: !!byok.allowBaseUrl,\r\n required: !!byok.required,\r\n }\r\n : undefined;\r\n\r\n /** The user's model for this request, null when they sent no key, or a Response refusing it. */\r\n const userModel = (request: Request, modelRef: string | undefined): ChatModel | Response | null => {\r\n if (!byok) return null;\r\n const provider = request.headers.get(USER_KEY_HEADERS.provider)?.trim();\r\n if (!provider) return null;\r\n if (!allowed.has(provider)) return json(403, { error: `bringing your own key isn't enabled for \"${provider}\" here` });\r\n const apiKey = request.headers.get(USER_KEY_HEADERS.key)?.trim() || undefined;\r\n const baseUrl = request.headers.get(USER_KEY_HEADERS.baseUrl)?.trim() || undefined;\r\n if (baseUrl && !byok.allowBaseUrl) return json(403, { error: \"custom API addresses aren't allowed here\" });\r\n const ref = modelRef ? splitModelRef(modelRef, [provider]) : undefined;\r\n const config: CreateChatModelOptions = {\r\n provider,\r\n ...(apiKey ? { apiKey } : {}),\r\n // Listing models needs no model; the catalog default (or a placeholder) is fine.\r\n model: ref?.model || aiProvider(provider)?.defaultModel || \"default\",\r\n ...(baseUrl ? { baseUrl } : {}),\r\n ...(byok.fetch ? { fetch: byok.fetch } : {}),\r\n };\r\n try {\r\n return byok.create ? byok.create(config, request) : createChatModel(config);\r\n } catch (err) {\r\n const status = err instanceof ChatModelError && err.status ? err.status : 400;\r\n return json(status, { error: (err as Error).message });\r\n }\r\n };\r\n\r\n const noModel = () => json(401, { error: \"Add your own API key to use AI here.\", needsKey: true });\r\n\r\n return async function handle(request: Request): Promise<Response | null> {\r\n const path = new URL(request.url).pathname;\r\n if (base && path !== base && !path.startsWith(`${base}/`)) return null;\r\n const rest = path.slice(base.length).replace(/\\/+$/, \"\");\r\n\r\n const refused = await options.authorize?.(request);\r\n if (refused) return refused;\r\n\r\n if (rest === \"\" && request.method === \"GET\") {\r\n return json(200, {\r\n id: model?.id ?? \"byok\",\r\n label: model?.label ?? \"Your API key\",\r\n models: model?.models ?? [],\r\n ...(model?.providers ? { providers: model.providers } : {}),\r\n ...(userKeyInfo ? { userKeys: userKeyInfo } : {}),\r\n ...(!model || byok?.required ? { needsKey: true } : {}),\r\n });\r\n }\r\n\r\n if (rest === \"/models\" && request.method === \"GET\") {\r\n const mine = userModel(request, undefined);\r\n if (mine instanceof Response) return mine;\r\n const target = mine ?? (byok?.required ? null : model);\r\n if (!target) return noModel();\r\n try {\r\n const models: ModelEntry[] = target.listModels ? await target.listModels({ signal: request.signal }) : (target.models ?? []);\r\n return json(200, { models });\r\n } catch (err) {\r\n const status = (err as { status?: number })?.status;\r\n return json(status && status >= 400 && status < 600 ? status : 502, { error: (err as Error)?.message ?? String(err) });\r\n }\r\n }\r\n\r\n if (rest !== \"/chat\" || request.method !== \"POST\") return json(404, { error: \"not found\" });\r\n\r\n const text = await request.text();\r\n if (text.length > maxBody) return json(413, { error: \"request too large\" });\r\n let chat: ChatRequest;\r\n try {\r\n chat = (JSON.parse(text) as { request: ChatRequest }).request;\r\n } catch {\r\n return json(400, { error: \"body must be JSON { request }\" });\r\n }\r\n if (!chat || !Array.isArray(chat.messages)) return json(400, { error: \"request.messages is required\" });\r\n if (options.prepare) chat = options.prepare(chat);\r\n\r\n const mine = userModel(request, chat.model);\r\n if (mine instanceof Response) return mine;\r\n const target = mine ?? (byok?.required ? null : model);\r\n if (!target) return noModel();\r\n if (mine && chat.model) chat = { ...chat, model: splitModelRef(chat.model, [request.headers.get(USER_KEY_HEADERS.provider)!.trim(), mine.id]).model };\r\n\r\n const encoder = new TextEncoder();\r\n const body = new ReadableStream<Uint8Array>({\r\n async start(controller) {\r\n const send = (value: unknown) => controller.enqueue(encoder.encode(`${JSON.stringify(value)}\\n`));\r\n try {\r\n const response = await target.complete(chat, { signal: request.signal, onText: (delta) => send({ type: \"text\", delta }) });\r\n send({ type: \"done\", response });\r\n } catch (err) {\r\n const status = (err as { status?: number })?.status;\r\n send({ type: \"error\", error: (err as Error)?.message ?? String(err), ...(status ? { status } : {}) });\r\n } finally {\r\n controller.close();\r\n }\r\n },\r\n });\r\n return new Response(body, { status: 200, headers: { \"content-type\": \"application/x-ndjson\", \"cache-control\": \"no-store\" } });\r\n };\r\n}\r\n\r\nconst json = (status: number, body: unknown) => new Response(JSON.stringify(body), { status, headers: { \"content-type\": \"application/json\" } });\r\n\r\n/** What the server said about itself (`GET {base}/`). */\r\nexport interface RemoteModelInfo {\r\n id: string;\r\n label: string;\r\n models?: ModelEntry[];\r\n providers?: { id: string; label: string }[];\r\n userKeys?: UserKeyInfo;\r\n /** The server has no model of its own (or requires user keys): calls need the user's key. */\r\n needsKey?: boolean;\r\n}\r\n\r\nexport interface RemoteChatModelOptions {\r\n fetch?: FetchLike;\r\n headers?: Record<string, string>;\r\n /** Skip the GET for model info (id/label/models default to \"remote\"). */\r\n info?: RemoteModelInfo;\r\n /**\r\n * The user's own provider key, read on every call (so a settings change\r\n * applies at once). Return nothing to use the server's model.\r\n */\r\n credentials?: () => UserCredentials | null | undefined;\r\n}\r\n\r\n/** Headers carrying the user's own key, for any request to a `createChatHandler` server. */\r\nexport function userKeyHeaders(credentials: UserCredentials | null | undefined): Record<string, string> {\r\n if (!credentials?.provider) return {};\r\n return {\r\n [USER_KEY_HEADERS.provider]: credentials.provider,\r\n ...(credentials.apiKey ? { [USER_KEY_HEADERS.key]: credentials.apiKey } : {}),\r\n ...(credentials.baseUrl ? { [USER_KEY_HEADERS.baseUrl]: credentials.baseUrl } : {}),\r\n };\r\n}\r\n\r\n/** The model a backend serves, as a browser-side `ChatModel` (plus what the server said about itself in `info`). */\r\nexport async function remoteChatModel(endpoint: string, options: RemoteChatModelOptions = {}): Promise<ChatModel & { info: RemoteModelInfo }> {\r\n const doFetch = options.fetch ?? defaultFetch();\r\n const root = endpoint.replace(/\\/+$/, \"\");\r\n let info = options.info;\r\n if (!info) {\r\n const res = await doFetch(`${root}/`, { headers: { Accept: \"application/json\", ...options.headers } });\r\n if (!res.ok) throw new ChatModelError(\"remote\", `HTTP ${res.status}`, res.status);\r\n info = (await res.json()) as RemoteModelInfo;\r\n }\r\n const { id, label, models, providers } = info;\r\n const headers = (extra: Record<string, string>) => ({ ...extra, ...options.headers, ...userKeyHeaders(options.credentials?.()) });\r\n\r\n return {\r\n id,\r\n label,\r\n info,\r\n ...(models ? { models } : {}),\r\n ...(providers ? { providers } : {}),\r\n async listModels(opts = {}) {\r\n const res = await doFetch(`${root}/models`, { headers: headers({ Accept: \"application/json\" }), ...(opts.signal ? { signal: opts.signal } : {}) });\r\n const body = (await res.json().catch(() => null)) as { models?: ModelEntry[]; error?: string } | null;\r\n if (!res.ok) throw new ChatModelError(id, body?.error ?? `HTTP ${res.status}`, res.status);\r\n return body?.models ?? [];\r\n },\r\n async complete(request, opts = {}): Promise<ChatResponse> {\r\n const res = await doFetch(`${root}/chat`, {\r\n method: \"POST\",\r\n headers: headers({ \"Content-Type\": \"application/json\", Accept: \"application/x-ndjson\" }),\r\n body: JSON.stringify({ request }),\r\n ...(opts.signal ? { signal: opts.signal } : {}),\r\n });\r\n if (!res.ok || !res.body) {\r\n const err = (await res.json().catch(() => null)) as { error?: string } | null;\r\n throw new ChatModelError(id, err?.error ?? `HTTP ${res.status}`, res.status);\r\n }\r\n const reader = res.body.getReader();\r\n const decoder = new TextDecoder();\r\n let buffer = \"\";\r\n let final: ChatResponse | undefined;\r\n const handle = (line: string) => {\r\n if (!line.trim()) return;\r\n const event = JSON.parse(line) as { type: string; delta?: string; response?: ChatResponse; error?: string; status?: number };\r\n if (event.type === \"text\" && event.delta) opts.onText?.(event.delta);\r\n else if (event.type === \"done\") final = event.response;\r\n else if (event.type === \"error\") throw new ChatModelError(id, event.error ?? \"failed\", event.status);\r\n };\r\n for (;;) {\r\n const { done, value } = await reader.read();\r\n if (value) buffer += decoder.decode(value, { stream: true });\r\n let nl: number;\r\n while ((nl = buffer.indexOf(\"\\n\")) >= 0) {\r\n handle(buffer.slice(0, nl));\r\n buffer = buffer.slice(nl + 1);\r\n }\r\n if (done) break;\r\n }\r\n handle(buffer);\r\n if (!final) throw new ChatModelError(id, \"the server closed the stream without a response\");\r\n return final;\r\n },\r\n };\r\n}\r\n","import type { ChatModel, ChatRequest } from \"./types.js\";\r\n\r\nexport interface RateLimitOptions {\r\n /** Tokens per rolling minute across every caller of this model (input + output). */\r\n tokensPerMinute?: number;\r\n /** Requests per rolling minute. */\r\n requestsPerMinute?: number;\r\n /** Token estimate for a request before it's sent (default: JSON length ÷ 4, plus room for the reply). */\r\n estimate?: (request: ChatRequest) => number;\r\n}\r\n\r\n/**\r\n * Wrap a model so calls wait for room under your provider's per-minute\r\n * limits instead of failing with 429s — for batch jobs (evals, bulk edits)\r\n * and for a server sharing one key. Each call reserves an estimate, which is\r\n * replaced by the real usage when the response reports it.\r\n */\r\nexport function rateLimitedChatModel(model: ChatModel, options: RateLimitOptions): ChatModel {\r\n const tpm = options.tokensPerMinute ?? Infinity;\r\n const rpm = options.requestsPerMinute ?? Infinity;\r\n const estimate = options.estimate ?? ((r: ChatRequest) => Math.ceil(JSON.stringify(r).length / 4) + (r.maxOutputTokens ?? 1000));\r\n const window: { at: number; tokens: number }[] = [];\r\n let gate: Promise<void> = Promise.resolve();\r\n\r\n const prune = (now: number) => {\r\n while (window.length && now - window[0]!.at >= 60_000) window.shift();\r\n };\r\n const used = () => window.reduce((n, e) => n + e.tokens, 0);\r\n\r\n /** Wait (in arrival order) until this request fits, then reserve it. */\r\n const reserve = (tokens: number, signal?: AbortSignal) => {\r\n const want = Math.min(tokens, tpm); // a single request bigger than the limit still goes, alone\r\n const turn = gate.then(async () => {\r\n for (;;) {\r\n if (signal?.aborted) throw signal.reason ?? new Error(\"aborted\");\r\n const now = Date.now();\r\n prune(now);\r\n if (window.length < rpm && used() + want <= tpm) break;\r\n const oldest = window[0];\r\n await new Promise((r) => setTimeout(r, oldest ? Math.max(50, 60_000 - (now - oldest.at)) : 50));\r\n }\r\n const entry = { at: Date.now(), tokens: want };\r\n window.push(entry);\r\n return entry;\r\n });\r\n gate = turn.then(\r\n () => undefined,\r\n () => undefined,\r\n );\r\n return turn;\r\n };\r\n\r\n return {\r\n ...model,\r\n async complete(request, opts = {}) {\r\n const entry = await reserve(estimate(request), opts.signal);\r\n const response = await model.complete(request, opts);\r\n const real = (response.usage?.inputTokens ?? 0) + (response.usage?.outputTokens ?? 0);\r\n if (real > 0) entry.tokens = real;\r\n return response;\r\n },\r\n };\r\n}\r\n","import { animationPreset, describeAnimation, describeProject, findCuts, keyframeTimeUs, readAnimation, type AnimationRecipe, type Clip, type ProjectDocument } from \"@miraiclip/core\";\r\n\r\n/**\r\n * The project as the model sees it: core's `describeProject`, plus what a\r\n * model needs to make RELATIVE edits (\"bigger\", \"louder\", \"keep the\r\n * entrance\") — track names, every clip's position, size and opacity, text\r\n * styling, volume, effects and its animation recipe.\r\n */\r\nexport function describeForAssistant(doc: ProjectDocument): string {\r\n const lines = [\r\n describeProject(doc),\r\n \"\",\r\n \"track names: \" +\r\n doc.trackOrder\r\n .map((id) => {\r\n const t = doc.tracks[id] as { name?: string; hidden?: boolean; muted?: boolean; solo?: boolean; locked?: boolean } | undefined;\r\n const flags = [t?.hidden && \"hidden\", t?.muted && \"muted\", t?.solo && \"solo\", t?.locked && \"locked\"].filter(Boolean);\r\n return `${id} = \"${t?.name ?? id}\"${flags.length ? ` (${flags.join(\", \")})` : \"\"}`;\r\n })\r\n .join(\", \") +\r\n \" (track flags muted / solo / locked / hidden: track/set-property)\",\r\n ];\r\n const cuts = findCuts(doc);\r\n lines.push(\r\n cuts.length\r\n ? `cuts (where one clip ends and the next starts on the same track; transitions go here): ${cuts.map((c) => `${n(c.atUs / 1e6)}s ${c.fromClipId}→${c.toClipId}${c.transition ? ` (${c.transition.kind})` : \"\"}`).join(\", \")}`\r\n : \"cuts: none\",\r\n );\r\n const clips = Object.values(doc.clips).sort((a, b) => a.trackId.localeCompare(b.trackId) || a.startUs - b.startUs);\r\n if (clips.length) {\r\n lines.push(\"clip details (transform x/y: 0.5 = center; scale 1 = full size; times in seconds):\");\r\n for (const clip of clips) lines.push(`- ${clip.id}: ${details(clip)}`);\r\n }\r\n return lines.join(\"\\n\");\r\n}\r\n\r\nconst n = (v: number) => Number(v.toFixed(3));\r\n\r\nfunction details(clip: Clip): string {\r\n const parts: string[] = [`${clip.kind} on ${clip.trackId}`, `${n(clip.startUs / 1e6)}–${n((clip.startUs + clip.durationUs) / 1e6)}s`];\r\n if (clip.kind !== \"audio\") {\r\n const t = clip.transform;\r\n parts.push(`x ${n(t.x)} y ${n(t.y)} scale ${n(t.scale)}${t.rotation ? ` rotation ${n(t.rotation)}°` : \"\"} opacity ${n(t.opacity)}`);\r\n }\r\n if (clip.kind === \"text\") {\r\n const c = clip as Clip & { fontSizePx: number; color: string; fontFamily: string; fontWeight?: number; textAlign?: string };\r\n parts.push(`font ${c.fontFamily} ${c.fontSizePx}px${c.fontWeight ? ` weight ${c.fontWeight}` : \"\"} color ${c.color}${c.textAlign ? ` align ${c.textAlign}` : \"\"}`);\r\n }\r\n if (clip.kind === \"caption\") {\r\n const c = clip as Clip & { words: { text: string }[]; style: { preset: string; color: string; highlightColor: string; backgroundColor?: string } };\r\n parts.push(\r\n `words \"${c.words.map((w) => w.text).join(\" \").slice(0, 80)}\" · style preset ${c.style.preset} color ${c.style.color} highlight ${c.style.highlightColor} box ${c.style.backgroundColor ?? \"none\"} (style.backgroundColor)`,\r\n );\r\n }\r\n if (clip.kind === \"html\") {\r\n const c = clip as Clip & { template: string; params: Record<string, unknown> };\r\n parts.push(`params ${JSON.stringify(c.params)} (template placeholders: ${htmlPlaceholders(c.template).join(\", \") || \"none\"})`);\r\n }\r\n if (\"volume\" in clip) {\r\n const c = clip as Clip & { volume: number; fadeInUs?: number; fadeOutUs?: number; trimStartUs: number };\r\n parts.push(`volume ${n(c.volume)}${c.fadeInUs ? ` fade-in ${n(c.fadeInUs / 1e6)}s` : \"\"}${c.fadeOutUs ? ` fade-out ${n(c.fadeOutUs / 1e6)}s` : \"\"}`);\r\n parts.push(`source from ${n(c.trimStartUs / 1e6)}s`);\r\n }\r\n if (clip.effects?.length) parts.push(`effects ${clip.effects.map((e) => `${e.kind}#${e.id}${e.enabled ? \"\" : \" (off)\"}`).join(\", \")}`);\r\n const volumeKeys = clip.animations?.volume ?? [];\r\n if (volumeKeys.length) parts.push(`volume keyframes ${volumeKeys.map((k) => `${n((clip.startUs + keyframeTimeUs(k, clip.durationUs)) / 1e6)}s=${n(k.value)}`).join(\", \")}`);\r\n const visual = Object.entries(clip.animations ?? {}).some(([prop, list]) => prop !== \"volume\" && list && list.length);\r\n if (visual) {\r\n const read = readClipAnimation(clip);\r\n parts.push(read.custom ? `custom keyframes (not presets): ${describeKeyframes(clip)}` : `animation ${describeAnimation(read.recipe)}`);\r\n }\r\n return parts.join(\" · \");\r\n}\r\n\r\n/** The `{{name}}` placeholders an html clip's template substitutes from its params. */\r\nexport function htmlPlaceholders(template: string): string[] {\r\n return [...new Set([...template.matchAll(/\\{\\{\\s*([\\w.-]+)\\s*\\}\\}/g)].map((m) => m[1]!))];\r\n}\r\n\r\n/**\r\n * core's `readAnimation`, stricter: a \"stale\" reading (In / Out plus a loop\r\n * whose spacing a trim broke) is only trusted when the loop's properties have\r\n * enough keyframes to be a loop. Two keyframes are someone's own animation,\r\n * and rewriting them as presets would destroy it.\r\n */\r\nexport function readClipAnimation(clip: Clip): { recipe: AnimationRecipe; custom: boolean } {\r\n const read = readAnimation(clip);\r\n if (read.custom) return { recipe: {}, custom: true };\r\n if (read.stale && read.recipe.loop) {\r\n const preset = animationPreset(read.recipe.loop.preset);\r\n const props = Object.keys(preset?.build(clip.transform, 1_000_000, \"linear\") ?? {}) as (keyof NonNullable<Clip[\"animations\"]>)[];\r\n const count = props.reduce((sum, prop) => sum + (clip.animations?.[prop]?.length ?? 0), 0);\r\n if (count < 4) return { recipe: {}, custom: true };\r\n }\r\n return { recipe: read.recipe, custom: false };\r\n}\r\n\r\n/** Visual keyframes per property, at timeline seconds: \"opacity 0.5s 0 → 1s 1; scale …\". */\r\nexport function describeKeyframes(clip: Clip): string {\r\n return Object.entries(clip.animations ?? {})\r\n .filter(([prop, list]) => prop !== \"volume\" && list?.length)\r\n .map(([prop, list]) => `${prop} ${list!.map((k) => `${n((clip.startUs + keyframeTimeUs(k, clip.durationUs)) / 1e6)}s ${n(k.value)}`).join(\" → \")}`)\r\n .join(\"; \");\r\n}\r\n","/**\r\n * Catch fields a command would silently ignore. Core's schemas strip unknown\r\n * keys, so `clip/move { at: … }` or `track/set-property { property, value }`\r\n * validate fine and change nothing — and a model then reports an edit that\r\n * never happened. Checking payload keys against the command's JSON Schema\r\n * turns those into errors the model can fix.\r\n */\r\ntype Schema = {\r\n type?: string;\r\n properties?: Record<string, Schema>;\r\n required?: string[];\r\n anyOf?: Schema[];\r\n oneOf?: Schema[];\r\n const?: unknown;\r\n additionalProperties?: boolean | Schema;\r\n};\r\n\r\n/** Object schemas a value may match (unions flattened). */\r\nfunction branches(schema: Schema): Schema[] {\r\n const alts = schema.anyOf ?? schema.oneOf;\r\n return alts ? alts.flatMap(branches) : [schema];\r\n}\r\n\r\n/** `path` entries for keys the schema doesn't declare (one level into nested objects). */\r\nfunction unknownIn(schema: Schema, value: Record<string, unknown>, prefix = \"\"): string[] {\r\n const props = schema.properties;\r\n if (!props || (schema.additionalProperties !== undefined && schema.additionalProperties !== false)) return [];\r\n const out: string[] = [];\r\n for (const [key, v] of Object.entries(value)) {\r\n const sub = props[key];\r\n if (!sub) out.push(prefix + key);\r\n else if (!prefix && v && typeof v === \"object\" && !Array.isArray(v)) {\r\n const objectBranch = branches(sub).find((b) => b.properties);\r\n if (objectBranch) out.push(...unknownIn(objectBranch, v as Record<string, unknown>, `${key}.`));\r\n }\r\n }\r\n return out;\r\n}\r\n\r\nexport interface UnknownFields {\r\n unknown: string[];\r\n /** The fields the command does take (for the error message). */\r\n allowed: string[];\r\n}\r\n\r\n/** Unknown payload fields for a command, judged against the best-matching schema branch. */\r\nexport function unknownFields(schema: unknown, payload: unknown): UnknownFields | null {\r\n if (!schema || !payload || typeof payload !== \"object\" || Array.isArray(payload)) return null;\r\n const value = payload as Record<string, unknown>;\r\n let candidates = branches(schema as Schema).filter((b) => b.properties);\r\n if (candidates.length === 0) return null;\r\n // A discriminated union: prefer the branch whose `kind` constant matches.\r\n const byKind = candidates.filter((b) => b.properties!.kind?.const !== undefined && b.properties!.kind.const === value.kind);\r\n if (byKind.length) candidates = byKind;\r\n let best: UnknownFields | null = null;\r\n for (const b of candidates) {\r\n const unknown = unknownIn(b, value);\r\n if (!best || unknown.length < best.unknown.length) best = { unknown, allowed: Object.keys(b.properties!) };\r\n }\r\n return best && best.unknown.length ? best : null;\r\n}\r\n","/**\r\n * Checks an html clip's template the way the renderer will read it: as XML\r\n * (it rasterizes through an SVG `foreignObject`). Markup that isn't\r\n * well-formed renders nothing, silently, so the assistant rejects it up front\r\n * with an error the model can fix.\r\n */\r\n\r\nconst VOID = new Set([\"br\", \"img\", \"hr\", \"input\", \"meta\", \"link\", \"source\", \"wbr\", \"col\", \"area\", \"base\", \"embed\", \"track\", \"param\"]);\r\nconst NAMED = new Set([\"amp\", \"lt\", \"gt\", \"quot\", \"apos\"]);\r\nconst HTML_ENTITY_HINTS: Record<string, string> = { nbsp: \"&#160;\", copy: \"&#169;\", reg: \"&#174;\", trade: \"&#8482;\", mdash: \"&#8212;\", ndash: \"&#8211;\", hellip: \"&#8230;\", rarr: \"&#8594;\", larr: \"&#8592;\", times: \"&#215;\", middot: \"&#183;\", bull: \"&#8226;\", rsquo: \"&#8217;\", lsquo: \"&#8216;\", ldquo: \"&#8220;\", rdquo: \"&#8221;\" };\r\n\r\n/** Why the markup won't parse as XML, or undefined when it's well-formed. */\r\nexport function markupProblem(markup: string): string | undefined {\r\n const stack: { name: string; at: number }[] = [];\r\n const n = markup.length;\r\n let i = 0;\r\n const near = (at: number) => JSON.stringify(markup.slice(Math.max(0, at - 30), at + 30));\r\n const entity = (at: number): string | undefined => {\r\n const m = /^&(#\\d+|#x[0-9a-fA-F]+|[A-Za-z][A-Za-z0-9]*);/.exec(markup.slice(at, at + 40));\r\n if (!m) return `a bare \"&\" near ${near(at)}: write &amp;`;\r\n const name = m[1]!;\r\n if (name.startsWith(\"#\") || NAMED.has(name)) return undefined;\r\n const hint = HTML_ENTITY_HINTS[name];\r\n return `\"&${name};\" is an HTML entity, which this XML markup doesn't know: ${hint ? `write ${hint}` : \"use a numeric reference (&#NNNN;) or the character itself\"}`;\r\n };\r\n\r\n while (i < n) {\r\n const c = markup[i]!;\r\n if (c === \"&\") {\r\n const problem = entity(i);\r\n if (problem) return problem;\r\n i = markup.indexOf(\";\", i) + 1;\r\n continue;\r\n }\r\n if (c !== \"<\") {\r\n i++;\r\n continue;\r\n }\r\n if (markup.startsWith(\"<!--\", i)) {\r\n const end = markup.indexOf(\"-->\", i + 4);\r\n if (end < 0) return `a comment is never closed near ${near(i)}`;\r\n i = end + 3;\r\n continue;\r\n }\r\n if (markup.startsWith(\"<![CDATA[\", i)) {\r\n const end = markup.indexOf(\"]]>\", i);\r\n if (end < 0) return `a CDATA section is never closed near ${near(i)}`;\r\n i = end + 3;\r\n continue;\r\n }\r\n if (markup.startsWith(\"<?\", i)) {\r\n const end = markup.indexOf(\"?>\", i);\r\n if (end < 0) return `\"<?\" is never closed near ${near(i)}`;\r\n i = end + 2;\r\n continue;\r\n }\r\n if (markup[i + 1] === \"!\") return `remove \"<!…\" declarations (no DOCTYPE) near ${near(i)}`;\r\n\r\n if (markup[i + 1] === \"/\") {\r\n const m = /^<\\/([A-Za-z_][\\w.:-]*)\\s*>/.exec(markup.slice(i, i + 200));\r\n if (!m) return `a broken closing tag near ${near(i)}`;\r\n const name = m[1]!;\r\n const open = stack.pop();\r\n if (!open) return `\"</${name}>\" closes nothing near ${near(i)}`;\r\n if (open.name !== name) return unclosed(open, `before \"</${name}>\"`);\r\n i += m[0].length;\r\n continue;\r\n }\r\n\r\n const tag = /^<([A-Za-z_][\\w.:-]*)/.exec(markup.slice(i, i + 200));\r\n if (!tag) return `a bare \"<\" near ${near(i)}: write &lt; (also inside CSS, e.g. '&lt;integer&gt;')`;\r\n const name = tag[1]!;\r\n const start = i;\r\n i += tag[0].length;\r\n const seen = new Set<string>();\r\n for (;;) {\r\n while (i < n && /\\s/.test(markup[i]!)) i++;\r\n if (i >= n) return `the <${name}> tag is never finished near ${near(start)}`;\r\n if (markup.startsWith(\"/>\", i)) {\r\n i += 2;\r\n break;\r\n }\r\n if (markup[i] === \">\") {\r\n i++;\r\n if (name.toLowerCase() === \"style\") {\r\n // CSS is text: \"<\" is never a tag here (e.g. '<integer>' in @property).\r\n const close = markup.slice(i).search(/<\\/style\\s*>/i);\r\n const body = close < 0 ? markup.slice(i) : markup.slice(i, i + close);\r\n const lt = body.indexOf(\"<\");\r\n if (lt >= 0) return `a bare \"<\" in the <style> near ${near(i + lt)}: in CSS write &lt; and &gt; (e.g. '&lt;integer&gt;')`;\r\n for (let a = body.indexOf(\"&\"); a >= 0; a = body.indexOf(\"&\", a + 1)) {\r\n const problem = entity(i + a);\r\n if (problem) return problem;\r\n }\r\n if (close < 0) return unclosed({ name, at: start }, \"at the end\");\r\n i += close;\r\n }\r\n stack.push({ name, at: start });\r\n break;\r\n }\r\n const attr = /^([A-Za-z_:][\\w.:-]*)/.exec(markup.slice(i, i + 200));\r\n if (!attr) return `unexpected \"${markup[i]}\" inside the <${name}> tag near ${near(i)}`;\r\n const attrName = attr[1]!;\r\n if (seen.has(attrName)) return `the <${name}> tag has \"${attrName}\" twice near ${near(start)}`;\r\n seen.add(attrName);\r\n i += attrName.length;\r\n while (i < n && /\\s/.test(markup[i]!)) i++;\r\n if (markup[i] !== \"=\") return `attribute \"${attrName}\" on <${name}> needs a quoted value (${attrName}=\"…\") near ${near(start)}`;\r\n i++;\r\n while (i < n && /\\s/.test(markup[i]!)) i++;\r\n const q = markup[i];\r\n if (q !== '\"' && q !== \"'\") return `quote the value of \"${attrName}\" on <${name}> near ${near(i)}`;\r\n const end = markup.indexOf(q, i + 1);\r\n if (end < 0) return `the value of \"${attrName}\" on <${name}> is never closed near ${near(i)}`;\r\n const value = markup.slice(i + 1, end);\r\n const lt = value.indexOf(\"<\");\r\n if (lt >= 0) return `\"<\" inside the \"${attrName}\" value on <${name}>: write &lt; near ${near(i + 1 + lt)}`;\r\n for (let a = value.indexOf(\"&\"); a >= 0; a = value.indexOf(\"&\", a + 1)) {\r\n const problem = entity(i + 1 + a);\r\n if (problem) return problem;\r\n }\r\n i = end + 1;\r\n }\r\n }\r\n const open = stack.pop();\r\n return open ? unclosed(open, \"at the end\") : undefined;\r\n\r\n function unclosed(open: { name: string; at: number }, where: string) {\r\n const fix = VOID.has(open.name.toLowerCase()) ? `write <${open.name} …/>` : `close it with </${open.name}>`;\r\n return `<${open.name}> is never closed (${where}) near ${near(open.at)}: ${fix}`;\r\n }\r\n}\r\n\r\n/** `asset:<id>` references in a template. */\r\nexport function assetRefs(markup: string): string[] {\r\n return [...new Set([...markup.matchAll(/asset:([\\w-]+)/g)].map((m) => m[1]!))];\r\n}\r\n","import {\r\n ANIMATION_EASINGS,\r\n ANIMATION_PRESETS,\r\n animationCommands,\r\n applyBeatGrid,\r\n applyCommands,\r\n planSoundFromEvents,\r\n commandCatalog,\r\n builtinTransitionParamSchemas,\r\n transitionHoldsFrames,\r\n describeAnimation,\r\n EFFECT_CATALOG,\r\n findCuts,\r\n fitAnimation,\r\n toToolDefinitions,\r\n tryDispatch,\r\n type AnimationEasing,\r\n type AnimationRecipe,\r\n type AnimationSlot,\r\n type AudioClip,\r\n type HtmlClip,\r\n type Command,\r\n type Cut,\r\n type Project,\r\n type ProjectDocument,\r\n} from \"@miraiclip/core\";\r\nimport { describeForAssistant, describeKeyframes, htmlPlaceholders, readClipAnimation } from \"./state.js\";\r\nimport { unknownFields } from \"./strict.js\";\r\nimport { assetRefs, markupProblem } from \"./markup.js\";\r\nimport type { ChatImage, ToolSpec } from \"./types.js\";\r\n\r\n/** What a tool's `run` gets. `project` is the turn's working copy. */\r\nexport interface ToolContext {\r\n project: Project;\r\n signal?: AbortSignal;\r\n}\r\n\r\n/**\r\n * A tool's answer. `changes` are human-readable lines for the app's \"what\r\n * changed\" list. `images` go to the model with the result, so it can look at\r\n * what it made (rendered frames); models that can't see get the text only.\r\n */\r\nexport type ToolOutput =\r\n | { ok: true; result?: unknown; changes?: string[]; images?: ChatImage[] }\r\n | { ok: false; error: unknown };\r\n\r\nexport interface AssistantTool extends ToolSpec {\r\n run(input: Record<string, unknown>, ctx: ToolContext): Promise<ToolOutput> | ToolOutput;\r\n}\r\n\r\nexport function defineTool(tool: AssistantTool): AssistantTool {\r\n if (!/^[a-zA-Z0-9_-]{1,64}$/.test(tool.name)) throw new Error(`Tool name must match ^[a-zA-Z0-9_-]{1,64}$ (got \"${tool.name}\")`);\r\n return tool;\r\n}\r\n\r\n/**\r\n * Adopt tools that come as definitions plus one runner — e.g.\r\n * `@miraiclip/audio-sources`' `audioToolDefinitions` + `runAudioTool`.\r\n * Accepts `{ name, description, input_schema }`, OpenAI function tools or\r\n * Gemini function declarations.\r\n */\r\nexport function toolsFromDefinitions(\r\n definitions: readonly unknown[],\r\n run: (name: string, input: Record<string, unknown>, ctx: ToolContext) => Promise<{ ok: boolean; result?: unknown; error?: unknown }>,\r\n options: { changes?: (name: string, input: Record<string, unknown>, result: unknown) => string[] | undefined } = {},\r\n): AssistantTool[] {\r\n return definitions.map((d) => {\r\n const def = d as { name?: string; description?: string; input_schema?: Record<string, unknown>; parametersJsonSchema?: Record<string, unknown>; function?: { name: string; description?: string; parameters?: Record<string, unknown> } };\r\n const name = def.function?.name ?? def.name!;\r\n return defineTool({\r\n name,\r\n description: def.function?.description ?? def.description ?? \"\",\r\n inputSchema: def.function?.parameters ?? def.input_schema ?? def.parametersJsonSchema ?? { type: \"object\", properties: {} },\r\n async run(input, ctx) {\r\n const out = await run(name, input, ctx);\r\n if (!out.ok) return { ok: false, error: out.error ?? \"failed\" };\r\n const changes = options.changes?.(name, input, out.result);\r\n return { ok: true, result: out.result, ...(changes?.length ? { changes } : {}) };\r\n },\r\n });\r\n });\r\n}\r\n\r\nconst S = 1_000_000;\r\n/** Slide presets are named by motion; models do better naming the edge, so these map edges to presets. */\r\nconst SLIDE_IN: Record<string, string> = { \"from-right\": \"left\", \"from-left\": \"right\", \"from-bottom\": \"up\", \"from-top\": \"down\" };\r\nconst SLIDE_OUT: Record<string, string> = { \"to-left\": \"left\", \"to-right\": \"right\", \"to-top\": \"up\", \"to-bottom\": \"down\" };\r\nfunction slideAliases(input: Record<string, unknown>): Record<string, unknown> {\r\n const out: Record<string, unknown> = {};\r\n if (typeof input.in === \"string\" && SLIDE_IN[input.in]) out.in = SLIDE_IN[input.in];\r\n if (typeof input.out === \"string\" && SLIDE_OUT[input.out]) out.out = SLIDE_OUT[input.out];\r\n return out;\r\n}\r\n/** The html template set_background uses: one `{{color}}` fill. */\r\nexport const BACKGROUND_TEMPLATE = '<div style=\"width:100%;height:100%;background:{{color}}\"></div>';\r\nconst seconds = (us: number) => `${Math.round(us / 100_000) / 10}s`;\r\nconst n3 = (v: number) => Number(v.toFixed(3));\r\nconst toUs = (s: unknown) => (typeof s === \"number\" && Number.isFinite(s) ? Math.round(s * S) : undefined);\r\n\r\n/* ---------- describing commands for people ---------- */\r\n\r\nconst PHRASES: Record<string, (p: Record<string, unknown>) => string> = {\r\n \"clip/add\": (p) =>\r\n p.kind === \"text\" ? `Added text \"${String(p.text ?? \"\").slice(0, 40)}\" at ${seconds(Number(p.startUs))}` : `Added a ${p.kind} clip at ${seconds(Number(p.startUs))}`,\r\n \"clip/remove\": (p) => `Removed clip ${p.clipId}`,\r\n \"clip/move\": (p) => `Moved clip ${p.clipId}${p.startUs !== undefined ? ` to ${seconds(Number(p.startUs))}` : \"\"}`,\r\n \"clip/trim\": (p) => `Trimmed clip ${p.clipId}`,\r\n \"clip/split\": (p) => `Split clip ${p.clipId} at ${seconds(Number(p.atUs))}`,\r\n \"clip/duplicate\": (p) => `Duplicated clip ${p.clipId}`,\r\n \"clip/set-property\": (p) => `Changed ${Object.keys(p).filter((k) => k !== \"clipId\").join(\", \")} of clip ${p.clipId}`,\r\n \"track/add\": (p) => `Added a ${p.kind} track`,\r\n \"track/remove\": (p) => `Removed track ${p.id}`,\r\n \"track/set-property\": (p) => `Changed track ${p.trackId}: ${Object.keys(p).filter((k) => k !== \"trackId\").join(\", \")}`,\r\n \"effect/add\": (p) => `Added ${p.kind} to clip ${p.clipId}`,\r\n \"effect/update\": (p) => `Adjusted an effect on clip ${p.clipId}`,\r\n \"effect/remove\": (p) => `Removed an effect from clip ${p.clipId}`,\r\n \"transition/add\": (p) => `Added a ${p.kind} transition`,\r\n \"transition/remove\": () => \"Removed a transition\",\r\n \"project/set-settings\": () => \"Changed the project settings\",\r\n};\r\n\r\n/** One line per command, for a \"what changed\" list (keyframe edits fold into one line per clip). */\r\nexport function describeCommands(commands: readonly Command[]): string[] {\r\n const lines: string[] = [];\r\n const keyframed = new Set<string>();\r\n for (const c of commands) {\r\n const p = (c.payload ?? {}) as Record<string, unknown>;\r\n if (c.type.startsWith(\"keyframe/\")) {\r\n const id = String(p.clipId);\r\n if (!keyframed.has(id)) lines.push(`Changed the animation of clip ${id}`);\r\n keyframed.add(id);\r\n continue;\r\n }\r\n const phrase = PHRASES[c.type];\r\n lines.push(phrase ? phrase(p) : `${c.type}`);\r\n }\r\n return lines;\r\n}\r\n\r\n/* ---------- the editor tools ---------- */\r\n\r\nconst US_NOTE = \"Times in commands are integer MICROSECONDS (1 s = 1000000).\";\r\n\r\nexport interface EditorToolsOptions {\r\n /** Transition kinds to offer (default: the built-ins). Add your registered custom kinds. */\r\n transitionKinds?: string[];\r\n /** The command catalog to offer in apply_commands (default: the built-ins). Pass `project.commandCatalog()` when you register custom commands. */\r\n commandCatalog?: Record<string, unknown>;\r\n /**\r\n * Runs the continuous-shot check over the document (e.g. `checkShot` from `@miraiclip/server-export`, or the app's\r\n * in-browser equivalent). The `check_sequence` tool is offered only when this is given, because the check needs a browser.\r\n */\r\n checkSequence?: (doc: ProjectDocument, options: { clipId?: string }) => Promise<unknown>;\r\n}\r\n\r\n/**\r\n * The tools that edit a Miraiclip project: read state, look up a command's\r\n * schema, apply command batches, and two high-level tools (transitions,\r\n * animation presets) that use the same logic as the editor's panels.\r\n */\r\nexport function editorTools(options: EditorToolsOptions = {}): AssistantTool[] {\r\n const transitionKinds = options.transitionKinds ?? Object.keys(builtinTransitionParamSchemas);\r\n const presetIds = (slot: AnimationSlot) => ANIMATION_PRESETS.filter((p) => p.slot === slot).map((p) => p.id.split(\":\")[1]!);\r\n\r\n const checkTools: AssistantTool[] = options.checkSequence\r\n ? [\r\n defineTool({\r\n name: \"check_sequence\",\r\n description:\r\n 'Check a stage sequence (data-libs=\"stage\") as one continuous shot: for every label it reports whether something survives into the next beat (carried / anchored / bare), subjects that leave the frame (data-keep-in-frame), uneven or metronomic beat spacing and stray hard cuts, as warnings with times and a hint each. Run it after composing or editing a sequence and fix the bare boundaries; warnings never block an export.',\r\n inputSchema: {\r\n type: \"object\",\r\n properties: { clipId: { type: \"string\", description: \"Check only this html clip (default: every html clip).\" } },\r\n additionalProperties: false,\r\n },\r\n async run(input, { project }) {\r\n try {\r\n const result = await options.checkSequence!(project.toJSON(), typeof input.clipId === \"string\" ? { clipId: input.clipId } : {});\r\n return { ok: true, result };\r\n } catch (error) {\r\n return { ok: false, error: error instanceof Error ? error.message : String(error) };\r\n }\r\n },\r\n }),\r\n defineTool({\r\n name: \"place_sfx_from_events\",\r\n description:\r\n \"Put sound effects on a continuous shot's hits. Reads the sound moments the sequence declared (tl.hit, presses, impacts, shakes, carry landings), adds one audio clip at each (one sound per hit kind, so the film keeps one material set) and optionally ducks a music clip under them. Add the sounds as audio assets first (prefer CC0; keep to one material). Audio clips have no pan yet, so where on screen a hit happened is reported but not applied. \" + US_NOTE,\r\n inputSchema: {\r\n type: \"object\",\r\n properties: {\r\n clipId: { type: \"string\", description: \"The html sequence clip.\" },\r\n sounds: { type: \"object\", description: 'Audio asset id per hit kind, e.g. { \"impact\": \"thud\", \"click\": \"tick\" }. Kinds: impact, click, whoosh, land, shake.', additionalProperties: { type: \"string\" } },\r\n trackId: { type: \"string\", description: 'Audio track for the effects (default \"sfx\", created if missing).' },\r\n musicClipId: { type: \"string\", description: \"A music audio clip to lower under the hits.\" },\r\n volume: { type: \"number\", minimum: 0, maximum: 4 },\r\n duckTo: { type: \"number\", minimum: 0, maximum: 1, description: \"Music level during a hit, as a fraction of its own (default 0.35).\" },\r\n },\r\n required: [\"clipId\", \"sounds\"],\r\n additionalProperties: false,\r\n },\r\n async run(input, { project }) {\r\n const doc = project.toJSON();\r\n const clipId = String(input.clipId ?? \"\");\r\n const seq = doc.clips[clipId];\r\n if (!seq || seq.kind !== \"html\") return { ok: false, error: `\"${clipId}\" is not an html clip` };\r\n let report: { events?: { name: string; t: number; kind: string; pan: number }[] } | null | undefined;\r\n try {\r\n const checked = (await options.checkSequence!(doc, { clipId })) as { clips?: { clipId: string; report: typeof report; skipped?: string }[] };\r\n const entry = checked.clips?.find((c) => c.clipId === clipId);\r\n report = entry?.report;\r\n if (!report) return { ok: false, error: entry?.skipped ?? \"the sequence could not be checked\" };\r\n } catch (error) {\r\n return { ok: false, error: error instanceof Error ? error.message : String(error) };\r\n }\r\n const sounds: Record<string, { assetId: string; durationUs: number }> = {};\r\n for (const [kind, assetId] of Object.entries((input.sounds ?? {}) as Record<string, string>)) {\r\n const asset = doc.assets[assetId];\r\n if (!asset || asset.kind !== \"audio\") return { ok: false, error: `sounds.${kind}: \"${assetId}\" is not an audio asset` };\r\n sounds[kind] = { assetId, durationUs: (asset as { durationUs?: number }).durationUs ?? 1_000_000 };\r\n }\r\n const music = input.musicClipId !== undefined ? doc.clips[String(input.musicClipId)] : undefined;\r\n if (input.musicClipId !== undefined && (!music || music.kind !== \"audio\")) return { ok: false, error: `\"${String(input.musicClipId)}\" is not an audio clip` };\r\n const trackId = typeof input.trackId === \"string\" ? input.trackId : \"sfx\";\r\n const plan = planSoundFromEvents({\r\n events: report.events ?? [],\r\n sequenceStartUs: seq.startUs,\r\n sounds,\r\n trackId,\r\n existingClipIds: Object.keys(doc.clips),\r\n ...(typeof input.volume === \"number\" ? { volume: input.volume } : {}),\r\n ...(typeof input.duckTo === \"number\" ? { duckTo: input.duckTo } : {}),\r\n ...(music ? { music: { clipId: music.id, startUs: music.startUs, durationUs: music.durationUs, volume: (music as AudioClip).volume } } : {}),\r\n });\r\n if (!plan.placements.length) return { ok: true, result: { placed: 0, skipped: plan.skipped, note: \"nothing placed: the sequence has no hits with a sound; add tl.hit calls or more kinds in sounds\" } };\r\n const commands: Command[] = [...(doc.tracks[trackId] ? [] : [{ type: \"track/add\", payload: { id: trackId, kind: \"audio\", name: \"SFX\" } }]), ...plan.commands];\r\n const out = applyChecked(project, commands);\r\n if (!out.ok) return out;\r\n return { ...out, result: { placed: plan.placements.length, placements: plan.placements, skipped: plan.skipped, ducks: plan.ducks.length }, changes: [`Placed ${plan.placements.length} sound effects on ${trackId}${plan.ducks.length ? ` and ducked the music under ${plan.ducks.length} hit${plan.ducks.length === 1 ? \"\" : \"s\"}` : \"\"}`] };\r\n },\r\n }),\r\n ]\r\n : [];\r\n\r\n const beatTool = defineTool({\r\n name: \"set_beat_grid\",\r\n description:\r\n \"Write a music beat grid onto a sequence so S.beat(n) and S.downbeat(n) land on the music. Pass beats and downbeats in seconds from the start of the music, and the music clip so the offset between music and sequence is applied. \" + US_NOTE,\r\n inputSchema: {\r\n type: \"object\",\r\n properties: {\r\n clipId: { type: \"string\", description: \"The html sequence clip.\" },\r\n beats: { type: \"array\", items: { type: \"number\" } },\r\n downbeats: { type: \"array\", items: { type: \"number\" } },\r\n musicClipId: { type: \"string\", description: \"The music audio clip the grid belongs to.\" },\r\n offsetSeconds: { type: \"number\", description: \"Instead of musicClipId: how far into the music the sequence starts.\" },\r\n },\r\n required: [\"clipId\", \"beats\"],\r\n additionalProperties: false,\r\n },\r\n run(input, { project }) {\r\n const doc = project.toJSON();\r\n const clipId = String(input.clipId ?? \"\");\r\n const seq = doc.clips[clipId];\r\n if (!seq || seq.kind !== \"html\") return { ok: false, error: `\"${clipId}\" is not an html clip` };\r\n let offset = typeof input.offsetSeconds === \"number\" ? input.offsetSeconds : 0;\r\n if (input.musicClipId !== undefined) {\r\n const music = doc.clips[String(input.musicClipId)];\r\n if (!music || music.kind !== \"audio\") return { ok: false, error: `\"${String(input.musicClipId)}\" is not an audio clip` };\r\n offset = (seq.startUs - music.startUs + (music as AudioClip).trimStartUs) / 1_000_000;\r\n }\r\n const grid = applyBeatGrid((seq as HtmlClip).template, { beats: (input.beats ?? []) as number[], ...(Array.isArray(input.downbeats) ? { downbeats: input.downbeats as number[] } : {}) }, offset);\r\n if (!grid.found) return { ok: false, error: 'the template has no stage root (data-libs=\"stage\")' };\r\n const out = applyChecked(project, [{ type: \"clip/set-property\", payload: { clipId, template: grid.template } }]);\r\n if (!out.ok) return out;\r\n return { ...out, result: { beats: grid.beats, downbeats: grid.downbeats, offsetSeconds: offset }, changes: [`Set a beat grid on ${clipId}: ${grid.beats} beats, ${grid.downbeats} downbeats`] };\r\n },\r\n });\r\n\r\n return [\r\n ...checkTools,\r\n defineTool({\r\n name: \"get_state\",\r\n description: `The project as it is now (after your edits so far): settings, assets, tracks with clips (ids, kinds, time ranges), transitions, and each clip's position, size, opacity, text style, volume, effects and animation. ${US_NOTE}`,\r\n inputSchema: {\r\n type: \"object\",\r\n properties: { includeJson: { type: \"boolean\", description: \"Also return the full document JSON (verbose; only for exact field values).\" } },\r\n additionalProperties: false,\r\n },\r\n run(input, { project }) {\r\n const summary = describeForAssistant(project.toJSON());\r\n return { ok: true, result: input.includeJson ? { summary, document: project.toJSON() } : summary };\r\n },\r\n }),\r\n\r\n defineTool({\r\n name: \"get_command_schema\",\r\n description: \"The JSON Schema of one editing command's payload. Look it up before using a command in apply_commands for the first time.\",\r\n inputSchema: {\r\n type: \"object\",\r\n properties: { type: { type: \"string\", description: 'Command type, e.g. \"clip/add\".' } },\r\n required: [\"type\"],\r\n additionalProperties: false,\r\n },\r\n run(input, { project }) {\r\n const catalog = project.commandCatalog();\r\n const schema = catalog[String(input.type)];\r\n return schema ? { ok: true, result: schema } : { ok: false, error: { error: `unknown command \"${input.type}\"`, validTypes: Object.keys(catalog).sort() } };\r\n },\r\n }),\r\n\r\n defineTool({\r\n name: \"apply_commands\",\r\n description:\r\n \"Apply editing commands as one all-or-nothing batch. On failure nothing changes and the error names the failing index and why (invalid payload issues, or the engine's rejection reason) — fix it and retry. \" +\r\n `Give new clips, tracks and assets your own readable ids. ${US_NOTE}`,\r\n inputSchema: applyCommandsSchema(options.commandCatalog ?? commandCatalog()),\r\n run(input, { project }) {\r\n const commands = input.commands as Command[] | undefined;\r\n if (!Array.isArray(commands) || commands.length === 0) return { ok: false, error: \"commands must be a non-empty array of { type, payload }\" };\r\n return applyChecked(project, commands);\r\n },\r\n }),\r\n\r\n defineTool({\r\n name: \"add_transition\",\r\n description:\r\n \"Add (or replace, or remove) a transition on cuts — where one clip ends exactly where the next starts on the same video track. Pick the cut by its two clip ids, by a time near it, or all cuts. \" +\r\n \"A transition overlaps both clips around the cut, so its length is capped by the spare footage there; cuts with none are skipped and reported. \" +\r\n \"zoomThrough is the exception: it dives into a region of the outgoing clip and arrives in the next one, holding the first/last frames, so it needs no spare footage (length capped by the shorter clip). \" +\r\n 'When a video clip directly follows another on its track, \"fade into / dissolve to / slide in / wipe to\" that clip means a transition on the cut before it, not an animation. ' +\r\n 'Slide / wipe direction is the direction of motion: \"from the left\" = right.',\r\n inputSchema: {\r\n type: \"object\",\r\n properties: {\r\n kind: { type: \"string\", enum: [...transitionKinds, \"none\"], description: '\"none\" removes the transition.' },\r\n fromClipId: { type: \"string\", description: \"The clip ending at the cut.\" },\r\n toClipId: { type: \"string\", description: \"The clip starting at the cut.\" },\r\n nearSeconds: { type: \"number\", description: \"Pick the cut closest to this time instead.\" },\r\n allCuts: { type: \"boolean\", description: \"Apply to every cut.\" },\r\n durationSeconds: { type: \"number\", minimum: 0.1, maximum: 3, description: \"Default 0.6.\" },\r\n direction: { type: \"string\", enum: [\"left\", \"right\", \"up\", \"down\"], description: \"wipe / slide only.\" },\r\n rect: {\r\n type: \"object\",\r\n description: \"zoomThrough only: the region to dive into, as fractions of the frame (default the centre quarter).\",\r\n properties: { x: { type: \"number\" }, y: { type: \"number\" }, w: { type: \"number\" }, h: { type: \"number\" } },\r\n required: [\"x\", \"y\", \"w\", \"h\"],\r\n additionalProperties: false,\r\n },\r\n ease: { type: \"string\", enum: [\"linear\", \"in\", \"out\", \"inOut\"], description: \"zoomThrough only (default inOut).\" },\r\n },\r\n required: [\"kind\"],\r\n additionalProperties: false,\r\n },\r\n run(input, { project }) {\r\n const cuts = findCuts(project.getState().doc);\r\n if (cuts.length === 0) return { ok: false, error: \"There are no cuts: transitions need two clips touching on the same video track.\" };\r\n let targets: Cut[];\r\n if (input.allCuts === true) targets = cuts;\r\n else if (typeof input.fromClipId === \"string\" || typeof input.toClipId === \"string\") {\r\n targets = cuts.filter((c) => (!input.fromClipId || c.fromClipId === input.fromClipId) && (!input.toClipId || c.toClipId === input.toClipId));\r\n if (targets.length === 0) return { ok: false, error: { error: noCutReason(project, input.fromClipId, input.toClipId), cuts: cuts.map(cutInfo) } };\r\n } else {\r\n const at = toUs(input.nearSeconds) ?? project.getState().playheadUs;\r\n const nearest = cuts.reduce((a, b) => (Math.abs(b.atUs - at) < Math.abs(a.atUs - at) ? b : a));\r\n if (input.nearSeconds !== undefined && Math.abs(nearest.atUs - at) > 1.5 * S) {\r\n return {\r\n ok: false,\r\n error: { error: `There is no cut near ${seconds(at)} (the nearest is at ${seconds(nearest.atUs)}). Transitions only go on cuts; nothing was added. Use one of these cuts, or tell the user there isn't one there.`, cuts: cuts.map(cutInfo) },\r\n };\r\n }\r\n targets = [nearest];\r\n }\r\n if (targets.length === 0) return { ok: false, error: { error: \"no cut matches\", cuts: cuts.map(cutInfo) } };\r\n const kind = String(input.kind);\r\n const wanted = toUs(input.durationSeconds) ?? 600_000;\r\n const commands: Command[] = [];\r\n const changes: string[] = [];\r\n const skipped: unknown[] = [];\r\n for (const cut of targets) {\r\n if (cut.transition) commands.push({ type: \"transition/remove\", payload: { transitionId: cut.transition.id } });\r\n if (kind === \"none\") {\r\n if (cut.transition) changes.push(`Removed the transition at ${seconds(cut.atUs)}`);\r\n continue;\r\n }\r\n const durationUs = Math.min(wanted, transitionHoldsFrames(kind) ? cut.maxHoldTransitionUs : cut.maxTransitionUs);\r\n if (durationUs < 100_000) {\r\n skipped.push({ ...cutInfo(cut), reason: `no spare footage (${cut.short === \"from\" ? \"the first clip ends where its media ends\" : cut.short === \"to\" ? \"the second clip starts at its media's start\" : \"both clips use all their media\"})` });\r\n if (cut.transition) commands.pop();\r\n continue;\r\n }\r\n const params =\r\n input.direction && (kind === \"wipe\" || kind === \"slide\")\r\n ? { direction: input.direction }\r\n : kind === \"zoomThrough\" && (input.rect || input.ease)\r\n ? { ...(input.rect ? { rect: input.rect } : {}), ...(input.ease ? { ease: input.ease } : {}) }\r\n : undefined;\r\n commands.push({ type: \"transition/add\", payload: { kind, fromClipId: cut.fromClipId, toClipId: cut.toClipId, durationUs, ...(params ? { params } : {}) } });\r\n changes.push(`${cut.transition ? \"Replaced the transition with\" : \"Added\"} ${kind} at ${seconds(cut.atUs)} (${seconds(durationUs)})`);\r\n }\r\n if (commands.length) {\r\n const result = applyCommands(project, commands);\r\n if (!result.ok) return { ok: false, error: result };\r\n }\r\n return { ok: true, result: { changed: changes.length, skipped }, changes };\r\n },\r\n }),\r\n\r\n defineTool({\r\n name: \"animate_clip\",\r\n description:\r\n \"Give a visual clip (video, image, text) an entrance (in), a loop while on screen, and/or an exit (out), as keyframes. Slots you leave out keep their current animation; \\\"none\\\" removes one; reset:true removes all. \" +\r\n `In/out presets: ${presetIds(\"in\").join(\", \")}. Loops: ${presetIds(\"loop\").join(\", \")}. Exits stay on the clip's end through trims. ` +\r\n 'Slides: give the edge, in: \"from-right\" (enters from the right edge, moving left), \"from-left\", \"from-top\", \"from-bottom\"; out: \"to-left\", \"to-right\", \"to-top\", \"to-bottom\". ' +\r\n \"For text, logos and overlays; between two video clips prefer add_transition. The clip's current animation is in get_state; pass only the slots to change. \" +\r\n \"Fading the whole VIDEO in at the start and out at the end is two calls: in on the FIRST clip of the main video track, out on the LAST one (an out on the first clip would fade to black at its cut).\",\r\n inputSchema: {\r\n type: \"object\",\r\n properties: {\r\n clipId: { type: \"string\" },\r\n in: { type: \"string\", enum: [...presetIds(\"in\"), ...Object.keys(SLIDE_IN), \"none\"], description: 'For slides, prefer \"from-left\" / \"from-right\" / \"from-top\" / \"from-bottom\": the edge the clip enters from.' },\r\n loop: { type: \"string\", enum: [...presetIds(\"loop\"), \"none\"] },\r\n out: { type: \"string\", enum: [...presetIds(\"out\"), ...Object.keys(SLIDE_OUT), \"none\"], description: 'For slides, prefer \"to-left\" / \"to-right\" / \"to-top\" / \"to-bottom\": the edge the clip leaves by.' },\r\n inSeconds: { type: \"number\", minimum: 0.2, maximum: 3, description: \"Default 0.6.\" },\r\n outSeconds: { type: \"number\", minimum: 0.2, maximum: 3, description: \"Default 0.6.\" },\r\n easing: { type: \"string\", enum: ANIMATION_EASINGS.map((e) => e.id), description: \"Default smooth.\" },\r\n reset: { type: \"boolean\" },\r\n },\r\n required: [\"clipId\"],\r\n additionalProperties: false,\r\n },\r\n run(input, { project }) {\r\n input = { ...input, ...slideAliases(input) };\r\n const clip = project.getState().doc.clips[String(input.clipId)];\r\n if (!clip) return { ok: false, error: `no clip \"${input.clipId}\"` };\r\n if (clip.kind === \"audio\") return { ok: false, error: \"audio clips have no picture to animate (use volume or fades)\" };\r\n const reading = readClipAnimation(clip);\r\n if (reading.custom && input.reset !== true) {\r\n return {\r\n ok: false,\r\n error: `${clip.id} has its own keyframes (${describeKeyframes(clip)}), not presets; presets would overwrite them. To remove one property's animation, apply_commands keyframe/clear { clipId, property }. To edit them, use set_keyframes. To replace them all with presets, pass reset: true. Nothing was changed.`,\r\n };\r\n }\r\n const current = input.reset === true ? {} : reading.recipe;\r\n const next: AnimationRecipe = { ...current };\r\n const easing = (typeof input.easing === \"string\" ? input.easing : \"smooth\") as AnimationEasing;\r\n for (const slot of [\"in\", \"loop\", \"out\"] as const) {\r\n const name = input[slot];\r\n if (name === undefined) continue;\r\n if (name === \"none\") {\r\n delete next[slot];\r\n continue;\r\n }\r\n const durationUs = slot === \"loop\" ? 0 : toUs(input[slot === \"in\" ? \"inSeconds\" : \"outSeconds\"]) ?? current[slot]?.durationUs ?? 600_000;\r\n next[slot] = { preset: `${slot}:${name}`, durationUs, easing: slot === \"loop\" ? \"linear\" : easing };\r\n }\r\n const recipe = fitAnimation(next, clip.durationUs);\r\n const result = applyCommands(project, animationCommands(clip, recipe));\r\n if (!result.ok) return { ok: false, error: result };\r\n const summary = describeAnimation(recipe) || \"no animation\";\r\n const atCut = input.in && input.in !== \"none\" && clip.kind === \"video\" ? findCuts(project.getState().doc).find((c) => c.toClipId === clip.id) : undefined;\r\n return {\r\n ok: true,\r\n result: {\r\n clipId: clip.id,\r\n animation: summary,\r\n ...(atCut ? { note: `${clip.id} starts at a cut right after ${atCut.fromClipId}. If the user meant going from ${atCut.fromClipId} into ${clip.id} (\"fade into\", \"slide in\", \"dissolve to\"), that is add_transition on this cut: undo this entrance with in:\"none\" and add the transition.` } : {}),\r\n },\r\n changes: [`${clip.id}: ${summary}`],\r\n };\r\n },\r\n }),\r\n\r\n defineTool({\r\n name: \"add_effect\",\r\n description:\r\n \"Add a visual effect to one or more clips (video, image, text). Kinds, with their params (min–max, default): \" +\r\n EFFECT_CATALOG.map((e) => `${e.kind} (${e.label}${e.params.length ? `: ${e.params.map((p) => (p.type === \"number\" ? `${p.key} ${p.min}–${p.max}, ${p.default}` : `${p.key} color`)).join(\"; \")}` : \"\"})`).join(\", \") +\r\n \". Omitted params take their defaults. Black & white is mono; brightness, contrast, saturation and hue are colorAdjust params (-1–1, 0 = unchanged).\",\r\n inputSchema: {\r\n type: \"object\",\r\n properties: {\r\n clipIds: { type: \"array\", items: { type: \"string\" }, minItems: 1 },\r\n kind: { type: \"string\", enum: EFFECT_CATALOG.map((e) => e.kind) },\r\n params: { type: \"object\", description: \"Effect params by key; see the list above.\" },\r\n },\r\n required: [\"clipIds\", \"kind\"],\r\n additionalProperties: false,\r\n },\r\n run(input, { project }) {\r\n const ids = (input.clipIds as string[]) ?? [];\r\n const doc = project.getState().doc;\r\n const missing = ids.filter((id) => !doc.clips[id]);\r\n if (missing.length) return { ok: false, error: `no clip ${missing.map((m) => `\"${m}\"`).join(\", \")}` };\r\n const audio = ids.filter((id) => doc.clips[id]!.kind === \"audio\");\r\n if (audio.length) return { ok: false, error: `audio clips have no picture: ${audio.join(\", \")}` };\r\n const params = input.params && typeof input.params === \"object\" ? (input.params as Record<string, unknown>) : undefined;\r\n const out = applyChecked(project, ids.map((clipId) => ({ type: \"effect/add\", payload: { clipId, kind: input.kind, ...(params ? { params } : {}) } })));\r\n if (!out.ok) return out;\r\n const label = EFFECT_CATALOG.find((e) => e.kind === input.kind)?.label ?? String(input.kind);\r\n return { ...out, changes: [`Added ${label} to ${ids.join(\", \")}`] };\r\n },\r\n }),\r\n\r\n defineTool({\r\n name: \"remove_effects\",\r\n description: \"Delete effects from clips: one kind, or every effect when kind is omitted. To turn effects off but keep them, use set_effects_enabled.\",\r\n inputSchema: {\r\n type: \"object\",\r\n properties: {\r\n clipIds: { type: \"array\", items: { type: \"string\" }, minItems: 1 },\r\n kind: { type: \"string\", description: \"Only effects of this kind (default: all).\" },\r\n },\r\n required: [\"clipIds\"],\r\n additionalProperties: false,\r\n },\r\n run(input, { project }) {\r\n const doc = project.getState().doc;\r\n const commands: Command[] = [];\r\n for (const id of (input.clipIds as string[]) ?? []) {\r\n const clip = doc.clips[id];\r\n if (!clip) return { ok: false, error: `no clip \"${id}\"` };\r\n for (const e of clip.effects ?? []) if (!input.kind || e.kind === input.kind) commands.push({ type: \"effect/remove\", payload: { clipId: id, effectId: e.id } });\r\n }\r\n if (!commands.length) return { ok: true, result: { removed: 0, note: \"those clips had no matching effects\" } };\r\n const out = applyChecked(project, commands);\r\n return out.ok ? { ...out, result: { removed: commands.length }, changes: [`Removed ${commands.length} effect${commands.length > 1 ? \"s\" : \"\"}`] } : out;\r\n },\r\n }),\r\n\r\n defineTool({\r\n name: \"trim_clip\",\r\n description:\r\n \"Trim a clip like an editor's edge drag, in seconds. removeFromStart cuts that much off the beginning (the clip's end stays put; for video/audio the source moves on too). \" +\r\n \"removeFromEnd cuts off the end. setDuration sets the length, keeping the start. startSeconds / endSeconds put the clip's edges at those timeline times (e.g. 0 and the composition length to cover the whole video; a video/audio clip's source shifts with its start edge). \" +\r\n \"ripple:true shifts later clips on the same track to close (or open) the gap.\",\r\n inputSchema: {\r\n type: \"object\",\r\n properties: {\r\n clipId: { type: \"string\" },\r\n removeFromStart: { type: \"number\", exclusiveMinimum: 0 },\r\n removeFromEnd: { type: \"number\", exclusiveMinimum: 0 },\r\n setDuration: { type: \"number\", exclusiveMinimum: 0 },\r\n startSeconds: { type: \"number\", minimum: 0, description: \"Timeline time for the clip's start edge.\" },\r\n endSeconds: { type: \"number\", exclusiveMinimum: 0, description: \"Timeline time for the clip's end edge.\" },\r\n ripple: { type: \"boolean\" },\r\n },\r\n required: [\"clipId\"],\r\n additionalProperties: false,\r\n },\r\n run(input, { project }) {\r\n const doc = project.getState().doc;\r\n const clip = doc.clips[String(input.clipId)];\r\n if (!clip) return { ok: false, error: `no clip \"${input.clipId}\"` };\r\n const head = toUs(input.removeFromStart) ?? 0;\r\n const tail = toUs(input.removeFromEnd) ?? 0;\r\n const ripple = input.ripple === true;\r\n const startAt = toUs(input.startSeconds);\r\n const endAt = toUs(input.endSeconds);\r\n if ((startAt !== undefined || endAt !== undefined) && (head || tail)) return { ok: false, error: \"use either startSeconds / endSeconds or removeFromStart / removeFromEnd, not both\" };\r\n let duration = clip.durationUs - head - tail;\r\n const set = toUs(input.setDuration);\r\n if (set) duration = set;\r\n // Without ripple, cutting the head keeps the clip's end in place; with ripple the clip keeps its start and later clips move up.\r\n let start = ripple ? clip.startUs : clip.startUs + head;\r\n let sourceShift = head;\r\n if (startAt !== undefined) {\r\n start = startAt;\r\n sourceShift = startAt - clip.startUs;\r\n if (endAt === undefined && !set) duration = clip.startUs + clip.durationUs - startAt;\r\n }\r\n if (endAt !== undefined) duration = endAt - start;\r\n if (duration <= 0) return { ok: false, error: `that leaves the clip ${seconds(duration)} long; it is ${seconds(clip.durationUs)} (${seconds(clip.startUs)}–${seconds(clip.startUs + clip.durationUs)})` };\r\n const trimmable = \"trimStartUs\" in clip;\r\n if (trimmable && (clip as { trimStartUs: number }).trimStartUs + sourceShift < 0) {\r\n return { ok: false, error: `${clip.id}'s media has only ${seconds((clip as { trimStartUs: number }).trimStartUs)} before its current start, so its start edge can move back at most that far` };\r\n }\r\n const commands: Command[] = [\r\n {\r\n type: \"clip/trim\",\r\n payload: {\r\n clipId: clip.id,\r\n startUs: start,\r\n durationUs: duration,\r\n ...(trimmable && sourceShift ? { trimStartUs: (clip as { trimStartUs: number }).trimStartUs + sourceShift } : {}),\r\n },\r\n },\r\n ];\r\n if (ripple) {\r\n const oldEnd = clip.startUs + clip.durationUs;\r\n const delta = start + duration - oldEnd;\r\n const later = Object.values(doc.clips)\r\n .filter((c) => c.trackId === clip.trackId && c.id !== clip.id && c.startUs >= oldEnd)\r\n .sort((x, y) => (delta < 0 ? x.startUs - y.startUs : y.startUs - x.startUs));\r\n for (const c of later) commands.push({ type: \"clip/move\", payload: { clipId: c.id, startUs: Math.max(0, c.startUs + delta) } });\r\n }\r\n const out = applyChecked(project, commands);\r\n if (!out.ok) return out;\r\n const after = project.getState().doc.clips[clip.id]!;\r\n return { ...out, changes: [`Trimmed ${clip.id} to ${seconds(after.startUs)}–${seconds(after.startUs + after.durationUs)}`] };\r\n },\r\n }),\r\n\r\n defineTool({\r\n name: \"set_effects_enabled\",\r\n description: \"Turn effects off (or back on) without deleting them, keeping their settings: one kind, or every effect when kind is omitted. To delete effects use remove_effects.\",\r\n inputSchema: {\r\n type: \"object\",\r\n properties: {\r\n clipIds: { type: \"array\", items: { type: \"string\" }, minItems: 1 },\r\n kind: { type: \"string\", description: \"Only effects of this kind (default: all).\" },\r\n enabled: { type: \"boolean\" },\r\n },\r\n required: [\"clipIds\", \"enabled\"],\r\n additionalProperties: false,\r\n },\r\n run(input, { project }) {\r\n const doc = project.getState().doc;\r\n const enabled = input.enabled === true;\r\n const commands: Command[] = [];\r\n for (const id of (input.clipIds as string[]) ?? []) {\r\n const clip = doc.clips[id];\r\n if (!clip) return { ok: false, error: `no clip \"${id}\"` };\r\n for (const e of clip.effects ?? []) {\r\n if ((!input.kind || e.kind === input.kind) && e.enabled !== enabled) commands.push({ type: \"effect/update\", payload: { clipId: id, effectId: e.id, enabled } });\r\n }\r\n }\r\n if (!commands.length) return { ok: true, result: { changed: 0, note: `no matching effects that are ${enabled ? \"off\" : \"on\"}` } };\r\n const out = applyChecked(project, commands);\r\n return out.ok ? { ...out, result: { changed: commands.length }, changes: [`Turned ${enabled ? \"on\" : \"off\"} ${commands.length} effect${commands.length > 1 ? \"s\" : \"\"}`] } : out;\r\n },\r\n }),\r\n\r\n defineTool({\r\n name: \"set_keyframes\",\r\n description:\r\n \"Animate one property of a clip so it CHANGES over time, with keyframes at TIMELINE seconds (the same times as get_state). Replaces that property's existing keyframes (including a preset's) unless keep:true. Between points the value moves smoothly; before the first and after the last it holds. \" +\r\n \"Properties: x, y (0–1, 0.5 = center), scale (1 = full size), rotation (degrees; 360 = one full turn, so a static 360 looks like 0), opacity (0–1), volume (0–1, audio/video). \" +\r\n \"Not for constant values (\\\"rotate 45°\\\", \\\"music at 30%\\\", \\\"louder\\\": set those with clip/set-property) or for removing an animation (keyframe/clear). Use it for motion and timed changes the presets don't cover: a spin (rotation 0 → 360 over the clip), a pan, ducking music (volume: normal → low just before a range, low → normal just after), or fading audio between two times.\",\r\n inputSchema: {\r\n type: \"object\",\r\n properties: {\r\n clipId: { type: \"string\" },\r\n property: { type: \"string\", enum: [\"x\", \"y\", \"scale\", \"rotation\", \"opacity\", \"volume\"] },\r\n points: {\r\n type: \"array\",\r\n minItems: 1,\r\n items: {\r\n type: \"object\",\r\n properties: { atSeconds: { type: \"number\", minimum: 0 }, value: { type: \"number\" } },\r\n required: [\"atSeconds\", \"value\"],\r\n additionalProperties: false,\r\n },\r\n },\r\n easing: { type: \"string\", enum: [\"linear\", \"easeIn\", \"easeOut\", \"easeInOut\", \"hold\"], description: \"Curve between points (default linear).\" },\r\n keep: { type: \"boolean\", description: \"Keep the property's other keyframes (default false: replace them).\" },\r\n },\r\n required: [\"clipId\", \"property\", \"points\"],\r\n additionalProperties: false,\r\n },\r\n run(input, { project }) {\r\n const clip = project.getState().doc.clips[String(input.clipId)];\r\n if (!clip) return { ok: false, error: `no clip \"${input.clipId}\"` };\r\n const property = String(input.property);\r\n if (property === \"volume\" && !(\"volume\" in clip)) return { ok: false, error: `${clip.id} is a ${clip.kind} clip and has no volume` };\r\n if (property !== \"volume\" && clip.kind === \"audio\") return { ok: false, error: \"audio clips only animate volume\" };\r\n const end = clip.startUs + clip.durationUs;\r\n const points = (input.points as { atSeconds: number; value: number }[]).map((p) => ({ atUs: toUs(p.atSeconds)!, value: p.value }));\r\n const outside = points.filter((p) => p.atUs < clip.startUs - 1000 || p.atUs > end + 1000);\r\n if (outside.length) return { ok: false, error: `${clip.id} runs ${seconds(clip.startUs)}–${seconds(end)}; points must be inside that range (got ${outside.map((p) => seconds(p.atUs)).join(\", \")})` };\r\n if (input.keep !== true && new Set(points.map((p) => p.value)).size < 2) {\r\n const how = property === \"volume\" ? \"clip/set-property { volume }\" : `clip/set-property { transform: { ${property} } }`;\r\n return {\r\n ok: false,\r\n error: `Keyframes are for a value that CHANGES over time; ${points.length === 1 ? \"one point\" : \"these points\"} hold${points.length === 1 ? \"s\" : \"\"} a single value. For a constant ${property}, set it directly with apply_commands ${how}. To remove an animation, apply keyframe/clear { clipId, property }. Nothing was changed.`,\r\n };\r\n }\r\n const commands: Command[] = [];\r\n if (input.keep !== true && (clip.animations?.[property as \"x\"]?.length ?? 0) > 0) commands.push({ type: \"keyframe/clear\", payload: { clipId: clip.id, property } });\r\n const easing = typeof input.easing === \"string\" ? input.easing : \"linear\";\r\n for (const p of points) {\r\n commands.push({ type: \"keyframe/set\", payload: { clipId: clip.id, property, timeUs: Math.min(clip.durationUs, Math.max(0, p.atUs - clip.startUs)), value: p.value, easing } });\r\n }\r\n const out = applyChecked(project, commands);\r\n if (!out.ok) return out;\r\n const list = points.map((p) => `${seconds(p.atUs)} ${n3(p.value)}`).join(\" → \");\r\n return { ...out, changes: [`${clip.id}: ${property} ${list}`] };\r\n },\r\n }),\r\n\r\n defineTool({\r\n name: \"set_background\",\r\n description:\r\n \"A solid color background behind everything (an html clip on a Background track at the very bottom). Adds one, recolors or re-times the existing one, or removes it with color \\\"none\\\". \" +\r\n \"Covers the whole video unless fromSeconds / toSeconds say otherwise. Only visible where nothing opaque is on top (behind scaled-down, faded or transparent clips, or where there's no footage).\",\r\n inputSchema: {\r\n type: \"object\",\r\n properties: {\r\n color: { type: \"string\", description: 'CSS color, e.g. \"#1E3A8A\", or \"none\" to remove the background.' },\r\n fromSeconds: { type: \"number\", minimum: 0 },\r\n toSeconds: { type: \"number\", exclusiveMinimum: 0 },\r\n },\r\n required: [\"color\"],\r\n additionalProperties: false,\r\n },\r\n run(input, { project }) {\r\n const doc = project.getState().doc;\r\n const color = String(input.color).trim();\r\n const existing = Object.values(doc.clips).find(\r\n (c) => c.kind === \"html\" && ((c as { template?: string }).template === BACKGROUND_TEMPLATE || doc.tracks[c.trackId]?.name === \"Background\"),\r\n );\r\n if (color.toLowerCase() === \"none\") {\r\n if (!existing) return { ok: true, result: { note: \"there is no background to remove\" } };\r\n const commands: Command[] = [{ type: \"clip/remove\", payload: { clipId: existing.id } }];\r\n if (!Object.values(doc.clips).some((c) => c.trackId === existing.trackId && c.id !== existing.id)) commands.push({ type: \"track/remove\", payload: { id: existing.trackId } });\r\n const out = applyChecked(project, commands);\r\n return out.ok ? { ...out, changes: [\"Removed the background\"] } : out;\r\n }\r\n let endUs = 0;\r\n for (const c of Object.values(doc.clips)) if (c.id !== existing?.id) endUs = Math.max(endUs, c.startUs + c.durationUs);\r\n const startUs = toUs(input.fromSeconds) ?? (existing && input.toSeconds === undefined ? existing.startUs : 0);\r\n const stopUs = toUs(input.toSeconds) ?? (existing && input.fromSeconds === undefined ? existing.startUs + existing.durationUs : endUs || 10 * S);\r\n if (stopUs <= startUs) return { ok: false, error: `toSeconds must be after fromSeconds (got ${seconds(startUs)}–${seconds(stopUs)})` };\r\n const commands: Command[] = [];\r\n if (existing) {\r\n const params = existing as { template: string; params: Record<string, unknown> };\r\n const key = htmlPlaceholders(params.template).find((k) => /colou?r|background|bg/i.test(k)) ?? \"color\";\r\n if (params.params[key] !== color) commands.push({ type: \"clip/set-property\", payload: { clipId: existing.id, params: { [key]: color } } });\r\n if (existing.startUs !== startUs || existing.durationUs !== stopUs - startUs) commands.push({ type: \"clip/trim\", payload: { clipId: existing.id, startUs, durationUs: stopUs - startUs } });\r\n if (!commands.length) return { ok: true, result: { note: \"the background already looks like that\" } };\r\n } else {\r\n const trackId = doc.tracks.background ? `background-${Object.keys(doc.tracks).length}` : \"background\";\r\n commands.push({ type: \"track/add\", payload: { id: trackId, kind: \"video\", name: \"Background\", index: 0 } });\r\n commands.push({ type: \"clip/add\", payload: { kind: \"html\", id: `${trackId}-clip`, trackId, startUs, durationUs: stopUs - startUs, template: BACKGROUND_TEMPLATE, params: { color } } });\r\n }\r\n const out = applyChecked(project, commands);\r\n if (!out.ok) return out;\r\n return { ...out, changes: [`${existing ? \"Changed the background to\" : \"Added a background:\"} ${color}, ${seconds(startUs)}–${seconds(stopUs)}`] };\r\n },\r\n }),\r\n\r\n defineTool({\r\n name: \"close_gaps\",\r\n description: \"Close the empty gaps on a track so its clips run back to back, keeping their order (the first clip stays where it is, or starts at 0 with fromZero).\",\r\n inputSchema: {\r\n type: \"object\",\r\n properties: { trackId: { type: \"string\" }, fromZero: { type: \"boolean\" } },\r\n required: [\"trackId\"],\r\n additionalProperties: false,\r\n },\r\n run(input, { project }) {\r\n const doc = project.getState().doc;\r\n if (!doc.tracks[String(input.trackId)]) return { ok: false, error: `no track \"${input.trackId}\"` };\r\n const clips = Object.values(doc.clips)\r\n .filter((c) => c.trackId === input.trackId)\r\n .sort((a, b) => a.startUs - b.startUs);\r\n let cursor = input.fromZero === true ? 0 : clips[0]?.startUs ?? 0;\r\n const commands: Command[] = [];\r\n for (const c of clips) {\r\n if (c.startUs !== cursor) commands.push({ type: \"clip/move\", payload: { clipId: c.id, startUs: cursor } });\r\n cursor += c.durationUs;\r\n }\r\n if (!commands.length) return { ok: true, result: { moved: 0, note: \"no gaps on that track\" } };\r\n const out = applyChecked(project, commands);\r\n return out.ok ? { ...out, changes: [`Closed ${commands.length} gap${commands.length > 1 ? \"s\" : \"\"} on ${input.trackId}`] } : out;\r\n },\r\n }),\r\n beatTool,\r\n ];\r\n}\r\n\r\n/* ---------- apply_commands, checked ---------- */\r\n\r\nclass Abort extends Error {}\r\n\r\n/**\r\n * Apply a batch as one all-or-nothing step, with two checks core doesn't do:\r\n * fields a command would silently ignore are errors, and commands that\r\n * changed nothing are reported, so the model never claims an edit that didn't\r\n * happen.\r\n */\r\nexport function applyChecked(project: Project, commands: Command[]): ToolOutput {\r\n const catalog = project.commandCatalog();\r\n const nothing = commands.length > 1 ? \"Nothing in this batch was applied (not even the commands before it): resend the whole corrected batch.\" : \"Nothing was applied.\";\r\n const templates = new Map<string, string>();\r\n const batchAssets = new Set(commands.flatMap((c) => (c?.type === \"asset/add\" && typeof (c.payload as { id?: unknown })?.id === \"string\" ? [(c.payload as { id: string }).id] : [])));\r\n for (const [index, command] of commands.entries()) {\r\n if (!catalog[command?.type]) {\r\n const looksLikeTool = /^[a-z]+(_[a-z]+)+$/.test(String(command?.type));\r\n return {\r\n ok: false,\r\n error: {\r\n failedIndex: index,\r\n kind: \"unknown-command\",\r\n commandType: command?.type,\r\n error: `\"${command?.type}\" is not a command${looksLikeTool ? \": it is a tool. Call it as its own tool call, not inside apply_commands\" : \"\"}. ${nothing}`,\r\n validTypes: Object.keys(catalog).sort(),\r\n },\r\n };\r\n }\r\n const htmlError = checkHtmlMarkup(project, command, batchAssets) ?? checkHtmlParams(project, command, templates);\r\n if (htmlError) return { ok: false, error: { failedIndex: index, commandType: command.type, error: `${htmlError} ${nothing}` } };\r\n const extra = unknownFields(catalog[command?.type], command?.payload);\r\n if (extra) {\r\n return {\r\n ok: false,\r\n error: {\r\n failedIndex: index,\r\n commandType: command.type,\r\n error: `unknown field${extra.unknown.length > 1 ? \"s\" : \"\"} ${extra.unknown.map((f) => `\"${f}\"`).join(\", \")}: ${command.type} would ignore ${extra.unknown.length > 1 ? \"them\" : \"it\"}. Its fields are: ${extra.allowed.join(\", \")}. ${nothing}`,\r\n },\r\n };\r\n }\r\n }\r\n const unchanged: number[] = [];\r\n let failure: unknown;\r\n try {\r\n project.transaction(() => {\r\n commands.forEach((command, index) => {\r\n const before = project.getState().doc;\r\n const result = tryDispatch(project, command);\r\n if (!result.ok) {\r\n const hint = rejectionHint(result.error as { code?: string; commandType?: string }, command);\r\n const skipped = commands.length > 1 ? { notApplied: describeCommands(commands.filter((_, i) => i !== index)) } : {};\r\n failure = { failedIndex: index, ...result.error, ...(hint ? { hint } : {}), note: nothing, ...skipped };\r\n throw new Abort();\r\n }\r\n if (project.getState().doc === before) unchanged.push(index);\r\n });\r\n });\r\n } catch (err) {\r\n if (err instanceof Abort) return { ok: false, error: failure };\r\n throw err;\r\n }\r\n const changed = commands.filter((_, i) => !unchanged.includes(i));\r\n return {\r\n ok: true,\r\n result: {\r\n applied: changed.length,\r\n ...(unchanged.length\r\n ? { unchanged: unchanged.map((i) => ({ index: i, type: commands[i]!.type, note: \"changed nothing: the project already had these values. Check the field names and values.\" })) }\r\n : {}),\r\n },\r\n ...(changed.length ? { changes: describeCommands(changed) } : {}),\r\n };\r\n}\r\n\r\n/** html clips substitute `{{name}}` placeholders: params with any other name change nothing on screen. */\r\n/** A new or replaced html template must parse as XML and use only assets that exist. */\r\nfunction checkHtmlMarkup(project: Project, command: Command, batchAssets: ReadonlySet<string>): string | undefined {\r\n const p = (command.payload ?? {}) as Record<string, unknown>;\r\n const isHtmlAdd = command.type === \"clip/add\" && p.kind === \"html\";\r\n const isTemplateSet = command.type === \"clip/set-property\" && \"template\" in p;\r\n if ((!isHtmlAdd && !isTemplateSet) || typeof p.template !== \"string\") return undefined;\r\n const label = isHtmlAdd ? `html clip \"${p.id ?? \"(new)\"}\"` : `the template for \"${p.clipId}\"`;\r\n const problem = markupProblem(p.template);\r\n if (problem) return `${label} won't render: its markup is parsed as XML and ${problem}.`;\r\n const assets = project.getState().doc.assets;\r\n const missing = assetRefs(p.template).filter((id) => !assets[id] && !batchAssets.has(id));\r\n if (missing.length) {\r\n const images = Object.values(assets).filter((a) => a.kind === \"image\").map((a) => a.id);\r\n return `${label} uses ${missing.map((id) => `\"asset:${id}\"`).join(\", \")}, but there is no such asset. Use an asset in the project (images: ${images.join(\", \") || \"none\"}), add it in this batch with asset/add, or leave the image out.`;\r\n }\r\n return undefined;\r\n}\r\n\r\nfunction checkHtmlParams(project: Project, command: Command, templates: Map<string, string>): string | undefined {\r\n const p = (command.payload ?? {}) as Record<string, unknown>;\r\n let template: string | undefined;\r\n if (command.type === \"clip/add\" && p.kind === \"html\" && typeof p.template === \"string\") {\r\n template = p.template;\r\n if (typeof p.id === \"string\") templates.set(p.id, template);\r\n } else if (command.type === \"clip/set-property\" && p.params && typeof p.params === \"object\") {\r\n const clip = project.getState().doc.clips[String(p.clipId)] as { kind?: string; template?: string } | undefined;\r\n template = templates.get(String(p.clipId)) ?? (clip?.kind === \"html\" ? clip.template : undefined);\r\n }\r\n if (template === undefined || !p.params || typeof p.params !== \"object\") return undefined;\r\n const names = htmlPlaceholders(template);\r\n const unknown = Object.keys(p.params).filter((k) => !names.includes(k));\r\n if (!unknown.length) return undefined;\r\n return `html params ${unknown.map((k) => `\"${k}\"`).join(\", \")} match no {{placeholder}} in the template, so they would change nothing. Its placeholders: ${names.join(\", \") || \"none\"}.`;\r\n}\r\n\r\n/** What to do instead, for rejections models commonly hit. */\r\nfunction rejectionHint(error: { code?: string }, _command: Command): string | undefined {\r\n switch (error.code) {\r\n case \"no-audio\":\r\n return \"fadeInUs / fadeOutUs fade audio only. To fade a text, image or video picture in or out, call animate_clip (in / out: fade).\";\r\n case \"asset-not-found\":\r\n return \"Clips can only use assets already in the project (see get_state). Audio found with search_audio is added with add_audio, which imports it.\";\r\n case \"not-caption\":\r\n case \"not-html\":\r\n case \"not-text\":\r\n return \"That field belongs to another clip kind: text clips take color / font fields, caption clips take style, html clips take params (named by the template's {{placeholders}}, shown in get_state).\";\r\n default:\r\n return undefined;\r\n }\r\n}\r\n\r\n/** Why two clips have no cut between them, so the model reports it instead of picking another cut. */\r\nfunction noCutReason(project: Project, fromId: unknown, toId: unknown): string {\r\n const doc = project.getState().doc;\r\n const a = typeof fromId === \"string\" ? doc.clips[fromId] : undefined;\r\n const b = typeof toId === \"string\" ? doc.clips[toId] : undefined;\r\n const tail = \"Nothing was added. Don't substitute a different cut: tell the user, and offer an animation (animate_clip) instead if that fits.\";\r\n for (const [given, clip] of [[fromId, a], [toId, b]] as const) if (typeof given === \"string\" && !clip) return `There is no clip \"${given}\". ${tail}`;\r\n if (a && b && a.trackId !== b.trackId) return `${a.id} is on track ${a.trackId} and ${b.id} on track ${b.trackId}: a transition joins two clips that touch on the SAME track, so these two have no cut between them. ${tail}`;\r\n if (a && b) return `${a.id} and ${b.id} don't touch (${a.id} ends at ${seconds(a.startUs + a.durationUs)}, ${b.id} starts at ${seconds(b.startUs)}). ${tail}`;\r\n return `No cut ${a ? `starts after ${a.id}` : `leads into ${b!.id}`} (it isn't directly after/before another clip on its track). ${tail}`;\r\n}\r\n\r\nfunction cutInfo(c: Cut) {\r\n return { fromClipId: c.fromClipId, toClipId: c.toClipId, atSeconds: c.atUs / S, maxSeconds: c.maxTransitionUs / S, maxZoomThroughSeconds: c.maxHoldTransitionUs / S, ...(c.transition ? { transition: c.transition.kind } : {}) };\r\n}\r\n\r\n/** apply_commands' input schema: `type` is an enum of the catalog's commands. */\r\nexport function applyCommandsSchema(catalog: Record<string, unknown>): Record<string, unknown> {\r\n const [dispatch] = toToolDefinitions(catalog, { mode: \"dispatch\" }) as { input_schema: Record<string, unknown> }[];\r\n return {\r\n type: \"object\",\r\n properties: {\r\n commands: { type: \"array\", minItems: 1, items: dispatch!.input_schema, description: \"Commands applied in order, as one batch.\" },\r\n },\r\n required: [\"commands\"],\r\n additionalProperties: false,\r\n };\r\n}\r\n","import { createProject, type Command, type Project } from \"@miraiclip/core\";\r\n\r\n/**\r\n * A working copy of a project that records every command that sticks.\r\n *\r\n * The assistant edits the copy while the model thinks (async, many steps);\r\n * the real project only changes when the turn commits, by replaying the\r\n * recorded commands in one transaction — one undo step, and nothing half-done\r\n * if the request fails. Commands inside a transaction that rolls back are\r\n * dropped, exactly as the copy drops their effects.\r\n *\r\n * Replay must produce the same ids the model saw, so commands whose ids are\r\n * optional get explicit ones before they run.\r\n */\r\nexport interface RecordingProject extends Project {\r\n /** Commands that changed the copy, in order. */\r\n readonly recorded: readonly Command[];\r\n}\r\n\r\nexport interface ForkOptions {\r\n /** Build the copy (default: `createProject(doc)`). Register custom commands here. */\r\n createFork?: (doc: ReturnType<Project[\"toJSON\"]>) => Project;\r\n /** Give commands explicit ids (default handles the built-ins with optional ids). */\r\n ensureIds?: (command: Command) => Command;\r\n}\r\n\r\nlet seq = 0;\r\nconst genId = (prefix: string) => `${prefix}-${Date.now().toString(36)}${(++seq).toString(36)}${Math.random().toString(36).slice(2, 6)}`;\r\n\r\n/** Built-in commands that would otherwise get a random id inside the engine. */\r\nexport function withExplicitIds(command: Command): Command {\r\n const p = (command.payload ?? {}) as Record<string, unknown>;\r\n const set = (field: string, prefix: string) =>\r\n p[field] === undefined ? { ...command, payload: { ...p, [field]: genId(prefix) } } : command;\r\n switch (command.type) {\r\n case \"clip/split\":\r\n case \"clip/duplicate\":\r\n return set(\"newClipId\", \"clip\");\r\n case \"effect/add\":\r\n return set(\"effectId\", \"fx\");\r\n case \"transition/add\":\r\n return set(\"id\", \"tr\");\r\n default:\r\n return command;\r\n }\r\n}\r\n\r\nexport function forkProject(project: Project, options: ForkOptions = {}): RecordingProject {\r\n const fork = (options.createFork ?? ((doc) => createProject(doc)))(project.toJSON());\r\n const state = project.getState();\r\n fork.setPlayhead(state.playheadUs);\r\n fork.setSelection([...state.selection]);\r\n const ensure = options.ensureIds ?? withExplicitIds;\r\n const recorded: Command[] = [];\r\n let depth = 0;\r\n let buffer: Command[] = [];\r\n\r\n return {\r\n ...fork,\r\n get recorded() {\r\n return recorded;\r\n },\r\n dispatch(command) {\r\n const explicit = ensure(command as Command);\r\n const before = fork.getState().doc;\r\n fork.dispatch(explicit);\r\n if (fork.getState().doc === before) return; // no-op: nothing to replay\r\n if (depth > 0) buffer.push(explicit);\r\n else recorded.push(explicit);\r\n },\r\n transaction(fn, label) {\r\n if (depth > 0) {\r\n fn();\r\n return;\r\n }\r\n depth = 1;\r\n buffer = [];\r\n try {\r\n fork.transaction(fn, label);\r\n recorded.push(...buffer);\r\n } finally {\r\n depth = 0;\r\n buffer = [];\r\n }\r\n },\r\n };\r\n}\r\n\r\nexport type CommitResult = { ok: true; applied: number } | { ok: false; error: string };\r\n\r\n/** Replay a fork's commands onto the real project as one undoable step. */\r\nexport function commitFork(project: Project, fork: RecordingProject, label?: string): CommitResult {\r\n if (fork.recorded.length === 0) return { ok: true, applied: 0 };\r\n try {\r\n project.transaction(() => {\r\n for (const command of fork.recorded) project.dispatch(command);\r\n }, label);\r\n return { ok: true, applied: fork.recorded.length };\r\n } catch (err) {\r\n return {\r\n ok: false,\r\n error: `The project changed while the assistant was working, so its edit no longer applies (${(err as Error)?.message ?? err}). Ask again.`,\r\n };\r\n }\r\n}\r\n","import { defineTool, type AssistantTool } from \"./tools.js\";\r\n\r\n/**\r\n * Skills: playbooks for bigger jobs (a launch video, captions, a reformat),\r\n * loaded by the model only when a request needs one.\r\n *\r\n * The system prompt lists each skill's name and description. The model calls\r\n * `load_skill` to read one, and `read_skill_file` for the reference files it\r\n * points to, so the prompt stays small however many skills there are. An app\r\n * can also start a request with a skill already loaded (`TurnOptions.skills`),\r\n * e.g. from a button.\r\n *\r\n * Skills are plain Markdown with a front matter (`name`, `description`), the\r\n * same shape as Claude Code skills. `MIRAICLIP_SKILLS` are the Miraiclip\r\n * plugin's skills, in their editor versions.\r\n */\r\nexport interface AssistantSkill {\r\n /** Lowercase letters, digits and dashes, e.g. \"captions\". */\r\n name: string;\r\n /** When to use it, in one or two sentences. The model picks skills by this. */\r\n description: string;\r\n /** The playbook (Markdown, without the front matter). */\r\n instructions: string;\r\n /** Reference files the playbook points to, by relative path (\"reference/craft.md\"). */\r\n files?: Readonly<Record<string, string>>;\r\n}\r\n\r\nconst NAME = /^[a-z0-9][a-z0-9-]{0,63}$/;\r\n\r\nexport function defineSkill(skill: AssistantSkill): AssistantSkill {\r\n if (!NAME.test(skill.name)) throw new Error(`Skill name must match ${NAME} (got \"${skill.name}\")`);\r\n if (!skill.description.trim()) throw new Error(`Skill \"${skill.name}\" needs a description`);\r\n return skill;\r\n}\r\n\r\n/** Split `---\\nkey: value\\n---\\nbody` into its fields and body. Values may be quoted. */\r\nexport function parseFrontMatter(markdown: string): { data: Record<string, string>; body: string } {\r\n const text = markdown.replace(/^/, \"\").replace(/\\r\\n/g, \"\\n\");\r\n const match = /^---\\n([\\s\\S]*?)\\n---\\n?/.exec(text);\r\n if (!match) return { data: {}, body: text.trim() };\r\n const data: Record<string, string> = {};\r\n for (const line of match[1]!.split(\"\\n\")) {\r\n const kv = /^([A-Za-z0-9_-]+):\\s*(.*)$/.exec(line);\r\n if (!kv) continue;\r\n let value = kv[2]!.trim();\r\n if ((value.startsWith('\"') && value.endsWith('\"')) || (value.startsWith(\"'\") && value.endsWith(\"'\"))) value = value.slice(1, -1);\r\n data[kv[1]!] = value;\r\n }\r\n return { data, body: text.slice(match[0].length).trim() };\r\n}\r\n\r\n/** One skill from its Markdown (front matter `name` and `description`). */\r\nexport function parseSkill(markdown: string, options: { name?: string; description?: string; files?: Record<string, string> } = {}): AssistantSkill {\r\n const { data, body } = parseFrontMatter(markdown);\r\n const name = data.name || options.name;\r\n const description = data.description || options.description;\r\n if (!name) throw new Error(\"Skill has no name (front matter `name:`)\");\r\n if (!description) throw new Error(`Skill \"${name}\" has no description (front matter \\`description:\\`)`);\r\n return defineSkill({ name, description, instructions: body, ...(options.files && Object.keys(options.files).length ? { files: options.files } : {}) });\r\n}\r\n\r\nexport interface SkillsFromFilesOptions {\r\n /**\r\n * Which file in a skill folder is the playbook, in order of preference.\r\n * Default `[\"SKILL.md\"]`. The Miraiclip plugin keeps the Claude Code\r\n * version in SKILL.md and the in-editor version in EDITOR.md, so an editor\r\n * passes `[\"EDITOR.md\"]` (folders without one are skipped). The other\r\n * entry file's front matter fills in a missing name or description.\r\n */\r\n entry?: string[];\r\n}\r\n\r\n/**\r\n * Skills from a folder of skill folders, given as `{ path: text }` — e.g.\r\n * Vite's `import.meta.glob(\"./skills/**\\/*.md\", { query: \"?raw\", import: \"default\", eager: true })`.\r\n * Every other `.md` in a skill's folder becomes one of its files.\r\n */\r\nexport function skillsFromFiles(files: Record<string, string>, options: SkillsFromFilesOptions = {}): AssistantSkill[] {\r\n const entry = options.entry ?? [\"SKILL.md\"];\r\n const ENTRY_FILES = new Set([...entry, \"SKILL.md\", \"EDITOR.md\"]);\r\n const paths = Object.keys(files).map((p) => p.replace(/\\\\/g, \"/\"));\r\n const original = new Map(Object.keys(files).map((p) => [p.replace(/\\\\/g, \"/\"), p]));\r\n const read = (p: string) => files[original.get(p) ?? p]!;\r\n // A skill folder is any folder holding one of the entry files.\r\n const dirs = [...new Set(paths.filter((p) => ENTRY_FILES.has(base(p))).map(dirname))].sort((a, b) => b.length - a.length);\r\n const skills: AssistantSkill[] = [];\r\n for (const dir of [...dirs].sort()) {\r\n const main = entry.map((f) => join(dir, f)).find((p) => p in files || original.has(p));\r\n if (!main) continue;\r\n const others = [...ENTRY_FILES].map((f) => join(dir, f)).filter((p) => p !== main && original.has(p));\r\n const fallback = others.map((p) => parseFrontMatter(read(p)).data).find((d) => d.name || d.description) ?? {};\r\n const own: Record<string, string> = {};\r\n for (const p of paths) {\r\n if (!p.startsWith(`${dir}/`) || !p.endsWith(\".md\") || ENTRY_FILES.has(p.slice(dir.length + 1))) continue;\r\n // Files of a nested skill folder belong to that skill.\r\n if (dirs.some((d) => d !== dir && d.startsWith(`${dir}/`) && p.startsWith(`${d}/`))) continue;\r\n own[p.slice(dir.length + 1)] = read(p);\r\n }\r\n skills.push(parseSkill(read(main), { name: fallback.name ?? base(dir), ...(fallback.description ? { description: fallback.description } : {}), files: own }));\r\n }\r\n return skills;\r\n}\r\n\r\n/** The skill list for the system prompt. */\r\nexport function describeSkills(skills: readonly AssistantSkill[]): string {\r\n if (!skills.length) return \"\";\r\n return [\r\n \"Skills (playbooks for bigger jobs):\",\r\n ...skills.map((s) => `- ${s.name}: ${s.description}`),\r\n \"When a request matches a skill, call load_skill with its name before your first edit and follow it. It may point to reference files: read them with read_skill_file when the step you're on needs them. A skill loaded in an earlier request may have been shortened in the history: load it again if you need it.\",\r\n ].join(\"\\n\");\r\n}\r\n\r\n/** `load_skill` and `read_skill_file` for these skills. */\r\nexport function skillTools(skills: readonly AssistantSkill[]): AssistantTool[] {\r\n if (!skills.length) return [];\r\n const byName = new Map(skills.map((s) => [s.name, s]));\r\n const names = skills.map((s) => s.name);\r\n return [\r\n defineTool({\r\n name: \"load_skill\",\r\n description: \"Read a skill's playbook (see the skill list in the instructions). Call it before starting a job that matches one, then follow it.\",\r\n inputSchema: { type: \"object\", properties: { name: { type: \"string\", enum: names } }, required: [\"name\"], additionalProperties: false },\r\n run(input) {\r\n const skill = byName.get(String(input.name));\r\n if (!skill) return { ok: false, error: `unknown skill \"${input.name}\"; skills are ${names.join(\", \")}` };\r\n return { ok: true, result: { name: skill.name, instructions: skill.instructions, files: Object.keys(skill.files ?? {}) } };\r\n },\r\n }),\r\n defineTool({\r\n name: \"read_skill_file\",\r\n description: \"Read a reference file of a skill (paths as load_skill lists them, e.g. reference/craft.md). Long files come in parts: pass `part` for the next one.\",\r\n inputSchema: {\r\n type: \"object\",\r\n properties: { skill: { type: \"string\", enum: names }, path: { type: \"string\" }, part: { type: \"integer\", minimum: 1 } },\r\n required: [\"skill\", \"path\"],\r\n additionalProperties: false,\r\n },\r\n run(input) {\r\n const skill = byName.get(String(input.skill));\r\n if (!skill) return { ok: false, error: `unknown skill \"${input.skill}\"; skills are ${names.join(\", \")}` };\r\n const path = String(input.path).replace(/^\\.\\//, \"\");\r\n const text = skill.files?.[path];\r\n if (text === undefined) return { ok: false, error: `\"${skill.name}\" has no file \"${path}\"; its files are ${Object.keys(skill.files ?? {}).join(\", \") || \"none\"}` };\r\n const parts = chunk(text, FILE_PART);\r\n const part = Math.min(Math.max(1, Number(input.part) || 1), parts.length);\r\n return { ok: true, result: { skill: skill.name, path, ...(parts.length > 1 ? { part, parts: parts.length } : {}), content: parts[part - 1] } };\r\n },\r\n }),\r\n ];\r\n}\r\n\r\n/** Below the agent's tool-result cap, split on paragraph breaks. */\r\nconst FILE_PART = 15_000;\r\n\r\nfunction chunk(text: string, max: number): string[] {\r\n if (text.length <= max) return [text];\r\n const out: string[] = [];\r\n let rest = text;\r\n while (rest.length > max) {\r\n let cut = rest.lastIndexOf(\"\\n\\n\", max);\r\n if (cut < max / 2) cut = rest.lastIndexOf(\"\\n\", max);\r\n if (cut < max / 2) cut = max;\r\n out.push(rest.slice(0, cut));\r\n rest = rest.slice(cut).replace(/^\\n+/, \"\");\r\n }\r\n if (rest) out.push(rest);\r\n return out;\r\n}\r\n\r\nconst base = (p: string) => p.slice(p.lastIndexOf(\"/\") + 1);\r\nconst dirname = (p: string) => (p.includes(\"/\") ? p.slice(0, p.lastIndexOf(\"/\")) : \"\");\r\nconst join = (dir: string, f: string) => (dir ? `${dir}/${f}` : f);\r\n","import type { Command, Project, ProjectDocument } from \"@miraiclip/core\";\r\nimport { describeForAssistant } from \"./state.js\";\r\nimport { commitFork, forkProject, type CommitResult, type ForkOptions } from \"./fork.js\";\r\nimport { editorTools, type AssistantTool } from \"./tools.js\";\r\nimport { describeSkills, skillTools, type AssistantSkill } from \"./skills.js\";\r\nimport { ChatModelError, type ChatMessage, type ChatImage, type ChatModel, type ChatRequest, type ChatResponse, type ChatUsage, type ToolCall } from \"./types.js\";\r\n\r\nexport interface AssistantOptions {\r\n model: ChatModel;\r\n /** Default `editorTools()`. Add your own (audio, captions, stock media…). */\r\n tools?: AssistantTool[];\r\n /** Extra system instructions (brand voice, house rules). */\r\n instructions?: string;\r\n /**\r\n * Playbooks the model can load when a request needs one (e.g.\r\n * `MIRAICLIP_SKILLS`). Adds `load_skill` and `read_skill_file` to the tools\r\n * and lists each skill's name and description in the system prompt.\r\n */\r\n skills?: readonly AssistantSkill[];\r\n /** Model round-trips per request (default 12). */\r\n maxSteps?: number;\r\n /** Model id when the adapter serves several. */\r\n modelId?: string;\r\n maxOutputTokens?: number;\r\n /** Vendor request fields, passed through. */\r\n params?: Record<string, unknown>;\r\n fork?: ForkOptions;\r\n}\r\n\r\nexport type AssistantEvent =\r\n | { type: \"text\"; delta: string }\r\n | { type: \"tool-start\"; call: ToolCall }\r\n | {\r\n type: \"tool-end\";\r\n call: ToolCall;\r\n ok: boolean;\r\n changes?: string[];\r\n error?: string;\r\n /** Images the tool returned (previews, storyboards) — the model sees them too; apps may show them. */\r\n images?: ChatImage[];\r\n }\r\n | { type: \"step\"; index: number };\r\n\r\nexport interface TurnOptions {\r\n /** Earlier messages of this conversation (`turn.messages` from the last turn). */\r\n history?: readonly ChatMessage[];\r\n /** What the user is looking at, e.g. \"Selected: clip t-1. Playhead: 4.2s.\" */\r\n context?: string;\r\n /** \"auto\" (default) commits when the request finishes; \"review\" waits for `turn.apply()`. */\r\n apply?: \"auto\" | \"review\";\r\n signal?: AbortSignal;\r\n onEvent?: (event: AssistantEvent) => void;\r\n /** History label for the undo step (default \"Assistant: <request>\"). */\r\n label?: string;\r\n /**\r\n * Skills to load up front for this request, by name (e.g. the user picked\r\n * \"Captions\" in the app). Their playbooks go into the system prompt, so the\r\n * model starts on them without a `load_skill` call. Unknown names are\r\n * ignored.\r\n */\r\n skills?: readonly string[];\r\n}\r\n\r\nexport interface AssistantTurn {\r\n /** The model's final answer to the user. */\r\n reply: string;\r\n /** Human-readable list of what the turn changed (empty when nothing). */\r\n changes: string[];\r\n /** The commands the turn makes, in order (replayed onto the project on apply). */\r\n commands: readonly Command[];\r\n /**\r\n * applied: committed as one undo step. review: waiting for apply()/discard().\r\n * no-changes: nothing to apply. failed / canceled: the project wasn't touched.\r\n */\r\n status: \"applied\" | \"review\" | \"no-changes\" | \"failed\" | \"canceled\";\r\n error?: string;\r\n /** The project as the turn left it (for previews in review mode). */\r\n preview: ProjectDocument;\r\n /** The conversation so far, without the system prompt: pass back as `history`. */\r\n messages: ChatMessage[];\r\n usage: Required<ChatUsage>;\r\n /** Commit a reviewed turn (no-op once applied). */\r\n apply(): CommitResult;\r\n discard(): void;\r\n}\r\n\r\nexport interface Assistant {\r\n readonly tools: readonly AssistantTool[];\r\n run(project: Project, request: string, options?: TurnOptions): Promise<AssistantTurn>;\r\n}\r\n\r\nconst MAX_TOOL_RESULT = 20_000;\r\n/** Pictures per tool result, and how many recent results keep theirs (see recentImagesOnly). */\r\nconst MAX_IMAGES_PER_RESULT = 4;\r\nconst KEEP_IMAGE_RESULTS = 2;\r\nconst MAX_OLD_TOOL_RESULT = 2_000;\r\n\r\nexport const DEFAULT_INSTRUCTIONS = `You are the editing assistant inside a video editor. You change the user's project by calling tools; every change you make lands as ONE undo step when you finish, so act confidently.\r\n\r\nHow to work:\r\n- The current project is summarized below. Clip, track and asset ids there are what tools expect. Call get_state to re-read it after big changes.\r\n- Prefer the high-level tools when they fit (add_transition, animate_clip, add_effect, set_effects_enabled, set_background, trim_clip, set_keyframes, close_gaps, and any audio or media tools). Call tools directly; apply_commands takes only editing commands like \"clip/add\", never tool names. Use apply_commands for everything else, batching related commands; look up a command's payload with get_command_schema the first time you use it, and use only the fields it lists.\r\n- Fading a picture (text, image, logo, video) in or out is animate_clip (in / out: fade). fadeInUs / fadeOutUs are audio fades. \"Fade the video in at the start / out at the end\" means the first clip's entrance and the last clip's exit.\r\n- Constant values are set directly with clip/set-property: rotation (\"rotate 45°\", \"tilt\"), volume (\"lower to 30%\", \"louder\"), position, size, opacity. Only values that CHANGE over a time range (\"duck the music between 10 and 15 s\", \"fade to silence between 18 and 20 s\", \"spin one full turn\", \"pan across\") are keyframes: set_keyframes, with times in timeline seconds. Remove an animation with keyframe/clear.\r\n- \"Cuts\" are the ones listed in the project summary; use their times (e.g. one sound per cut).\r\n- \"Add N more seconds of footage\" adds a new clip (clip/add with trimStartUs for the source point) after the last one; it does not stretch an existing clip.\r\n- Caption looks (box behind the text, outline, shadow, colors, word preset) are the caption clip's style (clip/set-property { style }).\r\n- \"The whole video\" means from 0 to the composition length (trim_clip startSeconds / endSeconds).\r\n- A solid color background behind everything is set_background (add, recolor, remove). Other html clips are recolored through the params their template uses (shown in the clip details). A new track goes on top unless track/add gets index 0 (the bottom).\r\n- \"Replace X with Y\" removes X and adds Y in the same turn, whichever tool adds Y (audio tools take replaceClipId, which does both).\r\n- Solo / mute / lock / hide a track with track/set-property (solo, muted, locked, hidden). \"Solo X\" is solo: true on X, not muting the others.\r\n- Relative requests (\"bigger\", \"louder\", \"a bit brighter\", \"twice as big\") start from the current values in the clip details below: make a visible change in the asked direction (about 25% for \"bigger\"/\"smaller\", ×2 for \"twice\").\r\n- When something is selected, \"this\", \"it\", \"the clip\" and any request that names no target mean ONLY the selection, even with words like \"at the end\" (the end of that clip). Edit nothing else. \"Here\" / \"now\" / \"this point\" mean the playhead. \"Clips\" without a kind mean the video clips on the main video track.\r\n- \"Again\" / \"also\" / \"another\" mean keep the original and add a copy (clip/duplicate, or clip/add with the same asset); never move the original. \"Instead\" means move or replace.\r\n- Removing a clip leaves a gap; when asked to close it, also run close_gaps (or trim_clip with ripple).\r\n- Commands use integer microseconds (1 second = 1000000). High-level tools take seconds.\r\n- Positions (transform x, y) are fractions of the frame: 0.5, 0.5 is the center.\r\n- Only reference media that exists in the project or that a tool returned. Never invent file URLs.\r\n- If a tool fails, read the error, fix the input and retry. A failed apply_commands batch applied NONE of its commands: resend all of them, corrected. If something can't be done, say why.\r\n- If the request is ambiguous in a way that matters, ask one short question instead of guessing.\r\n\r\nWhen you're done, reply in one to three short sentences: what you changed, in plain words (times in seconds, names over ids). Describe only edits your tools reported as applied; if a tool says something changed nothing or failed, fix it or say so. No markdown headings.`;\r\n\r\nexport function createAssistant(options: AssistantOptions): Assistant {\r\n const skills = options.skills ?? [];\r\n const tools = [...(options.tools ?? editorTools()), ...skillTools(skills)];\r\n const skillByName = new Map(skills.map((s) => [s.name, s]));\r\n const byName = new Map(tools.map((t) => [t.name, t]));\r\n const specs = tools.map(({ name, description, inputSchema }) => ({ name, description, inputSchema }));\r\n const maxSteps = options.maxSteps ?? 12;\r\n\r\n return {\r\n tools,\r\n async run(project, request, turn = {}) {\r\n const fork = forkProject(project, options.fork);\r\n const emit = turn.onEvent ?? (() => {});\r\n const usage = { inputTokens: 0, outputTokens: 0 };\r\n const changes: string[] = [];\r\n const history = trimHistory(turn.history ?? []);\r\n const added: ChatMessage[] = [{ role: \"user\", content: request }];\r\n const system = [\r\n DEFAULT_INSTRUCTIONS,\r\n options.instructions?.trim(),\r\n describeSkills(skills),\r\n ...(turn.skills ?? []).flatMap((name) => {\r\n const skill = skillByName.get(name);\r\n if (!skill) return [];\r\n const files = Object.keys(skill.files ?? {});\r\n return [\r\n `The user started this request with the \"${skill.name}\" skill. It's loaded; follow it:\\n\\n${skill.instructions}${files.length ? `\\n\\n(Its files, for read_skill_file: ${files.join(\", \")}.)` : \"\"}`,\r\n ];\r\n }),\r\n `Current project:\\n${describeForAssistant(fork.toJSON())}`,\r\n turn.context?.trim() ? `What the user is looking at: ${turn.context.trim()}` : undefined,\r\n selectionNote(fork.toJSON(), fork.getState().selection),\r\n ]\r\n .filter(Boolean)\r\n .join(\"\\n\\n\");\r\n\r\n let reply = \"\";\r\n let status: AssistantTurn[\"status\"] = \"no-changes\";\r\n let error: string | undefined;\r\n\r\n let seesImages = true;\r\n try {\r\n for (let step = 0; ; step++) {\r\n if (step >= maxSteps) {\r\n reply = (reply ? `${reply}\\n\\n` : \"\") + `I stopped after ${maxSteps} steps; what I finished is applied. Ask me to continue if there's more.`;\r\n break;\r\n }\r\n emit({ type: \"step\", index: step });\r\n const request: ChatRequest = {\r\n messages: recentImagesOnly([{ role: \"system\", content: system }, ...history, ...added], seesImages ? KEEP_IMAGE_RESULTS : 0),\r\n tools: specs,\r\n ...(options.modelId ? { model: options.modelId } : {}),\r\n ...(options.maxOutputTokens ? { maxOutputTokens: options.maxOutputTokens } : {}),\r\n ...(options.params ? { params: options.params } : {}),\r\n };\r\n const callOptions = { ...(turn.signal ? { signal: turn.signal } : {}), onText: (delta: string) => emit({ type: \"text\", delta }) };\r\n let response: ChatResponse;\r\n try {\r\n response = await options.model.complete(request, callOptions);\r\n } catch (err) {\r\n // A model that can't take images (or a provider that refuses them here) gets the text only, for the rest of the turn.\r\n const hasImages = request.messages.some((m) => m.role === \"tool\" && m.images?.length);\r\n const refused = err instanceof ChatModelError && (err.status === 400 || err.status === 415 || /image|inline|multimodal|vision|content type/i.test(err.message));\r\n if (!hasImages || !refused || turn.signal?.aborted) throw err;\r\n seesImages = false;\r\n response = await options.model.complete({ ...request, messages: recentImagesOnly(request.messages, 0) }, callOptions);\r\n }\r\n usage.inputTokens += response.usage?.inputTokens ?? 0;\r\n usage.outputTokens += response.usage?.outputTokens ?? 0;\r\n added.push({ role: \"assistant\", content: response.content, ...(response.toolCalls.length ? { toolCalls: response.toolCalls } : {}) });\r\n if (response.toolCalls.length === 0) {\r\n reply = response.content.trim();\r\n break;\r\n }\r\n for (const call of response.toolCalls) {\r\n if (turn.signal?.aborted) throw abortError();\r\n emit({ type: \"tool-start\", call });\r\n const out = await runTool(byName.get(call.name), call, fork, turn.signal);\r\n if (out.ok && out.changes?.length) changes.push(...out.changes);\r\n emit({\r\n type: \"tool-end\",\r\n call,\r\n ok: out.ok,\r\n ...(out.ok && out.changes?.length ? { changes: out.changes } : {}),\r\n ...(!out.ok ? { error: short(out.error) } : {}),\r\n ...(out.ok && out.images?.length ? { images: out.images.slice(0, MAX_IMAGES_PER_RESULT) } : {}),\r\n });\r\n added.push({\r\n role: \"tool\",\r\n toolCallId: call.id,\r\n name: call.name,\r\n content: cap(out.ok ? out.result ?? { ok: true } : { ok: false, error: out.error }, MAX_TOOL_RESULT),\r\n ...(out.ok && out.images?.length ? { images: out.images.slice(0, MAX_IMAGES_PER_RESULT) } : {}),\r\n });\r\n }\r\n }\r\n status = fork.recorded.length === 0 ? \"no-changes\" : turn.apply === \"review\" ? \"review\" : \"applied\";\r\n } catch (err) {\r\n status = turn.signal?.aborted || (err as Error)?.name === \"AbortError\" ? \"canceled\" : \"failed\";\r\n error = (err as Error)?.message ?? String(err);\r\n }\r\n\r\n let committed = false;\r\n let discarded = false;\r\n const label = turn.label ?? `Assistant: ${request.length > 40 ? `${request.slice(0, 39)}…` : request}`;\r\n const apply = (): CommitResult => {\r\n if (committed) return { ok: true, applied: 0 };\r\n if (discarded) return { ok: false, error: \"This edit was discarded.\" };\r\n if (status === \"failed\" || status === \"canceled\") return { ok: false, error: error ?? status };\r\n const result = commitFork(project, fork, label);\r\n if (result.ok) committed = true;\r\n return result;\r\n };\r\n if (status === \"applied\") {\r\n const result = apply();\r\n if (!result.ok) {\r\n status = \"failed\";\r\n error = result.error;\r\n }\r\n }\r\n\r\n return {\r\n reply,\r\n changes,\r\n commands: fork.recorded,\r\n status,\r\n ...(error !== undefined ? { error } : {}),\r\n preview: fork.toJSON(),\r\n messages: [...history, ...added],\r\n usage,\r\n apply,\r\n discard() {\r\n if (!committed) discarded = true;\r\n },\r\n };\r\n },\r\n };\r\n}\r\n\r\nasync function runTool(tool: AssistantTool | undefined, call: ToolCall, project: Project, signal?: AbortSignal) {\r\n if (!tool) return { ok: false as const, error: `unknown tool \"${call.name}\"` };\r\n if (\"__invalidArguments\" in call.arguments) return { ok: false as const, error: \"the tool arguments weren't valid JSON; send a JSON object\" };\r\n try {\r\n return await tool.run(call.arguments, { project, ...(signal ? { signal } : {}) });\r\n } catch (err) {\r\n if ((err as Error)?.name === \"AbortError\") throw err;\r\n return { ok: false as const, error: (err as Error)?.message ?? String(err) };\r\n }\r\n}\r\n\r\n/**\r\n * Pictures cost a lot of input: keep them only on the latest few tool results\r\n * that have any (the model has already looked at the older ones and said what\r\n * it saw), and drop them from the rest.\r\n */\r\nexport function recentImagesOnly(messages: ChatMessage[], keep: number): ChatMessage[] {\r\n let seen = 0;\r\n const out = [...messages];\r\n for (let i = out.length - 1; i >= 0; i--) {\r\n const m = out[i]!;\r\n if (m.role !== \"tool\" || !m.images?.length) continue;\r\n if (seen++ < keep) continue;\r\n const { images: _dropped, ...rest } = m;\r\n out[i] = { ...rest, content: `${rest.content} [${_dropped.length} image${_dropped.length > 1 ? \"s\" : \"\"} shown earlier]` };\r\n }\r\n return out;\r\n}\r\n\r\nfunction cap(value: unknown, max: number): string {\r\n const text = typeof value === \"string\" ? value : JSON.stringify(value);\r\n return text.length > max ? `${text.slice(0, max)}… [truncated ${text.length - max} chars]` : text;\r\n}\r\n\r\nconst short = (e: unknown) => {\r\n const text = typeof e === \"string\" ? e : JSON.stringify(e);\r\n return text.length > 300 ? `${text.slice(0, 299)}…` : text;\r\n};\r\n\r\n/**\r\n * Earlier turns keep their shape but not their bulky tool output. A turn cut\r\n * short (canceled, failed) can leave tool calls without results, which\r\n * vendors reject: those get a \"not run\" result.\r\n */\r\nfunction trimHistory(history: readonly ChatMessage[]): ChatMessage[] {\r\n const answered = new Set(history.flatMap((m) => (m.role === \"tool\" ? [m.toolCallId] : [])));\r\n const out: ChatMessage[] = [];\r\n for (const m of history) {\r\n if (m.role === \"system\") continue;\r\n if (m.role === \"tool\") {\r\n out.push(m.content.length > MAX_OLD_TOOL_RESULT ? { ...m, content: `${m.content.slice(0, MAX_OLD_TOOL_RESULT)}… [truncated]` } : m);\r\n continue;\r\n }\r\n out.push(m);\r\n if (m.role === \"assistant\") {\r\n for (const call of m.toolCalls ?? []) {\r\n if (!answered.has(call.id)) out.push({ role: \"tool\", toolCallId: call.id, name: call.name, content: '{\"ok\":false,\"error\":\"not run: the request was interrupted\"}' });\r\n }\r\n }\r\n }\r\n return out;\r\n}\r\n\r\nfunction abortError() {\r\n const err = new Error(\"canceled\");\r\n err.name = \"AbortError\";\r\n return err;\r\n}\r\n\r\n/** The selection, stated as a scope rule right before the request (models drift to \"all clips\" otherwise). */\r\nfunction selectionNote(doc: ProjectDocument, selection: readonly string[]): string | undefined {\r\n const clips = selection.map((id) => doc.clips[id]).filter((c): c is NonNullable<typeof c> => !!c);\r\n if (!clips.length) return undefined;\r\n const list = clips.map((c) => `${c.id} (${c.kind}, ${Number((c.startUs / 1e6).toFixed(2))}–${Number(((c.startUs + c.durationUs) / 1e6).toFixed(2))}s)`).join(\", \");\r\n return `Selected: ${list}. In this request, \"this\", \"it\", \"the clip\" and anything that names no target mean ONLY ${clips.map((c) => c.id).join(\", \")}. Leave every other clip alone.`;\r\n}\r\n","// Generated by scripts/bundle-skills.mjs from plugins/miraiclip/skills (EDITOR.md + reference files). Don't edit.\r\nimport type { AssistantSkill } from \"./skills.js\";\r\n\r\n/** The Miraiclip plugin's skills, in their in-editor versions: pass to `createAssistant({ skills })`. */\r\nexport const MIRAICLIP_SKILLS: readonly AssistantSkill[] = [\r\n {\r\n \"name\": \"captions\",\r\n \"description\": \"Add, fix or restyle word-timed captions or subtitles in the editor - karaoke, pop, word-by-word, boxed or subtitle-bar looks - from a transcript, an SRT/VTT the user pastes, or the editor's own transcription. Use for any request about captions, subtitles or on-screen text that follows speech.\",\r\n \"instructions\": \"# Captions\\n\\nLoad `edit-video` first if you haven't. Captions are `caption` clips on their own video track above everything else: one clip per short phrase, each word timed (`words: [{ text, startUs, durationUs }]`, times relative to the clip's start).\\n\\n## 1. Where the words come from\\n\\n| The project or user has… | Do |\\n| --- | --- |\\n| Caption clips already | Edit or restyle them; don't rebuild |\\n| A pasted SRT/VTT or word-timed JSON | Build caption clips from it (step 3) |\\n| Plain text, no timings | Spread the words evenly over the speech the user points to, about 2.5–3 words a second; say the timing is approximate |\\n| Nothing | You can't hear the audio. Ask the user to transcribe it with the editor's transcription feature (if it has one) or paste a transcript, then come back |\\n\\nNever invent the words someone says. Fix obvious mis-hearings of names and brands; keep the word count per phrase the same so timings still line up.\\n\\nTimeline time = source time + `clip.startUs − clip.trimStartUs` of the video clip the speech belongs to. If the video was recut, map each word through the segment it falls in, and drop words that were cut.\\n\\n## 2. Pick a look\\n\\nIf the editor has a caption-look tool (`style_captions`), add the clips first, then apply a look with it (and the brand accent as its colour). Otherwise set `style` on each caption clip:\\n\\n| Look | `style` | Use for |\\n| --- | --- | --- |\\n| Social karaoke (default) | `{\\\"preset\\\":\\\"karaoke\\\",\\\"fontWeight\\\":800,\\\"fontSizeFrac\\\":0.055,\\\"strokeColor\\\":\\\"#000000\\\",\\\"strokeWidthFrac\\\":0.12,\\\"highlightColor\\\":\\\"#ffd400\\\"}` | Reels, Shorts, TikTok, podcasts |\\n| Pop, one word big | `{\\\"preset\\\":\\\"pop\\\",\\\"display\\\":\\\"word\\\",\\\"fontWeight\\\":900,\\\"fontSizeFrac\\\":0.075,\\\"textTransform\\\":\\\"uppercase\\\",\\\"strokeColor\\\":\\\"#000000\\\",\\\"strokeWidthFrac\\\":0.14,\\\"highlightColor\\\":\\\"#ffffff\\\"}`, one word per clip | Hype edits, hooks |\\n| Boxed highlight | `{\\\"preset\\\":\\\"highlight\\\",\\\"fontWeight\\\":700,\\\"fontSizeFrac\\\":0.05,\\\"color\\\":\\\"#ffffff\\\",\\\"highlightColor\\\":\\\"#0a0d18\\\",\\\"activeBackgroundColor\\\":\\\"#ffd400\\\"}` | Clean brand look |\\n| Subtitle bar | `{\\\"preset\\\":\\\"plain\\\",\\\"fontWeight\\\":600,\\\"fontSizeFrac\\\":0.042,\\\"backgroundColor\\\":\\\"#000000b3\\\"}`, 6–8 words | Interviews, accessibility |\\n| Reveal | `{\\\"preset\\\":\\\"reveal\\\",\\\"fontWeight\\\":800,\\\"fontSizeFrac\\\":0.055,\\\"shadowColor\\\":\\\"#000000\\\",\\\"shadowBlurFrac\\\":0.3,\\\"shadowOffsetFrac\\\":0}` | Storytelling, quotes |\\n\\n## 3. Build\\n\\n- Words per clip: 3–4 for 9:16 social, 4–6 for 16:9 social, 6–8 for subtitles. Break phrases at punctuation and pauses, never mid-name.\\n- Position: `transform.y` 0.78–0.82 on 16:9, 0.68–0.72 on 9:16 (above the platform UI).\\n- `track/add` `{ \\\"id\\\": \\\"captions\\\", \\\"kind\\\": \\\"video\\\" }` on top if it's missing, then one `apply_commands` batch of `clip/add` (kind `caption`), ids `cap-1`, `cap-2`, …. Long videos: batches of about 150 commands.\\n- Each clip runs from its first word's start to its last word's end (a little hold, ≤ 0.3 s, is fine); clips must not overlap.\\n\\n## 4. Check\\n\\nFrom the state: phrases stay within the words-per-clip limits above (they keep a caption to two lines at these sizes); captions don't overlap a lower third or callout in time and place (move them up, `y` 0.7, while it's on); nothing runs past the video's end.\\n\\nTo restyle every caption, use the look tool, or one batch of `clip/set-property` `{ clipId, style }` per caption clip (style merges).\\n\\nTell the user how many captions you added, the look, and to play one stretch to check the timing.\"\r\n },\r\n {\r\n \"name\": \"continuous-shot\",\r\n \"description\": \"Make a continuous shot: one unbroken camera move where every beat grows out of the last, with no cuts and nothing that feels like slides. Use when the user asks for a continuous shot, one take, no cuts, a single camera move, or says a film should not feel like a slideshow. Offers three concepts, writes a beat sheet, builds with the version-2 stage kit, runs check_sequence until no boundary is bare, and reviews on a contact sheet.\",\r\n \"instructions\": \"# Continuous shot\\n\\nOne take: a subject, a camera, and beats that each grow out of the one before. The shot is checked by measurement, not by feel: `check_sequence` says which beats start from nothing.\\n\\nLoad `sequence-core` (the stage kit API, including the version-2 verbs) and `motion` before building, and `look` for the palette and type. Load `direction` if the brief is thin. Never export; the user does that.\\n\\n## 1. Three concepts\\n\\nBefore any plan, offer **three concepts** that differ in their central picture, not in wording. Each is one sentence naming the subject and the world it travels through, e.g. \\\"a single dot that becomes the product\\\", \\\"a cursor that opens each screen\\\", \\\"a camera that falls into each detail\\\". Present them as choices the user can click (in the studio, as options) and wait for the pick. If the user is not there, pick the one that best matches the brief, say which, and continue.\\n\\n## 2. Beat sheet\\n\\nWrite the beat sheet (`reference/beat-sheet.md`) and show it: time · beat · what moves · what is still · what carries into the next beat. Choose a carry type for every boundary from `reference/carry-types.md`. Include the hook, one declared rest and the lockup. Ask for changes once; don't build until it is agreed, unless the user is away.\\n\\n## 3. Build\\n\\nCompose one sequence per act with `data-stage-version=\\\"2\\\"` on the stage root. Mark every beat with `tl.label(name, time)` (labels are what the check looks at). Use the carry verbs from the sheet, the operated camera (`tl.camera`, `tl.follow`, `tl.whip`, `tl.handheld`) and declared rests (`tl.rest`). Put `data-keep-in-frame=\\\"t0,t1\\\"` on the subject. Use `S.*` units, never pixels.\\n\\n## 4. Check until clean\\n\\nRun `check_sequence`. Fix every warning:\\n\\n- **bare boundary**: carry something across it with the verb its hint suggests, or redesign the beat;\\n- **framing**: keep the subject in frame, or follow it with the camera;\\n- **cadence**: vary the beat lengths;\\n- **hard cut**: hide it in a move or remove it.\\n\\nRepeat until no boundary is bare and every subject stays in frame. A carry score under 0.5 is never acceptable. A deliberate `tl.cut` is allowed only if the sheet says why.\\n\\n## 5. Contact sheet and review\\n\\nShow the boundary frames (the filmstrip `compose_sequence` returns, and the frames around every label) and grade the draft against `reference/scorecard.md`. Fix any fail, then tell the user which concept was built, the beat sheet in one line, and what to look at. Warnings never block an export, but don't hand over a draft with a bare boundary. When the user is ready to export the final, suggest motion blur (`motionBlur: {}` on the export): a continuous shot moves a lot, and the shutter smear is what keeps fast moves from strobing. It is slower to render, so it is for the final, not for drafts or previews.\\n\\n## More than one act\\n\\nIf the film needs more than one act, use the existing hand-off between acts: the next act starts from the element and camera state the last one ended on (see `sequence-core`, \\\"Hand-off between acts\\\"). Run `check_sequence` across the join; it reports a hard join between clips as `act-join`.\\n\\n## Sound and music\\n\\nSound moments are `tl.hit` events; carry landings, presses, impacts and shakes add their own. After the check is clean:\\n\\n1. Pick one material for the film's sounds (all wood, all glass, all soft synth) and add one CC0 sound per hit kind as audio assets.\\n2. `place_sfx_from_events` puts one sound at every hit and ducks the music under them. It reports where on screen each hit happened; audio clips have no pan yet, so that is not applied.\\n3. To land carries on the music: Get the beat grid of the music (the host analyses it) and write it with `set_beat_grid`, then build carries on `S.beat(n)` and `S.downbeat(n)`.\\n\\nSound effects on the big moments only; a hit on every beat is noise.\",\r\n \"files\": {\r\n \"reference/beat-sheet.md\": \"# Beat sheet\\n\\nWrite the sheet before any HTML. It is the plan the shot is built and checked against. Show it to the user before building.\\n\\n## Template\\n\\n| time | beat | what moves | what is still | what carries into the next beat |\\n|---|---|---|---|---|\\n\\n- **time**: start, in seconds; the last row is the end. Vary beat lengths: a film of equal beats reads as a metronome (the check warns under 0.25 variation).\\n- **beat**: a name, which becomes the `tl.label`.\\n- **what moves**: the subject and the camera.\\n- **what is still**: only elements in a declared `tl.rest`, or the persistent ground. Everything else moves, or says why not.\\n- **what carries**: which carry type (see `carry-types.md`) and which element survives.\\n\\nRules for the sheet:\\n- Row 1 is the hook: the first second shows something moving that makes a viewer ask what it is.\\n- Include at least one rest before the payoff, written as `rest 7.4–8.5` with what is allowed to move inside it.\\n- The last row is the lockup: the cast folds into the logo or name. Nothing new arrives.\\n- No row may say \\\"cut\\\" unless it is a deliberate `tl.cut`; give it a reason.\\n- Words: at most about 7 on screen at any time.\\n\\n## Worked example: a 14 s film for \\\"Kiln\\\", an invented app that schedules pottery studio time\\n\\n| time | beat | what moves | what is still | carries |\\n|---|---|---|---|---|\\n| 0.0 | spark | a glowing dot drifts across a dark dot-grid, camera follows | the grid | persistent ground; the dot is the subject |\\n| 1.6 | pot | the dot grows and morphs into a rounded vase silhouette; grid parallax shifts | none | same element, new job (morph) |\\n| 3.4 | slot | the vase hops to the first slot of a weekly calendar that unrolls from its base | the calendar frame | same element hops onto the calendar |\\n| 5.4 | fill | six more vases gather into the week; camera pulls back to show it | none | container (gather) |\\n| 7.4 | rest | nothing moves except the cursor's idle blink | everything but the cursor | rest 7.4–8.5, allow `.cursor` |\\n| 8.6 | press | the cursor presses \\\"Book\\\"; button squashes, screen shakes once | none | cause (press, shake, hit) |\\n| 9.2 | confirm | an iris opens from the button into a confirmation card | none | material state (iris) |\\n| 11.0 | lockup | the card folds into the wordmark \\\"Kiln\\\" on the grid | the grid | same element (morph) into the logo |\\n| 13.0 | end | a slow handheld float, URL fades in | the wordmark | none; end hold under 1.2 s counts as exempt |\\n\\nSheet checks: beat lengths 1.6, 1.8, 2.0, 2.0, 1.2, 0.6, 1.8, 2.0, 1.0 (varied); one rest, declared; every boundary names a carry; hook at 0; lockup at 11.\\n\",\r\n \"reference/carry-types.md\": \"# Carry types\\n\\nA continuous shot is a chain of beats where something from each beat becomes the next. Pick the carry for each boundary from this list, say which one in the beat sheet, and build it with the verbs named. `check_sequence` confirms it landed: the boundary must read **carried** (or **anchored** for a held ground).\\n\\nAll verbs need `data-stage-version=\\\"2\\\"`. Sizes are in `S.*` units so every aspect ratio works.\\n\\n## 1. Same element, new job\\n\\nOne element stays on screen and becomes something else: a dot becomes a button, a button becomes a card.\\nVerbs: `tl.morph(from, to)` (box, radius and colour become the target's; content rises in), `tl.hop(target, { to })` for an arc to a new slot.\\nExample: a seed-shaped dot on a dark ground morphs into a rounded search field; the placeholder text rises as it settles.\\n\\n## 2. Scale\\n\\nThe camera falls into a detail and the detail is the next world. What was a full stop is a planet; what was a window is a room.\\nVerbs: `tl.camera({ anchor, at, zoom })` for a deep zoom that keeps the anchor fixed on screen, `tl.zoomThrough(target, { into })` to hand over to an inner scene on a frame where only the target's surface is visible.\\nExample: push into the dot of an \\\"i\\\" until its surface fills the frame, and the next scene is lit from inside it.\\n\\n## 3. Cause\\n\\nA hit or press causes the next beat: the click that sends the message, the drop that breaks the surface.\\nVerbs: `tl.press(target, t)`, `tl.impact(target, pos)`, `tl.shake(pos)`, `tl.hit(name, pos)`. The effect of the cause (ripple, iris, scatter) starts on the same frame as the hit.\\nExample: a cursor presses \\\"Send\\\"; the button squashes, the screen shakes once, and the iris that opens from the button reveals the reply.\\n\\n## 4. Container\\n\\nMany small things gather into one box, which is the next beat's subject.\\nVerbs: `tl.gather(targets, { into })`, then `tl.morph` or `tl.hop` to move the container on.\\nExample: ten scattered receipts fly on fanned arcs into one folder; the folder is the card that comes forward.\\n\\n## 5. Persistent ground\\n\\nThe ground stays and the cast changes. A dot grid, a horizon or a paper texture is always on screen and the camera travels across it, so the eye never loses its place. This reads **anchored** when nothing else survives.\\nVerbs: `S.grid({ depth, spacing, dot })`, `data-depth` parallax layers, `tl.follow(target)`.\\nExample: a lattice of dots stays under every beat; a bright dot rolls through it and the camera follows.\\n\\n## 6. Split that collapses\\n\\nOne element breaks into parts that act separately, then the parts rejoin as a different whole.\\nVerbs: `tl.gather` in reverse reads as the collapse; for the split, `tl.hop` each part to its own slot with a short stagger. Hide the original at the instant of the split.\\nExample: a headline splits into words that hop to the corners of a diagram, then fold back into the product name.\\n\\n## 7. Material state\\n\\nAn element changes state: solid to liquid, flat to deep, light to dark. The shape or position stays, the surface changes.\\nVerbs: `tl.morph` with `radius` and colour, `tl.iris` for a lighting change that spreads from a point, `data-lod` to fade detail in as the camera nears.\\nExample: a flat white card sharpens into a screen glowing from within, as an iris of dark opens from its centre.\\n\\n## 8. The subject as the pointer\\n\\nThe subject (a cursor, a ball, a finger) leads the eye, and the camera and the next beat are where it points or lands.\\nVerbs: `tl.pointer({ path, clicks })`, `tl.follow(target, { lead })`, `tl.whip(target)`.\\nExample: a cursor slides to a toggle, clicks, and the whip pan lands on the panel it opened.\\n\\n## Choosing\\n\\n- Every boundary needs one. If you can't name which, the beat is a cut: either redesign it or declare `tl.cut` and treat it as a deliberate hard cut.\\n- Don't use the same carry on more than two boundaries in a row; the shot starts to feel like a formula.\\n- Pair a carry with a sound moment (`tl.hit`): the landing is where the picture and the sound agree.\\n- A rest (`tl.rest`) is not a carry; it is a pause between two carries, and it must be declared.\\n\",\r\n \"reference/scorecard.md\": \"# Scorecard (review)\\n\\n`check_sequence` measures carry, framing and rhythm. These points it can't measure. Grade every draft against them, say what you changed, and show the contact sheet.\\n\\n1. **A hook in the first second.** Something moves and raises a question before any text is read. Fail: a logo, a title card, a fade in from black.\\n2. **One subject the camera chases.** A single element the viewer can name (the dot, the cursor, the vase) is followed through the film. Fail: the camera wanders between equal elements.\\n3. **A rest before the payoff.** One declared `tl.rest` (0.6–1.2 s) before the key moment, so the payoff lands. Fail: constant motion with no pause, or an undeclared still.\\n4. **Small elements on a big ground.** The subject is small against a large ground at some point, so scale changes can be felt. Fail: everything fills the frame at all times.\\n5. **No confetti or flourishes that aren't the product's own behaviour.** A particle burst, lens flare or sparkle that the product wouldn't do on its own is removed. A button squashing when pressed is fine.\\n6. **A lockup the cast folds into.** The last beat is the logo or name made out of what has been on screen, not a new card. Fail: a fresh end card fades in.\\n7. **A look chosen fresh.** Palette, type and ground are chosen for this subject (see the `look` skill), not reused from the last film or the sample.\\n\\nGrade each: pass, weak (say how to fix) or fail (fix now). Don't present a draft with a fail.\\n\"\r\n }\r\n },\r\n {\r\n \"name\": \"direction\",\r\n \"description\": \"Direct a film from a brief before building it — the world or metaphor, the protagonist element and how it evolves, acts and a timed beat sheet with the technique and carry-over for each beat, the words per beat, the look and the music direction. Load it first for any film made from a brief.\",\r\n \"instructions\": \"# Direction\\n\\nA film that feels made, not assembled, was directed first. Before any tool that builds, write a **treatment**: one page that decides what the film is, and what happens on screen second by second. Then the look (`look` skill), the media and music (`media-sound`), and the acts (`sequence-core` + `motion`) follow from it.\\n\\n## 1. The idea\\n\\n- **The world.** One sentence: what world the film lives in, a metaphor taken from the subject.\\n - A CRM's tools are planets that collide into one.\\n - A course on agents is a live agent console.\\n - A recipe book is a kitchen notebook.\\n - A city break is a route drawn on a map.\\n\\n Not \\\"a modern, clean video\\\".\\n- **The protagonist element.** One thing on screen that the film follows and transforms. This is what makes a film continuous instead of a slideshow. Examples:\\n - ten orbs drift, collide and become one logo, which becomes a window, which opens into the product;\\n - a cursor types a question, the question becomes a search bar, the results become the film's chapters;\\n - a line draws a route, stops become chapters, and the route closes into the logo.\\n\\n Write the chain: element → what it becomes → what that becomes.\\n- **The promise.** What the viewer should know or feel at the end, in one line. Every beat serves it.\\n\\n## 2. Acts and beats\\n\\n- **Length:** what the user asked for, within 10%. Otherwise:\\n - 15–30 s for a launch, promo or intro;\\n - 30–45 s for a trailer;\\n - 45–90 s for a documentary.\\n- **Acts:** 2–4 acts of 8–25 s, each one `compose_sequence`. An act is one movement of the idea. A 15 s film is one or two acts.\\n- **Beats:**\\n - 0.5–2 s each in motion graphics; 3–6 s in a documentary.\\n - Each beat is **one idea and one move**, and says something new.\\n - Each beat begins from something the last beat left on screen.\\n\\nWrite the beat sheet as a table, one row per beat:\\n\\n| Time | Beat (what it says) | On screen (what moves) | Technique | Carries into the next |\\n|---|---|---|---|---|\\n| 0.0–1.4 | Ten tools. Ten bills. | Six orbs drift in, labels on; the line types above | staggered-entrance, typewriter | The orbs |\\n| 1.4–2.6 | One place. | The orbs accelerate inwards, collide, one big orb | collide-and-merge | The big orb |\\n| … | | | | |\\n\\n- **Techniques** come from the `motion` skill's index. Pick one main technique per beat.\\n- **Carry-over** is the element that bridges to the next beat: a word, a shape, a picture, a colour, the camera's move. If a row has no carry-over, the cut must be hidden (blur-through, zoom-through, whip-pan, match-cut), or the beat must change.\\n- **Words:** at most about 7 words on screen per beat, and a reading budget of about 3–4 words a second plus half a second. Real material only: no invented people, numbers, reviews or quotes. Every word on screen is in the beat sheet.\\n\\n## 3. The arc by format\\n\\nUse these as guides. Reorder, merge or add the beats the brief needs.\\n\\n- **Launch / promo / intro:** the viewer's problem (concrete, visual) → the turn (the product appears, out of the problem) → 2–4 things it does, shown in action → proof (a real number or name) → the call to action.\\n- **Course:** the learner's goal → the course (level, length, format) → what you learn → a lesson moment → what you build → who teaches (only a real, named instructor) → what you leave with → how to join.\\n- **Explainer:** the question → the idea, drawn → the steps, each growing from the last → the result.\\n- **Trailer:** a quiet cold open → the title → who → the stakes → a build of short hits → the title reveal.\\n- **Documentary:** cold open → title → chapters of stills, people, facts and quotes → the reveal. Slower beats, still moving.\\n\\n## 4. Look and sound direction\\n\\nIn the treatment, name:\\n\\n- **The look**, in one line: palette, type and finish from the subject (`look` skill), and the grid and picture treatment.\\n- **The camera's character**: floating and tilting (playful), pushing and whipping (energetic), slow pushes and drifts (reflective).\\n- **The music**: mood, tempo and where it hits (the collide, the title reveal), and the sound effects for the big moves (`media-sound`).\\n\\n## 5. Learn from breakdowns\\n\\nThe `motion` skill's `reference/breakdowns/` are grammar studies of whole films. Read the one closest to your brief with `read_skill_file`, and borrow its grammar, never its words or look:\\n\\n| Breakdown | What to borrow |\\n|---|---|\\n| `typed-ui-launch.md` | Typed type (typewriter, retype-in-place); a title becoming a window (carry-over, morph); blur-through cuts; zoom-out-reveal from five to five hundred; scale-to-fill and match-cut on the wordmark |\\n| `editorial-explainer.md` | A rail or timeline that draws (line-draw); labelled cut-outs that slide on an editorial grid (staggered-entrance, parallax); highlight-sweep on key terms; zoom-through into a detail |\\n| `kinetic-type-promo.md` | Words as the whole picture (kinetic-type, swap-in-place, scale-to-fill); cuts on the beat; whip-pan between statements; accent-punctuation |\\n| `documentary-trailer.md` | Slow push and parallax on stills; mask-reveal titles; blur-through between chapters; a count-up for a real figure; holds that stay alive |\\n| `playful-shapes.md` | Shapes as characters (pop-in, overshoot, orbit); collide-and-merge into the brand; split-and-scatter; depth-stack and a tilting camera |\\n\\n## 6. Check the treatment\\n\\nBefore building, check that:\\n\\n- the world and the protagonist element come from *this* subject, not a template;\\n- the chain of transformations runs from the first beat to the last;\\n- every beat has one idea, a technique and a carry-over (or a hidden cut);\\n- the words are real, and at most about 7 per beat;\\n- the length adds up to what was asked;\\n- the end lands: the promise, then a held end card (at most 1.5 s, still drifting).\\n\\n## 7. Record it, show it, then build\\n\\n1. `plan_film`, with the look and the concept taken from the treatment:\\n - idea = the world;\\n - motif = the protagonist element;\\n - layout = the grid;\\n - motion = the camera's character.\\n2. `direct_film` with the treatment: the world, the promise, the protagonist chain, the camera, the music (mood, and tempo if known), and the acts with their beats. For each beat give its time, the exact words on screen, what moves, the technique and the carry-over.\\n - It checks the treatment (lengths, words, reading time, techniques, carry-overs) and refuses it with the fixes when something is off.\\n - When it accepts the treatment, it returns a storyboard that the user sees in the chat.\\n3. **Wait for approval.** Unless the user said to just make it, end your turn there. Summarise the world, the protagonist chain and the acts in two or three lines. The chat offers **Approve & build** and **Change direction**.\\n - If the user asks for changes, revise the treatment and call `direct_film` again.\\n - The acts can't be built until the treatment is approved.\\n4. After approval:\\n - get the pictures (`media-sound`);\\n - add the music and take its beat grid (`get_beat_grid`), which snaps the beats to the music;\\n - compose one `compose_sequence` per act, in order, each starting where the last ended, and fix each act from its filmstrip before the next;\\n - place the sound effects on the big moves;\\n - check the whole film with `preview_film`.\\n\\nIn your reply, give the world and the protagonist chain in one line each, then the acts and their beats in one line each.\\n\\n`reference/treatment.md` is a blank treatment to fill in, with a worked example.\",\r\n \"files\": {\r\n \"reference/treatment.md\": \"# Treatment\\n\\nFill this in before building. Keep it to one page: it is a decision, not an essay.\\n\\n```\\nFilm: <subject> — <format>, <length> s, <aspect>\\nPromise: <what the viewer knows or feels at the end, one line>\\nWorld: <the metaphor the film lives in, one sentence>\\nProtagonist: <element> → <becomes> → <becomes> → <ends as>\\nLook: <palette (bg / ink / accent / second)>, <type>, <finish>, <grid>, <picture treatment>\\nCamera: <its character: floating / pushing / drifting …>\\nMusic: <mood>, <tempo>, hits at <times>; SFX on <moves>\\n\\nAct 1 — <name> (<start>–<end> s)\\n| Time | Beat | On screen | Technique | Carries into the next |\\n…\\nAct 2 — <name> (<start>–<end> s)\\n…\\nEnd card: <the line>, held <≤1.5> s, still drifting\\n```\\n\\n## Worked example\\n\\nA fictional neighbourhood bakery app, \\\"Crumb\\\", which lets you order bread for collection before it sells out.\\n\\n```\\nFilm: Crumb — launch, 15 s, 16:9\\nPromise: Order the loaf tonight; it's waiting for you at 8.\\nWorld: The bakery's morning, told by one loaf of bread.\\nProtagonist: a loaf (a rounded shape) → it rises → it becomes the app's order card →\\n the card's \\\"Ready 8:00\\\" becomes a clock → the clock face becomes the logo's O\\nLook: flour-white bg #f6f1e7, crust-brown ink #3b2a1f, accent oven orange #e8772e,\\n second rye #8a6a4f; a rounded serif for titles, a soft sans for labels;\\n paper grain; centred axis; no photos — the loaf is drawn\\nCamera: warm and slow: gentle pushes, one tilt when the card appears, a pull back at the end\\nMusic: light acoustic pluck, ~110 bpm (a bar ≈ 2.2 s), a soft hit at 7.6 s (the order\\n is placed); SFX: a pop on the card, ticks on the clock, a bell on the logo\\n\\nAct 1 — Sold out (0–7.6 s)\\n| 0.0–1.6 | \\\"By 9, it's gone.\\\" | Empty shelf line; the loaf shape slides off it and away | staggered-entrance, motion-blur | The shelf line |\\n| 1.6–3.2 | \\\"Every morning.\\\" | The shelf line becomes a sunrise arc; the loaf rises over it like the sun | morph, path-follow | The loaf |\\n| 3.2–5.4 | \\\"Not anymore.\\\" | The loaf drops into the frame centre and puffs (it rises) | overshoot | The loaf's outline |\\n| 5.4–7.6 | (no words) | The outline stretches into a rounded card; \\\"Sourdough · 1\\\" types in | morph, typewriter | The card |\\n\\nAct 2 — Ready at 8 (7.6–15 s)\\n| 7.6–9.4 | \\\"Order tonight.\\\" | A \\\"Reserve\\\" button pops; tap ripple; the card tilts towards us | pop-in, depth-stack | The card's time row |\\n| 9.4–11.4 | \\\"Ready at 8:00.\\\" | \\\"8:00\\\" swells; its digits roll from 23:14 to 8:00 | count-up, zoom-through | The clock digits |\\n| 11.4–13.5 | \\\"Crumb.\\\" | The 0 of 8:00 is pulled out; it becomes the O in the wordmark | carry-over, match-cut | The wordmark |\\n| 13.5–15.0 | \\\"crumb.app\\\" | The URL rises under the wordmark; slow pull back, grain | mask-reveal, holds | — |\\n```\\n\\nEvery beat has one idea and one move, and something that carries over. The words are at most 4 per beat. The length is 15 s, as asked.\\n\"\r\n }\r\n },\r\n {\r\n \"name\": \"edit-video\",\r\n \"description\": \"The base playbook for editing in a Miraiclip editor - the edit loop, timing and layer rules, and where the craft references are (formats, safe zones, type, pacing, HTML title recipes, motion). Load it for any edit bigger than one change, and before any other skill.\",\r\n \"instructions\": \"# Edit video in the editor\\n\\nYou edit the user's open project with tools. Each request lands as one undo step when you finish. You can't watch the video, so you check your work by reading the project back (`get_state`) and by doing the geometry and timing in numbers.\\n\\n## Pick the workflow\\n\\n| The user wants… | Load |\\n| --- | --- |\\n| A recorded talk, interview or vlog tightened and packaged (punch-ins, captions, lower third, music) | `talking-head` |\\n| Captions or subtitles | `captions` |\\n| A launch, intro, feature promo or announcement for a product, service or business, from a brief, with no footage or only screenshots | `launch-video` (a recipe on top of `make-film`) |\\n| Any other film from a brief: a course or programme promo, a trailer or teaser for a show, film, book or event, a documentary, biography, recap or explainer | `make-film` |\\n| Motion graphics, an animated sequence or a title sequence on its own | `sequence-core` and `motion` (and `look` for its colours and type) |\\n| The same video in another shape (9:16, 1:1, 4:5, 16:9) | `reformat` |\\n| A screen recording turned into a tutorial or walkthrough | `screen-tutorial` |\\n| A reusable template | `make-template` |\\n| One change (a title, music, a transition, a look) | No other skill: the loop below |\\n\\n## The loop\\n\\n1. **Read** the project summary (or `get_state`). Use the ids it shows.\\n2. **Settle the brief** if the request is open: shape, length, tone, the one thing to remember. Ask one short question only if guessing would waste the edit.\\n3. **Plan** the edit as a list of steps. Give new clips and tracks readable ids (`title`, `cap-3`, `s2`).\\n4. **Apply**: prefer the high-level tools (`add_transition`, `animate_clip`, `add_effect`, `trim_clip`, `close_gaps`, `set_keyframes`, `set_background`, and any text, caption or audio tools the editor has). Batch everything else in `apply_commands`; it is all-or-nothing, so a failed batch applied nothing: fix and resend all of it.\\n5. **Check**: `get_state` after big changes. Check every new clip's start and end against its neighbours, every text against the safe zone, and that nothing ends after the video does.\\n\\n## Rules that prevent most failures\\n\\n- **Commands use integer microseconds** (1 s = 1000000). High-level tools take seconds.\\n- **Tracks are layers**: the first track is the bottom. Bottom-up: background → footage → B-roll/overlays → titles → captions. Add a track with `track/add` before putting clips on it.\\n- **Position with `transform`**: `x`, `y` are the clip's centre as fractions of the frame (0.5, 0.5 = centre); `scale: 1` fits the clip in the frame. `transform` in `clip/set-property` merges.\\n- **Keyframe times are relative to the clip's start**; `anchor: \\\"end\\\"` measures from its end so exits follow trims.\\n- **Transitions** need two clips on one track that touch exactly, and source media past the cut. Text, image, caption and HTML clips always have enough; a video clip that plays its whole file has none until trimmed.\\n- **Look up a command's schema** (`get_command_schema`) the first time you use it. `reference/commands.md` is the short list.\\n- **Never invent media.** Use assets in the project or ones a tool returned. Never invent stats, quotes or testimonials either: use the user's, or mark them as placeholders.\\n- **Never present sample media as the brand's product.** A sample or placeholder screenshot in a browser frame labelled with the brand's address tells viewers \\\"this is their product\\\" when it isn't. Use screenshots the user supplied; otherwise design an abstract UI card in HTML (shapes, the brand's colours, their real feature names), or leave that scene out.\\n- **HTML must parse as XML** (`reference/html-recipes.md` → Rules); the editor rejects a clip that won't, with the reason. `asset:<id>` must name an asset in the project.\\n\\n## Brand and direction\\n\\nDecide the look and the voice before the first scene, in this order:\\n\\n1. **What the user asked for wins.** Colours, theme, style, mood, references (\\\"like Apple\\\", \\\"playful\\\", \\\"dark and minimal\\\"), pacing and tone of voice in the request are applied as asked, even when they differ from the brand's own. Don't trade them for a default.\\n2. **Otherwise, the brand's own identity.** For a named brand, company, product or website, use its real look and voice: call `get_brand` if the editor has it (the saved brand kit, or the brand's website: colours, fonts, logo, and its own headlines, buttons and copy). Use its primary colour for the accent moments, its background and text colours or a dark/light version that fits them, its fonts (or the closest the editor has), and its logo on the end card.\\n3. **Only when there's nothing to go on**, use the base kit's neutral palette, and say so in your reply so the user can give you their colours.\\n\\nMix them sensibly: \\\"make it energetic\\\" for a brand with a calm site keeps the brand's colours and type and changes the motion and copy rhythm.\\n\\n**Voice**: write the copy the way the brand writes. Reuse its own terms and phrases (product names, the words in its headlines and buttons), match its sentence length and tone (plain, bold, technical, warm), and keep its capitalisation. Facts, numbers and claims come only from the user or the brand's own copy.\\n\\n## Making it look good\\n\\nRead `reference/craft.md` before building titles, graphics or captions: formats, safe zones, type sizes, pacing, motion timing, colour, audio levels and a review checklist.\\n\\n- **Fill the frame.** A full-frame scene's root is the composition's size (1920×1080 or 1080×1920): lay content across it on the grid (16:9: x 160–1760 px), with headlines at the craft sizes (hero 110–160 px). A small block of content in one corner of a big empty frame reads as unfinished.\\n- **Designed text**: if the editor has a text-style tool (`add_text_style`), start there. For anything custom, use **HTML clips** (`clip/add` with `kind: \\\"html\\\"`) built from the tested patterns in `reference/html-recipes.md` (real CSS layout, fonts, gradients, `@keyframes`). Images inside HTML are `src=\\\"asset:<id>\\\"`.\\n- **Moving whole clips**: `animate_clip` (presets: in, loop, out, easing). Hand-made moves are `set_keyframes`. `reference/motion.md` has the presets and the useful moves (punch-in, picture-in-picture, push-in, ducking).\\n- **Looks**: `add_effect`. At most one look per video, low intensities (0.1–0.4).\\n- **Audio**: music under speech at about 0.15–0.3, fade in 1 s and out 2 s, ends with the video.\\n\\n## Before you finish\\n\\nGo through `reference/craft.md` → Review checklist against the project state: text inside safe zones and readable sizes, no overlaps between captions and lower thirds, no gaps or black frames between clips, no clip running past the end, consistent fonts and colours.\\n\\n## Hand-off\\n\\nReply in one to three sentences: what the video now is or what changed (times in seconds, names over ids), and anything the user should check (a claim on screen, a music choice). Suggest previewing the parts you changed.\",\r\n \"files\": {\r\n \"reference/commands.md\": \"# Commands (short reference)\\n\\nThe source of truth is `get_command_schema` on the live server. Times are integer microseconds. `?` = optional.\\n\\n## Project, assets, tracks\\n\\n| Command | Payload |\\n| --- | --- |\\n| `project/set-settings` | `{ width?, height?, fps?, name? }` |\\n| `asset/add` | `{ id, kind: \\\"video\\\"\\\\|\\\"audio\\\"\\\\|\\\"image\\\"\\\\|\\\"font\\\", src, durationUs?, width?, height?, fps?, name? }`; fonts: `family`, `weight?`, `style?`, `weightRange?`; provenance: `source?`, `license?`, `attribution?` |\\n| `asset/remove` | `{ id }` (fails while a clip uses it) |\\n| `track/add` | `{ id, kind: \\\"video\\\"\\\\|\\\"audio\\\", name?, index? }` (later = on top) |\\n| `track/remove` / `track/rename` / `track/reorder` | `{ id }` / `{ trackId, name }` / `{ trackId, index }` |\\n| `track/set-property` | `{ trackId, muted?, solo?, locked?, hidden? }` |\\n\\n## Clips\\n\\n`clip/add` payload by `kind` (all take `id`, `trackId`, `startUs`, `durationUs`, `transform?: { x, y, scale, rotation, opacity }`):\\n\\n| kind | Extra fields |\\n| --- | --- |\\n| `video` | `assetId`, `trimStartUs?` (in-point in the file), `volume?`, `fadeInUs?`, `fadeOutUs?` |\\n| `audio` | same as video (goes on an audio track) |\\n| `image` | `assetId` |\\n| `text` | `text` (`\\\\n` for lines), `fontFamily?`, `fontSizePx?` (48), `color?`, `fontWeight?`, `fontStyle?`, `lineHeight?`, `letterSpacing?` (em), `textAlign?` |\\n| `caption` | `words: [{ text, startUs, durationUs }]` (times relative to the clip), `style?` (see below) |\\n| `html` | `template`, `params?`, `widthPx?`, `heightPx?`, `animated?`, `animationOffsetUs?` |\\n\\nCaption `style`: `preset` (`plain`, `highlight`, `karaoke`, `pop`, `reveal`), `display` (`block`\\\\|`word`), `fontFamily`, `fontSizeFrac` (0.06 = 6% of frame height), `fontWeight`, `color`, `highlightColor`, `backgroundColor`, `activeBackgroundColor`, `textTransform` (`uppercase`), `strokeColor`, `strokeWidthFrac`, `shadowColor`, `shadowBlurFrac`, `shadowOffsetFrac`, `lineHeight`, `letterSpacing`. Style merges on `clip/set-property`; `null` clears optional fields.\\n\\n| Command | Payload |\\n| --- | --- |\\n| `clip/remove` | `{ clipId }` |\\n| `clip/move` | `{ clipId, startUs?, trackId? }` |\\n| `clip/trim` | `{ clipId, startUs?, durationUs?, trimStartUs? }` |\\n| `clip/split` | `{ clipId, atUs, newClipId? }` (`atUs` is timeline time) |\\n| `clip/duplicate` | `{ clipId, newClipId?, startUs?, trackId? }` |\\n| `clip/set-property` | `{ clipId, …fields of its kind }`: `transform` merges; html: `template`, `params` (merge), `unsetParams`, `widthPx`/`heightPx` (`null` = composition size), `animated`; custom kinds: `props` (merge), `unsetProps` |\\n| `clip/set-speed` | `{ clipId, speed, keepDuration?, ripple? }`: video/audio, 0.0625–16 (0 = freeze, video only). Default keeps the footage, so the length changes; `keepDuration: true` keeps the length; `ripple: true` moves later clips on the track |\\n| `clip/freeze` | `{ clipId, atUs, durationUs, freezeClipId?, rightClipId?, ripple? }`: hold the frame at `atUs` (timeline time; the clip's end holds its last frame). Splits the clip and pushes the rest of the track back unless `ripple: false` |\\n\\nThere is no ripple delete. To remove a section and close the gap, `clip/split` at both ends, `clip/remove` the middle, and `clip/move` every later clip left by the removed length, or rebuild the sequence with `mc recut`.\\n\\n## Keyframes, effects, transitions\\n\\n| Command | Payload |\\n| --- | --- |\\n| `keyframe/set` | `{ clipId, property: x\\\\|y\\\\|scale\\\\|rotation\\\\|opacity\\\\|volume, timeUs, value, easing?, anchor?: \\\"start\\\"\\\\|\\\"end\\\" }` |\\n| `keyframe/remove` | `{ clipId, property, timeUs, anchor? }` |\\n| `keyframe/clear` | `{ clipId, property? }` |\\n| `effect/add` | `{ clipId, kind, params?, enabled?, index?, effectId? }` |\\n| `effect/update` | `{ clipId, effectId, params?, enabled?, region? }` (params merge; `region: null` = whole clip) |\\n| `effect/set-region-keyframe` | `{ clipId, effectId, property: x\\\\|y\\\\|width\\\\|height, timeUs, value, easing?, anchor? }` |\\n| `effect/clear-region-keyframes` | `{ clipId, effectId, property? }` |\\n| `effect/remove` / `effect/reorder` | `{ clipId, effectId }` / `{ clipId, effectId, index }` |\\n\\n`effect/add` takes `region?: { x, y, width, height, shape?: \\\"rect\\\"|\\\"ellipse\\\", feather?, invert? }` (fractions of the CLIP's frame, top-left origin) to apply the effect to part of the clip only: `blur` or `pixelate` a password field, or `colorAdjust` with `brightness: -0.5` and `invert: true` for a spotlight. The region follows the clip through zooms and moves.\\n\\nEffect ids are generated unless you pass `effectId` in `effect/add`. Pass one (e.g. `\\\"grade\\\"`) so you can update it later without reading the project JSON.\\n| `transition/add` | `{ kind, fromClipId, toClipId, durationUs, params?, id? }` |\\n| `transition/update` / `transition/remove` | `{ transitionId, durationUs?, params? }` / `{ transitionId }` |\\n\\nTransition kinds: `crossDissolve`, `dipToBlack`, `dipToWhite`, `wipe` and `slide` (`params.direction`: `left`, `right`, `up`, `down`).\\n\\n## Markers and plugins\\n\\n| Command | Payload |\\n| --- | --- |\\n| `marker/add` | `{ id?, timeUs, label, durationUs?, data? }`: a chapter, tutorial step or note (doesn't render) |\\n| `marker/update` | `{ markerId, timeUs?, label?, durationUs?\\\\|null, data?\\\\|null }` |\\n| `marker/remove` | `{ markerId }` |\\n| `plugin/add` | `{ name, version, url, integrity?, replace? }`: a plugin module whose custom kinds the document uses (the server must run with `--allow-plugins`) |\\n| `plugin/remove` | `{ name }` |\\n\\n## Effect kinds (param = default)\\n\\n| Category | Kinds |\\n| --- | --- |\\n| color | colorAdjust(brightness=0, contrast=0, saturation=0, hue=0), mono(intensity=1), invert, warm(intensity=1), cool(intensity=1), hueShift(hue=90), vibrance(amount=0.5), exposure(stops=0.5), contrastCurve(intensity=0.7), gamma(gamma=1.4), channelSwap, duotone(intensity, shadow=#1e1b4b, highlight=#f472b6), gradientMap(intensity, dark, mid, light), colorPop(intensity, color=#ef4444, tolerance=0.08), solarize, threshold, posterize(levels=4) |\\n| film | sepia, vintage(0.8), polaroid, kodachrome, technicolor, thermal, grain(intensity=0.3), tealOrange, noir, bleachBypass, crossProcess, faded, matte, goldenHour, moonlight, cyberpunk, vaporwave, cyanotype, infrared, lomo, nightVision (all `intensity`, default 1) |\\n| stylize | pixelate, halftone, dotMatrix, crosshatch, neonEdges(intensity, color), sketch, emboss, sharpen(amount=0.8), toon, oilPaint, hexPixelate, retro8bit, dither, frostedGlass |\\n| glitch | rgbSplit, scanlines, crt, vhs, glitch, tvStatic, lensFringe |\\n| light | blur(amount=0.02), vignette(intensity=0.6, size=0.5), glow(intensity=0.6, radius, threshold=0.55), dreamy, tiltShift(blur, focus=0.5, band=0.25), zoomBlur, motionBlur(distance, angle=0), colorVignette(intensity=0.8, color=#7c3aed, size=0.5), lightLeak(intensity=0.8, color=#ff7a18) |\\n| distort | fisheye, pinch, swirl, wave, ripple, mirrorX, mirrorY, kaleidoscope |\\n| frame | chromaKey(color=#00ff00, similarity=0.4, smoothness=0.1, spill=0.1), letterbox(size=0.12), roundedCorners(radius=0.08) |\\n\\nSizes are fractions of the frame. Film looks at intensity 1 are strong: start at 0.2–0.4.\\n\\n## Errors\\n\\nA failed `dispatch` / `apply_commands` changes nothing. `apply_commands` reports `failedIndex` and the error:\\n\\n| `kind` / code | Fix |\\n| --- | --- |\\n| `unknown-command` | Use a type from `list_commands` |\\n| `invalid-payload` | Per-field issues listed; check `get_command_schema` |\\n| `clip-not-found`, `track-not-found`, `asset-not-found`, `effect-not-found`, `transition-not-found`, `keyframe-not-found` | Read ids from `get_state` (or `get_project_json` for effect ids); earlier commands in the same batch count |\\n| `duplicate-id` | Pick a new id |\\n| `not-adjacent`, `different-tracks`, `duplicate-boundary` | Transition clips must touch on one track; one transition per cut |\\n| `insufficient-handles` | Trim the clips so each has half the transition's length of extra media past the cut, or shorten the transition |\\n| `not-html`, `not-text` | That field belongs to another clip kind |\\n| `font-needs-family` | Font assets need `family` |\\n\",\r\n \"reference/craft.md\": \"# Video craft\\n\\nConcrete defaults for videos people watch to the end. Break them when the brief says to, not by accident.\\n\\n## Formats\\n\\n| Target | Settings (`project/set-settings`) | Notes |\\n| --- | --- | --- |\\n| YouTube, website, landing page, slides | 1920×1080, 30 fps | Default. 60 fps only for screen recordings with fast scrolling |\\n| Reels, TikTok, Shorts, Stories | 1080×1920, 30 fps | Design for sound off and a thumb over the bottom |\\n| Instagram/LinkedIn feed | 1080×1350 (4:5) or 1080×1080 | 4:5 takes more of the feed |\\n| Footage shot at 24/25/29.97 | Match the footage's fps | `fps: 29.97` is stored exactly |\\n\\nSet the format before adding clips. Changing it later means repositioning everything (the `reformat` skill does that).\\n\\n## Safe zones\\n\\nKeep text and faces inside these margins (fractions of the frame):\\n\\n| Shape | Keep clear |\\n| --- | --- |\\n| 16:9 | 5% on every side (title safe). Lower thirds sit at `y ≈ 0.82–0.86` |\\n| 9:16 | Top 12% (status bar, account name), bottom 22% (caption, buttons), right 15% (like/share column). Captions at `y ≈ 0.68–0.72`, titles at `y ≈ 0.25–0.4` |\\n| 1:1, 4:5 | 6% on every side |\\n\\n## Type\\n\\n- One font family per video, two weights (e.g. 800 for headlines, 500 for body). With no brand font, use **Inter** (OFL): download `InterVariable.woff2` from https://github.com/rsms/inter/releases into the folder and register it once: `{ \\\"type\\\": \\\"asset/add\\\", \\\"payload\\\": { \\\"id\\\": \\\"inter\\\", \\\"kind\\\": \\\"font\\\", \\\"src\\\": \\\"InterVariable.woff2\\\", \\\"family\\\": \\\"Inter\\\", \\\"weightRange\\\": [100, 900] } }`. System fonts differ between the machine you preview on and the one that exports, so a registered font also keeps the look stable. In an editor, register the font by URL (`src` an https URL) or use one the project already has.\\n- Sizes for a 1920×1080 frame (scale by frame width for other shapes; on 1080×1920 use roughly the same pixel sizes, since the width is about the same):\\n\\n| Role | Size | Weight | Letter spacing |\\n| --- | --- | --- | --- |\\n| Hero headline (≤ 6 words) | 110–160 px | 800 | −0.02 to −0.04 em |\\n| Headline | 80–96 px | 800 | −0.02 em |\\n| Body / supporting line | 34–44 px | 500 | 0 |\\n| Eyebrow / label | 24–28 px | 600, uppercase | 0.2–0.32 em |\\n| Captions | `fontSizeFrac` 0.045–0.06 (16:9), 0.035–0.045 (9:16) | 700–800 | 0 |\\n\\n- Max ~7 words on screen at once for a headline, ~12 for a supporting line. If it needs more, it needs another scene.\\n- Reading time: on screen for at least `0.3 s × words + 1 s`. A 5-word title needs ≥ 2.5 s settled (after its entrance finishes).\\n- Contrast: white or near-white text on dark, or dark on light. Over footage, add a scrim (a gradient behind the text) or a text shadow, never bare text on a busy shot.\\n- Left-align multi-line text. Centre only short single lines and end cards.\\n\\n## Colour\\n\\n- Pick a palette before building: background, ink (text), muted text, one accent, optionally a second accent. Write them as CSS variables in every HTML clip so scenes match.\\n- Dark, slightly coloured backgrounds (#0a0d18 rather than #000) look finished. Pure black and pure white backgrounds look like placeholders.\\n- Accents go on one thing per scene: the key word, the number, the button.\\n- Effects as a look: one per video, low intensity (`grain` 0.1–0.15, `vignette` 0.2–0.3, `warm`/`cool` 0.2–0.4). Never stack several looks.\\n\\n## Pacing\\n\\n| Content | Shot or scene length |\\n| --- | --- |\\n| Social promo, hook | First 1.5–2 s must say what this is. No logo intro first |\\n| Motion-graphics scene | 2.5–4 s each, including the entrance |\\n| Talking head | Cut every pause > 0.4 s. Punch in (scale 1.1–1.2) on every other segment, or every 5–8 s, to hide jump cuts |\\n| B-roll over voice | 2–4 s per shot |\\n| End card | 2.5–3.5 s, holds still long enough to read the URL |\\n\\n- Total length: social 15–45 s, product launch 30–75 s, explainer 60–120 s. Shorter is almost always better.\\n- Structure for promos: hook → problem or promise → 2–4 proof points (one per scene) → call to action.\\n- Enter elements in reading order with 80–150 ms between them (stagger). Everything arriving at once reads as a slide, not a video.\\n\\n## Motion\\n\\n- Entrances: 400–700 ms, ease-out (`cubic-bezier(.2,.8,.2,1)` in CSS, `smooth` or `snappy` presets). Exits: 250–400 ms, ease-in, or let a transition carry them.\\n- Distances: slide 40–80 px (or `OFFSET` 0.12 for presets), scale from 0.9–0.95 (subtle) or 0.6 with overshoot (pop) for one hero element per scene.\\n- One continuous motion per scene keeps it alive: a slow Ken Burns on the background (`--loop kenburns`), a playhead sweep, a counter.\\n- Between scenes: `crossDissolve` 400–600 ms by default. `slide` or `wipe` only when there's a reason (a list advancing, before/after). `dipToBlack` for a change of chapter.\\n- No spins, bounces or glitches unless the brief is playful.\\n\\n## Audio\\n\\n| Situation | Volume |\\n| --- | --- |\\n| Music alone | 0.8–1.0 |\\n| Music under a voice | 0.15–0.3, or duck: keyframe `volume` down 300 ms before speech, back up after |\\n| Voice | 1.0 (raise up to 1.5 if quiet) |\\n| Sound effects (whoosh on transitions, click on CTA) | 0.4–0.7, sparingly |\\n\\n- Music: fade in 0.5–1 s (`fadeInUs`), fade out 1.5–3 s ending with the video. Trim the clip to end exactly at the video's end.\\n- Cut on the beat when you can: line scene starts up with strong beats (ask the user for the BPM, or listen for where the track changes).\\n- If there's no music file, say so. Don't invent one; offer to make the video without music or ask the user to add a licensed track to the folder.\\n\\n## Honest content\\n\\n- Numbers, claims, customer names and quotes on screen must come from the user's material. If you're not sure a claim is true, ask or leave it out.\\n- Don't put logos or brand names of other companies on screen unless the user supplied them.\\n\\n## Review checklist\\n\\nGo through it on the contact sheet and on full-size `preview_frame`s (in an editor without previews: against the project state, in numbers):\\n\\n1. **First frame and first 2 s**: is there something to look at immediately, and does it say what the video is?\\n2. **Legibility**: every piece of text readable at phone size, on screen long enough (reading-time rule above), contrast OK over its background.\\n3. **Overflow and clipping**: nothing runs off the frame or out of its HTML raster (`widthPx` × `heightPx` cuts content off), no text wraps into an orphan word.\\n4. **Safe zones** for the target platform.\\n5. **Black or empty frames**: gaps between clips, a scene ending before the next starts, an animation starting from `opacity: 0` and never arriving.\\n6. **Consistency**: same fonts, sizes, colours and positions for the same roles across scenes.\\n7. **Pacing**: no scene that just sits there; nothing too fast to read.\\n8. **Audio**: music level under voice, fades at the ends, no audio running past the last frame.\\n9. **Ending**: CTA or end card holds still, then the video ends (no trailing black).\\n\",\r\n \"reference/html-recipes.md\": \"# HTML clip recipes\\n\\nTested patterns for titles and motion graphics. All of them render the same in `preview_frame` and in export.\\n\\n## Rules (the renderer is strict)\\n\\n- The template is parsed as **XML**: close every tag (`<br/>`, `<img .../>`), quote attributes, write `&amp;`, and write `<` / `>` inside CSS as `&lt;` / `&gt;` (e.g. `@property` syntax `'&lt;integer&gt;'`).\\n- Inline `<svg>` needs `xmlns=\\\"http://www.w3.org/2000/svg\\\"` or it draws nothing.\\n- No scripts, no network: images are `src=\\\"asset:<id>\\\"`, fonts are font assets named by `family` in the CSS, anything else is a `data:` URI.\\n- Static clips (no `animated`) render once. Clips with `\\\"animated\\\": true` render every frame and play CSS `@keyframes`:\\n - Delay elements with the custom property `--d` (e.g. `style=\\\"--d:.4s\\\"`). Never set `animation-delay`; the renderer controls it.\\n - Always use `animation-fill-mode: both` (the `both` keyword) so elements stay at their end state.\\n - `--T` is the clip's length in seconds: `--d: calc(var(--T) - .5s)` starts an exit 0.5 s before the end. `--d` inherits, so give animated children their own.\\n - CSS transitions don't play. Use keyframes.\\n - Infinite animations work, but every animated clip costs a full render per frame. Prefer **one animated full-frame clip per scene** over many small animated clips, and make anything that doesn't move a static clip.\\n- Raster size: `widthPx` × `heightPx` (default: the composition size). Content outside it is cut off. For a full-frame scene, give the root element the composition's exact pixel size.\\n- In JSON, the template is a string: use single quotes inside CSS and double quotes for attributes (escaped as `\\\\\\\"`), or no quotes where CSS allows.\\n\\n## Base kit\\n\\nPut this `<style>` at the top of every scene so all scenes share one look. Change the palette variables per brand.\\n\\n```html\\n<div class=\\\"root\\\"><style>\\n*{box-sizing:border-box;margin:0}\\n.root{--bg:#0a0d18;--ink:#fff;--muted:#a8b2cc;--a1:#8b7bff;--a2:#22d3ee;--card:#121828;--line:#ffffff1c;\\n width:1920px;height:1080px;position:relative;overflow:hidden;font-family:Inter,sans-serif;color:var(--ink)}\\n@keyframes rise{from{transform:translateY(48px);opacity:0;filter:blur(10px)}}\\n@keyframes fadein{from{opacity:0}}\\n@keyframes pop{0%{transform:scale(.6);opacity:0}70%{transform:scale(1.06);opacity:1}100%{transform:scale(1)}}\\n@keyframes reveal{from{clip-path:inset(0 100% 0 0)}to{clip-path:inset(0 0 0 0)}}\\n@keyframes growx{from{transform:scaleX(0)}}\\n@keyframes out{to{opacity:0;transform:translateY(-24px)}}\\n.r{display:inline-block;animation:rise .7s cubic-bezier(.2,.8,.2,1) both}\\n.f{animation:fadein .6s ease-out both}\\n.p{display:inline-block;animation:pop .55s cubic-bezier(.2,.8,.2,1) both}\\n.grad{background:linear-gradient(90deg,var(--a1),var(--a2));-webkit-background-clip:text;background-clip:text;color:transparent}\\n.eyebrow{font-weight:600;font-size:26px;letter-spacing:.32em;color:var(--muted)}\\n.card{border-radius:28px;background:var(--card);border:1px solid var(--line);box-shadow:0 40px 120px #0008}\\n</style>\\n<!-- scene content -->\\n</div>\\n```\\n\\nFor 1080×1920, change the root size to `width:1080px;height:1920px`.\\n\\n## Background (static, full frame, bottom track)\\n\\n```html\\n<div style=\\\"width:1920px;height:1080px;background:radial-gradient(900px 600px at 15% 20%,#2a1f6b 0%,transparent 70%),radial-gradient(800px 600px at 90% 85%,#0e4a5c 0%,transparent 70%),#0a0d18\\\"></div>\\n```\\n\\nOne clip spanning the whole video, then `mc animate --clip bg --loop kenburns` and optionally `effect/add` `grain` with `{ \\\"intensity\\\": 0.12 }`. The slow drift keeps static scenes alive.\\n\\n## Kinetic headline (word by word)\\n\\n```html\\n<div style=\\\"position:absolute;left:160px;top:360px\\\">\\n <div class=\\\"eyebrow f\\\" style=\\\"--d:.05s\\\">MIRAICLIP</div>\\n <div style=\\\"height:28px\\\"></div>\\n <div style=\\\"font-weight:800;font-size:118px;line-height:1.08;letter-spacing:-.02em\\\">\\n <span class=\\\"r\\\" style=\\\"--d:.20s\\\">Build</span> <span class=\\\"r\\\" style=\\\"--d:.32s\\\">video</span> <span class=\\\"r\\\" style=\\\"--d:.44s\\\">editors,</span>\\n </div>\\n <div class=\\\"grad\\\" style=\\\"font-weight:800;font-size:118px;line-height:1.08;letter-spacing:-.02em;animation:reveal 1s cubic-bezier(.65,0,.35,1) both;--d:1.05s\\\">not video editor plumbing.</div>\\n</div>\\n```\\n\\nWord spans need the space between them outside the spans. 0.12 s per word. A gradient line is revealed as a whole (`reveal`), not word by word, so the gradient stays continuous.\\n\\n## Lower third (over footage)\\n\\nStatic raster, positioned with `transform`, animated with presets: `widthPx: 900, heightPx: 140, transform: { \\\"x\\\": 0.3, \\\"y\\\": 0.84 }`, then `mc animate --clip lt --in left --out fade --easing snappy`.\\n\\n```html\\n<div style=\\\"display:flex;align-items:center;gap:22px;height:140px;padding:0 34px;border-radius:20px;background:#0a0d18e6;border:1px solid #ffffff22;font-family:Inter,sans-serif;color:#fff\\\">\\n <div style=\\\"width:8px;height:76px;border-radius:4px;background:linear-gradient(#8b7bff,#22d3ee)\\\"></div>\\n <div><div style=\\\"font:800 46px Inter,sans-serif\\\">{{name}}</div><div style=\\\"font:500 30px Inter,sans-serif;color:#c3cbe0\\\">{{role}}</div></div>\\n</div>\\n```\\n\\nPut the text in `params` (`{ \\\"name\\\": \\\"Ada Lovelace\\\", \\\"role\\\": \\\"Founder, Analytical Co.\\\" }`) so it can be changed with `clip/set-property` without touching the markup. Hold it on screen 4–6 s.\\n\\n## Stat counters\\n\\n```html\\n<style>\\n@property --n{syntax:'&lt;integer&gt;';inherits:false;initial-value:0}\\n@keyframes count{to{--n:var(--to)}}\\n.num{font-weight:800;font-size:150px;line-height:1;letter-spacing:-.04em;counter-reset:n var(--n);animation:count 1.4s cubic-bezier(.2,.8,.2,1) both}\\n.num::after{content:counter(n)}\\n</style>\\n<div class=\\\"card r\\\" style=\\\"--d:.35s;width:507px;height:400px;padding:56px 48px\\\">\\n <div class=\\\"grad\\\"><span class=\\\"num\\\" style=\\\"--to:125;--d:.5s\\\"></span><span style=\\\"font-size:64px;margin-left:6px\\\">min</span></div>\\n <div style=\\\"margin-top:28px;font-size:32px;line-height:1.35;color:var(--muted);font-weight:500\\\">timeline with 4,464 clips</div>\\n</div>\\n```\\n\\nCounters are integers. For decimals or thousands separators, count the integer part and write the rest as static text. Three cards in a row with 0.15 s stagger (`display:flex;gap:40px`) fill a 16:9 frame.\\n\\n## Code card\\n\\n```html\\n<style>.ln{font-family:'JetBrains Mono',Consolas,Menlo,monospace;font-size:28px;line-height:1.75;white-space:pre;color:#d7dcf0;animation:rise .5s cubic-bezier(.2,.8,.2,1) both}\\n.k{color:#8b7bff}.s{color:#5eead4}.c{color:#6b7591}</style>\\n<div class=\\\"card f\\\" style=\\\"--d:.35s;position:absolute;left:900px;top:300px;width:860px;padding:44px 48px\\\">\\n <div class=\\\"ln\\\" style=\\\"--d:.6s\\\">project.<span class=\\\"k\\\">dispatch</span>({</div>\\n <div class=\\\"ln\\\" style=\\\"--d:.75s\\\"> type: <span class=\\\"s\\\">\\\"clip/add\\\"</span>,</div>\\n <div class=\\\"ln\\\" style=\\\"--d:.9s\\\">})</div>\\n</div>\\n```\\n\\nKeep lines under ~45 characters at 28 px in an 860 px card, or the text runs out of the card. Check with `preview_frame`.\\n\\n## Checklist / steps\\n\\n```html\\n<style>.row{display:flex;align-items:center;gap:22px;margin-top:22px;padding:24px 30px;border-radius:20px;background:var(--card);border:1px solid var(--line);font-size:30px;animation:rise .5s cubic-bezier(.2,.8,.2,1) both}\\n.ok{width:44px;height:44px;border-radius:50%;background:var(--a2);color:#04121a;font-weight:900;font-size:26px;display:flex;align-items:center;justify-content:center}</style>\\n<div style=\\\"position:absolute;left:900px;top:260px;width:860px\\\">\\n <div class=\\\"row\\\" style=\\\"--d:1.0s\\\"><div class=\\\"ok\\\">✓</div>Cut the pauses</div>\\n <div class=\\\"row\\\" style=\\\"--d:1.3s\\\"><div class=\\\"ok\\\">✓</div>Add captions</div>\\n</div>\\n```\\n\\n0.3 s between rows so each can be read as it lands.\\n\\n## Bar chart\\n\\n```html\\n<style>@keyframes growy{from{transform:scaleY(0)}}\\n.bar{position:absolute;bottom:0;width:120px;border-radius:14px 14px 0 0;transform-origin:bottom;animation:growy .8s cubic-bezier(.2,.8,.2,1) both}</style>\\n<div style=\\\"position:absolute;left:300px;top:300px;width:1320px;height:560px;border-bottom:2px solid #ffffff33\\\">\\n <div class=\\\"bar\\\" style=\\\"left:80px;height:180px;background:#ffffff33;--d:.3s\\\"></div>\\n <div class=\\\"bar\\\" style=\\\"left:320px;height:290px;background:#ffffff33;--d:.45s\\\"></div>\\n <div class=\\\"bar\\\" style=\\\"left:560px;height:520px;background:linear-gradient(#8b7bff,#22d3ee);--d:.6s\\\"></div>\\n</div>\\n```\\n\\nHighlight the bar the story is about; mute the rest. Label values above bars with a `.f` text that fades in after its bar.\\n\\n## Line drawing (SVG)\\n\\n```html\\n<style>@keyframes draw{from{stroke-dashoffset:1200}}.path{stroke-dasharray:1200;animation:draw 1.4s ease-out both}</style>\\n<svg xmlns=\\\"http://www.w3.org/2000/svg\\\" width=\\\"1200\\\" height=\\\"400\\\" style=\\\"position:absolute;left:360px;top:420px\\\">\\n <path class=\\\"path\\\" d=\\\"M10 380 L300 250 L600 300 L1190 20\\\" fill=\\\"none\\\" stroke=\\\"#22d3ee\\\" stroke-width=\\\"10\\\" stroke-linecap=\\\"round\\\"/>\\n</svg>\\n```\\n\\n`stroke-dasharray` must be at least the path's length.\\n\\n## Screenshot in a browser frame\\n\\nRegister the screenshot as an image asset (`id: \\\"shot\\\"`), then:\\n\\n```html\\n<div class=\\\"card r\\\" style=\\\"--d:.2s;position:absolute;left:260px;top:170px;width:1400px;overflow:hidden\\\">\\n <div style=\\\"height:52px;display:flex;align-items:center;gap:10px;padding:0 22px;background:#1a2033\\\">\\n <span style=\\\"width:14px;height:14px;border-radius:50%;background:#ff5f57\\\"></span><span style=\\\"width:14px;height:14px;border-radius:50%;background:#febc2e\\\"></span><span style=\\\"width:14px;height:14px;border-radius:50%;background:#28c840\\\"></span>\\n <span style=\\\"margin-left:24px;font-size:22px;color:#8c95ad\\\">{{url}}</span>\\n </div>\\n <img src=\\\"asset:shot\\\" style=\\\"display:block;width:1400px;height:788px;object-fit:cover;object-position:top\\\"/>\\n</div>\\n```\\n\\nFor a slow scroll through a tall screenshot, animate `object-position` from `top` to `bottom` over the scene, or put the screenshot in its own image clip and keyframe `y`.\\n\\n## Scrim behind text over footage\\n\\nA static full-width HTML clip on the track under the text: `heightPx: 420, transform: { \\\"y\\\": 0.81 }`:\\n\\n```html\\n<div style=\\\"width:1920px;height:420px;background:linear-gradient(transparent,#000000cc)\\\"></div>\\n```\\n\\n## Call to action / end card\\n\\n```html\\n<style>@keyframes pulse{0%,100%{box-shadow:0 0 0 0 #22d3ee66}50%{box-shadow:0 0 0 22px #22d3ee00}}\\n.c{position:absolute;inset:0;display:flex;flex-direction:column;align-items:center;justify-content:center}\\n.cta{margin-top:70px;padding:26px 54px;border-radius:999px;background:#fff;color:#0a0d18;font-weight:700;font-size:40px;animation:pulse 1.6s ease-in-out 3 both}</style>\\n<div class=\\\"c\\\">\\n <div class=\\\"p\\\" style=\\\"--d:.1s;font-weight:800;font-size:180px;letter-spacing:-.045em\\\">{{brand}}</div>\\n <div class=\\\"f\\\" style=\\\"--d:.55s;margin-top:18px;font-size:42px;color:var(--muted);font-weight:500\\\">{{tagline}}</div>\\n <div class=\\\"f\\\" style=\\\"--d:.9s\\\"><div class=\\\"cta\\\" style=\\\"--d:1.3s\\\">{{url}}</div></div>\\n</div>\\n```\\n\\nThe pulse runs a fixed number of times (`3`) so the card ends still. Hold the end card ≥ 2.5 s.\\n\\n## Progress bar (9:16 stories, tutorials)\\n\\nAn animated clip on the top track spanning the whole video: `\\\"animated\\\": true, \\\"widthPx\\\": 1080, \\\"heightPx\\\": 12, \\\"transform\\\": { \\\"y\\\": 0.006 }`.\\n\\n```html\\n<div style=\\\"width:1080px;height:12px;background:#ffffff26\\\"><style>@keyframes growx{from{transform:scaleX(0)}}</style><div style=\\\"height:12px;background:#fff;transform-origin:left;animation:growx var(--T) linear both\\\"></div></div>\\n```\\n\\n`var(--T)` makes the bar exactly as long as the clip.\\n\\n## Beat-synced, playful style\\n\\nTested in the \\\"funky launch\\\" gallery sample. Pick the music's BPM first; then one beat = `60 / BPM` s, one bar = 4 beats. Give every scene a whole number of bars and write every `--d` in beats (`--d: calc(<beats> * <beat>s)`, or compute it when you generate the template). Hard cuts on bar lines feel energetic; save dissolves for calm videos.\\n\\n| Pattern | CSS |\\n| --- | --- |\\n| Colour flash per beat | `.flash{position:absolute;inset:0;animation:flash <4 beats>s step-end both}` with `@keyframes flash{0%{background:#2fd3b9}25%{background:#ff6f5e}50%{background:#ffb423}75%{background:#39a7ff}}` |\\n| Sticker look | `border:4px solid #0c1010;box-shadow:8px 9px 0 #0c1010;border-radius:22px` (hard offset shadow, no blur) |\\n| Boing in (overshoot) | `@keyframes boing{0%{transform:scale(0) rotate(-12deg)}60%{transform:scale(1.12) rotate(3deg)}80%{transform:scale(.96)}100%{transform:scale(1)}}` .6s |\\n| Slam a word | `@keyframes slam{0%{transform:scale(2.4);opacity:0;filter:blur(18px)}55%{transform:scale(.94);opacity:1;filter:blur(0)}100%{transform:scale(1)}}` .45s, one word or letter per beat |\\n| Retro offset shadow | `text-shadow:10px 10px 0 #ff6f5e,20px 20px 0 #39a7ff` |\\n| Bounce on every beat | `animation:bob <beat>s cubic-bezier(.3,0,.3,1) infinite both` with a squash/stretch keyframe |\\n| Sunburst | `repeating-conic-gradient(#ffffff22 0 8deg,transparent 8deg 22deg)` on a 2400 px square, slowly rotated |\\n| Fly in from the right | `@keyframes fromright{0%{transform:translateX(120%) skewX(-12deg)}70%{transform:translateX(-4%) skewX(3deg)}100%{transform:none}}` |\\n\\n### Typewriter\\n\\n```html\\n<style>@keyframes type{from{width:0}to{width:calc(var(--n) * 1ch)}}\\n.tw{display:inline-block;overflow:hidden;white-space:nowrap;vertical-align:bottom}</style>\\n<span class=\\\"tw\\\" style=\\\"--d:.5s;--n:43;animation:type 2.5s steps(43) both\\\">make a funky launch video for miraiclip.com</span>\\n```\\n\\nMonospace font only, `--n` = character count, and **no `letter-spacing`** (it makes the text wider than `n × 1ch` and the end gets cut off).\\n\\n### One element after another in the same spot\\n\\nShow item *i* from its start beat and hide it at the next one with two nested wrappers (one animation each, because `--d` sets the delay for every animation on an element):\\n\\n```html\\n<style>@keyframes on{from{opacity:0}to{opacity:1}}@keyframes off{from{opacity:1}to{opacity:0}}\\n.show{animation:on .01s step-end both}.hide{animation:off .01s step-end both}</style>\\n<div class=\\\"hide\\\" style=\\\"--d:2.22s;position:absolute;inset:0\\\"><div class=\\\"show\\\" style=\\\"--d:1.11s;position:absolute;inset:0\\\">…item 2…</div></div>\\n```\\n\\n### Screenshot montage\\n\\nFor one screenshot per beat, use **image clips** on their own track (one clip per beat, hard cuts, a small `scale` keyframe bump 0.8 → 0.74 over 180 ms, `roundedCorners`), not images inside an animated HTML clip: an animated HTML clip re-inlines every image on every frame. Put titles and labels in one HTML overlay clip above them.\\n\",\r\n \"reference/motion.md\": \"# Motion: presets and keyframes\\n\\nTwo ways to move things:\\n\\n| Moving… | Use |\\n| --- | --- |\\n| A whole clip (footage, photo, logo, a static HTML card, a caption block) | Animation presets via `mc animate`, or `keyframe/set` |\\n| Parts inside a title or graphic (words, cards, bars, counters) | CSS `@keyframes` in an animated HTML clip (see `html-recipes.md`) |\\n\\n## Animation presets\\n\\nIn a Miraiclip editor, the `animate_clip` tool takes these same presets (`in`, `loop`, `out`, `easing`), on the clip as it is now; the `mc` commands below are for Claude Code.\\n\\nThe same presets as the Miraiclip editor's animation panel. A recipe has up to three slots: **in** (from the clip's start), **loop** (between in and out), **out** (into the end, end-anchored so it follows trims).\\n\\n```bash\\nMC=\\\"node ${CLAUDE_PLUGIN_ROOT}/scripts/mc.mjs\\\"\\n$MC presets # list them\\n$MC animate --clip title --in up --out fade # 600 ms in, 500 ms out, smooth\\n$MC animate --clip logo --in pop --in-ms 500 --easing snappy\\n$MC animate --clip photo-1,photo-2,photo-3 --loop kenburns\\n$MC animate --clip badge --in zoom --loop pulse --out fade\\n$MC animate --clip title # no slots: removes the animation\\n```\\n\\n| Slot | Presets |\\n| --- | --- |\\n| in / out | `fade`, `up`, `down`, `left`, `right` (slide 12% of the frame + fade), `zoom` (from 60%), `spin`, `pop` (0 → 110% → 100%) |\\n| loop | `pulse` (scale ±6%, 1.2 s), `float` (y ±2%, 2 s), `sway` (±3°, 2.4 s), `kenburns` (scale +15% and drift over the clip) |\\n| easing | `smooth` (ease-out in, ease-in out), `snappy` (fast start, long settle), `linear` |\\n\\n- The command reads the **saved** project file, so `apply_commands` the clip first, then run `animate`, then `apply_commands` its output. Presets animate from the clip's current `transform`, so set position and scale first.\\n- Applying a recipe replaces the clip's visual keyframes (x, y, scale, rotation, opacity). Volume keyframes stay.\\n- In and out share the clip: if they don't fit, both shrink.\\n\\n## Hand-made keyframes\\n\\n```json\\n{ \\\"type\\\": \\\"keyframe/set\\\", \\\"payload\\\": { \\\"clipId\\\": \\\"photo\\\", \\\"property\\\": \\\"scale\\\", \\\"timeUs\\\": 0, \\\"value\\\": 1, \\\"easing\\\": \\\"easeOut\\\" } }\\n{ \\\"type\\\": \\\"keyframe/set\\\", \\\"payload\\\": { \\\"clipId\\\": \\\"photo\\\", \\\"property\\\": \\\"scale\\\", \\\"timeUs\\\": 4000000, \\\"value\\\": 1.2 } }\\n{ \\\"type\\\": \\\"keyframe/set\\\", \\\"payload\\\": { \\\"clipId\\\": \\\"photo\\\", \\\"property\\\": \\\"opacity\\\", \\\"timeUs\\\": 400000, \\\"anchor\\\": \\\"end\\\", \\\"value\\\": 1, \\\"easing\\\": \\\"easeIn\\\" } }\\n{ \\\"type\\\": \\\"keyframe/set\\\", \\\"payload\\\": { \\\"clipId\\\": \\\"photo\\\", \\\"property\\\": \\\"opacity\\\", \\\"timeUs\\\": 0, \\\"anchor\\\": \\\"end\\\", \\\"value\\\": 0 } }\\n```\\n\\n- Properties: `x`, `y`, `scale`, `rotation` (degrees), `opacity`, `volume`.\\n- `timeUs` is relative to the clip's start, or with `anchor: \\\"end\\\"`, measured back from its end.\\n- The easing on a keyframe shapes the segment **leaving** it. Values: `linear`, `hold`, `easeIn`, `easeOut`, `easeInOut`, or a bezier `{ \\\"kind\\\": \\\"bezier\\\", \\\"x1\\\": 0.2, \\\"y1\\\": 0.8, \\\"x2\\\": 0.2, \\\"y2\\\": 1 }`.\\n- `keyframe/clear` with a `property` removes all keyframes of that property.\\n\\n## Useful moves\\n\\n| Move | How |\\n| --- | --- |\\n| Punch-in on a talking head (hide a jump cut) | `clip/set-property` `{ \\\"transform\\\": { \\\"scale\\\": 1.15 } }` on every other segment. Static, not animated. Keep the face in frame: adjust `y` a little (0.45–0.48) if the head gets cut |\\n| Slow push-in on a photo or wide shot | `--loop kenburns`, or scale 1 → 1.1 over the clip, `linear` |\\n| Picture-in-picture | `transform: { \\\"x\\\": 0.8, \\\"y\\\": 0.78, \\\"scale\\\": 0.32 }` on an upper track, plus `effect/add` `roundedCorners`, then `--in pop` |\\n| Split screen | Two clips on two tracks, `x: 0.25` and `x: 0.75`, `scale` so each fills half; crop is not available, so use footage that frames well |\\n| Logo sting | Logo image clip, `--in pop --easing snappy`, then `--loop pulse` only if it stays on screen > 3 s |\\n| Music duck under a line of voice | `volume` keyframes: 0.8 → 0.2 over 300 ms before the line, back to 0.8 over 500 ms after |\\n\"\r\n }\r\n },\r\n {\r\n \"name\": \"launch-video\",\r\n \"description\": \"Make a product or service launch, feature announcement, promo, intro or teaser film in the editor, in the brand's own look and voice — directed for this brand (a treatment), approved, then built as continuous motion-graphics acts. Use when the user wants a launch, promo or intro video and has no footage, or only screenshots and a logo.\",\r\n \"instructions\": \"# Launch video\\n\\nA recipe: the launch's story order and defaults. How to direct, design and build comes from the skills `make-film` calls on (`direction`, `look`, `media-sound`, `sequence-core`, `motion`). Load `make-film` and follow its steps, with what this file adds. For a film that isn't promoting a product or business (a course, a show, a story, an event), `make-film` alone is the recipe.\\n\\nWhen the user asks for a continuous shot, one take or no cuts, load `continuous-shot` instead and follow it; it replaces the act-by-act build below.\\n\\nA launch film people watch to the end is fast, specific and always moving. A deck of cards with a headline each is not a film, even with transitions.\\n\\n## Brand first\\n\\n- The user's direction (colours, theme, style, mood, voice) wins.\\n- Otherwise call `get_brand` with the brand's site (the user's URL, or the brand's well-known domain). It reads the colours, light or dark look, fonts, logo, the site's own images and its copy, and imports the font, logo and images as `brand-*` assets. If the editor has no `get_brand`, ask the user for colours, font and screenshots.\\n- The film's look is the brand's: `plan_film` with `style: \\\"brand\\\"`. A look the user names (\\\"like a movie trailer\\\", \\\"playful orbs\\\") is designed in the brand's colours unless they say otherwise.\\n- **Only the brand's media goes in the film**: `brand-logo`, `brand-image-N` (each listed with what it shows), and files the user names. Anything else already in the project belongs to something else, however much it looks like a product screenshot; the editor refuses it.\\n- **Copy comes from the brand's own words:** its headline, product and feature names, button wording. Facts and numbers come only from the user or the brand's site. List the brand's material (headings, features, numbers, quotes, images) before writing the treatment, and give each beat its own item.\\n\\n## The launch arc\\n\\n1. **The problem**: the viewer's pain, concrete and visual, in their words. Many tools, lost leads, hours of busywork.\\n2. **The turn**: the product appears *out of* the problem. The protagonist element is born here: the scattered tools collide into it, the messy inbox folds into it.\\n3. **The product in action**: 2–4 things it does, each shown happening (a message answered, a booking landing, a funnel filling), each growing from the last. Use real product images where they exist, on screens or cards the camera pushes into.\\n4. **Proof**: one real number or name (only real ones).\\n5. **The call to action**: the brand's own line and the address, held and drifting.\\n\\n**Defaults when the user doesn't say:**\\n\\n| Setting | Default |\\n|---|---|\\n| Length | 20–35 s, as 2–3 acts |\\n| Music | Bright and driving, about 115–128 bpm |\\n| Camera | Pushing and tilting, with whip-pans or zoom-throughs between ideas |\\n| Type | The brand's font, set large: 1–4 words for the punches, a short line at most |\\n| Format | 16:9; offer 9:16 after |\\n\\nA length the user asks for is a requirement, within 10%. Each extra 10 s needs new material (a feature, a proof point), not a slower film. If there isn't enough material, make it shorter and say why.\\n\\n## Library scenes are blueprints\\n\\nThe editor's launch scenes are worked examples of single moments: a typed hook, a word slam, a 3D product card, a feature trio, a counting stat, a wordmark reveal. When one is close to a beat you want, read its code with `read_scene` and take the technique into your act. Don't fill them in as the film.\\n\\nOnly when `compose_sequence` isn't available does a launch fall back to `design_scene` for the brand's own moments, plus `add_scene` where a library scene fits a beat as-is.\\n\\n## Deliver\\n\\nReply with:\\n\\n- the length, and the length asked for if it differs, with the reason;\\n- the world and the protagonist chain;\\n- the acts in one line each;\\n- the look: the colours (hex) and fonts, and where they came from (the user, the brand's site or kit, or neutral defaults), and which brand images appear.\\n\\nThen say what to check: claims and numbers on screen, and the music licence. Offer a 9:16 version (`reformat`).\"\r\n },\r\n {\r\n \"name\": \"look\",\r\n \"description\": \"Invent a film's look from its subject — palette, type, finish, the frame and its grid, picture treatments — and pass it to plan_film. Load it when planning any film from a brief; reference/scenes.md has the craft for design_scene stills.\",\r\n \"instructions\": \"# Look\\n\\nEvery film gets its own look, taken from the subject's world, never the last film's. Decide it before `plan_film`, and pass it as `style: \\\"custom\\\"` with its tokens. For a brand's film, use `style: \\\"brand\\\"` after `get_brand`. The user's direction always wins: the colours, mood or style they name.\\n\\n## Palette\\n\\n- **From what the subject looks like and how it should feel.** An environmental film might take moss green, river-water teal, smog grey and a warning amber. A jazz festival might take brass and night blue; a children's science course chalkboard green and crayon colours; a fintech launch ink, paper and one signal green.\\n- **Four tokens:**\\n - `bg`: the background;\\n - `ink`: the text;\\n - `primary`: the one accent;\\n - `secondary`: a second accent, used much less.\\n\\n The editor derives the rest: muted text, card, line, and text on the accent.\\n- **Contrast:** body text must read on the background, at better than 4.5:1. The accent must stand out from both.\\n- **One accent:**\\n - Use it on the key word, small accent shapes, the end dot, and one highlighted thing per frame.\\n - An accent everywhere is no accent.\\n - Photos may be the only saturated thing in the film; that is a look too.\\n- **Light or dark from the tone:**\\n - paper and daylight for calm, craft, learning and food;\\n - night and glow for tech, drama and music.\\n\\n Not dark by default.\\n\\n## Type\\n\\n- **A typeface that speaks the subject's language:**\\n\\n | Subject | Typeface |\\n |---|---|\\n | Nature, history | A humanist serif |\\n | Urgency, news | A condensed grotesk |\\n | Product, UI | A geometric sans |\\n | Kids, playful | A rounded sans |\\n | Code | A mono |\\n | Something handmade | A script, only then |\\n\\n Any Google Fonts family works.\\n- **Two families at most:** a display face for titles (`.k-display`) and a body face.\\n- **Scale, in container units:**\\n\\n | Use | Size |\\n |---|---|\\n | Hero | 11–16 cqmin |\\n | Headline | 7–10 cqmin |\\n | Body | 3.6–5 cqmin |\\n | Labels | 2.6–3.2 cqmin, in caps with `letter-spacing: .2em` |\\n\\n Nothing under 2.5 cqmin. A word set huge (bleeding past the edges) is a motif, not a size.\\n- **Setting:** tight leading on titles (1–1.05), `text-wrap: balance`, and the key word in the accent or in italic. At most about 7 words on screen in motion graphics, and about 25 in a still scene.\\n\\n## Finish\\n\\n- **Grain and letterbox** only for cinematic subjects (documentary, trailer).\\n- **Texture** belongs to the concept: paper grain, a dot grid, a notebook grid, scanlines, soft colour blooms. Make it subtle, never on top of text. Make blooms and glows with `radial-gradient(circle, <colour>, transparent 65%)`, not `filter: blur()` on a big shape.\\n- **Pace and joins from the tone:** slow dissolves for reflection, cuts and whips for energy.\\n- **Music words** that fit the subject (the media-sound skill has the rest).\\n\\n## The frame\\n\\n- **Units:**\\n - `cqmin` for type, spacing and radii;\\n - `cqw` / `cqh` for layout widths and heights;\\n - in a sequence's script, `S.w` / `S.h` / `S.min`.\\n\\n Never `px` for type, never `vw` / `vh`.\\n- **Safe area:** keep text 6% in from every edge (`inset: 8cqh 7cqw`). With letterbox bars, keep it in the middle 70% of the height.\\n- **Portrait:** `@container (max-aspect-ratio: 1/1) { … }` re-lays the frame for 9:16. Stack columns and enlarge type a little. Every film should survive a `reformat` to 9:16.\\n- **Tokens in CSS:**\\n\\n | Variable | What it is |\\n |---|---|\\n | `var(--bg)` | Background |\\n | `var(--ink)` | Text |\\n | `var(--mut)` | Muted text |\\n | `var(--p)` | Accent |\\n | `var(--s)` | Second accent |\\n | `var(--card)` | Card surface |\\n | `var(--line)` | Hairlines |\\n | `var(--onp)` | Text on the accent |\\n\\n Never hard-code a colour the plan already has, except in a sequence's tweens, which need the hex.\\n\\n## Grid and composition\\n\\n- **Pick a grid for the film** and keep to it:\\n - a centred axis for statements;\\n - thirds for picture plus caption;\\n - a 12-column editorial grid for explainers;\\n - a free field for playful shapes.\\n- **Vary the composition from beat to beat:** full-bleed, split, grid, type only, close-up crop, diagram. Never the same composition twice in a row.\\n- **Hierarchy:** one thing is biggest. The eye lands on it first, then the label, then the detail.\\n- **Balance:** no big empty half, and nothing crammed in a corner. Empty space is fine when it is meant.\\n\\n## Pictures\\n\\n- **Show them** with `<img src=\\\"asset:ID\\\" class=\\\"k-cover\\\"/>` inside a sized frame with `overflow: hidden`. Never stretch a picture; use `object-fit: cover`.\\n- **Treat pictures to fit the look.** Pick one treatment for the whole film:\\n - **Duotone:** `filter: grayscale(1) contrast(1.1)`, plus an accent layer with `mix-blend-mode: multiply`.\\n - **Frames:** rounded, film-strip edges, a polaroid border, a cut-out with a drop shadow.\\n - **Texture:** soft blur and low opacity, when the pictures are atmosphere.\\n- **Composition:**\\n - A face or subject goes on a third, not dead centre under the text.\\n - Crop for a close-up: use `object-position` and a larger scale.\\n- **Legibility over pictures:** a gradient from the background (`linear-gradient(transparent 45%, color-mix(in srgb, var(--bg) 85%, transparent))`), the caption on a third.\\n\\n## Checklist before plan_film\\n\\n- The palette and type come from this subject. If it could be any brief's look, start again.\\n- It isn't one of the named styles unless the user named it. The editor refuses a copy of a named style.\\n- Text reads on the background, and the accent is used for one thing.\\n- You know the grid, the picture treatment and the texture.\\n\\n`reference/scenes.md` is the craft for still scenes built with `design_scene` (its CSS animation classes, patterns and an example).\",\r\n \"files\": {\r\n \"reference/scenes.md\": \"# Still scenes (design_scene)\\n\\nHow to write `design_scene` scenes that look made for the brief and finished. Every scene is HTML and CSS in a full-frame box that is a CSS container. Units, safe area, tokens, type scale and picture treatments are in this skill's EDITOR.md (the frame applies to every film). This file is what is particular to still scenes: their CSS animation classes, rhythm across scenes, and patterns.\\n\\nFor motion graphics (one continuous move across a whole act), use `compose_sequence` instead, with the `sequence-core` and `motion` skills.\\n\\n## Motion\\n\\n- Entrances: `class=\\\"k-rise\\\" style=\\\"--d:.4s\\\"`. Stagger related items 0.12–0.25 s apart, in reading order. Everything has arrived by `T − 1 s`, so the scene holds before it fades.\\n- `--d` is inherited: set it on each element you stagger (and `--d:0s` on a container whose own animation must start at once).\\n- One continuous move per scene (the camera push, `k-pan`, or `k-kb` on a picture), so nothing is ever frozen.\\n- Your own `@keyframes` are welcome. Time them with `--T` when they last the whole scene: `animation: drift var(--T) linear both`.\\n- Lines and shapes that draw: SVG with `stroke-dasharray` / `stroke-dashoffset` keyframes, or `.k-draw` on a thin div.\\n- Typing: `.k-type` on an inline-block line, with `animation-timing-function: steps(n)` where n ≈ its characters.\\n\\n## Rhythm\\n\\n- **Plan the sequence of compositions** before designing: e.g. full-bleed photo → split → big type → grid → close-up crop → diagram → full-bleed. Never the same composition twice in a row, and not one pattern on a loop.\\n- **Change scale**: follow a wide shot with a detail. Crop into a picture (`object-position` and a larger scale) for a close-up, and a big number after small text.\\n- **Keep it alive**: a scene's last entrance lands no more than about 2 s before it ends. Long scenes get a second beat (a label, a line that draws, a detail that appears) or a continuous move (`k-kb` on the picture, `k-pan` on a layer).\\n- **Captions over photos**: a gradient for legibility, the caption on a third, and a small label or source line. With letterbox bars, keep it above the bottom bar.\\n\\n## Patterns\\n\\nThese are starting points. Mix them and make them belong to the concept.\\n\\n- **Statement**: one line set huge, the key word in the accent, a small label above. Use for a hook, a turn or a promise.\\n- **Split**: a picture on one side (`k-cover` in a rounded frame with `k-kb`), headline and two lines on the other. In portrait, the picture goes above.\\n- **Full-bleed picture**: the picture fills the frame (`k-fill` + `k-cover` + `k-kb`). A gradient from `var(--bg)` keeps the caption legible: `linear-gradient(transparent 45%, color-mix(in srgb, var(--bg) 85%, transparent))`.\\n- **Grid**: 3–4 cards with a picture or icon, a title and one line. Deal them in with a stagger and give each a slight rotation.\\n- **Diagram**: boxes joined by SVG lines that draw, for a flow, a loop or an architecture. Label each box. The film's own idea drawn out beats a stock card layout.\\n- **Console / log**: monospaced lines typing in one after another, with prompts, statuses and a highlighted result. Use for technical subjects.\\n- **List as a path**: items along a line or a rail that draws, each ticking off as it's reached.\\n- **Number**: one real figure set huge, with its unit and one line of meaning.\\n- **Collage**: 3–5 pictures overlapping at angles, settling while the camera pulls back.\\n- **Close**: the name, one line, the call to action, the address.\\n\\n## Before you send a scene\\n\\n- Text inside the frame and its boxes; nothing overlapping; nothing under 2.5cqmin.\\n- The frame is balanced: no big empty half, no content crammed in a corner.\\n- It follows the concept: motif, layout and motion. It doesn't look like the scene before it.\\n- Real words only: no placeholders, no invented people or numbers.\\n\\n## Example: a console syllabus (course on building AI agents)\\n\\n```html\\n<div class=\\\"con k-fill\\\">\\n <div class=\\\"con-bar k-fade\\\"><i></i><i></i><i></i><span>agent.run(course)</span></div>\\n <div class=\\\"con-body\\\">\\n <p class=\\\"k-type\\\" style=\\\"--d:.3s\\\">&gt; plan --modules 4</p>\\n <p class=\\\"con-ok k-rise\\\" style=\\\"--d:1.1s\\\"><b>01</b> Agent fundamentals <em>2 weeks</em></p>\\n <p class=\\\"con-ok k-rise\\\" style=\\\"--d:1.35s\\\"><b>02</b> Tools and APIs <em>3 weeks</em></p>\\n <p class=\\\"con-ok k-rise\\\" style=\\\"--d:1.6s\\\"><b>03</b> Planning and memory <em>3 weeks</em></p>\\n <p class=\\\"con-ok k-rise\\\" style=\\\"--d:1.85s\\\"><b>04</b> Multi-agent systems <em>4 weeks</em></p>\\n <p class=\\\"con-done k-pop\\\" style=\\\"--d:2.4s\\\">✓ 12 weeks · 4 projects</p>\\n </div>\\n</div>\\n```\\n\\n```css\\n.con{inset:14cqh 12cqw;display:flex;flex-direction:column;border-radius:2.4cqmin;background:var(--card);border:.15cqmin solid var(--line);overflow:hidden;font-family:'JetBrains Mono Variable',ui-monospace,monospace}\\n.con-bar{display:flex;align-items:center;gap:1cqmin;padding:2cqmin 3cqmin;border-bottom:.15cqmin solid var(--line);font-size:2.8cqmin;color:var(--mut)}\\n.con-bar i{width:1.4cqmin;height:1.4cqmin;border-radius:50%;background:var(--line)}\\n.con-bar span{margin-left:2cqmin}\\n.con-body{flex:1;padding:4cqmin 5cqmin;display:flex;flex-direction:column;justify-content:center;gap:2.4cqmin;font-size:4.6cqmin}\\n.con-ok b{color:var(--p);margin-right:2cqmin}\\n.con-ok em{font-style:normal;color:var(--mut);margin-left:2cqmin;font-size:3.2cqmin}\\n.con-done{align-self:flex-start;margin-top:1cqmin;padding:1.2cqmin 2.6cqmin;border-radius:1cqmin;background:var(--p);color:var(--onp);font-size:3.4cqmin}\\n```\\n\"\r\n }\r\n },\r\n {\r\n \"name\": \"make-film\",\r\n \"description\": \"Make any film from a brief in the editor — a product or service intro, a launch or promo, a course or programme promo, a trailer or teaser, a documentary, biography or recap, an explainer — directed for the brief (a treatment), with its own look, built as continuous motion-graphics acts (compose_sequence) or designed scenes. Use whenever the user asks for a video about something and has no footage to edit, or only a few stills.\",\r\n \"instructions\": \"# Make a film from a brief\\n\\n**Design for the brief; don't fill a template.** The user asked for *their* film. Invent the world, the look and the moves that belong to this subject, then build them. A film assembled from the same parts as the last one reads as a template, whatever the words.\\n\\nLoad `edit-video` first if you haven't (its Brand and direction rules apply). This recipe calls on five skills, each for one job. Load each with `load_skill` when you reach its step:\\n\\n| Skill | Its job |\\n|---|---|\\n| `direction` | Brief → treatment: the world, the protagonist element, acts and a timed beat sheet |\\n| `look` | Palette, type, finish, grid, picture treatment, from the subject |\\n| `media-sound` | Pictures (find or generate), music chosen first, sound effects, mix |\\n| `sequence-core` | The contract for compose_sequence acts: the stage kit API and its rules |\\n| `motion` | The motion-graphics grammar and about 30 techniques, plus breakdowns of whole films |\\n| `continuous-shot` | When the ask is a continuous shot, one take, no cuts, or \\\"not a slideshow\\\": three concepts, a beat sheet, and a measured check of every beat |\\n\\n## 1. Read the brief\\n\\nDecide, in one line each:\\n\\n- **Subject**: what the film is about, by name.\\n- **Format**: the job the film does:\\n\\n | The brief is… | Format |\\n |---|---|\\n | A product, app, service, business or feature to introduce or promote (not a course) | `launch` |\\n | A course, class, bootcamp, workshop, certification or learning programme | `course` |\\n | A show, film, book, game, album or event to tease | `trailer` |\\n | A real story: biography, history, true crime, a recap, a profile | `documentary` |\\n | How something works, a concept, a process | `explainer` |\\n\\n- **Build mode**:\\n - **Motion graphics** (`compose_sequence`) whenever it is available. Use it for launches, intros, promos, explainers, courses and trailers, and for anything where the user says \\\"motion graphics\\\", \\\"animated\\\", \\\"dynamic\\\", \\\"kinetic\\\", or names shapes or type as the subject. The film is a few continuous acts.\\n - **Designed scenes** (`design_scene`, with `add_scene` where a library scene fits) for documentaries built on stills, or when `compose_sequence` isn't available. The film is a row of scenes joined by dissolves.\\n\\nThe user's own direction always wins: a style, colours, a mood, a length.\\n\\n## 2. Direct\\n\\nLoad `direction` and write the treatment. It covers:\\n\\n- the world;\\n- the protagonist chain;\\n- the promise;\\n- acts with a timed beat sheet, with a technique and a carry-over per beat;\\n- words per beat;\\n- the look, camera and music direction.\\n\\nRead the closest breakdown in `motion` → `reference/breakdowns/` to borrow its grammar.\\n\\n## 3. Look, plan and approval\\n\\nLoad `look`, decide the palette, type and finish for this subject, and call `plan_film` with:\\n\\n- `format`, `subject`, `seconds`, `reason`;\\n- the look as `style: \\\"custom\\\"` tokens (or `style: \\\"brand\\\"` after `get_brand`; a named style only when the user names it);\\n- the `concept`, from the treatment:\\n - idea = the world;\\n - motif = the protagonist element;\\n - layout = the grid;\\n - motion = the camera's character;\\n - imagery = the shot list.\\n\\nIt registers the fonts and returns the film's tokens and music words.\\n\\nThen record the treatment with `direct_film`. It checks the treatment and shows the user a storyboard. **Unless the user said to just make it, end your turn and wait for their approval** (the chat offers Approve & build and Change direction). Revise with `direct_film` if they ask for changes.\\n\\n## 4. Material (after approval)\\n\\nA film from a brief gets its own timeline. First remove the project's existing clips and tracks, in one `apply_commands` batch, unless the user asked to add to this project. Use 16:9 unless asked. Then load `media-sound`.\\n\\n- **Words come from the subject's world.** Use its real names, facts, lines and dates. Never invent people, numbers, reviews or quotes; mark a paraphrase as one (\\\"— paraphrased\\\"). The editor refuses placeholder copy.\\n- **Pictures:** only the subject's own (user-added, `brand-*`, or from `find_images` / `generate_image`). Get them before building.\\n- **Music first:** add the music under the whole film (volume 0.7, fade in 1 s and out 2 s). Then call `get_beat_grid`. It measures the beats and gives every act its beat times (`S.beat(n)`), so the big moves land on the music.\\n\\n## 5. Build\\n\\n**Motion graphics.** Load `sequence-core` and `motion`. Then compose one `compose_sequence` per act, in order:\\n\\n- Each act is one timeline. Elements persist and transform; the camera keeps moving (stillness only in a declared `tl.rest`); beats are labels; each beat grows out of the last.\\n- The next act starts where the last one ended.\\n- Look at each filmstrip and fix the act with `replace` before composing the next.\\n- Use `design_scene` only for a single still moment, if one is needed at all.\\n\\n**Designed scenes.** Read `look` → `reference/scenes.md`.\\n\\n- Use `design_scene` for the film's own moments: your HTML and CSS following the concept. Check every preview, and fix it with `replace`.\\n- Use `add_scene` only where a library scene fits a beat as-is.\\n- Vary the compositions; don't settle into a loop.\\n- Scenes dissolve into each other. Dip (`join: \\\"dip\\\"`) only at chapter breaks, and fade out (`fadeOut`) only at the very end.\\n\\n**Both modes:**\\n\\n- Use quote marks only for real quotes, with the speaker.\\n- A film *about* an organisation isn't *by* it: no official-looking end card, no logo lock-up or \\\"Join us\\\" unless the user is that organisation. Credit the sources.\\n- Once the acts are in, place sound effects on the big moves with `add_sound_effects` (a whoosh on a whip, a pop on a pop-in, an impact on a collide), and check that the music still runs to the end.\\n\\n## 6. Check and deliver\\n\\n`preview_film` shows the film and measures its motion. When a reviewer model is set (AI settings → Reviewer), call `review_film` next. It sends the film to that model for notes with times, fixes and a verdict. Fix the notes that matter; if you disagree with one, say why in your reply.\\n\\nCheck that:\\n\\n- it reads as one piece;\\n- the length is within 10% of what was asked;\\n- there is no unrelated media;\\n- the music runs to the end.\\n\\nReply with:\\n\\n- the length;\\n- the world and the protagonist chain, in one line each;\\n- the look in one line;\\n- the acts or beats in one line each;\\n- what to check (facts, quotes, image rights, music licence).\\n\\nOffer their own pictures, a 9:16 version (`reformat`), or another direction.\"\r\n },\r\n {\r\n \"name\": \"make-template\",\r\n \"description\": \"Design a reusable video template in the editor from a description - a short multi-scene video whose every visible word, colour and media slot becomes an editable field - for launches, product tours, agent demos, metrics reels, testimonials and similar. Use for \\\"Create with AI\\\" in the templates panel and whenever the user wants a template rather than one video.\",\r\n \"instructions\": \"# Make a template\\n\\nLoad `edit-video` first if you haven't. A template is a short, finished-looking video built so the editor can turn its texts, colours and media into a form: people fill the form and get their own video. Build it in the project as normal clips; the editor makes the fields afterwards.\\n\\n## 1. Brief → beat sheet\\n\\nSettle in two lines: what it's for (launch, walkthrough, results, proof…), who watches, the one thing they remember. Then write the beats before building:\\n\\n```\\n# Len Beat On screen Motion\\n1 2.6s Hook eyebrow + two-line claim track-in, line 2 wipes on\\n2 4.2s Line chart title, panel, 6-point line, tooltip line draws, dots pop\\n…\\n6 2.8s End card wordmark, line, button glow blooms, button pings\\n```\\n\\n- 4–7 scenes, 2.6–4.4 s each (5–25 s in total; shorter when the request asks for one scene). Hook ≤ 2.8 s and says what it is. End card about 2.8 s.\\n- One idea per scene, at most about 7 words of headline.\\n- **Every scene keeps moving after it lands**: a loop, a count, a draw, a drift, a ping. A scene that animates in and then holds looks like a slide.\\n\\n## 2. Build for editability\\n\\n- **Text**: every visible word is either a text-style clip (`add_text_style`, if the editor has it) or a `{{placeholder}}` in an HTML clip's `params` (e.g. `{{headline}}`, `{{stat1}}`), with short placeholder copy as the value. Name params by role in reading order (`hook_line1`, `bar3_value`). Each one becomes a field.\\n- **Colour**: HTML clips take their colours from params named by role, as `#RRGGBB` values: `primary` and `secondary` for the two brand colours, `accent`, `bg`, `ink` (text). Brand kits match these names, so one brand change recolours the whole video. Never hard-code white text on a background that might change.\\n- **Data is params too**: counts, chart points, percentages; keep the HTML working for any reasonable value (a counter from 0 to `{{value}}`).\\n- **Media**: use the project's sample media as placeholders (each becomes a media slot). Never invent media URLs. Images inside HTML are `src=\\\"asset:<id>\\\"`.\\n- **Defaults are samples**: invented names and companies, never real ones, and never present placeholder numbers or quotes as real.\\n- **Look**: apply the user's direction (colours, theme, style, mood) as asked. When they ask for a template for their own brand, take its colours, fonts and voice from `get_brand` (`edit-video` → Brand and direction) as the defaults of the colour params and copy.\\n\\n## 3. Scenes\\n\\nOne animated full-frame HTML clip per scene, back to back on one track, over a background (`set_background`, or a static full-frame HTML background clip with `animate_clip` loop kenburns). Build each from `reference/html-recipes.md` in the `edit-video` skill (base kit, `--d` staggers in reading order, XML-strict markup). `reference/patterns.md` in this skill has the patterns that worked for the library's templates (counters, typing, spinner to check, spotlight, hotspots, charts, rings, avatars, star fills, walls, marquees, stamps): its classes (`.r`, `.disp`, `--spring`…) come from a build kit you don't have here, so copy the idea and the `@keyframes`, and define the classes in the scene's own `<style>`.\\n\\nBetween scenes: `add_transition` crossDissolve about 0.3 s. Music, if the request implies it: a sample track at volume 0.5 with a 2 s fade-out.\\n\\nPitfalls (from the template library): one start time per element (nest elements for different times); inline `<svg>` needs `xmlns=\\\"http://www.w3.org/2000/svg\\\"`; write `<` and `>` in CSS as `&lt;` `&gt;` and `&#160;` instead of `&nbsp;`; no `transform-style:preserve-3d`; springs overshoot, so anything clipped by an edge eases out instead.\\n\\n## 4. Check\\n\\n`reference/scorecard.md` has the rubric. From the state: scenes back to back, every scene's entrances settled at least 1 s before its end, every visible word reachable as a field (text clip or `{{param}}`), `primary`/`secondary` used, nothing hard-coded that a theme or brand change would break.\\n\\n## 5. Reply\\n\\nFirst line: a short template name (2–4 words). Then one sentence: what it is, how many scenes, and which fields matter most.\",\r\n \"files\": {\r\n \"reference/patterns.md\": \"# Scene patterns\\n\\nPatterns proven in the first five templates (launch film, product tour, AI agent demo, metrics reel, testimonial wall). Each one renders the same in the studio preview and in export, in all four themes. Snippets assume the kit's `BASE` CSS, so `lift`, `clear`, `popIn`, `reveal`, `grow`, `spin`, `gone`, `--spring`, `--bounce`, `--out` and `--move` exist.\\n\\nContents:\\n- Timing: start times, nesting, show-then-hide, staggers\\n- Type: hook, typing, streaming lines, highlighter, wipe-down quote, stamp\\n- Numbers and data: counter, line chart, ranked bars, goal ring, star fill, mini sparklines\\n- UI: product window with a screenshot, spotlight, hotspots, spinner to check, workflow, phone tap and sheet, command palette\\n- People and proof: avatar from a name, quote card wall, marquee rows, ticker\\n- Endings: end card\\n\\n## Timing\\n\\n**Start times.** `style=\\\"--d:.4s\\\"` is when an element's animations start, measured from the scene's start. Children inherit it unless they set their own. Every animation on one element shares it.\\n\\n**Different times on one thing: nest.**\\n\\n```html\\n<!-- fades in at .9 s, fades out at 2.05 s -->\\n<div class=\\\"cap\\\" style=\\\"--d:.9s\\\"><div style=\\\"--d:2.05s\\\">Caption</div></div>\\n```\\n```css\\n.cap{animation:fadein .3s ease-out both}\\n.cap>div{animation:fadeout .25s ease-in both}\\n```\\n\\n**Staggers.** Generate them in JS so they stay even: ``${[1,2,3].map((i)=>`<div class=\\\"card\\\" style=\\\"--d:${(0.3+(i-1)*0.14).toFixed(2)}s\\\">…</div>`).join(\\\"\\\")}``. Use 0.12–0.14 s for cards and rows, 0.45–0.5 s for steps that should be read one at a time.\\n\\n**Syncing to a linear reveal.** If a line draws from `d0` over `D` seconds with `linear`, point i of n (0-based) is reached at `d0 + D·i/(n−1)`. Pop each dot then.\\n\\n## Type\\n\\n**Hook** (every template): the eyebrow tracks in, line 1 rises, and line 2 (accent) wipes on and keeps shimmering.\\n\\n```css\\n@keyframes track{from{letter-spacing:.9em;opacity:0;filter:blur(6px)}}\\n.eyebrow{animation:track 1.1s var(--out) both}\\n.l2{display:inline-block;animation:reveal .9s cubic-bezier(.65,0,.35,1) both,shimmer 3.2s linear infinite}\\n```\\n```html\\n<div class=\\\"eyebrow\\\" style=\\\"--d:.05s\\\">{{brand}}</div>\\n<div class=\\\"h1 disp r\\\" style=\\\"--d:.15s\\\">{{hook1}}</div><div class=\\\"h1 disp grad l2\\\" style=\\\"--d:.6s\\\">{{hook2}}</div>\\n```\\n\\n**Typing** (editable text, any length up to the cap): `.typed` animates `max-width` with `steps()`. Shorten it per line with `animation-duration`, and follow it with `<span class=\\\"caret\\\"></span>`.\\n\\n**Streaming lines** (an answer arriving): each line is `display:table` with `animation:reveal .8s steps(24,end) both`, staggered by about 0.35 s.\\n\\n**Highlighter across wrapped lines:**\\n\\n```css\\n@keyframes mark{from{background-size:0% 100%}to{background-size:100% 100%}}\\n.mk{background:linear-gradient(color-mix(in srgb,{{secondary}} 34%,transparent),color-mix(in srgb,{{secondary}} 34%,transparent)) no-repeat 0 85%/0% 100%;\\n -webkit-box-decoration-break:clone;box-decoration-break:clone;animation:mark 1.2s cubic-bezier(.65,0,.35,1) both}\\n```\\n\\n**Wipe-down quote** (line by line, stepped): `@keyframes wipeDown{from{clip-path:inset(0 0 100% 0)}to{clip-path:inset(0 0 0 0)}}` with `steps(4,end)`, where 4 is about the number of lines.\\n\\n**Stamp slam:** `@keyframes slam{from{transform:rotate(-8deg) scale(2.4);opacity:0}}` with `cubic-bezier(.5,0,.75,0)` (accelerating into the hit), on an element whose resting transform is `rotate(-8deg)`, with a `var(--shade)` background so it reads over anything.\\n\\n## Numbers and data\\n\\n**Counter** (whole numbers up to 4 digits):\\n\\n```css\\n@property --n{syntax:'&lt;integer&gt;';inherits:false;initial-value:0}\\n@keyframes count{from{--n:0}to{--n:var(--to)}}\\n.num{counter-reset:n var(--n);animation:count 1.6s var(--out) both}\\n.num::after{content:counter(n)}\\n```\\n```html\\n<span class=\\\"pre\\\">{{heroPre}}</span><span class=\\\"num\\\" style=\\\"--to:{{heroNum}};--d:.2s\\\"></span><span class=\\\"unit\\\">{{heroUnit}}</span>\\n```\\n\\nKeep symbols in their own fields (`$`, `K`, `%`). Field pattern: `^[0-9]{1,4}$`. Inside `.grad`, put any blur on the `.grad` wrapper, never on `.num`.\\n\\n**Line chart from field values:**\\n\\n```html\\n<div class=\\\"plot\\\"> <!-- position:relative; a fixed height -->\\n <div class=\\\"draw\\\" style=\\\"--d:.7s\\\"><svg xmlns=\\\"http://www.w3.org/2000/svg\\\" viewBox=\\\"0 0 500 100\\\" preserveAspectRatio=\\\"none\\\">\\n <g transform=\\\"translate(0 100) scale(1 -1)\\\">\\n <path d=\\\"M0 0 L0 {{p1}} L100 {{p2}} … L500 {{p6}} L500 0 Z\\\" fill=\\\"url(#cg)\\\"/>\\n <path d=\\\"M0 {{p1}} L100 {{p2}} … L500 {{p6}}\\\" fill=\\\"none\\\" stroke=\\\"{{primary}}\\\" stroke-width=\\\"6\\\" vector-effect=\\\"non-scaling-stroke\\\"/>\\n </g></svg></div>\\n <div class=\\\"dot\\\" style=\\\"left:0%;bottom:calc({{p1}} * 1%)\\\"><i style=\\\"--d:.7s\\\"></i></div> …\\n</div>\\n```\\n\\n- Flipping the y axis makes each field (0–100) a height directly.\\n- `preserveAspectRatio=\\\"none\\\"` plus `non-scaling-stroke` keeps the line crisp at any shape.\\n- Draw it on with a `reveal` on the wrapper (`linear`), not `stroke-dashoffset`, which non-scaling strokes break. A 2 px scan line can travel with it: `@keyframes scan{from{left:0}to{left:100%}}`, same timing.\\n- Dots and tooltips are HTML placed with `bottom:calc({{pN}} * 1%)`.\\n\\n**Ranked bars, scaled to the largest** (pure CSS):\\n\\n```html\\n<div class=\\\"fill\\\" style=\\\"width:calc({{b1v}} / max({{b1v}},{{b2v}},{{b3v}},{{b4v}},{{b5v}}) * 82%);--d:.55s\\\"></div>\\n```\\n\\nAnimate with `@keyframes wgrow{from{width:0}}`, and put the value right after the bar in the same flex row so it rides along. The leader gets the gradient.\\n\\n**Goal ring that laps past 100 %:**\\n\\n```css\\n@property --a{syntax:'&lt;number&gt;';inherits:false;initial-value:0}\\n@keyframes ring{from{--a:0}to{--a:var(--to)}}\\n.rg{position:absolute;inset:0;border-radius:50%;mask:radial-gradient(farthest-side,transparent calc(100% - 50px),#000 calc(100% - 49px));animation:ring 1.9s cubic-bezier(.45,0,.2,1) both}\\n.r1{background:conic-gradient({{primary}} calc(min(var(--a),100) * 3.6deg),color-mix(in srgb,var(--ink) 10%,transparent) 0)}\\n.r2{inset:64px;background:conic-gradient({{secondary}} calc(max(var(--a) - 100,0) * 3.6deg),transparent 0)}\\n```\\n\\nGive the number in the middle the same duration and easing. On paper and pop, set the ring on its own card (`.gw::before` with `var(--surface)`, `var(--border)`, `var(--shadow)`).\\n\\n**Star fill from a rating field** (`4.9`): `style=\\\"--r:{{rating}}\\\"` on a wrapper. Two rows of ★★★★★ are stacked; the top one is `width:calc(var(--r) / 5 * 100%);overflow:hidden;white-space:nowrap`, and grows from `width:0`.\\n\\n**Mini sparklines** (decoration, not data): an SVG polyline in `viewBox=\\\"0 0 100 30\\\"` with `preserveAspectRatio=\\\"none\\\"`, an area polygon under it, and a `reveal` 0.5 s after its tile lands.\\n\\n## UI\\n\\n**Product window with a screenshot field:**\\n- a frame with a 44 px bar of three dots;\\n- `<img src=\\\"asset:shot\\\" alt=\\\"\\\"/>` at a fixed size with `object-fit:cover`;\\n- `var(--surface)`, `var(--border)`, `var(--shadow)`.\\n\\nWrite the coordinates of the areas you point at in the image's own pixel space, plus the bar offset. For the 9:16 window, scale them by its width ratio (for example, 0.762 for 960 / 1260).\\n\\n**Spotlight gliding between areas:** one element inside the window's `overflow:hidden`. `box-shadow:0 0 0 2400px #000000a6` dims everything else, and `outline:4px solid {{secondary}}` marks the edge. Animate `left/top/width/height` through keyframe stops (hold, move, hold), `cubic-bezier(.65,0,.35,1)`, and show one caption per stop with show-then-hide.\\n\\n**Hotspots:** a zero-size `.pin` at the point holds a numbered dot (`popIn` with `--bounce`), a ping ring (`@keyframes ping{from{transform:scale(.6);opacity:.8}to{transform:scale(2.6);opacity:0}}`, infinite) and a label that `reveal`s out of it. Flip a label to the left near the right edge.\\n\\n**Spinner to check:**\\n\\n```html\\n<div class=\\\"st\\\"><div class=\\\"sw\\\" style=\\\"--d:1.05s\\\"><div class=\\\"ring\\\" style=\\\"--d:0s\\\"></div></div><div class=\\\"ok\\\" style=\\\"--d:1.05s\\\">✓</div></div>\\n```\\n```css\\n.sw{position:absolute;inset:0;animation:gone .18s ease-in both}\\n.ring{position:absolute;inset:4px;border-radius:50%;border:4px solid color-mix(in srgb,var(--ink) 18%,transparent);border-top-color:{{secondary}};animation:spin .7s linear infinite}\\n.ok{position:absolute;inset:0;border-radius:50%;background:{{secondary}};animation:popIn .7s var(--spring) both,clear .3s var(--out) both}\\n```\\n\\n**Workflow run:**\\n- node cards land in a wave;\\n- the wires between them `grow` from the left as each node lights (`@keyframes lit{to{border-color:{{primary}};box-shadow:…}}` on an inset overlay);\\n- a check pops on each node;\\n- a result chip closes it.\\n\\nIn 9:16 the flow becomes a column, and the wires use `growY` with `transform-origin:center top`.\\n\\n**Phone:** a 420×860 frame with 64 px corners and a notch. A finger dot fades in, a tap ripple runs (`@keyframes tap{0%{transform:scale(.3);opacity:.9}100%{transform:scale(2.2);opacity:0}}`), then a sheet slides up (`@keyframes sheet{from{transform:translateY(100%)}}` with the spring) and a check pops on it.\\n\\n**Command palette:** the room dims (`@keyframes dim{to{background:var(--shade)}}`), the palette opens with a scale and fade, the query is typed, and a row gets picked (a background pulse), then the palette closes and a toast pops.\\n\\n## People and proof\\n\\n**Avatar from a name** (no image, and it follows edits):\\n\\n```css\\n.av{width:64px;height:64px;border-radius:50%;overflow:hidden;font-size:0;line-height:64px;text-align:center;white-space:nowrap;\\n color:#fff;background:linear-gradient(135deg,{{primary}},{{secondary}});font-family:var(--display);font-weight:var(--dw)}\\n.av::first-letter{font-size:30px}\\n```\\n```html\\n<span class=\\\"av\\\">{{name}}</span>\\n```\\n\\nThe same trick at 440 px makes a portrait card with a big initial.\\n\\n**Quote card wall:**\\n- four columns of six cards, each column moving 420 px over the scene, alternating `up` and `down` (one column at a different speed);\\n- the wall tipped back with `transform:perspective(2400px) rotateX(14deg)` on the wall element itself;\\n- the top and bottom faded with `mask:linear-gradient(transparent,#000 14%,#000 80%,transparent)`.\\n\\nIn 9:16, hide two columns.\\n\\n**Marquee rows:** two rows of pills translating in opposite directions (`translateX(0 → −640px)` and back) over the scene, with the edges faded by a horizontal mask. Repeat the items so a row never runs out.\\n\\n**Ticker:** one strip at the bottom of the hook, `translateX(0 → −50%)` linear over about twice the scene length, with its content written twice. Fill it with the template's own numbers (other fields), so it previews what's coming.\\n\\n## Endings\\n\\n**End card** (in the starter):\\n- a radial glow blooms;\\n- the wordmark comes into focus;\\n- a gradient dot drops with `--bounce`;\\n- the tagline rises;\\n- the button pings three times (`@keyframes pulse{from{transform:scale(1);opacity:.7}to{transform:scale(1.35,1.8);opacity:0}}`).\\n\\nThe end card has `last: true`, so it doesn't exit.\\n\",\r\n \"reference/scorecard.md\": \"# Scorecard, notes and metadata\\n\\n## Rubric\\n\\nScore 1–5 for the template and for the closest reference in its category (a template or component that does the same job). Compare like for like: the same copy, the same accent colour, about the same length.\\n\\n| Criterion | What to check | Bar to pass |\\n| --- | --- | --- |\\n| Hook | Is the message clear by second 2? | Equal or better |\\n| Motion | Easing, timing, holds, no jank or pops; every scene keeps moving after it lands | Equal or better |\\n| Typography and layout | Hierarchy, spacing, safe margins, in every theme and both formats | Equal or better |\\n| Detail | Depth, micro-motion, transitions between beats | Equal or better |\\n| Editability | Can a non-coder change every word, image, number and colour? | Better: every visible word, image and data point is a field |\\n| Formats | How many shapes look designed, not cropped | Better: at least 2 (16:9 and 9:16), plus four themes |\\n| Export fidelity | Export matches the preview | Export = preview (the user checks this on their machine) |\\n\\nA template **passes** when the first four are equal or better and editability and formats are better.\\n\\nWhat keeps a 4 from being a 5 on motion is usually the camera: a move into the area that matters (a zoom into the chart, a dolly through the wall). Note that as the next improvement rather than blocking on it.\\n\\n## notes.md\\n\\n```markdown\\n# Notes · <Title>\\n\\nBuilt and checked <date> in the React studio (<commit>), headless Chromium. Export is yours.\\n\\n## Checks\\n\\n- [x] Opens with no console errors; <n> fields.\\n- [x] 16:9 motion beats (`contact-sheet.png`); all four themes, one settled frame per scene (`contact-sheet-themes.png`); 9:16 in <themes> (`contact-sheet-vertical.png`).\\n- [ ] Speed 1.00 and export, on the user's machine.\\n\\nFixed while checking: <one line each>.\\n\\n## Scores (1–5)\\n\\nReference: <what it was compared with, described by category; internal only>.\\n\\n| Criterion | Reference | Ours | Note |\\n| --- | --- | --- | --- |\\n| Hook | | | |\\n| Motion | | | |\\n| Typography and layout | | | |\\n| Detail | | | |\\n| Editability | | | |\\n| Formats | | | |\\n\\n**Verdict: passes / not yet.**\\n\\n## Craft notes\\n\\n- <a new pattern or pitfall, in one or two lines; also add it to reference/patterns.md>\\n```\\n\\n## template.json\\n\\nCatalog metadata that the template library website reads later:\\n\\n```json\\n{\\n \\\"id\\\": \\\"metrics-reel\\\",\\n \\\"title\\\": \\\"Metrics reel\\\",\\n \\\"goal\\\": \\\"report-and-present\\\",\\n \\\"type\\\": \\\"results\\\",\\n \\\"formats\\\": [\\\"16:9\\\", \\\"9:16\\\"],\\n \\\"themes\\\": [\\\"terminal\\\", \\\"midnight\\\", \\\"paper\\\", \\\"pop\\\"],\\n \\\"defaultTheme\\\": \\\"terminal\\\",\\n \\\"durationSec\\\": 24,\\n \\\"style\\\": [\\\"data-viz\\\", \\\"kinetic-type\\\"],\\n \\\"pace\\\": \\\"steady\\\",\\n \\\"industry\\\": [\\\"saas\\\", \\\"any\\\"],\\n \\\"startFrom\\\": [\\\"numbers\\\"],\\n \\\"fields\\\": 61,\\n \\\"assets\\\": [{ \\\"url\\\": \\\"https://assets.miraiclip.com/templates/_shared/\\\", \\\"license\\\": \\\"SIL OFL 1.1\\\" }],\\n \\\"source\\\": \\\"new\\\"\\n}\\n```\\n\\n| Key | Values |\\n| --- | --- |\\n| `goal` | `launch`, `show-how-it-works`, `build-trust`, `report-and-present`, `grow-on-social`, `explain-and-teach`, `celebrate-and-connect` |\\n| `pace` | `calm`, `steady`, `energetic` |\\n| `startFrom` | `text`, `screenshots`, `numbers`, `footage`, `recording` |\\n| `assets` | One entry per file or shared folder, with its licence (and the credit, if the licence needs one) |\\n\"\r\n }\r\n },\r\n {\r\n \"name\": \"media-sound\",\r\n \"description\": \"Pictures, music and sound effects for a film from a brief — when to find vs generate images and their rights, choosing music by mood and tempo before building, timing acts to the music, placing sound effects on moves, and mix levels. Load it when a film needs pictures or sound.\",\r\n \"instructions\": \"# Media and sound\\n\\n## Pictures\\n\\n**Only the subject's own media.** A film may show:\\n\\n- pictures or clips the user added in this chat or names;\\n- a brand's own `brand-*` assets (from `get_brand`);\\n- pictures you get with the image tools.\\n\\nAnything else already in the project (samples, an earlier project's media) has nothing to do with this subject. The editor refuses it.\\n\\n**Find or generate:**\\n\\n| The moment shows… | Tool | Query |\\n|---|---|---|\\n| Something real: a person, place, product, show, event, building | `find_images` | One specific query per moment: \\\"Lisbon tram Alfama street\\\", \\\"The Mentalist Patrick Jane Simon Baker\\\" |\\n| A concept, mood or story moment with no real picture | `generate_image` | Describe the picture, not the film: \\\"a student coding late at night, warm desk lamp\\\". The film's palette is added for you, and pictures come without text |\\n\\n**Never:**\\n\\n- generate a real person's face, a real product's screens or a real logo;\\n- put text in a generated picture (set the words in HTML).\\n\\n**Rights:** web images belong to their owners. Say so in your reply, and credit the source on screen when a film is *about* an organisation (\\\"Images: NASA\\\").\\n\\n**Not set up?** If a tool says it isn't configured, tell the user they can add a Tavily or Gemini key in AI settings → Images. Then design with type, shape and motion: a film without pictures can be very good.\\n\\n**Use pictures the way the look says** (the `look` skill: frames, duotone, texture). In motion graphics a picture is usually an element that moves: it flies in with blur, it is a tile in a grid that becomes a wall, it is the inside of a window the camera pushes into. It rarely holds as a full-frame slide.\\n\\n## Music\\n\\n**Choose the music before you build.** The music sets the pace, so its length and tempo shape the acts:\\n\\n1. Pick the mood and tempo from the brief and the look:\\n\\n | Film | Mood | Tempo |\\n |---|---|---|\\n | Playful | Bouncy, plucky | About 110–125 bpm |\\n | Launch | Bright, driving | About 115–128 bpm |\\n | Calm explainer, course | Light, warm | About 90–110 bpm |\\n | Trailer | Low drones and pulses that build to hits | Free |\\n | Documentary | Piano, strings, ambient | Slow |\\n\\n2. `search_audio` with the music words `plan_film` returned, plus the mood and genre (\\\"upbeat electronic pluck\\\", \\\"warm acoustic\\\", \\\"cinematic tension build\\\"). Prefer a track at least as long as the film. Use `generate_audio` (music, `instrumental: true`) only when the user asks, or when nothing fits.\\n3. Plan act lengths in whole musical phrases.\\n - At 120 bpm a bar is 2 s, so a 4-bar phrase is 8 s and an 8-bar phrase is 16 s.\\n - At 100 bpm, 4 bars are 9.6 s.\\n\\n If you don't know the tempo, keep acts at 8–12 s and beats at 0.5 s multiples. Big moves land on whole seconds.\\n4. Add it before composing the acts: `add_audio` at 0 s, covering the whole film. Fade in 1 s and out 2 s.\\n5. Call `get_beat_grid`. It measures the music's tempo and beats, and from then on every act composed with `compose_sequence` gets them as `S.beats`. It tells you the tempo, the bar length and the beat times; move the treatment's beats onto them.\\n\\n**The beat grid.** After `get_beat_grid`, `S.beats` / `S.beat(n)` in a sequence give the beat times *within that act*, so a move can land on the beat: `tl.label(\\\"hit\\\", S.beat(8)).from(\\\".w3\\\", {...}, \\\"hit\\\")`. Put each beat's label on a beat. compose_sequence reports how many labels land within 80 ms of a beat. Without a grid, `S.beat(n)` is n × 0.5 s, a steady 120 bpm.\\n\\n## Sound effects\\n\\nSound effects sell motion. Place them on moves, sparingly:\\n\\n| Move | Sound |\\n|---|---|\\n| A whip pan, zoom-through, fast fly-in | A short whoosh, starting about 0.1 s before the move peaks |\\n| A pop-in, chip, badge | A soft pop or click |\\n| Typing | Light key ticks (a short loop, not one per letter) |\\n| A count-up, ticking list | Ticks |\\n| A collide-and-merge, title reveal, logo lock-up | One impact or hit, with a riser into it |\\n| A blur-through | An air swell |\\n\\n- Place them with `add_sound_effects`: name the act and its labels, and the kind of sound for each (whoosh, pop, tick, impact, riser, swell). It finds a fitting sound, puts it on its own track at the label's time in the film (a whoosh starts slightly before its move), and mixes it under the music.\\n- **At most one effect per beat**, and none on every element of a stagger. Three good whooshes beat twenty.\\n- Silence is a tool: drop the effects in a reflective act.\\n\\n## Mix levels\\n\\n| Layer | Volume |\\n|---|---|\\n| Music, no voice | 0.7 |\\n| Music under a voice | 0.25–0.4 |\\n| Voice | 1 |\\n| Sound effects | 0.4–0.7 (they should be felt more than heard) |\\n\\n- Fade music out over the last 2 s, ending on the end card's hold.\\n- Nothing should clip: if effects pile up at a hit, lower the music there or drop an effect.\\n\\n## In your reply\\n\\nSay what the music is (title and source) and its licence. Say which pictures came from the web, and that they belong to their owners. Say which pictures were generated.\"\r\n },\r\n {\r\n \"name\": \"motion\",\r\n \"description\": \"The motion-graphics grammar for compose_sequence acts — continuity, hidden cuts, a moving camera, kinetic type — plus about 30 atomic techniques with working stage-kit code. Load it before composing any act.\",\r\n \"instructions\": \"# Motion\\n\\nA film made from a brief should read as **one continuous move**, not slides. This skill is the grammar for that, and a library of techniques you combine inside compose_sequence acts. Each technique is a file with what it is, when to use it, its timing, the usual mistake, and a working snippet (markup, CSS, and the body of `stage.build((tl, S) => { … })`). Read one with `read_skill_file` (skill `motion`, path `reference/rules/<name>.md`), adapt it to your act, never paste it unchanged.\\n\\n## The grammar in short\\n\\n1. **Every beat grows out of the last one.** A title becomes a window's label. Tiles become accents. Five photos become five hundred. Nothing appears from nowhere inside an act. See `reference/continuity.md`.\\n2. **Hidden cuts only.** When something must change completely, hide the cut:\\n - inside a 0.2–0.3 s blur (blur-through);\\n - in a push into an element (zoom-through);\\n - in a whip (whip-pan);\\n - or on the same element at two framings (match-cut).\\n\\n Never dissolve one full layout into another.\\n3. **The camera keeps moving.** Push, pull, pan with the type, tilt in depth, untilt to resolve. A beat with a still camera needs something else moving; the only allowed stillness is a rest declared with `tl.rest`. See `reference/camera.md`.\\n4. **Type is the main actor.** Lines type, retype in place, rise from masks and swap words. The key word is in the accent colour. Never more than about 7 words on screen.\\n5. **One accent colour.** Use it on the key word, the small accent shapes and the end dot, and nowhere else.\\n6. **Blur on every fast move** and a soft focus-in on every new element (motion-blur).\\n7. **Short beats.** Most beats last 0.3–1.2 s. Holds are for reading, and they stay alive. See `reference/timing.md` for beat lengths, the reading budget and the motion targets.\\n8. **The easing set.** expo.out in, expo.in out, power3.inOut / expo.inOut to move between places, back.out for pops, sine.inOut for drift (easing-tokens).\\n\\n## How to build an act with these\\n\\n1. Write the act's beats first: each beat is one idea, one move, and what it hands to the next beat.\\n2. For each beat, pick **one** main technique from the index, plus a carry-over into the next beat. Two or three techniques at once is noise.\\n3. Name every beat with `tl.label(…)`, and put each beat's moves at its label. compose_sequence shows the beats on the timeline and checks the layout just after each one.\\n4. Keep the camera moving across the whole act: one long `tl.camera(…)` per beat, overlapping the next.\\n5. Look at the filmstrip compose_sequence returns. Check that every frame has something moving, that the hand-offs are visible, and that nothing is off-frame. Fix with `replace`.\\n\\n## Things the stage kit does for you, and things to remember\\n\\n- Sizes in the script come from `S.w(%)`, `S.h(%)` and `S.min(%)`, so the act fits 16:9, 9:16 and 1:1. In CSS, use container units (cqw, cqh, cqmin).\\n- The timeline owns `transform` on everything it animates. Centre elements with a grid wrapper, or with margins, never with a CSS `transform: translate(-50%, -50%)` on an animated element.\\n- `S.split(sel, \\\"words\\\" | \\\"chars\\\")` wraps each word or character in a span (`.word` / `.char`) and returns them in reading order. Styled children such as `<em>` stay, with their own words split inside them.\\n- For randomness, use `S.rand(seed)`: the same frames on every render. Measuring the DOM (`offsetLeft`, `getBoundingClientRect`) is fine: fonts have loaded when the script runs.\\n- Tween colours to real colour values (hex or rgb), not `var(--…)`.\\n- Elements outside `.world` (a `.hud` layer) don't move with the camera.\\n\\n## Techniques\\n\\n| Technique | What it is / when |\\n|---|---|\\n| `accent-punctuation` | An accent square or dot jumps to the end of each word as it lands |\\n| `anticipation` | A small counter-move before a big fast move |\\n| `blur-through` | A hidden cut: the beat blurs out as the next blurs in |\\n| `carry-over` | The last beat's element becomes part of the next (title → window label) |\\n| `collide-and-merge` | Many elements converge and become one (tools → platform) |\\n| `count-up` | A number rolls up on digit reels; for real stats only |\\n| `depth-stack` | Cards fan out in depth while the camera tilts |\\n| `easing-tokens` | The five eases and what each one is for |\\n| `grid-assemble` | Tiles fly in to a grid, then a highlight wave keeps it alive |\\n| `highlight-sweep` | A marker bar sweeps behind the key word |\\n| `holds` | Reading time that stays alive: push, drift, a breathing accent |\\n| `kinetic-type` | Words punch in one at a time; the key word lands in the accent |\\n| `line-draw` | A line draws at a steady rate; nodes pop as it reaches them |\\n| `mask-reveal` | Lines rise from behind an invisible edge |\\n| `match-cut` | A hard cut between two framings of the same element |\\n| `morph` | One shape grows into the next container (dot → card → window) |\\n| `motion-blur` | Blur that peaks with speed on every fast move |\\n| `orbit` | Satellites circle a hub, with labels kept upright |\\n| `overshoot` | Landing past the mark and settling (back.out / elastic.out) |\\n| `parallax` | Layers drift at different speeds to show depth |\\n| `path-follow` | An arc from A to B with separate x and y eases |\\n| `pop-in` | Chips and badges appear with an overshoot and focus-in |\\n| `retype-in-place` | A typed line deletes and a new one types on the same baseline |\\n| `scale-to-fill` | A word grows until it bleeds past both edges |\\n| `split-and-scatter` | Letters fly apart, or assemble from scattered positions |\\n| `staggered-entrance` | A list enters item by item with one shared move |\\n| `swap-in-place` | One word in a fixed sentence slides out and the next slides in |\\n| `typewriter` | Characters type with a caret that follows |\\n| `whip-pan` | A very fast blurred camera move to the next panel |\\n| `wipe` | A clip-path wipe with an accent bar riding the edge |\\n| `zoom-out-reveal` | The camera pulls back to show the few are part of many |\\n| `zoom-through` | The camera pushes into an element and the next beat is inside it |\\n\\n## More reading\\n\\n- `reference/continuity.md`: carry-overs, catalogued, and how one act hands off to the next.\\n- `reference/timing.md`: beat lengths, the reading budget, holds, and the motion targets the film is checked against.\\n- `reference/camera.md`: the `.world` layer, camera moves and their sizes.\\n- `reference/breakdowns/`: grammar studies of finished films, beat by beat (what happens, the technique, what carries over). Read one with `read_skill_file` when you want to see how a whole film is put together; borrow its grammar, not its content or look.\",\r\n \"files\": {\r\n \"reference/breakdowns/documentary-trailer.md\": \"# Breakdown: documentary trailer (40 s)\\n\\nA grammar study of a style, not a description of one particular film: the trailer for a documentary or a true story. Still photographs carry it, kept alive by slow pushes and parallax. Titles rise out of darkness, facts are stated plainly, and the music builds to one reveal. Its pace is slower than motion graphics, but it is never still.\\n\\nUse it to learn the grammar. Build your own film's version of each idea; don't copy the beats.\\n\\n## Look\\n\\n- **Background:** near-black, with fine grain and a slight vignette. Letterbox bars are optional, and when used text stays inside the middle 70%.\\n- **Type:** a restrained serif or a light condensed sans, generously tracked for titles (caps, `letter-spacing: .3em`) and plain for facts. Off-white, never pure white.\\n- **Colour:** desaturated pictures (or duotone into one warm accent). The accent appears once or twice: a date, the title.\\n- **Pictures:** real archive stills, documents and places. Each one gets a slow move. Captions name the place and date in small caps.\\n- **Words on screen:** one short line per card, sometimes a single date or number.\\n\\n## Beats\\n\\n| # | Time | What happens | Technique | Carried into the next beat |\\n|---|---|---|---|---|\\n| 1 | 0.0–4.0 | Black. A single caption fades up: a place and a date. A low drone starts | holds, mask-reveal | The date's position |\\n| 2 | 4.0–9.0 | A first still fades up behind the caption. The camera pushes slowly in; foreground and background drift apart (the picture is cut into two layers) | parallax, holds | The push |\\n| 3 | 9.0–10.0 | The still blurs out as the next blurs in: a hidden cut on the push | blur-through | The push's direction |\\n| 4 | 10.0–15.0 | A face (a portrait still), framed on a third. A real quote rises line by line beside it, attributed | mask-reveal | The portrait |\\n| 5 | 15.0–16.0 | The title card: letters rise from a mask, very wide spacing, then hold dark | mask-reveal, holds | The black |\\n| 6 | 16.0–24.0 | The build: four stills, 2 s each, each with a single plain fact. The cuts tighten (2 s → 1.5 s → 1 s), each a blur-through on the music's pulse | blur-through, kinetic-type | The tempo |\\n| 7 | 24.0–27.0 | A real figure counts up on black (\\\"1,947 days\\\"), with one line under it | count-up | The number's centre |\\n| 8 | 27.0–33.0 | The fastest section: half-second stills, a whip between two of them, the drone rising | whip-pan, motion-blur | The rising sound |\\n| 9 | 33.0–34.0 | Silence and black: a hold, the one dead moment, on purpose | holds | — |\\n| 10 | 34.0–40.0 | The title again, this time with the accent, then the date or platform. The slow push continues to the end | mask-reveal, holds | — |\\n\\n## The grammar, in short\\n\\n- **Stills are never still.** Every picture has a slow push, a pan or parallax for its whole time on screen. The camera move carries across the cut (a blur-through on a continuing push).\\n- **Plain words.** Facts in short declarative lines. Only real quotes, with the speaker. Dates and places as small-caps captions.\\n- **Acceleration.** Shot lengths shrink as the film builds (4 s → 2 s → 1 s → 0.5 s), then stop dead for the reveal. The rhythm tells the story.\\n- **Darkness is the transition.** Titles come out of black, and pictures fade up from black. Use dips only at the act breaks, not between every still.\\n- **One number.** A single counted figure is a turning point. A wall of statistics is a report.\\n- **The accent is saved** for the title and the payoff.\\n\\n## Timing targets\\n\\nThis is a documentary-paced film, so its targets are lower than motion graphics:\\n\\n- Motion density at least 0.6 (slow pushes count as motion; they should be visible, not imperceptible).\\n- Holds over 0.8 s only where meant: the opening caption, the title, the silence.\\n\",\r\n \"reference/breakdowns/editorial-explainer.md\": \"# Breakdown: editorial explainer (20 s)\\n\\nA grammar study of a style, not a description of one particular film: the magazine-style explainer. Cut-out pictures and labels sit on a clean editorial grid, a rail draws the argument across the frame, and the camera tracks along it. It explains one idea in a few steps, and every step is attached to the line that runs through the film.\\n\\nUse it to learn the grammar. Build your own film's version of each idea; don't copy the beats.\\n\\n## Look\\n\\n- **Background:** off-white paper, with a faint 12-column grid or dot grid. Hairline rules in a warm grey.\\n- **Type:** a serif for headlines (big, tight, sentence case) and a small sans in caps for labels and numbers (\\\"01\\\", \\\"STEP\\\", \\\"SOURCE\\\").\\n- **Colour:** one strong accent (a red, cobalt or green) for the rail, key words and markers. Everything else is ink on paper.\\n- **Pictures:** cut-outs (subjects with their background removed) or tightly cropped photos in hard-edged frames, often duotoned into the ink and accent. Small captions next to them.\\n- **Words on screen:** a headline (5–8 words) plus labels. Never paragraphs.\\n\\n## Beats\\n\\n| # | Time | What happens | Technique | Carried into the next beat |\\n|---|---|---|---|---|\\n| 1 | 0.0–1.8 | The question is set as a headline. It rises line by line from a mask, and the key word is underlined in the accent as it lands | mask-reveal, highlight-sweep | The underline |\\n| 2 | 1.8–3.0 | The underline stretches past the headline and becomes the rail: a line that draws to the right edge. The camera starts to track along it | morph, line-draw | The rail and the camera's track |\\n| 3 | 3.0–5.5 | The first node on the rail pops. A cut-out picture slides up behind it with a label (\\\"01 · Collect\\\") and a one-line caption. Slight parallax: the picture moves slower than the label | pop-in, staggered-entrance, parallax | The rail |\\n| 4 | 5.5–8.0 | The rail keeps drawing. Node 2 pops, and a second cut-out and label arrive from the opposite side, so the frame stays balanced while tracking | line-draw, staggered-entrance | The rail |\\n| 5 | 8.0–10.5 | Node 3. The picture is a diagram instead: three small boxes joined by lines that draw | line-draw, grid-assemble | The diagram's middle box |\\n| 6 | 10.5–12.0 | The camera pushes into the middle box. It fills the frame and becomes the next scene's background panel | zoom-through | The panel |\\n| 7 | 12.0–15.0 | Inside the panel, a real figure counts up, with its unit and source label. The key word of its caption gets the marker sweep | count-up, highlight-sweep | The figure |\\n| 8 | 15.0–17.0 | The camera pulls back out through the panel. The whole rail is visible at once, all nodes lit: the argument at a glance | zoom-out-reveal | The full rail |\\n| 9 | 17.0–20.0 | The rail curls into an underline under the answer (the headline's own words, now resolved). It holds and drifts | morph, holds | — |\\n\\n## The grammar, in short\\n\\n- **One line runs through the film.** The rail is the protagonist: it starts as an underline, carries the steps and ends as an underline. Every step hangs off it.\\n- **Track, don't cut.** The camera follows the line sideways (a long, slow `x` move on `.world`), so new material enters from the frame edge as if it was always there.\\n- **Balance alternates.** Content arrives above, then below the rail, or left then right, so the frame never tips.\\n- **Labels make it editorial.** Small caps, numbers and sources next to every picture. They are what make a frame read as \\\"explained\\\" rather than \\\"decorated\\\".\\n- **One push in, one pull out.** A single zoom-through into a detail and back is the film's big move. More than one gets dizzying.\\n- **Markers instead of colour floods.** The accent appears as lines, dots and sweeps behind words.\\n\\n## Timing targets\\n\\n- 8–10 beats in 20 s; steps take 2–3 s each, because labels need reading time.\\n- The rail draws at a steady rate (`linear`); everything else eases `expo.out`.\\n- Motion density at least 0.85, from the constant tracking. No hold over 0.8 s apart from the final 1.5 s.\\n\",\r\n \"reference/breakdowns/kinetic-type-promo.md\": \"# Breakdown: kinetic-type promo (12 s)\\n\\nA grammar study of a style, not a description of one particular film: the all-type promo. There are no pictures. Words are the picture: they punch in on the beat, swap in place, fill the frame and fly through it. It suits a launch, an event, a sale or a manifesto, anything with a few strong lines and a beat to cut to.\\n\\nUse it to learn the grammar. Build your own film's version of each idea; don't copy the beats.\\n\\n## Look\\n\\n- **Background:** a flat colour that changes at the turns: black → the accent → white. The colour flips are the cuts.\\n- **Type:** one heavy grotesk, very large (headline 14–30 cqmin, some words bleeding off the frame), tight tracking, all caps or sentence case but not both. A second weight at most.\\n- **Colour:** two or three flat colours that swap roles (text and background trade places).\\n- **Shapes:** a few accent shapes (a bar, a dot, a square) that act as punctuation.\\n- **Words on screen:** 1–4 at a time.\\n\\n## Beats (at 120 bpm, a beat every 0.5 s)\\n\\n| # | Time | What happens | Technique | Carried into the next beat |\\n|---|---|---|---|---|\\n| 1 | 0.0–1.0 | On black: \\\"YOU\\\" punches in (scale from 1.4, blur to sharp), then \\\"MAKE\\\" beside it on the next beat | kinetic-type, motion-blur | The line's baseline |\\n| 2 | 1.0–2.0 | The third word swaps in place every beat: \\\"VIDEOS\\\" → \\\"SLIDES\\\" → \\\"TEMPLATES\\\" (each slides up out of a mask) | swap-in-place, mask-reveal | The slot |\\n| 3 | 2.0–2.5 | The last word \\\"TEMPLATES\\\" is struck through by an accent bar that wipes across | wipe, accent-punctuation | The accent bar |\\n| 4 | 2.5–3.0 | The bar grows to fill the frame: the background is now the accent colour | scale-to-fill | The accent background |\\n| 5 | 3.0–4.5 | \\\"WE MAKE\\\" in black on the accent; then \\\"FILMS.\\\" scales from tiny to bleeding past both edges | kinetic-type, scale-to-fill | The giant word |\\n| 6 | 4.5–5.0 | Whip pan left: the giant word streaks out with heavy blur, and the next panel streaks in | whip-pan, motion-blur | The camera's speed |\\n| 7 | 5.0–7.0 | Three short claims, one per beat pair, each a different size and position on a 3×3 grid (\\\"NO TIMELINE.\\\" / \\\"NO KEYFRAMES.\\\" / \\\"JUST WORDS.\\\") | kinetic-type, grid-assemble | The last claim |\\n| 8 | 7.0–8.0 | \\\"JUST WORDS.\\\" splits into letters that scatter, then reassemble as the product name | split-and-scatter | The name's letters |\\n| 9 | 8.0–10.0 | The name holds, centre. Each beat, one letter bounces (a ripple across the name). An accent dot lands after it | overshoot, accent-punctuation | The name |\\n| 10 | 10.0–12.0 | The colours flip to white. The URL types under the name; the camera eases back slightly | typewriter, holds | — |\\n\\n## The grammar, in short\\n\\n- **The beat is the editor.** Every entrance, swap and flip lands on a beat (`S.beat(n)`). Off-beat moves look like mistakes.\\n- **Type does everything.** Scale, weight, position and colour do the work pictures would. Contrast between neighbours keeps it lively: a tiny word next to a giant one, a left-aligned line after a centred one.\\n- **Swap, don't replace.** A sentence stays and one word changes, so the eye stays in one place while the meaning moves.\\n- **Colour flips are cuts.** A whole-frame colour change on a beat is the cleanest hidden cut in this style.\\n- **Words fly through the camera.** scale-to-fill and whip-pan carry the energy between sections.\\n- **Never more than 4 words.** If a line needs more, it's two beats.\\n\\n## Timing targets\\n\\n- 18–24 moves in 12 s: a change on most beats.\\n- Arrivals in 0.2–0.35 s (`expo.out`), exits in 0.15–0.25 s (`expo.in`).\\n- Motion density at least 0.9. Median change is high: the colour flips spike it, and that's intended.\\n\",\r\n \"reference/breakdowns/playful-shapes.md\": \"# Breakdown: playful shapes intro (15 s)\\n\\nA grammar study of a style, not a description of one particular film: the bright, bouncy intro, where soft 3D-ish shapes are the characters. Orbs, pills and blobs drift, bounce, gather and merge, and the brand is what they become. It suits an introduction to a service, a friendly launch, or a feature for a broad audience.\\n\\nUse it to learn the grammar. Build your own film's version of each idea; don't copy the beats.\\n\\n## Look\\n\\n- **Background:** a light, saturated-but-soft ground (pale lilac, mint, butter) or a deep night blue for a glowing version. Soft colour blooms behind.\\n- **Shapes:** spheres with a highlight (a radial gradient from a light spot to the colour) and a soft shadow or glow. 4–8 colours from one cheerful palette, plus the brand's colour for the hero shape.\\n- **Type:** a rounded or geometric sans, bold, short lines. Labels sit on or under the shapes.\\n- **Depth:** shapes sit at different scales and blur levels (near ones bigger and sharper), and the camera tilts gently, so the field feels 3D.\\n- **Words on screen:** 2–5.\\n\\n## Beats\\n\\n| # | Time | What happens | Technique | Carried into the next beat |\\n|---|---|---|---|---|\\n| 1 | 0.0–1.2 | Orbs pop in one by one at scattered positions, each with an overshoot and a focus-in. Each carries a label: the jobs a small business juggles | pop-in, overshoot, staggered-entrance | The orbs |\\n| 2 | 1.2–3.0 | The orbs drift, each at its own speed and scale (depth). The camera floats and tilts a few degrees. A line above: \\\"Seven apps. Seven logins.\\\" | parallax, depth-stack, kinetic-type | The orbs' drift |\\n| 3 | 3.0–3.6 | Anticipation: every orb pulls back slightly outwards | anticipation | The tension |\\n| 4 | 3.6–4.4 | They rush to the centre and collide. A squash-and-stretch on impact, a flash, one big orb in the brand colour | collide-and-merge, motion-blur | The big orb |\\n| 5 | 4.4–5.6 | The big orb settles with a wobble (elastic). The brand name pops onto it. A soft hit in the music | overshoot, pop-in | The big orb |\\n| 6 | 5.6–8.5 | The old orbs come back as small satellites orbiting the big one. Each satellite now shows a feature, connected | orbit | The orbit |\\n| 7 | 8.5–10.5 | The camera follows one satellite and pushes into it. It opens into a rounded card showing the feature (a simple UI drawn in shapes) | zoom-through, morph | The card |\\n| 8 | 10.5–12.5 | The card's elements bounce in one by one (a toggle flips, a chip pops, a checkmark draws) | pop-in, line-draw | The card |\\n| 9 | 12.5–15.0 | Pull back: the card shrinks back into a satellite, and the whole system is visible again. The URL rises under it; the satellites keep orbiting | zoom-out-reveal, mask-reveal, holds | — |\\n\\n## The grammar, in short\\n\\n- **Shapes are characters.** They have weight: they anticipate before a big move, overshoot when they land, and squash on impact. That gives the film its personality.\\n- **The story is a transformation.** Many things become one (collide-and-merge), and the one gives back the many as connected parts (orbit). That is the whole argument of an all-in-one product, told without a sentence.\\n- **Depth makes it rich.** Scale and blur by distance, plus a slowly tilting camera. Flat shapes on a flat field look like clip art.\\n- **Bouncy but controlled.** Use `back.out` and `elastic.out` on landings, and `sine.inOut` on drift. Elastic goes only on the hero moment; elastic everywhere is seasick.\\n- **Always something floating.** Between beats the shapes keep drifting, so a hold never freezes.\\n\\n## Timing targets\\n\\n- 9–12 beats in 15 s.\\n- Landings take 0.4–0.7 s, drifts are continuous, and the collide is fast (0.5–0.8 s, `expo.in`).\\n- Motion density at least 0.9. No hold over 0.8 s except the final 1.5 s, which still orbits.\\n\",\r\n \"reference/breakdowns/typed-ui-launch.md\": \"# Breakdown: typed-UI launch film (13.4 s)\\n\\nA light, minimal product launch film for an AI video tool, 13.4 s at 16:9. It reads as one continuous move: every idea grows out of the one before it, the camera never stops, and the cuts are hidden inside blurs and zooms.\\n\\nThis file describes the film's grammar: timing, techniques, what carries over at each change, and how it measures. It is not code to copy. Use it to understand how a film like this is built, then build your own film's version of each idea.\\n\\n## Look\\n\\n- Background: warm off-white paper (#f7f6f2-ish), with very soft colour blooms (pale blue and pink) drifting in the corners.\\n- Type: one geometric sans, regular weight, dark grey-black. The key word of each line is set in one blue accent (#3b6cf6-ish), sometimes as a gradient.\\n- Accent objects: small blue squares, and one blue dot at the very end.\\n- Imagery: soft product UI panels (editor screenshots, almost unreadable, used as texture) and, late in the film, a set of vivid photographic thumbnails. The photos are the only saturated thing in the film.\\n- Words on screen: never more than 6 at once.\\n\\n## Beats\\n\\n| # | Time | What happens | Technique | Carried into the next beat |\\n|---|---|---|---|---|\\n| 1 | 0.0–2.1 | \\\"Still hand-animating\\\" types in, about 10 characters/s, with a caret. The second half is in the blue accent. The line fades and sharpens in over 0.3 s. From 1.0 s, blurred UI panels drift in around the edges. The camera eases back (the type shrinks from about 1.35× to 1×). | Typewriter; blur-in; slow pull-back; depth blur on background texture | The line's position and the caret |\\n| 2 | 2.2–4.4 | The line is replaced in place: \\\"your launch video?\\\" types in, with \\\"video?\\\" in blue. The caret blinks at the end for about 1 s. The panels keep drifting. | In-place retype (same baseline, same caret) | The whole frame |\\n| 3 | 4.3–4.8 | The whole frame blurs out over 0.3 s into the paper and its colour bloom. The brand name blurs in, small, in a soft gradient. | Blur-through transition (the cut is hidden inside the blur) | The brand name |\\n| 4 | 4.8–5.2 | The brand name becomes the label of the app's window: the window appears around it, tilted in perspective (about 8–10°), and the camera is already rotating. | **Carry-over**: the name becomes part of the next object; 3D tilt | The window, the tilt |\\n| 5 | 5.2–5.9 | Very close on the tilted window, \\\"Hello, Sri.\\\" types in blue italic, then \\\"What are we shipping today?\\\" types in grey. The camera pans right with the typing, so the text slides across the frame at an angle. | Camera follows the typing; extreme close-up; perspective | The question and the camera move |\\n| 6 | 5.9–7.0 | Option cards slide in under the question one after another (\\\"Ship the demo\\\", \\\"Cut a launch video\\\", \\\"Render the changelog\\\", \\\"Draft the release post\\\"). The camera keeps panning across them. | Staggered entrance during a camera move | The window |\\n| 7 | 7.0–7.9 | The camera pulls back and un-tilts: the window flattens and shrinks to a small card in the centre, then blurs out. | Pull-back reveal; perspective resolves to flat | The centre of the frame |\\n| 8 | 8.1–8.9 | A grid of small tiles (template thumbnails) blurs in at the centre, with four dark labelled cards around it. The tiles flicker between blue and grey as if being browsed. | Blur-in; living texture (tiles keep changing) | The tiles |\\n| 9 | 8.9–9.5 | The grid collapses: tiles fly out and a few become small blue squares that move with the text. \\\"Turn your product into video\\\" types word by word, with \\\"video\\\" in blue, while the squares jump alongside like punctuation. The dark cards drift outwards. | Elements **transform** into the next beat's accents; kinetic words | The centre column |\\n| 10 | 9.6–10.8 | Five photos fly in with motion blur and lock into a staggered column. A chip, \\\"5 components\\\", pops in over them (blur to sharp). | Motion-blurred fly-in; pop | The photo grid |\\n| 11 | 10.9–11.1 | A hard zoom out: the five photos are revealed as part of a wall of many more, and the chip now says \\\"500 components\\\". | **Zoom-out reveal** with the counter changing (the same object, bigger idea) | The whole frame |\\n| 12 | 11.2–12.3 | The wall blurs out. The brand wordmark is set huge, bleeding past both edges. Its colour sweeps from pale grey-blue to navy to near-black, left to right. | Scale-to-fill; colour sweep | The wordmark |\\n| 13 | 12.4–13.1 | A match cut to an extreme crop of the wordmark's last letter and the blue dot. It holds with a slight drift. | **Match cut on the same element** (wide → detail) | — |\\n| 14 | 13.2–13.4 | Blur and fade out. | Blur-out | — |\\n\\n## The grammar, in short\\n\\n- **Beats are short.** 14 beats in 13.4 s: most last 0.3–1.2 s. The longest holds are about 1 s, on the two questions.\\n- **Every change has a carry-over.** It might be the same line retyped, the brand name becoming the window label, tiles becoming squares, five photos becoming five hundred, or the wordmark becoming its own crop. Nothing just appears from nowhere.\\n- **Hidden cuts.** Where a hard change is needed, it happens inside a 0.2–0.3 s blur (beats 3, 7, 11–12), or as a match cut on the same element (beat 13). No dissolves between unrelated layouts.\\n- **The camera is always moving:** pull-back, rotation, pan with the type, untilt, zoom-out. It moves in depth (tilt and perspective), not just in 2D.\\n- **Type is the main actor.** Lines type in with a caret, the key word is in the accent colour, and lines are replaced in place.\\n- **Texture, not information.** The UI panels are atmosphere; nobody is meant to read them.\\n- **One accent colour,** used on the key word, the small squares and the final dot.\\n- **Motion blur on every fast move,** and a blur-in on every new element.\\n\\n## How it measures\\n\\nMeasured on 192×108 greyscale frames at 10 fps (`plan/sequences.md` → motion metrics):\\n\\n| Metric | Value |\\n|---|---|\\n| Motion density (share of steps with visible change) | 0.99 |\\n| Median change per step (0–255 scale) | 3.5 |\\n| Holds over 0.8 s with no visible change | none |\\n| Jumps (a big single-step change with calm frames either side) | none |\\n\\nFor comparison, a recent slide-like film made by the assistant (centred cards, about 4 s each, dissolves) measured density 0.63, median change 0.76, and 5 holds over 0.8 s. A 90 s documentary-style film measured 0.32, 0.45 and 17 holds.\\n\",\r\n \"reference/camera.md\": \"# Camera\\n\\nThe camera is the `.world` layer: everything inside `<div class=\\\"world\\\">…</div>` moves with it. `tl.camera(vars, position)` animates that layer:\\n\\n| Camera var | What it does | Typical range |\\n|---|---|---|\\n| `zoom` | push in (> 1) or pull back (< 1) | 0.9–1.15 for drift; 3–7 for zoom-through and reveals |\\n| `x`, `y` | pan; always `S.w(%)` / `S.h(%)` | 2–6% for drift; 100% for a whip to the next panel |\\n| `rotate` | roll | ±1–3° |\\n| `tiltX`, `tiltY` | tilt in depth (perspective is set for you) | 6–20° |\\n| `duration`, `ease` | default 1.2 s, power2.inOut | match the beat or overlap it |\\n\\nElements **outside** `.world` (for example a `.hud` with a chip or a caption) don't move with the camera.\\n\\nThe camera zooms about the `.world` transform-origin (the centre by default). To push into a detail, put the origin on that detail. Measure it with `getBoundingClientRect()` before any camera move, then `tl.set(\\\".world\\\", { transformOrigin: \\\"x% y%\\\" }, 0)`.\\n\\n## The camera keeps moving, except in a declared rest\\n\\nGive each beat one camera move that lasts the beat, or longer, overlapping the next. A static camera is allowed only during a hold that has something else moving, and for less than 0.8 s. The one exception is a stretch you declare with `tl.rest(from, to)` (stage version 2): everything in `.world` is dead still on purpose, for a punchline or a pause. Undeclared stillness is still flagged, and the build fails if something moves during a declared rest. After composing, run `check_sequence` to see which beats start from nothing.\\n\\n## Moves and when to use them\\n\\n- **Slow push (zoom 1 → 1.05–1.1, sine.inOut):** the default for any beat. It adds life without drawing attention.\\n- **Pull back (zoom 1.3 → 1):** opening a beat close on a detail and revealing context. At an extreme, it's a zoom-out-reveal.\\n- **Pan with the type (x −3 to −6%):** while a line types or words land left to right, drift the same way so the text slides across at an angle.\\n- **Tilt in depth (tiltX/tiltY 8–20°):** windows, cards and stacks feel like objects in a space. Combine it with a slight zoom.\\n- **Untilt to resolve (tilt → 0, power3.inOut):** the end of a section. The frame flattens as the idea lands.\\n- **Push-through (zoom to 4–7, expo.in):** into an element whose colour becomes the next background (zoom-through). Reset the camera on the frame where only that colour fills the screen.\\n- **Whip (x ±100%, 0.3–0.4 s, power4.inOut, with blur):** to the next panel (whip-pan).\\n- **Kick (zoom −5% in 0.25 s, then back):** on an impact (collide-and-merge, a beat hit).\\n\\n## Rules\\n\\n- Sizes are always in S units (`S.w`, `S.h`, `S.min`). Never pixel numbers: the act must work in 16:9, 9:16 and 1:1.\\n- One camera move at a time per property. Start the next move when the last one ends (or with `\\\">\\\"`), not on top of it.\\n- Pair tilt with `transform-style: preserve-3d` on `.world` and on any wrapper when children use `z` (depth-stack).\\n- Keep the subject inside the safe area during moves (about 5% in from each edge, more in 9:16). Check the filmstrip.\\n- Don't reset the camera to neutral between beats. Carry the motion on.\\n\\n## The operated camera (version 2)\\n\\nAdd `data-stage-version=\\\"2\\\"` to the stage root and the camera becomes a track you can operate, not just keyframe. Everything below sums on the one `.world` layer, so moves never fight each other.\\n\\n| Call | Use it to |\\n|---|---|\\n| `tl.camera({ zoom })` | Zoom in log space: 1 to 100 reads 10 halfway, so a long dive feels even |\\n| `tl.camera({ anchor, at: [x%, y%], zoom })` | Hold a subject at a screen position while the zoom changes. Use it for every zoom above 50× |\\n| `tl.follow(target, { lead: 0.1 })` | Chase a moving subject, keyed slightly ahead of it |\\n| `tl.whip(target, { duration })` | Snap to the next subject and land with it centred |\\n| `tl.zoomThrough(target, { into })` | Push into a surface and hand over to the scene inside it |\\n| `tl.handheld({ amount }, from, to)` | A slow float so a held shot still breathes |\\n| `tl.shake(position, { amp, decay, seed })` | A hit shake that reads the same at any zoom |\\n\\nKeep the subject inside the safe area while the camera moves, and put hits on the beat with `tl.hit` so sound can follow them.\\n\",\r\n \"reference/continuity.md\": \"# Continuity\\n\\nThe difference between a motion-graphics film and a slideshow is continuity: **every beat begins from something already on screen in the beat before.** If you can cover the previous frame with your hand and the new beat still makes sense on its own, it's a slide.\\n\\n## The test, beat by beat\\n\\nFor every change of beat, write one line: *\\\"<what> from beat N becomes / moves into <what> in beat N+1\\\"*. If you can't fill it in, design a carry-over before writing code.\\n\\n## Carry-overs, catalogued\\n\\n| Carry-over | Example | Technique |\\n|---|---|---|\\n| Same object, new role | The brand name becomes the label of the app window that grows around it | carry-over, morph |\\n| Same line, new words | \\\"Still hand-animating\\\" deletes back and \\\"your launch video?\\\" types on the same baseline | retype-in-place, swap-in-place |\\n| Shape grows into container | A dot becomes a card, the card becomes a window | morph |\\n| Parts become accents | Grid tiles fly out, and a few become the small squares that punctuate the next line | grid-assemble → accent-punctuation |\\n| Many become one | Ten orbs collide and merge into the logo | collide-and-merge |\\n| One becomes many | Five photos are revealed as part of a wall of five hundred | zoom-out-reveal |\\n| Same element, new framing | The wordmark cuts to an extreme close-up of its last letter | match-cut |\\n| Into the element | The camera pushes into a card, and the next beat is inside it | zoom-through |\\n| A path between | A dot travels from \\\"Lead\\\" to \\\"Booked\\\" and the target lights up | path-follow, line-draw |\\n| The camera itself | A continuous pan that keeps going across a whip into the next panel | whip-pan, parallax |\\n\\nWhen nothing can carry over (a genuinely new subject), hide the cut: blur-through (0.25–0.3 s) or whip-pan. Use this once or twice per act at most.\\n\\n## Keep the anchor still while things change around it\\n\\nThe eye follows one thing at a time. During a hand-off, keep **one** element as the anchor: the caret, the key word, the logo. Move it smoothly while the new beat arrives around it. Changing everything at once reads as a cut, even without one.\\n\\n## Between acts\\n\\nA film is a few acts, each one compose_sequence clip. The join between acts follows the same rule:\\n\\n- **The next act starts where the last one ended.** If act 1 ends on the logo centred at 60% height, act 2's first frame has the logo centred at 60% height (same size, same colour), and act 2 moves on from there.\\n- Write the end state of each act down (what's on screen, where, what size) before composing the next act, and reproduce it as act 2's starting state with `tl.set(…, 0)`.\\n- The default join is a 0.3 s dissolve. When the two frames match, the dissolve is invisible and the film reads as one move. Use `join: \\\"cut\\\"` when they match exactly. Use `join: \\\"dip\\\"` only at a real chapter break.\\n- Keep the camera's motion going across the join: if act 1 ends drifting left, start act 2 drifting left.\\n\\n## Things that break continuity\\n\\n- A dissolve between two unrelated layouts.\\n- An element that pops in from nothing in the middle of an act, with no cause (no line reaching it, no word landing).\\n- Re-animating a whole sentence to change one word.\\n- An act that starts on an empty frame and builds from zero (only the very first act may).\\n- Resetting the camera to a neutral position between beats.\\n\\nRun `check_sequence` after composing: it names every beat that starts from nothing (bare) and suggests the carry that would fix it.\\n\",\r\n \"reference/rules/accent-punctuation.md\": \"# Accent punctuation\\n\\n**What:** A small accent shape (a square, a dot) moves with the words like punctuation, jumping to the end of each word as it lands.\\n**When:** Kinetic lines of short words (\\\"Plan. Build. Ship.\\\"); it carries the accent colour and gives the eye a guide.\\n**Timing:** Each jump 0.25–0.3 s (expo.out), timed with the word it follows.\\n**Mistake:** More than one accent object per line, or an accent that wanders without following anything.\\n\\n```html\\n<div class=\\\"world\\\"><p class=\\\"line\\\"><span class=\\\"txt\\\">Plan. Build. Ship.</span><i class=\\\"sq\\\"></i></p></div>\\n```\\n\\n```css\\n.line{position:absolute;left:14cqw;top:42cqh;font-size:11cqmin;font-weight:900;white-space:nowrap}.sq{position:absolute;left:0;top:.62em;width:.22em;height:.22em;background:var(--p)}\\n```\\n\\n```js\\nconst words = S.split(\\\".line .txt\\\", \\\"words\\\");\\ntl.label(\\\"say\\\", 0.2).set(\\\".sq\\\", { x: 0, scale: 0 }, 0);\\nwords.forEach((w, i) => {\\n const at = \\\"say+=\\\" + i * 0.45;\\n tl.from(w, { y: S.h(5), opacity: 0, blur: 8, duration: 0.4, ease: \\\"expo.out\\\" }, at)\\n .to(\\\".sq\\\", { x: w.offsetLeft + w.offsetWidth + S.min(1), scale: 1, rotate: 90 * (i + 1), duration: 0.3, ease: \\\"expo.out\\\" }, at);\\n});\\ntl.camera({ x: -S.w(3), zoom: 1.05, duration: 3, ease: \\\"sine.inOut\\\" }, 0);\\n```\\n\",\r\n \"reference/rules/anticipation.md\": \"# Anticipation\\n\\n**What:** A small move the opposite way just before the main move: a card pulls back and dips before it launches.\\n**When:** Before any big, fast move (a launch, a throw, a whip). It tells the eye where to look a fraction before things happen.\\n**Timing:** Wind-up 0.12–0.2 s (power2.out), then the main move 0.4–0.6 s (expo.out). The wind-up is 2–5% of the main move's distance.\\n**Mistake:** A wind-up as big or as slow as the move itself: it reads as hesitation, not energy.\\n\\n```html\\n<div class=\\\"world\\\"><div class=\\\"card\\\">Ship it</div></div>\\n```\\n\\n```css\\n.card{position:absolute;left:30cqw;top:42cqh;padding:3cqmin 5cqmin;border-radius:2cqmin;background:var(--p);color:var(--bg);font-size:6cqmin;font-weight:700}\\n```\\n\\n```js\\ntl.label(\\\"wind\\\", 0.4)\\n .to(\\\".card\\\", { x: -S.w(2), scale: 0.94, duration: 0.18, ease: \\\"power2.out\\\" }, \\\"wind\\\")\\n .to(\\\".card\\\", { x: S.w(30), scale: 1, duration: 0.55, ease: \\\"expo.out\\\" }, \\\">\\\")\\n .camera({ x: -S.w(4), duration: 1.6, ease: \\\"sine.inOut\\\" }, \\\"wind\\\");\\n```\\n\",\r\n \"reference/rules/blur-through.md\": \"# Blur-through\\n\\n**What:** A hidden cut: the whole beat blurs out and the next one blurs in, overlapping by a few frames, so no layout ever dissolves into another.\\n**When:** A hard change of idea inside an act when nothing can carry over (and at most once or twice per act).\\n**Timing:** Out 0.25–0.3 s (power2.in, blur to 16–24 px, slight scale up). In 0.4 s (expo.out) starting halfway through the out.\\n**Mistake:** A slow cross-dissolve between two full layouts: both are half-visible and the frame turns to mush.\\n\\n```html\\n<div class=\\\"world\\\"><div class=\\\"center\\\"><p class=\\\"a\\\">Too many tools.</p></div><div class=\\\"center\\\"><p class=\\\"b\\\">One platform.</p></div></div>\\n```\\n\\n```css\\n.center{position:absolute;inset:0;display:grid;place-items:center}.a,.b{font-size:10cqmin;font-weight:800}.b{color:var(--p)}\\n```\\n\\n```js\\ntl.set(\\\".b\\\", { opacity: 0 }, 0)\\n .label(\\\"a\\\", 0)\\n .from(\\\".a\\\", { opacity: 0, blur: 12, duration: 0.5, ease: \\\"expo.out\\\" }, \\\"a\\\")\\n .label(\\\"cut\\\", 1.6)\\n .to(\\\".a\\\", { opacity: 0, blur: 22, scale: 1.08, duration: 0.28, ease: \\\"power2.in\\\" }, \\\"cut\\\")\\n .fromTo(\\\".b\\\", { opacity: 0, blur: 22, scale: 0.94 }, { opacity: 1, blur: 0, scale: 1, duration: 0.4, ease: \\\"expo.out\\\" }, \\\"cut+=0.14\\\")\\n .camera({ zoom: 1.08, duration: 3.4, ease: \\\"sine.inOut\\\" }, \\\"a\\\");\\n```\\n\",\r\n \"reference/rules/carry-over.md\": \"# Carry-over\\n\\n**What:** Something from the last beat becomes part of the next: a title becomes the label of the window that appears around it.\\n**When:** Every change of beat inside an act. It is the main thing that makes a film one continuous move instead of slides.\\n**Timing:** The hand-off move 0.5–0.8 s (expo.inOut); the new container arrives during it, never after.\\n**Mistake:** Fading the old element out and a new copy in: the viewer must see the same object travel.\\n\\n```html\\n<div class=\\\"world\\\"><div class=\\\"win\\\"><div class=\\\"bar\\\"><i></i><i></i><i></i></div><div class=\\\"body\\\"><p>Hello, Sam.</p><p class=\\\"q\\\">What are we shipping today?</p></div></div><div class=\\\"center\\\"><p class=\\\"name\\\">Orbit Studio</p></div></div>\\n```\\n\\n```css\\n.center{position:absolute;inset:0;display:grid;place-items:center}.name{font-size:10cqmin;font-weight:800;white-space:nowrap}.win{position:absolute;left:14cqw;top:14cqh;width:72cqw;height:72cqh;border-radius:2cqmin;background:var(--card);border:.3cqmin solid var(--line);overflow:hidden}.bar{height:8cqh;display:flex;gap:1cqmin;align-items:center;padding-left:2cqmin;border-bottom:.3cqmin solid var(--line)}.bar i{width:1.4cqmin;height:1.4cqmin;border-radius:50%;background:var(--line)}.body{padding:6cqmin;font-size:5cqmin;font-weight:600}.q{color:var(--mut)}\\n```\\n\\n```js\\ntl.label(\\\"name\\\", 0)\\n .from(\\\".name\\\", { opacity: 0, blur: 12, scale: 1.2, duration: 0.6, ease: \\\"expo.out\\\" }, \\\"name\\\")\\n .label(\\\"hand\\\", 1.3)\\n // The name travels into the window's title bar; the window grows around it at the same time.\\n .to(\\\".name\\\", { x: -S.w(22), y: -S.h(32), scale: 0.32, duration: 0.7, ease: \\\"expo.inOut\\\" }, \\\"hand\\\")\\n .from(\\\".win\\\", { scale: 0.7, opacity: 0, blur: 10, duration: 0.7, ease: \\\"expo.inOut\\\" }, \\\"hand\\\")\\n .from(\\\".body p\\\", { y: S.h(3), opacity: 0, duration: 0.5, stagger: 0.15, ease: \\\"expo.out\\\" }, \\\"hand+=0.6\\\")\\n .camera({ zoom: 1.1, tiltY: -8, duration: 3.4, ease: \\\"sine.inOut\\\" }, \\\"name\\\");\\n```\\n\\n## With `tl.hop`, `tl.gather` and `tl.morph` (version 2)\\n\\nThe hand-off can be a verb: `tl.hop` carries an element to its next slot on an arc and lands with a squash, `tl.gather` flies many elements into one container, and `tl.morph` turns one box into another. Each one records the carry, so the shot check counts it.\\n\\n```html v2\\n<div class=\\\"world\\\"><div class=\\\"dot\\\"></div><div class=\\\"slot\\\"><p class=\\\"label\\\">Ready</p></div></div>\\n```\\n\\n```css v2\\n.dot{position:absolute;left:12cqw;top:60cqh;width:7cqmin;height:7cqmin;border-radius:50%;background:var(--p)}.slot{position:absolute;left:62cqw;top:56cqh;width:26cqw;height:16cqh;border-radius:2cqmin;background:var(--card);border:.3cqmin solid var(--line);display:grid;place-items:center}.label{margin:0;font-size:4.4cqmin;font-weight:700}\\n```\\n\\n```js v2\\ntl.label(\\\"dot\\\", 0)\\n .from(\\\".dot\\\", { scale: 0, duration: 0.4, ease: \\\"back.out\\\" }, \\\"dot\\\")\\n .from(\\\".slot\\\", { opacity: 0, duration: 0.4, ease: \\\"expo.out\\\" }, \\\"dot+=0.2\\\")\\n .hop(\\\".dot\\\", { to: \\\".slot\\\", height: S.h(14), duration: 0.7 }, 0.9)\\n .impact(\\\".slot\\\", 1.55, { squash: 0.06 })\\n .from(\\\".label\\\", { y: S.h(2), opacity: 0, duration: 0.4, ease: \\\"expo.out\\\" }, 1.6)\\n .camera({ zoom: 1.08, duration: 3, ease: \\\"sine.inOut\\\" }, \\\"dot\\\");\\n```\\n\",\r\n \"reference/rules/collide-and-merge.md\": \"# Collide and merge\\n\\n**What:** Several elements converge on one point and become one new element.\\n**When:** Many-into-one ideas: tools into a platform, steps into a result, voices into one message.\\n**Timing:** Gather 0.6–0.8 s (expo.in, accelerating into the hit). The merged element pops 0.1 s before they arrive (back.out). A short camera kick (zoom −5%) on the hit.\\n**Mistake:** The parts arriving one by one and fading: the hit must be one moment.\\n\\n```html\\n<div class=\\\"world\\\"><i class=\\\"part\\\"></i><i class=\\\"part\\\"></i><i class=\\\"part\\\"></i><i class=\\\"part\\\"></i><i class=\\\"part\\\"></i><div class=\\\"core\\\">One</div></div>\\n```\\n\\n```css\\n.part{position:absolute;left:50%;top:50%;width:9cqmin;height:9cqmin;margin:-4.5cqmin 0 0 -4.5cqmin;border-radius:50%;background:var(--s);box-shadow:0 0 4cqmin var(--s)}.part:nth-child(2n){background:var(--p);box-shadow:0 0 4cqmin var(--p)}.core{position:absolute;left:50%;top:50%;width:24cqmin;height:24cqmin;margin:-12cqmin 0 0 -12cqmin;border-radius:50%;background:var(--p);color:var(--bg);display:grid;place-items:center;font-size:6cqmin;font-weight:800}\\n```\\n\\n```js\\nconst spots = [[-32, -20], [30, -24], [-36, 18], [34, 16], [0, 30]];\\ntl.set(\\\".core\\\", { scale: 0, opacity: 0 }, 0)\\n .set(\\\".part\\\", { x: (i) => S.w(spots[i][0]), y: (i) => S.h(spots[i][1]) }, 0)\\n .label(\\\"drift\\\", 0)\\n .from(\\\".part\\\", { scale: 0, opacity: 0, duration: 0.6, stagger: 0.08, ease: \\\"back.out\\\" }, \\\"drift\\\")\\n .to(\\\".part\\\", { y: (i) => S.h(spots[i][1] - 4), duration: 1.6, ease: \\\"sine.inOut\\\" }, \\\"drift+=0.4\\\")\\n .label(\\\"hit\\\", 2)\\n .to(\\\".part\\\", { x: 0, y: 0, scale: 0.4, duration: 0.7, stagger: 0.03, ease: \\\"expo.in\\\" }, \\\"hit\\\")\\n .to(\\\".core\\\", { scale: 1, opacity: 1, duration: 0.5, ease: \\\"back.out\\\" }, \\\"hit+=0.6\\\")\\n .to(\\\".part\\\", { opacity: 0, duration: 0.15 }, \\\"hit+=0.75\\\")\\n .camera({ zoom: 0.95, duration: 0.25, ease: \\\"power2.out\\\" }, \\\"hit+=0.7\\\")\\n .camera({ zoom: 1.04, duration: 1.6, ease: \\\"sine.inOut\\\" }, \\\">\\\");\\n```\\n\",\r\n \"reference/rules/count-up.md\": \"# Count-up\\n\\n**What:** A number rolls up to its value on digit reels (each digit is a column of 0–9 that slides), like an odometer.\\n**When:** Stats and counters: \\\"500 templates\\\", \\\"3× more calls\\\". Rolling digits read as growth; a static number reads as a slide.\\n**Timing:** 1.2–1.6 s (expo.out), reels staggered 0.1 s from the last digit. Hold the result 0.6–1 s with a slow push.\\n**Mistake:** Inventing numbers: only count to a real figure from the brief or the user. The value is a `{{param}}`-friendly attribute.\\n\\n```html\\n<div class=\\\"world\\\"><div class=\\\"center\\\"><div class=\\\"stat\\\"><p><span class=\\\"num\\\" data-to=\\\"500\\\"></span><span class=\\\"plus\\\">+</span></p><p class=\\\"cap\\\">templates</p></div></div></div>\\n```\\n\\n```css\\n.center{position:absolute;inset:0;display:grid;place-items:center}.stat{text-align:center}.stat p:first-child{font-size:22cqmin;font-weight:900;line-height:1;letter-spacing:-.03em}.slot{display:inline-block;height:1em;overflow:hidden;vertical-align:top}.reel{display:flex;flex-direction:column}.reel span{height:1em;line-height:1}.plus{color:var(--p)}.cap{font-size:5cqmin;color:var(--mut);margin-top:1.6cqmin}\\n```\\n\\n```js\\n// Build one reel per digit of data-to: 0–9 twice, so each reel spins at least once.\\nconst host = document.querySelector(\\\".num\\\");\\nconst reels = [], stops = [];\\nfor (const ch of host.getAttribute(\\\"data-to\\\")) {\\n const slot = document.createElement(\\\"span\\\");\\n slot.className = \\\"slot\\\";\\n if (/[0-9]/.test(ch)) {\\n const reel = document.createElement(\\\"span\\\");\\n reel.className = \\\"reel\\\";\\n for (let k = 0; k < 20; k++) {\\n const d = document.createElement(\\\"span\\\");\\n d.textContent = String(k % 10);\\n reel.appendChild(d);\\n }\\n slot.appendChild(reel);\\n reels.push(reel);\\n stops.push(10 + Number(ch));\\n } else slot.textContent = ch;\\n host.appendChild(slot);\\n}\\ntl.label(\\\"count\\\", 0.3)\\n .from(\\\".stat\\\", { opacity: 0, blur: 10, scale: 0.9, duration: 0.5, ease: \\\"expo.out\\\" }, 0)\\n .to(reels, { y: (i) => -stops[i] * 5 + \\\"%\\\", duration: 1.4, stagger: { each: 0.1, from: \\\"last\\\" }, ease: \\\"expo.out\\\" }, \\\"count\\\")\\n .from(\\\".plus\\\", { scale: 0, duration: 0.4, ease: \\\"back.out\\\" }, \\\">-=0.3\\\")\\n .camera({ zoom: 1.1, duration: 3.2, ease: \\\"sine.inOut\\\" }, 0);\\n```\\n\\nEach reel holds 20 digits, so stopping on digit d means moving it up by (10 + d) × 5% of its height.\\n\",\r\n \"reference/rules/depth-stack.md\": \"# Depth stack\\n\\n**What:** Flat cards fan out in depth (z) while the camera tilts, so a stack of things becomes a space.\\n**When:** Layers of a product, versions, a pile of documents, options to choose from.\\n**Timing:** Fan-out 0.8–1 s (expo.inOut), stagger 0.05 s. Camera tilt 8–20° over the whole beat, then untilt to resolve.\\n**Mistake:** Forgetting `transform-style: preserve-3d` on `.world` and the wrapper: the cards flatten into one plane.\\n\\n```html\\n<div class=\\\"world\\\"><div class=\\\"center\\\"><div class=\\\"stack\\\"><div class=\\\"card\\\">Draft</div><div class=\\\"card\\\">Review</div><div class=\\\"card\\\">Approved</div><div class=\\\"card\\\">Live</div></div></div></div>\\n```\\n\\n```css\\n.center{position:absolute;inset:0;display:grid;place-items:center}.world,.center,.stack{transform-style:preserve-3d}.stack{display:grid}.card{grid-area:1/1;width:44cqmin;height:28cqmin;border-radius:2cqmin;background:var(--card);border:.3cqmin solid var(--line);display:grid;place-items:center;font-size:5cqmin;font-weight:700}.card:last-child{background:var(--p);color:var(--bg)}\\n```\\n\\n```js\\nconst cards = document.querySelectorAll(\\\".card\\\");\\ntl.label(\\\"stack\\\", 0)\\n .from(cards, { y: S.h(6), opacity: 0, duration: 0.5, stagger: 0.06, ease: \\\"expo.out\\\" }, \\\"stack\\\")\\n .label(\\\"fan\\\", 0.9)\\n .to(cards, { z: (i) => S.min((i - 3) * 16), x: (i) => S.w((i - 1.5) * 6), y: (i) => -S.h((i - 1.5) * 5), duration: 0.9, stagger: 0.05, ease: \\\"expo.inOut\\\" }, \\\"fan\\\")\\n .camera({ tiltX: 18, tiltY: -24, zoom: 0.95, duration: 1.4, ease: \\\"power3.inOut\\\" }, \\\"fan\\\")\\n .camera({ tiltX: 0, tiltY: 0, zoom: 1.05, duration: 1.2, ease: \\\"power3.inOut\\\" }, \\\"fan+=1.9\\\");\\n```\\n\",\r\n \"reference/rules/easing-tokens.md\": \"# Easing tokens\\n\\n**What:** A small set of eases, each with one job, used consistently across the film.\\n**When:** Always: pick from these before trying anything else.\\n**Timing:** expo.out for entrances (0.4–0.7 s); expo.in for exits (0.3–0.4 s); power3.inOut / expo.inOut for moves between places and the camera (0.6–1.2 s); back.out for pops; sine.inOut for drift and holds (2–4 s); linear only for lines drawing at a steady rate.\\n**Mistake:** Linear moves for objects (mechanical), or a different ease on every element (no house style).\\n\\n```html\\n<div class=\\\"world\\\"><div class=\\\"rows\\\"><p><span>expo.out</span><i></i></p><p><span>expo.in</span><i></i></p><p><span>power3.inOut</span><i></i></p><p><span>back.out</span><i></i></p><p><span>sine.inOut</span><i></i></p></div></div>\\n```\\n\\n```css\\n.rows{position:absolute;left:8cqw;top:14cqh;display:flex;flex-direction:column;gap:6cqh;font:500 3.6cqmin ui-monospace,monospace;color:var(--mut)}.rows p{position:relative;display:flex;align-items:center;width:84cqw;height:5cqmin}.rows span{width:22cqw}.rows i{position:absolute;left:22cqw;width:5cqmin;height:5cqmin;border-radius:50%;background:var(--p)}\\n```\\n\\n```js\\nconst eases = [\\\"expo.out\\\", \\\"expo.in\\\", \\\"power3.inOut\\\", \\\"back.out\\\", \\\"sine.inOut\\\"];\\ntl.label(\\\"go\\\", 0.3);\\ndocument.querySelectorAll(\\\".rows i\\\").forEach((dot, i) => tl.to(dot, { x: S.w(52), duration: 1.2, ease: eases[i] }, \\\"go\\\"));\\ntl.camera({ zoom: 1.03, duration: 2.4, ease: \\\"sine.inOut\\\" }, 0);\\n```\\n\",\r\n \"reference/rules/grid-assemble.md\": \"# Grid assemble\\n\\n**What:** Tiles fly in from scattered, seeded positions and lock into a grid; then the grid stays alive (a wave of highlight passes through it).\\n**When:** Libraries, catalogues, \\\"all your templates\\\", any many-of-a-kind moment. The grid can later collapse or zoom out into a bigger wall.\\n**Timing:** Fly-in 0.6–0.8 s (expo.out), stagger 0.03–0.05 s. A highlight wave 0.3 s per tile, from the centre.\\n**Mistake:** Tiles that land and freeze: once assembled, something in the grid must keep changing.\\n\\n```html\\n<div class=\\\"world\\\"><div class=\\\"center\\\"><div class=\\\"grid\\\"><i></i><i></i><i></i><i></i><i></i><i></i><i></i><i></i><i></i><i></i><i></i><i></i></div></div></div>\\n```\\n\\n```css\\n.center{position:absolute;inset:0;display:grid;place-items:center}.grid{display:grid;grid-template-columns:repeat(4,14cqmin);gap:2cqmin}.grid i{height:10cqmin;border-radius:1.4cqmin;background:var(--card);border:.3cqmin solid var(--line)}\\n```\\n\\n```js\\nconst r = S.rand(3);\\nconst tiles = document.querySelectorAll(\\\".grid i\\\");\\nconst off = [...tiles].map(() => [(r() - 0.5) * 120, (r() - 0.5) * 100, (r() - 0.5) * 90]);\\ntl.label(\\\"fly\\\", 0)\\n .from(tiles, { x: (i) => S.w(off[i][0]), y: (i) => S.h(off[i][1]), rotate: (i) => off[i][2], opacity: 0, blur: 10, duration: 0.7, stagger: 0.04, ease: \\\"expo.out\\\" }, \\\"fly\\\")\\n .label(\\\"wave\\\", 1.5)\\n .to(tiles, { backgroundColor: \\\"#ffcf5a\\\", duration: 0.25, stagger: { each: 0.05, from: \\\"center\\\" }, ease: \\\"power2.out\\\" }, \\\"wave\\\")\\n .to(tiles, { backgroundColor: \\\"#1c2140\\\", duration: 0.4, stagger: { each: 0.05, from: \\\"center\\\" }, ease: \\\"power2.in\\\" }, \\\"wave+=0.3\\\")\\n .camera({ zoom: 1.08, tiltX: 8, duration: 3.4, ease: \\\"sine.inOut\\\" }, \\\"fly\\\");\\n```\\n\\nTween colours to real colour values (hex, rgb), not `var(--…)`: the timeline interpolates numbers.\\n\",\r\n \"reference/rules/highlight-sweep.md\": \"# Highlight sweep\\n\\n**What:** A marker bar sweeps behind the key word, and the word flips to the contrast colour as it is covered.\\n**When:** Emphasis in a sentence that is already on screen; editorial and explainer looks.\\n**Timing:** 0.4–0.5 s (expo.inOut) after the line has landed; the colour flip at 40% of the sweep.\\n**Mistake:** Sweeping several words in one sentence: emphasis on everything is emphasis on nothing.\\n\\n```html\\n<div class=\\\"world\\\"><div class=\\\"center\\\"><p class=\\\"s\\\">Make <span class=\\\"key\\\"><i class=\\\"mark\\\"></i><span class=\\\"kw\\\">video</span></span> from your product</p></div></div>\\n```\\n\\n```css\\n.center{position:absolute;inset:0;display:grid;place-items:center}.s{font-size:7.4cqmin;font-weight:700;white-space:nowrap}.key{position:relative;display:inline-block;isolation:isolate}.mark{position:absolute;left:-.12em;right:-.12em;top:.08em;bottom:.02em;background:var(--p);z-index:-1;transform-origin:0 50%}\\n```\\n\\n```js\\ntl.label(\\\"line\\\", 0)\\n .from(\\\".s\\\", { opacity: 0, y: S.h(4), blur: 10, duration: 0.5, ease: \\\"expo.out\\\" }, \\\"line\\\")\\n .label(\\\"mark\\\", 0.9)\\n .from(\\\".mark\\\", { scaleX: 0, duration: 0.45, ease: \\\"expo.inOut\\\" }, \\\"mark\\\")\\n .to(\\\".kw\\\", { color: \\\"#10142a\\\", duration: 0.15 }, \\\"mark+=0.18\\\")\\n .camera({ zoom: 1.07, duration: 2.6, ease: \\\"sine.inOut\\\" }, \\\"line\\\");\\n```\\n\",\r\n \"reference/rules/holds.md\": \"# Holds\\n\\n**What:** A hold is a moment for reading, not a frozen frame: the main element stays put while something small keeps moving (a slow push, a drift, a blinking caret, a breathing glow).\\n**When:** Every time the viewer has to read: questions, stats, the end card.\\n**Timing:** Reading time: about 0.3 s per word plus 0.5 s. Nothing fully still for more than 0.8 s; the end card may hold up to 1.5 s with a slow drift. The exception is a stretch declared with `tl.rest(from, to)` (stage version 2), which is dead still on purpose and exempt from the motion metrics; the build fails if anything moves inside it.\\n**Mistake:** An undeclared hold where nothing moves at all: the motion metrics flag it, and it reads as the film stalling.\\n\\n```html\\n<div class=\\\"world\\\"><div class=\\\"bloom\\\"></div><div class=\\\"center\\\"><div class=\\\"stat\\\"><p class=\\\"big\\\">3×</p><p class=\\\"cap\\\">more booked calls</p><i class=\\\"bar\\\"></i></div></div></div>\\n```\\n\\n```css\\n.center{position:absolute;inset:0;display:grid;place-items:center}.bloom{position:absolute;inset:-20cqh -20cqw;background:radial-gradient(circle at 40% 45%,#ffcf5a2e,transparent 35%)}.stat{text-align:center}.big{font-size:24cqmin;font-weight:900;line-height:1}.cap{font-size:5cqmin;color:var(--mut)}.bar{display:block;margin:2cqmin auto 0;width:20cqmin;height:.8cqmin;border-radius:1cqmin;background:var(--p);transform-origin:50% 50%}\\n```\\n\\n```js\\ntl.label(\\\"in\\\", 0)\\n .from(\\\".stat\\\", { opacity: 0, blur: 12, scale: 0.92, duration: 0.6, ease: \\\"expo.out\\\" }, \\\"in\\\")\\n .label(\\\"hold\\\", 0.6)\\n // While the viewer reads: a slow push, a drifting bloom, a breathing accent.\\n .camera({ zoom: 1.06, duration: 2.6, ease: \\\"sine.inOut\\\" }, \\\"hold\\\")\\n .to(\\\".bloom\\\", { x: S.w(6), y: -S.h(4), duration: 2.6, ease: \\\"sine.inOut\\\" }, \\\"hold\\\")\\n .to(\\\".bar\\\", { scaleX: 1.4, duration: 1.3, ease: \\\"sine.inOut\\\" }, \\\"hold\\\")\\n .to(\\\".bar\\\", { scaleX: 1, duration: 1.3, ease: \\\"sine.inOut\\\" }, \\\">\\\");\\n```\\n\",\r\n \"reference/rules/kinetic-type.md\": \"# Kinetic type\\n\\n**What:** Words arrive one at a time, each with its own punch (scale down from big, a little rotation), and the key word lands in the accent colour.\\n**When:** Short claims of 3–7 words that carry the film. Type is the main actor.\\n**Timing:** 0.12–0.2 s between words; each word 0.35–0.5 s (expo.out). Hold the full line 0.6–1 s while the camera keeps moving.\\n**Mistake:** Animating every word differently. Pick one move for the line and vary only the key word.\\n\\n```html\\n<div class=\\\"world\\\"><p class=\\\"kt\\\"><span class=\\\"txt\\\">Turn your product into</span>&#160;<em>video</em></p></div>\\n```\\n\\n```css\\n.kt{position:absolute;inset:0;display:flex;flex-wrap:wrap;align-items:center;align-content:center;justify-content:center;text-align:center;padding:0 10cqw;font-size:9cqmin;font-weight:800;letter-spacing:-.02em;line-height:1.1}.kt em{font-style:normal;color:var(--p)}\\n```\\n\\n```js\\nconst words = [...S.split(\\\".kt .txt\\\", \\\"words\\\"), document.querySelector(\\\".kt em\\\")];\\ntl.label(\\\"say\\\", 0.2)\\n .from(words, { scale: 1.8, opacity: 0, blur: 10, rotate: (i) => (i % 2 ? 4 : -4), duration: 0.45, stagger: 0.16, ease: \\\"expo.out\\\" }, \\\"say\\\")\\n .from(\\\".kt em\\\", { y: S.h(3), duration: 0.5, ease: \\\"back.out\\\" }, \\\"<\\\")\\n .camera({ zoom: 1.12, rotate: -1.5, duration: 3.2, ease: \\\"sine.inOut\\\" }, \\\"say\\\");\\n```\\n\\nS.split keeps styled children: the key word can stay an `<em>` inside the line, and its own `.word` spans animate with the rest.\\n\",\r\n \"reference/rules/line-draw.md\": \"# Line draw\\n\\n**What:** A line grows across the frame at a steady rate and each node pops as the line reaches it.\\n**When:** Timelines, steps, journeys, connections between two things, charts that build.\\n**Timing:** The line linear at 30–50% of the frame per second; each node pops (back.out, 0.35 s) exactly when the line reaches it.\\n**Mistake:** Nodes popping on their own schedule: the line must be the cause.\\n\\n```html\\n<div class=\\\"world\\\"><div class=\\\"track\\\"><i class=\\\"line\\\"></i><span class=\\\"node\\\" style=\\\"left:0%\\\"><b>Sign up</b></span><span class=\\\"node\\\" style=\\\"left:33%\\\"><b>Onboard</b></span><span class=\\\"node\\\" style=\\\"left:66%\\\"><b>Launch</b></span><span class=\\\"node\\\" style=\\\"left:100%\\\"><b>Grow</b></span></div></div>\\n```\\n\\n```css\\n.track{position:absolute;left:14cqw;right:14cqw;top:48cqh;height:.8cqmin}.line{position:absolute;inset:0;background:var(--p);transform-origin:0 50%;border-radius:1cqmin}.node{position:absolute;top:-1.6cqmin;width:4cqmin;height:4cqmin;margin-left:-2cqmin;border-radius:50%;background:var(--ink)}.node b{position:absolute;top:6cqmin;left:-10cqmin;width:24cqmin;text-align:center;font-size:3.6cqmin;font-weight:600;white-space:nowrap}\\n```\\n\\n```js\\nconst fracs = [0, 0.33, 0.66, 1], drawS = 2;\\ntl.label(\\\"draw\\\", 0.3)\\n .from(\\\".line\\\", { scaleX: 0, duration: drawS, ease: \\\"linear\\\" }, \\\"draw\\\");\\ndocument.querySelectorAll(\\\".node\\\").forEach((n, i) => tl.from(n, { scale: 0, duration: 0.35, ease: \\\"back.out\\\" }, \\\"draw+=\\\" + fracs[i] * drawS));\\ntl.camera({ x: -S.w(3), zoom: 1.06, duration: 2.8, ease: \\\"sine.inOut\\\" }, \\\"draw\\\");\\n```\\n\",\r\n \"reference/rules/mask-reveal.md\": \"# Mask reveal\\n\\n**What:** Each line rises into view from behind an invisible edge: the line sits in an `overflow: hidden` slot and moves up from below it.\\n**When:** Headlines and statements. It feels precise and editorial, and it never shows half-faded text.\\n**Timing:** 0.6–0.8 s per line (expo.out), lines 0.1–0.15 s apart. Exit the same way, upwards, 0.4 s (power3.in).\\n**Mistake:** Forgetting the slot: without `overflow: hidden` on the wrapper the text just slides in from below.\\n\\n```html\\n<div class=\\\"world\\\"><h1 class=\\\"head\\\"><span class=\\\"slot\\\"><span class=\\\"ln\\\">Every lead.</span></span><span class=\\\"slot\\\"><span class=\\\"ln\\\">One <em>inbox</em>.</span></span></h1></div>\\n```\\n\\n```css\\n.head{position:absolute;left:14cqw;top:30cqh;font-size:11cqmin;line-height:1.05;font-weight:800;letter-spacing:-.02em}.slot{display:block;overflow:hidden;padding-bottom:.08em}.ln{display:block}.head em{font-style:normal;color:var(--p)}\\n```\\n\\n```js\\ntl.label(\\\"in\\\", 0.2)\\n .from(\\\".ln\\\", { y: \\\"110%\\\", duration: 0.75, stagger: 0.12, ease: \\\"expo.out\\\" }, \\\"in\\\")\\n .camera({ zoom: 1.08, duration: 3, ease: \\\"sine.inOut\\\" }, \\\"in\\\")\\n .label(\\\"out\\\", 2.4)\\n .to(\\\".ln\\\", { y: \\\"-110%\\\", duration: 0.4, stagger: 0.06, ease: \\\"power3.in\\\" }, \\\"out\\\");\\n```\\n\\nA wipe variant without the slot: animate `clipPath` from `\\\"inset(0 0 100% 0)\\\"` to `\\\"inset(0 0 0% 0)\\\"`.\\n\",\r\n \"reference/rules/match-cut.md\": \"# Match cut\\n\\n**What:** A hard cut between two framings of the same element: the wide shot of a wordmark cuts to an extreme close-up of its last letter.\\n**When:** Endings, reveals, and any change you want to feel decisive. The cut is hidden because the eye stays on the same thing.\\n**Timing:** Hold the wide 0.6–1 s, cut on a frame (tl.set), then drift the close-up slowly (scale +3–6% over 1–1.5 s).\\n**Mistake:** Cutting to a different element: without the shared shape it's just a jump.\\n\\n```html\\n<div class=\\\"world\\\"><div class=\\\"center\\\"><p class=\\\"mark\\\">orbit<i class=\\\"dot\\\"></i></p></div></div>\\n```\\n\\n```css\\n.center{position:absolute;inset:0;display:grid;place-items:center}.mark{font-size:20cqmin;font-weight:800;letter-spacing:-.04em;line-height:1}.mark .dot{display:inline-block;width:.16em;height:.16em;border-radius:50%;background:var(--p);margin-left:.04em}\\n```\\n\\n```js\\n// Centre the camera on the detail we cut to: the world's transform-origin goes on the dot.\\nconst d = document.querySelector(\\\".dot\\\").getBoundingClientRect();\\nconst ox = ((d.left + d.width / 2) / S.width) * 100, oy = ((d.top + d.height / 2) / S.height) * 100;\\ntl.set(\\\".world\\\", { transformOrigin: ox + \\\"% \\\" + oy + \\\"%\\\" }, 0)\\n .label(\\\"wide\\\", 0)\\n .from(\\\".mark\\\", { opacity: 0, blur: 12, duration: 0.5, ease: \\\"expo.out\\\" }, \\\"wide\\\")\\n .camera({ zoom: 1.05, duration: 1.4, ease: \\\"sine.out\\\" }, \\\"wide\\\")\\n .label(\\\"cut\\\", 1.5)\\n .camera({ zoom: 4, duration: 0.01 }, \\\"cut\\\")\\n .camera({ zoom: 4.3, duration: 1.4, ease: \\\"sine.inOut\\\" }, \\\">\\\");\\n```\\n\\nThe camera zooms about the `.world` transform-origin: measure the detail (getBoundingClientRect, before any camera move) and put the origin on it.\\n\",\r\n \"reference/rules/morph.md\": \"# Morph\\n\\n**What:** One shape becomes another: a dot grows into a card, a card into a window, by changing size and corner radius rather than swapping elements.\\n**When:** Whenever the next beat's container can grow out of something already on screen.\\n**Timing:** 0.5–0.8 s (power3.inOut or expo.inOut). Content inside fades or rises in during the last 40%.\\n**Mistake:** Cross-fading two different elements and calling it a morph: the outline must change continuously.\\n\\n```html\\n<div class=\\\"world\\\"><div class=\\\"center\\\"><div class=\\\"shape\\\"><p class=\\\"copy\\\">Your whole funnel, in one window</p></div></div></div>\\n```\\n\\n```css\\n.center{position:absolute;inset:0;display:grid;place-items:center}.shape{width:8cqmin;height:8cqmin;border-radius:50%;background:var(--p);display:grid;place-items:center;overflow:hidden}.copy{color:var(--bg);font-size:4.4cqmin;font-weight:700;text-align:center;padding:0 4cqmin;white-space:nowrap}\\n```\\n\\n```js\\ntl.label(\\\"dot\\\", 0)\\n .from(\\\".shape\\\", { scale: 0, duration: 0.5, ease: \\\"back.out\\\" }, \\\"dot\\\")\\n .label(\\\"grow\\\", 0.9)\\n .to(\\\".shape\\\", { width: S.min(110), height: S.min(34), borderRadius: \\\"4cqmin\\\", duration: 0.7, ease: \\\"expo.inOut\\\" }, \\\"grow\\\")\\n .from(\\\".copy\\\", { opacity: 0, y: S.h(3), duration: 0.4, ease: \\\"expo.out\\\" }, \\\"grow+=0.45\\\")\\n .camera({ zoom: 1.08, tiltX: 6, duration: 3, ease: \\\"sine.inOut\\\" }, \\\"dot\\\");\\n```\\n\\nCentre with a grid wrapper, never with a CSS `transform` on the animated element: the timeline writes `transform` and would replace it.\\n\\n## With `tl.morph` (version 2)\\n\\nOn a `data-stage-version=\\\"2\\\"` sequence, `tl.morph(from, to, …)` does the whole move for you: the source's box, corner radius and colour become the target's, the target's content rises in over the last 40 %, and the two hand over on the last frame with no swap. Write the target where it should end up, and morph the source into it.\\n\\n```html v2\\n<div class=\\\"world\\\"><div class=\\\"shape\\\"></div><div class=\\\"win\\\"><p class=\\\"copy\\\">Your whole funnel, in one window</p></div></div>\\n```\\n\\n```css v2\\n.shape{position:absolute;left:46cqw;top:44cqh;width:8cqmin;height:8cqmin;border-radius:50%;background:var(--p)}.win{position:absolute;left:14cqw;top:36cqh;width:72cqw;height:28cqh;border-radius:4cqmin;background:var(--p);display:grid;place-items:center}.copy{margin:0;color:var(--bg);font-size:4.4cqmin;font-weight:700;text-align:center}\\n```\\n\\n```js v2\\ntl.label(\\\"dot\\\", 0)\\n .from(\\\".shape\\\", { scale: 0, duration: 0.5, ease: \\\"back.out\\\" }, \\\"dot\\\")\\n .morph(\\\".shape\\\", \\\".win\\\", { duration: 0.8, ease: \\\"expo.inOut\\\" }, 0.9)\\n .camera({ zoom: 1.08, tiltX: 6, duration: 3, ease: \\\"sine.inOut\\\" }, \\\"dot\\\");\\n```\\n\\nThe source must be `position: absolute` or `relative`, and the target stays hidden until the hand-over, so don't animate its opacity. Chain morphs (a dot into a card, the card into a pill) and each one is a carried subject.\\n\",\r\n \"reference/rules/motion-blur.md\": \"# Motion blur\\n\\n**What:** Fast moves carry blur, peaking where the speed peaks. With expo.out, blur from the from-value to 0 does this on its own.\\n**When:** Every fly-in, fly-out, whip and fast scale. Also the blur-in on new elements (a soft 8–14 px focus-in).\\n**Timing:** Blur 10–20 px on the fast part. Entrances: from blur, 0.4–0.6 s expo.out. Exits: to blur, 0.3–0.4 s expo.in.\\n**Mistake:** Blur on slow moves (it looks out of focus, not fast), or blur that stays after the element has stopped.\\n\\n```html\\n<div class=\\\"world\\\"><div class=\\\"center\\\"><div class=\\\"card\\\">Fast</div></div></div>\\n```\\n\\n```css\\n.center{position:absolute;inset:0;display:grid;place-items:center}.card{padding:4cqmin 8cqmin;border-radius:2cqmin;background:var(--p);color:var(--bg);font-size:9cqmin;font-weight:900}\\n```\\n\\n```js\\ntl.label(\\\"in\\\", 0.2)\\n .fromTo(\\\".card\\\", { x: -S.w(70), blur: 18 }, { x: 0, blur: 0, duration: 0.55, ease: \\\"expo.out\\\" }, \\\"in\\\")\\n .label(\\\"out\\\", 2)\\n .to(\\\".card\\\", { x: S.w(70), blur: 18, duration: 0.4, ease: \\\"expo.in\\\" }, \\\"out\\\")\\n .camera({ zoom: 1.05, duration: 2.6, ease: \\\"sine.inOut\\\" }, \\\"in\\\");\\n```\\n\",\r\n \"reference/rules/orbit.md\": \"# Orbit\\n\\n**What:** Satellites circle a hub: the ring rotates and each satellite counter-rotates so its label stays upright.\\n**When:** Ecosystems, integrations, \\\"everything connects to one place\\\", a playful idle loop around a logo.\\n**Timing:** A slow turn (60–120° over 4–6 s, linear or sine.inOut) while other beats happen; satellites pop in with a stagger.\\n**Mistake:** Spinning fast or a full turn per second: it becomes a loading spinner.\\n\\n```html\\n<div class=\\\"world\\\"><div class=\\\"hub\\\">Hub</div><div class=\\\"ring\\\"><span>SMS</span><span>Email</span><span>Calls</span><span>Chat</span><span>Ads</span><span>Pay</span></div></div>\\n```\\n\\n```css\\n.hub{position:absolute;left:50%;top:50%;width:22cqmin;height:22cqmin;margin:-11cqmin 0 0 -11cqmin;border-radius:50%;background:var(--p);color:var(--bg);display:grid;place-items:center;font-size:5cqmin;font-weight:800}.ring{position:absolute;left:50%;top:50%;width:0;height:0}.ring span{position:absolute;left:-6cqmin;top:-6cqmin;width:12cqmin;height:12cqmin;border-radius:50%;background:var(--card);border:.3cqmin solid var(--line);display:grid;place-items:center;font-size:2.8cqmin;font-weight:600}\\n```\\n\\n```js\\nconst sats = document.querySelectorAll(\\\".ring span\\\");\\nconst R = S.min(32);\\ntl.set(sats, { x: (i) => R * Math.cos((i / sats.length) * 2 * Math.PI), y: (i) => R * Math.sin((i / sats.length) * 2 * Math.PI) }, 0)\\n .label(\\\"in\\\", 0)\\n .from(\\\".hub\\\", { scale: 0, duration: 0.5, ease: \\\"back.out\\\" }, \\\"in\\\")\\n .from(sats, { scale: 0, opacity: 0, duration: 0.45, stagger: 0.08, ease: \\\"back.out\\\" }, \\\"in+=0.3\\\")\\n .to(\\\".ring\\\", { rotate: 90, duration: 5, ease: \\\"linear\\\" }, \\\"in\\\")\\n .to(sats, { rotate: -90, duration: 5, ease: \\\"linear\\\" }, \\\"in\\\")\\n .camera({ zoom: 1.08, tiltX: 10, duration: 5, ease: \\\"sine.inOut\\\" }, \\\"in\\\");\\n```\\n\\nPlace satellites with x/y on the satellites themselves, never with a CSS transform (the timeline owns `transform`).\\n\",\r\n \"reference/rules/overshoot.md\": \"# Overshoot\\n\\n**What:** The element passes its resting value and settles back (back.out), or springs (elastic.out) for a playful brief.\\n**When:** Anything that lands: chips, badges, icons, a logo arriving. Use back.out by default; elastic only when the brief is playful.\\n**Timing:** 0.45–0.7 s. Stagger 0.06–0.1 s between siblings so they land one after another.\\n**Mistake:** Overshoot on text paragraphs or on every element at once: it wobbles the whole frame.\\n\\n```html\\n<div class=\\\"world\\\"><div class=\\\"row\\\"><span class=\\\"chip\\\">Leads</span><span class=\\\"chip\\\">Calls</span><span class=\\\"chip\\\">Reviews</span></div></div>\\n```\\n\\n```css\\n.row{position:absolute;inset:0;display:flex;align-items:center;justify-content:center;gap:3cqmin}.chip{padding:2cqmin 4cqmin;border-radius:10cqmin;background:var(--card);border:.3cqmin solid var(--p);font-size:4.4cqmin;font-weight:600}\\n```\\n\\n```js\\ntl.label(\\\"land\\\", 0.3)\\n .from(\\\".chip\\\", { scale: 0, opacity: 0, duration: 0.6, stagger: 0.09, ease: \\\"back.out\\\" }, \\\"land\\\")\\n .to(\\\".chip\\\", { y: -S.h(3), duration: 0.5, stagger: 0.09, ease: \\\"elastic.out\\\" }, \\\"land+=1.1\\\")\\n .camera({ zoom: 1.06, duration: 2.4, ease: \\\"sine.inOut\\\" }, \\\"land\\\");\\n```\\n\",\r\n \"reference/rules/parallax.md\": \"# Parallax\\n\\n**What:** Layers at different depths move at different speeds: far things move little, near things move a lot.\\n**When:** Any pan or drift over a scene with depth (backgrounds, blooms, UI panels as texture, foreground particles).\\n**Timing:** The whole beat (2–4 s), sine.inOut. Rough ratio far : mid : near = 1 : 2.5 : 5.\\n**Mistake:** Every layer at the same speed (that's a pan), or near layers so fast they pull focus from the subject.\\n\\n```html\\n<div class=\\\"world\\\"><div class=\\\"far\\\"></div><div class=\\\"mid\\\"><i></i><i></i><i></i></div><p class=\\\"title\\\">One platform</p><div class=\\\"near\\\"><i></i><i></i></div></div>\\n```\\n\\n```css\\n.far{position:absolute;inset:-10cqh -10cqw;background:radial-gradient(circle at 30% 40%,#5ad1ff33,transparent 40%),radial-gradient(circle at 75% 60%,#ff5d8f33,transparent 45%)}.mid i{position:absolute;width:22cqmin;height:14cqmin;border-radius:1.6cqmin;background:var(--card);opacity:.7}.mid i:nth-child(1){left:8cqw;top:14cqh}.mid i:nth-child(2){left:66cqw;top:10cqh}.mid i:nth-child(3){left:58cqw;top:64cqh}.title{position:absolute;inset:0;display:grid;place-items:center;font-size:10cqmin;font-weight:800}.near i{position:absolute;width:4cqmin;height:4cqmin;border-radius:50%;background:var(--p);filter:blur(.6cqmin)}.near i:nth-child(1){left:20cqw;top:76cqh}.near i:nth-child(2){left:84cqw;top:30cqh}\\n```\\n\\n```js\\ntl.label(\\\"drift\\\", 0)\\n .to(\\\".far\\\", { x: -S.w(3), duration: 4, ease: \\\"sine.inOut\\\" }, \\\"drift\\\")\\n .to(\\\".mid\\\", { x: -S.w(8), duration: 4, ease: \\\"sine.inOut\\\" }, \\\"drift\\\")\\n .to(\\\".title\\\", { x: -S.w(4), duration: 4, ease: \\\"sine.inOut\\\" }, \\\"drift\\\")\\n .to(\\\".near\\\", { x: -S.w(18), duration: 4, ease: \\\"sine.inOut\\\" }, \\\"drift\\\");\\n```\\n\\n## With `data-depth` (version 2)\\n\\nOn a `data-stage-version=\\\"2\\\"` sequence, put `data-depth` on the layers instead of animating each one: under any camera pan a layer at depth `d` moves 1 / (1 + d) as far as depth 0, so far layers (larger depth) move less and foreground layers (negative depth, down to −0.5) move more. One camera move drives them all.\\n\\n```html v2\\n<div class=\\\"world\\\"><div class=\\\"far\\\" data-depth=\\\"4\\\"></div><div class=\\\"mid\\\" data-depth=\\\"1.5\\\"><i></i><i></i><i></i></div><p class=\\\"title\\\">One platform</p><div class=\\\"near\\\" data-depth=\\\"-0.5\\\"><i></i><i></i></div></div>\\n```\\n\\n```css v2\\n.far{position:absolute;inset:-10cqh -10cqw;background:radial-gradient(circle at 30% 40%,#5ad1ff33,transparent 40%),radial-gradient(circle at 75% 60%,#ff5d8f33,transparent 45%)}.mid i{position:absolute;width:22cqmin;height:14cqmin;border-radius:1.6cqmin;background:var(--card);opacity:.7}.mid i:nth-child(1){left:8cqw;top:14cqh}.mid i:nth-child(2){left:66cqw;top:10cqh}.mid i:nth-child(3){left:58cqw;top:64cqh}.title{position:absolute;inset:0;display:grid;place-items:center;margin:0;font-size:10cqmin;font-weight:800}.near i{position:absolute;width:4cqmin;height:4cqmin;border-radius:50%;background:var(--p)}.near i:nth-child(1){left:20cqw;top:76cqh}.near i:nth-child(2){left:84cqw;top:30cqh}\\n```\\n\\n```js v2\\ntl.label(\\\"drift\\\", 0)\\n .camera({ x: -S.w(8), duration: 4, ease: \\\"sine.inOut\\\" }, \\\"drift\\\")\\n .handheld({ amount: S.min(0.3) }, 0, 4);\\n```\\n\",\r\n \"reference/rules/path-follow.md\": \"# Path follow\\n\\n**What:** An element travels along a curve: separate eases on x and y give an arc without any path data.\\n**When:** Anything that goes from A to B with intent: a message flying to a phone, a lead travelling to a calendar, a cursor.\\n**Timing:** 1–1.6 s. x with power2.inOut; y up with sine.out for the first half, down with sine.in for the second.\\n**Mistake:** Straight-line travel at constant speed: it reads as mechanical. Arc it and ease it.\\n\\n```html\\n<div class=\\\"world\\\"><div class=\\\"a\\\">Lead</div><div class=\\\"b\\\">Booked</div><i class=\\\"ball\\\"></i></div>\\n```\\n\\n```css\\n.a,.b{position:absolute;top:66cqh;padding:1.6cqmin 3cqmin;border-radius:1.4cqmin;background:var(--card);font-size:4cqmin;font-weight:600}.a{left:8cqw}.b{left:76cqw}.ball{position:absolute;left:16cqw;top:60cqh;width:4cqmin;height:4cqmin;border-radius:50%;background:var(--p);box-shadow:0 0 3cqmin var(--p)}\\n```\\n\\n```js\\ntl.label(\\\"go\\\", 0.4)\\n .from(\\\".a,.b\\\", { opacity: 0, y: S.h(4), duration: 0.5, stagger: 0.15, ease: \\\"expo.out\\\" }, 0)\\n .to(\\\".ball\\\", { x: S.w(64), duration: 1.4, ease: \\\"power2.inOut\\\" }, \\\"go\\\")\\n .to(\\\".ball\\\", { y: -S.h(40), duration: 0.7, ease: \\\"sine.out\\\" }, \\\"go\\\")\\n .to(\\\".ball\\\", { y: 0, duration: 0.7, ease: \\\"sine.in\\\" }, \\\"go+=0.7\\\")\\n .to(\\\".b\\\", { scale: 1.12, backgroundColor: \\\"#ffcf5a\\\", color: \\\"#10142a\\\", duration: 0.35, ease: \\\"back.out\\\" }, \\\"go+=1.35\\\")\\n .camera({ x: -S.w(4), duration: 2.6, ease: \\\"sine.inOut\\\" }, \\\"go\\\");\\n```\\n\\nFor more than one bump, use keyframes: `y: [0, -S.h(30), -S.h(10), 0]` (the duration is shared evenly).\\n\",\r\n \"reference/rules/pop-in.md\": \"# Pop-in\\n\\n**What:** A small element appears from nothing with a quick overshoot and a focus-in.\\n**When:** Chips, badges, labels, counters on top of a picture, notification bubbles.\\n**Timing:** 0.35–0.5 s (back.out), from scale 0 and blur 8 px. Pop chips 0.1–0.2 s apart.\\n**Mistake:** Popping big things (cards, headlines): use a rise or a morph for those.\\n\\n```html\\n<div class=\\\"world\\\"><div class=\\\"center\\\"><div class=\\\"tile\\\"><span class=\\\"chip\\\">5 components</span></div></div></div>\\n```\\n\\n```css\\n.center{position:absolute;inset:0;display:grid;place-items:center}.tile{position:relative;width:56cqmin;height:34cqmin;border-radius:2cqmin;background:linear-gradient(135deg,#5ad1ff55,#ff5d8f55)}.chip{position:absolute;right:-4cqmin;top:-3cqmin;padding:1.4cqmin 2.6cqmin;border-radius:6cqmin;background:var(--p);color:var(--bg);font-size:3.6cqmin;font-weight:700}\\n```\\n\\n```js\\ntl.label(\\\"tile\\\", 0)\\n .from(\\\".tile\\\", { opacity: 0, y: S.h(5), blur: 10, duration: 0.6, ease: \\\"expo.out\\\" }, \\\"tile\\\")\\n .from(\\\".chip\\\", { scale: 0, blur: 8, duration: 0.45, ease: \\\"back.out\\\" }, \\\"tile+=0.5\\\")\\n .camera({ zoom: 1.08, tiltY: -6, duration: 2.6, ease: \\\"sine.inOut\\\" }, \\\"tile\\\");\\n```\\n\",\r\n \"reference/rules/retype-in-place.md\": \"# Retype in place\\n\\n**What:** A typed line deletes back to the caret and a new line types on the same baseline: the same object, a new idea.\\n**When:** A question that turns into its answer, a before/after line, \\\"hand-animating\\\" → \\\"your launch video?\\\".\\n**Timing:** Delete at twice the typing speed (0.03–0.04 s per character). 0.2 s pause with the caret blinking, then type.\\n**Mistake:** Clearing the line with a fade or a cut: the carry-over is the caret staying put.\\n\\n```html\\n<div class=\\\"world\\\"><p class=\\\"line\\\"><span class=\\\"a\\\">Still hand-animating</span><span class=\\\"b\\\">your launch video?</span><i class=\\\"caret\\\"></i></p></div>\\n```\\n\\n```css\\n.line{position:absolute;left:12cqw;top:44cqh;font-size:7cqmin;font-weight:600;white-space:nowrap}.b{position:absolute;left:0;top:0;color:var(--p)}.caret{position:absolute;left:0;top:.12em;width:.07em;height:1.05em;background:var(--ink)}\\n```\\n\\n```js\\nconst a = S.split(\\\".a\\\", \\\"chars\\\"), b = S.split(\\\".b\\\", \\\"chars\\\");\\nconst end = (c) => c.offsetLeft + c.offsetWidth;\\ntl.set([...a, ...b], { opacity: 0 }, 0).label(\\\"a\\\", 0.3).label(\\\"del\\\", 2.2).label(\\\"b\\\", 3.1);\\na.forEach((c, i) => tl.set(c, { opacity: 1 }, \\\"a+=\\\" + i * 0.07).set(\\\".caret\\\", { x: end(c) }, \\\"<\\\"));\\na.slice().reverse().forEach((c, i) => tl.set(c, { opacity: 0 }, \\\"del+=\\\" + i * 0.035).set(\\\".caret\\\", { x: c.offsetLeft }, \\\"<\\\"));\\nb.forEach((c, i) => tl.set(c, { opacity: 1 }, \\\"b+=\\\" + i * 0.07).set(\\\".caret\\\", { x: end(c) }, \\\"<\\\"));\\ntl.camera({ zoom: 1.06, x: -S.w(2), duration: 4.6, ease: \\\"sine.inOut\\\" }, 0);\\n```\\n\",\r\n \"reference/rules/scale-to-fill.md\": \"# Scale to fill\\n\\n**What:** A word or wordmark grows until it bleeds past both edges of the frame, often with a colour sweep.\\n**When:** The brand moment near the end, or the single word that sums up the film.\\n**Timing:** 0.8–1.2 s (expo.inOut), then a slow drift. Aim for about 1.2–1.4× the frame width.\\n**Mistake:** Scaling the whole world with the camera: the background grows too and nothing feels big.\\n\\n```html\\n<div class=\\\"world\\\"><div class=\\\"center\\\"><p class=\\\"big\\\">launch</p></div></div>\\n```\\n\\n```css\\n.center{position:absolute;inset:0;display:grid;place-items:center}.big{font-size:12cqmin;font-weight:900;letter-spacing:-.04em;white-space:nowrap;color:var(--mut)}\\n```\\n\\n```js\\ntl.label(\\\"set\\\", 0)\\n .from(\\\".big\\\", { opacity: 0, blur: 10, duration: 0.5, ease: \\\"expo.out\\\" }, \\\"set\\\")\\n .label(\\\"fill\\\", 1)\\n // Measure the word so the scale fits any frame size.\\n .to(\\\".big\\\", { scale: (i, el) => (S.width * 1.3) / el.offsetWidth, color: \\\"#ffcf5a\\\", duration: 1.1, ease: \\\"expo.inOut\\\" }, \\\"fill\\\")\\n .camera({ x: -S.w(2), duration: 3, ease: \\\"sine.inOut\\\" }, \\\"set\\\");\\n```\\n\",\r\n \"reference/rules/split-and-scatter.md\": \"# Split and scatter\\n\\n**What:** A word breaks into its letters, which fly apart (or gather from scattered positions into the word).\\n**When:** Breaking something apart (problems, chaos, \\\"ten tools\\\"), or assembling a name out of noise. Reverse it to assemble.\\n**Timing:** Scatter 0.6–0.9 s (expo.out) with a small stagger (0.02 s). Use S.rand(seed) for the directions so every render matches.\\n**Mistake:** Math.random for the directions: the frames change every render, and the lint refuses it.\\n\\n```html\\n<div class=\\\"world\\\"><div class=\\\"center\\\"><p class=\\\"w\\\">scattered</p></div></div>\\n```\\n\\n```css\\n.center{position:absolute;inset:0;display:grid;place-items:center}.w{font-size:14cqmin;font-weight:900;letter-spacing:-.02em;white-space:nowrap}\\n```\\n\\n```js\\nconst chars = S.split(\\\".w\\\", \\\"chars\\\");\\nconst r = S.rand(7);\\nconst dx = chars.map(() => (r() - 0.5) * 90), dy = chars.map(() => (r() - 0.5) * 80), rot = chars.map(() => (r() - 0.5) * 160);\\ntl.label(\\\"whole\\\", 0)\\n .from(chars, { opacity: 0, y: S.h(6), duration: 0.5, stagger: 0.03, ease: \\\"expo.out\\\" }, \\\"whole\\\")\\n .label(\\\"burst\\\", 1.4)\\n .to(chars, { x: (i) => S.w(dx[i]), y: (i) => S.h(dy[i]), rotate: (i) => rot[i], opacity: 0.5, blur: 3, duration: 0.8, stagger: 0.02, ease: \\\"expo.out\\\" }, \\\"burst\\\")\\n .camera({ zoom: 0.92, duration: 2.6, ease: \\\"sine.inOut\\\" }, \\\"whole\\\");\\n```\\n\\nTo assemble instead: `tl.from(chars, { x: …, y: …, rotate: …, opacity: 0 })` with the same seeded values.\\n\",\r\n \"reference/rules/staggered-entrance.md\": \"# Staggered entrance\\n\\n**What:** Siblings enter one after another with the same move, so a list reads in order.\\n**When:** Lists, option cards, feature rows, grids. Stagger from \\\"first\\\" for reading order, from \\\"center\\\" for symmetric groups.\\n**Timing:** Each item 0.4–0.6 s (expo.out). Stagger 0.06–0.12 s. The whole group in under 1 s.\\n**Mistake:** Stagger so long the last item arrives after the viewer has stopped reading, or no stagger at all (the group lands as a block).\\n\\n```html\\n<div class=\\\"world\\\"><ul class=\\\"list\\\"><li>Capture every lead</li><li>Reply in seconds</li><li>Book the call</li><li>Get the review</li></ul></div>\\n```\\n\\n```css\\n.list{position:absolute;left:24cqw;top:24cqh;list-style:none;padding:0;display:flex;flex-direction:column;gap:2.6cqmin;font-size:5.4cqmin;font-weight:600}.list li{padding:1.6cqmin 3cqmin;border-left:.8cqmin solid var(--p);background:var(--card)}\\n```\\n\\n```js\\ntl.label(\\\"list\\\", 0.2)\\n .from(\\\".list li\\\", { x: -S.w(4), opacity: 0, blur: 8, duration: 0.5, stagger: 0.08, ease: \\\"expo.out\\\" }, \\\"list\\\")\\n .camera({ y: -S.h(3), zoom: 1.05, duration: 2.6, ease: \\\"sine.inOut\\\" }, \\\"list\\\");\\n```\\n\\nFrom the centre: `stagger: { each: 0.06, from: \\\"center\\\" }`.\\n\",\r\n \"reference/rules/swap-in-place.md\": \"# Swap in place\\n\\n**What:** One word in a fixed sentence changes: the old word slides up and out, the new one slides up into the same slot.\\n**When:** Lists of benefits, audiences or use cases (\\\"Make it faster / simpler / yours\\\"). The sentence is the carry-over.\\n**Timing:** Each swap 0.45 s (expo.inOut); hold each word 0.6–0.9 s.\\n**Mistake:** Re-typing or re-animating the whole sentence for each word.\\n\\n```html\\n<div class=\\\"world\\\"><div class=\\\"center\\\"><p class=\\\"line\\\">Make it <span class=\\\"slot\\\"><b>faster</b><b>simpler</b><b>yours</b></span></p></div></div>\\n```\\n\\n```css\\n.center{position:absolute;inset:0;display:grid;place-items:center}.line{font-size:9cqmin;font-weight:800;white-space:nowrap}.slot{display:inline-grid;overflow:hidden;vertical-align:bottom;color:var(--p);padding-bottom:.1em}.slot b{grid-area:1/1}\\n```\\n\\n```js\\nconst words = document.querySelectorAll(\\\".slot b\\\");\\ntl.set([...words].slice(1), { y: \\\"110%\\\" }, 0)\\n .from(\\\".line\\\", { opacity: 0, blur: 10, duration: 0.5, ease: \\\"expo.out\\\" }, 0);\\nfor (let i = 1; i < words.length; i++) {\\n const at = \\\"swap\\\" + i;\\n tl.label(at, 0.4 + i * 1.1)\\n .to(words[i - 1], { y: \\\"-110%\\\", duration: 0.45, ease: \\\"expo.inOut\\\" }, at)\\n .to(words[i], { y: \\\"0%\\\", duration: 0.45, ease: \\\"expo.inOut\\\" }, at);\\n}\\ntl.camera({ zoom: 1.06, duration: 3.4, ease: \\\"sine.inOut\\\" }, 0);\\n```\\n\\nThe slot is as wide as the widest word (all words share one grid cell).\\n\",\r\n \"reference/rules/typewriter.md\": \"# Typewriter\\n\\n**What:** Characters appear one by one with a caret that follows the last typed character.\\n**When:** Prompts, questions, a search box, an opening line. It makes the viewer read at the typing speed.\\n**Timing:** About 10–14 characters per second (0.07–0.1 s each). Caret blink 0.4 s on, 0.4 s off while holding.\\n**Mistake:** Typing long sentences (over about 30 characters): the viewer waits. Type the first half and reveal the rest.\\n\\n```html\\n<div class=\\\"world\\\"><p class=\\\"line\\\"><span class=\\\"txt\\\">What are we shipping today?</span><i class=\\\"caret\\\"></i></p></div>\\n```\\n\\n```css\\n.line{position:absolute;left:12cqw;top:44cqh;font-size:7cqmin;font-weight:600;white-space:nowrap}.caret{position:absolute;left:0;top:.12em;width:.07em;height:1.05em;background:var(--p)}\\n```\\n\\n```js\\nconst chars = S.split(\\\".txt\\\", \\\"chars\\\");\\nconst each = 0.08;\\ntl.label(\\\"type\\\", 0.4).set(chars, { opacity: 0 }, 0);\\n// Each character and the caret change on the same frame; the caret sits at the end of the\\n// last typed character (measured after fonts have loaded, so the widths are the real ones).\\nchars.forEach((c, i) => tl.set(c, { opacity: 1 }, \\\"type+=\\\" + i * each).set(\\\".caret\\\", { x: c.offsetLeft + c.offsetWidth }, \\\"<\\\"));\\ntl.to(\\\".caret\\\", { opacity: 0, duration: 0.01 }, \\\">+=0.4\\\")\\n .to(\\\".caret\\\", { opacity: 1, duration: 0.01 }, \\\"+=0.4\\\")\\n .camera({ x: -S.w(3), zoom: 1.04, duration: 3.6, ease: \\\"sine.inOut\\\" }, \\\"type\\\");\\n```\\n\\nTyping is opacity, not `display`: S.split gives each character an inline display of its own.\\n\",\r\n \"reference/rules/whip-pan.md\": \"# Whip pan\\n\\n**What:** A very fast camera move sideways with heavy blur, from one panel to the next.\\n**When:** Lists of equals (feature after feature), energetic promos, a hard change of subject that should still feel like one move.\\n**Timing:** 0.3–0.4 s (power4.inOut). Blur peaks at the middle: 12–20 px up in half the time, down in the other half.\\n**Mistake:** A whip at normal speed: without the speed and blur it is just a slow slide.\\n\\n```html\\n<div class=\\\"world\\\"><div class=\\\"strip\\\"><section class=\\\"p1\\\">Capture</section><section class=\\\"p2\\\">Convert</section></div></div>\\n```\\n\\n```css\\n.strip{position:absolute;inset:0}.strip section{position:absolute;top:0;width:100cqw;height:100cqh;display:grid;place-items:center;font-size:14cqmin;font-weight:800}.p1{left:0}.p2{left:100cqw;color:var(--p)}\\n```\\n\\n```js\\ntl.label(\\\"hold\\\", 0)\\n .camera({ zoom: 1.04, duration: 1.2, ease: \\\"sine.out\\\" }, \\\"hold\\\")\\n .label(\\\"whip\\\", 1.3)\\n .camera({ x: -S.w(100), zoom: 1, duration: 0.36, ease: \\\"power4.inOut\\\" }, \\\"whip\\\")\\n .to(\\\".world\\\", { blur: 18, duration: 0.18, ease: \\\"power2.in\\\" }, \\\"whip\\\")\\n .to(\\\".world\\\", { blur: 0, duration: 0.18, ease: \\\"power2.out\\\" }, \\\">\\\")\\n .camera({ x: -S.w(101), zoom: 1.05, duration: 1.6, ease: \\\"sine.inOut\\\" }, \\\"whip+=0.36\\\");\\n```\\n\\nAdd an SFX whoosh centred on the whip (see the media-sound skill).\\n\\n## With `tl.whip` (version 2)\\n\\n`tl.whip(target, { duration })` snaps the camera to the target with an expo in-out and lands with it centred, so you don't compute the pan by hand.\\n\\n```html v2\\n<div class=\\\"world\\\"><div class=\\\"strip\\\"><section class=\\\"p1\\\">Capture</section><section class=\\\"p2\\\">Convert</section></div></div>\\n```\\n\\n```css v2\\n.strip{position:absolute;inset:0}.strip section{position:absolute;top:0;width:100cqw;height:100cqh;display:grid;place-items:center;font-size:14cqmin;font-weight:800}.p1{left:0}.p2{left:100cqw;color:var(--p)}\\n```\\n\\n```js v2\\ntl.label(\\\"hold\\\", 0)\\n .camera({ zoom: 1.04, duration: 1.2, ease: \\\"sine.out\\\" }, \\\"hold\\\")\\n .label(\\\"whip\\\", 1.3)\\n .whip(\\\".p2\\\", { duration: 0.36 }, \\\"whip\\\")\\n .shake(\\\"whip+=0.36\\\", { amp: S.min(0.8), decay: 0.3 })\\n .to(\\\".world\\\", { blur: 18, duration: 0.18, ease: \\\"power2.in\\\" }, \\\"whip\\\")\\n .to(\\\".world\\\", { blur: 0, duration: 0.18, ease: \\\"power2.out\\\" }, \\\">\\\")\\n .camera({ zoom: 1.05, duration: 1.6, ease: \\\"sine.inOut\\\" }, \\\"whip+=0.36\\\");\\n```\\n\",\r\n \"reference/rules/wipe.md\": \"# Wipe\\n\\n**What:** A panel is revealed by a moving edge (clip-path inset), with an accent bar riding the edge.\\n**When:** Before/after, revealing a screenshot or picture, replacing one colour field with another.\\n**Timing:** 0.6–0.9 s (power3.inOut). The bar moves with exactly the same timing and ease as the clip.\\n**Mistake:** A wipe with no visible edge: it looks like a rendering glitch. Give it a bar or a hard colour edge.\\n\\n```html\\n<div class=\\\"world\\\"><div class=\\\"panel\\\"><p>After</p></div><i class=\\\"edge\\\"></i></div>\\n```\\n\\n```css\\n.panel{position:absolute;left:15cqw;top:20cqh;width:70cqw;height:60cqh;border-radius:2cqmin;background:var(--p);color:var(--bg);display:grid;place-items:center;font-size:12cqmin;font-weight:900}.edge{position:absolute;left:15cqw;top:16cqh;width:1cqmin;height:68cqh;background:var(--ink)}\\n```\\n\\n```js\\ntl.label(\\\"wipe\\\", 0.4)\\n .fromTo(\\\".panel\\\", { clipPath: \\\"inset(0% 100% 0% 0%)\\\" }, { clipPath: \\\"inset(0% 0% 0% 0%)\\\", duration: 0.8, ease: \\\"power3.inOut\\\" }, \\\"wipe\\\")\\n .fromTo(\\\".edge\\\", { x: 0 }, { x: S.w(70), duration: 0.8, ease: \\\"power3.inOut\\\" }, \\\"wipe\\\")\\n .to(\\\".edge\\\", { opacity: 0, duration: 0.2 }, \\\">\\\")\\n .camera({ zoom: 1.05, duration: 2.6, ease: \\\"sine.inOut\\\" }, 0);\\n```\\n\",\r\n \"reference/rules/zoom-out-reveal.md\": \"# Zoom-out reveal\\n\\n**What:** The camera starts close on a few items and pulls back hard to show they are part of something much bigger; a counter changes with it.\\n**When:** Scale claims: 5 → 500 templates, one store → a thousand stores, one message → a whole inbox.\\n**Timing:** Hold close 0.8–1.2 s, pull back 0.8–1 s (expo.inOut), counter swap at the midpoint.\\n**Mistake:** Zooming out to a wall of identical empty boxes: the wall needs variety (colour, content) to read as \\\"many\\\".\\n\\n```html\\n<div class=\\\"world\\\"><div class=\\\"center\\\"><div class=\\\"wall\\\"></div></div></div><div class=\\\"hud\\\"><span class=\\\"chip\\\"><span class=\\\"slot\\\"><b>5</b><b>500</b></span> templates</span></div>\\n```\\n\\n```css\\n.center{position:absolute;inset:0;display:grid;place-items:center}.wall{display:grid;grid-template-columns:repeat(9,9cqmin);gap:1.4cqmin}.wall i{height:6cqmin;border-radius:.8cqmin}.hud{position:absolute;left:0;right:0;bottom:10cqh;display:flex;justify-content:center}.chip{padding:1.4cqmin 3cqmin;border-radius:6cqmin;background:var(--ink);color:var(--bg);font-size:4cqmin;font-weight:700}.slot{display:inline-grid;overflow:hidden;vertical-align:bottom}.slot b{grid-area:1/1}\\n```\\n\\n```js\\n// The wall is built here: 9 × 7 tiles in seeded colours.\\nconst wall = document.querySelector(\\\".wall\\\");\\nconst r = S.rand(11), hues = [\\\"#ffcf5a\\\", \\\"#5ad1ff\\\", \\\"#ff5d8f\\\", \\\"#8f7bff\\\", \\\"#3ddc97\\\"];\\nfor (let k = 0; k < 63; k++) {\\n const t = document.createElement(\\\"i\\\");\\n t.style.background = hues[Math.floor(r() * hues.length)];\\n t.style.opacity = String(0.35 + r() * 0.65);\\n wall.appendChild(t);\\n}\\ntl.camera({ zoom: 4.2, duration: 0.01 }, 0)\\n .set(\\\".slot b:last-child\\\", { y: \\\"110%\\\" }, 0)\\n .label(\\\"close\\\", 0.02)\\n .camera({ zoom: 3.8, duration: 1.1, ease: \\\"sine.inOut\\\" }, \\\"close\\\")\\n .from(\\\".chip\\\", { scale: 0, blur: 8, duration: 0.45, ease: \\\"back.out\\\" }, \\\"close+=0.3\\\")\\n .label(\\\"reveal\\\", 1.2)\\n .camera({ zoom: 1, duration: 0.9, ease: \\\"expo.inOut\\\" }, \\\"reveal\\\")\\n .to(\\\".slot b:first-child\\\", { y: \\\"-110%\\\", duration: 0.4, ease: \\\"expo.inOut\\\" }, \\\"reveal+=0.35\\\")\\n .to(\\\".slot b:last-child\\\", { y: \\\"0%\\\", duration: 0.4, ease: \\\"expo.inOut\\\" }, \\\"reveal+=0.35\\\")\\n .camera({ zoom: 1.04, duration: 1.4, ease: \\\"sine.inOut\\\" }, \\\">\\\");\\n```\\n\\nThe `.hud` sits outside `.world`, so the camera doesn't move it: use that for chips and captions that must stay put.\\n\",\r\n \"reference/rules/zoom-through.md\": \"# Zoom-through\\n\\n**What:** The camera pushes into an element until it fills the frame, and the next beat is already inside it.\\n**When:** Going deeper: from a product window into a feature, from a map into a city, from a card into its story.\\n**Timing:** Push 0.5–0.7 s with expo.in (it accelerates into the cut). The reset happens on a frame where only the element's colour fills the screen.\\n**Mistake:** Zooming into an element whose colour differs from the next beat's background: the reset flashes.\\n\\n```html\\n<div class=\\\"world\\\"><div class=\\\"card\\\"><span>Automations</span></div><div class=\\\"next\\\"><p>Every reply, <em>on time</em></p></div></div>\\n```\\n\\n```css\\n.card{position:absolute;left:50%;top:50%;width:30cqmin;height:18cqmin;margin:-9cqmin 0 0 -15cqmin;border-radius:2cqmin;background:var(--card);display:grid;place-items:center;font-size:3.6cqmin;font-weight:600}.next{position:absolute;inset:0;display:grid;place-items:center;background:var(--card);opacity:0}.next p{font-size:9cqmin;font-weight:800}.next em{font-style:normal;color:var(--p)}\\n```\\n\\n```js\\ntl.label(\\\"show\\\", 0)\\n .from(\\\".card\\\", { scale: 0.6, opacity: 0, blur: 10, duration: 0.5, ease: \\\"expo.out\\\" }, \\\"show\\\")\\n .label(\\\"push\\\", 1.2)\\n .camera({ zoom: 7, duration: 0.6, ease: \\\"expo.in\\\" }, \\\"push\\\")\\n .label(\\\"inside\\\", 1.8)\\n .set(\\\".next\\\", { opacity: 1 }, \\\"inside\\\")\\n .camera({ zoom: 1, duration: 0.01 }, \\\"inside\\\")\\n .from(\\\".next p\\\", { scale: 1.3, blur: 14, opacity: 0, duration: 0.6, ease: \\\"expo.out\\\" }, \\\"inside\\\")\\n .camera({ zoom: 1.06, duration: 2, ease: \\\"sine.inOut\\\" }, \\\"inside+=0.05\\\");\\n```\\n\\n## With `tl.zoomThrough` (version 2)\\n\\n`tl.zoomThrough(target, { into, … })` pushes the camera into the target until it fills the frame, brings the inner scene in over the last 12 % of the push, and resets the camera on the frame where only the target's surface is visible. The inner scene (`into`) sits outside `.world`, and the target and `into` should share a colour so the hand-over doesn't flash.\\n\\n```html v2\\n<div class=\\\"world\\\"><div class=\\\"card\\\"><span>Automations</span></div></div><div class=\\\"next\\\"><p>Every reply, <em>on time</em></p></div>\\n```\\n\\n```css v2\\n.card{position:absolute;left:50%;top:50%;width:30cqmin;height:18cqmin;margin:-9cqmin 0 0 -15cqmin;border-radius:2cqmin;background:var(--card);display:grid;place-items:center;font-size:3.6cqmin;font-weight:600}.next{position:absolute;inset:0;display:grid;place-items:center;background:var(--card)}.next p{margin:0;font-size:9cqmin;font-weight:800}.next em{font-style:normal;color:var(--p)}\\n```\\n\\n```js v2\\ntl.label(\\\"show\\\", 0)\\n .from(\\\".card\\\", { scale: 0.6, opacity: 0, blur: 10, duration: 0.5, ease: \\\"expo.out\\\" }, \\\"show\\\")\\n .zoomThrough(\\\".card\\\", { fit: \\\"cover\\\", into: \\\".next\\\", duration: 0.7, ease: \\\"expo.in\\\" }, 1.2)\\n .label(\\\"inside\\\", 1.9)\\n .from(\\\".next p\\\", { scale: 1.3, blur: 14, opacity: 0, duration: 0.6, ease: \\\"expo.out\\\" }, \\\"inside\\\")\\n .camera({ zoom: 1.06, duration: 2, ease: \\\"sine.inOut\\\" }, \\\"inside+=0.05\\\");\\n```\\n\\nFor a dive that goes much deeper (tens or hundreds of times), key the zoom in screen space with `tl.camera({ anchor, at: [50, 50], zoom })` instead, and give each level `data-lod` so it fades in as it grows.\\n\",\r\n \"reference/timing.md\": \"# Timing\\n\\nMotion graphics are fast. Most beats are shorter than you think, and the viewer reads faster than you fear, but only a few words at a time.\\n\\n## Beat lengths\\n\\n| What | Length |\\n|---|---|\\n| A typical beat (one idea, one move) | 0.3–1.2 s |\\n| Entrance of an element | 0.4–0.7 s (expo.out) |\\n| Exit | 0.3–0.4 s (expo.in) |\\n| Move between places, morph, hand-off | 0.5–0.9 s (power3.inOut / expo.inOut) |\\n| Pop (chips, badges) | 0.35–0.5 s (back.out) |\\n| Hidden cut (blur-through, whip) | 0.25–0.4 s |\\n| Stagger between siblings | 0.06–0.12 s (the whole group lands in under 1 s) |\\n| Typing | 10–14 characters per second |\\n| Camera move | as long as the beat, or longer, overlapping the next beat |\\n| An act | 8–25 s, with a beat every 0.6–2 s |\\n\\nAs a rough guide, a 13–14 s launch film has about 14 beats.\\n\\n## Reading budget\\n\\n- **Up to 7 words on screen at once.** Split anything longer across beats, or retype in place.\\n- A line needs about **0.3 s per word plus 0.5 s** on screen, fully legible, before it changes. A 4-word line: about 1.7 s from landing to leaving.\\n- Count from when the last word has landed, not from when the first one starts.\\n- Numbers and names need more time than words: add 0.3 s for each.\\n\\n## Holds\\n\\nA hold is reading time, not a frozen frame.\\n\\n- Nothing on screen may be fully still for more than **0.8 s**. During a hold, keep one small thing moving: a slow camera push (3–6%), a drift, a blinking caret, a breathing accent (holds).\\n- The end card may hold up to **1.5 s**, with a slow drift.\\n- If a hold feels long, the beat is too long. Shorten it, don't add decoration.\\n\\n## Motion targets\\n\\nThe film is checked against these, measured on small greyscale frames at 10 fps (frame-to-frame change on a 0–255 scale):\\n\\n| Metric | Target for motion-graphics briefs |\\n|---|---|\\n| Motion density (share of steps with visible change) | ≥ 0.85 |\\n| Median change per step | ≥ 1.5 |\\n| Holds with no visible change | none over 0.8 s (end card: up to 1.5 s) |\\n| Jumps (one big change with calm frames on both sides) | only where you meant a cut |\\n\\nFor reference, a well-made 13 s launch film measures density 0.99 with a median change of 3.5. Slide-like films with dissolves measure about 0.6 and 0.8, with several dead holds.\\n\\nDocumentary-paced films have lower targets (density ≥ 0.6, median change ≥ 0.6, no hold over 2 s), but the camera still keeps moving, except in rests you declare with `tl.rest`.\\n\\n## Fixing what the checks find\\n\\n`compose_sequence` measures each act after placing it (its `motion` line and `problems`), and `preview_film` measures the whole film (`motion`, `motionProblems`, and the `stillest` acts or scenes). Each finding has a fix:\\n\\n| Finding | What it means | Fix |\\n|---|---|---|\\n| \\\"nothing moves 4.2–5.6 s\\\" | A hold: every element has landed and the camera has stopped | Keep a slow camera move running across the beat (a push of 4–8%, a drift, a tilt). Give the landed elements a breathing move (holds). Or start the next beat sooner. Look at the act's script at that time: usually a `tl.camera` ends early, or the next label starts too late |\\n| \\\"62% of the time something visibly moves\\\" | Too much of the act is still between moves | Overlap the beats: start each move before the last one ends (`\\\"<+=0.3\\\"`, `\\\"label-=0.2\\\"`). Make the camera moves long and overlapping, so one is always running |\\n| \\\"motion is faint: median change 0.8\\\" | Things move, but too little to see at a glance | Move further (5–20% of the frame, not 1–2%). Scale more (0.8 → 1, not 0.97 → 1). Move several elements at once, or a big one. A slow drift alone reads as still |\\n| \\\"a hard jump at 3.0 s\\\" | One frame changes a lot with calm on both sides: an unhidden cut or a `tl.set` that swaps a big element | Hide it: blur-through, zoom-through, whip-pan or a match cut. Or animate into the change instead of setting it |\\n| \\\"9 words on screen at 2.4 s\\\" | Too much to read at once | Split the line across two beats, or clear the last beat's words before the new ones arrive |\\n| \\\"beat … adds 8 words but lasts 1.0 s\\\" | Not enough time to read them | About 3.5 words a second plus half a second. Lengthen the beat, or cut words |\\n\\nFix with `compose_sequence` and `replace: \\\"<act id>\\\"`, then check the new `motion` line.\\n\\n## Rhythm\\n\\n- Vary beat lengths: three quick beats (0.4–0.6 s) then a longer one (1.2 s) reads as rhythm. Equal beats read as a metronome.\\n- Put the strongest move on the beat where the music hits (when there's a beat grid, use `S.beat(n)` for positions).\\n- Let the act end on motion that hands off (a drift, a push), not on a full stop. The only exception is the film's final frame.\\n\"\r\n }\r\n },\r\n {\r\n \"name\": \"reformat\",\r\n \"description\": \"Turn the open project into another aspect ratio or platform version - 16:9 to 9:16 (Reels, TikTok, Shorts), 1:1 or 4:5, or a short social cut-down - re-laying out footage, titles and captions for the new frame. Use when the user wants the same video in a different shape.\",\r\n \"instructions\": \"# Reformat\\n\\nLoad `edit-video` first if you haven't. A reformat changes the frame, then re-lays out every clip for it. It edits this project; if the user wants to keep the original shape too, tell them to duplicate the project first (one sentence, then go on if they already said so).\\n\\n## 1. Plan per clip\\n\\nRead the state once and decide, for every clip:\\n\\n| Clip | In the new frame |\\n| --- | --- |\\n| Footage with a subject (video/image) | Fill the frame and keep the subject centred (formulas below) |\\n| Footage where the whole frame matters (screen recordings, slides) | Keep `scale: 1`, and put a blurred copy behind it: the same clip on a lower track, scaled to fill, `add_effect` blur (0.04) and darken (brightness −0.2), volume 0 |\\n| Full-frame HTML scenes | Rewrite the template for the new size (root `width`/`height`, stack side-by-side layouts vertically, recheck font sizes): `clip/set-property` `{ template, widthPx: null, heightPx: null }` |\\n| Sized HTML (lower thirds, badges) and text | Move `x`/`y` into the new safe zone (`reference/craft.md` → Safe zones); break long lines |\\n| Captions | `fontSizeFrac` × oldH/newH × 1.2 (16:9 → 9:16: 0.055 → about 0.04), `y` → 0.7, 3 words per clip if you can rebuild them (`captions`) |\\n\\n### Reframing footage\\n\\n`scale: 1` fits the source inside the frame. To fill a `W×H` frame from a `sw×sh` source:\\n\\n- `fill = max((W/H) / (sw/sh), (sw/sh) / (W/H))`: 16:9 → 9:16 is 3.16, → 1:1 is 1.78, → 4:5 is 2.22.\\n- Subject at horizontal fraction `f` of the source (0.5 = centre): `x = 0.5 + (0.5 − f) × fill`. Keep `|x − 0.5| ≤ (fill − 1) / 2`, or the frame shows an empty edge. Same for `y`.\\n- You can't see the footage: assume the subject is centred unless the user says otherwise, and say so.\\n\\n## 2. Apply\\n\\n1. `project/set-settings` with the new `width` and `height` (same fps).\\n2. One `apply_commands` batch with every `clip/set-property` from the plan.\\n3. Animated clips: keyframes on `x`/`y`/`scale` hold values from the old layout. Re-apply preset animations with `animate_clip` (they start from the new transform); scale hand-made keyframes yourself (`set_keyframes`).\\n\\n## 3. Cut-downs\\n\\nFor a shorter social version: keep the strongest 15–30 s with the hook first, remove or `trim_clip` the rest, `close_gaps`, keep the end card. Social versions open on the most visual moment, not the title.\\n\\n## 4. Check and hand off\\n\\nFrom the state: every text and caption inside the new safe zones, nothing left at old positions, no footage showing an edge. Tell the user the new shape and length, what you assumed about subject position, and to scrub through the footage shots to check the framing.\"\r\n },\r\n {\r\n \"name\": \"screen-tutorial\",\r\n \"description\": \"Turn a screen recording in the editor into a tutorial or product walkthrough - chapters per step, zooms onto the action, sped-up waiting, held frames, blurred private data, step callouts. Use when the project holds a screen recording and the user wants a how-to, demo or walkthrough.\",\r\n \"instructions\": \"# Screen tutorial\\n\\nLoad `edit-video` first if you haven't. A tutorial guides the eye: zoom to where the action is, cut or speed up dead time, hold a frame when something needs reading, and hide anything private.\\n\\nYou can't see the recording. The step times and the places on screen come from the user (\\\"at 0:12 they click Save, top right\\\"), from markers or a recording log already in the project, or from a transcript of the narration. If you have none of these, ask the user for the steps (time, what happens, where on screen) before editing.\\n\\n## 1. Structure\\n\\n- One `marker/add` per step: `durationUs` for the step's span, `data: { \\\"kind\\\": \\\"step\\\", \\\"n\\\": 1 }`, a 2–5 word `label`. Markers become chapters.\\n- The recording sits on its own track at `scale: 1`, in the recording's shape.\\n\\n## 2. Pace\\n\\n- **Dead time** (loading, long typing, waiting): `clip/split` around it and `clip/set-speed` the middle 2–4× with `ripple: true`. Over 4× looks broken: cut instead.\\n- **Hold a frame** when the viewer must read something: `clip/freeze` for 1–2 s.\\n- **Cut** mistakes and repeats (split, remove, `close_gaps`).\\n- Aim for one step every 4–10 s of finished video.\\n\\n## 3. Zoom to the action\\n\\n`set_keyframes` on the recording's `scale`, `x`, `y`. For a target box centred at `(cx, cy)` (fractions of the frame) at zoom `z`:\\n\\n- `scale = z`, `x = 0.5 + (0.5 − cx) × z`, `y = 0.5 + (0.5 − cy) × z`\\n- clamp so the frame stays filled: `x` and `y` within `[1 − z/2, z/2]`\\n\\nZoom in over 0.4–0.6 s with easeInOut, stay while the step happens, zoom out before jumping to a distant part of the screen. `z` 1.5–2.5. Zoom when the target is small, not on every click.\\n\\n## 4. Privacy and focus\\n\\n- **Blur private data** the user points out (emails, keys, names): `effect/add` `pixelate` (or `blur` 0.03) with `region: { x, y, width, height }` as fractions of the clip's frame; it follows your zooms. If it moves, keyframe it with `effect/set-region-keyframe`. If you were told about private data but not where it is, ask.\\n- **Spotlight** a control: `effect/add` `colorAdjust` `{ brightness: -0.45 }` with `region: { …, shape: \\\"ellipse\\\", feather: 0.5, invert: true }` for the step's span.\\n\\n## 5. Callouts and titles\\n\\n- Step labels: a small pill or lower third near (not over) the target, 2–5 words, one on screen at a time (`reference/html-recipes.md`).\\n- **An intro (≤ 3–4 s) and an end card** (the result or next step). When `compose_sequence` is available, make each one a short act in the product's look (`get_brand` if it's a product), using one technique from the `motion` skill:\\n - the title types in (typewriter), then the three things you'll learn swap in place (swap-in-place);\\n - or the product's own UI card pushes in (zoom-through) and hands off to the recording's first frame.\\n\\n Use `plan_film` (format `explainer`, the brand's look) before it, so the act gets the colours and fonts. Without `compose_sequence`, use an html title (`reference/html-recipes.md` in `edit-video`).\\n- Captions from the narration if there are words (`captions`), clear of the callouts.\\n\\n## 6. Check and hand off\\n\\nFrom the state: zoom values within the clamp, every step's label on screen during its action, sped-up parts ≤ 4×, nothing private left without a region. Reply with the steps one line each (so the user can paste them into a help article) and ask them to check every frame where private data appears.\"\r\n },\r\n {\r\n \"name\": \"sequence-core\",\r\n \"description\": \"The contract for compose_sequence acts — the stage kit API (tl, S, camera), positions, units, params, the hand-off between acts, and the rules every sequence must pass. Load it before writing your first act; load motion for the techniques.\",\r\n \"instructions\": \"# Sequence core\\n\\nA motion-graphics film is a few **acts**. Each act is one `compose_sequence` call: one continuous animation on **one timeline**, 8–25 s long, with the beats marked as labels. Elements stay on screen and change from beat to beat, and the camera keeps moving. This file is the contract for what you write. The `motion` skill has the techniques.\\n\\n## What you write\\n\\n`compose_sequence` takes four things, and wraps them in the film's stage (colours, fonts, the root):\\n\\n- **`markup`**: the act's elements.\\n - Everything the camera sees goes inside `<div class=\\\"world\\\">…</div>`.\\n - Put a `<div class=\\\"hud\\\">…</div>` beside it, not inside, for anything that must stay put while the camera moves (a caption bar, a small logo, a progress rail).\\n- **`css`**: the look of those elements.\\n- **`script`**: the body of `stage.build((tl, S) => { … })`. Write the body only, not the call.\\n- **`durationSeconds`**: the act's length. The timeline must end within it. End it on a slow drift (a camera move, a breathing accent), so the act is still moving at its last frame.\\n\\n`params` (optional): `{{name}}` in the markup becomes an editable field in the editor, filled from `params`. Use it for the words people will want to change. Give each param a meaningful name (`headline`, `step2`, `cta`), not `a`, `b`, `c`. Values are inserted as text.\\n\\nThe stage gives you these CSS variables, from `plan_film`:\\n\\n| Variable | What it is |\\n|---|---|\\n| `--bg` | Background |\\n| `--ink` | Text |\\n| `--mut` | Muted text |\\n| `--p` | The accent |\\n| `--s` | The second accent |\\n| `--card` | A card surface |\\n| `--line` | Hairlines |\\n| `--onp` | Text on the accent |\\n\\nIt also gives you two fonts: the body font on the root, and the display font on `.k-display`. Any other font family you name in the CSS is added to the project for you. Static helpers `k-fill` (fill the frame) and `k-cover` (a cover-fit picture) work. The animated `k-*` classes (`k-rise`, `k-pop`, `k-cam`, …) belong to `design_scene` and are refused here: in a sequence every move is on the timeline.\\n\\n## The timeline: `tl`\\n\\nAll times are in **seconds**. Every method returns `tl`, so calls chain.\\n\\n| Call | Does |\\n|---|---|\\n| `tl.to(targets, vars, position)` | Animate from the current state to `vars` |\\n| `tl.from(targets, vars, position)` | Animate from `vars` to the current (CSS) state: the usual entrance |\\n| `tl.fromTo(targets, fromVars, toVars, position)` | Both ends explicit |\\n| `tl.set(targets, vars, position)` | Change instantly at that time (a word swap, a caret jump, a hard cut) |\\n| `tl.label(name, position)` | Mark a beat. Define a label before you use it in a position |\\n| `tl.camera(vars, position)` | Move the camera (the `.world` layer). See below |\\n\\n**Targets**: a CSS selector (`\\\".card\\\"`), an element, or a list of elements (`S.split(…)` returns one).\\n\\n**Vars**:\\n\\n- **Transform:** `x`, `y` (always through `S.w` / `S.h` / `S.min`), `scale`, `scaleX`, `scaleY`, `rotate`, `rotateX`, `rotateY` (degrees).\\n- **Look:** `opacity`, `blur` (px, mapped to `filter: blur()`), `color`, `backgroundColor`, `borderRadius`, `width`, `height`, `clipPath` (strings like `\\\"inset(0 100% 0 0)\\\"`), CSS variables (`\\\"--n\\\": 10`).\\n- **Keyframes:** an array (`x: [0, S.w(10), 0]`).\\n- **Timing:** `duration` (default 0.6), `delay`, `ease`, and `stagger` (seconds between targets, or `{ each, from: \\\"first\\\" | \\\"center\\\" | \\\"last\\\" }`).\\n\\nAny value can be a function `(i, el) => value`.\\n\\n**Eases**:\\n\\n| Ease | Use it for |\\n|---|---|\\n| `\\\"expo.out\\\"` | Arrivals |\\n| `\\\"expo.in\\\"` | Exits |\\n| `\\\"power3.inOut\\\"` / `\\\"expo.inOut\\\"` | Moving between places |\\n| `\\\"back.out\\\"` | Pops |\\n| `\\\"elastic.out\\\"` | Wobbly landings |\\n| `\\\"sine.inOut\\\"` | Drift |\\n| `\\\"linear\\\"` | Constant rates |\\n| `\\\"steps(n)\\\"` | Ticking, typing |\\n\\n`power1`–`power4`, `circ`, `bounce` and anime.js names (`\\\"out(3)\\\"`) also work.\\n\\n**Positions**:\\n\\n| Position | Means |\\n|---|---|\\n| `2.4` | At that second |\\n| `\\\"hook\\\"` | At the label |\\n| `\\\"hook+=0.3\\\"` / `\\\"hook-=0.2\\\"` | Offset from the label |\\n| `\\\"<\\\"` | With the previous step |\\n| `\\\">\\\"` | After the previous step |\\n| `\\\"<+=0.2\\\"` | Shortly after the previous step starts |\\n| `\\\"+=0.5\\\"` | 0.5 s after everything so far |\\n| *(none)* | At the end of everything so far |\\n\\n## The camera\\n\\n`tl.camera({ x, y, zoom, rotate, tiltX, tiltY, duration, ease }, position)` moves `.world`:\\n\\n- `zoom` is scale;\\n- `tiltX` / `tiltY` rotate it in depth, with perspective already set;\\n- the default is a 1.2 s `power2.inOut`-style ease.\\n\\nZoom pivots on `.world`'s transform origin (the centre). To push into a corner, first `tl.set(\\\".world\\\", { transformOrigin: \\\"70% 30%\\\" }, t)`. A camera move with `duration: 0.01` is an instant reset, which hides a cut.\\n\\n## Continuous shot (version 2)\\n\\nPut `data-stage-version=\\\"2\\\"` on the stage root to turn on the operated camera and the carry verbs. Without it everything above behaves as before. With it, `tl.camera` keys a camera track that follow, handheld and shake add to (zoom is interpolated in log space, so 1 to 100 reads 10 halfway), and these calls exist. Times are seconds, sizes are stage pixels (use `S.*`), and every call returns `tl`.\\n\\n**Carry one subject from beat to beat**\\n\\n| Call | Does |\\n|---|---|\\n| `tl.morph(from, to, { duration, ease, content, radius }, position)` | The `from` element's box, radius and colour become `to`'s, and `to`'s content rises in over the last 40 %. The two hand over on the last frame with no swap. `from` must be `position: absolute` or `relative` |\\n| `tl.iris(target, { from, duration, ease, edge }, position)` | A circular clip opens from the centre of `from` until `target` covers the stage |\\n| `tl.zoomThrough(target, { fit: \\\"cover\\\" \\\\| \\\"contain\\\", into, duration, ease }, position)` | Pushes the camera into `target` until it fills the frame. With `into` (an element outside `.world`), that scene takes over on the last frame and the camera resets |\\n| `tl.hop(target, { to, height, duration, squash }, position)` | An arc to another element's slot, landing with a squash |\\n| `tl.gather(targets, { into, arc, stagger, spin, duration }, position)` | Many elements fly on fanned arcs into one box |\\n| `tl.impact(target, position, { squash, duration })` | A squash and spring-back, and a hit |\\n| `tl.press(target, time)` | A pressed-button reaction, and a click hit |\\n| `tl.pointer({ path, clicks, look })` | A cursor that passes through every `{ t, x, y }` knot on a smooth path and dips on each click time. `S.pointerPath(log)` builds the path from a recorded click log |\\n\\nElements these verbs drive keep their own `transform` and `opacity` for the verb's duration: don't animate the same properties with `tl.to`. Ease `\\\"spring(zeta, omega)\\\"` (defaults 0.55, 14) is a damped spring that has settled by the end of the tween.\\n\\n**Operate the camera**\\n\\n| Call | Does |\\n|---|---|\\n| `tl.camera({ anchor, at: [x%, y%], zoom, duration, ease }, position)` | Keys the zoom in screen space: the anchor element stays at that screen position, at any zoom. Use it for every zoom above 50× |\\n| `tl.follow(target, { lead, from, to }, position)` | The camera keeps `target` near the centre, leading it by `lead` seconds (default 0.1) |\\n| `tl.whip(target, { duration }, position)` | A fast expo snap that lands with `target` centred |\\n| `tl.handheld({ amount }, from, to)` | A slow float, exactly zero outside its range and inside declared rests |\\n| `tl.shake(position, { amp, decay, seed })` | A deterministic hit shake in screen space (the same size at any zoom), recorded as a hit |\\n\\n`data-depth=\\\"d\\\"` on a child of `.world` gives parallax from the one camera (a layer moves 1 / (1 + d) as far as depth 0). `data-lod=\\\"minPx,maxPx\\\"` fades an element in by its size on screen and drops it when it is off-frame.\\n\\n**Say what the shot does**\\n\\n| Call | Does |\\n|---|---|\\n| `tl.hit(name, position, { pan, kind })` | Marks a sound moment (`kind`: `impact`, `click`, `whoosh`, `land`, `shake`). Shakes, presses, impacts, pointer clicks and carry landings add their own |\\n| `tl.rest(from, to, { allow })` | Declares a dead-still stretch. Only elements in `allow` may move, and the camera layers are zero. The build fails, naming what moved, if anything else does |\\n| `tl.cut(position)` | Marks a deliberate hard cut |\\n\\nThe stage root reports `data-stage-carries`, `data-stage-events`, `data-stage-rests` and `data-stage-cuts` as JSON, for the checks and for placing sound.\\n\\n**Check the shot**\\n\\nMark every beat with `tl.label(name, time)`: labels are the beats the check looks at. Put `data-keep-in-frame=\\\"t0,t1\\\"` on a subject that must stay on screen (add `,whole` to also forbid it being cut off by the frame edge). Then run `check_sequence` (when the tool is offered). For each label it compares the frame 0.2 s before and after and reports the beat as **carried** (something survives and moves, scales, or the camera moves with it), **anchored** (something large stays put) or **bare** (nothing survives), with a hint naming what left and what arrived. It also warns about subjects that leave the frame, beats spaced too evenly, and hard cuts outside a burst of cuts. A bare beat is fixed by carrying an element across it (`tl.morph`, `tl.hop`, `tl.zoomThrough`, `tl.gather`). Beats at `tl.cut` times, bursts of three or more beats within 1.5 s, and the last 1.2 s are not scored. Warnings never block an export.\\n\\n## The stage helpers: `S`\\n\\n| Helper | Gives |\\n|---|---|\\n| `S.w(p)`, `S.h(p)`, `S.min(p)` | `p` percent of the stage's width, height or shorter side, in pixels. Use these for every x, y and size in the script |\\n| `S.width`, `S.height`, `S.portrait` | The stage size, and whether it is portrait |\\n| `S.split(target, \\\"words\\\" \\\\| \\\"chars\\\")` | Wraps each word or character in a span (`.word` / `.char`). Returns them in reading order. Styled children (`<em>`) stay, with their words split inside |\\n| `S.rand(seed)` | A repeatable random function: `const r = S.rand(7); r()` gives 0–1, the same numbers on every render |\\n| `S.rect(el)`, `S.center(el)` | An element's resting box `{ x, y, w, h }` and centre `{ x, y }` in stage pixels (version 2) |\\n| `S.grid({ depth, spacing, dot })` | A faint dot lattice behind the scene that keeps its dot size at any zoom, so camera moves read on an empty ground (version 2) |\\n| `S.pointerPath(log)` | Turns a recorded click log `[{ t, x, y, type }]` into `{ path, clicks }` for `tl.pointer` (version 2) |\\n| `S.beats`, `S.beat(n)` | The music's beat grid in seconds, when the film has one (otherwise `S.beat(n)` is n × 0.5 s) |\\n| `S.downbeats`, `S.downbeat(n)` | The first beat of each bar in seconds, from `data-downbeats` (otherwise `S.downbeat(n)` is n × 2 s). Land a carry on `S.downbeat(n)` to put it on the music |\\n\\n## Rules (compose_sequence checks them)\\n\\nEach rule protects one of two things: every frame must be the same however often and in whatever order it is rendered (preview, scrubbing, export), and the act must fit any frame size.\\n\\n| Rule | Instead |\\n|---|---|\\n| `random`: no `Math.random()` | `S.rand(seed)` |\\n| `clock`: no `Date`, `performance.now()` | Timeline positions |\\n| `async-loop`: no `requestAnimationFrame`, `setTimeout`, `setInterval` | Put every change on the timeline. Frames are sought, never played |\\n| `network`: no `fetch`, `XMLHttpRequest`, `import()` | Pictures are `asset:ID` references |\\n| `infinite`: no `repeat: -1`, `loop: true` | Repeat a finite number of times within the act |\\n| `owned-hooks`: no `__timelines`, `__miraiclipSeek`… | `stage.build` only |\\n| `gsap`: GSAP isn't available | The `tl` API above |\\n| `units`: no `vw`, `vh`, `vmin` | `cqw`, `cqh`, `cqmin` in CSS; `S.w/h/min` in the script |\\n| `fixed`: no `position: fixed` | `position: absolute` inside the stage |\\n| `external-script`: no `<script src>` | Everything inline |\\n| `build`: `stage.build` exactly once | One timeline per act |\\n| `version`: continuous-shot verbs need `data-stage-version=\\\"2\\\"` | Add it to the stage root |\\n| `carry-target`: a verb names an element the markup doesn't have | Add the element or fix the selector |\\n| `zoom-target`: `zoomThrough` target outside `.world` | Move it inside the world |\\n| `duration`, `libs` | compose_sequence sets these for you |\\n\\nWarnings (fix them):\\n\\n- `pixels`: a raw number over 40 for x, y, left, top, width or height won't fit another frame size. Use `S.*`.\\n- `zoom-anchor`: a zoom above 50× without an `anchor` drifts off its subject. Key it in screen space.\\n- `verb-transform`: a CSS `transform` on an element that `morph`, `hop`, `gather`, `impact` or `press` drives is replaced. Put it on a wrapper.\\n- `engine`: calling anime.js directly bypasses the kit's deterministic seeking. Use `tl`.\\n- `big-blur`: a large layer (30% of the frame or more) with `filter: blur()` can render with tile-shaped artifacts while the camera tilts. Make soft glows and colour blooms with a `radial-gradient(circle, <colour>, transparent 65%)` instead. Blur on small elements (an entrance focus-in, motion blur on a word) is fine.\\n\\nThe tool also runs the act once before placing it. A script error, or a timeline longer than the act, comes back as an error with the reason, and nothing is placed.\\n\\n## Things that trip people up\\n\\n- **The timeline owns `transform`** on every element it animates: a CSS `transform` on that element is replaced. Centre with a grid or flex wrapper, `inset` and margins, or put the CSS transform on a wrapper and animate the child.\\n- **`from` animates to the CSS state.** Write the CSS for where the element should *end up*, then `tl.from` its entrance.\\n- **Elements that appear later** start hidden with `tl.set(el, { opacity: 0 }, 0)`, or with a `from` at their beat. CSS `opacity: 0` with a later `to` also works. Nothing should flash at frame 0 by accident.\\n- **Colours** tween to real values (`\\\"#ffcf5a\\\"`, `\\\"rgb(…)\\\"`), not `var(--p)`. Read the plan's hex values.\\n- **Measuring** (`el.offsetWidth`, `getBoundingClientRect()`) is fine: the script runs after fonts load.\\n- **Keep the DOM fixed.** Create elements in the markup, or with `S.split`, before the timeline. Don't add or remove elements during it: use `opacity` and `tl.set`.\\n\\n## Hand-off between acts\\n\\nActs join with a 0.3 s dissolve by default. Design the dissolve away:\\n\\n- The next act's first frame starts where the last act's final frame ended: the same element, in the same place, at the same size.\\n- Then it moves on. The orb that became the logo at the end of act 1 is the logo at the start of act 2.\\n- Use `join: \\\"cut\\\"` when both sides hide the cut themselves (a blur-through or a whip on each side).\\n- Use `join: \\\"dip\\\"` only at a real chapter break.\\n\\n## The loop\\n\\n1. Write the act's beats (motion skill, `reference/timing.md`).\\n2. Compose it.\\n3. Look at the filmstrip compose_sequence returns, and read its problems:\\n - layout at each beat;\\n - a timeline that ends early;\\n - too few beats;\\n - a still camera.\\n4. Fix with `replace: \\\"<act id>\\\"`, which keeps the act's place and length.\\n5. Then compose the next act.\\n\\n`preview_film` shows the whole film when every act is in.\"\r\n },\r\n {\r\n \"name\": \"talking-head\",\r\n \"description\": \"Package a recorded talk, interview, podcast clip or vlog in the editor - punch-ins on every other segment to hide jump cuts, captions, a lower third with the speaker's name, music under speech and an end card. Use when the project has footage of someone speaking and the user wants it tightened or packaged.\",\r\n \"instructions\": \"# Talking-head package\\n\\nLoad `edit-video` first if you haven't; its loop and `reference/craft.md` apply.\\n\\nYou can't hear the audio, so you can't find pauses or fillers yourself. Cutting by speech needs word timings: caption clips already in the project, a transcript the user pastes, or the editor's own transcription or cleanup feature (if it has one, point the user to it and continue with the rest). Never cut blind at guessed times.\\n\\n## 1. Brief\\n\\nFrom the request (ask once if it matters): platform and shape, who is speaking (name and role, for the lower third), keep it verbatim or trim to the best part. Defaults: same shape, captions, lower third, no music unless asked.\\n\\n## 2. Cuts (only with word timings)\\n\\n- With words: keep everything except pauses longer than about 0.45 s (leave 120 ms around words) and filler words (um, uh, erm, hmm). Do it with `clip/split` at both ends of each removed range, `clip/remove` the middle, then `close_gaps`.\\n- To trim to the best part: pick the passage from the transcript (start on a strong sentence, not a greeting) and `trim_clip` to it.\\n- Report the before and after length.\\n\\n## 3. Punch-ins\\n\\nEvery hard cut inside the same shot shows as a jump. On every other segment: `clip/set-property` `{ \\\"transform\\\": { \\\"scale\\\": 1.15, \\\"y\\\": 0.53 } }` (the slight `y` keeps the head in frame). Skip segments shorter than 1 s. Without cuts, punch in on a new segment every 5–8 s only if the user wants movement.\\n\\n## 4. Captions\\n\\nFollow the `captions` skill. For 9:16: 3 words per clip, `y` 0.7.\\n\\n## 5. Lower third, music, end card\\n\\n- **Lower third** at 1–2 s for 5 s: a text-style tool's lower third if the editor has one, else the Lower third recipe in `reference/html-recipes.md` with the name and role. `animate_clip` in from-left, out fade, easing snappy. While it's on, captions move up (`y` 0.7), or put the lower third top-left (`y` 0.12).\\n- **Music** only if asked: under speech at 0.12–0.2, fade in 1 s, out 2 s, trimmed to end with the video.\\n- **End card** (optional): a 3 s HTML scene after the last segment with name and handle or URL; crossDissolve from the last segment if it has headroom.\\n\\n## 6. Check and hand off\\n\\nFrom the state: captions never overlap the lower third, punch-in values the same on alternating segments, no gaps, music ends with the video. Report the length, what was cut (if anything), and ask the user to watch one punch-in to check the framing.\"\r\n }\r\n];\r\n","import { defineChatModel, type ChatModel, type ChatRequest, type ChatResponse, type ToolCall } from \"./types.js\";\r\n\r\nexport type ScriptStep =\r\n | string\r\n | { text?: string; toolCalls?: (Omit<ToolCall, \"id\"> & { id?: string })[] }\r\n | ((request: ChatRequest, index: number) => ScriptStep);\r\n\r\n/**\r\n * A model that answers from a script: for tests, demos and offline\r\n * development. Each `complete` call takes the next step; a string is a final\r\n * text answer, an object can call tools. Requests are kept in `requests`.\r\n */\r\nexport function scriptedChatModel(steps: ScriptStep[], options: { id?: string } = {}): ChatModel & { requests: ChatRequest[] } {\r\n const requests: ChatRequest[] = [];\r\n let i = 0;\r\n const model = defineChatModel({\r\n id: options.id ?? \"scripted\",\r\n label: \"Scripted model\",\r\n async complete(request: ChatRequest, opts: { onText?: (delta: string) => void } = {}): Promise<ChatResponse> {\r\n requests.push(structuredClone(request));\r\n let step: ScriptStep = steps[Math.min(i, steps.length - 1)] ?? \"Done.\";\r\n while (typeof step === \"function\") step = step(request, i);\r\n i++;\r\n const s = typeof step === \"string\" ? { text: step } : step;\r\n if (s.text) opts.onText?.(s.text);\r\n const toolCalls = (s.toolCalls ?? []).map((c, n) => ({ id: c.id ?? `call_${i}_${n}`, name: c.name, arguments: c.arguments }));\r\n return { content: s.text ?? \"\", toolCalls, finish: toolCalls.length ? \"tool_calls\" : \"stop\", usage: { inputTokens: 10, outputTokens: 5 } };\r\n },\r\n });\r\n return Object.assign(model, { requests });\r\n}\r\n"],"mappings":"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","names":["base","json","chunk","FINISH","usage","base","json","readStream","FINISH","usage","json","base","base","mine","target","n","n","name","open","lt","describeAnimation","findCuts","seq","findCuts","describeAnimation","commands","out","usage","request","n"]}