@build-astron-co/nimbus 0.2.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 (313) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +628 -0
  3. package/bin/nimbus +38 -0
  4. package/package.json +80 -0
  5. package/src/__tests__/app.test.ts +76 -0
  6. package/src/__tests__/audit.test.ts +877 -0
  7. package/src/__tests__/circuit-breaker.test.ts +116 -0
  8. package/src/__tests__/cli-run.test.ts +115 -0
  9. package/src/__tests__/context-manager.test.ts +502 -0
  10. package/src/__tests__/context.test.ts +242 -0
  11. package/src/__tests__/enterprise.test.ts +401 -0
  12. package/src/__tests__/generator.test.ts +433 -0
  13. package/src/__tests__/hooks.test.ts +582 -0
  14. package/src/__tests__/init.test.ts +436 -0
  15. package/src/__tests__/intent-parser.test.ts +229 -0
  16. package/src/__tests__/llm-router.test.ts +209 -0
  17. package/src/__tests__/lsp.test.ts +293 -0
  18. package/src/__tests__/modes.test.ts +336 -0
  19. package/src/__tests__/permissions.test.ts +338 -0
  20. package/src/__tests__/serve.test.ts +275 -0
  21. package/src/__tests__/sessions.test.ts +227 -0
  22. package/src/__tests__/sharing.test.ts +288 -0
  23. package/src/__tests__/snapshots.test.ts +581 -0
  24. package/src/__tests__/state-db.test.ts +334 -0
  25. package/src/__tests__/stream-with-tools.test.ts +732 -0
  26. package/src/__tests__/subagents.test.ts +176 -0
  27. package/src/__tests__/system-prompt.test.ts +169 -0
  28. package/src/__tests__/tool-converter.test.ts +256 -0
  29. package/src/__tests__/tool-schemas.test.ts +397 -0
  30. package/src/__tests__/tools.test.ts +143 -0
  31. package/src/__tests__/version.test.ts +49 -0
  32. package/src/agent/compaction-agent.ts +227 -0
  33. package/src/agent/context-manager.ts +435 -0
  34. package/src/agent/context.ts +427 -0
  35. package/src/agent/deploy-preview.ts +426 -0
  36. package/src/agent/index.ts +68 -0
  37. package/src/agent/loop.ts +717 -0
  38. package/src/agent/modes.ts +429 -0
  39. package/src/agent/permissions.ts +466 -0
  40. package/src/agent/subagents/base.ts +116 -0
  41. package/src/agent/subagents/cost.ts +51 -0
  42. package/src/agent/subagents/explore.ts +42 -0
  43. package/src/agent/subagents/general.ts +54 -0
  44. package/src/agent/subagents/index.ts +102 -0
  45. package/src/agent/subagents/infra.ts +59 -0
  46. package/src/agent/subagents/security.ts +69 -0
  47. package/src/agent/system-prompt.ts +436 -0
  48. package/src/app.ts +122 -0
  49. package/src/audit/activity-log.ts +290 -0
  50. package/src/audit/compliance-checker.ts +540 -0
  51. package/src/audit/cost-tracker.ts +318 -0
  52. package/src/audit/index.ts +23 -0
  53. package/src/audit/security-scanner.ts +596 -0
  54. package/src/auth/guard.ts +75 -0
  55. package/src/auth/index.ts +56 -0
  56. package/src/auth/oauth.ts +455 -0
  57. package/src/auth/providers.ts +470 -0
  58. package/src/auth/sso.ts +113 -0
  59. package/src/auth/store.ts +505 -0
  60. package/src/auth/types.ts +187 -0
  61. package/src/build.ts +141 -0
  62. package/src/cli/index.ts +16 -0
  63. package/src/cli/init.ts +854 -0
  64. package/src/cli/openapi-spec.ts +356 -0
  65. package/src/cli/run.ts +237 -0
  66. package/src/cli/serve-auth.ts +80 -0
  67. package/src/cli/serve.ts +462 -0
  68. package/src/cli/web.ts +67 -0
  69. package/src/cli.ts +1417 -0
  70. package/src/clients/core-engine-client.ts +227 -0
  71. package/src/clients/enterprise-client.ts +334 -0
  72. package/src/clients/generator-client.ts +351 -0
  73. package/src/clients/git-client.ts +627 -0
  74. package/src/clients/github-client.ts +410 -0
  75. package/src/clients/helm-client.ts +504 -0
  76. package/src/clients/index.ts +80 -0
  77. package/src/clients/k8s-client.ts +497 -0
  78. package/src/clients/llm-client.ts +161 -0
  79. package/src/clients/rest-client.ts +130 -0
  80. package/src/clients/service-discovery.ts +33 -0
  81. package/src/clients/terraform-client.ts +482 -0
  82. package/src/clients/tools-client.ts +1843 -0
  83. package/src/clients/ws-client.ts +115 -0
  84. package/src/commands/analyze/index.ts +352 -0
  85. package/src/commands/apply/helm.ts +473 -0
  86. package/src/commands/apply/index.ts +213 -0
  87. package/src/commands/apply/k8s.ts +454 -0
  88. package/src/commands/apply/terraform.ts +582 -0
  89. package/src/commands/ask.ts +167 -0
  90. package/src/commands/audit/index.ts +238 -0
  91. package/src/commands/auth-cloud.ts +294 -0
  92. package/src/commands/auth-list.ts +134 -0
  93. package/src/commands/auth-profile.ts +121 -0
  94. package/src/commands/auth-status.ts +141 -0
  95. package/src/commands/aws/ec2.ts +501 -0
  96. package/src/commands/aws/iam.ts +397 -0
  97. package/src/commands/aws/index.ts +133 -0
  98. package/src/commands/aws/lambda.ts +396 -0
  99. package/src/commands/aws/rds.ts +439 -0
  100. package/src/commands/aws/s3.ts +439 -0
  101. package/src/commands/aws/vpc.ts +393 -0
  102. package/src/commands/aws-discover.ts +649 -0
  103. package/src/commands/aws-terraform.ts +805 -0
  104. package/src/commands/azure/aks.ts +376 -0
  105. package/src/commands/azure/functions.ts +253 -0
  106. package/src/commands/azure/index.ts +116 -0
  107. package/src/commands/azure/storage.ts +478 -0
  108. package/src/commands/azure/vm.ts +355 -0
  109. package/src/commands/billing/index.ts +256 -0
  110. package/src/commands/chat.ts +314 -0
  111. package/src/commands/config.ts +346 -0
  112. package/src/commands/cost/cloud-cost-estimator.ts +266 -0
  113. package/src/commands/cost/estimator.ts +79 -0
  114. package/src/commands/cost/index.ts +594 -0
  115. package/src/commands/cost/parsers/terraform.ts +273 -0
  116. package/src/commands/cost/parsers/types.ts +25 -0
  117. package/src/commands/cost/pricing/aws.ts +544 -0
  118. package/src/commands/cost/pricing/azure.ts +499 -0
  119. package/src/commands/cost/pricing/gcp.ts +396 -0
  120. package/src/commands/cost/pricing/index.ts +40 -0
  121. package/src/commands/demo.ts +250 -0
  122. package/src/commands/doctor.ts +794 -0
  123. package/src/commands/drift/index.ts +439 -0
  124. package/src/commands/explain.ts +277 -0
  125. package/src/commands/feedback.ts +389 -0
  126. package/src/commands/fix.ts +324 -0
  127. package/src/commands/fs/index.ts +402 -0
  128. package/src/commands/gcp/compute.ts +325 -0
  129. package/src/commands/gcp/functions.ts +271 -0
  130. package/src/commands/gcp/gke.ts +438 -0
  131. package/src/commands/gcp/iam.ts +344 -0
  132. package/src/commands/gcp/index.ts +129 -0
  133. package/src/commands/gcp/storage.ts +284 -0
  134. package/src/commands/generate-helm.ts +1249 -0
  135. package/src/commands/generate-k8s.ts +1560 -0
  136. package/src/commands/generate-terraform.ts +1460 -0
  137. package/src/commands/gh/index.ts +863 -0
  138. package/src/commands/git/index.ts +1343 -0
  139. package/src/commands/helm/index.ts +1126 -0
  140. package/src/commands/help.ts +539 -0
  141. package/src/commands/history.ts +142 -0
  142. package/src/commands/import.ts +868 -0
  143. package/src/commands/index.ts +367 -0
  144. package/src/commands/init.ts +1046 -0
  145. package/src/commands/k8s/index.ts +1137 -0
  146. package/src/commands/login.ts +631 -0
  147. package/src/commands/logout.ts +83 -0
  148. package/src/commands/onboarding.ts +228 -0
  149. package/src/commands/plan/display.ts +279 -0
  150. package/src/commands/plan/index.ts +599 -0
  151. package/src/commands/preview.ts +452 -0
  152. package/src/commands/questionnaire.ts +1270 -0
  153. package/src/commands/resume.ts +55 -0
  154. package/src/commands/team/index.ts +346 -0
  155. package/src/commands/template.ts +232 -0
  156. package/src/commands/tf/index.ts +1034 -0
  157. package/src/commands/upgrade.ts +550 -0
  158. package/src/commands/usage/index.ts +134 -0
  159. package/src/commands/version.ts +170 -0
  160. package/src/compat/index.ts +2 -0
  161. package/src/compat/runtime.ts +12 -0
  162. package/src/compat/sqlite.ts +107 -0
  163. package/src/config/index.ts +17 -0
  164. package/src/config/manager.ts +530 -0
  165. package/src/config/safety-policy.ts +358 -0
  166. package/src/config/schema.ts +125 -0
  167. package/src/config/types.ts +527 -0
  168. package/src/context/context-db.ts +199 -0
  169. package/src/demo/index.ts +349 -0
  170. package/src/demo/scenarios/full-journey.ts +229 -0
  171. package/src/demo/scenarios/getting-started.ts +127 -0
  172. package/src/demo/scenarios/helm-release.ts +341 -0
  173. package/src/demo/scenarios/k8s-deployment.ts +194 -0
  174. package/src/demo/scenarios/terraform-vpc.ts +170 -0
  175. package/src/demo/types.ts +92 -0
  176. package/src/engine/cost-estimator.ts +438 -0
  177. package/src/engine/diagram-generator.ts +256 -0
  178. package/src/engine/drift-detector.ts +902 -0
  179. package/src/engine/executor.ts +1035 -0
  180. package/src/engine/index.ts +76 -0
  181. package/src/engine/orchestrator.ts +636 -0
  182. package/src/engine/planner.ts +720 -0
  183. package/src/engine/safety.ts +743 -0
  184. package/src/engine/verifier.ts +770 -0
  185. package/src/enterprise/audit.ts +348 -0
  186. package/src/enterprise/auth.ts +270 -0
  187. package/src/enterprise/billing.ts +822 -0
  188. package/src/enterprise/index.ts +17 -0
  189. package/src/enterprise/teams.ts +443 -0
  190. package/src/generator/best-practices.ts +1608 -0
  191. package/src/generator/helm.ts +630 -0
  192. package/src/generator/index.ts +37 -0
  193. package/src/generator/intent-parser.ts +514 -0
  194. package/src/generator/kubernetes.ts +976 -0
  195. package/src/generator/terraform.ts +1867 -0
  196. package/src/history/index.ts +8 -0
  197. package/src/history/manager.ts +322 -0
  198. package/src/history/types.ts +34 -0
  199. package/src/hooks/config.ts +432 -0
  200. package/src/hooks/engine.ts +391 -0
  201. package/src/hooks/index.ts +4 -0
  202. package/src/llm/auth-bridge.ts +198 -0
  203. package/src/llm/circuit-breaker.ts +140 -0
  204. package/src/llm/config-loader.ts +201 -0
  205. package/src/llm/cost-calculator.ts +171 -0
  206. package/src/llm/index.ts +8 -0
  207. package/src/llm/model-aliases.ts +115 -0
  208. package/src/llm/provider-registry.ts +63 -0
  209. package/src/llm/providers/anthropic.ts +433 -0
  210. package/src/llm/providers/bedrock.ts +477 -0
  211. package/src/llm/providers/google.ts +405 -0
  212. package/src/llm/providers/ollama.ts +767 -0
  213. package/src/llm/providers/openai-compatible.ts +340 -0
  214. package/src/llm/providers/openai.ts +328 -0
  215. package/src/llm/providers/openrouter.ts +338 -0
  216. package/src/llm/router.ts +1035 -0
  217. package/src/llm/types.ts +232 -0
  218. package/src/lsp/client.ts +298 -0
  219. package/src/lsp/languages.ts +116 -0
  220. package/src/lsp/manager.ts +278 -0
  221. package/src/mcp/client.ts +402 -0
  222. package/src/mcp/index.ts +5 -0
  223. package/src/mcp/manager.ts +133 -0
  224. package/src/nimbus.ts +214 -0
  225. package/src/plugins/index.ts +27 -0
  226. package/src/plugins/loader.ts +334 -0
  227. package/src/plugins/manager.ts +376 -0
  228. package/src/plugins/types.ts +284 -0
  229. package/src/scanners/cicd-scanner.ts +258 -0
  230. package/src/scanners/cloud-scanner.ts +466 -0
  231. package/src/scanners/framework-scanner.ts +469 -0
  232. package/src/scanners/iac-scanner.ts +388 -0
  233. package/src/scanners/index.ts +539 -0
  234. package/src/scanners/language-scanner.ts +276 -0
  235. package/src/scanners/package-manager-scanner.ts +277 -0
  236. package/src/scanners/types.ts +172 -0
  237. package/src/sessions/manager.ts +365 -0
  238. package/src/sessions/types.ts +44 -0
  239. package/src/sharing/sync.ts +296 -0
  240. package/src/sharing/viewer.ts +97 -0
  241. package/src/snapshots/index.ts +2 -0
  242. package/src/snapshots/manager.ts +530 -0
  243. package/src/state/artifacts.ts +147 -0
  244. package/src/state/audit.ts +137 -0
  245. package/src/state/billing.ts +240 -0
  246. package/src/state/checkpoints.ts +117 -0
  247. package/src/state/config.ts +67 -0
  248. package/src/state/conversations.ts +14 -0
  249. package/src/state/credentials.ts +154 -0
  250. package/src/state/db.ts +58 -0
  251. package/src/state/index.ts +26 -0
  252. package/src/state/messages.ts +115 -0
  253. package/src/state/projects.ts +123 -0
  254. package/src/state/schema.ts +236 -0
  255. package/src/state/sessions.ts +147 -0
  256. package/src/state/teams.ts +200 -0
  257. package/src/telemetry.ts +108 -0
  258. package/src/tools/aws-ops.ts +952 -0
  259. package/src/tools/azure-ops.ts +579 -0
  260. package/src/tools/file-ops.ts +593 -0
  261. package/src/tools/gcp-ops.ts +625 -0
  262. package/src/tools/git-ops.ts +773 -0
  263. package/src/tools/github-ops.ts +799 -0
  264. package/src/tools/helm-ops.ts +943 -0
  265. package/src/tools/index.ts +17 -0
  266. package/src/tools/k8s-ops.ts +819 -0
  267. package/src/tools/schemas/converter.ts +184 -0
  268. package/src/tools/schemas/devops.ts +612 -0
  269. package/src/tools/schemas/index.ts +73 -0
  270. package/src/tools/schemas/standard.ts +1144 -0
  271. package/src/tools/schemas/types.ts +705 -0
  272. package/src/tools/terraform-ops.ts +862 -0
  273. package/src/types/ambient.d.ts +193 -0
  274. package/src/types/config.ts +83 -0
  275. package/src/types/drift.ts +116 -0
  276. package/src/types/enterprise.ts +335 -0
  277. package/src/types/index.ts +20 -0
  278. package/src/types/plan.ts +44 -0
  279. package/src/types/request.ts +65 -0
  280. package/src/types/response.ts +54 -0
  281. package/src/types/service.ts +51 -0
  282. package/src/ui/App.tsx +997 -0
  283. package/src/ui/DeployPreview.tsx +169 -0
  284. package/src/ui/Header.tsx +68 -0
  285. package/src/ui/InputBox.tsx +350 -0
  286. package/src/ui/MessageList.tsx +585 -0
  287. package/src/ui/PermissionPrompt.tsx +151 -0
  288. package/src/ui/StatusBar.tsx +158 -0
  289. package/src/ui/ToolCallDisplay.tsx +409 -0
  290. package/src/ui/chat-ui.ts +853 -0
  291. package/src/ui/index.ts +33 -0
  292. package/src/ui/ink/index.ts +711 -0
  293. package/src/ui/streaming.ts +176 -0
  294. package/src/ui/types.ts +57 -0
  295. package/src/utils/analytics.ts +72 -0
  296. package/src/utils/cost-warning.ts +27 -0
  297. package/src/utils/env.ts +46 -0
  298. package/src/utils/errors.ts +69 -0
  299. package/src/utils/event-bus.ts +38 -0
  300. package/src/utils/index.ts +24 -0
  301. package/src/utils/logger.ts +171 -0
  302. package/src/utils/rate-limiter.ts +121 -0
  303. package/src/utils/service-auth.ts +49 -0
  304. package/src/utils/validation.ts +53 -0
  305. package/src/version.ts +4 -0
  306. package/src/watcher/index.ts +163 -0
  307. package/src/wizard/approval.ts +383 -0
  308. package/src/wizard/index.ts +25 -0
  309. package/src/wizard/prompts.ts +338 -0
  310. package/src/wizard/types.ts +171 -0
  311. package/src/wizard/ui.ts +556 -0
  312. package/src/wizard/wizard.ts +304 -0
  313. package/tsconfig.json +24 -0
