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.
Files changed (175) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +510 -0
  3. package/dist/advisor/advisor.d.ts +50 -0
  4. package/dist/advisor/advisor.js +178 -0
  5. package/dist/agents/base.d.ts +62 -0
  6. package/dist/agents/base.js +215 -0
  7. package/dist/agents/orchestrator.d.ts +46 -0
  8. package/dist/agents/orchestrator.js +192 -0
  9. package/dist/agents/team.d.ts +51 -0
  10. package/dist/agents/team.js +210 -0
  11. package/dist/api/models.d.ts +23 -0
  12. package/dist/api/models.js +514 -0
  13. package/dist/api/ollama.d.ts +153 -0
  14. package/dist/api/ollama.js +751 -0
  15. package/dist/api/openrouter.d.ts +55 -0
  16. package/dist/api/openrouter.js +223 -0
  17. package/dist/commands/commands.d.ts +43 -0
  18. package/dist/commands/commands.js +308 -0
  19. package/dist/config/config.d.ts +8 -0
  20. package/dist/config/config.js +311 -0
  21. package/dist/context/compaction.d.ts +54 -0
  22. package/dist/context/compaction.js +251 -0
  23. package/dist/context/custom-agents.d.ts +107 -0
  24. package/dist/context/custom-agents.js +397 -0
  25. package/dist/context/custom-tools.d.ts +120 -0
  26. package/dist/context/custom-tools.js +564 -0
  27. package/dist/context/git-checkpoint.d.ts +52 -0
  28. package/dist/context/git-checkpoint.js +240 -0
  29. package/dist/context/neuro-md.d.ts +48 -0
  30. package/dist/context/neuro-md.js +202 -0
  31. package/dist/context/neuroignore.d.ts +102 -0
  32. package/dist/context/neuroignore.js +441 -0
  33. package/dist/context/repo-map.d.ts +38 -0
  34. package/dist/context/repo-map.js +220 -0
  35. package/dist/context/skill-standard.d.ts +262 -0
  36. package/dist/context/skill-standard.js +1156 -0
  37. package/dist/context/skill-system.d.ts +75 -0
  38. package/dist/context/skill-system.js +578 -0
  39. package/dist/context/tree-sitter.d.ts +99 -0
  40. package/dist/context/tree-sitter.js +1956 -0
  41. package/dist/core/acp.d.ts +325 -0
  42. package/dist/core/acp.js +1498 -0
  43. package/dist/core/api-server.d.ts +143 -0
  44. package/dist/core/api-server.js +550 -0
  45. package/dist/core/approval.d.ts +81 -0
  46. package/dist/core/approval.js +432 -0
  47. package/dist/core/auto-compact.d.ts +127 -0
  48. package/dist/core/auto-compact.js +436 -0
  49. package/dist/core/auto-mode.d.ts +232 -0
  50. package/dist/core/auto-mode.js +831 -0
  51. package/dist/core/background-session.d.ts +166 -0
  52. package/dist/core/background-session.js +696 -0
  53. package/dist/core/cicd.d.ts +257 -0
  54. package/dist/core/cicd.js +1443 -0
  55. package/dist/core/cloud-sync.d.ts +156 -0
  56. package/dist/core/cloud-sync.js +582 -0
  57. package/dist/core/code-review.d.ts +132 -0
  58. package/dist/core/code-review.js +1191 -0
  59. package/dist/core/completion.d.ts +49 -0
  60. package/dist/core/completion.js +384 -0
  61. package/dist/core/context.d.ts +38 -0
  62. package/dist/core/context.js +144 -0
  63. package/dist/core/diff-preview.d.ts +35 -0
  64. package/dist/core/diff-preview.js +173 -0
  65. package/dist/core/doom-loop.d.ts +51 -0
  66. package/dist/core/doom-loop.js +179 -0
  67. package/dist/core/engine.d.ts +183 -0
  68. package/dist/core/engine.js +942 -0
  69. package/dist/core/extended-thinking.d.ts +103 -0
  70. package/dist/core/extended-thinking.js +269 -0
  71. package/dist/core/fallback.d.ts +54 -0
  72. package/dist/core/fallback.js +104 -0
  73. package/dist/core/git-worktree.d.ts +139 -0
  74. package/dist/core/git-worktree.js +614 -0
  75. package/dist/core/headless.d.ts +30 -0
  76. package/dist/core/headless.js +101 -0
  77. package/dist/core/i18n.d.ts +83 -0
  78. package/dist/core/i18n.js +739 -0
  79. package/dist/core/linting.d.ts +129 -0
  80. package/dist/core/linting.js +699 -0
  81. package/dist/core/model-router.d.ts +109 -0
  82. package/dist/core/model-router.js +581 -0
  83. package/dist/core/multi-model.d.ts +243 -0
  84. package/dist/core/multi-model.js +1099 -0
  85. package/dist/core/multi-session.d.ts +144 -0
  86. package/dist/core/multi-session.js +442 -0
  87. package/dist/core/multimodal.d.ts +125 -0
  88. package/dist/core/multimodal.js +286 -0
  89. package/dist/core/observability.d.ts +93 -0
  90. package/dist/core/observability.js +737 -0
  91. package/dist/core/os-sandbox.d.ts +122 -0
  92. package/dist/core/os-sandbox.js +1193 -0
  93. package/dist/core/outcome-grading.d.ts +228 -0
  94. package/dist/core/outcome-grading.js +1123 -0
  95. package/dist/core/output-styles.d.ts +57 -0
  96. package/dist/core/output-styles.js +382 -0
  97. package/dist/core/parallel-agents.d.ts +183 -0
  98. package/dist/core/parallel-agents.js +563 -0
  99. package/dist/core/plugin-bundle.d.ts +236 -0
  100. package/dist/core/plugin-bundle.js +887 -0
  101. package/dist/core/plugin-sdk.d.ts +139 -0
  102. package/dist/core/plugin-sdk.js +273 -0
  103. package/dist/core/prompt-cache.d.ts +163 -0
  104. package/dist/core/prompt-cache.js +599 -0
  105. package/dist/core/sandbox.d.ts +127 -0
  106. package/dist/core/sandbox.js +369 -0
  107. package/dist/core/scheduled-tasks.d.ts +151 -0
  108. package/dist/core/scheduled-tasks.js +502 -0
  109. package/dist/core/security-scanner.d.ts +160 -0
  110. package/dist/core/security-scanner.js +1494 -0
  111. package/dist/core/session.d.ts +83 -0
  112. package/dist/core/session.js +269 -0
  113. package/dist/core/shell-completion.d.ts +51 -0
  114. package/dist/core/shell-completion.js +674 -0
  115. package/dist/core/smart-monitor.d.ts +146 -0
  116. package/dist/core/smart-monitor.js +1199 -0
  117. package/dist/core/spec-driven.d.ts +233 -0
  118. package/dist/core/spec-driven.js +1485 -0
  119. package/dist/core/spending-warnings.d.ts +123 -0
  120. package/dist/core/spending-warnings.js +456 -0
  121. package/dist/core/sub-agent.d.ts +298 -0
  122. package/dist/core/sub-agent.js +1023 -0
  123. package/dist/core/telemetry.d.ts +157 -0
  124. package/dist/core/telemetry.js +412 -0
  125. package/dist/core/terminal-ux.d.ts +134 -0
  126. package/dist/core/terminal-ux.js +649 -0
  127. package/dist/core/testing.d.ts +146 -0
  128. package/dist/core/testing.js +1200 -0
  129. package/dist/core/types.d.ts +439 -0
  130. package/dist/core/types.js +6 -0
  131. package/dist/core/undo-redo.d.ts +112 -0
  132. package/dist/core/undo-redo.js +290 -0
  133. package/dist/core/updater.d.ts +159 -0
  134. package/dist/core/updater.js +608 -0
  135. package/dist/core/vim-mode.d.ts +151 -0
  136. package/dist/core/vim-mode.js +771 -0
  137. package/dist/core/voice.d.ts +137 -0
  138. package/dist/core/voice.js +538 -0
  139. package/dist/core/web-dashboard.d.ts +109 -0
  140. package/dist/core/web-dashboard.js +484 -0
  141. package/dist/hooks/hooks.d.ts +74 -0
  142. package/dist/hooks/hooks.js +160 -0
  143. package/dist/hooks/llm-evaluator.d.ts +165 -0
  144. package/dist/hooks/llm-evaluator.js +560 -0
  145. package/dist/index.d.ts +3 -0
  146. package/dist/index.js +1186 -0
  147. package/dist/lsp/lsp-manager.d.ts +63 -0
  148. package/dist/lsp/lsp-manager.js +351 -0
  149. package/dist/mcp/client.d.ts +133 -0
  150. package/dist/mcp/client.js +684 -0
  151. package/dist/mcp/mcp-apps.d.ts +70 -0
  152. package/dist/mcp/mcp-apps.js +1007 -0
  153. package/dist/tools/bash.d.ts +5 -0
  154. package/dist/tools/bash.js +195 -0
  155. package/dist/tools/browser.d.ts +92 -0
  156. package/dist/tools/browser.js +1570 -0
  157. package/dist/tools/extended.d.ts +6 -0
  158. package/dist/tools/extended.js +191 -0
  159. package/dist/tools/file.d.ts +10 -0
  160. package/dist/tools/file.js +382 -0
  161. package/dist/tools/github.d.ts +389 -0
  162. package/dist/tools/github.js +759 -0
  163. package/dist/tools/index.d.ts +9 -0
  164. package/dist/tools/index.js +40 -0
  165. package/dist/tools/memory.d.ts +6 -0
  166. package/dist/tools/memory.js +197 -0
  167. package/dist/tools/registry.d.ts +29 -0
  168. package/dist/tools/registry.js +64 -0
  169. package/dist/tools/web.d.ts +6 -0
  170. package/dist/tools/web.js +150 -0
  171. package/dist/ui/renderer.d.ts +97 -0
  172. package/dist/ui/renderer.js +279 -0
  173. package/dist/ui/theme.d.ts +27 -0
  174. package/dist/ui/theme.js +106 -0
  175. 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