English | 简体中文
操作 Schema、API 和 CLI 模板已随 SDK 2.2.0 发布。运行仍需宿主提供声明的能力。示例和模板要求
>=2.0.0-beta.48 <3.0.0;beta.48 是计划中的首个支持版本,不代表宿主功能已发布。beta.47 会被拒绝,包括仍使用该版本号的开发构建;若宿主功能延期,必须提高最低版本。SDK 发布及公开 JSON Schema 地址不能证明宿主支持;这些示例不是已上架插件。
- 文本去重:一个输入、折叠组中的布尔选项、一个输出和结果计数。建议先看这个示例。
- 音视频合并:两个文件卡片、双栏布局、内联 MP4/MKV 选择器、平台提示和输入顺序识别。SDK 教学版本使用
example.media-merge,不使用官方保留身份motrix.media-merge。
两个目录都包含完整清单、处理函数、中英文语言文件和 TypeScript 配置,是可以执行的源码示例。下文代码片段只解释局部字段,不是完整清单。
对熟悉 TypeScript 的开发者,修改现有示例的工作量较小;从零定义操作的学习成本中等。主要难点是对齐参数 Schema、UI 引用、翻译键和文件授权,无需编写 React 或 CSS。这是基于源码的评估,没有经过开发者可用性研究。CLI 已提供 operation 模板,自动生成初始引用和文件;basic-resolver 和 post-action 仍为生命周期 Hook 模板。类型仍需手写维护,尚未实现从 Schema 生成 TypeScript 类型或 UI 预览。
在任意工作目录使用已发布的 SDK 2.2.0 CLI,选择尚未占用的项目名:
pnpm --package=@motrix/plugin-cli@2.2.0 dlx motrix-plugin init my-operation -t operation -p acme
cd my-operation
pnpm install
pnpm run typecheck
pnpm run validate
pnpm run pack
pnpm run lint生成项目声明 API/CLI 2.2.0 或兼容的新版本,无需链接本地 SDK。已发布的 2.2.0 CLI 仍会显示过时的“尚未发布”提示,并生成源码依赖安装说明;该版本请按上方执行 pnpm install,不用生成文档里的 file: 配置。本次文档和下一版 CLI 会修正文案。宿主兼容性要求仍有效,不能通过修改宿主 Schema 或权限来安装。
也可使用已发布的 create 包装器:
pnpm create motrix-plugin@2.2.0 my-operation operation它默认使用发布者 me;自定义发布者请使用完整 CLI 的 -p。仅在开发 SDK 未发布改动时,才需要构建本仓库、调用仓库内 CLI,并使用匹配的本地 API/CLI 依赖。
生成器提供完整清单、处理函数、中英文语言文件、基于安装包的严格 tsconfig.json、esbuild 配置、package scripts、.gitignore 和成对 README。操作、命令和激活 ID 自动使用项目的发布者与名称。模板只申请所选文件读写权限,包含插件页面及单任务菜单入口,使用 preserve-inputs。提供明确的 typecheck、validate、pack、lint 脚本;pack 不代替类型检查,处理函数类型仍需手写维护。生成项目不会安装、启动、签名或发布插件。
使用 Node.js 20 或更高版本,以及仓库声明的 pnpm 版本。从仓库根目录开始:
pnpm install --frozen-lockfile
pnpm build
pnpm --filter @motrix/plugin-api exec tsc --noEmit -p ../../examples/text-tools/tsconfig.json
cd examples/text-tools
node ../../packages/plugin-cli/dist/bin/motrix-plugin.js validate
node ../../packages/plugin-cli/dist/bin/motrix-plugin.js pack
node ../../packages/plugin-cli/dist/bin/motrix-plugin.js lint将 text-tools 换成 media-merge 即可运行另一个示例。pack 会自行打包 src/index.ts,将未签名 .moext 写入示例目录下被 Git 忽略的 dist/。lint 读取构建后的 bundle,必须放在 pack 之后。这些命令不会签名或发布。通过兼容宿主的正常第三方安装流程安装,并检查所申请的权限。
示例直接使用当前仓库的 CLI 和 ambient API 类型声明,不需要为示例单独安装依赖。独立项目可使用已发布的 CLI/API 2.2.0 及模板生成的包级 types 配置,替代相对路径类型声明。打包时保持 motrix:plugin-api 为 external。pack 负责校验和打包,不代替 TypeScript 类型检查。
操作是一张由宿主渲染、连接到沙箱处理函数的表单:
manifest → host form → user file authorization → host job → sandbox handler
→ validate and save result
| 编写位置 | 职责 |
|---|---|
commands[].argsSchema / resultSchema |
数据类型、必填项、默认值、范围和返回值 |
operations.items[].fields |
标签、控件、说明和呈现条件 |
operations.items[].files |
输入输出角色、文本或媒体用途、文件名建议 |
operations.items[].ui |
布局、操作按钮文案和重复执行策略 |
operations.items[].entries |
插件详情页和任务菜单入口 |
operations.items[].result / errors |
展示的结果和本地化业务错误 |
commands.registerOperation() |
现有沙箱中的业务逻辑 |
locales/*.json |
插件提供的界面文案 |
宿主管理组件、加载 Skeleton、键盘和焦点行为、进度、取消、资源限制、文件授权及最终输出保存。插件不提供 renderer JavaScript、HTML、CSS、DOM 访问或 React。长期配置仍属于 contributes.configuration 和 config API;操作参数属于本次调用的表单,不会自动保存为偏好设置。
contributes.operations 的结构是 { "version": 1, "items": [...] }。每个操作包含 id、title、command、fields、entries,还可以声明 description、icon、requires、files、ui、result、errors。
操作 ID 和处理命令 ID 都必须以插件 ID 加点号开头。处理命令必须在清单中声明 execution: "operation",不能公开,建议显式填写 public: false。通过 commands.registerOperation 注册处理函数,并声明 onCommand:<command-id> 激活事件。普通 commands.execute 和跨插件调用不能创建操作上下文或调用这一受控处理函数。
操作图标支持 action、media-merge、document、checksum。当前宿主提供以下能力标识:
| 能力标识 | 含义 |
|---|---|
operations.v1 |
操作描述和执行 |
operations.ui.v1 |
可组合布局与呈现 |
files.selected.v1 |
所选文件引用 |
files.text.v1 |
受限 UTF-8 文本读写 |
media.merge-streams.v1 |
受限媒体探测和轨道合并 |
在 requires 中列出所需能力。自定义 ui 必须声明 operations.ui.v1;未知的必需能力会使操作不可用。能力声明不是权限,也不授予文件访问权。
参数和结果 Schema 都必须是扁平对象,设置 additionalProperties: false,最多 24 个属性,只支持 string、boolean、number、integer。不支持嵌套对象、数组、正则或通用 JSON Schema 组合。非枚举字符串必须显式指定不超过 4096 的 maxLength;数字和整数必须指定 minimum、maximum。只支持字符串枚举,最多 64 个互不重复的值。默认值必须通过同一 Schema 的校验。
例如,在 argsSchema.properties 中定义一次格式参数:
{
"format": {
"type": "string",
"enum": ["mp4", "mkv"],
"default": "mp4"
}
}需要必填时,将 format 加入 argsSchema.required。UI 字段只引用它,不重新声明类型或枚举。标签依次取 field.label、Schema 属性的 title、参数名。可见说明使用 field.help,Schema 的 description 不会自动显示为字段说明。处理函数的 TypeScript 泛型目前仍需手写,应与 Schema 保持一致。宿主执行时会重新校验参数,并校验结果;renderer 校验不是权限边界。
每个参数必须在 fields 中恰好声明一次。field 是参数名;控件名支持 text、textarea、number、switch、select、segmented,必须与 Schema 类型匹配。
| Schema 或文件绑定 | 默认控件 | 可选呈现 |
|---|---|---|
| 字符串 | 文本输入框 | textarea |
| 数字或整数 | 数字输入框 | 范围来自 Schema;整数步长为 1 |
| 布尔值 | 开关 | 暂无其他布尔控件 |
| 字符串枚举 | 下拉选择 | segmented 显示单选分段按钮 |
| 输入文件绑定 | 文件卡片 | 预设图标、色调和桌面拖放入口 |
| 输出文件绑定 | 保存位置卡片 | 可通过布局附加普通参数控件 |
{
"field": "format",
"label": "%format%",
"widget": "segmented",
"optionLabels": { "mp4": "MP4", "mkv": "MKV" }
}optionLabels 只改变显示标签,不改变提交值;没有映射的枚举值直接显示原值。必填状态来自 argsSchema.required,宿主目前提供无障碍必填说明,不显示星号。控件行为和执行校验仍由宿主管理。
appearance 接受 placeholder、browseLabel、drop、icon、tone:
placeholder用于文本、数字、多行输入框和未选择文件的卡片。browseLabel当前是文件卡片的无障碍名称,不是可见操作文字。选择、更换、拖入等通用指引由宿主本地化。help提供补充说明。文件选中后,卡片优先显示所在目录。不要在说明或占位文字中重复宿主的操作指引。- 文件图标:
video、audio、folder、document、text、checksum。 - 文件色调:
neutral、blue、violet、amber,均为宿主预设。 drop: true仅适用于输入文件,实际支持和授权取决于宿主;它不会启用浏览器本机文件上传。
文件字段不能声明 widget、optionLabels、visibleWhen、enabledWhen。图标、色调和浏览标签只适用于文件。协议不接受任意样式、图片 URL、class 名称、事件处理器或 HTML。
不写 ui 时,宿主按照 fields 顺序纵向渲染。提供 ui 时必须填写 ui.layout。布局中的 field 节点和 accessory 必须将全部字段恰好绑定一次,不能遗漏或重复。
| 节点 | 属性与行为 |
|---|---|
field |
field,可选 span: 1|2,可选普通参数 accessory,放在标签行 |
text |
纯文本 text,可选 tone: muted|info、platform: desktop|web、span |
stack |
children,可选 title、span,纵向排列 |
grid |
children,可选 columns: 1|2、title、span,窄窗口变为一栏 |
group |
children,可选 title、collapsible、span,带边框分组 |
媒体示例采用以下布局:
{
"type": "grid",
"columns": 2,
"children": [
{ "type": "field", "field": "videoInput" },
{ "type": "field", "field": "audioInput" },
{ "type": "field", "field": "output", "span": 2, "accessory": "format" }
]
}文件不能成为 accessory,输入文件也不能包含 accessory。折叠组必须有标题,默认展开,不能直接或通过嵌套容器包含文件字段,暂不支持配置初始折叠状态。columns 只用于 grid。平台过滤只适用于 text 节点。文本是静态纯文本,不支持 Markdown、HTML 或表单、结果变量插值。
ui.submitLabel、repeatLabel、footer 分别配置执行按钮、再次执行按钮和页脚文案。取消、任务状态、进度和结果容器由宿主提供,不支持额外操作按钮或自定义成功、失败布局。
普通参数字段可以使用 visibleWhen 和 enabledWhen。支持 { field, equals }、{ field, in: [...] }、{ all: [...] }、{ any: [...] }、{ not: ... },采用严格的标量相等判断:
{
"visibleWhen": { "field": "mode", "equals": "replace" },
"enabledWhen": { "not": { "field": "readOnly", "equals": true } }
}引用的字段和值必须符合参数 Schema,不能引用文件字段。带条件的必填字段需要有效默认值。隐藏或禁用字段保留原值,仍会参与校验和提交;条件不会改变必填规则、清空值或授予权限。业务规则也应在处理函数中检查。不支持表达式、回调、计算默认值、动态选项或异步字段校验。
文件参数在参数 Schema 中是必填的有界字符串,不能有默认值或枚举,maxLength <= 128。当前示例将长度约束为 36。必须将它视为本次调用的不透明引用,不能当成路径,也不能自行构造、解析、持久化或跨任务复用。
{
"input": { "kind": "input-file", "use": "text", "extensions": ["txt"] },
"output": {
"kind": "output-file",
"use": "text",
"extensions": ["txt"],
"suggestedName": { "stemFrom": "input", "suffix": "-unique" }
}
}use 为 text 或 media。输入申请 files.selected.read,输出申请 files.selected.write;媒体绑定还必须申请 ffmpeg。输出必须有 extensions 或 extensionFrom。扩展名使用不带点的小写字符;extensionFrom 引用 format 这样的字符串枚举。suggestedName.stemFrom 引用一个输入,其后缀只允许最多 32 个 ASCII 字母、数字、下划线或连字符。建议路径不授予写权限;必要时,提交会先打开保存选择器。
Electron 使用原生打开、保存对话框,桌面拖放由宿主确认。Web 在目录策略内选择服务端文件,不上传浏览器所在电脑的文件。任务菜单绑定经过宿主检查的任务文件。插件自行传入的路径不属于这些授权来源。
宿主将授权绑定到所有者、插件、操作以及本次调用和策略版本。草稿 15 分钟后过期,刷新表单不会延长有效期。再次执行会生成新引用。宿主检查包括解析后的路径策略、文件身份和变更、执行时授权;还负责输入快照、临时输出、取消清理、结果校验和原子无覆盖保存。已有输出会被拒绝,不能提供保存保证的文件系统也会被拒绝。重置表单不会删除已经成功生成的文件。官方身份没有豁免。
媒体探测和合并只接受支持的独立容器;播放列表、concat 脚本、图片序列、网络协议和间接文件访问不能借此绕过授权。OperationContext 不提供任意 FFmpeg 参数接口。
在模块顶层注册一次,将实际工作放进处理函数。完整处理函数见两个示例的源码文件。操作上下文提供:
| API | 契约 |
|---|---|
jobId |
本次任务标识,可用于插件日志 |
signal |
aborted、reason、throwIfAborted、添加和移除取消监听;是有数量限制的沙箱 AbortSignal 子集 |
progress({ phase, fraction? }) |
异步进度报告;phase 为 preparing 或 running,fraction 按 0~1 表达 |
fail(code) |
抛出由操作 errors 映射的稳定业务错误 |
files.readText(ref) |
读取授权的 UTF-8 输入 |
files.writeText(ref, content) |
写入授权的新文本输出 |
media.probe(ref) |
返回有界轨道信息和可选时长 |
media.mergeStreams({ video, audio, output, format, expectedDurationMs? }) |
使用授权引用合并轨道,格式为 MP4 或 MKV |
等待能力调用完成,在工作过程中检查取消,不要捕获取消后伪装成成功。即使处理函数不配合,宿主取消也会撤销能力访问,停止并清理宿主管理的工作。
宿主管理任务阶段:accepted → preparing → running → validating → committing → cleaning → succeeded/failed/cancelled。取消可能进入 cancelling;宿主重启后,未完成任务标为 interrupted,不会重放。仅关闭表单不会取消任务。最终保存存在提交边界:迟到的取消不能删除已经保存的结果。
进度中的 phase 当前不会覆盖宿主的阶段标签,也没有自定义进度文案。每秒最多调用 20 次,应明显低于这一频率。宿主合并更新,上报进度最多显示为 99%,成功保存后由宿主设置 100%。媒体合并进度自动转发。
声明 { "invalidInput": "%invalidInput%" } 错误映射后,调用 context.fail('invalidInput')。没有映射的异常使用宿主通用失败文案,不要依赖原始异常字符串直接展示给用户。
result.fields 选择要按“标题:值”展示的普通返回字段,标题来自 resultSchema.properties[key].title。result.files 指定输出文件绑定,结果 Schema 中也必须声明该字段。返回已授权的输出引用,不能返回路径。所有已声明输出都必须包含在 result.files 中。宿主校验结果和输出后才保存文件。桌面提供打开文件夹操作,Web 不提供桌面文件夹操作。暂不支持自定义结果卡片、表格或值格式化器。
ui.repeat |
普通选项 | 输入 | 输出 |
|---|---|---|---|
省略或 reset |
Schema 默认值 | 清空 | 清空 |
preserve-options |
保留 | 清空 | 清空 |
preserve-inputs |
保留 | 对未变化文件重新检查并授权 | 清空 |
媒体合并使用 preserve-options:“再次合并”清空两个输入和输出,保留 MP4/MKV。文本去重使用 preserve-inputs。再次执行不会自动重新应用上次任务绑定。
普通页面切换会保留 renderer 中的表单状态;刷新可以恢复有效的宿主草稿,但不会延长授权期限。运行中的任务归宿主管理。重启后不会把历史成功任务恢复成新表单的当前结果。插件安装或更新会重载描述并使过时表单、授权失效;兼容协议内的插件更新不需要重启应用。语言切换只更新标签,不使当前表单或任务失效。
entries 支持 plugin-detail 和 task-menu,每种最多一个。详情页直接展示单个操作,多操作时提供切换入口。任务菜单可以将所选任务绑定到输入字段:
{
"surface": "task-menu",
"selection": {
"count": { "min": 1, "max": 1 },
"every": { "status": "completed", "files": "single", "local": true }
},
"bind": { "input": { "source": "task-file", "index": 0 } }
}当前任务条件限于 1~4 个已完成的单文件任务,且存在本地最终文件。即使只选择最少数量的任务,绑定索引也必须存在。不支持任意任务判断式或脚本生成菜单标签。不适用的任务选择不显示入口;插件停用、缺少能力、安全限制或缺少 FFmpeg 时显示对应不可用状态。宿主准备和执行时会重新检查资格,不只在画菜单时检查。两个入口共用同一表单和处理函数。
设置 l10n: "locales",使用占满整个字段的 %key% 引用。声明本地化后必须提供 en-US.json;其他语言可以不完整,缺失时回退英文。"Choose %input%" 这样的混合文本无效,应将完整句子放入一个翻译键。ID、参数名和枚举实际值不翻译。
操作会解析标题与说明、字段标签与帮助、appearance 文案、选项标签、分组标题、静态文本、按钮与页脚、错误文案、Schema 标题与说明。宿主语言决定显示语言;通用状态、取消和文件操作文字来自宿主语言资源。
翻译后的描述也有大小限制,短 %key% 不能绕过限制。CLI 检查英文引用覆盖,不验证每种语言的翻译质量或界面适配,应在宿主检查示例的两种语言。
宿主控件负责标签与说明关联、必填说明、状态 live region 和键盘行为。所有字段都应有清晰标签,包括可见标签被压缩隐藏的 accessory。自定义 browseLabel 应说明文件选择用途,不要只依靠图标或色调传递含义。
下列是当前实现限制,不代表每个插件能同时耗尽每类资源。
| 项目 | 当前限制 |
|---|---|
| 每插件操作数 | 16 |
| 每操作字段数、每个数据 Schema 属性数 | 24 |
| 输入、输出文件 | 4 个输入、1 个输出;文件字段全部必填 |
| 布局 | 64 个节点、深度 4、每个容器最多 24 个子节点 |
| 条件 | 单操作累计 64 个节点、深度 4;all、any 最多 8 个子项,in 最多 64 个值 |
| 描述校验预算 | 单操作 8 KiB,全部操作声明 64 KiB;采用保守结构计数,不等于清单文件原始大小 |
| 通用 Schema 预算 | 每个参数或结果 Schema 8 KiB,另受有界 Schema 校验约束 |
| 文本能力 | UTF-8;输入、输出各 1 MiB,最多读取 4 次,每个输出写入一次 |
| 媒体能力 | 每个输入 8 GiB、总输入 16 GiB;最多 4 次探测、同时 2 次探测、1 次合并 |
| 宿主操作超时 | 媒体操作 1 小时,其他操作 60 秒;沙箱或资源限制可能更早终止 |
| 草稿配额 | 每个所有者 4 个,全局 16 个;刷新复用同一有效草稿 |
文件快照和暂存还需要足够可用磁盘空间,不能通过在清单增加字段来提高限制。宿主可能在处理函数开始前就报告资源或磁盘空间限制。
尚未实现:自定义 HTML/JS/CSS 组件、任意图片和样式、动态选项或异步字段校验、计算值、数组或对象编辑器、表格、树、多步骤向导、任意额外按钮、复杂结果布局、目录选择、多文件数组字段、浏览器本机上传。适合时可以拆成几个小操作;超出组件或能力集合的需求需要协议与宿主扩展,不能依靠未声明的旁路。
在当前协议内修改 UI 文案、布局、选项或业务函数,可以通过插件更新交付。新控件、布局节点、条件运算符、能力或结果渲染器需要 SDK Schema/API/CLI 和宿主共同支持。operations.version 选择协议,requires 在支持操作机制的宿主上检查能力,engines.motrix 声明真实的最低宿主版本。旧宿主可能在安装时拒绝未知清单,不一定能进入不可用操作页面。
当前引擎最低版本目标为 beta.48。正式发布前须确认 beta.48 实际包含这些能力;若延期,最低版本必须提高到首个实际支持的版本。使用配套 SDK/CLI,并保持包清单与注册表的兼容性、权限信息一致。CLI 在构建时内嵌 Schema,单独更新 Schema 依赖不能更新旧 CLI。公开 Schema 地址也可能落后于当前开发实现。官方包的签名继续由官方插件仓库负责,本仓库示例保持未签名。
| 现象 | 检查项 |
|---|---|
| 清单校验失败 | 宿主和 CLI 是否匹配;处理命令是否为私有 execution: operation;是否为扁平有界 Schema;是否声明所需能力 |
| 布局被拒绝 | 包括 accessory 在内,每个字段是否恰好引用一次;结构与深度是否超限 |
| 条件字段被拒绝 | 引用的普通字段和值是否存在;有条件的必填项是否有默认值 |
| 文件绑定被拒绝 | 必填字符串、无默认值与枚举、权限、输出扩展名和返回输出是否齐全 |
显示 %key% 或意外英文 |
l10n、英文覆盖、当前语言键、是否使用完整字段引用 |
| 文件操作文案重复 | 从 help、placeholder 中移除重复的宿主指引;browseLabel 是无障碍名称 |
resourceLimit |
进度频率、读取或探测次数、文件大小、并发草稿和可用磁盘空间 |
| 输出被拒绝 | 选择尚不存在的目标文件,不能关闭防覆盖保护 |
从完整示例开始修改。示例 README 提供 Electron/Web、多语言、取消和文件授权的预期行为与手工检查步骤。通过 CLI 校验不能证明这些运行流程全部正确。