dsh-github-flow 0.1.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.md ADDED
@@ -0,0 +1,130 @@
1
+ # DSH GitHub Flow (`dsh-github-flow`)
2
+
3
+ 基于 GitHub CLI (`gh`) 为 DeepSeek Harness (DSH) 打造的现代化原生集成插件。
4
+ 深度打通本地 Git 仓库开发与远程 GitHub 协作(PR、Issue、Actions CI 诊断与代码审查),兼备 Agent 模型工具、人类 Slash 指令以及原生 Web UI 双重视图(主驾驶舱与右侧栏快捷面板)。
5
+
6
+ ---
7
+
8
+ ## 🌟 核心特性
9
+
10
+ 1. **零鉴权配置,开箱即用**:
11
+ - 自动复用宿主机已通过 `gh auth login` 认证的登录凭据(OAuth Token / 系统 Keyring),无需在 DSH 中存储任何明文 GitHub PAT。
12
+ 2. **工作区目录深度亲和 (CWD 绑定)**:
13
+ - 自动继承当前会话所在的 Workspace 物理目录,`gh` 自动识别当前 Git 仓库与本地工作分支,无需 Agent 每次传参 `owner/repo`。
14
+ 3. **高信噪比与 Token 防溢出治理**:
15
+ - 拒绝 70+ 个碎片化微型工具撑爆上下文,聚合为 **5 大领域核心 Tool**。
16
+ - 所有查询原生采用 `--json <fields>` 按需返回;对 `diff`、`log_failed` 设置安全截断保护(默认 24KB),防止模型上下文窗口溢出崩溃。
17
+ 4. **原生双重视图(驾驶舱与右侧栏)**:
18
+ - **全局驾驶舱**(主导航栏 `sidebar.panellist`,Order 20):展示账号全局仓库、本地工作区矩阵、个人待办 PR 与 Issues;
19
+ - **右侧栏工作区面板**(`sidebarRightTabs`,Order 40):在会话引导页与右侧抽屉常驻呈现当前仓库的 PR 列表、Checks 状态药丸与快捷操作。
20
+ 5. **符合 Cordis 官方微内核架构**:
21
+ - 暴露原生 `GitHubService`(`ctx.github`),允许生态其他插件复用;
22
+ - 具备 Schemastery 强类型运行时配置校验(`Config`);
23
+ - 支持领域事件系统(`github/pr:create`, `github/issue:create`)。
24
+
25
+ ---
26
+
27
+ ## 📦 项目结构
28
+
29
+ ```text
30
+ dsh-github-flow/
31
+ ├── package.json # 模块定义、Cordis bundle 声明及 peerDependencies
32
+ ├── cordis.patch.yml # DSH Profile 自动装配补丁(含默认配置项)
33
+ ├── tsconfig.json # TypeScript 编译配置
34
+ ├── README.md # 插件说明文档
35
+ ├── icon.svg # 官方矢量微渐变包图标
36
+ ├── src/
37
+ │ ├── index.ts # Host 插件主入口:apply(ctx) 生命周期、服务挂载与 Config Schema
38
+ │ ├── service.ts # Cordis 原生 GitHubService 抽象
39
+ │ ├── types.ts # 数据结构、事件定义与接口类型
40
+ │ ├── executor.ts # 底层 gh 执行引擎(execFile 参数数组隔离、防注入、超时与截断)
41
+ │ ├── commands/
42
+ │ │ └── gh.ts # 人类交互斜杠命令 (/gh status, /gh help)
43
+ │ ├── api/
44
+ │ │ └── routes.ts # 面向 Web 视口的 Host HTTP 路由 (/api/github/*,受 effect 生命周期管理)
45
+ │ ├── tools/ # Agent 模型工具集(规范化 defineTool DSL)
46
+ │ │ ├── pr.ts # github_pr: PR 查看、比对、Checks、评审与合并
47
+ │ │ ├── issue.ts # github_issue: Issue 检索、创建、追加评论、关闭
48
+ │ │ ├── run.ts # github_run: Actions CI 监控与 --log-failed 报错定位
49
+ │ │ ├── repo.ts # github_repo: 仓库元数据与跨仓库搜索
50
+ │ │ └── api.ts # github_api: 万能 REST/GraphQL 逃生门
51
+ │ └── client.ts # Client 页面扩展:注册右侧栏 tab 与主面板视口
52
+ └── lib/ # TypeScript 编译输出产物(双端 ESM)
53
+ ```
54
+
55
+ ---
56
+
57
+ ## 🛠️ Agent 工具集速查
58
+
59
+ | 工具名称 | 核心操作 (`action`) | 典型场景 |
60
+ |---|---|---|
61
+ | `github_pr` | `list`, `view`, `create`, `diff`, `checks`, `review`, `merge` | 让 Agent 查看 PR 详情、检查 CI 状态、执行代码评审、合并 PR |
62
+ | `github_issue` | `list`, `view`, `create`, `comment`, `close`, `reopen` | 查看与搜索 Issue、创建 Issue、追加排查进展评论 |
63
+ | `github_run` | `list`, `view`, `log_failed`, `rerun`, `cancel` | 查看 CI 构建流水线,特别是 `log_failed` 可直接提取报错日志供 Agent 修复 |
64
+ | `github_repo` | `view`, `search_code`, `search_repos` | 查看仓库元数据、跨仓库代码搜索 |
65
+ | `github_api` | 任意端点(如 `repos/{owner}/{repo}/releases`) | 底层直接调用 REST / GraphQL API(支持 `--jq` 预过滤) |
66
+
67
+ ---
68
+
69
+ ## 💬 人类 Slash 命令
70
+
71
+ 在 DSH 对话输入框直接使用:
72
+ - `/gh status`:检查当前 GitHub CLI 账号登录状态、授权作用域与当前本地目录关联仓库;
73
+ - `/gh help`:查看使用帮助与常见语法示例。
74
+
75
+ ---
76
+
77
+ ## ⚙️ 插件配置项 (Config)
78
+
79
+ 在 `cordis.patch.yml` 或 Profile 配置中自由覆盖如下字段:
80
+
81
+ ```yaml
82
+ - insert:
83
+ - id: github-flow
84
+ name: dsh-github-flow
85
+ config:
86
+ ghPath: 'gh' # GitHub CLI 程序路径(内网或特定环境可指定绝对路径)
87
+ defaultTimeoutMs: 30000 # 命令执行超时时间(毫秒)
88
+ maxOutputChars: 24000 # 单次命令最大返回字符数(防 Token 溢出保护)
89
+ cacheTtlMs: 15000 # 界面概览数据缓存时间(毫秒)
90
+ defaultListLimit: 20 # 默认查询列表返回条数
91
+ ```
92
+
93
+ ---
94
+
95
+ ## 🔌 生态服务扩展 (Cordis Service)
96
+
97
+ 其他 DSH 插件可通过依赖注入复用本插件的能力:
98
+
99
+ ```ts
100
+ import type { Context } from '@deepseek-ai/cordis';
101
+
102
+ export const inject = ['github'];
103
+
104
+ export function apply(ctx: Context) {
105
+ // 通过 ctx.github 直接调用底层安全执行引擎
106
+ const auth = await ctx.github.checkAuth();
107
+ const repo = await ctx.github.getRepoMetadata();
108
+ }
109
+ ```
110
+
111
+ ---
112
+
113
+ ## 🖥️ 页面安装与体验
114
+
115
+ 1. **依赖前提**:
116
+ 宿主机已安装 GitHub CLI (`gh`) 并已登录:
117
+ ```bash
118
+ gh auth status
119
+ ```
120
+ 2. **在 DSH 中安装与挂载**:
121
+ 通过 DSH Profile 配置加载本插件 bundle:
122
+ ```yaml
123
+ # cordis.patch.yml
124
+ - insert:
125
+ - id: github-flow
126
+ name: dsh-github-flow
127
+ ```
128
+ 3. **界面体验**:
129
+ - **右侧栏**:在右侧抽屉常驻呈现当前仓库的 PR、CI 状态药丸与快捷操作按钮;
130
+ - **主视口**:左侧导航栏点击 GitHub 图标进入全局驾驶舱。
@@ -0,0 +1,7 @@
1
+ - insert:
2
+ - id: github-flow
3
+ name: dsh-github-flow
4
+ config:
5
+ defaultTimeoutMs: 30000
6
+ maxOutputChars: 24000
7
+ cacheTtlMs: 15000
package/icon.svg ADDED
@@ -0,0 +1,17 @@
1
+ <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 128 128" width="128" height="128">
2
+ <defs>
3
+ <linearGradient id="bgGrad" x1="0%" y1="0%" x2="100%" y2="100%">
4
+ <stop offset="0%" stop-color="#2ea043" />
5
+ <stop offset="50%" stop-color="#1f6feb" />
6
+ <stop offset="100%" stop-color="#8957e5" />
7
+ </linearGradient>
8
+ <filter id="shadow" x="-20%" y="-20%" width="140%" height="140%">
9
+ <feDropShadow dx="0" dy="4" stdDeviation="6" flood-color="#000" flood-opacity="0.3" />
10
+ </filter>
11
+ </defs>
12
+ <rect width="128" height="128" rx="28" fill="url(#bgGrad)" />
13
+ <rect width="126" height="126" x="1" y="1" rx="27" fill="none" stroke="rgba(255,255,255,0.2)" stroke-width="2" />
14
+ <g transform="translate(24, 24) scale(5)" filter="url(#shadow)" fill="#ffffff">
15
+ <path fill-rule="evenodd" clip-rule="evenodd" d="M8 0C3.58 0 0 3.58 0 8C0 11.54 2.29 14.53 5.47 15.59C5.87 15.66 6.02 15.42 6.02 15.21C6.02 15.02 6.01 14.39 6.01 13.72C4 14.09 3.48 13.23 3.32 12.78C3.23 12.55 2.84 11.84 2.5 11.65C2.22 11.5 1.82 11.13 2.49 11.12C3.12 11.11 3.57 11.7 3.72 11.94C4.44 13.15 5.59 12.81 6.05 12.6C6.12 12.08 6.33 11.72 6.56 11.52C4.78 11.32 2.92 10.63 2.92 7.58C2.92 6.71 3.23 5.99 3.74 5.43C3.66 5.23 3.38 4.41 3.82 3.31C3.82 3.31 4.49 3.1 6.02 4.13C6.66 3.95 7.34 3.86 8.02 3.86C8.7 3.86 9.38 3.95 10.02 4.13C11.55 3.09 12.22 3.31 12.22 3.31C12.66 4.41 12.38 5.23 12.3 5.43C12.81 5.99 13.12 6.7 13.12 7.58C13.12 10.65 11.25 11.32 9.47 11.52C9.76 11.77 10.01 12.25 10.01 13C10.01 14.08 10 14.95 10 15.21C10 15.42 10.15 15.67 10.55 15.59C13.71 14.53 16 11.53 16 8C16 3.58 12.42 0 8 0Z" />
16
+ </g>
17
+ </svg>
@@ -0,0 +1,3 @@
1
+ import type { GhExecutor } from '../executor.js';
2
+ import type { PluginConfig } from '../types.js';
3
+ export declare function registerApiRoutes(ctx: any, executor: GhExecutor, config?: PluginConfig): void;
@@ -0,0 +1,230 @@
1
+ const cacheMap = new Map();
2
+ export function registerApiRoutes(ctx, executor, config) {
3
+ const webServer = ctx.get ? ctx.get('webServer') : ctx.webServer;
4
+ if (!webServer || typeof webServer.register !== 'function')
5
+ return;
6
+ const cacheTtlMs = config?.cacheTtlMs || 15_000;
7
+ const listLimit = String(config?.defaultListLimit || 20);
8
+ function sendJson(res, status, data) {
9
+ res.statusCode = status;
10
+ res.setHeader('Content-Type', 'application/json; charset=utf-8');
11
+ res.setHeader('Cache-Control', 'no-store');
12
+ res.end(JSON.stringify(data));
13
+ }
14
+ // 接入 Cordis effect 管理生命周期,卸载或重载时注销所有路由
15
+ const registerWithEffect = (fn) => {
16
+ if (ctx.effect) {
17
+ return ctx.effect(() => {
18
+ const disposers = fn();
19
+ return () => {
20
+ disposers.forEach((dispose) => {
21
+ if (typeof dispose === 'function')
22
+ dispose();
23
+ });
24
+ };
25
+ }, 'github-flow: api routes');
26
+ }
27
+ else {
28
+ fn();
29
+ }
30
+ };
31
+ registerWithEffect(() => {
32
+ const disposers = [];
33
+ // 1. 获取全局与当前工作区仓库的 GitHub 概览
34
+ disposers.push(webServer.register({
35
+ kind: 'exact',
36
+ path: '/api/github/overview',
37
+ handler: async (req, res) => {
38
+ if (req.method !== 'GET') {
39
+ sendJson(res, 405, { ok: false, message: '仅支持 GET 请求' });
40
+ return;
41
+ }
42
+ try {
43
+ const url = new URL(req.url || '', 'http://127.0.0.1');
44
+ const cwd = url.searchParams.get('cwd') || process.cwd();
45
+ const now = Date.now();
46
+ const cached = cacheMap.get(cwd);
47
+ if (cached && now - cached.time < cacheTtlMs) {
48
+ sendJson(res, 200, { ok: true, data: cached.data, fromCache: true, cwd });
49
+ return;
50
+ }
51
+ const auth = await executor.checkAuth(cwd);
52
+ const repo = await executor.getRepoMetadata(cwd);
53
+ let pullRequests = [];
54
+ let issues = [];
55
+ let runs = [];
56
+ if (repo && auth.loggedIn) {
57
+ // 并发拉取当前工作区仓库的 PRs、Issues 和 Runs
58
+ const [prsRes, issuesRes, runsRes] = await Promise.all([
59
+ executor.run([
60
+ 'pr',
61
+ 'list',
62
+ '--json',
63
+ 'number,title,state,author,headRefName,baseRefName,isDraft,mergeable,reviewDecision,statusCheckRollup,url,updatedAt',
64
+ '-L',
65
+ '10',
66
+ ], { cwd, timeoutMs: 12_000 }),
67
+ executor.run(['issue', 'list', '--json', 'number,title,state,author,labels,assignees,url,updatedAt', '-L', '10'], { cwd, timeoutMs: 12_000 }),
68
+ executor.run(['run', 'list', '--json', 'databaseId,name,status,conclusion,event,headBranch,url,createdAt', '-L', '5'], { cwd, timeoutMs: 12_000 }),
69
+ ]);
70
+ if (prsRes.ok && Array.isArray(prsRes.data))
71
+ pullRequests = prsRes.data;
72
+ if (issuesRes.ok && Array.isArray(issuesRes.data))
73
+ issues = issuesRes.data;
74
+ if (runsRes.ok && Array.isArray(runsRes.data))
75
+ runs = runsRes.data;
76
+ }
77
+ const freshData = {
78
+ auth,
79
+ repo: repo || undefined,
80
+ pullRequests,
81
+ issues,
82
+ runs,
83
+ lastUpdated: new Date().toISOString(),
84
+ };
85
+ cacheMap.set(cwd, { data: freshData, time: now });
86
+ sendJson(res, 200, { ok: true, data: freshData, fromCache: false, cwd });
87
+ }
88
+ catch (err) {
89
+ sendJson(res, 500, { ok: false, error: err.message || '获取 GitHub 概览失败' });
90
+ }
91
+ },
92
+ }));
93
+ // 2. 强制刷新缓存
94
+ disposers.push(webServer.register({
95
+ kind: 'exact',
96
+ path: '/api/github/refresh',
97
+ handler: async (req, res) => {
98
+ cacheMap.clear();
99
+ sendJson(res, 200, { ok: true, message: '缓存已清空,下次读取将拉取最新数据' });
100
+ },
101
+ }));
102
+ // 3. 右侧栏快捷操作代理接口
103
+ disposers.push(webServer.register({
104
+ kind: 'exact',
105
+ path: '/api/github/action',
106
+ handler: async (req, res) => {
107
+ if (req.method !== 'POST') {
108
+ sendJson(res, 405, { ok: false, message: '仅支持 POST 请求' });
109
+ return;
110
+ }
111
+ let body = '';
112
+ req.on('data', (chunk) => {
113
+ body += chunk;
114
+ });
115
+ req.on('end', async () => {
116
+ try {
117
+ const payload = body ? JSON.parse(body) : {};
118
+ const cwd = payload.cwd || process.cwd();
119
+ switch (payload.action) {
120
+ case 'create_pr': {
121
+ const cmd = ['pr', 'create', '--title', payload.title || 'Update', '--body', payload.body || 'Created from DSH Rightbar'];
122
+ if (payload.draft)
123
+ cmd.push('--draft');
124
+ const r = await executor.run(cmd, { cwd });
125
+ if (!r.ok)
126
+ throw new Error(r.error);
127
+ if (ctx.emit) {
128
+ ctx.emit('github/pr:create', cwd, { title: payload.title || 'Update', url: r.rawOutput || '' });
129
+ }
130
+ sendJson(res, 200, { ok: true, message: 'PR 创建成功', url: r.rawOutput });
131
+ break;
132
+ }
133
+ case 'create_issue': {
134
+ const cmd = ['issue', 'create', '--title', payload.title || 'New Issue', '--body', payload.body || 'Created from DSH Rightbar'];
135
+ const r = await executor.run(cmd, { cwd });
136
+ if (!r.ok)
137
+ throw new Error(r.error);
138
+ if (ctx.emit) {
139
+ ctx.emit('github/issue:create', cwd, { title: payload.title || 'New Issue', url: r.rawOutput || '' });
140
+ }
141
+ sendJson(res, 200, { ok: true, message: 'Issue 创建成功', url: r.rawOutput });
142
+ break;
143
+ }
144
+ case 'open_browser': {
145
+ // 浏览器外链在 Client 端原生调用 window.open,服务端安全闭环响应
146
+ sendJson(res, 200, { ok: true, message: '外链已通过客户端安全打开' });
147
+ break;
148
+ }
149
+ default:
150
+ sendJson(res, 400, { ok: false, message: `未知的操作类型: ${payload.action}` });
151
+ }
152
+ }
153
+ catch (err) {
154
+ sendJson(res, 500, { ok: false, error: err.message || '操作执行失败' });
155
+ }
156
+ });
157
+ },
158
+ }));
159
+ // 4. 获取全局视角的驾驶舱概览数据 (不绑定单一项目)
160
+ disposers.push(webServer.register({
161
+ kind: 'exact',
162
+ path: '/api/github/global-overview',
163
+ handler: async (req, res) => {
164
+ if (req.method !== 'GET') {
165
+ sendJson(res, 405, { ok: false, message: '仅支持 GET 请求' });
166
+ return;
167
+ }
168
+ try {
169
+ const auth = await executor.checkAuth();
170
+ let userRepos = [];
171
+ let myPrs = [];
172
+ let myIssues = [];
173
+ let workspaceMatrix = [];
174
+ if (auth.loggedIn) {
175
+ // 并发拉取全局概览数据
176
+ const [reposRes, prsRes, issuesRes] = await Promise.all([
177
+ executor.run(['repo', 'list', '--limit', listLimit, '--json', 'name,nameWithOwner,description,defaultBranchRef,isPrivate,stargazerCount,updatedAt,url'], { timeoutMs: 12_000 }),
178
+ executor.run(['search', 'prs', '--author=@me', '--state=open', '--limit', '10', '--json', 'number,title,repository,url,updatedAt'], { timeoutMs: 12_000 }),
179
+ executor.run(['search', 'issues', '--author=@me', '--state=open', '--limit', '10', '--json', 'number,title,repository,url,updatedAt'], { timeoutMs: 12_000 }),
180
+ ]);
181
+ if (reposRes.ok && Array.isArray(reposRes.data)) {
182
+ userRepos = reposRes.data.map((r) => ({
183
+ name: r.name || '',
184
+ nameWithOwner: r.nameWithOwner || '',
185
+ description: r.description || '',
186
+ defaultBranch: r.defaultBranchRef?.name || 'main',
187
+ isPrivate: Boolean(r.isPrivate),
188
+ stargazerCount: r.stargazerCount || 0,
189
+ updatedAt: r.updatedAt || '',
190
+ url: r.url || `https://github.com/${r.nameWithOwner}`,
191
+ }));
192
+ }
193
+ if (prsRes.ok && Array.isArray(prsRes.data)) {
194
+ myPrs = prsRes.data;
195
+ }
196
+ if (issuesRes.ok && Array.isArray(issuesRes.data)) {
197
+ myIssues = issuesRes.data;
198
+ }
199
+ }
200
+ // 安全解析工作区矩阵:优先通过官方工作区服务获取,无服务时仅展示当前运行工作区,绝不私自侵入宿主私有文件
201
+ const currentCwd = process.cwd();
202
+ const currentRepo = await executor.getRepoMetadata(currentCwd);
203
+ workspaceMatrix = [
204
+ {
205
+ id: 'current',
206
+ title: '当前活动工作区',
207
+ path: currentCwd,
208
+ hasGit: Boolean(currentRepo),
209
+ repo: currentRepo || undefined,
210
+ sessionCount: 1,
211
+ },
212
+ ];
213
+ const globalData = {
214
+ auth,
215
+ userRepos,
216
+ workspaceMatrix,
217
+ myPrs,
218
+ myIssues,
219
+ lastUpdated: new Date().toISOString(),
220
+ };
221
+ sendJson(res, 200, { ok: true, data: globalData });
222
+ }
223
+ catch (err) {
224
+ sendJson(res, 500, { ok: false, error: err.message || '获取全局驾驶舱数据失败' });
225
+ }
226
+ },
227
+ }));
228
+ return disposers;
229
+ });
230
+ }
@@ -0,0 +1,13 @@
1
+ /**
2
+ * GitHub 官方高精度的 Invertocat 矢量路径
3
+ */
4
+ export declare const GITHUB_OCTOCAT_PATH = "M8 0C3.58 0 0 3.58 0 8C0 11.54 2.29 14.53 5.47 15.59C5.87 15.66 6.02 15.42 6.02 15.21C6.02 15.02 6.01 14.39 6.01 13.72C4 14.09 3.48 13.23 3.32 12.78C3.23 12.55 2.84 11.84 2.5 11.65C2.22 11.5 1.82 11.13 2.49 11.12C3.12 11.11 3.57 11.7 3.72 11.94C4.44 13.15 5.59 12.81 6.05 12.6C6.12 12.08 6.33 11.72 6.56 11.52C4.78 11.32 2.92 10.63 2.92 7.58C2.92 6.71 3.23 5.99 3.74 5.43C3.66 5.23 3.38 4.41 3.82 3.31C3.82 3.31 4.49 3.1 6.02 4.13C6.66 3.95 7.34 3.86 8.02 3.86C8.7 3.86 9.38 3.95 10.02 4.13C11.55 3.09 12.22 3.31 12.22 3.31C12.66 4.41 12.38 5.23 12.3 5.43C12.81 5.99 13.12 6.7 13.12 7.58C13.12 10.65 11.25 11.32 9.47 11.52C9.76 11.77 10.01 12.25 10.01 13C10.01 14.08 10 14.95 10 15.21C10 15.42 10.15 15.67 10.55 15.59C13.71 14.53 16 11.53 16 8C16 3.58 12.42 0 8 0Z";
5
+ /**
6
+ * 基础矢量 Logo
7
+ */
8
+ export declare function createOctocatSvg(size?: number, color?: string): any;
9
+ /**
10
+ * 现代暗黑科技感徽标 Artwork(用于卡片与主要入口)
11
+ * 带有暗夜流光微渐变与精致白描
12
+ */
13
+ export declare function createOctocatBadge(size?: number): any;
@@ -0,0 +1,41 @@
1
+ /**
2
+ * GitHub 官方高精度的 Invertocat 矢量路径
3
+ */
4
+ export const GITHUB_OCTOCAT_PATH = "M8 0C3.58 0 0 3.58 0 8C0 11.54 2.29 14.53 5.47 15.59C5.87 15.66 6.02 15.42 6.02 15.21C6.02 15.02 6.01 14.39 6.01 13.72C4 14.09 3.48 13.23 3.32 12.78C3.23 12.55 2.84 11.84 2.5 11.65C2.22 11.5 1.82 11.13 2.49 11.12C3.12 11.11 3.57 11.7 3.72 11.94C4.44 13.15 5.59 12.81 6.05 12.6C6.12 12.08 6.33 11.72 6.56 11.52C4.78 11.32 2.92 10.63 2.92 7.58C2.92 6.71 3.23 5.99 3.74 5.43C3.66 5.23 3.38 4.41 3.82 3.31C3.82 3.31 4.49 3.1 6.02 4.13C6.66 3.95 7.34 3.86 8.02 3.86C8.7 3.86 9.38 3.95 10.02 4.13C11.55 3.09 12.22 3.31 12.22 3.31C12.66 4.41 12.38 5.23 12.3 5.43C12.81 5.99 13.12 6.7 13.12 7.58C13.12 10.65 11.25 11.32 9.47 11.52C9.76 11.77 10.01 12.25 10.01 13C10.01 14.08 10 14.95 10 15.21C10 15.42 10.15 15.67 10.55 15.59C13.71 14.53 16 11.53 16 8C16 3.58 12.42 0 8 0Z";
5
+ /**
6
+ * 基础矢量 Logo
7
+ */
8
+ export function createOctocatSvg(size = 18, color = "currentColor") {
9
+ return React.createElement("svg", {
10
+ viewBox: "0 0 16 16",
11
+ width: size,
12
+ height: size,
13
+ fill: color,
14
+ style: { flexShrink: 0, display: "inline-block", verticalAlign: "middle" }
15
+ }, React.createElement("path", {
16
+ fillRule: "evenodd",
17
+ clipRule: "evenodd",
18
+ d: GITHUB_OCTOCAT_PATH
19
+ }));
20
+ }
21
+ /**
22
+ * 现代暗黑科技感徽标 Artwork(用于卡片与主要入口)
23
+ * 带有暗夜流光微渐变与精致白描
24
+ */
25
+ export function createOctocatBadge(size = 36) {
26
+ var iconSize = Math.round(size * 0.58);
27
+ return React.createElement("div", {
28
+ style: {
29
+ display: "flex",
30
+ alignItems: "center",
31
+ justifyContent: "center",
32
+ width: `${size}px`,
33
+ height: `${size}px`,
34
+ borderRadius: "10px",
35
+ background: "linear-gradient(135deg, #2ea043 0%, #1f6feb 100%)", // GitHub 科技绿蓝微渐变
36
+ boxShadow: "0 2px 8px rgba(31, 111, 235, 0.25), inset 0 1px 0 rgba(255, 255, 255, 0.2)",
37
+ flexShrink: 0,
38
+ position: "relative"
39
+ }
40
+ }, createOctocatSvg(iconSize, "#ffffff"));
41
+ }
@@ -0,0 +1,3 @@
1
+ export declare const name = "dsh-github-flow";
2
+ export declare const inject: readonly ['slots'];
3
+ export declare function apply(ctx: any): void;
@@ -0,0 +1,36 @@
1
+ import { GITHUB_PANEL_CSS } from './style.js';
2
+ import { createGithubPanelComponent } from './panel.js';
3
+ export const name = 'dsh-github-flow';
4
+ export const inject = ['slots'];
5
+ export function apply(ctx) {
6
+ // 1. 注入样式
7
+ if (typeof styles !== 'undefined' && styles.insert) {
8
+ styles.insert(GITHUB_PANEL_CSS);
9
+ }
10
+ else if (typeof document !== 'undefined') {
11
+ const styleEl = document.createElement('style');
12
+ styleEl.textContent = GITHUB_PANEL_CSS;
13
+ document.head.appendChild(styleEl);
14
+ }
15
+ // 2. 侧边栏图标组件
16
+ function GithubSidebarIcon() {
17
+ return React.createElement('div', { className: 'dsh-github-icon', title: 'GitHub 工作台' }, React.createElement('svg', { viewBox: '0 0 16 16', width: 18, height: 18, fill: 'currentColor' }, React.createElement('path', {
18
+ d: 'M8 0c4.42 0 8 3.58 8 8a8.013 8.013 0 0 1-5.45 7.59c-.4.08-.55-.17-.55-.38 0-.27.01-1.13.01-2.2 0-.75-.25-1.23-.54-1.48 1.78-.2 3.65-.88 3.65-3.95 0-.88-.31-1.59-.82-2.15.08-.2.36-1.02-.08-2.12 0 0-.67-.22-2.2.82-.64-.18-1.32-.27-2-.27-.68 0-1.36.09-2 .27-1.53-1.03-2.2-.82-2.2-.82-.44 1.1-.16 1.92-.08 2.12-.51.56-.82 1.28-.82 2.15 0 3.06 1.86 3.75 3.64 3.95-.23.2-.44.55-.51 1.07-.46.21-1.61.55-2.33-.66-.15-.24-.6-.83-1.23-.82-.67.01-.27.38.01.53.34.19.73.9.82 1.13.16.45.68 1.31 2.69.94 0 .67.01 1.3.01 1.49 0 .21-.15.45-.55.38A7.995 7.995 0 0 1 0 8c0-4.42 3.58-8 8-8Z',
19
+ })));
20
+ }
21
+ // 3. 注册到 sidebar.panellist(排在 schedules 自动任务后,workspaces 工作区前)
22
+ if (ctx.slots && typeof ctx.slots.register === 'function') {
23
+ ctx.slots.register({
24
+ slot: 'sidebar.panellist',
25
+ id: 'github',
26
+ order: 20,
27
+ label: 'GitHub',
28
+ }, GithubSidebarIcon);
29
+ // 4. 注册中央主视图(main slot, key: 'github')
30
+ const GithubWorkspacePanel = createGithubPanelComponent();
31
+ ctx.slots.register({
32
+ slot: 'main',
33
+ key: 'github',
34
+ }, GithubWorkspacePanel);
35
+ }
36
+ }
@@ -0,0 +1 @@
1
+ export declare function createGithubPanelComponent(): () => any;
@@ -0,0 +1,75 @@
1
+ export function createGithubPanelComponent() {
2
+ return function GithubWorkspacePanel() {
3
+ const [loading, setLoading] = React.useState(true);
4
+ const [data, setData] = React.useState(null);
5
+ const [error, setError] = React.useState(null);
6
+ const loadData = React.useCallback(async (forceRefresh = false) => {
7
+ setLoading(true);
8
+ setError(null);
9
+ try {
10
+ if (forceRefresh) {
11
+ await fetch('/api/github/refresh', { method: 'POST' }).catch(() => { });
12
+ }
13
+ const res = await fetch('/api/github/overview');
14
+ const json = await res.json();
15
+ if (json.ok && json.data) {
16
+ setData(json.data);
17
+ }
18
+ else {
19
+ setError(json.error || json.message || '获取 GitHub 概览失败');
20
+ }
21
+ }
22
+ catch (err) {
23
+ setError(err.message || '网络连接异常');
24
+ }
25
+ finally {
26
+ setLoading(false);
27
+ }
28
+ }, []);
29
+ React.useEffect(() => {
30
+ loadData(false);
31
+ // 60秒自动温和轮询
32
+ const timer = setInterval(() => loadData(false), 60_000);
33
+ return () => clearInterval(timer);
34
+ }, [loadData]);
35
+ const h = React.createElement;
36
+ // 头部区域
37
+ const headerEl = h('div', { className: 'dsh-github-header' }, h('div', { className: 'dsh-github-title' }, h('svg', { viewBox: '0 0 16 16', width: 22, height: 22, fill: 'currentColor' }, h('path', {
38
+ d: 'M8 0c4.42 0 8 3.58 8 8a8.013 8.013 0 0 1-5.45 7.59c-.4.08-.55-.17-.55-.38 0-.27.01-1.13.01-2.2 0-.75-.25-1.23-.54-1.48 1.78-.2 3.65-.88 3.65-3.95 0-.88-.31-1.59-.82-2.15.08-.2.36-1.02-.08-2.12 0 0-.67-.22-2.2.82-.64-.18-1.32-.27-2-.27-.68 0-1.36.09-2 .27-1.53-1.03-2.2-.82-2.2-.82-.44 1.1-.16 1.92-.08 2.12-.51.56-.82 1.28-.82 2.15 0 3.06 1.86 3.75 3.64 3.95-.23.2-.44.55-.51 1.07-.46.21-1.61.55-2.33-.66-.15-.24-.6-.83-1.23-.82-.67.01-.27.38.01.53.34.19.73.9.82 1.13.16.45.68 1.31 2.69.94 0 .67.01 1.3.01 1.49 0 .21-.15.45-.55.38A7.995 7.995 0 0 1 0 8c0-4.42 3.58-8 8-8Z',
39
+ })), h('h2', null, 'GitHub 工作台')), h('button', {
40
+ className: 'dsh-github-refresh-btn',
41
+ onClick: () => loadData(true),
42
+ disabled: loading,
43
+ }, loading ? '刷新中...' : '🔄 刷新状态'));
44
+ if (loading && !data) {
45
+ return h('div', { className: 'dsh-github-panel' }, headerEl, h('div', { className: 'dsh-github-empty' }, '正在连接 GitHub CLI 并获取仓库状态...'));
46
+ }
47
+ if (error && !data) {
48
+ return h('div', { className: 'dsh-github-panel' }, headerEl, h('div', { className: 'dsh-github-empty', style: { borderColor: '#f85149', color: '#f85149' } }, `⚠️ ${error}`));
49
+ }
50
+ const auth = data?.auth;
51
+ const repo = data?.repo;
52
+ const prs = data?.pullRequests || [];
53
+ const issues = data?.issues || [];
54
+ const runs = data?.runs || [];
55
+ // 概览卡片区
56
+ const cardsEl = h('div', { className: 'dsh-github-overview-cards' }, h('div', { className: 'dsh-github-card' }, h('div', { className: 'dsh-github-card-title' }, '认证账号'), h('div', { className: 'dsh-github-card-value' }, auth?.loggedIn ? `👤 ${auth.user || '已登录'}` : '❌ 未登录', auth?.loggedIn ? h('span', { className: 'dsh-github-badge dsh-badge-green' }, 'Active') : null)), h('div', { className: 'dsh-github-card' }, h('div', { className: 'dsh-github-card-title' }, '当前关联仓库'), h('div', { className: 'dsh-github-card-value' }, repo
57
+ ? h('a', { href: repo.url, target: '_blank', style: { color: 'inherit', textDecoration: 'none' } }, repo.nameWithOwner)
58
+ : '未关联 GitHub 仓库')), h('div', { className: 'dsh-github-card' }, h('div', { className: 'dsh-github-card-title' }, '待审 PR / 活跃任务'), h('div', { className: 'dsh-github-card-value' }, `${prs.length} PRs · ${issues.length} Issues`)));
59
+ // Pull Requests 区域
60
+ const prsSectionEl = h('div', { className: 'dsh-github-section' }, h('div', { className: 'dsh-github-section-header' }, h('h3', null, `🔀 Pull Requests (${prs.length})`)), prs.length === 0
61
+ ? h('div', { className: 'dsh-github-empty' }, '当前无开放的 Pull Request')
62
+ : h('div', { className: 'dsh-github-list' }, prs.map((pr) => h('div', { key: pr.number, className: 'dsh-github-item' }, h('div', { className: 'dsh-github-item-main' }, h('a', { className: 'dsh-github-item-title', href: pr.url, target: '_blank' }, `#${pr.number} ${pr.title}`), h('div', { className: 'dsh-github-item-meta' }, h('span', null, `由 ${pr.author?.login || '未知'} 提交`), h('span', null, `${pr.headRefName} → ${pr.baseRefName}`), pr.mergeable === 'CONFLICTING'
63
+ ? h('span', { className: 'dsh-github-badge dsh-badge-red' }, '冲突 Conflicting')
64
+ : h('span', { className: 'dsh-github-badge dsh-badge-green' }, '可合并 Mergeable'))), h('div', { className: 'dsh-github-item-actions' }, h('a', { className: 'dsh-github-btn-sm', href: pr.url, target: '_blank', style: { textDecoration: 'none' } }, '在 GitHub 打开'))))));
65
+ // GitHub Actions 区域
66
+ const runsSectionEl = h('div', { className: 'dsh-github-section' }, h('div', { className: 'dsh-github-section-header' }, h('h3', null, `⚡ GitHub Actions 最近流水线 (${runs.length})`)), runs.length === 0
67
+ ? h('div', { className: 'dsh-github-empty' }, '暂无工作流运行记录')
68
+ : h('div', { className: 'dsh-github-list' }, runs.map((r) => h('div', { key: r.databaseId, className: 'dsh-github-item' }, h('div', { className: 'dsh-github-item-main' }, h('a', { className: 'dsh-github-item-title', href: r.url, target: '_blank' }, r.name), h('div', { className: 'dsh-github-item-meta' }, h('span', null, `分支: ${r.headBranch}`), h('span', null, `触发事件: ${r.event}`), r.conclusion === 'success'
69
+ ? h('span', { className: 'dsh-github-badge dsh-badge-green' }, '成功 Success')
70
+ : r.conclusion === 'failure'
71
+ ? h('span', { className: 'dsh-github-badge dsh-badge-red' }, '失败 Failure')
72
+ : h('span', { className: 'dsh-github-badge dsh-badge-yellow' }, r.status || '进行中'))), h('div', { className: 'dsh-github-item-actions' }, h('a', { className: 'dsh-github-btn-sm', href: r.url, target: '_blank', style: { textDecoration: 'none' } }, '查看流水线'))))));
73
+ return h('div', { className: 'dsh-github-panel' }, headerEl, cardsEl, prsSectionEl, runsSectionEl);
74
+ };
75
+ }
@@ -0,0 +1 @@
1
+ export declare const GITHUB_PANEL_CSS = "\n.dsh-github-icon {\n display: flex;\n align-items: center;\n justify-content: center;\n width: 100%;\n height: 100%;\n cursor: pointer;\n color: var(--dsw-alias-label-secondary, #8c8c8c);\n transition: color 0.15s ease;\n}\n.dsh-github-icon:hover {\n color: var(--dsw-alias-label-primary, #ffffff);\n}\n.dsh-github-icon svg {\n width: 18px;\n height: 18px;\n fill: currentColor;\n}\n\n/* \u4E3B\u9762\u677F\u5BB9\u5668 */\n.dsh-github-panel {\n display: flex;\n flex-direction: column;\n width: 100%;\n height: 100%;\n overflow-y: auto;\n padding: 24px 32px;\n box-sizing: border-box;\n background: var(--dsw-alias-bg-primary, #1e1e20);\n color: var(--dsw-alias-label-primary, #ececec);\n font-family: -apple-system, BlinkMacSystemFont, \"Segoe UI\", Roboto, sans-serif;\n}\n\n/* \u9876\u90E8\u6807\u9898\u4E0E\u5DE5\u5177\u680F */\n.dsh-github-header {\n display: flex;\n align-items: center;\n justify-content: space-between;\n margin-bottom: 24px;\n padding-bottom: 16px;\n border-bottom: 1px solid var(--dsw-alias-border-l1, #333336);\n}\n.dsh-github-title {\n display: flex;\n align-items: center;\n gap: 12px;\n}\n.dsh-github-title h2 {\n margin: 0;\n font-size: 20px;\n font-weight: 600;\n}\n.dsh-github-refresh-btn {\n display: inline-flex;\n align-items: center;\n gap: 6px;\n padding: 6px 14px;\n font-size: 13px;\n border-radius: 6px;\n border: 1px solid var(--dsw-alias-border-l1, #444);\n background: var(--dsw-alias-bg-secondary, #2a2a2e);\n color: var(--dsw-alias-label-primary, #fff);\n cursor: pointer;\n transition: all 0.15s ease;\n}\n.dsh-github-refresh-btn:hover {\n background: var(--dsw-alias-interactive-bg-hover, #3a3a3e);\n}\n\n/* \u6982\u89C8\u5361\u7247\u533A */\n.dsh-github-overview-cards {\n display: grid;\n grid-template-columns: repeat(auto-fit, minmax(280px, 1fr));\n gap: 16px;\n margin-bottom: 24px;\n}\n.dsh-github-card {\n padding: 16px 20px;\n border-radius: 8px;\n border: 1px solid var(--dsw-alias-border-l1, #333336);\n background: var(--dsw-alias-bg-secondary, #252528);\n}\n.dsh-github-card-title {\n font-size: 12px;\n text-transform: uppercase;\n letter-spacing: 0.5px;\n color: var(--dsw-alias-label-tertiary, #888);\n margin-bottom: 8px;\n}\n.dsh-github-card-value {\n font-size: 16px;\n font-weight: 600;\n display: flex;\n align-items: center;\n gap: 8px;\n}\n.dsh-github-badge {\n display: inline-block;\n font-size: 11px;\n padding: 2px 8px;\n border-radius: 12px;\n font-weight: 500;\n}\n.dsh-badge-green { background: rgba(46, 160, 67, 0.2); color: #3fb950; }\n.dsh-badge-red { background: rgba(248, 81, 73, 0.2); color: #f85149; }\n.dsh-badge-yellow { background: rgba(210, 153, 34, 0.2); color: #d29922; }\n.dsh-badge-purple { background: rgba(163, 113, 247, 0.2); color: #a371f7; }\n\n/* \u6A21\u5757\u5206\u680F\u533A */\n.dsh-github-section {\n margin-bottom: 28px;\n}\n.dsh-github-section-header {\n display: flex;\n align-items: center;\n justify-content: space-between;\n margin-bottom: 12px;\n}\n.dsh-github-section-header h3 {\n margin: 0;\n font-size: 15px;\n font-weight: 600;\n}\n\n/* \u5217\u8868\u6761\u76EE */\n.dsh-github-list {\n display: flex;\n flex-direction: column;\n gap: 8px;\n}\n.dsh-github-item {\n display: flex;\n align-items: center;\n justify-content: space-between;\n padding: 12px 16px;\n border-radius: 6px;\n border: 1px solid var(--dsw-alias-border-l1, #333336);\n background: var(--dsw-alias-bg-secondary, #252528);\n transition: border-color 0.15s ease;\n}\n.dsh-github-item:hover {\n border-color: var(--dsw-alias-border-l2, #555);\n}\n.dsh-github-item-main {\n display: flex;\n flex-direction: column;\n gap: 4px;\n}\n.dsh-github-item-title {\n font-size: 14px;\n font-weight: 500;\n color: var(--dsw-alias-label-primary, #fff);\n text-decoration: none;\n}\n.dsh-github-item-title:hover {\n text-decoration: underline;\n}\n.dsh-github-item-meta {\n font-size: 12px;\n color: var(--dsw-alias-label-secondary, #8c8c8c);\n display: flex;\n gap: 12px;\n}\n.dsh-github-item-actions {\n display: flex;\n align-items: center;\n gap: 8px;\n}\n.dsh-github-btn-sm {\n font-size: 12px;\n padding: 4px 10px;\n border-radius: 4px;\n border: 1px solid var(--dsw-alias-border-l1, #444);\n background: transparent;\n color: var(--dsw-alias-label-primary, #fff);\n cursor: pointer;\n}\n.dsh-github-btn-sm:hover {\n background: var(--dsw-alias-interactive-bg-hover, #333);\n}\n.dsh-github-empty {\n padding: 24px;\n text-align: center;\n font-size: 13px;\n color: var(--dsw-alias-label-tertiary, #666);\n border: 1px dashed var(--dsw-alias-border-l1, #333);\n border-radius: 6px;\n}\n";