# Meegle 插件开发 CLI 命令详解

> 本文件是 [`../SKILL.md`](../SKILL.md) 的命令字典补充。SKILL.md 里 A 类速查表的"详见"列均指向本文。

## CLI 基础

**命令前缀**：所有命令均通过以下方式调用：

```bash
lpm <command> [options]
```

**工程识别**：大部分命令需要在插件工程目录下执行（存在 `plugin.config.json`）。以下命令例外，可在任意目录执行：
- `login` — 全局认证
- `whoami` — 查看登录态
- `create` — 创建新插件工程

**配置来源**：`siteDomain`、`pluginId` 等信息自动从 `plugin.config.json` 读取，无需手动传入。

---

## 命令详解

### 登录认证

```bash
# 方式 A：浏览器 OAuth 授权（交互式）
lpm login --site-domain <域名>

# 方式 B：直接设置永久 Developer Token（推荐）
lpm login --site-domain <域名> --token <developer_token>
```

| 参数 | 必填 | 说明 |
|------|------|------|
| `--site-domain` | 是 | Meegle 站点域名，含协议（如 `https://meego.feishu-boe.cn`） |
| `--token` | 否 | 永久 Developer Token，有则跳过 OAuth |

### 查看登录态

```bash
lpm whoami
```

只读，列出已登录的 Meegle 站点，并标记建议用于 `lpm create` 的站点（最近登录优先）。无参数，可在任意目录执行，**绝不打印 token**。AI 取 `siteDomain` 前可先跑它，按输出与用户确认。

### 创建插件

```bash
lpm create --site-domain <域名> --name "<名称>" [--force]
```

| 参数 | 必填 | 说明 |
|------|------|------|
| `--site-domain` | 是 | 站点域名 |
| `--name` | 是 | 插件名称 |
| `--description` | 否 | 短描述 |
| `--force` | 否 | 已在插件目录中时跳过确认 |

**产出**：在当前目录创建插件工程，生成 `plugin.config.json` + `src/` + `node_modules/`。

### 启动调试

```bash
lpm start --auto
```

| 参数 | 必填 | 说明 |
|------|------|------|
| `--auto` | 否 | 自动打开浏览器调试页面（推荐） |
| `--source-type` | 否 | `local`（用本地配置）或 `remote`（用远端配置，默认） |

**行为**：启动 webpack-dev-server + 热更新，`--auto` 时自动拼接调试 URL 并在浏览器中打开。

### 同步配置

```bash
# 本地 → 远端 + 拉回模板（推送本地点位配置 + 自动拉回远端配置和模板代码，一条命令完成）
lpm update --source-type=local

# 仅远端 → 本地（只拉取，不推送）
lpm update
```

| 参数 | 必填 | 说明 |
|------|------|------|
| `--source-type` | 否 | `local`（推送到远端 + 自动拉回模板，常规配置流程用这个）或默认不传（仅从远端拉取） |
| `--allow-delete` | 否 | 用户**明示同意删除**后才加：绕过 push 前的删除闸口（本地配置丢了远端点位时默认 exit 2 拦下）。删除不可逆，无用户明示同意不得加 |

**行为**：
- `--source-type=local`：push 前先跑删除闸口（本地相比远端丢点位且未带 `--allow-delete` → exit 2、不推送），通过后把 `point.config.local.json` 推到远端，再**自动**拉回远端最新配置 + 后端模板代码，刷新 `plugin.config.json.resources` 和 `src/features/<resourceId>/index.tsx`
- 不传 `--source-type`：从远端拉取最新点位定义，生成/更新代码模板，更新 `plugin.config.json` 的 resources；仅在本地没有未推送的改动时使用

> Stage Config 配置流程：`local-config set --from <draft>`（本地校验 + 暂存到 `point.config.local.json`）→ `update --source-type=local`（推送 + 拉回模板代码）。两步跑完，本地 + 远端 + 模板代码都同步完成。

### 构建

```bash
lpm build [--zip]
```

| 参数 | 必填 | 说明 |
|------|------|------|
| `--zip` | 否 | 构建后打包为 zip |
| `--source-type` | 否 | `local` 或 `remote` |

**产出**：`build/` 目录。

### 构建发布

**完整发布是两步串行操作**，不可跳步：

```bash
# 第 1 步：构建 + 上传产物
lpm release
# 输出：Artifact version: <artifactVersion>

# 第 2 步：版本发布（必须使用第 1 步输出的 artifactVersion）
lpm publish \
  --artifact-version <artifactVersion> \
  --release-notes "<版本描述>"
```

**release 参数**：

| 参数 | 必填 | 说明 |
|------|------|------|
| `--source-type` | 否 | `local` 或 `remote` |

**publish 参数**：

