---
name: movieclaw-plugin-dev
description: 用户想让 MovieClaw 拥有它现在没有的能力或对接时使用，不管用户有没有说「插件」。典型说法：「做一个企业微信 / 钉钉 / Slack / Bark 通道」「接入某某平台」「能不能支持某某站点」「下载完 / 入库后 / 删片后自动做某事」「按订阅规则组表达不了的规则淘汰种子」「定时去拉某个片单」「给 AI 助手加个查某某的能力」「给外部系统开个接口」。这类需求先判断现有功能能否做到，做不到的靠写插件（.mcplugin）实现；本技能覆盖从判断、开发、打包、安装、验证到卸载的全过程，也用于升级、回滚、排查已装的插件，以及回答「插件是什么、要批准什么、安不安全」这类问题。
---

# MovieClaw 插件开发

你要帮用户写一个 MovieClaw 插件，并在这台服务器上把它装起来、验证、必要时卸载。
插件是一个目录：一份清单 `movieclaw-plugin.toml` + Python 代码，打包成 `.mcplugin`（zip）后安装。
装上后默认在**独立的低权限进程**里运行，崩溃只伤它自己；装、升级、回滚、卸载都**不用重启**。
第三方插件和商店元数据（`store.json`）在 <https://github.com/movieclaw/movieclaw-plugins>，写法见该仓库 `example/GUIDE.md`。
本仓库不收新的第三方插件；`store.json` 不要写进清单（多一个字段上传会被拒），打进 zip 也会被解出后忽略。

本文件是判断、流程与规范，**读完再动手**；第 2 节告诉你动手前还必须读哪份参考（路径以本技能目录为锚）。

## 0. 先判断：要不要写插件、做不做得到

**先分流**：

- 用户只是问插件是什么、要批准什么、安不安全、怎么卸载 → 读 `references/explaining-to-users.md` 照着答，不走开发流程。
- 用户要停用 / 启用 / 卸载 / 恢复某个插件（含官方插件）→ 第 3 节第 12 步，不走开发流程。
- 用户只是想「做一次某件事」（建订阅、删种子、扫媒体库）→ 直接用 `mclaw` 工具，不要写插件。
- 想要的效果**已有功能或设置**能做到 → 用现有的，不要写插件。先按下表查；查到「已有但只覆盖一部分」时，
  先讲清已有的能做到哪、剩下的部分能不能用插件补。
- 需要**持续自动**地响应系统里发生的事、改变系统的决策、给系统加一种新实现，或开一个接口（给 AI 助手或外部系统用）→ 写插件。
  确认系统里确实没有之后，不要只回答「目前不支持」：对照下面的能力地图和「做不到的」，能做就按本技能走，做不到就直说。

**先查系统里有没有**（都用 mclaw 工具）：

| 需求 | 查什么 |
|---|---|
| 通道、消息推送 | `channels list`（输出很长，只看各项 `id` / `title`）；已经会推哪些事件：`channels im push config get` |
| 下载器 | `dl list` 看已接入的；支持哪些类型看 `dl add --help`（只有 qBittorrent、Transmission） |
| 订阅只要 / 不要某些资源（制作组、分辨率、HDR、字幕、体积、免费） | `rules update --help`：订阅规则组都能设（如制作组白名单），规则组表达不了的才用钩子 |
| 站点 | `site list`、`site --help` |
| 播放器跳过片头、片尾、广告、预告 | 内置：剧集库的片段识别，`library update --help` 的 `--detect-media-segments` |
| 首页显示什么 | `ui prefs show`：首页行可以选某个库、合集或类型，每个库都有按入库时间排的「最近添加」 |
| 某件事发生后是否已有插件在处理 | `app plugins list -o table` 的 `durable` 一行看消费者 |
| 有没有装过某个插件 | `app plugins packages list` |

**能力地图**（逐项清单见 `references/extension-points.md`）：

