dsh-github-flow 0.4.0 → 0.6.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.
Files changed (82) hide show
  1. package/README.en.md +91 -12
  2. package/README.md +91 -12
  3. package/docs/UPGRADE-NOTES.md +9 -12
  4. package/docs/architecture.md +63 -29
  5. package/docs/images/1.png +0 -0
  6. package/lib/api/routes-actions.js +268 -5
  7. package/lib/api/routes-overview.js +147 -29
  8. package/lib/cache.d.ts +77 -4
  9. package/lib/cache.js +114 -7
  10. package/lib/client.d.ts +9 -4
  11. package/lib/client.js +5293 -942
  12. package/lib/commands/gh.d.ts +6 -1
  13. package/lib/commands/gh.js +144 -71
  14. package/lib/executor/error-classifier.d.ts +55 -0
  15. package/lib/executor/error-classifier.js +199 -0
  16. package/lib/executor/git-parser.d.ts +157 -1
  17. package/lib/executor/git-parser.js +513 -4
  18. package/lib/executor.d.ts +147 -4
  19. package/lib/executor.js +866 -77
  20. package/lib/main.js +28 -4
  21. package/lib/renderers/agent-task.d.ts +16 -0
  22. package/lib/renderers/agent-task.js +107 -0
  23. package/lib/renderers/codespace.d.ts +16 -0
  24. package/lib/renderers/codespace.js +103 -0
  25. package/lib/renderers/discussion.d.ts +16 -0
  26. package/lib/renderers/discussion.js +101 -0
  27. package/lib/renderers/markdown.d.ts +4 -0
  28. package/lib/renderers/markdown.js +4 -0
  29. package/lib/renderers/project.d.ts +20 -0
  30. package/lib/renderers/project.js +128 -0
  31. package/lib/renderers/skill.d.ts +30 -0
  32. package/lib/renderers/skill.js +180 -0
  33. package/lib/renderers/status.d.ts +5 -0
  34. package/lib/renderers/status.js +52 -0
  35. package/lib/telemetry.d.ts +70 -0
  36. package/lib/telemetry.js +161 -0
  37. package/lib/tools/agent-task.d.ts +54 -0
  38. package/lib/tools/agent-task.js +135 -0
  39. package/lib/tools/codespace.d.ts +51 -0
  40. package/lib/tools/codespace.js +154 -0
  41. package/lib/tools/discussion.d.ts +70 -0
  42. package/lib/tools/discussion.js +195 -0
  43. package/lib/tools/help.js +129 -14
  44. package/lib/tools/image-utils.d.ts +27 -0
  45. package/lib/tools/image-utils.js +73 -3
  46. package/lib/tools/project.d.ts +80 -0
  47. package/lib/tools/project.js +241 -0
  48. package/lib/tools/repo.js +13 -1
  49. package/lib/tools/skill.d.ts +24 -1
  50. package/lib/tools/skill.js +115 -5
  51. package/lib/tools/status.d.ts +3 -2
  52. package/lib/tools/status.js +9 -4
  53. package/lib/types.d.ts +336 -0
  54. package/lib/ui/commit-modal.js +144 -14
  55. package/lib/ui/help-modal.d.ts +21 -0
  56. package/lib/ui/help-modal.js +300 -0
  57. package/lib/ui/issue-modal.d.ts +14 -0
  58. package/lib/ui/issue-modal.js +366 -0
  59. package/lib/ui/markdown-lite.d.ts +66 -0
  60. package/lib/ui/markdown-lite.js +312 -0
  61. package/lib/ui/network-banner.d.ts +12 -0
  62. package/lib/ui/network-banner.js +35 -0
  63. package/lib/ui/pr-modal.d.ts +17 -0
  64. package/lib/ui/pr-modal.js +516 -0
  65. package/lib/ui/repo-badges.d.ts +43 -0
  66. package/lib/ui/repo-badges.js +82 -0
  67. package/lib/ui/repo-selector.d.ts +14 -0
  68. package/lib/ui/repo-selector.js +112 -0
  69. package/lib/ui/storage.d.ts +56 -0
  70. package/lib/ui/storage.js +163 -0
  71. package/lib/ui/styles.d.ts +1 -1
  72. package/lib/ui/styles.js +1219 -0
  73. package/lib/ui/sync-settings-modal.js +1 -1
  74. package/lib/ui/upgrade-card.js +39 -14
  75. package/lib/ui/utils.d.ts +35 -0
  76. package/lib/ui/utils.js +172 -0
  77. package/lib/ui/workspace-panel.js +198 -31
  78. package/lib/upgrade-manager.d.ts +5 -1
  79. package/lib/upgrade-manager.js +24 -1
  80. package/lib/version.d.ts +1 -1
  81. package/lib/version.js +1 -1
  82. package/package.json +2 -2
