neuro-cli 4.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/LICENSE +21 -0
- package/README.md +510 -0
- package/dist/advisor/advisor.d.ts +50 -0
- package/dist/advisor/advisor.js +178 -0
- package/dist/agents/base.d.ts +62 -0
- package/dist/agents/base.js +215 -0
- package/dist/agents/orchestrator.d.ts +46 -0
- package/dist/agents/orchestrator.js +192 -0
- package/dist/agents/team.d.ts +51 -0
- package/dist/agents/team.js +210 -0
- package/dist/api/models.d.ts +23 -0
- package/dist/api/models.js +514 -0
- package/dist/api/ollama.d.ts +153 -0
- package/dist/api/ollama.js +751 -0
- package/dist/api/openrouter.d.ts +55 -0
- package/dist/api/openrouter.js +223 -0
- package/dist/commands/commands.d.ts +43 -0
- package/dist/commands/commands.js +308 -0
- package/dist/config/config.d.ts +8 -0
- package/dist/config/config.js +311 -0
- package/dist/context/compaction.d.ts +54 -0
- package/dist/context/compaction.js +251 -0
- package/dist/context/custom-agents.d.ts +107 -0
- package/dist/context/custom-agents.js +397 -0
- package/dist/context/custom-tools.d.ts +120 -0
- package/dist/context/custom-tools.js +564 -0
- package/dist/context/git-checkpoint.d.ts +52 -0
- package/dist/context/git-checkpoint.js +240 -0
- package/dist/context/neuro-md.d.ts +48 -0
- package/dist/context/neuro-md.js +202 -0
- package/dist/context/neuroignore.d.ts +102 -0
- package/dist/context/neuroignore.js +441 -0
- package/dist/context/repo-map.d.ts +38 -0
- package/dist/context/repo-map.js +220 -0
- package/dist/context/skill-standard.d.ts +262 -0
- package/dist/context/skill-standard.js +1156 -0
- package/dist/context/skill-system.d.ts +75 -0
- package/dist/context/skill-system.js +578 -0
- package/dist/context/tree-sitter.d.ts +99 -0
- package/dist/context/tree-sitter.js +1956 -0
- package/dist/core/acp.d.ts +325 -0
- package/dist/core/acp.js +1498 -0
- package/dist/core/api-server.d.ts +143 -0
- package/dist/core/api-server.js +550 -0
- package/dist/core/approval.d.ts +81 -0
- package/dist/core/approval.js +432 -0
- package/dist/core/auto-compact.d.ts +127 -0
- package/dist/core/auto-compact.js +436 -0
- package/dist/core/auto-mode.d.ts +232 -0
- package/dist/core/auto-mode.js +831 -0
- package/dist/core/background-session.d.ts +166 -0
- package/dist/core/background-session.js +696 -0
- package/dist/core/cicd.d.ts +257 -0
- package/dist/core/cicd.js +1443 -0
- package/dist/core/cloud-sync.d.ts +156 -0
- package/dist/core/cloud-sync.js +582 -0
- package/dist/core/code-review.d.ts +132 -0
- package/dist/core/code-review.js +1191 -0
- package/dist/core/completion.d.ts +49 -0
- package/dist/core/completion.js +384 -0
- package/dist/core/context.d.ts +38 -0
- package/dist/core/context.js +144 -0
- package/dist/core/diff-preview.d.ts +35 -0
- package/dist/core/diff-preview.js +173 -0
- package/dist/core/doom-loop.d.ts +51 -0
- package/dist/core/doom-loop.js +179 -0
- package/dist/core/engine.d.ts +183 -0
- package/dist/core/engine.js +942 -0
- package/dist/core/extended-thinking.d.ts +103 -0
- package/dist/core/extended-thinking.js +269 -0
- package/dist/core/fallback.d.ts +54 -0
- package/dist/core/fallback.js +104 -0
- package/dist/core/git-worktree.d.ts +139 -0
- package/dist/core/git-worktree.js +614 -0
- package/dist/core/headless.d.ts +30 -0
- package/dist/core/headless.js +101 -0
- package/dist/core/i18n.d.ts +83 -0
- package/dist/core/i18n.js +739 -0
- package/dist/core/linting.d.ts +129 -0
- package/dist/core/linting.js +699 -0
- package/dist/core/model-router.d.ts +109 -0
- package/dist/core/model-router.js +581 -0
- package/dist/core/multi-model.d.ts +243 -0
- package/dist/core/multi-model.js +1099 -0
- package/dist/core/multi-session.d.ts +144 -0
- package/dist/core/multi-session.js +442 -0
- package/dist/core/multimodal.d.ts +125 -0
- package/dist/core/multimodal.js +286 -0
- package/dist/core/observability.d.ts +93 -0
- package/dist/core/observability.js +737 -0
- package/dist/core/os-sandbox.d.ts +122 -0
- package/dist/core/os-sandbox.js +1193 -0
- package/dist/core/outcome-grading.d.ts +228 -0
- package/dist/core/outcome-grading.js +1123 -0
- package/dist/core/output-styles.d.ts +57 -0
- package/dist/core/output-styles.js +382 -0
- package/dist/core/parallel-agents.d.ts +183 -0
- package/dist/core/parallel-agents.js +563 -0
- package/dist/core/plugin-bundle.d.ts +236 -0
- package/dist/core/plugin-bundle.js +887 -0
- package/dist/core/plugin-sdk.d.ts +139 -0
- package/dist/core/plugin-sdk.js +273 -0
- package/dist/core/prompt-cache.d.ts +163 -0
- package/dist/core/prompt-cache.js +599 -0
- package/dist/core/sandbox.d.ts +127 -0
- package/dist/core/sandbox.js +369 -0
- package/dist/core/scheduled-tasks.d.ts +151 -0
- package/dist/core/scheduled-tasks.js +502 -0
- package/dist/core/security-scanner.d.ts +160 -0
- package/dist/core/security-scanner.js +1494 -0
- package/dist/core/session.d.ts +83 -0
- package/dist/core/session.js +269 -0
- package/dist/core/shell-completion.d.ts +51 -0
- package/dist/core/shell-completion.js +674 -0
- package/dist/core/smart-monitor.d.ts +146 -0
- package/dist/core/smart-monitor.js +1199 -0
- package/dist/core/spec-driven.d.ts +233 -0
- package/dist/core/spec-driven.js +1485 -0
- package/dist/core/spending-warnings.d.ts +123 -0
- package/dist/core/spending-warnings.js +456 -0
- package/dist/core/sub-agent.d.ts +298 -0
- package/dist/core/sub-agent.js +1023 -0
- package/dist/core/telemetry.d.ts +157 -0
- package/dist/core/telemetry.js +412 -0
- package/dist/core/terminal-ux.d.ts +134 -0
- package/dist/core/terminal-ux.js +649 -0
- package/dist/core/testing.d.ts +146 -0
- package/dist/core/testing.js +1200 -0
- package/dist/core/types.d.ts +439 -0
- package/dist/core/types.js +6 -0
- package/dist/core/undo-redo.d.ts +112 -0
- package/dist/core/undo-redo.js +290 -0
- package/dist/core/updater.d.ts +159 -0
- package/dist/core/updater.js +608 -0
- package/dist/core/vim-mode.d.ts +151 -0
- package/dist/core/vim-mode.js +771 -0
- package/dist/core/voice.d.ts +137 -0
- package/dist/core/voice.js +538 -0
- package/dist/core/web-dashboard.d.ts +109 -0
- package/dist/core/web-dashboard.js +484 -0
- package/dist/hooks/hooks.d.ts +74 -0
- package/dist/hooks/hooks.js +160 -0
- package/dist/hooks/llm-evaluator.d.ts +165 -0
- package/dist/hooks/llm-evaluator.js +560 -0
- package/dist/index.d.ts +3 -0
- package/dist/index.js +1186 -0
- package/dist/lsp/lsp-manager.d.ts +63 -0
- package/dist/lsp/lsp-manager.js +351 -0
- package/dist/mcp/client.d.ts +133 -0
- package/dist/mcp/client.js +684 -0
- package/dist/mcp/mcp-apps.d.ts +70 -0
- package/dist/mcp/mcp-apps.js +1007 -0
- package/dist/tools/bash.d.ts +5 -0
- package/dist/tools/bash.js +195 -0
- package/dist/tools/browser.d.ts +92 -0
- package/dist/tools/browser.js +1570 -0
- package/dist/tools/extended.d.ts +6 -0
- package/dist/tools/extended.js +191 -0
- package/dist/tools/file.d.ts +10 -0
- package/dist/tools/file.js +382 -0
- package/dist/tools/github.d.ts +389 -0
- package/dist/tools/github.js +759 -0
- package/dist/tools/index.d.ts +9 -0
- package/dist/tools/index.js +40 -0
- package/dist/tools/memory.d.ts +6 -0
- package/dist/tools/memory.js +197 -0
- package/dist/tools/registry.d.ts +29 -0
- package/dist/tools/registry.js +64 -0
- package/dist/tools/web.d.ts +6 -0
- package/dist/tools/web.js +150 -0
- package/dist/ui/renderer.d.ts +97 -0
- package/dist/ui/renderer.js +279 -0
- package/dist/ui/theme.d.ts +27 -0
- package/dist/ui/theme.js +106 -0
- package/package.json +83 -0
|
@@ -0,0 +1,759 @@
|
|
|
1
|
+
// ============================================================
|
|
2
|
+
// NeuroCLI - GitHub Integration
|
|
3
|
+
// PR/Issue/Repo management via `gh` CLI and git fallbacks
|
|
4
|
+
// ============================================================
|
|
5
|
+
import { execSync } from 'child_process';
|
|
6
|
+
function execGh(args, options) {
|
|
7
|
+
const timeout = options?.timeout ?? 60000;
|
|
8
|
+
const cwd = options?.cwd ?? process.cwd();
|
|
9
|
+
try {
|
|
10
|
+
const stdout = execSync(`gh ${args}`, {
|
|
11
|
+
encoding: 'utf-8',
|
|
12
|
+
cwd,
|
|
13
|
+
timeout,
|
|
14
|
+
maxBuffer: 50 * 1024 * 1024,
|
|
15
|
+
env: { ...process.env, GH_PROMPT_DISABLED: '1', NO_COLOR: '1' },
|
|
16
|
+
});
|
|
17
|
+
return { stdout: stdout.trim(), stderr: '', exitCode: 0 };
|
|
18
|
+
}
|
|
19
|
+
catch (error) {
|
|
20
|
+
return {
|
|
21
|
+
stdout: error.stdout?.toString().trim() ?? '',
|
|
22
|
+
stderr: error.stderr?.toString().trim() ?? '',
|
|
23
|
+
exitCode: error.status ?? 1,
|
|
24
|
+
};
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
function execGit(args, options) {
|
|
28
|
+
const timeout = options?.timeout ?? 30000;
|
|
29
|
+
const cwd = options?.cwd ?? process.cwd();
|
|
30
|
+
try {
|
|
31
|
+
const stdout = execSync(`git ${args}`, {
|
|
32
|
+
encoding: 'utf-8',
|
|
33
|
+
cwd,
|
|
34
|
+
timeout,
|
|
35
|
+
maxBuffer: 10 * 1024 * 1024,
|
|
36
|
+
env: { ...process.env, NO_COLOR: '1' },
|
|
37
|
+
});
|
|
38
|
+
return { stdout: stdout.trim(), stderr: '', exitCode: 0 };
|
|
39
|
+
}
|
|
40
|
+
catch (error) {
|
|
41
|
+
return {
|
|
42
|
+
stdout: error.stdout?.toString().trim() ?? '',
|
|
43
|
+
stderr: error.stderr?.toString().trim() ?? '',
|
|
44
|
+
exitCode: error.status ?? 1,
|
|
45
|
+
};
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
function parseJSON(text) {
|
|
49
|
+
try {
|
|
50
|
+
return JSON.parse(text);
|
|
51
|
+
}
|
|
52
|
+
catch {
|
|
53
|
+
return null;
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
function checkGhAvailable() {
|
|
57
|
+
const result = execGh('--version');
|
|
58
|
+
return result.exitCode === 0;
|
|
59
|
+
}
|
|
60
|
+
function getRepoFlag() {
|
|
61
|
+
// Attempt to auto-detect the repo from the current directory
|
|
62
|
+
const result = execGit('remote get-url origin');
|
|
63
|
+
if (result.exitCode === 0 && result.stdout) {
|
|
64
|
+
// Parse owner/repo from various remote URL formats
|
|
65
|
+
const url = result.stdout;
|
|
66
|
+
// ssh://git@github.com/owner/repo.git or git@github.com:owner/repo.git
|
|
67
|
+
const match = url.match(/github\.com[/:]([^/]+\/[^/]+?)(?:\.git)?$/);
|
|
68
|
+
if (match) {
|
|
69
|
+
return `--repo ${match[1]}`;
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
return '';
|
|
73
|
+
}
|
|
74
|
+
// ---- GitHubIntegration Class ----
|
|
75
|
+
export class GitHubIntegration {
|
|
76
|
+
cwd;
|
|
77
|
+
ghAvailable = null;
|
|
78
|
+
repoFlag;
|
|
79
|
+
constructor(cwd) {
|
|
80
|
+
this.cwd = cwd ?? process.cwd();
|
|
81
|
+
this.ghAvailable = null;
|
|
82
|
+
this.repoFlag = '';
|
|
83
|
+
}
|
|
84
|
+
ensureGh() {
|
|
85
|
+
if (this.ghAvailable === null) {
|
|
86
|
+
this.ghAvailable = checkGhAvailable();
|
|
87
|
+
if (this.ghAvailable) {
|
|
88
|
+
this.repoFlag = getRepoFlag();
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
return this.ghAvailable;
|
|
92
|
+
}
|
|
93
|
+
gh(args, timeout) {
|
|
94
|
+
const repoPart = this.repoFlag ? `${this.repoFlag} ` : '';
|
|
95
|
+
return execGh(`${repoPart}${args}`, { cwd: this.cwd, timeout });
|
|
96
|
+
}
|
|
97
|
+
git(args, timeout) {
|
|
98
|
+
return execGit(args, { cwd: this.cwd, timeout });
|
|
99
|
+
}
|
|
100
|
+
// ---- Pull Requests ----
|
|
101
|
+
/**
|
|
102
|
+
* Create a pull request
|
|
103
|
+
*/
|
|
104
|
+
createPR(options) {
|
|
105
|
+
if (!this.ensureGh()) {
|
|
106
|
+
return { success: false, error: 'GitHub CLI (`gh`) is not installed or not authenticated' };
|
|
107
|
+
}
|
|
108
|
+
const flags = [
|
|
109
|
+
`--title "${options.title.replace(/"/g, '\\"')}"`,
|
|
110
|
+
`--body "${options.body.replace(/"/g, '\\"')}"`,
|
|
111
|
+
`--head "${options.head}"`,
|
|
112
|
+
];
|
|
113
|
+
if (options.base)
|
|
114
|
+
flags.push(`--base "${options.base}"`);
|
|
115
|
+
if (options.draft)
|
|
116
|
+
flags.push('--draft');
|
|
117
|
+
if (options.labels?.length)
|
|
118
|
+
flags.push(`--label ${options.labels.map(l => `"${l}"`).join(',')}`);
|
|
119
|
+
if (options.reviewers?.length)
|
|
120
|
+
flags.push(`--reviewer ${options.reviewers.map(r => `"${r}"`).join(',')}`);
|
|
121
|
+
if (options.assignees?.length)
|
|
122
|
+
flags.push(`--assignee ${options.assignees.map(a => `"${a}"`).join(',')}`);
|
|
123
|
+
const result = this.gh(`pr create ${flags.join(' ')} --json number,title,body,state,htmlUrl,headRef,headSha,baseRef,baseSha,author,createdAt,updatedAt,additions,deletions,changedFiles,commits,labels,assignees,reviewRequests,mergeable`);
|
|
124
|
+
if (result.exitCode !== 0) {
|
|
125
|
+
return { success: false, error: result.stderr || result.stdout || 'Failed to create PR' };
|
|
126
|
+
}
|
|
127
|
+
const pr = parseJSON(result.stdout);
|
|
128
|
+
if (!pr) {
|
|
129
|
+
return { success: false, error: 'Failed to parse PR creation response' };
|
|
130
|
+
}
|
|
131
|
+
return {
|
|
132
|
+
success: true,
|
|
133
|
+
pr: this.mapPRDetail(pr),
|
|
134
|
+
};
|
|
135
|
+
}
|
|
136
|
+
/**
|
|
137
|
+
* List pull requests
|
|
138
|
+
*/
|
|
139
|
+
listPRs(options) {
|
|
140
|
+
if (!this.ensureGh()) {
|
|
141
|
+
return { success: false, error: 'GitHub CLI (`gh`) is not installed or not authenticated' };
|
|
142
|
+
}
|
|
143
|
+
const opts = options ?? {};
|
|
144
|
+
const flags = ['--json number,title,body,state,htmlUrl,headRef,headSha,baseRef,baseSha,author,createdAt,updatedAt,additions,deletions,changedFiles,commits,labels,assignees,reviewRequests,mergeable'];
|
|
145
|
+
if (opts.state && opts.state !== 'all')
|
|
146
|
+
flags.push(`--state ${opts.state}`);
|
|
147
|
+
if (opts.limit)
|
|
148
|
+
flags.push(`--limit ${opts.limit}`);
|
|
149
|
+
if (opts.label)
|
|
150
|
+
flags.push(`--label "${opts.label}"`);
|
|
151
|
+
if (opts.author)
|
|
152
|
+
flags.push(`--author "${opts.author}"`);
|
|
153
|
+
if (opts.assignee)
|
|
154
|
+
flags.push(`--assignee "${opts.assignee}"`);
|
|
155
|
+
if (opts.base)
|
|
156
|
+
flags.push(`--base "${opts.base}"`);
|
|
157
|
+
if (opts.head)
|
|
158
|
+
flags.push(`--head "${opts.head}"`);
|
|
159
|
+
if (opts.sort)
|
|
160
|
+
flags.push(`--sort ${opts.sort}`);
|
|
161
|
+
if (opts.direction)
|
|
162
|
+
flags.push(`--${opts.direction}`);
|
|
163
|
+
const result = this.gh(`pr list ${flags.join(' ')}`);
|
|
164
|
+
if (result.exitCode !== 0) {
|
|
165
|
+
return { success: false, error: result.stderr || 'Failed to list PRs' };
|
|
166
|
+
}
|
|
167
|
+
const prs = parseJSON(result.stdout);
|
|
168
|
+
if (!prs) {
|
|
169
|
+
return { success: false, error: 'Failed to parse PR list response' };
|
|
170
|
+
}
|
|
171
|
+
return {
|
|
172
|
+
success: true,
|
|
173
|
+
prs: prs.map(p => this.mapPRDetail(p)),
|
|
174
|
+
};
|
|
175
|
+
}
|
|
176
|
+
/**
|
|
177
|
+
* Get PR details and diff
|
|
178
|
+
*/
|
|
179
|
+
reviewPR(prNumber) {
|
|
180
|
+
if (!this.ensureGh()) {
|
|
181
|
+
return { success: false, error: 'GitHub CLI (`gh`) is not installed or not authenticated' };
|
|
182
|
+
}
|
|
183
|
+
// Get PR details
|
|
184
|
+
const detailResult = this.gh(`pr view ${prNumber} --json number,title,body,state,url,headRef,headSha,baseRef,baseSha,author,createdAt,updatedAt,additions,deletions,changedFiles,commits,labels,assignees,reviewRequests,mergeable,reviews,comments`);
|
|
185
|
+
if (detailResult.exitCode !== 0) {
|
|
186
|
+
return { success: false, error: detailResult.stderr || 'Failed to get PR details' };
|
|
187
|
+
}
|
|
188
|
+
const detail = parseJSON(detailResult.stdout);
|
|
189
|
+
if (!detail) {
|
|
190
|
+
return { success: false, error: 'Failed to parse PR details' };
|
|
191
|
+
}
|
|
192
|
+
// Get PR diff
|
|
193
|
+
const diffResult = this.gh(`pr diff ${prNumber}`, 30000);
|
|
194
|
+
const diff = diffResult.exitCode === 0 ? diffResult.stdout : '';
|
|
195
|
+
// Get changed files
|
|
196
|
+
const filesResult = this.gh(`pr diff ${prNumber} --name-only`);
|
|
197
|
+
const fileNames = filesResult.exitCode === 0
|
|
198
|
+
? filesResult.stdout.split('\n').filter(Boolean)
|
|
199
|
+
: [];
|
|
200
|
+
// Get detailed file changes
|
|
201
|
+
const filesJsonResult = this.gh(`pr view ${prNumber} --json files`);
|
|
202
|
+
let files = [];
|
|
203
|
+
if (filesJsonResult.exitCode === 0) {
|
|
204
|
+
const filesData = parseJSON(filesJsonResult.stdout);
|
|
205
|
+
if (filesData?.files) {
|
|
206
|
+
files = filesData.files.map((f) => ({
|
|
207
|
+
filename: f.path ?? f.filename ?? '',
|
|
208
|
+
status: f.status ?? 'modified',
|
|
209
|
+
additions: f.additions ?? 0,
|
|
210
|
+
deletions: f.deletions ?? 0,
|
|
211
|
+
changes: (f.additions ?? 0) + (f.deletions ?? 0),
|
|
212
|
+
patch: f.patch,
|
|
213
|
+
}));
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
const reviews = (detail.reviews ?? []).map((r) => ({
|
|
217
|
+
user: r.author?.login ?? r.user?.login ?? 'unknown',
|
|
218
|
+
state: r.state ?? '',
|
|
219
|
+
body: r.body ?? '',
|
|
220
|
+
}));
|
|
221
|
+
const comments = (detail.comments ?? []).map((c) => ({
|
|
222
|
+
user: c.author?.login ?? c.user?.login ?? 'unknown',
|
|
223
|
+
body: c.body ?? '',
|
|
224
|
+
path: c.path ?? '',
|
|
225
|
+
line: c.line ?? 0,
|
|
226
|
+
}));
|
|
227
|
+
return {
|
|
228
|
+
success: true,
|
|
229
|
+
review: {
|
|
230
|
+
number: detail.number ?? prNumber,
|
|
231
|
+
title: detail.title ?? '',
|
|
232
|
+
body: detail.body ?? '',
|
|
233
|
+
state: detail.state ?? '',
|
|
234
|
+
url: detail.url ?? detail.htmlUrl ?? '',
|
|
235
|
+
diff,
|
|
236
|
+
commits: detail.commits ?? 0,
|
|
237
|
+
additions: detail.additions ?? 0,
|
|
238
|
+
deletions: detail.deletions ?? 0,
|
|
239
|
+
changed_files: detail.changedFiles ?? fileNames.length,
|
|
240
|
+
files,
|
|
241
|
+
reviews,
|
|
242
|
+
comments,
|
|
243
|
+
},
|
|
244
|
+
};
|
|
245
|
+
}
|
|
246
|
+
/**
|
|
247
|
+
* Merge a PR
|
|
248
|
+
*/
|
|
249
|
+
mergePR(prNumber, method) {
|
|
250
|
+
if (!this.ensureGh()) {
|
|
251
|
+
return { success: false, error: 'GitHub CLI (`gh`) is not installed or not authenticated' };
|
|
252
|
+
}
|
|
253
|
+
const mergeMethod = method ?? 'merge';
|
|
254
|
+
const result = this.gh(`pr merge ${prNumber} --${mergeMethod} --json sha,merged`);
|
|
255
|
+
if (result.exitCode !== 0) {
|
|
256
|
+
return { success: false, error: result.stderr || 'Failed to merge PR' };
|
|
257
|
+
}
|
|
258
|
+
const data = parseJSON(result.stdout);
|
|
259
|
+
return {
|
|
260
|
+
success: true,
|
|
261
|
+
result: {
|
|
262
|
+
merged: data?.merged ?? true,
|
|
263
|
+
sha: data?.sha ?? '',
|
|
264
|
+
message: `PR #${prNumber} merged using ${mergeMethod}`,
|
|
265
|
+
method: mergeMethod,
|
|
266
|
+
},
|
|
267
|
+
};
|
|
268
|
+
}
|
|
269
|
+
// ---- Issues ----
|
|
270
|
+
/**
|
|
271
|
+
* Create an issue
|
|
272
|
+
*/
|
|
273
|
+
createIssue(options) {
|
|
274
|
+
if (!this.ensureGh()) {
|
|
275
|
+
return { success: false, error: 'GitHub CLI (`gh`) is not installed or not authenticated' };
|
|
276
|
+
}
|
|
277
|
+
const flags = [
|
|
278
|
+
`--title "${options.title.replace(/"/g, '\\"')}"`,
|
|
279
|
+
`--body "${options.body.replace(/"/g, '\\"')}"`,
|
|
280
|
+
];
|
|
281
|
+
if (options.labels?.length)
|
|
282
|
+
flags.push(`--label ${options.labels.map(l => `"${l}"`).join(',')}`);
|
|
283
|
+
if (options.assignees?.length)
|
|
284
|
+
flags.push(`--assignee ${options.assignees.map(a => `"${a}"`).join(',')}`);
|
|
285
|
+
if (options.milestone)
|
|
286
|
+
flags.push(`--milestone "${options.milestone}"`);
|
|
287
|
+
const result = this.gh(`issue create ${flags.join(' ')} --json number,title,body,state,htmlUrl,author,createdAt,updatedAt,labels,assignees,comments,milestone`);
|
|
288
|
+
if (result.exitCode !== 0) {
|
|
289
|
+
return { success: false, error: result.stderr || result.stdout || 'Failed to create issue' };
|
|
290
|
+
}
|
|
291
|
+
const issue = parseJSON(result.stdout);
|
|
292
|
+
if (!issue) {
|
|
293
|
+
return { success: false, error: 'Failed to parse issue creation response' };
|
|
294
|
+
}
|
|
295
|
+
return {
|
|
296
|
+
success: true,
|
|
297
|
+
issue: this.mapIssueDetail(issue),
|
|
298
|
+
};
|
|
299
|
+
}
|
|
300
|
+
/**
|
|
301
|
+
* List issues
|
|
302
|
+
*/
|
|
303
|
+
listIssues(options) {
|
|
304
|
+
if (!this.ensureGh()) {
|
|
305
|
+
return { success: false, error: 'GitHub CLI (`gh`) is not installed or not authenticated' };
|
|
306
|
+
}
|
|
307
|
+
const opts = options ?? {};
|
|
308
|
+
const flags = ['--json number,title,body,state,htmlUrl,author,createdAt,updatedAt,labels,assignees,comments,milestone'];
|
|
309
|
+
if (opts.state && opts.state !== 'all')
|
|
310
|
+
flags.push(`--state ${opts.state}`);
|
|
311
|
+
if (opts.limit)
|
|
312
|
+
flags.push(`--limit ${opts.limit}`);
|
|
313
|
+
if (opts.label)
|
|
314
|
+
flags.push(`--label "${opts.label}"`);
|
|
315
|
+
if (opts.author)
|
|
316
|
+
flags.push(`--author "${opts.author}"`);
|
|
317
|
+
if (opts.assignee)
|
|
318
|
+
flags.push(`--assignee "${opts.assignee}"`);
|
|
319
|
+
if (opts.milestone)
|
|
320
|
+
flags.push(`--milestone "${opts.milestone}"`);
|
|
321
|
+
if (opts.sort)
|
|
322
|
+
flags.push(`--sort ${opts.sort}`);
|
|
323
|
+
if (opts.direction)
|
|
324
|
+
flags.push(`--${opts.direction}`);
|
|
325
|
+
const result = this.gh(`issue list ${flags.join(' ')}`);
|
|
326
|
+
if (result.exitCode !== 0) {
|
|
327
|
+
return { success: false, error: result.stderr || 'Failed to list issues' };
|
|
328
|
+
}
|
|
329
|
+
const issues = parseJSON(result.stdout);
|
|
330
|
+
if (!issues) {
|
|
331
|
+
return { success: false, error: 'Failed to parse issues list response' };
|
|
332
|
+
}
|
|
333
|
+
return {
|
|
334
|
+
success: true,
|
|
335
|
+
issues: issues.map(i => this.mapIssueDetail(i)),
|
|
336
|
+
};
|
|
337
|
+
}
|
|
338
|
+
/**
|
|
339
|
+
* Close an issue
|
|
340
|
+
*/
|
|
341
|
+
closeIssue(issueNumber) {
|
|
342
|
+
if (!this.ensureGh()) {
|
|
343
|
+
return { success: false, error: 'GitHub CLI (`gh`) is not installed or not authenticated' };
|
|
344
|
+
}
|
|
345
|
+
const result = this.gh(`issue close ${issueNumber}`);
|
|
346
|
+
if (result.exitCode !== 0) {
|
|
347
|
+
return { success: false, error: result.stderr || 'Failed to close issue' };
|
|
348
|
+
}
|
|
349
|
+
return { success: true };
|
|
350
|
+
}
|
|
351
|
+
/**
|
|
352
|
+
* Comment on an issue
|
|
353
|
+
*/
|
|
354
|
+
commentIssue(issueNumber, body) {
|
|
355
|
+
if (!this.ensureGh()) {
|
|
356
|
+
return { success: false, error: 'GitHub CLI (`gh`) is not installed or not authenticated' };
|
|
357
|
+
}
|
|
358
|
+
const result = this.gh(`issue comment ${issueNumber} --body "${body.replace(/"/g, '\\"')}"`);
|
|
359
|
+
if (result.exitCode !== 0) {
|
|
360
|
+
return { success: false, error: result.stderr || 'Failed to comment on issue' };
|
|
361
|
+
}
|
|
362
|
+
return { success: true };
|
|
363
|
+
}
|
|
364
|
+
// ---- Branches ----
|
|
365
|
+
/**
|
|
366
|
+
* Create a branch (falls back to git if gh is unavailable)
|
|
367
|
+
*/
|
|
368
|
+
createBranch(name, base) {
|
|
369
|
+
// Use git directly for branch creation - more reliable
|
|
370
|
+
if (base) {
|
|
371
|
+
const checkoutResult = this.git(`checkout ${base}`);
|
|
372
|
+
if (checkoutResult.exitCode !== 0) {
|
|
373
|
+
return { success: false, error: checkoutResult.stderr || `Failed to checkout base branch: ${base}` };
|
|
374
|
+
}
|
|
375
|
+
}
|
|
376
|
+
const result = this.git(`checkout -b ${name}`);
|
|
377
|
+
if (result.exitCode !== 0) {
|
|
378
|
+
return { success: false, error: result.stderr || 'Failed to create branch' };
|
|
379
|
+
}
|
|
380
|
+
// Push to remote if gh is available
|
|
381
|
+
if (this.ensureGh()) {
|
|
382
|
+
const pushResult = this.git(`push -u origin ${name}`);
|
|
383
|
+
if (pushResult.exitCode !== 0) {
|
|
384
|
+
// Branch created locally but failed to push - still success
|
|
385
|
+
return { success: true, error: `Branch created locally but push failed: ${pushResult.stderr}` };
|
|
386
|
+
}
|
|
387
|
+
}
|
|
388
|
+
return { success: true };
|
|
389
|
+
}
|
|
390
|
+
/**
|
|
391
|
+
* List branches
|
|
392
|
+
*/
|
|
393
|
+
listBranches() {
|
|
394
|
+
const result = this.git('branch -a --no-color');
|
|
395
|
+
if (result.exitCode !== 0) {
|
|
396
|
+
return { success: false, error: result.stderr || 'Failed to list branches' };
|
|
397
|
+
}
|
|
398
|
+
const branches = result.stdout
|
|
399
|
+
.split('\n')
|
|
400
|
+
.filter(Boolean)
|
|
401
|
+
.map(line => {
|
|
402
|
+
const trimmed = line.trim();
|
|
403
|
+
const isHead = trimmed.startsWith('* ');
|
|
404
|
+
const name = trimmed.replace(/^\* /, '').replace(/^remotes\/origin\//, '');
|
|
405
|
+
const isRemote = trimmed.includes('remotes/');
|
|
406
|
+
return {
|
|
407
|
+
name,
|
|
408
|
+
isHead,
|
|
409
|
+
isRemote,
|
|
410
|
+
ref: isRemote ? `refs/remotes/origin/${name}` : `refs/heads/${name}`,
|
|
411
|
+
};
|
|
412
|
+
});
|
|
413
|
+
return { success: true, branches };
|
|
414
|
+
}
|
|
415
|
+
// ---- Releases ----
|
|
416
|
+
/**
|
|
417
|
+
* Create a release
|
|
418
|
+
*/
|
|
419
|
+
createRelease(options) {
|
|
420
|
+
if (!this.ensureGh()) {
|
|
421
|
+
return { success: false, error: 'GitHub CLI (`gh`) is not installed or not authenticated' };
|
|
422
|
+
}
|
|
423
|
+
const flags = [
|
|
424
|
+
options.tagName,
|
|
425
|
+
`--title "${(options.name ?? options.tagName).replace(/"/g, '\\"')}"`,
|
|
426
|
+
];
|
|
427
|
+
if (options.body) {
|
|
428
|
+
flags.push(`--notes "${options.body.replace(/"/g, '\\"')}"`);
|
|
429
|
+
}
|
|
430
|
+
else if (options.notes) {
|
|
431
|
+
flags.push(`--notes "${options.notes.replace(/"/g, '\\"')}"`);
|
|
432
|
+
}
|
|
433
|
+
if (options.target)
|
|
434
|
+
flags.push(`--target "${options.target}"`);
|
|
435
|
+
if (options.draft)
|
|
436
|
+
flags.push('--draft');
|
|
437
|
+
if (options.prerelease)
|
|
438
|
+
flags.push('--prerelease');
|
|
439
|
+
if (options.discussionCategory)
|
|
440
|
+
flags.push(`--discussion-category "${options.discussionCategory}"`);
|
|
441
|
+
const result = this.gh(`release create ${flags.join(' ')} --json tagName,name,body,draft,prerelease,htmlUrl,createdAt,publishedAt,author,assets`);
|
|
442
|
+
if (result.exitCode !== 0) {
|
|
443
|
+
return { success: false, error: result.stderr || result.stdout || 'Failed to create release' };
|
|
444
|
+
}
|
|
445
|
+
const release = parseJSON(result.stdout);
|
|
446
|
+
if (!release) {
|
|
447
|
+
return { success: false, error: 'Failed to parse release response' };
|
|
448
|
+
}
|
|
449
|
+
return {
|
|
450
|
+
success: true,
|
|
451
|
+
release: this.mapReleaseInfo(release),
|
|
452
|
+
};
|
|
453
|
+
}
|
|
454
|
+
/**
|
|
455
|
+
* List releases
|
|
456
|
+
*/
|
|
457
|
+
listReleases() {
|
|
458
|
+
if (!this.ensureGh()) {
|
|
459
|
+
return { success: false, error: 'GitHub CLI (`gh`) is not installed or not authenticated' };
|
|
460
|
+
}
|
|
461
|
+
const result = this.gh('release list --json tagName,name,body,draft,prerelease,htmlUrl,createdAt,publishedAt,author,assets --limit 50');
|
|
462
|
+
if (result.exitCode !== 0) {
|
|
463
|
+
return { success: false, error: result.stderr || 'Failed to list releases' };
|
|
464
|
+
}
|
|
465
|
+
const releases = parseJSON(result.stdout);
|
|
466
|
+
if (!releases) {
|
|
467
|
+
return { success: false, error: 'Failed to parse releases response' };
|
|
468
|
+
}
|
|
469
|
+
return {
|
|
470
|
+
success: true,
|
|
471
|
+
releases: releases.map(r => this.mapReleaseInfo(r)),
|
|
472
|
+
};
|
|
473
|
+
}
|
|
474
|
+
// ---- Repository ----
|
|
475
|
+
/**
|
|
476
|
+
* Get repository information
|
|
477
|
+
*/
|
|
478
|
+
getRepoInfo() {
|
|
479
|
+
if (!this.ensureGh()) {
|
|
480
|
+
// Fallback: try to get basic info from git
|
|
481
|
+
return this.getRepoInfoFromGit();
|
|
482
|
+
}
|
|
483
|
+
const result = this.gh('repo view --json name,description,htmlUrl,defaultBranchRef,visibility,primaryLanguage,stargazerCount,forkCount,issues,pullRequests,watchers,createdAt,updatedAt,pushedAt,licenseInfo,repositoryTopics');
|
|
484
|
+
if (result.exitCode !== 0) {
|
|
485
|
+
return this.getRepoInfoFromGit();
|
|
486
|
+
}
|
|
487
|
+
const repo = parseJSON(result.stdout);
|
|
488
|
+
if (!repo) {
|
|
489
|
+
return this.getRepoInfoFromGit();
|
|
490
|
+
}
|
|
491
|
+
const remoteResult = this.git('remote get-url origin');
|
|
492
|
+
const full_name = this.extractRepoSlug(remoteResult.exitCode === 0 ? remoteResult.stdout : '') ?? repo.name;
|
|
493
|
+
return {
|
|
494
|
+
success: true,
|
|
495
|
+
repo: {
|
|
496
|
+
name: repo.name ?? '',
|
|
497
|
+
full_name,
|
|
498
|
+
description: repo.description ?? '',
|
|
499
|
+
html_url: repo.htmlUrl ?? '',
|
|
500
|
+
default_branch: repo.defaultBranchRef?.name ?? 'main',
|
|
501
|
+
visibility: repo.visibility ?? 'unknown',
|
|
502
|
+
language: repo.primaryLanguage?.name ?? '',
|
|
503
|
+
stars: repo.stargazerCount ?? 0,
|
|
504
|
+
forks: repo.forkCount ?? 0,
|
|
505
|
+
open_issues: (repo.issues?.totalCount ?? repo.issues ?? 0) + (repo.pullRequests?.totalCount ?? 0),
|
|
506
|
+
watchers: repo.watchers?.totalCount ?? repo.watchers ?? 0,
|
|
507
|
+
created_at: repo.createdAt ?? '',
|
|
508
|
+
updated_at: repo.updatedAt ?? '',
|
|
509
|
+
pushed_at: repo.pushedAt ?? '',
|
|
510
|
+
license: repo.licenseInfo ? { key: repo.licenseInfo.key, name: repo.licenseInfo.name } : undefined,
|
|
511
|
+
topics: (repo.repositoryTopics ?? []).map((t) => t.name ?? t.topic ?? t),
|
|
512
|
+
},
|
|
513
|
+
};
|
|
514
|
+
}
|
|
515
|
+
/**
|
|
516
|
+
* Search code in repository
|
|
517
|
+
*/
|
|
518
|
+
searchCode(query) {
|
|
519
|
+
if (!this.ensureGh()) {
|
|
520
|
+
return { success: false, error: 'GitHub CLI (`gh`) is not installed or not authenticated' };
|
|
521
|
+
}
|
|
522
|
+
// Get the repo slug for scoped search
|
|
523
|
+
const remoteResult = this.git('remote get-url origin');
|
|
524
|
+
const repoSlug = this.extractRepoSlug(remoteResult.exitCode === 0 ? remoteResult.stdout : '');
|
|
525
|
+
const scopedQuery = repoSlug ? `repo:${repoSlug} ${query}` : query;
|
|
526
|
+
const result = this.gh(`search code "${scopedQuery}" --json path,textMatches,repository`);
|
|
527
|
+
if (result.exitCode !== 0) {
|
|
528
|
+
return { success: false, error: result.stderr || 'Failed to search code' };
|
|
529
|
+
}
|
|
530
|
+
const results = parseJSON(result.stdout);
|
|
531
|
+
if (!results) {
|
|
532
|
+
return { success: false, error: 'Failed to parse search results' };
|
|
533
|
+
}
|
|
534
|
+
return {
|
|
535
|
+
success: true,
|
|
536
|
+
results: results.map(r => ({
|
|
537
|
+
name: r.path?.split('/').pop() ?? '',
|
|
538
|
+
path: r.path ?? '',
|
|
539
|
+
html_url: r.html_url ?? '',
|
|
540
|
+
repository: { full_name: r.repository?.full_name ?? r.repository?.nameWithOwner ?? '' },
|
|
541
|
+
text_matches: (r.text_matches ?? r.textMatches ?? []).map((m) => ({
|
|
542
|
+
fragment: m.fragment ?? '',
|
|
543
|
+
matches: (m.matches ?? []).map((match) => ({
|
|
544
|
+
indices: match.indices ?? [],
|
|
545
|
+
text: match.text ?? '',
|
|
546
|
+
})),
|
|
547
|
+
})),
|
|
548
|
+
})),
|
|
549
|
+
};
|
|
550
|
+
}
|
|
551
|
+
// ---- GitHub Actions ----
|
|
552
|
+
/**
|
|
553
|
+
* List GitHub Actions workflow runs
|
|
554
|
+
*/
|
|
555
|
+
getWorkflowRuns() {
|
|
556
|
+
if (!this.ensureGh()) {
|
|
557
|
+
return { success: false, error: 'GitHub CLI (`gh`) is not installed or not authenticated' };
|
|
558
|
+
}
|
|
559
|
+
const result = this.gh('run list --json databaseId,name,headBranch,headSha,status,conclusion,htmlUrl,createdAt,updatedAt,event,workflowId --limit 20');
|
|
560
|
+
if (result.exitCode !== 0) {
|
|
561
|
+
return { success: false, error: result.stderr || 'Failed to list workflow runs' };
|
|
562
|
+
}
|
|
563
|
+
const runs = parseJSON(result.stdout);
|
|
564
|
+
if (!runs) {
|
|
565
|
+
return { success: false, error: 'Failed to parse workflow runs response' };
|
|
566
|
+
}
|
|
567
|
+
return {
|
|
568
|
+
success: true,
|
|
569
|
+
runs: runs.map(r => ({
|
|
570
|
+
id: r.databaseId ?? r.id ?? 0,
|
|
571
|
+
name: r.name ?? '',
|
|
572
|
+
head_branch: r.headBranch ?? '',
|
|
573
|
+
head_sha: r.headSha ?? '',
|
|
574
|
+
status: r.status ?? '',
|
|
575
|
+
conclusion: r.conclusion ?? null,
|
|
576
|
+
html_url: r.htmlUrl ?? '',
|
|
577
|
+
created_at: r.createdAt ?? '',
|
|
578
|
+
updated_at: r.updatedAt ?? '',
|
|
579
|
+
run_number: r.runNumber ?? 0,
|
|
580
|
+
event: r.event ?? '',
|
|
581
|
+
workflow_id: r.workflowId ?? 0,
|
|
582
|
+
})),
|
|
583
|
+
};
|
|
584
|
+
}
|
|
585
|
+
/**
|
|
586
|
+
* Trigger a workflow
|
|
587
|
+
*/
|
|
588
|
+
triggerWorkflow(workflowId, ref) {
|
|
589
|
+
if (!this.ensureGh()) {
|
|
590
|
+
return { success: false, error: 'GitHub CLI (`gh`) is not installed or not authenticated' };
|
|
591
|
+
}
|
|
592
|
+
const refFlag = ref ? `--ref "${ref}"` : '';
|
|
593
|
+
const result = this.gh(`workflow run ${workflowId} ${refFlag}`.trim());
|
|
594
|
+
if (result.exitCode !== 0) {
|
|
595
|
+
return { success: false, error: result.stderr || 'Failed to trigger workflow' };
|
|
596
|
+
}
|
|
597
|
+
return { success: true };
|
|
598
|
+
}
|
|
599
|
+
/**
|
|
600
|
+
* Get workflow run status
|
|
601
|
+
*/
|
|
602
|
+
getWorkflowRunStatus(runId) {
|
|
603
|
+
if (!this.ensureGh()) {
|
|
604
|
+
return { success: false, error: 'GitHub CLI (`gh`) is not installed or not authenticated' };
|
|
605
|
+
}
|
|
606
|
+
const runResult = this.gh(`run view ${runId} --json databaseId,name,status,conclusion,htmlUrl,jobs`);
|
|
607
|
+
if (runResult.exitCode !== 0) {
|
|
608
|
+
return { success: false, error: runResult.stderr || 'Failed to get workflow run status' };
|
|
609
|
+
}
|
|
610
|
+
const run = parseJSON(runResult.stdout);
|
|
611
|
+
if (!run) {
|
|
612
|
+
return { success: false, error: 'Failed to parse workflow run status' };
|
|
613
|
+
}
|
|
614
|
+
const jobs = (run.jobs ?? []).map((j) => ({
|
|
615
|
+
id: j.databaseId ?? j.id ?? 0,
|
|
616
|
+
name: j.name ?? '',
|
|
617
|
+
status: j.status ?? '',
|
|
618
|
+
conclusion: j.conclusion ?? null,
|
|
619
|
+
started_at: j.startedAt ?? '',
|
|
620
|
+
completed_at: j.completedAt ?? null,
|
|
621
|
+
steps: (j.steps ?? []).map((s) => ({
|
|
622
|
+
name: s.name ?? '',
|
|
623
|
+
status: s.status ?? '',
|
|
624
|
+
conclusion: s.conclusion ?? null,
|
|
625
|
+
number: s.number ?? 0,
|
|
626
|
+
})),
|
|
627
|
+
}));
|
|
628
|
+
return {
|
|
629
|
+
success: true,
|
|
630
|
+
status: {
|
|
631
|
+
id: run.databaseId ?? runId,
|
|
632
|
+
name: run.name ?? '',
|
|
633
|
+
status: run.status ?? '',
|
|
634
|
+
conclusion: run.conclusion ?? null,
|
|
635
|
+
html_url: run.htmlUrl ?? '',
|
|
636
|
+
jobs,
|
|
637
|
+
},
|
|
638
|
+
};
|
|
639
|
+
}
|
|
640
|
+
// ---- Private Helpers ----
|
|
641
|
+
mapPRDetail(p) {
|
|
642
|
+
return {
|
|
643
|
+
number: p.number ?? 0,
|
|
644
|
+
title: p.title ?? '',
|
|
645
|
+
body: p.body ?? '',
|
|
646
|
+
state: p.state ?? '',
|
|
647
|
+
html_url: p.htmlUrl ?? p.url ?? '',
|
|
648
|
+
head: {
|
|
649
|
+
ref: p.headRefName ?? p.headRef ?? '',
|
|
650
|
+
sha: p.headRefOid ?? p.headSha ?? '',
|
|
651
|
+
},
|
|
652
|
+
base: {
|
|
653
|
+
ref: p.baseRefName ?? p.baseRef ?? '',
|
|
654
|
+
sha: p.baseRefOid ?? p.baseSha ?? '',
|
|
655
|
+
},
|
|
656
|
+
user: { login: p.author?.login ?? p.user?.login ?? '' },
|
|
657
|
+
created_at: p.createdAt ?? '',
|
|
658
|
+
updated_at: p.updatedAt ?? '',
|
|
659
|
+
mergeable: p.mergeable ?? null,
|
|
660
|
+
additions: p.additions ?? 0,
|
|
661
|
+
deletions: p.deletions ?? 0,
|
|
662
|
+
changed_files: p.changedFiles ?? 0,
|
|
663
|
+
commits: p.commits ?? 0,
|
|
664
|
+
labels: (p.labels ?? []).map((l) => ({
|
|
665
|
+
name: l.name ?? '',
|
|
666
|
+
color: l.color ?? '',
|
|
667
|
+
})),
|
|
668
|
+
assignees: (p.assignees ?? []).map((a) => ({
|
|
669
|
+
login: a.login ?? '',
|
|
670
|
+
})),
|
|
671
|
+
requested_reviewers: (p.reviewRequests ?? p.requestedReviewers ?? []).map((r) => ({
|
|
672
|
+
login: r.login ?? r.slug ?? '',
|
|
673
|
+
})),
|
|
674
|
+
};
|
|
675
|
+
}
|
|
676
|
+
mapIssueDetail(i) {
|
|
677
|
+
return {
|
|
678
|
+
number: i.number ?? 0,
|
|
679
|
+
title: i.title ?? '',
|
|
680
|
+
body: i.body ?? '',
|
|
681
|
+
state: i.state ?? '',
|
|
682
|
+
html_url: i.htmlUrl ?? i.url ?? '',
|
|
683
|
+
user: { login: i.author?.login ?? i.user?.login ?? '' },
|
|
684
|
+
created_at: i.createdAt ?? '',
|
|
685
|
+
updated_at: i.updatedAt ?? '',
|
|
686
|
+
labels: (i.labels ?? []).map((l) => ({
|
|
687
|
+
name: l.name ?? '',
|
|
688
|
+
color: l.color ?? '',
|
|
689
|
+
})),
|
|
690
|
+
assignees: (i.assignees ?? []).map((a) => ({
|
|
691
|
+
login: a.login ?? '',
|
|
692
|
+
})),
|
|
693
|
+
comments: i.comments ?? i.commentCount ?? 0,
|
|
694
|
+
milestone: i.milestone ? {
|
|
695
|
+
title: i.milestone.title ?? '',
|
|
696
|
+
number: i.milestone.number ?? 0,
|
|
697
|
+
} : undefined,
|
|
698
|
+
};
|
|
699
|
+
}
|
|
700
|
+
mapReleaseInfo(r) {
|
|
701
|
+
return {
|
|
702
|
+
id: r.id ?? r.databaseId ?? 0,
|
|
703
|
+
tagName: r.tagName ?? '',
|
|
704
|
+
name: r.name ?? '',
|
|
705
|
+
body: r.body ?? '',
|
|
706
|
+
draft: r.draft ?? false,
|
|
707
|
+
prerelease: r.prerelease ?? false,
|
|
708
|
+
html_url: r.htmlUrl ?? r.url ?? '',
|
|
709
|
+
created_at: r.createdAt ?? '',
|
|
710
|
+
published_at: r.publishedAt ?? '',
|
|
711
|
+
author: { login: r.author?.login ?? '' },
|
|
712
|
+
assets: (r.assets ?? []).map((a) => ({
|
|
713
|
+
name: a.name ?? '',
|
|
714
|
+
url: a.url ?? a.downloadUrl ?? '',
|
|
715
|
+
size: a.size ?? 0,
|
|
716
|
+
download_count: a.downloadCount ?? 0,
|
|
717
|
+
})),
|
|
718
|
+
};
|
|
719
|
+
}
|
|
720
|
+
extractRepoSlug(remoteUrl) {
|
|
721
|
+
if (!remoteUrl)
|
|
722
|
+
return null;
|
|
723
|
+
const match = remoteUrl.match(/github\.com[/:]([^/]+\/[^/]+?)(?:\.git)?$/);
|
|
724
|
+
return match ? match[1] : null;
|
|
725
|
+
}
|
|
726
|
+
getRepoInfoFromGit() {
|
|
727
|
+
const remoteResult = this.git('remote get-url origin');
|
|
728
|
+
if (remoteResult.exitCode !== 0) {
|
|
729
|
+
return { success: false, error: 'Not a git repository or no remote configured' };
|
|
730
|
+
}
|
|
731
|
+
const slug = this.extractRepoSlug(remoteResult.stdout);
|
|
732
|
+
const [owner, name] = slug ? slug.split('/') : ['unknown', 'unknown'];
|
|
733
|
+
const branchResult = this.git('rev-parse --abbrev-ref HEAD');
|
|
734
|
+
const defaultBranch = branchResult.exitCode === 0 ? branchResult.stdout : 'main';
|
|
735
|
+
const logResult = this.git('log -1 --format=%ci');
|
|
736
|
+
const pushedAt = logResult.exitCode === 0 ? logResult.stdout : '';
|
|
737
|
+
return {
|
|
738
|
+
success: true,
|
|
739
|
+
repo: {
|
|
740
|
+
name,
|
|
741
|
+
full_name: slug ?? name,
|
|
742
|
+
description: '',
|
|
743
|
+
html_url: slug ? `https://github.com/${slug}` : '',
|
|
744
|
+
default_branch: defaultBranch,
|
|
745
|
+
visibility: 'unknown',
|
|
746
|
+
language: '',
|
|
747
|
+
stars: 0,
|
|
748
|
+
forks: 0,
|
|
749
|
+
open_issues: 0,
|
|
750
|
+
watchers: 0,
|
|
751
|
+
created_at: '',
|
|
752
|
+
updated_at: '',
|
|
753
|
+
pushed_at: pushedAt,
|
|
754
|
+
topics: [],
|
|
755
|
+
},
|
|
756
|
+
};
|
|
757
|
+
}
|
|
758
|
+
}
|
|
759
|
+
//# sourceMappingURL=github.js.map
|