maiass 5.7.31
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 +26 -0
- package/README.md +347 -0
- package/build.js +127 -0
- package/lib/account-info.js +476 -0
- package/lib/colors.js +49 -0
- package/lib/commit.js +885 -0
- package/lib/config-command.js +310 -0
- package/lib/config-manager.js +344 -0
- package/lib/config.js +150 -0
- package/lib/devlog.js +182 -0
- package/lib/env-display.js +162 -0
- package/lib/git-info.js +509 -0
- package/lib/header.js +152 -0
- package/lib/input-utils.js +116 -0
- package/lib/logger.js +285 -0
- package/lib/machine-fingerprint.js +229 -0
- package/lib/maiass-command.js +79 -0
- package/lib/maiass-pipeline.js +1204 -0
- package/lib/maiass-variables.js +152 -0
- package/lib/secure-storage.js +256 -0
- package/lib/symbols.js +200 -0
- package/lib/token-validator.js +184 -0
- package/lib/version-command.js +256 -0
- package/lib/version-manager.js +902 -0
- package/maiass-standalone.cjs +148 -0
- package/maiass.cjs +34 -0
- package/maiass.mjs +167 -0
- package/package.json +45 -0
- package/setup-env.js +83 -0
package/lib/commit.js
ADDED
|
@@ -0,0 +1,885 @@
|
|
|
1
|
+
// Commit functionality for MAIASS - port of maiass.sh commit behavior
|
|
2
|
+
import { execSync } from 'child_process';
|
|
3
|
+
import { log } from './logger.js';
|
|
4
|
+
import { SYMBOLS } from './symbols.js';
|
|
5
|
+
import { getGitInfo, getGitStatus } from './git-info.js';
|
|
6
|
+
import readline from 'readline';
|
|
7
|
+
import { loadEnvironmentConfig } from './config.js';
|
|
8
|
+
import { generateMachineFingerprint } from './machine-fingerprint.js';
|
|
9
|
+
import { storeSecureVariable, retrieveSecureVariable } from './secure-storage.js';
|
|
10
|
+
import { getSingleCharInput, getMultiLineInput } from './input-utils.js';
|
|
11
|
+
import { logCommit } from './devlog.js';
|
|
12
|
+
import colors from './colors.js';
|
|
13
|
+
import chalk from 'chalk';
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* Get color for credit display based on remaining credits (matches bashmaiass)
|
|
17
|
+
* @param {number} credits - Remaining credits
|
|
18
|
+
* @returns {Function} Chalk color function
|
|
19
|
+
*/
|
|
20
|
+
function getCreditColor(credits) {
|
|
21
|
+
if (credits >= 1000) {
|
|
22
|
+
return chalk.hex('#00AA00'); // Green
|
|
23
|
+
} else if (credits > 600) {
|
|
24
|
+
// Interpolate green to yellow
|
|
25
|
+
const ratio = (credits - 600) / 400;
|
|
26
|
+
const r = Math.round(0 + (255 - 0) * (1 - ratio));
|
|
27
|
+
const g = Math.round(170 + (255 - 170) * (1 - ratio));
|
|
28
|
+
return chalk.hex(`#${r.toString(16).padStart(2, '0')}${g.toString(16).padStart(2, '0')}00`);
|
|
29
|
+
} else if (credits > 300) {
|
|
30
|
+
// Interpolate yellow to orange
|
|
31
|
+
const ratio = (credits - 300) / 300;
|
|
32
|
+
const g = Math.round(165 + (255 - 165) * ratio);
|
|
33
|
+
return chalk.hex(`#FF${g.toString(16).padStart(2, '0')}00`);
|
|
34
|
+
} else {
|
|
35
|
+
// Interpolate orange to red
|
|
36
|
+
const ratio = credits / 300;
|
|
37
|
+
const g = Math.round(0 + 165 * ratio);
|
|
38
|
+
return chalk.hex(`#FF${g.toString(16).padStart(2, '0')}00`);
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
/**
|
|
43
|
+
* Print gradient line (matches bashmaiass)
|
|
44
|
+
* Gradient from soft pink (#f7b2c4) to burgundy (#6b0022)
|
|
45
|
+
* @param {number} length - Length of line
|
|
46
|
+
*/
|
|
47
|
+
function printGradientLine(length = 50) {
|
|
48
|
+
// Create gradient from soft pink to burgundy
|
|
49
|
+
const startColor = { r: 0xf7, g: 0xb2, b: 0xc4 }; // soft pink
|
|
50
|
+
const endColor = { r: 0x6b, g: 0x00, b: 0x22 }; // burgundy
|
|
51
|
+
|
|
52
|
+
let line = '';
|
|
53
|
+
for (let i = 0; i < length; i++) {
|
|
54
|
+
const ratio = length > 1 ? i / (length - 1) : 0;
|
|
55
|
+
const r = Math.round(startColor.r + (endColor.r - startColor.r) * ratio);
|
|
56
|
+
const g = Math.round(startColor.g + (endColor.g - startColor.g) * ratio);
|
|
57
|
+
const b = Math.round(startColor.b + (endColor.b - startColor.b) * ratio);
|
|
58
|
+
line += chalk.hex(`#${r.toString(16).padStart(2, '0')}${g.toString(16).padStart(2, '0')}${b.toString(16).padStart(2, '0')}`)('═');
|
|
59
|
+
}
|
|
60
|
+
console.log(line);
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
/**
|
|
64
|
+
* Execute git command with proper error handling
|
|
65
|
+
* @param {string} command - Git command to execute
|
|
66
|
+
* @param {boolean} silent - Whether to suppress output
|
|
67
|
+
* @returns {string|null} Command output or null if failed
|
|
68
|
+
*/
|
|
69
|
+
function executeGitCommand(command, silent = false) {
|
|
70
|
+
try {
|
|
71
|
+
const result = execSync(command, {
|
|
72
|
+
encoding: 'utf8',
|
|
73
|
+
stdio: silent ? 'pipe' : 'inherit'
|
|
74
|
+
});
|
|
75
|
+
return typeof result === 'string' ? result.trim() : '';
|
|
76
|
+
} catch (error) {
|
|
77
|
+
if (!silent) {
|
|
78
|
+
log.error(SYMBOLS.CROSS, `Git command failed: ${command}\n${error.message}`);
|
|
79
|
+
}
|
|
80
|
+
return null;
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
/**
|
|
85
|
+
* Check if remote exists
|
|
86
|
+
* @param {string} remoteName - Name of remote (default: origin)
|
|
87
|
+
* @returns {boolean} True if remote exists
|
|
88
|
+
*/
|
|
89
|
+
function remoteExists(remoteName = 'origin') {
|
|
90
|
+
const result = executeGitCommand(`git remote get-url ${remoteName}`, true);
|
|
91
|
+
return result !== null;
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
/**
|
|
95
|
+
* Create anonymous subscription automatically if no API key exists
|
|
96
|
+
* @returns {Promise<string|null>} API key or null if failed
|
|
97
|
+
*/
|
|
98
|
+
async function createAnonymousSubscriptionIfNeeded() {
|
|
99
|
+
const debugMode = process.env.MAIASS_DEBUG === 'true';
|
|
100
|
+
|
|
101
|
+
try {
|
|
102
|
+
// Check if we already have a token in environment (from secure storage or config files)
|
|
103
|
+
if (process.env.MAIASS_AI_TOKEN) {
|
|
104
|
+
if (debugMode) {
|
|
105
|
+
log.debug(SYMBOLS.INFO, '[MAIASS DEBUG] Using existing MAIASS_AI_TOKEN from environment');
|
|
106
|
+
}
|
|
107
|
+
return process.env.MAIASS_AI_TOKEN;
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
// Check if we have an anonymous token in secure storage
|
|
111
|
+
const existingToken = retrieveSecureVariable('MAIASS_AI_TOKEN');
|
|
112
|
+
if (existingToken) {
|
|
113
|
+
if (debugMode) {
|
|
114
|
+
log.debug(SYMBOLS.INFO, '[MAIASS DEBUG] Using existing anonymous token from secure storage');
|
|
115
|
+
}
|
|
116
|
+
// Set in environment for this session
|
|
117
|
+
process.env.MAIASS_AI_TOKEN = existingToken;
|
|
118
|
+
return existingToken;
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
log.info(SYMBOLS.INFO, 'No AI API key found. Creating anonymous subscription...');
|
|
122
|
+
|
|
123
|
+
const machineFingerprint = generateMachineFingerprint();
|
|
124
|
+
const endpoint = process.env.MAIASS_AI_HOST || 'https://pound.maiass.net';
|
|
125
|
+
|
|
126
|
+
if (debugMode) {
|
|
127
|
+
log.debug(SYMBOLS.INFO, `[MAIASS DEBUG] Creating anonymous subscription at: ${endpoint}/v1/token`);
|
|
128
|
+
log.debug(SYMBOLS.INFO, `[MAIASS DEBUG] Machine fingerprint: ${JSON.stringify(machineFingerprint, null, 2)}`);
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
const response = await fetch(`${endpoint}/v1/token`, {
|
|
132
|
+
method: 'POST',
|
|
133
|
+
headers: {
|
|
134
|
+
'Content-Type': 'application/json',
|
|
135
|
+
'X-Client-Name': 'nodemaiass',
|
|
136
|
+
'X-Client-Version': '5.7.5'
|
|
137
|
+
},
|
|
138
|
+
body: JSON.stringify({
|
|
139
|
+
machine_fingerprint: machineFingerprint
|
|
140
|
+
})
|
|
141
|
+
});
|
|
142
|
+
|
|
143
|
+
if (!response.ok) {
|
|
144
|
+
const errorText = await response.text();
|
|
145
|
+
let errorData = {};
|
|
146
|
+
try {
|
|
147
|
+
errorData = JSON.parse(errorText);
|
|
148
|
+
} catch (e) {
|
|
149
|
+
errorData = { error: errorText };
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
if (debugMode) {
|
|
153
|
+
log.debug(SYMBOLS.WARNING, `[MAIASS DEBUG] Anonymous subscription failed: ${response.status} ${response.statusText}`);
|
|
154
|
+
log.debug(SYMBOLS.WARNING, `[MAIASS DEBUG] Error response: ${errorText}`);
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
log.error(SYMBOLS.CROSS, `Failed to create anonymous subscription: ${errorData.error || response.statusText}`);
|
|
158
|
+
return null;
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
const data = await response.json();
|
|
162
|
+
|
|
163
|
+
if (debugMode) {
|
|
164
|
+
log.debug(SYMBOLS.INFO, `[MAIASS DEBUG] Anonymous subscription response: ${JSON.stringify(data, null, 2)}`);
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
// Extract fields - try multiple field names for compatibility
|
|
168
|
+
const apiKey = data.apiKey || data.api_key || data.token;
|
|
169
|
+
const subscriptionId = data.id || data.subscription_id;
|
|
170
|
+
const credits = data.creditsRemaining || data.credits_remaining || data.credits;
|
|
171
|
+
const purchaseUrl = data.purchaseUrl || data.payment_url || data.top_up_url;
|
|
172
|
+
|
|
173
|
+
if (apiKey) {
|
|
174
|
+
// Store the token in secure storage
|
|
175
|
+
const stored = storeSecureVariable('MAIASS_AI_TOKEN', apiKey);
|
|
176
|
+
|
|
177
|
+
if (subscriptionId) {
|
|
178
|
+
storeSecureVariable('MAIASS_SUBSCRIPTION_ID', subscriptionId);
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
if (stored) {
|
|
182
|
+
log.success(SYMBOLS.CHECKMARK, 'Anonymous subscription created and stored securely');
|
|
183
|
+
log.info(SYMBOLS.INFO, ` API Key: ${apiKey.substring(0, 8)}...`);
|
|
184
|
+
log.info(SYMBOLS.INFO, ` Credits: ${credits || 'N/A'}`);
|
|
185
|
+
|
|
186
|
+
if (subscriptionId) {
|
|
187
|
+
log.info(SYMBOLS.INFO, ` Subscription ID: ${subscriptionId.substring(0, 12)}...`);
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
// Warn if zero credits
|
|
191
|
+
if (!credits || credits === 0) {
|
|
192
|
+
log.warning(SYMBOLS.WARNING, '⚠️ Your anonymous API key has zero credits. Please purchase credits to use AI features.');
|
|
193
|
+
if (purchaseUrl) {
|
|
194
|
+
log.info(SYMBOLS.INFO, ` Purchase credits here: ${purchaseUrl}`);
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
} else {
|
|
198
|
+
log.warning(SYMBOLS.WARNING, 'Anonymous subscription created but could not store securely');
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
// Set in environment for this session
|
|
202
|
+
process.env.MAIASS_AI_TOKEN = apiKey;
|
|
203
|
+
if (subscriptionId) {
|
|
204
|
+
process.env.MAIASS_SUBSCRIPTION_ID = subscriptionId;
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
return apiKey;
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
return null;
|
|
211
|
+
|
|
212
|
+
} catch (error) {
|
|
213
|
+
log.error(SYMBOLS.CROSS, `Failed to create anonymous subscription: ${error.message}`);
|
|
214
|
+
return null;
|
|
215
|
+
}
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
/**
|
|
219
|
+
* Get AI commit message suggestion
|
|
220
|
+
* @param {Object} gitInfo - Git information object
|
|
221
|
+
* @returns {Promise<string|null>} AI suggested commit message or null
|
|
222
|
+
*/
|
|
223
|
+
async function getAICommitSuggestion(gitInfo) {
|
|
224
|
+
const config = loadEnvironmentConfig();
|
|
225
|
+
|
|
226
|
+
// Enhanced debug logging: output the diff and parameters when debug or verbose
|
|
227
|
+
const debugMode = process.env.MAIASS_DEBUG === 'true' || (process.env.MAIASS_VERBOSITY && ['debug','verbose'].includes(process.env.MAIASS_VERBOSITY));
|
|
228
|
+
|
|
229
|
+
if (debugMode) {
|
|
230
|
+
log.debug(SYMBOLS.INFO, '[MAIASS DEBUG] --- AI Commit Suggestion Starting ---');
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
// Try to get existing token or create anonymous subscription
|
|
234
|
+
let maiassToken = await createAnonymousSubscriptionIfNeeded();
|
|
235
|
+
|
|
236
|
+
const aiHost = process.env.MAIASS_AI_HOST || 'https://pound.maiass.net';
|
|
237
|
+
const aiEndpoint = aiHost + '/proxy';
|
|
238
|
+
const aiModel = process.env.MAIASS_AI_MODEL || 'gpt-3.5-turbo';
|
|
239
|
+
const aiTemperature = parseFloat(process.env.MAIASS_AI_TEMPERATURE || '0.7');
|
|
240
|
+
const commitMessageStyle = process.env.MAIASS_AI_COMMIT_MESSAGE_STYLE || 'bullet';
|
|
241
|
+
const maxCharacters = parseInt(process.env.MAIASS_AI_MAX_CHARACTERS || '8000');
|
|
242
|
+
|
|
243
|
+
if (debugMode) {
|
|
244
|
+
log.debug(SYMBOLS.INFO, `[MAIASS DEBUG] AI Host: ${aiHost}`);
|
|
245
|
+
log.debug(SYMBOLS.INFO, `[MAIASS DEBUG] AI Endpoint: ${aiEndpoint}`);
|
|
246
|
+
log.debug(SYMBOLS.INFO, `[MAIASS DEBUG] Token available: ${maiassToken ? 'Yes' : 'No'}`);
|
|
247
|
+
if (maiassToken) {
|
|
248
|
+
log.debug(SYMBOLS.INFO, `[MAIASS DEBUG] Token preview: ${maiassToken.substring(0, 8)}...`);
|
|
249
|
+
}
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
if (!maiassToken) {
|
|
253
|
+
if (debugMode) {
|
|
254
|
+
log.debug(SYMBOLS.WARNING, '[MAIASS DEBUG] No AI token available - cannot proceed with AI suggestion');
|
|
255
|
+
}
|
|
256
|
+
return null;
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
try {
|
|
260
|
+
// Get changelog filenames from environment or use defaults
|
|
261
|
+
const changelogName = process.env.MAIASS_CHANGELOG_NAME || 'CHANGELOG.md';
|
|
262
|
+
const internalChangelogName = process.env.MAIASS_CHANGELOG_INTERNAL_NAME || '.CHANGELOG_internal.md';
|
|
263
|
+
|
|
264
|
+
// Build git diff command - first try with exclusions, then fallback to all files
|
|
265
|
+
const gitDiffCommand = `git diff --cached`;
|
|
266
|
+
|
|
267
|
+
if (debugMode) {
|
|
268
|
+
log.debug(SYMBOLS.INFO, `[MAIASS DEBUG] Git diff command: ${gitDiffCommand}`);
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
// Get git diff for staged changes
|
|
272
|
+
let gitDiff = executeGitCommand(gitDiffCommand, true);
|
|
273
|
+
|
|
274
|
+
if (debugMode) {
|
|
275
|
+
log.debug(SYMBOLS.INFO, `[MAIASS DEBUG] Raw git diff length: ${gitDiff ? gitDiff.length : 0} characters`);
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
// If we have a diff, filter out changelog content manually
|
|
279
|
+
if (gitDiff) {
|
|
280
|
+
// Split diff into file sections and filter out changelog files
|
|
281
|
+
const diffSections = gitDiff.split(/^diff --git /m);
|
|
282
|
+
const filteredSections = diffSections.filter(section => {
|
|
283
|
+
if (!section.trim()) return false;
|
|
284
|
+
// Check if this section is for a changelog file
|
|
285
|
+
const isChangelog = section.includes(`a/${changelogName}`) ||
|
|
286
|
+
section.includes(`b/${changelogName}`) ||
|
|
287
|
+
section.includes(`a/${internalChangelogName}`) ||
|
|
288
|
+
section.includes(`b/${internalChangelogName}`);
|
|
289
|
+
return !isChangelog;
|
|
290
|
+
});
|
|
291
|
+
|
|
292
|
+
// Reconstruct the diff
|
|
293
|
+
if (filteredSections.length > 1) {
|
|
294
|
+
gitDiff = 'diff --git ' + filteredSections.slice(1).join('diff --git ');
|
|
295
|
+
} else if (filteredSections.length === 1 && filteredSections[0].trim()) {
|
|
296
|
+
gitDiff = filteredSections[0];
|
|
297
|
+
} else {
|
|
298
|
+
gitDiff = '';
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
if (debugMode) {
|
|
302
|
+
log.debug(SYMBOLS.INFO, `[MAIASS DEBUG] Filtered git diff length: ${gitDiff.length} characters`);
|
|
303
|
+
log.debug(SYMBOLS.INFO, `[MAIASS DEBUG] Excluded changelog files: ${changelogName}, ${internalChangelogName}`);
|
|
304
|
+
}
|
|
305
|
+
}
|
|
306
|
+
if (!gitDiff) {
|
|
307
|
+
if (debugMode) {
|
|
308
|
+
log.debug(SYMBOLS.WARNING, '[MAIASS DEBUG] No git diff found for staged changes - cannot generate AI suggestion');
|
|
309
|
+
}
|
|
310
|
+
return null;
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
// Truncate git diff if it exceeds character limit (matching bash script behavior)
|
|
314
|
+
if (gitDiff.length > maxCharacters) {
|
|
315
|
+
gitDiff = gitDiff.substring(0, maxCharacters) + '...[truncated]';
|
|
316
|
+
log.info(SYMBOLS.INFO, `Git diff truncated to ${maxCharacters} characters`);
|
|
317
|
+
}
|
|
318
|
+
|
|
319
|
+
if (debugMode) {
|
|
320
|
+
log.debug(SYMBOLS.INFO, '[MAIASS DEBUG] --- AI Commit Suggestion Context ---');
|
|
321
|
+
log.debug(SYMBOLS.INFO, `[MAIASS DEBUG] Model: ${aiModel}`);
|
|
322
|
+
log.debug(SYMBOLS.INFO, `[MAIASS DEBUG] Temperature: ${aiTemperature}`);
|
|
323
|
+
log.debug(SYMBOLS.INFO, `[MAIASS DEBUG] Commit Message Style: ${commitMessageStyle}`);
|
|
324
|
+
log.debug(SYMBOLS.INFO, `[MAIASS DEBUG] Max Characters: ${maxCharacters}`);
|
|
325
|
+
log.debug(SYMBOLS.INFO, `[MAIASS DEBUG] Git diff length: ${gitDiff.length} characters`);
|
|
326
|
+
log.debug(SYMBOLS.INFO, '[MAIASS DEBUG] --- GIT DIFF FED TO AI ---');
|
|
327
|
+
log.debug(SYMBOLS.INFO, gitDiff);
|
|
328
|
+
}
|
|
329
|
+
|
|
330
|
+
// Create AI prompt based on commit message style
|
|
331
|
+
let prompt;
|
|
332
|
+
if (commitMessageStyle === 'bullet') {
|
|
333
|
+
prompt = `Analyze the following git diff and create a commit message with bullet points. Format as:
|
|
334
|
+
Brief summary title
|
|
335
|
+
- feat: add user authentication
|
|
336
|
+
- fix(api): resolve syntax error
|
|
337
|
+
- docs: update README
|
|
338
|
+
|
|
339
|
+
Use past tense verbs. No blank line between title and bullets. Keep concise. Do not wrap the response in quotes.
|
|
340
|
+
|
|
341
|
+
Git diff:
|
|
342
|
+
${gitDiff}`;
|
|
343
|
+
} else {
|
|
344
|
+
prompt = `Analyze the following git diff and create a concise, descriptive commit message. Use conventional commit format when appropriate (feat:, fix:, docs:, etc.). Keep it under 72 characters for the first line.
|
|
345
|
+
|
|
346
|
+
Git diff:
|
|
347
|
+
${gitDiff}`;
|
|
348
|
+
}
|
|
349
|
+
|
|
350
|
+
// Make API request
|
|
351
|
+
const requestBody = {
|
|
352
|
+
model: aiModel,
|
|
353
|
+
messages: [
|
|
354
|
+
{
|
|
355
|
+
role: 'system',
|
|
356
|
+
content: 'You are a helpful assistant that writes concise, descriptive git commit messages based on code changes.'
|
|
357
|
+
},
|
|
358
|
+
{
|
|
359
|
+
role: 'user',
|
|
360
|
+
content: prompt
|
|
361
|
+
}
|
|
362
|
+
],
|
|
363
|
+
max_tokens: 150,
|
|
364
|
+
temperature: aiTemperature
|
|
365
|
+
};
|
|
366
|
+
|
|
367
|
+
if (debugMode) {
|
|
368
|
+
log.debug(SYMBOLS.INFO, '[MAIASS DEBUG] --- Making AI API Request ---');
|
|
369
|
+
log.debug(SYMBOLS.INFO, `[MAIASS DEBUG] Request URL: ${aiEndpoint}`);
|
|
370
|
+
log.debug(SYMBOLS.INFO, `[MAIASS DEBUG] Request body: ${JSON.stringify(requestBody, null, 2)}`);
|
|
371
|
+
}
|
|
372
|
+
|
|
373
|
+
const response = await fetch(aiEndpoint, {
|
|
374
|
+
method: 'POST',
|
|
375
|
+
headers: {
|
|
376
|
+
'Content-Type': 'application/json',
|
|
377
|
+
'Authorization': `Bearer ${maiassToken}`,
|
|
378
|
+
'X-Machine-Fingerprint': generateMachineFingerprint(),
|
|
379
|
+
'X-Client-Name': 'nodemaiass',
|
|
380
|
+
'X-Client-Version': '5.7.5',
|
|
381
|
+
'X-Subscription-ID': process.env.MAIASS_SUBSCRIPTION_ID || ''
|
|
382
|
+
},
|
|
383
|
+
body: JSON.stringify(requestBody)
|
|
384
|
+
});
|
|
385
|
+
|
|
386
|
+
if (debugMode) {
|
|
387
|
+
log.debug(SYMBOLS.INFO, `[MAIASS DEBUG] Response status: ${response.status} ${response.statusText}`);
|
|
388
|
+
log.debug(SYMBOLS.INFO, `[MAIASS DEBUG] Response headers: ${JSON.stringify(Object.fromEntries(response.headers), null, 2)}`);
|
|
389
|
+
}
|
|
390
|
+
|
|
391
|
+
if (!response.ok) {
|
|
392
|
+
const errorText = await response.text();
|
|
393
|
+
if (debugMode) {
|
|
394
|
+
log.debug(SYMBOLS.WARNING, `[MAIASS DEBUG] Response error body: ${errorText}`);
|
|
395
|
+
}
|
|
396
|
+
log.error(SYMBOLS.WARNING, `AI API request failed: ${response.status} ${response.statusText}`);
|
|
397
|
+
return null;
|
|
398
|
+
}
|
|
399
|
+
|
|
400
|
+
const data = await response.json();
|
|
401
|
+
|
|
402
|
+
if (debugMode) {
|
|
403
|
+
log.debug(SYMBOLS.INFO, `[MAIASS DEBUG] Response data: ${JSON.stringify(data, null, 2)}`);
|
|
404
|
+
}
|
|
405
|
+
|
|
406
|
+
if (data.choices && data.choices.length > 0) {
|
|
407
|
+
let suggestion = data.choices[0].message.content.trim();
|
|
408
|
+
|
|
409
|
+
// Enhanced debug logging: output the suggestion returned by AI
|
|
410
|
+
if (debugMode) {
|
|
411
|
+
log.debug(SYMBOLS.INFO, '[MAIASS DEBUG] --- AI SUGGESTION RETURNED ---');
|
|
412
|
+
log.debug(SYMBOLS.INFO, suggestion);
|
|
413
|
+
}
|
|
414
|
+
|
|
415
|
+
// Clean up any quotes that might wrap the entire response
|
|
416
|
+
if ((suggestion.startsWith("'") && suggestion.endsWith("'")) ||
|
|
417
|
+
(suggestion.startsWith('"') && suggestion.endsWith('"'))) {
|
|
418
|
+
suggestion = suggestion.slice(1, -1).trim();
|
|
419
|
+
}
|
|
420
|
+
|
|
421
|
+
// Extract credit information from billing data
|
|
422
|
+
let creditsUsed, creditsRemaining;
|
|
423
|
+
if (data.billing) {
|
|
424
|
+
creditsUsed = data.billing.credits_used;
|
|
425
|
+
creditsRemaining = data.billing.credits_remaining;
|
|
426
|
+
|
|
427
|
+
// Show warnings if available
|
|
428
|
+
if (data.billing.warning) {
|
|
429
|
+
log.warning(SYMBOLS.WARNING, data.billing.warning);
|
|
430
|
+
}
|
|
431
|
+
|
|
432
|
+
// Display proxy messages if available
|
|
433
|
+
if (data.messages && Array.isArray(data.messages)) {
|
|
434
|
+
data.messages.forEach(message => {
|
|
435
|
+
const icon = message.icon || '';
|
|
436
|
+
const text = message.text || '';
|
|
437
|
+
|
|
438
|
+
switch (message.type) {
|
|
439
|
+
case 'error':
|
|
440
|
+
log.error(icon, text);
|
|
441
|
+
break;
|
|
442
|
+
case 'warning':
|
|
443
|
+
log.warning(icon, text);
|
|
444
|
+
break;
|
|
445
|
+
case 'info':
|
|
446
|
+
log.info(icon, text);
|
|
447
|
+
break;
|
|
448
|
+
case 'notice':
|
|
449
|
+
log.blue(icon, text);
|
|
450
|
+
break;
|
|
451
|
+
case 'success':
|
|
452
|
+
log.success(icon, text);
|
|
453
|
+
break;
|
|
454
|
+
default:
|
|
455
|
+
log.plain(`${icon} ${text}`);
|
|
456
|
+
}
|
|
457
|
+
});
|
|
458
|
+
}
|
|
459
|
+
} else if (data.usage) {
|
|
460
|
+
// Fallback to legacy token display
|
|
461
|
+
const totalTokens = data.usage.total_tokens || 0;
|
|
462
|
+
const promptTokens = data.usage.prompt_tokens || 0;
|
|
463
|
+
const completionTokens = data.usage.completion_tokens || 0;
|
|
464
|
+
log.info(SYMBOLS.INFO, `Total Tokens: ${totalTokens} (${promptTokens} + ${completionTokens})`);
|
|
465
|
+
}
|
|
466
|
+
|
|
467
|
+
return {
|
|
468
|
+
suggestion,
|
|
469
|
+
creditsUsed,
|
|
470
|
+
creditsRemaining
|
|
471
|
+
};
|
|
472
|
+
}
|
|
473
|
+
|
|
474
|
+
if (debugMode) {
|
|
475
|
+
log.debug(SYMBOLS.WARNING, '[MAIASS DEBUG] No valid AI response received - no choices in response data');
|
|
476
|
+
}
|
|
477
|
+
return null;
|
|
478
|
+
} catch (error) {
|
|
479
|
+
if (debugMode) {
|
|
480
|
+
log.debug(SYMBOLS.WARNING, `[MAIASS DEBUG] AI suggestion error details: ${error.stack || error.message}`);
|
|
481
|
+
}
|
|
482
|
+
log.error(SYMBOLS.WARNING, `AI suggestion failed: ${error.message}`);
|
|
483
|
+
return null;
|
|
484
|
+
}
|
|
485
|
+
}
|
|
486
|
+
|
|
487
|
+
/**
|
|
488
|
+
* Get user input using readline
|
|
489
|
+
* @param {string} prompt - Prompt to display
|
|
490
|
+
* @returns {Promise<string>} User input
|
|
491
|
+
*/
|
|
492
|
+
function getUserInput(prompt) {
|
|
493
|
+
const rl = readline.createInterface({
|
|
494
|
+
input: process.stdin,
|
|
495
|
+
output: process.stdout
|
|
496
|
+
});
|
|
497
|
+
|
|
498
|
+
return new Promise((resolve) => {
|
|
499
|
+
rl.on('SIGINT', () => {
|
|
500
|
+
rl.close();
|
|
501
|
+
log.warning(SYMBOLS.WARNING, 'Operation cancelled by user (Ctrl+C)');
|
|
502
|
+
process.exit(0);
|
|
503
|
+
});
|
|
504
|
+
|
|
505
|
+
rl.question(prompt, (answer) => {
|
|
506
|
+
rl.close();
|
|
507
|
+
resolve(answer.trim());
|
|
508
|
+
});
|
|
509
|
+
});
|
|
510
|
+
}
|
|
511
|
+
|
|
512
|
+
/**
|
|
513
|
+
* Get multi-line commit message from user
|
|
514
|
+
* @param {string} jiraTicket - JIRA ticket number to prepend
|
|
515
|
+
* @returns {Promise<string>} Multi-line commit message
|
|
516
|
+
*/
|
|
517
|
+
async function getMultiLineCommitMessage(jiraTicket) {
|
|
518
|
+
const prompt = 'Enter multiple lines (press Enter three times to finish):';
|
|
519
|
+
return await getMultiLineInput(prompt);
|
|
520
|
+
}
|
|
521
|
+
|
|
522
|
+
/**
|
|
523
|
+
* Get commit message with AI integration and JIRA ticket handling
|
|
524
|
+
* @param {Object} gitInfo - Git information object
|
|
525
|
+
* @param {Object} options - Commit options
|
|
526
|
+
* @returns {Promise<string>} Final commit message
|
|
527
|
+
*/
|
|
528
|
+
async function getCommitMessage(gitInfo, options = {}) {
|
|
529
|
+
const { silent = false } = options;
|
|
530
|
+
|
|
531
|
+
// Load environment config to ensure tokens are loaded from secure storage
|
|
532
|
+
loadEnvironmentConfig();
|
|
533
|
+
|
|
534
|
+
const aiMode = process.env.MAIASS_AI_MODE || 'ask';
|
|
535
|
+
const maiassToken = process.env.MAIASS_AI_TOKEN;
|
|
536
|
+
const jiraTicket = gitInfo.jiraTicket;
|
|
537
|
+
|
|
538
|
+
// Display JIRA ticket if found
|
|
539
|
+
if (jiraTicket) {
|
|
540
|
+
log.info(SYMBOLS.INFO, `Jira Ticket Number: ${jiraTicket}`);
|
|
541
|
+
}
|
|
542
|
+
|
|
543
|
+
let useAI = false;
|
|
544
|
+
let aiSuggestion = null;
|
|
545
|
+
|
|
546
|
+
// Handle AI commit message modes
|
|
547
|
+
switch (aiMode) {
|
|
548
|
+
case 'ask':
|
|
549
|
+
if (maiassToken) {
|
|
550
|
+
let reply;
|
|
551
|
+
if (silent) {
|
|
552
|
+
log.info('', 'Would you like to use AI to suggest a commit message? [y/N] y');
|
|
553
|
+
reply = 'y';
|
|
554
|
+
} else {
|
|
555
|
+
reply = await getSingleCharInput('Would you like to use AI to suggest a commit message? [y/N] ');
|
|
556
|
+
}
|
|
557
|
+
useAI = reply === 'y';
|
|
558
|
+
} else {
|
|
559
|
+
// No token found - try to create anonymous subscription (matches bashmaiass behavior)
|
|
560
|
+
const debugMode = process.env.MAIASS_DEBUG === 'true';
|
|
561
|
+
if (debugMode) {
|
|
562
|
+
log.debug(SYMBOLS.INFO, '[MAIASS DEBUG] No AI token found, attempting to create anonymous subscription...');
|
|
563
|
+
}
|
|
564
|
+
const anonToken = await createAnonymousSubscriptionIfNeeded();
|
|
565
|
+
if (anonToken) {
|
|
566
|
+
// Token created successfully, now ask if they want to use AI
|
|
567
|
+
let reply;
|
|
568
|
+
if (silent) {
|
|
569
|
+
log.info('', 'Would you like to use AI to suggest a commit message? [y/N] y');
|
|
570
|
+
reply = 'y';
|
|
571
|
+
} else {
|
|
572
|
+
reply = await getSingleCharInput('Would you like to use AI to suggest a commit message? [y/N] ');
|
|
573
|
+
}
|
|
574
|
+
useAI = reply === 'y';
|
|
575
|
+
} else {
|
|
576
|
+
log.warning(SYMBOLS.WARNING, 'Failed to create anonymous subscription. AI features will be disabled.');
|
|
577
|
+
}
|
|
578
|
+
}
|
|
579
|
+
break;
|
|
580
|
+
case 'autosuggest':
|
|
581
|
+
if (maiassToken) {
|
|
582
|
+
useAI = true;
|
|
583
|
+
}
|
|
584
|
+
break;
|
|
585
|
+
case 'off':
|
|
586
|
+
default:
|
|
587
|
+
useAI = false;
|
|
588
|
+
break;
|
|
589
|
+
}
|
|
590
|
+
|
|
591
|
+
// Try to get AI suggestion if requested
|
|
592
|
+
if (useAI) {
|
|
593
|
+
const aiResult = await getAICommitSuggestion(gitInfo);
|
|
594
|
+
|
|
595
|
+
if (aiResult && aiResult.suggestion) {
|
|
596
|
+
aiSuggestion = aiResult.suggestion;
|
|
597
|
+
|
|
598
|
+
// Display credits used and remaining (matching bashmaiass)
|
|
599
|
+
if (aiResult.creditsUsed !== undefined) {
|
|
600
|
+
log.branded(`Credits used: ${aiResult.creditsUsed}`, colors.White);
|
|
601
|
+
}
|
|
602
|
+
if (aiResult.creditsRemaining !== undefined) {
|
|
603
|
+
const creditColor = getCreditColor(aiResult.creditsRemaining);
|
|
604
|
+
console.log(`${colors.BSoftPink('|))')} ${creditColor(`${aiResult.creditsRemaining} Credits left`)}`);
|
|
605
|
+
}
|
|
606
|
+
|
|
607
|
+
// Display AI suggestion with gradient lines (matching bashmaiass)
|
|
608
|
+
console.log(`${colors.BSoftPink('|))')} ${colors.BMagenta('Commit message suggestion from MAIASS:')}`);
|
|
609
|
+
printGradientLine(60);
|
|
610
|
+
console.log(chalk.bold.inverse(aiSuggestion));
|
|
611
|
+
printGradientLine(60);
|
|
612
|
+
|
|
613
|
+
let reply;
|
|
614
|
+
if (silent) {
|
|
615
|
+
log.info('', 'Use this AI suggestion? [Y/n/e=edit] Y');
|
|
616
|
+
reply = 'Y';
|
|
617
|
+
} else {
|
|
618
|
+
reply = await getSingleCharInput('Use this AI suggestion? [Y/n/e=edit] ');
|
|
619
|
+
}
|
|
620
|
+
|
|
621
|
+
switch (reply) {
|
|
622
|
+
case 'n':
|
|
623
|
+
log.info(SYMBOLS.INFO, 'AI suggestion declined, entering manual mode');
|
|
624
|
+
useAI = false;
|
|
625
|
+
break;
|
|
626
|
+
case 'e':
|
|
627
|
+
log.info(SYMBOLS.INFO, 'Edit mode: You can modify the AI suggestion');
|
|
628
|
+
log.info('', 'Current AI suggestion:');
|
|
629
|
+
log.aisuggestion('', aiSuggestion);
|
|
630
|
+
console.log();
|
|
631
|
+
log.info('', 'Enter your modified commit message (press Enter three times when finished, or just Enter to keep AI suggestion):');
|
|
632
|
+
|
|
633
|
+
const editedMessage = await getMultiLineCommitMessage(jiraTicket);
|
|
634
|
+
const finalEditedMessage = (editedMessage || aiSuggestion).trim();
|
|
635
|
+
|
|
636
|
+
// Prepend JIRA ticket if found and not already present
|
|
637
|
+
if (jiraTicket && finalEditedMessage && !finalEditedMessage.startsWith(jiraTicket)) {
|
|
638
|
+
return `${jiraTicket} ${finalEditedMessage}`;
|
|
639
|
+
}
|
|
640
|
+
return finalEditedMessage;
|
|
641
|
+
case 'y':
|
|
642
|
+
case '':
|
|
643
|
+
// Accept AI suggestion - prepend JIRA ticket if needed and trim whitespace
|
|
644
|
+
const trimmedSuggestion = aiSuggestion.trim();
|
|
645
|
+
if (jiraTicket && trimmedSuggestion && !trimmedSuggestion.startsWith(jiraTicket)) {
|
|
646
|
+
return `${jiraTicket} ${trimmedSuggestion}`;
|
|
647
|
+
}
|
|
648
|
+
return trimmedSuggestion;
|
|
649
|
+
default:
|
|
650
|
+
log.info(SYMBOLS.INFO, `Invalid input '${reply}'. AI suggestion declined, entering manual mode`);
|
|
651
|
+
useAI = false;
|
|
652
|
+
break;
|
|
653
|
+
}
|
|
654
|
+
} else {
|
|
655
|
+
log.warning(SYMBOLS.WARNING, 'AI suggestion failed, falling back to manual entry');
|
|
656
|
+
useAI = false;
|
|
657
|
+
}
|
|
658
|
+
}
|
|
659
|
+
|
|
660
|
+
// Manual commit message entry
|
|
661
|
+
if (jiraTicket) {
|
|
662
|
+
log.info(SYMBOLS.INFO, `Enter a commit message (Jira ticket ${jiraTicket} will be prepended)`);
|
|
663
|
+
} else {
|
|
664
|
+
log.info(SYMBOLS.INFO, 'Enter a commit message (starting with Jira Ticket# when relevant)');
|
|
665
|
+
log.info(SYMBOLS.INFO, 'Please enter a ticket number or \'fix:\' or \'feature:\' or \'devops:\' to start the commit message');
|
|
666
|
+
}
|
|
667
|
+
|
|
668
|
+
const manualMessage = await getMultiLineCommitMessage(jiraTicket);
|
|
669
|
+
|
|
670
|
+
// Prepend JIRA ticket if found and not already present
|
|
671
|
+
if (jiraTicket && manualMessage && !manualMessage.startsWith(jiraTicket)) {
|
|
672
|
+
return `${jiraTicket} ${manualMessage}`;
|
|
673
|
+
}
|
|
674
|
+
|
|
675
|
+
return manualMessage;
|
|
676
|
+
}
|
|
677
|
+
|
|
678
|
+
/**
|
|
679
|
+
* Handle staged commit process
|
|
680
|
+
* @param {Object} gitInfo - Git information object
|
|
681
|
+
* @param {Object} options - Commit options
|
|
682
|
+
* @returns {Promise<boolean>} True if commit was successful
|
|
683
|
+
*/
|
|
684
|
+
async function handleStagedCommit(gitInfo, options = {}) {
|
|
685
|
+
const { silent = false } = options;
|
|
686
|
+
// Check if there are actually staged changes
|
|
687
|
+
if (!gitInfo.status || gitInfo.status.stagedCount === 0) {
|
|
688
|
+
log.warning(SYMBOLS.INFO, 'Nothing to commit, working tree clean');
|
|
689
|
+
return true;
|
|
690
|
+
}
|
|
691
|
+
|
|
692
|
+
// Show staged changes
|
|
693
|
+
const stagedOutput = executeGitCommand('git diff --cached --name-status', true);
|
|
694
|
+
if (!stagedOutput) {
|
|
695
|
+
log.warning(SYMBOLS.INFO, 'No staged changes to show');
|
|
696
|
+
return true;
|
|
697
|
+
}
|
|
698
|
+
|
|
699
|
+
log.critical(SYMBOLS.INFO, 'Staged changes detected:');
|
|
700
|
+
|
|
701
|
+
// Display the staged changes
|
|
702
|
+
console.log(stagedOutput);
|
|
703
|
+
|
|
704
|
+
// Get commit message
|
|
705
|
+
const commitMessage = await getCommitMessage(gitInfo, { silent });
|
|
706
|
+
if (!commitMessage) {
|
|
707
|
+
log.error(SYMBOLS.CROSS, 'No commit message provided');
|
|
708
|
+
return false;
|
|
709
|
+
}
|
|
710
|
+
|
|
711
|
+
// Commit changes
|
|
712
|
+
const verbosity = process.env.MAIASS_VERBOSITY || 'brief';
|
|
713
|
+
const quietMode = verbosity !== 'debug';
|
|
714
|
+
|
|
715
|
+
try {
|
|
716
|
+
// Cross-platform commit message handling
|
|
717
|
+
let commitCommand, result;
|
|
718
|
+
// Ensure JIRA ticket is always prepended if present and not already
|
|
719
|
+
let finalCommitMessage = commitMessage;
|
|
720
|
+
const jiraTicket = gitInfo && gitInfo.jiraTicket;
|
|
721
|
+
if (jiraTicket && finalCommitMessage && !finalCommitMessage.startsWith(jiraTicket)) {
|
|
722
|
+
finalCommitMessage = `${jiraTicket} ${finalCommitMessage}`;
|
|
723
|
+
}
|
|
724
|
+
if (process.platform === 'win32') {
|
|
725
|
+
// Write commit message to a temporary file to avoid quoting/newline issues
|
|
726
|
+
const fs = (await import('fs')).default;
|
|
727
|
+
const os = (await import('os')).default;
|
|
728
|
+
const path = (await import('path')).default;
|
|
729
|
+
const tmpFile = path.join(os.tmpdir(), `maiass-commit-msg-${Date.now()}.txt`);
|
|
730
|
+
fs.writeFileSync(tmpFile, finalCommitMessage, { encoding: 'utf8' });
|
|
731
|
+
commitCommand = `git commit -F "${tmpFile}"`;
|
|
732
|
+
result = executeGitCommand(commitCommand, quietMode);
|
|
733
|
+
fs.unlinkSync(tmpFile);
|
|
734
|
+
} else {
|
|
735
|
+
// Use echo/pipe for non-Windows
|
|
736
|
+
commitCommand = `echo ${JSON.stringify(commitMessage)} | git commit -F -`;
|
|
737
|
+
result = executeGitCommand(commitCommand, quietMode);
|
|
738
|
+
}
|
|
739
|
+
|
|
740
|
+
if (result === null) {
|
|
741
|
+
log.error(SYMBOLS.CROSS, 'Commit failed');
|
|
742
|
+
return false;
|
|
743
|
+
}
|
|
744
|
+
|
|
745
|
+
log.success(SYMBOLS.CHECKMARK, 'Changes committed successfully');
|
|
746
|
+
|
|
747
|
+
// Log the commit to devlog.sh (equivalent to logthis in maiass.sh)
|
|
748
|
+
logCommit(commitMessage, gitInfo);
|
|
749
|
+
|
|
750
|
+
// Ask about pushing to remote
|
|
751
|
+
if (remoteExists('origin')) {
|
|
752
|
+
let reply;
|
|
753
|
+
if (silent) {
|
|
754
|
+
// In silent mode, automatically push
|
|
755
|
+
reply = 'y';
|
|
756
|
+
console.log('🔄 |)) Automatically pushing to remote (silent mode)');
|
|
757
|
+
} else {
|
|
758
|
+
reply = await getSingleCharInput('Do you want to push this commit to remote? [y/N] ');
|
|
759
|
+
}
|
|
760
|
+
|
|
761
|
+
if (reply === 'y') {
|
|
762
|
+
|
|
763
|
+
const pushResult = executeGitCommand(`git push --set-upstream origin ${gitInfo.branch}`, false);
|
|
764
|
+
if (pushResult !== null) {
|
|
765
|
+
log.success(SYMBOLS.CHECKMARK, 'Commit pushed.');
|
|
766
|
+
} else {
|
|
767
|
+
log.error(SYMBOLS.CROSS, 'Push failed');
|
|
768
|
+
return false;
|
|
769
|
+
}
|
|
770
|
+
}
|
|
771
|
+
} else {
|
|
772
|
+
log.warning(SYMBOLS.WARNING, 'No remote found.');
|
|
773
|
+
}
|
|
774
|
+
|
|
775
|
+
return true;
|
|
776
|
+
} catch (error) {
|
|
777
|
+
log.error(SYMBOLS.CROSS, `Commit failed: ${error.message}`);
|
|
778
|
+
return false;
|
|
779
|
+
}
|
|
780
|
+
}
|
|
781
|
+
|
|
782
|
+
/**
|
|
783
|
+
* Main commit function - checks for changes and handles commit workflow
|
|
784
|
+
* @param {Object} options - Commit options
|
|
785
|
+
* @returns {Promise<boolean>} True if process completed successfully
|
|
786
|
+
*/
|
|
787
|
+
export async function commitThis(options = {}) {
|
|
788
|
+
const { autoStage = false, commitsOnly = false, silent = false } = options;
|
|
789
|
+
|
|
790
|
+
// Get git information
|
|
791
|
+
const gitInfo = getGitInfo();
|
|
792
|
+
if (!gitInfo) {
|
|
793
|
+
log.error(SYMBOLS.CROSS, 'Not in a git repository');
|
|
794
|
+
return false;
|
|
795
|
+
}
|
|
796
|
+
|
|
797
|
+
log.info(SYMBOLS.GEAR, 'Checking for Changes\n');
|
|
798
|
+
|
|
799
|
+
const status = gitInfo.status;
|
|
800
|
+
|
|
801
|
+
// Check if there are any changes
|
|
802
|
+
if (status.isClean) {
|
|
803
|
+
log.success(SYMBOLS.CHECKMARK, 'No changes found. Working directory is clean.');
|
|
804
|
+
if (commitsOnly) {
|
|
805
|
+
log.info('', 'Thank you for using MAIASS.');
|
|
806
|
+
}
|
|
807
|
+
return true;
|
|
808
|
+
}
|
|
809
|
+
|
|
810
|
+
// Display status message matching bashmaiass style
|
|
811
|
+
if (status.unstagedCount > 0 || status.untrackedCount > 0) {
|
|
812
|
+
log.warning(SYMBOLS.WARNING, 'There are unstaged changes in your working directory');
|
|
813
|
+
}
|
|
814
|
+
|
|
815
|
+
// Handle unstaged/untracked changes first
|
|
816
|
+
if (status.unstagedCount > 0 || status.untrackedCount > 0) {
|
|
817
|
+
if (!autoStage) {
|
|
818
|
+
let reply;
|
|
819
|
+
if (silent) {
|
|
820
|
+
// In silent mode, automatically stage
|
|
821
|
+
reply = 'y';
|
|
822
|
+
console.log('🔄 |)) Automatically staging changes (silent mode)');
|
|
823
|
+
} else {
|
|
824
|
+
reply = await getSingleCharInput('Do you want to stage and commit them? [y/N] ');
|
|
825
|
+
}
|
|
826
|
+
if (reply === 'y') {
|
|
827
|
+
// Stage all changes
|
|
828
|
+
const stageResult = executeGitCommand('git add -A', false);
|
|
829
|
+
if (stageResult === null) {
|
|
830
|
+
log.error(SYMBOLS.CROSS, 'Failed to stage changes');
|
|
831
|
+
return false;
|
|
832
|
+
}
|
|
833
|
+
|
|
834
|
+
// Refresh git info to get updated status
|
|
835
|
+
const updatedGitInfo = getGitInfo();
|
|
836
|
+
return await handleStagedCommit(updatedGitInfo, { silent });
|
|
837
|
+
} else {
|
|
838
|
+
// Check if there are staged changes to commit
|
|
839
|
+
if (status.stagedCount > 0) {
|
|
840
|
+
log.info(SYMBOLS.INFO, 'Proceeding with staged changes only');
|
|
841
|
+
return await handleStagedCommit(gitInfo, { silent });
|
|
842
|
+
}
|
|
843
|
+
|
|
844
|
+
// Handle the case where user declined to stage and there are no staged changes
|
|
845
|
+
if (commitsOnly) {
|
|
846
|
+
// In commits-only mode, it's OK to have unstaged changes
|
|
847
|
+
log.info('', 'No changes staged for commit.');
|
|
848
|
+
log.success('', 'Thank you for using MAIASS.');
|
|
849
|
+
return true;
|
|
850
|
+
} else {
|
|
851
|
+
// In pipeline mode, we cannot proceed with unstaged changes
|
|
852
|
+
log.warning('', 'No changes staged for commit.');
|
|
853
|
+
log.error(SYMBOLS.CROSS, 'Cannot proceed on release/changelog pipeline with uncommitted changes');
|
|
854
|
+
log.success('', 'Thank you for using MAIASS.');
|
|
855
|
+
return false;
|
|
856
|
+
}
|
|
857
|
+
}
|
|
858
|
+
} else {
|
|
859
|
+
// Auto-stage all changes
|
|
860
|
+
const stageResult = executeGitCommand('git add -A', false);
|
|
861
|
+
if (stageResult === null) {
|
|
862
|
+
console.log(colors.Red(`${SYMBOLS.CROSS} Failed to stage changes`));
|
|
863
|
+
return false;
|
|
864
|
+
}
|
|
865
|
+
|
|
866
|
+
// Refresh git info and commit
|
|
867
|
+
const updatedGitInfo = getGitInfo();
|
|
868
|
+
return await handleStagedCommit(updatedGitInfo, { silent });
|
|
869
|
+
}
|
|
870
|
+
} else if (status.stagedCount > 0) {
|
|
871
|
+
// Only staged changes present, proceed directly to commit
|
|
872
|
+
return await handleStagedCommit(gitInfo, { silent });
|
|
873
|
+
}
|
|
874
|
+
|
|
875
|
+
return true;
|
|
876
|
+
}
|
|
877
|
+
|
|
878
|
+
// Export individual functions for testing and reuse
|
|
879
|
+
export {
|
|
880
|
+
getCommitMessage,
|
|
881
|
+
handleStagedCommit,
|
|
882
|
+
getAICommitSuggestion,
|
|
883
|
+
executeGitCommand,
|
|
884
|
+
remoteExists
|
|
885
|
+
};
|