@@ -0,0 +1,241 @@
1
+ import { renderProjectMarkdown } 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
+ /**
12
+ * 检测错误信息是否属于 Scope 权限不足,并返回标准自愈对象
13
+ */
14
+ export function checkScopeRequired(errorMsg, scope, command, tool) {
15
+ const lower = (errorMsg || '').toLowerCase();
16
+ if (lower.includes('missing required scopes') || lower.includes('needs the') || lower.includes('scope')) {
17
+ return {
18
+ status: 'SCOPE_REQUIRED',
19
+ scope,
20
+ command,
21
+ tool,
22
+ };
23
+ }
24
+ return null;
25
+ }
26
+ export function createProjectTool(executor) {
27
+ return {
28
+ name: 'github_project',
29
+ description: '管理 GitHub Projects v2 项目看板与任务规划:查看项目列表与卡片条目、关联 Issue/PR 到看板、创建草稿任务、删除看板条目。未授权时提供平台感知自愈命令。',
30
+ parameters: {
31
+ type: 'object',
32
+ properties: {
33
+ action: {
34
+ type: 'string',
35
+ enum: ['list', 'view', 'item-list', 'item-add', 'item-create', 'item-delete'],
36
+ description: 'Project 操作类型。list 查看项目列表、view 查看项目详情、item-list 查看看板条目、item-add 添加现有Issue/PR、item-create 创建草稿条目、item-delete 删除条目。',
37
+ },
38
+ owner: {
39
+ type: 'string',
40
+ description: '项目所属用户或组织(缺省为 "@me" 即当前用户)。',
41
+ },
42
+ project_number: {
43
+ type: 'number',
44
+ description: '项目数字编号(view, item-list, item-add, item-create 动作必需)。',
45
+ },
46
+ item_id: {
47
+ type: 'string',
48
+ description: '项目条目唯一标识 ID(item-delete 动作必需)。',
49
+ },
50
+ url: {
51
+ type: 'string',
52
+ description: '要添加到项目的 Issue 或 PR URL(item-add 动作必需)。',
53
+ },
54
+ title: {
55
+ type: 'string',
56
+ description: '草稿条目标题(item-create 动作必需)。',
57
+ },
58
+ body: {
59
+ type: 'string',
60
+ description: '草稿条目正文描述(item-create 动作可选)。',
61
+ },
62
+ query: {
63
+ type: 'string',
64
+ description: '过滤查询语法(item-list 动作可选,如 "is:open assignee:@me")。',
65
+ },
66
+ closed: {
67
+ type: 'boolean',
68
+ description: '是否包含已关闭的项目(list 动作使用,默认 false)。',
69
+ },
70
+ limit: {
71
+ type: 'number',
72
+ description: '返回数量上限(list 与 item-list 动作使用,默认 30)。',
73
+ },
74
+ confirm: {
75
+ type: 'boolean',
76
+ description: '危险操作确认标记(item-delete 动作必需)。未确认时输出影响评估并拦截。',
77
+ },
78
+ },
79
+ required: ['action'],
80
+ },
81
+ output: {
82
+ schema: { type: 'object' },
83
+ render(args, value) {
84
+ return [{ type: 'text', text: renderProjectMarkdown(args, value) }];
85
+ },
86
+ },
87
+ async execute(args, execContext) {
88
+ const cwd = execContext?.cwd || process.cwd();
89
+ const sessionId = execContext?.sessionId || execContext?.session?.id || process.env.DSH_SESSION_ID;
90
+ const owner = typeof args.owner === 'string' && args.owner.trim() ? args.owner.trim() : '@me';
91
+ const runOpts = { cwd, sessionId };
92
+ switch (args.action) {
93
+ case 'list': {
94
+ const limit = typeof args.limit === 'number' && args.limit > 0 ? Math.min(args.limit, 100) : 30;
95
+ const listCmd = [
96
+ 'project', 'list',
97
+ '--format', 'json',
98
+ '--owner', owner,
99
+ '-L', String(limit),
100
+ ];
101
+ if (args.closed === true)
102
+ listCmd.push('--closed');
103
+ const res = await executor.run(listCmd, runOpts);
104
+ if (!res.ok) {
105
+ const scopeFix = checkScopeRequired(res.error || '', 'read:project', 'gh auth refresh -s read:project', 'github_project');
106
+ if (scopeFix)
107
+ return scopeFix;
108
+ throw new Error(res.error);
109
+ }
110
+ return normalizeOutput(res.data || res.rawOutput);
111
+ }
112
+ case 'view': {
113
+ if (!args.project_number || typeof args.project_number !== 'number') {
114
+ throw new Error('view 操作必须提供 project_number(项目数字编号)');
115
+ }
116
+ const viewCmd = [
117
+ 'project', 'view', String(args.project_number),
118
+ '--format', 'json',
119
+ '--owner', owner,
120
+ ];
121
+ const res = await executor.run(viewCmd, runOpts);
122
+ if (!res.ok) {
123
+ const scopeFix = checkScopeRequired(res.error || '', 'read:project', 'gh auth refresh -s read:project', 'github_project');
124
+ if (scopeFix)
125
+ return scopeFix;
126
+ throw new Error(res.error);
127
+ }
128
+ return normalizeOutput(res.data || res.rawOutput);
129
+ }
130
+ case 'item-list': {
131
+ if (!args.project_number || typeof args.project_number !== 'number') {
132
+ throw new Error('item-list 操作必须提供 project_number(项目数字编号)');
133
+ }
134
+ const limit = typeof args.limit === 'number' && args.limit > 0 ? Math.min(args.limit, 100) : 30;
135
+ const itemCmd = [
136
+ 'project', 'item-list', String(args.project_number),
137
+ '--format', 'json',
138
+ '--owner', owner,
139
+ '-L', String(limit),
140
+ ];
141
+ if (args.query)
142
+ itemCmd.push('--query', String(args.query).trim());
143
+ const res = await executor.run(itemCmd, runOpts);
144
+ if (!res.ok) {
145
+ const scopeFix = checkScopeRequired(res.error || '', 'read:project', 'gh auth refresh -s read:project', 'github_project');
146
+ if (scopeFix)
147
+ return scopeFix;
148
+ throw new Error(res.error);
149
+ }
150
+ return normalizeOutput(res.data || res.rawOutput);
151
+ }
152
+ case 'item-add': {
153
+ if (!args.project_number || typeof args.project_number !== 'number') {
154
+ throw new Error('item-add 操作必须提供 project_number(项目数字编号)');
155
+ }
156
+ if (!args.url || typeof args.url !== 'string' || !args.url.trim()) {
157
+ throw new Error('item-add 操作必须提供 url(Issue 或 PR 的完整链接)');
158
+ }
159
+ const addCmd = [
160
+ 'project', 'item-add', String(args.project_number),
161
+ '--owner', owner,
162
+ '--url', args.url.trim(),
163
+ '--format', 'json',
164
+ ];
165
+ const res = await executor.run(addCmd, runOpts);
166
+ if (!res.ok) {
167
+ const scopeFix = checkScopeRequired(res.error || '', 'project', 'gh auth refresh -s project', 'github_project');
168
+ if (scopeFix)
169
+ return scopeFix;
170
+ throw new Error(res.error);
171
+ }
172
+ return {
173
+ message: `已成功将条目添加到项目 #${args.project_number}`,
174
+ output: res.rawOutput || 'Item added',
175
+ };
176
+ }
177
+ case 'item-create': {
178
+ if (!args.project_number || typeof args.project_number !== 'number') {
179
+ throw new Error('item-create 操作必须提供 project_number(项目数字编号)');
180
+ }
181
+ if (!args.title || typeof args.title !== 'string' || !args.title.trim()) {
182
+ throw new Error('item-create 操作必须提供 title(草稿条目标题)');
183
+ }
184
+ const createCmd = [
185
+ 'project', 'item-create', String(args.project_number),
186
+ '--owner', owner,
187
+ '--title', args.title.trim(),
188
+ '--format', 'json',
189
+ ];
190
+ if (args.body)
191
+ createCmd.push('--body', String(args.body).trim());
192
+ const res = await executor.run(createCmd, runOpts);
193
+ if (!res.ok) {
194
+ const scopeFix = checkScopeRequired(res.error || '', 'project', 'gh auth refresh -s project', 'github_project');
195
+ if (scopeFix)
196
+ return scopeFix;
197
+ throw new Error(res.error);
198
+ }
199
+ return {
200
+ message: `已成功在项目 #${args.project_number} 中创建条目「${args.title.trim()}」`,
201
+ output: res.rawOutput || 'Draft item created',
202
+ };
203
+ }
204
+ case 'item-delete': {
205
+ if (!args.item_id || typeof args.item_id !== 'string' || !args.item_id.trim()) {
206
+ throw new Error('item-delete 操作必须提供 item_id(条目唯一 ID)');
207
+ }
208
+ const cleanItemId = args.item_id.trim();
209
+ // 确认防线拦截
210
+ if (args.confirm !== true) {
211
+ return {
212
+ status: 'AWAITING_CONFIRMATION',
213
+ warning: `⚠️ 删除确认拦截:你即将从看板中删除条目(ID: ${cleanItemId})。`,
214
+ impact: '该操作将从项目看板中物理移除该条目(若是关联的 Issue/PR,实体不会被删除,仅解除看板关联)。',
215
+ requiredAction: '如确认删除,请在参数中附加 `confirm: true` 重新调用。',
216
+ };
217
+ }
218
+ const deleteCmd = [
219
+ 'project', 'item-delete',
220
+ '--owner', owner,
221
+ '--id', cleanItemId,
222
+ '--format', 'json',
223
+ ];
224
+ const res = await executor.run(deleteCmd, runOpts);
225
+ if (!res.ok) {
226
+ const scopeFix = checkScopeRequired(res.error || '', 'project', 'gh auth refresh -s project', 'github_project');
227
+ if (scopeFix)
228
+ return scopeFix;
229
+ throw new Error(res.error);
230
+ }
231
+ return {
232
+ message: `条目「${cleanItemId}」已从项目看板中成功删除`,
233
+ output: res.rawOutput || 'Item deleted',
234
+ };
235
+ }
236
+ default:
237
+ throw new Error(`未知的 Project 操作: ${args.action}`);
238
+ }
239
+ },
240
+ };
241
+ }
package/lib/tools/repo.js CHANGED
@@ -239,7 +239,19 @@ export function createRepoTool(executor) {
239
239
  if (content.includes('\0')) {
240
240
  throw new Error(`「${args.path}」是二进制文件(检测到 NUL 字节),不适合文本读取。请改用 github_api(endpoint: 'repos/${owner}/${repoName}/contents/${args.path.split('/').map(encodeURIComponent).join('/')}', raw: true, binary 场景自行落盘) 获取字节流。`);
241
241
  }
242
- return { path: args.path, ref: args.ref, content, size: content.length, ...(res.degraded ? { degraded: res.degraded, degradedReason: res.degradedReason } : {}) };
242
+ const resultObj = {
243
+ path: args.path,
244
+ content,
245
+ size: content.length,
246
+ };
247
+ if (args.ref) {
248
+ resultObj.ref = args.ref;
249
+ }
250
+ if (res.degraded) {
251
+ resultObj.degraded = res.degraded;
252
+ resultObj.degradedReason = res.degradedReason;
253
+ }
254
+ return resultObj;
243
255
  }
