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
package/lib/tools/secret.js
CHANGED
|
@@ -45,17 +45,63 @@ function renderSecretOutput(args, value) {
|
|
|
45
45
|
}
|
|
46
46
|
return JSON.stringify(value, null, 2);
|
|
47
47
|
}
|
|
48
|
+
/**
|
|
49
|
+
* 渲染 variable 工具输出——与 renderSecretOutput 物理分离(S3 D1 决策)。
|
|
50
|
+
* 为什么必须独立函数而非同一渲染器的分支:variable 的 value 是 CI 明文(可回显,
|
|
51
|
+
* 这正是 get action 的用途),secret 的 value 永不显示。若共用渲染逻辑再
|
|
52
|
+
* 「选择性放开」,未来任一侧的重构都可能意外污染另一侧的安全假设——
|
|
53
|
+
* 两个函数的边界比一个函数的分支更可审。
|
|
54
|
+
*/
|
|
55
|
+
function renderVariableOutput(args, value) {
|
|
56
|
+
if (!value)
|
|
57
|
+
return '无返回内容';
|
|
58
|
+
if (value.status === 'AWAITING_CONFIRMATION') {
|
|
59
|
+
return [
|
|
60
|
+
`> ### ${value.warning}`,
|
|
61
|
+
`> - **影响评估**: ${value.impact}`,
|
|
62
|
+
`> - **确认指引**: ${value.requiredAction}`,
|
|
63
|
+
].join('\n');
|
|
64
|
+
}
|
|
65
|
+
if (Array.isArray(value.items) && args?.action === 'list') {
|
|
66
|
+
const list = value.items.filter(Boolean);
|
|
67
|
+
if (list.length === 0)
|
|
68
|
+
return '📭 当前作用域尚未配置任何 Variable。';
|
|
69
|
+
const rows = list.map((v) => {
|
|
70
|
+
const name = `\`${String(v.name || '-')}\``;
|
|
71
|
+
const date = v.createdAt ? String(v.createdAt).slice(0, 10) : '-';
|
|
72
|
+
return `| ${name} | ${date} |`;
|
|
73
|
+
});
|
|
74
|
+
return [
|
|
75
|
+
`**当前已配置 ${list.length} 个 Variable(值为 CI 明文,可经 get 查看):**\n`,
|
|
76
|
+
'| 变量名 | 创建日期 |',
|
|
77
|
+
'| :--- | :--- |',
|
|
78
|
+
...rows,
|
|
79
|
+
].join('\n');
|
|
80
|
+
}
|
|
81
|
+
if (typeof value.result === 'string') {
|
|
82
|
+
return value.result;
|
|
83
|
+
}
|
|
84
|
+
if (typeof value.message === 'string') {
|
|
85
|
+
return `**${value.message}**`;
|
|
86
|
+
}
|
|
87
|
+
return JSON.stringify(value, null, 2);
|
|
88
|
+
}
|
|
48
89
|
export function createSecretTool(executor) {
|
|
49
90
|
return {
|
|
50
91
|
name: 'github_secret',
|
|
51
|
-
description: '管理 GitHub Actions 仓库密钥 (Secrets)
|
|
92
|
+
description: '管理 GitHub Actions 仓库密钥 (Secrets) 与变量 (Variables):查看名称清单、设置/更新/删除值。Secret 密文经标准输入安全传递,绝不出现在命令行参数或输出中;Variable 为 CI 明文,可经 get 查看值。Secret 删除需 confirm: true。',
|
|
52
93
|
parameters: {
|
|
53
94
|
type: 'object',
|
|
54
95
|
properties: {
|
|
55
96
|
action: {
|
|
56
97
|
type: 'string',
|
|
57
|
-
enum: ['list', 'set', 'delete'],
|
|
58
|
-
description: '
|
|
98
|
+
enum: ['list', 'get', 'set', 'delete'],
|
|
99
|
+
description: '操作类型。get 仅 kind=variable 可用(secret 值写入后 GitHub 侧加密存储,物理不可读取)。',
|
|
100
|
+
},
|
|
101
|
+
kind: {
|
|
102
|
+
type: 'string',
|
|
103
|
+
enum: ['secret', 'variable'],
|
|
104
|
+
description: '操作对象类型,默认 secret(向后兼容)。variable 为 Actions 明文变量:set 走 -b 直传、get 可回显值;secret 走 stdin 密文通道且永不回显。',
|
|
59
105
|
},
|
|
60
106
|
repo: {
|
|
61
107
|
type: 'string',
|
|
@@ -63,33 +109,33 @@ export function createSecretTool(executor) {
|
|
|
63
109
|
},
|
|
64
110
|
name: {
|
|
65
111
|
type: 'string',
|
|
66
|
-
description: '
|
|
112
|
+
description: '名称(set/get/delete 动作使用,如 "NPM_TOKEN"、"DEPLOY_ENV")。',
|
|
67
113
|
},
|
|
68
114
|
value: {
|
|
69
115
|
type: 'string',
|
|
70
|
-
description: '
|
|
116
|
+
description: '值内容(set 动作使用)。secret:经子进程标准输入传递(防进程列表窃取);variable:经 -b 参数直传(CI 明文,无 stdin 红线)。',
|
|
71
117
|
},
|
|
72
118
|
org: {
|
|
73
119
|
type: 'string',
|
|
74
|
-
description: '
|
|
120
|
+
description: '组织级作用域的组织名(可选,映射 --org)。',
|
|
75
121
|
},
|
|
76
122
|
env: {
|
|
77
123
|
type: 'string',
|
|
78
|
-
description: '
|
|
124
|
+
description: '环境级作用域的部署环境名(可选,映射 --env)。',
|
|
79
125
|
},
|
|
80
126
|
app: {
|
|
81
127
|
type: 'string',
|
|
82
128
|
enum: ['actions', 'agents', 'codespaces', 'dependabot'],
|
|
83
|
-
description: '
|
|
129
|
+
description: '密钥归属的应用(可选,仅 secret 的 --app,默认 actions;variable 无此维度)。',
|
|
84
130
|
},
|
|
85
131
|
visibility: {
|
|
86
132
|
type: 'string',
|
|
87
133
|
enum: ['all', 'private', 'selected'],
|
|
88
|
-
description: '组织级密钥的可见范围(可选,仅 org 场景有意义)。',
|
|
134
|
+
description: '组织级密钥的可见范围(可选,仅 secret + org 场景有意义)。',
|
|
89
135
|
},
|
|
90
136
|
confirm: {
|
|
91
137
|
type: 'boolean',
|
|
92
|
-
description: '
|
|
138
|
+
description: '删除操作的确认标志。删除不可逆,未传 true 时仅输出影响评估。',
|
|
93
139
|
},
|
|
94
140
|
},
|
|
95
141
|
required: ['action'],
|
|
@@ -97,13 +143,16 @@ export function createSecretTool(executor) {
|
|
|
97
143
|
output: {
|
|
98
144
|
schema: { type: 'object' },
|
|
99
145
|
render(args, value) {
|
|
100
|
-
|
|
146
|
+
// 渲染分流即安全边界:variable 与 secret 永不共享渲染路径(见函数注释)
|
|
147
|
+
const renderer = args?.kind === 'variable' ? renderVariableOutput : renderSecretOutput;
|
|
148
|
+
return [{ type: 'text', text: renderer(args, value) }];
|
|
101
149
|
},
|
|
102
150
|
},
|
|
103
151
|
async execute(args, execContext) {
|
|
104
152
|
const cwd = execContext?.cwd || process.cwd();
|
|
105
153
|
const sessionId = execContext?.sessionId || execContext?.session?.id || process.env.DSH_SESSION_ID;
|
|
106
|
-
|
|
154
|
+
const isVariable = args.kind === 'variable';
|
|
155
|
+
// org 作用域挂在组织上,与具体仓库无关:既不需要仓库识别,
|
|
107
156
|
// 也不能把 -R 与 --org 同时传给 gh(两者语义冲突,gh 会报错)。
|
|
108
157
|
const isOrgScope = Boolean(args.org);
|
|
109
158
|
let repoTarget = args.repo;
|
|
@@ -114,34 +163,67 @@ export function createSecretTool(executor) {
|
|
|
114
163
|
}
|
|
115
164
|
}
|
|
116
165
|
if (!repoTarget && !isOrgScope) {
|
|
117
|
-
throw new Error('未能自动识别当前工作区关联的 GitHub 仓库。请通过 `repo` 参数显式指定目标仓库 [owner/repo],或改用 `org`
|
|
166
|
+
throw new Error('未能自动识别当前工作区关联的 GitHub 仓库。请通过 `repo` 参数显式指定目标仓库 [owner/repo],或改用 `org` 参数操作组织级作用域。');
|
|
167
|
+
}
|
|
168
|
+
// app 限定符仅 secret 域存在(variable 命令无 --app);误传时明确报错而非静默忽略
|
|
169
|
+
if (isVariable && args.app) {
|
|
170
|
+
throw new Error("variable 无 --app 维度(app 仅 secret 域支持),请移除 app 参数。");
|
|
171
|
+
}
|
|
172
|
+
if (isVariable && args.visibility) {
|
|
173
|
+
throw new Error("variable 无 --visibility 维度(visibility 仅组织级 secret 支持),请移除 visibility 参数。");
|
|
118
174
|
}
|
|
119
175
|
const runOpts = { cwd, sessionId };
|
|
120
176
|
// org/env/app 三类作用域标志对所有动作通用;仓库作用域时三项均不追加
|
|
121
177
|
const scopeFlags = [];
|
|
122
178
|
if (args.org) {
|
|
123
179
|
scopeFlags.push('--org', args.org);
|
|
124
|
-
if (args.visibility)
|
|
180
|
+
if (!isVariable && args.visibility)
|
|
125
181
|
scopeFlags.push('--visibility', args.visibility);
|
|
126
182
|
}
|
|
127
183
|
else if (args.env) {
|
|
128
184
|
scopeFlags.push('--env', args.env);
|
|
129
185
|
}
|
|
130
|
-
if (args.app)
|
|
186
|
+
if (!isVariable && args.app)
|
|
131
187
|
scopeFlags.push('--app', args.app);
|
|
132
188
|
const targetArgs = isOrgScope ? [] : ['-R', repoTarget];
|
|
189
|
+
// gh 命令域分流:secret → gh secret …;variable → gh variable …
|
|
190
|
+
// (两级分流:域前缀 + 每个分支内的行为差异——stdin vs -b、get 可用性)
|
|
191
|
+
const domain = isVariable ? 'variable' : 'secret';
|
|
133
192
|
switch (args.action) {
|
|
134
193
|
case 'list': {
|
|
135
|
-
|
|
194
|
+
// 字段集各自实测钉死:secret 有 updatedAt/visibility;variable 仅 4 字段
|
|
195
|
+
// (createdAt,name,numSelectedRepos,selectedReposURL——无 value,get 才返回)
|
|
196
|
+
const fields = isVariable ? 'name,createdAt,numSelectedRepos' : 'name,updatedAt,visibility';
|
|
197
|
+
const res = await executor.run([domain, 'list', ...targetArgs, ...scopeFlags, '--json', fields], runOpts);
|
|
136
198
|
if (!res.ok)
|
|
137
199
|
throw new Error(res.error);
|
|
138
200
|
return normalizeOutput(res.data || res.rawOutput);
|
|
139
201
|
}
|
|
202
|
+
case 'get': {
|
|
203
|
+
if (!args.name)
|
|
204
|
+
throw new Error('get 操作必须提供 name');
|
|
205
|
+
if (!isVariable) {
|
|
206
|
+
throw new Error('secret 的值写入后由 GitHub 侧加密存储,物理上不可读取(这正是 secret 与 variable 的设计区别)。请改用 kind: \'variable\' 或直接 set 覆盖。');
|
|
207
|
+
}
|
|
208
|
+
// gh variable get 输出变量值(纯文本,无 --json)
|
|
209
|
+
const res = await executor.run(['variable', 'get', args.name, ...targetArgs, ...scopeFlags], { ...runOpts, rawText: true });
|
|
210
|
+
if (!res.ok)
|
|
211
|
+
throw new Error(res.error);
|
|
212
|
+
return { name: args.name, value: res.rawOutput || '' };
|
|
213
|
+
}
|
|
140
214
|
case 'set': {
|
|
141
215
|
if (!args.name)
|
|
142
|
-
throw new Error('set 操作必须提供 name
|
|
216
|
+
throw new Error('set 操作必须提供 name');
|
|
143
217
|
if (typeof args.value !== 'string' || args.value.length === 0) {
|
|
144
|
-
throw new Error('set 操作必须提供 value
|
|
218
|
+
throw new Error('set 操作必须提供 value,且不允许为空字符串');
|
|
219
|
+
}
|
|
220
|
+
if (isVariable) {
|
|
221
|
+
// variable 是 CI 明文(workflow 中 env 直接引用可见),无 secret 的
|
|
222
|
+
// stdin 红线;gh variable set 原生支持 -b 直传(实测 help)。
|
|
223
|
+
const res = await executor.run(['variable', 'set', args.name, '--body', args.value, ...targetArgs, ...scopeFlags], runOpts);
|
|
224
|
+
if (!res.ok)
|
|
225
|
+
throw new Error(res.error);
|
|
226
|
+
return { message: `Variable「${args.name}」已更新(值: ${args.value})` };
|
|
145
227
|
}
|
|
146
228
|
// 安全红线:密文只走 stdin。严禁退化为 -b <value> —— 那会把明文
|
|
147
229
|
// 留在进程列表与 shell 历史里,等于密钥泄漏。gh 本地会用仓库公钥
|
|
@@ -154,21 +236,24 @@ export function createSecretTool(executor) {
|
|
|
154
236
|
case 'delete': {
|
|
155
237
|
if (!args.name)
|
|
156
238
|
throw new Error('delete 操作必须提供 name');
|
|
239
|
+
const kindLabel = isVariable ? 'Variable' : 'Secret';
|
|
157
240
|
if (args.confirm !== true) {
|
|
158
241
|
return {
|
|
159
242
|
status: 'AWAITING_CONFIRMATION',
|
|
160
|
-
warning: `⚠️ 高危操作拦截:你即将删除
|
|
161
|
-
impact:
|
|
243
|
+
warning: `⚠️ 高危操作拦截:你即将删除 ${kindLabel}「${args.name}」${args.org ? `(组织 ${args.org})` : ''}。`,
|
|
244
|
+
impact: isVariable
|
|
245
|
+
? '删除后所有引用该变量的工作流将取不到值,运行中的构建立即失败。'
|
|
246
|
+
: '删除后所有引用该密钥的 CI/CD 工作流将立即无法取到值,运行中的构建会失败。',
|
|
162
247
|
requiredAction: '如确认删除,请向用户核实并附加 `confirm: true` 重新调用。',
|
|
163
248
|
};
|
|
164
249
|
}
|
|
165
|
-
const res = await executor.run([
|
|
250
|
+
const res = await executor.run([domain, 'delete', args.name, ...targetArgs, ...scopeFlags], runOpts);
|
|
166
251
|
if (!res.ok)
|
|
167
252
|
throw new Error(res.error);
|
|
168
|
-
return { message:
|
|
253
|
+
return { message: `${kindLabel}「${args.name}」已删除` };
|
|
169
254
|
}
|
|
170
255
|
default:
|
|
171
|
-
throw new Error(`未知的 Secret 操作: ${args.action}
|
|
256
|
+
throw new Error(`未知的 Secret/Variable 操作: ${args.action}(kind: ${args.kind || 'secret'})`);
|
|
172
257
|
}
|
|
173
258
|
},
|
|
174
259
|
};
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import type { GhExecutor } from '../executor.js';
|
|
2
|
+
export declare function createStatusTool(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
|
+
org: {
|
|
14
|
+
type: string;
|
|
15
|
+
description: string;
|
|
16
|
+
};
|
|
17
|
+
exclude: {
|
|
18
|
+
type: string;
|
|
19
|
+
items: {
|
|
20
|
+
type: string;
|
|
21
|
+
};
|
|
22
|
+
description: string;
|
|
23
|
+
};
|
|
24
|
+
};
|
|
25
|
+
};
|
|
26
|
+
output: {
|
|
27
|
+
schema: {
|
|
28
|
+
type: string;
|
|
29
|
+
};
|
|
30
|
+
render(args: any, value: any): {
|
|
31
|
+
type: string;
|
|
32
|
+
text: string;
|
|
33
|
+
}[];
|
|
34
|
+
};
|
|
35
|
+
execute(args: any, execContext: any): Promise<{
|
|
36
|
+
parsed: import("../renderers/status.js").ParsedStatusReport;
|
|
37
|
+
rawText: string;
|
|
38
|
+
}>;
|
|
39
|
+
};
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
import { renderStatusMarkdown, parseStatusOutput } from '../renderers/markdown.js';
|
|
2
|
+
export function createStatusTool(executor) {
|
|
3
|
+
return {
|
|
4
|
+
name: 'github_status',
|
|
5
|
+
description: '聚合展示当前登录用户的跨仓库全局待办与动态大盘:包括指派给我的 Issues、指派给我的 PR、等待我评审的 PR、近期 @提及 以及订阅仓库的最新动态。无需指定单仓,账号维度一站式概览。',
|
|
6
|
+
parameters: {
|
|
7
|
+
type: 'object',
|
|
8
|
+
properties: {
|
|
9
|
+
action: {
|
|
10
|
+
type: 'string',
|
|
11
|
+
enum: ['view'],
|
|
12
|
+
description: '操作类型,默认为 view(查看全局待办看板)。',
|
|
13
|
+
},
|
|
14
|
+
org: {
|
|
15
|
+
type: 'string',
|
|
16
|
+
description: '组织名称(可选,限定仅汇报指定组织内部的待办与动态)。',
|
|
17
|
+
},
|
|
18
|
+
exclude: {
|
|
19
|
+
type: 'array',
|
|
20
|
+
items: { type: 'string' },
|
|
21
|
+
description: '要排除的仓库名称列表 [owner/repo](可选,支持排除噪音仓库)。',
|
|
22
|
+
},
|
|
23
|
+
},
|
|
24
|
+
},
|
|
25
|
+
output: {
|
|
26
|
+
schema: { type: 'object' },
|
|
27
|
+
render(args, value) {
|
|
28
|
+
return [{ type: 'text', text: renderStatusMarkdown(args, value) }];
|
|
29
|
+
},
|
|
30
|
+
},
|
|
31
|
+
async execute(args, execContext) {
|
|
32
|
+
const cwd = execContext?.cwd || process.cwd();
|
|
33
|
+
const sessionId = execContext?.sessionId || execContext?.session?.id || process.env.DSH_SESSION_ID;
|
|
34
|
+
const statusCmd = ['status'];
|
|
35
|
+
if (args?.org) {
|
|
36
|
+
statusCmd.push('-o', String(args.org));
|
|
37
|
+
}
|
|
38
|
+
if (args?.exclude && Array.isArray(args.exclude)) {
|
|
39
|
+
for (const ex of args.exclude) {
|
|
40
|
+
statusCmd.push('-e', String(ex));
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
const res = await executor.run(statusCmd, { cwd, sessionId, rawText: true });
|
|
44
|
+
if (!res.ok)
|
|
45
|
+
throw new Error(res.error);
|
|
46
|
+
const parsed = parseStatusOutput(res.rawOutput || '');
|
|
47
|
+
return {
|
|
48
|
+
parsed,
|
|
49
|
+
rawText: res.rawOutput || '',
|
|
50
|
+
};
|
|
51
|
+
},
|
|
52
|
+
};
|
|
53
|
+
}
|
package/lib/types.d.ts
CHANGED
|
@@ -24,6 +24,12 @@ export interface PluginConfig {
|
|
|
24
24
|
updateCheckIntervalMs?: number;
|
|
25
25
|
/** 自定义 npm Registry 镜像地址,留空则自动选用 npmmirror 与 npmjs */
|
|
26
26
|
registryUrl?: string;
|
|
27
|
+
/**
|
|
28
|
+
* 是否向 gh 子进程注入 AI_AGENT='dsh' 环境变量,默认 true。
|
|
29
|
+
* gh ≥2.99 检测该变量后切换 agent 专属行为(text-only spinner、误用时打印完整 help);
|
|
30
|
+
* 关闭后恢复 gh 原生行为(用于对照实验或偏好原生输出的场景)。
|
|
31
|
+
*/
|
|
32
|
+
agentEnvInjection?: boolean;
|
|
27
33
|
/**
|
|
28
34
|
* 代理地址预置默认值(如 "http://127.0.0.1:7890")。
|
|
29
35
|
* 仅在用户未在驾驶舱保存过代理设置时生效;运行时 UI 保存的值优先级更高。
|
|
@@ -31,6 +37,15 @@ export interface PluginConfig {
|
|
|
31
37
|
*/
|
|
32
38
|
proxyUrl?: string;
|
|
33
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
|
+
}
|
|
34
49
|
export interface GhExecutionOptions {
|
|
35
50
|
/** 命令执行的工作目录,优先使用当前 Agent 会话所在的 Workspace 物理路径 */
|
|
36
51
|
cwd?: string;
|
|
@@ -85,6 +100,12 @@ export interface AuthStatus {
|
|
|
85
100
|
scopes?: string[];
|
|
86
101
|
installGuide?: AuthInstallGuide;
|
|
87
102
|
raw?: string;
|
|
103
|
+
/** 网络是否发生异常(超时、DNS解析失败、连接被拒、代理阻断等) */
|
|
104
|
+
networkError?: boolean;
|
|
105
|
+
/** 网络异常的人类可读原因或排查建议 */
|
|
106
|
+
networkErrorReason?: string;
|
|
107
|
+
/** 本地是否已检测到持久化凭据(如 hosts.yml 或 Keyring) */
|
|
108
|
+
hasLocalToken?: boolean;
|
|
88
109
|
}
|
|
89
110
|
export interface RepoMetadata {
|
|
90
111
|
nameWithOwner: string;
|
|
@@ -96,6 +117,66 @@ export interface RepoMetadata {
|
|
|
96
117
|
isPrivate?: boolean;
|
|
97
118
|
url?: string;
|
|
98
119
|
}
|
|
120
|
+
/** Git 分支详情项(供分支切换器下拉列表消费) */
|
|
121
|
+
export interface GitBranchItem {
|
|
122
|
+
name: string;
|
|
123
|
+
isCurrent: boolean;
|
|
124
|
+
isRemote: boolean;
|
|
125
|
+
upstream?: string;
|
|
126
|
+
shortSha: string;
|
|
127
|
+
subject: string;
|
|
128
|
+
relativeTime: string;
|
|
129
|
+
}
|
|
130
|
+
export interface GitBranchesResult {
|
|
131
|
+
current: string;
|
|
132
|
+
branches: GitBranchItem[];
|
|
133
|
+
}
|
|
134
|
+
/**
|
|
135
|
+
* 本地 Git 工作区状态快照(ahead/behind 是 remote-tracking ref 的「上次 fetch」视图,
|
|
136
|
+
* 非实时网络状态——UI 渲染时必须带上这一语义提示,否则会误导用户)。
|
|
137
|
+
*/
|
|
138
|
+
export interface GitLocalStatus {
|
|
139
|
+
branch: string;
|
|
140
|
+
/** 领先远程的提交数;无 upstream 时为 -1(未知) */
|
|
141
|
+
ahead: number;
|
|
142
|
+
/** 落后远程的提交数;无 upstream 时为 -1(未知) */
|
|
143
|
+
behind: number;
|
|
144
|
+
/** 未提交变更文件数(含 untracked) */
|
|
145
|
+
dirtyCount: number;
|
|
146
|
+
hasUpstream: boolean;
|
|
147
|
+
/** 未提交文件清单(porcelain 解析,截断前 20 条防快照膨胀) */
|
|
148
|
+
dirtyFiles: Array<{
|
|
149
|
+
status: string;
|
|
150
|
+
path: string;
|
|
151
|
+
}>;
|
|
152
|
+
}
|
|
153
|
+
/** 单条提交记录(结构化解析) */
|
|
154
|
+
export interface GitCommitEntry {
|
|
155
|
+
sha: string;
|
|
156
|
+
fullSha?: string;
|
|
157
|
+
author: string;
|
|
158
|
+
email?: string;
|
|
159
|
+
date?: string;
|
|
160
|
+
/** git 的相对时间原文(如 "2 hours ago")——本地化由渲染层处理 */
|
|
161
|
+
relative: string;
|
|
162
|
+
subject: string;
|
|
163
|
+
body?: string;
|
|
164
|
+
stat?: string;
|
|
165
|
+
}
|
|
166
|
+
/**
|
|
167
|
+
* 本地 Git 提交记录三段视图(与 GitLocalStatus 的 ahead/behind 计数同口径,
|
|
168
|
+
* 均为 remote-tracking 快照)。
|
|
169
|
+
*/
|
|
170
|
+
export interface GitCommitLog {
|
|
171
|
+
/** 本地领先远程的提交(待 push) */
|
|
172
|
+
aheadCommits: GitCommitEntry[];
|
|
173
|
+
/** 远程领先本地的提交(待 pull,快照口径) */
|
|
174
|
+
behindCommits: GitCommitEntry[];
|
|
175
|
+
/** 最近提交(默认 15 条,支持下滚分页) */
|
|
176
|
+
recent: GitCommitEntry[];
|
|
177
|
+
/** 仓库总提交数(rev-list --count HEAD) */
|
|
178
|
+
totalCount?: number;
|
|
179
|
+
}
|
|
99
180
|
export interface PrItem {
|
|
100
181
|
number: number;
|
|
101
182
|
title: string;
|
|
@@ -168,8 +249,33 @@ export interface GitHubOverviewData {
|
|
|
168
249
|
runs: WorkflowRunItem[];
|
|
169
250
|
lastUpdated: string;
|
|
170
251
|
error?: string;
|
|
252
|
+
/** 网络是否发生异常(超时、连接拒绝、代理阻断等) */
|
|
253
|
+
networkError?: boolean;
|
|
254
|
+
/** 网络异常的原因或提示 */
|
|
255
|
+
networkErrorReason?: string;
|
|
171
256
|
/** 插件版本与升级信息(由内存缓存快速读取,不阻塞首屏业务) */
|
|
172
257
|
updateInfo?: UpdateInfo;
|
|
258
|
+
/** 本地 Git 状态(ahead/behind/dirty,remote-tracking 快照语义);非 Git 工作区为 undefined */
|
|
259
|
+
gitStatus?: GitLocalStatus | null;
|
|
260
|
+
/** 提交记录三段(本地领先/远程领先/最近);探测失败为 undefined */
|
|
261
|
+
commitLog?: GitCommitLog | null;
|
|
262
|
+
/** 最新 Release 锚定(已过滤 Draft);无 Release 仓库为 null */
|
|
263
|
+
releaseInfo?: {
|
|
264
|
+
tagName: string;
|
|
265
|
+
isPrerelease?: boolean;
|
|
266
|
+
publishedAt?: string;
|
|
267
|
+
url?: string;
|
|
268
|
+
} | null;
|
|
269
|
+
/** 当前分支关联的 PR;无 PR 分支为 null */
|
|
270
|
+
branchPr?: {
|
|
271
|
+
number: number;
|
|
272
|
+
title: string;
|
|
273
|
+
state?: string;
|
|
274
|
+
url?: string;
|
|
275
|
+
isDraft?: boolean;
|
|
276
|
+
} | null;
|
|
277
|
+
/** 当前工作区的 Git 分支列表(供右侧栏分支选择器 0ms 秒开) */
|
|
278
|
+
branches?: GitBranchesResult | null;
|
|
173
279
|
}
|
|
174
280
|
export interface UserRepoItem {
|
|
175
281
|
name: string;
|
|
@@ -188,6 +294,8 @@ export interface WorkspaceMatrixItem {
|
|
|
188
294
|
hasGit: boolean;
|
|
189
295
|
repo?: RepoMetadata;
|
|
190
296
|
sessionCount: number;
|
|
297
|
+
/** 本地 Git 状态摘要(可选——探测失败/非 Git 工作区不携带) */
|
|
298
|
+
gitStatus?: GitLocalStatus;
|
|
191
299
|
}
|
|
192
300
|
export interface GlobalIssueItem {
|
|
193
301
|
number: number;
|
|
@@ -228,6 +336,10 @@ export interface GlobalOverviewData {
|
|
|
228
336
|
issueStats?: IssueStats;
|
|
229
337
|
lastUpdated: string;
|
|
230
338
|
error?: string;
|
|
339
|
+
/** 网络是否发生异常(超时、连接拒绝、代理阻断等) */
|
|
340
|
+
networkError?: boolean;
|
|
341
|
+
/** 网络异常的原因或提示 */
|
|
342
|
+
networkErrorReason?: string;
|
|
231
343
|
/** 工作区矩阵读取失败的原因。区分「没有工作区」与「读不到工作区」,避免把故障显示成空状态 */
|
|
232
344
|
workspaceError?: string;
|
|
233
345
|
/** 插件版本与升级信息(由内存缓存快速读取,不阻塞首屏业务) */
|
|
@@ -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
|
+
}
|