@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,1343 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Git Commands
|
|
3
|
+
*
|
|
4
|
+
* CLI commands for Git operations
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import { gitClient } from '../../clients';
|
|
8
|
+
import { ui } from '../../wizard/ui';
|
|
9
|
+
import { historyManager } from '../../history';
|
|
10
|
+
|
|
11
|
+
export interface GitCommandOptions {
|
|
12
|
+
directory?: string;
|
|
13
|
+
all?: boolean;
|
|
14
|
+
amend?: boolean;
|
|
15
|
+
remote?: string;
|
|
16
|
+
branch?: string;
|
|
17
|
+
force?: boolean;
|
|
18
|
+
setUpstream?: boolean;
|
|
19
|
+
rebase?: boolean;
|
|
20
|
+
prune?: boolean;
|
|
21
|
+
limit?: number;
|
|
22
|
+
staged?: boolean;
|
|
23
|
+
file?: string;
|
|
24
|
+
create?: boolean;
|
|
25
|
+
// tag options
|
|
26
|
+
message?: string;
|
|
27
|
+
annotated?: boolean;
|
|
28
|
+
tagName?: string;
|
|
29
|
+
// reset options
|
|
30
|
+
soft?: boolean;
|
|
31
|
+
mixed?: boolean;
|
|
32
|
+
hard?: boolean;
|
|
33
|
+
// revert options
|
|
34
|
+
noCommit?: boolean;
|
|
35
|
+
noEdit?: boolean;
|
|
36
|
+
// cherry-pick options
|
|
37
|
+
// blame options
|
|
38
|
+
lineRange?: string;
|
|
39
|
+
// init options
|
|
40
|
+
bare?: boolean;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* Show git status
|
|
45
|
+
*/
|
|
46
|
+
export async function gitStatusCommand(options: GitCommandOptions = {}): Promise<void> {
|
|
47
|
+
ui.header('Git Status');
|
|
48
|
+
|
|
49
|
+
ui.startSpinner({ message: 'Getting git status...' });
|
|
50
|
+
|
|
51
|
+
try {
|
|
52
|
+
const available = await gitClient.isAvailable();
|
|
53
|
+
if (!available) {
|
|
54
|
+
ui.stopSpinnerFail('Git Tools Service not available');
|
|
55
|
+
ui.error('Please ensure the Git Tools Service is running.');
|
|
56
|
+
return;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
const result = await gitClient.status(options.directory);
|
|
60
|
+
|
|
61
|
+
if (result.success) {
|
|
62
|
+
ui.stopSpinnerSuccess(`On branch ${result.status.branch}`);
|
|
63
|
+
|
|
64
|
+
if (result.status.ahead > 0 || result.status.behind > 0) {
|
|
65
|
+
ui.info(
|
|
66
|
+
`Your branch is ${result.status.ahead} commit(s) ahead, ${result.status.behind} commit(s) behind`
|
|
67
|
+
);
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
if (result.status.staged.length > 0) {
|
|
71
|
+
ui.success('Changes to be committed:');
|
|
72
|
+
result.status.staged.forEach(f => ui.info(` ${f}`));
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
if (result.status.modified.length > 0) {
|
|
76
|
+
ui.warning('Changes not staged for commit:');
|
|
77
|
+
result.status.modified.forEach(f => ui.info(` modified: ${f}`));
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
if (result.status.untracked.length > 0) {
|
|
81
|
+
ui.info('Untracked files:');
|
|
82
|
+
result.status.untracked.forEach(f => ui.info(` ${f}`));
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
if (result.status.deleted.length > 0) {
|
|
86
|
+
ui.error('Deleted files:');
|
|
87
|
+
result.status.deleted.forEach(f => ui.info(` deleted: ${f}`));
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
if (
|
|
91
|
+
result.status.staged.length === 0 &&
|
|
92
|
+
result.status.modified.length === 0 &&
|
|
93
|
+
result.status.untracked.length === 0 &&
|
|
94
|
+
result.status.deleted.length === 0
|
|
95
|
+
) {
|
|
96
|
+
ui.success('Working tree clean');
|
|
97
|
+
}
|
|
98
|
+
} else {
|
|
99
|
+
ui.stopSpinnerFail('Failed to get status');
|
|
100
|
+
if (result.error) {
|
|
101
|
+
ui.error(result.error);
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
} catch (error: any) {
|
|
105
|
+
ui.stopSpinnerFail('Error getting git status');
|
|
106
|
+
ui.error(error.message);
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
/**
|
|
111
|
+
* Stage files
|
|
112
|
+
*/
|
|
113
|
+
export async function gitAddCommand(
|
|
114
|
+
files: string[],
|
|
115
|
+
options: GitCommandOptions = {}
|
|
116
|
+
): Promise<void> {
|
|
117
|
+
ui.header('Git Add');
|
|
118
|
+
|
|
119
|
+
if (options.all) {
|
|
120
|
+
ui.info('Staging all changes');
|
|
121
|
+
} else {
|
|
122
|
+
ui.info(`Staging: ${files.join(', ')}`);
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
ui.startSpinner({ message: 'Staging files...' });
|
|
126
|
+
|
|
127
|
+
try {
|
|
128
|
+
const available = await gitClient.isAvailable();
|
|
129
|
+
if (!available) {
|
|
130
|
+
ui.stopSpinnerFail('Git Tools Service not available');
|
|
131
|
+
ui.error('Please ensure the Git Tools Service is running.');
|
|
132
|
+
return;
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
const result = await gitClient.add(files, {
|
|
136
|
+
directory: options.directory,
|
|
137
|
+
all: options.all,
|
|
138
|
+
});
|
|
139
|
+
|
|
140
|
+
if (result.success) {
|
|
141
|
+
ui.stopSpinnerSuccess('Files staged');
|
|
142
|
+
if (result.output) {
|
|
143
|
+
ui.info(result.output);
|
|
144
|
+
}
|
|
145
|
+
} else {
|
|
146
|
+
ui.stopSpinnerFail('Failed to stage files');
|
|
147
|
+
if (result.error) {
|
|
148
|
+
ui.error(result.error);
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
} catch (error: any) {
|
|
152
|
+
ui.stopSpinnerFail('Error staging files');
|
|
153
|
+
ui.error(error.message);
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
/**
|
|
158
|
+
* Create a commit
|
|
159
|
+
*/
|
|
160
|
+
export async function gitCommitCommand(
|
|
161
|
+
message: string,
|
|
162
|
+
options: GitCommandOptions = {}
|
|
163
|
+
): Promise<void> {
|
|
164
|
+
ui.header('Git Commit');
|
|
165
|
+
|
|
166
|
+
if (options.amend) {
|
|
167
|
+
ui.info('Amending previous commit');
|
|
168
|
+
}
|
|
169
|
+
if (options.all) {
|
|
170
|
+
ui.info('Committing all changes');
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
ui.startSpinner({ message: 'Creating commit...' });
|
|
174
|
+
|
|
175
|
+
try {
|
|
176
|
+
const available = await gitClient.isAvailable();
|
|
177
|
+
if (!available) {
|
|
178
|
+
ui.stopSpinnerFail('Git Tools Service not available');
|
|
179
|
+
ui.error('Please ensure the Git Tools Service is running.');
|
|
180
|
+
return;
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
const result = await gitClient.commit(message, {
|
|
184
|
+
directory: options.directory,
|
|
185
|
+
all: options.all,
|
|
186
|
+
amend: options.amend,
|
|
187
|
+
});
|
|
188
|
+
|
|
189
|
+
if (result.success) {
|
|
190
|
+
ui.stopSpinnerSuccess(`Committed ${result.commit.shortHash}`);
|
|
191
|
+
ui.info(`Author: ${result.commit.author}`);
|
|
192
|
+
ui.info(`Message: ${result.commit.message}`);
|
|
193
|
+
} else {
|
|
194
|
+
ui.stopSpinnerFail('Failed to create commit');
|
|
195
|
+
if (result.error) {
|
|
196
|
+
ui.error(result.error);
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
} catch (error: any) {
|
|
200
|
+
ui.stopSpinnerFail('Error creating commit');
|
|
201
|
+
ui.error(error.message);
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
/**
|
|
206
|
+
* Push to remote
|
|
207
|
+
*/
|
|
208
|
+
export async function gitPushCommand(options: GitCommandOptions = {}): Promise<void> {
|
|
209
|
+
ui.header('Git Push');
|
|
210
|
+
|
|
211
|
+
const remote = options.remote || 'origin';
|
|
212
|
+
ui.info(`Remote: ${remote}`);
|
|
213
|
+
if (options.branch) {
|
|
214
|
+
ui.info(`Branch: ${options.branch}`);
|
|
215
|
+
}
|
|
216
|
+
if (options.force) {
|
|
217
|
+
ui.warning('Force push enabled');
|
|
218
|
+
}
|
|
219
|
+
if (options.setUpstream) {
|
|
220
|
+
ui.info('Setting upstream');
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
ui.startSpinner({ message: 'Pushing to remote...' });
|
|
224
|
+
|
|
225
|
+
try {
|
|
226
|
+
const available = await gitClient.isAvailable();
|
|
227
|
+
if (!available) {
|
|
228
|
+
ui.stopSpinnerFail('Git Tools Service not available');
|
|
229
|
+
ui.error('Please ensure the Git Tools Service is running.');
|
|
230
|
+
return;
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
const result = await gitClient.push({
|
|
234
|
+
directory: options.directory,
|
|
235
|
+
remote: options.remote,
|
|
236
|
+
branch: options.branch,
|
|
237
|
+
force: options.force,
|
|
238
|
+
setUpstream: options.setUpstream,
|
|
239
|
+
});
|
|
240
|
+
|
|
241
|
+
if (result.success) {
|
|
242
|
+
ui.stopSpinnerSuccess('Pushed to remote');
|
|
243
|
+
if (result.output) {
|
|
244
|
+
ui.info(result.output);
|
|
245
|
+
}
|
|
246
|
+
} else {
|
|
247
|
+
ui.stopSpinnerFail('Failed to push');
|
|
248
|
+
if (result.error) {
|
|
249
|
+
ui.error(result.error);
|
|
250
|
+
}
|
|
251
|
+
}
|
|
252
|
+
} catch (error: any) {
|
|
253
|
+
ui.stopSpinnerFail('Error pushing to remote');
|
|
254
|
+
ui.error(error.message);
|
|
255
|
+
}
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
/**
|
|
259
|
+
* Pull from remote
|
|
260
|
+
*/
|
|
261
|
+
export async function gitPullCommand(options: GitCommandOptions = {}): Promise<void> {
|
|
262
|
+
ui.header('Git Pull');
|
|
263
|
+
|
|
264
|
+
const remote = options.remote || 'origin';
|
|
265
|
+
ui.info(`Remote: ${remote}`);
|
|
266
|
+
if (options.branch) {
|
|
267
|
+
ui.info(`Branch: ${options.branch}`);
|
|
268
|
+
}
|
|
269
|
+
if (options.rebase) {
|
|
270
|
+
ui.info('Rebasing enabled');
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
ui.startSpinner({ message: 'Pulling from remote...' });
|
|
274
|
+
|
|
275
|
+
try {
|
|
276
|
+
const available = await gitClient.isAvailable();
|
|
277
|
+
if (!available) {
|
|
278
|
+
ui.stopSpinnerFail('Git Tools Service not available');
|
|
279
|
+
ui.error('Please ensure the Git Tools Service is running.');
|
|
280
|
+
return;
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
const result = await gitClient.pull({
|
|
284
|
+
directory: options.directory,
|
|
285
|
+
remote: options.remote,
|
|
286
|
+
branch: options.branch,
|
|
287
|
+
rebase: options.rebase,
|
|
288
|
+
});
|
|
289
|
+
|
|
290
|
+
if (result.success) {
|
|
291
|
+
ui.stopSpinnerSuccess('Pulled from remote');
|
|
292
|
+
if (result.output) {
|
|
293
|
+
ui.info(result.output);
|
|
294
|
+
}
|
|
295
|
+
} else {
|
|
296
|
+
ui.stopSpinnerFail('Failed to pull');
|
|
297
|
+
if (result.error) {
|
|
298
|
+
ui.error(result.error);
|
|
299
|
+
}
|
|
300
|
+
}
|
|
301
|
+
} catch (error: any) {
|
|
302
|
+
ui.stopSpinnerFail('Error pulling from remote');
|
|
303
|
+
ui.error(error.message);
|
|
304
|
+
}
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
/**
|
|
308
|
+
* Fetch from remote
|
|
309
|
+
*/
|
|
310
|
+
export async function gitFetchCommand(options: GitCommandOptions = {}): Promise<void> {
|
|
311
|
+
ui.header('Git Fetch');
|
|
312
|
+
|
|
313
|
+
if (options.all) {
|
|
314
|
+
ui.info('Fetching from all remotes');
|
|
315
|
+
} else {
|
|
316
|
+
const remote = options.remote || 'origin';
|
|
317
|
+
ui.info(`Remote: ${remote}`);
|
|
318
|
+
}
|
|
319
|
+
if (options.prune) {
|
|
320
|
+
ui.info('Pruning remote-tracking branches');
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
ui.startSpinner({ message: 'Fetching from remote...' });
|
|
324
|
+
|
|
325
|
+
try {
|
|
326
|
+
const available = await gitClient.isAvailable();
|
|
327
|
+
if (!available) {
|
|
328
|
+
ui.stopSpinnerFail('Git Tools Service not available');
|
|
329
|
+
ui.error('Please ensure the Git Tools Service is running.');
|
|
330
|
+
return;
|
|
331
|
+
}
|
|
332
|
+
|
|
333
|
+
const result = await gitClient.fetch({
|
|
334
|
+
directory: options.directory,
|
|
335
|
+
remote: options.remote,
|
|
336
|
+
all: options.all,
|
|
337
|
+
prune: options.prune,
|
|
338
|
+
});
|
|
339
|
+
|
|
340
|
+
if (result.success) {
|
|
341
|
+
ui.stopSpinnerSuccess('Fetched from remote');
|
|
342
|
+
if (result.output) {
|
|
343
|
+
ui.info(result.output);
|
|
344
|
+
}
|
|
345
|
+
} else {
|
|
346
|
+
ui.stopSpinnerFail('Failed to fetch');
|
|
347
|
+
if (result.error) {
|
|
348
|
+
ui.error(result.error);
|
|
349
|
+
}
|
|
350
|
+
}
|
|
351
|
+
} catch (error: any) {
|
|
352
|
+
ui.stopSpinnerFail('Error fetching from remote');
|
|
353
|
+
ui.error(error.message);
|
|
354
|
+
}
|
|
355
|
+
}
|
|
356
|
+
|
|
357
|
+
/**
|
|
358
|
+
* Show commit log
|
|
359
|
+
*/
|
|
360
|
+
export async function gitLogCommand(options: GitCommandOptions = {}): Promise<void> {
|
|
361
|
+
ui.header('Git Log');
|
|
362
|
+
|
|
363
|
+
const limit = options.limit || 10;
|
|
364
|
+
|
|
365
|
+
ui.startSpinner({ message: 'Fetching commit log...' });
|
|
366
|
+
|
|
367
|
+
try {
|
|
368
|
+
const available = await gitClient.isAvailable();
|
|
369
|
+
if (!available) {
|
|
370
|
+
ui.stopSpinnerFail('Git Tools Service not available');
|
|
371
|
+
ui.error('Please ensure the Git Tools Service is running.');
|
|
372
|
+
return;
|
|
373
|
+
}
|
|
374
|
+
|
|
375
|
+
const result = await gitClient.log({
|
|
376
|
+
directory: options.directory,
|
|
377
|
+
limit,
|
|
378
|
+
branch: options.branch,
|
|
379
|
+
});
|
|
380
|
+
|
|
381
|
+
if (result.success) {
|
|
382
|
+
ui.stopSpinnerSuccess(`Found ${result.commits.length} commits`);
|
|
383
|
+
|
|
384
|
+
if (result.commits.length > 0) {
|
|
385
|
+
ui.table({
|
|
386
|
+
columns: [
|
|
387
|
+
{ key: 'hash', header: 'Hash' },
|
|
388
|
+
{ key: 'author', header: 'Author' },
|
|
389
|
+
{ key: 'date', header: 'Date' },
|
|
390
|
+
{ key: 'message', header: 'Message' },
|
|
391
|
+
],
|
|
392
|
+
data: result.commits.map(commit => ({
|
|
393
|
+
hash: commit.shortHash,
|
|
394
|
+
author: commit.author,
|
|
395
|
+
date: commit.date,
|
|
396
|
+
message: commit.message.substring(0, 50) + (commit.message.length > 50 ? '...' : ''),
|
|
397
|
+
})),
|
|
398
|
+
});
|
|
399
|
+
}
|
|
400
|
+
} else {
|
|
401
|
+
ui.stopSpinnerFail('Failed to get log');
|
|
402
|
+
if (result.error) {
|
|
403
|
+
ui.error(result.error);
|
|
404
|
+
}
|
|
405
|
+
}
|
|
406
|
+
} catch (error: any) {
|
|
407
|
+
ui.stopSpinnerFail('Error getting git log');
|
|
408
|
+
ui.error(error.message);
|
|
409
|
+
}
|
|
410
|
+
}
|
|
411
|
+
|
|
412
|
+
/**
|
|
413
|
+
* List branches
|
|
414
|
+
*/
|
|
415
|
+
export async function gitBranchCommand(options: GitCommandOptions = {}): Promise<void> {
|
|
416
|
+
ui.header('Git Branches');
|
|
417
|
+
|
|
418
|
+
ui.startSpinner({ message: 'Fetching branches...' });
|
|
419
|
+
|
|
420
|
+
try {
|
|
421
|
+
const available = await gitClient.isAvailable();
|
|
422
|
+
if (!available) {
|
|
423
|
+
ui.stopSpinnerFail('Git Tools Service not available');
|
|
424
|
+
ui.error('Please ensure the Git Tools Service is running.');
|
|
425
|
+
return;
|
|
426
|
+
}
|
|
427
|
+
|
|
428
|
+
const result = await gitClient.branches({
|
|
429
|
+
directory: options.directory,
|
|
430
|
+
all: options.all,
|
|
431
|
+
});
|
|
432
|
+
|
|
433
|
+
if (result.success) {
|
|
434
|
+
ui.stopSpinnerSuccess(`Found ${result.branches.length} branches`);
|
|
435
|
+
|
|
436
|
+
if (result.branches.length > 0) {
|
|
437
|
+
result.branches.forEach(branch => {
|
|
438
|
+
const prefix = branch.current ? '* ' : ' ';
|
|
439
|
+
const tracking = branch.tracking ? ` -> ${branch.tracking}` : '';
|
|
440
|
+
ui.info(`${prefix}${branch.name}${tracking}`);
|
|
441
|
+
});
|
|
442
|
+
}
|
|
443
|
+
} else {
|
|
444
|
+
ui.stopSpinnerFail('Failed to list branches');
|
|
445
|
+
if (result.error) {
|
|
446
|
+
ui.error(result.error);
|
|
447
|
+
}
|
|
448
|
+
}
|
|
449
|
+
} catch (error: any) {
|
|
450
|
+
ui.stopSpinnerFail('Error listing branches');
|
|
451
|
+
ui.error(error.message);
|
|
452
|
+
}
|
|
453
|
+
}
|
|
454
|
+
|
|
455
|
+
/**
|
|
456
|
+
* Checkout branch or file
|
|
457
|
+
*/
|
|
458
|
+
export async function gitCheckoutCommand(
|
|
459
|
+
target: string,
|
|
460
|
+
options: GitCommandOptions = {}
|
|
461
|
+
): Promise<void> {
|
|
462
|
+
ui.header('Git Checkout');
|
|
463
|
+
|
|
464
|
+
ui.info(`Target: ${target}`);
|
|
465
|
+
if (options.create) {
|
|
466
|
+
ui.info('Creating new branch');
|
|
467
|
+
}
|
|
468
|
+
|
|
469
|
+
ui.startSpinner({ message: `Checking out ${target}...` });
|
|
470
|
+
|
|
471
|
+
try {
|
|
472
|
+
const available = await gitClient.isAvailable();
|
|
473
|
+
if (!available) {
|
|
474
|
+
ui.stopSpinnerFail('Git Tools Service not available');
|
|
475
|
+
ui.error('Please ensure the Git Tools Service is running.');
|
|
476
|
+
return;
|
|
477
|
+
}
|
|
478
|
+
|
|
479
|
+
const result = await gitClient.checkout(target, {
|
|
480
|
+
directory: options.directory,
|
|
481
|
+
create: options.create,
|
|
482
|
+
});
|
|
483
|
+
|
|
484
|
+
if (result.success) {
|
|
485
|
+
ui.stopSpinnerSuccess(`Checked out ${target}`);
|
|
486
|
+
if (result.output) {
|
|
487
|
+
ui.info(result.output);
|
|
488
|
+
}
|
|
489
|
+
} else {
|
|
490
|
+
ui.stopSpinnerFail(`Failed to checkout ${target}`);
|
|
491
|
+
if (result.error) {
|
|
492
|
+
ui.error(result.error);
|
|
493
|
+
}
|
|
494
|
+
}
|
|
495
|
+
} catch (error: any) {
|
|
496
|
+
ui.stopSpinnerFail(`Error checking out ${target}`);
|
|
497
|
+
ui.error(error.message);
|
|
498
|
+
}
|
|
499
|
+
}
|
|
500
|
+
|
|
501
|
+
/**
|
|
502
|
+
* Show diff
|
|
503
|
+
*/
|
|
504
|
+
export async function gitDiffCommand(options: GitCommandOptions = {}): Promise<void> {
|
|
505
|
+
ui.header('Git Diff');
|
|
506
|
+
|
|
507
|
+
if (options.staged) {
|
|
508
|
+
ui.info('Showing staged changes');
|
|
509
|
+
}
|
|
510
|
+
if (options.file) {
|
|
511
|
+
ui.info(`File: ${options.file}`);
|
|
512
|
+
}
|
|
513
|
+
|
|
514
|
+
ui.startSpinner({ message: 'Getting diff...' });
|
|
515
|
+
|
|
516
|
+
try {
|
|
517
|
+
const available = await gitClient.isAvailable();
|
|
518
|
+
if (!available) {
|
|
519
|
+
ui.stopSpinnerFail('Git Tools Service not available');
|
|
520
|
+
ui.error('Please ensure the Git Tools Service is running.');
|
|
521
|
+
return;
|
|
522
|
+
}
|
|
523
|
+
|
|
524
|
+
const result = await gitClient.diff({
|
|
525
|
+
directory: options.directory,
|
|
526
|
+
staged: options.staged,
|
|
527
|
+
file: options.file,
|
|
528
|
+
});
|
|
529
|
+
|
|
530
|
+
if (result.success) {
|
|
531
|
+
ui.stopSpinnerSuccess('Diff retrieved');
|
|
532
|
+
if (result.diff) {
|
|
533
|
+
// Display diff as raw text - sideBySideDiff requires original/modified strings
|
|
534
|
+
console.log(result.diff);
|
|
535
|
+
} else {
|
|
536
|
+
ui.info('No changes');
|
|
537
|
+
}
|
|
538
|
+
} else {
|
|
539
|
+
ui.stopSpinnerFail('Failed to get diff');
|
|
540
|
+
if (result.error) {
|
|
541
|
+
ui.error(result.error);
|
|
542
|
+
}
|
|
543
|
+
}
|
|
544
|
+
} catch (error: any) {
|
|
545
|
+
ui.stopSpinnerFail('Error getting diff');
|
|
546
|
+
ui.error(error.message);
|
|
547
|
+
}
|
|
548
|
+
}
|
|
549
|
+
|
|
550
|
+
/**
|
|
551
|
+
* Merge a branch
|
|
552
|
+
*/
|
|
553
|
+
export async function gitMergeCommand(
|
|
554
|
+
branch: string,
|
|
555
|
+
options: GitCommandOptions = {}
|
|
556
|
+
): Promise<void> {
|
|
557
|
+
ui.header('Git Merge');
|
|
558
|
+
|
|
559
|
+
ui.info(`Merging branch: ${branch}`);
|
|
560
|
+
|
|
561
|
+
ui.startSpinner({ message: `Merging ${branch}...` });
|
|
562
|
+
|
|
563
|
+
try {
|
|
564
|
+
const available = await gitClient.isAvailable();
|
|
565
|
+
if (!available) {
|
|
566
|
+
ui.stopSpinnerFail('Git Tools Service not available');
|
|
567
|
+
ui.error('Please ensure the Git Tools Service is running.');
|
|
568
|
+
return;
|
|
569
|
+
}
|
|
570
|
+
|
|
571
|
+
const result = await gitClient.merge(branch, {
|
|
572
|
+
directory: options.directory,
|
|
573
|
+
});
|
|
574
|
+
|
|
575
|
+
if (result.success) {
|
|
576
|
+
ui.stopSpinnerSuccess(`Merged ${branch}`);
|
|
577
|
+
if (result.output) {
|
|
578
|
+
ui.info(result.output);
|
|
579
|
+
}
|
|
580
|
+
} else {
|
|
581
|
+
ui.stopSpinnerFail(`Failed to merge ${branch}`);
|
|
582
|
+
if (result.error) {
|
|
583
|
+
ui.error(result.error);
|
|
584
|
+
}
|
|
585
|
+
}
|
|
586
|
+
} catch (error: any) {
|
|
587
|
+
ui.stopSpinnerFail(`Error merging ${branch}`);
|
|
588
|
+
ui.error(error.message);
|
|
589
|
+
}
|
|
590
|
+
}
|
|
591
|
+
|
|
592
|
+
/**
|
|
593
|
+
* Stash operations
|
|
594
|
+
*/
|
|
595
|
+
export async function gitStashCommand(
|
|
596
|
+
stashAction: 'push' | 'pop' | 'list' | 'drop' | 'apply' | 'clear',
|
|
597
|
+
options: GitCommandOptions & { message?: string; index?: number } = {}
|
|
598
|
+
): Promise<void> {
|
|
599
|
+
ui.header(`Git Stash ${stashAction}`);
|
|
600
|
+
|
|
601
|
+
if (options.message) {
|
|
602
|
+
ui.info(`Message: ${options.message}`);
|
|
603
|
+
}
|
|
604
|
+
|
|
605
|
+
ui.startSpinner({ message: `Running stash ${stashAction}...` });
|
|
606
|
+
|
|
607
|
+
try {
|
|
608
|
+
const available = await gitClient.isAvailable();
|
|
609
|
+
if (!available) {
|
|
610
|
+
ui.stopSpinnerFail('Git Tools Service not available');
|
|
611
|
+
ui.error('Please ensure the Git Tools Service is running.');
|
|
612
|
+
return;
|
|
613
|
+
}
|
|
614
|
+
|
|
615
|
+
const result = await gitClient.stash(stashAction, {
|
|
616
|
+
directory: options.directory,
|
|
617
|
+
message: options.message,
|
|
618
|
+
index: options.index,
|
|
619
|
+
});
|
|
620
|
+
|
|
621
|
+
if (result.success) {
|
|
622
|
+
ui.stopSpinnerSuccess(`Stash ${stashAction} complete`);
|
|
623
|
+
if (result.output) {
|
|
624
|
+
console.log(result.output);
|
|
625
|
+
}
|
|
626
|
+
} else {
|
|
627
|
+
ui.stopSpinnerFail(`Stash ${stashAction} failed`);
|
|
628
|
+
if (result.error) {
|
|
629
|
+
ui.error(result.error);
|
|
630
|
+
}
|
|
631
|
+
}
|
|
632
|
+
} catch (error: any) {
|
|
633
|
+
ui.stopSpinnerFail(`Error during stash ${stashAction}`);
|
|
634
|
+
ui.error(error.message);
|
|
635
|
+
}
|
|
636
|
+
}
|
|
637
|
+
|
|
638
|
+
/**
|
|
639
|
+
* Clone a repository
|
|
640
|
+
*/
|
|
641
|
+
export async function gitCloneCommand(
|
|
642
|
+
url: string,
|
|
643
|
+
targetPath?: string,
|
|
644
|
+
options: GitCommandOptions = {}
|
|
645
|
+
): Promise<void> {
|
|
646
|
+
ui.header('Git Clone');
|
|
647
|
+
|
|
648
|
+
ui.info(`URL: ${url}`);
|
|
649
|
+
if (targetPath) {
|
|
650
|
+
ui.info(`Path: ${targetPath}`);
|
|
651
|
+
}
|
|
652
|
+
if (options.branch) {
|
|
653
|
+
ui.info(`Branch: ${options.branch}`);
|
|
654
|
+
}
|
|
655
|
+
if (options.limit) {
|
|
656
|
+
ui.info(`Depth: ${options.limit}`);
|
|
657
|
+
}
|
|
658
|
+
|
|
659
|
+
ui.startSpinner({ message: `Cloning ${url}...` });
|
|
660
|
+
|
|
661
|
+
try {
|
|
662
|
+
const available = await gitClient.isAvailable();
|
|
663
|
+
if (!available) {
|
|
664
|
+
ui.stopSpinnerFail('Git Tools Service not available');
|
|
665
|
+
ui.error('Please ensure the Git Tools Service is running.');
|
|
666
|
+
return;
|
|
667
|
+
}
|
|
668
|
+
|
|
669
|
+
const result = await gitClient.clone(url, targetPath, {
|
|
670
|
+
branch: options.branch,
|
|
671
|
+
depth: options.limit,
|
|
672
|
+
});
|
|
673
|
+
|
|
674
|
+
if (result.success) {
|
|
675
|
+
ui.stopSpinnerSuccess('Repository cloned');
|
|
676
|
+
if (result.output) {
|
|
677
|
+
ui.info(result.output);
|
|
678
|
+
}
|
|
679
|
+
} else {
|
|
680
|
+
ui.stopSpinnerFail('Failed to clone repository');
|
|
681
|
+
if (result.error) {
|
|
682
|
+
ui.error(result.error);
|
|
683
|
+
}
|
|
684
|
+
}
|
|
685
|
+
} catch (error: any) {
|
|
686
|
+
ui.stopSpinnerFail('Error cloning repository');
|
|
687
|
+
ui.error(error.message);
|
|
688
|
+
}
|
|
689
|
+
}
|
|
690
|
+
|
|
691
|
+
/**
|
|
692
|
+
* Tag operations
|
|
693
|
+
*/
|
|
694
|
+
export async function gitTagCommand(
|
|
695
|
+
tagAction: 'list' | 'create' | 'delete' | 'push' | 'show',
|
|
696
|
+
options: GitCommandOptions & { tagArg?: string } = {}
|
|
697
|
+
): Promise<void> {
|
|
698
|
+
ui.header(`Git Tag ${tagAction}`);
|
|
699
|
+
|
|
700
|
+
ui.startSpinner({ message: `Running tag ${tagAction}...` });
|
|
701
|
+
|
|
702
|
+
try {
|
|
703
|
+
const available = await gitClient.isAvailable();
|
|
704
|
+
if (!available) {
|
|
705
|
+
ui.stopSpinnerFail('Git Tools Service not available');
|
|
706
|
+
ui.error('Please ensure the Git Tools Service is running.');
|
|
707
|
+
return;
|
|
708
|
+
}
|
|
709
|
+
|
|
710
|
+
if (tagAction === 'list') {
|
|
711
|
+
const result = await gitClient.tagList({ directory: options.directory });
|
|
712
|
+
if (result.success) {
|
|
713
|
+
ui.stopSpinnerSuccess(`Found ${result.tags.length} tag(s)`);
|
|
714
|
+
if (result.tags.length > 0) {
|
|
715
|
+
result.tags.forEach(tag => ui.info(` ${tag}`));
|
|
716
|
+
} else {
|
|
717
|
+
ui.info('No tags found');
|
|
718
|
+
}
|
|
719
|
+
} else {
|
|
720
|
+
ui.stopSpinnerFail('Failed to list tags');
|
|
721
|
+
if (result.error) {
|
|
722
|
+
ui.error(result.error);
|
|
723
|
+
}
|
|
724
|
+
}
|
|
725
|
+
} else if (tagAction === 'create') {
|
|
726
|
+
if (!options.tagArg) {
|
|
727
|
+
ui.stopSpinnerFail('Tag name required');
|
|
728
|
+
ui.error('Usage: nimbus git tag create <name> [--message <msg>]');
|
|
729
|
+
return;
|
|
730
|
+
}
|
|
731
|
+
const result = await gitClient.tagCreate(options.tagArg, {
|
|
732
|
+
directory: options.directory,
|
|
733
|
+
message: options.message,
|
|
734
|
+
annotated: options.annotated || !!options.message,
|
|
735
|
+
force: options.force,
|
|
736
|
+
});
|
|
737
|
+
if (result.success) {
|
|
738
|
+
ui.stopSpinnerSuccess(`Tag '${options.tagArg}' created`);
|
|
739
|
+
if (result.output) {
|
|
740
|
+
ui.info(result.output);
|
|
741
|
+
}
|
|
742
|
+
} else {
|
|
743
|
+
ui.stopSpinnerFail(`Failed to create tag '${options.tagArg}'`);
|
|
744
|
+
if (result.error) {
|
|
745
|
+
ui.error(result.error);
|
|
746
|
+
}
|
|
747
|
+
}
|
|
748
|
+
} else if (tagAction === 'delete') {
|
|
749
|
+
if (!options.tagArg) {
|
|
750
|
+
ui.stopSpinnerFail('Tag name required');
|
|
751
|
+
ui.error('Usage: nimbus git tag delete <name>');
|
|
752
|
+
return;
|
|
753
|
+
}
|
|
754
|
+
const result = await gitClient.tagDelete(options.tagArg, { directory: options.directory });
|
|
755
|
+
if (result.success) {
|
|
756
|
+
ui.stopSpinnerSuccess(`Tag '${options.tagArg}' deleted`);
|
|
757
|
+
} else {
|
|
758
|
+
ui.stopSpinnerFail(`Failed to delete tag '${options.tagArg}'`);
|
|
759
|
+
if (result.error) {
|
|
760
|
+
ui.error(result.error);
|
|
761
|
+
}
|
|
762
|
+
}
|
|
763
|
+
} else if (tagAction === 'push') {
|
|
764
|
+
const result = await gitClient.tagPush({
|
|
765
|
+
directory: options.directory,
|
|
766
|
+
remote: options.remote,
|
|
767
|
+
tagName: options.tagArg,
|
|
768
|
+
});
|
|
769
|
+
if (result.success) {
|
|
770
|
+
ui.stopSpinnerSuccess('Tags pushed');
|
|
771
|
+
if (result.output) {
|
|
772
|
+
ui.info(result.output);
|
|
773
|
+
}
|
|
774
|
+
} else {
|
|
775
|
+
ui.stopSpinnerFail('Failed to push tags');
|
|
776
|
+
if (result.error) {
|
|
777
|
+
ui.error(result.error);
|
|
778
|
+
}
|
|
779
|
+
}
|
|
780
|
+
} else if (tagAction === 'show') {
|
|
781
|
+
if (!options.tagArg) {
|
|
782
|
+
ui.stopSpinnerFail('Tag name required');
|
|
783
|
+
ui.error('Usage: nimbus git tag show <name>');
|
|
784
|
+
return;
|
|
785
|
+
}
|
|
786
|
+
const result = await gitClient.tagShow(options.tagArg, { directory: options.directory });
|
|
787
|
+
if (result.success) {
|
|
788
|
+
ui.stopSpinnerSuccess(`Tag info for '${options.tagArg}'`);
|
|
789
|
+
if (result.output) {
|
|
790
|
+
console.log(result.output);
|
|
791
|
+
}
|
|
792
|
+
} else {
|
|
793
|
+
ui.stopSpinnerFail(`Failed to show tag '${options.tagArg}'`);
|
|
794
|
+
if (result.error) {
|
|
795
|
+
ui.error(result.error);
|
|
796
|
+
}
|
|
797
|
+
}
|
|
798
|
+
}
|
|
799
|
+
} catch (error: any) {
|
|
800
|
+
ui.stopSpinnerFail(`Error during tag ${tagAction}`);
|
|
801
|
+
ui.error(error.message);
|
|
802
|
+
}
|
|
803
|
+
}
|
|
804
|
+
|
|
805
|
+
/**
|
|
806
|
+
* Show remote URL
|
|
807
|
+
*/
|
|
808
|
+
export async function gitRemoteCommand(
|
|
809
|
+
remoteName?: string,
|
|
810
|
+
options: GitCommandOptions = {}
|
|
811
|
+
): Promise<void> {
|
|
812
|
+
ui.header('Git Remote');
|
|
813
|
+
|
|
814
|
+
const name = remoteName || 'origin';
|
|
815
|
+
ui.info(`Remote: ${name}`);
|
|
816
|
+
|
|
817
|
+
ui.startSpinner({ message: 'Fetching remote URL...' });
|
|
818
|
+
|
|
819
|
+
try {
|
|
820
|
+
const available = await gitClient.isAvailable();
|
|
821
|
+
if (!available) {
|
|
822
|
+
ui.stopSpinnerFail('Git Tools Service not available');
|
|
823
|
+
ui.error('Please ensure the Git Tools Service is running.');
|
|
824
|
+
return;
|
|
825
|
+
}
|
|
826
|
+
|
|
827
|
+
const result = await gitClient.remote(name, { directory: options.directory });
|
|
828
|
+
|
|
829
|
+
if (result.success) {
|
|
830
|
+
ui.stopSpinnerSuccess(`Remote '${result.remote}'`);
|
|
831
|
+
if (result.url) {
|
|
832
|
+
ui.info(`URL: ${result.url}`);
|
|
833
|
+
} else {
|
|
834
|
+
ui.warning('No URL found for this remote');
|
|
835
|
+
}
|
|
836
|
+
} else {
|
|
837
|
+
ui.stopSpinnerFail('Failed to get remote URL');
|
|
838
|
+
if (result.error) {
|
|
839
|
+
ui.error(result.error);
|
|
840
|
+
}
|
|
841
|
+
}
|
|
842
|
+
} catch (error: any) {
|
|
843
|
+
ui.stopSpinnerFail('Error fetching remote URL');
|
|
844
|
+
ui.error(error.message);
|
|
845
|
+
}
|
|
846
|
+
}
|
|
847
|
+
|
|
848
|
+
/**
|
|
849
|
+
* Reset to a commit
|
|
850
|
+
*/
|
|
851
|
+
export async function gitResetCommand(
|
|
852
|
+
target: string,
|
|
853
|
+
options: GitCommandOptions = {}
|
|
854
|
+
): Promise<void> {
|
|
855
|
+
ui.header('Git Reset');
|
|
856
|
+
|
|
857
|
+
let mode: 'soft' | 'mixed' | 'hard' = 'mixed';
|
|
858
|
+
if (options.soft) {
|
|
859
|
+
mode = 'soft';
|
|
860
|
+
} else if (options.hard) {
|
|
861
|
+
mode = 'hard';
|
|
862
|
+
}
|
|
863
|
+
|
|
864
|
+
ui.info(`Target: ${target}`);
|
|
865
|
+
ui.info(`Mode: ${mode}`);
|
|
866
|
+
|
|
867
|
+
if (mode === 'hard') {
|
|
868
|
+
ui.warning(
|
|
869
|
+
'WARNING: --hard reset will discard all uncommitted changes. This cannot be undone.'
|
|
870
|
+
);
|
|
871
|
+
}
|
|
872
|
+
|
|
873
|
+
ui.startSpinner({ message: `Resetting to ${target} (${mode})...` });
|
|
874
|
+
|
|
875
|
+
try {
|
|
876
|
+
const available = await gitClient.isAvailable();
|
|
877
|
+
if (!available) {
|
|
878
|
+
ui.stopSpinnerFail('Git Tools Service not available');
|
|
879
|
+
ui.error('Please ensure the Git Tools Service is running.');
|
|
880
|
+
return;
|
|
881
|
+
}
|
|
882
|
+
|
|
883
|
+
const result = await gitClient.reset(target, { directory: options.directory, mode });
|
|
884
|
+
|
|
885
|
+
if (result.success) {
|
|
886
|
+
ui.stopSpinnerSuccess(`Reset to ${target} (${mode}) complete`);
|
|
887
|
+
if (result.output) {
|
|
888
|
+
ui.info(result.output);
|
|
889
|
+
}
|
|
890
|
+
} else {
|
|
891
|
+
ui.stopSpinnerFail(`Failed to reset to ${target}`);
|
|
892
|
+
if (result.error) {
|
|
893
|
+
ui.error(result.error);
|
|
894
|
+
}
|
|
895
|
+
}
|
|
896
|
+
} catch (error: any) {
|
|
897
|
+
ui.stopSpinnerFail(`Error resetting to ${target}`);
|
|
898
|
+
ui.error(error.message);
|
|
899
|
+
}
|
|
900
|
+
}
|
|
901
|
+
|
|
902
|
+
/**
|
|
903
|
+
* Revert a commit
|
|
904
|
+
*/
|
|
905
|
+
export async function gitRevertCommand(
|
|
906
|
+
commit: string,
|
|
907
|
+
options: GitCommandOptions = {}
|
|
908
|
+
): Promise<void> {
|
|
909
|
+
ui.header('Git Revert');
|
|
910
|
+
|
|
911
|
+
ui.info(`Reverting commit: ${commit}`);
|
|
912
|
+
if (options.noCommit) {
|
|
913
|
+
ui.info('--no-commit: staging revert without creating a commit');
|
|
914
|
+
}
|
|
915
|
+
if (options.noEdit) {
|
|
916
|
+
ui.info('--no-edit: using default revert commit message');
|
|
917
|
+
}
|
|
918
|
+
|
|
919
|
+
ui.startSpinner({ message: `Reverting ${commit}...` });
|
|
920
|
+
|
|
921
|
+
try {
|
|
922
|
+
const available = await gitClient.isAvailable();
|
|
923
|
+
if (!available) {
|
|
924
|
+
ui.stopSpinnerFail('Git Tools Service not available');
|
|
925
|
+
ui.error('Please ensure the Git Tools Service is running.');
|
|
926
|
+
return;
|
|
927
|
+
}
|
|
928
|
+
|
|
929
|
+
const result = await gitClient.revert(commit, {
|
|
930
|
+
directory: options.directory,
|
|
931
|
+
noCommit: options.noCommit,
|
|
932
|
+
noEdit: options.noEdit,
|
|
933
|
+
});
|
|
934
|
+
|
|
935
|
+
if (result.success) {
|
|
936
|
+
ui.stopSpinnerSuccess(`Reverted commit ${commit}`);
|
|
937
|
+
if (result.output) {
|
|
938
|
+
ui.info(result.output);
|
|
939
|
+
}
|
|
940
|
+
} else {
|
|
941
|
+
ui.stopSpinnerFail(`Failed to revert ${commit}`);
|
|
942
|
+
if (result.error) {
|
|
943
|
+
ui.error(result.error);
|
|
944
|
+
}
|
|
945
|
+
}
|
|
946
|
+
} catch (error: any) {
|
|
947
|
+
ui.stopSpinnerFail(`Error reverting ${commit}`);
|
|
948
|
+
ui.error(error.message);
|
|
949
|
+
}
|
|
950
|
+
}
|
|
951
|
+
|
|
952
|
+
/**
|
|
953
|
+
* Cherry-pick operations
|
|
954
|
+
*/
|
|
955
|
+
export async function gitCherryPickCommand(
|
|
956
|
+
cherryPickAction: 'pick' | 'abort' | 'continue',
|
|
957
|
+
options: GitCommandOptions & { commit?: string } = {}
|
|
958
|
+
): Promise<void> {
|
|
959
|
+
ui.header(`Git Cherry-Pick ${cherryPickAction}`);
|
|
960
|
+
|
|
961
|
+
ui.startSpinner({ message: `Running cherry-pick ${cherryPickAction}...` });
|
|
962
|
+
|
|
963
|
+
try {
|
|
964
|
+
const available = await gitClient.isAvailable();
|
|
965
|
+
if (!available) {
|
|
966
|
+
ui.stopSpinnerFail('Git Tools Service not available');
|
|
967
|
+
ui.error('Please ensure the Git Tools Service is running.');
|
|
968
|
+
return;
|
|
969
|
+
}
|
|
970
|
+
|
|
971
|
+
if (cherryPickAction === 'pick') {
|
|
972
|
+
if (!options.commit) {
|
|
973
|
+
ui.stopSpinnerFail('Commit hash required');
|
|
974
|
+
ui.error('Usage: nimbus git cherry-pick <commit-hash>');
|
|
975
|
+
return;
|
|
976
|
+
}
|
|
977
|
+
const result = await gitClient.cherryPick(options.commit, {
|
|
978
|
+
directory: options.directory,
|
|
979
|
+
noCommit: options.noCommit,
|
|
980
|
+
});
|
|
981
|
+
if (result.success) {
|
|
982
|
+
ui.stopSpinnerSuccess(`Cherry-picked ${options.commit}`);
|
|
983
|
+
if (result.output) {
|
|
984
|
+
ui.info(result.output);
|
|
985
|
+
}
|
|
986
|
+
} else {
|
|
987
|
+
ui.stopSpinnerFail(`Failed to cherry-pick ${options.commit}`);
|
|
988
|
+
if (result.error) {
|
|
989
|
+
ui.error(result.error);
|
|
990
|
+
}
|
|
991
|
+
}
|
|
992
|
+
} else if (cherryPickAction === 'abort') {
|
|
993
|
+
const result = await gitClient.cherryPickAbort({ directory: options.directory });
|
|
994
|
+
if (result.success) {
|
|
995
|
+
ui.stopSpinnerSuccess('Cherry-pick aborted');
|
|
996
|
+
} else {
|
|
997
|
+
ui.stopSpinnerFail('Failed to abort cherry-pick');
|
|
998
|
+
if (result.error) {
|
|
999
|
+
ui.error(result.error);
|
|
1000
|
+
}
|
|
1001
|
+
}
|
|
1002
|
+
} else if (cherryPickAction === 'continue') {
|
|
1003
|
+
const result = await gitClient.cherryPickContinue({ directory: options.directory });
|
|
1004
|
+
if (result.success) {
|
|
1005
|
+
ui.stopSpinnerSuccess('Cherry-pick continued');
|
|
1006
|
+
if (result.output) {
|
|
1007
|
+
ui.info(result.output);
|
|
1008
|
+
}
|
|
1009
|
+
} else {
|
|
1010
|
+
ui.stopSpinnerFail('Failed to continue cherry-pick');
|
|
1011
|
+
if (result.error) {
|
|
1012
|
+
ui.error(result.error);
|
|
1013
|
+
}
|
|
1014
|
+
}
|
|
1015
|
+
}
|
|
1016
|
+
} catch (error: any) {
|
|
1017
|
+
ui.stopSpinnerFail(`Error during cherry-pick ${cherryPickAction}`);
|
|
1018
|
+
ui.error(error.message);
|
|
1019
|
+
}
|
|
1020
|
+
}
|
|
1021
|
+
|
|
1022
|
+
/**
|
|
1023
|
+
* Show blame for a file
|
|
1024
|
+
*/
|
|
1025
|
+
export async function gitBlameCommand(
|
|
1026
|
+
file: string,
|
|
1027
|
+
options: GitCommandOptions = {}
|
|
1028
|
+
): Promise<void> {
|
|
1029
|
+
ui.header('Git Blame');
|
|
1030
|
+
|
|
1031
|
+
ui.info(`File: ${file}`);
|
|
1032
|
+
if (options.lineRange) {
|
|
1033
|
+
ui.info(`Line range: ${options.lineRange}`);
|
|
1034
|
+
}
|
|
1035
|
+
|
|
1036
|
+
ui.startSpinner({ message: `Getting blame for ${file}...` });
|
|
1037
|
+
|
|
1038
|
+
try {
|
|
1039
|
+
const available = await gitClient.isAvailable();
|
|
1040
|
+
if (!available) {
|
|
1041
|
+
ui.stopSpinnerFail('Git Tools Service not available');
|
|
1042
|
+
ui.error('Please ensure the Git Tools Service is running.');
|
|
1043
|
+
return;
|
|
1044
|
+
}
|
|
1045
|
+
|
|
1046
|
+
const result = await gitClient.blame(file, {
|
|
1047
|
+
directory: options.directory,
|
|
1048
|
+
lineRange: options.lineRange,
|
|
1049
|
+
});
|
|
1050
|
+
|
|
1051
|
+
if (result.success) {
|
|
1052
|
+
ui.stopSpinnerSuccess(`Blame retrieved for ${file}`);
|
|
1053
|
+
if (result.blame.length > 0) {
|
|
1054
|
+
result.blame.forEach(line => console.log(line));
|
|
1055
|
+
} else {
|
|
1056
|
+
ui.info('No blame data returned');
|
|
1057
|
+
}
|
|
1058
|
+
} else {
|
|
1059
|
+
ui.stopSpinnerFail(`Failed to get blame for ${file}`);
|
|
1060
|
+
if (result.error) {
|
|
1061
|
+
ui.error(result.error);
|
|
1062
|
+
}
|
|
1063
|
+
}
|
|
1064
|
+
} catch (error: any) {
|
|
1065
|
+
ui.stopSpinnerFail(`Error getting blame for ${file}`);
|
|
1066
|
+
ui.error(error.message);
|
|
1067
|
+
}
|
|
1068
|
+
}
|
|
1069
|
+
|
|
1070
|
+
/**
|
|
1071
|
+
* Initialize a repository
|
|
1072
|
+
*/
|
|
1073
|
+
export async function gitInitCommand(options: GitCommandOptions = {}): Promise<void> {
|
|
1074
|
+
ui.header('Git Init');
|
|
1075
|
+
|
|
1076
|
+
if (options.directory) {
|
|
1077
|
+
ui.info(`Directory: ${options.directory}`);
|
|
1078
|
+
}
|
|
1079
|
+
if (options.bare) {
|
|
1080
|
+
ui.info('Creating bare repository');
|
|
1081
|
+
}
|
|
1082
|
+
|
|
1083
|
+
ui.startSpinner({ message: 'Initializing repository...' });
|
|
1084
|
+
|
|
1085
|
+
try {
|
|
1086
|
+
const available = await gitClient.isAvailable();
|
|
1087
|
+
if (!available) {
|
|
1088
|
+
ui.stopSpinnerFail('Git Tools Service not available');
|
|
1089
|
+
ui.error('Please ensure the Git Tools Service is running.');
|
|
1090
|
+
return;
|
|
1091
|
+
}
|
|
1092
|
+
|
|
1093
|
+
const result = await gitClient.init({ directory: options.directory, bare: options.bare });
|
|
1094
|
+
|
|
1095
|
+
if (result.success) {
|
|
1096
|
+
ui.stopSpinnerSuccess('Repository initialized');
|
|
1097
|
+
if (result.output) {
|
|
1098
|
+
ui.info(result.output);
|
|
1099
|
+
}
|
|
1100
|
+
} else {
|
|
1101
|
+
ui.stopSpinnerFail('Failed to initialize repository');
|
|
1102
|
+
if (result.error) {
|
|
1103
|
+
ui.error(result.error);
|
|
1104
|
+
}
|
|
1105
|
+
}
|
|
1106
|
+
} catch (error: any) {
|
|
1107
|
+
ui.stopSpinnerFail('Error initializing repository');
|
|
1108
|
+
ui.error(error.message);
|
|
1109
|
+
}
|
|
1110
|
+
}
|
|
1111
|
+
|
|
1112
|
+
/**
|
|
1113
|
+
* Main git command router
|
|
1114
|
+
*/
|
|
1115
|
+
export async function gitCommand(subcommand: string, args: string[]): Promise<void> {
|
|
1116
|
+
const options: GitCommandOptions = {};
|
|
1117
|
+
|
|
1118
|
+
// Extract positional args and options
|
|
1119
|
+
const positionalArgs: string[] = [];
|
|
1120
|
+
|
|
1121
|
+
for (let i = 0; i < args.length; i++) {
|
|
1122
|
+
const arg = args[i];
|
|
1123
|
+
if (arg === '-d' || arg === '--directory') {
|
|
1124
|
+
options.directory = args[++i];
|
|
1125
|
+
} else if (arg === '-a' || arg === '--all') {
|
|
1126
|
+
options.all = true;
|
|
1127
|
+
} else if (arg === '--amend') {
|
|
1128
|
+
options.amend = true;
|
|
1129
|
+
} else if (arg === '-r' || arg === '--remote') {
|
|
1130
|
+
options.remote = args[++i];
|
|
1131
|
+
} else if (arg === '-b' || arg === '--branch') {
|
|
1132
|
+
options.branch = args[++i];
|
|
1133
|
+
} else if (arg === '-f' || arg === '--force') {
|
|
1134
|
+
options.force = true;
|
|
1135
|
+
} else if (arg === '-u' || arg === '--set-upstream') {
|
|
1136
|
+
options.setUpstream = true;
|
|
1137
|
+
} else if (arg === '--rebase') {
|
|
1138
|
+
options.rebase = true;
|
|
1139
|
+
} else if (arg === '-p' || arg === '--prune') {
|
|
1140
|
+
options.prune = true;
|
|
1141
|
+
} else if (arg === '-n' || arg === '--limit') {
|
|
1142
|
+
options.limit = parseInt(args[++i], 10);
|
|
1143
|
+
} else if (arg === '-s' || arg === '--staged') {
|
|
1144
|
+
options.staged = true;
|
|
1145
|
+
} else if (arg === '--file') {
|
|
1146
|
+
options.file = args[++i];
|
|
1147
|
+
} else if (arg === '-c' || arg === '--create') {
|
|
1148
|
+
options.create = true;
|
|
1149
|
+
} else if (arg === '-m' || arg === '--message') {
|
|
1150
|
+
options.message = args[++i];
|
|
1151
|
+
positionalArgs.push(options.message);
|
|
1152
|
+
} else if (arg === '--soft') {
|
|
1153
|
+
options.soft = true;
|
|
1154
|
+
} else if (arg === '--mixed') {
|
|
1155
|
+
options.mixed = true;
|
|
1156
|
+
} else if (arg === '--hard') {
|
|
1157
|
+
options.hard = true;
|
|
1158
|
+
} else if (arg === '--no-commit') {
|
|
1159
|
+
options.noCommit = true;
|
|
1160
|
+
} else if (arg === '--no-edit') {
|
|
1161
|
+
options.noEdit = true;
|
|
1162
|
+
} else if (arg === '--annotated') {
|
|
1163
|
+
options.annotated = true;
|
|
1164
|
+
} else if (arg === '--bare') {
|
|
1165
|
+
options.bare = true;
|
|
1166
|
+
} else if (arg === '--line-range') {
|
|
1167
|
+
options.lineRange = args[++i];
|
|
1168
|
+
} else if (!arg.startsWith('-')) {
|
|
1169
|
+
positionalArgs.push(arg);
|
|
1170
|
+
}
|
|
1171
|
+
}
|
|
1172
|
+
|
|
1173
|
+
const startTime = Date.now();
|
|
1174
|
+
const entry = historyManager.addEntry('git', [subcommand, ...args]);
|
|
1175
|
+
|
|
1176
|
+
try {
|
|
1177
|
+
switch (subcommand) {
|
|
1178
|
+
case 'status':
|
|
1179
|
+
await gitStatusCommand(options);
|
|
1180
|
+
break;
|
|
1181
|
+
case 'add':
|
|
1182
|
+
if (positionalArgs.length < 1 && !options.all) {
|
|
1183
|
+
ui.error('Usage: nimbus git add <files...> or nimbus git add -a');
|
|
1184
|
+
return;
|
|
1185
|
+
}
|
|
1186
|
+
await gitAddCommand(positionalArgs, options);
|
|
1187
|
+
break;
|
|
1188
|
+
case 'commit':
|
|
1189
|
+
if (positionalArgs.length < 1) {
|
|
1190
|
+
ui.error('Usage: nimbus git commit -m "message"');
|
|
1191
|
+
return;
|
|
1192
|
+
}
|
|
1193
|
+
await gitCommitCommand(positionalArgs[0], options);
|
|
1194
|
+
break;
|
|
1195
|
+
case 'push':
|
|
1196
|
+
await gitPushCommand(options);
|
|
1197
|
+
break;
|
|
1198
|
+
case 'pull':
|
|
1199
|
+
await gitPullCommand(options);
|
|
1200
|
+
break;
|
|
1201
|
+
case 'fetch':
|
|
1202
|
+
await gitFetchCommand(options);
|
|
1203
|
+
break;
|
|
1204
|
+
case 'log':
|
|
1205
|
+
await gitLogCommand(options);
|
|
1206
|
+
break;
|
|
1207
|
+
case 'branch':
|
|
1208
|
+
await gitBranchCommand(options);
|
|
1209
|
+
break;
|
|
1210
|
+
case 'checkout':
|
|
1211
|
+
if (positionalArgs.length < 1) {
|
|
1212
|
+
ui.error('Usage: nimbus git checkout <branch-or-file>');
|
|
1213
|
+
return;
|
|
1214
|
+
}
|
|
1215
|
+
await gitCheckoutCommand(positionalArgs[0], options);
|
|
1216
|
+
break;
|
|
1217
|
+
case 'diff':
|
|
1218
|
+
await gitDiffCommand(options);
|
|
1219
|
+
break;
|
|
1220
|
+
case 'merge':
|
|
1221
|
+
if (positionalArgs.length < 1) {
|
|
1222
|
+
ui.error('Usage: nimbus git merge <branch>');
|
|
1223
|
+
return;
|
|
1224
|
+
}
|
|
1225
|
+
await gitMergeCommand(positionalArgs[0], options);
|
|
1226
|
+
break;
|
|
1227
|
+
case 'clone':
|
|
1228
|
+
if (positionalArgs.length < 1) {
|
|
1229
|
+
ui.error('Usage: nimbus git clone <url> [path] [--branch <branch>] [--limit <depth>]');
|
|
1230
|
+
return;
|
|
1231
|
+
}
|
|
1232
|
+
await gitCloneCommand(positionalArgs[0], positionalArgs[1], options);
|
|
1233
|
+
break;
|
|
1234
|
+
case 'stash': {
|
|
1235
|
+
const validStashActions = ['push', 'pop', 'list', 'drop', 'apply', 'clear'];
|
|
1236
|
+
const stashAction = (positionalArgs[0] || 'push') as
|
|
1237
|
+
| 'push'
|
|
1238
|
+
| 'pop'
|
|
1239
|
+
| 'list'
|
|
1240
|
+
| 'drop'
|
|
1241
|
+
| 'apply'
|
|
1242
|
+
| 'clear';
|
|
1243
|
+
if (!validStashActions.includes(stashAction)) {
|
|
1244
|
+
ui.error(`Unknown stash action: ${stashAction}`);
|
|
1245
|
+
ui.info('Actions: push, pop, list, drop, apply, clear');
|
|
1246
|
+
return;
|
|
1247
|
+
}
|
|
1248
|
+
await gitStashCommand(stashAction, {
|
|
1249
|
+
...options,
|
|
1250
|
+
message: options.message,
|
|
1251
|
+
});
|
|
1252
|
+
break;
|
|
1253
|
+
}
|
|
1254
|
+
case 'tag': {
|
|
1255
|
+
const validTagActions = ['list', 'create', 'delete', 'push', 'show'];
|
|
1256
|
+
const tagAction = (positionalArgs[0] || 'list') as
|
|
1257
|
+
| 'list'
|
|
1258
|
+
| 'create'
|
|
1259
|
+
| 'delete'
|
|
1260
|
+
| 'push'
|
|
1261
|
+
| 'show';
|
|
1262
|
+
if (!validTagActions.includes(tagAction)) {
|
|
1263
|
+
ui.error(`Unknown tag action: ${tagAction}`);
|
|
1264
|
+
ui.info('Actions: list (default), create, delete, push, show');
|
|
1265
|
+
return;
|
|
1266
|
+
}
|
|
1267
|
+
// The second positional arg is the tag name (for create/delete/push/show)
|
|
1268
|
+
const tagArg = positionalArgs[1];
|
|
1269
|
+
await gitTagCommand(tagAction, { ...options, tagArg });
|
|
1270
|
+
break;
|
|
1271
|
+
}
|
|
1272
|
+
case 'remote': {
|
|
1273
|
+
const remoteName = positionalArgs[0];
|
|
1274
|
+
await gitRemoteCommand(remoteName, options);
|
|
1275
|
+
break;
|
|
1276
|
+
}
|
|
1277
|
+
case 'reset': {
|
|
1278
|
+
if (positionalArgs.length < 1) {
|
|
1279
|
+
ui.error('Usage: nimbus git reset <commit-ref> [--soft | --mixed | --hard]');
|
|
1280
|
+
return;
|
|
1281
|
+
}
|
|
1282
|
+
await gitResetCommand(positionalArgs[0], options);
|
|
1283
|
+
break;
|
|
1284
|
+
}
|
|
1285
|
+
case 'revert': {
|
|
1286
|
+
if (positionalArgs.length < 1) {
|
|
1287
|
+
ui.error('Usage: nimbus git revert <commit-hash> [--no-commit] [--no-edit]');
|
|
1288
|
+
return;
|
|
1289
|
+
}
|
|
1290
|
+
await gitRevertCommand(positionalArgs[0], options);
|
|
1291
|
+
break;
|
|
1292
|
+
}
|
|
1293
|
+
case 'cherry-pick': {
|
|
1294
|
+
// Determine action: if first positional arg is 'abort' or 'continue', use it as action
|
|
1295
|
+
let cherryPickAction: 'pick' | 'abort' | 'continue' = 'pick';
|
|
1296
|
+
let cherryPickCommit: string | undefined;
|
|
1297
|
+
if (positionalArgs[0] === 'abort') {
|
|
1298
|
+
cherryPickAction = 'abort';
|
|
1299
|
+
} else if (positionalArgs[0] === 'continue') {
|
|
1300
|
+
cherryPickAction = 'continue';
|
|
1301
|
+
} else {
|
|
1302
|
+
cherryPickAction = 'pick';
|
|
1303
|
+
cherryPickCommit = positionalArgs[0];
|
|
1304
|
+
if (!cherryPickCommit) {
|
|
1305
|
+
ui.error('Usage: nimbus git cherry-pick <commit-hash> [--no-commit]');
|
|
1306
|
+
ui.info('Also: nimbus git cherry-pick abort | continue');
|
|
1307
|
+
return;
|
|
1308
|
+
}
|
|
1309
|
+
}
|
|
1310
|
+
await gitCherryPickCommand(cherryPickAction, { ...options, commit: cherryPickCommit });
|
|
1311
|
+
break;
|
|
1312
|
+
}
|
|
1313
|
+
case 'blame': {
|
|
1314
|
+
if (positionalArgs.length < 1) {
|
|
1315
|
+
ui.error('Usage: nimbus git blame <file> [--line-range <start,end>]');
|
|
1316
|
+
return;
|
|
1317
|
+
}
|
|
1318
|
+
await gitBlameCommand(positionalArgs[0], options);
|
|
1319
|
+
break;
|
|
1320
|
+
}
|
|
1321
|
+
case 'init': {
|
|
1322
|
+
// optional directory as positional arg
|
|
1323
|
+
if (positionalArgs[0]) {
|
|
1324
|
+
options.directory = positionalArgs[0];
|
|
1325
|
+
}
|
|
1326
|
+
await gitInitCommand(options);
|
|
1327
|
+
break;
|
|
1328
|
+
}
|
|
1329
|
+
default:
|
|
1330
|
+
ui.error(`Unknown git subcommand: ${subcommand}`);
|
|
1331
|
+
ui.info(
|
|
1332
|
+
'Available commands: status, add, commit, push, pull, fetch, log, branch, checkout, diff, merge, clone, stash, tag, remote, reset, revert, cherry-pick, blame, init'
|
|
1333
|
+
);
|
|
1334
|
+
}
|
|
1335
|
+
|
|
1336
|
+
historyManager.completeEntry(entry.id, 'success', Date.now() - startTime);
|
|
1337
|
+
} catch (error: any) {
|
|
1338
|
+
historyManager.completeEntry(entry.id, 'failure', Date.now() - startTime, {
|
|
1339
|
+
error: error.message,
|
|
1340
|
+
});
|
|
1341
|
+
throw error;
|
|
1342
|
+
}
|
|
1343
|
+
}
|