G
GitHub CLI 只读工具集
作者:鹿Sir开发工具v1
基于 GitHub CLI 的只读查询工具集,极致省 Token:带超时、缓存与重试的仓库搜索、源码读取、代码搜索、PR/Issue/CI 状态查询,内置写入操作黑名单与错误自愈规则。当用户需要查 GitHub 仓库、读源码、搜代码、看 PR/Issue/CI 状态时触发。触发词:GitHub 查询、读源码、仓库搜索、PR 列表、CI 日志。
下载量
396
点赞
98
价格
免费
技能文档
---
name: gh-cli-readonly-agent
title: GitHub CLI 只读工具集
category: 开发工具
description: 基于 GitHub CLI 的只读查询工具集,极致省 Token:带超时、缓存与重试的仓库搜索、源码读取、代码搜索、PR/Issue/CI 状态查询,内置写入操作黑名单与错误自愈规则。当用户需要查 GitHub 仓库、读源码、搜代码、看 PR/Issue/CI 状态时触发。触发词:GitHub 查询、读源码、仓库搜索、PR 列表、CI 日志。
---
# GitHub CLI 只读工具集
面向 Agent 的 GitHub CLI(`gh`)只读查询技能:所有操作严格只读、输出极致裁剪省 Token,内置超时/缓存/重试封装、写入黑名单与错误自愈规则。
## 技能工作流
### 步骤1:环境自检
执行 `gh auth status || gh auth login` 确认认证;再把「预置函数」章节的封装函数加载进当前 shell(运行环境已预置则跳过),`type gh_timed` 验证可用。
### 步骤2:明确查询意图
把用户需求归到四类之一:仓库/代码搜索(§仓库搜索、§代码搜索)、源码读取(§源码读取)、PR/Issue/CI 查询(§PR/Issue/CI 查询)、元数据 API 直查(§gh api 模板)。
### 步骤3:按规范执行查询
- 读操作优先用预置函数(`gh_read`/`gh_tree`/`gh_default_branch`/`truncate_by_quota`)。
- 所有 `gh api` 走 `gh_timed`;高频查询走 `gh_cached`;网络/限流错误用 `retry` 包裹。
- 必带 `--json` 裁剪字段、`--limit ≤10`,输出用 `| jq -c` 紧凑化。
### 步骤4:交付结果
把裁剪后的 JSON/文本整理成结论先行、表格或分点的中文回答;超 5000 字符自动截断并标注 `[已截断]`。
### 步骤5:错误自愈
按「错误自愈与熔断规则」章节处理 401/403/404/5xx;严禁落入「写入类 API 黑名单」。
## 核心约束 (HARD RULES)
- **只读**:严禁任何写入/创建/合并/评论/删除操作(见写入类 API 黑名单)。
- **省Token**:必带 `--json` 裁剪字段,必带 `--limit` (默认≤10),必用 `| jq -c` 紧凑输出。
- **超时**:所有 `gh api` 必须通过 `gh_timed` 调用。
- **缓存**:高频查询必须通过 `gh_cached` 调用。
- **重试**:网络/限流错误必须通过 `retry` 包裹。
- **缩写**:全篇使用 `O/R` 代替 `owner/repo`。
- **前置自检**:执行前必须通过 `gh auth status || gh auth login`。
- **Windows CMD**:jq 单引号改为双引号,如 `jq ".content | @base64d"`(CMD 双引号内 `|`、`>` 等特殊符需 `^` 转义)。
- **PowerShell**:推荐单引号包裹 jq 表达式(单引号内可直接写双引号,无需转义),如 `jq -c '.content | @base64d'`;单引号内表示单引号本身写作 `''`。
## 1. 预置函数(运行环境未预置时先加载下方实现)
| 函数签名 | 作用 |
|---|---|
| `gh_timed <args>` | 带30s超时保护的gh调用 |
| `gh_cached <args>` | 带30分钟TTL的脚本层缓存 |
| `retry <args>` | 指数退避重试(最多3次) |
| `gh_read O/R path [ref]` | 读取文件内容并自动base64解码 |
| `gh_tree O/R [depth]` | 获取目录树(默认限制3层,输出去重path列表) |
| `truncate_by_quota O/R path` | 根据TOKEN_QUOTA自适应截断大文件 |
| `gh_default_branch O/R` | 获取默认分支(封装 main→master→API 兜底探测) |
```bash
# === 预置函数实现(bash/Linux-macOS,环境启动时自动加载) ===
gh_timed() { timeout "${GH_TIMEOUT:-30}" gh "$@"; }
gh_cached() {
local h; h=$(printf '%s' "$*" | (sha256sum 2>/dev/null || shasum -a 256) | cut -c1-16)
local f="/tmp/gh_${h}.json"
local mtime; mtime=$(stat -c %Y "$f" 2>/dev/null || stat -f %m "$f" 2>/dev/null || echo 0)
if [ -f "$f" ] && [ $(( $(date +%s) - mtime )) -lt 1800 ]; then cat "$f"; else gh "$@" | tee "$f"; fi
}
retry() { for i in 1 2 3; do "$@" && return 0; sleep $((i*i)); done; return 1; }
gh_read() { gh_timed api "repos/$1/contents/$2?ref=${3:-main}" | jq -r '(.content // "") | @base64d'; }
gh_tree() {
gh_timed api "repos/$1/git/trees/main?recursive=1" \
| jq -c ".tree[] | select(.type==\"blob\") | .path | split(\"/\")[0:${2:-3}] | join(\"/\")" \
| sort -u | head -100
}
truncate_by_quota() {
local quota=${TOKEN_QUOTA:-3000}
local lines=$((quota / 8))
[ "$lines" -gt 500 ] && lines=500
[ "$lines" -lt 50 ] && lines=50
gh_timed api "repos/$1/contents/$2" | jq -r "(.content // \"\") | @base64d | split(\"\n\") | .[0:$lines] | join(\"\n\")"
}
gh_default_branch() { gh_timed api "repos/$1" | jq -r '.default_branch // "main"'; }
```
### 1.1 Windows / PowerShell 适配(Agent 运行于 pwsh 7 时)
```powershell
function gh_timed { gh @args } # gh 无原生超时;超时兜底由运行环境或降级方案中的 curl --max-time 承担
function retry {
# Exponential backoff retry (max 3 attempts), mirrors the bash version.
param(
[Parameter(Mandatory = $true, Position = 0)]
[scriptblock]$Command,
[int]$MaxAttempts = 3
)
for ($i = 1; $i -le $MaxAttempts; $i++) {
& $Command
if ($LASTEXITCODE -eq 0) { return $true }
if ($i -lt $MaxAttempts) { Start-Sleep -Seconds ($i * $i) }
}
return $false
}
function gh_cached {
$key = (($args -join ' ') | Get-FileHash -Algorithm SHA256).Hash.Substring(0,16)
$f = Join-Path $env:TEMP "gh_$key.json"
if ((Test-Path $f) -and ((Get-Date) - (Get-Item $f).LastWriteTime).TotalMinutes -lt 30) {
Get-Content $f -Raw
} else {
$r = gh @args | Out-String; Set-Content -Path $f -Value $r; $r
}
}
function gh_read { param($or,$path,$ref='main') gh api "repos/$or/contents/$path?ref=$ref" | jq -r '(.content // "") | @base64d' }
function gh_tree {
param($or,$depth=3)
gh api "repos/$or/git/trees/main?recursive=1" |
jq -c ".tree[] | select(.type==\"blob\") | .path | split(\"/\")[0:$depth] | join(\"/\")" |
Sort-Object -Unique | Select-Object -First 100
}
function truncate_by_quota {
param($or,$path)
$quota = if ($env:TOKEN_QUOTA) { [int]$env:TOKEN_QUOTA } else { 3000 }
$lines = [Math]::Min(500, [Math]::Max(50, [int]($quota / 8)))
gh api "repos/$or/contents/$path" | jq -r "(.content // \"\") | @base64d | split(\"\n\") | .[0:$lines] | join(\"\n\")"
}
function gh_default_branch { param($or) gh api "repos/$or" | jq -r '.default_branch // "main"' }
```
> Windows 注意:`date` 是 `Get-Date` 别名,取近1年用 `(Get-Date).AddYears(-1).ToString('yyyy-MM-dd')`;临时目录用 `$env:TEMP` 而非 `/tmp`。
### 1.2 Windows CMD 实际示例(jq 双引号 + `^` 转义)
```cmd
:: 读文件并解码(CMD 中 jq 用双引号包裹表达式)
gh api repos/cli/cli/contents/README.md | jq ".content | @base64d"
:: 分页遍历(URL 用双引号,& 等特殊符用 ^ 转义)
gh api "repos/cli/cli/issues?state=open" --paginate | jq ".[].title"
```
## 2. 仓库搜索 (stars>500)
```bash
# 高星Python仓库 (Top 10)
gh search repos --stars:>500 --language=python --sort=stars --order=desc --limit 10 --json fullName,description,stargazersCount | jq -c '.'
# 活跃TypeScript仓库 (近1年有提交)
gh search repos --stars:>500 --language=typescript --pushed:>$(date -v-1y +%Y-%m-%d 2>/dev/null || date -d '-1 year' +%Y-%m-%d) --limit 10 --json fullName,url | jq -c '.'
# 关键词搜索
gh search repos --stars:>500 "fast json parser" --language=rust --limit 5 --json fullName,url | jq -c '.'
```
*限流警告:Search API限流10次/分钟,高频搜索必用 `gh_cached`。*
*限流降级:Search API 触发 403 且缓存未命中时,改用 `gh api repos/O/R` 或 `gh repo view O/R` 直读仓库元数据,避免阻塞等待。*
## 3. 源码读取 (极致省Token)
```bash
# 读文件 (推荐用预置函数)
gh_read O/R src/utils/parser.rs
gh_read O/R config.yaml v2.1.0 # 指定tag/branch
# 读目录树 (直接输出去重path列表,勿再jq二次解析)
gh_tree O/R 3
# 大文件截断 (按行/字符)
gh_read O/R large_file.py | head -200
gh_read O/R large_file.py | sed -n '50,150p'
# 动态截断 (根据剩余配额自动调整)
truncate_by_quota O/R large_file.py
# 敏感信息脱敏 (读取后管道处理)
gh_read O/R config.py | sed -E 's/(api[_-]?key|token|secret|password)\s*[=:]\s*["\x27][^"\x27]+["\x27]/\1="***"/gi'
```
> Note: 读取前先 `gh_default_branch O/R` 获取默认分支(已封装 main→master→API 兜底探测),避免 404;若仍 404 请检查仓库可见性与认证 scope。
## 4. 代码搜索
```bash
# 仓库内搜索
gh search code "def parse_config" --repo O/R --limit 10 --json path,snippet | jq -c '.'
# 跨仓库/限定语言
gh search code "class HttpClient" --language=python --limit 5 --json repository,path | jq -c '.'
# 限定扩展名
gh search code "TODO" --repo O/R --ext=ts --limit 10 --json path,snippet | jq -c '.'
```
## 5. PR / Issue / CI 只读查询
```bash
# PR 列表与详情
gh pr list --repo O/R --limit 10 --json number,title,state | jq -c '.'
gh pr view NUM --repo O/R --json title,body,author,reviewDecision | jq -c '.'
gh pr diff NUM --repo O/R | head -100
gh pr checks NUM --repo O/R
# Issue 列表与详情
gh issue list --repo O/R --state open --limit 10 --json number,title,labels | jq -c '.'
gh issue view NUM --repo O/R --json title,body,state | jq -c '.'
# CI 运行状态与失败日志
gh run list --repo O/R --limit 10 --json databaseId,status,conclusion | jq -c '.'
gh run view RUN_ID --repo O/R --log-failed | head -200
```
## 6. gh api 高频只读模板 (GET only)
```bash
# Repo Meta
gh_timed api repos/O/R | jq -c '{stars:.stargazers_count,forks:.forks_count,default_branch}'
# Commit Detail
gh_timed api repos/O/R/commits/main | jq -c '{sha,message,author:.author.login,stats}'
# Rate Limit 观测
gh_timed api rate_limit | jq -c '.resources | {core,search}'
# 分页遍历 (自动处理Link头)
gh_timed api repos/O/R/issues?state=open --paginate | jq -c '.[].title'
```
## 7. 错误自愈与熔断规则 (Agent自动执行)
| 错误码/条件 | 自动动作 |
|---|---|
| `401` | 触发 `gh auth login` 重新认证 |
| `403` (限流) | `retry` 指数退避,连续3次则暂停5分钟 |
| `404` | 检查仓库名拼写/可见性,及 `gh auth status` 认证与 scope;**勿主动 `gh auth refresh --scopes repo`**(避免授予写权限) |
| `5xx` | `retry` 指数退避,连续5次则暂停10分钟并告警 |
| 返回文本 > 5000字符 | 自动截断并标注 `[已截断]` |
| 单轮工具调用 > 20条 | 暂停30s防Token爆炸 |
## 8. 写入类 API 黑名单 (严禁调用)
**正则拦截**:`gh (pr|issue|repo|release|workflow|gist) (create|merge|comment|edit|close|delete|fork|run|disable|review)`
**API拦截**:`gh api -X (POST|PUT|DELETE|PATCH)`
**文件写入**:`gh api repos/O/R/contents/PATH` (PUT/POST/DELETE 方法)
> 只读白名单澄清:`gh run list` / `gh run view` / `gh pr checks` / `gh pr diff` 属只读,可正常使用(黑名单正则仅拦截第二组动词,不误伤 `gh run list`)。
## 9. 降级方案 (gh不可用时)
```bash
curl -s --max-time 30 -H "Authorization: token $GITHUB_TOKEN" \
-H "Accept: application/vnd.github.v3+json" \
"https://api.github.com/repos/O/R/contents/README.md" \
| jq -c '.content | @base64d'
```
## 10. 验证与测试 (Validation & Testing)
> 纯静态断言 + 单行命令 + 预期锚点;Agent 自检 / CI 集成两用,零额外运行时 Token。exit 0 = 通过。
| ID | 测试项 | 命令 | 通过锚点 |
| :--- | :--- | :--- | :--- |
| T1 | 写操作拦截 | `echo "gh pr create" \| grep -Eq 'gh (pr\|issue\|repo\|release\|workflow\|gist) (create\|merge\|comment\|edit\|close\|delete\|fork\|run\|disable\|review)'` | exit 0 |
| T2 | 认证状态 | `gh auth status` | 含 `Logged in to github.com` |
| T3 | 超时机制(本地模拟, Bash侧) | `timeout 1 sleep 2; test $? -eq 124` | exit 0 |
| T4 | 读文件解码 | `gh_read cli/cli README.md \| grep -q 'GitHub CLI'` | exit 0 |
| T5 | 目录树限深 | `gh_tree cli/cli 2 \| head -1 \| awk -F/ '{exit !(NF<=2)}'` | exit 0 |
| T6 | 动态截断 | `TOKEN_QUOTA=400 truncate_by_quota cli/cli README.md \| wc -l` | `49` 或 `50` |
| T7 | 降级 curl | `curl -s --max-time 5 https://api.github.com/rate_limit \| jq -e '.resources.core'` | exit 0 |
| T8 | PowerShell | `pwsh -NoProfile -Command "gh --version"` | 含 `gh version` |
| T9 | 函数加载 | `type gh_timed gh_cached retry gh_read gh_tree truncate_by_quota gh_default_branch >/dev/null 2>&1` | exit 0 |
### 一键自检(Bash,全只读)
```bash
t() { eval "$1" >/dev/null 2>&1 && echo "PASS $2" || echo "FAIL $2"; }
t 'echo "gh pr create" | grep -Eq "gh (pr|issue|repo|release|workflow|gist) (create|merge|comment|edit|close|delete|fork|run|disable|review)"' T1_blacklist
t 'gh auth status' T2_auth
t 'timeout 1 sleep 2; test $? -eq 124' T3_timeout
t 'gh_read cli/cli README.md | grep -q "GitHub CLI"' T4_read
t 'gh_tree cli/cli 2 | head -1 | awk -F/ "{exit !(NF<=2)}"' T5_tree
t 'TOKEN_QUOTA=400 truncate_by_quota cli/cli README.md | wc -l | grep -Eq "^(49|50)$"' T6_truncate
t 'curl -s --max-time 5 https://api.github.com/rate_limit | jq -e ".resources.core"' T7_fallback
t 'pwsh -NoProfile -Command "gh --version"' T8_pwsh
t 'type gh_timed gh_cached retry gh_read gh_tree truncate_by_quota gh_default_branch >/dev/null 2>&1' T9_functions
```
> T1/T9 纯静态(无网络);T2–T6 需 `gh` 认证;T7 仅验证降级通路。全为只读断言,零写风险。脚本中 `$1`/`$2` 为 `t()` 函数参数,非技能变量。
## 11. 实际使用场景 (Agent 对话示例)
> 展示 Agent 收到任务时如何组合工具,均只读、省 Token。`O/R` 以下以 `cli/cli` 为例。
### 场景A:"看看 cli/cli 最近3天的 PR 情况"
```bash
gh pr list --repo cli/cli --limit 10 --json number,title,author,updatedAt,state \
| jq -c '.[] | select(.updatedAt >= "'"$(date -u -d '-3 days' +%Y-%m-%dT%H:%M:%SZ)"'") | {n:.number,t:.title,a:.author.login,s:.state}'
```
### 场景B:"这个仓库用了什么构建工具?找找配置文件"
```bash
# 先取目录树前2层,再定位构建文件
gh_tree cli/cli 2
gh_search() { gh search code "filename:go.mod OR filename:package.json OR filename:Makefile" --repo cli/cli --limit 5 --json path | jq -c '.[].path'; }
```
### 场景C:"某次 CI 失败原因"
```bash
gh run list --repo cli/cli --limit 5 --json databaseId,status,conclusion \
| jq -c '.[] | select(.conclusion=="failure") | .databaseId' \
| head -1 | xargs -I{} gh run view {} --repo cli/cli --log-failed | head -50
```
> 组合原则:先 `gh_default_branch` 定分支 → `gh_tree` 定文件 → `gh_read` 读内容 → 全程 `--json` + `jq -c` 裁剪,单轮工具调用 ≤20 条。使用说明
# GitHub CLI 只读工具集 基于 GitHub CLI(`gh`)的只读查询技能:仓库搜索、源码读取、代码搜索、PR/Issue/CI 状态查询,全程裁剪输出极致省 Token,内置写入操作黑名单。 ## 使用 对 Agent 说: ```text 看看 cli/cli 最近3天的 PR 情况 读一下 facebook/react 的目录结构 这个仓库 CI 为什么挂了? ``` 前提:本机已安装并登录 `gh`(`gh auth status` 可验证)。 ## 工作原理 - 预置 `gh_timed`(超时)、`gh_cached`(30 分钟缓存)、`retry`(指数退避)等封装函数; - 查询统一走 `--json` + `jq -c` 紧凑输出,单轮调用 ≤20 条; - 内置正则黑名单拦截一切写操作,只读安全。
支持平台:Qoder · QoderWork · Claude · Codex 等 AI 编程助手