244
256
  case 'read_dir': {
245
257
  let repoTarget = args.repo;
@@ -43,6 +43,29 @@ export declare function createSkillTool(executor: GhExecutor): {
43
43
  type: string;
44
44
  description: string;
45
45
  };
46
+ directory: {
47
+ type: string;
48
+ description: string;
49
+ };
50
+ dry_run: {
51
+ type: string;
52
+ description: string;
53
+ };
54
+ fix: {
55
+ type: string;
56
+ description: string;
57
+ };
58
+ tag: {
59
+ type: string;
60
+ description: string;
61
+ };
62
+ skill_names: {
63
+ type: string;
64
+ items: {
65
+ type: string;
66
+ };
67
+ description: string;
68
+ };
46
69
  confirm: {
47
70
  type: string;
48
71
  description: string;
@@ -59,5 +82,5 @@ export declare function createSkillTool(executor: GhExecutor): {
59
82
  text: string;
60
83
  }[];
61
84
  };
62
- execute(args: any, execContext: any): Promise<Record<string, any>>;
85
+ execute(args: any, execContext: any): Promise<Record<string, any> | import("../renderers/skill.js").SkillPublishReport>;
63
86
  };
@@ -1,4 +1,6 @@
1
- import { renderSkillMarkdown } from '../renderers/markdown.js';
1
+ import path from 'node:path';
2
+ import fs from 'node:fs';
3
+ import { renderSkillMarkdown, parseSkillPublishOutput } from '../renderers/markdown.js';
2
4
  function normalizeOutput(data) {
3
5
  if (data && typeof data === 'object' && !Array.isArray(data)) {
4
6
  return data;
@@ -11,14 +13,14 @@ function normalizeOutput(data) {
11
13
  export function createSkillTool(executor) {
12
14
  return {
13
15
  name: 'github_skill',
14
- description: '通过 GitHub CLI 官方生态发现与管理 Agent Skills:跨仓库检索开源 Skills、预览 SKILL.md 内容、安装到本地 Agent 宿主(Claude Code / Cursor / Copilot 等)、查看已安装列表。install 会写入本地磁盘,需 confirm: true。基于 gh CLI preview 特性(需 gh >= 2.9x)。',
16
+ description: '通过 GitHub CLI 官方生态发现与管理 Agent Skills:跨仓库检索开源 Skills、预览 SKILL.md 内容、安装到本地 Agent 宿主(Claude Code / Cursor / Copilot 等)、查看已安装列表、合规自检与安全发布(publish)以及检查与同步更新(update)。install 会写入磁盘需 confirm;publish 默认 --dry-run 自检,正式发布需 tag 与 confirm;update 默认 --dry-run 检查。基于 gh CLI preview 特性(需 gh >= 2.9x)。',
15
17
  parameters: {
16
18
  type: 'object',
17
19
  properties: {
18
20
  action: {
19
21
  type: 'string',
20
- enum: ['search', 'preview', 'install', 'list'],
21
- description: 'Skill 操作类型。',
22
+ enum: ['search', 'preview', 'install', 'list', 'publish', 'update'],
23
+ description: 'Skill 操作类型。search 检索、preview 预览、install 安装、list 查看已安装、publish 校验/发布、update 检查与同步更新。',
22
24
  },
23
25
  query: {
24
26
  type: 'string',
@@ -53,9 +55,30 @@ export function createSkillTool(executor) {
53
55
  type: 'number',
54
56
  description: '搜索返回数量上限(search 动作使用,默认 15)。',
55
57
  },
58
+ directory: {
59
+ type: 'string',
60
+ description: '待检查或发布的技能目录(publish 动作使用,缺省为当前工作区根目录)。',
61
+ },
62
+ dry_run: {
63
+ type: 'boolean',
64
+ description: '是否仅进行合规性验证而不发布(publish 动作使用,默认 true,零副作用安全验证模式)。',
65
+ },
66
+ fix: {
67
+ type: 'boolean',
68
+ description: '是否自动修复问题(如剥离残留的安装元数据,publish 动作使用,默认 false)。',
69
+ },
70
+ tag: {
71
+ type: 'string',
72
+ description: '发布的版本 Tag(如 "v1.0.0"),正式发布(dry_run: false)时必填(防交互式挂起)。',
73
+ },
74
+ skill_names: {
75
+ type: 'array',
76
+ items: { type: 'string' },
77
+ description: '要更新的特定 Skill 名称列表(update 动作可选)。',
78
+ },
56
79
  confirm: {
57
80
  type: 'boolean',
58
- description: '安装操作确认标志。install 会向本地磁盘写入文件,未传 true 时仅输出将要执行的安装预览。',
81
+ description: '高危操作确认标志。install 写入本地磁盘、publish 正式创建 Release 时必需。未传 true 时输出影响评估并拦截。',
59
82
  },
60
83
  },
61
84
  required: ['action'],
@@ -138,6 +161,93 @@ export function createSkillTool(executor) {
138
161
  throw new Error(res.error);
139
162
  return normalizeOutput(res.data || res.rawOutput);
140
163
  }
164
+ case 'publish': {
165
+ const isFix = args.fix === true;
166
+ const isDryRun = args.dry_run !== false && !isFix;
167
+ const targetDir = typeof args.directory === 'string' && args.directory.trim() ? args.directory.trim() : undefined;
168
+ // 1. 目录存在性前置安全校验
169
+ if (targetDir) {
170
+ const resolvedPath = path.resolve(cwd, targetDir);
171
+ if (!fs.existsSync(resolvedPath)) {
172
+ throw new Error(`指定的技能目录不存在: ${targetDir}`);
173
+ }
174
+ }
175
+ // 2. 正式发布分支(dry_run 为 false 且非 fix 模式)
176
+ if (!isDryRun && !isFix) {
177
+ // 防线 1: 必须显式指定版本 tag,防止 CLI 弹出交互式向导导致子进程永久挂起
178
+ if (!args.tag || typeof args.tag !== 'string' || !args.tag.trim()) {
179
+ throw new Error('正式发布 Skill(dry_run: false)必须提供版本 tag(如 "v1.0.0"),以防止 CLI 尝试进入交互式向导导致进程挂起');
180
+ }
181
+ const cleanTag = args.tag.trim();
182
+ // 防线 2: 破坏性发布二级确认拦截(创建 Release 与打 Tag 属于公开制品发布)
183
+ if (args.confirm !== true) {
184
+ return {
185
+ status: 'AWAITING_CONFIRMATION',
186
+ warning: `⚠️ 发布确认拦截:你即将正式发布 Agent Skill 并创建 GitHub Release(Tag: ${cleanTag})。`,
187
+ impact: `该操作将在仓库中打上「agent-skills」Topic、创建 Git Tag「${cleanTag}」并发布正式 GitHub Release,向所有用户公开提供该版本。`,
188
+ requiredAction: `如确认发布,请核实版本号并在参数中附加 \`confirm: true\` 重新调用。`,
189
+ };
190
+ }
191
+ const publishCmd = ['skill', 'publish'];
192
+ if (targetDir)
193
+ publishCmd.push(targetDir);
194
+ publishCmd.push('--tag', cleanTag);
195
+ const res = await executor.run(publishCmd, { ...runOpts, rawText: true, timeoutMs: 120_000 });
196
+ if (!res.ok) {
197
+ if (res.rawOutput) {
198
+ return parseSkillPublishOutput(res.rawOutput, res.error || '', false, 'publish', targetDir, cleanTag);
199
+ }
200
+ throw new Error(res.error || '发布 Skill 失败');
201
+ }
202
+ return parseSkillPublishOutput(res.rawOutput || '', '', true, 'publish', targetDir, cleanTag);
203
+ }
204
+ // 3. 自动剥离安装元数据分支 (--fix)
205
+ if (isFix) {
206
+ const fixCmd = ['skill', 'publish'];
207
+ if (targetDir)
208
+ fixCmd.push(targetDir);
209
+ fixCmd.push('--fix');
210
+ const res = await executor.run(fixCmd, { ...runOpts, rawText: true, timeoutMs: 60_000 });
211
+ const output = res.rawOutput || (res.ok ? '' : res.error || '');
212
+ return parseSkillPublishOutput(output, res.error || '', res.ok, 'fix', targetDir);
213
+ }
214
+ // 4. 默认只读自检分支 (--dry-run)
215
+ const dryRunCmd = ['skill', 'publish'];
216
+ if (targetDir)
217
+ dryRunCmd.push(targetDir);
218
+ dryRunCmd.push('--dry-run');
219
+ const res = await executor.run(dryRunCmd, { ...runOpts, rawText: true, timeoutMs: 60_000 });
220
+ const combinedOutput = res.rawOutput || '';
221
+ const stderrText = res.error || '';
222
+ return parseSkillPublishOutput(combinedOutput, stderrText, res.ok, 'dry-run', targetDir);
223
+ }
224
+ case 'update': {
225
+ const isDryRun = args.dry_run !== false;
226
+ const updateCmd = ['skill', 'update'];
227
+ if (isDryRun)
228
+ updateCmd.push('--dry-run');
229
+ if (args.all === true)
230
+ updateCmd.push('--all');
231
+ if (Array.isArray(args.skill_names) && args.skill_names.length > 0) {
232
+ args.skill_names.forEach((name) => {
233
+ if (name && typeof name === 'string' && name.trim()) {
234
+ updateCmd.push(name.trim());
235
+ }
236
+ });
237
+ }
238
+ else if (args.skill_name && typeof args.skill_name === 'string') {
239
+ updateCmd.push(args.skill_name.trim());
240
+ }
241
+ const res = await executor.run(updateCmd, { ...runOpts, rawText: true, timeoutMs: 90_000 });
242
+ if (!res.ok)
243
+ throw new Error(res.error);
244
+ return {
245
+ mode: isDryRun ? 'dry-run' : 'update',
246
+ action: 'update',
247
+ message: isDryRun ? 'Agent Skills 更新检查完成(Dry-run 只读模式)' : 'Agent Skills 已完成更新同步',
248
+ output: res.rawOutput || 'All skills up to date.',
249
+ };
250
+ }
141
251
  default:
142
252
  throw new Error(`未知的 Skill 操作: ${args.action}`);
143
253
  }
@@ -1,5 +1,6 @@
1
1
  import type { GhExecutor } from '../executor.js';
2
- export declare function createStatusTool(executor: GhExecutor): {
2
+ import { TelemetryCollector } from '../telemetry.js';
3
+ export declare function createStatusTool(executor: GhExecutor, telemetryCollector?: TelemetryCollector): {
3
4
  name: string;
4
5
  description: string;
5
6
  parameters: {
@@ -32,7 +33,7 @@ export declare function createStatusTool(executor: GhExecutor): {
32
33
  text: string;
33
34
  }[];
34
35
  };
35
- execute(args: any, execContext: any): Promise<{
36
+ execute(args: any, execContext: any): Promise<import("../telemetry.js").TelemetrySummary | {
36
37
  parsed: import("../renderers/status.js").ParsedStatusReport;
37
38
  rawText: string;
38
39
  }>;
@@ -1,15 +1,16 @@
1
1
  import { renderStatusMarkdown, parseStatusOutput } from '../renderers/markdown.js';
2
- export function createStatusTool(executor) {
2
+ import { defaultTelemetryCollector } from '../telemetry.js';
3
+ export function createStatusTool(executor, telemetryCollector) {
3
4
  return {
4
5
  name: 'github_status',
5
- description: '聚合展示当前登录用户的跨仓库全局待办与动态大盘:包括指派给我的 Issues、指派给我的 PR、等待我评审的 PR、近期 @提及 以及订阅仓库的最新动态。无需指定单仓,账号维度一站式概览。',
6
+ description: '聚合展示当前登录用户的跨仓库全局待办与动态大盘(view),或审计当前工具链运行时健康度、API 降级率与自愈建议(health)。无需指定单仓,账号与系统维度一站式概览。',
6
7
  parameters: {
7
8
  type: 'object',
8
9
  properties: {
9
10
  action: {
10
11
  type: 'string',
11
- enum: ['view'],
12
- description: '操作类型,默认为 view(查看全局待办看板)。',
12
+ enum: ['view', 'health'],
13
+ description: '操作类型。view 查看个人全局待办与动态看板(默认)、health 查看工具链健康度与 API 降级率审计报表。',
13
14
  },
14
15
  org: {
15
16
  type: 'string',
@@ -29,6 +30,10 @@ export function createStatusTool(executor) {
29
30
  },
30
31
  },
31
32
  async execute(args, execContext) {
33
+ if (args?.action === 'health') {
34
+ const collector = telemetryCollector || defaultTelemetryCollector;
35
+ return collector.getSummary();
36
+ }
32
37
  const cwd = execContext?.cwd || process.cwd();
33
38
  const sessionId = execContext?.sessionId || execContext?.session?.id || process.env.DSH_SESSION_ID;
34
39
  const statusCmd = ['status'];