跳到正文

FluxDown 0.5.0 原生 GPUI 客户端正式发布——作者来信:为什么我把整个界面从头重写了一遍。去看看

FluxDown
下载 文档 定价 更新日志 赞助
插件开发 Manifest 参考

插件开发

Manifest 参考

manifest.json 全部字段及校验规则、设置项 widget 矩阵、URL pattern 规则,以及安装与加载时的检查方式。

编辑此页

manifest.json 位于插件包根部,声明插件是什么、带有哪些入口脚本、向界面贡献什么,以及需要哪些能力。所有键名使用 camelCase。未知键会被拒绝——写错字段名会让整份 manifest 校验失败,而不是被静默忽略。

本页是逐字段的参考。如果想按步骤上手,请看写第一个插件(手写 JavaScript)或 TypeScript 项目(SDK、构建与打包)。权限与授权见权限、授权与审计,打包与发布见打包与插件市场。

完整示例

{
  "identity": "acme@demo",
  "name": "Acme Demo",
  "version": "1.0.0",
  "description": "解析 Acme 分享链接,并整理下载完成的文件。",
  "homepage": "https://example.com/acme-demo",
  "icon": "icon.png",
  "minAppVersion": "0.9.0",
  "resolvers": [
    { "match": { "urls": ["*://acme.example/share/*"] }, "entry": "resolve.js", "timeoutMs": 15000, "multi": true }
  ],
  "hooks": {
    "entry": "hooks.js",
    "events": ["onDone", "onError", "onCancel"],
    "match": { "urls": ["*://acme.example/*"] }
  },
  "main": { "entry": "main.js", "events": ["onDone"], "background": false },
  "contributes": {
    "views": [{ "id": "dashboard", "title": "看板", "location": "activity" }],
    "commands": [{ "id": "refresh", "title": "刷新", "palette": true }]
  },
  "settings": [
    { "key": "quality", "title": "偏好画质", "type": "string", "widget": "select",
      "options": [ { "value": "hd", "label": "高清" }, { "value": "sd", "label": "标清" } ], "default": "hd" }
  ],
  "permissions": []
}

插件可以任意组合 resolvers、subscriptions、hooks、interceptors、auth、main 与 contributes。一项都不声明的 manifest 合法,只是什么也不做。

顶层字段

字段必填类型规则
identity是string^[a-z0-9_-]+@[a-z0-9_-]+$——小写作者、@、名称。禁止点号。永久 ID:设置、存储、授权记录与安装目录都以它为键。
name是string显示名,不能为空,也不能只有空白字符。
version是string主.次.补丁 三段整数。解析的严格程度见版本号写法。
description否string显示在插件列表里。
homepage否string项目或作者主页。
icon否string包内路径。禁止 ..、绝对路径、盘符、以 / 或 \ 开头,以及空路径段。
minAppVersion否string要求的最低 FluxDown 版本,解析规则同 version。见 minAppVersion 的实际行为。
resolvers否array至多一项。见 resolvers[0]。
subscriptions否array至多一项,订阅源 provider。见 subscriptions[0]。
hooks否object下载生命周期回调。见 hooks。
interceptors否object任务拦截器(请求改写、分片过滤、后处理)。见 interceptors。
auth否object平台登录入口。见 auth。
main否object常驻脚本,承载可移植视图、命令与后台逻辑。见 main。
contributes否object视图、命令与浏览器嗅探规则。见 contributes。
permissions否array能力授权,未知值一律拒绝。见 permissions。
settings否array声明式设置项。见 settings[]。

版本号写法

version 与 minAppVersion 都按三个整数比较;第三段数字之后的内容,客户端会忽略。

场景接受的写法
FluxDown 客户端(安装与加载)MAJOR.MINOR.PATCH,可带前导 v,可带 -pre 或 +build 后缀(比较前会被截掉)。1.2.3、v1.2.3、1.2.3-rc.1 都解析为 1.2.3。必须恰好三段。
插件索引与 fluxdown-plugin validate严格模式:只能是 MAJOR.MINOR.PATCH,仅数字、不允许前导零、不允许 v 前缀、不允许后缀。

请始终写严格形式。客户端能容忍的版本号,发布到市场时仍可能被拒绝。

minAppVersion 的实际行为

