diff --git a/README.md b/README.md index e0f371f6c..0d6fc24c2 100644 --- a/README.md +++ b/README.md @@ -233,6 +233,24 @@ lark-cli api POST /open-apis/im/v1/messages --params '{"receive_id_type":"chat_i --format csv # Comma-separated values ``` +### JSON Output Contract + +With `--format json` (the default), success and error envelopes are distinct. + +Success goes to **stdout**, exit code `0`: + +```json +{ "ok": true, "identity": "user", "data": { "guid": "..." }, "meta": { "count": 1 } } +``` + +Errors go to **stderr**, non-zero exit code: + +```json +{ "ok": false, "identity": "user", "error": { "type": "api", "subtype": "...", "code": 99991679, "message": "...", "hint": "..." } } +``` + +To check whether a command succeeded, test `ok == true` (or the exit code) — **not** `code == 0`. Unlike raw OpenAPI responses (`{"code": 0, "msg": "ok", ...}`), the success envelope carries no `code` or `msg` field; `code` appears only inside `error` as the upstream OpenAPI code. See [errs/ERROR_CONTRACT.md](errs/ERROR_CONTRACT.md) for the full error taxonomy. + ### Pagination ```bash diff --git a/README.zh.md b/README.zh.md index 6d8221f2a..74b706405 100644 --- a/README.zh.md +++ b/README.zh.md @@ -234,6 +234,24 @@ lark-cli api POST /open-apis/im/v1/messages --params '{"receive_id_type":"chat_i --format csv # 逗号分隔值 ``` +### JSON 输出契约 + +`--format json`(默认)下,成功与错误的信封结构不同。 + +成功信封写入 **stdout**,退出码 0: + +```json +{ "ok": true, "identity": "user", "data": { "guid": "..." }, "meta": { "count": 1 } } +``` + +错误信封写入 **stderr**,退出码非 0: + +```json +{ "ok": false, "identity": "user", "error": { "type": "api", "subtype": "...", "code": 99991679, "message": "...", "hint": "..." } } +``` + +判断命令是否成功,请检查 `ok == true`(或进程退出码),**不要用 `code == 0`**。与原始 OpenAPI 响应(`{"code": 0, "msg": "ok", ...}`)不同,成功信封没有 `code` 和 `msg` 字段;`code` 只出现在错误信封的 `error` 内,含义是上游 OpenAPI 的 numeric code。完整错误分类见 [errs/ERROR_CONTRACT.md](errs/ERROR_CONTRACT.md)。 + ### 分页 ```bash diff --git a/errs/ERROR_CONTRACT.md b/errs/ERROR_CONTRACT.md index 99dce8948..5262ce58d 100644 --- a/errs/ERROR_CONTRACT.md +++ b/errs/ERROR_CONTRACT.md @@ -72,6 +72,28 @@ other category. `error.type` is `"policy"`, `error.subtype` is one of `challenge_required` / `access_denied`, and process exit is `6` via `CategoryPolicy`. +### Success envelope (stdout) + +For contrast: success responses render to **stdout** as an +`output.Envelope` (`internal/output/envelope.go`), exit code `0`: + +```json +{ + "ok": true, + "identity": "user", + "data": { "guid": "e297d3d0-..." }, + "meta": { "count": 1 } +} +``` + +Consumers must branch on `ok` (or the process exit code). The success +envelope has **no top-level `code` or `msg` field** — `code` exists only +inside `error`, where it is the upstream numeric code (invariant 4). +Wrappers that follow the raw OpenAPI convention and test `code == 0` +will misclassify every successful call as a failure, which is +especially dangerous around write commands (e.g. retrying a create that +already succeeded). + ## Categories | Category | When | Exit | Typed struct | diff --git a/skills/lark-shared/SKILL.md b/skills/lark-shared/SKILL.md index 272b2bae4..e78ddd496 100644 --- a/skills/lark-shared/SKILL.md +++ b/skills/lark-shared/SKILL.md @@ -146,6 +146,24 @@ lark-cli update **重要**:始终使用 `lark-cli update` 更新,它会同时更新 CLI 和 AI Skills。 +## JSON 输出契约 + +`--format json`(默认)下,成功与错误的信封结构不同: + +成功信封写入 **stdout**(退出码 0): + +```json +{ "ok": true, "identity": "user", "data": { "guid": "..." }, "meta": { "count": 1 } } +``` + +错误信封写入 **stderr**(退出码非 0): + +```json +{ "ok": false, "identity": "user", "error": { "type": "api", "subtype": "...", "code": 99991679, "message": "...", "hint": "..." } } +``` + +**判断成功必须用 `ok == true`(或进程退出码 0),不要用 `code == 0`**:成功信封没有顶层 `code` / `msg` 字段,`code` 只出现在错误信封的 `error` 内,含义是上游 OpenAPI 的 numeric code。按 OpenAPI 老格式 `{"code": 0, "msg": "ok"}` 判断会把所有成功调用误判为失败;封装写入类命令(如 `task +create`)时尤其危险,误判会绕过幂等逻辑导致重复创建。 + ## 安全规则 - **禁止输出密钥**(appSecret、accessToken)到终端明文。 diff --git a/skills/lark-task/references/lark-task-create.md b/skills/lark-task/references/lark-task-create.md index 7c946df4d..f8bb5fc23 100644 --- a/skills/lark-task/references/lark-task-create.md +++ b/skills/lark-task/references/lark-task-create.md @@ -46,7 +46,20 @@ lark-cli task +create --summary "Test Task" --dry-run 1. Confirm with the user: task summary, due date, assignee, and tasklist if necessary. - **Crucial Rule for Assignee**: If the user explicitly or implicitly says "create a task for me" (给我创建一个任务), or "help me create a task" (帮我新建/创建一个任务), you MUST assign the task to the current logged-in user. You can get the current user's `open_id` by executing `lark-cli auth status` (it already outputs JSON by default, so do not add `--json`) or `lark-cli contact +get-user` first, extracting `.identities.user.openId` (from `auth status`) or `.data.user.open_id` (from `contact +get-user`), and then passing it to the `--assignee` parameter. 2. Execute `lark-cli task +create --summary "..." ...` -3. Report the result: task ID and summary. +3. Judge success by `ok == true` in the stdout JSON (the success envelope has no `code` field — do not test `code == 0`), then report the result: task ID (`data.guid`) and summary. + +Example success response: + +```json +{ + "ok": true, + "identity": "user", + "data": { + "guid": "e297d3d0-4b60-4a5f-a4d4-xxxxxxxxxxxx", + "url": "https://applink.larkoffice.com/client/todo/detail?guid=e297d3d0-4b60-4a5f-a4d4-xxxxxxxxxxxx" + } +} +``` > [!CAUTION] > This is a **Write Operation** -- You must confirm the user's intent before executing.