dsh-github-flow 0.2.1 → 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 +234 -130
- package/README.md +217 -112
- package/docs/UPGRADE-NOTES.md +15 -9
- package/docs/architecture.md +120 -108
- package/docs/images/2.png +0 -0
- package/docs/npm-publish-guide.md +12 -1
- package/lib/api/routes-actions.js +117 -0
- package/lib/api/routes-overview.js +198 -3
- package/lib/cache.d.ts +4 -0
- package/lib/cache.js +14 -0
- package/lib/client.d.ts +13 -1
- package/lib/client.js +3162 -2162
- 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 +131 -17
- package/lib/executor.js +411 -223
- package/lib/git-watcher.d.ts +36 -0
- package/lib/git-watcher.js +91 -0
- package/lib/main.js +11 -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 -26
- package/lib/renderers/markdown.js +15 -517
- 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 +233 -11
- package/lib/tools/image-utils.d.ts +8 -0
- package/lib/tools/image-utils.js +34 -0
- package/lib/tools/issue.d.ts +17 -1
- package/lib/tools/issue.js +114 -8
- package/lib/tools/label.d.ts +80 -0
- package/lib/tools/label.js +237 -0
- package/lib/tools/pr.d.ts +12 -0
- package/lib/tools/pr.js +96 -4
- package/lib/tools/repo.d.ts +8 -0
- package/lib/tools/repo.js +127 -3
- package/lib/tools/ruleset.d.ts +80 -0
- package/lib/tools/ruleset.js +177 -0
- package/lib/tools/run.d.ts +8 -0
- package/lib/tools/run.js +104 -3
- package/lib/tools/secret.d.ts +5 -0
- package/lib/tools/secret.js +108 -23
- package/lib/tools/status.d.ts +39 -0
- package/lib/tools/status.js +53 -0
- package/lib/types.d.ts +112 -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,64 @@
|
|
|
1
|
+
import type { GhExecutor } from '../executor.js';
|
|
2
|
+
export declare function createCacheTool(executor: GhExecutor): {
|
|
3
|
+
name: string;
|
|
4
|
+
description: string;
|
|
5
|
+
parameters: {
|
|
6
|
+
type: string;
|
|
7
|
+
properties: {
|
|
8
|
+
action: {
|
|
9
|
+
type: string;
|
|
10
|
+
enum: string[];
|
|
11
|
+
description: string;
|
|
12
|
+
};
|
|
13
|
+
repo: {
|
|
14
|
+
type: string;
|
|
15
|
+
description: string;
|
|
16
|
+
};
|
|
17
|
+
cache_id: {
|
|
18
|
+
type: string;
|
|
19
|
+
description: string;
|
|
20
|
+
};
|
|
21
|
+
key: {
|
|
22
|
+
type: string;
|
|
23
|
+
description: string;
|
|
24
|
+
};
|
|
25
|
+
ref: {
|
|
26
|
+
type: string;
|
|
27
|
+
description: string;
|
|
28
|
+
};
|
|
29
|
+
limit: {
|
|
30
|
+
type: string;
|
|
31
|
+
description: string;
|
|
32
|
+
};
|
|
33
|
+
sort: {
|
|
34
|
+
type: string;
|
|
35
|
+
enum: string[];
|
|
36
|
+
description: string;
|
|
37
|
+
};
|
|
38
|
+
order: {
|
|
39
|
+
type: string;
|
|
40
|
+
enum: string[];
|
|
41
|
+
description: string;
|
|
42
|
+
};
|
|
43
|
+
all: {
|
|
44
|
+
type: string;
|
|
45
|
+
description: string;
|
|
46
|
+
};
|
|
47
|
+
confirm: {
|
|
48
|
+
type: string;
|
|
49
|
+
description: string;
|
|
50
|
+
};
|
|
51
|
+
};
|
|
52
|
+
required: string[];
|
|
53
|
+
};
|
|
54
|
+
output: {
|
|
55
|
+
schema: {
|
|
56
|
+
type: string;
|
|
57
|
+
};
|
|
58
|
+
render(args: any, value: any): {
|
|
59
|
+
type: string;
|
|
60
|
+
text: string;
|
|
61
|
+
}[];
|
|
62
|
+
};
|
|
63
|
+
execute(args: any, execContext: any): Promise<Record<string, any>>;
|
|
64
|
+
};
|
|
@@ -0,0 +1,186 @@
|
|
|
1
|
+
import { renderCacheMarkdown } from '../renderers/markdown.js';
|
|
2
|
+
function normalizeOutput(data) {
|
|
3
|
+
if (data && typeof data === 'object' && !Array.isArray(data)) {
|
|
4
|
+
return data;
|
|
5
|
+
}
|
|
6
|
+
if (Array.isArray(data)) {
|
|
7
|
+
return { items: data, count: data.length };
|
|
8
|
+
}
|
|
9
|
+
return { result: data !== undefined && data !== null ? data : '' };
|
|
10
|
+
}
|
|
11
|
+
export function createCacheTool(executor) {
|
|
12
|
+
return {
|
|
13
|
+
name: 'github_cache',
|
|
14
|
+
description: '管理 GitHub Actions 构建缓存 (Caches):查看缓存列表与占用容量、按 ID 或 Key 删除指定缓存、以及全量清空仓库缓存。全量清空不可逆,需 confirm: true。',
|
|
15
|
+
parameters: {
|
|
16
|
+
type: 'object',
|
|
17
|
+
properties: {
|
|
18
|
+
action: {
|
|
19
|
+
type: 'string',
|
|
20
|
+
enum: ['list', 'delete'],
|
|
21
|
+
description: 'Cache 操作类型。list 查看缓存列表与体积、delete 删除缓存。',
|
|
22
|
+
},
|
|
23
|
+
repo: {
|
|
24
|
+
type: 'string',
|
|
25
|
+
description: '目标仓库 [owner/repo],留空则识别当前工作区仓库。',
|
|
26
|
+
},
|
|
27
|
+
cache_id: {
|
|
28
|
+
type: 'string',
|
|
29
|
+
description: '缓存数字 ID(delete 动作指定单条缓存删除时使用)。',
|
|
30
|
+
},
|
|
31
|
+
key: {
|
|
32
|
+
type: 'string',
|
|
33
|
+
description: '缓存 Key 键名前缀或完整键名(list 动作过滤检索,或 delete 动作按键删除时使用)。',
|
|
34
|
+
},
|
|
35
|
+
ref: {
|
|
36
|
+
type: 'string',
|
|
37
|
+
description: '分支或 PR Git Ref(如 "refs/heads/main" 或 "refs/pull/42/merge",用于过滤或定向删除)。',
|
|
38
|
+
},
|
|
39
|
+
limit: {
|
|
40
|
+
type: 'number',
|
|
41
|
+
description: '返回缓存数量上限(list 动作使用,默认 30)。',
|
|
42
|
+
},
|
|
43
|
+
sort: {
|
|
44
|
+
type: 'string',
|
|
45
|
+
enum: ['created_at', 'last_accessed_at', 'size_in_bytes'],
|
|
46
|
+
description: '排序字段(list 动作使用,默认 last_accessed_at)。',
|
|
47
|
+
},
|
|
48
|
+
order: {
|
|
49
|
+
type: 'string',
|
|
50
|
+
enum: ['asc', 'desc'],
|
|
51
|
+
description: '排序方向(list 动作使用,默认 desc)。',
|
|
52
|
+
},
|
|
53
|
+
all: {
|
|
54
|
+
type: 'boolean',
|
|
55
|
+
description: '是否清空仓库全部缓存(delete 动作使用,高危操作,必需配合 confirm: true)。',
|
|
56
|
+
},
|
|
57
|
+
confirm: {
|
|
58
|
+
type: 'boolean',
|
|
59
|
+
description: '高危破坏性操作确认标志(delete 当 all: true 时必需)。未传 true 时输出影响评估报告并拦截。',
|
|
60
|
+
},
|
|
61
|
+
},
|
|
62
|
+
required: ['action'],
|
|
63
|
+
},
|
|
64
|
+
output: {
|
|
65
|
+
schema: { type: 'object' },
|
|
66
|
+
render(args, value) {
|
|
67
|
+
return [{ type: 'text', text: renderCacheMarkdown(args, value) }];
|
|
68
|
+
},
|
|
69
|
+
},
|
|
70
|
+
async execute(args, execContext) {
|
|
71
|
+
const cwd = execContext?.cwd || process.cwd();
|
|
72
|
+
const sessionId = execContext?.sessionId || execContext?.session?.id || process.env.DSH_SESSION_ID;
|
|
73
|
+
let repoTarget = args.repo;
|
|
74
|
+
if (!repoTarget) {
|
|
75
|
+
const meta = await executor.getRepoMetadata(cwd, sessionId);
|
|
76
|
+
if (meta?.nameWithOwner) {
|
|
77
|
+
repoTarget = meta.nameWithOwner;
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
if (!repoTarget) {
|
|
81
|
+
throw new Error('未能自动识别当前工作区关联的 GitHub 仓库。请通过 `repo` 参数显式指定目标仓库 [owner/repo](例如: "ItQianChen/dsh-github-flow"),或在当前工作区目录下关联 Git 远程仓库。');
|
|
82
|
+
}
|
|
83
|
+
const repoArgs = ['-R', repoTarget];
|
|
84
|
+
const runOpts = { cwd, sessionId };
|
|
85
|
+
switch (args.action) {
|
|
86
|
+
case 'list': {
|
|
87
|
+
const listCmd = [
|
|
88
|
+
'cache',
|
|
89
|
+
'list',
|
|
90
|
+
...repoArgs,
|
|
91
|
+
'--json',
|
|
92
|
+
'createdAt,id,key,lastAccessedAt,ref,sizeInBytes,version',
|
|
93
|
+
];
|
|
94
|
+
const limit = typeof args.limit === 'number' && args.limit > 0 ? args.limit : 30;
|
|
95
|
+
listCmd.push('-L', String(limit));
|
|
96
|
+
if (args.key) {
|
|
97
|
+
listCmd.push('-k', String(args.key));
|
|
98
|
+
}
|
|
99
|
+
if (args.ref) {
|
|
100
|
+
listCmd.push('-r', String(args.ref));
|
|
101
|
+
}
|
|
102
|
+
if (args.sort) {
|
|
103
|
+
listCmd.push('--sort', args.sort);
|
|
104
|
+
}
|
|
105
|
+
if (args.order) {
|
|
106
|
+
listCmd.push('--order', args.order);
|
|
107
|
+
}
|
|
108
|
+
const res = await executor.run(listCmd, runOpts);
|
|
109
|
+
if (!res.ok)
|
|
110
|
+
throw new Error(res.error);
|
|
111
|
+
let rawData = res.data;
|
|
112
|
+
if (!rawData && typeof res.rawOutput === 'string') {
|
|
113
|
+
try {
|
|
114
|
+
rawData = JSON.parse(res.rawOutput);
|
|
115
|
+
}
|
|
116
|
+
catch {
|
|
117
|
+
rawData = [];
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
return normalizeOutput(rawData || []);
|
|
121
|
+
}
|
|
122
|
+
case 'delete': {
|
|
123
|
+
// 1. 全量清空分支
|
|
124
|
+
if (args.all === true) {
|
|
125
|
+
if (args.confirm !== true) {
|
|
126
|
+
return {
|
|
127
|
+
status: 'AWAITING_CONFIRMATION',
|
|
128
|
+
action: 'delete',
|
|
129
|
+
all: true,
|
|
130
|
+
repo: repoTarget,
|
|
131
|
+
warning: `⚠️ 高危破坏性操作:即将清空仓库 ${repoTarget} 的所有 GitHub Actions 构建缓存`,
|
|
132
|
+
impact: `清空缓存不可逆,会导致后续流水线构建无法复用依赖与构建产物,显著增加 CI 运行耗时。`,
|
|
133
|
+
requiredAction: `如确认彻底清空,请在参数中附加 \`confirm: true\` 重新调用。示例:\`github_cache(action: 'delete', all: true, confirm: true)\``,
|
|
134
|
+
};
|
|
135
|
+
}
|
|
136
|
+
const delCmd = ['cache', 'delete', '--all', '--succeed-on-no-caches', ...repoArgs];
|
|
137
|
+
if (args.ref) {
|
|
138
|
+
delCmd.push('-r', String(args.ref));
|
|
139
|
+
}
|
|
140
|
+
const res = await executor.run(delCmd, runOpts);
|
|
141
|
+
if (!res.ok)
|
|
142
|
+
throw new Error(res.error);
|
|
143
|
+
return {
|
|
144
|
+
success: true,
|
|
145
|
+
all: true,
|
|
146
|
+
message: `🗑️ 成功清空仓库 ${repoTarget} 的 Actions 构建缓存` + (args.ref ? ` (Ref: ${args.ref})` : ''),
|
|
147
|
+
detail: res.rawOutput || '',
|
|
148
|
+
};
|
|
149
|
+
}
|
|
150
|
+
// 2. 按 cache_id 删除
|
|
151
|
+
if (args.cache_id !== undefined && args.cache_id !== null && String(args.cache_id).trim() !== '') {
|
|
152
|
+
const delCmd = ['cache', 'delete', String(args.cache_id), ...repoArgs];
|
|
153
|
+
const res = await executor.run(delCmd, runOpts);
|
|
154
|
+
if (!res.ok)
|
|
155
|
+
throw new Error(res.error);
|
|
156
|
+
return {
|
|
157
|
+
success: true,
|
|
158
|
+
cache_id: String(args.cache_id),
|
|
159
|
+
message: `🗑️ 成功删除仓库 ${repoTarget} 中 ID 为 "${args.cache_id}" 的 Actions 缓存`,
|
|
160
|
+
detail: res.rawOutput || '',
|
|
161
|
+
};
|
|
162
|
+
}
|
|
163
|
+
// 3. 按 key 删除
|
|
164
|
+
if (args.key !== undefined && args.key !== null && String(args.key).trim() !== '') {
|
|
165
|
+
const delCmd = ['cache', 'delete', String(args.key), ...repoArgs];
|
|
166
|
+
if (args.ref) {
|
|
167
|
+
delCmd.push('-r', String(args.ref));
|
|
168
|
+
}
|
|
169
|
+
const res = await executor.run(delCmd, runOpts);
|
|
170
|
+
if (!res.ok)
|
|
171
|
+
throw new Error(res.error);
|
|
172
|
+
return {
|
|
173
|
+
success: true,
|
|
174
|
+
key: String(args.key),
|
|
175
|
+
message: `🗑️ 成功删除仓库 ${repoTarget} 中 Key 为 "${args.key}" 的 Actions 缓存` + (args.ref ? ` (Ref: ${args.ref})` : ''),
|
|
176
|
+
detail: res.rawOutput || '',
|
|
177
|
+
};
|
|
178
|
+
}
|
|
179
|
+
throw new Error('删除缓存时必须指定 `cache_id`、`key` 或 `all: true`(全量删除需配合 confirm: true)。');
|
|
180
|
+
}
|
|
181
|
+
default:
|
|
182
|
+
throw new Error(`不支持的 cache 操作: "${args.action}"。可选值: list, delete。`);
|
|
183
|
+
}
|
|
184
|
+
},
|
|
185
|
+
};
|
|
186
|
+
}
|
|
@@ -0,0 +1,155 @@
|
|
|
1
|
+
import type { GhExecutor } from '../executor.js';
|
|
2
|
+
export declare function createGistTool(executor: GhExecutor): {
|
|
3
|
+
name: string;
|
|
4
|
+
description: string;
|
|
5
|
+
parameters: {
|
|
6
|
+
type: string;
|
|
7
|
+
properties: {
|
|
8
|
+
action: {
|
|
9
|
+
type: string;
|
|
10
|
+
enum: string[];
|
|
11
|
+
description: string;
|
|
12
|
+
};
|
|
13
|
+
gist_id: {
|
|
14
|
+
type: string;
|
|
15
|
+
description: string;
|
|
16
|
+
};
|
|
17
|
+
filename: {
|
|
18
|
+
type: string;
|
|
19
|
+
description: string;
|
|
20
|
+
};
|
|
21
|
+
files: {
|
|
22
|
+
type: string;
|
|
23
|
+
items: {
|
|
24
|
+
type: string;
|
|
25
|
+
};
|
|
26
|
+
description: string;
|
|
27
|
+
};
|
|
28
|
+
content: {
|
|
29
|
+
type: string;
|
|
30
|
+
description: string;
|
|
31
|
+
};
|
|
32
|
+
description: {
|
|
33
|
+
type: string;
|
|
34
|
+
description: string;
|
|
35
|
+
};
|
|
36
|
+
public: {
|
|
37
|
+
type: string;
|
|
38
|
+
description: string;
|
|
39
|
+
};
|
|
40
|
+
filter: {
|
|
41
|
+
type: string;
|
|
42
|
+
description: string;
|
|
43
|
+
};
|
|
44
|
+
limit: {
|
|
45
|
+
type: string;
|
|
46
|
+
description: string;
|
|
47
|
+
};
|
|
48
|
+
confirm: {
|
|
49
|
+
type: string;
|
|
50
|
+
description: string;
|
|
51
|
+
};
|
|
52
|
+
};
|
|
53
|
+
required: string[];
|
|
54
|
+
};
|
|
55
|
+
output: {
|
|
56
|
+
schema: {
|
|
57
|
+
type: string;
|
|
58
|
+
};
|
|
59
|
+
render(args: any, value: any): {
|
|
60
|
+
type: string;
|
|
61
|
+
text: string;
|
|
62
|
+
}[];
|
|
63
|
+
};
|
|
64
|
+
execute(args: any, execContext: any): Promise<{
|
|
65
|
+
items: import("../renderers/gist.js").ParsedGistSummary[];
|
|
66
|
+
count: number;
|
|
67
|
+
rawText: string;
|
|
68
|
+
filename?: undefined;
|
|
69
|
+
content?: undefined;
|
|
70
|
+
url?: undefined;
|
|
71
|
+
public?: undefined;
|
|
72
|
+
description?: undefined;
|
|
73
|
+
success?: undefined;
|
|
74
|
+
message?: undefined;
|
|
75
|
+
detail?: undefined;
|
|
76
|
+
status?: undefined;
|
|
77
|
+
action?: undefined;
|
|
78
|
+
gist_id?: undefined;
|
|
79
|
+
warning?: undefined;
|
|
80
|
+
impact?: undefined;
|
|
81
|
+
requiredAction?: undefined;
|
|
82
|
+
} | {
|
|
83
|
+
items?: undefined;
|
|
84
|
+
count?: undefined;
|
|
85
|
+
rawText?: undefined;
|
|
86
|
+
gist_id: string;
|
|
87
|
+
filename: any;
|
|
88
|
+
content: string;
|
|
89
|
+
url?: undefined;
|
|
90
|
+
public?: undefined;
|
|
91
|
+
description?: undefined;
|
|
92
|
+
success?: undefined;
|
|
93
|
+
message?: undefined;
|
|
94
|
+
detail?: undefined;
|
|
95
|
+
status?: undefined;
|
|
96
|
+
action?: undefined;
|
|
97
|
+
warning?: undefined;
|
|
98
|
+
impact?: undefined;
|
|
99
|
+
requiredAction?: undefined;
|
|
100
|
+
} | {
|
|
101
|
+
items?: undefined;
|
|
102
|
+
count?: undefined;
|
|
103
|
+
rawText?: undefined;
|
|
104
|
+
filename?: undefined;
|
|
105
|
+
content?: undefined;
|
|
106
|
+
success: boolean;
|
|
107
|
+
url: string;
|
|
108
|
+
gist_id: string;
|
|
109
|
+
public: boolean;
|
|
110
|
+
description: any;
|
|
111
|
+
message: string;
|
|
112
|
+
detail?: undefined;
|
|
113
|
+
status?: undefined;
|
|
114
|
+
action?: undefined;
|
|
115
|
+
warning?: undefined;
|
|
116
|
+
impact?: undefined;
|
|
117
|
+
requiredAction?: undefined;
|
|
118
|
+
} | {
|
|
119
|
+
items?: undefined;
|
|
120
|
+
count?: undefined;
|
|
121
|
+
rawText?: undefined;
|
|
122
|
+
filename?: undefined;
|
|
123
|
+
content?: undefined;
|
|
124
|
+
url?: undefined;
|
|
125
|
+
public?: undefined;
|
|
126
|
+
description?: undefined;
|
|
127
|
+
success: boolean;
|
|
128
|
+
gist_id: string;
|
|
129
|
+
message: string;
|
|
130
|
+
detail: string;
|
|
131
|
+
status?: undefined;
|
|
132
|
+
action?: undefined;
|
|
133
|
+
warning?: undefined;
|
|
134
|
+
impact?: undefined;
|
|
135
|
+
requiredAction?: undefined;
|
|
136
|
+
} | {
|
|
137
|
+
items?: undefined;
|
|
138
|
+
count?: undefined;
|
|
139
|
+
rawText?: undefined;
|
|
140
|
+
filename?: undefined;
|
|
141
|
+
content?: undefined;
|
|
142
|
+
url?: undefined;
|
|
143
|
+
public?: undefined;
|
|
144
|
+
description?: undefined;
|
|
145
|
+
success?: undefined;
|
|
146
|
+
message?: undefined;
|
|
147
|
+
detail?: undefined;
|
|
148
|
+
status: string;
|
|
149
|
+
action: string;
|
|
150
|
+
gist_id: string;
|
|
151
|
+
warning: string;
|
|
152
|
+
impact: string;
|
|
153
|
+
requiredAction: string;
|
|
154
|
+
}>;
|
|
155
|
+
};
|
|
@@ -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
|
+
}
|