dsh-github-flow 0.3.0 → 0.5.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 +287 -143
- package/README.md +270 -125
- package/docs/UPGRADE-NOTES.md +12 -9
- package/docs/architecture.md +126 -126
- package/lib/api/routes-actions.js +249 -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 +14 -1
- package/lib/client.js +4287 -2500
- package/lib/commands/gh.d.ts +6 -1
- package/lib/commands/gh.js +144 -71
- package/lib/executor/git-parser.d.ts +57 -0
- package/lib/executor/git-parser.js +255 -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 +70 -48
- package/lib/executor.js +371 -356
- package/lib/git-watcher.d.ts +36 -0
- package/lib/git-watcher.js +91 -0
- package/lib/main.js +37 -3
- package/lib/renderers/agent-task.d.ts +16 -0
- package/lib/renderers/agent-task.js +107 -0
- package/lib/renderers/cache.d.ts +8 -0
- package/lib/renderers/cache.js +66 -0
- package/lib/renderers/codespace.d.ts +16 -0
- package/lib/renderers/codespace.js +103 -0
- package/lib/renderers/common.d.ts +24 -0
- package/lib/renderers/common.js +88 -0
- package/lib/renderers/discussion.d.ts +16 -0
- package/lib/renderers/discussion.js +101 -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 +19 -32
- package/lib/renderers/markdown.js +19 -624
- package/lib/renderers/pr.d.ts +4 -0
- package/lib/renderers/pr.js +107 -0
- package/lib/renderers/project.d.ts +20 -0
- package/lib/renderers/project.js +128 -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 +34 -0
- package/lib/renderers/skill.js +241 -0
- package/lib/renderers/status.d.ts +28 -0
- package/lib/renderers/status.js +176 -0
- package/lib/telemetry.d.ts +70 -0
- package/lib/telemetry.js +161 -0
- package/lib/tools/agent-task.d.ts +54 -0
- package/lib/tools/agent-task.js +135 -0
- package/lib/tools/cache.d.ts +64 -0
- package/lib/tools/cache.js +186 -0
- package/lib/tools/codespace.d.ts +51 -0
- package/lib/tools/codespace.js +154 -0
- package/lib/tools/discussion.d.ts +70 -0
- package/lib/tools/discussion.js +195 -0
- package/lib/tools/gist.d.ts +155 -0
- package/lib/tools/gist.js +190 -0
- package/lib/tools/help.js +214 -13
- 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/project.d.ts +80 -0
- package/lib/tools/project.js +241 -0
- package/lib/tools/repo.js +13 -1
- package/lib/tools/ruleset.d.ts +80 -0
- package/lib/tools/ruleset.js +177 -0
- package/lib/tools/skill.d.ts +24 -1
- package/lib/tools/skill.js +115 -5
- package/lib/tools/status.d.ts +40 -0
- package/lib/tools/status.js +58 -0
- package/lib/types.d.ts +44 -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 +219 -0
- package/lib/ui/help-modal.d.ts +15 -0
- package/lib/ui/help-modal.js +202 -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 +35 -0
- package/lib/ui/storage.js +158 -0
- package/lib/ui/styles.d.ts +6 -0
- package/lib/ui/styles.js +1544 -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 +222 -0
- package/lib/ui/utils.d.ts +45 -0
- package/lib/ui/utils.js +226 -0
- package/lib/ui/workspace-panel.d.ts +4 -0
- package/lib/ui/workspace-panel.js +489 -0
- package/lib/upgrade-manager.d.ts +5 -1
- package/lib/upgrade-manager.js +24 -1
- package/lib/version.d.ts +1 -1
- package/lib/version.js +1 -1
- package/package.json +2 -2
package/lib/tools/skill.js
CHANGED
|
@@ -1,4 +1,6 @@
|
|
|
1
|
-
import
|
|
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
|
|
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: '
|
|
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
|
}
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import type { GhExecutor } from '../executor.js';
|
|
2
|
+
import { TelemetryCollector } from '../telemetry.js';
|
|
3
|
+
export declare function createStatusTool(executor: GhExecutor, telemetryCollector?: TelemetryCollector): {
|
|
4
|
+
name: string;
|
|
5
|
+
description: string;
|
|
6
|
+
parameters: {
|
|
7
|
+
type: string;
|
|
8
|
+
properties: {
|
|
9
|
+
action: {
|
|
10
|
+
type: string;
|
|
11
|
+
enum: string[];
|
|
12
|
+
description: string;
|
|
13
|
+
};
|
|
14
|
+
org: {
|
|
15
|
+
type: string;
|
|
16
|
+
description: string;
|
|
17
|
+
};
|
|
18
|
+
exclude: {
|
|
19
|
+
type: string;
|
|
20
|
+
items: {
|
|
21
|
+
type: string;
|
|
22
|
+
};
|
|
23
|
+
description: string;
|
|
24
|
+
};
|
|
25
|
+
};
|
|
26
|
+
};
|
|
27
|
+
output: {
|
|
28
|
+
schema: {
|
|
29
|
+
type: string;
|
|
30
|
+
};
|
|
31
|
+
render(args: any, value: any): {
|
|
32
|
+
type: string;
|
|
33
|
+
text: string;
|
|
34
|
+
}[];
|
|
35
|
+
};
|
|
36
|
+
execute(args: any, execContext: any): Promise<import("../telemetry.js").TelemetrySummary | {
|
|
37
|
+
parsed: import("../renderers/status.js").ParsedStatusReport;
|
|
38
|
+
rawText: string;
|
|
39
|
+
}>;
|
|
40
|
+
};
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
import { renderStatusMarkdown, parseStatusOutput } from '../renderers/markdown.js';
|
|
2
|
+
import { defaultTelemetryCollector } from '../telemetry.js';
|
|
3
|
+
export function createStatusTool(executor, telemetryCollector) {
|
|
4
|
+
return {
|
|
5
|
+
name: 'github_status',
|
|
6
|
+
description: '聚合展示当前登录用户的跨仓库全局待办与动态大盘(view),或审计当前工具链运行时健康度、API 降级率与自愈建议(health)。无需指定单仓,账号与系统维度一站式概览。',
|
|
7
|
+
parameters: {
|
|
8
|
+
type: 'object',
|
|
9
|
+
properties: {
|
|
10
|
+
action: {
|
|
11
|
+
type: 'string',
|
|
12
|
+
enum: ['view', 'health'],
|
|
13
|
+
description: '操作类型。view 查看个人全局待办与动态看板(默认)、health 查看工具链健康度与 API 降级率审计报表。',
|
|
14
|
+
},
|
|
15
|
+
org: {
|
|
16
|
+
type: 'string',
|
|
17
|
+
description: '组织名称(可选,限定仅汇报指定组织内部的待办与动态)。',
|
|
18
|
+
},
|
|
19
|
+
exclude: {
|
|
20
|
+
type: 'array',
|
|
21
|
+
items: { type: 'string' },
|
|
22
|
+
description: '要排除的仓库名称列表 [owner/repo](可选,支持排除噪音仓库)。',
|
|
23
|
+
},
|
|
24
|
+
},
|
|
25
|
+
},
|
|
26
|
+
output: {
|
|
27
|
+
schema: { type: 'object' },
|
|
28
|
+
render(args, value) {
|
|
29
|
+
return [{ type: 'text', text: renderStatusMarkdown(args, value) }];
|
|
30
|
+
},
|
|
31
|
+
},
|
|
32
|
+
async execute(args, execContext) {
|
|
33
|
+
if (args?.action === 'health') {
|
|
34
|
+
const collector = telemetryCollector || defaultTelemetryCollector;
|
|
35
|
+
return collector.getSummary();
|
|
36
|
+
}
|
|
37
|
+
const cwd = execContext?.cwd || process.cwd();
|
|
38
|
+
const sessionId = execContext?.sessionId || execContext?.session?.id || process.env.DSH_SESSION_ID;
|
|
39
|
+
const statusCmd = ['status'];
|
|
40
|
+
if (args?.org) {
|
|
41
|
+
statusCmd.push('-o', String(args.org));
|
|
42
|
+
}
|
|
43
|
+
if (args?.exclude && Array.isArray(args.exclude)) {
|
|
44
|
+
for (const ex of args.exclude) {
|
|
45
|
+
statusCmd.push('-e', String(ex));
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
const res = await executor.run(statusCmd, { cwd, sessionId, rawText: true });
|
|
49
|
+
if (!res.ok)
|
|
50
|
+
throw new Error(res.error);
|
|
51
|
+
const parsed = parseStatusOutput(res.rawOutput || '');
|
|
52
|
+
return {
|
|
53
|
+
parsed,
|
|
54
|
+
rawText: res.rawOutput || '',
|
|
55
|
+
};
|
|
56
|
+
},
|
|
57
|
+
};
|
|
58
|
+
}
|
package/lib/types.d.ts
CHANGED
|
@@ -37,6 +37,15 @@ export interface PluginConfig {
|
|
|
37
37
|
*/
|
|
38
38
|
proxyUrl?: string;
|
|
39
39
|
}
|
|
40
|
+
/** 数据同步与自动化感知偏好配置 */
|
|
41
|
+
export interface SyncSettings {
|
|
42
|
+
/** 刷新策略三选一:'swr' 秒开并在首屏拉新 | 'auto' SWR秒开+云端定时轮询 | 'manual' 仅手动点击拉取 */
|
|
43
|
+
strategy: 'swr' | 'auto' | 'manual';
|
|
44
|
+
/** 云端定时同步周期(毫秒,不少于 300,000 即 5 分钟,默认 600,000 即 10 分钟) */
|
|
45
|
+
cloudSyncIntervalMs: number;
|
|
46
|
+
/** 是否开启本地 .git 监控(感知代码修改、暂存与分支切换,仅右侧栏当前工作区生效,默认开启,用户可取消勾选) */
|
|
47
|
+
localGitWatch: boolean;
|
|
48
|
+
}
|
|
40
49
|
export interface GhExecutionOptions {
|
|
41
50
|
/** 命令执行的工作目录,优先使用当前 Agent 会话所在的 Workspace 物理路径 */
|
|
42
51
|
cwd?: string;
|
|
@@ -91,6 +100,12 @@ export interface AuthStatus {
|
|
|
91
100
|
scopes?: string[];
|
|
92
101
|
installGuide?: AuthInstallGuide;
|
|
93
102
|
raw?: string;
|
|
103
|
+
/** 网络是否发生异常(超时、DNS解析失败、连接被拒、代理阻断等) */
|
|
104
|
+
networkError?: boolean;
|
|
105
|
+
/** 网络异常的人类可读原因或排查建议 */
|
|
106
|
+
networkErrorReason?: string;
|
|
107
|
+
/** 本地是否已检测到持久化凭据(如 hosts.yml 或 Keyring) */
|
|
108
|
+
hasLocalToken?: boolean;
|
|
94
109
|
}
|
|
95
110
|
export interface RepoMetadata {
|
|
96
111
|
nameWithOwner: string;
|
|
@@ -101,6 +116,22 @@ export interface RepoMetadata {
|
|
|
101
116
|
description?: string;
|
|
102
117
|
isPrivate?: boolean;
|
|
103
118
|
url?: string;
|
|
119
|
+
stargazerCount?: number;
|
|
120
|
+
forkCount?: number;
|
|
121
|
+
}
|
|
122
|
+
/** Git 分支详情项(供分支切换器下拉列表消费) */
|
|
123
|
+
export interface GitBranchItem {
|
|
124
|
+
name: string;
|
|
125
|
+
isCurrent: boolean;
|
|
126
|
+
isRemote: boolean;
|
|
127
|
+
upstream?: string;
|
|
128
|
+
shortSha: string;
|
|
129
|
+
subject: string;
|
|
130
|
+
relativeTime: string;
|
|
131
|
+
}
|
|
132
|
+
export interface GitBranchesResult {
|
|
133
|
+
current: string;
|
|
134
|
+
branches: GitBranchItem[];
|
|
104
135
|
}
|
|
105
136
|
/**
|
|
106
137
|
* 本地 Git 工作区状态快照(ahead/behind 是 remote-tracking ref 的「上次 fetch」视图,
|
|
@@ -220,6 +251,10 @@ export interface GitHubOverviewData {
|
|
|
220
251
|
runs: WorkflowRunItem[];
|
|
221
252
|
lastUpdated: string;
|
|
222
253
|
error?: string;
|
|
254
|
+
/** 网络是否发生异常(超时、连接拒绝、代理阻断等) */
|
|
255
|
+
networkError?: boolean;
|
|
256
|
+
/** 网络异常的原因或提示 */
|
|
257
|
+
networkErrorReason?: string;
|
|
223
258
|
/** 插件版本与升级信息(由内存缓存快速读取,不阻塞首屏业务) */
|
|
224
259
|
updateInfo?: UpdateInfo;
|
|
225
260
|
/** 本地 Git 状态(ahead/behind/dirty,remote-tracking 快照语义);非 Git 工作区为 undefined */
|
|
@@ -241,6 +276,8 @@ export interface GitHubOverviewData {
|
|
|
241
276
|
url?: string;
|
|
242
277
|
isDraft?: boolean;
|
|
243
278
|
} | null;
|
|
279
|
+
/** 当前工作区的 Git 分支列表(供右侧栏分支选择器 0ms 秒开) */
|
|
280
|
+
branches?: GitBranchesResult | null;
|
|
244
281
|
}
|
|
245
282
|
export interface UserRepoItem {
|
|
246
283
|
name: string;
|
|
@@ -301,6 +338,10 @@ export interface GlobalOverviewData {
|
|
|
301
338
|
issueStats?: IssueStats;
|
|
302
339
|
lastUpdated: string;
|
|
303
340
|
error?: string;
|
|
341
|
+
/** 网络是否发生异常(超时、连接拒绝、代理阻断等) */
|
|
342
|
+
networkError?: boolean;
|
|
343
|
+
/** 网络异常的原因或提示 */
|
|
344
|
+
networkErrorReason?: string;
|
|
304
345
|
/** 工作区矩阵读取失败的原因。区分「没有工作区」与「读不到工作区」,避免把故障显示成空状态 */
|
|
305
346
|
workspaceError?: string;
|
|
306
347
|
/** 插件版本与升级信息(由内存缓存快速读取,不阻塞首屏业务) */
|
|
@@ -324,11 +365,13 @@ export interface UpdateInfo {
|
|
|
324
365
|
checkedAt: number;
|
|
325
366
|
}
|
|
326
367
|
export type UpgradeState = 'idle' | 'upgrading' | 'success' | 'failed';
|
|
368
|
+
export type UpgradeErrorType = 'MINIMUM_RELEASE_AGE_VIOLATION' | 'TIMEOUT' | 'UNKNOWN';
|
|
327
369
|
export interface UpgradeStatusResult {
|
|
328
370
|
state: UpgradeState;
|
|
329
371
|
targetVersion?: string;
|
|
330
372
|
message?: string;
|
|
331
373
|
log?: string;
|
|
374
|
+
errorType?: UpgradeErrorType;
|
|
332
375
|
startedAt?: number;
|
|
333
376
|
completedAt?: number;
|
|
334
377
|
/** 识别到的宿主环境类型:desktop 为桌面版,web 为网页/CLI版 */
|
|
@@ -338,6 +381,7 @@ export interface UpgradeExecutionResult {
|
|
|
338
381
|
ok: boolean;
|
|
339
382
|
message: string;
|
|
340
383
|
needRestart: boolean;
|
|
384
|
+
errorType?: UpgradeErrorType;
|
|
341
385
|
log?: string;
|
|
342
386
|
environment?: 'desktop' | 'web';
|
|
343
387
|
}
|
|
@@ -0,0 +1,161 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 认证与安装引导卡片组件(响应式自适应中央大面板与右侧栏)
|
|
3
|
+
*/
|
|
4
|
+
export function AuthGuideCard(props) {
|
|
5
|
+
var auth = props ? props.auth : null;
|
|
6
|
+
var onRefresh = props ? props.onRefresh : () => { };
|
|
7
|
+
var isRightbar = Boolean(props && props.isRightbar);
|
|
8
|
+
var [copied, setCopied] = React.useState(false);
|
|
9
|
+
var [launching, setLaunching] = React.useState(false);
|
|
10
|
+
var [toastMsg, setToastMsg] = React.useState(null);
|
|
11
|
+
var isNetwork = Boolean(auth?.networkError);
|
|
12
|
+
var notInstalled = !isNetwork && Boolean(auth && auth.installed === false);
|
|
13
|
+
var notLoggedIn = !isNetwork && !notInstalled && Boolean(!auth?.loggedIn);
|
|
14
|
+
var guide = auth?.installGuide;
|
|
15
|
+
var defaultCmd = isNetwork
|
|
16
|
+
? "gh auth status"
|
|
17
|
+
: notInstalled
|
|
18
|
+
? (guide?.command || "winget install --id GitHub.cli")
|
|
19
|
+
: "gh auth login";
|
|
20
|
+
var copyCmd = (cmd) => {
|
|
21
|
+
try {
|
|
22
|
+
if (navigator && navigator.clipboard && navigator.clipboard.writeText) {
|
|
23
|
+
navigator.clipboard.writeText(cmd);
|
|
24
|
+
}
|
|
25
|
+
else {
|
|
26
|
+
var input = document.createElement("textarea");
|
|
27
|
+
input.value = cmd;
|
|
28
|
+
document.body.appendChild(input);
|
|
29
|
+
input.select();
|
|
30
|
+
document.execCommand("copy");
|
|
31
|
+
document.body.removeChild(input);
|
|
32
|
+
}
|
|
33
|
+
setCopied(true);
|
|
34
|
+
setTimeout(() => setCopied(false), 2000);
|
|
35
|
+
}
|
|
36
|
+
catch (e) {
|
|
37
|
+
console.warn("复制失败:", e);
|
|
38
|
+
}
|
|
39
|
+
};
|
|
40
|
+
var launchTerminal = async (cmd) => {
|
|
41
|
+
setLaunching(true);
|
|
42
|
+
setToastMsg(null);
|
|
43
|
+
try {
|
|
44
|
+
var res = await fetch("/api/github/action", {
|
|
45
|
+
method: "POST",
|
|
46
|
+
headers: { "Content-Type": "application/json" },
|
|
47
|
+
body: JSON.stringify({ action: "open_terminal", command: cmd })
|
|
48
|
+
});
|
|
49
|
+
var json = await res.json();
|
|
50
|
+
if (json.ok) {
|
|
51
|
+
setToastMsg("已唤起系统终端,请在弹出窗口中完成操作;若未弹出,可手动打开终端执行上方命令");
|
|
52
|
+
setTimeout(() => setToastMsg(null), 6000);
|
|
53
|
+
}
|
|
54
|
+
else {
|
|
55
|
+
setToastMsg("❌ " + (json.message || "唤起失败,请手动打开终端执行上方命令"));
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
catch (e) {
|
|
59
|
+
setToastMsg("无法连接后台服务,请手动打开终端执行上方命令");
|
|
60
|
+
}
|
|
61
|
+
finally {
|
|
62
|
+
setLaunching(false);
|
|
63
|
+
}
|
|
64
|
+
};
|
|
65
|
+
var openDownload = async () => {
|
|
66
|
+
var url = guide?.downloadUrl || "https://cli.github.com/";
|
|
67
|
+
try {
|
|
68
|
+
await fetch("/api/github/action", {
|
|
69
|
+
method: "POST",
|
|
70
|
+
headers: { "Content-Type": "application/json" },
|
|
71
|
+
body: JSON.stringify({ action: "open_browser", url })
|
|
72
|
+
});
|
|
73
|
+
}
|
|
74
|
+
catch (e) {
|
|
75
|
+
window.open(url, "_blank", "noopener,noreferrer");
|
|
76
|
+
}
|
|
77
|
+
};
|
|
78
|
+
var openProxySettings = () => {
|
|
79
|
+
try {
|
|
80
|
+
window.dispatchEvent(new CustomEvent("dsh-github-open-proxy"));
|
|
81
|
+
}
|
|
82
|
+
catch (e) { }
|
|
83
|
+
};
|
|
84
|
+
var h = React.createElement;
|
|
85
|
+
return h("div", {
|
|
86
|
+
className: "dsh-github-guide-card " + (isNetwork ? "warning" : notInstalled ? "danger" : "warning"),
|
|
87
|
+
style: isRightbar ? { padding: "12px", marginBottom: "12px" } : undefined
|
|
88
|
+
}, h("div", { className: "dsh-github-guide-header" }, isNetwork
|
|
89
|
+
? "🌐 无法连接 GitHub 服务器"
|
|
90
|
+
: notInstalled
|
|
91
|
+
? "⚠️ 未检测到 GitHub CLI (gh)"
|
|
92
|
+
: "🔑 GitHub 账号尚未登录", h("span", { className: "dsh-github-badge " + (isNetwork ? "dsh-badge-yellow" : notInstalled ? "dsh-badge-red" : "dsh-badge-yellow") }, isNetwork ? "网络受限" : notInstalled ? "未安装" : "未认证")), h("div", { className: "dsh-github-guide-desc" }, isNetwork
|
|
93
|
+
? (auth?.networkErrorReason || "检测到本机已安装 GitHub CLI,但当前无法连接至 api.github.com(网络超时、DNS 无法解析或被系统代理阻断)。")
|
|
94
|
+
: notInstalled
|
|
95
|
+
? (guide?.description || "DSH GitHub Flow 插件依赖官方 GitHub CLI 工具与云端进行安全交互,当前系统 PATH 中未检测到该工具。")
|
|
96
|
+
: "检测到本机已安装 GitHub CLI,请完成账号授权以解锁仓库管理、PR 审查和 Issues 待办。"), h("div", { className: "dsh-github-cmd-box" }, h("code", null, defaultCmd), h("button", {
|
|
97
|
+
className: "dsh-github-copy-btn " + (copied ? "copied" : ""),
|
|
98
|
+
onClick: () => copyCmd(defaultCmd),
|
|
99
|
+
title: "复制命令到剪贴板"
|
|
100
|
+
}, copied ? "✓ 已复制" : "📋 复制命令")), !notInstalled && !isRightbar
|
|
101
|
+
? (isNetwork
|
|
102
|
+
? h("ol", { className: "dsh-github-steps-list" }, h("li", null, "检查系统本地网络连接或 DNS 解析,确认能正常访问 github.com;"), h("li", null, "若使用了网络代理工具,可在下方点击「配置网络代理」填写 HTTP 或 SOCKS5 代理;"), h("li", null, "配置或恢复网络后,切回本面板或点击「重试连接」即可自动同步数据。"))
|
|
103
|
+
: h("ol", { className: "dsh-github-steps-list" }, h("li", null, "打开系统终端(PowerShell / CMD / Terminal),运行上述命令;"), h("li", null, "按提示依次选择「GitHub.com」➜「HTTPS」➜「Login with a web browser」;"), h("li", null, "按回车打开浏览器确认授权后,切回本页面即可自动恢复。")))
|
|
104
|
+
: null, h("div", { className: "dsh-github-guide-actions" }, isNetwork
|
|
105
|
+
? h("button", {
|
|
106
|
+
className: "dsh-github-action-btn",
|
|
107
|
+
onClick: onRefresh,
|
|
108
|
+
title: "重新发起网络连接校验"
|
|
109
|
+
}, "🔄 重试网络连接")
|
|
110
|
+
: null, isNetwork
|
|
111
|
+
? h("button", {
|
|
112
|
+
className: "dsh-github-action-btn",
|
|
113
|
+
onClick: openProxySettings,
|
|
114
|
+
title: "打开网络代理设置,配置 HTTP/SOCKS5 代理服务器"
|
|
115
|
+
}, "⚙️ 配置网络代理")
|
|
116
|
+
: null, h("button", {
|
|
117
|
+
className: "dsh-github-action-btn",
|
|
118
|
+
onClick: () => launchTerminal(defaultCmd),
|
|
119
|
+
disabled: launching,
|
|
120
|
+
title: "自动唤起系统终端并执行该命令"
|
|
121
|
+
}, launching ? "正在唤起..." : (isNetwork ? "💻 终端排查状态" : notInstalled ? "💻 唤起终端安装" : "💻 唤起终端登录")), notInstalled
|
|
122
|
+
? h("button", {
|
|
123
|
+
className: "dsh-github-action-btn",
|
|
124
|
+
onClick: openDownload,
|
|
125
|
+
title: "打开 GitHub CLI 官方下载主页获取安装包"
|
|
126
|
+
}, "🌐 官网安装包 (.msi/.pkg)")
|
|
127
|
+
: null, !isNetwork
|
|
128
|
+
? h("button", {
|
|
129
|
+
className: "dsh-github-action-btn",
|
|
130
|
+
onClick: function () {
|
|
131
|
+
setToastMsg("正在快速检测登录状态...");
|
|
132
|
+
var probeController = new AbortController();
|
|
133
|
+
var probeTimer = setTimeout(function () { probeController.abort(); }, 10000);
|
|
134
|
+
fetch("/api/github/auth?force=true", { signal: probeController.signal })
|
|
135
|
+
.then(function (res) { return res.json(); })
|
|
136
|
+
.then(function (json) {
|
|
137
|
+
clearTimeout(probeTimer);
|
|
138
|
+
if (json && json.ok && json.auth && json.auth.loggedIn) {
|
|
139
|
+
setToastMsg("✅ 已识别到 GitHub CLI 登录态,正在拉取数据...");
|
|
140
|
+
clearStoredGlobalOverview();
|
|
141
|
+
onRefresh();
|
|
142
|
+
}
|
|
143
|
+
else {
|
|
144
|
+
setToastMsg("⚠️ 暂未检测到登录凭据,请在终端执行 gh auth login 完成授权");
|
|
145
|
+
onRefresh();
|
|
146
|
+
}
|
|
147
|
+
})
|
|
148
|
+
.catch(function () {
|
|
149
|
+
clearTimeout(probeTimer);
|
|
150
|
+
setToastMsg("❌ 检测超时或后端不可达,请稍后重试");
|
|
151
|
+
onRefresh();
|
|
152
|
+
});
|
|
153
|
+
},
|
|
154
|
+
title: "重新检测登录与安装状态"
|
|
155
|
+
}, "🔄 刷新状态")
|
|
156
|
+
: null), toastMsg
|
|
157
|
+
? h("div", { style: { fontSize: "12px", color: "var(--dsw-alias-link, #0969da)", marginTop: "8px" } }, `ℹ️ ${toastMsg}`)
|
|
158
|
+
: null, h("div", { className: "dsh-github-guide-tip" }, notInstalled
|
|
159
|
+
? "💡 提示:使用包管理器安装完成后,若未立即识别,可重启 DeepSeek Harness 应用刷新系统环境变量 PATH。"
|
|
160
|
+
: "💡 提示:凭证由官方 GitHub CLI 加密保存在操作系统安全凭据库中,授权完毕切回本窗口将自动识别刷新。"));
|
|
161
|
+
}
|