| 参数 | 必填 | 说明 |
|------|------|------|
| `--artifact-version` | **是** | 来自 `release` 输出的构建产物版本号 |
| `--release-notes` | 是 | 版本描述 / release notes（中文） |
| `--version` | 否 | 语义化版本号，不传则自动 patch +1 |
| `--store` | 否 | `yes`（发布到商店）/ `no`（仅内部）。**不传则继承上一版本 visibility**；首次发布默认 `yes` |
| `--upgrade` | 否 | CLI 默认 `manual`；AI 不主动传，仅当用户明确要求 `all`（自动升级所有安装）/ `limit`（按范围）时才显式传 |

**关键**：`--artifact-version` 必须从 `release` 的 stdout 中提取（正则：`/Artifact version: (\S+)/`），不可编造。

### 修改基本信息

```bash
lpm update-description \
  --name "<插件名称>" \
  --short-description "<短描述>" \
  --detail-description "<详情描述>" \
  --category-ids "<id1>,<id2>"
```

| 参数 | 必填 | 说明 |
|------|------|------|
| `--name` | 否 | 插件名称 |
| `--short-description` | 否 | 短描述（纯文本） |
| `--detail-description` | 否 | 详情描述（纯文本，CLI 自动转富文本） |
| `--category-ids` | 否 | 分类 ID 列表，逗号分隔（从 `list-categories` 获取） |
| `--icon` | 否 | 图标 URL |

> 所有参数均为可选，只传需要修改的字段即可。

### 点位配置管理

```bash
# 写入远端配置到 .lpm-cache/config/remote.json
lpm local-config get --remote

# 把 draft 写入本地 point.config.local.json（全量替换语义，本地 staging；不推送远端）。
# 写本地前跑删除闸口：draft 相比远端丢点位且未带 --allow-delete → exit 2、不写本地。
# 成功后 CLI 会：
#   1) 把已消费的 draft 和 .lpm-cache/config/remote.json 基线移出活跃目录（你无需手动清理）
#   2) 把新的完整配置写入工程根 point.config.local.json
lpm local-config set --from .lpm-cache/config/draft-<timestamp>.json
# 用户明示同意删除后才加 --allow-delete 绕过删除闸口：
lpm local-config set --from .lpm-cache/config/draft-<timestamp>.json --allow-delete

# 预览本地 vs 远端差异（CRITICAL — 推送前必跑）
# stdout：[ADDED] / [MODIFIED] / [DELETED] 列表
# exit 0: 无删除，可直接推送
# exit 2: 有删除，stderr 打印 DELETION_REQUIRES_CONFIRMATION 清单 + 指引，AI 必须转呈用户获得明示确认
lpm local-config diff
```

- `get` / `schema` 的 stdout 只回一行相对路径，JSON 落在 `.lpm-cache/` 里；AI 用 `lpm ai peek` 按需取片段，不要把整份 JSON 带进对话 context
- `set` 接受 `--from <path>`（相对或绝对路径）+ `--allow-delete`（删除确认后才加），不推送远端——推送走 `update --source-type=local`
- `diff` 无参数，对比 `point.config.local.json` 与远端当前配置

> **CRITICAL — 全量提交 + 删除确认**：`local-config set` 是全量覆盖语义，减少点位不可逆。删除在 `set` / `diff` / `update --source-type=local` 三处都会 exit 2 硬拦——必须把 stderr 逐字转呈用户，等用户明示同意删除具体点位，才用 `--allow-delete`（set / update 支持）绕过那一步重跑。完整规则见 [`../../meegle-plugin/references/shared.md`](../../meegle-plugin/references/shared.md) 的"全量提交约束"和"删除点位前置检查协议"两节。

### AI 辅助命令（`lpm ai *`）

完整列表跑 `lpm ai --help`。各命令具体用法在用到的 workflow doc 内 inline 展示（如 `lpm ai peek` 见 [`../../meegle-plugin/references/feature-config-plan.md`](../../meegle-plugin/references/feature-config-plan.md)）。

**🔒 粒度铁律**（draft 编辑通用约定）：peek 整点位 → AI 改 → set / Write 整点位回去；不钻 `extension[...].sub_comp[...]` 深叶子路径。

### 生成 Schema

```bash
lpm schema
```

CLI 把 JSON Schema 写入 `.lpm-cache/schema/point-schema.json` 并在 stdout 回显路径。用 `lpm ai peek` 按需切片，**不要 Read 整个文件**（约 30K tokens）。

### 清理工作区（兜底）

```bash
lpm workspace clean --scope=all          # 清全部（保留 state.json）
lpm workspace clean --scope=mcp          # 只清 MCP 缓存
lpm workspace clean --include-state      # 连 workflow checkpoint 一起清
```

`.lpm-cache/` 的生命周期由 CLI 自动维护——`local-config set` / `publish` 成功时会按子目录清理。仅在中断后需要强制重置时才手动调用 `workspace clean`。

### 分类列表

```bash
lpm list-categories [--type plugin]
```

| 参数 | 必填 | 说明 |
|------|------|------|
| `--type` | 否 | `plugin`（默认）/ `ai` |

