diff --git a/README.md b/README.md index 6361d109..35f94d5d 100644 --- a/README.md +++ b/README.md @@ -59,6 +59,7 @@ The MiniMax Code Agent communicates through the host MCP client. Business reques | --- | --- | --- | | [Dynamic Workflow Console](plugins/stevenjj33/workflow-console/) | Run and supervise MiniMax Code dynamic workflows from one page: live status, a stage-and-dependency flow graph, per-node output, approval and control, templates, and report export | [stevenjj33](https://github.com/stevenjj33) | | [MiniMax Photo](plugins/webguhui/minimax-photo/) | Layered image editing with an in-app AI assistant, local recovery and programmatic editing tools | [WebGuHui](https://github.com/WebGuHui) | +| [Cutline](plugins/klarkxy/cutline/) | Local timeline editing with MCP tools for cutting, captions, motion graphics, grading, audio and export | [klarkxy](https://github.com/klarkxy) | | [MiniMax Video](plugins/webguhui/minimax-video/) | Local video editing with captions, a host Agent assistant, reviewable timeline edits, and MP4/MP3 export | [WebGuHui](https://github.com/WebGuHui) | | [AI Office](plugins/cristinamadureira353-svg/ai-office/) | Manage five model-powered employees in a pixel office: client tasks, chats, breaks, meetings and quota-aware offline states; uses your own API keys | [最爱双皮奶](https://github.com/cristinamadureira353-svg) | | [Token Usage Board](plugins/amszuidas/mcode-token-usage-board/) | Explore local Token usage by date, model, and session, including input, output, and cache usage | [amszuidas](https://github.com/amszuidas) | diff --git a/README.zh-CN.md b/README.zh-CN.md index 58b60d69..b2da4d9a 100644 --- a/README.zh-CN.md +++ b/README.zh-CN.md @@ -59,6 +59,7 @@ MiniMax Code Agent 通过宿主 MCP 客户端与 MiniApp 协作。业务请求 | --- | --- | --- | | [动态工作流控制台](plugins/stevenjj33/workflow-console/README.zh-CN.md) | 在一个页面里运行并盯住 MiniMax Code 动态工作流:实时状态、按阶段与依赖画出的流程图、节点输出、审核与控制、模板与报告导出 | [stevenjj33](https://github.com/stevenjj33) | | [MiniMax Photo](plugins/webguhui/minimax-photo/) | 图层图片编辑、应用内 AI 助手、本地恢复与程序化编辑工具 | [WebGuHui](https://github.com/WebGuHui) | +| [Cutline](plugins/klarkxy/cutline/) | 本地时间线剪辑,MCP 工具覆盖剪切、字幕、动效、调色、音频与导出 | [klarkxy](https://github.com/klarkxy) | | [MiniMax Video](plugins/webguhui/minimax-video/) | 本地视频剪辑、字幕、宿主 Agent 助手、可预览的时间线修改与 MP4/MP3 导出 | [WebGuHui](https://github.com/WebGuHui) | | [AI 事务所](plugins/cristinamadureira353-svg/ai-office/README.zh-CN.md) | 五位真实模型员工的像素事务所:接单、聊天、休息和开会,额度耗尽时角色暂停;使用玩家自己的 API Key | [最爱双皮奶](https://github.com/cristinamadureira353-svg) | | [Token 用量看板](plugins/amszuidas/mcode-token-usage-board/README.zh-CN.md) | 按时间、模型和会话查看本机 Token 用量,包含输入、输出和缓存用量 | [amszuidas](https://github.com/amszuidas) | diff --git a/plugins/klarkxy/cutline/.gitignore b/plugins/klarkxy/cutline/.gitignore new file mode 100644 index 00000000..d829fd57 --- /dev/null +++ b/plugins/klarkxy/cutline/.gitignore @@ -0,0 +1,3 @@ +# Test fixtures: files the tests create and read (a clip outside the library, +# a Cutline data folder to import from). Machine-specific, never tracked. +tests/.tmp/ diff --git a/plugins/klarkxy/cutline/.minimax-plugin/plugin.json b/plugins/klarkxy/cutline/.minimax-plugin/plugin.json new file mode 100644 index 00000000..54cf2123 --- /dev/null +++ b/plugins/klarkxy/cutline/.minimax-plugin/plugin.json @@ -0,0 +1,22 @@ +{ + "schemaVersion": 1, + "name": "cutline", + "displayName": "Cutline", + "version": "0.1.0", + "description": "浏览器里的视频剪辑台:时间线、字幕、动效、调色与导出,附带 21 个 MCP 工具,聊天里就能剪片。", + "author": "klarkxy", + "icon": "icon.png", + "category": "Studio", + "exampleQueries": [ + "打开 Cutline 剪辑台", + "用 Cutline 把这段素材剪成一条竖屏短视频", + "Cutline 现在时间线上有什么?帮我加个字幕" + ], + "apps": [], + "mcpServers": [ + "servers.mcp.json" + ], + "skills": [ + "skills/cutline/SKILL.md" + ] +} diff --git a/plugins/klarkxy/cutline/LICENSE b/plugins/klarkxy/cutline/LICENSE new file mode 100644 index 00000000..3f0ff373 --- /dev/null +++ b/plugins/klarkxy/cutline/LICENSE @@ -0,0 +1,51 @@ +MIT License + +Copyright (c) 2026 klarkxy + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. + +--- + +This package vendors FableCut, which is MIT licensed as well: + +MIT License + +Copyright (c) 2026 ronak-create + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. + +The vendored sources live in miniapp/node/vendor/fablecut/ and +miniapp/client/. Every change made to them is listed in PATCHES.md, and the +upstream revision they came from is recorded in upstream.json. diff --git a/plugins/klarkxy/cutline/PATCHES.md b/plugins/klarkxy/cutline/PATCHES.md new file mode 100644 index 00000000..58092064 --- /dev/null +++ b/plugins/klarkxy/cutline/PATCHES.md @@ -0,0 +1,466 @@ +# Patches to the vendored FableCut source + +## Compact monitor and localized menus + +The monitor header keeps one row of tabs and icon controls. Canvas aspect, frame rate, export framing and resolution move into a keyboard-accessible popover; the original controls and stored values remain unchanged. Snap targets and sequence actions follow the selected language while user sequence names stay untouched and escaped. Playback controls wrap when the monitor is narrow, avoiding overlap with timecodes. Six browser tests cover menu localization, raw values, escaped names, popover operation and layout at five viewport widths. + +## Icon-first controls + +Thirteen media and timeline actions use inline SVG icons with localized accessible names, shortcut tooltips and visually hidden text. Their IDs, handlers and enum values remain unchanged. Import keeps a keyboard-focusable file input and visible focus ring. Empty states use one short prompt; Export, navigation and field labels remain visible. The two range-delete labels remain distinct. Eleven tests cover names, keyboard access, action behavior and 32-pixel targets at three viewport widths. + +## Responsive editor layout + +The client toolbar now has primary and secondary rows with wrapping controls and a non-wrapping zoom group. Media and inspector panels have accessible collapse/restore rails with browser-local preferences and responsive defaults. The monitor meter occupies a separate gutter outside the picture; monitor fitting uses the remaining picture area without changing canvas resolution. Chinese monitor and range-edit terminology was revised. Six browser tests cover English/Chinese layouts at 1280x900, 923x899 and 720x800, panel restoration, meter separation and monitor zoom. Host checks covered the empty workspace and a synthetic timeline; the test project is retained separately. + +Everything under `miniapp/node/vendor/fablecut/` and `miniapp/client/` is +vendored from [FableCut](https://github.com/ronak-create/FableCut) 1.16.0, +commit `fd2af081d73ec94464b88df8c21f640adcc78985` (MIT, © 2026 ronak-create). +The upstream revision is recorded in `upstream.json`. + +The changes below are the whole difference. Each one says what it is for, so +the upstream files can be re-vendored later and the patches re-applied. + +Files this package adds that are not upstream: + +| File | What it is | +| --- | --- | +| `miniapp/node/server.mjs` | The Node entry: `start(context)`, the HTTP server, the routing, the lifecycle. | +| `miniapp/node/adapters/local-api.mjs` | The in-process transport that lets the MCP tools call the editor without a socket. | +| `miniapp/node/adapters/mcp-http.mjs` | The Streamable HTTP MCP endpoint; rejects browser Origin/Fetch Metadata requests so page scripts cannot acquire Agent file capabilities. | +| `miniapp/node/adapters/export-tool.mjs` | The wrapper that adds `cutline_export` to the upstream tool list without touching the upstream dispatcher. | +| `miniapp/node/export/job-state.mjs` | The state of one export job: ids, the managed root, the atomic state file. | +| `miniapp/node/export/prepare.mjs` | Copies the saved project, its media, the library assets, the editor and the runner into a job folder under the data dir. | +| `miniapp/node/export/export-mcp.mjs` | The `cutline_export` tool definition and the text it answers with. | +| `miniapp/node/export/runner.mjs` | The standalone runner: its own loopback server, its own secret, its own headless browser. Copied into every job folder. | +| `miniapp/node/vendor/puppeteer/` | A bundled build of Puppeteer Core and the 55 runtime packages it pulls in, the browser automation library the runner drives the render with. See its `PROVENANCE.md` and `licenses/INVENTORY.md`. | +| `miniapp/node/vendor/fablecut/processes.js` | Tracks media child processes and waits for their termination when the Host stops the runtime. | +| `miniapp/node/vendor/fablecut/media-types.js` | The shared managed-media allowlist and browser-executable document classification used by uploads, imports, URL imports and safe static serving. | + +--- + +## Concurrency, managed-media and export hardening + +Whole-project MCP writes require an explicit `baseRevision`; a revision embedded in the submitted document is not accepted as evidence of a prior read, and the server allocates the next revision. Forced REST writes also advance the current on-disk revision instead of rewinding it. The Agent manual no longer tells callers to increment a document revision themselves or claims that an unbased targeted patch preserves same-field concurrent edits. + +Managed media copies share one video/audio/image/SVG allowlist, with font support confined to the library. Upload, project-folder import and standalone-export preparation reject non-media documents before copying. Every Node and standalone-runner response carries `X-Content-Type-Options: nosniff`; media, library, proxy and export documents with browser-executable extensions also carry a sandbox CSP, while each editor's own static payload remains executable. + +Standalone export preparation rejects unknown built-in or saved delivery preset IDs instead of allowing a later Custom fallback, and applies the same managed-media boundary as an interactive import. Runner heartbeat failures enter the same bounded teardown as render failures, concurrent cleanup is idempotent, and browser shutdown has a bounded close attempt before the process is killed. An independent teardown deadline also bounds a cleanup started by the overall render timeout. + +Locale repainting covers the export batch list and timeline transition/fade tooltips. User-facing toast keys and the batch counter are translated without passing user content or stored enum values through the translator. + +## `miniapp/node/vendor/fablecut/server.js` + +1. **The static root is configurable.** `ROOT` stays the module's own folder + (the encode profiles are watched there); a new `STATIC_DIR` takes the + editor's static files from `FABLECUT_STATIC_DIR`, which the entry points at + the client payload. The last two lines of the request handler serve from + `STATIC_DIR` instead of `ROOT`. + +2. **The request handler is a plain function.** `const server = http.createServer(async (req, res) => {…})` + became `async function handleRequest(req, res) {…}`, and the module exports + it. A Mini App must bind only the port the Host assigns, so the upstream + server cannot create and listen on a server of its own. + +3. **The standalone listener is gone.** The `server.listen(PORT, HOST, …)` + block, with its banner and its `console.log` lines, was removed together + with the `PORT`/`HOST` constants: the Host owns stdout, and a fixed port + is not allowed. `startExportSweep()` now runs at load, where the listen + callback used to call it. + +4. **`shutdown()` is a library call.** It no longer closes a server or calls + `process.exit`, and it no longer registers `SIGINT`/`SIGTERM` handlers. + It closes the export sweep, the export sessions, the auto-version timer, + the two debounce timers, the directory watchers and the live-reload + clients; the entry calls it from `dispose()`. The `fs.watch` calls now keep + their handles in a `watchers` array so they can be closed. + +5. **The logger is injectable.** `console.warn` on a damaged `project.json` + goes through `log.warn`, which reads `globalThis.__FABLECUT_LOG__` — the + entry sets that to `context.logger`. The fallback is stderr. + +6. **The host gate has a same-origin rule.** `FABLECUT_TRUST_HOST=1` accepts a + request whose `Origin` names the same host as the request itself, whatever + host name the Host serves the page under. A cross-origin request still + fails the origin check. The Host already guards the assigned port with a + per-run credential, so a request that reaches the handler is authenticated. + +7. **`path=` needs the local transport, not a header.** The `X-FableCut-Client: + mcp` header is gone as the gate: the page is same-origin and can set any + header it likes. The absolute-path capability is granted to a request that + carries the symbol `Symbol.for("fablecut.localRequest")`, which only the + in-process transport sets and which cannot be forged from a page. + + The split is the upstream one, with the page on the outside of it: the page + cannot name a file outside `media/` and `library/` at all, while the local + MCP client — which the user asked for, and which can read any file the user + can — still can. The editor page itself never uses `path=`; it reads served + `src=` paths only. + +## `miniapp/node/vendor/fablecut/mcp-server.js` + +1. **A transport can replace the HTTP client.** `apiJSON`, `apiBytes` and + `httpOk` use a transport when one is installed (`setTransport`), and + `ensureUIServer()` then reports that the editor is there. Upstream reaches + the editor over HTTP on 127.0.0.1:7777; here the editor is this same + process, so the tools call its request handler in-process. Without a + transport the upstream HTTP path is unchanged. + +2. **The stdio plumbing is gone.** The newline-delimited JSON-RPC loop over + stdin and its `process.stdout.write` were replaced by `handleMessage(msg)`, + which answers with the response object, and by the module exports. The + Host owns stdin and stdout. + +3. **`fablecut_set_project` requires an explicit, trusted base revision.** + Upstream compared the file's revision with `lastReadRevision`, a + process-global that records the last read. With several MCP sessions in + one process, another session's read answered for this write and a stale + document slipped through. The tool now requires `baseRevision` from the + caller's last `fablecut_get_project`; the submitted document's own revision + is not treated as proof of that read. A different current revision refuses + the write unless `force:true` was explicitly supplied, and a successful + write stores current revision + 1. The write helper also refuses writes + after runtime disposal has begun. + +4. **`relinkMedia` and `replaceMedia` see the file's details.** The ops are + applied synchronously, but the details come from ffprobe, which is + asynchronous: upstream handed the op a Promise, so a replaced media entry + kept no duration and no width and its clips were never trimmed to fit. The + files the ops name are probed first, and the op reads a plain value. + +5. **The editor's location is settable.** Upstream printed its own loopback + URL, so the user could open the editor in a browser. A host that serves the + page itself has no such URL to print: `setEditorLocation()` replaces the + three strings, and the entry points them at the Mini App. + +6. **`fablecut_import_project` was added.** The upstream editor can import a + project document, but a document on its own is not portable: its media + entries name `/media/…` paths that belong to the data folder it was made + in. The new tool imports a project *folder*: the document is read, the + media files it uses are copied into this installation's library (never + overwriting anything), the entries point at the copies, the originals are + left untouched, and the project is saved as the open one at the actual + current revision. It prevalidates document/media real paths, refuses missing, + escaping or unsupported non-media sources, supports nested source folders, + and copies into safe unique destination names. Library references are + resolved by content rather than silently reusing a different asset. It snapshots the existing project + before replacement and refuses concurrent changes during probing. It writes + the same shared `project.json` that `fablecut_set_project` writes. + +7. **The `where` parameters say what is possible.** The four `where` + descriptions and the `fablecut_export` and `fablecut_frame` descriptions + no longer offer a headless browser: in this package there is none, because + a background browser cannot load the editor's authenticated origin. + +## `miniapp/node/vendor/fablecut/export-jobs.js` + +`createExportJobs` takes `allowHeadless`. When it is false — as it is here — +`where: "headless"` is refused with an explanation, and `where: "auto"` with +no editor tab open is refused as well. The compositor is the editor tab. + +## The standalone export runner + +This package adds a second way to render, beside the editor-tab export. It is +built entirely from this package's own files; the vendored FableCut sources are +unchanged for it. + +`cutline_export {action:"prepare"}` copies into a job folder under the +data dir: + +- the saved project document, +- every media and library file the document names (byte-for-byte copies, never + hard links), plus the user's saved presets, templates, delivery presets and + fonts, +- the editor's client payload and the whole `vendor/fablecut/` runtime, +- a copy of `runner.mjs` and `job-state.mjs`, +- the bundled Puppeteer Core build under `vendor/puppeteer/`. + +The copy refuses a source that is missing or that resolves outside the +project's own media or library folder, before the job becomes runnable. + +The copied runtime folder gets a `package.json` of its own that says +`"type": "commonjs"`. Without it the nearest `package.json` above the job +folder decides how the runtime's `.js` files are read, and an ancestor that +says `"type": "module"` turns them into ES modules - the runner's +`require` of `server.js` then fails with "require is not defined". + +The runner then loads its **copy** of the runtime with `FABLECUT_DATA_DIR` and +`FABLECUT_STATIC_DIR` pointing at the job folder, so it reads the snapshot and +never the live project, and it does not depend on the installed package +directory after the handoff. It serves the editor on an ephemeral loopback port +of its own: + +- every request has to carry a secret the runner generated for that run; the + secret never appears in a URL, in an argument or in a log line, +- only the packaged render assets, the snapshot, the media and library copies + and the render's own begin/frame/audio/end endpoints are served; every other + route, method and query parameter is refused before the upstream handler sees + it, +- the page may not reach any other origin: the browser's requests are + intercepted and anything that is not this origin is aborted. + +The render itself is upstream's: the editor page's `fastExport`, its +`renderAudioMix` and the upstream server's ffmpeg encoding are used unchanged. +The upstream `allowHeadless: false` is left alone - the runner does not use +upstream's headless path, it drives its own browser. + +## Media process ownership +`analyze.js`, `denoise.js`, `encode-profiles.js`, `export-jobs.js`, +`import-url.js`, `mcp-server.js`, `media-probe.js`, `proxy.js`, and `server.js` +import the local `processes.js` adapter instead of `child_process`. Their +argument arrays and media operations remain unchanged. The adapter tracks +asynchronous children, stops them on disposal and rejects new work after +shutdown. The entry also cleans up children when binding the Host port fails. + +## `miniapp/node/vendor/fablecut/analyze.js` + +The `node analyze.js