@snapcommit/cli 3.1.0 ā 3.3.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/cursor-style.js +140 -39
- package/dist/lib/github.js +37 -0
- package/dist/repl.js +52 -1
- package/package.json +1 -1
|
@@ -140,38 +140,100 @@ async function executeCommitWithAI(intent) {
|
|
|
140
140
|
}
|
|
141
141
|
// Count files
|
|
142
142
|
let fileCount = status.staged + status.unstaged + status.untracked;
|
|
143
|
-
//
|
|
144
|
-
|
|
143
|
+
// Get all changed files with details
|
|
144
|
+
let changedFiles = [];
|
|
145
145
|
try {
|
|
146
146
|
const output = (0, child_process_1.execSync)('git status --short', { encoding: 'utf-8' });
|
|
147
|
-
const lines = output.split('\n').filter(line => line.trim())
|
|
148
|
-
lines.
|
|
149
|
-
const
|
|
147
|
+
const lines = output.split('\n').filter(line => line.trim());
|
|
148
|
+
changedFiles = lines.map(line => {
|
|
149
|
+
const fileStatus = line.substring(0, 2);
|
|
150
150
|
const file = line.substring(3);
|
|
151
|
-
|
|
152
|
-
|
|
151
|
+
let symbol = 'ā';
|
|
152
|
+
let color = chalk_1.default.yellow;
|
|
153
|
+
if (fileStatus.includes('M')) {
|
|
154
|
+
symbol = 'ā';
|
|
155
|
+
color = chalk_1.default.yellow;
|
|
153
156
|
}
|
|
154
|
-
else if (
|
|
155
|
-
|
|
157
|
+
else if (fileStatus.includes('?')) {
|
|
158
|
+
symbol = '+';
|
|
159
|
+
color = chalk_1.default.green;
|
|
156
160
|
}
|
|
157
|
-
else if (
|
|
158
|
-
|
|
161
|
+
else if (fileStatus.includes('D')) {
|
|
162
|
+
symbol = '-';
|
|
163
|
+
color = chalk_1.default.red;
|
|
159
164
|
}
|
|
165
|
+
return { file, status: fileStatus, symbol, color };
|
|
160
166
|
});
|
|
161
|
-
if (fileCount > 10) {
|
|
162
|
-
console.log(chalk_1.default.gray(` ... and ${fileCount - 10} more`));
|
|
163
|
-
}
|
|
164
167
|
}
|
|
165
168
|
catch {
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
169
|
+
// Fallback to staging all
|
|
170
|
+
try {
|
|
171
|
+
(0, git_1.stageAllChanges)();
|
|
172
|
+
}
|
|
173
|
+
catch (error) {
|
|
174
|
+
console.log(chalk_1.default.red(`ā ${error.message}\n`));
|
|
175
|
+
return;
|
|
176
|
+
}
|
|
170
177
|
}
|
|
178
|
+
// Show interactive file selection
|
|
179
|
+
console.log(chalk_1.default.blue(`\nš¦ Changes (${fileCount} ${fileCount === 1 ? 'file' : 'files'}):\n`));
|
|
180
|
+
changedFiles.forEach((item, index) => {
|
|
181
|
+
console.log(chalk_1.default.gray(` ${index + 1}. `) + item.color(`${item.symbol} ${item.file}`));
|
|
182
|
+
});
|
|
171
183
|
console.log();
|
|
172
|
-
//
|
|
184
|
+
// Interactive file selection
|
|
185
|
+
const rlModule = await Promise.resolve().then(() => __importStar(require('readline')));
|
|
186
|
+
const rlFileSelect = rlModule.createInterface({
|
|
187
|
+
input: process.stdin,
|
|
188
|
+
output: process.stdout,
|
|
189
|
+
});
|
|
190
|
+
const fileSelection = await new Promise((resolve) => {
|
|
191
|
+
rlFileSelect.question(chalk_1.default.cyan('Select files: ') +
|
|
192
|
+
chalk_1.default.gray('(Enter=all, 1-3=specific, 1,3,5=multiple): '), (ans) => {
|
|
193
|
+
rlFileSelect.close();
|
|
194
|
+
resolve(ans.trim());
|
|
195
|
+
});
|
|
196
|
+
});
|
|
197
|
+
// Process selection
|
|
198
|
+
let filesToStage = [];
|
|
199
|
+
if (!fileSelection || fileSelection.toLowerCase() === 'all' || fileSelection.toLowerCase() === 'a') {
|
|
200
|
+
// Stage all files
|
|
201
|
+
filesToStage = changedFiles.map(f => f.file);
|
|
202
|
+
}
|
|
203
|
+
else {
|
|
204
|
+
// Parse selection (e.g., "1,3,5" or "1-3")
|
|
205
|
+
const selections = fileSelection.split(',').map(s => s.trim());
|
|
206
|
+
const selectedIndices = new Set();
|
|
207
|
+
for (const sel of selections) {
|
|
208
|
+
if (sel.includes('-')) {
|
|
209
|
+
// Range (e.g., "1-3")
|
|
210
|
+
const [start, end] = sel.split('-').map(n => parseInt(n.trim()));
|
|
211
|
+
for (let i = start; i <= end; i++) {
|
|
212
|
+
if (i >= 1 && i <= changedFiles.length) {
|
|
213
|
+
selectedIndices.add(i - 1);
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
}
|
|
217
|
+
else {
|
|
218
|
+
// Single number
|
|
219
|
+
const index = parseInt(sel);
|
|
220
|
+
if (index >= 1 && index <= changedFiles.length) {
|
|
221
|
+
selectedIndices.add(index - 1);
|
|
222
|
+
}
|
|
223
|
+
}
|
|
224
|
+
}
|
|
225
|
+
filesToStage = Array.from(selectedIndices).map(i => changedFiles[i].file);
|
|
226
|
+
}
|
|
227
|
+
// Stage selected files
|
|
228
|
+
if (filesToStage.length === 0) {
|
|
229
|
+
console.log(chalk_1.default.gray('\nā No files selected\n'));
|
|
230
|
+
return;
|
|
231
|
+
}
|
|
173
232
|
try {
|
|
174
|
-
(
|
|
233
|
+
for (const file of filesToStage) {
|
|
234
|
+
(0, child_process_1.execSync)(`git add "${file}"`, { encoding: 'utf-8', stdio: 'pipe' });
|
|
235
|
+
}
|
|
236
|
+
console.log(chalk_1.default.gray(`\nā Staged ${filesToStage.length} ${filesToStage.length === 1 ? 'file' : 'files'}\n`));
|
|
175
237
|
}
|
|
176
238
|
catch (error) {
|
|
177
239
|
console.log(chalk_1.default.red(`ā ${error.message}\n`));
|
|
@@ -184,14 +246,14 @@ async function executeCommitWithAI(intent) {
|
|
|
184
246
|
console.log(chalk_1.default.cyan('š¤ ') + chalk_1.default.white.bold(commitMessage.split('\n')[0]));
|
|
185
247
|
console.log();
|
|
186
248
|
// ONE PROMPT - like Cursor's commit button
|
|
187
|
-
const
|
|
188
|
-
const
|
|
249
|
+
const rlModule2 = await Promise.resolve().then(() => __importStar(require('readline')));
|
|
250
|
+
const rlCommit = rlModule2.createInterface({
|
|
189
251
|
input: process.stdin,
|
|
190
252
|
output: process.stdout,
|
|
191
253
|
});
|
|
192
254
|
const response = await new Promise((resolve) => {
|
|
193
|
-
|
|
194
|
-
|
|
255
|
+
rlCommit.question(chalk_1.default.gray('ā Press Enter to commit, or edit message: '), (ans) => {
|
|
256
|
+
rlCommit.close();
|
|
195
257
|
resolve(ans.trim());
|
|
196
258
|
});
|
|
197
259
|
});
|
|
@@ -203,6 +265,23 @@ async function executeCommitWithAI(intent) {
|
|
|
203
265
|
try {
|
|
204
266
|
(0, child_process_1.execSync)(`git commit -m "${commitMessage.replace(/"/g, '\\"')}"`, { encoding: 'utf-8', stdio: 'pipe' });
|
|
205
267
|
console.log(chalk_1.default.green(`\nā Committed`));
|
|
268
|
+
// Log commit stats
|
|
269
|
+
try {
|
|
270
|
+
const { logCommit } = await Promise.resolve().then(() => __importStar(require('../db/database')));
|
|
271
|
+
const commitHash = (0, child_process_1.execSync)('git rev-parse HEAD', { encoding: 'utf-8' }).trim();
|
|
272
|
+
const stats = (0, child_process_1.execSync)(`git diff HEAD~1 HEAD --numstat | awk '{inserted+=$1; deleted+=$2} END {print inserted" "deleted}'`, { encoding: 'utf-8' }).trim().split(' ');
|
|
273
|
+
logCommit({
|
|
274
|
+
message: commitMessage,
|
|
275
|
+
hash: commitHash,
|
|
276
|
+
files_changed: filesToStage.length,
|
|
277
|
+
insertions: parseInt(stats[0]) || 0,
|
|
278
|
+
deletions: parseInt(stats[1]) || 0,
|
|
279
|
+
timestamp: Date.now(),
|
|
280
|
+
});
|
|
281
|
+
}
|
|
282
|
+
catch {
|
|
283
|
+
// Silent fail - don't break user experience
|
|
284
|
+
}
|
|
206
285
|
}
|
|
207
286
|
catch (error) {
|
|
208
287
|
console.log(chalk_1.default.red(`\nā Commit failed: ${error.message}\n`));
|
|
@@ -292,10 +371,14 @@ async function executeGitHubCommand(intent) {
|
|
|
292
371
|
}
|
|
293
372
|
break;
|
|
294
373
|
case 'pr_merge':
|
|
295
|
-
|
|
374
|
+
let prNumber = intent.target || intent.options?.number;
|
|
375
|
+
// Smart PR detection: "merge my PR" / "merge this PR"
|
|
296
376
|
if (!prNumber) {
|
|
297
|
-
|
|
298
|
-
|
|
377
|
+
prNumber = await github.findPRNumber('current');
|
|
378
|
+
if (!prNumber) {
|
|
379
|
+
console.log(chalk_1.default.red('\nā No PR found for current branch\n'));
|
|
380
|
+
return;
|
|
381
|
+
}
|
|
299
382
|
}
|
|
300
383
|
console.log(chalk_1.default.blue(`\nš Merging PR #${prNumber}...`));
|
|
301
384
|
await github.mergePullRequest(prNumber);
|
|
@@ -362,10 +445,16 @@ async function executeGitHubCommand(intent) {
|
|
|
362
445
|
console.log(chalk_1.default.green(`ā Issue #${reopenIssueNum} reopened\n`));
|
|
363
446
|
break;
|
|
364
447
|
case 'pr_review':
|
|
365
|
-
|
|
448
|
+
let reviewPrNum = intent.target || intent.options?.number;
|
|
449
|
+
// Smart PR detection: "approve this PR" / "approve my PR"
|
|
366
450
|
if (!reviewPrNum) {
|
|
367
|
-
|
|
368
|
-
|
|
451
|
+
const context = intent.options?.context || 'current';
|
|
452
|
+
reviewPrNum = await github.findPRNumber(context);
|
|
453
|
+
if (!reviewPrNum) {
|
|
454
|
+
console.log(chalk_1.default.red('\nā No PR found for current branch'));
|
|
455
|
+
console.log(chalk_1.default.gray(' Try: "approve PR #123" or create a PR first\n'));
|
|
456
|
+
return;
|
|
457
|
+
}
|
|
369
458
|
}
|
|
370
459
|
const reviewType = intent.options?.type || 'APPROVE';
|
|
371
460
|
const reviewBody = intent.options?.body || '';
|
|
@@ -385,10 +474,14 @@ async function executeGitHubCommand(intent) {
|
|
|
385
474
|
}
|
|
386
475
|
break;
|
|
387
476
|
case 'pr_comment':
|
|
388
|
-
|
|
477
|
+
let commentPrNum = intent.target || intent.options?.number;
|
|
478
|
+
// Smart PR detection
|
|
389
479
|
if (!commentPrNum) {
|
|
390
|
-
|
|
391
|
-
|
|
480
|
+
commentPrNum = await github.findPRNumber('current');
|
|
481
|
+
if (!commentPrNum) {
|
|
482
|
+
console.log(chalk_1.default.red('\nā No PR found for current branch\n'));
|
|
483
|
+
return;
|
|
484
|
+
}
|
|
392
485
|
}
|
|
393
486
|
const prComment = intent.options?.body || intent.options?.comment || '';
|
|
394
487
|
if (!prComment) {
|
|
@@ -416,10 +509,14 @@ async function executeGitHubCommand(intent) {
|
|
|
416
509
|
break;
|
|
417
510
|
case 'pr_show':
|
|
418
511
|
case 'pr_status':
|
|
419
|
-
|
|
512
|
+
let showPrNum = intent.target || intent.options?.number;
|
|
513
|
+
// Smart PR detection: "show my PR" / "show this PR"
|
|
420
514
|
if (!showPrNum) {
|
|
421
|
-
|
|
422
|
-
|
|
515
|
+
showPrNum = await github.findPRNumber('current');
|
|
516
|
+
if (!showPrNum) {
|
|
517
|
+
console.log(chalk_1.default.red('\nā No PR found for current branch\n'));
|
|
518
|
+
return;
|
|
519
|
+
}
|
|
423
520
|
}
|
|
424
521
|
console.log(chalk_1.default.blue(`\nš PR #${showPrNum}:\n`));
|
|
425
522
|
const prDetails = await github.getPullRequest(showPrNum);
|
|
@@ -442,10 +539,14 @@ async function executeGitHubCommand(intent) {
|
|
|
442
539
|
}
|
|
443
540
|
break;
|
|
444
541
|
case 'pr_diff':
|
|
445
|
-
|
|
542
|
+
let diffPrNum = intent.target || intent.options?.number;
|
|
543
|
+
// Smart PR detection: "what changed in my PR"
|
|
446
544
|
if (!diffPrNum) {
|
|
447
|
-
|
|
448
|
-
|
|
545
|
+
diffPrNum = await github.findPRNumber('current');
|
|
546
|
+
if (!diffPrNum) {
|
|
547
|
+
console.log(chalk_1.default.red('\nā No PR found for current branch\n'));
|
|
548
|
+
return;
|
|
549
|
+
}
|
|
449
550
|
}
|
|
450
551
|
console.log(chalk_1.default.blue(`\nš PR #${diffPrNum} changes:\n`));
|
|
451
552
|
const files = await github.getPullRequestFiles(diffPrNum);
|
package/dist/lib/github.js
CHANGED
|
@@ -2,6 +2,8 @@
|
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.getCurrentRepo = getCurrentRepo;
|
|
4
4
|
exports.getCurrentBranch = getCurrentBranch;
|
|
5
|
+
exports.findPRNumber = findPRNumber;
|
|
6
|
+
exports.findIssueNumber = findIssueNumber;
|
|
5
7
|
exports.createPullRequest = createPullRequest;
|
|
6
8
|
exports.listPullRequests = listPullRequests;
|
|
7
9
|
exports.getPullRequest = getPullRequest;
|
|
@@ -63,6 +65,41 @@ function getCurrentBranch() {
|
|
|
63
65
|
return 'main';
|
|
64
66
|
}
|
|
65
67
|
}
|
|
68
|
+
/**
|
|
69
|
+
* Find PR number from context (current branch, latest, etc.)
|
|
70
|
+
*/
|
|
71
|
+
async function findPRNumber(context) {
|
|
72
|
+
try {
|
|
73
|
+
if (context === 'current' || !context) {
|
|
74
|
+
// Find PR for current branch
|
|
75
|
+
const branch = getCurrentBranch();
|
|
76
|
+
const prs = await listPullRequests({ state: 'open', limit: 50 });
|
|
77
|
+
const pr = prs.find((p) => p.head.ref === branch);
|
|
78
|
+
return pr ? pr.number : null;
|
|
79
|
+
}
|
|
80
|
+
else if (context === 'latest' || context === 'mine') {
|
|
81
|
+
// Get latest PR
|
|
82
|
+
const prs = await listPullRequests({ state: 'open', limit: 1 });
|
|
83
|
+
return prs.length > 0 ? prs[0].number : null;
|
|
84
|
+
}
|
|
85
|
+
return null;
|
|
86
|
+
}
|
|
87
|
+
catch (error) {
|
|
88
|
+
return null;
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
/**
|
|
92
|
+
* Find issue number from context (latest, etc.)
|
|
93
|
+
*/
|
|
94
|
+
async function findIssueNumber(context) {
|
|
95
|
+
try {
|
|
96
|
+
const issues = await listIssues({ state: 'open', limit: 1 });
|
|
97
|
+
return issues.length > 0 ? issues[0].number : null;
|
|
98
|
+
}
|
|
99
|
+
catch (error) {
|
|
100
|
+
return null;
|
|
101
|
+
}
|
|
102
|
+
}
|
|
66
103
|
/**
|
|
67
104
|
* GitHub API request helper - uses locally stored token
|
|
68
105
|
*/
|
package/dist/repl.js
CHANGED
|
@@ -89,7 +89,7 @@ async function startREPL() {
|
|
|
89
89
|
console.log(chalk_1.default.gray(' GitHub: ') + chalk_1.default.cyan('"create a PR to main"'));
|
|
90
90
|
console.log(chalk_1.default.gray(' ') + chalk_1.default.cyan('"check CI status"'));
|
|
91
91
|
}
|
|
92
|
-
console.log(chalk_1.default.gray(' Other: ') + chalk_1.default.cyan('cd <path>') + chalk_1.default.gray(' ⢠') + chalk_1.default.cyan('exit') + chalk_1.default.gray(' ⢠') + chalk_1.default.cyan('help') + chalk_1.default.gray(' ⢠') + chalk_1.default.cyan('update\n'));
|
|
92
|
+
console.log(chalk_1.default.gray(' Other: ') + chalk_1.default.cyan('stats') + chalk_1.default.gray(' ⢠') + chalk_1.default.cyan('cd <path>') + chalk_1.default.gray(' ⢠') + chalk_1.default.cyan('exit') + chalk_1.default.gray(' ⢠') + chalk_1.default.cyan('help') + chalk_1.default.gray(' ⢠') + chalk_1.default.cyan('update\n'));
|
|
93
93
|
// Show GitHub setup reminder if not connected
|
|
94
94
|
if (!githubConnected) {
|
|
95
95
|
console.log(chalk_1.default.yellow.bold('āāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā'));
|
|
@@ -123,6 +123,56 @@ async function startREPL() {
|
|
|
123
123
|
rl.close();
|
|
124
124
|
process.exit(0);
|
|
125
125
|
}
|
|
126
|
+
// Stats command - show commit stats
|
|
127
|
+
if (line === 'stats' || line.startsWith('stats ')) {
|
|
128
|
+
const parts = line.split(' ');
|
|
129
|
+
const period = parts[1] || 'week';
|
|
130
|
+
let daysBack = 7;
|
|
131
|
+
if (period === 'today')
|
|
132
|
+
daysBack = 1;
|
|
133
|
+
else if (period === 'week')
|
|
134
|
+
daysBack = 7;
|
|
135
|
+
else if (period === 'month')
|
|
136
|
+
daysBack = 30;
|
|
137
|
+
else if (period === 'year')
|
|
138
|
+
daysBack = 365;
|
|
139
|
+
try {
|
|
140
|
+
const { getStats } = await Promise.resolve().then(() => __importStar(require('./db/database')));
|
|
141
|
+
const stats = getStats(daysBack);
|
|
142
|
+
console.log(chalk_1.default.bold.cyan(`\nš Stats (${period}):\n`));
|
|
143
|
+
console.log(chalk_1.default.white(` ⢠${chalk_1.default.bold.green(stats.totalCommits)} commits`));
|
|
144
|
+
console.log(chalk_1.default.white(` ⢠${chalk_1.default.bold.green(stats.totalInsertions.toLocaleString())} lines added`));
|
|
145
|
+
console.log(chalk_1.default.white(` ⢠${chalk_1.default.bold.red(stats.totalDeletions.toLocaleString())} lines removed`));
|
|
146
|
+
// Calculate busiest day
|
|
147
|
+
if (stats.totalCommits > 0) {
|
|
148
|
+
const commitsByDay = {};
|
|
149
|
+
const { execSync } = await Promise.resolve().then(() => __importStar(require('child_process')));
|
|
150
|
+
try {
|
|
151
|
+
const since = Date.now() - daysBack * 24 * 60 * 60 * 1000;
|
|
152
|
+
const sinceDate = new Date(since).toISOString().split('T')[0];
|
|
153
|
+
const logOutput = execSync(`git log --since="${sinceDate}" --format="%ai"`, { encoding: 'utf-8' });
|
|
154
|
+
logOutput.split('\n').filter(line => line.trim()).forEach(line => {
|
|
155
|
+
const day = line.split(' ')[0];
|
|
156
|
+
commitsByDay[day] = (commitsByDay[day] || 0) + 1;
|
|
157
|
+
});
|
|
158
|
+
const busiestDay = Object.entries(commitsByDay).sort(([, a], [, b]) => b - a)[0];
|
|
159
|
+
if (busiestDay) {
|
|
160
|
+
const dayName = new Date(busiestDay[0]).toLocaleDateString('en-US', { weekday: 'long' });
|
|
161
|
+
console.log(chalk_1.default.white(` ⢠Busiest day: ${chalk_1.default.bold.cyan(dayName)} (${busiestDay[1]} commits)`));
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
catch {
|
|
165
|
+
// Silent fail
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
console.log();
|
|
169
|
+
}
|
|
170
|
+
catch (error) {
|
|
171
|
+
console.log(chalk_1.default.red(`\nā Failed to get stats: ${error.message}\n`));
|
|
172
|
+
}
|
|
173
|
+
rl.prompt();
|
|
174
|
+
return;
|
|
175
|
+
}
|
|
126
176
|
// CD command - navigate to different repo
|
|
127
177
|
if (line.startsWith('cd ')) {
|
|
128
178
|
const targetPath = line.substring(3).trim();
|
|
@@ -210,6 +260,7 @@ async function startREPL() {
|
|
|
210
260
|
console.log(chalk_1.default.cyan(' "list my pull requests"'));
|
|
211
261
|
console.log(chalk_1.default.cyan(' "merge PR #123"\n'));
|
|
212
262
|
console.log(chalk_1.default.white.bold('System Commands:\n'));
|
|
263
|
+
console.log(chalk_1.default.gray(' ⢠') + chalk_1.default.cyan('stats') + chalk_1.default.gray(' - Show commit stats (stats today/week/month/year)'));
|
|
213
264
|
console.log(chalk_1.default.gray(' ⢠') + chalk_1.default.cyan('cd <path>') + chalk_1.default.gray(' - Navigate to different repo'));
|
|
214
265
|
console.log(chalk_1.default.gray(' ⢠') + chalk_1.default.cyan('update') + chalk_1.default.gray(' - Update to latest version'));
|
|
215
266
|
console.log(chalk_1.default.gray(' ⢠') + chalk_1.default.cyan('exit/quit') + chalk_1.default.gray(' - Exit SnapCommit\n'));
|