运行中的 FluxDown 比 minAppVersion 旧(或任一侧无法解析)时,插件不会加载。它会出现在插件列表里,加载状态为失败,原因是「需要 App ≥ X,当前 Y」,并不是被静默跳过。安装这样的包会被拒绝,提示「安装后未能加载插件(校验失败或版本门槛不满足)」,并回滚。

当你开始使用旧版本不认识的字段、事件或权限时,请同步抬高 minAppVersion:旧客户端遇到未知键或未知权限会直接判 manifest 非法,版本门槛则能让用户看到清晰的提示。

resolvers[0]

resolver 把一个页面 URL 解析成一个或多个可下载文件。

{
  "resolvers": [
    {
      "match": { "urls": ["*://host.com/share/*", "*://cdn.host.com/*"] },
      "entry": "resolver.js",
      "timeoutMs": 15000,
      "multi": false
    }
  ]
}
字段必填规则
match.urls是非空的 URL pattern 列表(规则见 URL pattern 规则)。
entry是脚本文件,安全相对路径。必须定义 globalThis.resolve。
timeoutMs否单次调用超时(毫秒),不能是 0。它替换默认的 10 秒,但无论写多大都被 30 秒硬顶封住。
multi否布尔值,默认 false。true 声明该 resolver 可能返回多文件清单,并参与前置预解析(创建任务之前先列出文件清单的预览)。false 时,单文件 resolver 不会为了预览被白白提前执行一次;如果它在任务开始时返回了多文件清单,引擎仍会把它拆成多个任务,所以 multi 只是前置预解析的声明,不是返回清单的前提。

resolve(ctx) 的契约与返回结构见 API 参考。

subscriptions[0]

订阅 provider 为订阅功能提供 RSS 之外的内容来源,例如某个站点用 API 列出的新剧集。

{
  "subscriptions": [
    { "providerId": "acme-feed", "entry": "subscribe.js", "timeoutMs": 20000 }
  ]
}
字段必填规则
providerId是^[a-z0-9_-]{1,64}$。必须稳定:它会写入订阅记录。rss 是内置 provider 的保留字,会被拒绝。
entry是脚本文件,安全相对路径。必须定义 globalThis.subscribe,函数名固定。
timeoutMs否单次调用超时(毫秒),不能是 0。默认 10 秒,可设置到 30 秒硬顶。

每个插件至多一个 provider。subscribe(ctx) 的契约、条目结构以及对返回条目的校验见订阅源指南。

hooks

hooks 在下载生命周期事件发生时运行你的代码。

{
  "hooks": {
    "entry": "hooks.js",
    "events": ["onStart", "onDone", "onError", "onCancel"],
    "match": { "urls": ["*://host.com/*"] }
  }
}
字段必填规则
entry是脚本文件,安全相对路径。须为每个订阅的事件各定义一个同名全局函数。
events是非空列表,每项只能取 onStart、onError、onDone、onMetaProbed、onCancel,其余一律拒绝。
match否URL 过滤器,pattern 规则同上;存在时 urls 不能为空。省略 = 所有任务都触发。

注意:同一插件如果还声明了 resolver,onMetaProbed 对它的任务永远不会触发——带 resolver 的任务直接跳过元数据探测。若你仍然订阅了它,FluxDown 会记一条警告日志。

事件参数、requestRetry、ffmpeg/ffprobe 与 yt-dlp 后处理见事件钩子指南。

interceptors

拦截器让插件在引擎运行任务时读取并修改任务:请求身份、并发、流媒体分片、后处理与重试决策。每个插件至多声明一个 interceptors 对象,并且需要 intercept 权限(较高风险,见权限)。

{
  "permissions": ["intercept"],
  "interceptors": {
    "entry": "intercept.js",
    "stages": ["prepare", "segments"],
    "match": { "urls": ["https://example.com/*"] },
    "timeoutMs": 3000,
    "onFailure": "continue"
  }
}
字段必填规则
entry是脚本文件,安全相对路径。必须定义唯一的全局函数 intercept(ctx),由它按 ctx.stage 分派。
stages是非空且不重复,每项只能取 prepare、probed、refresh、selectVariant、selectPeriod、segments、liveSegments、finalize、failed。未知阶段会被拒绝并列出合法值。
match是URL 过滤器,pattern 规则同上,urls 不能为空。任务的原始 URL 或解析后的 URL 命中任一 pattern 即被拦截。
timeoutMs否单次调用的墙钟超时,不能是 0。默认 5 秒,受硬顶限制;liveSegments 另被限制在 2 秒内,refresh 另被限制在 5 秒内。
onFailure否continue(默认)或 fail。continue 时该插件的报错或非法返回值会记入任务活动并被忽略;fail 时任务失败,错误为 [插件] <identity>(<阶段>):<消息>。

