dsh-github-flow 0.3.0 → 0.4.0
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/README.en.md +220 -143
- package/README.md +202 -124
- package/docs/UPGRADE-NOTES.md +14 -8
- package/docs/architecture.md +111 -129
- package/lib/api/routes-actions.js +117 -0
- package/lib/api/routes-overview.js +94 -2
- package/lib/cache.d.ts +4 -0
- package/lib/cache.js +14 -0
- package/lib/client.d.ts +13 -1
- package/lib/client.js +2906 -2475
- package/lib/executor/git-parser.d.ts +25 -0
- package/lib/executor/git-parser.js +143 -0
- package/lib/executor/platform-guide.d.ts +14 -0
- package/lib/executor/platform-guide.js +40 -0
- package/lib/executor/process-utils.d.ts +51 -0
- package/lib/executor/process-utils.js +162 -0
- package/lib/executor/version-utils.d.ts +12 -0
- package/lib/executor/version-utils.js +27 -0
- package/lib/executor.d.ts +45 -48
- package/lib/executor.js +184 -353
- package/lib/git-watcher.d.ts +36 -0
- package/lib/git-watcher.js +91 -0
- package/lib/main.js +10 -0
- package/lib/renderers/cache.d.ts +8 -0
- package/lib/renderers/cache.js +66 -0
- package/lib/renderers/common.d.ts +24 -0
- package/lib/renderers/common.js +88 -0
- package/lib/renderers/gist.d.ts +15 -0
- package/lib/renderers/gist.js +99 -0
- package/lib/renderers/issue.d.ts +4 -0
- package/lib/renderers/issue.js +70 -0
- package/lib/renderers/label.d.ts +4 -0
- package/lib/renderers/label.js +48 -0
- package/lib/renderers/markdown.d.ts +15 -32
- package/lib/renderers/markdown.js +15 -624
- package/lib/renderers/pr.d.ts +4 -0
- package/lib/renderers/pr.js +107 -0
- package/lib/renderers/release.d.ts +4 -0
- package/lib/renderers/release.js +74 -0
- package/lib/renderers/repo.d.ts +4 -0
- package/lib/renderers/repo.js +149 -0
- package/lib/renderers/ruleset.d.ts +4 -0
- package/lib/renderers/ruleset.js +74 -0
- package/lib/renderers/run.d.ts +4 -0
- package/lib/renderers/run.js +79 -0
- package/lib/renderers/skill.d.ts +4 -0
- package/lib/renderers/skill.js +61 -0
- package/lib/renderers/status.d.ts +23 -0
- package/lib/renderers/status.js +124 -0
- package/lib/tools/cache.d.ts +64 -0
- package/lib/tools/cache.js +186 -0
- package/lib/tools/gist.d.ts +155 -0
- package/lib/tools/gist.js +190 -0
- package/lib/tools/help.js +107 -6
- package/lib/tools/image-utils.d.ts +8 -0
- package/lib/tools/image-utils.js +34 -0
- package/lib/tools/issue.d.ts +8 -1
- package/lib/tools/issue.js +26 -4
- package/lib/tools/label.d.ts +80 -0
- package/lib/tools/label.js +237 -0
- package/lib/tools/pr.d.ts +8 -0
- package/lib/tools/pr.js +48 -3
- package/lib/tools/ruleset.d.ts +80 -0
- package/lib/tools/ruleset.js +177 -0
- package/lib/tools/status.d.ts +39 -0
- package/lib/tools/status.js +53 -0
- package/lib/types.d.ts +39 -0
- package/lib/ui/auth-guide-card.d.ts +4 -0
- package/lib/ui/auth-guide-card.js +161 -0
- package/lib/ui/branch-selector.d.ts +10 -0
- package/lib/ui/branch-selector.js +163 -0
- package/lib/ui/commit-modal.d.ts +8 -0
- package/lib/ui/commit-modal.js +89 -0
- package/lib/ui/icons.d.ts +7 -0
- package/lib/ui/icons.js +53 -0
- package/lib/ui/network-banner.d.ts +10 -0
- package/lib/ui/network-banner.js +54 -0
- package/lib/ui/storage.d.ts +26 -0
- package/lib/ui/storage.js +134 -0
- package/lib/ui/styles.d.ts +6 -0
- package/lib/ui/styles.js +1285 -0
- package/lib/ui/sync-settings-modal.d.ts +11 -0
- package/lib/ui/sync-settings-modal.js +350 -0
- package/lib/ui/sync-status-badge.d.ts +8 -0
- package/lib/ui/sync-status-badge.js +64 -0
- package/lib/ui/upgrade-card.d.ts +7 -0
- package/lib/ui/upgrade-card.js +197 -0
- package/lib/ui/utils.d.ts +10 -0
- package/lib/ui/utils.js +66 -0
- package/lib/ui/workspace-panel.d.ts +4 -0
- package/lib/ui/workspace-panel.js +464 -0
- package/lib/version.d.ts +1 -1
- package/lib/version.js +1 -1
- package/package.json +1 -1
|
@@ -0,0 +1,190 @@
|
|
|
1
|
+
import { renderGistMarkdown, parseGistListOutput } from '../renderers/markdown.js';
|
|
2
|
+
export function createGistTool(executor) {
|
|
3
|
+
return {
|
|
4
|
+
name: 'github_gist',
|
|
5
|
+
description: '管理 GitHub Gist 代码片段与诊断留痕:查看 Gist 列表、检视代码正文 (view)、通过文本内容或本地文件创建新 Gist (create,默认安全 secret 模式)、编辑以及安全删除。删除操作不可逆,需 confirm: true。',
|
|
6
|
+
parameters: {
|
|
7
|
+
type: 'object',
|
|
8
|
+
properties: {
|
|
9
|
+
action: {
|
|
10
|
+
type: 'string',
|
|
11
|
+
enum: ['list', 'view', 'create', 'edit', 'delete'],
|
|
12
|
+
description: 'Gist 操作类型。list 查看列表、view 查看正文、create 新建、edit 修改、delete 物理删除(需 confirm)。',
|
|
13
|
+
},
|
|
14
|
+
gist_id: {
|
|
15
|
+
type: 'string',
|
|
16
|
+
description: 'Gist 唯一 ID 或完整 URL(view, edit, delete 动作必需,例如 "5b0e0062eb8e9654adad7bb1d81cc75f")。',
|
|
17
|
+
},
|
|
18
|
+
filename: {
|
|
19
|
+
type: 'string',
|
|
20
|
+
description: '文件名(view 动作指定查看文件、create 动作命名单文件、edit 动作指定编辑文件;默认 "snippet.txt")。',
|
|
21
|
+
},
|
|
22
|
+
files: {
|
|
23
|
+
type: 'array',
|
|
24
|
+
items: { type: 'string' },
|
|
25
|
+
description: '本地文件路径列表(create 动作上传多文件时使用)。',
|
|
26
|
+
},
|
|
27
|
+
content: {
|
|
28
|
+
type: 'string',
|
|
29
|
+
description: '代码片段或文本正文内容(create 动作直接传正文时使用,通过 stdin 安全管道传入)。',
|
|
30
|
+
},
|
|
31
|
+
description: {
|
|
32
|
+
type: 'string',
|
|
33
|
+
description: 'Gist 描述摘要或用途备注(create, edit 动作可选)。',
|
|
34
|
+
},
|
|
35
|
+
public: {
|
|
36
|
+
type: 'boolean',
|
|
37
|
+
description: '是否公开创建为 Public Gist(create 动作可选,默认 false 即 Secret 弱保密模式)。',
|
|
38
|
+
},
|
|
39
|
+
filter: {
|
|
40
|
+
type: 'string',
|
|
41
|
+
description: '按正则表达式过滤检索描述或文件内容(list 动作可选)。',
|
|
42
|
+
},
|
|
43
|
+
limit: {
|
|
44
|
+
type: 'number',
|
|
45
|
+
description: '返回 Gist 数量上限(list 动作使用,默认 10)。',
|
|
46
|
+
},
|
|
47
|
+
confirm: {
|
|
48
|
+
type: 'boolean',
|
|
49
|
+
description: '高危破坏性操作确认标志(delete 动作必需)。未传 true 时输出影响评估报告并拦截。',
|
|
50
|
+
},
|
|
51
|
+
},
|
|
52
|
+
required: ['action'],
|
|
53
|
+
},
|
|
54
|
+
output: {
|
|
55
|
+
schema: { type: 'object' },
|
|
56
|
+
render(args, value) {
|
|
57
|
+
return [{ type: 'text', text: renderGistMarkdown(args, value) }];
|
|
58
|
+
},
|
|
59
|
+
},
|
|
60
|
+
async execute(args, execContext) {
|
|
61
|
+
const cwd = execContext?.cwd || process.cwd();
|
|
62
|
+
const sessionId = execContext?.sessionId || execContext?.session?.id || process.env.DSH_SESSION_ID;
|
|
63
|
+
const runOpts = { cwd, sessionId, rawText: true };
|
|
64
|
+
switch (args.action) {
|
|
65
|
+
case 'list': {
|
|
66
|
+
const listCmd = ['gist', 'list'];
|
|
67
|
+
const limit = typeof args.limit === 'number' && args.limit > 0 ? args.limit : 10;
|
|
68
|
+
listCmd.push('-L', String(limit));
|
|
69
|
+
if (args.public === true) {
|
|
70
|
+
listCmd.push('--public');
|
|
71
|
+
}
|
|
72
|
+
else if (args.public === false) {
|
|
73
|
+
listCmd.push('--secret');
|
|
74
|
+
}
|
|
75
|
+
if (args.filter) {
|
|
76
|
+
listCmd.push('--filter', String(args.filter));
|
|
77
|
+
}
|
|
78
|
+
const res = await executor.run(listCmd, runOpts);
|
|
79
|
+
if (!res.ok)
|
|
80
|
+
throw new Error(res.error);
|
|
81
|
+
const items = parseGistListOutput(res.rawOutput || '');
|
|
82
|
+
return { items, count: items.length, rawText: res.rawOutput || '' };
|
|
83
|
+
}
|
|
84
|
+
case 'view': {
|
|
85
|
+
if (!args.gist_id) {
|
|
86
|
+
throw new Error('查看 Gist 时 `gist_id` 参数为必填项(可提供 Gist 数字字母 ID 或完整 URL)。');
|
|
87
|
+
}
|
|
88
|
+
const viewCmd = ['gist', 'view', String(args.gist_id), '-r'];
|
|
89
|
+
if (args.filename) {
|
|
90
|
+
viewCmd.push('-f', String(args.filename));
|
|
91
|
+
}
|
|
92
|
+
const res = await executor.run(viewCmd, runOpts);
|
|
93
|
+
if (!res.ok)
|
|
94
|
+
throw new Error(res.error);
|
|
95
|
+
return {
|
|
96
|
+
gist_id: String(args.gist_id),
|
|
97
|
+
filename: args.filename,
|
|
98
|
+
content: res.rawOutput || '',
|
|
99
|
+
};
|
|
100
|
+
}
|
|
101
|
+
case 'create': {
|
|
102
|
+
if (!args.content && (!args.files || !Array.isArray(args.files) || args.files.length === 0)) {
|
|
103
|
+
throw new Error('创建 Gist 时必须提供 `content`(代码片段文本)或 `files`(本地文件路径数组)。');
|
|
104
|
+
}
|
|
105
|
+
const createCmd = ['gist', 'create'];
|
|
106
|
+
let stdinData;
|
|
107
|
+
if (args.content) {
|
|
108
|
+
createCmd.push('-');
|
|
109
|
+
const fn = args.filename ? String(args.filename).trim() : 'snippet.txt';
|
|
110
|
+
createCmd.push('-f', fn);
|
|
111
|
+
stdinData = String(args.content);
|
|
112
|
+
}
|
|
113
|
+
else if (args.files) {
|
|
114
|
+
for (const f of args.files) {
|
|
115
|
+
createCmd.push(String(f));
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
if (args.public === true) {
|
|
119
|
+
createCmd.push('--public');
|
|
120
|
+
}
|
|
121
|
+
if (args.description) {
|
|
122
|
+
createCmd.push('-d', String(args.description));
|
|
123
|
+
}
|
|
124
|
+
const res = await executor.run(createCmd, { ...runOpts, stdin: stdinData });
|
|
125
|
+
if (!res.ok)
|
|
126
|
+
throw new Error(res.error);
|
|
127
|
+
const rawUrl = (res.rawOutput || '').trim();
|
|
128
|
+
const urlMatch = rawUrl.match(/gist\.github\.com\/([^/]+\/)?([0-9a-fA-F]+)/i);
|
|
129
|
+
const gistId = urlMatch ? urlMatch[2] : rawUrl;
|
|
130
|
+
return {
|
|
131
|
+
success: true,
|
|
132
|
+
url: rawUrl,
|
|
133
|
+
gist_id: gistId,
|
|
134
|
+
public: args.public === true,
|
|
135
|
+
description: args.description,
|
|
136
|
+
message: `✅ 成功创建 Gist: ${rawUrl}`,
|
|
137
|
+
};
|
|
138
|
+
}
|
|
139
|
+
case 'edit': {
|
|
140
|
+
if (!args.gist_id) {
|
|
141
|
+
throw new Error('编辑 Gist 时 `gist_id` 参数为必填项。');
|
|
142
|
+
}
|
|
143
|
+
const editCmd = ['gist', 'edit', String(args.gist_id)];
|
|
144
|
+
if (args.description) {
|
|
145
|
+
editCmd.push('-d', String(args.description));
|
|
146
|
+
}
|
|
147
|
+
if (args.filename) {
|
|
148
|
+
editCmd.push('-f', String(args.filename));
|
|
149
|
+
}
|
|
150
|
+
const res = await executor.run(editCmd, runOpts);
|
|
151
|
+
if (!res.ok)
|
|
152
|
+
throw new Error(res.error);
|
|
153
|
+
return {
|
|
154
|
+
success: true,
|
|
155
|
+
gist_id: String(args.gist_id),
|
|
156
|
+
message: `✅ 成功更新 Gist "${args.gist_id}"`,
|
|
157
|
+
detail: res.rawOutput || '',
|
|
158
|
+
};
|
|
159
|
+
}
|
|
160
|
+
case 'delete': {
|
|
161
|
+
if (!args.gist_id) {
|
|
162
|
+
throw new Error('删除 Gist 时 `gist_id` 参数为必填项。');
|
|
163
|
+
}
|
|
164
|
+
if (args.confirm !== true) {
|
|
165
|
+
return {
|
|
166
|
+
status: 'AWAITING_CONFIRMATION',
|
|
167
|
+
action: 'delete',
|
|
168
|
+
gist_id: String(args.gist_id),
|
|
169
|
+
warning: `⚠️ 高危破坏性操作:即将永久删除 Gist 代码片段 "${args.gist_id}"`,
|
|
170
|
+
impact: `删除 Gist 为物理删除,无法通过 GitHub API 恢复,所有引用该 Gist URL 的外部链接将失效并返回 404。`,
|
|
171
|
+
requiredAction: `如确认彻底删除,请在参数中附加 \`confirm: true\` 重新调用。示例:\`github_gist(action: 'delete', gist_id: '${args.gist_id}', confirm: true)\``,
|
|
172
|
+
};
|
|
173
|
+
}
|
|
174
|
+
const delCmd = ['gist', 'delete', String(args.gist_id), '--yes'];
|
|
175
|
+
const res = await executor.run(delCmd, runOpts);
|
|
176
|
+
if (!res.ok)
|
|
177
|
+
throw new Error(res.error);
|
|
178
|
+
return {
|
|
179
|
+
success: true,
|
|
180
|
+
gist_id: String(args.gist_id),
|
|
181
|
+
message: `🗑️ 成功删除 Gist 代码片段 "${args.gist_id}"`,
|
|
182
|
+
detail: res.rawOutput || '',
|
|
183
|
+
};
|
|
184
|
+
}
|
|
185
|
+
default:
|
|
186
|
+
throw new Error(`不支持的 gist 操作: "${args.action}"。可选值: list, view, create, edit, delete。`);
|
|
187
|
+
}
|
|
188
|
+
},
|
|
189
|
+
};
|
|
190
|
+
}
|
package/lib/tools/help.js
CHANGED
|
@@ -13,6 +13,11 @@ const KNOWLEDGE_BASE = {
|
|
|
13
13
|
| \`github_release\` | 版本发布与构建资产交付 | 查看发版历史、自动生成 Release Notes、上传 .tgz 等构建产物 |
|
|
14
14
|
| \`github_secret\` | CI/CD 密钥与变量管理 | 查看 NPM_TOKEN 等密钥清单、经 stdin 安全写入密文、删除密钥;kind:'variable' 管理明文变量(set/get/delete) |
|
|
15
15
|
| \`github_skill\` | 跨 Agent 开源 Skills 生态发现 | 检索/预览/安装 GitHub 上的 Agent Skills (gh preview 特性) |
|
|
16
|
+
| \`github_label\` | 标签体系管理与跨仓库克隆 | 查看标签列表、创建/编辑标签、从源仓库一键 clone 标签体系、删除标签(需 confirm) |
|
|
17
|
+
| \`github_ruleset\` | 分支保护规则与策略诊断 | 检查分支适用的合并策略规则 (check)、查看规则集列表与详情,权威回答“PR 为何无法合并” |
|
|
18
|
+
| \`github_cache\` | Actions 构建缓存盘点与安全清理 | 查看缓存列表与体积占用、按 ID/Key 删除指定缓存、全量清空缓存(需 confirm) |
|
|
19
|
+
| \`github_gist\` | 代码片段与调试日志分享治理 | 查看 Gist 列表与代码正文、创建 Secret/Public 代码片段、编辑与物理删除(需 confirm) |
|
|
20
|
+
| \`github_status\` | 跨仓库待办与动态聚合看板 | 账号维度汇总指派给我的 Issue/PR、等待评审、被 @提及 以及订阅仓库近期动态 |
|
|
16
21
|
| \`github_help\` | 路由导航、官方语法速查与故障自愈 | 遇到不确定的操作、搜索语法或接口报错时查询用法与诊断配方 |
|
|
17
22
|
|
|
18
23
|
💡 **使用准则**:优先调用高层语义工具(pr / issue / run / repo / release / secret);当高层工具无法满足特殊接口需求时,再降级使用 \`github_api\`。
|
|
@@ -35,7 +40,9 @@ const KNOWLEDGE_BASE = {
|
|
|
35
40
|
- **反向提交**: \`github_pr(action: 'revert', pr_number: 123, confirm: true)\`
|
|
36
41
|
- 在远端创建反向提交与新 PR(非本地操作),需 confirm。
|
|
37
42
|
- **分支落后自愈**: \`github_pr(action: 'update_branch', pr_number: 123)\`
|
|
38
|
-
- 将基准分支最新提交同步进 PR 分支,解决「分支落后需更新」的 CI
|
|
43
|
+
- 将基准分支最新提交同步进 PR 分支,解决「分支落后需更新」的 CI 门禁失败。
|
|
44
|
+
- **上传本地附件/图片**: \`github_pr(action: 'create', title: 'UI 重构', body: '对比图如下', attach_images: ['./before.png', './after.png'])\`
|
|
45
|
+
- 需 gh ≥ 2.99;支持 create/edit/comment 动作,单批上限 50 个文件,禁止网络 URL,与 view_images 对称闭环。`,
|
|
39
46
|
issue: `### 🎯 github_issue 详细操作指南
|
|
40
47
|
|
|
41
48
|
- **筛选 Issue**: \`github_issue(action: 'list', state: 'open' | 'closed' | 'all', labels: ['bug'], assignees: ['octocat'], limit: 20)\`
|
|
@@ -45,6 +52,8 @@ const KNOWLEDGE_BASE = {
|
|
|
45
52
|
- 经 gh api 下载正文+评论图片到本地(~/.dsh/data/dsh-github-flow/images/,内容寻址去重),以图片内容块返回,Agent 可直接查看用户报障截图;魔数校验 + 字节护栏(单图 5MB、目录 15MB)。
|
|
46
53
|
- **按 URL 精准获取**: 附加 \`image_urls: ['<view 返回清单中的 URL>']\` 只下载指定图片(可单张可批量);传入 URL 必须在清单内(防任意下载)。
|
|
47
54
|
- 触达目录上限时返回 \`remaining\` 结构化清单,直接作为下一批 image_urls 续传即可。
|
|
55
|
+
- **上传本地附件/图片**: \`github_issue(action: 'comment', issue_number: 42, body: '复现证据', attach_images: ['./bug.png'])\`
|
|
56
|
+
- 需 gh ≥ 2.99;支持 create/edit/comment 动作,单批上限 50 个本地文件(绝对路径或工作区相对路径,禁止网络 URL)。
|
|
48
57
|
- **关联开发分支**: \`github_issue(action: 'develop', issue_number: 42, branch_name: 'fix-login-bug')\`
|
|
49
58
|
- 自动在远端和本地创建带有官方关联关系的开发分支并切换过去,实现“读需求 -> 开分支写代码”的无缝衔接。
|
|
50
59
|
- **编辑 Issue**: \`github_issue(action: 'edit', issue_number: 42, title: '新标题', add_labels: ['p1'], remove_labels: ['triage'])\`
|
|
@@ -147,6 +156,61 @@ const KNOWLEDGE_BASE = {
|
|
|
147
156
|
- 支持的 agent 值:claude-code / cursor / github-copilot / codex / gemini-cli 等。
|
|
148
157
|
- **查看已安装**: \`github_skill(action: 'list')\`
|
|
149
158
|
- ⚠️ 若报「unknown command」,说明宿主机 gh 版本过低,需升级到 2.9x 以上。`,
|
|
159
|
+
label: `### 🏷️ github_label 详细操作指南
|
|
160
|
+
|
|
161
|
+
- **查看标签列表**: \`github_label(action: 'list', limit: 30, search: 'bug')\`
|
|
162
|
+
- 支持按名称或描述模糊检索,支持 \`sort: 'created' | 'name'\` 与 \`order: 'asc' | 'desc'\`。
|
|
163
|
+
- **创建标签**: \`github_label(action: 'create', name: 'priority/p0', color: 'd73a4a', description: '最高优先级缺陷')\`
|
|
164
|
+
- 颜色自动去除前导 \`#\` 并校验 6 位十六进制;已存在时可附加 \`force: true\` 强制覆盖更新。
|
|
165
|
+
- **编辑标签**: \`github_label(action: 'edit', name: 'old-name', new_name: 'new-name', color: '0075ca')\`
|
|
166
|
+
- 支持修改描述、色值以及重命名标签(\`new_name\`)。
|
|
167
|
+
- **删除标签**: \`github_label(action: 'delete', name: 'deprecated', confirm: true)\`
|
|
168
|
+
- 高危破坏性操作:会导致所有关联 Issue/PR 自动解绑该标记,必须显式附加 \`confirm: true\`;底层自动传 \`--yes\` 杜绝无头交互挂起。
|
|
169
|
+
- **跨仓库克隆标签**: \`github_label(action: 'clone', source_repo: 'cli/cli', force: true)\`
|
|
170
|
+
- 将源仓库的标准标签体系一键同步到当前仓库(或指定的 \`repo\`),新仓库初始化刚需。`,
|
|
171
|
+
ruleset: `### 🛡️ github_ruleset 详细操作指南
|
|
172
|
+
|
|
173
|
+
- **检查分支合并策略约束**: \`github_ruleset(action: 'check', branch: 'master')\`
|
|
174
|
+
- **PR 无法合并的策略视角权威答案**:输出指定分支匹配到的全部硬性规则约束(必审人数、指定 CI、禁止 force push 等)。
|
|
175
|
+
- 若无规则约束,输出排障引导,建议联动 \`github_pr(action: 'checks')\` 检查 CI 执行状态。
|
|
176
|
+
- 检查默认分支可附加 \`default_branch: true\`。
|
|
177
|
+
- **查看规则集列表**: \`github_ruleset(action: 'list', limit: 30)\`
|
|
178
|
+
- 默认包含上级组织继承规则;附加 \`no_parents: true\` 仅查看仓库自身规则集;支持 \`org: 'org-name'\` 查看组织级规则集(需 \`admin:org\` 权限)。
|
|
179
|
+
- **查看规则集详情**: \`github_ruleset(action: 'view', ruleset_id: '4898070')\`
|
|
180
|
+
- 查看具体 Ruleset 详细配置规则与适用范围,\`ruleset_id\` 必填。`,
|
|
181
|
+
cache: `### ⚡ github_cache 详细操作指南
|
|
182
|
+
|
|
183
|
+
- **查看缓存列表与体积**: \`github_cache(action: 'list', limit: 30, sort: 'size_in_bytes', order: 'desc')\`
|
|
184
|
+
- 返回仓库 Actions 构建缓存清单、占用体积(自动换算为 KB/MB/GB)、关联 Git Ref 以及最后访问时间。
|
|
185
|
+
- 可附加 \`key: 'npm-cache'\` 过滤特定前缀,或通过 \`ref: 'refs/heads/main'\` 限定分支。
|
|
186
|
+
- **精准删除单条缓存**:
|
|
187
|
+
- 按数字 ID 删除: \`github_cache(action: 'delete', cache_id: '12345678')\`
|
|
188
|
+
- 按 Key 键名删除: \`github_cache(action: 'delete', key: 'Linux-npm-18-abcd1234')\`
|
|
189
|
+
- **全量清空仓库缓存**:
|
|
190
|
+
\`github_cache(action: 'delete', all: true, confirm: true)\`
|
|
191
|
+
- 批量破坏性操作:会导致后续流水线构建重新拉取依赖,必须显式附加 \`confirm: true\`;底层自动注入 \`--succeed-on-no-caches\` 保证幂等执行。`,
|
|
192
|
+
gist: `### 📝 github_gist 详细操作指南
|
|
193
|
+
|
|
194
|
+
- **查看我的 Gist 列表**: \`github_gist(action: 'list', limit: 10)\`
|
|
195
|
+
- 默认查看全部;可附加 \`public: true\` 仅看公开、\`public: false\` 仅看私密;支持 \`filter: 'regex'\` 正则过滤。
|
|
196
|
+
- **检视代码片段正文**: \`github_gist(action: 'view', gist_id: '5b0e0062eb8e9654adad7bb1d81cc75f')\`
|
|
197
|
+
- 直接输出原始代码正文,\`gist_id\` 必填(防交互挂起);可选 \`filename: 'main.py'\` 限定文件。
|
|
198
|
+
- **创建 Gist 代码片段**:
|
|
199
|
+
- 直接传文本内容: \`github_gist(action: 'create', content: 'console.log("hello");', filename: 'demo.ts', description: '排错测试片段')\`
|
|
200
|
+
- 传本地文件列表: \`github_gist(action: 'create', files: ['./build.log', './error.txt'], description: 'CI 失败排错日志')\`
|
|
201
|
+
- 默认创建 Secret Gist(Unlisted 弱保密,拥有链接者可访问);若要公开需显式附加 \`public: true\`。
|
|
202
|
+
- ⚠️ **安全须知**: 切勿将敏感凭据、API Token 存入 Gist。
|
|
203
|
+
- **编辑 Gist**: \`github_gist(action: 'edit', gist_id: '...', description: '新描述', filename: 'patch.diff')\`
|
|
204
|
+
- **删除 Gist**: \`github_gist(action: 'delete', gist_id: '...', confirm: true)\`
|
|
205
|
+
- 物理删除不可逆,必须显式附加 \`confirm: true\`;底层自动注入 \`--yes\` 杜绝交互式卡死。`,
|
|
206
|
+
status: `### 🧭 github_status 详细操作指南
|
|
207
|
+
|
|
208
|
+
- **查看个人待办与动态大盘**: \`github_status()\` 或 \`github_status(action: 'view')\`
|
|
209
|
+
- 账号维度一站式汇总:指派给我的 Issues、指派给我的 PR、等待我评审的 PR、近期 @提及 以及订阅仓库最新动态。
|
|
210
|
+
- **限定组织**: \`github_status(org: 'my-org')\`
|
|
211
|
+
- 仅汇报指定组织内部的待办事项与动态。
|
|
212
|
+
- **排除噪音仓库**: \`github_status(exclude: ['owner/noisy-repo'])\`
|
|
213
|
+
- 过滤高频刷屏但无需关注的仓库活动。`,
|
|
150
214
|
mapping: `### 🗺️ gh 命令 ↔ 插件工具映射总表
|
|
151
215
|
|
|
152
216
|
#### 一、已覆盖域映射(语义化 action 精确对照)
|
|
@@ -210,20 +274,52 @@ const KNOWLEDGE_BASE = {
|
|
|
210
274
|
**Skill(github_skill,4 action)**
|
|
211
275
|
\`gh skill search/preview/install/list\` → \`github_skill(action:…)\`;install 需 confirm(写磁盘)。
|
|
212
276
|
|
|
277
|
+
**Label(github_label,5 action)**
|
|
278
|
+
| gh 命令 | 插件调用 | 备注 |
|
|
279
|
+
| :--- | :--- | :--- |
|
|
280
|
+
| \`gh label list\` | \`github_label(action:'list')\` | 支持搜索与排序 |
|
|
281
|
+
| \`gh label create\` | \`github_label(action:'create', name:…, color:…)\` | 颜色十六进制校验 |
|
|
282
|
+
| \`gh label edit\` | \`github_label(action:'edit', name:…, new_name:…)\` | 支持重命名 |
|
|
283
|
+
| \`gh label delete\` | \`github_label(action:'delete', name:…, confirm:true)\` | 高危解绑,需 confirm |
|
|
284
|
+
| \`gh label clone\` | \`github_label(action:'clone', source_repo:…)\` | 跨仓库一键克隆 |
|
|
285
|
+
|
|
286
|
+
**Ruleset(github_ruleset,3 action)**
|
|
287
|
+
| gh 命令 | 插件调用 | 备注 |
|
|
288
|
+
| :--- | :--- | :--- |
|
|
289
|
+
| \`gh ruleset check\` | \`github_ruleset(action:'check', branch:…)\` | PR 策略排障核心 |
|
|
290
|
+
| \`gh ruleset list\` | \`github_ruleset(action:'list')\` | 规则集概览 |
|
|
291
|
+
| \`gh ruleset view\` | \`github_ruleset(action:'view', ruleset_id:…)\` | 规则详情,ID 必填 |
|
|
292
|
+
|
|
293
|
+
**Cache(github_cache,2 action)**
|
|
294
|
+
| gh 命令 | 插件调用 | 备注 |
|
|
295
|
+
| :--- | :--- | :--- |
|
|
296
|
+
| \`gh cache list\` | \`github_cache(action:'list')\` | 缓存盘点与过滤 |
|
|
297
|
+
| \`gh cache delete\` | \`github_cache(action:'delete', cache_id:…)\` | all:true 需 confirm |
|
|
298
|
+
|
|
299
|
+
**Gist(github_gist,5 action)**
|
|
300
|
+
| gh 命令 | 插件调用 | 备注 |
|
|
301
|
+
| :--- | :--- | :--- |
|
|
302
|
+
| \`gh gist list/view\` | \`github_gist(action:'list'/'view')\` | 代码正文检视 |
|
|
303
|
+
| \`gh gist create\` | \`github_gist(action:'create', content:…)\` | 默认 secret 弱保密 |
|
|
304
|
+
| \`gh gist edit\` | \`github_gist(action:'edit', gist_id:…)\` | 描述与文件修改 |
|
|
305
|
+
| \`gh gist delete\` | \`github_gist(action:'delete', gist_id:…, confirm:true)\` | 物理删除,需 confirm |
|
|
306
|
+
|
|
307
|
+
**Status(github_status,账号维度聚合面板)**
|
|
308
|
+
| gh 命令 | 插件调用 | 备注 |
|
|
309
|
+
| :--- | :--- | :--- |
|
|
310
|
+
| \`gh status\` | \`github_status(action:'view')\` | 跨仓待办大盘与动态 |
|
|
311
|
+
|
|
213
312
|
#### 二、归属提示(命令域与工具名不同构)
|
|
214
313
|
|
|
215
314
|
- \`gh workflow …\` / \`gh search …\` / \`gh variable …\` 分别归 **github_run / github_repo / github_secret**(不是独立工具)
|
|
216
315
|
- \`gh api\` 的等价物是 \`github_api\`(全端点逃生门,支持 raw/paginate/jq)
|
|
217
316
|
- 图片查看:\`github_issue\`/\`github_pr\` 的 \`view_images\`、\`github_repo\` 的 \`readme\` + image_urls(插件独有能力,gh 无对应命令)
|
|
317
|
+
- 附件上传:\`github_issue\`/\`github_pr\` 的 \`attach_images\` 映射 gh ≥2.99 原生可重复 \`--attach\` flag(支持 create/edit/comment)
|
|
218
318
|
|
|
219
319
|
#### 三、未覆盖域引导(经 github_api 调用)
|
|
220
320
|
|
|
221
321
|
| gh 域 | 用途 | 推荐 REST 端点 |
|
|
222
322
|
| :--- | :--- | :--- |
|
|
223
|
-
| \`gh label\` | 标签管理 | \`repos/{o}/{r}/labels\` |
|
|
224
|
-
| \`gh cache\` | Actions 缓存 | \`repos/{o}/{r}/actions/caches\` |
|
|
225
|
-
| \`gh gist\` | 代码片段 | \`gists\` |
|
|
226
|
-
| \`gh ruleset\` | 分支保护规则 | \`repos/{o}/{r}/rulesets\` |
|
|
227
323
|
| \`gh project\` | Projects v2 | \`graphql\`(Projects API 仅 GraphQL) |
|
|
228
324
|
| \`gh discussion\` | 讨论 | \`graphql\`(需仓库开启 discussions) |
|
|
229
325
|
| \`gh codespace\` | 云端开发环境 | \`user/codespaces\` |
|
|
@@ -233,7 +329,7 @@ const KNOWLEDGE_BASE = {
|
|
|
233
329
|
| \`gh attestation/gpg-key/ssh-key\` | 签名与密钥 | \`repos/{o}/{r}/attestations\` 等 |
|
|
234
330
|
| \`gh agent-task\` | Copilot 会话 | \`graphql\`(需 Copilot 授权) |
|
|
235
331
|
|
|
236
|
-
> 用法示例:\`github_api(endpoint: 'repos/owner/repo/
|
|
332
|
+
> 用法示例:\`github_api(endpoint: 'repos/owner/repo/pulls')\`(原有 \`repos/{o}/{r}/labels\` 端点已由原生 \`github_label\` 工具接管)`,
|
|
237
333
|
search_syntax: `### 🔍 GitHub 官方高级检索语法速查手册
|
|
238
334
|
|
|
239
335
|
在 \`github_repo\`、\`github_api\` 或搜索场景中,请严格遵守 GitHub 官方搜索语法:
|
|
@@ -286,6 +382,11 @@ export function createHelpTool(executor) {
|
|
|
286
382
|
'release',
|
|
287
383
|
'secret',
|
|
288
384
|
'skill',
|
|
385
|
+
'label',
|
|
386
|
+
'ruleset',
|
|
387
|
+
'cache',
|
|
388
|
+
'gist',
|
|
389
|
+
'status',
|
|
289
390
|
'mapping',
|
|
290
391
|
'search_syntax',
|
|
291
392
|
'troubleshooting',
|
|
@@ -77,3 +77,11 @@ export declare function downloadImages(urls: string[], run: (url: string) => Pro
|
|
|
77
77
|
}>): Promise<ImageBatchResult>;
|
|
78
78
|
/** render 的 image 内容块构造(text 块由调用方自行拼):仅双合法字段才产出块 */
|
|
79
79
|
export declare function buildImageBlocks(value: any): any[];
|
|
80
|
+
/**
|
|
81
|
+
* 校验并解析本地附件路径列表(Issue/PR --attach 专用)。
|
|
82
|
+
* - 仅接受本地绝对路径或工作区相对路径
|
|
83
|
+
* - 严格拒绝 URL(HTTP/HTTPS)
|
|
84
|
+
* - 单批限制最多 50 个文件(GitHub 限制)
|
|
85
|
+
* - 校验本地文件物理存在性
|
|
86
|
+
*/
|
|
87
|
+
export declare function validateAttachPaths(attachImages: unknown, cwd: string): string[];
|
package/lib/tools/image-utils.js
CHANGED
|
@@ -245,3 +245,37 @@ export function buildImageBlocks(value) {
|
|
|
245
245
|
}
|
|
246
246
|
return blocks;
|
|
247
247
|
}
|
|
248
|
+
/**
|
|
249
|
+
* 校验并解析本地附件路径列表(Issue/PR --attach 专用)。
|
|
250
|
+
* - 仅接受本地绝对路径或工作区相对路径
|
|
251
|
+
* - 严格拒绝 URL(HTTP/HTTPS)
|
|
252
|
+
* - 单批限制最多 50 个文件(GitHub 限制)
|
|
253
|
+
* - 校验本地文件物理存在性
|
|
254
|
+
*/
|
|
255
|
+
export function validateAttachPaths(attachImages, cwd) {
|
|
256
|
+
if (!attachImages)
|
|
257
|
+
return [];
|
|
258
|
+
if (!Array.isArray(attachImages)) {
|
|
259
|
+
throw new Error('`attach_images` 必须为本地文件路径数组(string[])。');
|
|
260
|
+
}
|
|
261
|
+
if (attachImages.length > 50) {
|
|
262
|
+
throw new Error(`单次上传附件数量超出 GitHub 限制(单批上限 50 个文件),当前提供: ${attachImages.length} 个。`);
|
|
263
|
+
}
|
|
264
|
+
const resolvedPaths = [];
|
|
265
|
+
for (const item of attachImages) {
|
|
266
|
+
if (typeof item !== 'string' || !item.trim()) {
|
|
267
|
+
throw new Error('`attach_images` 数组中的文件路径不能为空。');
|
|
268
|
+
}
|
|
269
|
+
const trimmed = item.trim();
|
|
270
|
+
// 严格禁止 URL
|
|
271
|
+
if (/^https?:\/\//i.test(trimmed)) {
|
|
272
|
+
throw new Error(`\`attach_images\` 仅支持本地文件路径上传,不支持网络 URL: "${trimmed}"。\n若需引用网络图片,请直接在正文/评论中使用 Markdown 语法 \`\`,或先将其下载至本地再上传。`);
|
|
273
|
+
}
|
|
274
|
+
const absPath = path.isAbsolute(trimmed) ? trimmed : path.resolve(cwd, trimmed);
|
|
275
|
+
if (!fs.existsSync(absPath)) {
|
|
276
|
+
throw new Error(`指定的附件文件不存在: "${absPath}"(输入路径: "${trimmed}")。`);
|
|
277
|
+
}
|
|
278
|
+
resolvedPaths.push(absPath);
|
|
279
|
+
}
|
|
280
|
+
return resolvedPaths;
|
|
281
|
+
}
|
package/lib/tools/issue.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import type { GhExecutor } from '../executor.js';
|
|
2
|
-
export { extractImageUrls as extractIssueImageUrls } from './image-utils.js';
|
|
2
|
+
export { extractImageUrls as extractIssueImageUrls, validateAttachPaths } from './image-utils.js';
|
|
3
3
|
export declare function createIssueTool(executor: GhExecutor): {
|
|
4
4
|
name: string;
|
|
5
5
|
description: string;
|
|
@@ -88,6 +88,13 @@ export declare function createIssueTool(executor: GhExecutor): {
|
|
|
88
88
|
};
|
|
89
89
|
description: string;
|
|
90
90
|
};
|
|
91
|
+
attach_images: {
|
|
92
|
+
type: string;
|
|
93
|
+
items: {
|
|
94
|
+
type: string;
|
|
95
|
+
};
|
|
96
|
+
description: string;
|
|
97
|
+
};
|
|
91
98
|
};
|
|
92
99
|
required: string[];
|
|
93
100
|
};
|
package/lib/tools/issue.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
|
+
import { compareGhVersions } from '../executor.js';
|
|
1
2
|
import { renderIssueMarkdown } from '../renderers/markdown.js';
|
|
2
|
-
import { extractImageUrls, downloadImages, buildImageBlocks, filterRequestedUrls } from './image-utils.js';
|
|
3
|
+
import { extractImageUrls, downloadImages, buildImageBlocks, filterRequestedUrls, validateAttachPaths } from './image-utils.js';
|
|
3
4
|
function normalizeOutput(data) {
|
|
4
5
|
if (data && typeof data === 'object' && !Array.isArray(data)) {
|
|
5
6
|
return data;
|
|
@@ -9,8 +10,8 @@ function normalizeOutput(data) {
|
|
|
9
10
|
}
|
|
10
11
|
return { result: data !== undefined && data !== null ? data : '' };
|
|
11
12
|
}
|
|
12
|
-
//
|
|
13
|
-
export { extractImageUrls as extractIssueImageUrls } from './image-utils.js';
|
|
13
|
+
// 共享图片工具链的同名再导出:保持路径与单测稳定
|
|
14
|
+
export { extractImageUrls as extractIssueImageUrls, validateAttachPaths } from './image-utils.js';
|
|
14
15
|
export function createIssueTool(executor) {
|
|
15
16
|
return {
|
|
16
17
|
name: 'github_issue',
|
|
@@ -90,6 +91,11 @@ export function createIssueTool(executor) {
|
|
|
90
91
|
items: { type: 'string' },
|
|
91
92
|
description: 'view_images 可选:仅下载指定 URL 的图片(从 view 返回的 images 清单中选取,不在清单内的会被拒绝)。留空下载全部(正文+评论)。',
|
|
92
93
|
},
|
|
94
|
+
attach_images: {
|
|
95
|
+
type: 'array',
|
|
96
|
+
items: { type: 'string' },
|
|
97
|
+
description: '本地图片/附件路径列表(create, edit, comment 动作使用,需 gh ≥ 2.99;禁止 URL,单批上限 50 个)。',
|
|
98
|
+
},
|
|
93
99
|
},
|
|
94
100
|
required: ['action'],
|
|
95
101
|
},
|
|
@@ -116,6 +122,18 @@ export function createIssueTool(executor) {
|
|
|
116
122
|
}
|
|
117
123
|
const repoArgs = ['-R', repoTarget];
|
|
118
124
|
const runOpts = { cwd, sessionId };
|
|
125
|
+
const appendAttachFlags = async (cmd, attachImages) => {
|
|
126
|
+
const attachPaths = validateAttachPaths(attachImages, cwd);
|
|
127
|
+
if (attachPaths.length > 0) {
|
|
128
|
+
const version = await executor.getGhVersion();
|
|
129
|
+
if (version && compareGhVersions(version, '2.99.0') < 0) {
|
|
130
|
+
throw new Error(`附件上传能力 (\`--attach\`) 需要 GitHub CLI 版本 ≥ 2.99.0,当前版本为: ${version}。\n请升级 gh(Windows: \`winget upgrade GitHub.cli\`,macOS: \`brew upgrade gh\`)后再试。`);
|
|
131
|
+
}
|
|
132
|
+
for (const p of attachPaths) {
|
|
133
|
+
cmd.push('--attach', p);
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
};
|
|
119
137
|
switch (args.action) {
|
|
120
138
|
case 'list': {
|
|
121
139
|
const listCmd = ['issue', 'list', ...repoArgs, '--json', 'number,title,state,author,labels,updatedAt,url'];
|
|
@@ -216,6 +234,7 @@ export function createIssueTool(executor) {
|
|
|
216
234
|
if (args.assignees && Array.isArray(args.assignees) && args.assignees.length > 0) {
|
|
217
235
|
cmd.push('--assignee', args.assignees.join(','));
|
|
218
236
|
}
|
|
237
|
+
await appendAttachFlags(cmd, args.attach_images);
|
|
219
238
|
const res = await executor.run(cmd, runOpts);
|
|
220
239
|
if (!res.ok)
|
|
221
240
|
throw new Error(res.error);
|
|
@@ -237,6 +256,7 @@ export function createIssueTool(executor) {
|
|
|
237
256
|
for (const l of args.remove_labels)
|
|
238
257
|
editCmd.push('--remove-label', l);
|
|
239
258
|
}
|
|
259
|
+
await appendAttachFlags(editCmd, args.attach_images);
|
|
240
260
|
const res = await executor.run(editCmd, runOpts);
|
|
241
261
|
if (!res.ok)
|
|
242
262
|
throw new Error(res.error);
|
|
@@ -257,7 +277,9 @@ export function createIssueTool(executor) {
|
|
|
257
277
|
case 'comment': {
|
|
258
278
|
if (!args.issue_number || !args.body)
|
|
259
279
|
throw new Error('comment 必须提供 issue_number 和 body');
|
|
260
|
-
const
|
|
280
|
+
const commentCmd = ['issue', 'comment', String(args.issue_number), ...repoArgs, '--body', args.body];
|
|
281
|
+
await appendAttachFlags(commentCmd, args.attach_images);
|
|
282
|
+
const res = await executor.run(commentCmd, runOpts);
|
|
261
283
|
if (!res.ok)
|
|
262
284
|
throw new Error(res.error);
|
|
263
285
|
return { message: '评论发布成功', output: res.rawOutput };
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
import type { GhExecutor } from '../executor.js';
|
|
2
|
+
/**
|
|
3
|
+
* 校验并归一化 6 位十六进制颜色(去除前导 #)
|
|
4
|
+
*/
|
|
5
|
+
export declare function normalizeHexColor(rawColor?: string): string | undefined;
|
|
6
|
+
export declare function createLabelTool(executor: GhExecutor): {
|
|
7
|
+
name: string;
|
|
8
|
+
description: string;
|
|
9
|
+
parameters: {
|
|
10
|
+
type: string;
|
|
11
|
+
properties: {
|
|
12
|
+
action: {
|
|
13
|
+
type: string;
|
|
14
|
+
enum: string[];
|
|
15
|
+
description: string;
|
|
16
|
+
};
|
|
17
|
+
repo: {
|
|
18
|
+
type: string;
|
|
19
|
+
description: string;
|
|
20
|
+
};
|
|
21
|
+
name: {
|
|
22
|
+
type: string;
|
|
23
|
+
description: string;
|
|
24
|
+
};
|
|
25
|
+
new_name: {
|
|
26
|
+
type: string;
|
|
27
|
+
description: string;
|
|
28
|
+
};
|
|
29
|
+
color: {
|
|
30
|
+
type: string;
|
|
31
|
+
description: string;
|
|
32
|
+
};
|
|
33
|
+
description: {
|
|
34
|
+
type: string;
|
|
35
|
+
description: string;
|
|
36
|
+
};
|
|
37
|
+
force: {
|
|
38
|
+
type: string;
|
|
39
|
+
description: string;
|
|
40
|
+
};
|
|
41
|
+
source_repo: {
|
|
42
|
+
type: string;
|
|
43
|
+
description: string;
|
|
44
|
+
};
|
|
45
|
+
limit: {
|
|
46
|
+
type: string;
|
|
47
|
+
description: string;
|
|
48
|
+
};
|
|
49
|
+
search: {
|
|
50
|
+
type: string;
|
|
51
|
+
description: string;
|
|
52
|
+
};
|
|
53
|
+
sort: {
|
|
54
|
+
type: string;
|
|
55
|
+
enum: string[];
|
|
56
|
+
description: string;
|
|
57
|
+
};
|
|
58
|
+
order: {
|
|
59
|
+
type: string;
|
|
60
|
+
enum: string[];
|
|
61
|
+
description: string;
|
|
62
|
+
};
|
|
63
|
+
confirm: {
|
|
64
|
+
type: string;
|
|
65
|
+
description: string;
|
|
66
|
+
};
|
|
67
|
+
};
|
|
68
|
+
required: string[];
|
|
69
|
+
};
|
|
70
|
+
output: {
|
|
71
|
+
schema: {
|
|
72
|
+
type: string;
|
|
73
|
+
};
|
|
74
|
+
render(args: any, value: any): {
|
|
75
|
+
type: string;
|
|
76
|
+
text: string;
|
|
77
|
+
}[];
|
|
78
|
+
};
|
|
79
|
+
execute(args: any, execContext: any): Promise<Record<string, any>>;
|
|
80
|
+
};
|