Skip to content

feat: 对照官方 CLI 全量对齐——修复契约缺陷、补齐能力、技能与版本绑定(v1.42.0) - #183

Merged
riba2534 merged 310 commits into
mainfrom
claude/official-parity
Oct 9, 2026
Merged

riba2534 merged 310 commits into
mainfrom
claude/official-parity

Conversation

@riba2534

@riba2534 riba2534 commented Oct 8, 2026 •

Copy link
Copy Markdown
Owner

概述

对照飞书官方 CLI(larksuite/cli)做的一次全量对齐。按 11 个领域逐项比对源码与服务端契约,修复了一批"代码自洽、但与服务端实际行为不符"的缺陷,补齐了官方已有、本项目缺失的高价值能力。

本项目比官方更稳妥的设计都原样保留,包括:

  • fail-closed 身份解析、host 白名单;
  • 本地 Markdown 转换器与画板全家桶;
  • 单文档 3 QPS 限流;
  • api --page-all 出错即失败。

这次修复的大部分缺陷,gofmt、go vet、go test 都发现不了,每一条都是用编译后的二进制在真实飞书环境里复现、修复再复验的。

发版号为 v1.42.0(按维护者决定继续使用 1.x,模块路径不变,go install ...@latest 照常可用);9 个 SKILL.md 的 compatibility 已同步为 v1.42.0+。
下文"行为变更"中的条目会改变部分旧用法,CHANGELOG.md 的 [Unreleased] 开头有迁移指南,逐条列出受影响的旧用法和改法;退出码分类保留。

⚠️ 行为变更(合并前请过目)

范围 变化
退出码 0 成功 / 1 一般 / 2 用法 / 3 鉴权或权限 / 4 网络 / 10 需确认 / 130 中断。错误文本不变。判断 == 1 的脚本需要调整
删除类操作 非交互且未带 --yes(或 --force)时 exit 10 且不执行。此前打印"操作已取消"后 exit 0,Agent 会误判为成功
日历 / 任务写命令 默认身份 Bot → --as auto(已登录即本人身份);calendar event-reply 改为必须 User
多维表格 record list 默认 100 条(原 20 条且无提示);table/field/view list 返回全量;视图配置 get/set 直接输出数组;role 输出去掉外层 data
电子表格 delete-rows/delete-cols/protect 的 --start/--end 按文档语义换算为接口口径(原来会多删一行 / 保护范围错位)
文档 doc import 部分失败 exit 1;content-update 纯文本选择器改为文本级替换,块级模式多处命中 exit 2
邮件 读取类命令正文默认输出解码后的明文(--raw-body 保留原值);draft-edit 改为局部修改
会议 vc bot meeting-join/leave 仅支持 Bot;minutes get --with-artifacts 的逐字稿改为写入文件
云盘 下载改为 60s 空闲超时(不再有 5 分钟总时长);push/pull 遇鉴权、权限或限流错误时终止整批;file delete 改为异步并轮询
IM 话题群 msg history 只返回根消息;user search --query 改走 v3 接口,不再返回 user_id
OKR cycle list 默认查 v2 用户周期(与 cycle detail 的 ID 一致),旧的租户周期用 --tenant
api 业务错误时 stdout 为空,错误写 stderr(--raw 例外)
搜索文档 search docs 改走 Search v2:每页最多 20 条(原 50),翻页改用 --page-token,--offset 大于 0 时报用法错误
画板 board export-code --output-path 的目标文件已存在时默认报错,覆盖需要加 --overwrite(原来静默覆盖)
本地路径 输出路径或输入文件命中敏感目录、输入文件不存在或是目录时,退出码统一为 2,且在发出任何网络请求之前报错

主要修复

会丢数据或有安全风险的

  • drive push --if-exists overwrite 原来是先删后传。现在带 file_token 原地覆盖:token、链接、协作者、评论都保留。实测覆盖后 token 不变;旧版 token 会变。
  • sheet delete-rows --start 2 原来会删掉第 2、3 两行。实测现在只删第 3 行。
  • doc content-update replace_all "旧"→"新" 原来会覆盖整段。现在是文本级替换,粗体等样式也保留。另外,本地导出的 Markdown 写回时,原来画板会被静默清空;现在先转换,无法转换的直接拒绝。
  • 删除表单题目原来会连带删除整列数据,且没有任何提示。现在加了 --keep-field 和确认门禁。
  • apps html-publish 原来会把 .git 一起打包发布到公网。
  • 输入/输出路径现在拒绝 ~/.ssh、~/.aws、~/.feishu-cli、/etc 等敏感目录。
  • 建块、建图在 5xx 时自动重试,原来可能生成重复块或重复图。现在重试复用幂等 token,或先回读确认。

与服务端契约不符的

  • wiki 节点解析从 get_node 迁移到 node_by_token。
  • 新增统一的资源 URL 解析器:只看路径前缀,?from=/wiki/ 不会再误导解析。
  • 业务错误随 HTTP 400 下发时,先解析业务码再判断:错误信息附带 log_id 和缺失的 scope。99991672 不再提示"重新登录"(重新登录解决不了)。
  • api --data 和表格写入中的大整数不再被舍入。
  • callout 背景色枚举原来整体错了一位:NOTE 显示成浅紫,修正后为浅蓝。
  • Office token 判定修正(原来会误判,导致图片上传后不渲染)。
  • 邮件:正文 base64 解码;In-Reply-To 补尖括号;draft-edit 不再丢失回复头和附件。
  • 会议与妙记:vc note transcript 按官方协议重写;meeting-events 原来文本模式永远显示 0 条;妙记搜索固定排序后翻页不再漏条。
  • 话题群消息重复、群成员列表缺少机器人。
  • 事件订阅原来按事件类型各开一条连接,会和同一应用的其他连接争抢事件;现在单进程只开一条连接。
  • 认证:scope 支持逗号分隔;auth status --verify 刷新 token 时加锁;刷新失败按错误码分类。
  • 集成阶段发现的跨领域问题:
    • SDK 的 token 缓存原来是全局的,同一 app_id 切换 base_url 后仍复用旧 token;现在随客户端实例重建而丢弃。
    • 中文在截断时被切成半个字符(9 处)。
    • Bot 异步删除失败时没有给出原因。
    • table-put 覆盖写入后,数值被存成了文本。

