Metadata-Version: 2.4
Name: lingora-cli
Version: 0.1.0rc2
Summary: Lingora translation synchronization CLI
Project-URL: Repository, https://github.com/brain/lingora
Requires-Python: >=3.12
Description-Content-Type: text/markdown

# Lingora CLI 命令手册

需要 Python 3.12+。以下示例假设已安装指定版本 `lingora-cli`；示例 UUID、域名和分支均为占位值，
不包含真实凭据。所有 HTTP 操作都使用 Lingora 公开 API。

## 安装与首次配置

```bash
# 本地试用；CI 和消费项目应改为固定版本 lingora-cli==x.y.z。
uv tool install --python 3.12 lingora-cli
lingora --version

# 使用包内的 lingora_example.json 模板，在当前目录创建 .lingorarc；不会联网，也不会覆盖已有文件。
lingora config init
```

创建完成后，至少需要把 `.lingorarc` 中的 `apiUrl`、`projectId`、`branch`、
`push.files` 和 `pull.languages` 改成目标项目的真实值，后续命令会默认读取该文件。
API Key 不得写入配置文件，应通过 `LINGORA_API_KEY` 或临时的 `--api-key` 参数提供。

## 1. 命令总览

| 命令 | 职责 | 副作用 |
| --- | --- | --- |
| `help [command ...]`、`--help` | 查看用法，例如 `help extract print` | 无 |
| `--version` | 查看安装包版本 | 无 |
| `config init` | 使用包内 `lingora_example.json` 模板创建无凭据的 `.lingorarc`，拒绝覆盖 | 新建本地文件 |
| `init` | `config init` 的兼容入口 | 新建本地文件 |
| `push` | 上传 Android 基础资源 | 写入服务端 |
| `pull` | 下载目标语言，校验后更新生成资源 | 写入本地生成目录 |
| `validate` | 校验已有生成资源 | 本地只读 |
| `extract print` | 从 Android XML 提取资源键及基础值 | 本地只读 |
| `extract check` | 校验 XML 结构、重复键等，返回键数量 | 本地只读 |
| `compare` | 对比本地与远端的键集合 | 远端只读 |
| `sync` | 补齐缺失键，可选移除未使用键 | 远端写入，需要 `--yes`；`--dry-run` 只读 |
| `tag` | 在明确范围内增加或移除 Key 标签 | 远端写入，需要 `--yes`；`--dry-run` 只读 |
| `branch` | 列出、创建或删除分支 | 创建会写入；删除需要 `--yes` |
| `merge` | 创建合并预览，或应用无冲突的合并 | 预览也会建立服务端记录；应用需要 `--apply --yes` |
| `delivery check` | 检查公开 CDN 清单、资源类型和缓存头 | 公开 HTTP GET |

`lingora --help` 显示顶层命令；`lingora COMMAND --help` 显示参数；
`lingora help COMMAND [SUBCOMMAND]` 返回同一份帮助并支持 `--json`；`lingora --version`
显示安装包版本。这些帮助命令不会读取配置或访问网络。

不提供项目级 `mock-ci` 命令。消费项目自行编排 `lingora push && lingora pull`。
正常 Android Gradle 构建不执行这些命令。

当前边界：资源格式仅支持 `ANDROID_XML`，同次处理一个基础语言和一个 namespace，可有多个 XML 文件。
`extract` 读取 XML 资源，不扫描 Kotlin、React、Vue、Svelte 或 Angular 源码；不提供自定义执行器、
持续 watch、浏览器登录或本机凭据存储。`sync` 是 Android 资源键同步，不是上传后再下载。
这些未实现能力不会注册为空壳命令。

## 2. 通用参数与优先级

通用参数可放在命令前后，嵌套命令同样适用，例如 `lingora --json branch` 和
`lingora delivery check --alias current --json`。