| 插件能做 | 靠什么 |
|---|---|
| 某件事发生后自动做点什么：下载完成、入库完成、删片 / 删文件 / 回收站、订阅新建 / 删除 / 开始下载 / 收齐 / 状态变化 | 可靠事件 |
| 改变系统的判断：订阅搜索词、淘汰或重排候选种子、选下载器、否决删种 | 决策钩子 |
| 加一种新实现：IM 通道（含只推送的，如 Bark）、站点类 / 站点数据包、定时任务（可整段替换内置的）、后台任务、入库流水线步骤 | 注册表 |
| 给 AI 助手加一个能力（查天气、查某个外部服务）、给外部系统开接口 | 插件接口：自动成为 mclaw 命令，AI 助手直接能调 |
| 接收外部平台推过来的消息（企业微信自建应用回调、GitHub / Slack 推送、Telegram Webhook） | 回调端点：宿主发地址，请求原样交给插件，插件自己验签 |
| 做事与存东西：调用系统操作（与 mclaw 同一份）、存状态、读写批准的目录、报告健康 | 服务 |

**做不到的**：给网页 / App 加界面、按钮或设置页（包括播放器里的按钮、首页卡片）、新的下载器类型、新的元数据来源、
新的 Webhook 格式、新的站点认证方式、替换播放 / 转码 / 媒体库扫描 / 刮削主流程、改 AI 助手本身。
**遇到要直说做不到**，并给替代办法：先给用户能直接用的现成功能（上表），再给插件能做的部分
（`references/extension-points.md` 第 5 节），不要先答应再做到一半。

**用户问「能不能……」「可以做……吗」时**，先给方案，不要直接动手：能不能做、怎么做、要申请什么权限、
用户要配合准备什么（账号、凭据、先接入的下载器 / 站点 / 媒体库等前置条件——先用 mclaw 查缺哪些）。
有要用户选的（接入方式、取舍、阈值）或前置条件不满足 → 到这里结束这一轮，等用户回答。
需求明确、只有一种合理做法时，可以先写好并检查、自测，再问要不要安装。

## 1. 原理速览

| 概念 | 一句话 |
|---|---|
| 插件 | 一个 `@plugin("<id>", ...)` 装饰的 `async def apply(ctx)`。`apply` 只做**登记**（监听事件、贡献注册表、挂路由、起后台任务），登记完就返回 |
| 条目 id | 全局唯一；小写字母开头，只用小写字母、数字和连字符，3～40 位，不含点，如 `hello-world`。清单的 `id` 必须与 `@plugin` 的名字**完全一致** |
| 契约 | 插件只能用**开放契约**：服务（`ctx.use`）、注册表（`ctx.contribute`）、事件与决策钩子（`ctx.on`）。清单见 `python <本技能>/scripts/contracts.py` |
| 可撤销 | 经 `ctx` 登记的一切在卸载时自动撤销；自己开的资源用 `ctx.effect(释放函数)` 登记，后台协程一律用 `ctx.task(...)` |
| 宿主操作 | 插件「做事」= 调用和 `mclaw` 同一份操作目录（如 `subscriptions.create`），清单 `permissions.operations` 申请，用户批准后才有 |
| 运行位置 | `runtime = "process"`（默认、推荐）独立进程；`inline` 在主进程里运行，需用户单独批准，除非用户明确要求否则不用 |
| 热插拔 | 插件包安装 / 升级 / 回滚 / 卸载都在运行中完成；新版本起不来时服务器**自动回到上一版** |

**组合与拆分**：一个插件可以同时用多个钩子、事件、注册表、路由；一个插件包只加载一个插件（名字 = 清单 id），
同生同死。一个插件 = 一件用户能说清楚的事；互不相关、权限差得多、稳定性差得多的事拆成多个插件。
替换内置东西：同 id 的插件包替换随带通道；注册表里 `override=True` 加原 id 替换某一项。详见 `references/composition.md`。

## 2. 动手前必须先读

源码根目录（下文记作 `$SRC`）= 本技能目录往上三级；或运行
`python -c "import movieclaw_sdk, pathlib; print(pathlib.Path(movieclaw_sdk.__file__).parents[1])"`。

第 0 节给方案时只需要 `references/extension-points.md`；下表是**动手写代码前**要读的。
示例插件都在本技能的 `references/examples/`（只有这一处，仓库和容器里没有别的副本）。它们是插件体系的验收插件，
CI 会把它们真实装进应用、跑完用户场景，写法可以照抄。示例大多是「本地插件」写法（`data/plugins.yaml`、从 `movieclaw_kernel` 导入），
做插件包时以 `templates/starter/` 为准，差别见 `references/examples/README.md`。

