@snapcommit/cli 2.0.5 → 2.2.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.
@@ -1,8 +1,41 @@
1
1
  "use strict";
2
2
  /**
3
3
  * Cursor-style natural language Git assistant
4
- * Shows preview, asks confirmation, handles errors automatically
4
+ * Fast, simple, auto-fixes errors, handles ALL Git + GitHub operations
5
5
  */
6
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
7
+ if (k2 === undefined) k2 = k;
8
+ var desc = Object.getOwnPropertyDescriptor(m, k);
9
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
10
+ desc = { enumerable: true, get: function() { return m[k]; } };
11
+ }
12
+ Object.defineProperty(o, k2, desc);
13
+ }) : (function(o, m, k, k2) {
14
+ if (k2 === undefined) k2 = k;
15
+ o[k2] = m[k];
16
+ }));
17
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
18
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
19
+ }) : function(o, v) {
20
+ o["default"] = v;
21
+ });
22
+ var __importStar = (this && this.__importStar) || (function () {
23
+ var ownKeys = function(o) {
24
+ ownKeys = Object.getOwnPropertyNames || function (o) {
25
+ var ar = [];
26
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
27
+ return ar;
28
+ };
29
+ return ownKeys(o);
30
+ };
31
+ return function (mod) {
32
+ if (mod && mod.__esModule) return mod;
33
+ var result = {};
34
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
35
+ __setModuleDefault(result, mod);
36
+ return result;
37
+ };
38
+ })();
6
39
  var __importDefault = (this && this.__importDefault) || function (mod) {
7
40
  return (mod && mod.__esModule) ? mod : { "default": mod };
8
41
  };
@@ -10,135 +43,216 @@ Object.defineProperty(exports, "__esModule", { value: true });
10
43
  exports.executeCursorStyle = executeCursorStyle;
11
44
  const chalk_1 = __importDefault(require("chalk"));
12
45
  const child_process_1 = require("child_process");
13
- const readline_1 = __importDefault(require("readline"));
14
46
  const git_1 = require("../utils/git");
15
47
  const auth_1 = require("../lib/auth");
