@nclamvn/vibecode-cli 1.5.0 → 1.7.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/.vibecode/learning/fixes.json +1 -0
- package/.vibecode/learning/preferences.json +1 -0
- package/bin/vibecode.js +86 -3
- package/docs-site/README.md +41 -0
- package/docs-site/blog/2019-05-28-first-blog-post.md +12 -0
- package/docs-site/blog/2019-05-29-long-blog-post.md +44 -0
- package/docs-site/blog/2021-08-01-mdx-blog-post.mdx +24 -0
- package/docs-site/blog/2021-08-26-welcome/docusaurus-plushie-banner.jpeg +0 -0
- package/docs-site/blog/2021-08-26-welcome/index.md +29 -0
- package/docs-site/blog/authors.yml +25 -0
- package/docs-site/blog/tags.yml +19 -0
- package/docs-site/docs/commands/agent.md +162 -0
- package/docs-site/docs/commands/assist.md +71 -0
- package/docs-site/docs/commands/build.md +53 -0
- package/docs-site/docs/commands/config.md +30 -0
- package/docs-site/docs/commands/debug.md +173 -0
- package/docs-site/docs/commands/doctor.md +34 -0
- package/docs-site/docs/commands/go.md +128 -0
- package/docs-site/docs/commands/index.md +79 -0
- package/docs-site/docs/commands/init.md +42 -0
- package/docs-site/docs/commands/learn.md +82 -0
- package/docs-site/docs/commands/lock.md +33 -0
- package/docs-site/docs/commands/plan.md +29 -0
- package/docs-site/docs/commands/review.md +31 -0
- package/docs-site/docs/commands/snapshot.md +34 -0
- package/docs-site/docs/commands/start.md +32 -0
- package/docs-site/docs/commands/status.md +37 -0
- package/docs-site/docs/commands/undo.md +83 -0
- package/docs-site/docs/configuration.md +72 -0
- package/docs-site/docs/faq.md +83 -0
- package/docs-site/docs/getting-started.md +119 -0
- package/docs-site/docs/guides/agent-mode.md +94 -0
- package/docs-site/docs/guides/debug-mode.md +83 -0
- package/docs-site/docs/guides/magic-mode.md +107 -0
- package/docs-site/docs/installation.md +98 -0
- package/docs-site/docs/intro.md +67 -0
- package/docs-site/docusaurus.config.ts +141 -0
- package/docs-site/package-lock.json +18039 -0
- package/docs-site/package.json +48 -0
- package/docs-site/sidebars.ts +70 -0
- package/docs-site/src/components/HomepageFeatures/index.tsx +72 -0
- package/docs-site/src/components/HomepageFeatures/styles.module.css +16 -0
- package/docs-site/src/css/custom.css +30 -0
- package/docs-site/src/pages/index.module.css +23 -0
- package/docs-site/src/pages/index.tsx +44 -0
- package/docs-site/src/pages/markdown-page.md +7 -0
- package/docs-site/src/theme/Footer/index.tsx +127 -0
- package/docs-site/src/theme/Footer/styles.module.css +285 -0
- package/docs-site/static/.nojekyll +0 -0
- package/docs-site/static/img/docusaurus-social-card.jpg +0 -0
- package/docs-site/static/img/docusaurus.png +0 -0
- package/docs-site/static/img/favicon.ico +0 -0
- package/docs-site/static/img/logo.svg +1 -0
- package/docs-site/static/img/undraw_docusaurus_mountain.svg +171 -0
- package/docs-site/static/img/undraw_docusaurus_react.svg +170 -0
- package/docs-site/static/img/undraw_docusaurus_tree.svg +40 -0
- package/docs-site/tsconfig.json +8 -0
- package/package.json +5 -2
- package/src/agent/orchestrator.js +104 -35
- package/src/commands/build.js +13 -3
- package/src/commands/debug.js +109 -1
- package/src/commands/git.js +923 -0
- package/src/commands/go.js +9 -2
- package/src/commands/learn.js +294 -0
- package/src/commands/shell.js +486 -0
- package/src/commands/undo.js +281 -0
- package/src/commands/watch.js +556 -0
- package/src/commands/wizard.js +322 -0
- package/src/core/backup.js +325 -0
- package/src/core/learning.js +295 -0
- package/src/debug/image-analyzer.js +304 -0
- package/src/debug/index.js +30 -1
- package/src/index.js +50 -0
- package/src/ui/__tests__/error-translator.test.js +390 -0
- package/src/ui/dashboard.js +364 -0
- package/src/ui/error-translator.js +775 -0
- package/src/utils/image.js +222 -0
|
@@ -0,0 +1,923 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Git Integration for Vibecode CLI
|
|
3
|
+
* Native git commands with enhanced UI and AI-powered commit messages
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import { exec } from 'child_process';
|
|
7
|
+
import { promisify } from 'util';
|
|
8
|
+
import chalk from 'chalk';
|
|
9
|
+
import inquirer from 'inquirer';
|
|
10
|
+
|
|
11
|
+
const execAsync = promisify(exec);
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* Main git command handler
|
|
15
|
+
*/
|
|
16
|
+
export async function gitCommand(subcommand, args = [], options = {}) {
|
|
17
|
+
// Check if in git repo
|
|
18
|
+
const isGitRepo = await checkGitRepo();
|
|
19
|
+
if (!isGitRepo) {
|
|
20
|
+
console.log(chalk.red('\n Khong phai git repository.'));
|
|
21
|
+
console.log(chalk.gray(' Chay: git init\n'));
|
|
22
|
+
return;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
// No subcommand = interactive menu
|
|
26
|
+
if (!subcommand) {
|
|
27
|
+
return interactiveGit(options);
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
switch (subcommand) {
|
|
31
|
+
case 'status':
|
|
32
|
+
case 's':
|
|
33
|
+
return gitStatus(options);
|
|
34
|
+
case 'commit':
|
|
35
|
+
case 'c':
|
|
36
|
+
return gitCommit(args, options);
|
|
37
|
+
case 'diff':
|
|
38
|
+
case 'd':
|
|
39
|
+
return gitDiff(args, options);
|
|
40
|
+
case 'branch':
|
|
41
|
+
case 'b':
|
|
42
|
+
return gitBranch(args, options);
|
|
43
|
+
case 'push':
|
|
44
|
+
return gitPush(options);
|
|
45
|
+
case 'pull':
|
|
46
|
+
return gitPull(options);
|
|
47
|
+
case 'log':
|
|
48
|
+
case 'l':
|
|
49
|
+
return gitLog(options);
|
|
50
|
+
case 'stash':
|
|
51
|
+
return gitStash(args, options);
|
|
52
|
+
case 'unstash':
|
|
53
|
+
return gitUnstash(options);
|
|
54
|
+
case 'add':
|
|
55
|
+
case 'a':
|
|
56
|
+
return gitAdd(args, options);
|
|
57
|
+
case 'reset':
|
|
58
|
+
return gitReset(args, options);
|
|
59
|
+
case 'checkout':
|
|
60
|
+
case 'co':
|
|
61
|
+
return gitCheckout(args, options);
|
|
62
|
+
default:
|
|
63
|
+
// Pass through to git
|
|
64
|
+
return gitPassthrough(subcommand, args);
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
/**
|
|
69
|
+
* Check if current directory is a git repository
|
|
70
|
+
*/
|
|
71
|
+
async function checkGitRepo() {
|
|
72
|
+
try {
|
|
73
|
+
await execAsync('git rev-parse --git-dir');
|
|
74
|
+
return true;
|
|
75
|
+
} catch {
|
|
76
|
+
return false;
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
/**
|
|
81
|
+
* Get current git status summary
|
|
82
|
+
*/
|
|
83
|
+
async function getGitStatus() {
|
|
84
|
+
try {
|
|
85
|
+
const { stdout: branch } = await execAsync('git branch --show-current');
|
|
86
|
+
const { stdout: status } = await execAsync('git status --porcelain');
|
|
87
|
+
|
|
88
|
+
const lines = status.trim().split('\n').filter(Boolean);
|
|
89
|
+
const staged = lines.filter(l => l[0] !== ' ' && l[0] !== '?').length;
|
|
90
|
+
const changed = lines.length;
|
|
91
|
+
const untracked = lines.filter(l => l.startsWith('??')).length;
|
|
92
|
+
|
|
93
|
+
// Get remote status
|
|
94
|
+
let ahead = 0;
|
|
95
|
+
let behind = 0;
|
|
96
|
+
try {
|
|
97
|
+
const { stdout: remote } = await execAsync('git rev-list --left-right --count HEAD...@{upstream} 2>/dev/null');
|
|
98
|
+
const parts = remote.trim().split('\t');
|
|
99
|
+
ahead = parseInt(parts[0]) || 0;
|
|
100
|
+
behind = parseInt(parts[1]) || 0;
|
|
101
|
+
} catch {
|
|
102
|
+
// No upstream
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
return {
|
|
106
|
+
branch: branch.trim() || 'HEAD detached',
|
|
107
|
+
changed,
|
|
108
|
+
staged,
|
|
109
|
+
untracked,
|
|
110
|
+
ahead,
|
|
111
|
+
behind,
|
|
112
|
+
files: lines
|
|
113
|
+
};
|
|
114
|
+
} catch {
|
|
115
|
+
return { branch: 'unknown', changed: 0, staged: 0, untracked: 0, ahead: 0, behind: 0, files: [] };
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
/**
|
|
120
|
+
* Interactive git menu
|
|
121
|
+
*/
|
|
122
|
+
async function interactiveGit(options) {
|
|
123
|
+
const status = await getGitStatus();
|
|
124
|
+
|
|
125
|
+
// Build status line
|
|
126
|
+
let syncStatus = '';
|
|
127
|
+
if (status.ahead > 0) syncStatus += chalk.green(` ${status.ahead}`);
|
|
128
|
+
if (status.behind > 0) syncStatus += chalk.red(` ${status.behind}`);
|
|
129
|
+
|
|
130
|
+
console.log(chalk.cyan(`
|
|
131
|
+
+----------------------------------------------------------------------+
|
|
132
|
+
| VIBECODE GIT |
|
|
133
|
+
| |
|
|
134
|
+
| Branch: ${chalk.green(status.branch.padEnd(54))}|
|
|
135
|
+
| Changes: ${chalk.yellow(String(status.changed).padEnd(10))} Staged: ${chalk.green(String(status.staged).padEnd(10))} Untracked: ${chalk.gray(String(status.untracked).padEnd(7))}|${syncStatus ? `
|
|
136
|
+
| Sync: ${syncStatus.padEnd(58)}|` : ''}
|
|
137
|
+
+----------------------------------------------------------------------+
|
|
138
|
+
`));
|
|
139
|
+
|
|
140
|
+
const choices = [
|
|
141
|
+
{ name: ` Status ${status.changed > 0 ? chalk.yellow(`(${status.changed} changes)`) : chalk.gray('(clean)')}`, value: 'status' },
|
|
142
|
+
{ name: ' Commit changes', value: 'commit' },
|
|
143
|
+
{ name: ' View diff', value: 'diff' },
|
|
144
|
+
new inquirer.Separator(),
|
|
145
|
+
{ name: ' Switch/create branch', value: 'branch' },
|
|
146
|
+
{ name: ' Push to remote', value: 'push' },
|
|
147
|
+
{ name: ' Pull from remote', value: 'pull' },
|
|
148
|
+
new inquirer.Separator(),
|
|
149
|
+
{ name: ' View log', value: 'log' },
|
|
150
|
+
{ name: ' Stash changes', value: 'stash' },
|
|
151
|
+
{ name: ' Unstash changes', value: 'unstash' },
|
|
152
|
+
new inquirer.Separator(),
|
|
153
|
+
{ name: ' Exit', value: 'exit' }
|
|
154
|
+
];
|
|
155
|
+
|
|
156
|
+
const { action } = await inquirer.prompt([{
|
|
157
|
+
type: 'list',
|
|
158
|
+
name: 'action',
|
|
159
|
+
message: 'Select action:',
|
|
160
|
+
choices,
|
|
161
|
+
pageSize: 15
|
|
162
|
+
}]);
|
|
163
|
+
|
|
164
|
+
if (action === 'exit') {
|
|
165
|
+
console.log(chalk.gray('\n Bye!\n'));
|
|
166
|
+
return;
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
switch (action) {
|
|
170
|
+
case 'status': await gitStatus(options); break;
|
|
171
|
+
case 'commit': await gitCommit([], options); break;
|
|
172
|
+
case 'diff': await gitDiff([], options); break;
|
|
173
|
+
case 'branch': await gitBranch([], options); break;
|
|
174
|
+
case 'push': await gitPush(options); break;
|
|
175
|
+
case 'pull': await gitPull(options); break;
|
|
176
|
+
case 'log': await gitLog(options); break;
|
|
177
|
+
case 'stash': await gitStash([], options); break;
|
|
178
|
+
case 'unstash': await gitUnstash(options); break;
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
// Return to menu
|
|
182
|
+
console.log('');
|
|
183
|
+
const { continueMenu } = await inquirer.prompt([{
|
|
184
|
+
type: 'confirm',
|
|
185
|
+
name: 'continueMenu',
|
|
186
|
+
message: 'Back to git menu?',
|
|
187
|
+
default: true
|
|
188
|
+
}]);
|
|
189
|
+
|
|
190
|
+
if (continueMenu) {
|
|
191
|
+
return interactiveGit(options);
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
/**
|
|
196
|
+
* Show git status with colors
|
|
197
|
+
*/
|
|
198
|
+
async function gitStatus(options) {
|
|
199
|
+
try {
|
|
200
|
+
const { stdout } = await execAsync('git status');
|
|
201
|
+
|
|
202
|
+
// Colorize output
|
|
203
|
+
const colored = stdout
|
|
204
|
+
.replace(/modified:/g, chalk.yellow('modified:'))
|
|
205
|
+
.replace(/new file:/g, chalk.green('new file:'))
|
|
206
|
+
.replace(/deleted:/g, chalk.red('deleted:'))
|
|
207
|
+
.replace(/renamed:/g, chalk.blue('renamed:'))
|
|
208
|
+
.replace(/Untracked files:/g, chalk.gray('Untracked files:'))
|
|
209
|
+
.replace(/On branch (\S+)/g, `On branch ${chalk.green('$1')}`)
|
|
210
|
+
.replace(/Your branch is ahead/g, chalk.green('Your branch is ahead'))
|
|
211
|
+
.replace(/Your branch is behind/g, chalk.red('Your branch is behind'))
|
|
212
|
+
.replace(/nothing to commit/g, chalk.green('nothing to commit'));
|
|
213
|
+
|
|
214
|
+
console.log('\n' + colored);
|
|
215
|
+
} catch (error) {
|
|
216
|
+
console.log(chalk.red(`\n Error: ${error.message}\n`));
|
|
217
|
+
}
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
/**
|
|
221
|
+
* Commit changes with optional AI-generated message
|
|
222
|
+
*/
|
|
223
|
+
async function gitCommit(args, options) {
|
|
224
|
+
const status = await getGitStatus();
|
|
225
|
+
|
|
226
|
+
if (status.changed === 0) {
|
|
227
|
+
console.log(chalk.yellow('\n No changes to commit.\n'));
|
|
228
|
+
return;
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
// Show what will be committed
|
|
232
|
+
console.log(chalk.cyan('\n Files to commit:'));
|
|
233
|
+
for (const file of status.files) {
|
|
234
|
+
const statusCode = file.substring(0, 2);
|
|
235
|
+
const filename = file.substring(3);
|
|
236
|
+
|
|
237
|
+
if (statusCode.includes('M')) {
|
|
238
|
+
console.log(chalk.yellow(` M ${filename}`));
|
|
239
|
+
} else if (statusCode.includes('A')) {
|
|
240
|
+
console.log(chalk.green(` A ${filename}`));
|
|
241
|
+
} else if (statusCode.startsWith('??')) {
|
|
242
|
+
console.log(chalk.gray(` ? ${filename}`));
|
|
243
|
+
} else if (statusCode.includes('D')) {
|
|
244
|
+
console.log(chalk.red(` D ${filename}`));
|
|
245
|
+
} else if (statusCode.includes('R')) {
|
|
246
|
+
console.log(chalk.blue(` R ${filename}`));
|
|
247
|
+
} else {
|
|
248
|
+
console.log(chalk.gray(` ${statusCode} ${filename}`));
|
|
249
|
+
}
|
|
250
|
+
}
|
|
251
|
+
console.log('');
|
|
252
|
+
|
|
253
|
+
let message = args.join(' ');
|
|
254
|
+
|
|
255
|
+
// If message provided via -m option
|
|
256
|
+
if (options.message) {
|
|
257
|
+
message = options.message;
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
// If no message, generate or ask
|
|
261
|
+
if (!message) {
|
|
262
|
+
const { messageChoice } = await inquirer.prompt([{
|
|
263
|
+
type: 'list',
|
|
264
|
+
name: 'messageChoice',
|
|
265
|
+
message: 'Commit message:',
|
|
266
|
+
choices: [
|
|
267
|
+
{ name: ' Enter message manually', value: 'manual' },
|
|
268
|
+
{ name: ' AI generate message', value: 'ai' },
|
|
269
|
+
{ name: ' Cancel', value: 'cancel' }
|
|
270
|
+
]
|
|
271
|
+
}]);
|
|
272
|
+
|
|
273
|
+
if (messageChoice === 'cancel') {
|
|
274
|
+
console.log(chalk.gray('\n Cancelled.\n'));
|
|
275
|
+
return;
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
if (messageChoice === 'manual') {
|
|
279
|
+
const { manualMessage } = await inquirer.prompt([{
|
|
280
|
+
type: 'input',
|
|
281
|
+
name: 'manualMessage',
|
|
282
|
+
message: 'Enter commit message:',
|
|
283
|
+
validate: (input) => input.length > 0 || 'Message cannot be empty'
|
|
284
|
+
}]);
|
|
285
|
+
message = manualMessage;
|
|
286
|
+
} else {
|
|
287
|
+
// AI generate
|
|
288
|
+
console.log(chalk.gray('\n Generating commit message...\n'));
|
|
289
|
+
message = await generateCommitMessage(status.files);
|
|
290
|
+
console.log(chalk.cyan(` Generated: ${chalk.white(message)}\n`));
|
|
291
|
+
|
|
292
|
+
const { useGenerated } = await inquirer.prompt([{
|
|
293
|
+
type: 'confirm',
|
|
294
|
+
name: 'useGenerated',
|
|
295
|
+
message: 'Use this message?',
|
|
296
|
+
default: true
|
|
297
|
+
}]);
|
|
298
|
+
|
|
299
|
+
if (!useGenerated) {
|
|
300
|
+
const { manualMessage } = await inquirer.prompt([{
|
|
301
|
+
type: 'input',
|
|
302
|
+
name: 'manualMessage',
|
|
303
|
+
message: 'Enter commit message:',
|
|
304
|
+
default: message,
|
|
305
|
+
validate: (input) => input.length > 0 || 'Message cannot be empty'
|
|
306
|
+
}]);
|
|
307
|
+
message = manualMessage;
|
|
308
|
+
}
|
|
309
|
+
}
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
// Confirm
|
|
313
|
+
const { confirm } = await inquirer.prompt([{
|
|
314
|
+
type: 'confirm',
|
|
315
|
+
name: 'confirm',
|
|
316
|
+
message: `Commit with message: "${chalk.cyan(message)}"?`,
|
|
317
|
+
default: true
|
|
318
|
+
}]);
|
|
319
|
+
|
|
320
|
+
if (!confirm) {
|
|
321
|
+
console.log(chalk.gray('\n Cancelled.\n'));
|
|
322
|
+
return;
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
try {
|
|
326
|
+
// Stage all if --auto or untracked files exist
|
|
327
|
+
if (options.auto || status.untracked > 0) {
|
|
328
|
+
await execAsync('git add -A');
|
|
329
|
+
console.log(chalk.gray(' Staged all changes.'));
|
|
330
|
+
}
|
|
331
|
+
|
|
332
|
+
// Commit
|
|
333
|
+
const escapedMessage = message.replace(/"/g, '\\"').replace(/`/g, '\\`');
|
|
334
|
+
await execAsync(`git commit -m "${escapedMessage}"`);
|
|
335
|
+
|
|
336
|
+
console.log(chalk.green(`\n Committed: ${message}\n`));
|
|
337
|
+
|
|
338
|
+
// Ask to push
|
|
339
|
+
const { shouldPush } = await inquirer.prompt([{
|
|
340
|
+
type: 'confirm',
|
|
341
|
+
name: 'shouldPush',
|
|
342
|
+
message: 'Push to remote?',
|
|
343
|
+
default: false
|
|
344
|
+
}]);
|
|
345
|
+
|
|
346
|
+
if (shouldPush) {
|
|
347
|
+
await gitPush(options);
|
|
348
|
+
}
|
|
349
|
+
} catch (error) {
|
|
350
|
+
if (error.message.includes('nothing to commit')) {
|
|
351
|
+
console.log(chalk.yellow('\n Nothing to commit. Stage files first with: vibecode git add\n'));
|
|
352
|
+
} else {
|
|
353
|
+
console.log(chalk.red(`\n Commit failed: ${error.message}\n`));
|
|
354
|
+
}
|
|
355
|
+
}
|
|
356
|
+
}
|
|
357
|
+
|
|
358
|
+
/**
|
|
359
|
+
* Generate commit message based on changed files
|
|
360
|
+
*/
|
|
361
|
+
async function generateCommitMessage(files) {
|
|
362
|
+
// Get diff for context
|
|
363
|
+
let diffSummary = '';
|
|
364
|
+
try {
|
|
365
|
+
const { stdout } = await execAsync('git diff --stat HEAD 2>/dev/null || git diff --stat --cached');
|
|
366
|
+
diffSummary = stdout;
|
|
367
|
+
} catch {
|
|
368
|
+
// Ignore
|
|
369
|
+
}
|
|
370
|
+
|
|
371
|
+
const fileNames = files.map(f => f.substring(3).toLowerCase());
|
|
372
|
+
|
|
373
|
+
// Analyze file types
|
|
374
|
+
const hasNewFiles = files.some(f => f.startsWith('A') || f.startsWith('??'));
|
|
375
|
+
const hasModified = files.some(f => f.includes('M'));
|
|
376
|
+
const hasDeleted = files.some(f => f.includes('D'));
|
|
377
|
+
|
|
378
|
+
// Detect categories
|
|
379
|
+
const hasTests = fileNames.some(f => f.includes('test') || f.includes('spec'));
|
|
380
|
+
const hasDocs = fileNames.some(f => f.includes('readme') || f.endsWith('.md'));
|
|
381
|
+
const hasConfig = fileNames.some(f => f.includes('config') || f.includes('package.json') || f.includes('.rc'));
|
|
382
|
+
const hasStyles = fileNames.some(f => f.endsWith('.css') || f.endsWith('.scss') || f.endsWith('.less'));
|
|
383
|
+
const hasSrc = fileNames.some(f => f.includes('src/'));
|
|
384
|
+
|
|
385
|
+
// Determine type
|
|
386
|
+
let type = 'chore';
|
|
387
|
+
let subject = 'update project';
|
|
388
|
+
|
|
389
|
+
if (hasTests && !hasSrc) {
|
|
390
|
+
type = 'test';
|
|
391
|
+
subject = 'update tests';
|
|
392
|
+
} else if (hasDocs && files.length === 1) {
|
|
393
|
+
type = 'docs';
|
|
394
|
+
subject = 'update documentation';
|
|
395
|
+
} else if (hasConfig && files.length <= 2) {
|
|
396
|
+
type = 'chore';
|
|
397
|
+
subject = 'update configuration';
|
|
398
|
+
} else if (hasStyles && !hasSrc) {
|
|
399
|
+
type = 'style';
|
|
400
|
+
subject = 'update styles';
|
|
401
|
+
} else if (hasNewFiles && !hasModified && !hasDeleted) {
|
|
402
|
+
type = 'feat';
|
|
403
|
+
const newFileNames = files.filter(f => f.startsWith('A') || f.startsWith('??')).map(f => f.substring(3));
|
|
404
|
+
if (newFileNames.length === 1) {
|
|
405
|
+
subject = `add ${newFileNames[0].split('/').pop()}`;
|
|
406
|
+
} else {
|
|
407
|
+
subject = 'add new files';
|
|
408
|
+
}
|
|
409
|
+
} else if (hasDeleted && !hasNewFiles && !hasModified) {
|
|
410
|
+
type = 'refactor';
|
|
411
|
+
subject = 'remove unused files';
|
|
412
|
+
} else if (hasModified && files.length === 1) {
|
|
413
|
+
const modifiedFile = files[0].substring(3);
|
|
414
|
+
subject = `update ${modifiedFile.split('/').pop()}`;
|
|
415
|
+
}
|
|
416
|
+
|
|
417
|
+
// Extract scope from file paths
|
|
418
|
+
let scope = '';
|
|
419
|
+
const dirs = fileNames.map(f => {
|
|
420
|
+
const parts = f.split('/');
|
|
421
|
+
return parts.length > 1 ? parts[0] : '';
|
|
422
|
+
}).filter(Boolean);
|
|
423
|
+
|
|
424
|
+
if (dirs.length > 0) {
|
|
425
|
+
const uniqueDirs = [...new Set(dirs)];
|
|
426
|
+
if (uniqueDirs.length === 1 && uniqueDirs[0] !== 'src') {
|
|
427
|
+
scope = `(${uniqueDirs[0]})`;
|
|
428
|
+
}
|
|
429
|
+
}
|
|
430
|
+
|
|
431
|
+
return `${type}${scope}: ${subject}`;
|
|
432
|
+
}
|
|
433
|
+
|
|
434
|
+
/**
|
|
435
|
+
* Show diff with syntax highlighting
|
|
436
|
+
*/
|
|
437
|
+
async function gitDiff(args, options) {
|
|
438
|
+
try {
|
|
439
|
+
const file = args[0];
|
|
440
|
+
let cmd = 'git diff';
|
|
441
|
+
|
|
442
|
+
if (file) {
|
|
443
|
+
cmd = `git diff -- "${file}"`;
|
|
444
|
+
} else if (options.staged) {
|
|
445
|
+
cmd = 'git diff --cached';
|
|
446
|
+
}
|
|
447
|
+
|
|
448
|
+
const { stdout } = await execAsync(cmd);
|
|
449
|
+
|
|
450
|
+
if (!stdout.trim()) {
|
|
451
|
+
console.log(chalk.yellow('\n No changes to show.\n'));
|
|
452
|
+
if (!options.staged) {
|
|
453
|
+
console.log(chalk.gray(' Tip: Use --staged to see staged changes.\n'));
|
|
454
|
+
}
|
|
455
|
+
return;
|
|
456
|
+
}
|
|
457
|
+
|
|
458
|
+
// Colorize diff
|
|
459
|
+
const colored = stdout
|
|
460
|
+
.split('\n')
|
|
461
|
+
.map(line => {
|
|
462
|
+
if (line.startsWith('+') && !line.startsWith('+++')) {
|
|
463
|
+
return chalk.green(line);
|
|
464
|
+
} else if (line.startsWith('-') && !line.startsWith('---')) {
|
|
465
|
+
return chalk.red(line);
|
|
466
|
+
} else if (line.startsWith('@@')) {
|
|
467
|
+
return chalk.cyan(line);
|
|
468
|
+
} else if (line.startsWith('diff ')) {
|
|
469
|
+
return chalk.bold.white('\n' + line);
|
|
470
|
+
} else if (line.startsWith('index ')) {
|
|
471
|
+
return chalk.gray(line);
|
|
472
|
+
}
|
|
473
|
+
return line;
|
|
474
|
+
})
|
|
475
|
+
.join('\n');
|
|
476
|
+
|
|
477
|
+
console.log('\n' + colored + '\n');
|
|
478
|
+
} catch (error) {
|
|
479
|
+
console.log(chalk.red(`\n Error: ${error.message}\n`));
|
|
480
|
+
}
|
|
481
|
+
}
|
|
482
|
+
|
|
483
|
+
/**
|
|
484
|
+
* Branch management
|
|
485
|
+
*/
|
|
486
|
+
async function gitBranch(args, options) {
|
|
487
|
+
const branchName = args[0];
|
|
488
|
+
|
|
489
|
+
if (!branchName) {
|
|
490
|
+
// Show branches
|
|
491
|
+
try {
|
|
492
|
+
const { stdout: branches } = await execAsync('git branch -a');
|
|
493
|
+
const { stdout: current } = await execAsync('git branch --show-current');
|
|
494
|
+
|
|
495
|
+
console.log(chalk.cyan('\n Branches:\n'));
|
|
496
|
+
|
|
497
|
+
const lines = branches.split('\n').filter(Boolean);
|
|
498
|
+
const localBranches = [];
|
|
499
|
+
const remoteBranches = [];
|
|
500
|
+
|
|
501
|
+
for (const line of lines) {
|
|
502
|
+
const trimmed = line.replace('*', '').trim();
|
|
503
|
+
if (line.includes('remotes/')) {
|
|
504
|
+
remoteBranches.push(trimmed);
|
|
505
|
+
} else {
|
|
506
|
+
localBranches.push(trimmed);
|
|
507
|
+
if (trimmed === current.trim()) {
|
|
508
|
+
console.log(chalk.green(` * ${trimmed}`));
|
|
509
|
+
} else {
|
|
510
|
+
console.log(` ${trimmed}`);
|
|
511
|
+
}
|
|
512
|
+
}
|
|
513
|
+
}
|
|
514
|
+
|
|
515
|
+
if (remoteBranches.length > 0) {
|
|
516
|
+
console.log(chalk.gray('\n Remote branches:'));
|
|
517
|
+
for (const rb of remoteBranches.slice(0, 5)) {
|
|
518
|
+
console.log(chalk.gray(` ${rb}`));
|
|
519
|
+
}
|
|
520
|
+
if (remoteBranches.length > 5) {
|
|
521
|
+
console.log(chalk.gray(` ... and ${remoteBranches.length - 5} more`));
|
|
522
|
+
}
|
|
523
|
+
}
|
|
524
|
+
console.log('');
|
|
525
|
+
|
|
526
|
+
// Ask to switch or create
|
|
527
|
+
const { action } = await inquirer.prompt([{
|
|
528
|
+
type: 'list',
|
|
529
|
+
name: 'action',
|
|
530
|
+
message: 'Action:',
|
|
531
|
+
choices: [
|
|
532
|
+
{ name: ' Switch branch', value: 'switch' },
|
|
533
|
+
{ name: ' Create new branch', value: 'create' },
|
|
534
|
+
{ name: ' Delete branch', value: 'delete' },
|
|
535
|
+
{ name: ' Back', value: 'exit' }
|
|
536
|
+
]
|
|
537
|
+
}]);
|
|
538
|
+
|
|
539
|
+
if (action === 'exit') return;
|
|
540
|
+
|
|
541
|
+
if (action === 'switch') {
|
|
542
|
+
const { branch } = await inquirer.prompt([{
|
|
543
|
+
type: 'list',
|
|
544
|
+
name: 'branch',
|
|
545
|
+
message: 'Select branch:',
|
|
546
|
+
choices: localBranches.filter(b => b !== current.trim())
|
|
547
|
+
}]);
|
|
548
|
+
|
|
549
|
+
await execAsync(`git checkout "${branch}"`);
|
|
550
|
+
console.log(chalk.green(`\n Switched to ${branch}\n`));
|
|
551
|
+
} else if (action === 'create') {
|
|
552
|
+
const { newBranch } = await inquirer.prompt([{
|
|
553
|
+
type: 'input',
|
|
554
|
+
name: 'newBranch',
|
|
555
|
+
message: 'New branch name:',
|
|
556
|
+
validate: (input) => /^[\w\-\/]+$/.test(input) || 'Invalid branch name'
|
|
557
|
+
}]);
|
|
558
|
+
|
|
559
|
+
await execAsync(`git checkout -b "${newBranch}"`);
|
|
560
|
+
console.log(chalk.green(`\n Created and switched to ${newBranch}\n`));
|
|
561
|
+
} else if (action === 'delete') {
|
|
562
|
+
const deletableBranches = localBranches.filter(b => b !== current.trim());
|
|
563
|
+
|
|
564
|
+
if (deletableBranches.length === 0) {
|
|
565
|
+
console.log(chalk.yellow('\n No branches to delete.\n'));
|
|
566
|
+
return;
|
|
567
|
+
}
|
|
568
|
+
|
|
569
|
+
const { branchToDelete } = await inquirer.prompt([{
|
|
570
|
+
type: 'list',
|
|
571
|
+
name: 'branchToDelete',
|
|
572
|
+
message: 'Select branch to delete:',
|
|
573
|
+
choices: deletableBranches
|
|
574
|
+
}]);
|
|
575
|
+
|
|
576
|
+
const { confirmDelete } = await inquirer.prompt([{
|
|
577
|
+
type: 'confirm',
|
|
578
|
+
name: 'confirmDelete',
|
|
579
|
+
message: `Delete branch "${branchToDelete}"?`,
|
|
580
|
+
default: false
|
|
581
|
+
}]);
|
|
582
|
+
|
|
583
|
+
if (confirmDelete) {
|
|
584
|
+
await execAsync(`git branch -d "${branchToDelete}"`);
|
|
585
|
+
console.log(chalk.green(`\n Deleted branch ${branchToDelete}\n`));
|
|
586
|
+
}
|
|
587
|
+
}
|
|
588
|
+
} catch (error) {
|
|
589
|
+
console.log(chalk.red(`\n Error: ${error.message}\n`));
|
|
590
|
+
}
|
|
591
|
+
} else {
|
|
592
|
+
// Create/switch to branch
|
|
593
|
+
try {
|
|
594
|
+
// Check if branch exists
|
|
595
|
+
const { stdout } = await execAsync('git branch');
|
|
596
|
+
const exists = stdout.split('\n').some(b => b.replace('*', '').trim() === branchName);
|
|
597
|
+
|
|
598
|
+
if (exists) {
|
|
599
|
+
await execAsync(`git checkout "${branchName}"`);
|
|
600
|
+
console.log(chalk.green(`\n Switched to ${branchName}\n`));
|
|
601
|
+
} else {
|
|
602
|
+
await execAsync(`git checkout -b "${branchName}"`);
|
|
603
|
+
console.log(chalk.green(`\n Created and switched to ${branchName}\n`));
|
|
604
|
+
}
|
|
605
|
+
} catch (error) {
|
|
606
|
+
console.log(chalk.red(`\n Error: ${error.message}\n`));
|
|
607
|
+
}
|
|
608
|
+
}
|
|
609
|
+
}
|
|
610
|
+
|
|
611
|
+
/**
|
|
612
|
+
* Push to remote
|
|
613
|
+
*/
|
|
614
|
+
async function gitPush(options) {
|
|
615
|
+
try {
|
|
616
|
+
const { stdout: branch } = await execAsync('git branch --show-current');
|
|
617
|
+
const branchName = branch.trim();
|
|
618
|
+
|
|
619
|
+
console.log(chalk.cyan(`\n Pushing to ${branchName}...`));
|
|
620
|
+
|
|
621
|
+
// Check if remote exists
|
|
622
|
+
try {
|
|
623
|
+
await execAsync('git remote get-url origin');
|
|
624
|
+
} catch {
|
|
625
|
+
console.log(chalk.yellow('\n No remote configured.'));
|
|
626
|
+
console.log(chalk.gray(' Run: git remote add origin <url>\n'));
|
|
627
|
+
return;
|
|
628
|
+
}
|
|
629
|
+
|
|
630
|
+
// Push with upstream
|
|
631
|
+
const { stdout, stderr } = await execAsync(`git push -u origin "${branchName}" 2>&1`);
|
|
632
|
+
|
|
633
|
+
console.log(chalk.green('\n Push successful!\n'));
|
|
634
|
+
if (stdout) console.log(chalk.gray(stdout));
|
|
635
|
+
} catch (error) {
|
|
636
|
+
if (error.message.includes('rejected')) {
|
|
637
|
+
console.log(chalk.red('\n Push rejected. Pull first with: vibecode git pull\n'));
|
|
638
|
+
} else {
|
|
639
|
+
console.log(chalk.red(`\n Push failed: ${error.message}\n`));
|
|
640
|
+
}
|
|
641
|
+
}
|
|
642
|
+
}
|
|
643
|
+
|
|
644
|
+
/**
|
|
645
|
+
* Pull from remote
|
|
646
|
+
*/
|
|
647
|
+
async function gitPull(options) {
|
|
648
|
+
try {
|
|
649
|
+
console.log(chalk.cyan('\n Pulling from remote...'));
|
|
650
|
+
|
|
651
|
+
const { stdout } = await execAsync('git pull 2>&1');
|
|
652
|
+
|
|
653
|
+
if (stdout.includes('Already up to date')) {
|
|
654
|
+
console.log(chalk.green('\n Already up to date.\n'));
|
|
655
|
+
} else {
|
|
656
|
+
console.log(chalk.green('\n Pull successful!'));
|
|
657
|
+
console.log(stdout + '\n');
|
|
658
|
+
}
|
|
659
|
+
} catch (error) {
|
|
660
|
+
if (error.message.includes('conflict')) {
|
|
661
|
+
console.log(chalk.red('\n Merge conflicts detected!'));
|
|
662
|
+
console.log(chalk.gray(' Resolve conflicts and commit.\n'));
|
|
663
|
+
} else {
|
|
664
|
+
console.log(chalk.red(`\n Pull failed: ${error.message}\n`));
|
|
665
|
+
}
|
|
666
|
+
}
|
|
667
|
+
}
|
|
668
|
+
|
|
669
|
+
/**
|
|
670
|
+
* Show commit log
|
|
671
|
+
*/
|
|
672
|
+
async function gitLog(options) {
|
|
673
|
+
try {
|
|
674
|
+
const count = options.count || 15;
|
|
675
|
+
const { stdout } = await execAsync(`git log --oneline -${count}`);
|
|
676
|
+
|
|
677
|
+
console.log(chalk.cyan('\n Recent commits:\n'));
|
|
678
|
+
|
|
679
|
+
const lines = stdout.split('\n').filter(Boolean);
|
|
680
|
+
for (let i = 0; i < lines.length; i++) {
|
|
681
|
+
const line = lines[i];
|
|
682
|
+
const [hash, ...messageParts] = line.split(' ');
|
|
683
|
+
const message = messageParts.join(' ');
|
|
684
|
+
|
|
685
|
+
// Highlight conventional commit types
|
|
686
|
+
let formattedMessage = message;
|
|
687
|
+
if (message.startsWith('feat')) {
|
|
688
|
+
formattedMessage = chalk.green('feat') + message.slice(4);
|
|
689
|
+
} else if (message.startsWith('fix')) {
|
|
690
|
+
formattedMessage = chalk.red('fix') + message.slice(3);
|
|
691
|
+
} else if (message.startsWith('docs')) {
|
|
692
|
+
formattedMessage = chalk.blue('docs') + message.slice(4);
|
|
693
|
+
} else if (message.startsWith('refactor')) {
|
|
694
|
+
formattedMessage = chalk.yellow('refactor') + message.slice(8);
|
|
695
|
+
} else if (message.startsWith('test')) {
|
|
696
|
+
formattedMessage = chalk.magenta('test') + message.slice(4);
|
|
697
|
+
} else if (message.startsWith('chore')) {
|
|
698
|
+
formattedMessage = chalk.gray('chore') + message.slice(5);
|
|
699
|
+
}
|
|
700
|
+
|
|
701
|
+
console.log(` ${chalk.yellow(hash)} ${formattedMessage}`);
|
|
702
|
+
}
|
|
703
|
+
console.log('');
|
|
704
|
+
} catch (error) {
|
|
705
|
+
console.log(chalk.red(`\n Error: ${error.message}\n`));
|
|
706
|
+
}
|
|
707
|
+
}
|
|
708
|
+
|
|
709
|
+
/**
|
|
710
|
+
* Stash changes
|
|
711
|
+
*/
|
|
712
|
+
async function gitStash(args, options) {
|
|
713
|
+
try {
|
|
714
|
+
const message = args.join(' ');
|
|
715
|
+
|
|
716
|
+
// Check if there are changes
|
|
717
|
+
const { stdout: status } = await execAsync('git status --porcelain');
|
|
718
|
+
if (!status.trim()) {
|
|
719
|
+
console.log(chalk.yellow('\n No changes to stash.\n'));
|
|
720
|
+
return;
|
|
721
|
+
}
|
|
722
|
+
|
|
723
|
+
const cmd = message ? `git stash push -m "${message}"` : 'git stash';
|
|
724
|
+
|
|
725
|
+
await execAsync(cmd);
|
|
726
|
+
console.log(chalk.green('\n Changes stashed!\n'));
|
|
727
|
+
|
|
728
|
+
// Show stash list
|
|
729
|
+
const { stdout: list } = await execAsync('git stash list');
|
|
730
|
+
if (list.trim()) {
|
|
731
|
+
console.log(chalk.gray(' Current stashes:'));
|
|
732
|
+
const stashes = list.trim().split('\n').slice(0, 3);
|
|
733
|
+
for (const s of stashes) {
|
|
734
|
+
console.log(chalk.gray(` ${s}`));
|
|
735
|
+
}
|
|
736
|
+
console.log('');
|
|
737
|
+
}
|
|
738
|
+
} catch (error) {
|
|
739
|
+
console.log(chalk.red(`\n Stash failed: ${error.message}\n`));
|
|
740
|
+
}
|
|
741
|
+
}
|
|
742
|
+
|
|
743
|
+
/**
|
|
744
|
+
* Unstash changes
|
|
745
|
+
*/
|
|
746
|
+
async function gitUnstash(options) {
|
|
747
|
+
try {
|
|
748
|
+
// List stashes
|
|
749
|
+
const { stdout } = await execAsync('git stash list');
|
|
750
|
+
|
|
751
|
+
if (!stdout.trim()) {
|
|
752
|
+
console.log(chalk.yellow('\n No stashes found.\n'));
|
|
753
|
+
return;
|
|
754
|
+
}
|
|
755
|
+
|
|
756
|
+
const stashes = stdout.trim().split('\n');
|
|
757
|
+
|
|
758
|
+
console.log(chalk.cyan('\n Available stashes:\n'));
|
|
759
|
+
|
|
760
|
+
const { stash } = await inquirer.prompt([{
|
|
761
|
+
type: 'list',
|
|
762
|
+
name: 'stash',
|
|
763
|
+
message: 'Select stash to apply:',
|
|
764
|
+
choices: stashes.map((s, i) => ({
|
|
765
|
+
name: ` ${s}`,
|
|
766
|
+
value: i
|
|
767
|
+
}))
|
|
768
|
+
}]);
|
|
769
|
+
|
|
770
|
+
const { action } = await inquirer.prompt([{
|
|
771
|
+
type: 'list',
|
|
772
|
+
name: 'action',
|
|
773
|
+
message: 'Action:',
|
|
774
|
+
choices: [
|
|
775
|
+
{ name: ' Pop (apply and remove)', value: 'pop' },
|
|
776
|
+
{ name: ' Apply (keep stash)', value: 'apply' },
|
|
777
|
+
{ name: ' Drop (delete stash)', value: 'drop' },
|
|
778
|
+
{ name: ' Cancel', value: 'cancel' }
|
|
779
|
+
]
|
|
780
|
+
}]);
|
|
781
|
+
|
|
782
|
+
if (action === 'cancel') return;
|
|
783
|
+
|
|
784
|
+
await execAsync(`git stash ${action} stash@{${stash}}`);
|
|
785
|
+
console.log(chalk.green(`\n Stash ${action} successful!\n`));
|
|
786
|
+
} catch (error) {
|
|
787
|
+
if (error.message.includes('conflict')) {
|
|
788
|
+
console.log(chalk.red('\n Merge conflicts detected!'));
|
|
789
|
+
console.log(chalk.gray(' Resolve conflicts manually.\n'));
|
|
790
|
+
} else {
|
|
791
|
+
console.log(chalk.red(`\n Unstash failed: ${error.message}\n`));
|
|
792
|
+
}
|
|
793
|
+
}
|
|
794
|
+
}
|
|
795
|
+
|
|
796
|
+
/**
|
|
797
|
+
* Stage files
|
|
798
|
+
*/
|
|
799
|
+
async function gitAdd(args, options) {
|
|
800
|
+
try {
|
|
801
|
+
if (args.length === 0 || options.all) {
|
|
802
|
+
await execAsync('git add -A');
|
|
803
|
+
console.log(chalk.green('\n Staged all changes.\n'));
|
|
804
|
+
} else {
|
|
805
|
+
for (const file of args) {
|
|
806
|
+
await execAsync(`git add "${file}"`);
|
|
807
|
+
console.log(chalk.green(` Staged: ${file}`));
|
|
808
|
+
}
|
|
809
|
+
console.log('');
|
|
810
|
+
}
|
|
811
|
+
} catch (error) {
|
|
812
|
+
console.log(chalk.red(`\n Error: ${error.message}\n`));
|
|
813
|
+
}
|
|
814
|
+
}
|
|
815
|
+
|
|
816
|
+
/**
|
|
817
|
+
* Reset staged files
|
|
818
|
+
*/
|
|
819
|
+
async function gitReset(args, options) {
|
|
820
|
+
try {
|
|
821
|
+
if (args.length === 0) {
|
|
822
|
+
const { confirm } = await inquirer.prompt([{
|
|
823
|
+
type: 'confirm',
|
|
824
|
+
name: 'confirm',
|
|
825
|
+
message: 'Unstage all files?',
|
|
826
|
+
default: true
|
|
827
|
+
}]);
|
|
828
|
+
|
|
829
|
+
if (confirm) {
|
|
830
|
+
await execAsync('git reset HEAD');
|
|
831
|
+
console.log(chalk.green('\n Unstaged all files.\n'));
|
|
832
|
+
}
|
|
833
|
+
} else {
|
|
834
|
+
for (const file of args) {
|
|
835
|
+
await execAsync(`git reset HEAD "${file}"`);
|
|
836
|
+
console.log(chalk.green(` Unstaged: ${file}`));
|
|
837
|
+
}
|
|
838
|
+
console.log('');
|
|
839
|
+
}
|
|
840
|
+
} catch (error) {
|
|
841
|
+
console.log(chalk.red(`\n Error: ${error.message}\n`));
|
|
842
|
+
}
|
|
843
|
+
}
|
|
844
|
+
|
|
845
|
+
/**
|
|
846
|
+
* Checkout files
|
|
847
|
+
*/
|
|
848
|
+
async function gitCheckout(args, options) {
|
|
849
|
+
if (args.length === 0) {
|
|
850
|
+
return gitBranch([], options);
|
|
851
|
+
}
|
|
852
|
+
|
|
853
|
+
const target = args[0];
|
|
854
|
+
|
|
855
|
+
try {
|
|
856
|
+
// Check if it's a branch
|
|
857
|
+
const { stdout: branches } = await execAsync('git branch');
|
|
858
|
+
const isBranch = branches.split('\n').some(b => b.replace('*', '').trim() === target);
|
|
859
|
+
|
|
860
|
+
if (isBranch) {
|
|
861
|
+
await execAsync(`git checkout "${target}"`);
|
|
862
|
+
console.log(chalk.green(`\n Switched to branch ${target}\n`));
|
|
863
|
+
} else {
|
|
864
|
+
// Assume it's a file - restore it
|
|
865
|
+
const { confirm } = await inquirer.prompt([{
|
|
866
|
+
type: 'confirm',
|
|
867
|
+
name: 'confirm',
|
|
868
|
+
message: `Discard changes to "${target}"?`,
|
|
869
|
+
default: false
|
|
870
|
+
}]);
|
|
871
|
+
|
|
872
|
+
if (confirm) {
|
|
873
|
+
await execAsync(`git checkout -- "${target}"`);
|
|
874
|
+
console.log(chalk.green(`\n Restored: ${target}\n`));
|
|
875
|
+
}
|
|
876
|
+
}
|
|
877
|
+
} catch (error) {
|
|
878
|
+
console.log(chalk.red(`\n Error: ${error.message}\n`));
|
|
879
|
+
}
|
|
880
|
+
}
|
|
881
|
+
|
|
882
|
+
/**
|
|
883
|
+
* Pass through to native git
|
|
884
|
+
*/
|
|
885
|
+
async function gitPassthrough(subcommand, args) {
|
|
886
|
+
try {
|
|
887
|
+
const cmd = `git ${subcommand} ${args.join(' ')}`.trim();
|
|
888
|
+
console.log(chalk.gray(`\n Running: ${cmd}\n`));
|
|
889
|
+
|
|
890
|
+
const { stdout, stderr } = await execAsync(cmd);
|
|
891
|
+
|
|
892
|
+
if (stdout) console.log(stdout);
|
|
893
|
+
if (stderr) console.log(chalk.yellow(stderr));
|
|
894
|
+
} catch (error) {
|
|
895
|
+
console.log(chalk.red(`\n Error: ${error.message}\n`));
|
|
896
|
+
}
|
|
897
|
+
}
|
|
898
|
+
|
|
899
|
+
/**
|
|
900
|
+
* Auto-commit after vibecode actions (for integration with other commands)
|
|
901
|
+
*/
|
|
902
|
+
export async function autoCommit(action, files = []) {
|
|
903
|
+
const isGitRepo = await checkGitRepo();
|
|
904
|
+
if (!isGitRepo) return false;
|
|
905
|
+
|
|
906
|
+
try {
|
|
907
|
+
// Check if there are changes
|
|
908
|
+
const { stdout } = await execAsync('git status --porcelain');
|
|
909
|
+
if (!stdout.trim()) return false;
|
|
910
|
+
|
|
911
|
+
// Stage and commit
|
|
912
|
+
await execAsync('git add -A');
|
|
913
|
+
await execAsync(`git commit -m "vibecode: ${action}"`);
|
|
914
|
+
|
|
915
|
+
console.log(chalk.gray(`\n Auto-committed: vibecode: ${action}`));
|
|
916
|
+
return true;
|
|
917
|
+
} catch {
|
|
918
|
+
// Silently fail - not critical
|
|
919
|
+
return false;
|
|
920
|
+
}
|
|
921
|
+
}
|
|
922
|
+
|
|
923
|
+
export default gitCommand;
|