| 你要做的 | 先读（读完再写代码） |
|---|---|
| 任何插件 | `references/extension-points.md`（确认要用的契约存在、没落在「做不到」里）、`templates/starter/` |
| 事件 / 钩子类（删片联动、关键字规则、选下载器） | `references/recipes.md` 第 1～3 节，对应示例 `references/examples/{delete_cascade,keyword_rules}.py` |
| IM 通道 | `references/recipes.md` 第 11 节（**选接入方式**）、`references/examples/ntfy-channel/`、`$SRC/movieclaw_sdk/channels.py` 文件头；只推送的通道再看 `$SRC/movieclaw_plugins/feishu/`；靠平台回调收消息的看 `references/examples/wecom-channel/` |
| 定时 / 后台任务、入库流水线 | `references/recipes.md` 第 6～7 节、`references/examples/{watchlist_feed,cloud_strm}.py` |
| 用户正要用的先准备好（开始播放预取下一集、收藏 / 打开详情先处理、新文件入库后慢慢补） | `references/recipes.md` 第 7、16 节（实时事件 + 插件自己建后台任务），完整示例 `$SRC/movieclaw_plugins/strm_prefetch/` |
| 驱动 AI 助手（把外部协议 / 平台的请求转成一轮对话、自动化） | `references/recipes.md` 第 15 节（**先定对端怎么进来**：只聊天用公开区签名链接，要操作系统才用管理员区 + 令牌；先 `mclaw llm providers list` 确认接了模型）；非 Python 写法看 `references/examples/go-a2a-bridge/` |
| 接收外部平台的回调 | `references/recipes.md` 第 14 节 |
| 开接口（含给 AI 助手加能力）、读写文件 | `references/recipes.md` 第 5、8、10 节、`templates/starter/`、`references/examples/cloud_strm.py` |
| 插件需要用户填的配置或凭据（账号、地址、Key） | `references/recipes.md` 第 13 节 |
| 站点 | `references/recipes.md` 第 12 节、`references/examples/site_pack/` |
| 一个插件里放好几件事、替换内置的东西 | `references/composition.md`（替换随带通道：把 `$SRC/movieclaw_plugins/<名>/` 复制到 `plugins/<同 id>/` 再改，id 不含点） |
| 写清单、申请权限 | `references/manifest.md` |
| 用户问「插件是什么、为什么要批准、会不会搞坏」 | `references/explaining-to-users.md` |
| 装不上、起不来、没反应 | `references/troubleshooting.md` |
| 想看系统自己怎么做：内置插件、各模块、某个事件 / 钩子在哪被调用、字段实际填什么 | `references/architecture.md` 第 7 节（源码地图） |

## 3. 开发流程

工作目录就是 bash / mclaw 的当前目录。每个插件放在工作目录的 `plugins/<条目 id>/` 下。
**不要 `cd` 进技能目录或 `$SRC`**：脚本一律用绝对路径调用（`python <技能目录绝对路径>/scripts/xxx.py …`），
插件路径写相对工作目录的 `plugins/<id>`，否则插件会被生成进主程序源码里。
**mclaw 命令一律用 mclaw 工具执行**：bash 里没有登录令牌，在 bash 里跑 `mclaw …` 只会报未授权，
它的输出（包括配合 grep 得到的计数）不能当作验证结果。

1. **澄清需求**：触发时机（什么事发生时）、要做什么、影响哪些数据、要不要配置。说清楚你打算用哪种扩展形态
   （`references/extension-points.md`），做不到的直说并给替代办法；有歧义先问。
   **接外部平台（IM 通道、通知、外部服务）时**：MovieClaw 多半跑在家里的 NAS 上、没有公网地址，平台推不进来。
   首选服务器主动连出去的方式（长轮询、长连接——如企业微信「智能机器人」长连接模式，只要 botId + secret），
   其次是只推送的群机器人 Webhook，需要公网回调地址的方式放最后；给用户的方案里讲清「能不能对话、要不要公网地址、
   绑定要填什么」。细节见 `references/recipes.md` 第 11 节。**只做用户要求的**：觉得还该多做点什么
   （多加几个关键词、多记几个字段），作为建议提出来，用户同意再加。
2. **查契约**：`python <本技能>/scripts/contracts.py` 列出全部开放契约；`python <本技能>/scripts/contracts.py <名字>`
   看某个事件 / 钩子的载荷与返回结构。只能用列出来的契约。要调用的宿主操作用 `mclaw <域> --help` 找，
   操作 id 与命令一一对应（`mclaw subscriptions create` ↔ `subscriptions.create`）。
