插件开发
私有阁插件采用 Worker + 沙箱 UI 分离架构,四层安全约束模型(声明 → 授权 → 校验 → 隔离),支持 JavaScript / TypeScript / Vue 3 / React 多种技术栈模板,内置权限声明、文件读写、网络请求(域名白名单)、剪贴板、通知推送、多窗口管理等丰富 API。
私有阁插件由独立 Worker 和可选的沙箱 UI 组成,使用 manifest.json 声明身份、入口、窗口、命令、激活事件和最小权限。最终发布物是 .lyp 文件,可在客户端“应用中心 → 开发中”加载和测试。工具链、规范和示例随源码包 plugin-toolkit/ 目录一并交付。
环境与创建
需要 Node.js 20 或更高版本,以及 npm、pnpm、yarn 或 bun 中任意一种包管理器。最简单的创建方式:
npm create siyouge-plugin@latest my-plugin
cd my-plugin
npm install
npm run doctor
npm run dev
可用模板:
| 模板 | 场景 |
|---|---|
vanilla-js | 原生 JavaScript Worker + UI |
vanilla-ts | 原生 TypeScript,适合入门 |
vue-ts | Vue 3 + TypeScript,推荐 UI 插件 |
react-ts | React + TypeScript |
worker-only | 只提供后台命令,无 UI |
custom | 自定义构建系统,输出标准产物 |
直接创建 Vue 插件:
npm create siyouge-plugin@latest my-plugin -- --template vue-ts
项目结构
my-plugin/
├─ package.json
├─ manifest.json
├─ siyouge.plugin.config.mjs
├─ src/
│ ├─ worker.ts
│ └─ ui/
│ ├─ index.html
│ └─ main.ts
├─ dist/
│ ├─ worker.js
│ └─ ui/index.html
└─ artifacts/
- Worker 是后台逻辑与命令入口。
- UI 运行在隔离页面,通过 SDK Bridge 调用宿主能力。
dist是开发模式加载的构建结果。artifacts存放最终.lyp包和发布元数据。
Manifest
最小示例:
{
"manifestVersion": 1,
"apiVersion": 1,
"id": "com.example.my-plugin",
"name": "My Plugin",
"version": "1.0.0",
"publisher": "Example",
"description": "一个私有阁插件示例。",
"engines": { "siyouge": ">=0.1.0" },
"main": "dist/worker.js",
"windows": [
{
"id": "main",
"entry": "dist/ui/index.html",
"primary": true,
"title": "My Plugin",
"decorations": { "mode": "siyouge", "tabs": false },
"size": { "width": 900, "height": 650, "minWidth": 640, "minHeight": 480 },
"behavior": { "resizable": true, "center": true, "singleton": true, "persistBounds": true }
}
],
"activationEvents": ["onCommand:com.example.my-plugin.run"],
"contributes": {
"commands": [{ "command": "com.example.my-plugin.run", "title": "运行插件" }]
},
"permissions": [{ "name": "storage" }]
}
新插件使用 windows,不要继续使用旧版 ui 字段。插件 ID 发布后必须保持不变;所有入口必须是插件根目录内的安全相对路径,不能包含 ..、绝对路径或 Windows 盘符。
权限模型
私有阁插件权限由四层共同约束:
1. Manifest 声明:插件先在 permissions 中声明所需能力。
2. 安装授权:安装 .lyp 时,客户端向用户展示权限并由用户确认。
3. 逐次校验:每次受保护调用都由 Rust 从插件注册表重新检查插件是否安装、启用并获得授权;插件不能自行传入授权列表绕过检查。
4. 运行隔离:Worker 和沙箱 UI 不能直接访问 Tauri IPC、Node.js、登录 Token、客户端 Store 或任意本地路径。
权限应按实际调用最小化声明。多声明权限不会自动获得能力,漏声明则会得到 PLUGIN_PERMISSION_DENIED 等错误。开发目录与正式安装包执行相同的权限检查。
一个同时读取用户资料、保存设置并请求远程 API 的插件可以这样声明:
"permissions": [
{ "name": "storage" },
{ "name": "user.profile.read" },
{
"name": "network.fetch",
"scope": { "hosts": ["https://api.example.com"] }
}
]
network.fetch 必须声明精确的 HTTPS origin。插件原生 fetch、XHR、WebSocket 和 EventSource 均被禁用,远程请求必须通过宿主 API;请求不能访问未列入 scope.hosts 的地址,也不能用宽泛通配符代替精确域名。
权限清单
Manifest v1 当前支持以下权限:
| 权限 | 对应能力 | Worker | UI | 关键限制 |
|---|---|---|---|---|
storage | api.storage.* | 是 | 是 | 数据按插件和用户隔离;单值最大 256 KB |
network.fetch | api.network.fetch | 是 | 是 | 必须配置精确 HTTPS scope.hosts;请求和响应有大小限制 |
user.profile.read | api.user.getCurrent() | 是 | 是 | 只返回用户资料副本,不返回 Token 或客户端 Store |
notification.show | api.notifications.show() | 是 | 是 | 不应在通知中暴露敏感数据或频繁打扰用户 |
files.read | 文件选择、读取文件及插件数据目录 | 插件数据目录 | 是 | UI 选择器返回插件绑定的不透明句柄;不能传绝对路径 |
files.write | 保存文件及写入插件数据目录 | 插件数据目录 | 是 | 安全相对路径;单文件当前最大 10 MB |
clipboard.read | api.clipboard.readText() | 否 | 是 | 敏感能力,只应由明确的用户操作触发 |
clipboard.write | api.clipboard.writeText() | 否 | 是 | 只能从插件 UI 调用 |
window.open | api.windows.open(windowId) | 否 | 是 | 只能打开本插件 Manifest 已声明的窗口 |
window.alwaysOnTop | api.window.setAlwaysOnTop() | 否 | 是 | Manifest 窗口声明置顶时也必须申请 |
external.open | api.external.openUrl() | 否 | 是 | 只允许 https: 和 mailto:,不能启动本地程序或 Shell |
当前插件窗口的关闭、最小化、最大化、聚焦、居中、调整尺寸和设置标题只影响调用者自己的窗口,因此不需要额外权限。跨窗口打开和置顶需要上表中的专门权限。
api.app.*、api.system.*、api.events.*、api.log.* 和 UI 调用本插件已声明命令等基础能力不需要 Manifest 权限;这不代表插件可以访问客户端内部状态,这些 API 只返回宿主明确暴露的数据。
文件权限不能让插件读取任意磁盘路径。UI 文件选择器返回与插件绑定的不透明句柄;Worker 只能读写自己的插件数据目录。..、绝对路径、跨插件句柄和���权窗口 ID 都会被拒绝。
Worker 与 UI
Worker 注册命令:
siyouge.register({
activate: (api) => api.log.info('plugin activated'),
commands: {
async 'com.example.my-plugin.run'(args, api) {
await api.storage.set('lastArgs', args)
return { ok: true }
}
},
deactivate: () => undefined
})
UI 调用宿主 API:
import { getSiyougeApi } from '@siyouge-im/plugin-sdk'
const api = getSiyougeApi()
const appearance = await api.system.getAppearance()
await api.storage.set('opened', true)
document.documentElement.dataset.theme = appearance.resolvedTheme
完整接口与限制以源码包 plugin-toolkit/docs/API.md、plugin-toolkit/docs/SPECIFICATION.md 和 SDK 类型定义为准。UI 不应依赖外链 CDN、外链字体、服务端直连或必须同源的路由模式;构建器会把标准 UI 打成包含脚本和样式的单 HTML。
客户端调试
保持 npm run dev 监听构建,然后:
1. 打开私有阁“应用中心 → 开发中”。
2. 开启开发者模式。
3. 点击“加载本地目录”。
4. 选择包含 manifest.json 的项目根目录。
5. 修改代码并等待构建完成。
6. 在开发插件卡片点击“重新加载”。
客户端只记录开发目录,不会复制或删除你的源码。调试主题、语言和事件订阅时,记得在 Vue onUnmounted 或 React effect cleanup 中取消订阅。
校验与打包
npm run build
npm run validate
npm run pack
默认包名:
artifacts/<plugin-id>-<version>.lyp
检查安装包:
npx siyouge-plugin inspect ./artifacts/com.example.my-plugin-1.0.0.lyp
pack 会生成同名 .release.json,包含包的 SHA-256、可选 Ed25519 签名和权限数组。签名需要通过 SIYOUGE_PLUGIN_PRIVATE_KEY 提供私钥;不要将私钥泄露给他人。
安装与发布
在“应用中心 → 开发中”选择“安装本地插件包”,安装刚生成的 .lyp,逐项验证:
1. 安装时展示的权限与 Manifest 一致。
2. 页面、命令、Worker 激活与持久化正常。
3. 深浅主题和中英文切换正常。
4. 停用、启用、升级、卸载以及保留/删除数据行为正确。
5. .lyp 的下载地址使用 HTTPS,市场记录中的 SHA-256 与打包输出一致。
远程发布前提升 SemVer,但保持插件 id 不变。开发目录测试不能替代真实 .lyp 安装测试。
工具链与 SDK
需要扩展官方工具链或开发官方级插件时,直接使用源码包内的 plugin-toolkit/ 工程:
使用官方脚手架创建插件项目
cd siyouge-plug-in
pnpm install
pnpm check
pnpm contract:client
pnpm packages:pack
pnpm plugins:validate
pnpm plugins:pack
- npm 工具包输出到
artifacts/npm。 - 官方
.lyp输出到artifacts/official。 - 官方插件源码位于
plugins/official,实验插件位于plugins/labs。 - 最完整、更新最及时的维护说明见
plugin-toolkit/docs/DEVELOPMENT.md。