补齐的能力(节选,完整列表见 CHANGELOG)

  • 文档:content-update 支持 --block-id 和本地资源;doc read --engine docs_ai --with-ids;doc create --content;doc history。
  • Slides:完整的编辑闭环。
  • apps html-publish --wait,可拿到访问链接或构建错误。
  • 画板:SVG 走服务端解析,生成可编辑节点。
  • 日历:重复日程的 --apply-to、分享链接、参会人管理。
  • 审批:退回、加签、催办。
  • OKR:目标和 KR 的写入与评论。
  • 邮件:附件、收信规则、线程整理。
  • 会议:vc meeting list-active。
  • IM:@file/stdin 统一输入、附件区、msg edit、联系人 v3 搜索。
  • 平台与工程:
    • skills install:技能内嵌进二进制,与 CLI 版本严格配套;
    • doctor 检测技能版本漂移;
    • --help 末尾指向相关技能;
    • update 命令、auth scopes;
    • CI、make release-package、make check-privacy。

技能目录 review 与文档实测(本轮追加)

技能目录(9 个领域技能逐条核对;依据是新二进制的 --help、源码和实跑结果)

  • 修正过时或错误的说法:身份、默认值、输出字段、退出码,以及示例的参数与取值。

  • 新增 Agent 调用契约 feishu-cli-platform/.../auth/references/agent-contract.md,内容包括:

    • 退出码含义;
    • 退出码 10 的确认门禁;
    • stdout/stderr 约定;
    • Bot 建资源后的自动授权;
    • 目标解析。

    各领域 SKILL.md 末尾统一指向它。

  • 内容按命令归属迁移:

    • msg、chat 之间互相调整;
    • drive add-comment 迁到 comment;
    • drive apply-permission 和 drive secure-label 迁到 perm;
    • drive search 迁到 platform/search。
  • 跨工作流的命令组必须使用显式前缀,命令组的 --help 会列出全部相关工作流。make check-skills 会强制检查这两条。

  • 身份表 identity.md 按 --help 逐条核对过。

  • 9 个 description 统一改为单行,结构为"能力 → 何时用 → 不用于"。

  • 评测数据:

    • trigger-evals.json 替换 17 条,仍是每个领域 8 个正例;
    • trigger-boundary-evals.json 新增 11 条;
    • evals.json 为新增的命令组补了 21 条用例。
  • ⚠️ 本次没有运行模型触发评测(按维护者决定)。description 的改动只做了静态核对:72 条正例都能在本领域 description 里找到覆盖短语;72 条近邻负例落在"不用于"或能力边界之外;31 条边界用例的归属一致。这只说明文字覆盖到位,不代表实际触发率。

review 中顺带修复的 Go 缺陷

  • msg read-users:已登录时必然失败。改用原始请求,新增 --as。
  • file version create:数字 status 解析错误,会把成功误报为失败,导致重复建版本。
  • 会议与妙记:
    • 妙记命令接受妙记链接;
    • vc notes 在会议号和日历路径下也会补拉纪要产物;
    • 产物部分失败时非零退出;
    • minutes download 同批同名文件自动改名。
  • 邮件:
    • 内联图片在 home 为软链接时被误拒;
    • --format 只接受服务端支持的取值;
    • thread-modify 的错误提示改为指向 thread-trash。
  • search docs 迁移到官方使用的 Search v2 端点:每页最多 20 条,翻页用 --page-token。
  • 其他:
    • okr comment create --select-all 配 --content-json 时选区为空;
    • board export-code 原来静默覆盖已有文件,现在需要 --overwrite;
    • slides 和 mail 的授权域补齐 scope;
    • 一批用法错误的退出码统一为 2。

文档转换器(这些都是全块类型往返实测时发现的)

  • doc export 导出的 <image/file token>、<whiteboard token>、视频重新导入时,原来整篇报 1770001 失败。现在下载原素材重新上传,画板按源码重建或复制节点。
  • 嵌套引用(原 1770030)、分栏(原 1770028,列内有空行时内容会被截断)。
  • 列表项的多个段落原来会被合并成一段。
  • 表格单元格里的公式没有转换。
  • 被服务端拒绝的单个块现在隔离跳过,建块阶段失败也会输出 JSON。
  • --download-images 时资源路径改为相对输出文件。
  • doc 子命令接受文档链接。
  • 建块按"每批最多 5 个画板"切分。实测单次请求出现第 6 个画板就报 1770035;此前含多个画板的导出文件重新导入会在第二批中止,doc add 则直接失败。
  • doc read --keyword 能命中导出时被转义的词。

本地路径安全(E2E 实测发现的 P0,已系统性修复)

  • 实测 doc export <文档> -o ~/.ssh/x.md 会真实写入 ~/.ssh。markdown fetch、doc export-file、drive export 等十余个命令也有问题:要么先发请求或先建导出任务再处理路径,要么不经校验直接写文件;doc import、doc add 读取本地文件时也不校验路径。
  • 修复分三层:
    • 原子写入口默认拒绝敏感目录,CLI 自管路径改用 Trusted 变体;
    • 所有用户指定的输出路径或目录,都在第一次网络请求之前校验;
    • 本地输入文件统一校验:命中敏感目录、文件不存在、是目录或无权限读取,一律返回退出码 2。
  • 审计了 27 个写入点、47 个读取点,逐条标注了归属。
  • 用 strace 复验了 13 个用例,包括真实文档 doc export -o 到敏感目录、--assets-dir、markdown fetch、export-file、drive export、board image、api -o、drive pull --local-dir,以及输入文件不存在或是目录:
    • 全部退出码 2;
    • AF_INET 连接数为 0,仅有查询本机 nscd 的 AF_UNIX 连接(对照组有 19 次 AF_INET);
    • 没有生成任何文件。
  • 正常路径回归通过:导出并下载图片、api -o、auth status、skills install。

