@snapcommit/cli 1.0.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.
Files changed (40) hide show
  1. package/README.md +162 -0
  2. package/dist/ai/anthropic-client.js +92 -0
  3. package/dist/ai/commit-generator.js +200 -0
  4. package/dist/ai/gemini-client.js +201 -0
  5. package/dist/ai/git-interpreter.js +209 -0
  6. package/dist/ai/smart-solver.js +260 -0
  7. package/dist/auth/supabase-client.js +288 -0
  8. package/dist/commands/activate.js +108 -0
  9. package/dist/commands/commit.js +255 -0
  10. package/dist/commands/conflict.js +233 -0
  11. package/dist/commands/doctor.js +113 -0
  12. package/dist/commands/git-advanced.js +311 -0
  13. package/dist/commands/github-auth.js +193 -0
  14. package/dist/commands/login.js +11 -0
  15. package/dist/commands/natural.js +305 -0
  16. package/dist/commands/onboard.js +111 -0
  17. package/dist/commands/quick.js +173 -0
  18. package/dist/commands/setup.js +163 -0
  19. package/dist/commands/stats.js +128 -0
  20. package/dist/commands/uninstall.js +131 -0
  21. package/dist/db/database.js +99 -0
  22. package/dist/index.js +144 -0
  23. package/dist/lib/auth.js +171 -0
  24. package/dist/lib/github.js +280 -0
  25. package/dist/lib/multi-repo.js +276 -0
  26. package/dist/lib/supabase.js +153 -0
  27. package/dist/license/manager.js +203 -0
  28. package/dist/repl/index.js +185 -0
  29. package/dist/repl/interpreter.js +524 -0
  30. package/dist/utils/analytics.js +36 -0
  31. package/dist/utils/auth-storage.js +65 -0
  32. package/dist/utils/dopamine.js +211 -0
  33. package/dist/utils/errors.js +56 -0
  34. package/dist/utils/git.js +105 -0
  35. package/dist/utils/heatmap.js +265 -0
  36. package/dist/utils/rate-limit.js +68 -0
  37. package/dist/utils/retry.js +46 -0
  38. package/dist/utils/ui.js +189 -0
  39. package/dist/utils/version.js +81 -0
  40. package/package.json +69 -0