| 参数 | 用途 |
| --- | --- |
| `--config PATH` | 读取指定 JSON；明确指定但不存在时失败 |
| `--no-config` | 完全不读取本地配置，与 `--config` 互斥 |
| `--api-url URL` | 服务根地址，不带 `/api/v1`；禁止内嵌凭据、query 或 fragment |
| `--project-id UUID` | Lingora 项目 UUID |
| `--api-key KEY` | 临时凭据参数；推荐环境注入以避免暴露于进程参数 |
| `--branch NAME`、`-b NAME` | Lingora 分支名称，不是 Git 分支或 UUID |
| `--timeout SECONDS` | 单次 HTTP 超时，默认 30，必须为有限正数 |
| `--json` | stdout 仅输出一个 JSON 结果，便于脚本或 MCP 消费 |

默认读取当前目录的 `.lingorarc`；没有该文件时，可仅通过完整参数执行。
显式配置缺失、内容非法或含 `apiKey` 时必须失败，不静默忽略。

配置优先级：命令参数 > `LINGORA_API_URL` / `LINGORA_PROJECT_ID` / `LINGORA_BRANCH` > JSON。
凭据优先级：`--api-key` > `LINGORA_API_KEY`。凭据不写入配置、安装包或本机凭据文件。
文件中资源相对路径以配置目录为准；命令行 `--files`、`--path` 相对路径以当前目录为准。

```bash
lingora config init
# 或者指定文件并在首次创建时填入连接信息；已有文件时不可再次创建。
lingora config init --config ci/lingora.json \
  --api-url https://lingora.example.com \
  --project-id 11111111-1111-1111-1111-111111111111 --branch develop
lingora push --config ci/lingora.json --branch develop
lingora pull --config ci/lingora.json --branch develop --json
```

`lingora init` 保留为兼容入口，行为与 `lingora config init` 相同。查看命令级帮助：

```bash
lingora help config init
lingora push --help
```

## 3. 配置文件完整说明

### 3.1 创建配置

```text
lingora config init [--config PATH] [--api-url URL] [--project-id UUID] [--branch NAME]
```

| 参数 | 用途 |
| --- | --- |
| `--config PATH` | 创建指定文件；默认是当前目录的 `.lingorarc` |
| `--api-url URL` | 创建时写入服务根地址；不发起网络请求 |
| `--project-id UUID` | 创建时写入项目 UUID |
| `--branch NAME`、`-b NAME` | 创建时写入默认 Lingora 分支 |
| `--json` | 返回包含绝对配置路径的单个 JSON 结果 |

命令使用排他创建，不覆盖已有文件；即使传入 `--api-key`，密钥也不会写入模板。

### 3.2 完整模板

包内 `lingora_example.json` 模板及 `config init` 生成的 `.lingorarc` 内容如下；字段用途、是否必填和默认值见下一节。

```json
{
  "apiUrl": "https://lingora.example.com",
  "projectId": "00000000-0000-0000-0000-000000000000",
  "branch": "your-branch",
  "format": "ANDROID_XML",
  "push": {
    "files": [
      {
        "path": "app/src/main/res/values/strings.xml",
        "language": "en",
        "namespace": "app"
      }
    ],
    "forceMode": "NO_FORCE",
    "overrideMode": "RECOMMENDED",
    "errorOnUnresolvedConflict": "auto",
    "overrideKeyDescriptions": false,
    "convertPlaceholdersToIcu": false,
    "tagNewKeys": [],
    "removeOtherKeys": false
  },
  "pull": {
    "path": "generated/lingora/res",
    "languages": ["zh-CN"],
    "namespaces": ["app"],
    "states": ["TRANSLATED", "REVIEWED"],
    "fileStructureTemplate": "values-{androidLanguageTag}/strings.xml",
    "emptyDir": true,
    "tags": [],
    "excludeTags": [],
    "supportArrays": false,
    "includeEmptyTranslations": false
  }
}
```

### 3.3 顶层字段