3. **生成骨架**：
   `python <本技能>/scripts/new_plugin.py plugins/<id> --id <id> --title "<中文标题>"`
   生成清单与入口模块，再按需求改。
4. **写代码**：遵守第 4 节规范。
5. **本地检查**：`python <本技能>/scripts/check_plugin.py plugins/<id>`。它用服务器同一套逻辑校验清单、契约、
   宿主操作，并真的导入入口模块找 `@plugin`。**有 ✗ 必须先修好**；⚠ 要逐条判断。
   **再自测判断逻辑**：把「该不该处理 / 怎么处理」写成模块顶层的纯函数（如 `verdict(title) -> 原因 | None`），
   在 bash 里 `cd plugins/<id> && python -c "from <模块> import verdict; assert …"` 跑几条**正例和反例**
   （反例 = 看着像但不该命中的，如片名里恰好含这几个字母）。钩子在热路径上、误判会直接影响下载，这步不能省。
6. **征得用户同意再安装**（第 5 节）。问完就结束这一轮、等用户回答；同一轮里提问又接着安装，不算征得同意。
7. **安装并加载**：`mclaw` 工具执行 `plugin dev plugins/<id> --once`。它会打包（版本自动加 `-dev.<时间戳>`）、
   上传、按清单申请批准、当场加载，成功打印 `✓ … 已加载`。**必须带 `--once`**，否则它会一直监视文件、卡到超时。
8. **验证**（不验证不算完成）：
   - `mclaw app plugins packages list`：插件在 `installed` 里，版本对；
   - `mclaw app plugins list`：找到条目 id（`id` 字段），`state` 应为 `active`，失败时看 `error`。
     这条输出很长（全部内置条目），会被截断；截断提示里给了完整输出的文件路径，用 bash 筛：
     `python -c "import json; d=json.load(open('<文件>')); [print(e['id'], e['state'], e['source'], e.get('error')) for e in d['plugins'] if e['source'] == 'package']"`
     （第三方插件包 `source` 是 `package`，本地插件是 `local`；随带的通道插件 `tier` 是 `official`）。
     也可以直接 `grep -n -A8 '"<条目 id>"' <文件>`；
   - 调插件自己的接口：插件路由会进入 mclaw 的命令目录。装好后先随便执行一条 mclaw 业务命令（如上面的
     `app plugins list`），看到「服务器接口目录已更新」后，下一次调用起就能用了：路由的 `operation_id`
     按点拆成命令，`plugins.import-log.recent` → `mclaw plugins import-log recent`（参数见 `--help`）；
   - 通道插件：`channels list` 里出现新通道，它的 id 是 `<条目 id>:<贡献 id>`（如 `bark-channel:bark`；替换随带通道时沿用原 id）；
     绑定和试推送的命令看 `channels bindings start --help`、`channels im push --help`，凭据由用户提供；
   - 触发它监听的事件并观察效果。事件不能安全地人为制造时（入库、删片这类会动真实数据的），
     **不要为了验证去改动用户的数据**：确认 `app plugins list` 里它的监听器已挂上（可靠事件会显示消费者），
     并告诉用户「下次真实发生时会怎样、去哪里看结果」；
   - 日志：用 bash 在运行日志目录（系统提示词「环境」里给了路径）的当天文件里找条目 id，
     如 `grep -n '<条目 id>' <日志目录>/movieclaw-$(date +%F).log | tail -50`；进程外插件的日志也汇总在这里。
9. **迭代**：改代码后重复 5 → 7，每次自动是新的开发版本。
   **装不上时**：先按报错原文查 `references/troubleshooting.md`。同一个现象连续两次没装上，就停下来向用户报告
   （报错原文、你的判断、建议的下一步），不要去翻宿主源码、模拟宿主的加载过程。
10. **正式安装**（用户要长期使用时）：清单里定好正式 `version`，然后
    `plugin pack plugins/<id>` → `app plugins packages upload --file <生成的 .mcplugin>` →
    `app plugins packages approve <id> --version <版本> --operations-json '<与清单一致>' --paths-json '<与清单一致>' --yes`。
11. **卸载**：`app plugins packages uninstall <id> --yes`（默认保留插件数据；用户要求清数据才加 `--purge-data`，它只删 `plugin_data` 里该 id 的行，不删设置文件和私有目录）。卸载后再查一次 `app plugins list`，确认条目已消失；替换内置插件的包卸载后内置版本自动回来。
    回到上一版：`app plugins packages rollback <id> --yes`。
