@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.
- package/LICENSE +21 -0
- package/README.md +628 -0
- package/bin/nimbus +38 -0
- package/package.json +80 -0
- package/src/__tests__/app.test.ts +76 -0
- package/src/__tests__/audit.test.ts +877 -0
- package/src/__tests__/circuit-breaker.test.ts +116 -0
- package/src/__tests__/cli-run.test.ts +115 -0
- package/src/__tests__/context-manager.test.ts +502 -0
- package/src/__tests__/context.test.ts +242 -0
- package/src/__tests__/enterprise.test.ts +401 -0
- package/src/__tests__/generator.test.ts +433 -0
- package/src/__tests__/hooks.test.ts +582 -0
- package/src/__tests__/init.test.ts +436 -0
- package/src/__tests__/intent-parser.test.ts +229 -0
- package/src/__tests__/llm-router.test.ts +209 -0
- package/src/__tests__/lsp.test.ts +293 -0
- package/src/__tests__/modes.test.ts +336 -0
- package/src/__tests__/permissions.test.ts +338 -0
- package/src/__tests__/serve.test.ts +275 -0
- package/src/__tests__/sessions.test.ts +227 -0
- package/src/__tests__/sharing.test.ts +288 -0
- package/src/__tests__/snapshots.test.ts +581 -0
- package/src/__tests__/state-db.test.ts +334 -0
- package/src/__tests__/stream-with-tools.test.ts +732 -0
- package/src/__tests__/subagents.test.ts +176 -0
- package/src/__tests__/system-prompt.test.ts +169 -0
- package/src/__tests__/tool-converter.test.ts +256 -0
- package/src/__tests__/tool-schemas.test.ts +397 -0
- package/src/__tests__/tools.test.ts +143 -0
- package/src/__tests__/version.test.ts +49 -0
- package/src/agent/compaction-agent.ts +227 -0
- package/src/agent/context-manager.ts +435 -0
- package/src/agent/context.ts +427 -0
- package/src/agent/deploy-preview.ts +426 -0
- package/src/agent/index.ts +68 -0
- package/src/agent/loop.ts +717 -0
- package/src/agent/modes.ts +429 -0
- package/src/agent/permissions.ts +466 -0
- package/src/agent/subagents/base.ts +116 -0
- package/src/agent/subagents/cost.ts +51 -0
- package/src/agent/subagents/explore.ts +42 -0
- package/src/agent/subagents/general.ts +54 -0
- package/src/agent/subagents/index.ts +102 -0
- package/src/agent/subagents/infra.ts +59 -0
- package/src/agent/subagents/security.ts +69 -0
- package/src/agent/system-prompt.ts +436 -0
- package/src/app.ts +122 -0
- package/src/audit/activity-log.ts +290 -0
- package/src/audit/compliance-checker.ts +540 -0
- package/src/audit/cost-tracker.ts +318 -0
- package/src/audit/index.ts +23 -0
- package/src/audit/security-scanner.ts +596 -0
- package/src/auth/guard.ts +75 -0
- package/src/auth/index.ts +56 -0
- package/src/auth/oauth.ts +455 -0
- package/src/auth/providers.ts +470 -0
- package/src/auth/sso.ts +113 -0
- package/src/auth/store.ts +505 -0
- package/src/auth/types.ts +187 -0
- package/src/build.ts +141 -0
- package/src/cli/index.ts +16 -0
- package/src/cli/init.ts +854 -0
- package/src/cli/openapi-spec.ts +356 -0
- package/src/cli/run.ts +237 -0
- package/src/cli/serve-auth.ts +80 -0
- package/src/cli/serve.ts +462 -0
- package/src/cli/web.ts +67 -0
- package/src/cli.ts +1417 -0
- package/src/clients/core-engine-client.ts +227 -0
- package/src/clients/enterprise-client.ts +334 -0
- package/src/clients/generator-client.ts +351 -0
- package/src/clients/git-client.ts +627 -0
- package/src/clients/github-client.ts +410 -0
- package/src/clients/helm-client.ts +504 -0
- package/src/clients/index.ts +80 -0
- package/src/clients/k8s-client.ts +497 -0
- package/src/clients/llm-client.ts +161 -0
- package/src/clients/rest-client.ts +130 -0
- package/src/clients/service-discovery.ts +33 -0
- package/src/clients/terraform-client.ts +482 -0
- package/src/clients/tools-client.ts +1843 -0
- package/src/clients/ws-client.ts +115 -0
- package/src/commands/analyze/index.ts +352 -0
- package/src/commands/apply/helm.ts +473 -0
- package/src/commands/apply/index.ts +213 -0
- package/src/commands/apply/k8s.ts +454 -0
- package/src/commands/apply/terraform.ts +582 -0
- package/src/commands/ask.ts +167 -0
- package/src/commands/audit/index.ts +238 -0
- package/src/commands/auth-cloud.ts +294 -0
- package/src/commands/auth-list.ts +134 -0
- package/src/commands/auth-profile.ts +121 -0
- package/src/commands/auth-status.ts +141 -0
- package/src/commands/aws/ec2.ts +501 -0
- package/src/commands/aws/iam.ts +397 -0
- package/src/commands/aws/index.ts +133 -0
- package/src/commands/aws/lambda.ts +396 -0
- package/src/commands/aws/rds.ts +439 -0
- package/src/commands/aws/s3.ts +439 -0
- package/src/commands/aws/vpc.ts +393 -0
- package/src/commands/aws-discover.ts +649 -0
- package/src/commands/aws-terraform.ts +805 -0
- package/src/commands/azure/aks.ts +376 -0
- package/src/commands/azure/functions.ts +253 -0
- package/src/commands/azure/index.ts +116 -0
- package/src/commands/azure/storage.ts +478 -0
- package/src/commands/azure/vm.ts +355 -0
- package/src/commands/billing/index.ts +256 -0
- package/src/commands/chat.ts +314 -0
- package/src/commands/config.ts +346 -0
- package/src/commands/cost/cloud-cost-estimator.ts +266 -0
- package/src/commands/cost/estimator.ts +79 -0
- package/src/commands/cost/index.ts +594 -0
- package/src/commands/cost/parsers/terraform.ts +273 -0
- package/src/commands/cost/parsers/types.ts +25 -0
- package/src/commands/cost/pricing/aws.ts +544 -0
- package/src/commands/cost/pricing/azure.ts +499 -0
- package/src/commands/cost/pricing/gcp.ts +396 -0
- package/src/commands/cost/pricing/index.ts +40 -0
- package/src/commands/demo.ts +250 -0
- package/src/commands/doctor.ts +794 -0
- package/src/commands/drift/index.ts +439 -0
- package/src/commands/explain.ts +277 -0
- package/src/commands/feedback.ts +389 -0
- package/src/commands/fix.ts +324 -0
- package/src/commands/fs/index.ts +402 -0
- package/src/commands/gcp/compute.ts +325 -0
- package/src/commands/gcp/functions.ts +271 -0
- package/src/commands/gcp/gke.ts +438 -0
- package/src/commands/gcp/iam.ts +344 -0
- package/src/commands/gcp/index.ts +129 -0
- package/src/commands/gcp/storage.ts +284 -0
- package/src/commands/generate-helm.ts +1249 -0
- package/src/commands/generate-k8s.ts +1560 -0
- package/src/commands/generate-terraform.ts +1460 -0
- package/src/commands/gh/index.ts +863 -0
- package/src/commands/git/index.ts +1343 -0
- package/src/commands/helm/index.ts +1126 -0
- package/src/commands/help.ts +539 -0
- package/src/commands/history.ts +142 -0
- package/src/commands/import.ts +868 -0
- package/src/commands/index.ts +367 -0
- package/src/commands/init.ts +1046 -0
- package/src/commands/k8s/index.ts +1137 -0
- package/src/commands/login.ts +631 -0
- package/src/commands/logout.ts +83 -0
- package/src/commands/onboarding.ts +228 -0
- package/src/commands/plan/display.ts +279 -0
- package/src/commands/plan/index.ts +599 -0
- package/src/commands/preview.ts +452 -0
- package/src/commands/questionnaire.ts +1270 -0
- package/src/commands/resume.ts +55 -0
- package/src/commands/team/index.ts +346 -0
- package/src/commands/template.ts +232 -0
- package/src/commands/tf/index.ts +1034 -0
- package/src/commands/upgrade.ts +550 -0
- package/src/commands/usage/index.ts +134 -0
- package/src/commands/version.ts +170 -0
- package/src/compat/index.ts +2 -0
- package/src/compat/runtime.ts +12 -0
- package/src/compat/sqlite.ts +107 -0
- package/src/config/index.ts +17 -0
- package/src/config/manager.ts +530 -0
- package/src/config/safety-policy.ts +358 -0
- package/src/config/schema.ts +125 -0
- package/src/config/types.ts +527 -0
- package/src/context/context-db.ts +199 -0
- package/src/demo/index.ts +349 -0
- package/src/demo/scenarios/full-journey.ts +229 -0
- package/src/demo/scenarios/getting-started.ts +127 -0
- package/src/demo/scenarios/helm-release.ts +341 -0
- package/src/demo/scenarios/k8s-deployment.ts +194 -0
- package/src/demo/scenarios/terraform-vpc.ts +170 -0
- package/src/demo/types.ts +92 -0
- package/src/engine/cost-estimator.ts +438 -0
- package/src/engine/diagram-generator.ts +256 -0
- package/src/engine/drift-detector.ts +902 -0
- package/src/engine/executor.ts +1035 -0
- package/src/engine/index.ts +76 -0
- package/src/engine/orchestrator.ts +636 -0
- package/src/engine/planner.ts +720 -0
- package/src/engine/safety.ts +743 -0
- package/src/engine/verifier.ts +770 -0
- package/src/enterprise/audit.ts +348 -0
- package/src/enterprise/auth.ts +270 -0
- package/src/enterprise/billing.ts +822 -0
- package/src/enterprise/index.ts +17 -0
- package/src/enterprise/teams.ts +443 -0
- package/src/generator/best-practices.ts +1608 -0
- package/src/generator/helm.ts +630 -0
- package/src/generator/index.ts +37 -0
- package/src/generator/intent-parser.ts +514 -0
- package/src/generator/kubernetes.ts +976 -0
- package/src/generator/terraform.ts +1867 -0
- package/src/history/index.ts +8 -0
- package/src/history/manager.ts +322 -0
- package/src/history/types.ts +34 -0
- package/src/hooks/config.ts +432 -0
- package/src/hooks/engine.ts +391 -0
- package/src/hooks/index.ts +4 -0
- package/src/llm/auth-bridge.ts +198 -0
- package/src/llm/circuit-breaker.ts +140 -0
- package/src/llm/config-loader.ts +201 -0
- package/src/llm/cost-calculator.ts +171 -0
- package/src/llm/index.ts +8 -0
- package/src/llm/model-aliases.ts +115 -0
- package/src/llm/provider-registry.ts +63 -0
- package/src/llm/providers/anthropic.ts +433 -0
- package/src/llm/providers/bedrock.ts +477 -0
- package/src/llm/providers/google.ts +405 -0
- package/src/llm/providers/ollama.ts +767 -0
- package/src/llm/providers/openai-compatible.ts +340 -0
- package/src/llm/providers/openai.ts +328 -0
- package/src/llm/providers/openrouter.ts +338 -0
- package/src/llm/router.ts +1035 -0
- package/src/llm/types.ts +232 -0
- package/src/lsp/client.ts +298 -0
- package/src/lsp/languages.ts +116 -0
- package/src/lsp/manager.ts +278 -0
- package/src/mcp/client.ts +402 -0
- package/src/mcp/index.ts +5 -0
- package/src/mcp/manager.ts +133 -0
- package/src/nimbus.ts +214 -0
- package/src/plugins/index.ts +27 -0
- package/src/plugins/loader.ts +334 -0
- package/src/plugins/manager.ts +376 -0
- package/src/plugins/types.ts +284 -0
- package/src/scanners/cicd-scanner.ts +258 -0
- package/src/scanners/cloud-scanner.ts +466 -0
- package/src/scanners/framework-scanner.ts +469 -0
- package/src/scanners/iac-scanner.ts +388 -0
- package/src/scanners/index.ts +539 -0
- package/src/scanners/language-scanner.ts +276 -0
- package/src/scanners/package-manager-scanner.ts +277 -0
- package/src/scanners/types.ts +172 -0
- package/src/sessions/manager.ts +365 -0
- package/src/sessions/types.ts +44 -0
- package/src/sharing/sync.ts +296 -0
- package/src/sharing/viewer.ts +97 -0
- package/src/snapshots/index.ts +2 -0
- package/src/snapshots/manager.ts +530 -0
- package/src/state/artifacts.ts +147 -0
- package/src/state/audit.ts +137 -0
- package/src/state/billing.ts +240 -0
- package/src/state/checkpoints.ts +117 -0
- package/src/state/config.ts +67 -0
- package/src/state/conversations.ts +14 -0
- package/src/state/credentials.ts +154 -0
- package/src/state/db.ts +58 -0
- package/src/state/index.ts +26 -0
- package/src/state/messages.ts +115 -0
- package/src/state/projects.ts +123 -0
- package/src/state/schema.ts +236 -0
- package/src/state/sessions.ts +147 -0
- package/src/state/teams.ts +200 -0
- package/src/telemetry.ts +108 -0
- package/src/tools/aws-ops.ts +952 -0
- package/src/tools/azure-ops.ts +579 -0
- package/src/tools/file-ops.ts +593 -0
- package/src/tools/gcp-ops.ts +625 -0
- package/src/tools/git-ops.ts +773 -0
- package/src/tools/github-ops.ts +799 -0
- package/src/tools/helm-ops.ts +943 -0
- package/src/tools/index.ts +17 -0
- package/src/tools/k8s-ops.ts +819 -0
- package/src/tools/schemas/converter.ts +184 -0
- package/src/tools/schemas/devops.ts +612 -0
- package/src/tools/schemas/index.ts +73 -0
- package/src/tools/schemas/standard.ts +1144 -0
- package/src/tools/schemas/types.ts +705 -0
- package/src/tools/terraform-ops.ts +862 -0
- package/src/types/ambient.d.ts +193 -0
- package/src/types/config.ts +83 -0
- package/src/types/drift.ts +116 -0
- package/src/types/enterprise.ts +335 -0
- package/src/types/index.ts +20 -0
- package/src/types/plan.ts +44 -0
- package/src/types/request.ts +65 -0
- package/src/types/response.ts +54 -0
- package/src/types/service.ts +51 -0
- package/src/ui/App.tsx +997 -0
- package/src/ui/DeployPreview.tsx +169 -0
- package/src/ui/Header.tsx +68 -0
- package/src/ui/InputBox.tsx +350 -0
- package/src/ui/MessageList.tsx +585 -0
- package/src/ui/PermissionPrompt.tsx +151 -0
- package/src/ui/StatusBar.tsx +158 -0
- package/src/ui/ToolCallDisplay.tsx +409 -0
- package/src/ui/chat-ui.ts +853 -0
- package/src/ui/index.ts +33 -0
- package/src/ui/ink/index.ts +711 -0
- package/src/ui/streaming.ts +176 -0
- package/src/ui/types.ts +57 -0
- package/src/utils/analytics.ts +72 -0
- package/src/utils/cost-warning.ts +27 -0
- package/src/utils/env.ts +46 -0
- package/src/utils/errors.ts +69 -0
- package/src/utils/event-bus.ts +38 -0
- package/src/utils/index.ts +24 -0
- package/src/utils/logger.ts +171 -0
- package/src/utils/rate-limiter.ts +121 -0
- package/src/utils/service-auth.ts +49 -0
- package/src/utils/validation.ts +53 -0
- package/src/version.ts +4 -0
- package/src/watcher/index.ts +163 -0
- package/src/wizard/approval.ts +383 -0
- package/src/wizard/index.ts +25 -0
- package/src/wizard/prompts.ts +338 -0
- package/src/wizard/types.ts +171 -0
- package/src/wizard/ui.ts +556 -0
- package/src/wizard/wizard.ts +304 -0
- 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
|
+
}
|