飞书文档 / 画板 E2E 实测(两条独立 lane,只用新建的 fp-test-* 资源,测完已删除并确认 1770003 resource deleted)

  • 文档往返:78 项。往返矩阵覆盖:

    • H1–H6;
    • 行内样式、三层嵌套列表、多段落列表项、任务列表、代码块、三层嵌套引用、6 种 Callout;
    • 表格:含公式、样式、图片单元格,大于 9 行和大于 9 列;
    • 列宽注释、图片、公式;
    • 11 种 Mermaid、journey 降级、PlantUML、SVG;
    • 含空列的分栏;
    • content-update 全部 10 种模式、doc history、原生 Markdown 文件、PDF/DOCX 导出。

    二次往返除 token 外一致。实测中发现的 8 个问题已全部修复并复测,包括上面的 1770035、关键词搜索和本地路径安全。

  • 画板:43/43 通过,覆盖:

    • 服务端与本地两种 Mermaid 引擎、PlantUML、服务端 SVG 解析;
    • svg-import、upload-image、nodes、image、lint、export-code(含 --overwrite);
    • update --overwrite --snapshot、clone(connector 重映射)、delete;
    • svg_to_board.py 五步管道(21 个节点,lint 0.90);
    • doc htmlbox 全生命周期。
  • 技能和 CLAUDE.md 里关于 Mermaid 的旧禁令已删除:字面花括号、方括号内冒号、par...and...end、11 个参与者的时序图,以及带 skinparam、缩进、可见性标记的 PlantUML。删除依据是 doc import 实测,这些写法全部一次渲染成画板。

  • 会议与妙记只做了一项只读调用:用维护者指定的妙记取逐字稿。其余领域按维护者的分工只做静态核对(对齐官方 CLI 的底层 API),没有实际调用。

验证

项 结果
gofmt -l cmd internal 无输出
go vet ./... 通过
go test ./... 19 个包全部 ok(含技能 review、转换器、路径安全新增的测试)
go test -race ./... 19 个包全部 ok,无 DATA RACE
make check-skills 通过:584 个命令都有唯一的技能归属,跨工作流命令组都用了显式前缀,1158 条示例参数检查通过,离线契约 9/9
make check-privacy 通过(扫描 1303 个文件)
make release-package VERSION=v1.42.0-rc 5 个平台包 + checksums.txt,结构自检和宿主平台实跑通过(产物已删除)
每个 P0 修复 都有单元测试,并做了反向验证:回退修复后新测试失败,恢复后通过。画板分批、关键词搜索还另外用修复前的二进制复现了缺陷作对照
真实二进制回归 11 个领域分别回归,集成后再做跨领域回归,README 新增的示例逐条实跑。证据摘要见下
模型触发评测 本次未运行(维护者决定)。description 的改动做了静态覆盖核对,见上文"技能目录 review"
集成分支真实回归摘要(写操作只针对新建的 fp-test-* 资源,验证后已删除)
  • 基础设施
    • 未知命令、拼错子命令:exit 2,并给出拼写建议。
    • 非交互删除不带 --yes:exit 10,未执行。
    • -o ~/.ssh/x:exit 2,文件未创建。
    • Bot 调用应用未开通权限的接口:exit 3,提示到开放平台开通,附 log_id。
    • 网络不可达:exit 4。
  • 文档:全块类型导入,permission_grant=granted;replace_all 文本级替换,段落其余文字和样式保留;NOTE callout 为浅蓝;带画板占位的导出写回被拒绝,并列出行号。
  • 电子表格:Sheet1! 前缀可用;delete-rows --start 2 只删第 3 行;1000000 与 15 位整数原值回读;table-put 时分秒往返一致。
  • 多维表格:150 条记录时默认返回 100 条并带 next_offset,--page-all 拿到 150 条、无重复;批量删除 120 条成功;resolve 判型正确。
  • 云盘:push 覆盖后 file_token 不变;pull/status 正常;分页提示带续翻 token;异步删除轮询到 success。
  • 日历 / 任务 / 审批:freebusy 默认查本人;以 auto 身份创建并删除日程;task complete 幂等;审批稀疏分页给出续翻提示。
  • 邮件 / 妙记:triage 用 inbox 文件夹名可用;正文已解码;妙记按创建时间严格倒序。
  • Slides / 画板:创建、加页、按页读取、截图、删页门禁;SVG 解析为 4 个可编辑节点。
  • IM
    • 话题群 history:旧版返回 19 条(只有 9 条根消息、6 条重复);新版 13 条,全部是根消息,无重复。
    • 群成员:旧版 0 个 bot,新版 26 个 bot。
    • event consume:加了 timeout 后,stdin EOF 不再导致提前退出。
    • 发消息只由 Bot 发给本人,验证后全部撤回:--markdown @file 样式归一、--attachment、msg edit、Bot 身份 reaction、资源下载用服务端文件名、stdin 输入。

未能真实验证的部分(只有单测覆盖)

项 原因
OKR、考勤、妙搭的真实数据 当前应用或用户缺少相应 scope,只验证了端点与错误路径
Slides lint 拦截(4000153) 本租户服务端没有拦截
统一逐字稿成功路径 找不到 unified 类型的纪要样本
妙搭真实发布、机器人入会/离会、真正发出邮件、审批写操作 对外可见,按约定只做了 dry-run

