插件开发任务流
插件开发任务流
从一个 CoreBox 指令到可发布插件包的最短稳定路径。
适用场景
这份任务流适合开发 index.js 先导脚本插件:在 CoreBox 中声明指令、返回搜索结果、执行复制/打开/导入等动作,然后通过 TUFF CLI 构建 .tpex 并发布到 Nexus。
如果你的插件需要重量级 Vue/React UI,仍然沿用同一条任务流:Manifest 声明能力,index.js 负责轻量注册与调度,Surface/UI 只在需要时加载。
1. Manifest
新插件优先使用 main: "index.js",并声明最新 sdkapi、分类、权限和权限用途。
{
"id": "com.example.quick-note",
"name": "quick-note",
"version": "0.1.0",
"author": "Example",
"sdkapi": 260626,
"category": "productivity",
"description": "Save selected text as a quick note.",
"main": "index.js",
"permissions": {
"required": ["clipboard.read"],
"optional": ["clipboard.write"]
},
"permissionReasons": {
"clipboard.read": "读取剪贴板中的文本作为笔记内容",
"clipboard.write": "将处理后的笔记内容复制回剪贴板"
},
"features": [
{
"id": "quick-note.save",
"name": "保存快速笔记",
"desc": "将输入或剪贴板文本保存为笔记",
"keywords": ["note", "memo", "笔记"],
"push": true,
"acceptedInputTypes": ["text"],
"commands": [
{ "type": "over", "value": ["note", "memo", "笔记"] }
]
}
]
}
检查点:
sdkapi >= 260114时必须声明category。permissions.required只放启动或核心路径必需的权限。- 每个非自动授权权限都要写
permissionReasons,便于用户理解授权原因。
2. Prelude
index.js 是插件的先导脚本。它运行在 Node.js 沙箱中,通过 globalThis 获取插件上下文。
const { clipboard, logger, box, TuffItemBuilder } = globalThis
const PLUGIN_NAME = 'quick-note'
const COPY_ACTION_ID = 'copy'
function getQueryText(query) {
return typeof query === 'string' ? query : query?.text ?? ''
}
function buildCopyItem(text) {
return new TuffItemBuilder('quick-note.copy')
.setSource('plugin', 'plugin-features', PLUGIN_NAME)
.setTitle('复制笔记内容')
.setSubtitle(text.slice(0, 80))
.setMeta({
pluginName: PLUGIN_NAME,
defaultAction: COPY_ACTION_ID,
})
.createAndAddAction(COPY_ACTION_ID, 'copy', '复制', text)
.build()
}
module.exports = {
async onFeatureTriggered(featureId, query, feature, signal) {
const text = getQueryText(query).trim()
if (!text) {
return []
}
logger.info('feature triggered', { featureId, featureName: feature?.name })
return [buildCopyItem(text)]
},
async onItemAction(item) {
const action = item.actions?.find(action => action.id === COPY_ACTION_ID || action.type === 'copy')
if (action?.payload) {
clipboard.writeText(action.payload)
}
box?.hide?.()
},
## }
检查点:
- 不再使用旧的
init(ctx)模型。 - 处理
query时兼容字符串和TuffQuery对象。 - 搜索结果用
TuffItemBuilder,并在meta中写清pluginName和默认动作。 - 异步搜索、网络请求或长任务要尊重
signal,便于 CoreBox 取消过期请求。
3. 选择 SDK
优先按任务选择最小 SDK,不要为了“以后可能会用”提前申请权限。
| 任务 | 推荐入口 | 说明 |
|---|---|---|
| CoreBox 搜索结果 | TuffItemBuilder、Feature SDK | 先导脚本内直接构建 item;复杂动态列表再使用 Feature SDK。 |
| 剪贴板 | clipboard 或 useClipboard() | 写剪贴板通常为低风险;读剪贴板要声明 clipboard.read 并按需请求。 |
| 插件配置 | storage 或 usePluginStorage() | 适合普通设置和非敏感数据,每插件有容量限制。 |
| 插件结构化数据 | usePluginSqlite() | 需要 sdkapi >= 260215 和 storage.sqlite。 |
| Secret | usePluginSecret() | API Key、Token、Provider secret 不允许明文写入 JSON、localStorage 或日志。 |
| 用户私有同步 | CloudSyncSDK | 走 /api/v1/sync/*,同步载荷必须使用密文 payload_enc 或 payload_ref。 |
| 内容包分享 | CloudShareSDK | 用于公开或团队可见的插件内容包,例如 snippet pack;不要混作用户私有同步。 |
| 传输通道 | typed transport SDK | 新能力优先走 typed transport,不新增 raw IPC 依赖。 |
| AI 能力 / Command | intelligence | 声明 intelligence.basic,先发现 capability health;每个命令的模板走 typed invoke options,不新增 raw IPC。 |
实现 Raycast 风格 AI Command 时,命令定义保留在插件内,provider 选择、审计、配额与 fallback 交给宿主:
const { intelligence } = globalThis
async function runRewriteCommand(text, tone) {
const status = await intelligence.getCapabilityStatus({ capabilityId: 'text.chat' })
if (!status.available) throw new Error(status.reason || 'AI 不可用')
return intelligence.text.chat(
{ messages: [{ role: 'user', content: text }] },
{
promptTemplate: '请以{{tone}}语气改写输入,只返回改写后的文本。',
promptVariables: { tone },
},
)
## }
命令、prompt variables、插件存储和日志都不得保存 provider credential。typed contract 见 Intelligence SDK。
4. 安全插件视图
插件 BrowserWindow 与 WebContentsView 要求 sdkapi >= 260615,并统一使用 Tuff 内置 preload。渲染页只会获得冻结的 $plugin、$config、$channel;可通过 $plugin.bridgeVersion 检测 bridge 能力版本。
不再支持自定义 preload、<webview>、渲染进程 require / process / Electron、生产远程 URL、popup 或下载。提高 SDK marker 前必须移除这些依赖。不兼容 surface 会在创建窗口前返回 PLUGIN_WINDOW_LEGACY_RUNTIME_UNSUPPORTED,且没有环境变量兼容开关。
5. 验证
发布前至少完成这些本地检查:
tuff validate --strict
tuff build
tuff publish --dry-run
仓库内改动还应补最近路径验证,例如 focused Vitest、文件级 ESLint 和 git diff --check。不要把全仓历史 lint 噪声当作当前插件改动失败。
6. 构建与发布
常规发布路径:
tuff login
tuff validate --strict
tuff build
tuff publish --dry-run
tuff publish --tag 0.1.0 --channel BETA
发布注意事项:
tuff build会生成dist/build/和.tpex插件包。tuff publish --dry-run只做本地预览,不上传。- Nexus API Key 至少需要
plugin:publish,当前发布预检会按 publisher 权限校验;plugin:publish口径包含发布前读取插件信息所需的能力。 - 如果 Nexus 拒绝 CLI Token,先执行
tuff login刷新浏览器授权会话,或检查 API Key scopes。
7. 插件包与内容包边界
插件包发布和插件内容包发布是两条不同链路:
- 插件包:发布
.tpex,更新插件代码、Manifest、资产和 Surface。 - 内容包:发布插件可导入的数据,例如
touch-snippets的tuff.snippet-pack+json。 - CloudSync:同步用户自己的加密业务数据,不用于公开市场内容。
- CloudShare:分发可安装内容包,内容应经过敏感信息过滤与目标插件校验。
当插件同时支持“同步”和“分享”时,建议把同步数据结构、分享数据结构、导入合并逻辑拆开维护,避免把用户私有数据直接发布成公开内容。