@@ -0,0 +1,863 @@
1
+ /**
2
+ * GitHub CLI Commands
3
+ *
4
+ * CLI commands for interacting with GitHub via the GitHub Tools Service
5
+ */
6
+
7
+ import { spawn } from 'node:child_process';
8
+ import { githubClient } from '../../clients/github-client';
9
+ import { ui } from '../../wizard/ui';
10
+
11
+ export interface GhCommandOptions {
12
+ owner?: string;
13
+ repo?: string;
14
+ json?: boolean;
15
+ }
16
+
17
+ /**
18
+ * Parse owner/repo from git remote or arguments
19
+ */
20
+ async function getOwnerRepo(
21
+ options: GhCommandOptions
22
+ ): Promise<{ owner: string; repo: string } | null> {
23
+ if (options.owner && options.repo) {
24
+ return { owner: options.owner, repo: options.repo };
25
+ }
26
+
27
+ // Try to get from git remote
28
+ try {
29
+ const output = await new Promise<string>((resolve, reject) => {
30
+ const proc = spawn('git', ['remote', 'get-url', 'origin'], {
31
+ stdio: ['ignore', 'pipe', 'pipe'],
32
+ });
33
+ let out = '';
34
+ proc.stdout?.on('data', (data: Buffer) => {
35
+ out += data.toString();
36
+ });
37
+ proc.on('close', () => resolve(out));
38
+ proc.on('error', reject);
39
+ });
40
+ const match = output.match(/github\.com[:/]([^/]+)\/([^/.]+)/);
41
+ if (match) {
42
+ return { owner: match[1], repo: match[2] };
43
+ }
44
+ } catch {
45
+ // Ignore git errors
46
+ }
47
+
48
+ ui.error('Could not determine repository. Use --owner and --repo options.');
49
+ return null;
50
+ }
51
+
52
+ /**
53
+ * Format a date for display
54
+ */
55
+ function formatDate(dateStr: string): string {
56
+ const date = new Date(dateStr);
57
+ return date.toLocaleDateString('en-US', {
58
+ month: 'short',
59
+ day: 'numeric',
60
+ year: 'numeric',
61
+ });
62
+ }
63
+
64
+ // ==========================================
65
+ // Pull Request Commands
66
+ // ==========================================
67
+
68
+ export interface PrListOptions extends GhCommandOptions {
69
+ state?: 'open' | 'closed' | 'all';
70
+ limit?: number;
71
+ }
72
+
73
+ /**
74
+ * List pull requests
75
+ */
76
+ export async function ghPrListCommand(options: PrListOptions = {}): Promise<void> {
77
+ const ownerRepo = await getOwnerRepo(options);
78
+ if (!ownerRepo) {
79
+ return;
80
+ }
81
+
82
+ ui.startSpinner({ message: 'Fetching pull requests...' });
83
+
84
+ const result = await githubClient.listPRs(ownerRepo.owner, ownerRepo.repo, {
85
+ state: options.state || 'open',
86
+ perPage: options.limit || 10,
87
+ });
88
+
89
+ if (!result.success || !result.data) {
90
+ ui.stopSpinnerFail('Failed to fetch pull requests');
91
+ ui.error(result.error || 'Failed to fetch pull requests');
92
+ return;
93
+ }
94
+
95
+ ui.stopSpinnerSuccess('Fetched pull requests');
96
+
97
+ if (options.json) {
98
+ console.log(JSON.stringify(result.data, null, 2));
99
+ return;
100
+ }
101
+
102
+ if (result.data.length === 0) {
103
+ ui.info('No pull requests found');
104
+ return;
105
+ }
106
+
107
+ ui.header(`Pull Requests - ${ownerRepo.owner}/${ownerRepo.repo}`);
108
+
109
+ ui.table({
110
+ columns: [
111
+ { key: 'number', header: '#' },
112
+ { key: 'title', header: 'Title' },
113
+ { key: 'author', header: 'Author' },
114
+ { key: 'state', header: 'State' },
115
+ { key: 'updated', header: 'Updated' },
116
+ ],
117
+ data: result.data.map(pr => ({
118
+ number: `#${pr.number}`,
119
+ title: pr.title.substring(0, 50) + (pr.title.length > 50 ? '...' : ''),
120
+ author: pr.user.login,
121
+ state: pr.draft
122
+ ? ui.color('draft', 'gray')
123
+ : pr.state === 'open'
124
+ ? ui.color('open', 'green')
125
+ : ui.color('closed', 'red'),
126
+ updated: formatDate(pr.updated_at),
127
+ })),
128
+ });
129
+ }
130
+
131
+ export interface PrViewOptions extends GhCommandOptions {
132
+ prNumber: number;
133
+ }
134
+
135
+ /**
136
+ * View a single pull request
137
+ */
138
+ export async function ghPrViewCommand(options: PrViewOptions): Promise<void> {
139
+ const ownerRepo = await getOwnerRepo(options);
140
+ if (!ownerRepo) {
141
+ return;
142
+ }
143
+
144
+ ui.startSpinner({ message: `Fetching PR #${options.prNumber}...` });
145
+
146
+ const result = await githubClient.getPR(ownerRepo.owner, ownerRepo.repo, options.prNumber);
147
+
148
+ if (!result.success || !result.data) {
149
+ ui.stopSpinnerFail('Failed to fetch pull request');
150
+ ui.error(result.error || 'Failed to fetch pull request');
151
+ return;
152
+ }
153
+
154
+ ui.stopSpinnerSuccess('Fetched pull request');
155
+
156
+ const pr = result.data;
157
+
158
+ if (options.json) {
159
+ console.log(JSON.stringify(pr, null, 2));
160
+ return;
161
+ }
162
+
163
+ ui.header(`PR #${pr.number}: ${pr.title}`);
164
+ ui.newLine();
165
+
166
+ ui.info(`Author: ${pr.user.login}`);
167
+ ui.info(`State: ${pr.state}${pr.draft ? ' (draft)' : ''}`);
168
+ ui.info(`Branch: ${pr.head.ref} → ${pr.base.ref}`);
169
+ ui.info(`Created: ${formatDate(pr.created_at)}`);
170
+ ui.info(`Updated: ${formatDate(pr.updated_at)}`);
171
+
172
+ if (pr.labels.length > 0) {
173
+ ui.info(`Labels: ${pr.labels.map(l => l.name).join(', ')}`);
174
+ }
175
+
176
+ if (pr.body) {
177
+ ui.newLine();
178
+ ui.box({ title: 'Description', content: pr.body });
179
+ }
180
+ }
181
+
182
+ export interface PrCreateOptions extends GhCommandOptions {
183
+ title: string;
184
+ body?: string;
185
+ head?: string;
186
+ base?: string;
187
+ draft?: boolean;
188
+ }
189
+
190
+ /**
191
+ * Create a pull request
192
+ */
193
+ export async function ghPrCreateCommand(options: PrCreateOptions): Promise<void> {
194
+ const ownerRepo = await getOwnerRepo(options);
195
+ if (!ownerRepo) {
196
+ return;
197
+ }
198
+
199
+ // Get current branch if head not specified
200
+ let head = options.head;
201
+ if (!head) {
202
+ try {
203
+ const branchOutput = await new Promise<string>((resolve, reject) => {
204
+ const proc = spawn('git', ['branch', '--show-current'], {
205
+ stdio: ['ignore', 'pipe', 'pipe'],
206
+ });
207
+ let out = '';
208
+ proc.stdout?.on('data', (data: Buffer) => {
209
+ out += data.toString();
210
+ });
211
+ proc.on('close', () => resolve(out));
212
+ proc.on('error', reject);
213
+ });
214
+ head = branchOutput.trim();
215
+ } catch {
216
+ ui.error('Could not determine current branch. Use --head option.');
217
+ return;
218
+ }
219
+ }
220
+
221
+ ui.startSpinner({ message: 'Creating pull request...' });
222
+
223
+ const result = await githubClient.createPR(ownerRepo.owner, ownerRepo.repo, {
224
+ title: options.title,
225
+ head,
226
+ base: options.base || 'main',
227
+ body: options.body,
228
+ draft: options.draft,
229
+ });
230
+
231
+ if (!result.success || !result.data) {
232
+ ui.stopSpinnerFail('Failed to create pull request');
233
+ ui.error(result.error || 'Failed to create pull request');
234
+ return;
235
+ }
236
+
237
+ ui.stopSpinnerSuccess('Created pull request');
238
+ ui.success(`Created PR #${result.data.number}: ${result.data.title}`);
239
+ }
240
+
241
+ export interface PrMergeOptions extends GhCommandOptions {
242
+ prNumber: number;
243
+ method?: 'merge' | 'squash' | 'rebase';
244
+ commitTitle?: string;
245
+ }
246
+
247
+ /**
248
+ * Merge a pull request
249
+ */
250
+ export async function ghPrMergeCommand(options: PrMergeOptions): Promise<void> {
251
+ const ownerRepo = await getOwnerRepo(options);
252
+ if (!ownerRepo) {
253
+ return;
254
+ }
255
+
256
+ ui.startSpinner({ message: `Merging PR #${options.prNumber}...` });
257
+
258
+ const result = await githubClient.mergePR(ownerRepo.owner, ownerRepo.repo, options.prNumber, {
259
+ mergeMethod: options.method,
260
+ commitTitle: options.commitTitle,
261
+ });
262
+
263
+ if (!result.success || !result.data) {
264
+ ui.stopSpinnerFail('Failed to merge pull request');
265
+ ui.error(result.error || 'Failed to merge pull request');
266
+ return;
267
+ }
268
+
269
+ ui.stopSpinnerSuccess('Merged pull request');
270
+ ui.success(result.data.message);
271
+ }
272
+
273
+ export interface PrReviewOptions extends GhCommandOptions {
274
+ prNumber: number;
275
+ event: 'APPROVE' | 'REQUEST_CHANGES' | 'COMMENT';
276
+ body?: string;
277
+ }
278
+
279
+ /**
280
+ * Review a pull request
281
+ */
282
+ export async function ghPrReviewCommand(options: PrReviewOptions): Promise<void> {
283
+ const ownerRepo = await getOwnerRepo(options);
284
+ if (!ownerRepo) {
285
+ return;
286
+ }
287
+
288
+ ui.startSpinner({ message: `Reviewing PR #${options.prNumber}...` });
289
+
290
+ const result = await githubClient.createPRReview(
291
+ ownerRepo.owner,
292
+ ownerRepo.repo,
293
+ options.prNumber,
294
+ options.event,
295
+ options.body
296
+ );
297
+
298
+ if (!result.success || !result.data) {
299
+ ui.stopSpinnerFail('Failed to review pull request');
300
+ ui.error(result.error || 'Failed to review pull request');
301
+ return;
302
+ }
303
+
304
+ ui.stopSpinnerSuccess('Review submitted');
305
+ ui.success(`Review submitted on PR #${options.prNumber}: ${options.event}`);
306
+ }
307
+
308
+ // ==========================================
309
+ // Issue Commands
310
+ // ==========================================
311
+
312
+ export interface IssueListOptions extends GhCommandOptions {
313
+ state?: 'open' | 'closed' | 'all';
314
+ limit?: number;
315
+ }
316
+
317
+ /**
318
+ * List issues
319
+ */
320
+ export async function ghIssueListCommand(options: IssueListOptions = {}): Promise<void> {
321
+ const ownerRepo = await getOwnerRepo(options);
322
+ if (!ownerRepo) {
323
+ return;
324
+ }
325
+
326
+ ui.startSpinner({ message: 'Fetching issues...' });
327
+
328
+ const result = await githubClient.listIssues(ownerRepo.owner, ownerRepo.repo, {
329
+ state: options.state || 'open',
330
+ perPage: options.limit || 10,
331
+ });
332
+
333
+ if (!result.success || !result.data) {
334
+ ui.stopSpinnerFail('Failed to fetch issues');
335
+ ui.error(result.error || 'Failed to fetch issues');
336
+ return;
337
+ }
338
+
339
+ ui.stopSpinnerSuccess('Fetched issues');
340
+
341
+ if (options.json) {
342
+ console.log(JSON.stringify(result.data, null, 2));
343
+ return;
344
+ }
345
+
346
+ if (result.data.length === 0) {
347
+ ui.info('No issues found');
348
+ return;
349
+ }
350
+
351
+ ui.header(`Issues - ${ownerRepo.owner}/${ownerRepo.repo}`);
352
+
353
+ ui.table({
354
+ columns: [
355
+ { key: 'number', header: '#' },
356
+ { key: 'title', header: 'Title' },
357
+ { key: 'author', header: 'Author' },
358
+ { key: 'state', header: 'State' },
359
+ { key: 'comments', header: 'Comments' },
360
+ { key: 'updated', header: 'Updated' },
361
+ ],
362
+ data: result.data.map(issue => ({
363
+ number: `#${issue.number}`,
364
+ title: issue.title.substring(0, 40) + (issue.title.length > 40 ? '...' : ''),
365
+ author: issue.user.login,
366
+ state: issue.state === 'open' ? ui.color('open', 'green') : ui.color('closed', 'red'),
367
+ comments: String(issue.comments),
368
+ updated: formatDate(issue.updated_at),
369
+ })),
370
+ });
371
+ }
372
+
373
+ export interface IssueViewOptions extends GhCommandOptions {
374
+ issueNumber: number;
375
+ }
376
+
377
+ /**
378
+ * View a single issue
379
+ */
380
+ export async function ghIssueViewCommand(options: IssueViewOptions): Promise<void> {
381
+ const ownerRepo = await getOwnerRepo(options);
382
+ if (!ownerRepo) {
383
+ return;
384
+ }
385
+
386
+ ui.startSpinner({ message: `Fetching issue #${options.issueNumber}...` });
387
+
388
+ const result = await githubClient.getIssue(ownerRepo.owner, ownerRepo.repo, options.issueNumber);
389
+
390
+ if (!result.success || !result.data) {
391
+ ui.stopSpinnerFail('Failed to fetch issue');
392
+ ui.error(result.error || 'Failed to fetch issue');
393
+ return;
394
+ }
395
+
396
+ ui.stopSpinnerSuccess('Fetched issue');
397
+
398
+ const issue = result.data;
399
+
400
+ if (options.json) {
401
+ console.log(JSON.stringify(issue, null, 2));
402
+ return;
403
+ }
404
+
405
+ ui.header(`Issue #${issue.number}: ${issue.title}`);
406
+ ui.newLine();
407
+
408
+ ui.info(`Author: ${issue.user.login}`);
409
+ ui.info(`State: ${issue.state}`);
410
+ ui.info(`Comments: ${issue.comments}`);
411
+ ui.info(`Created: ${formatDate(issue.created_at)}`);
412
+ ui.info(`Updated: ${formatDate(issue.updated_at)}`);
413
+
414
+ if (issue.labels.length > 0) {
415
+ ui.info(`Labels: ${issue.labels.map(l => l.name).join(', ')}`);
416
+ }
417
+
418
+ if (issue.body) {
419
+ ui.newLine();
420
+ ui.box({ title: 'Description', content: issue.body });
421
+ }
422
+ }
423
+
424
+ export interface IssueCreateOptions extends GhCommandOptions {
425
+ title: string;
426
+ body?: string;
427
+ labels?: string[];
428
+ assignees?: string[];
429
+ }
430
+
431
+ /**
432
+ * Create an issue
433
+ */
434
+ export async function ghIssueCreateCommand(options: IssueCreateOptions): Promise<void> {
435
+ const ownerRepo = await getOwnerRepo(options);
436
+ if (!ownerRepo) {
437
+ return;
438
+ }
439
+
440
+ ui.startSpinner({ message: 'Creating issue...' });
441
+
442
+ const result = await githubClient.createIssue(ownerRepo.owner, ownerRepo.repo, {
443
+ title: options.title,
444
+ body: options.body,
445
+ labels: options.labels,
446
+ assignees: options.assignees,
447
+ });
448
+
449
+ if (!result.success || !result.data) {
450
+ ui.stopSpinnerFail('Failed to create issue');
451
+ ui.error(result.error || 'Failed to create issue');
452
+ return;
453
+ }
454
+
455
+ ui.stopSpinnerSuccess('Created issue');
456
+ ui.success(`Created issue #${result.data.number}: ${result.data.title}`);
457
+ }
458
+
459
+ export interface IssueCloseOptions extends GhCommandOptions {
460
+ issueNumber: number;
461
+ }
462
+
463
+ /**
464
+ * Close an issue
465
+ */
466
+ export async function ghIssueCloseCommand(options: IssueCloseOptions): Promise<void> {
467
+ const ownerRepo = await getOwnerRepo(options);
468
+ if (!ownerRepo) {
469
+ return;
470
+ }
471
+
472
+ ui.startSpinner({ message: `Closing issue #${options.issueNumber}...` });
473
+
474
+ const result = await githubClient.closeIssue(
475
+ ownerRepo.owner,
476
+ ownerRepo.repo,
477
+ options.issueNumber
478
+ );
479
+
480
+ if (!result.success || !result.data) {
481
+ ui.stopSpinnerFail('Failed to close issue');
482
+ ui.error(result.error || 'Failed to close issue');
483
+ return;
484
+ }
485
+
486
+ ui.stopSpinnerSuccess('Closed issue');
487
+ ui.success(`Closed issue #${options.issueNumber}`);
488
+ }
489
+
490
+ export interface IssueCommentOptions extends GhCommandOptions {
491
+ issueNumber: number;
492
+ body: string;
493
+ }
494
+
495
+ /**
496
+ * Add a comment to an issue
497
+ */
498
+ export async function ghIssueCommentCommand(options: IssueCommentOptions): Promise<void> {
499
+ const ownerRepo = await getOwnerRepo(options);
500
+ if (!ownerRepo) {
501
+ return;
502
+ }
503
+
504
+ ui.startSpinner({ message: 'Adding comment...' });
505
+
506
+ const result = await githubClient.addComment(
507
+ ownerRepo.owner,
508
+ ownerRepo.repo,
509
+ options.issueNumber,
510
+ options.body
511
+ );
512
+
513
+ if (!result.success || !result.data) {
514
+ ui.stopSpinnerFail('Failed to add comment');
515
+ ui.error(result.error || 'Failed to add comment');
516
+ return;
517
+ }
518
+
519
+ ui.stopSpinnerSuccess('Added comment');
520
+ ui.success('Comment added');
521
+ }
522
+
523
+ // ==========================================
524
+ // Repository Commands
525
+ // ==========================================
526
+
527
+ export interface RepoInfoOptions extends GhCommandOptions {}
528
+
529
+ /**
530
+ * Get repository info
531
+ */
532
+ export async function ghRepoInfoCommand(options: RepoInfoOptions = {}): Promise<void> {
533
+ const ownerRepo = await getOwnerRepo(options);
534
+ if (!ownerRepo) {
535
+ return;
536
+ }
537
+
538
+ ui.startSpinner({ message: 'Fetching repository info...' });
539
+
540
+ const result = await githubClient.getRepo(ownerRepo.owner, ownerRepo.repo);
541
+
542
+ if (!result.success || !result.data) {
543
+ ui.stopSpinnerFail('Failed to fetch repository info');
544
+ ui.error(result.error || 'Failed to fetch repository info');
545
+ return;
546
+ }
547
+
548
+ ui.stopSpinnerSuccess('Fetched repository info');
549
+
550
+ const repo = result.data;
551
+
552
+ if (options.json) {
553
+ console.log(JSON.stringify(repo, null, 2));
554
+ return;
555
+ }
556
+
557
+ ui.header(repo.full_name);
558
+ ui.newLine();
559
+
560
+ if (repo.description) {
561
+ ui.info(repo.description);
562
+ ui.newLine();
563
+ }
564
+
565
+ ui.info(`Visibility: ${repo.private ? 'Private' : 'Public'}`);
566
+ ui.info(`Default Branch: ${repo.default_branch}`);
567
+ if (repo.language) {
568
+ ui.info(`Language: ${repo.language}`);
569
+ }
570
+ ui.info(`Stars: ${repo.stargazers_count}`);
571
+ ui.info(`Forks: ${repo.forks_count}`);
572
+ ui.info(`Open Issues: ${repo.open_issues_count}`);
573
+ }
574
+
575
+ export interface RepoBranchesOptions extends GhCommandOptions {
576
+ limit?: number;
577
+ }
578
+
579
+ /**
580
+ * List repository branches
581
+ */
582
+ export async function ghRepoBranchesCommand(options: RepoBranchesOptions = {}): Promise<void> {
583
+ const ownerRepo = await getOwnerRepo(options);
584
+ if (!ownerRepo) {
585
+ return;
586
+ }
587
+
588
+ ui.startSpinner({ message: 'Fetching branches...' });
589
+
590
+ const result = await githubClient.listBranches(ownerRepo.owner, ownerRepo.repo, {
591
+ perPage: options.limit || 20,
592
+ });
593
+
594
+ if (!result.success || !result.data) {
595
+ ui.stopSpinnerFail('Failed to fetch branches');
596
+ ui.error(result.error || 'Failed to fetch branches');
597
+ return;
598
+ }
599
+
600
+ ui.stopSpinnerSuccess('Fetched branches');
601
+
602
+ if (options.json) {
603
+ console.log(JSON.stringify(result.data, null, 2));
604
+ return;
605
+ }
606
+
607
+ if (result.data.length === 0) {
608
+ ui.info('No branches found');
609
+ return;
610
+ }
611
+
612
+ ui.header(`Branches - ${ownerRepo.owner}/${ownerRepo.repo}`);
613
+
614
+ ui.table({
615
+ columns: [
616
+ { key: 'name', header: 'Name' },
617
+ { key: 'sha', header: 'SHA' },
618
+ { key: 'protected', header: 'Protected' },
619
+ ],
620
+ data: result.data.map(branch => ({
621
+ name: branch.name,
622
+ sha: branch.commit.sha.substring(0, 7),
623
+ protected: branch.protected ? ui.color('yes', 'yellow') : 'no',
624
+ })),
625
+ });
626
+ }
627
+
628
+ // ==========================================
629
+ // Main Router
630
+ // ==========================================
631
+
632
+ /**
633
+ * Main gh command router
634
+ */
635
+ export async function ghCommand(subcommand: string, args: string[]): Promise<void> {
636
+ // Parse common options
637
+ const options: GhCommandOptions = {};
638
+ const cleanArgs: string[] = [];
639
+
640
+ for (let i = 0; i < args.length; i++) {
641
+ const arg = args[i];
642
+
643
+ if (arg === '--owner' && args[i + 1]) {
644
+ options.owner = args[++i];
645
+ } else if (arg === '--repo' && args[i + 1]) {
646
+ options.repo = args[++i];
647
+ } else if (arg === '--json') {
648
+ options.json = true;
649
+ } else {
650
+ cleanArgs.push(arg);
651
+ }
652
+ }
653
+
654
+ // PR commands
655
+ if (subcommand === 'pr') {
656
+ const prSubcommand = cleanArgs[0];
657
+
658
+ if (prSubcommand === 'list' || !prSubcommand) {
659
+ const prOptions: PrListOptions = { ...options };
660
+ for (let i = 1; i < cleanArgs.length; i++) {
661
+ if (cleanArgs[i] === '--state' && cleanArgs[i + 1]) {
662
+ prOptions.state = cleanArgs[++i] as 'open' | 'closed' | 'all';
663
+ } else if ((cleanArgs[i] === '--limit' || cleanArgs[i] === '-n') && cleanArgs[i + 1]) {
664
+ prOptions.limit = parseInt(cleanArgs[++i], 10);
665
+ }
666
+ }
667
+ await ghPrListCommand(prOptions);
668
+ return;
669
+ }
670
+
671
+ if (prSubcommand === 'view') {
672
+ const prNumber = parseInt(cleanArgs[1], 10);
673
+ if (isNaN(prNumber)) {
674
+ ui.error('Usage: nimbus gh pr view <number>');
675
+ return;
676
+ }
677
+ await ghPrViewCommand({ ...options, prNumber });
678
+ return;
679
+ }
680
+
681
+ if (prSubcommand === 'create') {
682
+ const prOptions: PrCreateOptions = { ...options, title: '' };
683
+ for (let i = 1; i < cleanArgs.length; i++) {
684
+ if ((cleanArgs[i] === '--title' || cleanArgs[i] === '-t') && cleanArgs[i + 1]) {
685
+ prOptions.title = cleanArgs[++i];
686
+ } else if ((cleanArgs[i] === '--body' || cleanArgs[i] === '-b') && cleanArgs[i + 1]) {
687
+ prOptions.body = cleanArgs[++i];
688
+ } else if (cleanArgs[i] === '--head' && cleanArgs[i + 1]) {
689
+ prOptions.head = cleanArgs[++i];
690
+ } else if (cleanArgs[i] === '--base' && cleanArgs[i + 1]) {
691
+ prOptions.base = cleanArgs[++i];
692
+ } else if (cleanArgs[i] === '--draft') {
693
+ prOptions.draft = true;
694
+ }
695
+ }
696
+ if (!prOptions.title) {
697
+ ui.error('Usage: nimbus gh pr create --title "PR Title" [--body "Description"] [--draft]');
698
+ return;
699
+ }
700
+ await ghPrCreateCommand(prOptions);
701
+ return;
702
+ }
703
+
704
+ if (prSubcommand === 'merge') {
705
+ const prNumber = parseInt(cleanArgs[1], 10);
706
+ if (isNaN(prNumber)) {
707
+ ui.error('Usage: nimbus gh pr merge <number> [--method squash|merge|rebase]');
708
+ return;
709
+ }
710
+ const mergeOptions: PrMergeOptions = { ...options, prNumber };
711
+ for (let i = 2; i < cleanArgs.length; i++) {
712
+ if (cleanArgs[i] === '--method' && cleanArgs[i + 1]) {
713
+ mergeOptions.method = cleanArgs[++i] as 'merge' | 'squash' | 'rebase';
714
+ }
715
+ }
716
+ await ghPrMergeCommand(mergeOptions);
717
+ return;
718
+ }
719
+
720
+ if (prSubcommand === 'review') {
721
+ const prNumber = parseInt(cleanArgs[1], 10);
722
+ if (isNaN(prNumber)) {
723
+ ui.error(
724
+ 'Usage: nimbus gh pr review <number> --event APPROVE|REQUEST_CHANGES|COMMENT [--body "..."]'
725
+ );
726
+ return;
727
+ }
728
+ const reviewOptions: PrReviewOptions = { ...options, prNumber, event: 'COMMENT' };
729
+ for (let i = 2; i < cleanArgs.length; i++) {
730
+ if (cleanArgs[i] === '--event' && cleanArgs[i + 1]) {
731
+ reviewOptions.event = cleanArgs[++i] as 'APPROVE' | 'REQUEST_CHANGES' | 'COMMENT';
732
+ } else if ((cleanArgs[i] === '--body' || cleanArgs[i] === '-b') && cleanArgs[i + 1]) {
733
+ reviewOptions.body = cleanArgs[++i];
734
+ }
735
+ }
736
+ await ghPrReviewCommand(reviewOptions);
737
+ return;
738
+ }
739
+
740
+ ui.error(`Unknown pr subcommand: ${prSubcommand}`);
741
+ return;
742
+ }
743
+
744
+ // Issue commands
745
+ if (subcommand === 'issue') {
746
+ const issueSubcommand = cleanArgs[0];
747
+
748
+ if (issueSubcommand === 'list' || !issueSubcommand) {
749
+ const issueOptions: IssueListOptions = { ...options };
750
+ for (let i = 1; i < cleanArgs.length; i++) {
751
+ if (cleanArgs[i] === '--state' && cleanArgs[i + 1]) {
752
+ issueOptions.state = cleanArgs[++i] as 'open' | 'closed' | 'all';
753
+ } else if ((cleanArgs[i] === '--limit' || cleanArgs[i] === '-n') && cleanArgs[i + 1]) {
754
+ issueOptions.limit = parseInt(cleanArgs[++i], 10);
755
+ }
756
+ }
757
+ await ghIssueListCommand(issueOptions);
758
+ return;
759
+ }
760
+
761
+ if (issueSubcommand === 'view') {
762
+ const issueNumber = parseInt(cleanArgs[1], 10);
763
+ if (isNaN(issueNumber)) {
764
+ ui.error('Usage: nimbus gh issue view <number>');
765
+ return;
766
+ }
767
+ await ghIssueViewCommand({ ...options, issueNumber });
768
+ return;
769
+ }
770
+
771
+ if (issueSubcommand === 'create') {
772
+ const issueOptions: IssueCreateOptions = { ...options, title: '' };
773
+ for (let i = 1; i < cleanArgs.length; i++) {
774
+ if ((cleanArgs[i] === '--title' || cleanArgs[i] === '-t') && cleanArgs[i + 1]) {
775
+ issueOptions.title = cleanArgs[++i];
776
+ } else if ((cleanArgs[i] === '--body' || cleanArgs[i] === '-b') && cleanArgs[i + 1]) {
777
+ issueOptions.body = cleanArgs[++i];
778
+ } else if (cleanArgs[i] === '--label' && cleanArgs[i + 1]) {
779
+ issueOptions.labels = issueOptions.labels || [];
780
+ issueOptions.labels.push(cleanArgs[++i]);
781
+ } else if (cleanArgs[i] === '--assignee' && cleanArgs[i + 1]) {
782
+ issueOptions.assignees = issueOptions.assignees || [];
783
+ issueOptions.assignees.push(cleanArgs[++i]);
784
+ }
785
+ }
786
+ if (!issueOptions.title) {
787
+ ui.error('Usage: nimbus gh issue create --title "Issue Title" [--body "Description"]');
788
+ return;
789
+ }
790
+ await ghIssueCreateCommand(issueOptions);
791
+ return;
792
+ }
793
+
794
+ if (issueSubcommand === 'close') {
795
+ const issueNumber = parseInt(cleanArgs[1], 10);
796
+ if (isNaN(issueNumber)) {
797
+ ui.error('Usage: nimbus gh issue close <number>');
798
+ return;
799
+ }
800
+ await ghIssueCloseCommand({ ...options, issueNumber });
801
+ return;
802
+ }
803
+
804
+ if (issueSubcommand === 'comment') {
805
+ const issueNumber = parseInt(cleanArgs[1], 10);
806
+ let body = '';
807
+ for (let i = 2; i < cleanArgs.length; i++) {
808
+ if ((cleanArgs[i] === '--body' || cleanArgs[i] === '-b') && cleanArgs[i + 1]) {
809
+ body = cleanArgs[++i];
810
+ }
811
+ }
812
+ if (isNaN(issueNumber) || !body) {
813
+ ui.error('Usage: nimbus gh issue comment <number> --body "Comment text"');
814
+ return;
815
+ }
816
+ await ghIssueCommentCommand({ ...options, issueNumber, body });
817
+ return;
818
+ }
819
+
820
+ ui.error(`Unknown issue subcommand: ${issueSubcommand}`);
821
+ return;
822
+ }
823
+
824
+ // Repo commands
825
+ if (subcommand === 'repo') {
826
+ const repoSubcommand = cleanArgs[0];
827
+
828
+ if (repoSubcommand === 'info' || !repoSubcommand) {
829
+ await ghRepoInfoCommand(options);
830
+ return;
831
+ }
832
+
833
+ if (repoSubcommand === 'branches') {
834
+ const branchOptions: RepoBranchesOptions = { ...options };
835
+ for (let i = 1; i < cleanArgs.length; i++) {
836
+ if ((cleanArgs[i] === '--limit' || cleanArgs[i] === '-n') && cleanArgs[i + 1]) {
837
+ branchOptions.limit = parseInt(cleanArgs[++i], 10);
838
+ }
839
+ }
840
+ await ghRepoBranchesCommand(branchOptions);
841
+ return;
842
+ }
843
+
844
+ ui.error(`Unknown repo subcommand: ${repoSubcommand}`);
845
+ return;
846
+ }
847
+
848
+ ui.error(`Unknown gh subcommand: ${subcommand}`);
849
+ console.log('');
850
+ console.log('Available subcommands:');
851
+ console.log(' pr list - List pull requests');
852
+ console.log(' pr view <number> - View a pull request');
853
+ console.log(' pr create - Create a pull request');
854
+ console.log(' pr merge <number> - Merge a pull request');
855
+ console.log(' pr review <number> - Review a pull request');
856
+ console.log(' issue list - List issues');
857
+ console.log(' issue view <number> - View an issue');
858
+ console.log(' issue create - Create an issue');
859
+ console.log(' issue close <number> - Close an issue');
860
+ console.log(' issue comment <n> - Add a comment');
861
+ console.log(' repo info - Show repository info');
862
+ console.log(' repo branches - List branches');
863
+ }