[{"data":1,"prerenderedAt":-1},["ShallowReactive",2],{"project-105444":3},{"id":4,"name":5,"fullName":6,"owner":7,"repo":5,"description":8,"homepage":8,"htmlUrl":8,"language":9,"languages":8,"totalLinesOfCode":8,"stars":10,"forks":11,"watchers":12,"openIssues":13,"contributorsCount":13,"subscribersCount":13,"size":13,"stars1d":14,"stars7d":14,"stars30d":14,"stars90d":13,"forks30d":13,"starsTrendScore":15,"compositeScore":16,"rankGlobal":8,"rankLanguage":8,"license":17,"archived":18,"fork":18,"defaultBranch":19,"hasWiki":20,"hasPages":18,"topics":21,"createdAt":8,"pushedAt":8,"updatedAt":22,"readmeContent":23,"aiSummary":8,"trendingCount":13,"starSnapshotCount":13,"syncStatus":24,"lastSyncTime":25,"discoverSource":26},105444,"OpenCinema","junjamrk\u002FOpenCinema","junjamrk",null,"TypeScript",112,3,54,0,44,132,79.21,"MIT License",false,"main",true,[],"2026-11-01 00:16:55","\u003Cp align=\"center\">\n  \u003Cpicture>\n    \u003Csource media=\"(prefers-color-scheme: dark)\" srcset=\"assets\u002Fbrand\u002FlogoDark.png\">\n    \u003Cimg src=\"assets\u002Fbrand\u002FlogoLight.png\" alt=\"OpenCinema\" width=\"420\">\n  \u003C\u002Fpicture>\n\u003C\u002Fp>\n\n\u003Ch3 align=\"center\">Your agent. Your canvas. Your film.\u003C\u002Fh3>\n\n\u003Cp align=\"center\">\n  A local-first AI video studio. One canvas connects storyboards, assets, 3D previs, generation and editing,\u003Cbr>\n  and every step you take by hand, your agent can continue with the same tools.\n\u003C\u002Fp>\n\n\u003Cp align=\"center\">\n  \u003Ca href=\"LICENSE\">\u003Cimg alt=\"MIT\" src=\"https:\u002F\u002Fimg.shields.io\u002Fbadge\u002Flicense-MIT-4FDBC8?style=flat-square\">\u003C\u002Fa>\n  \u003Cimg alt=\"0.4.0 in development\" src=\"https:\u002F\u002Fimg.shields.io\u002Fbadge\u002Fversion-0.4.0%20in%20development-101315?style=flat-square\">\n  \u003Cimg alt=\"Bun 1.3.14+\" src=\"https:\u002F\u002Fimg.shields.io\u002Fbadge\u002FBun-1.3.14%2B-101315?style=flat-square\">\n  \u003Cimg alt=\"Go 1.26+\" src=\"https:\u002F\u002Fimg.shields.io\u002Fbadge\u002FGo-1.26%2B-101315?style=flat-square\">\n  \u003Cimg alt=\"Node 22+\" src=\"https:\u002F\u002Fimg.shields.io\u002Fbadge\u002FNode.js-22%2B-101315?style=flat-square\">\n  \u003Cimg alt=\"MCP\" src=\"https:\u002F\u002Fimg.shields.io\u002Fbadge\u002FMCP-stdio%20%2B%20Streamable%20HTTP-101315?style=flat-square\">\n\u003C\u002Fp>\n\n\u003Cp align=\"center\">\n  \u003Cstrong>English\u003C\u002Fstrong> ·\n  \u003Ca href=\"readmeZh.md\">中文\u003C\u002Fa> ·\n  \u003Ca href=\"readmeJa.md\">日本語\u003C\u002Fa>\n\u003C\u002Fp>\n\n\u003Cp align=\"center\">\n  \u003Ca href=\"#quick-start\">Quick start\u003C\u002Fa> ·\n  \u003Ca href=\"#features\">Features\u003C\u002Fa> ·\n  \u003Ca href=\"roadmap.md\">Roadmap\u003C\u002Fa> ·\n  \u003Ca href=\"#architecture\">Architecture\u003C\u002Fa> ·\n  \u003Ca href=\"#extend-opencinema\">Extend\u003C\u002Fa> ·\n  \u003Ca href=\"#contributing\">Contribute\u003C\u002Fa> ·\n  \u003Ca href=\"assets\u002Fpromo\u002FopenCinemaPromo.mp4\">Product film\u003C\u002Fa>\n\u003C\u002Fp>\n\n\u003Cp align=\"center\">\n  \u003Cimg src=\"assets\u002Fscreenshots\u002Fcanvas.png\" alt=\"The OpenCinema canvas: text, image, video and audio sources, a Blockout previs and generation nodes wired into one graph\" width=\"960\">\n\u003C\u002Fp>\n\u003Cp align=\"center\">\u003Csub>Real interface screenshot of a sample project. Generated results depend on the models, references and reviews you configure.\u003C\u002Fsub>\u003C\u002Fp>\n\n## Why OpenCinema\n\nGeneration tools answer how to produce a clip. OpenCinema answers how to finish a film.\n\n- **Previs first, then generate, then fix only what needs fixing.** Block out movement and cameras in 3D, review each shot, and replace a single shot without redoing the film.\n- **One canvas for the whole film.** Scripts, references, previs, generation, review and the edit plan are nodes on one canvas, and edges are reference relationships.\n- **Your agent works on the same canvas.** Describe what you want in Codex, Claude Code or any MCP client. The agent reads and writes nodes through public MCP tools, and you can open an editor and change things by hand at any time. Both paths share one command path, one revision and one undo history.\n- **Swap the generation backend.** Cloud models, local services and your existing ComfyUI workflows all plug into the same generation node.\n- **Everything stays local.** The Go server listens on loopback only, a project is a plain folder on your disk, and credentials stay in local settings. Agents only ever see “configured” or “not configured”.\n\n> **Status: 0.4.0, in development, unreleased.** Source builds work on macOS and Linux; Windows archives are not supported yet. The workbench interface is Chinese only for now, so button names below include an English gloss. AI generation uses the provider you configure and may cost money. See [Known limitations](#known-limitations).\n\n## How it works\n\n```mermaid\nflowchart LR\n  A[\"Codex · Claude Code\u003Cbr\u002F>any MCP agent\"] -->|MCP stdio| B[\"plugin bridge\u003Cbr\u002F>build\u002FopenCinema\"]\n  B -->|Streamable HTTP| G[\"Go server\u003Cbr\u002F>authorization · files · tasks · providers\"]\n  G \u003C-->|SSE control channel| W[\"React workbench\u003Cbr\u002F>canvas · node plugins · editors\"]\n  W -->|composer| G -->|host request queue| A\n  G --> P[\"generation services\u003Cbr\u002F>cloud APIs · local services · ComfyUI\"]\n  G --> L[\"local tools\u003Cbr\u002F>FFmpeg · Video Depth Anything\"]\n```\n\nAn agent first calls `getAppState` to pin the project and canvas, then discovers tools from their live schemas and executes them. Side-effecting calls carry an `operationId`, and canvas writes carry `expectedRevision`. Conflicts are rejected, and unknown results are looked up rather than blindly retried.\n\n## Quick start\n\n### 1. Requirements\n\n| Tool | Version | Used for |\n| --- | --- | --- |\n| [Bun](https:\u002F\u002Fbun.sh) | 1.3.14+ | Dependencies, web and plugin builds |\n| [Go](https:\u002F\u002Fgo.dev\u002Fdl\u002F) | 1.26+ | Compiling the local server |\n| [Node.js](https:\u002F\u002Fnodejs.org) | 22+ | Only for Claude Code and Codex integration. A full release package needs only this |\n| FFmpeg | Optional | Audio and video processing. Download it from **Settings → FFmpeg** if you don't have it |\n| Python | 3.10–3.12, optional | Only for the Video Depth node |\n\n### 2. Build and run\n\n```bash\ngit clone https:\u002F\u002Fgithub.com\u002Fjunjamrk\u002FOpenCinema.git\ncd OpenCinema\nbun install --frozen-lockfile\nbun run build\nbun start\n```\n\nOpen \u003Chttp:\u002F\u002F127.0.0.1:47920>. The server listens on localhost only. Settings and credentials are stored in `data\u002F` inside the repository, which Git ignores.\n\n### 3. Open a project\n\nClick **打开工程** (Open Project) and enter the absolute path of an existing folder, such as `\u002FUsers\u002Fme\u002FMovies\u002FmyFilm`. An empty folder works too; the first canvas is created for you. After that, the home screen lists it under recent projects so you can get back in one click.\n\n### 4. Choose how to generate\n\nYou can use both and switch per node at any time.\n\n| | Configure a media provider | Connect an agent |\n| --- | --- | --- |\n| Best for | Calling models directly | Planning, building canvases, batch work |\n| Requires | A provider API key or local service | Claude Code, Codex or another MCP client |\n| Cost | Billed by the provider | Host subscriptions do not cover every video model |\n\n**Configure a media provider**\n\n1. Open **Settings → 媒体服务** (Media Services). The list shows which providers are configured.\n2. Select a provider, enter the API key and save the connection.\n3. On a generation node, click the parameters button and choose a model.\n\n**Connect an agent**\n\n\u003Ctable>\n\u003Ctr>\n\u003Ctd width=\"36%\">\u003Cimg src=\"assets\u002Fscreenshots\u002FsettingsAgent.png\" alt=\"Workbench settings, Host Agent tab: setup commands for Claude Code and Codex\" width=\"100%\">\u003C\u002Ftd>\n\u003Ctd valign=\"top\">\n\nClaude Code, from the root of the built repository:\n\n```bash\nclaude --plugin-dir .\u002Fbuild\u002FopenCinema\n```\n\nCodex:\n\n```bash\ncodex plugin marketplace add . --json\n```\n\n```bash\ncodex plugin add opencinema@openCinemaStandalone --json\n```\n\nThen ask the agent to connect to the OpenCinema workbench. In Claude Code you can use `\u002Fopencinema:opencinema`. Once the top right of the workbench shows it as connected, requests from the bottom input go to the agent.\n\nOther MCP clients can copy a connection config from **Settings → 宿主 Agent** (Host Agent). See [Claude setup](docs\u002FclaudeSetup.md), in Chinese, for details.\n\u003C\u002Ftd>\n\u003C\u002Ftr>\n\u003C\u002Ftable>\n\n> Keep the workbench page open. Canvas and node tools run inside the page, and the agent operates the canvas through it.\n\n### 5. Make your first shot\n\n1. Drag an image onto the canvas.\n2. Press `Tab` and choose **视频生成** (Video Generation).\n3. Drag a connection from the right port of the image node to the left port of the generation node.\n4. Select the generation node, click the parameters button above it, and pick a model, duration and aspect ratio.\n5. Describe the shot in the bottom input and press `⌘\u002FCtrl + Enter`.\n\nThe node shows progress, and the finished video is saved to `assets\u002F` in your project. From there you can capture frames, review the take, or connect it to the next node.\n\n### A typical session\n\n1. Open a project, drop in reference stills, a reference clip and an audio bed, and write a short brief.\n2. Block the action and the camera in Blockout, export RGB and Depth for the shot, and freeze them as reference nodes.\n3. Wire the brief, the still and the previs into a video generation node and set the reference roles.\n4. Type the request into the composer, for example “make the chase feel faster”. The agent shows the generation plan first. Confirm it, then generate candidates.\n5. Review each candidate. Rejected ones stay in the project.\n6. In the edit plan, replace only the shot that failed, add the audio track and render a new cut.\n\n### Troubleshooting\n\n| Symptom | Fix |\n| --- | --- |\n| The send button is disabled | Read the hint under the input and use its connect-agent or configure-model button |\n| No models to choose from | Configure a provider under **Settings → 媒体服务**, or add a custom one |\n| Audio or video processing fails | Download FFmpeg under **Settings → FFmpeg**, or switch to the system install |\n| Port 47920 is in use | Run `build\u002Fserver\u002FopenCinema -listen 127.0.0.1:47921 -webRoot build\u002Fweb -dataDirectory data` |\n| The agent asks you to connect the workbench | Open the workbench page, enter the project, and ask the agent to read the state again |\n\n## Features\n\n### The canvas and 13 node plugins\n\n\u003Ctable>\n\u003Ctr>\n\u003Ctd width=\"34%\">\u003Cimg src=\"assets\u002Fscreenshots\u002FnodeMenu.png\" alt=\"Add node palette\" width=\"100%\">\u003C\u002Ftd>\n\u003Ctd valign=\"top\">\n\nPress \u003Ckbd>Tab\u003C\u002Fkbd> for the node palette, or drop files straight onto the canvas. Nodes connect by port type, and sources wired into a generation node appear as numbered references.\n\n| Category | Nodes |\n| --- | --- |\n| Text and media | Text · Image · Video · Audio |\n| Generation | Image · Video · Audio Generation |\n| Space and camera | Blockout · 3D Director |\n| Analysis | Video Analysis · Media Info · Motion · Video Depth |\n| Workspace extension | Collaboration |\n\nMultiple canvases, groups, alignment guides, grid snapping, auto-arrange, batch connect, undo and redo are built in and shared by agents and people. Shortcuts and the node menu are customizable in Settings.\n\u003C\u002Ftd>\n\u003C\u002Ftr>\n\u003C\u002Ftable>\n\n### You set the scope\n\n\u003Cimg src=\"assets\u002Fscreenshots\u002FcomposerScope.png\" alt=\"The composer shows the current scope: Shot 02 candidates, five references\" width=\"960\">\n\nSelect one node and the composer acts on it alone. Marquee-select a group and the scope becomes those nodes. Select nothing and it is the whole canvas. Generation nodes can switch between generating and letting the agent adjust the node.\n\n### Editors\n\n\u003Ctable>\n\u003Ctr>\n\u003Ctd width=\"68%\">\u003Cimg src=\"assets\u002Fscreenshots\u002FimageEditor.png\" alt=\"Image editor: inpaint and mark, crop and transform, grid split, lighting, camera angle, panorama\" width=\"100%\">\u003C\u002Ftd>\n\u003Ctd valign=\"top\">\u003Cimg src=\"assets\u002Fscreenshots\u002FmediaEditing.png\" alt=\"Media editing on a video node: edit plan, trim, frame capture, extract audio, separate, video analysis, review\" width=\"100%\">\u003C\u002Ftd>\n\u003C\u002Ftr>\n\u003C\u002Ftable>\n\nEvery operation creates a new copy or node and keeps the original.\n\n- **Images**: inpainting with brush or box selection, markup, crop and transform, grid split, lighting, camera angle and panorama. Lighting and camera changes become generation drafts.\n- **Video**: trim to a new node, capture first, last or current frame, extract audio, separate audio and video, change speed, video analysis, transcription, and entry points for review and the edit plan.\n- **Audio**: waveform editing, segment joining, speed change and transcription. Processing runs through local FFmpeg.\n\n### Blockout: see the shot before you generate\n\n\u003Cimg src=\"assets\u002Fscreenshots\u002Fblockout.png\" alt=\"Blockout 3D previs: white-model stage, character library, camera blocking, shot monitor and timeline\" width=\"960\">\n\nPlace characters and set pieces on a white-model stage, block the action, record camera moves, and export RGB, Depth and Normal layers per shot plus a full animatic. Freezing an export turns it into reference nodes wired into video generation. The 3D Director node generates animation options from text for you to frame by hand.\n\nBlockouts and relative depth are **references** for framing and motion rhythm. A plain video reference does not make a model reproduce motion exactly.\n\n### Analysis\n\n- **Video Analysis**: detects cut points and splits a clip into keyframes, shot segments and the audio track, with no Python or model API required.\n- **Motion**: pose skeleton and 2D motion for clips up to 30 seconds, with optional relative depth and an exportable production pack. It is not a 3D camera solve.\n- **Video Depth**: temporal relative depth from a locally prepared Video Depth Anything Small model. Builds never download models.\n- **Media Info**: local codec, duration, frame rate and track inspection, with no upload.\n\n### Generation plan, review and edit plan\n\n- **See the plan first.** `getGenerationPlan` returns the final prompt, the actual model parameters and the role of every reference, without paying or submitting anything.\n- **Review is a recorded human decision.** Six checks cover direction, action, identity, continuity, unexpected cuts and deformation. Rejected or unreviewed clips cannot enter a composition. The software never judges quality for you.\n- **Replace one shot, keep the rest.** The edit plan stores 2–16 clips with order, in and out points, format and one audio track. Replacing a clip leaves the others untouched, and rendering creates a new output node.\n\nSee the [shot workflow](docs\u002FvideoShotWorkflow.md), in Chinese.\n\n### Your models, your tools\n\n\u003Ctable>\n\u003Ctr>\n\u003Ctd width=\"36%\">\u003Cimg src=\"assets\u002Fscreenshots\u002Fproviders.png\" alt=\"Media services settings: the provider list with configuration status\" width=\"100%\">\u003C\u002Ftd>\n\u003Ctd valign=\"top\">\n\nImage, video and audio generation can each go through your host agent or a configured model.\n\n| Provider | Image | Video | Audio |\n| --- | --- | --- | --- |\n| TF-router | ✓ | ✓ | ✓ |\n| APIMart | ✓ | ✓ | |\n| Compshare | ✓ | ✓ | |\n| Metaso MiniMax H3 | | ✓ | |\n| Volcengine Ark Seedance | | ✓ | |\n| fal | ✓ | ✓ | ✓ |\n| Replicate | ✓ | ✓ | ✓ |\n| Local or LAN video service | | ✓ | |\n| ComfyUI workflows | ✓ | ✓ | ✓ |\n| Custom | ✓ | ✓ | ✓ |\n\u003C\u002Ftd>\n\u003C\u002Ftr>\n\u003C\u002Ftable>\n\n- **fal and Replicate presets**: Kling, FLUX and ElevenLabs on fal, Veo, FLUX and MiniMax Speech on Replicate. Add other models in Settings.\n- **Jobs survive restarts**: after a disconnect or server restart, fal, Replicate, Ark, OpenAI-style, ComfyUI, `httpTask` and `adapter` jobs resume observing the original task instead of submitting a new paid request. Results are downloaded into the project and verified with FFprobe before they count as complete.\n- **FFmpeg**: use a system install or a per-platform, SHA-pinned download. Commands run through filter and option allowlists.\n- **Credentials** are entered only in the local settings panel. A configured credential does not mean the account is authenticated, billing is authorized or generation has been verified.\n\nSee the [provider docs](docs\u002FvideoProvider.md), in Chinese.\n\n### ComfyUI workflows\n\nComfyUI does the generation and OpenCinema treats each workflow as a model, with no manifest to write:\n\n1. Get the workflow running in ComfyUI, then choose **Export (API)**. The regular canvas save format cannot be imported.\n2. Open **Settings → 媒体服务 → ComfyUI 工作流**, enable the connection, enter the address (`http:\u002F\u002F127.0.0.1:8188` by default) and click **保存并检测** (Save and Check).\n3. Click **导入工作流** (Import Workflow), confirm which node receives the prompt, and mark each loader node as first frame, last frame, reference or keep as is.\n4. Pick the workflow in a generation node. Agents use it through the same node tools.\n\nWorkflows run as black boxes. Only the inputs you confirmed are replaced, `seed` and `noise_seed` change on every submission, and output must go through a save node. At import you can add extra ratios and durations and tick fields to adjust on the node.\n\n> Workflows run on ComfyUI, and custom nodes can execute arbitrary code. Only import workflows you trust. Agent tools cannot add or change workflows, so only you can import them in Settings.\n\n### Collaboration and project management\n\n- **Collaboration**: canvas collaboration on one server, with time-limited edit or read-only invites, per-user undo and reconnection. It does not start a public service or offer character-level co-editing.\n- **Asset library**: global assets across projects that you can drag onto any canvas.\n- **Project export**: download the project as an archive with canvases and assets.\n- **Media history**: media nodes keep their generation history so you can switch back at any time.\n\n## Architecture\n\n| Layer | Where | Responsibility |\n| --- | --- | --- |\n| Go server | `apps\u002Fserver` | Authorization and path boundaries, atomic file writes, the MCP endpoint and `operationId` records, media tasks and provider protocols, FFmpeg and the depth model, collaboration |\n| React workbench | `apps\u002Fweb` | The canvas (@xyflow\u002Freact), node views, editors, the tool catalog, the SSE control channel to Go |\n| Editing session | `packages\u002FworkbenchCore` | Framework-independent document, revisions, undo and redo, and the save queue |\n| Node SDK | `packages\u002FnodeScaffold` · `packages\u002FnodeShared` | Plugin contract, Zod tool registration, build output, cross-plugin business such as generation, review and the edit plan |\n| Node plugins | `packages\u002Fnodes\u002F*` | 13 built-in nodes and the collaboration extension |\n| MCP bridge and plugin package | `packages\u002Fmcp` · `packages\u002FopenCinema` | stdio adapter, widget resource, launcher and leases, Codex and Claude manifests and skill |\n\nThree invariants hold the design together: **one source of truth for schemas** (Zod → JSON Schema → Go → stdio), **one command path for people and agents** with shared revisions and undo, and **traceable side effects** (`operationId` idempotency, `unknown` is never retried automatically, outputs carry provenance and review records).\n\n## Extend OpenCinema\n\n### Write a node plugin\n\nMost of OpenCinema is built from plugins, including all 13 built-in nodes. A plugin is an ESM module that runs in the workbench page, exchanges data through ports, and exposes capabilities as tools. Buttons and agents call the same tool, and agents discover it with no protocol changes.\n\nPlugins can be developed outside this repository. The scaffold is not on npm yet, so pack it first from the repository root:\n\n```bash\ncd packages\u002FnodeScaffold && npm pack\n```\n\nGenerate a project where you keep your plugins, replacing `\u003Crepo>` with the real path:\n\n```bash\nnpx --yes --package \u003Crepo>\u002Fpackages\u002FnodeScaffold\u002Ftoonflow-nodes-scaffold-0.4.0.tgz createOpenCinemaPlugin helloNode --name \"Word Count\" --scaffold file:\u003Crepo>\u002Fpackages\u002FnodeScaffold\u002Ftoonflow-nodes-scaffold-0.4.0.tgz\n```\n\nInstall, check and build it:\n\n```bash\ncd helloNode && npm install && npm run typecheck && npm run build\n```\n\nThen open **Settings → 插件 → 安装插件包** (Plugins → Install package) and choose `dist\u002Finstall.json`.\n\n\u003Cdetails>\n\u003Csummary>Example: a node that counts upstream text\u003C\u002Fsummary>\n\nDeclare the plugin in the `openCinema` block of `package.json`. `getService` only returns services listed in `requires`:\n\n```json\n\"openCinema\": {\n  \"id\": \"helloNode\",\n  \"name\": \"Word Count\",\n  \"description\": \"Counts the characters of upstream text\",\n  \"category\": \"Text and media\",\n  \"requires\": { \"workbench\": 1 }\n}\n```\n\n`src\u002FreactPlugin.ts`:\n\n```ts\nimport { z } from \"zod\";\nimport type { NodePluginDefinition } from \"@toonflow\u002Fnodes-scaffold\u002Fcontracts\";\nimport type { NodeWorkbenchService } from \"@toonflow\u002Fnodes-scaffold\u002FworkbenchServices\";\nimport { openCinema, version } from \"..\u002Fpackage.json\";\nimport PluginView from \".\u002FreactView\";\n\nconst plugin: NodePluginDefinition\u003Ctypeof PluginView> = {\n  ...openCinema, version, apiVersion: 1, renderer: \"react\", view: PluginView,\n  createData: () => ({ label: openCinema.name, handles: [\n    { id: \"text\", type: \"target\", dataType: \"STRING\", label: \"Text\" },\n    { id: \"count\", type: \"source\", dataType: \"STRING\", label: \"Count\" },\n  ], outputs: {} }),\n  activate(context) {\n    const workbench = context.getService\u003CNodeWorkbenchService>(\"workbench\");\n    context.registerTool({\n      name: \"countText\", description: \"Counts the characters of upstream text and writes the result to the count output.\",\n      parameters: z.strictObject({}),\n      outputSchema: z.strictObject({ count: z.number().int().nonnegative() }),\n      annotations: { readOnlyHint: false, destructiveHint: false, idempotentHint: true, openWorldHint: false },\n      async execute(_args, { signal }) {\n        workbench.assertWritable();\n        const text = workbench.getInputs(\"text\").map(input => String(input.value ?? \"\")).join(\"\");\n        await workbench.updateData({ outputs: { count: { dataType: \"STRING\", value: String(text.length) } } }, undefined, signal);\n        return { count: text.length };\n      },\n    });\n  },\n};\n\nexport default plugin;\n```\n\n`src\u002FreactView.tsx`:\n\n```tsx\nimport type { ReactNodeViewProps } from \"@toonflow\u002Fnodes-scaffold\u002Freact\";\n\nexport default function PluginView({ node, controller, readOnly }: ReactNodeViewProps) {\n  const outputs = node.data?.outputs as { count?: { value?: string } } | undefined;\n  return \u003Cdiv className=\"nodrag\">\n    \u003Cbutton type=\"button\" disabled={readOnly} onClick={() => void controller.callNodeTool(node.id, \"countText\", {})}>Count\u003C\u002Fbutton>\n    \u003Cspan>{outputs?.count?.value ?? \"Not counted yet\"}\u003C\u002Fspan>\n  \u003C\u002Fdiv>;\n}\n```\n\nAfter installing, **Word Count** appears in the node palette, and `getNodeTools` returns `node:countText` with its schema.\n\u003C\u002Fdetails>\n\n\u003Cdetails>\n\u003Csummary>Plugin parts and where code belongs\u003C\u002Fsummary>\n\n| Part | Purpose |\n| --- | --- |\n| `createData` | Initial node data and ports. Port types include `STRING`, `IMAGE`, `VIDEO`, `AUDIO`, `FILE` |\n| `view` | The React view |\n| `registerTool` | Publishes a tool with Zod parameters |\n| `composer` | Optional hook into the bottom input |\n| `requires` | Required services and host capabilities such as `workbench`, `workspaceFiles` or `ffmpeg` |\n| `category`, `description` | The node's group and summary in the node palette |\n| `onDispose` | Releases workers, listeners and other resources |\n\nNode-specific logic stays in its plugin package, logic shared by several plugins goes in `packages\u002FnodeShared`, and generic contracts go in `packages\u002FnodeScaffold`.\n\nTo ship a plugin as a built-in node, move it to `packages\u002Fnodes\u002F\u003Cid>\u002F`, import the build config from `@toonflow\u002Fnodes-scaffold\u002FreactBuild`, set `outDir: \"..\u002F..\u002F..\u002Fbuild\u002Fnodes\u002F\u003Cid>\"`, add `order` to the `openCinema` block, add the workspace dependency to [`apps\u002Fweb\u002Fpackage.json`](apps\u002Fweb\u002Fpackage.json), and run `bun run generate:nodes`.\n\u003C\u002Fdetails>\n\nA plugin can also provide generation services by declaring `providers` in `viteConfig.ts`. They use the manifest format below and pass the same validation at install time.\n\n> Installed plugins run in the page. `requires` only limits `getService` and is not a sandbox, so a plugin can still read and write the workspace. Only install packages you trust.\n\nSee the [node SDK](packages\u002FnodeScaffold\u002Freadme.md), in Chinese, for the full contract.\n\n### Add a provider\n\nIf a service matches one of these protocols, a JSON manifest is all you need:\n\n| Field | Service shape |\n| --- | --- |\n| `videoProtocol: \"openaiVideo\"` | Video jobs with multipart submit, poll by ID and content download; local or self-hosted |\n| `videoProtocol: \"fal\"` | fal queue |\n| `videoProtocol: \"replicate\"` | Replicate predictions |\n| `videoProtocol: \"httpTask\"` | Any task-style HTTP service. Declare auth, submit, polling, state mapping and output paths in the manifest, with no code |\n| `videoProtocol: \"adapter\"` | Your own local program that exchanges JSON over stdin and stdout, for flows such as request signing |\n| `protocol: \"openai\"` | `images\u002Fgenerations` style image API |\n| `endpoints.image`, etc. | Synchronous JSON API returning result URLs |\n\nOpen **Settings → 媒体服务** and add a custom provider. Start from a template, replace the address and model IDs, validate the manifest and add it, then enter the API key in the connection settings. Never put credentials in the manifest; the server rejects them.\n\n\u003Cdetails>\n\u003Csummary>Example manifest\u003C\u002Fsummary>\n\n```json\n{\n  \"id\": \"remoteVideo\",\n  \"label\": \"My Video Service\",\n  \"apiUrl\": \"https:\u002F\u002Fvideo.example.com\u002Fv1\",\n  \"videoProtocol\": \"openaiVideo\",\n  \"transport\": \"public\",\n  \"cancelMode\": \"none\",\n  \"endpoints\": { \"video\": \"videos\" },\n  \"rules\": [{ \"field\": \"apiKey\", \"title\": \"API Key\", \"type\": \"input\", \"value\": \"\", \"props\": { \"type\": \"password\" } }],\n  \"models\": [{\n    \"id\": \"deployedModelName\",\n    \"label\": \"Deployed video model\",\n    \"type\": \"video\",\n    \"mode\": [\"text\", \"endFrameOptional\"],\n    \"imageRatios\": [\"16:9\", \"9:16\"],\n    \"audio\": false,\n    \"durationResolutionMap\": [{ \"duration\": [4, 8], \"resolution\": [\"720p\"] }],\n    \"video\": { \"inputMap\": { \"firstFrame\": \"input_reference\" } }\n  }]\n}\n```\n\u003C\u002Fdetails>\n\n- For a local or LAN service, set `transport` to `\"local\"` and use an explicit IP and port. The API key is optional, and image, video and audio all work.\n- Models can declare `parameters`, a subset of JSON Schema. Nodes generate a form from it, and agents read and write the same values.\n- An `adapter` executable must be placed by hand in `providerAdapters\u002F\u003Cprovider id>\u002F` under the data directory. A manifest cannot declare commands.\n\nSee the [provider docs](docs\u002FvideoProvider.md), in Chinese.\n\n### The agent tool surface\n\n| Tier | Discovery | Contents |\n| --- | --- | --- |\n| Core tools | `tools\u002Flist` | `getAppState`, `renderOpenCinema`, canvas read and write, `workspaceFiles`, `readWorkspaceImage`, `getOperation`, the host request queue |\n| Workbench extensions | `getWorkbenchTools` → `workbenchTools` | Grouping, arranging, canvas management, asset library, provider and plugin management, FFmpeg and depth-model configuration |\n| Node and workspace plugins | `getNodeTools` \u002F `getWorkspaceTools` → `nodeTools` \u002F `workspaceTools` | Shot plan, generation, review, edit plan, Blockout, motion analysis and other specialist functions |\n\nEvery tier has live schemas, output validation and `annotations`, and errors carry a machine code and a recovery action. Hosts that support MCP Apps can open the workbench inside the conversation with `renderOpenCinema`. See the [MCP SDK](packages\u002Fmcp\u002FREADME.md) and the [plugin notes](packages\u002FopenCinema\u002Freadme.md), in Chinese.\n\n### Release packages\n\n`bun run release` archives an existing `build\u002FopenCinema` as `openCinema-\u003Cversion>-\u003Cplatform>-\u003Carch>.zip` with checksums and a manifest. The unpacked folder needs only Node.js 22+ to load as a plugin. See the [release guide](docs\u002FreleaseGuide.md), in Chinese.\n\n## Contributing\n\n```bash\nbun install --frozen-lockfile\n```\n\n```bash\nbun run dev:web\n```\n\n```bash\nbun run dev:server\n```\n\nBefore opening a pull request:\n\n```bash\nbun run typecheck\n```\n\n```bash\nbun run build\n```\n\n- **Read the [contributing guide](contributing.md) first**: new files and folders use lowerCamelCase, React component tags use PascalCase, canvas edits go through public commands or MCP tools, and file operations go through `@toonflow\u002Ffile`.\n- **Explain the change**: the problem, the final behavior, and the commands you actually ran. Include before and after screenshots for UI work, call and read-back results for agent capabilities, and real output specs for media tasks. Templates: [pull request](docs\u002FpullRequestTemplate.md), [issue](docs\u002FissueTemplate.md).\n- **Do not commit** `build\u002F`, `data\u002F`, credentials, personal media or machine-specific absolute paths.\n- **Do not add test files**: testing in this repository is handled separately. Use type checks, builds and manual verification.\n- **New dependencies**: explain why they are needed and their license, keep the lockfile, and update the [third-party notices](thirdPartyNotices.md).\n- **Welcome directions**: node and provider examples, English tool descriptions and interface strings, documentation and tutorials, performance on large canvases and media previews, Windows verification.\n\nSecurity issues follow [security.md](security.md), and community interaction follows the [code of conduct](codeOfConduct.md). [Issues](https:\u002F\u002Fgithub.com\u002Fjunjamrk\u002FOpenCinema\u002Fissues) · [Changelog](changelog.md)\n\n## Known limitations\n\n- Node and canvas tools run inside the workbench page. With the page closed, agents can work with project files but cannot run nodes.\n- Installed plugins are not sandboxed. `requires` only limits `getService`, and plugins still run in the page.\n- ComfyUI progress reflects the current node, not the whole workflow. Uploaded references stay in its `input` folder because ComfyUI has no delete API. ComfyUI loses job records on restart. Such a job shows as unconfirmed and is never resubmitted automatically.\n- Pricing and balance queries are available for TF-router only. Actual cost comes from the balance difference and shows as unknown when tasks on the same account overlap.\n- Release archives must match your OS and CPU architecture. Hosts, providers and platforms are verified separately, so a passing build does not mean everything works.\n\n## License and acknowledgements\n\n[MIT](LICENSE), preserving the HBAI-Ltd copyright. Blockout and Motion are adapted from Sam Wasserman's tools of the same names (Apache-2.0). Fonts, icons, model weights, FFmpeg builds and user media keep their own terms, see the [third-party notices](thirdPartyNotices.md). The distribution and documentation approach references [Cowart](https:\u002F\u002Fgithub.com\u002Fzhongerxin\u002FCowart).\n",2,"2026-10-31 04:26:48","CREATED_QUERY"]