Install any skill in seconds. Free to start, no credit card required.
Get Started Free →基于飞书官方 API 的云盘管理技能,支持文件列表查询、上传、下载、文件夹创建、权限管理、文件搜索、统计信息、快捷方式、复制移动等完整功能。参考 feishu-drive 技能开发,修复了原技能中的 API 调用错误,并新增了自动化权限管理功能。
.claude/skills/leoyeai-feishu-cloud-drive/SKILL.md| Test case | Without → With | Effect | Δ tokens | Δ turns |
|---|---|---|---|---|
| case-01 | ✗→✓ | ▲ Improved | 212% | 0% |
| case-05 | ✗→✓ | ▲ Improved | 133% | 0% |
| case-07 | ✗→✓ | ▲ Improved | 126% | 0% |
| case-08 | ✗→✓ | ▲ Improved | 276% | 0% |
| case-14 | ✗→✓ | ▲ Improved | 229% | 0% |
本技能参考了社区中的 feishu-drive 技能,但在实现过程中发现原技能存在以下问题:
GET /open-apis/drive/v1/files/:token/children 应改为 drive/explorer/v2 版本)因此基于飞书官方文档重新开发了此技能,修复了所有已知问题,并新增了权限管理功能。
你是飞书云盘管理专家,负责通过飞书官方 API 实现文件列表查询、上传、下载和文件夹管理。
设置环境变量:
bashexport FEISHU_APP_ID="your_app_id" export FEISHU_APP_SECRET="your_app_secret" export FEISHU_ROOT_FOLDER_TOKEN="your_folder_token" # 可选:指定默认根目录
在使用技能之前,先指定一个根目录,后续所有操作都以此目录为基准:
pythonfrom feishu_drive_client import create_client, FeishuDriveClient import os # 方式1:使用便捷函数(推荐) # 自动从环境变量 FEISHU_APP_ID, FEISHU_APP_SECRET, FEISHU_ROOT_FOLDER_TOKEN 读取 client = create_client() # 方式2:手动创建客户端,从环境变量读取 client = FeishuDriveClient( app_id=os.getenv("FEISHU_APP_ID"), app_secret=os.getenv("FEISHU_APP_SECRET"), root_folder_token=os.getenv("FEISHU_ROOT_FOLDER_TOKEN") # 可选 ) # 方式3:手动指定所有参数 client = FeishuDriveClient( app_id="your_app_id", app_secret="your_app_secret", root_folder_token="your_folder_token" # 可选 ) # 方式4:后续动态设置根目录 client = FeishuDriveClient(app_id, app_secret) client.set_root_folder("your_folder_token")
设置根目录后,所有操作默认在根目录下进行:
python# 在根目录下创建子文件夹(无需传入 folder_token) result = client.create_folder("新文件夹") # 在根目录下上传文件 result = client.upload_file("/path/to/file.jpg") # 列出根目录内容 result = client.list_folder() # 如需操作其他目录,可临时指定 folder_token result = client.create_folder("其他位置的文件夹", parent_folder_token="其他token")
优先级规则:传入的 folder_token > 设置的 root_folder_token > 空字符串(根目录)
| 项目 | 值 | |------|---| | Base URL | https://open.feishu.cn/open-apis/drive/v1 | | 认证方式 | Authorization: Bearer {tenant_access_token} | | Content-Type | application/json (文件上传用 multipart/form-data) |
接口说明: 获取用户"我的空间"根文件夹的元数据
GET /open-apis/drive/explorer/v2/root_folder/meta响应示例:
json{ "code": 0, "data": { "token": "nodcnXXXXXX", "id": "7110173013420512356", "user_id": "7103496998321312356" }, "msg": "success" }
注意事项:
drive:drive 或 drive:drive.metadata:readonly接口说明: 在指定父文件夹下创建新文件夹
POST /open-apis/drive/v1/files/create_folder请求参数:
json{ "name": "文件夹名称", "folder_token": "父文件夹token" }
响应示例:
json{ "code": 0, "data": { "token": "fldcnXXXXXX", "url": "https://xxx.feishu.cn/drive/folder/fldcnXXXXXX", "name": "文件夹名称" } }
注意事项:
folder_token 为父文件夹 token,可以通过 URL 获取或通过 API 查询接口说明: 获取指定文件夹下的文件和子文件夹列表
推荐方法 (Explorer v2):
GET /open-apis/drive/explorer/v2/folder/:folder_token/children响应示例:
json{ "code": 0, "data": { "children": { "file_token1": { "token": "file_token1", "name": "文件名", "type": "file" }, "folder_token1": { "token": "folder_token1", "name": "文件夹名", "type": "folder" } }, "parentToken": "folder_token" }, "msg": "success" }
注意事项:
type 为 folder 表示文件夹,为 file 表示文件children 是字典格式,不是数组接口说明: 上传文件到指定文件夹
POST /open-apis/drive/v1/files/upload_all
Content-Type: multipart/form-data请求参数: | 参数 | 类型 | 必填 | 说明 | |------|------|------|------| | file | binary | 是 | 文件二进制内容 | | file_name | string | 是 | 文件名 | | parent_type | string | 是 | 父节点类型,explorer = 云空间 | | parent_node | string | 是 | 父节点 token(文件夹 token) | | size | number | 是 | 文件大小(字节) |
响应示例:
json{ "code": 0, "data": { "file_token": "file_token" } }
注意事项:
parent_type 使用 explorer 表示云空间文件夹size 参数必填,建议在上传前计算文件大小接口说明: 根据文件 token 下载文件
GET /open-apis/drive/v1/files/:file_token/download请求参数: | 参数 | 类型 | 必填 | 说明 | |------|------|------|------| | file_token | string | 是 | 文件 token |
响应:
注意事项:
Authorization: Bearer {tenant_access_token}接口说明: 通过邮箱或手机号获取用户的 open_id
POST /open-apis/contact/v3/users/batch_get_id请求参数:
json{ "emails": ["user@example.com"], "mobiles": ["13800138000"], "include_resigned": false }
响应示例:
json{ "code": 0, "data": { "user_list": [ { "user_id": "ou_xxxxxxxxx", "email": "user@example.com", "status": { "is_activated": true, "is_resigned": false } } ] } }
注意事项:
contact:user.id:readonly 权限user_id 就是 open_id接口说明: 为文件夹添加用户权限
POST /open-apis/drive/v1/permissions/:token/members?type=folder请求参数:
json{ "member_type": "openid", "member_id": "ou_xxxxxxxxx", "perm": "full_access", "perm_type": "container", "type": "user" }
权限类型: | perm | 说明 | |------|------| | view | 可阅读 | | edit | 可编辑 | | full_access | 可管理(完全访问) |
注意事项:
member_id 必须是 open_id,不能是 user_id接口说明: 获取文件的详细信息
GET /open-apis/drive/v1/files/:file_token请求参数: | 参数 | 类型 | 必填 | 说明 | |------|------|------|------| | file_token | string | 是 | 文件或文件夹 token |
响应示例:
json{ "code": 0, "data": { "token": "token", "name": "名称", "type": "file/folder", "size": 1024, "created_time": 1234567890, "owner": { "open_id": "xxx" } } }
接口说明: 删除指定文件夹(删除后进入回收站)
DELETE /open-apis/drive/v1/files/:folder_token?type=folder请求参数: | 参数 | 类型 | 必填 | 说明 | |------|------|------|------| | folder_token | string | 是 | 文件夹 token | | type | string | 是 | 固定值 folder |
响应示例:
json{ "code": 0, "msg": "success" }
注意事项:
Python 示例:
python# 删除文件夹(带确认提示) def delete_folder_with_confirm(client, folder_token, folder_name, confirmed=False): # 1. 显示文件夹信息 print(f"即将删除文件夹: {folder_name}") print(f"Token: {folder_token}") # 2. 请求用户确认(在自主运行环境中,通过参数传递确认状态) if not confirmed: print("请设置 confirmed=True 确认删除") return # 3. 执行删除 result = client.delete_folder(folder_token) if result.get("code") == 0: print("删除成功!文件夹已进入回收站") else: print(f"删除失败: {result.get('msg')}") # 使用示例 - 必须显式确认 delete_folder_with_confirm(client, "folder_token", "测试文件夹", confirmed=True)
接口说明: 删除指定文件(删除后进入回收站)
DELETE /open-apis/drive/v1/files/:file_token?type=file请求参数: | 参数 | 类型 | 必填 | 说明 | |------|------|------|------| | file_token | string | 是 | 文件 token | | type | string | 是 | 固定值 file |
注意事项:
接口说明: 将文件或文件夹移动到指定文件夹
POST /open-apis/drive/v1/files/:file_token/move请求参数:
json{ "type": "file", "destination_folder_token": "目标文件夹token" }
类型说明: | type 值 | 说明 | |---------|------| | file | 普通文件 | | folder | 文件夹 | | doc | 文档 | | sheet | 电子表格 | | bitable | 多维表格 | | docx | 新版文档 |
接口说明: 复制文件到指定文件夹(异步操作)
POST /open-apis/drive/v1/files/:file_token/copy请求参数:
json{ "type": "file", "destination_folder_token": "目标文件夹token", "name": "复制后的新名称(可选)" }
响应示例:
json{ "code": 0, "data": { "ticket": "task_ticket_xxx" }, "msg": "success" }
注意事项:
check_task_status(ticket) 查询复制进度接口说明: 批量获取多个文件的元数据信息
POST /open-apis/drive/v1/metas/batch_query请求参数:
json{ "file_tokens": ["token1", "token2", "token3"] }
限制: 最多 100 个文件 token
接口说明: 获取文件的阅读、点赞、评论等统计信息
POST /open-apis/drive/v1/files/:file_token/statistics响应示例:
json{ "code": 0, "data": { "uv": 100, "pv": 200, "like_count": 10, "comment_count": 5 } }
接口说明: 获取谁访问了该文件的记录
POST /open-apis/drive/v1/files/:file_token/view_records请求参数:
json{ "page_size": 50, "page_token": "可选的分页token" }
接口说明: 在指定文件夹创建文件的快捷方式
POST /open-apis/drive/v1/files/create_shortcut请求参数:
json{ "file_token": "源文件token", "folder_token": "目标文件夹token", "type": "file" }
接口说明: 在云空间中搜索文件
POST /open-apis/suite/docs-api/search/object请求参数:
json{ "search_key": "title", "search_value": "搜索关键词", "page_size": 50 }
search_key 说明: | 值 | 说明 | |----|------| | title | 按标题搜索 | | content | 按内容搜索 |
注意事项:
user_access_token,不支持 tenant_access_token接口说明: 查询复制、移动等异步操作的任务状态
GET /open-apis/drive/v1/files/task_check?ticket=xxx请求参数: | 参数 | 类型 | 必填 | 说明 | |------|------|------|------| | ticket | string | 是 | 异步任务 ticket |
响应示例:
json{ "code": 0, "data": { "status": "success", "file_token": "新文件token" } }
status 说明: | 值 | 说明 | |----|------| | pending | 等待中 | | processing | 处理中 | | success | 成功 | | failed | 失败 |
| 错误码 | 含义 | 解决方案 | |--------|------|----------| | 0 | 成功 | — | | 99991663 | token 过期 | 重新获取 tenant_access_token | | 1061002 | 无权限 | 检查应用权限和文件夹授权 | | 1061001 | 文件/文件夹不存在 | 检查 token 是否正确 | | 1061045 | 文件大小超限 | 使用分片上传或减小文件大小 | | 404 | 文件不存在 | 常见原因:使用了字典 key 而非 item'token'],参见下方常见错误 |
在获取文件夹内容后尝试下载文件时,出现 404 错误。
混淆了飞书 API 返回数据结构中的两种"token":
json{ "children": { "nodcn5OkQG6q8Y4SuSncukJmCtc": { // ← 错误:这是字典的 key,不是下载用 token "token": "KdLTbke3BoN85RxQg5qcswFYnah", // ← 正确:这才是下载用的 token "name": "Screenshot_20260323-115306.png", "type": "file" } } }
pythonresult = client.list_folder(folder_token) children = result["data"]["children"] for token, item in children.items(): # ← token 实际是 dict_key (nodcn...) file_token = token # ← 错误!这会导致 404 client.download_file(file_token, "/path/to/file")
pythonresult = client.list_folder(folder_token) children = result["data"]["children"] for dict_key, item in children.items(): # dict_key 是 nodcn... file_token = item["token"] # ← 正确!从 item 中获取 token client.download_file(file_token, "/path/to/file") # 或者直接使用 list_all() 方法,它已经处理了这个问题 files = client.list_all(folder_token) for file in files: client.download_file(file["token"], f"/path/to/{file['name']}")
| 位置 | 值 | 用途 | |------|-----|------| | 字典 key | nodcn5OkQG6q8Y4SuSncukJmCtc | 仅用于迭代,通常不使用 | | item['token'] | KdLTbke3BoN85RxQg5qcswFYnah | 下载、删除、复制、移动等所有操作 |
确保应用已获取以下权限:
drive:file:read - 读取文件drive:file:write - 写入文件drive:folder:read - 读取文件夹drive:folder:write - 创建文件夹contact:user.id:readonly - 获取用户 ID(用于权限管理)get_file_info 测试 token 是否可用| Case | Status | Duration (ms) | Turns | Tokens | Tool calls | ||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| Without | With | Δ | Without | With | Δ | Without | With | Δ | Without | With | Δ | ||
case-01 | fail→pass | 10,703 | 9,248 | -14% | 1 | 1 | 0% | 2,294 | 7,148 | +212% | 0 | 0 | — |
case-02 | fail→fail | 13,326 | 12,447 | -7% | 1 | 1 | 0% | 2,849 | 7,967 | +180% | 0 | 0 | — |
case-03 | fail→fail | 7,923 | 4,077 | -49% | 1 | 1 | 0% | 1,648 | 6,081 | +269% | 0 | 0 | — |
case-04 | pass→pass | 8,926 | 5,140 | -42% | 1 | 1 | 0% | 1,718 | 6,277 | +265% | 0 | 0 | — |
case-05 | fail→pass | 16,066 | 11,882 | -26% | 1 | 1 | 0% | 3,382 | 7,896 | +133% | 0 | 0 | — |
case-06 | fail→fail | 11,678 | 11,409 | -2% | 1 | 1 | 0% | 2,509 | 7,493 | +199% | 0 | 0 | — |
case-07 | fail→pass | 13,486 | 4,676 | -65% | 1 | 1 | 0% | 2,760 | 6,230 | +126% | 0 | 0 | — |
case-08 | fail→pass | 7,991 | 3,897 | -51% | 1 | 1 | 0% | 1,609 | 6,042 | +276% | 0 | 0 | — |
case-09 | pass→pass | 7,331 | 3,207 | -56% | 1 | 1 | 0% | 1,353 | 5,783 | +327% | 0 | 0 | — |
case-10 | fail→fail | 6,760 | 6,415 | -5% | 1 | 1 | 0% | 1,304 | 6,349 | +387% | 0 | 0 | — |
case-11 | pass→pass | 8,202 | 3,898 | -52% | 1 | 1 | 0% | 1,631 | 5,915 | +263% | 0 | 0 | — |
case-12 | pass→pass | 10,311 | 5,981 | -42% | 1 | 1 | 0% | 2,120 | 6,360 | +200% | 0 | 0 | — |
case-13 | pass→pass | 8,404 | 3,801 | -55% | 1 | 1 | 0% | 1,609 | 5,956 | +270% | 0 | 0 | — |
case-14 | fail→pass | 9,050 | 4,353 | -52% | 1 | 1 | 0% | 1,848 | 6,075 | +229% | 0 | 0 | — |
case-15 | fail→pass | 11,432 | 3,084 | -73% | 1 | 1 | 0% | 2,377 | 5,733 | +141% | 0 | 0 | — |
case-16 | fail→pass | 4,910 | 4,853 | -1% | 1 | 1 | 0% | 940 | 6,088 | +548% | 0 | 0 | — |
case-17 | fail→pass | 12,515 | 5,169 | -59% | 1 | 1 | 0% | 2,243 | 6,178 | +175% | 0 | 0 | — |
case-18 | pass→pass | 8,068 | 5,017 | -38% | 1 | 1 | 0% | 1,517 | 6,192 | +308% | 0 | 0 | — |
case-19 | pass→pass | 12,429 | 8,033 | -35% | 1 | 1 | 0% | 2,544 | 6,959 | +174% | 0 | 0 | — |
case-20 | pass→pass | 9,505 | 8,088 | -15% | 1 | 1 | 0% | 1,933 | 6,839 | +254% | 0 | 0 | — |
case-21 | pass→pass | 11,785 | 8,620 | -27% | 1 | 1 | 0% | 2,380 | 7,087 | +198% | 0 | 0 | — |
case-22 | pass→pass | 7,586 | 5,457 | -28% | 1 | 1 | 0% | 1,392 | 6,278 | +351% | 0 | 0 | — |
DecimalAI ran this skill against gemini-3.6-flash twice over the same eval suite — once with the skill loaded and once without — and compared the two runs case by case. 22 cases were attempted. The headline lift of +36 percentage points is the difference between those two pass rates over the 22 comparable cases.
Without the skill loaded, the model failed this case. With it loaded, the same prompt on the same model passed. This is one improved case from the latest verified run; every case, including any that regressed, is in the table above.
Other measured skills in the registry, with their headline benchmark lift.