@snapcommit/cli 1.0.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/README.md +162 -0
- package/dist/ai/anthropic-client.js +92 -0
- package/dist/ai/commit-generator.js +200 -0
- package/dist/ai/gemini-client.js +201 -0
- package/dist/ai/git-interpreter.js +209 -0
- package/dist/ai/smart-solver.js +260 -0
- package/dist/auth/supabase-client.js +288 -0
- package/dist/commands/activate.js +108 -0
- package/dist/commands/commit.js +255 -0
- package/dist/commands/conflict.js +233 -0
- package/dist/commands/doctor.js +113 -0
- package/dist/commands/git-advanced.js +311 -0
- package/dist/commands/github-auth.js +193 -0
- package/dist/commands/login.js +11 -0
- package/dist/commands/natural.js +305 -0
- package/dist/commands/onboard.js +111 -0
- package/dist/commands/quick.js +173 -0
- package/dist/commands/setup.js +163 -0
- package/dist/commands/stats.js +128 -0
- package/dist/commands/uninstall.js +131 -0
- package/dist/db/database.js +99 -0
- package/dist/index.js +144 -0
- package/dist/lib/auth.js +171 -0
- package/dist/lib/github.js +280 -0
- package/dist/lib/multi-repo.js +276 -0
- package/dist/lib/supabase.js +153 -0
- package/dist/license/manager.js +203 -0
- package/dist/repl/index.js +185 -0
- package/dist/repl/interpreter.js +524 -0
- package/dist/utils/analytics.js +36 -0
- package/dist/utils/auth-storage.js +65 -0
- package/dist/utils/dopamine.js +211 -0
- package/dist/utils/errors.js +56 -0
- package/dist/utils/git.js +105 -0
- package/dist/utils/heatmap.js +265 -0
- package/dist/utils/rate-limit.js +68 -0
- package/dist/utils/retry.js +46 -0
- package/dist/utils/ui.js +189 -0
- package/dist/utils/version.js +81 -0
- package/package.json +69 -0
package/README.md
ADDED
|
@@ -0,0 +1,162 @@
|
|
|
1
|
+
# SnapCommit
|
|
2
|
+
|
|
3
|
+
**Snap. Commit. Track.**
|
|
4
|
+
|
|
5
|
+
Instant AI commits. Beautiful progress tracking. Never write commit messages again.
|
|
6
|
+
|
|
7
|
+
[](https://www.npmjs.com/package/snapcommit)
|
|
8
|
+
[](https://opensource.org/licenses/MIT)
|
|
9
|
+
|
|
10
|
+
## What is SnapCommit?
|
|
11
|
+
|
|
12
|
+
SnapCommit is a CLI tool that makes Git & GitHub effortless:
|
|
13
|
+
|
|
14
|
+
1. **⚡ Instant AI Commits** - Professional messages in one command (`snap`)
|
|
15
|
+
2. **🌍 Natural Language** - Just say what you want: "create a PR", "merge main"
|
|
16
|
+
3. **📊 Beautiful Stats** - Track your coding journey with gorgeous analytics
|
|
17
|
+
4. **🔥 Streak Tracking** - Gamification to keep you motivated
|
|
18
|
+
5. **🚀 Universal** - Works everywhere (any IDE, any terminal, any OS)
|
|
19
|
+
|
|
20
|
+
## Quick Start
|
|
21
|
+
|
|
22
|
+
```bash
|
|
23
|
+
# Install globally
|
|
24
|
+
npm install -g snapcommit
|
|
25
|
+
|
|
26
|
+
# Set up (adds aliases)
|
|
27
|
+
snapcommit setup
|
|
28
|
+
|
|
29
|
+
# Make your first commit
|
|
30
|
+
snap # That's it!
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
## Installation
|
|
34
|
+
|
|
35
|
+
### Option 1: npm (Recommended)
|
|
36
|
+
|
|
37
|
+
```bash
|
|
38
|
+
npm install -g snapcommit
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
### Option 2: Install Scripts
|
|
42
|
+
|
|
43
|
+
**macOS / Linux:**
|
|
44
|
+
```bash
|
|
45
|
+
curl -fsSL https://snapcommit.dev/install.sh | bash
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
**Windows (PowerShell):**
|
|
49
|
+
```powershell
|
|
50
|
+
iwr https://snapcommit.dev/install.ps1 | iex
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
## Setup
|
|
54
|
+
|
|
55
|
+
After installation, run setup to integrate with your shell:
|
|
56
|
+
|
|
57
|
+
```bash
|
|
58
|
+
snapcommit setup
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
This adds `snap` and `sc` aliases. Restart your terminal and you're ready!
|
|
62
|
+
|
|
63
|
+
## Usage
|
|
64
|
+
|
|
65
|
+
### Interactive Mode
|
|
66
|
+
|
|
67
|
+
Type `snap` to enter interactive mode:
|
|
68
|
+
|
|
69
|
+
```bash
|
|
70
|
+
$ snap
|
|
71
|
+
⚡ SnapCommit Interactive Mode
|
|
72
|
+
|
|
73
|
+
snap> commit my authentication work
|
|
74
|
+
✅ feat(auth): implement user authentication system
|
|
75
|
+
|
|
76
|
+
snap> create a PR
|
|
77
|
+
✅ PR #42 created
|
|
78
|
+
|
|
79
|
+
snap> check ci
|
|
80
|
+
✅ All checks passed
|
|
81
|
+
```
|
|
82
|
+
|
|
83
|
+
### Just Talk to It 💬
|
|
84
|
+
|
|
85
|
+
SnapCommit is **Cursor for Git** - no commands to memorize. Just describe what you want:
|
|
86
|
+
|
|
87
|
+
```bash
|
|
88
|
+
snap> commit my authentication work
|
|
89
|
+
✅ feat(auth): implement user authentication system
|
|
90
|
+
|
|
91
|
+
snap> I messed up, go back to how it was before
|
|
92
|
+
✅ Reverted last commit
|
|
93
|
+
|
|
94
|
+
snap> open a pull request for this feature
|
|
95
|
+
✅ PR #42 created
|
|
96
|
+
|
|
97
|
+
snap> did the tests pass?
|
|
98
|
+
✅ All CI checks passed
|
|
99
|
+
```
|
|
100
|
+
|
|
101
|
+
**That's the whole point** - talk naturally, like you would to a teammate. SnapCommit figures out what you mean.
|
|
102
|
+
|
|
103
|
+
## Authentication
|
|
104
|
+
|
|
105
|
+
**SnapCommit includes built-in AI models!** No API keys needed.
|
|
106
|
+
|
|
107
|
+
1. **Sign up at:** https://snapcommit.dev/login
|
|
108
|
+
2. **Subscribe:** $9.99/month or $100/year
|
|
109
|
+
3. **Get your token:** From your dashboard
|
|
110
|
+
4. **Authenticate CLI:** Enter token when prompted
|
|
111
|
+
|
|
112
|
+
After installing, the CLI will guide you through authentication on first use.
|
|
113
|
+
|
|
114
|
+
## Features
|
|
115
|
+
|
|
116
|
+
- ✅ **AI Commit Messages** - Claude 4.5 Sonnet generates perfect commits
|
|
117
|
+
- ✅ **Natural Language Git** - No more memorizing commands
|
|
118
|
+
- ✅ **GitHub Integration** - PRs, CI checks, workflows, releases
|
|
119
|
+
- ✅ **Progress Tracking** - Streaks, heatmaps, milestones
|
|
120
|
+
- ✅ **Multi-Repo Support** - Switch between projects seamlessly
|
|
121
|
+
- ✅ **Conflict Resolution** - AI-guided merge conflict solving
|
|
122
|
+
- ✅ **Smart Error Handling** - Auto-retry with intelligent fixes
|
|
123
|
+
- ✅ **Privacy First** - Local-first, minimal data sent to AI
|
|
124
|
+
- ✅ **Cross-Platform** - macOS, Linux, Windows
|
|
125
|
+
|
|
126
|
+
## Pricing
|
|
127
|
+
|
|
128
|
+
- **Monthly:** $9.99/month - Unlimited AI commits, all features
|
|
129
|
+
- **Yearly:** $100/year - Save 17% (best value!)
|
|
130
|
+
|
|
131
|
+
## Supported Platforms
|
|
132
|
+
|
|
133
|
+
- ✅ macOS (zsh, bash, fish)
|
|
134
|
+
- ✅ Linux (zsh, bash, fish)
|
|
135
|
+
- ✅ Windows (PowerShell, Git Bash, WSL)
|
|
136
|
+
- ✅ Any terminal, any IDE
|
|
137
|
+
|
|
138
|
+
## Requirements
|
|
139
|
+
|
|
140
|
+
- Node.js 18+
|
|
141
|
+
- Git installed
|
|
142
|
+
- API keys (we'll help you get them)
|
|
143
|
+
|
|
144
|
+
## Documentation
|
|
145
|
+
|
|
146
|
+
Full docs at [snapcommit.dev/docs](https://snapcommit.dev/docs)
|
|
147
|
+
|
|
148
|
+
## Support
|
|
149
|
+
|
|
150
|
+
- 🐦 [Follow on X/Twitter](https://x.com/Arjun06061)
|
|
151
|
+
- 📧 [Email support](mailto:support@snapcommit.dev)
|
|
152
|
+
- 💡 [Suggestions & Feedback](https://x.com/Arjun06061)
|
|
153
|
+
|
|
154
|
+
## License
|
|
155
|
+
|
|
156
|
+
MIT © SnapCommit Team
|
|
157
|
+
|
|
158
|
+
---
|
|
159
|
+
|
|
160
|
+
**Made with ❤️ for developers who want to focus on building, not git messages.**
|
|
161
|
+
|
|
162
|
+
[Website](https://snapcommit.dev) • [X/Twitter](https://x.com/Arjun06061)
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.generateCommitMessage = generateCommitMessage;
|
|
7
|
+
exports.analyzeCommand = analyzeCommand;
|
|
8
|
+
const sdk_1 = __importDefault(require("@anthropic-ai/sdk"));
|
|
9
|
+
const retry_1 = require("../utils/retry");
|
|
10
|
+
const client = new sdk_1.default({
|
|
11
|
+
apiKey: process.env.ANTHROPIC_API_KEY,
|
|
12
|
+
});
|
|
13
|
+
async function generateCommitMessage(diff) {
|
|
14
|
+
try {
|
|
15
|
+
// Try Claude first, fallback to Gemini if it fails
|
|
16
|
+
const message = await (0, retry_1.retryWithBackoff)(() => client.messages.create({
|
|
17
|
+
model: 'claude-sonnet-4-20250514',
|
|
18
|
+
max_tokens: 500,
|
|
19
|
+
temperature: 0.3,
|
|
20
|
+
messages: [
|
|
21
|
+
{
|
|
22
|
+
role: 'user',
|
|
23
|
+
content: `You are an expert software engineer writing commit messages.
|
|
24
|
+
|
|
25
|
+
Analyze this git diff and generate a professional conventional commit message.
|
|
26
|
+
|
|
27
|
+
Rules:
|
|
28
|
+
1. Use conventional commits format: type(scope): message
|
|
29
|
+
2. Types: feat, fix, docs, style, refactor, test, chore
|
|
30
|
+
3. Keep first line under 72 characters
|
|
31
|
+
4. Be specific and clear
|
|
32
|
+
5. If multiple changes, use the most significant one
|
|
33
|
+
6. Don't mention file names unless critical
|
|
34
|
+
7. Focus on WHAT changed and WHY, not HOW
|
|
35
|
+
|
|
36
|
+
Git Diff:
|
|
37
|
+
\`\`\`
|
|
38
|
+
${diff}
|
|
39
|
+
\`\`\`
|
|
40
|
+
|
|
41
|
+
Reply with ONLY the commit message, nothing else.`,
|
|
42
|
+
},
|
|
43
|
+
],
|
|
44
|
+
}), {
|
|
45
|
+
maxAttempts: 3,
|
|
46
|
+
initialDelay: 1000,
|
|
47
|
+
maxDelay: 5000,
|
|
48
|
+
});
|
|
49
|
+
const content = message.content[0];
|
|
50
|
+
if (content.type === 'text') {
|
|
51
|
+
return content.text.trim();
|
|
52
|
+
}
|
|
53
|
+
throw new Error('Unexpected response format from Claude');
|
|
54
|
+
}
|
|
55
|
+
catch (error) {
|
|
56
|
+
throw new Error(`Claude API error: ${error.message}`);
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
async function analyzeCommand(naturalLanguage) {
|
|
60
|
+
try {
|
|
61
|
+
const message = await client.messages.create({
|
|
62
|
+
model: 'claude-sonnet-4-20250514',
|
|
63
|
+
max_tokens: 200,
|
|
64
|
+
temperature: 0.2,
|
|
65
|
+
messages: [
|
|
66
|
+
{
|
|
67
|
+
role: 'user',
|
|
68
|
+
content: `You are a terminal command expert. Convert this natural language request into a precise terminal command.
|
|
69
|
+
|
|
70
|
+
Request: "${naturalLanguage}"
|
|
71
|
+
|
|
72
|
+
Rules:
|
|
73
|
+
1. Reply with ONLY the command, nothing else
|
|
74
|
+
2. No explanations, no markdown, no backticks
|
|
75
|
+
3. If multiple commands needed, separate with &&
|
|
76
|
+
4. Use common Unix commands (works on Mac/Linux)
|
|
77
|
+
5. Be safe - no destructive commands without confirmation
|
|
78
|
+
|
|
79
|
+
Reply with the command:`,
|
|
80
|
+
},
|
|
81
|
+
],
|
|
82
|
+
});
|
|
83
|
+
const content = message.content[0];
|
|
84
|
+
if (content.type === 'text') {
|
|
85
|
+
return content.text.trim();
|
|
86
|
+
}
|
|
87
|
+
throw new Error('Unexpected response format from Claude');
|
|
88
|
+
}
|
|
89
|
+
catch (error) {
|
|
90
|
+
throw new Error(`Claude API error: ${error.message}`);
|
|
91
|
+
}
|
|
92
|
+
}
|
|
@@ -0,0 +1,200 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Enhanced commit message generator
|
|
4
|
+
* Generates multiple options for user to choose from
|
|
5
|
+
*/
|
|
6
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
7
|
+
if (k2 === undefined) k2 = k;
|
|
8
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
9
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
10
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
11
|
+
}
|
|
12
|
+
Object.defineProperty(o, k2, desc);
|
|
13
|
+
}) : (function(o, m, k, k2) {
|
|
14
|
+
if (k2 === undefined) k2 = k;
|
|
15
|
+
o[k2] = m[k];
|
|
16
|
+
}));
|
|
17
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
18
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
19
|
+
}) : function(o, v) {
|
|
20
|
+
o["default"] = v;
|
|
21
|
+
});
|
|
22
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
23
|
+
var ownKeys = function(o) {
|
|
24
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
25
|
+
var ar = [];
|
|
26
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
27
|
+
return ar;
|
|
28
|
+
};
|
|
29
|
+
return ownKeys(o);
|
|
30
|
+
};
|
|
31
|
+
return function (mod) {
|
|
32
|
+
if (mod && mod.__esModule) return mod;
|
|
33
|
+
var result = {};
|
|
34
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
35
|
+
__setModuleDefault(result, mod);
|
|
36
|
+
return result;
|
|
37
|
+
};
|
|
38
|
+
})();
|
|
39
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
40
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
41
|
+
};
|
|
42
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
43
|
+
exports.generateCommitOptions = generateCommitOptions;
|
|
44
|
+
exports.displayCommitOptions = displayCommitOptions;
|
|
45
|
+
exports.editCommitMessage = editCommitMessage;
|
|
46
|
+
const anthropic_client_1 = require("./anthropic-client");
|
|
47
|
+
const gemini_client_1 = require("./gemini-client");
|
|
48
|
+
const chalk_1 = __importDefault(require("chalk"));
|
|
49
|
+
/**
|
|
50
|
+
* Generate multiple commit message options
|
|
51
|
+
* Gives user choice of styles
|
|
52
|
+
*/
|
|
53
|
+
async function generateCommitOptions(diff) {
|
|
54
|
+
try {
|
|
55
|
+
// Generate 3 different styles in parallel
|
|
56
|
+
const [detailed, concise, conventional] = await Promise.all([
|
|
57
|
+
generateDetailedCommit(diff),
|
|
58
|
+
generateConciseCommit(diff),
|
|
59
|
+
generateConventionalCommit(diff),
|
|
60
|
+
]);
|
|
61
|
+
return [
|
|
62
|
+
{
|
|
63
|
+
message: conventional,
|
|
64
|
+
description: 'Conventional Commits format (recommended)',
|
|
65
|
+
style: 'conventional',
|
|
66
|
+
},
|
|
67
|
+
{
|
|
68
|
+
message: detailed,
|
|
69
|
+
description: 'Detailed with full context',
|
|
70
|
+
style: 'detailed',
|
|
71
|
+
},
|
|
72
|
+
{
|
|
73
|
+
message: concise,
|
|
74
|
+
description: 'Short and simple',
|
|
75
|
+
style: 'concise',
|
|
76
|
+
},
|
|
77
|
+
];
|
|
78
|
+
}
|
|
79
|
+
catch (error) {
|
|
80
|
+
// Fallback to single generation
|
|
81
|
+
const message = await (0, anthropic_client_1.generateCommitMessage)(diff);
|
|
82
|
+
return [{
|
|
83
|
+
message,
|
|
84
|
+
description: 'AI-generated',
|
|
85
|
+
style: 'conventional',
|
|
86
|
+
}];
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
/**
|
|
90
|
+
* Generate detailed commit message
|
|
91
|
+
*/
|
|
92
|
+
async function generateDetailedCommit(diff) {
|
|
93
|
+
const prompt = `You are an expert software engineer writing a detailed commit message.
|
|
94
|
+
|
|
95
|
+
Analyze this git diff and generate a comprehensive conventional commit message.
|
|
96
|
+
|
|
97
|
+
Requirements:
|
|
98
|
+
1. Use conventional commits format: type(scope): subject
|
|
99
|
+
2. Include a detailed body explaining WHAT changed and WHY
|
|
100
|
+
3. List key changes as bullet points
|
|
101
|
+
4. Be specific and thorough
|
|
102
|
+
5. Keep subject line under 72 characters
|
|
103
|
+
|
|
104
|
+
Git Diff:
|
|
105
|
+
\`\`\`
|
|
106
|
+
${diff}
|
|
107
|
+
\`\`\`
|
|
108
|
+
|
|
109
|
+
Reply with ONLY the commit message (subject + body), nothing else.`;
|
|
110
|
+
try {
|
|
111
|
+
return await (0, anthropic_client_1.generateCommitMessage)(diff);
|
|
112
|
+
}
|
|
113
|
+
catch {
|
|
114
|
+
return await (0, gemini_client_1.generateCommitMessageGemini)(diff);
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
/**
|
|
118
|
+
* Generate concise commit message
|
|
119
|
+
*/
|
|
120
|
+
async function generateConciseCommit(diff) {
|
|
121
|
+
const prompt = `You are an expert software engineer writing a concise commit message.
|
|
122
|
+
|
|
123
|
+
Analyze this git diff and generate a SHORT conventional commit message.
|
|
124
|
+
|
|
125
|
+
Requirements:
|
|
126
|
+
1. Use conventional commits format: type(scope): subject
|
|
127
|
+
2. Keep it to ONE line only (subject only, no body)
|
|
128
|
+
3. Be specific but brief
|
|
129
|
+
4. Under 72 characters total
|
|
130
|
+
|
|
131
|
+
Git Diff:
|
|
132
|
+
\`\`\`
|
|
133
|
+
${diff}
|
|
134
|
+
\`\`\`
|
|
135
|
+
|
|
136
|
+
Reply with ONLY the one-line commit message, nothing else.`;
|
|
137
|
+
try {
|
|
138
|
+
// For concise, we need a different AI call with specific instructions
|
|
139
|
+
const message = await (0, anthropic_client_1.generateCommitMessage)(diff);
|
|
140
|
+
// Extract just the first line
|
|
141
|
+
return message.split('\n')[0];
|
|
142
|
+
}
|
|
143
|
+
catch {
|
|
144
|
+
const message = await (0, gemini_client_1.generateCommitMessageGemini)(diff);
|
|
145
|
+
return message.split('\n')[0];
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
/**
|
|
149
|
+
* Generate conventional commit message (our standard)
|
|
150
|
+
*/
|
|
151
|
+
async function generateConventionalCommit(diff) {
|
|
152
|
+
return await (0, anthropic_client_1.generateCommitMessage)(diff);
|
|
153
|
+
}
|
|
154
|
+
/**
|
|
155
|
+
* Display commit options to user
|
|
156
|
+
*/
|
|
157
|
+
function displayCommitOptions(options) {
|
|
158
|
+
console.log(chalk_1.default.white.bold('\n✨ AI-Generated Commit Messages:\n'));
|
|
159
|
+
options.forEach((option, index) => {
|
|
160
|
+
const number = index + 1;
|
|
161
|
+
const badge = index === 0 ? chalk_1.default.green(' (Recommended)') : '';
|
|
162
|
+
console.log(chalk_1.default.cyan.bold(`Option ${number}:`) + chalk_1.default.gray(` ${option.description}`) + badge);
|
|
163
|
+
console.log();
|
|
164
|
+
// Display the commit message with nice formatting
|
|
165
|
+
const lines = option.message.split('\n');
|
|
166
|
+
lines.forEach((line, i) => {
|
|
167
|
+
if (i === 0) {
|
|
168
|
+
// Subject line - bold
|
|
169
|
+
console.log(chalk_1.default.white.bold(` ${line}`));
|
|
170
|
+
}
|
|
171
|
+
else if (line.trim()) {
|
|
172
|
+
// Body lines - gray
|
|
173
|
+
console.log(chalk_1.default.gray(` ${line}`));
|
|
174
|
+
}
|
|
175
|
+
else {
|
|
176
|
+
console.log();
|
|
177
|
+
}
|
|
178
|
+
});
|
|
179
|
+
console.log();
|
|
180
|
+
});
|
|
181
|
+
}
|
|
182
|
+
/**
|
|
183
|
+
* Let user edit a commit message
|
|
184
|
+
*/
|
|
185
|
+
async function editCommitMessage(original) {
|
|
186
|
+
const readline = await Promise.resolve().then(() => __importStar(require('readline')));
|
|
187
|
+
console.log(chalk_1.default.yellow('\n📝 Edit your commit message:'));
|
|
188
|
+
console.log(chalk_1.default.gray('Current:'));
|
|
189
|
+
console.log(chalk_1.default.white(` ${original}\n`));
|
|
190
|
+
const rl = readline.createInterface({
|
|
191
|
+
input: process.stdin,
|
|
192
|
+
output: process.stdout,
|
|
193
|
+
});
|
|
194
|
+
return new Promise((resolve) => {
|
|
195
|
+
rl.question(chalk_1.default.cyan('New message: '), (answer) => {
|
|
196
|
+
rl.close();
|
|
197
|
+
resolve(answer.trim() || original);
|
|
198
|
+
});
|
|
199
|
+
});
|
|
200
|
+
}
|
|
@@ -0,0 +1,201 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Google Gemini AI Client (Fallback)
|
|
4
|
+
* Used when Claude is unavailable or for cost optimization
|
|
5
|
+
*/
|
|
6
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
7
|
+
exports.generateCommitMessageGemini = generateCommitMessageGemini;
|
|
8
|
+
exports.interpretGitCommandGemini = interpretGitCommandGemini;
|
|
9
|
+
exports.needsClarificationGemini = needsClarificationGemini;
|
|
10
|
+
exports.solveGitProblemGemini = solveGitProblemGemini;
|
|
11
|
+
const generative_ai_1 = require("@google/generative-ai");
|
|
12
|
+
const retry_1 = require("../utils/retry");
|
|
13
|
+
const genAI = new generative_ai_1.GoogleGenerativeAI(process.env.GOOGLE_AI_API_KEY || '');
|
|
14
|
+
async function generateCommitMessageGemini(diff) {
|
|
15
|
+
try {
|
|
16
|
+
const model = genAI.getGenerativeModel({ model: 'gemini-2.0-flash-exp' });
|
|
17
|
+
const prompt = `You are an expert software engineer writing commit messages.
|
|
18
|
+
|
|
19
|
+
Analyze this git diff and generate a professional conventional commit message.
|
|
20
|
+
|
|
21
|
+
Rules:
|
|
22
|
+
1. Use conventional commits format: type(scope): message
|
|
23
|
+
2. Types: feat, fix, docs, style, refactor, test, chore
|
|
24
|
+
3. Keep first line under 72 characters
|
|
25
|
+
4. Be specific and clear
|
|
26
|
+
5. If multiple changes, use the most significant one
|
|
27
|
+
6. Don't mention file names unless critical
|
|
28
|
+
7. Focus on WHAT changed and WHY, not HOW
|
|
29
|
+
|
|
30
|
+
Git Diff:
|
|
31
|
+
\`\`\`
|
|
32
|
+
${diff}
|
|
33
|
+
\`\`\`
|
|
34
|
+
|
|
35
|
+
Reply with ONLY the commit message, nothing else.`;
|
|
36
|
+
const result = await (0, retry_1.retryWithBackoff)(() => model.generateContent(prompt), {
|
|
37
|
+
maxAttempts: 3,
|
|
38
|
+
initialDelay: 1000,
|
|
39
|
+
maxDelay: 5000,
|
|
40
|
+
});
|
|
41
|
+
const response = await result.response;
|
|
42
|
+
const text = response.text();
|
|
43
|
+
if (!text) {
|
|
44
|
+
throw new Error('Empty response from Gemini');
|
|
45
|
+
}
|
|
46
|
+
return text.trim();
|
|
47
|
+
}
|
|
48
|
+
catch (error) {
|
|
49
|
+
throw new Error(`Gemini API error: ${error.message}`);
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
async function interpretGitCommandGemini(userInput, context) {
|
|
53
|
+
try {
|
|
54
|
+
const model = genAI.getGenerativeModel({ model: 'gemini-2.0-flash-exp' });
|
|
55
|
+
const prompt = `You are a Git expert assistant. Interpret the user's natural language request into a structured Git operation.
|
|
56
|
+
|
|
57
|
+
User request: "${userInput}"
|
|
58
|
+
|
|
59
|
+
Current context:
|
|
60
|
+
- Branch: ${context.currentBranch}
|
|
61
|
+
- Uncommitted changes: ${context.hasUncommittedChanges}
|
|
62
|
+
- Last commit: ${context.lastCommitHash || 'none'}
|
|
63
|
+
|
|
64
|
+
Respond with a JSON object (and ONLY JSON, no markdown):
|
|
65
|
+
{
|
|
66
|
+
"action": "commit|revert|merge|branch|reset|stash|push|pull|rebase|cherry-pick|etc",
|
|
67
|
+
"target": "specific target if any (branch, commit, file)",
|
|
68
|
+
"options": {"key": "value"},
|
|
69
|
+
"confidence": 0.95,
|
|
70
|
+
"explanation": "This will undo your last commit but keep all your changes staged",
|
|
71
|
+
"gitCommands": ["git reset --soft HEAD~1", "git status"],
|
|
72
|
+
"riskLevel": "safe|medium|high",
|
|
73
|
+
"needsConfirmation": true,
|
|
74
|
+
"educationalTip": "The --soft flag keeps your changes staged. Use --hard to discard changes entirely (dangerous!)"
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
Risk levels:
|
|
78
|
+
- safe: Read-only, undo-able (status, log, diff)
|
|
79
|
+
- medium: Changes state but recoverable (commit, stash, branch)
|
|
80
|
+
- high: Destructive, affects remote, or hard to undo (push --force, reset --hard, delete branch)
|
|
81
|
+
|
|
82
|
+
Respond with ONLY the JSON object, no other text.`;
|
|
83
|
+
const result = await model.generateContent(prompt);
|
|
84
|
+
const response = await result.response;
|
|
85
|
+
const text = response.text();
|
|
86
|
+
// Clean up markdown if present
|
|
87
|
+
let jsonText = text.trim();
|
|
88
|
+
if (jsonText.startsWith('```json')) {
|
|
89
|
+
jsonText = jsonText.replace(/```json\n?/, '').replace(/```\n?$/, '');
|
|
90
|
+
}
|
|
91
|
+
else if (jsonText.startsWith('```')) {
|
|
92
|
+
jsonText = jsonText.replace(/```\n?/, '').replace(/```\n?$/, '');
|
|
93
|
+
}
|
|
94
|
+
const intent = JSON.parse(jsonText);
|
|
95
|
+
// Validate
|
|
96
|
+
if (!intent.action || !intent.explanation || !intent.gitCommands) {
|
|
97
|
+
throw new Error('Invalid intent structure from Gemini');
|
|
98
|
+
}
|
|
99
|
+
return intent;
|
|
100
|
+
}
|
|
101
|
+
catch (error) {
|
|
102
|
+
throw new Error(`Gemini API error: ${error.message}`);
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
/**
|
|
106
|
+
* Check if user input needs clarification
|
|
107
|
+
*/
|
|
108
|
+
async function needsClarificationGemini(userInput) {
|
|
109
|
+
try {
|
|
110
|
+
const model = genAI.getGenerativeModel({ model: 'gemini-2.0-flash-exp' });
|
|
111
|
+
const prompt = `You are a helpful Git assistant. Determine if this user request is ambiguous and requires clarification.
|
|
112
|
+
|
|
113
|
+
User request: "${userInput}"
|
|
114
|
+
|
|
115
|
+
Return a JSON object:
|
|
116
|
+
{
|
|
117
|
+
"needsClarification": true|false,
|
|
118
|
+
"options": [
|
|
119
|
+
{ "label": "Description of option 1", "value": "clarified command 1" },
|
|
120
|
+
{ "label": "Description of option 2", "value": "clarified command 2" }
|
|
121
|
+
]
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
Examples of ambiguous requests:
|
|
125
|
+
- "undo that" → could mean: undo last commit, undo last change, undo last push
|
|
126
|
+
- "go back" → could mean: go back 1 commit, discard changes, switch branch
|
|
127
|
+
- "clean up" → could mean: clean untracked files, delete merged branches, remove stale changes
|
|
128
|
+
|
|
129
|
+
If the request is clear and unambiguous, return: { "needsClarification": false }
|
|
130
|
+
|
|
131
|
+
Respond with ONLY the JSON object, no other text.`;
|
|
132
|
+
const result = await model.generateContent(prompt);
|
|
133
|
+
const response = await result.response;
|
|
134
|
+
let text = response.text().trim();
|
|
135
|
+
// Clean up markdown
|
|
136
|
+
if (text.startsWith('```json')) {
|
|
137
|
+
text = text.replace(/```json\n?/, '').replace(/```\n?$/, '');
|
|
138
|
+
}
|
|
139
|
+
else if (text.startsWith('```')) {
|
|
140
|
+
text = text.replace(/```\n?/, '').replace(/```\n?$/, '');
|
|
141
|
+
}
|
|
142
|
+
return JSON.parse(text);
|
|
143
|
+
}
|
|
144
|
+
catch (error) {
|
|
145
|
+
// If clarification check fails, assume no clarification needed
|
|
146
|
+
return { needsClarification: false };
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
/**
|
|
150
|
+
* Try to solve a Git problem using AI
|
|
151
|
+
*/
|
|
152
|
+
async function solveGitProblemGemini(userInput, context) {
|
|
153
|
+
try {
|
|
154
|
+
const model = genAI.getGenerativeModel({ model: 'gemini-2.0-flash-exp' });
|
|
155
|
+
const prompt = `You are an expert Git problem solver. A user encountered an issue.
|
|
156
|
+
|
|
157
|
+
User's problem: "${userInput}"
|
|
158
|
+
|
|
159
|
+
Current context:
|
|
160
|
+
- Branch: ${context.currentBranch}
|
|
161
|
+
- Git status:
|
|
162
|
+
\`\`\`
|
|
163
|
+
${context.gitStatus}
|
|
164
|
+
\`\`\`
|
|
165
|
+
${context.recentError ? `- Recent error: ${context.recentError}` : ''}
|
|
166
|
+
|
|
167
|
+
Your goal is to provide a solution. Return a JSON object:
|
|
168
|
+
{
|
|
169
|
+
"success": true,
|
|
170
|
+
"message": "A clear explanation of what went wrong and how you fixed it",
|
|
171
|
+
"gitCommands": ["git command 1", "git command 2"]
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
OR if you cannot solve it:
|
|
175
|
+
{
|
|
176
|
+
"success": false,
|
|
177
|
+
"message": "Explanation of why you couldn't solve it or what more information is needed"
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
Prioritize safe, non-destructive commands. If a destructive command is necessary, clearly state its implications in the message.
|
|
181
|
+
|
|
182
|
+
Respond with ONLY the JSON object, no other text.`;
|
|
183
|
+
const result = await model.generateContent(prompt);
|
|
184
|
+
const response = await result.response;
|
|
185
|
+
let text = response.text().trim();
|
|
186
|
+
// Clean up markdown
|
|
187
|
+
if (text.startsWith('```json')) {
|
|
188
|
+
text = text.replace(/```json\n?/, '').replace(/```\n?$/, '');
|
|
189
|
+
}
|
|
190
|
+
else if (text.startsWith('```')) {
|
|
191
|
+
text = text.replace(/```\n?/, '').replace(/```\n?$/, '');
|
|
192
|
+
}
|
|
193
|
+
return JSON.parse(text);
|
|
194
|
+
}
|
|
195
|
+
catch (error) {
|
|
196
|
+
return {
|
|
197
|
+
success: false,
|
|
198
|
+
message: `AI problem solver failed: ${error.message}`,
|
|
199
|
+
};
|
|
200
|
+
}
|
|
201
|
+
}
|