@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.
@@ -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
- console.log(chalk_1.default.yellow('\n⚠️ Merge conflicts detected!\n'));
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
- console.log(chalk_1.default.red('\n❌ Merge conflict detected!\n'));
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
- // Get list of conflicted files
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
- if (!conflictedFilesOutput) {
1547
- // No conflicted files found, might be a different error
1548
- return false;
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
- const conflictedFiles = conflictedFilesOutput.split('\n').filter(f => f.trim());
1551
- console.log(chalk_1.default.yellow('📋 Conflicted files:\n'));
1552
- // Count conflicts per file
1553
- const fileConflicts = [];
1554
- for (const file of conflictedFiles) {
1555
- try {
1556
- const diffOutput = (0, child_process_1.execSync)(`git diff ${file}`, { encoding: 'utf-8', stdio: 'pipe' });
1557
- const conflictCount = (diffOutput.match(/^<<<<<<< HEAD/gm) || []).length;
1558
- fileConflicts.push({ file, count: conflictCount });
1559
- console.log(chalk_1.default.cyan(` • ${file}`) + chalk_1.default.gray(` (${conflictCount} conflict${conflictCount === 1 ? '' : 's'})`));
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
- catch {
1562
- fileConflicts.push({ file, count: 0 });
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
- console.log();
1567
- // Try AI resolution
1568
- console.log(chalk_1.default.blue('🤖 Attempting AI resolution...\n'));
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
- const resolved = await resolveConflictWithAI(file);
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
- console.log(chalk_1.default.yellow(` ⚠️ ${file} needs manual review`));
1588
- failedFiles.push(file);
1514
+ // Ignore staging failures; user can stage manually later
1589
1515
  }
1516
+ return { success: true };
1590
1517
  }
1591
- console.log();
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.red('❌ Could not analyze conflicts'));
1620
- console.log(chalk_1.default.gray(` ${error.message}\n`));
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
- * Resolve conflict with AI (calls Gemini to analyze and resolve)
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
@@ -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) {
@@ -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
+ }