Refly CLI 文件命令完全解析:refly file 的 list、get、download、upload 实操与实现原理
【免费下载链接】reflyThe first open-source agent skills builder. Define skills by vibe workflow, run on Claude Code, Cursor, Codex & more. Build Clawdbot 🦞· APIs for Lovable · Bots for Slack & Lark/Feishu · Skills are infrastructure, not prompts.项目地址: https://gitcode.com/GitHub_Trending/re/refly
在 Refly 开源项目中,@refly/cli是让 Agent(如 Claude Code、Cursor)通过命令行编排后端工作流的入口,而refly file命令组正是 Agent 获取和回传工作流产出物(图片、文档、报表等)的关键通道。本文以 File Reference 文档 为骨架,结合 file 命令组源码 与后端 Drive CLI 控制器,逐一拆解refly file list / get / download / upload四个子命令的完整参数、底层实现链路、鉴权与错误处理机制,读完即可在自己的脚本或 Skill 中安全地拉取和上传 Refly Drive 文件。
1. 文件命令组的定位:Agent 与 Drive 之间的桥梁
refly file是 File Reference 参考文档 所描述的命令集合,在 CLI 中注册为一个独立的命令组。从 入口定义 可以看到,它聚合了四个子命令:
export const fileCommand = new Command('file') .description('Manage files and documents') .addCommand(fileListCommand) .addCommand(fileGetCommand) .addCommand(fileDownloadCommand) .addCommand(fileUploadCommand);这四个子命令分别对应:
refly file list—— 分页列出 Drive 中的文件,支持按画布/执行结果过滤;refly file get—— 查询单个文件的元数据,可选返回文件内容;refly file download—— 将文件下载为本地文件(二进制流);refly file upload—— 将本地文件或目录批量上传到指定画布(canvas)。
Refly CLI 遵循 JSON-First 设计:所有命令输出统一的结构化 JSON,Agent 只需信任ok、payload、error、hint字段即可完成自动化决策。这一约定见 SKILL.md 基础规则,也是 File Reference 中"Trust CLI JSON"理念的基础。
File Reference 文档还给出了文件命令的使用语境:文件 ID 通常来自action results(见 Node Reference)或workflow outputs(见 Workflow Reference),推荐用--result-id或--canvas-id把文件列表收敛到某次具体的运行上下文中。
2.refly file list:分页列出文件并支持上下文档位过滤
File Reference 中给出的用法:
# List files refly file list [options] --page <n> # Page number (default: 1) --page-size <n> # Files per page (default: 20) --canvas-id <id> # Filter by canvas ID --result-id <id> # Filter by action result ID --include-content # Include file content in response对照 list 命令源码,参数定义与默认值完全一致:
export const fileListCommand = new Command('list') .description('List files') .option('--page <n>', 'Page number (default: 1)', '1') .option('--page-size <n>', 'Number of files per page (default: 20)', '20') .option('--canvas-id <id>', 'Filter by canvas ID') .option('--result-id <id>', 'Filter by action result ID') .option('--include-content', 'Include file content in response')实现上有两个值得注意的细节:
- 查询参数拼装:
page、pageSize必发,canvasId、resultId、includeContent(值为true)按需追加,最终请求/v1/cli/drive/files?${params}(list.ts 第 35-49 行)。 - 后端默认值兜底:服务端在 DriveCliController.listFiles 中对查询参数做了
parseInt并回退默认值(page回退 1、pageSize回退 20),includeContent仅当字面量'true'时生效。这意味着即使 CLI 侧传参异常,后端也能保证分页语义稳定。
响应结构由 CLI 侧的ListFilesResponse类型声明(list.ts 第 19-24 行):
interface ListFilesResponse { files: FileInfo[]; // fileId / name / type / size? / createdAt / updatedAt total: number; page: number; pageSize: number; }成功时 CLI 输出ok('file.list', ...)并原样透出total/page/pageSize/files字段;失败时走统一的错误通道(见第 7 节)。
实践提示:当一次工作流运行产生多个文件时,先用refly file list --canvas-id <c-xxx>拿到该画布下所有fileId,再结合--result-id <ar-xxx>精确到某一次 action 结果,比盲目翻页效率高得多。
3.refly file get:查询文件详情,控制是否返回内容
File Reference 中的定义:
# Get file details refly file get <fileId> [options] --no-content # Exclude file contentget 命令源码 中有一个容易踩坑的 Commander 语义:--no-content是反向开关,默认行为是包含内容:
.option('--no-content', 'Exclude file content from response') .action(async (fileId, options) => { const includeContent = options.content !== false; const result = await apiRequest<FileInfo>( `/v1/cli/drive/files/${fileId}?includeContent=${includeContent}`, );也就是说:
refly file get <fileId>→ 请求带includeContent=true,返回含content字段的完整信息;refly file get <fileId> --no-content→ 请求带includeContent=false,响应中content被省略。
这与后端 DriveCliController.getFile 的默认值一致——服务端同样以includeContent查询参数是否为'true'决定返回内容。对于只需要元数据(名称、类型、大小、时间戳)的场景,加上--no-content可以显著减小 JSON 体积,在 Agent 多轮调用时节省 token。
get返回的FileInfo类型(get.ts 第 10-18 行)在 list 的基础上多了可选的content?: string字段。
4.refly file download:流式下载与默认文件名的来源
File Reference 给出的用法:
# Download file refly file download <fileId> [options] -o, --output <path> # Output path (defaults to original filename)"defaults to original filename" 这句承诺背后有一条完整的实现链路,值得展开:
- 后端以附件流返回:DriveCliController.downloadFile 调用
driveService.getDriveFileStream后,设置了四个关键响应头再发送二进制数据:
res.setHeader('Content-Type', contentType || 'application/octet-stream'); res.setHeader('Content-Disposition', `attachment; filename="${encodeURIComponent(filename)}"`); res.setHeader('Content-Length', data.length.toString()); res.setHeader('Last-Modified', lastModified.toUTCString());- CLI 侧用流式请求解析:apiRequestStream 与普通
apiRequest共用同一套 OAuth / API Key 鉴权逻辑,但默认超时放宽到5 分钟(普通 JSON 请求为 30 秒),并会从Content-Disposition头中解析原始文件名。它同时兼容filename="name.ext"与 RFC 5987 的filename*=UTF-8''name.ext两种写法,并对解析结果做decodeURIComponent:
const match = contentDisposition.match(/filename\*?=(?:UTF-8'')?["']?([^"';\n]+)["']?/i); if (match) { filename = decodeURIComponent(match[1]); }- 落地写入:download 命令 按优先级确定落盘路径:
-o/--output指定路径 > 响应头中的原始文件名 >fileId本身,最终通过path.resolve转为绝对路径并用fs.writeFileSync写入,成功后输出ok('file.download', { fileId, path, filename, contentType, size })。
这条链路意味着:不指定-o时,中文文件名也能被正确还原(后端encodeURIComponent+ 前端decodeURIComponent的对称处理);而在脚本中批量下载时,用-o显式指定路径可以完全绕过响应头依赖,行为更确定。
SKILL.md 中的 "Pattern A: File Generation Skills" 就示范了典型用法——工作流跑完后遍历工具调用产出的文件列表,逐个执行refly file download "$FILE_ID" -o "$HOME/Desktop/${FILE_NAME}"并打开,这正是图片/视频/音频生成类技能的标准收尾动作。
5.refly file upload:预签名三步上传流程与目录过滤
File Reference 中的定义:
# Upload file(s) refly file upload <path> [options] --canvas-id <id> # Canvas ID (required) --filter <ext> # Filter by extensions (e.g., pdf,docx,png)<path>可以是单个文件也可以是目录,--canvas-id为必填项。其实现分为"本地文件解析"与"上传协议"两层。
5.1 本地文件解析:过滤、排序与数量上限
upload 命令源码 中定义了MAX_FILES = 10,并通过 resolveFilesToUpload 处理两种输入:
- 单文件:若给了
--filter,则检查扩展名(不含点、转小写)是否在白名单中,不匹配直接返回空列表,最终以NOT_FOUND错误退出并提示 "No files matching filter"; - 目录:读取目录下第一层的文件(不递归子目录),先剔除非文件条目,再按
--filter过滤,然后按文件大小升序排序(小文件优先上传,快速出结果),最后截取前 10 个。
5.2 上传协议:presign → PUT → confirm
apiUploadDriveFile 注释明确写道 "3-step process: presign -> PUT to OSS -> confirm",对应后端两个端点(DriveCliController):
| 步骤 | 请求 | 说明 |
|---|---|---|
| 1. presign | POST /v1/cli/drive/file/upload/presign | 提交canvasId / filename / size / contentType,返回uploadId、presignedUrl、expiresIn |
| 2. PUT 存储 | PUT <presignedUrl> | 文件字节直传对象存储,带Content-Type与Content-Length,超时 5 分钟,网络错误自动重试 1 次(uploadToPresignedUrl) |
| 3. confirm | POST /v1/cli/drive/file/upload/confirm | 提交uploadId,返回最终DriveFileUploadResult(fileId / name / type / size / storageKey / url?) |
MIME 类型由mime包按扩展名推断,未知类型回退application/octet-stream(getMimeType)。
5.3 顺序上传、进度展示与部分失败语义
- 多文件顺序串行上传,pretty 输出模式下逐阶段刷新进度:
Getting upload URL...→Uploading <name> (<size>)...→Confirming upload...(upload.ts 第 68-89 行); - 单个文件失败不会中断整批:错误被收集进
errors数组,批次结束后若results.length === 0则以INTERNAL_ERROR退出并附每个文件的错误明细,否则输出Uploaded X of Y file(s)的部分成功摘要(upload.ts 第 121-148 行)。
因此在脚本中判断上传结果时,应检查 JSON 中payload.uploaded与payload.failed两个计数,而不是只看进程退出码。
6. 鉴权机制:OAuth 与 API Key 双通道
所有 file 子命令的 API 调用都经由 apiRequest / apiRequestStream 发起,两者复用同一套鉴权逻辑,由getAuthMethod()决定走哪条通道:
- API Key 模式:请求头携带
X-API-Key: <apiKey>; - OAuth 模式(默认):请求头携带
Authorization: Bearer <accessToken>;若本地 token 已过期,会先调用POST /v1/auth/cli/oauth/refresh用 refresh token 换新 token 并持久化(refreshAccessToken),刷新失败则抛出Session expired, please login again。
后端侧对应地,整个v1/cli/drive路由都挂载了JwtAuthGuard(DriveCliController 类定义),并从中提取登录用户作为数据隔离依据——文件列表、详情、下载、上传均只对当前用户自己的 Drive 数据可见。
使用前提:先完成npm install -g @refly/cli与refly login(见 CLI README),可用refly status验证连接与认证状态。
7. 统一输出契约与错误处理:让 Agent 可机读
File Reference 面向的读者不仅是人,更是执行 SKILL.md 规则的 Agent,因此输出契约值得单独说明。所有 file 子命令成功时调用ok(type, payload)、失败时调用fail(code, message, ...),这两者定义在 output.ts:
- 成功格式:
{ ok: true, type: 'file.list' | 'file.get' | 'file.download' | 'file.upload', version: '1.0', payload: {...} },退出码 0; - 错误格式:
{ ok: false, type: 'error', error: { code, message, details?, hint?, suggestedFix?, recoverable? } },其中recoverable标记该错误是否可通过调整参数重试同一命令(如INVALID_INPUT、TIMEOUT、RATE_LIMIT属可恢复,见 isRecoverableError)。
错误码到退出码的映射(getExitCode):
| 错误类别 | 退出码 |
|---|---|
认证类(AUTH_*) | 2 |
参数校验(VALIDATION_*/INVALID_INPUT) | 3 |
网络 / 超时(NETWORK_*/TIMEOUT) | 4 |
未找到(*_NOT_FOUND/NOT_FOUND) | 5 |
| 其他 | 1 |
服务端 HTTP 状态还会被 mapAPIError 细化映射:401/403 →AuthError,404 →NOT_FOUND(hint: "Check the resource ID"),409 →CONFLICT,422 →INVALID_INPUT,5xx →API_ERROR。这对脚本化很有价值:refly file get拿错 ID 会得到退出码 5,与"网络不通"(退出码 4)在自动化分支中可以明确区分。
8. 与工作流的协作:fileId 从哪里来
回到 File Reference 的 Interaction 一节,完整的取文件闭环是:
- 运行技能/工作流:
refly skill run --id <installationId> --input '<json>'返回RUN_ID(we-xxx前缀); - 等待完成:
refly workflow status <runId> --watch; - 拿到文件列表:
refly workflow toolcalls <runId> --files --latest直接返回最近一次工具调用的files数组(含fileId,df-xxx前缀); - 下载:
refly file download <fileId> -o <path>; - 回传输入:反向流程则用
refly file upload <path> --canvas-id <c-xxx>把本地材料挂到画布,供后续工作流节点引用。
各类 ID 的前缀约定在 SKILL.md 的 ID Types 表 中有明确登记:df-xxx专用于file download,we-xxx用于 workflow 命令,c-xxx仅出现在浏览器 URL 中——这也是 File Reference 强调"不要伪造 ID"(No fabricated IDs)的原因,所有 ID 都应从 CLI JSON 输出中提取。
9. 速查小结
| 命令 | 关键参数 | 底层端点 | 适用场景 |
|---|---|---|---|
refly file list | --page、--page-size、--canvas-id、--result-id、--include-content | GET /v1/cli/drive/files | 盘点画布/某次运行产出的文件 |
refly file get <fileId> | --no-content(默认含内容) | GET /v1/cli/drive/files/:fileId | 读取文本类文件内容或仅取元数据 |
refly file download <fileId> | -o, --output(默认原始文件名) | GET /v1/cli/drive/files/:fileId/download | 把二进制产物落到本地 |
refly file upload <path> | --canvas-id(必填)、--filter | POST .../file/upload/presign→PUT→POST .../file/upload/confirm | 上传单文件或目录(≤10 个,小文件优先) |
以上全部内容均可在当前仓库中追溯验证:命令参考见 file.md,命令实现见 packages/cli/src/commands/file/,客户端传输逻辑见 packages/cli/src/api/client.ts,服务端路由见 apps/api/src/modules/drive/drive-cli.controller.ts。掌握了这四个命令及其 JSON 契约,就能在任意 Agent 环境中稳定地完成 Refly 工作流"产出文件 → 拉取到本地 → 输入文件 → 回传到画布"的双向文件交换。
【免费下载链接】reflyThe first open-source agent skills builder. Define skills by vibe workflow, run on Claude Code, Cursor, Codex & more. Build Clawdbot 🦞· APIs for Lovable · Bots for Slack & Lark/Feishu · Skills are infrastructure, not prompts.项目地址: https://gitcode.com/GitHub_Trending/re/refly
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考