@xiongtao.xt/tau-plugins 0.23.1 → 0.25.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/API.md CHANGED
@@ -2,11 +2,11 @@
2
2
 
3
3
  本文介绍编写命令、页面、侧边栏、设置、数据存储、网络请求和摘录所需的接口。插件接口从 `@tau/plugins` 导入,界面组件从 `@tau/ui` 导入。所述能力需要 Tau 0.59.0 或更高版本。
4
4
 
5
- 本文对应 SDK 0.23.1;最低客户端版本只负责版本门槛,使用新领域接口时仍需包含对应实现的客户端构建。SDK 升级步骤见 [DEVELOPMENT.md](DEVELOPMENT.md#升级-sdk-与同步旧模板)。
5
+ 本文对应 SDK 0.25.0;最低客户端版本只负责版本门槛,使用新领域接口时仍需包含对应实现的客户端构建。SDK 升级步骤见 [DEVELOPMENT.md](DEVELOPMENT.md#升级-sdk-与同步旧模板)。独立后台与双入口构建见 [BACKEND.md](BACKEND.md)。
6
6
 
7
7
  博客发布提供方见 [PUBLISH.md](PUBLISH.md),需要 SDK 0.21.0 和包含该能力的客户端;独立博客站点读取数据见 [PUBLISH-DATA.md](PUBLISH-DATA.md)。
8
8
 
9
- 想在文档里添加自定义块,见 [EDITOR.md](EDITOR.md);需要编译 Tailwind、Typst、Markdown 等内容,见 [SERVICES.md](SERVICES.md)。
9
+ 想在文档里添加自定义块,见 [EDITOR.md](EDITOR.md);需要编译 Tailwind、Markdown 等内容,见 [SERVICES.md](SERVICES.md)。
10
10
 
11
11
  AI 功能声明、用户模型分配、流式对话、取消与统一用量审计见 [AI.md](AI.md)(SDK 0.7.0 及以上)。
12
12
 
@@ -26,7 +26,7 @@ AI 功能声明、用户模型分配、流式对话、取消与统一用量审
26
26
 
27
27
  聊天工具自定义渲染使用 `await context.chat.registerToolRenderer`,数据与生命周期见 [MCP.md](MCP.md#聊天工具自定义渲染)。类型契约自 SDK 0.15.0 提供,需要包含实现的客户端构建。
28
28
 
29
- 阅读顺序:先了解[入口与生命周期](#入口与生命周期),再按功能查阅[命令](#命令)、[存储](#私有存储)、[页面](#页面)、[设置](#设置)、[网络请求](#网络请求)、[摘录](#摘录)、[材料动作](#材料动作)和[资源清理](#资源清理)。完整可运行示例见 `src/index.tsx`。
29
+ 阅读顺序:先了解[入口与生命周期](#入口与生命周期),再按功能查阅[命令](#命令)、[存储](#私有存储)、[页面](#页面)、[设置](#设置)、[网络请求](#网络请求)、[摘录](#摘录)、[材料动作](#材料动作)、[业务数据](#业务数据)、[关联已有内容](#关联已有内容)和[资源清理](#资源清理)。完整可运行示例见 `src/index.tsx`。
30
30
 
31
31
  ## manifest
32
32
 
@@ -38,13 +38,14 @@ AI 功能声明、用户模型分配、流式对话、取消与统一用量审
38
38
  | `name` | `我的计数器` | 插件管理中显示的名称 |
39
39
  | `version` | `1.0.0` | 插件版本,格式为 `主版本.次版本.修订号`,也可带 `-beta.1` 等预发布后缀 |
40
40
  | `minAppVersion` | `0.59.0` | 所需最低 Tau 客户端版本,不是 SDK 版本;低于要求时不加载 |
41
- | `formatVersion` | `1` | 包格式版本,保留模板值 |
41
+ | `formatVersion` | `1` 或 `2` | 普通渲染插件为 1;声明独立后台时为 2,见 BACKEND.md |
42
42
  | `apiVersion` | `1` | API 协议版本,保留模板值 |
43
43
  | `platform` | `desktop` | 运行平台,保留模板值 |
44
44
  | `format` | `cjs` | 产物格式,保留模板值 |
45
45
  | `main` | `main.js` | 构建入口,保留模板值 |
46
46
  | `categories` | `["page"]` | 插件中心分类,可多选 |
47
47
  | `keywords` | `["计数", "打卡"]` | 搜索关键词,最多 5 个,每个不超过 20 字 |
48
+ | `permissions` | `["cards:read"]` | 可申请的业务数据权限,见[业务数据](#业务数据);不声明的接口无法调用 |
48
49
 
49
50
  分类值为 `page`、`editor`、`whiteboard`、`sync`、`upload`、`voice`、`ai`、`video`、`blog-publish`、`blog-template`。仅提供命令或普通设置时可以省略;分类不授予权限。声明静态编辑器、白板扩展时分别设置 `editor: true`、`whiteboard: true`,详见对应文档。
50
51
 
@@ -103,6 +104,13 @@ export default definePlugin({
103
104
  | `signal` | `AbortSignal` | 本次运行结束时中止,可用于停止异步工作 |
104
105
  | `commands` | `PluginContext["commands"]` | 注册命令 |
105
106
  | `storage` | `PluginStorage` | 保存当前插件在当前工作区的数据 |
107
+ | `permissions` | `PluginPermissions` | 查询与申请业务数据权限 |
108
+ | `data` | `PluginData` | 读取与修改卡片、文章、项目文档、待办和日程 |
109
+ | `content` | `PluginContent` | 读取与修改正文 |
110
+ | `events` | `PluginEvents` | 订阅业务数据变化 |
111
+ | `app` | `PluginApp` | 选择、打开已有内容,执行导出、演示和共享 |
112
+ | `entities` | `PluginOwnEntities` | 管理插件自有内容、类型和反链 |
113
+ | `backend` | `PluginBackendClient` | 调用与订阅本机后台,见 BACKEND.md |
106
114
  | `pages` | `PluginPages` | 注册并打开页面 |
107
115
  | `settings` | `PluginSettings` | 注册并打开设置区域 |
108
116
  | `editor` | `PluginEditor` | 开发台编辑器贡献;正文使用静态扩展,见 EDITOR.md |
@@ -159,7 +167,7 @@ context.commands.register({
159
167
  | `storage.set(key, value)` | `Promise<void>` | 保存或替换该 key 的值 |
160
168
  | `storage.delete(key)` | `Promise<void>` | 删除该 key |
161
169
 
162
- 数据按插件与工作区隔离,停用和重启不会删除。同一个插件在不同工作区读到不同数据;升级时保持插件 ID 不变,并自行处理旧数据结构。
170
+ 数据按插件与工作区隔离,保存在当前笔记库里并随云同步,换一台设备也能读到;停用和重启不会删除。同一个插件在不同工作区读到不同数据;升级时保持插件 ID 不变,并自行处理旧数据结构。单个值不超过 1 MB,较大的内容应放进插件内容的正文。
163
171
 
164
172
  `JsonValue` 支持 `null`、布尔值、字符串、有限数字、这些值组成的数组和普通对象。不能存储 `undefined`、函数、Date、Map、循环引用、NaN 或 Infinity;日期应转换为字符串。
165
173
 
@@ -488,6 +496,277 @@ function NoteActions({ context, target }: { context: PluginContext; target: Plug
488
496
  - 每个动作有 `id`、`label` 和 `run()`。直接用 `label` 作为按钮文字,不要按 `id` 写死列表:Tau 以后可能增加新的动作。
489
497
  - 动作执行后由 Tau 自己给出成功或失败提示,插件不需要再弹提示。
490
498
 
499
+ ## 业务数据
500
+
501
+ 插件可以读取和修改用户的卡片、文章、项目文档、待办和日程,类型契约自 SDK 0.25.0 提供,需要包含此能力的客户端。
502
+
503
+ ### 声明与授权
504
+
505
+ 先在 manifest 中声明需要的权限,这是插件能申请的上限:
506
+
507
+ ```json
508
+ { "permissions": ["cards:read", "todos:read"] }
509
+ ```
510
+
511
+ | 权限 | 可调用的接口 |
512
+ | ------------------------------------------------------ | --------------------------------------------------------------- |
513
+ | `cards:read` / `articles:read` / `documents:read` | 对应实体的 `get`、`query`,以及其正文的 `content.read` |
514
+ | `cards:write` / `articles:write` / `documents:write` | `create`(项目文档没有)、`update`,以及其正文的 `content.edit` |
515
+ | `cards:delete` / `articles:delete` | `delete` |
516
+ | `todos:read` / `todos:write` / `todos:delete` | `context.data.todos` 的查询、新建与修改、删除 |
517
+ | `calendar:read` / `calendar:write` / `calendar:delete` | `context.data.calendar` 的查询、新建与修改、删除 |
518
+
519
+ 插件第一次调用某个接口时,Tau 会弹窗询问用户:「仅这次」在插件停用或应用重启前有效,「始终允许」保存到用户撤销为止,「拒绝」在本次运行内不再询问,连续拒绝 3 次后转为已阻止。应用不在前台时不弹窗,请求会记在插件详情页的「权限」中等待用户处理。用户可以随时在那里撤销。
520
+
521
+ 未声明、被拒绝或无法询问时,接口抛出 `PluginPermissionError`,`reason` 依次为 `undeclared`、`denied` / `blocked`、`unavailable`。建议在页面中处理这个错误,显示说明和重试按钮,而不是在启动时连续申请:
522
+
523
+ ```tsx
524
+ import { PluginPermissionError } from "@tau/plugins";
525
+
526
+ try {
527
+ setCards((await context.data.cards.query({ limit: 20 })).items);
528
+ } catch (error) {
529
+ if (error instanceof PluginPermissionError) setNeedsPermission(true);
530
+ else throw error;
531
+ }
532
+ ```
533
+
534
+ 也可以在用户点击按钮时一次申请多项:`await context.permissions.request(["cards:read", "todos:read"])`,全部获准时返回 `true`。`context.permissions.query(permission)` 返回 `granted`、`prompt` 或 `denied`,不会弹窗。
535
+
536
+ 插件与 Tau 运行在同一环境中,权限约束的是这些接口,不是安全隔离。获准读取的内容可以通过 `context.fetch` 发送出去,用户界面会如实提示这一点;请只读取功能需要的数据。
537
+
538
+ ### 读取实体
539
+
540
+ ```ts
541
+ const weekAgo = Date.now() - 7 * 86_400_000;
542
+ const page = await context.data.cards.query({ updatedAfter: weekAgo, limit: 50 });
543
+ for (const card of page.items) console.log(card.title, card.tags);
544
+ if (page.cursor) await context.data.cards.query({ updatedAfter: weekAgo, cursor: page.cursor });
545
+
546
+ const article = await context.data.articles.get(id); // 不存在时为 null
547
+ ```
548
+
549
+ `cards`、`articles`、`documents` 都提供 `get(id)` 与 `query(query)`。查询参数:
550
+
551
+ | 字段 | 说明 |
552
+ | -------------------------------- | ----------------------------------------------------------- |
553
+ | `createdAfter` / `createdBefore` | 创建时间范围,毫秒时间戳,含起点不含终点 |
554
+ | `updatedAfter` / `updatedBefore` | 修改时间范围,正文修改也计入 |
555
+ | `tag` | 按标签筛选;层级标签按前缀命中,「读书」也命中「读书/小说」 |
556
+ | `sort` | `updated`(默认)或 `created`,均为倒序 |
557
+ | `limit` | 1–200,默认 50 |
558
+ | `cursor` | 上一页返回的 `cursor`,原样传回读取下一页 |
559
+
560
+ 返回项都有 `id`、`title`、`tags`、`wordCount`、`createdAt`、`updatedAt`。卡片没有标题字段,`title` 是 AI 标题或正文开头。卡片和文章另有 `pinned` 与 `contentRef`;项目文档有 `kind`(`document` 等)、`projectIds`,非富文本类型的 `contentRef` 为 `null`。`id` 与 `contentRef` 是不透明字符串,只用于传回 Tau,不要解析或自行拼接。
561
+
562
+ 待办与日程:
563
+
564
+ ```ts
565
+ const todos = await context.data.todos.query({ completed: false, limit: 20 });
566
+ const events = await context.data.calendar.query({ start: "2026-10-19", end: "2026-10-26" });
567
+ ```
568
+
569
+ `todos.query` 可按 `source`(`all` / `todo` / `document`)、`completed`、`keyword` 与截止日期 `start` / `end`(`YYYY-MM-DD`)筛选,每页最多 100 条。来自文档勾选项的待办带 `source`,指明所在文档。`calendar.query` 的 `start`、`end` 必填,可以是日期或带时区的 ISO 时间。
570
+
571
+ ### 读取正文
572
+
573
+ ```ts
574
+ const snapshot = await context.content.read(card.contentRef); // 默认 Markdown
575
+ console.log(snapshot?.markdown, snapshot?.wordCount);
576
+ ```
577
+
578
+ 需要该正文所属实体的读取权限(例如卡片正文需要 `cards:read`)。`format: "nodes"` 返回编辑器节点 JSON,其结构会随编辑器演进,优先使用 Markdown。读取的是已保存的内容,正在编辑的修改最多晚约 1 秒。
579
+
580
+ ### 修改与删除
581
+
582
+ ```ts
583
+ import { PluginConflictError } from "@tau/plugins";
584
+
585
+ const card = await context.data.cards.create({ markdown: "# 读书笔记\n\n第一段", tags: ["读书"] });
586
+ try {
587
+ await context.data.cards.update(card.id, { pinned: true }, { expectedRevision: card.revision });
588
+ } catch (error) {
589
+ if (error instanceof Error && error.name === "PluginConflictError") {
590
+ // 用户刚在别处改过这张卡片:重新读取,再决定是否重试
591
+ } else throw error;
592
+ }
593
+ await context.data.cards.delete(card.id, { expectedRevision: card.revision });
594
+ ```
595
+
596
+ | 接口 | 可修改的字段 |
597
+ | ------------------------------- | ---------------------------------------------------------------------------------------------------- |
598
+ | `cards.create` / `cards.update` | 新建时 `markdown`、`tags`、`pinned`;修改时 `tags`、`pinned` |
599
+ | `articles.create` / `update` | 新建时 `title`(必填)、`markdown`、`tags`;修改时 `title`、`tags`、`pinned` |
600
+ | `documents.update` | `title`、`tags`;项目文档的新建、移动与删除涉及文档树,暂不开放 |
601
+ | `todos.create` / `update` | `title`、`dueAt`(`YYYY-MM-DD`、带时区的 ISO 时间或 `null`);`setCompleted(id, completed)` 标记完成 |
602
+ | `calendar.create` / `update` | `title`、`start`、`end`、`allDay`、`calendarId`;全天日程的 `end` 不含当天 |
603
+
604
+ - **revision**:读到的每一项都带 `revision`,修改和删除时作为 `expectedRevision` 传回。期间用户或其他插件改过它,就会抛出 `PluginConflictError`,不会覆盖别人的修改。修改正文不会改变实体的 `revision`。
605
+ - **删除可以恢复**:插件删除的卡片、文章、待办和日程,用户 7 天内可以在插件详情页的「权限 → 最近删除」中恢复。有子待办的待办不能删除。
606
+ - **各处同步**:写入后,打开中的页面、其他窗口、搜索与云同步都会跟着更新,不需要插件通知。
607
+
608
+ ### 修改正文
609
+
610
+ ```ts
611
+ const snapshot = await context.content.read(card.contentRef);
612
+ await context.content.edit(
613
+ card.contentRef,
614
+ { op: "append", markdown: "\n今天读完了第三章。" },
615
+ { expectedRevision: snapshot!.revision }
616
+ );
617
+ ```
618
+
619
+ | `op` | 作用 |
620
+ | ------------- | -------------------------------------------------------------- |
621
+ | `append` | 在末尾追加 Markdown,原有内容保持不变 |
622
+ | `prepend` | 在开头插入 Markdown,原有内容保持不变 |
623
+ | `replaceText` | 把 `find` 换成 `markdown`;`find` 必须在当前正文中恰好出现一次 |
624
+ | `replaceAll` | 用 `markdown` 替换整篇 |
625
+
626
+ 需要所属实体的写入权限(卡片正文需要 `cards:write`)。正文正在编辑器中打开时,修改会立即出现在编辑器里,协作者也能看到。插件的修改不进入编辑器的撤销记录,用户无法用撤销键退回,请在修改前确认用户意图。`replaceText` 与 `replaceAll` 会把整篇按 Markdown 重建,Markdown 无法表达的格式可能被简化;只是添加内容时请用 `append` 或 `prepend`。返回新的 `revision`,连续修改时传给下一次调用。
627
+
628
+ ### 变更事件
629
+
630
+ ```ts
631
+ context.events.on(["card.created", "card.updated", "card.deleted"], event => {
632
+ queryClient.invalidateQueries({ queryKey: ["cards"] });
633
+ });
634
+ ```
635
+
636
+ | 事件 | 说明 |
637
+ | ------------------------------------------------ | ------------------------------------------------------- |
638
+ | `card.created` / `card.updated` / `card.deleted` | `id` 与 `context.data.cards` 一致;正文修改也算 updated |
639
+ | `article.*`、`document.*` | 同上,文章移入回收站算 deleted |
640
+ | `todos.changed`、`calendar.changed` | 待办、日程有变化,没有 `id`,需要时重新查询 |
641
+ | `workspace.reset` | 同步下载替换了整个库,应全量刷新 |
642
+
643
+ 事件只说明哪一项变了,不携带数据,需要时用 `context.data` 重新读取。`origin` 标出变化来源:`{ kind: "plugin", pluginId }` 表示由插件写入(可用来忽略自己写入引起的事件),其他情况为 `{ kind: "app" }`;待办与日程的事件恒为 `app`。同一项在半秒内的多次变化合并为一次,持续编辑时最多每 2 秒一次。订阅需要在 manifest 中声明对应的读取权限;用户尚未允许时事件不会送达,也不会因此弹窗。`on` 返回的句柄在插件停用时自动注销。
644
+
645
+ ## 关联已有内容
646
+
647
+ 自建页面(画布、看板等)需要引用用户已有的卡片、日记、待办时,用 Tau 的搜索选择窗口让用户挑选,点击时再用 Tau 打开它们。类型契约自 SDK 0.25.0 提供。
648
+
649
+ ```tsx
650
+ import type { PluginEntityRef } from "@tau/plugins";
651
+
652
+ async function linkExisting() {
653
+ const picked = await context.app.pick({ kinds: ["card", "diary", "todo"], multiple: true });
654
+ // picked: [{ ref: { kind: "card", id: "12" }, title: "读书笔记" }, ...];用户取消时为空数组
655
+ await context.storage.set("links", picked);
656
+ }
657
+
658
+ function LinkChip({ link }: { link: { ref: PluginEntityRef; title: string } }) {
659
+ return <button onClick={() => void context.app.open(link.ref)}>{link.title}</button>;
660
+ }
661
+ ```
662
+
663
+ | `kind` | 内容 | 打开后 |
664
+ | ------------ | ---------------- | -------------------------------------------------- |
665
+ | `card` | 卡片 | 卡片详情 |
666
+ | `article` | 文章 | 文章详情 |
667
+ | `document` | 项目文档 | 所在项目并选中该文档 |
668
+ | `diary` | 日记里的一条记录 | 当天日记并定位到这一条 |
669
+ | `whiteboard` | 白板 | 白板 |
670
+ | `todo` | 待办 | 待办页并打开详情;文档里的勾选项打开所在文档并定位 |
671
+ | `event` | 日程 | 日程页并打开详情 |
672
+
673
+ - `pick` 与 `open` 不需要权限:用户亲手挑选,就是同意插件引用这些内容。插件拿到的只有引用和选择时的标题;要读取正文或刷新标题,仍需对应的读取权限(`context.data.*.get`)。
674
+ - 两者都只能在用户操作(点击、按键)之后调用,在定时器或事件回调里调用会抛错。
675
+ - `ref` 是可序列化的普通对象,可以存进插件自己的数据;`id` 与 `context.data` 中同类实体一致。目标被删除后,`open` 抛出「内容不存在」,可以据此提示用户移除这条关联。
676
+
677
+ ## 正文组件
678
+
679
+ 插件页面里可以直接嵌入 Tau 的正文编辑器,效果与卡片详情相同:实时协作、内置与已安装的编辑器扩展、文档待办都会生效,修改自动保存。组件自 SDK 0.25.0 提供,只能在插件页面内使用。
680
+
681
+ ```tsx
682
+ import { ContentEditor, ContentPreview } from "@tau/plugins";
683
+
684
+ function CardPanel({ card }: { card: PluginCard }) {
685
+ return <ContentEditor contentRef={card.contentRef} placeholder="写点什么" />;
686
+ }
687
+
688
+ function CardRow({ card }: { card: PluginCard }) {
689
+ return <ContentPreview contentRef={card.contentRef} maxBlocks={3} />;
690
+ }
691
+ ```
692
+
693
+ | 组件 | 属性 | 所需权限 |
694
+ | ---------------- | ------------------------------------------------ | ------------------------------------------ |
695
+ | `ContentEditor` | `contentRef`、`readonly?`、`placeholder?`、`at?` | 可编辑:所属实体的写入权限;只读:读取权限 |
696
+ | `ContentPreview` | `contentRef`、`maxBlocks?`(默认 3) | 所属实体的读取权限 |
697
+
698
+ - `contentRef` 来自 `context.data` 返回的 `contentRef`,目前支持卡片、文章与项目文档的正文。
699
+ - 组件挂载时只检查授权,不弹窗;尚未决定时显示「允许访问」,用户点击后才询问。已拒绝时提示用户到插件详情页修改。
700
+ - 用户在组件里的编辑与在卡片详情里的编辑相同,产生的事件 `origin` 为 `app`,可以在编辑器的撤销中撤回;这与 `context.content.edit` 不同。
701
+ - `ContentPreview` 显示保存后的内容,编辑中的改动约 1 秒后反映出来。
702
+ - 组件在插件页面内同步渲染,必须与 Tau 在同一运行环境中执行。
703
+
704
+ ## 宿主动作
705
+
706
+ 导出、演示、协作共享这类自带界面的功能,用 `context.app.run` 交给 Tau 完成,界面与卡片详情中的相同。动作自 SDK 0.25.0 提供。
707
+
708
+ ```tsx
709
+ <button
710
+ onClick={() => void context.app.run("content.export", { ref: card.contentRef, format: "pdf" })}
711
+ >
712
+ 导出 PDF
713
+ </button>
714
+ ```
715
+
716
+ | 动作 | 参数 | 效果 |
717
+ | ----------------- | ------------------------------------------------------ | -------------------------------------------------- |
718
+ | `content.export` | `ref`、`format`:`markdown` / `image` / `pdf` / `epub` | Markdown 直接保存文件,其余打开对应的导出窗口 |
719
+ | `content.present` | `ref` | 全屏演示 |
720
+ | `content.share` | `ref` | 打开「内容协作」窗口,由用户确认发起共享或管理成员 |
721
+
722
+ - `ref` 是 `context.data` 返回的 `contentRef`,目前支持卡片、文章与项目文档的正文。
723
+ - 不需要数据权限:界面和确认都由 Tau 提供,插件拿不到正文。与 `open` 一样,只能在用户操作之后调用;内容已删除时抛出「内容不存在」。
724
+ - 对应界面打开后即返回,不等待用户完成导出或结束演示。同一时间只显示一个,插件停用时自动关闭。
725
+
726
+ ## 插件内容
727
+
728
+ 插件可以创建属于自己的内容(如每周一篇周记、看板上的卡片),每项带一份正文。正文和卡片正文一样实时保存、随云同步,用 `ContentEditor` 编辑,或用 `context.content` 读写。读写自己的内容不需要业务数据权限。接口自 SDK 0.25.0 提供。
729
+
730
+ ```tsx
731
+ import { NotebookPen } from "lucide-react";
732
+
733
+ context.entities.registerKind({
734
+ kind: "note",
735
+ label: "周记",
736
+ icon: NotebookPen,
737
+ open: { page: "review", params: note => ({ week: note.key }) },
738
+ });
739
+
740
+ async function noteOf(week: string) {
741
+ return (
742
+ (await context.entities.find("note", week)) ??
743
+ (await context.entities.create({ kind: "note", key: week, title: `${week} 周记` }))
744
+ );
745
+ }
746
+
747
+ function NotePanel({ note }: { note: PluginOwnEntity }) {
748
+ return <ContentEditor contentRef={note.contentRef} placeholder="这一周想记下什么" />;
749
+ }
750
+ ```
751
+
752
+ | 方法 | 说明 |
753
+ | ----------------------------------------------------- | ---------------------------------------------------------------------------------------- |
754
+ | `registerKind(definition)` | 声明内容类型:`kind`、显示名 `label`、图标 `icon`、在 Tau 中打开时进入的页面 `open` |
755
+ | `create({ kind, key, title?, meta? })` | 新建一项,正文为空;同一类型下 `key` 已存在时抛出 `PluginConflictError` |
756
+ | `get(id)` / `find(kind, key)` | 读取一项,不存在时为 `null` |
757
+ | `query({ kind?, cursor?, limit? })` | 按创建时间倒序分页,默认 50 条、最多 200 条 |
758
+ | `backlinks(id)` | 哪些卡片、文章、项目文档引用了这项内容:`{ items: [{ ref, title, snippets }], omitted }` |
759
+ | `update(id, { title?, meta? }, { expectedRevision })` | 修改标题或 `meta`(最大 64 KB 的 JSON 对象) |
760
+ | `delete(id, { expectedRevision })` | 删除一项及其正文,用户可在 7 天内恢复 |
761
+
762
+ - 插件内容在 Tau 里以 `label` 与 `icon` 显示:全局搜索、@ 面板、正文中的引用卡片、脉络与漫步都用它们,而不是笼统的「插件内容」。`icon` 与侧边栏图标同一写法(线性单色 SVG 组件,用 `currentColor`),渲染结果必须是单个 `<svg>`、不超过 16 KB;Tau 在声明时渲染一次并随笔记库保存,所以插件停用、卸载后的 7 天内或在其他设备上,这些地方仍显示你声明的类型名与图标。图标只按形状显示,颜色跟随主题,多色、渐变和位图不会保留。不传 `icon` 时显示通用的插件图标。
763
+ - 插件内容会出现在 Tau 的全局搜索与 AI 检索中(标题与正文)。用户点击时 Tau 按 `open` 打开插件页面,`params` 由实体字段算出;命中正文中间某处时页面参数另带 `at`,原样交给 `<ContentEditor at={params.at}>` 即可滚动到命中处。插件停用时,Tau 用自带的只读页显示这项内容。
764
+ - `registerKind` 声明 `mentionable: true` 后,用户可以在正文里用 @ 引用这类内容(默认不可);引用处点击即打开。`backlinks` 只返回插件已获准读取的类型(`cards:read` 等),不会询问权限,其余来源只计入 `omitted`;`ref` 可直接交给 `context.app.open`。
765
+ - `key` 由插件决定,用来找回同一项内容,例如周记用周次。`meta` 保存少量结构化信息,正文放在正文里。
766
+ - 只有先声明的类型才能新建;`registerKind` 在 `activate` 中调用,停用时自动撤销。
767
+ - `delete` 需要传入读取时的 `revision`,内容已被修改时抛出 `PluginConflictError`。删除后,用户可在插件详情页的「最近删除」中 7 天内恢复,恢复后 ID、`key` 和正文都不变,别处指向它的引用重新生效;若插件在此期间又用同一个 `key` 新建了内容,则无法恢复。删除不触发确认窗口,需要确认时由插件自己询问用户。
768
+ - 卸载插件后,它创建的内容保留 7 天,期间重新安装即可找回,超过 7 天后删除。
769
+
491
770
  ## 资源清理
492
771
 
493
772
  `Disposable` 的签名为 `{ dispose(): void | Promise<void> }`。
package/BACKEND.md ADDED
@@ -0,0 +1,68 @@
1
+ # 本机后台开发
2
+
3
+ 后台入口需要 SDK 0.25.0 及支持包格式 2 的客户端构建。不要只修改版本号就认为客户端已经具备运行能力。普通渲染插件仍使用格式 1。使用 Node 内置模块前执行 `pnpm add -D @types/node@24.0.0 --save-exact` 安装类型声明。
4
+
5
+ 仓库提供完整双入口样本:`scripts/plugins/fixtures/backend-system.tsx` 为界面,`backend-system.ts` 为后台。在宿主仓库运行 `node scripts/plugins/verify-backend-author.mjs`,会打包 SDK,在仓库外安装 tarball、类型检查、构建双入口 ZIP,并用真实 Electron utilityProcess 调用样本。结果位于 `output/plugin-backend-sdk/`;该命令不安装到用户目录、不发布到市场。
6
+
7
+ 在 manifest.json 中将 formatVersion 设为 2,保留 main.js,并增加 backend:
8
+
9
+ ```json
10
+ {
11
+ "entry": "backend.js",
12
+ "runtime": "node",
13
+ "apiVersion": 1,
14
+ "scope": "application",
15
+ "activation": "app-ready",
16
+ "lifetime": "persistent",
17
+ "reason": "连接本机设备并向插件页面提供设备状态",
18
+ "systemAccess": ["network"]
19
+ }
20
+ ```
21
+
22
+ 模板从 src/backend.ts 构建独立的 backend.js。Node 内置模块可直接引用,第三方 JS 依赖打入 bundle;仅 @tau/plugins/backend 由宿主提供。不打包 node_modules,不运行安装脚本,不让前端入口引用后台文件。main、后台及样式的摘要由构建生成。
23
+
24
+ application 作用域支持 on-demand、app-ready;workspace 支持 on-demand、workspace-ready。lifetime 为 idle 或 persistent,常驻直到所属作用域结束。安装不等于启用,声明不等于授权;无授权的自动启动进入等待处理状态。系统用途字段用于用户知情和审核,不是操作系统沙箱。
25
+
26
+ pnpm build 生成整包 ZIP,pnpm install:local 仍只用于首次开发安装且拒绝覆盖既有目录。后台新版本与界面同包更新,不能只替换 backend.js。
27
+
28
+ ## 定义后台方法
29
+
30
+ ```ts
31
+ import { defineBackend } from "@tau/plugins/backend";
32
+ import { platform } from "node:os";
33
+
34
+ export default defineBackend({
35
+ create() {
36
+ return {
37
+ activate(ctx) {
38
+ ctx.rpc.register(
39
+ "system.platform",
40
+ {
41
+ input: {
42
+ parse(value: unknown) {
43
+ if (value !== null) throw new Error("此方法不接受参数");
44
+ return null;
45
+ },
46
+ },
47
+ output: {
48
+ parse(value: unknown) {
49
+ if (typeof value !== "string") throw new Error("平台结果无效");
50
+ return value;
51
+ },
52
+ },
53
+ },
54
+ () => platform()
55
+ );
56
+ },
57
+ };
58
+ },
59
+ });
60
+ ```
61
+
62
+ 渲染入口通过 `await ctx.backend.call("system.platform", null, { signal })` 调用。请求和结果必须为有限 JSON,普通消息最多 1 MiB。后台必须提供输入、输出解析器,异常返回调用方。`ctx.rpc.emit(name, value)` 与 `ctx.backend.subscribe(name, listener)` 用于事件;订阅返回可回收句柄。停用或切库后旧连接失效。
63
+
64
+ 外部程序通过 `ctx.process.run(toolId, args, { signal })` 执行,返回 exitCode/stdout/stderr。同时在 backend.systemAccess 声明 process,并以 backend.tools 列出 `{ id, command, args? }`。参数以数组传递,不自动经过 Shell;stdout/stderr 合计最多 512 KiB,单次执行 30 秒。Windows 只接受可解析到唯一 EXE 的工具,使用宿主 Job Object;Unix 使用进程组监督。需要取消时把 RPC 的 request.signal 继续传给工具。
65
+
66
+ 不要在模块顶层启动任务。计时器、订阅等使用 `ctx.register` 清理;后台长期任务应响应 `ctx.signal`。主进程扫描不会执行后台,客户端会在首次运行前要求本机代码执行授权。独立进程不会限制 Node 对本机文件、网络的实际权限。
67
+
68
+ 插件中心每个后台版本都需人工重点审核,审核者本人的 CI 提交也不自动批准。更新后本机授权重新确认;有在途任务、订阅或插件 MCP 连接时先结束这些工作。已知撤回的中心版本禁止执行;离线只能依据上次已获取的发布状态。原始 stdout/stderr 不写宿主日志,管理页展示有界错误、启动耗时、失败/超时次数及进程资源采样。
package/CHANGELOG.md CHANGED
@@ -1,5 +1,27 @@
1
1
  # 更新记录
2
2
 
3
+ ## 0.25.0
4
+
5
+ - 新增独立本机后台:格式 2 插件声明 `backend`,通过 `@tau/plugins/backend` 的 `defineBackend` 注册方法,界面经 `context.backend.call/subscribe` 调用与订阅。支持按需或随作用域启动、取消、受监督的外部工具与本机执行授权;构建脚本生成双入口及完整性摘要,详见 BACKEND.md。普通插件继续使用格式 1。
6
+ - 新增业务数据只读接口:`context.data` 读取卡片、文章、项目文档、待办与日程,`context.content.read` 按 `contentRef` 读取正文(Markdown 或编辑器节点)。查询按时间范围、标签筛选并分页。
7
+ - 新增权限:manifest `permissions` 声明可申请的上限(`cards:read`、`articles:read`、`documents:read`、`todos:read`、`calendar:read`),首次调用时由用户选择「仅这次」「始终允许」或「拒绝」。未获准时抛出 `PluginPermissionError`;`context.permissions` 可查询状态与一次申请多项。
8
+ - 新增 `context.events.on`:卡片、文章、项目文档的创建 / 更新 / 删除,待办与日程的集合级变化,以及同步下载后的 `workspace.reset`。事件不携带数据,需要对应读取权限才会送达。
9
+ - 新增写入:卡片与文章的新建、修改、删除,项目文档的标题与标签,待办与日程的新建、修改、完成、删除;`context.content.edit` 追加、插入或替换正文。修改与删除需传入读取时的 `revision`,数据已被别处修改时抛出 `PluginConflictError`。插件删除的内容用户可在 7 天内恢复。
10
+ - 权限词表增加 `*:write` 与 `*:delete`;事件增加 `origin`,可识别由插件写入的变化。
11
+ - 新增 `context.app.pick` 与 `context.app.open`:用 Tau 的搜索窗口让用户选择卡片、文章、项目文档、日记、白板、待办或日程,并打开它们的页面。用户亲手选择即可引用,不需要数据权限;只能在用户操作后调用。
12
+ - 新增正文组件 `ContentEditor` 与 `ContentPreview`:在插件页面嵌入与卡片详情相同的编辑器(协作、已安装的编辑器扩展、文档待办都生效),或只读显示正文开头几块。按所属内容的读取 / 写入权限放行,未授权时由用户点击「允许访问」后再询问。
13
+ - 新增 `context.app.run`:把卡片、文章、项目文档的正文交给 Tau 导出(Markdown、图片、PDF、EPUB)、全屏演示或发起协作共享,界面与卡片详情中的相同。只能在用户操作后调用,不需要数据权限。
14
+ - 新增 `context.entities`:插件创建自己的内容(如周记),每项带一份正文,用 `ContentEditor` 编辑或 `context.content` 读写,不需要业务数据权限。卸载插件后内容保留 7 天,期间重新安装可找回。插件内容可被全局搜索与 AI 检索找到,点击结果按 `registerKind` 声明的 `open` 打开插件页面;`ContentEditor` 增加 `at`,打开到命中处。声明 `mentionable` 的类型可在正文中用 @ 引用;`context.entities.backlinks` 列出引用了它的卡片、文章与项目文档(只含已获准读取的类型)。
15
+ - `context.entities.registerKind` 增加 `icon`,与侧边栏图标同一写法;新增 `PluginIcon` / `PluginIconProps`,`PluginSidebarIcon` / `PluginSidebarIconProps` 保留为同义名。插件内容在搜索、@ 面板、正文中的引用、脉络与漫步里按声明的类型名与图标显示,插件停用或卸载后的 7 天内同样如此。
16
+ - 新增 `context.entities.delete`:删除插件自己的内容及正文,用户可在插件详情页 7 天内恢复,恢复后 ID 与正文不变。
17
+ - `context.storage` 的数据改为保存在笔记库里,随云同步;升级后首次启动自动迁移本机已有的数据,单个值不超过 1 MB。
18
+ - 权限约束的是 SDK 接口;插件仍与应用运行在同一环境,不提供安全隔离。
19
+
20
+ ## 0.24.0
21
+
22
+ - 移除 `PluginServices.typst`:宿主不再内置 Typst 编译服务(原生二进制约 37 MB/平台随之从安装包移除)。Typst 排版由 `tau.typst` 插件 v0.2.0 以 WebAssembly 引擎在渲染进程本地完成,首次使用联网下载编译组件与字体并长期缓存。
23
+ - 旧版 `tau.typst` 插件(0.1.x)在新宿主上不再能实时编译,已保存的排版缓存与源码照常显示。
24
+
3
25
  ## 0.23.1
4
26
 
5
27
  - 修正 FFprobe 升级说明中已删除类型的名称为 `MediaInfo`,接口与 0.23.0 相同。
package/DEVELOPMENT.md CHANGED
@@ -8,7 +8,7 @@
8
8
  - pnpm 11.9.0
9
9
  - Tau 桌面客户端 0.59.0 或更高版本
10
10
 
11
- 模板锁定公开 SDK `@xiongtao.xt/tau-plugins@0.23.1` 和 UI `@xiongtao.xt/tau-ui@0.1.1`,通过 npm 别名安装为 `@tau/plugins`、`@tau/ui`。SDK 提供类型契约,运行实现由 Tau 注入;新领域能力还需包含相应实现的客户端构建,不能只依据客户端版本号判断。
11
+ 模板锁定公开 SDK `@xiongtao.xt/tau-plugins@0.25.0` 和 UI `@xiongtao.xt/tau-ui@0.1.1`,通过 npm 别名安装为 `@tau/plugins`、`@tau/ui`。SDK 提供类型契约,运行实现由 Tau 注入;新领域能力还需包含相应实现的客户端构建,不能只依据客户端版本号判断。
12
12
 
13
13
  ## 跑通示例
14
14
 
@@ -43,6 +43,7 @@ pnpm install:local
43
43
  | `EDITOR.md` | 在文档中添加自定义块、源码预览块和插件 Skill |
44
44
  | `AI.md` | AI 功能声明、用户模型分配、流式对话和统一用量审计 |
45
45
  | `MCP.md` | 注册 MCP、聊天工具渲染,复用工具开关与服务器管理 |
46
+ | `BACKEND.md` | 显式声明本机后台、启动策略与独立产物构建 |
46
47
  | `WHITEBOARD.md` | 静态白板扩展、工具与发布边界 |
47
48
  | `SYNC.md` | 同步存储提供方、配置视图与取消 |
48
49
  | `UPLOAD.md` | 图片与附件上传、上传提供方与配置 |
@@ -51,7 +52,7 @@ pnpm install:local
51
52
  | `VIDEO.md` | 视频来源的链接解析、播放源与账号配置视图 |
52
53
  | `PUBLISH.md` | 博客发布提供方、文件上传与 Git 快照 |
53
54
  | `PUBLISH-DATA.md` | 独立站点读取发布数据与公开子入口的构建边界 |
54
- | `SERVICES.md` | Tailwind、Typst、Markdown、FFmpeg 编译转换服务 |
55
+ | `SERVICES.md` | Tailwind、Markdown、FFmpeg 编译转换服务 |
55
56
  | `AGENTS.md` | 给 AI 编程助手的项目指引 |
56
57
  | `build.mjs` / `install-local.mjs` | 打包与本地安装脚本,日常开发通常不需要修改 |
57
58
 
@@ -61,10 +62,10 @@ pnpm install:local
61
62
 
62
63
  GitHub 模板创建的仓库不会自动跟随模板更新。已有项目先提交或备份自己的改动,再按需同步 [上游模板](https://github.com/LastKnightCoder/tau-plugin-template) 的构建脚本、开发文档和 `.github/workflows/release.yml`,保留自己的插件 ID、入口、用户说明与版本记录。
63
64
 
64
- 先在 `pnpm-workspace.yaml` 的 `minimumReleaseAgeExclude` 中增加新 SDK 的精确例外 `@xiongtao.xt/tau-plugins@0.23.1`,暂时保留旧锁文件所需的已核实例外,再更新依赖及锁文件:
65
+ 先在 `pnpm-workspace.yaml` 的 `minimumReleaseAgeExclude` 中增加新 SDK 的精确例外 `@xiongtao.xt/tau-plugins@0.25.0`,暂时保留旧锁文件所需的已核实例外,再更新依赖及锁文件:
65
66
 
66
67
  ```sh
67
- pnpm add @tau/plugins@npm:@xiongtao.xt/tau-plugins@0.23.1 --save-exact
68
+ pnpm add @tau/plugins@npm:@xiongtao.xt/tau-plugins@0.25.0 --save-exact
68
69
  pnpm install --frozen-lockfile
69
70
  pnpm build
70
71
  ```
@@ -80,7 +81,9 @@ pnpm build
80
81
  | 0.19.0–0.20.0 | 发布数据子入口、站点级数据归清单、分类与关键词,见 PUBLISH-DATA.md 与 API.md |
81
82
  | 0.21.0 | 博客发布提供方、配置与 Git 快照服务,见 PUBLISH.md |
82
83
  | 0.22.0 | 视频提供方与视频页面信息查询,见 VIDEO.md |
83
- | 0.23.0 | 删除 `ffmpeg.probe` 与 `MediaInfo`;`ffmpeg.run` 保留,见 SERVICES.md |
84
+ | 0.23.0 | 删除 `ffmpeg.probe` 与 `MediaInfo`;`ffmpeg.run` 保留,见 SERVICES.md |
85
+ | 0.24.0 | 删除 `services.typst`;Typst 编译由独立插件实现,见 SERVICES.md |
86
+ | 0.25.0 | 数据读写与权限、正文组件、插件实体、宿主动作、存储随库同步,见 API.md;本机后台双入口见 BACKEND.md |
84
87
 
85
88
  更早版本的完整变更见已安装包的 `node_modules/@tau/plugins/CHANGELOG.md`。需要本地媒体元数据时自行实现,不能把视频页面解析当作本地文件探测。SDK 根入口、白板/同步契约、UI 和 React 由 Tau 提供,保持模板的 external 配置;普通第三方库由构建打入 ZIP。独立站点数据子入口的用途另见 PUBLISH-DATA.md。
86
89
 
package/MCP.md CHANGED
@@ -58,7 +58,9 @@ export default definePlugin({
58
58
 
59
59
  本地工具填写 `name`、`description`、对象类型的 JSON Schema `inputSchema`,以及 `execute(args, { signal })`。执行结果为 `{ content: string }`,结构化数据可用 JSON.stringify 返回。工具名称使用 1–128 位字母、数字、点、横线或下划线;宿主会生成全局工具名称,作者不要自行拼接命名空间。所有插件工具沿用现有 execute 级审批。
60
60
 
61
- 需要本地进程时,把 transport 改为下面的结构,命令、参数、工作目录与应用内手动添加 MCP 的含义相同:
61
+ 需要本地进程时,先按 BACKEND.md 使用格式 2、独立后台入口及 `systemAccess: ["process"]`,并在 `backend.tools` 声明对应 command 和固定参数前缀。该版本需要系统能力审核和本机后台授权;未声明的旧 stdio 插件需更新,HTTP 和本地 JS tools 不受此要求影响。命令不经过 Shell,工作目录须为绝对路径;环境只包含宿主最小集合与插件显式传入值。Windows 目标必须是 EXE。
62
+
63
+ 例如声明 `tools: [{ id: "mcp", command: "node", args: ["/path/to/your-server.mjs"] }]` 后注册:
62
64
 
63
65
  ```ts
64
66
  context.mcp.register({
package/SERVICES.md CHANGED
@@ -1,6 +1,6 @@
1
1
  # 编译与转换服务
2
2
 
3
- 需要在插件里编译 Tailwind、Typst,转换 Markdown,或处理音视频时,可以使用这些服务。通过 `context.services` 调用,类型从 `@tau/plugins` 导入,需要 Tau 0.59.0 或更高版本及实现相应能力的客户端构建。本文对应 SDK 0.23.0。TikZ 编译属于独立绘图插件,不再提供宿主服务。
3
+ 需要在插件里编译 Tailwind,转换 Markdown,或处理音视频时,可以使用这些服务。通过 `context.services` 调用,类型从 `@tau/plugins` 导入,需要 Tau 0.59.0 或更高版本及实现相应能力的客户端构建。本文对应 SDK 0.24.0。TikZ 编译属于独立绘图插件,Typst 排版属于 `tau.typst` 插件(自带 WebAssembly 引擎),均不再提供宿主服务。
4
4
 
5
5
  所有服务都是异步方法,只处理传入数据并返回结果,不读写笔记、不选择或保存文件、不自动注入样式。
6
6
 
@@ -9,7 +9,6 @@
9
9
  | 方法 | 输入 | 返回 |
10
10
  | ---------------------------------------- | ------------------------------------------------- | --------------------------------------- |
11
11
  | `tailwind.compile(input, options?)` | `{ source, language?: "html" \| "tsx" \| "jsx" }` | `{ css }` |
12
- | `typst.compile(input, options?)` | `{ source, format?: "html", inputs?, files? }` | `{ html }` |
13
12
  | `markdown.deserialize(source, options?)` | Markdown 字符串 | `{ nodes: Descendant[], warnings }` |
14
13
  | `markdown.serialize(nodes, options?)` | `Descendant[]` | `{ markdown, warnings }` |
15
14
  | `markdown.render(source, options?)` | Markdown 字符串 | `{ html, css, warnings }` |
@@ -33,11 +32,11 @@ SDK 0.16.0 的 `usePluginServices()` 返回当前插件可用的同一组服务
33
32
  ```ts
34
33
  const controller = new AbortController();
35
34
  try {
36
- const { html } = await context.services.typst.compile(
37
- { source: "= 阅读报告\n本周阅读记录" },
35
+ const { css } = await context.services.tailwind.compile(
36
+ { source: '<div class="p-4">Hello</div>', language: "html" },
38
37
  { signal: controller.signal }
39
38
  );
40
- // 将 html 用于插件自己的预览。
39
+ // 将 css 用于插件自己的预览。
41
40
  } catch (error) {
42
41
  if (error instanceof Error && error.name === "AbortError") return;
43
42
  throw error;
@@ -60,20 +59,6 @@ const { css } = await context.services.tailwind.compile({
60
59
 
61
60
  固定插件界面通常在打包时编译样式;此服务主要用于运行时生成内容。返回 CSS 由插件自行使用并在停用时清理。
62
61
 
63
- ## Typst
64
-
65
- 目前只输出 HTML 正文片段,不支持 PDF 和 SVG。`inputs` 对应 Typst 的 `sys.inputs`:
66
-
67
- ```ts
68
- const { html } = await context.services.typst.compile({
69
- source: '#include "parts/intro.typ"\n#sys.inputs.at("title")',
70
- inputs: { title: "阅读报告" },
71
- files: [{ name: "parts/intro.typ", data: new Blob(["本周阅读记录"]) }],
72
- });
73
- ```
74
-
75
- 附件名是本次编译中的相对路径,不能使用绝对路径、`..` 或反斜杠。图片和包含文件等需显式通过 `files` 提供。可用字体依赖客户端运行环境;不承诺任意宏包或排版效果均可导出为 HTML。公式需要使用 Typst 相应的 HTML 写法,不能把 PDF 能力等同于 HTML 支持。
76
-
77
62
  ## Markdown 与编辑器节点
78
63
 
79
64
  ```ts