48
+ const github_connect_1 = require("./github-connect");
49
+ const github = __importStar(require("../lib/github"));
16
50
  async function executeCursorStyle(userInput) {
17
51
  if (!(0, git_1.isGitRepo)()) {
18
52
  console.log(chalk_1.default.red('\n❌ Not a git repository\n'));
19
53
  return;
20
54
  }
21
- // Get AI interpretation of the request
22
- console.log(chalk_1.default.blue('\n✨ Understanding your request...\n'));
23
- const plan = await interpretRequest(userInput);
24
- if (!plan) {
25
- console.log(chalk_1.default.red('❌ Could not understand the request\n'));
55
+ const input = userInput.toLowerCase().trim();
56
+ // FAST PATH: Simple commit/push (no AI needed)
57
+ if (input.includes('commit') || input.includes('push') || input.includes('save')) {
58
+ await handleCommitAndPush();
26
59
  return;
27
60
  }
28
- // Show the execution plan
29
- console.log(chalk_1.default.bold('📋 Here\'s what I\'ll do:\n'));
30
- plan.actions.forEach((action, i) => {
31
- console.log(chalk_1.default.gray(` ${i + 1}. `) + chalk_1.default.white(action.description));
32
- });
33
- console.log();
34
- // If commit is involved, show the AI-generated message
35
- if (plan.commitMessage) {
36
- console.log(chalk_1.default.gray('📝 Commit message: ') + chalk_1.default.cyan(`"${plan.commitMessage}"`));
37
- console.log();
61
+ // COMPLEX PATH: Use AI for everything else (branch, merge, rebase, undo, GitHub, etc.)
62
+ await handleComplexCommand(userInput);
63
+ }
64
+ /**
65
+ * Fast path: Commit and push (no AI interpretation needed)
66
+ */
67
+ async function handleCommitAndPush() {
68
+ const status = (0, git_1.getGitStatus)();
69
+ const hasChanges = status.staged > 0 || status.unstaged > 0 || status.untracked > 0;
70
+ if (!hasChanges) {
71
+ console.log(chalk_1.default.gray('\n✓ Branch clean\n'));
72
+ return;
38
73
  }
39
- // Ask for confirmation (natural language!)
40
- const needsConfirm = plan.actions.some(a => a.requiresConfirmation);
41
- if (needsConfirm) {
42
- const confirmPrompt = plan.commitMessage
43
- ? chalk_1.default.cyan('What next? ') + chalk_1.default.gray('(') + chalk_1.default.white('continue') + chalk_1.default.gray(' • ') + chalk_1.default.white('cancel') + chalk_1.default.gray(' • ') + chalk_1.default.white('edit message') + chalk_1.default.gray('): ')
44
- : chalk_1.default.cyan('What next? ') + chalk_1.default.gray('(') + chalk_1.default.white('continue') + chalk_1.default.gray(' • ') + chalk_1.default.white('cancel') + chalk_1.default.gray('): ');
45
- const answer = await askQuestion(confirmPrompt);
46
- const input = answer.toLowerCase().trim();
47
- // Cancel commands
48
- if (input === 'cancel' || input === 'no' || input === 'n' || input === 'stop' || input === 'abort') {
49
- console.log(chalk_1.default.gray('\n✗ Cancelled\n'));
50
- return;
74
+ // Stage all
75
+ try {
76
+ (0, git_1.stageAllChanges)();
77
+ }
78
+ catch (error) {
79
+ console.log(chalk_1.default.red(`❌ ${error.message}\n`));
80
+ return;
81
+ }
82
+ // Generate AI commit message
83
+ const diff = (0, git_1.getGitDiff)(true);
84
+ const commitMessage = await generateCommitMessage(diff);
85
+ // Commit
86
+ try {
87
+ (0, child_process_1.execSync)(`git commit -m "${commitMessage.replace(/"/g, '\\"')}"`, { encoding: 'utf-8', stdio: 'pipe' });
88
+ console.log(chalk_1.default.green(`✓ ${commitMessage.split('\n')[0]}`));
89
+ }
90
+ catch (error) {
91
+ console.log(chalk_1.default.red(`❌ Commit failed\n`));
92
+ return;
93
+ }
94
+ // Push
95
+ try {
96
+ (0, child_process_1.execSync)('git push', { encoding: 'utf-8', stdio: 'pipe' });
97
+ console.log(chalk_1.default.green('✓ Pushed\n'));
98
+ }
99
+ catch (error) {
100
+ if (error.message.includes('no configured push destination')) {
101
+ console.log(chalk_1.default.gray('✓ Committed locally (no remote)\n'));
51
102
  }
52
- // Edit commands
53
- if (plan.commitMessage && (input === 'edit' || input === 'e' || input === 'change' || input === 'edit message')) {
54
- plan.commitMessage = await editMessage(plan.commitMessage);
103
+ else {
104
+ console.log(chalk_1.default.yellow(`⚠️ ${error.message}\n`));
55
105
  }
56
- // Everything else (including "continue", "yes", "go ahead", "do it", "y", or just Enter) → proceed
57
106
  }
58
- // Execute the plan
59
- console.log(chalk_1.default.blue('\n⚙️ Executing...\n'));
60
- for (const action of plan.actions) {
107
+ }
108
+ /**
109
+ * Complex path: Use AI to interpret and execute any Git/GitHub command
110
+ */
111
+ async function handleComplexCommand(userInput) {
112
+ const token = (0, auth_1.getToken)();
113
+ if (!token) {
114
+ console.log(chalk_1.default.red('❌ Not authenticated\n'));
115
+ return;
116
+ }
117
+ // Get AI interpretation
118
+ const intent = await getAIInterpretation(userInput, token);
119
+ if (!intent) {
120
+ console.log(chalk_1.default.red('❌ Could not understand command\n'));
121
+ return;
122
+ }
123
+ // Execute based on type
124
+ if (intent.type === 'git') {
125
+ await executeGitCommands(intent.gitCommands || []);
126
+ }
127
+ else if (intent.type === 'github') {
128
+ await executeGitHubCommand(intent);
129
+ }
130
+ console.log(chalk_1.default.green('✓ Done\n'));
131
+ }
132
+ /**
133
+ * Execute Git commands with auto-retry on errors
134
+ */
135
+ async function executeGitCommands(commands) {
136
+ for (const cmd of commands) {
61
137
  try {
62
- await executeAction(action, plan.commitMessage);
138
+ (0, child_process_1.execSync)(cmd, { encoding: 'utf-8', stdio: 'pipe' });
63
139
  }
64
140
  catch (error) {
65
141
  // Try to auto-fix common errors
66
- const fixed = await tryAutoFix(error, action);
142
+ const fixed = await tryAutoFix(error, cmd);
67
143
  if (!fixed) {
68
- console.log(chalk_1.default.red(`\n❌ Error: ${error.message}\n`));
69
- console.log(chalk_1.default.yellow('💡 Try: "undo last change" to revert\n'));
144
+ console.log(chalk_1.default.red(`❌ ${error.message}\n`));
70
145
  return;
71
146
  }
72
147
  }
73
148
  }
74
- console.log(chalk_1.default.green('\n✅ All done!\n'));
75
149
  }
76
- async function interpretRequest(userInput) {
77
- // This will call the backend AI to interpret the request
78
- // For now, let's handle some common patterns
79
- const input = userInput.toLowerCase();
80
- const actions = [];
81
- let commitMessage;
82
- // "commit and push" pattern
83
- if (input.includes('commit') && input.includes('push')) {
84
- // Generate commit message
85
- const diff = await getChangeDiff();
86
- if (diff) {
87
- commitMessage = await generateCommitMessage(diff);
150
+ /**
151
+ * Execute GitHub operations (PRs, CI, issues)
152
+ */
153
+ async function executeGitHubCommand(intent) {
154
+ if (!(0, github_connect_1.isGitHubConnected)()) {
155
+ console.log(chalk_1.default.yellow('⚠️ GitHub not connected. Run: snap github connect\n'));
156
+ return;
157
+ }
158
+ try {
159
+ switch (intent.action) {
160
+ case 'pr_create':
161
+ console.log(chalk_1.default.blue('Creating PR...'));
162
+ const pr = await github.createPullRequest(intent.options || {});
163
+ console.log(chalk_1.default.green(`✓ PR created: ${pr.html_url}`));
164
+ break;
165
+ case 'pr_list':
166
+ console.log(chalk_1.default.blue('Listing PRs...'));
167
+ const prs = await github.listPullRequests({ state: 'open', limit: 10 });
168
+ if (prs.length === 0) {
169
+ console.log(chalk_1.default.gray('No open PRs'));
170
+ }
171
+ else {
172
+ prs.forEach((p) => console.log(chalk_1.default.cyan(` #${p.number}: ${p.title}`)));
173
+ }
174
+ break;
175
+ default:
176
+ console.log(chalk_1.default.yellow(`⚠️ GitHub action not implemented: ${intent.action}\n`));
177
+ }
178
+ }
179
+ catch (error) {
180
+ console.log(chalk_1.default.red(`❌ ${error.message}\n`));
181
+ }
182
+ }
183
+ /**
184
+ * Auto-fix common Git errors (like Cursor does)
185
+ */
186
+ async function tryAutoFix(error, command) {
187
+ const errorMsg = error.message?.toLowerCase() || '';
188
+ // Merge conflict
189
+ if (errorMsg.includes('conflict')) {
190
+ console.log(chalk_1.default.yellow('⚠️ Merge conflict - attempting auto-resolve...'));
191
+ try {
192
+ // Try to accept current changes
193
+ (0, child_process_1.execSync)('git add .', { encoding: 'utf-8', stdio: 'pipe' });
194
+ (0, child_process_1.execSync)('git commit --no-edit', { encoding: 'utf-8', stdio: 'pipe' });
195
+ console.log(chalk_1.default.green('✓ Auto-resolved'));
196
+ return true;
197
+ }
198
+ catch {
199
+ return false;
200
+ }
201
+ }
202
+ // Diverged branches
203
+ if (errorMsg.includes('diverged') || errorMsg.includes('non-fast-forward')) {
204
+ console.log(chalk_1.default.yellow('⚠️ Branches diverged - pulling and retrying...'));
205
+ try {
206
+ (0, child_process_1.execSync)('git pull --rebase', { encoding: 'utf-8', stdio: 'pipe' });
207
+ (0, child_process_1.execSync)(command, { encoding: 'utf-8', stdio: 'pipe' });
208
+ console.log(chalk_1.default.green('✓ Fixed'));
209
+ return true;
210
+ }
211
+ catch {
212
+ return false;
88
213
  }
89
- actions.push({
90
- type: 'stage',
91
- description: 'Stage all changes',
92
- requiresConfirmation: false,
93
- });
94
- actions.push({
95
- type: 'commit',
96
- description: `Commit with message: "${commitMessage || 'Update'}"`,
97
- requiresConfirmation: true,
98
- requiresCommitMessage: true,
99
- });
100
- actions.push({
101
- type: 'push',
102
- description: 'Push to origin',
103
- command: 'git push',
104
- requiresConfirmation: true,
105
- });
106
- return {
107
- actions,
108
- commitMessage,
109
- explanation: 'Stage all changes, commit with AI-generated message, and push to remote',
110
- };
111
214
  }
112
- // "commit" only
113
- if (input.includes('commit') && !input.includes('push')) {
114
- const diff = await getChangeDiff();
115
- if (diff) {
116
- commitMessage = await generateCommitMessage(diff);
215
+ // Unstaged changes
216
+ if (errorMsg.includes('unstaged') || errorMsg.includes('uncommitted')) {
217
+ console.log(chalk_1.default.yellow('⚠️ Unstaged changes - stashing and retrying...'));
218
+ try {
219
+ (0, child_process_1.execSync)('git stash', { encoding: 'utf-8', stdio: 'pipe' });
220
+ (0, child_process_1.execSync)(command, { encoding: 'utf-8', stdio: 'pipe' });
221
+ (0, child_process_1.execSync)('git stash pop', { encoding: 'utf-8', stdio: 'pipe' });
222
+ console.log(chalk_1.default.green('✓ Fixed'));
223
+ return true;
224
+ }
225
+ catch {
226
+ return false;
117
227
  }
118
- actions.push({
119
- type: 'stage',
120
- description: 'Stage all changes',
121
- requiresConfirmation: false,
122
- });
123
- actions.push({
124
- type: 'commit',
125
- description: `Commit with message: "${commitMessage || 'Update'}"`,
126
- requiresConfirmation: true,
127
- requiresCommitMessage: true,
128
- });
129
- return {
130
- actions,
131
- commitMessage,
132
- explanation: 'Stage and commit changes with AI-generated message',
133
- };
134
228
  }
135
- // Let the AI handle complex requests
136
- return await getAIInterpretation(userInput);
229
+ return false;
137
230
  }
138
- async function getChangeDiff() {
231
+ /**
232
+ * Get AI interpretation for complex commands
233
+ */
234
+ async function getAIInterpretation(userInput, token) {
139
235
  try {
140
- (0, git_1.stageAllChanges)();
141
- return (0, git_1.getGitDiff)(true);
236
+ const currentBranch = (0, git_1.getCurrentBranch)();
237
+ const status = (0, git_1.getGitStatus)();
238
+ const response = await fetch('https://snapcommit.dev/api/ai/interpret', {
239
+ method: 'POST',
240
+ headers: { 'Content-Type': 'application/json' },
241
+ body: JSON.stringify({
242
+ userInput,
243
+ token,
244
+ context: {
245
+ currentBranch,
246
+ hasUncommittedChanges: status.unstaged > 0 || status.untracked > 0,
247
+ lastCommitHash: (0, child_process_1.execSync)('git log -1 --format=%H 2>/dev/null || echo ""', { encoding: 'utf-8' }).trim(),
248
+ remoteBranch: (0, child_process_1.execSync)('git rev-parse --abbrev-ref @{upstream} 2>/dev/null || echo ""', { encoding: 'utf-8' }).trim(),
249
+ },
250
+ }),
251
+ });
252
+ if (!response.ok)
253
+ return null;
254
+ const data = await response.json();
255
+ return data.intent;
142
256
  }
143
257
  catch {
144
258
  return null;
@@ -176,141 +290,4 @@ async function generateCommitMessage(diff) {
176
290
  return 'Update changes';
177
291
  }
178
292
  }
179
- async function getAIInterpretation(userInput) {
180
- const token = (0, auth_1.getToken)();
181
- if (!token)
182
- return null;
183
- try {
184
- const currentBranch = (0, git_1.getCurrentBranch)();
185
- const status = (0, git_1.getGitStatus)();
186
- const response = await fetch('https://snapcommit.dev/api/ai/interpret', {
187
- method: 'POST',
188
- headers: { 'Content-Type': 'application/json' },
189
- body: JSON.stringify({
190
- userInput,
191
- token,
192
- context: {
193
- currentBranch,
194
- hasUncommittedChanges: status.unstaged > 0 || status.untracked > 0,
195
- lastCommitHash: (0, child_process_1.execSync)('git log -1 --format=%H 2>/dev/null || echo ""', { encoding: 'utf-8' }).trim(),
196
- remoteBranch: (0, child_process_1.execSync)('git rev-parse --abbrev-ref @{upstream} 2>/dev/null || echo ""', { encoding: 'utf-8' }).trim(),
197
- },
198
- }),
199
- });
200
- const data = await response.json();
201
- // Convert AI intent to execution plan
202
- // This is a simplified version - in production, this would be more sophisticated
203
- return convertIntentToPlan(data.intent);
204
- }
205
- catch {
206
- return null;
207
- }
208
- }
209
- function convertIntentToPlan(intent) {
210
- // Convert the AI intent format to our execution plan format
211
- // This bridges the existing intent format with the new Cursor-style execution
212
- if (!intent || !intent.gitCommands)
213
- return null;
214
- const actions = intent.gitCommands.map((cmd, i) => ({
215
- type: inferActionType(cmd),
216
- description: intent.explanation || `Execute: ${cmd}`,
217
- command: cmd,
218
- requiresConfirmation: intent.needsConfirmation || intent.riskLevel !== 'safe',
219
- }));
220
- return {
221
- actions,
222
- explanation: intent.explanation,
223
- };
224
- }
225
- function inferActionType(command) {
226
- if (command.includes('git add'))
227
- return 'stage';
228
- if (command.includes('git commit'))
229
- return 'commit';
230
- if (command.includes('git push'))
231
- return 'push';
232
- if (command.includes('git checkout') || command.includes('git switch'))
233
- return 'checkout';
234
- if (command.includes('git merge'))
235
- return 'merge';
236
- if (command.includes('git reset'))
237
- return 'reset';
238
- if (command.includes('git branch'))
239
- return 'branch';
240
- if (command.includes('git pull'))
241
- return 'pull';
242
- if (command.includes('git rebase'))
243
- return 'rebase';
244
- if (command.includes('git cherry-pick'))
245
- return 'cherry-pick';
246
- return 'stage';
247
- }
248
- async function executeAction(action, commitMessage) {
249
- switch (action.type) {
250
- case 'stage':
251
- (0, git_1.stageAllChanges)();
252
- console.log(chalk_1.default.green(' ✓ Staged all changes'));
253
- break;
254
- case 'commit':
255
- if (commitMessage) {
256
- (0, child_process_1.execSync)(`git commit -m "${commitMessage.replace(/"/g, '\\"')}"`, { encoding: 'utf-8' });
257
- console.log(chalk_1.default.green(` ✓ Committed`));
258
- }
259
- break;
260
- case 'push':
261
- (0, child_process_1.execSync)('git push', { encoding: 'utf-8', stdio: 'pipe' });
262
- console.log(chalk_1.default.green(' ✓ Pushed to remote'));
263
- break;
264
- default:
265
- if (action.command) {
266
- (0, child_process_1.execSync)(action.command, { encoding: 'utf-8', stdio: 'pipe' });
267
- console.log(chalk_1.default.green(` ✓ ${action.description}`));
268
- }
269
- break;
270
- }
271
- }
272
- async function tryAutoFix(error, action) {
273
- const errorMsg = error.message?.toLowerCase() || '';
274
- // Handle "no remote configured"
275
- if (errorMsg.includes('no configured push destination')) {
276
- console.log(chalk_1.default.yellow('\n⚠️ No remote repository configured'));
277
- console.log(chalk_1.default.gray(' You need to add a remote first\n'));
278
- return false;
279
- }
280
- // Handle merge conflicts
281
- if (errorMsg.includes('conflict')) {
282
- console.log(chalk_1.default.yellow('\n⚠️ Merge conflict detected'));
283
- console.log(chalk_1.default.gray(' Use: "resolve conflicts" to fix\n'));
284
- return false;
285
- }
286
- // Handle auth failures
287
- if (errorMsg.includes('authentication') || errorMsg.includes('permission denied')) {
288
- console.log(chalk_1.default.yellow('\n⚠️ Authentication failed'));
289
- console.log(chalk_1.default.gray(' Check your Git credentials\n'));
290
- return false;
291
- }
292
- return false;
293
- }
294
- function askQuestion(query) {
295
- const rl = readline_1.default.createInterface({
296
- input: process.stdin,
297
- output: process.stdout,
298
- terminal: false, // Don't process terminal escape sequences
299
- });
300
- return new Promise((resolve) => {
301
- // Write prompt manually
302
- process.stdout.write(query);
303
- // Listen for ONE line of input
304
- rl.once('line', (answer) => {
305
- rl.close();
306
- resolve(answer.trim());
307
- });
308
- });
309
- }
310
- async function editMessage(originalMessage) {
311
- console.log(chalk_1.default.cyan('\n✏️ Edit commit message:\n'));
312
- console.log(chalk_1.default.gray(' (Press Enter to keep, or type new message)\n'));
313
- console.log(chalk_1.default.white(` Current: ${chalk_1.default.cyan(originalMessage)}\n`));
314
- const newMessage = await askQuestion(chalk_1.default.yellow(' New message: '));
315
- return newMessage.trim() || originalMessage;
316
- }
293
+ // All complex logic removed - keeping it DEAD SIMPLE!
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@snapcommit/cli",
3
- "version": "2.0.5",
3
+ "version": "2.2.0",
4
4
  "description": "Instant AI commits. Beautiful progress tracking. Never write commit messages again.",
5
5
  "main": "dist/index.js",
6
6
  "bin": {