--- name: feishu-cli-auth description: >- 飞书 OAuth 认证和 User Access Token 管理(Device Flow,RFC 8628)。 支持一键创建飞书应用、按域申请推荐权限、auth check 预检 scope、auth login 登录、Token 自动刷新。 覆盖 AI Agent 两步授权(--no-wait 拿链接 → --device-code 续轮询)、JSON 事件流解析、部分 scope 未授予(missing_scopes)的判读与补授。 当用户请求登录飞书、获取 Token、OAuth 授权、用户身份授权、device flow、权限缺失、Token 过期、create-app、99991672、99991679, 或其他飞书技能遇到 User Access Token 问题时使用。 本技能同时承载两个相关子命令:doctor 做配置/网络/代理体检(错误信息不指向 scope/token 时用), profile 管理多 App / 多账号独立配置(多租户切换)。 注意:profile 指 CLI 配置切换,与邮箱 mailbox profile(feishu-cli-mail)无关。 argument-hint: login | status | check | logout | doctor | profile | config create-app user-invocable: true allowed-tools: Bash(feishu-cli auth:*), Bash(feishu-cli config:*), Bash(feishu-cli doctor:*), Bash(feishu-cli profile:*), Read --- # 飞书认证 本项目默认使用 App Token。只有搜索、消息历史/互动、审批任务、会议/妙记、邮箱等用户身份场景需要 User Access Token。 ## 推荐流程(人工 / 交互终端) ```bash # 1. 预检:缺什么 scope 一目了然(auth check 返回 missing 时先到开放平台开通) feishu-cli auth check --scope "search:docs:read search:message" # 2a. 按业务域登录,自动带该域推荐 scope feishu-cli auth login --domain search --recommend # 2b. 一次授全:对全部业务域申请推荐 scope(单独用 --recommend,不带 --domain) feishu-cli auth login --recommend # 2c. 精确控制:显式指定 scope(与 --domain/--recommend 互斥) feishu-cli auth login --scope "search:docs:read search:message" ``` `--recommend` 三种用法:单独用 = 全部 20 个业务域的推荐 scope;配 `--domain X` = 仅该域推荐 scope;都不传且在交互终端下 = 弹出选择提示。**非交互环境(无 tty)必须显式指定范围**,否则报错。 > 💡 `auth login` 是**增量授权**:多次登录申请的 scope 在飞书服务端累积,补授新 scope 不会丢掉之前已授的。(本地 `token.json` 虽被新 token 覆盖,但其 `scope` 是服务端返回的累积值。) ## AI Agent 授权(两步模式 ⭐) AI Agent 的 harness 通常**只把最终回复发给用户、且单轮有 timeout**,不适合在同一轮里阻塞等授权。用两步模式:第一步拿链接发给用户并结束本轮,用户授权后下一轮再续轮询。 ```bash # 第一步:只取 device_code + 授权链接,立即返回不轮询 feishu-cli auth login --recommend --no-wait --json # → 输出一行 device_authorization 事件,把 verification_uri_complete 发给用户后结束本轮 # 第二步(用户回复已授权后):用 device_code 续上轮询 feishu-cli auth login --device-code --json ``` 要点: - **scope 自动恢复**:第二步从 device_code 缓存读回第一步申请的 scope,**不用也不能**再传 `--scope/--domain/--recommend`(重传会报错)。 - **device_code 有效期约 10 分钟**,超时需从第一步重来;每次重新跑第一步都会作废上一个链接。 - **不要用短 timeout 反复重试**第一步——每次重启都会让上一个授权链接失效。 若 harness 支持后台任务,也可一步阻塞 + 后台运行: ```bash # run_in_background 跑:阻塞轮询最长约 10 分钟,stdout 逐行出 JSON 事件 feishu-cli auth login --recommend --json ``` 阻塞模式务必 `run_in_background`,或把单命令 timeout 设到 ≥ 600s。 ### JSON 事件 schema `--json` 模式按 JSONL 逐行输出事件,Agent 解析这两个即可: **`device_authorization`**(第一步 / 阻塞模式开头): ```json {"event":"device_authorization","verification_uri_complete":"https://accounts.feishu.cn/oauth/v1/device/verify?flow_id=...&user_code=XXXX-XXXX","user_code":"XXXX-XXXX","device_code":"...","expires_in":600,"interval":5,"requested_scopes":["..."]} ``` → 把 `verification_uri_complete`(已含 user_code,可直接点开)**原样**发给用户,不要做任何 URL 编码/改写。 **`authorization_complete`**(授权成功,token 已落盘): ```json {"event":"authorization_complete","expires_at":"...","scope":"...","refresh_token_present":true,"granted_scopes":["..."],"missing_scopes":["..."],"requested_scopes":["..."]} ``` → 上述 6 个字段常驻(`scope` 即落盘 `token.json` 的累积 scope 值);`refresh_expires_at`(拿到 refresh token 时)、`warnings`/`hints`(refresh 缺失或 scope 未授予时)为条件字段,无对应情况时 key 不出现——解析勿假设必存。 ## 授权结果判读 收到 `authorization_complete` 即代表**登录成功、token 已写入 `token.json`**——但还要看这几个字段: | 字段 | 含义 | 处理 | |---|---|---| | `refresh_token_present: false` | 没拿到 refresh token | 应用未开通 `offline_access`,开通后 `auth logout && auth login` | | `missing_scopes` 非空 | 部分申请的 scope 未授予(**warning,不是失败**) | 这些 scope 没在开放平台开通;其余 `granted_scopes` 照常可用 | | `granted_scopes` | 实际拿到的 scope | 以此为准,可 `auth check` 复核 | **补授权(增量)**:`missing_scopes` 非空时,先到飞书开放平台开通这些 scope,再照 CLI 的 hint 执行 `auth login --scope "<缺失的>"` 即可。多次 login 的 scope 在服务端累积,补授只需带缺失的那几个,**不会丢掉**之前已授的,无需重跑全量。 ## 常用命令 ```bash feishu-cli auth status feishu-cli auth status -o json --verify feishu-cli auth check --scope "REQ_SCOPES" feishu-cli auth login --domain --recommend feishu-cli auth logout feishu-cli auth token --as user|bot|auto # v1.29+ 导出 token 给 curl/Python 用 feishu-cli config create-app --save feishu-cli doctor --json feishu-cli profile current ``` ### `auth token` 导出 Token 给外部工具用(v1.29+) 让 curl / Python requests / 任何 HTTP 工具复用本 CLI 的 Token 全生命周期管理 (Device Flow 登录、2 小时自动刷新、多 profile),不再各自实现 OAuth 流程。 | `--as` | 输出 | 适用场景 | |---|---|---| | `user` | User Access Token(`eyJhbGc...`,自动刷新) | 真人身份调 API,含 `auth login` 授权过的 scope | | `bot` | Tenant Access Token(`t-g10...`,2h 有效) | App 身份,调 tenant scope API | | `auto`(默认) | 优先 user,没有再回退 bot | 兼容兜底 | ```bash # 给 curl 用 TOKEN=$(feishu-cli auth token --as user) curl -H "Authorization: Bearer $TOKEN" \ https://open.feishu.cn/open-apis/authen/v1/user_info # 给 Python 用 TOKEN=$(feishu-cli auth token --as bot) python3 -c "import requests; print(requests.get('https://open.feishu.cn/open-apis/im/v1/chats', headers={'Authorization': f'Bearer $TOKEN'}).json())" ``` > 想直接调任意 OpenAPI 而不写 curl?用 `feishu-cli api `(详见 `feishu-cli-schema` skill)。 ### Agent 判读:auth check / auth status 的 JSON 契约 **`auth check --scope "..."`** —— 执行业务前预检某组 scope 够不够。退出码 `0`=满足、非 `0`=缺失或未登录;stdout 出 JSON: | 字段 | 说明 | |---|---| | `ok` | `true` 表示所有 required scope 都已授权 | | `granted` / `missing` | 已有 / 缺失的 scope 列表 | | `error` | 仅失败时出现:`not_logged_in`(没登录)/ `token_expired`(access + refresh 都失效) | | `suggestion` | `ok=false` 时给出的修复命令(已拼好 `auth login --scope`) | ```bash # 满足才往下执行业务命令 feishu-cli auth check --scope "search:docs:read" && feishu-cli search docs --query "..." ``` **`auth status -o json`** —— 看本地 token 现状(默认不连服务端,加 `--verify` 才在线核验)。JSON 字段按字母序输出,已登录时关键字段(按实际输出顺序): | 字段 | 说明 | |---|---| | `access_token` | 脱敏后的 access token(前 6 + 末 6) | | `access_token_valid` | access token 是否还在有效期内 | | `expires_at` | access token 过期时间(RFC3339) | | `health` | `healthy` / `missing_refresh_token`(没拿到 refresh,对应未开 `offline_access`)/ `needs_relogin`(refresh 也过期) | | `identity` | `user`(User Token 可用)/ `bot`(仅剩 App Token 可用) | | `logged_in` | 是否登录 | | `refresh_expires_at` | refresh token 过期时间(有 refresh 时出现) | | `refresh_token_present` | 是否拿到 refresh token | | `refresh_token_valid` | refresh token 是否还在有效期内 | | `scope` | 当前 token 已授权 scope 列表(空格分隔) | | `token_status` | `valid` / `needs_refresh`(access 过期但 refresh 可用,下次调用自动刷新)/ `expired` | | `cached_user.open_id` / `.name` | **当前登录的是谁**——需要本人 open_id(发消息给自己、查自己任务)时从这里取(仅当本地有 user cache 时出现) | | `note` | 健康度提示文案(如 `missing_refresh_token` / `expired` 场景出现) | | `verified` / `verify_error` | 仅 `--verify`:在线调 `user_info` 核验 token 是否仍被服务端接受 | 未登录时返回 `{"logged_in": false, "identity": "bot", "note": "..."}`。 > 判断"任务能不能干"优先用 `auth check`(按 scope 精确判定);`auth status` 看整体健康度和当前身份。下面「排错」表的状态值即来自这两个命令(`auth check` 的 `error` / `auth status` 的 `health`)。 ## Token 解析策略 CLI 把命令分成三类,对应 `cmd/utils.go` 里三个 helper: ### 1. 读类 · User 优先 + Tenant 兜底(`resolveOptionalUserTokenWithFallback`) 登录后自动用 token.json 里的 User Token;未登录则回落 App Token(要求 Bot 在群/有相应权限)。 优先级链:`--user-access-token` → `FEISHU_USER_ACCESS_TOKEN` → `~/.feishu-cli/token.json`(access 过期会自动刷新)→ `config.yaml` 的 `user_access_token` → App Token 兜底。 涉及命令: - 消息读:`msg history`(container 路径)、`msg list`、`msg get`、`msg mget`、`msg thread-messages`、`msg resource-download` - 任务读:`task get`、`task list`、`task subtask list`、`task comment list`、`tasklist get/list/tasks` - 日历读:`calendar get/list/primary/agenda/freebusy/suggestion/room-find`、`calendar event get/list/search`、`calendar attendee list` - 文件/文档读:`file meta/stats/list/version list/version get`、`file download`、`doc blocks`(读)、`board image/nodes/export-code/lint` - **sheet 全家桶**(项目历史就是这种行为):`sheet read/export/get/meta/list-sheets/find/replace/write/append/insert/delete/clear/import-md/dropdown/filter/filter-view/protect/style/merge/...`,所有 sheet 子命令都走 fallback,登录后默认 User、未登录 Tenant 兜底 - wiki 读:`wiki get/nodes/spaces/space-get/export/export-tree`、`wiki member list` - drive:`drive pull/push/status` - 其他:`user read` ### 2. 写类 / 默认 Bot 身份(`resolveOptionalUserToken`) 默认 App Token(Bot 身份),仅当显式传 `--user-access-token` 或 `FEISHU_USER_ACCESS_TOKEN` 时才切到 User Token,**不会自动加载 token.json**。 涉及命令:所有 `add/create/update/delete/move/copy/import/upload/send/reply/forward/merge-forward` 类、`comment reply`、`doc content-update / table 写`、`msg delete`(Bot 自撤回,传显式 User Token 给管理员撤回)等。 ### 3. 必须 User Token(`resolveRequiredUserToken` / `requireUserToken`) 强制 User Token,没登录直接报错。 | 命令 | 典型 scope | |---|---| | `search docs / messages / apps` | `search:docs:read` / `search:message` | | `msg pin/unpin/pins` | `im:message.pins` | | `msg reaction add/remove/list` | `im:message.reactions` | | `msg search-chats` | `im:chat:read` | | `msg flag create/cancel/list` | `im:feed.flag:read/write` | | `chat get/update/delete`、`chat member list/add/remove` | `im:chat:*`、`im:chat.members:*` | | `approval task query/approve/reject/transfer` + `instance get/cancel/cc` | `approval:task` / `approval:instance:*` | | `task my` (`my_tasks`) | `task:task:read` | | `vc search/notes/recording`、`minutes *` | `vc:*`、`minutes:*` 相关 scope | | `mail *` | `mail:user_mailbox:*` | | `drive upload/download/export/import/move/add-comment/task-result/search` | `drive:drive`、`drive:file:*`、`search:docs:read` | | `calendar rsvp` | `calendar:calendar.event:reply` | | `markdown create/fetch/overwrite` | `docx:document` | `chat create` 和 `chat link` **不在此表**:默认走 App Token,仅显式 `--user-access-token` 时切到 User 身份创建群/获取链接。 登录时 CLI 会自动注入 `offline_access` 和 `auth:user.id:read`。如果 `refresh_token_present=false`,通常是应用未开通 `offline_access`,需要开通后重新登录。 ## 业务域登录 `--domain` 可重复或逗号分隔。可选域:`approval attendance bitable calendar chat contact doc_access docs drive event im mail minutes search sheets slides task vc whiteboard wiki`,或 `all`。 ```bash feishu-cli auth login --domain search --recommend # 单域 feishu-cli auth login --domain vc --domain minutes --recommend # 多域 feishu-cli auth login --recommend # 全部域(等价 --domain all --recommend) ``` `--scope` 与 `--domain/--recommend` 互斥;最终以 `auth check --scope` 结果为准。 ## 排错 | 现象 | 处理 | |---|---| | `not_logged_in` | 执行 `auth login --scope "..." --json` | | `token_expired` 且 refresh 可用 | 业务命令会自动刷新;也可重新登录 | | `needs_relogin` | refresh token 已过期,重新登录 | | `missing_refresh_token` | 开通 `offline_access` 后 `auth logout && auth login` | | `99991672` | access token 无效或过期,重新登录 | | `99991679` | 应用未开通 scope,先在开放平台开通权限 | ## 环境诊断(doctor) 用户报“feishu-cli 不工作”“突然连不上”“配置有问题”时,先用 doctor 缩小问题面: ```bash feishu-cli doctor feishu-cli doctor --json feishu-cli doctor --offline feishu-cli doctor --only user_token feishu-cli doctor --only proxy feishu-cli doctor --only user_token,endpoint_open # 多值,逗号分隔 ``` `doctor` 共 6 项检查;`--only` 支持单个值或逗号分隔多个值(typo 会被本地校验拒收): | 检查名 | 含义 | |---|---| | `config_file` | 配置文件存在性、字段完整性 | | `user_token` | `~/.feishu-cli/token.json` 是否存在 / 过期 / refresh 可用 | | `endpoint_open` | `open.feishu.cn` 可达性 | | `endpoint_larksuite` | `open.larksuite.com` 可达性(海外站) | | `proxy` | `HTTPS_PROXY` / `NO_PROXY` 是否会拦截 OpenAPI 域名 | | `dependencies` | 二进制依赖(如 jq、git)状态 | 每项状态为 `pass` / `warn` / `fail` / `skip`。整体退出码:全部 `pass`/`skip` 或仅 `warn` → `0`;任一 `fail` → `1`。 `--json` 输出 schema: ```json { "ok": true, "checks": [ {"name": "user_token", "status": "pass", "message": "...", "hint": "..."} ] } ``` CI 用法:`feishu-cli doctor --offline --json | jq -e '.ok == true'`。 常见命中:`proxy` 检查发现 `HTTPS_PROXY` 拦截 → 把 `.feishu.cn,.larkoffice.com,.larksuite.com` 加入 `NO_PROXY`;`HTTPS_PROXY` 中的 userinfo(`user:pass@host`)会被 redact 后再上报,无需担心日志泄漏。 已经明确是 scope 缺失时,不需要 doctor,直接 `auth check --scope "..."`。 > **v1.27.1 新增**:使用 `--config ` 显式覆盖配置时,CLI 会在 stderr 打印 warning,提示 profile 系统被绕过(`token.json` 自动加载等行为失效)。AI Agent 调试时若看到该 warning,意味着当前命令未走 profile 体系,token 解析仅依赖该 `--config` 文件中的字段。 ## 多 App Profile(profile) 用户需要在多个飞书租户、多个 App ID 或工作/个人账号之间切换时,用 profile 管理独立的 `config.yaml` 和 `token.json`: ```bash feishu-cli profile add work --app-id cli_xxx --app-secret secret_xxx --use feishu-cli profile list --json feishu-cli profile current feishu-cli profile use work feishu-cli profile use - # toggle 到上一个 profile(previous-profile 指针) feishu-cli profile rename old-name new-name feishu-cli profile remove old-name feishu-cli profile migrate --name work ``` 解析优先级: 1. `FEISHU_PROFILE=` 环境变量强制覆盖。 2. `~/.feishu-cli/active-profile` 指针。 3. 指针缺失或失效时,回退到 `profiles/` 下字典序第一个(可能不是预期的那个)。 4. 未启用 profile 系统时,继续使用旧布局 `~/.feishu-cli/config.yaml` 和 `~/.feishu-cli/token.json`。 踩坑(违反直觉、可能丢数据,逐条留意): 1. **`profile add` 不会自动迁移**旧配置;已有配置接入 profile 系统必须显式运行 `profile migrate`,避免静默改变当前环境。 2. **`profile migrate` 不可逆且不删旧文件**:`~/.feishu-cli/config.yaml` / `token.json` 仍留在原位,需要时手动清理;不要把 migrate 当成"备份+迁移"用。 3. **`profile use ` 不会自动登录**:切到一个新建的 profile 后,必须再 `auth login` 才有 User Token。 4. **进程内锁仅 `sync.Mutex`,不跨进程**:并发 `profile use` / `profile rename` 在不同 shell 里同时跑会有 race。 5. **`profile use -` 在没有上一个 profile 时直接报错**:CLI 不会自动回退到字典序首位;先 `profile list` 确认目标,再 `profile use ` 显式切换。 profile 名校验规则 `[A-Za-z0-9_-]{1,64}`(禁止 `.` / `..` / `profiles` / `cache` 等保留名),违反时 CLI 自身会报错。`profile rename` 会自动同步 active 与 previous 指针,不需要手动改文件。 `auth logout` 会清理当前 profile 的 token 和用户 profile 缓存。 ## Agent 约定 1. 执行业务前先 `auth check --scope`,缺什么报什么。 2. 登录优先用**两步模式**(`--no-wait --json` 拿链接 → 用户授权后 `--device-code --json` 续轮询);能开后台任务时也可 `--json` 阻塞 + `run_in_background`。把 `verification_uri_complete` 原样发给用户,不改写 URL。 3. 授权成功后读 `authorization_complete` 的 `missing_scopes`:非空只是 warning,开通后按需补授(增量授权,补缺失的几个即可,不会丢已授 scope)。 4. 不把 `user_access_token` / `device_code` 写入文档、代码或日志。 5. 错误信息明确指向 scope/token 时直接 `auth check`;只有错误不明确("突然不工作"/网络异常)才用 `doctor` 缩小问题面,不要混用。