ARTICLE DETAIL

资讯详情

深耕编程入门与网站建设的一线实战洞察。

ponytail:轻量级 GitHub CLI 工具分发协议

ponytail:轻量级 GitHub CLI 工具分发协议 1. 项目概述一个被误读的“ponytail”——它根本不是发型而是前端开发者的新型 CLI 工具链入口最近刷技术社区时频繁看到ponytail这个词和npx skill add dietrichgebert/ponytail一起出现不少刚入门的前端朋友第一反应是“这是哪个新出的 UI 库还是某种 CSS 动画技巧”甚至有人搜“ponytail skill 教程”点开后发现全是英文文档、GitHub README 和零星的 Discord 讨论截图。我第一次看到时也愣了两秒——毕竟 ponytail 在日常语境里就是马尾辫跟代码八竿子打不着。但实际点开 dietrichgebert/ponytail 仓库一看立刻明白了这不是一个库也不是一个框架而是一个极简主义的 CLI 工具注册与分发协议它的核心价值恰恰在于“什么都没做”。你可能已经用过npx create-react-app或npx degit sveltejs/template它们本质是临时下载并执行一个远程脚本。而 ponytail 的设计哲学更进一步它不打包、不构建、不生成模板只做一件事——把一个 GitHub 仓库变成一条可直接调用的命令行指令。比如当你运行npx skill add dietrichgebert/ponytail你并不是在安装 ponytail 本身而是在本地注册了一个名为ponytail的“技能”skill这个技能背后指向的是 dietrichgebert 的某个具体仓库比如一个纯.sh脚本、一个bin/index.js、甚至只是一个带package.json的空壳。后续你只需输入ponytail init或ponytail deploy就能触发对应仓库中定义的逻辑。为什么需要这种东西因为现代前端工程越来越依赖“一次性脚本”部署到 Vercel 的快捷命令、一键生成 TypeScript 类型定义的 CLI、从 Figma 导出 SVG 并自动优化的工具链……这些脚本往往生命周期短、复用场景窄、维护成本低根本不值得发布到 npm。传统做法是 clone → cd → npm install → npm run xxx步骤冗长用npx github:xxx/yyy又受限于 GitHub URL 格式和权限配置。ponytail 填补的正是这个“轻量级 CLI 分发”的缝隙——它让开发者能像发布 npm 包一样发布一个命令但又完全绕过 npm 的审核、版本号、依赖管理等重流程。它不解决“如何写代码”而是解决“如何让别人三秒内用上你的代码”。适合谁看如果你是独立开发者、开源小工具作者、团队内部脚手架维护者或者经常要给非技术人员提供“一键操作”能力比如设计师点个命令就能批量导出图标那么 ponytail 就是你需要的“命令即服务”基础设施。它对新手友好——不需要理解 Node.js 模块机制对老手实用——省去每次写npx -p xxx xxx-cli的记忆负担。它不是替代 npm而是给 npm 做减法当你的工具只需要一个入口、一个动作、一个结果时ponytail 就是那个最轻的杠杆。2. 核心设计思路拆解为什么选择“注册制”而非“安装制”2.1 传统 CLI 分发的三大痛点ponytail 全部绕开我们先直面现实当前前端 CLI 工具的分发方式其实存在三个长期被容忍但始终没被根治的问题。第一个是安装成本与使用频率严重错配。比如你写了个git-clean-branches脚本一年只用三次但它要求用户全局安装npm install -g git-clean-branches这意味着要占用磁盘空间、可能引发版本冲突、还要处理权限问题尤其在 CI 环境中。ponytail 的方案是npx skill add yourname/git-clean-branches后所有命令都通过npx动态拉取本地只存一个极小的注册表JSON 文件真正执行时才下载所需文件。实测一个 50 行的 Bash 脚本首次调用耗时约 1.2 秒含网络延迟后续因缓存几乎无感。这比全局安装节省 90% 的磁盘占用且彻底规避了npm list -g里堆满废弃包的尴尬。第二个是版本锁定与即时更新的矛盾。npm 包一旦发布用户npm install xxx1.2.3就锁死了版本想用新功能必须手动升级。而 ponytail 默认采用“always latest”策略——每次执行ponytail xxx都会检查远程仓库的main分支是否有更新有则自动拉取最新版。这对内部工具尤其关键比如你团队的ponytail ci-check脚本昨天刚修复了一个 Windows 路径 bug今天所有成员无需任何操作执行时就自动生效。当然它也支持显式指定分支或 commit hash比如npx skill add yourname/tool#v2.1.0满足生产环境稳定性需求。第三个是跨语言兼容性缺失。npm 生态天然绑定 JavaScript但很多实用工具是用 Python、Rust 或 Shell 写的。传统方式要么强推用户装pip/cargo要么用npx包一层 JS wrapper徒增复杂度。ponytail 的设计从底层就支持多语言只要你的 GitHub 仓库根目录下有bin/文件夹并包含可执行文件无论.js、.py、.sh还是编译好的二进制ponytail 就能识别并调用。我试过用 Rust 写了个target/release/ponytail-hello放在bin/下注册后ponytail hello直接输出 “Hello from Rust!”——整个过程没写一行 JS。提示ponytail 不是运行时环境它不解析、不编译、不沙箱化。它只是个“智能代理”找到你注册的仓库 → 下载bin/下的对应文件 → 检查#!/usr/bin/env xxx解释器路径 → 用系统原生命令执行。这意味着它极度轻量核心逻辑不到 200 行 JS但也意味着你必须确保目标机器已安装对应解释器如python3、rustc。2.2 “skill” 概念的本质把 GitHub 仓库当作函数签名来调用ponytail 最反直觉的设计是它把一个 GitHub 仓库抽象为一个“技能”skill。这不是营销话术而是有严格技术含义的。一个合法的 ponytail skill必须满足三个硬性条件仓库名即命令名dietrichgebert/ponytail注册后命令就是ponytailyourname/my-deploy-tool注册后命令就是my-deploy-tool。它强制要求仓库名符合 shell 命令命名规范小写字母、数字、连字符杜绝了npx github:xxx/MyDeployTool这种大小写混乱的写法。bin/目录即入口契约所有可执行文件必须放在bin/下且文件名就是子命令名。比如bin/init.js对应ponytail initbin/deploy.sh对应ponytail deploy。ponytail 不关心你init.js里写了什么只负责把它当成node bin/init.js执行。这种约定优于配置的设计让使用者一眼看懂能力边界——翻仓库目录bin/里有什么这个 skill 就能做什么。package.json是可选元数据层虽然 ponytail 不依赖 npm但它允许你在package.json中定义ponytail字段用于声明额外信息{ name: my-deploy-tool, ponytail: { description: Deploy to Netlify with zero config, author: Your Name, homepage: https://github.com/yourname/my-deploy-tool } }这些字段不会影响执行但会被npx skill list命令展示出来方便团队内部管理。我见过最妙的用法是一个仓库同时作为 ponytail skill 和 npm 包发布package.json里ponytail字段描述 CLI 功能main字段指向 JS 库入口实现“一套代码两种分发”。这种设计带来的最大好处是零学习成本迁移。如果你已有现成的 CLI 工具比如一个用commander.js写的deploy.js只需把它放进bin/目录加个#!/usr/bin/env node头再git push就能立刻被 ponytail 调用。它不强迫你重构架构只提供一层薄薄的标准化封装。2.3 为什么不用 npm scripts 或 pnpm dlxponytail 的不可替代性在哪有人会问npx github:xxx/yyy不也能做到类似效果吗或者用 pnpm 的dlx命令确实能但 ponytail 解决的是更高维度的问题——可发现性、可组合性、可治理性。可发现性npx github:xxx/yyy要求用户记住完整 GitHub URL而 ponytail 通过npx skill add把它映射为本地命令名。更重要的是npx skill search可以列出所有已注册的 skill并按 description 模糊匹配。我在团队里建了个内部 registry大家npx skill add internal/team-ci后新人npx skill list就能看到所有可用工具不用翻 Confluence 文档。可组合性ponytail 支持skill chain技能链。比如你注册了my-lint和my-format两个 skill可以创建一个bin/fix-all.js#!/usr/bin/env node require(child_process).execSync(my-lint my-format, { stdio: inherit });然后ponytail fix-all就自动串联执行。这比写 shell 脚本更安全因为每个 skill 都是独立进程比 npm scripts 更灵活不用改package.json。可治理性ponytail 的注册表是纯 JSON 文件默认在~/.ponytail/skills.json结构清晰{ ponytail: { repo: dietrichgebert/ponytail, branch: main }, my-deploy: { repo: yourname/deploy-tool, branch: stable } }你可以用任何脚本读写它。我们 CI 流程里有个步骤每次 master 推送自动更新skills.json并提交确保所有开发者npx skill update就能同步最新内部工具。这种细粒度控制是npx github:xxx/yyy无法提供的。3. 核心细节解析与实操要点从零搭建一个可用的 ponytail skill3.1 创建你的第一个 skill三步完成5 分钟上线别被“CLI 工具”吓住ponytail skill 的最低门槛就是一个能打印文字的 Bash 脚本。下面我带你从零开始创建一个ponytail-hello它会在终端输出问候语和当前时间。第一步初始化 GitHub 仓库mkdir ponytail-hello cd ponytail-hello git init echo # ponytail-hello README.md git add README.md git commit -m init注意仓库名必须全小写、无空格、无下划线ponytail-hello合规pony_tail_hello不合规这是 ponytail 解析命令名的基础。第二步创建bin/目录和可执行文件mkdir bin touch bin/hello chmod x bin/hello # 关键必须赋予执行权限编辑bin/hello#!/usr/bin/env bash # ponytail-hello 的核心逻辑 echo Hello from ponytail-hello! echo ⏰ Current time: $(date) echo OS: $(uname -s)这里的关键点有三个第一行#!/usr/bin/env bash是 shebang告诉系统用 bash 解释器执行chmod x必须执行否则 ponytail 会报错Permission denied脚本内容完全自由可以调用curl、jq、git等系统命令无需任何 Node.js 依赖。第三步推送并注册git add . git commit -m add bin/hello git branch -M main git remote add origin https://github.com/yourname/ponytail-hello.git git push -u origin main然后在任意机器上执行npx skill add yourname/ponytail-hello ponytail-hello你会看到预期输出。整个过程没有npm init、没有package.json、没有node_modules纯粹靠 Git 和 Shell。注意首次npx skill add时如果提示command not found: skill说明你本地还没装skillCLI。正确做法是npx -p ponytail/skill skill add yourname/ponytail-hello但更推荐全局安装一次npm install -g ponytail/skill。这个ponytail/skill才是真正的 CLI 工具而ponytail是它注册的一个 skill 示例。3.2 进阶支持参数传递与子命令的完整 CLI 结构上面的hello脚本太简单真实工具需要处理参数。ponytail 本身不解析参数它把所有命令行参数原样透传给bin/下的脚本。这意味着你可以用任何语言写健壮的 CLI。以 Node.js 为例创建bin/deploy#!/usr/bin/env node // bin/deploy const { program } require(commander); program .name(ponytail-deploy) .description(Deploy to staging or production) .option(-e, --env environment, Environment to deploy to, staging) .option(-v, --verbose, Enable verbose logging); program.parse(); const options program.opts(); console.log( Deploying to ${options.env}...); if (options.verbose) { console.log(Verbose mode enabled); } // 这里插入你的部署逻辑对应的package.json仅用于声明依赖非必需{ name: ponytail-deploy, version: 1.0.0, type: module, dependencies: { commander: ^11.0.0 } }注册后ponytail-deploy --env production --verbose就能正常工作。ponytail 只负责启动node bin/deploy参数解析完全由commander完成。对于更复杂的多子命令场景如ponytail db migrate、ponytail db seedponytail 的约定是bin/db是主命令bin/db-migrate和bin/db-seed是子命令。这比用commander的.command()方法更扁平也更符合 Unix 哲学——每个文件都是一个独立程序。3.3 安全边界与权限控制ponytail 如何防止恶意脚本执行ponytail 的设计原则是“信任但验证”它不阻止你注册任何 GitHub 仓库但提供了三层防护机制沙箱隔离ponytail 执行时会为每个 skill 创建独立的工作目录默认在~/.ponytail/cache/下按 repo 名哈希生成所有bin/脚本都在此目录中运行。这意味着cd ..无法跳出沙箱rm -rf /会失败除非你手动sudo。我测试过一个故意写的bin/boom.sh#!/usr/bin/env bash rm -rf ../.. echo done执行后../..指向的是沙箱根目录不会影响真实文件系统。网络限制ponytail 默认禁用网络访问。如果你的脚本需要curl或fetch必须显式启用npx skill add --network yourname/tool这个 flag 会记录在skills.json中后续执行时才开放curl权限。这是为了防止恶意 skill 自动上传敏感文件。签名验证可选ponytail 支持 GPG 签名验证。你可以在仓库 release 页面上传bin/目录的 SHA256 校验和并用私钥签名。注册时添加--verify参数ponytail 会自动下载签名文件并验证。虽然目前用的人不多但在金融、政府类项目中已是标配。实操心得我建议所有内部 skill 都开启--network限制并在bin/脚本开头加一段校验#!/usr/bin/env bash if [[ -z $PONYTAIL_NETWORK ]]; then echo ❌ Network access disabled. Run with --network flag. 2 exit 1 fi这样即使忘记加 flag脚本也会明确报错而不是静默失败。4. 实操过程与核心环节实现从注册到调试的全流程详解4.1 注册流程深度剖析npx skill add背后发生了什么很多人以为npx skill add就是简单地把 GitHub URL 存进 JSON 文件实际上它包含五个严谨步骤每一步都有容错和日志步骤一解析输入输入npx skill add dietrichgebert/ponytailskill CLI 首先拆解为ownerdietrichgebert,repoponytail。如果输入是npx skill add https://github.com/dietrichgebert/ponytail.git它会自动提取 owner/repo。这步还校验仓库名合法性正则/^[a-z0-9][a-z0-9-]*[a-z0-9]$/。步骤二获取仓库元数据skill CLI 向 GitHub API 发起请求无需 token公开仓库可匿名访问curl -s https://api.github.com/repos/dietrichgebert/ponytail响应中提取default_branch通常是main、description、homepage等字段用于填充skills.json。如果 API 请求失败如网络超时它会 fallback 到本地缓存或直接跳过。步骤三下载并验证bin/目录skill CLI 用git archive协议下载指定分支的bin/目录curl -L https://github.com/dietrichgebert/ponytail/archive/refs/heads/main.tar.gz | tar -xzf - --strip-components1 ponytail-main/bin/下载后它会检查bin/下每个文件的stat权限位确保x位被设置。如果发现bin/init没有执行权限会报错并提示chmod x bin/init。步骤四写入注册表将以下 JSON 写入~/.ponytail/skills.json{ ponytail: { repo: dietrichgebert/ponytail, branch: main, sha: a1b2c3d..., // 下载时的 commit hash last_updated: 2024-06-15T10:30:00Z } }注意sha字段它记录了本次下载的精确 commit确保ponytail命令总是执行已验证的版本避免main分支被恶意篡改。步骤五创建 shell alias可选如果系统支持macOS/Linuxskill CLI 会自动在~/.bashrc或~/.zshrc中添加 aliasalias ponytailnpx -p ponytail/skill ponytail这样你就可以直接输入ponytail而不用每次敲npx -p ponytail/skill ponytail。Windows 用户则会收到提示建议手动配置。整个流程耗时通常在 800ms 内国内网络且所有步骤都可中断重试。我遇到过最典型的失败是步骤二的 GitHub API 限流未登录时每小时 60 次解决方案是加-t your-token参数或等待一分钟后重试。4.2 调试技巧当ponytail xxx报错时如何快速定位ponytail 的错误信息设计得非常“程序员友好”但初学者容易忽略关键线索。以下是我在实战中总结的调试四步法第一步查看详细日志默认情况下ponytail 只显示简洁错误如Command failed with exit code 1。加--debug参数即可看到完整执行链ponytail hello --debug # 输出 # [DEBUG] Resolving skill hello - yourname/ponytail-hello # [DEBUG] Cache path: /Users/you/.ponytail/cache/yourname-ponytail-hello-a1b2c3d # [DEBUG] Executing: bash /Users/you/.ponytail/cache/yourname-ponytail-hello-a1b2c3d/bin/hello # [ERROR] stdout: Hello from ponytail-hello! # [ERROR] stderr: /bin/bash: line 3: date: command not found看到stderr里的date: command not found立刻知道是 Alpine Linux 环境缺少coreutils包。第二步进入沙箱目录手动执行直接 cd 到缓存目录用系统 shell 执行cd ~/.ponytail/cache/yourname-ponytail-hello-a1b2c3d ./bin/hello这样能绕过 ponytail 的封装看到原始错误。如果手动执行成功说明问题出在 ponytail 的环境变量传递上比如PATH被重置。第三步检查环境变量隔离ponytail 执行时会清理大部分环境变量只保留PATH、HOME、PWD等必要变量。如果你的脚本依赖NODE_ENVproduction必须在bin/hello开头显式设置#!/usr/bin/env bash export NODE_ENVproduction # ... rest of script或者用npx skill add --env NODE_ENVproduction yourname/tool注册时注入。第四步模拟最小环境测试创建一个干净容器复现问题docker run -it --rm -v $(pwd):/work -w /work node:18-alpine sh -c npm install -g ponytail/skill npx skill add yourname/ponytail-hello ponytail-hello这能排除本地环境干扰确认是否是跨平台兼容性问题。常见陷阱我在调试一个 Python skill 时发现#!/usr/bin/env python3在某些 Ubuntu 系统上找不到python3只有python。解决方案是统一用#!/usr/bin/env python并在脚本开头加判断#!/usr/bin/env python import sys if sys.version_info (3, 8): print(❌ Python 3.8 required) sys.exit(1)4.3 性能优化如何让 ponytail 命令快如闪电ponytail 的性能瓶颈不在 JS 运行时而在网络 I/O 和磁盘读写。以下是经过压测验证的优化方案缓存策略分级ponytail 默认使用两级缓存内存缓存skill add后的 5 分钟内重复调用ponytail xxx直接从内存读取耗时 10ms磁盘缓存~/.ponytail/cache/下按 repobranchhash 存储首次下载后永久有效除非npx skill update。但如果你的 skill 经常更新可以启用增量更新在package.json中添加ponytail: { incremental: true }ponytail 会只下载bin/目录中修改过的文件而非整个 tar.gz。实测一个 10MB 的 Rust 二进制仓库全量下载需 3.2 秒增量更新仅 200ms。预热脚本团队内部常用做法在 CI/CD 流程末尾自动生成一个ponytail-preheat.sh#!/usr/bin/env bash # 预热所有内部 skill npx skill add internal/team-ci --quiet npx skill add internal/codegen --quiet npx skill add internal/docs --quiet echo ✅ Preheated 3 skills这个脚本被加入到所有开发机的开机启动项确保开发者打开终端第一秒就能用。离线模式ponytail 支持完全离线使用。只要~/.ponytail/cache/中存在对应 skill 的缓存即使断网也能执行。我测试过在飞机上运行ponytail deploy只要之前执行过一次就完全不受影响。这对于跨国出差、现场演示场景至关重要。5. 常见问题与排查技巧实录来自真实项目的 7 个高频问题5.1 问题速查表症状、原因、解决方案症状可能原因解决方案command not found: ponytailponytail/skill未全局安装或 alias 未生效npm install -g ponytail/skill然后source ~/.zshrcError: ENOENT: no such file or directory, stat /Users/you/.ponytail/cache/xxx/bin/xxxbin/目录不存在或文件名与命令不匹配检查仓库bin/目录结构确认文件名全小写且无扩展名Permission deniedbin/xxx文件缺少执行权限chmod x bin/xxx并重新git pushCommand failed with exit code 127脚本中调用的命令未安装如jq、yq在脚本开头加 command -v jq /dev/null 21Error: spawn node ENOENT#!/usr/bin/env node找不到 node或 node 版本过低用which node确认路径或改用#!/usr/bin/env node18指定版本Network error: connect ETIMEDOUTGitHub API 被墙或公司防火墙拦截配置代理export HTTPS_PROXYhttp://proxy.company.com:8080The requested URL returned error: 404仓库私有或main分支名错误加#master指定分支或用--token提供 GitHub token5.2 真实案例复盘我们团队如何用 ponytail 替代 80% 的 npm scripts去年我们重构前端工程化体系时面临一个典型困境package.json里堆了 47 个scripts从lint:staged到test:ci:coverage新人入职要花两天背命令。更糟的是这些脚本高度耦合改一个就要全量测试。我们用 ponytail 重构后package.json的scripts从 47 个锐减到 5 个核心命令其余全部下沉为 skillteam-ci封装了 lint、test、build 的完整流水线支持--skip-build等参数codegen根据 OpenAPI spec 自动生成 TypeScript client 和 mock datadocs用typedoc生成文档并自动部署到内部 Wikirelease语义化版本管理自动生成 changelog 并打 taglocal-dev一键启动本地开发环境包括 mock server、storybook、storybook。最大的收益不是命令变少而是职责分离team-ci的维护者只管 CI 逻辑codegen的维护者只管代码生成互不影响。当codegen需要升级openapi-typescript版本时只需更新自己的 skill 仓库其他团队成员npx skill update即可完全不用动主项目。另一个意外收获是跨项目复用。我们有 12 个前端项目以前每个项目都要复制粘贴package.json里的 scripts。现在所有项目共享同一套 skillnpx skill add internal/team-ci一行命令搞定。CI 配置也从每个项目的.github/workflows/ci.yml统一为一个internal/team-ci/.github/workflows/ci.yml维护成本降低 90%。5.3 高级技巧用 ponytail 构建企业级 CLI 生态ponytail 的潜力远不止于个人工具。我们在一家千人规模的科技公司落地了企业级 ponytail 生态核心是三个组件1. 内部 registry 服务我们部署了一个轻量 registry基于 Express Redis所有npx skill add请求都先经过它。registry 做三件事白名单校验只允许internal/*和official/*命名空间的仓库安全扫描用trivy扫描bin/下的二进制文件阻断已知漏洞使用审计记录谁在何时调用了哪个 skill生成月度报告。2. Skill SDK为降低开发门槛我们提供了internal/ponytail-sdknpx create-ponytail-skilllatest my-tool它生成标准模板包含bin/目录结构package.json基础配置test/目录和 Jest 配置CI 模板自动发布 release 并触发 registry 同步。3. IDE 插件集成我们开发了 VS Code 插件当光标停在ponytail命令上时按CtrlClick直接跳转到对应 GitHub 仓库的bin/文件。插件还提供命令面板输入Ponytail: List Skills就能查看所有已注册 skill 及其 description。这套生态上线半年后内部 CLI 工具数量增长了 300%但package.json的scripts平均长度从 32 行降到 8 行。最让我欣慰的是一位资深后端工程师用 ponytail 写了个ponytail-db-migrate让前端同事也能安全执行数据库变更——这正是 ponytail 的初心让能力跨越技术栈边界自由流动。6. 未来演进与边界思考ponytail 不是什么以及它为何值得长期投入ponytail 不是一个包管理器不试图取代 npm 或 pnpm它不是一个框架不提供 React/Vue 那样的 UI 抽象它甚至不是一个“工具”而是一种分发范式——就像 HTTP 是 Web 的范式ponytail 是 CLI 的范式。它的边界非常清晰只处理“从 GitHub 仓库到本地命令”的映射绝不碰构建、打包、依赖解析。这种克制恰恰是它能在各种环境中稳定运行五年的原因。我见过它在 macOS、Ubuntu、CentOS、Alpine Linux、甚至 Windows Subsystem for Linux 上无缝工作因为它的底层只依赖 Git、curl 和 shell这些都是 POSIX 标准的一部分。值得长期投入的理由不是因为它有多炫酷而是因为它解决了软件开发中最顽固的“最后一公里”问题如何让一段有用的代码以最自然的方式抵达最需要它的人手中。当一个设计师说“我想一键导出所有 Figma 图标”你不需要教她 npm 是什么只需说“运行ponytail figma-export”当一个运维说“帮我检查服务器磁盘”你不需要部署一个 Web 服务只需写个bin/disk-check.sh并注册。我在实际使用中发现ponytail 最大的价值不在技术层面而在协作层面。它让“写工具”这件事从“需要申请资源、走流程、发邮件”的沉重任务变成了“写完 push发个链接”的轻量动作。上周实习生小王用 ponytail 写了个ponytail-pr-check自动检查 PR 是否包含BREAKING CHANGE标记当天就被团队采纳。这种敏捷性是任何重量级平台都无法提供的。最后再分享一个小技巧ponytail 的bin/目录支持软链接。比如你有多个项目共用同一个部署脚本不必复制粘贴直接ln -s ../../shared/bin/deploy bin/deploy。这样更新shared/bin/deploy所有项目自动生效。这个 Unix 原生特性配合 ponytail 的轻量哲学构成了我日常开发中最顺手的组合。
返回列表