校验在 auth 之后进行,顺序为:entry 路径 → stages 非空 → 每个阶段都已知 → 阶段不重复 → match.urls 非空 → timeoutMs ≠ 0 → permissions 含 intercept。入口文件必须在包内存在,并与 hooks.entry 一样在加载时读取。各阶段的上下文、返回值、RequestPatch 规则与 defineInterceptors 见拦截器指南。

auth

{
  "auth": { "entry": "auth.js", "timeoutMs": 20000 }
}
字段必填规则
entry是脚本文件,安全相对路径。必须定义 globalThis.authenticate。
timeoutMs否单次 authenticate 调用超时(毫秒),不能是 0。它替换默认的 30 秒,但同样被 30 秒硬顶封住;与 resolvers[0].timeoutMs 是相互独立的预算。

声明 auth.entry 时 permissions 必须包含 "auth",否则校验失败。五个登录 action 与 flux.auth 的完整接口见账号登录指南与 API 参考。

main

{ "main": { "entry": "main.js", "events": ["onDone"], "background": false } }
字段必填规则
entry是classic 脚本(不是 ES module),包内安全相对路径,扩展名 .js,文件必须存在。
events否投递给脚本的事件,每项须是 onStart、onError、onDone、onMetaProbed、onCancel 之一。
background否布尔值。true 表示脚本随 daemon 常驻,需要 background 权限。

main 是插件的常驻部分:可移植视图、命令与后台逻辑都在 daemon 内的这个脚本里运行。见可移植界面指南。

contributes

{
  "contributes": {
    "views": [{ "id": "dashboard", "title": "看板", "location": "activity",
                "desktop": { "entry": "ui/dashboard.js", "networkHosts": ["api.example.com"] },
                "portable": true }],
    "commands": [{ "id": "refresh", "title": "刷新", "palette": true, "taskMenu": false }],
    "sniffRules": "rules.json"
  }
}

views / commands 为空数组等同于不声明。

views[]

至多 16 项。

字段必填规则
id是匹配 ^[a-z][a-z0-9-]{0,31}$,在视图间唯一。
title是1–40 个字符。
location是activity、settings、taskDetail 或 window;settings 至多一个视图使用。
desktop否桌面原生实现,见下。没有 desktop 的视图是可移植视图,要求声明 main。
portable否布尔值,默认 false。仅允许与 desktop 同时声明。true 表示 main 也通过 flux.ui.view 实现了该视图,让不能运行桌面脚本的客户端也能显示;要求声明 main。

视图在哪里显示,取决于它的声明方式:

声明方式桌面(GPUI)Web移动端
无 desktop(可移植)由 main 渲染由 main 渲染不渲染任何内容
desktop + portable: true运行桌面原生脚本;若本机读不到该插件的脚本包,则回退到 main 的实现由 main 渲染不渲染任何内容
仅 desktop运行桌面原生脚本;若本机读不到该插件的脚本包(例如 daemon 在远端主机上),GPUI 客户端显示「仅桌面端可用」提示完全不显示——Web 只列出可移植视图,不会出现「仅桌面端可用」卡片不渲染任何内容

想让同一个视图在 Web 上也可用,就声明 portable: true,把桌面脚本当作增强。节点树与事件的细节见视图参考与可移植界面指南;原生脚本见桌面原生界面指南。

views[].desktop

字段必填规则
entry是界面脚本,包内安全相对路径,扩展名只能是 .js 或 .mjs,文件必须存在于包内。
width / height否初始窗口尺寸(逻辑像素),仅当视图的 location 为 window 时允许。有限数,范围 [240, 4096]。
networkHosts否界面脚本可连接的网络主机,每个视图至多 64 项;每项须是小写 DNS 主机名(RFC 1123 标签,总长不超过 253 个字符)或 IP 字面量,不带 scheme、端口、路径或通配符,且在视图内不得重复。

任何带 desktop 的视图都要求声明 desktop 权限。desktop.entry 是由 GPUI 桌面客户端内置的 GPUI Shell 执行的 ES module(可 import 自 "gpui-kit"、"gpui-base"、"gpui-component")。引擎和 daemon 从不编译或执行它,只校验路径并确认文件存在。Web 与移动端不会运行它。

