commic 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/.husky/pre-commit +2 -0
- package/README.md +306 -0
- package/biome.json +50 -0
- package/dist/ai/AIService.d.ts +51 -0
- package/dist/ai/AIService.d.ts.map +1 -0
- package/dist/ai/AIService.js +351 -0
- package/dist/ai/AIService.js.map +1 -0
- package/dist/config/ConfigManager.d.ts +49 -0
- package/dist/config/ConfigManager.d.ts.map +1 -0
- package/dist/config/ConfigManager.js +124 -0
- package/dist/config/ConfigManager.js.map +1 -0
- package/dist/errors/CustomErrors.d.ts +54 -0
- package/dist/errors/CustomErrors.d.ts.map +1 -0
- package/dist/errors/CustomErrors.js +99 -0
- package/dist/errors/CustomErrors.js.map +1 -0
- package/dist/git/GitService.d.ts +77 -0
- package/dist/git/GitService.d.ts.map +1 -0
- package/dist/git/GitService.js +219 -0
- package/dist/git/GitService.js.map +1 -0
- package/dist/index.d.ts +3 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +48 -0
- package/dist/index.js.map +1 -0
- package/dist/orchestrator/MainOrchestrator.d.ts +63 -0
- package/dist/orchestrator/MainOrchestrator.d.ts.map +1 -0
- package/dist/orchestrator/MainOrchestrator.js +225 -0
- package/dist/orchestrator/MainOrchestrator.js.map +1 -0
- package/dist/types/index.d.ts +55 -0
- package/dist/types/index.d.ts.map +1 -0
- package/dist/types/index.js +2 -0
- package/dist/types/index.js.map +1 -0
- package/dist/ui/UIManager.d.ts +118 -0
- package/dist/ui/UIManager.d.ts.map +1 -0
- package/dist/ui/UIManager.js +369 -0
- package/dist/ui/UIManager.js.map +1 -0
- package/dist/validation/ConventionalCommitsValidator.d.ts +33 -0
- package/dist/validation/ConventionalCommitsValidator.d.ts.map +1 -0
- package/dist/validation/ConventionalCommitsValidator.js +114 -0
- package/dist/validation/ConventionalCommitsValidator.js.map +1 -0
- package/package.json +49 -0
- package/src/ai/AIService.ts +413 -0
- package/src/config/ConfigManager.ts +141 -0
- package/src/errors/CustomErrors.ts +176 -0
- package/src/git/GitService.ts +246 -0
- package/src/index.ts +55 -0
- package/src/orchestrator/MainOrchestrator.ts +263 -0
- package/src/types/index.ts +60 -0
- package/src/ui/UIManager.ts +420 -0
- package/src/validation/ConventionalCommitsValidator.ts +139 -0
- package/tsconfig.json +24 -0
|
@@ -0,0 +1,351 @@
|
|
|
1
|
+
import { GoogleGenerativeAI } from '@google/generative-ai';
|
|
2
|
+
import { APIError, ValidationError } from '../errors/CustomErrors.js';
|
|
3
|
+
import { ConventionalCommitsValidator } from '../validation/ConventionalCommitsValidator.js';
|
|
4
|
+
/**
|
|
5
|
+
* Handles AI-powered commit message generation using Google's Gemini API
|
|
6
|
+
*/
|
|
7
|
+
export class AIService {
|
|
8
|
+
genAI;
|
|
9
|
+
model;
|
|
10
|
+
constructor(apiKey, modelName) {
|
|
11
|
+
this.genAI = new GoogleGenerativeAI(apiKey);
|
|
12
|
+
this.model = this.genAI.getGenerativeModel({ model: modelName });
|
|
13
|
+
}
|
|
14
|
+
/**
|
|
15
|
+
* Build prompt for Gemini API to generate commit messages
|
|
16
|
+
* @param diff Git diff information
|
|
17
|
+
* @param count Number of suggestions to generate (3-5)
|
|
18
|
+
* @returns Formatted prompt string
|
|
19
|
+
*/
|
|
20
|
+
buildPrompt(diff, count) {
|
|
21
|
+
// Combine staged and unstaged diffs
|
|
22
|
+
const combinedDiff = [diff.staged, diff.unstaged].filter((d) => d.length > 0).join('\n\n');
|
|
23
|
+
// Truncate diff if too large (Gemini input token limit: 1,048,576 tokens)
|
|
24
|
+
// Using ~800K characters (approx 200K-266K tokens) leaves plenty of room for prompt
|
|
25
|
+
// 1 token ≈ 3-4 characters on average, so 800K chars ≈ 200K-266K tokens
|
|
26
|
+
const maxDiffLength = 800000;
|
|
27
|
+
const truncatedDiff = combinedDiff.length > maxDiffLength
|
|
28
|
+
? combinedDiff.substring(0, maxDiffLength) +
|
|
29
|
+
'\n\n[... diff truncated due to size limit ...]'
|
|
30
|
+
: combinedDiff;
|
|
31
|
+
return `You are an expert Git commit message writer. Analyze the ENTIRE Git diff below and generate ${count} commit messages that summarize ALL changes together. Each commit message should cover the complete set of changes, not individual features.
|
|
32
|
+
|
|
33
|
+
IMPORTANT:
|
|
34
|
+
- Analyze ALL changes in the diff as a single commit
|
|
35
|
+
- Each suggested message should describe the complete set of changes
|
|
36
|
+
- Do NOT create separate messages for different parts of the diff
|
|
37
|
+
- Consider all file changes, additions, deletions, and modifications together
|
|
38
|
+
- Provide different perspectives/styles for the SAME set of changes
|
|
39
|
+
|
|
40
|
+
CRITICAL RULES:
|
|
41
|
+
1. Format: type(scope)?: description
|
|
42
|
+
2. Valid types: feat, fix, docs, style, refactor, test, chore, perf, ci, build
|
|
43
|
+
3. Use imperative mood (add, fix, update - NOT added, fixed, updated)
|
|
44
|
+
4. Description starts with lowercase
|
|
45
|
+
5. Single-line messages: max 72 chars, no body
|
|
46
|
+
6. Multi-line messages: blank line between subject and body
|
|
47
|
+
7. At least 2 messages should be single-line
|
|
48
|
+
8. Each message must summarize ALL changes in the diff
|
|
49
|
+
|
|
50
|
+
OUTPUT FORMAT:
|
|
51
|
+
Separate each commit message with exactly "---" on its own line.
|
|
52
|
+
Return ONLY the messages, no explanations or numbering.
|
|
53
|
+
|
|
54
|
+
EXAMPLES (each covers all changes):
|
|
55
|
+
feat(auth): add JWT validation and user login flow
|
|
56
|
+
---
|
|
57
|
+
fix: resolve authentication issues and update error handling
|
|
58
|
+
---
|
|
59
|
+
refactor(auth): improve JWT implementation and error messages
|
|
60
|
+
|
|
61
|
+
Update token validation logic and add comprehensive error handling
|
|
62
|
+
---
|
|
63
|
+
feat: implement user authentication system
|
|
64
|
+
|
|
65
|
+
Add JWT token validation, login endpoints, and error handling
|
|
66
|
+
|
|
67
|
+
GIT DIFF:
|
|
68
|
+
${truncatedDiff}
|
|
69
|
+
|
|
70
|
+
Generate ${count} commit messages that each describe ALL the changes above:`;
|
|
71
|
+
}
|
|
72
|
+
/**
|
|
73
|
+
* Parse API response into CommitSuggestion array
|
|
74
|
+
* Tries multiple parsing strategies for robustness
|
|
75
|
+
* @param response Raw response text from API
|
|
76
|
+
* @returns Array of commit suggestions
|
|
77
|
+
*/
|
|
78
|
+
parseResponse(response) {
|
|
79
|
+
if (!response || response.trim().length === 0) {
|
|
80
|
+
return [];
|
|
81
|
+
}
|
|
82
|
+
let messages = [];
|
|
83
|
+
// Strategy 1: Split by "---" on its own line
|
|
84
|
+
const tripleDashPattern = /\n---\n/g;
|
|
85
|
+
if (tripleDashPattern.test(response)) {
|
|
86
|
+
messages = response
|
|
87
|
+
.split(tripleDashPattern)
|
|
88
|
+
.map((msg) => msg.trim())
|
|
89
|
+
.filter((msg) => msg.length > 0);
|
|
90
|
+
}
|
|
91
|
+
// Strategy 2: Split by "---" anywhere
|
|
92
|
+
if (messages.length === 0 || messages.length === 1) {
|
|
93
|
+
messages = response
|
|
94
|
+
.split('---')
|
|
95
|
+
.map((msg) => msg.trim())
|
|
96
|
+
.filter((msg) => msg.length > 0);
|
|
97
|
+
}
|
|
98
|
+
// Strategy 3: Split by numbered items (1., 2., etc.)
|
|
99
|
+
if (messages.length === 0 || messages.length === 1) {
|
|
100
|
+
const numberedPattern = /^\d+\.\s+/gm;
|
|
101
|
+
if (numberedPattern.test(response)) {
|
|
102
|
+
messages = response
|
|
103
|
+
.split(numberedPattern)
|
|
104
|
+
.map((msg) => msg.trim())
|
|
105
|
+
.filter((msg) => msg.length > 0 && !/^\d+\./.test(msg));
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
// Strategy 4: Split by double newlines (common for multi-line messages)
|
|
109
|
+
if (messages.length === 0 || messages.length === 1) {
|
|
110
|
+
const doubleNewlinePattern = /\n\n+/;
|
|
111
|
+
if (doubleNewlinePattern.test(response)) {
|
|
112
|
+
const parts = response.split(doubleNewlinePattern);
|
|
113
|
+
// Filter out parts that look like commit messages (start with type:)
|
|
114
|
+
messages = parts
|
|
115
|
+
.map((msg) => msg.trim())
|
|
116
|
+
.filter((msg) => {
|
|
117
|
+
const trimmed = msg.trim();
|
|
118
|
+
return (trimmed.length > 0 &&
|
|
119
|
+
/^(feat|fix|docs|style|refactor|test|chore|perf|ci|build)(\([^)]+\))?(!)?:\s/.test(trimmed));
|
|
120
|
+
});
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
// Strategy 5: Try to extract commit messages by pattern matching
|
|
124
|
+
if (messages.length === 0) {
|
|
125
|
+
const commitPattern = /(feat|fix|docs|style|refactor|test|chore|perf|ci|build)(\([^)]+\))?(!)?:\s[^\n]+(?:\n(?!---|\d+\.)[^\n]+)*/g;
|
|
126
|
+
const matches = response.match(commitPattern);
|
|
127
|
+
if (matches) {
|
|
128
|
+
messages = matches.map((msg) => msg.trim());
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
// If still no messages, try to extract any line starting with a valid type
|
|
132
|
+
if (messages.length === 0) {
|
|
133
|
+
const lines = response.split('\n');
|
|
134
|
+
const validTypeLines = [];
|
|
135
|
+
let currentMessage = '';
|
|
136
|
+
for (const line of lines) {
|
|
137
|
+
const trimmed = line.trim();
|
|
138
|
+
if (/^(feat|fix|docs|style|refactor|test|chore|perf|ci|build)(\([^)]+\))?(!)?:\s/.test(trimmed)) {
|
|
139
|
+
if (currentMessage) {
|
|
140
|
+
validTypeLines.push(currentMessage.trim());
|
|
141
|
+
}
|
|
142
|
+
currentMessage = trimmed;
|
|
143
|
+
}
|
|
144
|
+
else if (currentMessage && trimmed.length > 0) {
|
|
145
|
+
currentMessage += `\n${trimmed}`;
|
|
146
|
+
}
|
|
147
|
+
else if (currentMessage && trimmed.length === 0) {
|
|
148
|
+
// Blank line - continue building message
|
|
149
|
+
currentMessage += '\n';
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
if (currentMessage) {
|
|
153
|
+
validTypeLines.push(currentMessage.trim());
|
|
154
|
+
}
|
|
155
|
+
messages = validTypeLines;
|
|
156
|
+
}
|
|
157
|
+
// Clean up messages - remove any that are clearly not commit messages
|
|
158
|
+
messages = messages
|
|
159
|
+
.map((msg) => {
|
|
160
|
+
// Remove common prefixes AI might add
|
|
161
|
+
return msg
|
|
162
|
+
.replace(/^(Here are|Here's|Generated|Commit messages?):?\s*/i, '')
|
|
163
|
+
.replace(/^[-*•]\s*/, '')
|
|
164
|
+
.trim();
|
|
165
|
+
})
|
|
166
|
+
.filter((msg) => {
|
|
167
|
+
// Must start with a valid commit type
|
|
168
|
+
return (msg.length > 0 &&
|
|
169
|
+
/^(feat|fix|docs|style|refactor|test|chore|perf|ci|build)(\([^)]+\))?(!)?:\s/.test(msg));
|
|
170
|
+
});
|
|
171
|
+
return messages.map((message) => ({
|
|
172
|
+
message: message.trim(),
|
|
173
|
+
type: ConventionalCommitsValidator.isSingleLine(message) ? 'single-line' : 'multi-line',
|
|
174
|
+
}));
|
|
175
|
+
}
|
|
176
|
+
/**
|
|
177
|
+
* Filter and validate suggestions, returning only valid ones
|
|
178
|
+
* @param suggestions Array of suggestions to validate
|
|
179
|
+
* @returns Array of valid suggestions
|
|
180
|
+
*/
|
|
181
|
+
filterValidSuggestions(suggestions) {
|
|
182
|
+
const validSuggestions = [];
|
|
183
|
+
for (const suggestion of suggestions) {
|
|
184
|
+
const validation = ConventionalCommitsValidator.validate(suggestion.message);
|
|
185
|
+
if (validation.valid) {
|
|
186
|
+
validSuggestions.push(suggestion);
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
return validSuggestions;
|
|
190
|
+
}
|
|
191
|
+
/**
|
|
192
|
+
* Check if suggestions meet minimum requirements
|
|
193
|
+
* @param suggestions Array of suggestions to check
|
|
194
|
+
* @returns true if meets minimum requirements
|
|
195
|
+
*/
|
|
196
|
+
meetsMinimumRequirements(suggestions) {
|
|
197
|
+
// Need at least 2 valid suggestions
|
|
198
|
+
if (suggestions.length < 2) {
|
|
199
|
+
return false;
|
|
200
|
+
}
|
|
201
|
+
// At least 1 should be single-line (relaxed from 2)
|
|
202
|
+
const singleLineCount = suggestions.filter((s) => s.type === 'single-line').length;
|
|
203
|
+
if (singleLineCount < 1) {
|
|
204
|
+
return false;
|
|
205
|
+
}
|
|
206
|
+
return true;
|
|
207
|
+
}
|
|
208
|
+
/**
|
|
209
|
+
* Generate commit message suggestions using Gemini API
|
|
210
|
+
* @param diff Git diff information
|
|
211
|
+
* @param count Number of suggestions to generate (3-5)
|
|
212
|
+
* @returns Array of validated commit suggestions
|
|
213
|
+
* @throws APIError if API request fails
|
|
214
|
+
* @throws ValidationError if no valid suggestions generated
|
|
215
|
+
*/
|
|
216
|
+
async generateCommitMessages(diff, count = 4) {
|
|
217
|
+
// Ensure count is within bounds
|
|
218
|
+
const requestCount = Math.max(3, Math.min(5, count));
|
|
219
|
+
let attempts = 0;
|
|
220
|
+
const maxAttempts = 3;
|
|
221
|
+
let bestSuggestions = [];
|
|
222
|
+
while (attempts < maxAttempts) {
|
|
223
|
+
attempts++;
|
|
224
|
+
try {
|
|
225
|
+
// Build prompt
|
|
226
|
+
const prompt = this.buildPrompt(diff, requestCount);
|
|
227
|
+
// Call Gemini API with timeout
|
|
228
|
+
const timeoutPromise = new Promise((_, reject) => setTimeout(() => reject(new Error('Request timeout')), 30000));
|
|
229
|
+
const result = await Promise.race([this.model.generateContent(prompt), timeoutPromise]);
|
|
230
|
+
const response = result.response;
|
|
231
|
+
const text = response.text();
|
|
232
|
+
if (!text || text.trim().length === 0) {
|
|
233
|
+
continue;
|
|
234
|
+
}
|
|
235
|
+
// Parse response
|
|
236
|
+
const parsedSuggestions = this.parseResponse(text);
|
|
237
|
+
if (parsedSuggestions.length === 0) {
|
|
238
|
+
continue;
|
|
239
|
+
}
|
|
240
|
+
// Filter valid suggestions
|
|
241
|
+
const validSuggestions = this.filterValidSuggestions(parsedSuggestions);
|
|
242
|
+
// If we have valid suggestions, check if they meet requirements
|
|
243
|
+
if (validSuggestions.length > 0) {
|
|
244
|
+
// If we meet minimum requirements, return them
|
|
245
|
+
if (this.meetsMinimumRequirements(validSuggestions)) {
|
|
246
|
+
// Limit to requested count, prioritizing single-line messages
|
|
247
|
+
const sorted = validSuggestions.sort((a, b) => {
|
|
248
|
+
if (a.type === 'single-line' && b.type !== 'single-line')
|
|
249
|
+
return -1;
|
|
250
|
+
if (a.type !== 'single-line' && b.type === 'single-line')
|
|
251
|
+
return 1;
|
|
252
|
+
return 0;
|
|
253
|
+
});
|
|
254
|
+
return sorted.slice(0, requestCount);
|
|
255
|
+
}
|
|
256
|
+
// Store best suggestions so far
|
|
257
|
+
if (validSuggestions.length > bestSuggestions.length) {
|
|
258
|
+
bestSuggestions = validSuggestions;
|
|
259
|
+
}
|
|
260
|
+
}
|
|
261
|
+
// If we have some valid suggestions but not enough, try to generate more
|
|
262
|
+
if (validSuggestions.length > 0 && validSuggestions.length < 2 && attempts < maxAttempts) {
|
|
263
|
+
// Request more messages in next attempt
|
|
264
|
+
continue;
|
|
265
|
+
}
|
|
266
|
+
// If we have at least 2 valid suggestions, return them even if not perfect
|
|
267
|
+
if (validSuggestions.length >= 2) {
|
|
268
|
+
return validSuggestions.slice(0, requestCount);
|
|
269
|
+
}
|
|
270
|
+
}
|
|
271
|
+
catch (error) {
|
|
272
|
+
const errorMessage = error.message.toLowerCase();
|
|
273
|
+
// Handle specific API errors that shouldn't be retried
|
|
274
|
+
if (errorMessage.includes('rate limit') || errorMessage.includes('quota')) {
|
|
275
|
+
throw APIError.rateLimitExceeded();
|
|
276
|
+
}
|
|
277
|
+
if (errorMessage.includes('api key') ||
|
|
278
|
+
errorMessage.includes('auth') ||
|
|
279
|
+
errorMessage.includes('permission')) {
|
|
280
|
+
throw APIError.authenticationFailed();
|
|
281
|
+
}
|
|
282
|
+
if (errorMessage.includes('timeout') || errorMessage.includes('request timeout')) {
|
|
283
|
+
if (attempts >= maxAttempts) {
|
|
284
|
+
throw APIError.timeout();
|
|
285
|
+
}
|
|
286
|
+
// Continue to retry on timeout
|
|
287
|
+
continue;
|
|
288
|
+
}
|
|
289
|
+
// For other errors, continue retrying
|
|
290
|
+
if (attempts < maxAttempts) {
|
|
291
|
+
}
|
|
292
|
+
}
|
|
293
|
+
}
|
|
294
|
+
// If we have some valid suggestions, return them as fallback
|
|
295
|
+
if (bestSuggestions.length >= 1) {
|
|
296
|
+
return bestSuggestions.slice(0, requestCount);
|
|
297
|
+
}
|
|
298
|
+
// Last resort: try to generate a simple fallback message
|
|
299
|
+
if (bestSuggestions.length === 0) {
|
|
300
|
+
const fallbackMessage = this.generateFallbackMessage(diff);
|
|
301
|
+
if (fallbackMessage) {
|
|
302
|
+
return [
|
|
303
|
+
{
|
|
304
|
+
message: fallbackMessage,
|
|
305
|
+
type: 'single-line',
|
|
306
|
+
},
|
|
307
|
+
];
|
|
308
|
+
}
|
|
309
|
+
}
|
|
310
|
+
// If all else fails, throw error with helpful message
|
|
311
|
+
throw ValidationError.noValidSuggestions();
|
|
312
|
+
}
|
|
313
|
+
/**
|
|
314
|
+
* Generate a simple fallback commit message when AI fails
|
|
315
|
+
* @param diff Git diff information
|
|
316
|
+
* @returns Simple commit message or null
|
|
317
|
+
*/
|
|
318
|
+
generateFallbackMessage(diff) {
|
|
319
|
+
const combinedDiff = `${diff.staged}\n${diff.unstaged}`.toLowerCase();
|
|
320
|
+
// Simple heuristics to determine commit type
|
|
321
|
+
let type = 'chore';
|
|
322
|
+
if (combinedDiff.includes('fix') ||
|
|
323
|
+
combinedDiff.includes('bug') ||
|
|
324
|
+
combinedDiff.includes('error')) {
|
|
325
|
+
type = 'fix';
|
|
326
|
+
}
|
|
327
|
+
else if (combinedDiff.includes('feat') ||
|
|
328
|
+
combinedDiff.includes('add') ||
|
|
329
|
+
combinedDiff.includes('new')) {
|
|
330
|
+
type = 'feat';
|
|
331
|
+
}
|
|
332
|
+
else if (combinedDiff.includes('doc') || combinedDiff.includes('readme')) {
|
|
333
|
+
type = 'docs';
|
|
334
|
+
}
|
|
335
|
+
else if (combinedDiff.includes('refactor')) {
|
|
336
|
+
type = 'refactor';
|
|
337
|
+
}
|
|
338
|
+
// Try to extract a simple description
|
|
339
|
+
const lines = combinedDiff.split('\n').slice(0, 5);
|
|
340
|
+
const fileChanges = lines.filter((l) => l.startsWith('+++') || l.startsWith('---'));
|
|
341
|
+
if (fileChanges.length > 0) {
|
|
342
|
+
const firstFile = fileChanges[0]
|
|
343
|
+
.replace(/^[+-]{3}\s+/, '')
|
|
344
|
+
.split('/')
|
|
345
|
+
.pop() || 'files';
|
|
346
|
+
return `${type}: update ${firstFile}`;
|
|
347
|
+
}
|
|
348
|
+
return `${type}: update code`;
|
|
349
|
+
}
|
|
350
|
+
}
|
|
351
|
+
//# sourceMappingURL=AIService.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"AIService.js","sourceRoot":"","sources":["../../src/ai/AIService.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
import type { Config } from '../types/index.js';
|
|
2
|
+
import type { UIManager } from '../ui/UIManager.js';
|
|
3
|
+
/**
|
|
4
|
+
* Manages persistent configuration for the Commit CLI
|
|
5
|
+
* Handles loading, saving, and validation of API key and model preferences
|
|
6
|
+
*/
|
|
7
|
+
export declare class ConfigManager {
|
|
8
|
+
private readonly configPath;
|
|
9
|
+
private readonly configDir;
|
|
10
|
+
private static readonly CONFIG_VERSION;
|
|
11
|
+
constructor(configPath?: string);
|
|
12
|
+
/**
|
|
13
|
+
* Load configuration from disk
|
|
14
|
+
* @returns Config object or null if not found
|
|
15
|
+
* @throws ConfigurationError if config file is corrupted
|
|
16
|
+
*/
|
|
17
|
+
load(): Promise<Config | null>;
|
|
18
|
+
/**
|
|
19
|
+
* Save configuration to disk
|
|
20
|
+
* Creates config directory if it doesn't exist
|
|
21
|
+
* @param config Configuration to save
|
|
22
|
+
* @throws ConfigurationError if save fails
|
|
23
|
+
*/
|
|
24
|
+
save(config: Config): Promise<void>;
|
|
25
|
+
/**
|
|
26
|
+
* Check if configuration file exists
|
|
27
|
+
* @returns true if config exists, false otherwise
|
|
28
|
+
*/
|
|
29
|
+
exists(): Promise<boolean>;
|
|
30
|
+
/**
|
|
31
|
+
* Get the configuration file path
|
|
32
|
+
* @returns Absolute path to config file
|
|
33
|
+
*/
|
|
34
|
+
getConfigPath(): string;
|
|
35
|
+
/**
|
|
36
|
+
* Validate API key format
|
|
37
|
+
* Basic validation - checks if it looks like a Gemini API key
|
|
38
|
+
* @param apiKey API key to validate
|
|
39
|
+
* @returns true if valid format
|
|
40
|
+
*/
|
|
41
|
+
static validateApiKeyFormat(apiKey: string): boolean;
|
|
42
|
+
/**
|
|
43
|
+
* Prompt user for configuration (API key and model)
|
|
44
|
+
* @param ui UIManager instance for prompts
|
|
45
|
+
* @returns New configuration object
|
|
46
|
+
*/
|
|
47
|
+
promptForConfig(ui: UIManager): Promise<Config>;
|
|
48
|
+
}
|
|
49
|
+
//# sourceMappingURL=ConfigManager.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ConfigManager.d.ts","sourceRoot":"","sources":["../../src/config/ConfigManager.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,mBAAmB,CAAC;AAChD,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,oBAAoB,CAAC;AAEpD;;;GAGG;AACH,qBAAa,aAAa;IACxB,OAAO,CAAC,QAAQ,CAAC,UAAU,CAAS;IACpC,OAAO,CAAC,QAAQ,CAAC,SAAS,CAAS;IACnC,OAAO,CAAC,MAAM,CAAC,QAAQ,CAAC,cAAc,CAAW;gBAErC,UAAU,CAAC,EAAE,MAAM;IAK/B;;;;OAIG;IACG,IAAI,IAAI,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC;IA6BpC;;;;;OAKG;IACG,IAAI,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAkBzC;;;OAGG;IACG,MAAM,IAAI,OAAO,CAAC,OAAO,CAAC;IAShC;;;OAGG;IACH,aAAa,IAAI,MAAM;IAIvB;;;;;OAKG;IACH,MAAM,CAAC,oBAAoB,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO;IAMpD;;;;OAIG;IACG,eAAe,CAAC,EAAE,EAAE,SAAS,GAAG,OAAO,CAAC,MAAM,CAAC;CAuBtD"}
|
|
@@ -0,0 +1,124 @@
|
|
|
1
|
+
import { promises as fs } from 'node:fs';
|
|
2
|
+
import { homedir } from 'node:os';
|
|
3
|
+
import { dirname, join } from 'node:path';
|
|
4
|
+
import { ConfigurationError } from '../errors/CustomErrors.js';
|
|
5
|
+
/**
|
|
6
|
+
* Manages persistent configuration for the Commit CLI
|
|
7
|
+
* Handles loading, saving, and validation of API key and model preferences
|
|
8
|
+
*/
|
|
9
|
+
export class ConfigManager {
|
|
10
|
+
configPath;
|
|
11
|
+
configDir;
|
|
12
|
+
static CONFIG_VERSION = '1.0.0';
|
|
13
|
+
constructor(configPath) {
|
|
14
|
+
this.configPath = configPath || join(homedir(), '.commic', 'config.json');
|
|
15
|
+
this.configDir = dirname(this.configPath);
|
|
16
|
+
}
|
|
17
|
+
/**
|
|
18
|
+
* Load configuration from disk
|
|
19
|
+
* @returns Config object or null if not found
|
|
20
|
+
* @throws ConfigurationError if config file is corrupted
|
|
21
|
+
*/
|
|
22
|
+
async load() {
|
|
23
|
+
try {
|
|
24
|
+
const configData = await fs.readFile(this.configPath, 'utf-8');
|
|
25
|
+
const config = JSON.parse(configData);
|
|
26
|
+
// Validate config structure
|
|
27
|
+
if (!config.apiKey || !config.model) {
|
|
28
|
+
throw ConfigurationError.configFileCorrupted();
|
|
29
|
+
}
|
|
30
|
+
return config;
|
|
31
|
+
}
|
|
32
|
+
catch (error) {
|
|
33
|
+
if (error.code === 'ENOENT') {
|
|
34
|
+
// Config file doesn't exist yet - this is fine on first run
|
|
35
|
+
return null;
|
|
36
|
+
}
|
|
37
|
+
if (error instanceof SyntaxError) {
|
|
38
|
+
throw ConfigurationError.configFileCorrupted();
|
|
39
|
+
}
|
|
40
|
+
if (error instanceof ConfigurationError) {
|
|
41
|
+
throw error;
|
|
42
|
+
}
|
|
43
|
+
throw ConfigurationError.configSaveFailed(error);
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
/**
|
|
47
|
+
* Save configuration to disk
|
|
48
|
+
* Creates config directory if it doesn't exist
|
|
49
|
+
* @param config Configuration to save
|
|
50
|
+
* @throws ConfigurationError if save fails
|
|
51
|
+
*/
|
|
52
|
+
async save(config) {
|
|
53
|
+
try {
|
|
54
|
+
// Ensure config directory exists
|
|
55
|
+
await fs.mkdir(this.configDir, { recursive: true });
|
|
56
|
+
// Add version to config
|
|
57
|
+
const configWithVersion = {
|
|
58
|
+
...config,
|
|
59
|
+
version: ConfigManager.CONFIG_VERSION,
|
|
60
|
+
};
|
|
61
|
+
// Write config file with pretty formatting
|
|
62
|
+
await fs.writeFile(this.configPath, JSON.stringify(configWithVersion, null, 2), 'utf-8');
|
|
63
|
+
}
|
|
64
|
+
catch (error) {
|
|
65
|
+
throw ConfigurationError.configSaveFailed(error);
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
/**
|
|
69
|
+
* Check if configuration file exists
|
|
70
|
+
* @returns true if config exists, false otherwise
|
|
71
|
+
*/
|
|
72
|
+
async exists() {
|
|
73
|
+
try {
|
|
74
|
+
await fs.access(this.configPath);
|
|
75
|
+
return true;
|
|
76
|
+
}
|
|
77
|
+
catch {
|
|
78
|
+
return false;
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
/**
|
|
82
|
+
* Get the configuration file path
|
|
83
|
+
* @returns Absolute path to config file
|
|
84
|
+
*/
|
|
85
|
+
getConfigPath() {
|
|
86
|
+
return this.configPath;
|
|
87
|
+
}
|
|
88
|
+
/**
|
|
89
|
+
* Validate API key format
|
|
90
|
+
* Basic validation - checks if it looks like a Gemini API key
|
|
91
|
+
* @param apiKey API key to validate
|
|
92
|
+
* @returns true if valid format
|
|
93
|
+
*/
|
|
94
|
+
static validateApiKeyFormat(apiKey) {
|
|
95
|
+
// Gemini API keys typically start with "AIza" and are around 39 characters
|
|
96
|
+
// This is a basic check - actual validation happens when making API calls
|
|
97
|
+
return apiKey.length > 20 && apiKey.trim() === apiKey;
|
|
98
|
+
}
|
|
99
|
+
/**
|
|
100
|
+
* Prompt user for configuration (API key and model)
|
|
101
|
+
* @param ui UIManager instance for prompts
|
|
102
|
+
* @returns New configuration object
|
|
103
|
+
*/
|
|
104
|
+
async promptForConfig(ui) {
|
|
105
|
+
ui.showSectionHeader('🔧 Configuration Setup');
|
|
106
|
+
ui.showInfo('Get your free API key at: https://aistudio.google.com/app/api-keys');
|
|
107
|
+
ui.newLine();
|
|
108
|
+
// Prompt for API key
|
|
109
|
+
const apiKey = await ui.promptForApiKey();
|
|
110
|
+
// Validate API key format
|
|
111
|
+
if (!ConfigManager.validateApiKeyFormat(apiKey)) {
|
|
112
|
+
throw ConfigurationError.invalidApiKey();
|
|
113
|
+
}
|
|
114
|
+
// Prompt for model selection
|
|
115
|
+
const availableModels = ['gemini-2.5-flash', 'gemini-flash-latest'];
|
|
116
|
+
const model = await ui.promptForModel(availableModels);
|
|
117
|
+
return {
|
|
118
|
+
apiKey,
|
|
119
|
+
model,
|
|
120
|
+
version: ConfigManager.CONFIG_VERSION,
|
|
121
|
+
};
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
//# sourceMappingURL=ConfigManager.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ConfigManager.js","sourceRoot":"","sources":["../../src/config/ConfigManager.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Base error class for Commit CLI
|
|
3
|
+
* Provides consistent error structure with user-friendly messages and suggestions
|
|
4
|
+
*/
|
|
5
|
+
declare class CommitCLIError extends Error {
|
|
6
|
+
readonly suggestion: string | null;
|
|
7
|
+
constructor(message: string, suggestion?: string | null);
|
|
8
|
+
}
|
|
9
|
+
/**
|
|
10
|
+
* Git repository related errors
|
|
11
|
+
* Thrown when Git operations fail or repository is invalid
|
|
12
|
+
*/
|
|
13
|
+
export declare class GitRepositoryError extends CommitCLIError {
|
|
14
|
+
constructor(message: string, suggestion?: string);
|
|
15
|
+
static noRepositoryFound(path: string): GitRepositoryError;
|
|
16
|
+
static noCommitsFound(): GitRepositoryError;
|
|
17
|
+
static noChanges(): GitRepositoryError;
|
|
18
|
+
static commitFailed(gitError: string): GitRepositoryError;
|
|
19
|
+
static pathNotAccessible(path: string): GitRepositoryError;
|
|
20
|
+
}
|
|
21
|
+
/**
|
|
22
|
+
* Configuration related errors
|
|
23
|
+
* Thrown when config file operations fail or configuration is invalid
|
|
24
|
+
*/
|
|
25
|
+
export declare class ConfigurationError extends CommitCLIError {
|
|
26
|
+
constructor(message: string, suggestion?: string);
|
|
27
|
+
static noApiKey(): ConfigurationError;
|
|
28
|
+
static invalidApiKey(): ConfigurationError;
|
|
29
|
+
static configFileCorrupted(): ConfigurationError;
|
|
30
|
+
static configSaveFailed(error: Error): ConfigurationError;
|
|
31
|
+
}
|
|
32
|
+
/**
|
|
33
|
+
* Gemini API related errors
|
|
34
|
+
* Thrown when API requests fail or return invalid responses
|
|
35
|
+
*/
|
|
36
|
+
export declare class APIError extends CommitCLIError {
|
|
37
|
+
constructor(message: string, suggestion?: string);
|
|
38
|
+
static requestFailed(error: Error): APIError;
|
|
39
|
+
static rateLimitExceeded(): APIError;
|
|
40
|
+
static invalidResponse(): APIError;
|
|
41
|
+
static authenticationFailed(): APIError;
|
|
42
|
+
static timeout(): APIError;
|
|
43
|
+
}
|
|
44
|
+
/**
|
|
45
|
+
* Commit message validation errors
|
|
46
|
+
* Thrown when generated messages don't meet Conventional Commits specification
|
|
47
|
+
*/
|
|
48
|
+
export declare class ValidationError extends CommitCLIError {
|
|
49
|
+
constructor(message: string, suggestion?: string);
|
|
50
|
+
static invalidConventionalCommit(errors: string[]): ValidationError;
|
|
51
|
+
static noValidSuggestions(): ValidationError;
|
|
52
|
+
}
|
|
53
|
+
export {};
|
|
54
|
+
//# sourceMappingURL=CustomErrors.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"CustomErrors.d.ts","sourceRoot":"","sources":["../../src/errors/CustomErrors.ts"],"names":[],"mappings":"AAAA;;;GAGG;AACH,cAAM,cAAe,SAAQ,KAAK;IAChC,SAAgB,UAAU,EAAE,MAAM,GAAG,IAAI,CAAC;gBAE9B,OAAO,EAAE,MAAM,EAAE,UAAU,GAAE,MAAM,GAAG,IAAW;CAM9D;AAED;;;GAGG;AACH,qBAAa,kBAAmB,SAAQ,cAAc;gBAElD,OAAO,EAAE,MAAM,EACf,UAAU,GAAE,MAA8E;IAK5F,MAAM,CAAC,iBAAiB,CAAC,IAAI,EAAE,MAAM,GAAG,kBAAkB;IAO1D,MAAM,CAAC,cAAc,IAAI,kBAAkB;IAO3C,MAAM,CAAC,SAAS,IAAI,kBAAkB;IAOtC,MAAM,CAAC,YAAY,CAAC,QAAQ,EAAE,MAAM,GAAG,kBAAkB;IAOzD,MAAM,CAAC,iBAAiB,CAAC,IAAI,EAAE,MAAM,GAAG,kBAAkB;CAM3D;AAED;;;GAGG;AACH,qBAAa,kBAAmB,SAAQ,cAAc;gBAElD,OAAO,EAAE,MAAM,EACf,UAAU,GAAE,MAAyD;IAKvE,MAAM,CAAC,QAAQ,IAAI,kBAAkB;IAOrC,MAAM,CAAC,aAAa,IAAI,kBAAkB;IAO1C,MAAM,CAAC,mBAAmB,IAAI,kBAAkB;IAOhD,MAAM,CAAC,gBAAgB,CAAC,KAAK,EAAE,KAAK,GAAG,kBAAkB;CAM1D;AAED;;;GAGG;AACH,qBAAa,QAAS,SAAQ,cAAc;gBAExC,OAAO,EAAE,MAAM,EACf,UAAU,GAAE,MAAwD;IAKtE,MAAM,CAAC,aAAa,CAAC,KAAK,EAAE,KAAK,GAAG,QAAQ;IAO5C,MAAM,CAAC,iBAAiB,IAAI,QAAQ;IAOpC,MAAM,CAAC,eAAe,IAAI,QAAQ;IAOlC,MAAM,CAAC,oBAAoB,IAAI,QAAQ;IAOvC,MAAM,CAAC,OAAO,IAAI,QAAQ;CAG3B;AAED;;;GAGG;AACH,qBAAa,eAAgB,SAAQ,cAAc;gBAE/C,OAAO,EAAE,MAAM,EACf,UAAU,GAAE,MAA8D;IAK5E,MAAM,CAAC,yBAAyB,CAAC,MAAM,EAAE,MAAM,EAAE,GAAG,eAAe;IAQnE,MAAM,CAAC,kBAAkB,IAAI,eAAe;CAM7C"}
|