@@ -0,0 +1,68 @@
1
+ "use strict";
2
+ /**
3
+ * Rate limiting to prevent API abuse and control costs
4
+ */
5
+ var __importDefault = (this && this.__importDefault) || function (mod) {
6
+ return (mod && mod.__esModule) ? mod : { "default": mod };
7
+ };
8
+ Object.defineProperty(exports, "__esModule", { value: true });
9
+ exports.checkRateLimit = checkRateLimit;
10
+ exports.trackRateLimit = trackRateLimit;
11
+ exports.formatResetTime = formatResetTime;
12
+ const better_sqlite3_1 = __importDefault(require("better-sqlite3"));
13
+ const path_1 = __importDefault(require("path"));
14
+ const os_1 = __importDefault(require("os"));
15
+ const DB_PATH = path_1.default.join(os_1.default.homedir(), '.snapcommit', 'snapcommit.db');
16
+ const db = new better_sqlite3_1.default(DB_PATH);
17
+ // Ensure rate_limit table exists
18
+ db.exec(`
19
+ CREATE TABLE IF NOT EXISTS rate_limit (
20
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
21
+ action TEXT NOT NULL,
22
+ timestamp INTEGER NOT NULL
23
+ );
24
+ `);
25
+ const RATE_LIMITS = {
26
+ free: { limit: 10, windowMs: 60 * 60 * 1000 }, // 10 per hour
27
+ pro: { limit: 100, windowMs: 60 * 60 * 1000 }, // 100 per hour
28
+ };
29
+ function checkRateLimit(plan) {
30
+ const config = RATE_LIMITS[plan];
31
+ const now = Date.now();
32
+ const windowStart = now - config.windowMs;
33
+ // Count recent actions
34
+ const count = db.prepare(`
35
+ SELECT COUNT(*) as count FROM rate_limit
36
+ WHERE action = 'commit' AND timestamp >= ?
37
+ `).get(windowStart);
38
+ const remaining = Math.max(0, config.limit - count.count);
39
+ const allowed = remaining > 0;
40
+ // Calculate reset time (start of next window)
41
+ const oldestInWindow = db.prepare(`
42
+ SELECT MIN(timestamp) as oldest FROM rate_limit
43
+ WHERE action = 'commit' AND timestamp >= ?
44
+ `).get(windowStart);
45
+ const resetAt = oldestInWindow?.oldest
46
+ ? oldestInWindow.oldest + config.windowMs
47
+ : now + config.windowMs;
48
+ return { allowed, remaining, resetAt };
49
+ }
50
+ function trackRateLimit(action = 'commit') {
51
+ db.prepare(`
52
+ INSERT INTO rate_limit (action, timestamp)
53
+ VALUES (?, ?)
54
+ `).run(action, Date.now());
55
+ // Cleanup old entries (keep last 7 days)
56
+ const sevenDaysAgo = Date.now() - (7 * 24 * 60 * 60 * 1000);
57
+ db.prepare('DELETE FROM rate_limit WHERE timestamp < ?').run(sevenDaysAgo);
58
+ }
59
+ function formatResetTime(resetAt) {
60
+ const seconds = Math.floor((resetAt - Date.now()) / 1000);
61
+ if (seconds < 60)
62
+ return `${seconds}s`;
63
+ const minutes = Math.floor(seconds / 60);
64
+ if (minutes < 60)
65
+ return `${minutes}m`;
66
+ const hours = Math.floor(minutes / 60);
67
+ return `${hours}h ${minutes % 60}m`;
68
+ }
@@ -0,0 +1,46 @@
1
+ "use strict";
2
+ /**
3
+ * Retry utility with exponential backoff
4
+ * For handling transient API errors gracefully
5
+ */
6
+ Object.defineProperty(exports, "__esModule", { value: true });
7
+ exports.retryWithBackoff = retryWithBackoff;
8
+ async function retryWithBackoff(fn, options = {}) {
9
+ const { maxAttempts = 3, initialDelay = 1000, maxDelay = 10000, backoffMultiplier = 2, } = options;
10
+ let lastError;
11
+ let delay = initialDelay;
12
+ for (let attempt = 1; attempt <= maxAttempts; attempt++) {
13
+ try {
14
+ return await fn();
15
+ }
16
+ catch (error) {
17
+ lastError = error;
18
+ // Don't retry on certain errors (e.g., auth failures, invalid requests)
19
+ if (isNonRetryableError(error)) {
20
+ throw error;
21
+ }
22
+ // If this was the last attempt, throw
23
+ if (attempt === maxAttempts) {
24
+ throw error;
25
+ }
26
+ // Wait before retrying
27
+ await sleep(Math.min(delay, maxDelay));
28
+ delay *= backoffMultiplier;
29
+ }
30
+ }
31
+ throw lastError;
32
+ }
33
+ function isNonRetryableError(error) {
34
+ // Don't retry on 4xx errors (client errors)
35
+ if (error.status && error.status >= 400 && error.status < 500) {
36
+ return true;
37
+ }
38
+ // Don't retry on authentication errors
39
+ if (error.message?.includes('authentication') || error.message?.includes('API key')) {
40
+ return true;
41
+ }
42
+ return false;
43
+ }
44
+ function sleep(ms) {
45
+ return new Promise(resolve => setTimeout(resolve, ms));
46
+ }
@@ -0,0 +1,189 @@
1
+ "use strict";
2
+ /**
3
+ * Beautiful terminal UI utilities
4
+ */
5
+ var __importDefault = (this && this.__importDefault) || function (mod) {
6
+ return (mod && mod.__esModule) ? mod : { "default": mod };
7
+ };
8
+ Object.defineProperty(exports, "__esModule", { value: true });
9
+ exports.SPINNER_FRAMES = void 0;
10
+ exports.createBox = createBox;
11
+ exports.createProgressBar = createProgressBar;
12
+ exports.createSpinner = createSpinner;
13
+ exports.formatNumber = formatNumber;
14
+ exports.formatDuration = formatDuration;
15
+ exports.createTable = createTable;
16
+ exports.displaySuccess = displaySuccess;
17
+ exports.displayError = displayError;
18
+ exports.displayWarning = displayWarning;
19
+ exports.displayInfo = displayInfo;
20
+ const chalk_1 = __importDefault(require("chalk"));
21
+ /**
22
+ * Create a beautiful box around text
23
+ */
24
+ function createBox(title, lines, width = 50) {
25
+ const border = '─'.repeat(width);
26
+ let box = chalk_1.default.gray(`┌${border}┐\n`);
27
+ box += chalk_1.default.white.bold(`│ ${title.padEnd(width - 2)} │\n`);
28
+ box += chalk_1.default.gray(`├${border}┤\n`);
29
+ lines.forEach(line => {
30
+ box += chalk_1.default.gray(`│ `) + line.padEnd(width - 2) + chalk_1.default.gray(` │\n`);
31
+ });
32
+ box += chalk_1.default.gray(`└${border}┘`);
33
+ return box;
34
+ }
35
+ /**
36
+ * Create a progress bar
37
+ */
38
+ function createProgressBar(current, total, width = 20) {
39
+ const percentage = Math.min(100, (current / total) * 100);
40
+ const filled = Math.floor((percentage / 100) * width);
41
+ const empty = width - filled;
42
+ const bar = chalk_1.default.green('█'.repeat(filled)) + chalk_1.default.gray('░'.repeat(empty));
43
+ return `${bar} ${percentage.toFixed(0)}%`;
44
+ }
45
+ /**
46
+ * Spinner animation frames
47
+ */
48
+ exports.SPINNER_FRAMES = ['⠋', '⠙', '⠹', '⠸', '⠼', '⠴', '⠦', '⠧', '⠇', '⠏'];
49
+ /**
50
+ * Create a spinner
51
+ */
52
+ function createSpinner(message = 'Loading...') {
53
+ let intervalId = null;
54
+ let frame = 0;
55
+ let currentMessage = message;
56
+ const start = () => {
57
+ frame = 0;
58
+ intervalId = setInterval(() => {
59
+ process.stdout.write(chalk_1.default.blue(`\r${exports.SPINNER_FRAMES[frame++ % exports.SPINNER_FRAMES.length]} ${currentMessage}`));
60
+ }, 100);
61
+ };
62
+ const stop = () => {
63
+ if (intervalId) {
64
+ clearInterval(intervalId);
65
+ intervalId = null;
66
+ process.stdout.write('\r' + ' '.repeat(currentMessage.length + 5) + '\r');
67
+ }
68
+ };
69
+ const update = (msg) => {
70
+ currentMessage = msg;
71
+ };
72
+ return { start, stop, update };
73
+ }
74
+ /**
75
+ * Format large numbers
76
+ */
77
+ function formatNumber(num) {
78
+ if (num >= 1000000) {
79
+ return (num / 1000000).toFixed(1) + 'M';
80
+ }
81
+ if (num >= 1000) {
82
+ return (num / 1000).toFixed(1) + 'K';
83
+ }
84
+ return num.toString();
85
+ }
86
+ /**
87
+ * Format time duration
88
+ */
89
+ function formatDuration(ms) {
90
+ const seconds = Math.floor(ms / 1000);
91
+ if (seconds < 60)
92
+ return `${seconds}s`;
93
+ const minutes = Math.floor(seconds / 60);
94
+ if (minutes < 60)
95
+ return `${minutes}m ${seconds % 60}s`;
96
+ const hours = Math.floor(minutes / 60);
97
+ if (hours < 24)
98
+ return `${hours}h ${minutes % 60}m`;
99
+ const days = Math.floor(hours / 24);
100
+ return `${days}d ${hours % 24}h`;
101
+ }
102
+ /**
103
+ * Create a table
104
+ */
105
+ function createTable(headers, rows) {
106
+ const colWidths = headers.map((header, i) => {
107
+ const maxWidth = Math.max(header.length, ...rows.map(row => (row[i] || '').length));
108
+ return maxWidth + 2; // Add padding
109
+ });
110
+ let table = '';
111
+ // Header
112
+ table += chalk_1.default.gray('┌');
113
+ table += colWidths.map(w => chalk_1.default.gray('─'.repeat(w))).join(chalk_1.default.gray('┬'));
114
+ table += chalk_1.default.gray('┐\n');
115
+ table += chalk_1.default.gray('│');
116
+ headers.forEach((header, i) => {
117
+ table += chalk_1.default.white.bold(` ${header.padEnd(colWidths[i] - 1)}`);
118
+ table += chalk_1.default.gray('│');
119
+ });
120
+ table += '\n';
121
+ table += chalk_1.default.gray('├');
122
+ table += colWidths.map(w => chalk_1.default.gray('─'.repeat(w))).join(chalk_1.default.gray('┼'));
123
+ table += chalk_1.default.gray('┤\n');
124
+ // Rows
125
+ rows.forEach(row => {
126
+ table += chalk_1.default.gray('│');
127
+ row.forEach((cell, i) => {
128
+ table += chalk_1.default.white(` ${cell.padEnd(colWidths[i] - 1)}`);
129
+ table += chalk_1.default.gray('│');
130
+ });
131
+ table += '\n';
132
+ });
133
+ table += chalk_1.default.gray('└');
134
+ table += colWidths.map(w => chalk_1.default.gray('─'.repeat(w))).join(chalk_1.default.gray('┴'));
135
+ table += chalk_1.default.gray('┘');
136
+ return table;
137
+ }
138
+ /**
139
+ * Display a success message
140
+ */
141
+ function displaySuccess(message, details) {
142
+ console.log();
143
+ console.log(chalk_1.default.green.bold(`✅ ${message}`));
144
+ if (details && details.length > 0) {
145
+ details.forEach(detail => {
146
+ console.log(chalk_1.default.gray(` ${detail}`));
147
+ });
148
+ }
149
+ console.log();
150
+ }
151
+ /**
152
+ * Display an error message
153
+ */
154
+ function displayError(message, details) {
155
+ console.log();
156
+ console.log(chalk_1.default.red.bold(`❌ ${message}`));
157
+ if (details && details.length > 0) {
158
+ details.forEach(detail => {
159
+ console.log(chalk_1.default.gray(` ${detail}`));
160
+ });
161
+ }
162
+ console.log();
163
+ }
164
+ /**
165
+ * Display a warning message
166
+ */
167
+ function displayWarning(message, details) {
168
+ console.log();
169
+ console.log(chalk_1.default.yellow.bold(`⚠️ ${message}`));
170
+ if (details && details.length > 0) {
171
+ details.forEach(detail => {
172
+ console.log(chalk_1.default.gray(` ${detail}`));
173
+ });
174
+ }
175
+ console.log();
176
+ }
177
+ /**
178
+ * Display an info message
179
+ */
180
+ function displayInfo(message, details) {
181
+ console.log();
182
+ console.log(chalk_1.default.blue.bold(`ℹ️ ${message}`));
183
+ if (details && details.length > 0) {
184
+ details.forEach(detail => {
185
+ console.log(chalk_1.default.gray(` ${detail}`));
186
+ });
187
+ }
188
+ console.log();
189
+ }
@@ -0,0 +1,81 @@
1
+ "use strict";
2
+ /**
3
+ * Version checking and auto-update notifications
4
+ */
5
+ var __importDefault = (this && this.__importDefault) || function (mod) {
6
+ return (mod && mod.__esModule) ? mod : { "default": mod };
7
+ };
8
+ Object.defineProperty(exports, "__esModule", { value: true });
9
+ exports.checkForUpdates = checkForUpdates;
10
+ const https_1 = __importDefault(require("https"));
11
+ const fs_1 = __importDefault(require("fs"));
12
+ const path_1 = __importDefault(require("path"));
13
+ const os_1 = __importDefault(require("os"));
14
+ const CACHE_FILE = path_1.default.join(os_1.default.homedir(), '.snapcommit', 'version-cache.json');
15
+ const CHECK_INTERVAL = 24 * 60 * 60 * 1000; // 24 hours
16
+ async function checkForUpdates() {
17
+ try {
18
+ // Read current version from package.json
19
+ const packageJsonPath = path_1.default.join(__dirname, '../../package.json');
20
+ const packageJson = JSON.parse(fs_1.default.readFileSync(packageJsonPath, 'utf-8'));
21
+ const currentVersion = packageJson.version;
22
+ // Check cache
23
+ let cache = null;
24
+ if (fs_1.default.existsSync(CACHE_FILE)) {
25
+ cache = JSON.parse(fs_1.default.readFileSync(CACHE_FILE, 'utf-8'));
26
+ if (cache && Date.now() - cache.lastCheck < CHECK_INTERVAL) {
27
+ // Use cached version
28
+ return {
29
+ hasUpdate: isNewerVersion(cache.latestVersion, currentVersion),
30
+ currentVersion,
31
+ latestVersion: cache.latestVersion,
32
+ };
33
+ }
34
+ }
35
+ // Fetch latest version from npm
36
+ const latestVersion = await getLatestVersionFromNpm('snapcommit');
37
+ // Update cache
38
+ const newCache = {
39
+ lastCheck: Date.now(),
40
+ latestVersion,
41
+ };
42
+ fs_1.default.writeFileSync(CACHE_FILE, JSON.stringify(newCache));
43
+ return {
44
+ hasUpdate: isNewerVersion(latestVersion, currentVersion),
45
+ currentVersion,
46
+ latestVersion,
47
+ };
48
+ }
49
+ catch (error) {
50
+ // Silent fail - don't interrupt user
51
+ return null;
52
+ }
53
+ }
54
+ function getLatestVersionFromNpm(packageName) {
55
+ return new Promise((resolve, reject) => {
56
+ https_1.default.get(`https://registry.npmjs.org/${packageName}/latest`, (res) => {
57
+ let data = '';
58
+ res.on('data', (chunk) => data += chunk);
59
+ res.on('end', () => {
60
+ try {
61
+ const json = JSON.parse(data);
62
+ resolve(json.version);
63
+ }
64
+ catch {
65
+ reject(new Error('Failed to parse npm response'));
66
+ }
67
+ });
68
+ }).on('error', reject);
69
+ });
70
+ }
71
+ function isNewerVersion(latest, current) {
72
+ const latestParts = latest.split('.').map(Number);
73
+ const currentParts = current.split('.').map(Number);
74
+ for (let i = 0; i < 3; i++) {
75
+ if (latestParts[i] > currentParts[i])
76
+ return true;
77
+ if (latestParts[i] < currentParts[i])
78
+ return false;
79
+ }
80
+ return false;
81
+ }
package/package.json ADDED
@@ -0,0 +1,69 @@
1
+ {
2
+ "name": "@snapcommit/cli",
3
+ "version": "1.0.0",
4
+ "description": "Instant AI commits. Beautiful progress tracking. Never write commit messages again.",
5
+ "main": "dist/index.js",
6
+ "bin": {
7
+ "snapcommit": "./dist/index.js",
8
+ "snap": "./dist/index.js",
9
+ "sc": "./dist/index.js"
10
+ },
11
+ "scripts": {
12
+ "dev": "tsx src/index.ts",
13
+ "build": "tsc",
14
+ "start": "node dist/index.js",
15
+ "prepublishOnly": "npm run build",
16
+ "test": "echo \"Tests coming soon\""
17
+ },
18
+ "keywords": [
19
+ "cli",
20
+ "terminal",
21
+ "ai",
22
+ "git",
23
+ "commit",
24
+ "claude",
25
+ "gemini",
26
+ "productivity",
27
+ "developer-tools",
28
+ "automation",
29
+ "progress-tracking"
30
+ ],
31
+ "author": "SnapCommit",
32
+ "license": "MIT",
33
+ "homepage": "https://snapcommit.dev",
34
+ "repository": {
35
+ "type": "git",
36
+ "url": "https://github.com/Arjun0606/snapcommit.git"
37
+ },
38
+ "bugs": {
39
+ "url": "https://github.com/Arjun0606/snapcommit/issues"
40
+ },
41
+ "engines": {
42
+ "node": ">=18.0.0"
43
+ },
44
+ "os": [
45
+ "darwin",
46
+ "linux",
47
+ "win32"
48
+ ],
49
+ "dependencies": {
50
+ "@anthropic-ai/sdk": "^0.30.1",
51
+ "@google/generative-ai": "^0.21.0",
52
+ "@supabase/supabase-js": "^2.78.0",
53
+ "better-sqlite3": "^11.5.0",
54
+ "chalk": "^5.3.0",
55
+ "commander": "^12.1.0",
56
+ "dotenv": "^16.4.5",
57
+ "ink": "^5.0.1",
58
+ "node-pty": "^1.0.0",
59
+ "open": "^10.2.0",
60
+ "react": "^18.3.1"
61
+ },
62
+ "devDependencies": {
63
+ "@types/better-sqlite3": "^7.6.12",
64
+ "@types/node": "^22.8.7",
65
+ "@types/react": "^18.3.12",
66
+ "tsx": "^4.19.2",
67
+ "typescript": "^5.6.3"
68
+ }
69
+ }