networkHosts 是强制生效的白名单:脚本的网络访问被限制在这些主机名之内。文件系统、进程与剪贴板访问则不受限制——脚本在桌面进程内以完全信任运行,请只安装你信任的插件,见权限。所有视图 networkHosts 的并集会显示在授权对话框里,是你批准内容的一部分。

commands[]

至多 32 项,要求声明 main。

字段必填规则
id是规则同视图 id,在命令间唯一。
title是1–40 个字符。
palette否布尔值。是否出现在命令面板。
taskMenu否布尔值。是否出现在任务右键菜单。

sniffRules

浏览器扩展嗅探规则包的 .json 安全相对路径,需要 browserRules 权限。

  • 文件必须存在于包内、不超过 256 KiB、能解析为 JSON,且含 "format": "fluxdown-sniff-pack" 与一个至多 500 条的 rules 数组。
  • 每条规则的格式见嗅探规则参考。

最小可用的 rules.json:

{
  "format": "fluxdown-sniff-pack",
  "packId": "acme-demo",
  "sequence": 1,
  "engine": 1,
  "issuedAt": "2026-10-17T00:00:00Z",
  "rules": [
    {
      "id": "video",
      "version": 1,
      "tier": "plugin",
      "stage": "response",
      "match": { "host": ["acme.example"], "ext": ["xyz"] },
      "action": { "kind": "resource", "type": "video" }
    }
  ]
}

规则如何落到浏览器扩展里:

  • 只有已启用、已加载且已授权的插件,其规则才会提供给扩展。停用插件,或有新增权限尚未批准时,它的规则会随之消失。
  • 每条规则的 id 会被改写为 plugin.<identity 中的 @ 换成 .>.<你写的 id>——上面的规则会变成 plugin.acme.demo.video。改写后超过 64 个字符的规则会被丢弃(并记日志)。
  • 无论你写什么,tier 都会被强制为 plugin。不是对象或缺少字符串 id 的规则会被丢弃。
  • 插件规则的优先级低于 local 和 official,高于 community 与 built-in。

permissions

插件主动申请的额外宿主能力。为空或省略 = 基础沙箱(flux.fetch 网络、flux.storage、日志、视图与命令贡献)。识别以下值:

值授予
ffmpegflux.ffmpeg / flux.ffprobe 接口——对下载完成的文件运行解析到的 ffmpeg。
ytdlpflux.ytdlp 接口——在 resolve 或任意 hook 里运行解析到的 yt-dlp。
auth声明 auth.entry 平台登录入口,并允许 flux.fetch / flux.auth 使用认证凭据。
tasksflux.tasks.*——允许在 main 中操作下载任务。
browserRules允许通过 contributes.sniffRules 贡献浏览器扩展嗅探规则。
desktop允许桌面脚本(contributes.views[].desktop)在桌面进程内以完全信任运行。
background允许 main 随 daemon 常驻(main.background)。

未知值会让整份 manifest 失败。风险等级、授权与审计见权限、授权与审计;各权限背后的接口见 API 参考。

某些声明要求同时具备对应权限:

声明要求
auth.entryauth
main.background: truebackground
任一 views[].desktopdesktop
contributes.sniffRulesbrowserRules

(tasks、ffmpeg、ytdlp 在调用接口时才检查,manifest 校验不要求。)

settings[]

每一项描述一个字段,应用据此在桌面与 Web 生成表单。key 必须唯一且非空。

{
  "settings": [
    { "key": "apiToken", "title": "API token", "type": "string", "widget": "password", "required": true },
    { "key": "quality",  "title": "偏好画质", "type": "string", "widget": "select",
      "options": [ { "value": "hd", "label": "高清" }, { "value": "sd", "label": "标清" } ],
      "default": "hd" },
    { "key": "maxRetries", "title": "最大重试次数", "type": "number", "min": 0, "max": 10, "default": "3" },
    { "key": "verbose", "title": "详细日志", "type": "boolean", "default": "false" }
  ]
}
字段必填说明
key是插件内唯一,不能为空。脚本里用 flux.settings.<key> 读回。
title是表单标签。
description否字段下方的说明文字。
type是string、number 或 boolean。
widget否缺省按 type 推导:string→text,number→number,boolean→toggle。
options仅 select非空的 { "value", "label" } 列表。
default否一律写成字符串,number 也是("3"),boolean 只能是 "true" / "false"。select 的 default 必须是某个 option 的 value;number 的 default 必须能解析为有限数且落在 min / max 区间内。
required否布尔值。表单拒绝保存空值。
min / max仅 number有限数、闭区间、min ≤ max。其他类型写了会被拒绝。
pattern仅 stringJavaScript RegExp 语法(不是 Rust regex),保存时对值做校验。安装时会编译一次,不合法的 pattern 会导致安装被拒绝。
helperScript仅 string一段脚本,宿主会在该字段旁渲染一个复制按钮。宿主只把脚本文本复制到剪贴板,绝不执行。用户把它粘贴到目标站点的浏览器开发者工具 Console 中运行(典型用途:读取 document.cookie 并把登录 Cookie 复制回该字段)。须非空、不超过 4 KB。
helperLabel与 helperScript 同时出现按钮文案,不超过 60 个字符。只能与 helperScript 一起使用;省略时用通用文案「复制获取脚本」。