输出 JSON 数组，每项含 `id` 和 `name`，用于 `update-description --category-ids`。

### 权限管理

`lpm perm` 管理**本插件对应 app 的 OpenAPI 权限 scope**（不操作 Meegle 产品业务数据）。在自建后端要调 OpenAPI 时用 —— 调用前先把要用的 scope 申请上。

```bash
# 查：已开通可调的 scope（granted）+ 还能申请的（applicable），各含覆盖的 OpenAPI
lpm perm list

# 校验：要调的这些 OpenAPI 能不能调 / 缺哪些权限（接口 key / 短名 / 中文名均可）
lpm perm check --apis <api1,api2,...>

# 申请：把这些权限 key 加到本插件（已开通的自动跳过）；先 perm check 看缺哪些 scope
lpm perm apply --scopes <key1,key2,...>
```

| 命令 | 参数 | 说明 |
|------|------|------|
| `perm list` | `[--raw]`；`[--plugin <id> --site-domain <url>]`（均可选，成对） | 默认 stdout 输出 CLI 派生视图：`{ appType, granted: [{scope,name,openapis:[{key,name,url}]}]`（已开通、当前可调，= `perm_info ∩ perm_meta`）`, applicable: [{scope,name,openapis}]`（还能申请的，= `perm_meta − perm_info`；`ai_node`/`ai_field` 省略，固定集不可申请）`}`。可行性看 `granted ∪ applicable` 天花板；实行看 `granted`、缺的从 `applicable` 申请。`--raw` 出后端原始 `{ perm_info, perm_meta }`（调试用） |
| `perm check` | `--apis <a,b,c>`（必填，逗号分隔的 OpenAPI 标识：resource key / 短名 / 中文名）；`[--plugin <id> --site-domain <url>]`（可选，成对） | CLI 把每个接口反查到归属 scope 再比对已开通，stdout 输出判决 JSON：`satisfied`（归属 scope 已在 granted）/ `needApply: [{scope,name,apis}]`（normal 缺、可申请）/ `infeasible: [{api,reason}]`（AI 应用缺固定集 scope，做不了）/ `unknown`（此 app 目录里查无此接口）/ `ambiguous: [{api,candidates}]`（中文名撞多个 scope，改用 resource key 消歧） |
| `perm apply` | `--scopes <a,b,c>`（必填，逗号分隔的权限 key）；`[--plugin <id> --site-domain <url>]`（可选，成对） | 先本地对照可申请目录（`perm_meta`）校验，非法 key 直接 `exit 1` 并提示用 `perm list`；合法则申请（`operate_type=1` 新增），stdout 输出 `{ perm_info: [...] }` 的 JSON（申请后该插件已勾选的权限列表） |

- **插件定位优先级**：`--plugin/--site-domain` 显式指定 > 插件工程目录的 `plugin.config.json` > 全局只读注册表（`~/.lpm/`，记录你碰过的插件，无 secret；单登录态 + 单插件时自动定位，多个时报错列出让你加 `--plugin`）。所以这几条命令**也能在插件工程目录之外跑**（如自己的后端代码仓）；都是 token-only、不需 pluginSecret。从工程目录外定位时会在 stderr 回显 `perm <action>: plugin <id> @ <domain>`。鉴权走 `login` 拿到的 dev token，没登录会 stderr 提示。
- `perm apply` 是对 app 权限的真实变更——编排 skill（`meegle-plugin-backend`）调它前会先列出即将申请的 scope 等用户点头，见 [`../../meegle-plugin/references/shared.md`](../../meegle-plugin/references/shared.md) 的"必须先确认"清单。
- 需审核的 scope（`perm list --raw` 的 `perm_meta[].auditable_bit` 按位标记）申请后还要在开发者后台填申请原因、走审批；`perm apply` 只负责把 scope 加到草稿。AI 应用（`app_type=ai_node`/`ai_field`）的权限按点位类型自动管，`perm apply` 对它会报错。

---

## 命令依赖关系

典型顺序（前置依赖 → 后置操作）：

1. `login` — 全局认证（一次性）
2. `create` — 创建插件工程（一次性）
3. `schema` → `local-config set --from <draft>` → `local-config diff` → `update --source-type=local` — 配置点位（三步：set 本地校验 + 暂存；diff 预览 + 删除确认 gate；update --source-type=local 推远端 + 拉回模板）
4. `start --auto` — 本地调试（依赖 step 3 的 `update`）
5. `update-description` — 修改基本信息（可随时）
6. `release` → `publish` — 构建发布（两步串行，不可跳）

按需（不在固定生命周期里）：`perm check --apis ...`（要调的接口能不能调 / 缺哪些 scope）→ `perm apply --scopes ...` —— 自建后端要调某些 OpenAPI 时，先核可行性再把缺的 scope 申请上（编排见 `meegle-plugin-backend`）。
