@snapcommit/cli 3.9.21 → 3.11.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/dist/commands/autopilot.js +443 -0
- package/dist/commands/cursor-style.js +208 -334
- package/dist/commands/natural.js +1 -1
- package/dist/commands/onboard.js +1 -0
- package/dist/commands/telemetry.js +44 -0
- package/dist/index.js +38 -0
- package/dist/repl.js +91 -5
- package/dist/types/prompt.js +2 -0
- package/dist/types/workflow.js +2 -0
- package/dist/utils/memory.js +80 -0
- package/dist/utils/prompt-helpers.js +62 -0
- package/dist/utils/prompt.js +78 -0
- package/dist/utils/settings.js +58 -0
- package/dist/utils/telemetry.js +81 -0
- package/package.json +1 -1
|
@@ -42,6 +42,7 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
|
42
42
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
43
43
|
exports.executeCursorStyle = executeCursorStyle;
|
|
44
44
|
const chalk_1 = __importDefault(require("chalk"));
|
|
45
|
+
const fs_1 = __importDefault(require("fs"));
|
|
45
46
|
const child_process_1 = require("child_process");
|
|
46
47
|
const git_1 = require("../utils/git");
|
|
47
48
|
const auth_1 = require("../lib/auth");
|
|
@@ -343,42 +344,8 @@ async function executeCommitWithAI(intent, originalInput = '') {
|
|
|
343
344
|
catch (pullError) {
|
|
344
345
|
// Handle rebase conflicts
|
|
345
346
|
if (pullError.message.includes('conflict')) {
|
|
346
|
-
console.log(chalk_1.default.yellow('\n⚠️ Merge conflicts detected - resolving...'));
|
|
347
|
-
// Try to auto-resolve conflicts
|
|
348
347
|
const resolved = await tryAdvancedConflictResolution();
|
|
349
|
-
if (resolved) {
|
|
350
|
-
console.log(chalk_1.default.green('✓ Conflicts resolved automatically!\n'));
|
|
351
|
-
}
|
|
352
|
-
else {
|
|
353
|
-
// Better messaging for manual resolution
|
|
354
|
-
console.log(chalk_1.default.yellow('\n⚠️ AI could not auto-resolve these conflicts\n'));
|
|
355
|
-
console.log(chalk_1.default.white('Why? These conflicts require human judgment:'));
|
|
356
|
-
console.log(chalk_1.default.gray(' • Business logic decisions (which approach is correct?)'));
|
|
357
|
-
console.log(chalk_1.default.gray(' • Architectural changes (incompatible design choices)'));
|
|
358
|
-
console.log(chalk_1.default.gray(' • Semantic conflicts (both valid, but different intent)\n'));
|
|
359
|
-
console.log(chalk_1.default.white('Conflicted files:'));
|
|
360
|
-
const conflicts = (0, child_process_1.execSync)('git diff --name-only --diff-filter=U', { encoding: 'utf-8' });
|
|
361
|
-
conflicts.split('\n').filter(f => f.trim()).forEach(file => {
|
|
362
|
-
console.log(chalk_1.default.red(` ✗ ${file}`));
|
|
363
|
-
});
|
|
364
|
-
console.log(chalk_1.default.cyan('\n📖 How to resolve manually:\n'));
|
|
365
|
-
console.log(chalk_1.default.white(' 1. Open each file above in your editor'));
|
|
366
|
-
console.log(chalk_1.default.gray(' Look for conflict markers: ') + chalk_1.default.yellow('<<<<<<< HEAD'));
|
|
367
|
-
console.log(chalk_1.default.gray(' ') + chalk_1.default.yellow('======='));
|
|
368
|
-
console.log(chalk_1.default.gray(' ') + chalk_1.default.yellow('>>>>>>> branch'));
|
|
369
|
-
console.log('');
|
|
370
|
-
console.log(chalk_1.default.white(' 2. Choose which code to keep (or merge both)'));
|
|
371
|
-
console.log(chalk_1.default.gray(' Remove the conflict markers when done'));
|
|
372
|
-
console.log('');
|
|
373
|
-
console.log(chalk_1.default.white(' 3. After fixing, stage the resolved files:'));
|
|
374
|
-
console.log(chalk_1.default.cyan(' git add <files>'));
|
|
375
|
-
console.log('');
|
|
376
|
-
console.log(chalk_1.default.white(' 4. Continue the rebase:'));
|
|
377
|
-
console.log(chalk_1.default.cyan(' git rebase --continue'));
|
|
378
|
-
console.log('');
|
|
379
|
-
console.log(chalk_1.default.white(' 5. Then push your changes:'));
|
|
380
|
-
console.log(chalk_1.default.cyan(' snap') + chalk_1.default.gray(' and type ') + chalk_1.default.cyan('push changes\n'));
|
|
381
|
-
console.log(chalk_1.default.gray('💡 Tip: Most conflicts are simple! Just pick the better code.\n'));
|
|
348
|
+
if (!resolved) {
|
|
382
349
|
return;
|
|
383
350
|
}
|
|
384
351
|
}
|
|
@@ -452,162 +419,6 @@ async function executeCommitWithAI(intent, originalInput = '') {
|
|
|
452
419
|
console.log();
|
|
453
420
|
}
|
|
454
421
|
}
|
|
455
|
-
/**
|
|
456
|
-
* AI-POWERED conflict resolution - intelligently resolves merge conflicts!
|
|
457
|
-
*/
|
|
458
|
-
async function tryAdvancedConflictResolution() {
|
|
459
|
-
try {
|
|
460
|
-
// Get conflicted files
|
|
461
|
-
const conflictedFiles = (0, child_process_1.execSync)('git diff --name-only --diff-filter=U', { encoding: 'utf-8' })
|
|
462
|
-
.split('\n')
|
|
463
|
-
.filter(f => f.trim());
|
|
464
|
-
if (conflictedFiles.length === 0)
|
|
465
|
-
return false;
|
|
466
|
-
console.log(chalk_1.default.white(`\n📋 Analyzing ${conflictedFiles.length} conflicted file(s)...\n`));
|
|
467
|
-
// For each conflicted file, use AI to resolve
|
|
468
|
-
for (const file of conflictedFiles) {
|
|
469
|
-
try {
|
|
470
|
-
// Read the conflicted file
|
|
471
|
-
const conflictContent = (0, child_process_1.execSync)(`git show :1:${file}`, { encoding: 'utf-8', stdio: 'pipe' });
|
|
472
|
-
const oursContent = (0, child_process_1.execSync)(`git show :2:${file}`, { encoding: 'utf-8', stdio: 'pipe' });
|
|
473
|
-
const theirsContent = (0, child_process_1.execSync)(`git show :3:${file}`, { encoding: 'utf-8', stdio: 'pipe' });
|
|
474
|
-
// Ask AI to resolve the conflict
|
|
475
|
-
console.log(chalk_1.default.blue(`🤖 AI analyzing: ${file}...`));
|
|
476
|
-
const resolution = await resolveConflictWithAIDetailed(file, conflictContent, oursContent, theirsContent);
|
|
477
|
-
if (resolution) {
|
|
478
|
-
console.log(chalk_1.default.green(`✓ AI resolved: ${file}`));
|
|
479
|
-
// Write the resolved content
|
|
480
|
-
const fs = await Promise.resolve().then(() => __importStar(require('fs')));
|
|
481
|
-
fs.writeFileSync(file, resolution);
|
|
482
|
-
(0, child_process_1.execSync)(`git add ${file}`, { encoding: 'utf-8', stdio: 'pipe' });
|
|
483
|
-
}
|
|
484
|
-
else {
|
|
485
|
-
// Fallback: ask user which version to keep
|
|
486
|
-
console.log(chalk_1.default.yellow(`\n⚠️ ${file} - Choose resolution:`));
|
|
487
|
-
console.log(chalk_1.default.gray(' 1. Keep your changes (ours)'));
|
|
488
|
-
console.log(chalk_1.default.gray(' 2. Keep their changes (theirs)'));
|
|
489
|
-
console.log(chalk_1.default.gray(' 3. Skip (resolve manually later)\n'));
|
|
490
|
-
const rlConflict = await Promise.resolve().then(() => __importStar(require('readline')));
|
|
491
|
-
const rlChoice = rlConflict.createInterface({
|
|
492
|
-
input: process.stdin,
|
|
493
|
-
output: process.stdout,
|
|
494
|
-
});
|
|
495
|
-
const choice = await new Promise((resolve) => {
|
|
496
|
-
rlChoice.question(chalk_1.default.cyan('Choice (1/2/3): '), (ans) => {
|
|
497
|
-
setImmediate(() => rlChoice.close());
|
|
498
|
-
resolve(ans.trim());
|
|
499
|
-
});
|
|
500
|
-
});
|
|
501
|
-
if (choice === '1') {
|
|
502
|
-
(0, child_process_1.execSync)(`git checkout --ours ${file}`, { encoding: 'utf-8', stdio: 'pipe' });
|
|
503
|
-
(0, child_process_1.execSync)(`git add ${file}`, { encoding: 'utf-8', stdio: 'pipe' });
|
|
504
|
-
console.log(chalk_1.default.green(`✓ Kept your changes: ${file}`));
|
|
505
|
-
}
|
|
506
|
-
else if (choice === '2') {
|
|
507
|
-
(0, child_process_1.execSync)(`git checkout --theirs ${file}`, { encoding: 'utf-8', stdio: 'pipe' });
|
|
508
|
-
(0, child_process_1.execSync)(`git add ${file}`, { encoding: 'utf-8', stdio: 'pipe' });
|
|
509
|
-
console.log(chalk_1.default.green(`✓ Kept their changes: ${file}`));
|
|
510
|
-
}
|
|
511
|
-
else {
|
|
512
|
-
console.log(chalk_1.default.gray(`⊘ Skipped: ${file}`));
|
|
513
|
-
return false; // User wants to resolve manually
|
|
514
|
-
}
|
|
515
|
-
}
|
|
516
|
-
}
|
|
517
|
-
catch (fileError) {
|
|
518
|
-
console.log(chalk_1.default.yellow(`⚠️ Couldn't auto-resolve ${file}`));
|
|
519
|
-
// Fallback: ask user
|
|
520
|
-
const rlFallback = await Promise.resolve().then(() => __importStar(require('readline')));
|
|
521
|
-
const rlFallbackChoice = rlFallback.createInterface({
|
|
522
|
-
input: process.stdin,
|
|
523
|
-
output: process.stdout,
|
|
524
|
-
});
|
|
525
|
-
const fallbackChoice = await new Promise((resolve) => {
|
|
526
|
-
rlFallbackChoice.question(chalk_1.default.cyan(`Keep (1) yours or (2) theirs? [1/2]: `), (ans) => {
|
|
527
|
-
setImmediate(() => rlFallbackChoice.close());
|
|
528
|
-
resolve(ans.trim());
|
|
529
|
-
});
|
|
530
|
-
});
|
|
531
|
-
if (fallbackChoice === '1') {
|
|
532
|
-
(0, child_process_1.execSync)(`git checkout --ours ${file}`, { encoding: 'utf-8', stdio: 'pipe' });
|
|
533
|
-
(0, child_process_1.execSync)(`git add ${file}`, { encoding: 'utf-8', stdio: 'pipe' });
|
|
534
|
-
}
|
|
535
|
-
else if (fallbackChoice === '2') {
|
|
536
|
-
(0, child_process_1.execSync)(`git checkout --theirs ${file}`, { encoding: 'utf-8', stdio: 'pipe' });
|
|
537
|
-
(0, child_process_1.execSync)(`git add ${file}`, { encoding: 'utf-8', stdio: 'pipe' });
|
|
538
|
-
}
|
|
539
|
-
else {
|
|
540
|
-
return false;
|
|
541
|
-
}
|
|
542
|
-
}
|
|
543
|
-
}
|
|
544
|
-
// Continue the rebase
|
|
545
|
-
try {
|
|
546
|
-
(0, child_process_1.execSync)('git rebase --continue', { encoding: 'utf-8', stdio: 'pipe' });
|
|
547
|
-
return true;
|
|
548
|
-
}
|
|
549
|
-
catch (continueError) {
|
|
550
|
-
// If rebase fails, might need to commit first
|
|
551
|
-
try {
|
|
552
|
-
(0, child_process_1.execSync)('git commit --no-edit', { encoding: 'utf-8', stdio: 'pipe' });
|
|
553
|
-
(0, child_process_1.execSync)('git rebase --continue', { encoding: 'utf-8', stdio: 'pipe' });
|
|
554
|
-
return true;
|
|
555
|
-
}
|
|
556
|
-
catch {
|
|
557
|
-
return false;
|
|
558
|
-
}
|
|
559
|
-
}
|
|
560
|
-
}
|
|
561
|
-
catch (error) {
|
|
562
|
-
console.log(chalk_1.default.red(`❌ Conflict resolution failed: ${error.message}`));
|
|
563
|
-
try {
|
|
564
|
-
(0, child_process_1.execSync)('git rebase --abort', { encoding: 'utf-8', stdio: 'pipe' });
|
|
565
|
-
}
|
|
566
|
-
catch { }
|
|
567
|
-
return false;
|
|
568
|
-
}
|
|
569
|
-
}
|
|
570
|
-
/**
|
|
571
|
-
* Use AI to intelligently resolve merge conflicts
|
|
572
|
-
*/
|
|
573
|
-
async function resolveConflictWithAIDetailed(filename, base, ours, theirs) {
|
|
574
|
-
try {
|
|
575
|
-
const axios = (await Promise.resolve().then(() => __importStar(require('axios')))).default;
|
|
576
|
-
const { getAuthConfig } = await Promise.resolve().then(() => __importStar(require('../lib/auth')));
|
|
577
|
-
// Get auth token
|
|
578
|
-
const authConfig = getAuthConfig();
|
|
579
|
-
if (!authConfig || !authConfig.token) {
|
|
580
|
-
console.log(chalk_1.default.yellow('⚠️ Authentication required for AI conflict resolution'));
|
|
581
|
-
return null;
|
|
582
|
-
}
|
|
583
|
-
// Get the backend URL from env or default
|
|
584
|
-
const backendUrl = process.env.SNAPCOMMIT_BACKEND_URL || 'https://www.snapcommit.dev';
|
|
585
|
-
const response = await axios.post(`${backendUrl}/api/ai/resolve-conflict`, {
|
|
586
|
-
filename,
|
|
587
|
-
base,
|
|
588
|
-
ours,
|
|
589
|
-
theirs,
|
|
590
|
-
token: authConfig.token,
|
|
591
|
-
}, {
|
|
592
|
-
headers: {
|
|
593
|
-
'Content-Type': 'application/json',
|
|
594
|
-
},
|
|
595
|
-
timeout: 30000, // 30 second timeout
|
|
596
|
-
});
|
|
597
|
-
if (response.data?.resolution) {
|
|
598
|
-
return response.data.resolution;
|
|
599
|
-
}
|
|
600
|
-
return null;
|
|
601
|
-
}
|
|
602
|
-
catch (error) {
|
|
603
|
-
// AI resolution failed - fall back to manual choice
|
|
604
|
-
if (error.response?.status === 403) {
|
|
605
|
-
console.log(chalk_1.default.red('\n❌ Subscription required for AI conflict resolution'));
|
|
606
|
-
console.log(chalk_1.default.cyan('Visit: https://www.snapcommit.dev/pricing\n'));
|
|
607
|
-
}
|
|
608
|
-
return null;
|
|
609
|
-
}
|
|
610
|
-
}
|
|
611
422
|
/**
|
|
612
423
|
* Execute Git commands - Cursor-style (clean, fast, auto-fix)
|
|
613
424
|
* NOW SUPPORTS: commits, pushes, pulls, merges, rebases, cherry-picks, stash,
|
|
@@ -710,51 +521,7 @@ async function executeGitCommands(commands) {
|
|
|
710
521
|
}
|
|
711
522
|
}
|
|
712
523
|
if (hasConflicts) {
|
|
713
|
-
|
|
714
|
-
// Try AI auto-resolution first
|
|
715
|
-
console.log(chalk_1.default.blue('🤖 Attempting AI conflict resolution...\n'));
|
|
716
|
-
const resolved = await tryAdvancedConflictResolution();
|
|
717
|
-
if (resolved) {
|
|
718
|
-
console.log(chalk_1.default.green('✓ Conflicts resolved automatically!\n'));
|
|
719
|
-
// Complete the merge
|
|
720
|
-
try {
|
|
721
|
-
(0, child_process_1.execSync)('git add -A', { encoding: 'utf-8', stdio: 'pipe' });
|
|
722
|
-
(0, child_process_1.execSync)('git commit --no-edit', { encoding: 'utf-8', stdio: 'pipe' });
|
|
723
|
-
console.log(chalk_1.default.green('✓ Merge completed\n'));
|
|
724
|
-
}
|
|
725
|
-
catch {
|
|
726
|
-
console.log(chalk_1.default.red('\n❌ Failed to complete merge after resolution\n'));
|
|
727
|
-
}
|
|
728
|
-
return;
|
|
729
|
-
}
|
|
730
|
-
// AI couldn't resolve - show manual instructions
|
|
731
|
-
console.log(chalk_1.default.yellow('⚠️ AI could not auto-resolve these conflicts\n'));
|
|
732
|
-
console.log(chalk_1.default.white('Why? These conflicts require human judgment:'));
|
|
733
|
-
console.log(chalk_1.default.gray(' • Business logic decisions (which approach is correct?)'));
|
|
734
|
-
console.log(chalk_1.default.gray(' • Architectural changes (incompatible design choices)'));
|
|
735
|
-
console.log(chalk_1.default.gray(' • Semantic conflicts (both valid, but different intent)\n'));
|
|
736
|
-
console.log(chalk_1.default.white('Conflicted files:'));
|
|
737
|
-
try {
|
|
738
|
-
const conflicts = (0, child_process_1.execSync)('git diff --name-only --diff-filter=U', { encoding: 'utf-8' });
|
|
739
|
-
conflicts.split('\n').filter(f => f.trim()).forEach(file => {
|
|
740
|
-
console.log(chalk_1.default.red(` ✗ ${file}`));
|
|
741
|
-
});
|
|
742
|
-
}
|
|
743
|
-
catch {
|
|
744
|
-
console.log(chalk_1.default.gray(' (could not list files)\n'));
|
|
745
|
-
}
|
|
746
|
-
console.log(chalk_1.default.cyan('\n📖 How to resolve manually:\n'));
|
|
747
|
-
console.log(chalk_1.default.white(' 1. Open each file above in your editor'));
|
|
748
|
-
console.log(chalk_1.default.gray(' Look for conflict markers: ') + chalk_1.default.yellow('<<<<<<< HEAD'));
|
|
749
|
-
console.log(chalk_1.default.gray(' ') + chalk_1.default.yellow('======='));
|
|
750
|
-
console.log(chalk_1.default.gray(' ') + chalk_1.default.yellow('>>>>>>> branch'));
|
|
751
|
-
console.log('');
|
|
752
|
-
console.log(chalk_1.default.white(' 2. Choose which code to keep (or merge both)'));
|
|
753
|
-
console.log(chalk_1.default.gray(' Remove the conflict markers when done'));
|
|
754
|
-
console.log('');
|
|
755
|
-
console.log(chalk_1.default.white(' 3. After fixing, in snap type:'));
|
|
756
|
-
console.log(chalk_1.default.cyan(' complete the merge\n'));
|
|
757
|
-
console.log(chalk_1.default.gray('💡 Tip: Most conflicts are simple! Just pick the better code.\n'));
|
|
524
|
+
await tryAdvancedConflictResolution();
|
|
758
525
|
return;
|
|
759
526
|
}
|
|
760
527
|
// Skip harmless errors
|
|
@@ -1536,120 +1303,227 @@ async function tryAutoFix(error, command) {
|
|
|
1536
1303
|
* Handle merge conflicts with AI resolution + clear guidance
|
|
1537
1304
|
*/
|
|
1538
1305
|
async function handleMergeConflict() {
|
|
1539
|
-
|
|
1306
|
+
const result = await tryAdvancedConflictResolution();
|
|
1307
|
+
return result;
|
|
1308
|
+
}
|
|
1309
|
+
/**
|
|
1310
|
+
* AI-POWERED conflict resolution - intelligently resolves merge conflicts!
|
|
1311
|
+
*/
|
|
1312
|
+
async function tryAdvancedConflictResolution() {
|
|
1313
|
+
const result = await attemptConflictAutoResolution();
|
|
1314
|
+
return displayConflictResolution(result);
|
|
1315
|
+
}
|
|
1316
|
+
async function attemptConflictAutoResolution() {
|
|
1317
|
+
const details = collectConflictDetails();
|
|
1318
|
+
if (details.length === 0) {
|
|
1319
|
+
return { status: 'no-conflicts', details, failedFiles: [] };
|
|
1320
|
+
}
|
|
1321
|
+
const token = (0, auth_1.getToken)();
|
|
1322
|
+
if (!token) {
|
|
1323
|
+
return {
|
|
1324
|
+
status: 'manual',
|
|
1325
|
+
details,
|
|
1326
|
+
failedFiles: details.map((detail) => detail.file),
|
|
1327
|
+
reason: 'missing-token',
|
|
1328
|
+
};
|
|
1329
|
+
}
|
|
1330
|
+
const failedFiles = [];
|
|
1331
|
+
let detectedReason;
|
|
1332
|
+
for (const detail of details) {
|
|
1333
|
+
const { success, reason } = await resolveConflictWithAI(detail, token);
|
|
1334
|
+
if (!success) {
|
|
1335
|
+
failedFiles.push(detail.file);
|
|
1336
|
+
if (!detectedReason && reason) {
|
|
1337
|
+
detectedReason = reason;
|
|
1338
|
+
}
|
|
1339
|
+
}
|
|
1340
|
+
}
|
|
1341
|
+
if (failedFiles.length === 0) {
|
|
1342
|
+
finalizePendingMerge();
|
|
1343
|
+
return { status: 'resolved', details, failedFiles: [] };
|
|
1344
|
+
}
|
|
1345
|
+
return {
|
|
1346
|
+
status: 'manual',
|
|
1347
|
+
details,
|
|
1348
|
+
failedFiles,
|
|
1349
|
+
reason: detectedReason,
|
|
1350
|
+
};
|
|
1351
|
+
}
|
|
1352
|
+
function displayConflictResolution(result) {
|
|
1353
|
+
if (result.status === 'no-conflicts') {
|
|
1354
|
+
return false;
|
|
1355
|
+
}
|
|
1356
|
+
if (result.details.length > 0) {
|
|
1357
|
+
printConflictSummary(result.details);
|
|
1358
|
+
}
|
|
1359
|
+
if (result.status === 'resolved') {
|
|
1360
|
+
console.log(chalk_1.default.green('🤖 AI resolved all merge conflicts!\n'));
|
|
1361
|
+
return true;
|
|
1362
|
+
}
|
|
1363
|
+
printManualResolutionGuide(result.details, result.failedFiles, result.reason);
|
|
1364
|
+
return false;
|
|
1365
|
+
}
|
|
1366
|
+
function collectConflictDetails() {
|
|
1367
|
+
let output = '';
|
|
1540
1368
|
try {
|
|
1541
|
-
|
|
1542
|
-
const conflictedFilesOutput = (0, child_process_1.execSync)('git diff --name-only --diff-filter=U', {
|
|
1369
|
+
output = (0, child_process_1.execSync)('git diff --name-only --diff-filter=U', {
|
|
1543
1370
|
encoding: 'utf-8',
|
|
1544
|
-
stdio: 'pipe'
|
|
1371
|
+
stdio: 'pipe',
|
|
1545
1372
|
}).trim();
|
|
1546
|
-
|
|
1547
|
-
|
|
1548
|
-
|
|
1373
|
+
}
|
|
1374
|
+
catch {
|
|
1375
|
+
return [];
|
|
1376
|
+
}
|
|
1377
|
+
if (!output) {
|
|
1378
|
+
return [];
|
|
1379
|
+
}
|
|
1380
|
+
const files = output
|
|
1381
|
+
.split('\n')
|
|
1382
|
+
.map((file) => file.trim())
|
|
1383
|
+
.filter(Boolean);
|
|
1384
|
+
return files.map((file) => {
|
|
1385
|
+
let conflictCount = 0;
|
|
1386
|
+
try {
|
|
1387
|
+
const content = fs_1.default.readFileSync(file, 'utf-8');
|
|
1388
|
+
conflictCount = (content.match(/^<<<<<<< /gm) || []).length;
|
|
1549
1389
|
}
|
|
1550
|
-
|
|
1551
|
-
|
|
1552
|
-
|
|
1553
|
-
|
|
1554
|
-
|
|
1555
|
-
|
|
1556
|
-
|
|
1557
|
-
|
|
1558
|
-
|
|
1559
|
-
|
|
1390
|
+
catch {
|
|
1391
|
+
conflictCount = 0;
|
|
1392
|
+
}
|
|
1393
|
+
return {
|
|
1394
|
+
file,
|
|
1395
|
+
conflictCount,
|
|
1396
|
+
base: readStageBlob(':1', file),
|
|
1397
|
+
ours: readStageBlob(':2', file),
|
|
1398
|
+
theirs: readStageBlob(':3', file),
|
|
1399
|
+
};
|
|
1400
|
+
});
|
|
1401
|
+
}
|
|
1402
|
+
function readStageBlob(stage, file) {
|
|
1403
|
+
try {
|
|
1404
|
+
const escaped = escapePathForShell(file);
|
|
1405
|
+
return (0, child_process_1.execSync)(`git show ${stage}:"${escaped}"`, { encoding: 'utf-8', stdio: 'pipe' });
|
|
1406
|
+
}
|
|
1407
|
+
catch {
|
|
1408
|
+
return '';
|
|
1409
|
+
}
|
|
1410
|
+
}
|
|
1411
|
+
function printConflictSummary(details) {
|
|
1412
|
+
if (!details.length) {
|
|
1413
|
+
return;
|
|
1414
|
+
}
|
|
1415
|
+
console.log(chalk_1.default.yellow('⚠️ Merge conflicts detected!\n'));
|
|
1416
|
+
const totalConflicts = details.reduce((sum, detail) => sum + (detail.conflictCount || 0), 0);
|
|
1417
|
+
const suffix = totalConflicts > 0
|
|
1418
|
+
? chalk_1.default.gray(` (${totalConflicts} conflict${totalConflicts === 1 ? '' : 's'} total)`)
|
|
1419
|
+
: '';
|
|
1420
|
+
console.log(chalk_1.default.red(`❌ Merge conflict${details.length === 1 ? '' : 's'} in ${details.length} file${details.length === 1 ? '' : 's'}`) + suffix);
|
|
1421
|
+
for (const detail of details) {
|
|
1422
|
+
const countText = detail.conflictCount
|
|
1423
|
+
? chalk_1.default.gray(` (${detail.conflictCount} conflict${detail.conflictCount === 1 ? '' : 's'})`)
|
|
1424
|
+
: '';
|
|
1425
|
+
console.log(chalk_1.default.white(` • ${detail.file}`) + countText);
|
|
1426
|
+
}
|
|
1427
|
+
console.log();
|
|
1428
|
+
}
|
|
1429
|
+
function printManualResolutionGuide(details, failedFiles, reason) {
|
|
1430
|
+
const filesToHighlight = failedFiles.length
|
|
1431
|
+
? details.filter((detail) => failedFiles.includes(detail.file))
|
|
1432
|
+
: details;
|
|
1433
|
+
if (!filesToHighlight.length) {
|
|
1434
|
+
return;
|
|
1435
|
+
}
|
|
1436
|
+
if (reason === 'missing-token') {
|
|
1437
|
+
console.log(chalk_1.default.red('❌ Authentication required for AI conflict resolution\n'));
|
|
1438
|
+
console.log(chalk_1.default.gray(' Run: ') + chalk_1.default.cyan('snap login') + chalk_1.default.gray(' to reconnect, then retry.\n'));
|
|
1439
|
+
}
|
|
1440
|
+
else if (reason === 'subscription-required') {
|
|
1441
|
+
console.log(chalk_1.default.red('❌ Subscription required for AI conflict resolution\n'));
|
|
1442
|
+
console.log(chalk_1.default.cyan('Visit: https://www.snapcommit.dev/pricing\n'));
|
|
1443
|
+
}
|
|
1444
|
+
else if (reason === 'auth-error') {
|
|
1445
|
+
console.log(chalk_1.default.red('❌ Your session expired for AI conflict resolution\n'));
|
|
1446
|
+
console.log(chalk_1.default.gray(' Run: ') + chalk_1.default.cyan('snap login') + chalk_1.default.gray(' to refresh your credentials.\n'));
|
|
1447
|
+
}
|
|
1448
|
+
console.log(chalk_1.default.yellow('🤖 AI tried to resolve but needs your help!\n'));
|
|
1449
|
+
console.log(chalk_1.default.white.bold('📋 What to do:'));
|
|
1450
|
+
console.log(chalk_1.default.gray(' 1. Open the files above in your editor'));
|
|
1451
|
+
console.log(chalk_1.default.gray(' 2. Look for conflict markers like ') + chalk_1.default.yellow('<<<<<<< HEAD'));
|
|
1452
|
+
console.log(chalk_1.default.gray(' 3. Decide which changes to keep (yours, theirs, or a merge)'));
|
|
1453
|
+
console.log(chalk_1.default.gray(' 4. Remove the markers ') + chalk_1.default.yellow('<<<<<<< ======= >>>>>>>'));
|
|
1454
|
+
console.log(chalk_1.default.gray(' 5. Stage the fixes: ') + chalk_1.default.cyan('git add <file>'));
|
|
1455
|
+
console.log(chalk_1.default.gray(' 6. Back in snap, type: ') + chalk_1.default.cyan('commit the resolved changes'));
|
|
1456
|
+
console.log();
|
|
1457
|
+
console.log(chalk_1.default.white.bold('💡 Helpful commands:'));
|
|
1458
|
+
console.log(chalk_1.default.cyan(' show the conflicts') + chalk_1.default.gray(' – Reprint this summary'));
|
|
1459
|
+
console.log(chalk_1.default.cyan(' abort the merge') + chalk_1.default.gray(' – Cancel and restore previous state'));
|
|
1460
|
+
console.log(chalk_1.default.cyan(' use my version / use their version') + chalk_1.default.gray(' – Quick helpers for specific files\n'));
|
|
1461
|
+
console.log(chalk_1.default.white('Files still needing attention:'));
|
|
1462
|
+
filesToHighlight.forEach((detail) => {
|
|
1463
|
+
const countText = detail.conflictCount
|
|
1464
|
+
? chalk_1.default.gray(` (${detail.conflictCount} conflict${detail.conflictCount === 1 ? '' : 's'})`)
|
|
1465
|
+
: '';
|
|
1466
|
+
console.log(chalk_1.default.cyan(` • ${detail.file}`) + countText);
|
|
1467
|
+
});
|
|
1468
|
+
console.log();
|
|
1469
|
+
}
|
|
1470
|
+
function finalizePendingMerge() {
|
|
1471
|
+
const commands = ['git rebase --continue', 'git merge --continue', 'git commit --no-edit'];
|
|
1472
|
+
for (const command of commands) {
|
|
1473
|
+
try {
|
|
1474
|
+
(0, child_process_1.execSync)(command, { encoding: 'utf-8', stdio: 'pipe' });
|
|
1475
|
+
return true;
|
|
1476
|
+
}
|
|
1477
|
+
catch {
|
|
1478
|
+
// Try the next strategy
|
|
1479
|
+
}
|
|
1480
|
+
}
|
|
1481
|
+
return false;
|
|
1482
|
+
}
|
|
1483
|
+
async function resolveConflictWithAI(detail, token) {
|
|
1484
|
+
try {
|
|
1485
|
+
const backendUrl = process.env.SNAPCOMMIT_BACKEND_URL || 'https://www.snapcommit.dev';
|
|
1486
|
+
const response = await fetch(`${backendUrl}/api/ai/resolve-conflict`, {
|
|
1487
|
+
method: 'POST',
|
|
1488
|
+
headers: { 'Content-Type': 'application/json' },
|
|
1489
|
+
body: JSON.stringify({
|
|
1490
|
+
filename: detail.file,
|
|
1491
|
+
base: detail.base,
|
|
1492
|
+
ours: detail.ours,
|
|
1493
|
+
theirs: detail.theirs,
|
|
1494
|
+
token,
|
|
1495
|
+
}),
|
|
1496
|
+
signal: AbortSignal.timeout(30000),
|
|
1497
|
+
});
|
|
1498
|
+
if (!response.ok) {
|
|
1499
|
+
if (response.status === 403) {
|
|
1500
|
+
return { success: false, reason: 'subscription-required' };
|
|
1560
1501
|
}
|
|
1561
|
-
|
|
1562
|
-
|
|
1563
|
-
console.log(chalk_1.default.cyan(` • ${file}`));
|
|
1502
|
+
if (response.status === 401) {
|
|
1503
|
+
return { success: false, reason: 'auth-error' };
|
|
1564
1504
|
}
|
|
1505
|
+
return { success: false, reason: 'api-error' };
|
|
1565
1506
|
}
|
|
1566
|
-
|
|
1567
|
-
|
|
1568
|
-
|
|
1569
|
-
let resolvedCount = 0;
|
|
1570
|
-
const failedFiles = [];
|
|
1571
|
-
for (const { file, count } of fileConflicts) {
|
|
1572
|
-
if (count === 0)
|
|
1573
|
-
continue;
|
|
1507
|
+
const data = await response.json().catch(() => null);
|
|
1508
|
+
if (data?.resolution) {
|
|
1509
|
+
fs_1.default.writeFileSync(detail.file, data.resolution, 'utf-8');
|
|
1574
1510
|
try {
|
|
1575
|
-
|
|
1576
|
-
if (resolved) {
|
|
1577
|
-
console.log(chalk_1.default.green(` ✓ Resolved: ${file}`));
|
|
1578
|
-
(0, child_process_1.execSync)(`git add ${file}`, { encoding: 'utf-8', stdio: 'pipe' });
|
|
1579
|
-
resolvedCount++;
|
|
1580
|
-
}
|
|
1581
|
-
else {
|
|
1582
|
-
console.log(chalk_1.default.yellow(` ⚠️ ${file} needs manual review`));
|
|
1583
|
-
failedFiles.push(file);
|
|
1584
|
-
}
|
|
1511
|
+
(0, child_process_1.execSync)(`git add -- "${escapePathForShell(detail.file)}"`, { encoding: 'utf-8', stdio: 'pipe' });
|
|
1585
1512
|
}
|
|
1586
1513
|
catch {
|
|
1587
|
-
|
|
1588
|
-
failedFiles.push(file);
|
|
1514
|
+
// Ignore staging failures; user can stage manually later
|
|
1589
1515
|
}
|
|
1516
|
+
return { success: true };
|
|
1590
1517
|
}
|
|
1591
|
-
|
|
1592
|
-
// If all resolved, commit
|
|
1593
|
-
if (failedFiles.length === 0) {
|
|
1594
|
-
(0, child_process_1.execSync)('git commit --no-edit', { encoding: 'utf-8', stdio: 'pipe' });
|
|
1595
|
-
console.log(chalk_1.default.green('✅ All conflicts resolved automatically!\n'));
|
|
1596
|
-
return true;
|
|
1597
|
-
}
|
|
1598
|
-
// Some failed - show manual instructions
|
|
1599
|
-
console.log(chalk_1.default.yellow(`⚠️ ${failedFiles.length} file${failedFiles.length === 1 ? '' : 's'} need${failedFiles.length === 1 ? 's' : ''} manual resolution:\n`));
|
|
1600
|
-
failedFiles.forEach(file => {
|
|
1601
|
-
console.log(chalk_1.default.cyan(` • ${file}`));
|
|
1602
|
-
});
|
|
1603
|
-
console.log();
|
|
1604
|
-
console.log(chalk_1.default.white.bold('📖 How to resolve manually:\n'));
|
|
1605
|
-
console.log(chalk_1.default.gray(' 1. Open the conflicted files in your editor'));
|
|
1606
|
-
console.log(chalk_1.default.gray(' 2. Look for ') + chalk_1.default.yellow('<<<<<<< HEAD') + chalk_1.default.gray(' markers'));
|
|
1607
|
-
console.log(chalk_1.default.gray(' 3. Choose which version to keep (or merge both)'));
|
|
1608
|
-
console.log(chalk_1.default.gray(' 4. Remove the conflict markers ') + chalk_1.default.yellow('(<<<<<<< ======= >>>>>>>)'));
|
|
1609
|
-
console.log(chalk_1.default.gray(' 5. Save the files'));
|
|
1610
|
-
console.log(chalk_1.default.gray(' 6. Type: ') + chalk_1.default.cyan('"commit the resolved changes"') + chalk_1.default.gray(' or ') + chalk_1.default.cyan('"continue"') + '\n');
|
|
1611
|
-
console.log(chalk_1.default.white.bold('💡 Useful commands:\n'));
|
|
1612
|
-
console.log(chalk_1.default.cyan(' "show the conflicts"') + chalk_1.default.gray(' - See conflict details again'));
|
|
1613
|
-
console.log(chalk_1.default.cyan(' "abort the merge"') + chalk_1.default.gray(' - Cancel and go back'));
|
|
1614
|
-
console.log(chalk_1.default.cyan(' "use their version"') + chalk_1.default.gray(' - Accept incoming changes'));
|
|
1615
|
-
console.log(chalk_1.default.cyan(' "use my version"') + chalk_1.default.gray(' - Keep your changes\n'));
|
|
1616
|
-
return false;
|
|
1518
|
+
return { success: false, reason: 'api-error' };
|
|
1617
1519
|
}
|
|
1618
1520
|
catch (error) {
|
|
1619
|
-
console.log(chalk_1.default.
|
|
1620
|
-
|
|
1621
|
-
return false;
|
|
1521
|
+
console.log(chalk_1.default.yellow(`⚠️ AI resolution failed: ${error.message}`));
|
|
1522
|
+
return { success: false, reason: 'api-error' };
|
|
1622
1523
|
}
|
|
1623
1524
|
}
|
|
1624
|
-
|
|
1625
|
-
|
|
1626
|
-
*/
|
|
1627
|
-
async function resolveConflictWithAI(file) {
|
|
1628
|
-
try {
|
|
1629
|
-
const token = (0, auth_1.getToken)();
|
|
1630
|
-
if (!token)
|
|
1631
|
-
return false;
|
|
1632
|
-
// Get the conflicted content
|
|
1633
|
-
const conflictContent = (0, child_process_1.execSync)(`git show :1:${file} 2>/dev/null || echo ""`, { encoding: 'utf-8' }).trim();
|
|
1634
|
-
const ourContent = (0, child_process_1.execSync)(`git show :2:${file} 2>/dev/null || echo ""`, { encoding: 'utf-8' }).trim();
|
|
1635
|
-
const theirContent = (0, child_process_1.execSync)(`git show :3:${file} 2>/dev/null || echo ""`, { encoding: 'utf-8' }).trim();
|
|
1636
|
-
if (!ourContent && !theirContent)
|
|
1637
|
-
return false;
|
|
1638
|
-
// Call AI to resolve (simplified - real implementation would call Gemini API)
|
|
1639
|
-
// For now, just try simple auto-resolution
|
|
1640
|
-
const diffOutput = (0, child_process_1.execSync)(`git diff ${file}`, { encoding: 'utf-8' });
|
|
1641
|
-
// If conflict is simple (just additions, no deletions), can auto-resolve
|
|
1642
|
-
const hasDeleteConflict = diffOutput.includes('-') && diffOutput.includes('+');
|
|
1643
|
-
if (!hasDeleteConflict) {
|
|
1644
|
-
// Simple conflict - keep both
|
|
1645
|
-
(0, child_process_1.execSync)(`git checkout --ours ${file}`, { encoding: 'utf-8', stdio: 'pipe' });
|
|
1646
|
-
return true;
|
|
1647
|
-
}
|
|
1648
|
-
return false;
|
|
1649
|
-
}
|
|
1650
|
-
catch {
|
|
1651
|
-
return false;
|
|
1652
|
-
}
|
|
1525
|
+
function escapePathForShell(value) {
|
|
1526
|
+
return value.replace(/(["\\$`])/g, '\\$1');
|
|
1653
1527
|
}
|
|
1654
1528
|
/**
|
|
1655
1529
|
* Handle force push with safety confirmation
|
package/dist/commands/natural.js
CHANGED
|
@@ -16,7 +16,7 @@ const git_1 = require("../utils/git");
|
|
|
16
16
|
* Main natural language command handler - Cursor-style!
|
|
17
17
|
* Now everything works like Cursor - just say what you want!
|
|
18
18
|
*/
|
|
19
|
-
async function naturalCommand(userInput) {
|
|
19
|
+
async function naturalCommand(userInput, prompt) {
|
|
20
20
|
// Ensure authentication first
|
|
21
21
|
const authConfig = await (0, auth_1.ensureAuth)();
|
|
22
22
|
if (!authConfig) {
|
package/dist/commands/onboard.js
CHANGED
|
@@ -75,6 +75,7 @@ async function onboardCommand() {
|
|
|
75
75
|
console.log(chalk_1.default.gray('Other commands:'));
|
|
76
76
|
console.log(chalk_1.default.cyan(' snapcommit doctor ') + chalk_1.default.gray('→ Check setup'));
|
|
77
77
|
console.log(chalk_1.default.cyan(' snapcommit stats ') + chalk_1.default.gray('→ See your progress'));
|
|
78
|
+
console.log(chalk_1.default.cyan(' snap autopilot ') + chalk_1.default.gray('→ AI workflows for conflicts & releases'));
|
|
78
79
|
console.log(chalk_1.default.cyan(' snapcommit --help ') + chalk_1.default.gray('→ All commands'));
|
|
79
80
|
console.log();
|
|
80
81
|
console.log(chalk_1.default.yellow.bold('💡 Pro tip: ') + chalk_1.default.white('Use') + chalk_1.default.cyan(' bq ') + chalk_1.default.white('for instant commits!'));
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.telemetryStatusCommand = telemetryStatusCommand;
|
|
7
|
+
exports.telemetryEnableCommand = telemetryEnableCommand;
|
|
8
|
+
exports.telemetryDisableCommand = telemetryDisableCommand;
|
|
9
|
+
const chalk_1 = __importDefault(require("chalk"));
|
|
10
|
+
const settings_1 = require("../utils/settings");
|
|
11
|
+
const telemetry_1 = require("../utils/telemetry");
|
|
12
|
+
async function telemetryStatusCommand() {
|
|
13
|
+
const settings = (0, settings_1.getSettings)();
|
|
14
|
+
console.log(chalk_1.default.bold('\nTelemetry Status:\n'));
|
|
15
|
+
console.log(chalk_1.default.white(` Enabled: ${(0, telemetry_1.isTelemetryEnabled)() ? chalk_1.default.green('yes') : chalk_1.default.red('no')}`));
|
|
16
|
+
if (settings.telemetryPromptedAt) {
|
|
17
|
+
const prompted = new Date(settings.telemetryPromptedAt).toLocaleString();
|
|
18
|
+
console.log(chalk_1.default.gray(` Last prompted: ${prompted}`));
|
|
19
|
+
}
|
|
20
|
+
else {
|
|
21
|
+
console.log(chalk_1.default.gray(' Last prompted: never'));
|
|
22
|
+
}
|
|
23
|
+
console.log('\nPrivacy: Data is anonymous diagnostics and usage patterns only. No repo contents or secrets.\n');
|
|
24
|
+
}
|
|
25
|
+
async function telemetryEnableCommand() {
|
|
26
|
+
if ((0, telemetry_1.isTelemetryEnabled)()) {
|
|
27
|
+
console.log(chalk_1.default.green('\nTelemetry is already enabled. Thank you! 🙌\n'));
|
|
28
|
+
return;
|
|
29
|
+
}
|
|
30
|
+
(0, telemetry_1.setTelemetryEnabled)(true);
|
|
31
|
+
(0, telemetry_1.markTelemetryPrompted)();
|
|
32
|
+
(0, telemetry_1.recordTelemetry)('telemetry_enabled');
|
|
33
|
+
console.log(chalk_1.default.green('\n✅ Telemetry enabled. Thanks for helping us make SnapCommit smarter.\n'));
|
|
34
|
+
}
|
|
35
|
+
async function telemetryDisableCommand() {
|
|
36
|
+
if (!(0, telemetry_1.isTelemetryEnabled)()) {
|
|
37
|
+
console.log(chalk_1.default.yellow('\nTelemetry is already disabled.\n'));
|
|
38
|
+
return;
|
|
39
|
+
}
|
|
40
|
+
(0, telemetry_1.setTelemetryEnabled)(false);
|
|
41
|
+
(0, settings_1.updateSettings)({ telemetryPromptedAt: Date.now() });
|
|
42
|
+
(0, telemetry_1.recordTelemetry)('telemetry_disabled');
|
|
43
|
+
console.log(chalk_1.default.green('\n✅ Telemetry disabled. You can re-enable anytime with `snap telemetry enable`.\n'));
|
|
44
|
+
}
|