按默认方案处理的决策点(如需调整请告知)

  • 不接入未公开的 sheet_ai 端点。图表、条件格式、透视表依赖它,列为后续。
  • docs_ai 作为可选引擎,与本地转换器并存:--engine docs_ai、doc create --content。
  • 妙搭只补发布闭环(get / release / --wait),db/file/env 等约 90 个命令暂不做。
  • 原本没有确认的删除命令,不新增确认门禁,避免破坏 cron 脚本。唯一例外是"删除表单题目会连带删除整列数据"。

合并后需要人工处理的一项

回归时留下了一个无法自动删除的测试日程:fp-test-att,2026-11-30 07:00–07:30。它的组织者被转给了应用机器人,而应用没有租户级的日历删除权限。这个日程只出现在本人和机器人的日历上,请在飞书客户端手动删除。

- body_plain_text/body_html/body_preview 默认解码为明文(兼容带/不带 padding),
  body_plain_text 额外清除 ANSI/控制/BiDi/零宽字符,防终端注入
- 新增 --raw-body 保留 API 原始编码(兼容旧消费方)
- 文本模式输出可读的邮件头 + 正文(优先由 HTML 还原段落,服务端纯文本会折叠换行)
- 解码使用 UseNumber,保留大整数精度
…screenshot,create 带页面

对齐官方 shortcuts/slides 并照搬其实测契约:
- 写页面命令的 lint 开关以 lint_xml 放在请求体(放 query 会被网关丢弃),--no-lint 关闭;
  4000153 lint 拒绝与 3350001 给出可执行提示
- replace-slide:block_replace 根元素自动注入 id(缺失实测 3350001)、<shape> 补 <content/>、
  兼容别名规范化并输出 normalizations、拒绝 str_replace,透出 failed_part_index/failed_reason/issues
- update-slide:整页替换,根 id 与 --slide-id 不一致拒绝,剥掉过期 <note id>(实测
  "block is not NoteBlock"),failed_reason 按失败退出
- add-slide / update-slide / create 的 <img src="@path"> 占位图自动上传替换 file_token
- create 支持 --slides(JSON 数组)/ --slide(可重复)一次最多 10 页,逐页添加,失败时报告进度
- delete-slide 走确认门禁(非交互需 --yes);screenshot 支持页码/slide_id 与 --content 直接渲染
- 各写命令支持 --dry-run,@file / - 读 stdin 绕开 shell 转义
新增 *TATError(Error() 文本不变)与 IsCredentialRejected:invalid_client /
unauthorized_client 及 20002(secret 无效)、20048(应用不存在)、99991543、10014 视为
App 凭证被确定性拒绝,打鉴权标签(原先退出码 1);5xx/临时错误不受影响。
…be 凭证探测

- --app-secret-stdin:从 stdin 读取 App Secret(终端下不回显),避免进入 shell 历史与 ps;
  与 --app-secret 互斥,stdin 为空报用法错误
- --probe:写入前用该凭证换取一次 tenant_access_token(5s 超时,对齐官方 init_probe.go);
  服务端明确拒绝时退出码 3 且不写配置/不创建 profile,网络或临时错误只告警
- config init 新增 --app-id / --app-secret-stdin / --base-url 预填模板(无参数时行为不变)
- profile add 在读 stdin / 探测前先校验名字与是否已存在
- callMailAPI 改用 CheckAPIResponse:随 HTTP 400 下发的业务错误(如 4038)保留业务码与 log_id,
  HasAPICode 可用;错误信息去掉 query 参数
- GetMailDraftRaw 读取实测的 data.draft.message.raw(旧实现只读 data.raw/data.draft.raw,永远为空),
  缺少 raw 时报错而非返回空串
- 引用块使用解码后的原文(优先由 HTML 还原段落),纯文本引用清理控制字符;
  HTML 模式引用块(飞书 history-quote-wrapper 结构)中原文/主题/显示名全部 HTML 转义,消除注入点
- In-Reply-To/References 规范化为 <id>;回复与转发写 X-LMS-Reply-To-Message-Id(原邮件 message_id)
- 回复地址优先 Reply-To;支持回复自己发出的邮件(回复原收件人,reply-all 保持 To/Cc 区分);按邮箱去重
- 地址统一经 net/mail 规范编码(显示名含逗号加引号、非 ASCII 走 RFC 2047);--to 等支持 "Doe, John" <x> 写法
- send/draft-create/reply/reply-all/forward 新增 --attach(扩展名黑名单、EML 编码后 ≤25MB)
- forward 默认携带原邮件普通附件(预签名链接下载,不带鉴权头,https+SSRF 防护),
  新增 --html/--plain-text/--no-original-attachments;超大附件提示未携带
- 新增 internal/mailmime:保序保折行的最小改动 EML 解析/序列化(未修改内容逐字节保留)
- draft-edit 先 GET format=raw,只修改显式传入的 --to/--cc/--bcc/--subject/--body/--from/--from-name,
  In-Reply-To/References/X-LMS 头、附件、内联图原样保留;替换正文默认保留回复/转发引用块(--drop-quote 删除)
- 识别服务端为纯文本草稿合成的未转义 HTML 备选,按纯文本处理
- --to/--subject/--body 不再必填(至少指定一项修改);--cc "" 清空抄送
--inline-images-auto-scan 的图片本就随 EML 以 multipart/related 嵌入,旧实现额外上传到云盘
(file_token 从未使用),白白多一次请求并要求 drive 权限与 open_id,移除该步骤及未用的 UploadMailInlineImage
- meeting-ids 路径与官方 vc +notes 一致,始终经录制接口补 minute_token(best-effort)
- 输出新增 note_id、note_display_type(normal/unified/unknown),便于路由到
  vc note transcript 或 doc export verbatim 文档
- 纪要详情失败(如 121005 无权限)、会议无纪要时记入 hint,不再静默丢弃
- tenant_access_token 换取返回 429 时读取 Retry-After(秒数或 HTTP-date,上限 5s,缺省 1s)
  等待后重试一次,超过剩余超时直接报错