| 字段 | 必填 / 默认值 | 含义 |
| --- | --- | --- |
| `apiUrl` | 远端操作必填 | Lingora 服务根地址，不带 `/api/v1`；只允许 HTTP(S)，不得包含凭据、query 或 fragment |
| `projectId` | 远端操作必填 | Lingora 项目 UUID |
| `branch` | 资源远端操作必填 | 默认 Lingora 分支名称；可被 `--branch` 或 `LINGORA_BRANCH` 覆盖 |
| `format` | 默认 `ANDROID_XML` | 资源格式；当前只支持 `ANDROID_XML` |
| `defaultNamespace` | 可选，默认空字符串 | `push.files[].namespace` 缺失时的兼容回退；新配置建议在每个文件上显式填写 namespace |
| `push` | 资源命令必填 | 上传源文件和导入行为，详见下表 |
| `pull` | 下载或校验时必填 | 下载目标、筛选条件和输出行为，详见下表 |
| `contentDeliveries` | 默认 `{}` | Delivery 别名到公开 URL 的映射，例如 `current` |
| `apiKey` | 禁止 | 不允许持久化；使用 `LINGORA_API_KEY` 或 `--api-key` |

配置优先级为命令参数 > 环境变量 > JSON。配置中的相对资源路径以配置文件目录为基准；
命令行传入的相对路径以当前工作目录为基准。

### 3.4 `push` 字段

| 字段 | 必填 / 默认值 | 含义 |
| --- | --- | --- |
| `push.files` | 必填、至少一个 | Android 基础资源文件数组；同批文件必须使用相同语言和 namespace，文件名及资源键不得重复 |
| `push.files[].path` | 必填 | Android XML 路径，相对于配置文件目录 |
| `push.files[].language` | 必填 | 基础语言标签，例如 `en`、`en-US` |
| `push.files[].namespace` | 默认 `defaultNamespace` | 文件所属 namespace；同批文件必须一致，空字符串表示默认 namespace |
| `push.forceMode` | `NO_FORCE` | 导入冲突策略：`KEEP`、`OVERRIDE` 或 `NO_FORCE` |
| `push.overrideMode` | `RECOMMENDED` | 覆盖范围：`RECOMMENDED` 遵守审核值保护，`ALL` 请求覆盖全部允许项 |
| `push.errorOnUnresolvedConflict` | `auto` | 未解决冲突的失败策略：`yes`、`no` 或 `auto` |
| `push.overrideKeyDescriptions` | `true`；模板为 `false` | 是否用 XML 描述覆盖服务端 Key 描述 |
| `push.convertPlaceholdersToIcu` | `true`；模板为 `false` | 是否在导入时将 Android 占位符转换为 ICU 格式 |
| `push.tagNewKeys` | `[]` | 给本次新增 Key 添加的标签 |
| `push.removeOtherKeys` | `false` | 是否删除当前导入范围内未出现的服务端 Key；具有删除副作用，谨慎开启 |

### 3.5 `pull` 字段

| 字段 | 必填 / 默认值 | 含义 |
| --- | --- | --- |
| `pull.path` | `generated/lingora/res` | 专用生成目录；不得包含配置文件或基础资源文件 |
| `pull.languages` | 下载、校验及配置式 Delivery 检查必填 | 目标语言列表，不得包含基础语言，且转换后的 Android qualifier 不得重复 |
| `pull.namespaces` | 默认源文件 namespace | 当前必须恰好等于 `push.files` 使用的单一 namespace |
| `pull.states` | `TRANSLATED`、`REVIEWED` | 下载状态；可选 `UNTRANSLATED`、`TRANSLATED`、`REVIEWED` |
| `pull.fileStructureTemplate` | `values-{androidLanguageTag}/strings.xml` | 生成路径模板；支持 `{languageTag}`、`{androidLanguageTag}`、`{snakeLanguageTag}`、`{namespace}` 和 `{extension}` |
| `pull.emptyDir` | `false`；模板为 `true` | 校验成功后是否替换整个专用生成目录 |
| `pull.tags` | `[]` | 仅下载命中任一标签的 Key |
| `pull.excludeTags` | `[]` | 排除命中任一标签的 Key |
| `pull.supportArrays` | `false` | 是否把 `name[0]`、`name[1]` 等索引 Key 重新导出为 Android 数组 |
| `pull.includeEmptyTranslations` | `false` | 是否包含空或未翻译的值 |

