@zhin.js/adapter-github 0.1.39 → 0.1.41

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@zhin.js/adapter-github",
3
- "version": "0.1.39",
3
+ "version": "0.1.41",
4
4
  "description": "Zhin.js adapter for GitHub (gh CLI) — treat issues/PRs as chat channels, full repo management, webhook notifications",
5
5
  "type": "module",
6
6
  "main": "./lib/index.js",
@@ -41,11 +41,11 @@
41
41
  "license": "MIT",
42
42
  "devDependencies": {
43
43
  "typescript": "^6.0.0",
44
- "zhin.js": "1.0.67"
44
+ "zhin.js": "1.0.71"
45
45
  },
46
46
  "peerDependencies": {
47
- "@zhin.js/http": "1.0.62",
48
- "zhin.js": "1.0.67"
47
+ "zhin.js": "1.0.71",
48
+ "@zhin.js/http": "1.0.64"
49
49
  },
50
50
  "peerDependenciesMeta": {
51
51
  "@zhin.js/http": {
@@ -3,12 +3,15 @@ name: github
3
3
  platforms:
4
4
  - github
5
5
  description: >-
6
- GitHub 全功能适配器(基于 gh CLI + GitHub App 认证):PR 管理(创建/合并/Review/关闭/Diff)、Issue 管理(创建/编辑/关闭/评论)、
7
- 仓库操作(Star/Fork/搜索/标签)、CI/Release/Branch 查询、文件查看/编辑、提交历史、事件轮询订阅、多用户 GitHub 账号绑定。
6
+ GitHub 全功能适配器技能:通过适配器内置工具完成用户交互操作(Star/Fork/账号绑定/Webhook 订阅/App 安装),
7
+ 通过 bash + gh CLI 完成仓库自动化操作(Issue/PR/Release/CI/搜索/文件/Discussion)。
8
8
  Bot 操作使用 GitHub App 身份,Star/Fork 等用户操作使用个人绑定的 OAuth Token。
9
+ 每个 Zhin 实例通过 GH_TOKEN 环境变量注入身份,支持多实例协同。
9
10
  channel ID 格式:owner/repo/issues/N 或 owner/repo/pull/N。
10
11
  keywords:
11
12
  - github
13
+ - gh
14
+ - cli
12
15
  - adapter:github
13
16
  - pr
14
17
  - pull request
@@ -41,61 +44,216 @@ keywords:
41
44
  - edit
42
45
  - webhook
43
46
  - subscribe
47
+ - discussion
44
48
  tags:
45
49
  - github
46
50
  - development
47
51
  - git
48
52
  - ci-cd
49
53
  tools:
50
- - github_pr
51
- - github_issue
52
- - github_repo
53
- - github_subscribe
54
- - github_unsubscribe
55
- - github_subscriptions
54
+ - bash
56
55
  - github_star
57
56
  - github_fork
58
- - github_search
59
- - github_label
60
- - github_assign
61
- - github_file
62
- - github_commits
63
- - github_edit
64
57
  - github_bind
65
58
  - github_unbind
66
59
  - github_whoami
60
+ - github_install
61
+ - github_subscribe
62
+ - github_unsubscribe
63
+ - github_subscriptions
64
+ always: false
65
+ requires:
66
+ bins:
67
+ - gh
68
+ env:
69
+ - GH_TOKEN
70
+ ---
71
+
72
+ # GitHub 全功能操作指南
73
+
74
+ 本技能提供两种操作方式:
75
+ - **适配器内置工具**(`github_*`):用户交互类操作,具备账号绑定、Device Flow 授权、频道级订阅等适配器专有逻辑
76
+ - **bash + gh CLI**:仓库自动化操作,灵活覆盖 GitHub API 全场景
77
+
78
+ ## 一、适配器内置工具
79
+
80
+ 以下工具由 GitHub 适配器注册,具有特殊行为(用户 OAuth 绑定、频道绑定等),**不能用 gh CLI 替代**。
81
+
82
+ ### 账号与身份
83
+
84
+ | 工具 | 说明 |
85
+ |------|------|
86
+ | `github_bind` | 绑定用户的 GitHub 账号(Device Flow 授权,无需输入密码)。用户想 star/fork 或操作自己的账号时,先引导使用此工具 |
87
+ | `github_unbind` | 解除用户绑定的 GitHub 账号 |
88
+ | `github_whoami` | 查看用户已绑定的 GitHub 账号信息 |
89
+ | `github_install` | 获取安装 GitHub App 的链接,安装后 Bot 可访问用户的仓库 |
90
+
91
+ ### 用户操作(使用绑定账号)
92
+
93
+ | 工具 | 说明 |
94
+ |------|------|
95
+ | `github_star` | Star 或取消 Star 一个仓库。优先使用用户绑定的 GitHub 账号,未绑定则降级为 Bot 默认账号 |
96
+ | `github_fork` | Fork 一个仓库。优先使用用户绑定的 GitHub 账号,未绑定则降级为 Bot 默认账号 |
97
+
98
+ ### Webhook 订阅(频道级)
99
+
100
+ | 工具 | 说明 |
101
+ |------|------|
102
+ | `github_subscribe` | 订阅仓库的 Webhook 事件,推送到**当前聊天通道** |
103
+ | `github_unsubscribe` | 取消订阅仓库的 Webhook 事件 |
104
+ | `github_subscriptions` | 查看当前聊天通道的订阅列表 |
105
+
106
+ ### 内置工具执行规则
107
+
108
+ 1. `github_star` 和 `github_fork` 优先使用用户绑定的 GitHub 账号,未绑定则降级为 Bot 默认账号
109
+ 2. 当用户想操作自己的 GitHub 账号时,先引导用户使用 `github_bind` 绑定
110
+ 3. Webhook 订阅关联到当前聊天通道,仅在该通道接收事件通知
111
+
67
112
  ---
68
113
 
69
- ## 工具概览
70
-
71
- | 工具 | 说明 | 子操作 |
72
- |------|------|--------|
73
- | `github_pr` | PR 操作 | list / view / diff / merge / create / review / close |
74
- | `github_issue` | Issue 操作 | list / view / create / close / edit / comment |
75
- | `github_repo` | 仓库信息查询 | — |
76
- | `github_search` | 全局搜索 | issues / repos / code |
77
- | `github_star` | Star/取消 Star | — |
78
- | `github_fork` | Fork 仓库 | — |
79
- | `github_label` | 标签管理 | add / remove / list |
80
- | `github_assign` | 指派管理 | add / remove |
81
- | `github_file` | 查看/编辑仓库文件 | — |
82
- | `github_commits` | 提交历史查询 | — |
83
- | `github_edit` | 文件直接编辑 | — |
84
- | `github_subscribe` | 订阅仓库 Webhook 事件 | — |
85
- | `github_unsubscribe` | 取消订阅 | — |
86
- | `github_subscriptions` | 查看当前订阅列表 | |
87
- | `github_bind` | 绑定你的 GitHub 账号(Device Flow) | — |
88
- | `github_unbind` | 解除 GitHub 账号绑定 | — |
89
- | `github_whoami` | 查看已绑定的 GitHub 账号 | — |
90
-
91
- ## 执行规则
92
-
93
- 1. 确认 channel / repo 格式与参数完整
94
- 2. `github_pr` `action` 参数决定子操作:list/view/diff/merge/create/review/close
95
- 3. `github_issue` `action` 参数决定子操作:list/view/create/close/edit/comment
96
- 4. 搜索支持三种类型:issues、repos、code
97
- 5. 文件操作需要 owner/repo/path 参数
98
- 6. Webhook 订阅关联到当前聊天通道,仅在该通道接收事件通知
99
- 7. `github_star` `github_fork` 优先使用用户绑定的 GitHub 账号,未绑定则降级为 Bot 默认账号
100
- 8. 用户通过 `github_bind` 发起 Device Flow 绑定(需 Bot 配置 client_id)
101
- 9. 当用户想操作自己的 GitHub 账号时,先引导用户使用 `github_bind` 绑定
114
+ ## 二、gh CLI 全场景速查
115
+
116
+ 当适配器内置工具不能满足需求(如 Release 管理、Workflow 触发、Discussion、gh api 万能调用),或需要更灵活的参数控制时,使用 `bash` 工具调用 `gh` CLI。当前进程已通过 `GH_TOKEN` 环境变量注入身份凭据。
117
+
118
+ ### 0. 前提检查
119
+
120
+ ```bash
121
+ gh --version
122
+ gh auth status
123
+ ```
124
+
125
+ ### 1. 仓库(gh repo)
126
+
127
+ ```bash
128
+ gh repo view owner/repo
129
+ gh repo list owner --limit 10 --json name,description
130
+ gh repo clone owner/repo
131
+ gh repo create owner/new-repo --public --description "描述"
132
+ ```
133
+
134
+ ### 2. Issue(gh issue)
135
+
136
+ ```bash
137
+ gh issue list -R owner/repo --state open --limit 20 --json number,title,state,labels,assignees
138
+ gh issue view 123 -R owner/repo --json number,title,body,state,labels,assignees --jq '{number,title,state,labels,assignees,body: .body[:2000]}'
139
+ gh issue create -R owner/repo --title "标题" --body "正文" --label "bug" --assignee "user1"
140
+ gh issue close 123 -R owner/repo
141
+ gh issue reopen 123 -R owner/repo
142
+ gh issue comment 123 -R owner/repo --body "评论内容"
143
+ gh issue edit 123 -R owner/repo --title "新标题"
144
+ gh issue edit 123 -R owner/repo --add-label "enhancement" --remove-label "bug"
145
+ gh issue edit 123 -R owner/repo --add-assignee "user2" --remove-assignee "user1"
146
+ ```
147
+
148
+ ### 3. Pull Request(gh pr)
149
+
150
+ ```bash
151
+ gh pr list -R owner/repo --state open --limit 20 --json number,title,state,author,mergeable
152
+ gh pr view 456 -R owner/repo --json number,title,body,state,author,mergeable,reviewDecision --jq '{number,title,state,author,mergeable,reviewDecision,body: .body[:2000]}'
153
+ gh pr create -R owner/repo --title "标题" --body "描述" --base main --head feature-branch
154
+ gh pr merge 456 -R owner/repo --squash --delete-branch
155
+ gh pr close 456 -R owner/repo
156
+ gh pr reopen 456 -R owner/repo
157
+ gh pr comment 456 -R owner/repo --body "评论内容"
158
+ gh pr review 456 -R owner/repo --approve
159
+ gh pr review 456 -R owner/repo --request-changes --body "需要修改的内容"
160
+ gh pr review 456 -R owner/repo --comment --body "一般性建议"
161
+ gh pr checks 456 -R owner/repo
162
+ gh pr diff 456 -R owner/repo | head -n 200
163
+ ```
164
+
165
+ ### 4. Release(gh release)
166
+
167
+ ```bash
168
+ gh release list -R owner/repo --limit 10
169
+ gh release view v1.0.0 -R owner/repo
170
+ gh release create v1.0.0 -R owner/repo --title "v1.0.0" --notes "发布说明" --target main
171
+ gh release delete v1.0.0 -R owner/repo --yes
172
+ ```
173
+
174
+ ### 5. Workflow / CI(gh run & gh workflow)
175
+
176
+ ```bash
177
+ gh run list -R owner/repo --limit 10 --json databaseId,displayTitle,status,conclusion
178
+ gh run view 12345 -R owner/repo
179
+ gh run rerun 12345 -R owner/repo --failed
180
+ gh workflow list -R owner/repo
181
+ gh workflow run ci.yml -R owner/repo --ref main
182
+ ```
183
+
184
+ ### 6. 搜索(gh search)
185
+
186
+ ```bash
187
+ gh search issues "关键词" --repo owner/repo --limit 10 --json number,title,state
188
+ gh search repos "关键词" --limit 10 --json fullName,description,stargazersCount
189
+ gh search code "函数名" --repo owner/repo --limit 10 --json path,textMatches
190
+ ```
191
+
192
+ ### 7. 标签与指派
193
+
194
+ ```bash
195
+ gh label list -R owner/repo
196
+ gh label create "priority:high" -R owner/repo --color FF0000 --description "高优先级"
197
+ gh issue edit 123 -R owner/repo --add-label "priority:high"
198
+ gh issue edit 123 -R owner/repo --remove-label "priority:low"
199
+ gh issue edit 123 -R owner/repo --add-assignee "user1"
200
+ gh issue edit 123 -R owner/repo --remove-assignee "user2"
201
+ ```
202
+
203
+ ### 8. 文件与提交
204
+
205
+ ```bash
206
+ gh api /repos/owner/repo/contents/path/to/file --jq '.content' | base64 -d
207
+ gh api /repos/owner/repo/commits --jq '.[0:10] | .[] | {sha: .sha[:7], message: .commit.message[:80], author: .commit.author.name, date: .commit.author.date}'
208
+ gh api /repos/owner/repo/compare/main...feature --jq '{ahead_by, behind_by, files: [.files[:20][] | {filename, status, changes}]}'
209
+ ```
210
+
211
+ ### 9. Discussion
212
+
213
+ ```bash
214
+ gh api /repos/owner/repo/discussions --jq '.[0:10] | .[] | {number, title, category: .category.name}'
215
+ gh api /repos/owner/repo/discussions/categories --jq '.[] | {id: .node_id, name, slug}'
216
+ gh api graphql -f query='mutation {
217
+ createDiscussion(input: {
218
+ repositoryId: "REPO_NODE_ID",
219
+ categoryId: "CATEGORY_NODE_ID",
220
+ title: "标题",
221
+ body: "正文"
222
+ }) { discussion { number url } }
223
+ }'
224
+ gh api -X POST /repos/owner/repo/discussions/{number}/comments -f body="评论内容"
225
+ ```
226
+
227
+ ### 10. 万能 fallback:gh api
228
+
229
+ ```bash
230
+ gh api /repos/owner/repo/xxx
231
+ gh api -X POST /repos/owner/repo/xxx -f key=value
232
+ gh api -X PATCH /repos/owner/repo/xxx -f key=value
233
+ gh api graphql -f query='{ repository(owner:"owner", name:"repo") { ... } }'
234
+ ```
235
+
236
+ ---
237
+
238
+ ## 三、输出控制黄金规则
239
+
240
+ **所有 gh CLI 命令都必须控制输出大小**,防止 token 爆炸:
241
+
242
+ | 手段 | 示例 |
243
+ |------|------|
244
+ | `--json` + `--jq` | 精确控制返回字段和条数 |
245
+ | `--limit N` | 列表不超过 20 条 |
246
+ | `--jq '.body[:2000]'` | 截断长文本字段 |
247
+ | `\| head -n 200` | 截断 diff 等大文本 |
248
+
249
+ **目标:单次调用输出 < 15KB。**
250
+
251
+ ## 四、常见错误与修复
252
+
253
+ | 错误 | 原因 | 修复 |
254
+ |------|------|------|
255
+ | `gh: command not found` | 未安装 gh CLI | 需管理员安装 |
256
+ | `HTTP 401` | Token 无效或过期 | 检查 GH_TOKEN 环境变量 |
257
+ | `HTTP 404` | 仓库/资源不存在或无权限 | 确认 owner/repo 和 Token scope |
258
+ | `HTTP 422` | 参数不合法 | 检查必填字段是否缺失 |
259
+ | `GraphQL: ...` | GraphQL 查询语法错误 | 检查 query 拼写与字段名 |
package/src/gh-client.ts CHANGED
@@ -248,142 +248,6 @@ export class GhClient {
248
248
  return this.del(`/repos/${repo}/pulls/comments/${commentId}`);
249
249
  }
250
250
 
251
- // ── Pull Requests ─────────────────────────────────────────────────
252
-
253
- async listPRs(repo: string, state: string = 'open', limit: number = 15) {
254
- return this.get<any[]>(`/repos/${repo}/pulls?state=${state}&per_page=${limit}`);
255
- }
256
-
257
- async getPR(repo: string, number: number) {
258
- return this.get<any>(`/repos/${repo}/pulls/${number}`);
259
- }
260
-
261
- async getPRDiff(repo: string, number: number): Promise<{ ok: boolean; data: string }> {
262
- return this.request('GET', `/repos/${repo}/pulls/${number}`, undefined, {
263
- Accept: 'application/vnd.github.diff',
264
- }) as Promise<{ ok: boolean; data: string }>;
265
- }
266
-
267
- async mergePR(repo: string, number: number, method: string = 'squash') {
268
- return this.put<any>(`/repos/${repo}/pulls/${number}/merge`, { merge_method: method });
269
- }
270
-
271
- async createPR(repo: string, title: string, body: string, head: string, base: string = 'main') {
272
- return this.post<any>(`/repos/${repo}/pulls`, { title, body, head, base });
273
- }
274
-
275
- async createPRReview(repo: string, number: number, event: 'APPROVE' | 'REQUEST_CHANGES' | 'COMMENT', body?: string) {
276
- return this.post<any>(`/repos/${repo}/pulls/${number}/reviews`, { event, body: body || '' });
277
- }
278
-
279
- async closePR(repo: string, number: number) {
280
- return this.patch<any>(`/repos/${repo}/pulls/${number}`, { state: 'closed' });
281
- }
282
-
283
- // ── Issues ────────────────────────────────────────────────────────
284
-
285
- async listIssues(repo: string, state: string = 'open', limit: number = 15) {
286
- return this.get<any[]>(`/repos/${repo}/issues?state=${state}&per_page=${limit}&direction=desc`);
287
- }
288
-
289
- async getIssue(repo: string, number: number) {
290
- return this.get<any>(`/repos/${repo}/issues/${number}`);
291
- }
292
-
293
- async createIssue(repo: string, title: string, body?: string, labels?: string[]) {
294
- return this.post<any>(`/repos/${repo}/issues`, { title, body, labels });
295
- }
296
-
297
- async closeIssue(repo: string, number: number) {
298
- return this.patch<any>(`/repos/${repo}/issues/${number}`, { state: 'closed', state_reason: 'completed' });
299
- }
300
-
301
- // ── Repository ────────────────────────────────────────────────────
302
-
303
- async getRepo(repo: string) {
304
- return this.get<any>(`/repos/${repo}`);
305
- }
306
-
307
- async listBranches(repo: string, limit: number = 30) {
308
- return this.get<any[]>(`/repos/${repo}/branches?per_page=${limit}`);
309
- }
310
-
311
- async listReleases(repo: string, limit: number = 10) {
312
- return this.get<any[]>(`/repos/${repo}/releases?per_page=${limit}`);
313
- }
314
-
315
- async listWorkflowRuns(repo: string, limit: number = 10) {
316
- return this.get<{ total_count: number; workflow_runs: any[] }>(`/repos/${repo}/actions/runs?per_page=${limit}`);
317
- }
318
-
319
- // ── Search ───────────────────────────────────────────────────────
320
-
321
- async searchIssues(query: string, limit: number = 15) {
322
- return this.get<{ total_count: number; items: any[] }>(`/search/issues?q=${encodeURIComponent(query)}&per_page=${limit}`);
323
- }
324
-
325
- async searchRepos(query: string, limit: number = 15) {
326
- return this.get<{ total_count: number; items: any[] }>(`/search/repositories?q=${encodeURIComponent(query)}&per_page=${limit}`);
327
- }
328
-
329
- async searchCode(query: string, limit: number = 15) {
330
- return this.get<{ total_count: number; items: any[] }>(`/search/code?q=${encodeURIComponent(query)}&per_page=${limit}`);
331
- }
332
-
333
- // ── Labels ───────────────────────────────────────────────────────
334
-
335
- async listLabels(repo: string) {
336
- return this.get<any[]>(`/repos/${repo}/labels?per_page=100`);
337
- }
338
-
339
- async addLabels(repo: string, issueNumber: number, labels: string[]) {
340
- return this.post<any[]>(`/repos/${repo}/issues/${issueNumber}/labels`, { labels });
341
- }
342
-
343
- async removeLabel(repo: string, issueNumber: number, label: string) {
344
- return this.del(`/repos/${repo}/issues/${issueNumber}/labels/${encodeURIComponent(label)}`);
345
- }
346
-
347
- // ── Assignees ────────────────────────────────────────────────────
348
-
349
- async addAssignees(repo: string, issueNumber: number, assignees: string[]) {
350
- return this.post<any>(`/repos/${repo}/issues/${issueNumber}/assignees`, { assignees });
351
- }
352
-
353
- async removeAssignees(repo: string, issueNumber: number, assignees: string[]) {
354
- return this.del<any>(`/repos/${repo}/issues/${issueNumber}/assignees`, { assignees });
355
- }
356
-
357
- // ── File Content ─────────────────────────────────────────────────
358
-
359
- async getFileContent(repo: string, filePath: string, ref?: string) {
360
- const qs = ref ? `?ref=${encodeURIComponent(ref)}` : '';
361
- return this.get<any>(`/repos/${repo}/contents/${filePath}${qs}`);
362
- }
363
-
364
- // ── Commits ──────────────────────────────────────────────────────
365
-
366
- async listCommits(repo: string, sha?: string, filePath?: string, limit: number = 15) {
367
- const params = new URLSearchParams({ per_page: String(limit) });
368
- if (sha) params.set('sha', sha);
369
- if (filePath) params.set('path', filePath);
370
- return this.get<any[]>(`/repos/${repo}/commits?${params}`);
371
- }
372
-
373
- async compareCommits(repo: string, base: string, head: string) {
374
- return this.get<any>(`/repos/${repo}/compare/${encodeURIComponent(base)}...${encodeURIComponent(head)}`);
375
- }
376
-
377
- // ── Update Issue / PR ────────────────────────────────────────────
378
-
379
- async updateIssue(repo: string, number: number, data: { title?: string; body?: string; state?: string; labels?: string[]; assignees?: string[] }) {
380
- return this.patch<any>(`/repos/${repo}/issues/${number}`, data);
381
- }
382
-
383
- async updatePR(repo: string, number: number, data: { title?: string; body?: string; state?: string; base?: string }) {
384
- return this.patch<any>(`/repos/${repo}/pulls/${number}`, data);
385
- }
386
-
387
251
  // ── Star ─────────────────────────────────────────────────────────
388
252
 
389
253
  async starRepo(repo: string) {