- token.json 解析失败的错误附带文件路径与 auth login / auth logout 修复建议
- convertEvent 处理全天日程 date(含排他结束日换算),get-event/list-events 不再输出空起止
- get-event 改走 HTTP 并补 self_rsvp_status、vchat.meeting_url、reminders;--share-link / event-share 输出分享链接
- freebusy 改用 freebusy/batch:默认当前登录用户、排序合并重叠区间,新增多人与 free/common_free 视图
- delete-event/update-event 先读取日程判型并说明影响范围,新增 --apply-to single|all|this-and-following;
  this-and-following 新序列把 COUNT 换算为 UNTIL,避免变成无限重复
- update-event 要求 --start/--end 成对(实测单边更新服务端返回成功但不生效)
- create/update/delete-event、attendee add/remove 支持 --as(默认 auto),event-reply 必需 User Token
- create-event --calendar-id 默认 primary,支持 --attendee-ids(失败回滚)与 --vchat;attendee 支持会议室、移除与 --type 过滤
- 新增 event-transfer;日历业务错误先解析业务码(search_event/suggestion/room_find/mget_instance)
修正 delete-event 与技能文档对重复日程删除语义互相矛盾的描述(实测:主体 ID 删序列不级联例外,实例 ID 只删一次),
修正 attendee add 可加会议室的错误说法(现已支持 --room-ids)
…wiki 场景

- DriveTaskCheckStatus 新增 Ready/Failed/Pending,删除任务返回的 fail 识别为失败终态
- 导出/导入/移动轮询:查询瞬时失败继续轮询,限流立即停止,全部失败返回 DrivePollError;调用方附带 --as 的续查命令
- drive task-result 新增 wiki_move / wiki_move_to_drive / wiki_delete_space 场景(新增 GetWikiMoveTask)
- CreateExportTask、task_check、metas/batch_query、docs_ai fetch 先解析业务码再判 HTTP 状态
- 导出文件下载改为流式 + 重试 + 原子写
- 分页续翻提示中的 page_token 按 shell 规则加引号
- comment list 新增 --page-token/--page-all/--page-limit,has_more 时 stderr 提示 page_token;
  新增 --solved-status/--comment-scope 过滤(服务端 is_solved/is_whole)
- comment list 输出 content(正文可读文本)与服务端原始 reply_list(过去 reply_list 从未填充,正文丢失);
  回复按 create_time 排序后取根回复作为正文
- comment reply list:content 渲染 @user_id 与文档链接(过去只拼 text_run),新增 elements 原始结构,支持分页
- CreateCommentReply / CreateNewComment 及新增读接口改用 CheckAPIResponse,业务码随 HTTP 400 下发时
  返回 code=N 而不是 HTTP 400 原文
…comment 支持 sheet/slides/base/file 锚点

- comment get(过去帮助里列出但命令不存在)与 batch-get 走 comments/batch_query(实测单条 GET 端点对 docx 返回 1069307)
- comment reply update:整体替换回复内容(--text / --content),--dry-run;1069303/1069302 给出身份与链接提示
- comment reply react:添加/取消表情回应,--emoji 按平台枚举本地校验(区分大小写)
- drive add-comment:新增 --type,--block-id 支持 sheet 单元格 <sheetId>!<cell>、slides <type>!<xml-id>、
  bitable 记录 <table>!<record>!<view>,file 全文评论;wiki 解包后支持上述类型;
  text 元素合计 10000 字符本地预检;--content 解析错误按用法错误退出(exit 2)
- 身份:读命令 --as user|bot|auto(默认 user);note transcript 仅 User;
  meeting-join/leave 仅 Bot(传 --user-access-token 报错)
- vc note transcript:按纪要类型分流(normal 读 verbatim 文档 / unified 走统一逐字稿),
  format=markdown|plain_text、locale、翻页与 121002 提示
- meeting-events:events 字段、--page-all、拒绝 9 位会议号、120002/120003 说明
- 新增 vc meeting list-active、meeting-join --call-id/--action
- minutes get 选择性产物与逐字稿写文件、2091005 申请权限需先征得同意
- minutes search 新参数名与 participant-ids/me、固定 create_time_desc 排序
- vc notes 新增 note_id / note_display_type / hint;新增逐字稿读取工作流
- evals 同步 join/leave 仅 Bot 的预期
…、评论分页/过滤/编辑/表情与锚点评论

- 修正 perm 工作流中「User Token 不适用于权限 API」的错误说法,补 1063004/1063002 排障
- comment 工作流补 get/batch-get、reply update/react、分页过滤与输出结构说明
- drive 工作流补 add-comment 的 sheet/slides/base/file 锚点示例与 apply-permission 1063006/1063007 说明
- ListMessagesOptions 新增 OnlyThreadRootMessages,群聊容器下发 only_thread_root_messages=true
  (对齐官方 im_chat_messages_list.go):话题群回复不再同时出现在 items 与 thread_replies,
  也不再占用翻页额度;回复统一由 ExpandThreadReplies 展开。
- 消息列表/详情/mget/群搜索/书签/消息搜索/文档搜索/drive 搜索等调用点改为
  ParseAPIResponse:业务错误随 HTTP 4xx 下发时保留 code/log_id,HasAPICode 分支可用。
- BaseV3Call/BitableV1Call 先解析飞书业务信封:业务错误随 HTTP 4xx 下发时
  仍保留 code=N 与 data.error.hint/path,并可 AsAPIError 取 log_id 等诊断
- 新增 BaseV3CallAny,返回非对象 data(视图 group/sort/visible_fields 为数组)
- 800004135(按接口方法限流,请求未执行)按 1s/2s/4s 有限重试
- 去掉对所有接口 data 顶层 "{" 开头字符串的反序列化(会误改用户名称/描述),
  改为 UnwrapBaseRoleData 只用于角色接口:解开多层 data、检查内层 code、
  解码 base_roles 每项二次序列化的 JSON
