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.
@@ -0,0 +1,63 @@
1
+ export function registerGhCommand(ctx, executor) {
2
+ const commandsService = ctx.get ? ctx.get('commands') : ctx.commands;
3
+ if (!commandsService || typeof commandsService.register !== 'function')
4
+ return;
5
+ const doRegister = () => {
6
+ return commandsService.register({
7
+ name: 'gh',
8
+ description: 'GitHub CLI 状态与诊断快捷指令',
9
+ async handler({ rawInput }) {
10
+ const trimmed = (rawInput || '').trim();
11
+ if (!trimmed || trimmed === 'status') {
12
+ const auth = await executor.checkAuth();
13
+ const repo = await executor.getRepoMetadata();
14
+ let md = `### GitHub 集成状态\n\n`;
15
+ if (auth.loggedIn) {
16
+ md += `- **登录账号**: \`${auth.user || '已登录'}\`\n`;
17
+ md += `- **授权作用域**: ${auth.scopes?.map((s) => `\`${s}\``).join(', ') || '标准权限'}\n`;
18
+ }
19
+ else {
20
+ md += `- **登录状态**: ❌ 未登录 GitHub\n`;
21
+ md += `> 提示:请在系统终端执行 \`gh auth login\` 进行登录。\n`;
22
+ }
23
+ if (repo) {
24
+ md += `\n**当前关联仓库**:\n`;
25
+ md += `- 仓库名: [${repo.nameWithOwner}](${repo.url})\n`;
26
+ md += `- 默认分支: \`${repo.defaultBranch}\`\n`;
27
+ if (repo.description)
28
+ md += `- 简介: ${repo.description}\n`;
29
+ }
30
+ else {
31
+ md += `\n*当前目录尚未关联 Git 远程仓库或未托管在 GitHub。*\n`;
32
+ }
33
+ return {
34
+ kind: 'markdown',
35
+ text: md,
36
+ };
37
+ }
38
+ if (trimmed === 'help') {
39
+ return {
40
+ kind: 'markdown',
41
+ text: `### DSH GitHub Flow 帮助\n\n- \`/gh status\`: 检查当前 GitHub CLI 账号与仓库连接\n- \`/gh help\`: 显示此帮助信息\n\n可以在对话中直接让 Agent 操作 GitHub,如:\n- *"查看当前仓库有哪些开放的 PR"* \n- *"查看最近一次 CI 失败的原因"* \n- *"帮我给当前分支创建 PR"*`,
42
+ };
43
+ }
44
+ return {
45
+ kind: 'error',
46
+ text: `未知命令选项: /gh ${trimmed}。可用用法: /gh status, /gh help`,
47
+ };
48
+ },
49
+ });
50
+ };
51
+ if (ctx.effect) {
52
+ ctx.effect(() => {
53
+ const dispose = doRegister();
54
+ return () => {
55
+ if (typeof dispose === 'function')
56
+ dispose();
57
+ };
58
+ }, 'github-flow: gh command');
59
+ }
60
+ else {
61
+ doRegister();
62
+ }
63
+ }
@@ -0,0 +1,22 @@
1
+ import type { GhExecutionOptions, GhResult, AuthStatus, RepoMetadata, PluginConfig } from './types.js';
2
+ export declare class GhExecutor {
3
+ ghPath: string;
4
+ defaultTimeoutMs: number;
5
+ maxOutputChars: number;
6
+ constructor(config?: PluginConfig);
7
+ updateConfig(config?: PluginConfig): void;
8
+ /**
9
+ * 安全执行 gh 命令
10
+ * 采用 execFile 参数数组隔离,彻底杜绝 Shell 字符串拼接注入漏洞
11
+ */
12
+ run<T = unknown>(args: string[], options?: GhExecutionOptions): Promise<GhResult<T>>;
13
+ /**
14
+ * 检查宿主机当前 GitHub CLI 的认证状态与账号信息
15
+ */
16
+ checkAuth(cwd?: string): Promise<AuthStatus>;
17
+ /**
18
+ * 获取当前目录(工作区)关联的远程仓库元数据
19
+ * 采用 gh repo view + 本地 git remote 双保险机制,彻底杜绝已配置远程却误判未关联的问题
20
+ */
21
+ getRepoMetadata(cwd?: string): Promise<RepoMetadata | null>;
22
+ }
@@ -0,0 +1,207 @@
1
+ import { execFile } from 'node:child_process';
2
+ import { promisify } from 'node:util';
3
+ const execFileAsync = promisify(execFile);
4
+ export class GhExecutor {
5
+ ghPath;
6
+ defaultTimeoutMs;
7
+ maxOutputChars;
8
+ constructor(config = {}) {
9
+ this.ghPath = config.ghPath || 'gh';
10
+ this.defaultTimeoutMs = config.defaultTimeoutMs || 30_000;
11
+ this.maxOutputChars = config.maxOutputChars || 24_000;
12
+ }
13
+ updateConfig(config = {}) {
14
+ if (config.ghPath)
15
+ this.ghPath = config.ghPath;
16
+ if (config.defaultTimeoutMs)
17
+ this.defaultTimeoutMs = config.defaultTimeoutMs;
18
+ if (config.maxOutputChars)
19
+ this.maxOutputChars = config.maxOutputChars;
20
+ }
21
+ /**
22
+ * 安全执行 gh 命令
23
+ * 采用 execFile 参数数组隔离,彻底杜绝 Shell 字符串拼接注入漏洞
24
+ */
25
+ async run(args, options = {}) {
26
+ const cwd = options.cwd || process.cwd();
27
+ const timeout = options.timeoutMs || this.defaultTimeoutMs;
28
+ try {
29
+ const { stdout, stderr } = await execFileAsync(this.ghPath, args, {
30
+ cwd,
31
+ timeout,
32
+ maxBuffer: 15 * 1024 * 1024, // 15MB 进程缓冲
33
+ env: {
34
+ ...process.env,
35
+ GH_NO_UPDATE_NOTIFIER: '1', // 禁用 CLI 版本更新检查,避免污染 stdout
36
+ NO_COLOR: '1', // 禁用 ANSI 颜色转义字符
37
+ ...options.env,
38
+ },
39
+ });
40
+ let rawOutput = (stdout || '').trim();
41
+ if (!rawOutput && stderr) {
42
+ rawOutput = stderr.trim();
43
+ }
44
+ let truncated = false;
45
+ if (rawOutput.length > this.maxOutputChars) {
46
+ // 保留前 1/3 字符和尾部 1/2 字符,保留开头上下文和错误尾部
47
+ const headLen = Math.floor(this.maxOutputChars * 0.35);
48
+ const tailLen = Math.floor(this.maxOutputChars * 0.55);
49
+ const head = rawOutput.slice(0, headLen);
50
+ const tail = rawOutput.slice(-tailLen);
51
+ rawOutput = `${head}\n\n... [⚠️ 提示:内容过长已由 DSH GitHub 插件截断中间部分,当前截取前 ${Math.round(headLen / 1024)}KB 与后 ${Math.round(tailLen / 1024)}KB] ...\n\n${tail}`;
52
+ truncated = true;
53
+ }
54
+ // 如果未被截断且命令带有 --json 或包含 api,尝试解析为 JSON
55
+ if (!options.rawText && !truncated && (args.includes('--json') || args.includes('api'))) {
56
+ try {
57
+ const parsed = JSON.parse(rawOutput);
58
+ return { ok: true, data: parsed, rawOutput, truncated: false };
59
+ }
60
+ catch {
61
+ // 部分特殊命令可能输出空数组或文本,降级为 rawOutput
62
+ return { ok: true, rawOutput, truncated };
63
+ }
64
+ }
65
+ return { ok: true, rawOutput, truncated };
66
+ }
67
+ catch (err) {
68
+ let friendlyError = err.message || '执行失败';
69
+ if (err.code === 'ENOENT') {
70
+ friendlyError = '系统未检测到 GitHub CLI (gh)。请先在系统终端安装: Windows 运行 `winget install GitHub.cli`,macOS 运行 `brew install gh`。';
71
+ }
72
+ else if (err.killed && err.signal === 'SIGTERM') {
73
+ friendlyError = `GitHub CLI 命令执行超时 (${timeout}ms)。请检查网络连通性。`;
74
+ }
75
+ else if (err.stderr) {
76
+ const stderrText = String(err.stderr).trim();
77
+ if (stderrText.includes('gh auth login') || stderrText.includes('authentication required')) {
78
+ friendlyError = 'GitHub 尚未登录。请在系统终端执行 `gh auth login`,或在聊天框输入 `/gh status` 查看诊断。';
79
+ }
80
+ else {
81
+ friendlyError = stderrText;
82
+ }
83
+ }
84
+ return {
85
+ ok: false,
86
+ error: friendlyError,
87
+ };
88
+ }
89
+ }
90
+ /**
91
+ * 检查宿主机当前 GitHub CLI 的认证状态与账号信息
92
+ */
93
+ async checkAuth(cwd) {
94
+ const res = await this.run(['auth', 'status'], { cwd, timeoutMs: 10_000, rawText: true });
95
+ const raw = (res.rawOutput || '') + '\n' + (res.error || '');
96
+ const loggedIn = raw.includes('Logged in to') || raw.includes('✓');
97
+ // 只要已登录或有常规输出,则必然安装了 gh
98
+ const isNotInstalled = Boolean(res.error && res.error.includes('系统未检测到 GitHub CLI'));
99
+ const installed = loggedIn || !isNotInstalled;
100
+ if (!installed) {
101
+ return {
102
+ installed: false,
103
+ loggedIn: false,
104
+ platform: process.platform,
105
+ raw: res.error,
106
+ };
107
+ }
108
+ if (!loggedIn) {
109
+ return {
110
+ installed: true,
111
+ loggedIn: false,
112
+ platform: process.platform,
113
+ raw,
114
+ };
115
+ }
116
+ // 解析用户名
117
+ let user;
118
+ const accountMatch = raw.match(/account\s+([A-Za-z0-9_-]+)/i);
119
+ if (accountMatch) {
120
+ user = accountMatch[1];
121
+ }
122
+ // 解析 Token scopes
123
+ let scopes;
124
+ const scopesMatch = raw.match(/Token scopes:\s*([^\r\n]+)/i);
125
+ if (scopesMatch) {
126
+ scopes = scopesMatch[1]
127
+ .split(',')
128
+ .map((s) => s.replace(/['"`]/g, '').trim())
129
+ .filter(Boolean);
130
+ }
131
+ return {
132
+ installed: true,
133
+ loggedIn: true,
134
+ platform: process.platform,
135
+ user,
136
+ host: 'github.com',
137
+ activeAccount: raw.includes('Active account: true'),
138
+ scopes,
139
+ raw,
140
+ };
141
+ }
142
+ /**
143
+ * 获取当前目录(工作区)关联的远程仓库元数据
144
+ * 采用 gh repo view + 本地 git remote 双保险机制,彻底杜绝已配置远程却误判未关联的问题
145
+ */
146
+ async getRepoMetadata(cwd) {
147
+ const targetCwd = cwd || process.cwd();
148
+ // 1. 优先尝试由 gh 直接根据当前上下文识别
149
+ const res = await this.run(['repo', 'view', '--json', 'nameWithOwner,name,owner,defaultBranchRef,description,isPrivate,url'], { cwd: targetCwd, timeoutMs: 10_000 });
150
+ if (res.ok && res.data && res.data.nameWithOwner) {
151
+ const d = res.data;
152
+ return {
153
+ nameWithOwner: d.nameWithOwner || '',
154
+ name: d.name || '',
155
+ owner: d.owner?.login || '',
156
+ defaultBranch: d.defaultBranchRef?.name || 'main',
157
+ description: d.description || '',
158
+ isPrivate: Boolean(d.isPrivate),
159
+ url: d.url || `https://github.com/${d.nameWithOwner}`,
160
+ };
161
+ }
162
+ // 2. 双保险兜底:如果 gh 未能自动推断(如当前分支未推送到 upstream),直接解析本地 git remote
163
+ try {
164
+ const { execFile } = await import('node:child_process');
165
+ const { promisify } = await import('node:util');
166
+ const execAsync = promisify(execFile);
167
+ const { stdout: remoteOut } = await execAsync('git', ['remote', '-v'], { cwd: targetCwd });
168
+ if (remoteOut) {
169
+ // 匹配 github.com 格式:git@github.com:owner/repo.git 或 https://github.com/owner/repo.git
170
+ const match = remoteOut.match(/github\.com[:/]([a-zA-Z0-9_.-]+)\/([a-zA-Z0-9_.-]+?)(?:\.git)?(?:\s|\()/i);
171
+ if (match) {
172
+ const owner = match[1];
173
+ const repoName = match[2];
174
+ const nameWithOwner = `${owner}/${repoName}`;
175
+ // 使用提取出的精确 owner/repo 向 gh 获取元数据
176
+ const directRes = await this.run(['repo', 'view', nameWithOwner, '--json', 'nameWithOwner,name,owner,defaultBranchRef,description,isPrivate,url'], { cwd: targetCwd, timeoutMs: 10_000 });
177
+ if (directRes.ok && directRes.data && directRes.data.nameWithOwner) {
178
+ const d = directRes.data;
179
+ return {
180
+ nameWithOwner: d.nameWithOwner || nameWithOwner,
181
+ name: d.name || repoName,
182
+ owner: d.owner?.login || owner,
183
+ defaultBranch: d.defaultBranchRef?.name || 'main',
184
+ description: d.description || '',
185
+ isPrivate: Boolean(d.isPrivate),
186
+ url: d.url || `https://github.com/${nameWithOwner}`,
187
+ };
188
+ }
189
+ // 若离线或网络受限,由本地 git remote 配置兜底返回
190
+ return {
191
+ nameWithOwner,
192
+ name: repoName,
193
+ owner,
194
+ defaultBranch: 'main',
195
+ description: '已成功关联本地 Git 远程仓库',
196
+ isPrivate: false,
197
+ url: `https://github.com/${nameWithOwner}`,
198
+ };
199
+ }
200
+ }
201
+ }
202
+ catch {
203
+ // 本地无 git 或无 remote 配置
204
+ }
205
+ return null;
206
+ }
207
+ }
package/lib/index.d.ts ADDED
@@ -0,0 +1,17 @@
1
+ import Schema from '@deepseek-ai/schemastery';
2
+ import type { PluginConfig } from './types.js';
3
+ export declare const name = "dsh-github-flow";
4
+ /**
5
+ * 仅将核心基础服务作为硬性依赖,解绑 webServer 与 commands。
6
+ * 在无 Web 界面环境(如 Headless、纯终端、CI 自动化模式)下也能即刻激活,杜绝 PENDING 卡死。
7
+ */
8
+ export declare const inject: readonly ['tools'];
9
+ /**
10
+ * 遵循官方 Cordis 规范导出的强类型配置接口与运行时校验器
11
+ */
12
+ export interface Config extends PluginConfig {
13
+ }
14
+ export declare const Config: Schema<Config>;
15
+ export declare function apply(ctx: any, config?: Config): void;
16
+ export { GitHubService } from './service.js';
17
+ export * from './types.js';
package/lib/index.js ADDED
@@ -0,0 +1,57 @@
1
+ import Schema from '@deepseek-ai/schemastery';
2
+ import { GhExecutor } from './executor.js';
3
+ import { GitHubService } from './service.js';
4
+ import { createPrTool } from './tools/pr.js';
5
+ import { createIssueTool } from './tools/issue.js';
6
+ import { createRunTool } from './tools/run.js';
7
+ import { createRepoTool } from './tools/repo.js';
8
+ import { createApiTool } from './tools/api.js';
9
+ import { registerGhCommand } from './commands/gh.js';
10
+ import { registerApiRoutes } from './api/routes.js';
11
+ export const name = 'dsh-github-flow';
12
+ /**
13
+ * 仅将核心基础服务作为硬性依赖,解绑 webServer 与 commands。
14
+ * 在无 Web 界面环境(如 Headless、纯终端、CI 自动化模式)下也能即刻激活,杜绝 PENDING 卡死。
15
+ */
16
+ export const inject = ['tools'];
17
+ export const Config = Schema.object({
18
+ ghPath: Schema.string().default('gh').description('GitHub CLI (gh) 二进制可执行文件路径'),
19
+ defaultTimeoutMs: Schema.number().default(30000).description('CLI 命令执行超时毫秒数'),
20
+ maxOutputChars: Schema.number().default(24000).description('防止 Token 溢出的单次截断安全阈值字符数'),
21
+ cacheTtlMs: Schema.number().default(15000).description('Web API 概览数据缓存有效时长 (ms)'),
22
+ defaultListLimit: Schema.number().default(20).description('默认查询列表返回条数'),
23
+ });
24
+ export function apply(ctx, config = {}) {
25
+ // 1. 初始化具备动态配置能力的执行引擎
26
+ const executor = new GhExecutor(config);
27
+ // 2. 将核心能力提升挂载为 Cordis 原生微内核服务 (ctx.github)
28
+ new GitHubService(ctx, executor);
29
+ // 3. 注册 Agent 模型工具集(5 大核心领域工具)
30
+ if (ctx.tools && typeof ctx.tools.register === 'function') {
31
+ ctx.tools.register(createPrTool(executor));
32
+ ctx.tools.register(createIssueTool(executor));
33
+ ctx.tools.register(createRunTool(executor));
34
+ ctx.tools.register(createRepoTool(executor));
35
+ ctx.tools.register(createApiTool(executor));
36
+ }
37
+ // 4. 可选依赖:命令服务(存在时动态激活,不阻塞主流程)
38
+ if (ctx.inject && typeof ctx.inject === 'function') {
39
+ ctx.inject(['commands'], (innerCtx) => {
40
+ registerGhCommand(innerCtx, executor);
41
+ });
42
+ }
43
+ else if (ctx.commands) {
44
+ registerGhCommand(ctx, executor);
45
+ }
46
+ // 5. 可选依赖:Web 界面服务(存在时动态注入路由并由 effect 管理生命周期)
47
+ if (ctx.inject && typeof ctx.inject === 'function') {
48
+ ctx.inject(['webServer'], (innerCtx) => {
49
+ registerApiRoutes(innerCtx, executor, config);
50
+ });
51
+ }
52
+ else if (ctx.webServer) {
53
+ registerApiRoutes(ctx, executor, config);
54
+ }
55
+ }
56
+ export { GitHubService } from './service.js';
57
+ export * from './types.js';
@@ -0,0 +1,28 @@
1
+ import type { GhExecutor } from './executor.js';
2
+ import type { GhExecutionOptions, GhResult, AuthStatus, RepoMetadata } from './types.js';
3
+ declare module '@deepseek-ai/cordis' {
4
+ interface Context {
5
+ github: GitHubService;
6
+ }
7
+ }
8
+ /**
9
+ * DSH GitHub 原生微内核服务
10
+ * 允许生态内其他插件通过 ctx.github 或 inject: ['github'] 消费 GitHub CLI 能力
11
+ */
12
+ export declare class GitHubService {
13
+ private ctx;
14
+ executor: GhExecutor;
15
+ constructor(ctx: any, executor: GhExecutor);
16
+ /**
17
+ * 执行原生 gh 命令
18
+ */
19
+ run<T = unknown>(args: string[], options?: GhExecutionOptions): Promise<GhResult<T>>;
20
+ /**
21
+ * 检查宿主机 GitHub CLI 认证状态
22
+ */
23
+ checkAuth(cwd?: string): Promise<AuthStatus>;
24
+ /**
25
+ * 获取指定工作区路径下的 Git 关联元数据
26
+ */
27
+ getRepoMetadata(cwd?: string): Promise<RepoMetadata | null>;
28
+ }
package/lib/service.js ADDED
@@ -0,0 +1,34 @@
1
+ /**
2
+ * DSH GitHub 原生微内核服务
3
+ * 允许生态内其他插件通过 ctx.github 或 inject: ['github'] 消费 GitHub CLI 能力
4
+ */
5
+ export class GitHubService {
6
+ ctx;
7
+ executor;
8
+ constructor(ctx, executor) {
9
+ this.ctx = ctx;
10
+ this.executor = executor;
11
+ // 挂载到 ctx 上
12
+ if (ctx) {
13
+ ctx.github = this;
14
+ }
15
+ }
16
+ /**
17
+ * 执行原生 gh 命令
18
+ */
19
+ async run(args, options = {}) {
20
+ return this.executor.run(args, options);
21
+ }
22
+ /**
23
+ * 检查宿主机 GitHub CLI 认证状态
24
+ */
25
+ async checkAuth(cwd) {
26
+ return this.executor.checkAuth(cwd);
27
+ }
28
+ /**
29
+ * 获取指定工作区路径下的 Git 关联元数据
30
+ */
31
+ async getRepoMetadata(cwd) {
32
+ return this.executor.getRepoMetadata(cwd);
33
+ }
34
+ }
@@ -0,0 +1,2 @@
1
+ import type { GhExecutor } from '../executor.js';
2
+ export declare function createApiTool(executor: GhExecutor): any;
@@ -0,0 +1,55 @@
1
+ import { defineSafeTool } from './define.js';
2
+ export function createApiTool(executor) {
3
+ return defineSafeTool({
4
+ name: 'github_api',
5
+ description: '通过 GitHub CLI 认证环境直接调用任意 GitHub REST 或 GraphQL API 端点(兜底逃生门)。支持 --jq 过滤。',
6
+ parameters: {
7
+ type: 'object',
8
+ properties: {
9
+ endpoint: {
10
+ type: 'string',
11
+ description: 'API 端点路径,例如 repos/{owner}/{repo}/releases 或 /user',
12
+ },
13
+ method: {
14
+ type: 'string',
15
+ enum: ['GET', 'POST', 'PATCH', 'DELETE'],
16
+ description: 'HTTP 方法,默认为 GET。',
17
+ },
18
+ jq: {
19
+ type: 'string',
20
+ description: '用于在 gh 内部预先过滤结果的 jq 表达式,极大精简返回结果。',
21
+ },
22
+ fields: {
23
+ type: 'object',
24
+ description: '请求参数或请求体键值对。',
25
+ },
26
+ },
27
+ required: ['endpoint'],
28
+ },
29
+ output: {
30
+ schema: { type: 'object' },
31
+ render(args, value) {
32
+ return [{ type: 'text', text: typeof value === 'string' ? value : JSON.stringify(value, null, 2) }];
33
+ },
34
+ },
35
+ async execute(args, execContext) {
36
+ const cwd = execContext?.cwd || process.cwd();
37
+ const cmd = ['api', args.endpoint];
38
+ if (args.method && args.method !== 'GET') {
39
+ cmd.push('-X', args.method);
40
+ }
41
+ if (args.jq) {
42
+ cmd.push('--jq', args.jq);
43
+ }
44
+ if (args.fields && typeof args.fields === 'object') {
45
+ for (const [k, v] of Object.entries(args.fields)) {
46
+ cmd.push('-F', `${k}=${typeof v === 'object' ? JSON.stringify(v) : v}`);
47
+ }
48
+ }
49
+ const res = await executor.run(cmd, { cwd });
50
+ if (!res.ok)
51
+ throw new Error(res.error);
52
+ return res.data || res.rawOutput;
53
+ },
54
+ });
55
+ }
@@ -0,0 +1,6 @@
1
+ /**
2
+ * DSH Tool 定义规范包装器
3
+ * 优先使用 @deepseek-ai/dsh-tools 的 defineTool DSL
4
+ * 在独立环境无此包时提供标准的运行时安全包装
5
+ */
6
+ export declare function defineSafeTool(spec: any): any;
@@ -0,0 +1,15 @@
1
+ /**
2
+ * DSH Tool 定义规范包装器
3
+ * 优先使用 @deepseek-ai/dsh-tools 的 defineTool DSL
4
+ * 在独立环境无此包时提供标准的运行时安全包装
5
+ */
6
+ export function defineSafeTool(spec) {
7
+ try {
8
+ const dshTools = globalThis.__DSH_TOOLS__;
9
+ if (dshTools && typeof dshTools.defineTool === 'function') {
10
+ return dshTools.defineTool(spec);
11
+ }
12
+ }
13
+ catch (e) { }
14
+ return spec;
15
+ }
@@ -0,0 +1,2 @@
1
+ import type { GhExecutor } from '../executor.js';
2
+ export declare function createIssueTool(executor: GhExecutor): any;
@@ -0,0 +1,109 @@
1
+ import { defineSafeTool } from './define.js';
2
+ export function createIssueTool(executor) {
3
+ return defineSafeTool({
4
+ name: 'github_issue',
5
+ description: '管理 GitHub Issues。支持查看 Issue 列表、详情、创建新 Issue、追加评论以及关闭 Issue。',
6
+ parameters: {
7
+ type: 'object',
8
+ properties: {
9
+ action: {
10
+ type: 'string',
11
+ enum: ['list', 'view', 'create', 'comment', 'close', 'reopen'],
12
+ description: 'Issue 操作类型。',
13
+ },
14
+ issue_number: {
15
+ type: 'number',
16
+ description: 'Issue 编号(view, comment, close, reopen 时使用)。',
17
+ },
18
+ repo: {
19
+ type: 'string',
20
+ description: '目标仓库 [owner/repo],留空则识别当前仓库。',
21
+ },
22
+ title: {
23
+ type: 'string',
24
+ description: 'Issue 标题(create 时使用)。',
25
+ },
26
+ body: {
27
+ type: 'string',
28
+ description: 'Issue 描述正文或追加评论内容。',
29
+ },
30
+ labels: {
31
+ type: 'array',
32
+ items: { type: 'string' },
33
+ description: '标签名称列表。',
34
+ },
35
+ assignees: {
36
+ type: 'array',
37
+ items: { type: 'string' },
38
+ description: '被指派人的 GitHub 登录名列表。',
39
+ },
40
+ },
41
+ required: ['action'],
42
+ },
43
+ output: {
44
+ schema: { type: 'object' },
45
+ render(args, value) {
46
+ return [{ type: 'text', text: typeof value === 'string' ? value : JSON.stringify(value, null, 2) }];
47
+ },
48
+ },
49
+ async execute(args, execContext) {
50
+ const cwd = execContext?.cwd || process.cwd();
51
+ const repoArgs = args.repo ? ['-R', args.repo] : [];
52
+ switch (args.action) {
53
+ case 'list': {
54
+ const res = await executor.run(['issue', 'list', ...repoArgs, '--json', 'number,title,state,author,labels,updatedAt', '-L', '20'], { cwd });
55
+ if (!res.ok)
56
+ throw new Error(res.error);
57
+ return res.data || res.rawOutput;
58
+ }
59
+ case 'view': {
60
+ if (!args.issue_number)
61
+ throw new Error('view 操作必须提供 issue_number');
62
+ const res = await executor.run(['issue', 'view', String(args.issue_number), ...repoArgs, '--json', 'number,title,body,state,author,labels,assignees,comments,url'], { cwd });
63
+ if (!res.ok)
64
+ throw new Error(res.error);
65
+ return res.data || res.rawOutput;
66
+ }
67
+ case 'create': {
68
+ const cmd = ['issue', 'create', ...repoArgs, '--title', args.title || '', '--body', args.body || ''];
69
+ if (args.labels && Array.isArray(args.labels) && args.labels.length > 0) {
70
+ cmd.push('--label', args.labels.join(','));
71
+ }
72
+ if (args.assignees && Array.isArray(args.assignees) && args.assignees.length > 0) {
73
+ cmd.push('--assignee', args.assignees.join(','));
74
+ }
75
+ const res = await executor.run(cmd, { cwd });
76
+ if (!res.ok)
77
+ throw new Error(res.error);
78
+ return { message: 'Issue 创建成功', url: res.rawOutput };
79
+ }
80
+ case 'comment': {
81
+ if (!args.issue_number || !args.body)
82
+ throw new Error('comment 必须提供 issue_number 和 body');
83
+ const res = await executor.run(['issue', 'comment', String(args.issue_number), ...repoArgs, '--body', args.body], { cwd });
84
+ if (!res.ok)
85
+ throw new Error(res.error);
86
+ return { message: '评论发布成功', output: res.rawOutput };
87
+ }
88
+ case 'close': {
89
+ if (!args.issue_number)
90
+ throw new Error('close 操作必须提供 issue_number');
91
+ const res = await executor.run(['issue', 'close', String(args.issue_number), ...repoArgs], { cwd });
92
+ if (!res.ok)
93
+ throw new Error(res.error);
94
+ return { message: `Issue #${args.issue_number} 已成功关闭` };
95
+ }
96
+ case 'reopen': {
97
+ if (!args.issue_number)
98
+ throw new Error('reopen 操作必须提供 issue_number');
99
+ const res = await executor.run(['issue', 'reopen', String(args.issue_number), ...repoArgs], { cwd });
100
+ if (!res.ok)
101
+ throw new Error(res.error);
102
+ return { message: `Issue #${args.issue_number} 已重新开启` };
103
+ }
104
+ default:
105
+ throw new Error(`未知的 Issue 操作: ${args.action}`);
106
+ }
107
+ },
108
+ });
109
+ }
@@ -0,0 +1,2 @@
1
+ import type { GhExecutor } from '../executor.js';
2
+ export declare function createPrTool(executor: GhExecutor): any;