自 2026-09-09 起,本博客有 24 轮生产流程用 gh api 查询 GitHub,其中 16 篇是 Verboo Code 教程,全部直接读取 verbeux-ai/code 仓库写成,一次 git clone 都没有。每篇教程需要从一个有 3,115 个条目的仓库里读五到十个文件。为了读十个文件把整个仓库克隆下来是浪费,而在我们运行的环境(带智能体沙箱的 VPS)里,克隆也不总是可行。
本文展示我们只通过 API 来列出、筛选、读取仓库并固定版本时用到的 gh(GitHub CLI)命令。所有命令都于 2026-10-22 用 gh 2.100.0 实际运行过。API 的限制来自 GitHub 官方文档,同日查阅。

怎样不克隆就列出 GitHub 仓库里的所有文件?
用 git/trees 端点加 recursive 参数。一次调用就能返回该分支上每个文件的路径和大小:
gh api "repos/verbeux-ai/code/git/trees/main?recursive=1" \
--jq '{truncated, total: (.tree|length), files: ([.tree[]|select(.type=="blob")]|length)}'
2026-10-22 的输出:
{"files":2737,"total":3115,"truncated":false}
这 3,115 个条目包括目录(type: "tree")和文件(type: "blob")。关键字段是 truncated:如果为 true,列表就不完整。GitHub 文档规定递归响应的上限是 100,000 个条目、7 MB。超过之后,就去掉 recursive 请求树,用每个 tree 条目的 sha 逐个子目录往下读。
文档里的一个细节:recursive 填任何值都会开启递归,包括 0 和 false。要关闭,就不要带这个参数。
怎样只看某个目录下的文件?
用 --jq 按路径前缀筛选同一棵树。这样打开之前就能看到每个文件的大小:
gh api "repos/verbeux-ai/code/git/trees/main?recursive=1" \
--jq '.tree[] | select(.type=="blob" and (.path|startswith("src/commands/add-dir/"))) | "\(.size)\t\(.path)"'
4825 src/commands/add-dir/add-dir.tsx
260 src/commands/add-dir/index.ts
3207 src/commands/add-dir/validation.ts
如果只看一个目录,contents 端点也能列出,不递归:
gh api repos/verbeux-ai/code/contents/src/commands/add-dir --jq '.[] | "\(.type)\t\(.name)"'
区别在上限:文档说明 contents 每个目录最多列出 1,000 个文件,超过就要改用 trees API。要在整个仓库里按文件名找文件,递归树是最短的路。
我们写 /add-dir 教程 时用的就是这一步:树里列出了这个命令的三个文件,再按其他前缀筛选,又找到了它涉及的权限和配置文件。
怎样在终端里读取文件内容?
向 contents 端点请求文件,并带上原始格式的请求头。返回的就是文件文本,没有 JSON,也没有 base64:
gh api repos/verbeux-ai/code/contents/src/commands/add-dir/index.ts \
-H "Accept: application/vnd.github.raw"
import type { Command } from '../../commands.js'
const addDir = {
type: 'local-jsx',
name: 'add-dir',
description: 'Add a new working directory',
argumentHint: '<path>',
load: () => import('./add-dir.js'),
} satisfies Command
要在大文件里找某一行,把输出交给 grep -n,它会同时给出行号:
gh api repos/verbeux-ai/code/contents/src/commands/add-dir/add-dir.tsx \
-H "Accept: application/vnd.github.raw" | grep -n "import"
陷阱:--jq 不能和原始格式一起用
--jq 需要 JSON。带上 raw 请求头后,响应是纯文本,gh 会报这个错:
invalid character 'i' looking for beginning of value
如果想不用管道就筛选(当环境禁止 | 时很有用,有些智能体沙箱就是这样),去掉请求头,在 jq 里直接解码 base64:
gh api repos/verbeux-ai/code/contents/src/commands/add-dir/add-dir.tsx \
--jq '.content | @base64d | split("\n") | to_entries[] | select(.value|test("import")) | "\(.key+1): \(.value)"'
这只适用于 1 MB 以内的文件。超过之后 content 字段为空,见下表。
通过 GitHub API 读取文件的大小上限是多少?
1 MB 以内一切正常。1 到 100 MB 之间,只有原始格式能返回内容。超过 100 MB,这个端点不提供文件。数字来自 contents 和 trees 端点的官方文档:
| 情况 | 会发生什么 | 怎么做 |
|---|---|---|
| 文件不超过 1 MB | 所有格式都可用 | 用 raw,或 --jq 加 @base64d |
| 文件 1 到 100 MB | content 为空,encoding 为 "none" | 使用 Accept: application/vnd.github.raw |
| 文件超过 100 MB | 端点不支持 | 克隆,或下载 release |
| 目录超过 1,000 个文件 | contents 会截断列表 | 使用 git/trees |
| 递归树超过 100,000 个条目或 7 MB | truncated: true | 不加 recursive,逐个子目录读取 |
在 verbeux-ai/code 里,最大的文件是 265,586 字节,所以全部落在表格第一行。
怎样读取某个特定版本的文件?
加上 ?ref=,后面跟标签、分支或提交哈希。这样才能保证教程描述的是读者安装的版本,而不是之后 main 上的改动:
gh api repos/verbeux-ai/code/releases/latest --jq .tag_name
gh api "repos/verbeux-ai/code/contents/package.json?ref=v0.15.38" \
-H "Accept: application/vnd.github.raw" | grep -m1 '"version"'
v0.15.38
"version": "0.15.38",
树也一样:在 git/trees/v0.15.38?recursive=1 里把 main 换成标签即可。想知道某个文件最后一次修改的时间,commits 端点可以按路径筛选:
gh api "repos/verbeux-ai/code/commits?path=src/commands/add-dir/add-dir.tsx&per_page=3" \
--jq '.[] | "\(.sha[0:7]) \(.commit.author.date[0:10])"'
为什么不用 gh search code 找文件?
因为代码搜索有自己的索引规则,而树没有,而且有时会不报错地返回空结果。同一天,搜索代码里确实存在的词,在 verbeux-ai/code 上返回为空,在 openai/codex 上却有结果:
gh search code "addDir" --repo verbeux-ai/code --limit 3
gh search code "useState" --repo openai/codex --limit 2 --json path --jq '.[].path'
GitHub 文档列出了这些限制:只索引默认分支,只索引小于 384 KB 的文件,只索引过去一年有活动的仓库,fork 只有星标多于上游时才会被收录。我们不知道是哪一条影响了我们的仓库。实际结论是:搜索为空并不能证明这个词不存在。递归树会列出全部内容,中间没有索引。
编程智能体能自己完成这些吗?
能,我们的教程就是这样写出来的。智能体先跑出树,按路径和大小挑文件,逐个以原始格式完整读取,再在正文里注明文件和版本。一篇典型教程要完整读五到十个文件,有的长达几百行。读取成本就在这里累积:在 Verboo Code 上 token 不限量,所以为了核对一个参数读完整个文件,和只读你以为够用的那一段,花费一样。