widget × type 合法矩阵

只有这些组合合法,其余组合校验失败:

widget允许的 type
text、password、textarea、folder、selectstring
toggleboolean
numbernumber

URL pattern 规则

pattern 不是正则,也不是浏览器扩展的 match pattern。完整规则:

  • * 是唯一通配符,匹配任意长度的字符(包括 / 和 :)。
  • 各个 * 之间的文本必须按顺序作为子串出现。
  • pattern 不以 * 开头时,首段必须是 URL 的前缀。
  • 不以 * 结尾时,末段必须是后缀。
  • 匹配不区分大小写(两侧整体转小写后比较,路径也一样)。
  • 单独一个 * 匹配一切。

以 https://www.youtube.com/watch?v=abc 为例:

pattern结果
*://www.youtube.com/watch*匹配
https://x.com/a(无通配符)只做精确匹配
*://x.com/* 对 https://y.com/a不匹配——找不到 ://x.com/
youtube.com/*不匹配——首段前缀锚定,而 URL 以 https:// 开头

校验

manifest 在安装时和每次加载时都会校验。校验失败的插件不会运行:它在列表里显示为失败并带有原因,原因同时写入日志(见调试与排错)。

检查顺序

identity 格式 → name 非空 → version 格式 → minAppVersion 格式 → icon 路径安全 → resolver 至多一个 → resolver 的 entry 路径、match.urls 非空、timeoutMs ≠ 0 → subscription 至多一个 → providerId 格式且不是 rss、entry 路径、timeoutMs ≠ 0 → hooks 的 entry 路径、events 非空且合法、match.urls(如有)非空 → auth 的 entry 路径、含 auth 权限、timeoutMs ≠ 0 → main(路径、.js、events 合法、background 权限)→ contributes(视图与命令的数量上限和 id、settings 位置至多一个、portable 只能与 desktop 同时声明、可移植视图与命令要求 main、desktop 的入口 / 尺寸 / 主机规则、desktop 权限、sniffRules 的路径 / 扩展名 / browserRules 权限)→ settings(key 唯一、逐字段规则与 widget × type 矩阵)→ permissions 全为已知值。

必须存在的文件

安装与加载时,包内必须有每个已声明的入口脚本(resolvers[0].entry、hooks.entry、auth.entry、subscriptions[0].entry、main.entry)、每个 desktop.entry 以及 sniffRules 指向的文件(后者还要通过上文的内容检查)。缺失时的错误是「包内缺少文件 ‘<路径>’」;入口脚本存在但无法按 UTF-8 文本读取时,错误是「读取入口文件 ‘<路径>’ 失败: …」。开发模式插件同样适用:插件会被列为加载失败,而不是等到第一次调用时才出错。

安装时的编译检查

语法错误会在安装时当场拒绝,而不是等到第一次下载才发现。编译检查覆盖 resolver、hooks、auth、subscription 四类入口脚本(按声明的种类,最多四个),不包括 main(按需编译)和桌面脚本(引擎从不编译)。每个 settings[].pattern 也会在安装时按 JavaScript RegExp 编译一次,不合法则拒绝安装。

安装失败不会留下任何改动:首次安装不会留下目录,更新则恢复到旧版本。

发布前自检

fluxdown-plugin validate 会运行同样的规则,并加上更严格的上架检查(严格版本号格式、所有入口与图标必须在包内、条目与体积上限)。见 SDK 参考;发布前手动校验的做法见打包。

这页文档有问题?

页面
操作
↑↓选择打开