@lark-project/cli
Version:
飞书项目插件开发工具
163 lines (105 loc) • 12.4 kB
Markdown
---
name: meegle-plugin-shared
version: 1.0.0
description: "Meegle 插件开发共享基础:插件工程识别、Device Code OAuth 认证、Token 管理、安全规则。所有 meegle-plugin-* skill 的公共前置依赖,不单独对用户触发。"
metadata:
requires:
bins: ["npx"]
cliHelp: "lpm --help"
---
# Meegle 插件开发共享规则
> 本文件只给通用前置规则(根原则 / 工程识别 / 认证 / 安全)。引用的 references 在下文各小节被触发条件时就地点名,**不要在进入 skill 的第一轮就预加载**。
## 三条根原则
### 根原则 1:无源即停(输出可溯源)
AI 输出每一项有业务语义的内容,必须可追溯到合法信息源;找不到 → 立即停下问用户。
### 根原则 2:数据完整性(写入前先拿基线)
任何对远端配置的写入都是全量覆盖,提交前以远端基线为参照、对比差异、对减少项让用户确认。
### 根原则 3:真实数据动作显式确认
任何对远端产生真实影响的动作,执行前向用户列出"将要做什么 + 影响哪些数据",等用户显式同意才执行。
**五类必须先确认的动作**:
1. **创建插件**(在 Meegle 后台生成插件记录,pluginId 从此绑定)
2. **配置点位**(`local-config set` 等,把 plugin.config.json 全量推到后台)
3. **配置基本信息**(更新插件名称 / 简短描述 / 详细描述 / 分类等元信息)
4. **发布插件**(`publish`,发到 Meegle 市场,用户可见,最不可逆)
5. **申请 OpenAPI 权限**(`lpm perm apply` 前列出即将申请的 scope 清单、等用户点头)——给插件 app 授 OpenAPI scope 是对 app 权限的真实变更:拓宽这个插件 app 能做的事、可能要走后台审批、在 app 的权限列表里可见,和创建 / 配置点位 / 配置基本信息 / 发布同级
> 遇到未分类的 CRITICAL → 按"无源即停"默认处理(最保守动作:停下问用户)。
## 插件工程识别
**判定**:cwd 存在 `plugin.config.json` 且含 `pluginId`(`MII_` 开头)。
**识别后**:CLI 自动从配置读 `siteDomain` / `pluginId` / `pluginSecret` / `resources`,skill 不需要手动传。这些字段由 CLI 维护(`local-config set` 一步完成"推+拉+生成模板"),skill 只读、不直接 Edit/Write。
**执行约定(CRITICAL — 工作目录锚定,本规则唯一权威源)**:agent 的 shell 在每条命令之间**不保证保留 cwd**,且 primary cwd 不一定等于插件工程目录(插件可能嵌在子目录、或你正坐在别的仓里)。因此**每一条 `lpm` 命令都带 `--cwd <插件工程绝对路径>` 前缀**,例如 `lpm --cwd /abs/path/to/plugin local-config diff`。`--cwd` 让 CLI 在执行前先 chdir 到插件根,`plugin.config.json` 与 `.lpm-cache` 都按它解析;目录不对时 CLI 直接 fail-fast(错误信息带 `--cwd` 提示),不会静默跑到错的地方。识别 / `create` 通过后**立即把插件工程的绝对路径记下来**(有 checkpoint 时用 `lpm --cwd <abs> ai state set` 写进 `.lpm-cache/state.json`),之后每条命令都用它。
## 认证
Token 由 CLI 按 `siteDomain` 分域管理。
**`--site-domain` 传参规则**:
- `create` / `login` 由 AI 传 `--site-domain`(用户未指定时**先跑 `lpm whoami`,一切以它列出的真实登录站点为准**:列出了已登录站点就原样列出全部真实站点供用户挑选——忽略 `← 建议` 标记、不预选、不附自定义项;仅当 whoami 报「未登录」时才用「飞书项目 / Meegle / 自定义域名」通用项让用户贴 URL)
- 其他命令不传 `--site-domain`,CLI 自动从 `plugin.config.json` 读取
**前置约定**:用户在接入插件开发前应已执行过 `lpm login`(见接入文档 Step 2)。skill 直接调 CLI,token 有效性由 CLI 在命令运行时判定。
**遇 auth 错误时**:CLI 会 stderr 打印完整登录指引(方式 A token 粘贴 / 方式 B Device Code OAuth + 可执行命令)。AI **逐字转呈** 给用户,由用户执行 `lpm login` 后重试命令。
## 安全规则
- **CLI 维护的文件/目录**(`.lpm/` 内部目录、`plugin.config.json`)只通过 CLI 命令操作;`plugin.config.json` 的点位数据由 `local-config set`(本地暂存)+ `update --source-type=local`(推远端 + 拉模板代码)两步维护,skill 读它、不直接写
- **禁止输出密钥**(accessToken、pluginSecret)到终端明文
### 自建后端红线
插件的"自建后端那一半"(webhook 接收 / 调 OpenAPI / 写回,由 feature「→ 后端那一半」编排、服务端代码交后端会话写):
- **OpenAPI 鉴权凭据进后端 env,不进前端 bundle**(`AUTHORING.md §8.1`)——后端代码里只写 `process.env.X` 引用,真值由用户设进后端运行环境;不从 `plugin.config.json` grep / decrypt `pluginSecret` 粘进任何地方
- **后端运行时拿 Meego 数据只走 OpenAPI**(`AUTHORING.md §8.2`)——不依赖 `lark-project` skill、不 `child_process.exec('lpm ...')`;`lark-project` / `lpm` 是开发期本地工具,没有面向运行时的鉴权与稳定性 SLA
### 全量提交约束(CRITICAL — 防数据丢失,本规则唯一权威源)
`local-config set` 是**全量替换**——提交什么就存什么,远端不做合并。**遗漏任何现有点位都会导致该点位被永久删除。**
**操作流程(不可省略任何一步)**:
1. 先 `local-config get --remote` 获取远端完整配置作为基础
2. 在完整配置基础上做局部增删改
3. 将修改后的**完整配置**作为 draft 三步提交:`local-config set --from <draft>`(本地校验 + 写入 `point.config.local.json`)→ `local-config diff`(预览改动;有删除时 exit 2 必须转呈用户获取确认)→ `update --source-type=local`(推远端 + 拉回模板代码)
**底线**:必须以 `get --remote` 的基线构造完整 draft 再提交;不能只传变更部分,不能直接 Edit/Write `plugin.config.json`。
### `local-config set` 用户确认 gate(CRITICAL)
`local-config set` 是全量替换、不可静默回滚,**执行前 MUST 列 ADDED / MODIFIED / DELETED 三类差异清单 + 等用户明示同意**("确认" / "OK" / "推送")才能跑。用户拒绝 / 提改 → 回上游改 draft 重跑,不就地改绕过。
清单格式、Checkpoint 恢复、变更字段对比展示、与 A2 删除 gate 分工见 [`feature-config-apply.md §A0`](feature-config-apply.md)(格式唯一权威源)。
### 删除点位前置检查协议(CRITICAL — 所有调用路径前置 gate)
减少点位(draft / 本地配置丢了远端还在的点位)在三处都会被**硬拦下、`exit 2`**,都打印同一份清单(`⚠️ DELETION_REQUIRES_CONFIRMATION` 标题 + 每项 `type[key] "name"`):
- `local-config set`:写本地前比对远端,丢点位 → exit 2,**不写本地**
- `local-config diff`:预览,丢点位 → exit 2
- `update --source-type=local`:推送前比对远端,丢点位 → exit 2,**不推送**
**任一处 exit 2 → 立即停止**:把 CLI 的 stderr **逐字转呈给用户**,等用户明示同意删除具体点位。不要静默删除、不要"帮用户总结"、不要自己判断"这是废弃点位应该删"。
用户明示同意后,才用 `--allow-delete` 绕过那一步重跑(`set` / `update` 均支持;`diff` 是纯预览无此参数)。**没有用户明示同意,绝不得加 `--allow-delete`**——删除远端点位不可逆。远端不可达时这些检查降级为告警放行(push 命中后端会再校)。
### 无源即停(CRITICAL — 根原则 1 的执行细则)
适用范围:代码、API 调用、字段值、配置参数等所有 AI 输出场景。
**合法信息源**:用户显式输入 / 粘贴、用户授权的 `<PLACEHOLDER: ...>`、权威工具的精确返回(点位标准能力 doc、飞书项目知识 MCP、CLI 实测输出)、工程已有代码/配置。
**非法信息源**(典型的"看起来合理但编造"):AI 经验类比("通常 SDK 都有 getX/setX")、概念脑补(从 schema 字段名推断 SDK 方法名)、命名约定猜测("通常驼峰所以这里也是")。
**遇到无源时的统一动作(强制话术 + 强制停产出)**:
触发"无源即停"时 AI **MUST**:
1. **立即停止产出**:本轮不得再调任何产出工具(Write / Edit 写入点位 JSON、代码文件、CLI 命令等)。"停下来问用户" ≠ "嘴上停、同时继续写"。
2. **逐字输出下述话术**(允许填入具体内容、可选项 A/B/C 可剪裁为场景相关的版本,但"无法找到 … 依据 / 可选 / 禁止猜测 / 请告诉我选哪个" 四要素必须齐全):
```
我无法在合法信息源中找到 "<具体内容>" 的依据。
可选:
A. 跳过此项,输出 TODO 占位(推荐——不写错的好过写错的)
B. 你提供真实值 / 示例 / 文档链接
C. 提供检索关键词,我重新查(可能找错了)
禁止我自行猜测——猜出来的内容会通过表层校验但运行时全废。
请告诉我选哪个。
```
3. **等待用户明确选择 A/B/C 之一** 才能继续;不得"选 A 同时继续产出其他项"的并发动作(同一批产出里任一项无源,整批阻塞)。
## MCP 检索技巧(飞书项目知识 MCP)
`mcp__feishu-project-knowledge__search_meegle_plugin_docs` 是 schema / point-type doc 没覆盖时的 fallback 主力。**实测有效模板**(按场景挑用):
| 场景 | 关键词模板 | 实测示例 |
|---|---|---|
| **点位上下文 API**(getContext / 字段值读写 / watch 等) | `<PointName 英文> 上下文` | `Page 上下文` / `CustomField 上下文` / `Control 上下文` |
| **点位整套介绍**(配置 + 开发 + 数据通信) | 直接搜 PointName 英文 | `CustomField` / `LiteAppComponent` / `Schedule` / `Button` |
| **总览 / 选型** | doc 入口词 | `添加插件功能` / `添加构成` / `客户端开发概述` |
| **具体功能** | 业务功能词 + 技术词 | `表格列 控件 DSL` / `字段值 读取` / `自动化连接器 配置` / `事件订阅` |
| **OpenAPI 接口** | API 中文名 + "接口" | `获取工作项详情 接口` / `创建自定义字段 接口` |
| **错码 / 限制 / 沙箱** | 直接关键词 | `客户端沙箱限制` / `客户端资源限制` / `saveFieldValue 错码` |
**通用搜索心法**:
- **MCP 是 fuzzy 文本检索**,描述性查询(业务功能 + 技术词)比单查 API 名命中相关 doc 更全
- **用官方术语**:`工作项`(不是"任务")/ `空间`(不是"项目")/ `字段类型`(不是"自定义字段类型")/ `控件`(不是"组件")
- **中英双语** 高难关键词同时拟(如 `排期 schedule` / `字段配置 field config`)
- **搜不到 ≠ 不存在** — 改换近义词 / 上一层概念 / 业务场景描述 重试 1-2 次;分概念查再汇总
**MCP 缓存协议**(防 context 爆炸 — 本节是权威源,其他 doc 引用本节不复述):
1. MCP 返回后立即把**完整原文** Write 到 `.lpm-cache/mcp/<slug>.md`,按子主题用 `## <主题>` 分节
2. **chat 回复只给路径 + ≤100 字摘要**,原文不回流
3. **重访同一查询**用 `lpm ai peek .lpm-cache/mcp/<slug>.md "<章节标题>"` 按需取节,不要 Read 整份;7 天以上视为过期重拉
`.lpm-cache/` 由 CLI 在 create/init 时自动写入 `.gitignore`,在 `local-config set` / `publish` 成功后按子目录自动清理,AI 只负责写入、不负责清理。
## Checkpoint(进度追踪)
state.json 的读写走 `lpm ai state get` / `lpm ai state set '<json>'`——CLI 经 `workspacePaths()` 把路径解析到插件根、`assertPluginRoot` 守卫,配合 `--cwd` 在任意目录都落到插件的 `.lpm-cache/state.json`。
**判断规则**:子 skill 执行前用 `lpm --cwd <projectRoot> ai state get` 读 checkpoint:
- **有输出** → 处于 workflow 编排中,按 [`references/checkpoint.md`](checkpoint.md) 的协议在每个 CLI 命令前后用 `lpm --cwd <projectRoot> ai state set` 更新 checkpoint
- **空输出** → 独立调用,不写 checkpoint
## 错误处理
遇到错误 → Read [`references/errors.md`](errors.md) 查对应处理方式。