服务端对 thread 容器忽略 start_time/end_time,且历史帮助写的是毫秒(服务端要秒)。
现不再把时间范围发给服务端,按消息 create_time 本地过滤当前页(秒/毫秒/RFC3339/日期均可,
结束时间含整秒边界),has_more 时 stderr 提示继续翻页;起点晚于终点按用法错误退出。
- 列表模式 --folder inbox/收件箱/自定义名称 解析为 folder_id(此前原样透传被服务端 4038 拒绝),--label 同理
- 新增 --max(1-400,--page-size 为别名)内部自动翻页,重复 page_token 防护
- 列表路径经 batch_get(format=metadata) 补全 时间/发件人/主题;搜索路径取搜索元信息
- 文本模式输出表格并提示下一页;JSON 保留 items,新增 messages/count/mailbox_id,has_more/page_token 为翻页后的状态
- --list-folders/--list-labels 文本模式输出表格
对齐官方 im +messages-send/+messages-reply:--markdown 正文与 msg_type 为 text/post 的
--content/--content-file 也修正 <at id=...>/<at open_id=...>/<at user_id=.../> 写法。
JSON 消息体先解码再逐字符串规范化后重新编码(不转义 HTML、保留数字精度),
避免直接正则替换 JSON 串写入未转义的双引号;无改动时原样返回。
P0:
- record list 默认显式下发 limit=100(服务端不传只给 20 条),has_more 时输出
  next_offset 并在 stderr 提示;新增 --page-all 按 offset 翻页(rev 变化告警、
  表结构变化报错、空页防死循环、1000 页上限),--limit 校验 1-2000
- field list / view list / table list 按 total 取全(每页 500,按 id 去重:实测字段
  列表跨页顺序不稳定);table list 显式 --offset/--limit 时保留单页并提示续翻
- record batch-delete 上限 500→200(实测 201 报 800010701),超出自动按 200 条
  串行分批,失败即停并注明已删除条数
- form field delete 新增 --keep-field(只移除题目、保留字段与数据);不带时视为
  危险操作:stderr 明确提示删除整列数据,走确认门禁(非交互需 --yes,dry-run 免确认)
- bitable create/copy 文本模式按平铺响应打印 base_token/name/URL
- 视图配置 get/set 输出解包后的 data(group/sort/visible_fields 原先输出整个信封),
  visible-fields set 支持直接传数组,--help 改为 base/v3 结构
- role list/get/create/update/delete 走角色专用解包

P1/P2:
- --base-token 接受 /base/、/wiki/ 链接(wiki 经 node_by_token 换出并校验 obj_type)
- 新增 bitable resolve --url(/base/ ?table= 经 blocks/list 判型、/wiki/、/record/、
  表单分享链接)与 bitable block list
- bitable create 新增 --table-name/--fields;table create 新增 --fields
- workflow/form/dashboard list 未传 --page-token 时自动翻页(重复游标防护)
- record list/search 与 view-sort-set 的排序条件 ≤10 本地校验
- data-query 本地校验 datasource 与 dimensions/measures,--help 改为实测可用 DSL
随 HTTP 4xx 下发的业务码(如 400002577 应用不存在)过去被报成 "HTTP 400: body",
按业务码分支的恢复提示走不到。
…--wait

- html-publish 过去只返回 release_id,拿不到构建结果与访问链接;--wait 每 20s 轮询
  release 直到 finished(输出 online_url)/ failed(输出 error_logs 并非零退出),
  进入人工审批(current_status=PENDING)时停止并提示审批链接,超时报告当前状态
- release get 展平 data.release,current_node_info 的 camelCase 统一为 snake_case(对齐官方)
- release list 支持 --status/--page-size/--page-token,has_more 时 stderr 提示续翻游标
- html-publish / release get 校验 app_ 前缀(meta_token 提示先 apps get 换 app_id)
- --app-type 改为 html / frontend / full_stack;旧写法 HTML 等保留为兼容别名(归一为小写,stderr 提示)
- FEISHU_CLI_AGENT_NAME(兼容 LARKSUITE_CLI_AGENT_NAME)非空时请求体带 source_agent
- apps list 去掉 Hidden,新增 --keyword / --ownership / --app-type,manifest hidden_commands 同步移除
- 修正 apps 帮助中 --scope"替换"的错误说法:auth login 是增量授权
业务错误随 HTTP 400/403 下发时返回带 log_id 的 *APIError,1063013(降级需审批)、
1063002(无权限)等密级标签专门提示真正可达
…/表单分享设置

- dashboard block create 的 --type 补 ranking(排行榜)、nps(NPS 图,需评分字段),
  二者缺 --data-config 时本地报错
- block create/update 新增 --position(12 列栅格 {x,y,w,h},四键必须为数字)
- 新增 dashboard block get-data(GET dashboards/blocks/:block_id/data,读取图表计算结果)
- 新增 dashboard share get/update 与 form share get/update(base/v3 share 端点,
  update 每次只改一个字段,布尔字段支持 =false 显式关闭)
- 导入层按 MediaRef 补齐资源:图片/附件/视频的 feishu://media/<token> 先下载原素材再上传到新块并
  replace_image/replace_file(跨文档 token 直接绑定会报 1770013);保留原显示宽高与对齐;附件上传到
  View 块下的 File 块(建 File 块服务端返回外层 View);下载失败时把空块替换为占位文本并计入 failures
- <whiteboard token> 复制源画板:Mermaid/PlantUML 生成的画板按 section 节点里的 syntax 源码重新导入,
  其余逐节点复制,都失败时插入源画板图片(whiteboard_as_image),再失败才占位并计入 failures
- 分栏:不再显式创建 grid_column(1770028),列内容写入建 Grid 时服务端生成的列,并清理列内自动空块
- 建块被拒(1770001/1770028/1770030/99992402)的批次逐块隔离,问题块计入 failures(kind=blocks),
  其余内容照常写入;权限/文档不存在等致命错误停止后续建块,但仍走完输出(-o json 含 document_id/url/
  partial_failure/failures)并以退出码 1 结束;doc add 共用嵌套创建、分栏与资源补齐逻辑