### 3.6 `contentDeliveries` 字段

每个键是本地别名，每个值是公开 Delivery 根 URL。例如：

```json
{
  "contentDeliveries": {
    "current": "https://cdn.example.com/project/channel"
  }
}
```

随后可运行 `lingora delivery check --alias current`。该 URL 只用于 CLI 检查；Android App
仍需在自己的运行时配置中设置 Delivery URL，并确保 URL 与 namespace 对应同一份资源。

## 4. push：完整参数上传

所有必要配置都可以直接跟在 `push` 后面，无须 `.lingorarc`。先通过安全的个人环境或 CI Secret
注入 `LINGORA_API_KEY`，不要将真实密钥直接写在示例命令或版本化文件里。

```bash
lingora push \
  --no-config \
  --api-url https://lingora.example.com \
  --project-id 11111111-1111-1111-1111-111111111111 \
  --branch develop \
  --files app/src/main/res/values/strings.xml app/src/main/res/values/arrays.xml \
  --base-language en-US \
  --namespace app \
  --format ANDROID_XML \
  --force-mode NO_FORCE \
  --override-mode RECOMMENDED \
  --error-on-unresolved-conflict auto \
  --no-override-key-descriptions \
  --no-convert-placeholders-to-icu \
  --no-remove-other-keys \
  --json
```

`push` 仅要求连接、分支和源文件信息，不要求配置下载目标语言。

| 参数 | 用途 / 默认值 |
| --- | --- |
| `--files PATH ...` | 一个或多个基础 XML，可重复使用；同次文件名和资源键不得重复 |
| `--base-language TAG` | 所有源文件的基础语言；使用配置时可覆盖其 language |
| `--namespace NAME` | 所有源文件的 namespace；空字符串表示默认空 namespace |
| `--format ANDROID_XML` | 资源格式，默认 `ANDROID_XML` |
| `--force-mode KEEP\|OVERRIDE\|NO_FORCE` | 冲突策略，默认 `NO_FORCE`，不会交互询问 |
| `--override-mode RECOMMENDED\|ALL` | 默认 `RECOMMENDED`，遵守服务端审核值保护策略 |
| `--error-on-unresolved-conflict yes\|no\|auto` | 未解决冲突时的失败策略，默认 `auto` |
| `--override-key-descriptions` / `--no-override-key-descriptions` | 覆盖描述开关；未配置时为 true，`config init` 模板为 false |
| `--convert-placeholders-to-icu` / `--no-convert-placeholders-to-icu` | 占位符转换；未配置时为 true，`config init` 模板为 false |
| `--tag-new-keys TAG ...` | 给新增键添加标签 |
| `--remove-other-keys` / `--no-remove-other-keys` | 默认 false；true 会删除本次导入范围内未出现的服务端键，谨慎使用 |

`push.files` 的多个文件通过一次 multipart import 提交，而不是分别上传后删除彼此的资源。
服务端保护、权限不足或冲突仍会使操作失败，CLI 不绕过限制。

## 5. pull 与 validate：下载和本地校验

```bash
lingora pull \
  --no-config \
  --api-url https://lingora.example.com \
  --project-id 11111111-1111-1111-1111-111111111111 \
  --branch develop \
  --files app/src/main/res/values/strings.xml app/src/main/res/values/arrays.xml \
  --base-language en-US \
  --namespace app \
  --languages ja zh-CN \
  --path generated/lingora/res \
  --states TRANSLATED REVIEWED \
  --file-structure-template 'values-{androidLanguageTag}/strings.xml' \
  --empty-dir \
  --json

lingora validate --config .lingorarc --json
```

基础文件用于类型、占位符和数组长度校验，不会随下载覆盖。
`pull` / `validate` 可使用 `--files`、`--base-language`、`--namespace`、`--format`，以及：

| 参数 | 用途 |
| --- | --- |
| `--languages TAG ...`、`-l` | 必填目标语言，不包含基础语言 |
| `--path DIR` | 专用生成目录，默认 `generated/lingora/res` |
| `--namespaces NAME ...`、`-n` | 必须与源文件的单一 namespace 一致 |
| `--states STATE ...`、`-s` | 默认 `TRANSLATED REVIEWED`；可含 `UNTRANSLATED` |
| `--file-structure-template TEMPLATE` | 默认 `values-{androidLanguageTag}/strings.xml` |
| `--empty-dir` / `--no-empty-dir` | 是否校验后替换完整生成目录；未配置为 false，`config init` 模板为 true |
| `--tags TAG ...` | 仅下载命中任一标签的键 |
| `--exclude-tags TAG ...` | 排除这些标签 |
| `--support-arrays` / `--no-support-arrays` | 将索引键按数组导出，默认 false |
| `--include-empty-translations` / `--no-include-empty-translations` | 默认 false |

`validate` 不联网、不写文件；下载相关筛选参数仅影响 `pull` 的请求。
下载、解包或校验失败不替换旧目录；不完整目标数组在下载暂存目录中整组移除，以使用 APK fallback。
不允许输出目录包含配置或源资源，也不允许 ZIP 路径穿越、额外文件或覆盖基础文件。
没有目标翻译时可成功但显示 `exported=none`；本地 `validate` 的 `languages=[]` 不代表翻译已下载。

## 6. extract、compare、sync：资源键工作流

```bash
# 本地读取资源键；数组转换为 days[0]、days[1] 等键。
lingora extract print --config .lingorarc --json
lingora extract check --config .lingorarc --json

# 仅比较键名集合，不比较所有翻译值；--check 在有差异时返回 1。
lingora compare --config .lingorarc --branch develop --json
lingora compare --config .lingorarc --check --json

# 先预览，再明确批准补齐缺失键。不会覆盖已有值或描述，不下载资源。
lingora sync --config .lingorarc --dry-run --json
lingora sync --config .lingorarc --yes --tag-new-keys added-by-cli --json

# 可选删除当前 namespace 内未出现在本地输入中的键。
lingora sync --config .lingorarc --yes --remove-unused --json
```

三个命令都可使用 `--files`、`--base-language`、`--namespace` 和 `--format` 指定源资源，
也支持第 2 节的通用参数。

| 命令 / 参数 | 用途 |
| --- | --- |
| `extract print` | 输出 namespace、基础语言以及展开后的 Key、类型和值 |
| `extract check` | 校验源 XML 并返回 `valid` 和 `keyCount`，不输出所有值 |
| `compare` | 返回本地新增 `added`、远端多余 `unused` 和共有 `shared` Key |
| `compare --check` | 有 `added` 或 `unused` 时返回退出码 1，适合 CI 门禁 |
| `sync --dry-run` | 只返回差异和计划，不写服务端，也不要求 `--yes` |
| `sync --yes` | 以 `KEEP` 策略补齐缺失 Key，不覆盖已有值或描述 |
| `sync --tag-new-keys TAG ...` | 给同步新增的 Key 添加标签 |
| `sync --remove-unused` | 同时删除当前 namespace 中未出现在本地输入里的 Key；只在配合 `--yes` 时写入 |

以上命令支持源资源参数；`extract` 不要求服务地址、项目 ID 或密钥。
`compare` 和 `sync` 需要项目、分支和凭据，不要求下载配置。
`sync` 固定使用 `KEEP`，只有差异需要写入时才要求 `--yes`；`--remove-unused` 不从配置自动开启。
`unused` 仅表示“不在这组输入文件里”，并不证明项目没有使用这些键；不要未审查就自动删除。
服务器键分页不完整、重复或总数变化时会失败，不拿部分列表继续写操作。

## 7. tag：管理 Key 标签

```bash
# 必须指定 namespace，以及 --keys 或 --all 范围。
lingora tag --config .lingorarc --namespace app \
  --keys hello welcome --tag android release --dry-run --json

lingora tag --config .lingorarc --namespace app \
  --keys hello welcome --tag android release --yes --json

lingora tag --config .lingorarc --namespace app --all \
  --filter-tag 'old-*' --filter-no-tag protected \
  --untag 'old-*' --tag current --yes --json
```

| 参数 | 用途 |
| --- | --- |
| `--namespace NAME` | 必填；限定要读取和修改的准确 namespace，空字符串表示默认 namespace |
| `--keys KEY ...` | 只处理列出的 Key；与 `--all` 二选一 |
| `--all` | 处理 namespace 中所有符合筛选条件的 Key；与 `--keys` 二选一 |
| `--tag TAG ...` | 增加标签；与 `--untag` 至少提供一个 |
| `--untag PATTERN ...` | 按 shell 风格模式移除标签；与 `--tag` 至少提供一个 |
| `--filter-tag PATTERN ...` | 只保留至少命中一个已有标签模式的 Key |
| `--filter-no-tag PATTERN ...` | 排除命中任一已有标签模式的 Key |
| `--dry-run` | 返回将发生的变更，不写服务端 |
| `--yes` | 批准实际标签写入；存在变更且未传入时失败 |

`--tag` 增加标签，`--untag` 移除标签；移除和筛选支持 shell 风格通配符，使用引号避免 shell 展开。
`--filter-tag` 匹配任一已有标签，`--filter-no-tag` 排除命中标签的键。
该命令不要求 Android 文件。每个 Key 单独请求，**不是跨 Key 事务**；失败时 JSON 错误的
`details.updatedKeyIds` 给出已经写入的 Key，`partial` 表示是否部分完成。
不要并发运行多个标签修改器，当前接口没有跨请求的标签并发合并机制。

## 8. branch 与 merge：项目分支

```bash
lingora branch --config .lingorarc --json
lingora branch feature/demo --config .lingorarc --origin develop --json
lingora branch --create feature/demo --config .lingorarc --origin develop --json
lingora branch --delete feature/demo --config .lingorarc --yes --json

# 创建服务端预览，但不合并文案。
lingora merge feature/demo --config .lingorarc --json

# 再创建并检查最新预览，无未解决冲突且未过时才应用。
lingora merge feature/demo --config .lingorarc --apply --yes --json
```

`branch` 参数：

| 参数 | 用途 |
| --- | --- |
| `NAME` | 创建名为 `NAME` 的分支；不传名称和写入选项时列出分支 |
| `--create NAME` | 创建分支，与位置参数 `NAME` 等价且不可同时使用 |
| `--delete NAME`、`-d NAME` | 删除分支，必须同时传 `--yes` |
| `--origin NAME`、`-o NAME` | 创建分支时指定来源；未传时使用配置的 `branch` |
| `--yes` | 批准删除；创建和列出不需要 |

`merge` 参数：

| 参数 | 用途 |
| --- | --- |
| `NAME` | 来源分支；省略时使用 `--branch`、`LINGORA_BRANCH` 或配置的 `branch` |
| `--apply` | 应用本次新建且通过检查的预览；省略时只返回预览 |
| `--yes` | 批准应用；必须与 `--apply` 同时使用 |
| `--delete-source-branch` | 合并成功后请求删除来源分支；要求 `--apply --yes` |

这些命令不要求本地 Android 文件，支持仅用 `--api-url`、`--project-id` 和环境密钥执行。
创建分支的来源必须来自 `--origin` 或配置的 `branch`，不会猜测 Git 分支。
合并目标由服务端的来源分支关系确定；不允许 CLI 自动解决冲突或跳过过时预览检查。
只有显式传 `--delete-source-branch` 才在合并后请求删除来源分支，未完成任务的额外保护仍由服务端执行。
查看分支需要 `translations.view`，分支写入和合并需要 `branch.management`。

## 9. delivery check：公开 CDN 检查

```bash
lingora delivery check --config .lingorarc --alias current --json

# 完全独立于项目配置和 API Key；languages 包含基础语言。
lingora delivery check --no-config \
  --url https://cdn.example.com/your-prefix \
  --namespace app --languages en-US ja zh-CN --json
```

| 参数 | 用途 |
| --- | --- |
| `--alias NAME` | 使用 `contentDeliveries.NAME`；与 `--url` 二选一 |
| `--url URL` | 直接检查公开 Delivery 根地址；与 `--alias` 二选一 |
| `--namespace NAME` | `--url` 模式必填；资源所在 namespace |
| `--languages TAG ...` | `--url` 模式必填；期望语言集合，必须包含基础语言 |
| `--timeout SECONDS` | 每次公开 HTTP 请求的超时，默认 30 秒 |

读取 `manifest.json` 和 `<namespace>/<locale>.json`，检查语言集合与翻译值类型。
缺少缓存头会作为 warnings 返回，不等于发布失败。
检查可读不能证明它来自最新项目或最近发布；该命令不创建或发布 Delivery。
App 仍自行配置运行时 Delivery URL，不从 CLI 配置或凭据读取。

## 10. JSON 协议、退出码与 MCP 调用

`--json` 的 stdout 只输出一个 JSON 文档，无进度文本；stderr 保留脱敏警告。

```json
{
  "ok": true,
  "command": "push",
  "data": {"affected": 3, "deleted": 0},
  "error": null,
  "exitCode": 0
}
```

失败时 `ok=false`、`error.message` 给出原因；部分操作可带 `error.details`。
退出码：0 成功；1 执行、校验、网络或凭据失败；2 命令参数错误。
`compare --check` 检测到键差异也返回 1，但会保留 `data` 中的差异结果。
`--help` / `--version` 是终端展示选项，不返回这个 JSON 协议；需要 JSON 帮助时使用 `help ... --json`。

MCP 层应构造参数数组调用固定版本命令，不拼接 shell，也不要从文本日志猜结果：

```python
import json
import os
import subprocess

result = subprocess.run(
    [
        "lingora", "push", "--no-config",
        "--api-url", "https://lingora.example.com",
        "--project-id", "11111111-1111-1111-1111-111111111111",
        "--branch", "develop",
        "--files", "app/src/main/res/values/strings.xml",
        "--base-language", "en-US", "--namespace", "app",
        "--force-mode", "NO_FORCE", "--json",
    ],
    env=os.environ.copy(),  # MCP 服务预先通过 Secret 注入 LINGORA_API_KEY。
    capture_output=True,
    text=True,
    timeout=120,
    check=False,
)
payload = json.loads(result.stdout)
```

MCP 还应对服务地址、可读写目录、项目和允许执行的写命令建立白名单，并在调用端获取写入授权。
stdout 与 stderr 不得混流；超时后不要盲目重试写操作，应先核对远端是否已经提交。
此处仅定义供自建 MCP 封装使用的 CLI 调用契约，不包含 CLI 的 MCP Server 实现。
Lingora 已有的 [OAuth 远程 MCP 服务](../be/docs/oauth-mcp.md) 直接复用后端用例，
不经过 CLI，也不使用这里的 `LINGORA_API_KEY`；两种接入方式的认证和调用链相互独立。