12. **停用 / 官方插件**：插件包与官方插件（随应用提供的，如通道、下载器、网盘规格预探）都能临时停用：
    `app plugins switch <id> --enabled=false`（启用：`--enabled=true`；布尔参数必须带 `=`），运行中生效、重启后保持，
    **停用会取消它排着的后台任务**（插件任务的类型以 `<id>:` 开头，停用前可用 `jobs list --active-only` 看有没有在跑的）。
    条目 id 从 `app plugins list` 的完整输出里筛：官方插件 `tier` 是 `official`、可停用的 `switchable` 为真
    （`python -c "import json; d=json.load(open('<文件>')); [print(e['id'], e['title'], e['state']) for e in d['plugins'] if e.get('switchable')]"`）。
    网页上的位置：「设置 → 插件」，官方插件在最上面一组，点进去「操作」里有停用 / 卸载。
    官方插件还能卸载：`app plugins bundled uninstall <id> --yes`（加 `--purge-data` 连数据删），随时
    `app plugins bundled restore <id>` 装回；已卸载的在 `app plugins list` 的 `removed` 里。被 `plugins.yaml` / 环境变量
    关掉的插件开关是锁住的（`locked_by` 写明原因），要用户去那里改。

命令参数以 `mclaw <命令> --help` 为准，报错时先看 `--help` 再重试。排查：`references/troubleshooting.md`。

## 4. 硬性规范

**必须**
- 清单 `id` = `@plugin` 名字；`entry` 是模块名（`hello.py` 或 `hello/__init__.py` 的 `hello`）。
- `inject=(...)` **只写服务**（`HOST_OPS`、`PLUGIN_DATA`、`PLUGIN_FILES`、`PLUGIN_ROUTES`、`PLUGIN_HEALTH`、`PLUGIN_JOBS`、
  `AGENT`、`DURABLE_EVENTS`）。插件包代码优先从 SDK 导入（`movieclaw_sdk.ops` 的 `HOST_OPS`、`movieclaw_sdk.jobs`、
  `movieclaw_sdk.agent` 的 `AGENT`、`movieclaw_sdk.events` 的系统事件）。
  注册表（如 `IM_CHANNELS`、`SCHEDULED_TASKS`）在 `apply` 里 `ctx.contribute`，事件 / 钩子用 `ctx.on`，**都不写进 inject**；
  用到的事件 / 钩子 / 注册表写进清单 `[requires]`（如 `"library.ingest.completed" = "^1.0"`）；服务写不写进 `[requires]` 都行（写了就锁定版本，如 `"plugin-jobs" = "^1.0"`）；只用服务时 `[requires]` 可以留空。
- 要调用的宿主操作同时写进 `@plugin(permissions=...)` 和清单 `permissions.operations`；
  危险操作（`mclaw` 帮助里带 ⚠ 的）必须逐个列出，不能靠 `领域.*` 覆盖。
- 可靠订阅必须有稳定 `id`（`ctx.on(EVENT, fn, id="xxx", durable=True)`），并且**幂等**（至少投递一次，同一事件可能来两次）：
  按 `ctx.delivery.event_id` 去重（`recipes.md` 第 1 节），或者做的事本身重复执行也没影响
  （删除类：对象已经不在了按成功处理，见 `examples/delete_cascade.py`）。
- 后台循环用 `ctx.task(coro, name=...)`，循环里捕获异常并记日志后继续，`CancelledError` 要原样抛出。
- 持久状态存插件数据（`PLUGIN_DATA`），读写文件走文件接口（`PLUGIN_FILES`）并在清单 `paths` 里申请。
- 连外网走用户的代理设置：`httpx.AsyncClient(transport=movieclaw_sdk.net.http_transport("<服务名>"))`，清单写 `network = true`。
- 主程序自带的库可以直接 import（如 `httpx`、`websockets`、`pydantic`、`fastapi`、`cryptography`、`parsel`、`pyyaml`）；
  别的依赖只能随包放进插件目录的 `vendor/`（纯 Python）；安装时不联网、不跑 pip。
- 日志用 `ctx.logger`，中文，带上关键 id。
- 按关键词匹配种子 / 发布名时：英文短词（TC、TS、CAM 这类）必须按**词边界**匹配，
  如 `re.compile(r"(?<![A-Za-z0-9])TC(?![A-Za-z0-9])", re.I)`，否则 `ts` 会命中 Hits、`cam` 会命中 Cameron；
  中文词用子串匹配即可。规则列表用元组 / 列表（保持顺序），不要用集合。

**禁止**
- 在决策钩子里产生副作用（发请求、写数据、调宿主操作）——钩子只做判断；副作用放到可靠事件或宿主操作里。
- 用内部服务（`DB`、`SECRETS`、`SETTING_STORE` 等未出现在契约清单里的）或直接 import 主程序的仓储层改数据库——
  第三方插件会被拒绝，进程外也拿不到。
- 在 `apply` 里做耗时工作（网络请求、全量扫描）：`apply` 有 30 秒上限，耗时工作放进 `ctx.task`。
- 修改 `$SRC` 下任何文件、修改本技能目录。插件只写在工作目录的 `plugins/` 下。
- 自行改成 `runtime = "inline"` 或加 `--allow-inline`。开放的扩展点（含 IM 通道）在独立进程里全部能用；
  报错看着像在说「只能在主进程里跑」，是你的代码有错（多半是 inject 写错），改代码，不要换运行方式。
- 重启、停止应用（`app restart` 等）或改服务器上的文件。插件的安装、升级、卸载都不需要重启；
  服务器上可能有人在播放、下载，重启会打断他们，也会打断你自己。觉得只有重启能解决时，停下来告诉用户。
- 为了让插件装上而删减用户要的功能（如去掉回调、去掉某种消息）。要删减先说明原因、等用户同意。
- 使用 `plugin dev` 时不带 `--once`；对不是开发用的服务器反复安装开发版本。

## 5. 安全与确认

批准安装 = 把清单里申请的宿主操作和路径授予这段代码，并在服务器上运行它。所以：

- 安装前向用户列出：条目 id、要做什么、申请的宿主操作（标出危险操作）、路径、是否联网、运行方式。
  **用户明确同意后才执行第 3 节第 7、10 步**。用户在本次对话里已经明确说过「直接装」的，视为同意。
- 申请最小权限：只列真正会调用的操作；能用演练（`dry_run`）验证的危险操作先演练。
- 不可恢复、又有取舍的行为（删种时连数据文件一起删会影响做种和分享率、覆盖文件、批量改订阅）不要替用户定默认值，
  列出选项请用户选。
- 装好后还要用户给凭据才能用的（通道绑定、外部服务的 Key），在征求安装同意的同一轮里一起说明要准备什么。
- 停用、卸载、回滚、清除数据：用户已经明确要求的（「帮我关掉 / 不想用了 / 卸了它」），直接执行并在结果里说明影响，
  不要再问一遍；只是问「能不能关、怎么关」的，讲清办法和影响，问要不要现在做。是你自己提议的，先说明影响、等用户同意。
  「暂时不用」做停用（随时启用），「不要了」才卸载。
- 不要把用户的凭据写进插件代码或清单；凭据写进配置模型（`SecretStr`），用户在插件的「设置」里填（`references/recipes.md` 第 13 节）。
- **绝不设法获取或截获登录凭据**：不读 mclaw 的配置 / 环境变量里的令牌，不用 strace、抓包、本地代理截请求头，
  不绕过鉴权直接 curl 需要登录的接口。mclaw 调不到的接口，就换上面的正规验证方式，或请用户在网页里打开确认。

## 6. 参考资料

| 文件 | 内容 |
|---|---|
| `references/extension-points.md` | 全部开放契约（用途、导入、示例）与「还没开放的 + 替代办法」 |
| `references/composition.md` | 一个插件组合多个扩展点、何时拆分、替换内置、插件之间配合 |
| `references/recipes.md` | 每种扩展点的写法片段（第 11 节含 IM 通道的接入方式选择） |
| `references/manifest.md` | 清单字段、权限、版本区间、安装 / 升级 / 回滚 / 卸载命令 |
| `references/architecture.md` | 原理：生命周期、进程外运行、权限模型、源码位置 |
| `references/explaining-to-users.md` | 给用户解释插件、权限、安全、卸载的标准说法 |
| `references/troubleshooting.md` | 按报错查原因 |
| `references/examples/` | 6 个经过测试的真实插件（先读 README） |
| `templates/starter/`、`scripts/` | 骨架；生成骨架、查契约、安装前检查的脚本 |