- --document-id 接受 /docx/、/wiki/ URL;JSON 新增 file_*、whiteboard_*、blocks_failed 统计
doc export -o 指定输出文件时,下载的图片/视频/画板引用改写为相对输出文件所在目录的路径
(--assets-dir 可为任意相对/绝对路径),doc import 按 Markdown 目录解析相对路径即可原地再导入;
输出到 stdout 时保持相对当前目录。含空白/括号的路径在 Markdown 链接中用 <...> 包裹。
- doc get/blocks/add/add-board/add-callout/update/delete/batch-update/table 的文档参数接入 resolveDocxArg,
  接受 docx token、/docx/ URL 与 /wiki/ URL(wiki 自动解析为底层 docx);doc delete 的 <block_id> 与文档参数
  相同时视为文档根块
- doc export-file 接入 resolveResourceArg:/docx/、/sheets/、/base/ URL 按路径推断 --doc-type,与显式
  --doc-type 冲突时报错,/wiki/ URL 解包为底层文档
- doc --help 按读取/创建导入/编辑分组列出全部子命令(补 read、history、add-board、add-callout、batch-update)
- doc media-download --help 补充单个素材 100MB 上限说明
… 帮助默认值

- markdown patch 与 overwrite 一致:未传 --name 且读不到远端文件名时拒绝写回,不再以 <token>.md 静默改名;
  新增 --name 显式指定写回文件名,dry-run 计划同步
- markdown create/fetch/overwrite/patch/diff 的参数校验错误统一用 clierr.Usagef(退出码 2)
- markdown diff --help 中 --format 默认值不再显示为 json(缺省输出 unified diff 文本)
- import 工作流与 doc-guide:扩展标签表改为 token 素材复用/画板复制/表格引用降级为链接;分栏与嵌套引用、
  列表多段落、单元格公式的新行为;roundtrip 两条路径(token 与 --download-images)均可原地再导入;
  失败判定补充 blocks/file/whiteboard/sheet/bitable 等 kind 与阶段一失败仍输出 JSON
- export:--download-images -o 时资源路径相对输出文件;export-file 接受 URL
- write/read:doc get/blocks/add/... 与 import --document-id 接受 URL(htmlbox 除外);分栏可由 doc import/add 写入
- markdown:patch 读不到远端名 fail-closed、参数错误退出码 2、diff --format 缺省为文本
- CLAUDE.md「Markdown ↔ 飞书文档双向转换」同步支持语法与往返说明
实测单次建块请求出现第 6 个画板即 1770035 resource count exceed limit,与文档已有画板数无关;
图片、附件不受此限。doc export 的 Markdown 含较多 <whiteboard token> 时,doc import 在第二批中止、
doc add 直接失败。blockBatchRanges 按「≤50 块且 ≤5 画板」切批,1770035 同时加入逐块隔离兜底,
并在错误提示中说明上限。技能文档补充 doc add 不把 Mermaid 代码块转画板。
…高亮说明

本地引擎导出会把 _ * 等转义成 \_,按原文搜索 ANCHOR_TOKEN 之类的词此前无法命中;
doc blocks --raw 实际输出纯文本(raw_content)而非 JSON;doc get/blocks 已接受链接,删除过时说法;
<mark> 高亮实际以下划线近似导入,CLAUDE.md/README 如实说明;表格导出已知问题按往返实测更新。
- AtomicWriteFile / AtomicWriteFrom 入口先调用 ValidateOutputPath,任何经它写出的用户文件
  都不会落进 ~/.ssh、~/.feishu-cli、/etc 等敏感目录(用法错误,不留临时文件)
- 新增 AtomicWriteFileTrusted / AtomicWriteFromTrusted,仅供 CLI 自管路径使用;
  config create-app --save 写 ~/.feishu-cli/config.yaml 改用 Trusted 变体
- 新增 MkdirAll(校验后建目录)与 StatInputFile / OpenInputFile / ReadInputFile / InputFileError:
  输入文件敏感目录、不存在、是目录、无权限读取统一为带路径的中文用法错误(退出码 2)
- readLocalInputFile 改走 safefile.ReadInputFile;safeOutputPath 把 "." / ".." 文件名收敛为 "_"
- GetBoardImage 请求前校验输出路径,目录/文件改走 safefile.MkdirAll + AtomicWriteFile(原为 os.WriteFile)
- DownloadFromPresignedURL 发起下载前校验输出目录,建目录改走 safefile.MkdirAll
- 上传入口(IM 图片/文件、云盘上传与覆盖、素材上传、文档素材、多维表格附件、表格图片、OKR 图片、
  任务附件、导入素材)函数入口兜底 ValidateInputPath,命令层漏校验时也不会把凭证发往远端
- 原生 Markdown 上传 / diff 读取与画板图表文件读取改走 safefile(敏感目录、不存在、是目录为用法错误)
- whiteboard-cli 本地引擎读取的源文件同样校验;邮件内嵌图片在 home 安全根内仍拒绝 ~/.ssh 等凭证目录
- --download-images 建资源目录改走 safefile.MkdirAll(敏感 --assets-dir 报用法错误、不下载)
- 视频资源文件名来自文档内容,路径分隔符替换为 "_","."/".." 回退默认名,防止经 "../" 写出 --assets-dir
- output.ParseOptions 解析 -o 时即校验(命令在发请求前解析选项),Render 写文件改走 safefile.AtomicWriteFile 兜底
- event consume --output-dir 的相对路径同样按敏感目录拒绝名单校验(cwd 为家目录时的 .ssh 等),
  建目录改走 safefile.MkdirAll
此前 doc export -o ~/.ssh/x.md 会真实写入敏感目录,markdown fetch / doc export-file / drive export
等先发请求(甚至先建导出任务)后才处理路径。现在:

- doc export(-o、--assets-dir)、doc export-file、drive export / export-download、markdown fetch、
  wiki export / export-tree(含 --assets-dir)、sheet export、board svg-export / export-code / image、
  board update --snapshot、doc media-download、file download、drive download / version-get、
  msg resource-download、minutes download、vc notes --output-dir、bitable record download-attachment
  在 token 刷新、wiki 解析、创建导出任务等任何网络请求之前校验输出位置,违规为用法错误(退出码 2)
- 默认落在当前目录的输出(doc export-file / sheet export 未传 -o)在创建导出任务前校验
- 写入改走 safefile.AtomicWriteFile / MkdirAll(board svg-export、export-code、update --snapshot、
  markdown fetch、wiki export / export-tree 原为 os.WriteFile 非原子写)
- markdown fetch 存到目录、wiki export 默认 /tmp/<标题>.md 时文件名收敛为单段,防止远端名含 "/" 越出目录
- msg resource-download 按服务端文件名改名前对最终路径再校验
- drive pull / push / status 的 --local-dir 拒绝敏感目录(cwd 为家目录时的 .ssh 也在 cwd 子树内),
  并先于 --delete-* 的 --yes 确认检查;--local-dir 不存在 / 不是目录改为用法错误
- skills install --dir 解析符号链接后拒绝敏感目录
- bitable record upload-attachment 的 --file 敏感目录检查前移到 wiki 链接解析之前(dry-run 不要求文件存在)
此前 doc import ~/.ssh/x.md、doc add <id> ~/.ssh/x.md 读本地文件不走 ValidateInputPath,
文件不存在时退出码 1。现在:

- doc import / doc add / doc update / doc batch-update / doc htmlbox --html-file / doc media-insert、
  markdown create / overwrite / diff、board update / import / create-notes / svg-import / upload-image、
  drive upload / import、file upload、media upload、doc import-file、msg send/reply/edit 的
  --image / --file / --attachment / --content-file、task upload-attachment、slides media-upload、
  okr upload-image、sheet import-md、sheet image write-batch --manifest、bitable record --from-file、
  perm batch-add --members-file 统一拒绝敏感目录;不存在 / 是目录 / 无权限读取为带路径的用法错误(退出码 2)
- doc add / doc update / doc batch-update 先读本地文件再解析 wiki 链接;drive upload、slides media-upload、
  markdown diff 等在任何网络请求(含 token 刷新)之前完成本地文件检查
- doc import 解析 Markdown 引用的本地图片 / 附件时拒绝敏感目录(按绝对路径判断,../images/a.png
  这类相对 Markdown 目录的引用照常放行),被拒的单张图片计入 failures,不影响整篇
- apps html-publish --path 及目录内每个文件拒绝敏感目录(不受 --allow-sensitive 影响,dry-run 同样非零退出)
- board import 的本地图表文件:敏感目录在 dry-run 前拒绝,存在性检查在联网前完成
- markdown create 先检查 --content-file 再按文件名推断 --name,目录参数给出"是目录"而不是名字后缀错误
- doc add 缺少源、doc update 缺少内容、drive upload / import 的参数错误改为用法错误(退出码 2)
- agent-contract:校验先于任何网络请求、覆盖的输出/输入参数、Markdown 引用图片计入 failures、
  输入文件不存在/是目录/无权限同为退出码 2
- docs export:wiki export 默认文件名收敛规则;敏感输出目录联网前拒绝
- drive 镜像:--local-dir 拒绝敏感目录且先于 --delete-* 确认检查
- event:--output-dir 相对路径同样拒绝敏感目录
board export-code / svg-export、drive export 此前返回 1,与 markdown fetch、doc media-download 的 2 不一致;
board 两条命令改为在发请求前就拒绝,不再先拉取画板再报错。
v2.0.0 含不兼容的行为变更,按 Go 大版本规则模块路径须带 /v2 后缀,否则 go install ...@latest
会忽略 v2 tag、继续安装 v1.x。改写全部 import;构建脚本注入的是 main.Version,不受影响;
go install 产物名仍为 feishu-cli。

BREAKING CHANGE: go install 改为 github.com/riba2534/feishu-cli/v2@latest
9 个 SKILL.md 的 compatibility、README 配套版本与 go install 说明、CLAUDE.md 中的 v1.42+ 标注统一为 v2.0;
迁移指南逐条给出受影响的旧用法与改法(退出码、--yes、日历/任务身份、bitable 默认条数与输出结构、
search docs 翻页、邮件正文、user_id、OKR 周期、话题群回复、逐字稿文件、export-code 覆盖、api 错误输出等)。
@riba2534 riba2534 changed the title feat: 对照官方 CLI 全量对齐——修复契约缺陷、补齐能力、技能与版本绑定(v1.42.0) feat!: 对照官方 CLI 全量对齐——修复契约缺陷、补齐能力、技能与版本绑定(v2.0.0) Oct 9, 2026
版本号升级为 v2.0.0,但模块路径保持 github.com/riba2534/feishu-cli(不加 /v2)。按 Go 版本规则
go install 无法安装 v2 tag,README 与迁移指南改为说明这一点并指向一键安装脚本、Release 包或源码编译;
同步把 calendar_common.go 注释中的版本号改为 v2.0。
按维护者决定继续使用 1.x 版本号(模块路径不变,go install @latest 照常可用):SKILL.md compatibility、
README、CLAUDE.md、注释与脚本示例中的版本号恢复为 v1.42;CHANGELOG 保留「从 v1.41 及以前升级」迁移指南,
去掉 go install 一行。
@riba2534 riba2534 changed the title feat!: 对照官方 CLI 全量对齐——修复契约缺陷、补齐能力、技能与版本绑定(v2.0.0) feat: 对照官方 CLI 全量对齐——修复契约缺陷、补齐能力、技能与版本绑定(v1.42.0) Oct 9, 2026
@riba2534
riba2534 merged commit bb970d5 into main Oct 9, 2026
2 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant