@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.
- package/README.md +162 -0
- package/dist/ai/anthropic-client.js +92 -0
- package/dist/ai/commit-generator.js +200 -0
- package/dist/ai/gemini-client.js +201 -0
- package/dist/ai/git-interpreter.js +209 -0
- package/dist/ai/smart-solver.js +260 -0
- package/dist/auth/supabase-client.js +288 -0
- package/dist/commands/activate.js +108 -0
- package/dist/commands/commit.js +255 -0
- package/dist/commands/conflict.js +233 -0
- package/dist/commands/doctor.js +113 -0
- package/dist/commands/git-advanced.js +311 -0
- package/dist/commands/github-auth.js +193 -0
- package/dist/commands/login.js +11 -0
- package/dist/commands/natural.js +305 -0
- package/dist/commands/onboard.js +111 -0
- package/dist/commands/quick.js +173 -0
- package/dist/commands/setup.js +163 -0
- package/dist/commands/stats.js +128 -0
- package/dist/commands/uninstall.js +131 -0
- package/dist/db/database.js +99 -0
- package/dist/index.js +144 -0
- package/dist/lib/auth.js +171 -0
- package/dist/lib/github.js +280 -0
- package/dist/lib/multi-repo.js +276 -0
- package/dist/lib/supabase.js +153 -0
- package/dist/license/manager.js +203 -0
- package/dist/repl/index.js +185 -0
- package/dist/repl/interpreter.js +524 -0
- package/dist/utils/analytics.js +36 -0
- package/dist/utils/auth-storage.js +65 -0
- package/dist/utils/dopamine.js +211 -0
- package/dist/utils/errors.js +56 -0
- package/dist/utils/git.js +105 -0
- package/dist/utils/heatmap.js +265 -0
- package/dist/utils/rate-limit.js +68 -0
- package/dist/utils/retry.js +46 -0
- package/dist/utils/ui.js +189 -0
- package/dist/utils/version.js +81 -0
- package/package.json +69 -0
|
@@ -0,0 +1,288 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Supabase authentication client for CLI
|
|
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.supabase = void 0;
|
|
10
|
+
exports.saveAuthTokens = saveAuthTokens;
|
|
11
|
+
exports.loadAuthTokens = loadAuthTokens;
|
|
12
|
+
exports.clearAuthTokens = clearAuthTokens;
|
|
13
|
+
exports.isAuthenticated = isAuthenticated;
|
|
14
|
+
exports.getCurrentSession = getCurrentSession;
|
|
15
|
+
exports.signInWithEmail = signInWithEmail;
|
|
16
|
+
exports.signUpWithEmail = signUpWithEmail;
|
|
17
|
+
exports.signInWithGitHub = signInWithGitHub;
|
|
18
|
+
exports.signOut = signOut;
|
|
19
|
+
exports.getAuthenticatedClient = getAuthenticatedClient;
|
|
20
|
+
exports.syncCommitToCloud = syncCommitToCloud;
|
|
21
|
+
const supabase_js_1 = require("@supabase/supabase-js");
|
|
22
|
+
const fs_1 = __importDefault(require("fs"));
|
|
23
|
+
const path_1 = __importDefault(require("path"));
|
|
24
|
+
const os_1 = __importDefault(require("os"));
|
|
25
|
+
const SUPABASE_URL = process.env.NEXT_PUBLIC_SUPABASE_URL || process.env.SUPABASE_URL || '';
|
|
26
|
+
const SUPABASE_ANON_KEY = process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY || process.env.SUPABASE_ANON_KEY || '';
|
|
27
|
+
// Supabase is optional - CLI works offline without it
|
|
28
|
+
// Only throw error when trying to use auth features
|
|
29
|
+
const hasSupabaseConfig = SUPABASE_URL && SUPABASE_ANON_KEY;
|
|
30
|
+
exports.supabase = hasSupabaseConfig
|
|
31
|
+
? (0, supabase_js_1.createClient)(SUPABASE_URL, SUPABASE_ANON_KEY)
|
|
32
|
+
: null;
|
|
33
|
+
// Helper to ensure Supabase is configured
|
|
34
|
+
function ensureSupabaseConfigured() {
|
|
35
|
+
if (!hasSupabaseConfig || !exports.supabase) {
|
|
36
|
+
throw new Error('Supabase not configured. Cloud sync features require NEXT_PUBLIC_SUPABASE_URL and NEXT_PUBLIC_SUPABASE_ANON_KEY in .env');
|
|
37
|
+
}
|
|
38
|
+
return exports.supabase;
|
|
39
|
+
}
|
|
40
|
+
// Auth token storage
|
|
41
|
+
const AUTH_DIR = path_1.default.join(os_1.default.homedir(), '.snapcommit');
|
|
42
|
+
const AUTH_FILE = path_1.default.join(AUTH_DIR, 'auth.json');
|
|
43
|
+
/**
|
|
44
|
+
* Save auth tokens to local storage
|
|
45
|
+
*/
|
|
46
|
+
function saveAuthTokens(data) {
|
|
47
|
+
if (!fs_1.default.existsSync(AUTH_DIR)) {
|
|
48
|
+
fs_1.default.mkdirSync(AUTH_DIR, { recursive: true });
|
|
49
|
+
}
|
|
50
|
+
fs_1.default.writeFileSync(AUTH_FILE, JSON.stringify(data, null, 2));
|
|
51
|
+
// Secure the file (Unix only)
|
|
52
|
+
try {
|
|
53
|
+
fs_1.default.chmodSync(AUTH_FILE, 0o600);
|
|
54
|
+
}
|
|
55
|
+
catch {
|
|
56
|
+
// Windows doesn't support chmod
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
/**
|
|
60
|
+
* Load auth tokens from local storage
|
|
61
|
+
*/
|
|
62
|
+
function loadAuthTokens() {
|
|
63
|
+
if (!fs_1.default.existsSync(AUTH_FILE)) {
|
|
64
|
+
return null;
|
|
65
|
+
}
|
|
66
|
+
try {
|
|
67
|
+
const data = fs_1.default.readFileSync(AUTH_FILE, 'utf-8');
|
|
68
|
+
return JSON.parse(data);
|
|
69
|
+
}
|
|
70
|
+
catch {
|
|
71
|
+
return null;
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
/**
|
|
75
|
+
* Clear auth tokens (logout)
|
|
76
|
+
*/
|
|
77
|
+
function clearAuthTokens() {
|
|
78
|
+
if (fs_1.default.existsSync(AUTH_FILE)) {
|
|
79
|
+
fs_1.default.unlinkSync(AUTH_FILE);
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
/**
|
|
83
|
+
* Check if user is authenticated
|
|
84
|
+
*/
|
|
85
|
+
async function isAuthenticated() {
|
|
86
|
+
const tokens = loadAuthTokens();
|
|
87
|
+
if (!tokens) {
|
|
88
|
+
return false;
|
|
89
|
+
}
|
|
90
|
+
// Check if token is expired
|
|
91
|
+
if (Date.now() > tokens.expires_at) {
|
|
92
|
+
// Try to refresh
|
|
93
|
+
const refreshed = await refreshAuthToken(tokens.refresh_token);
|
|
94
|
+
return refreshed !== null;
|
|
95
|
+
}
|
|
96
|
+
return true;
|
|
97
|
+
}
|
|
98
|
+
/**
|
|
99
|
+
* Refresh auth token
|
|
100
|
+
*/
|
|
101
|
+
async function refreshAuthToken(refreshToken) {
|
|
102
|
+
try {
|
|
103
|
+
const sb = ensureSupabaseConfigured();
|
|
104
|
+
const { data, error } = await sb.auth.refreshSession({
|
|
105
|
+
refresh_token: refreshToken,
|
|
106
|
+
});
|
|
107
|
+
if (error || !data.session) {
|
|
108
|
+
clearAuthTokens();
|
|
109
|
+
return null;
|
|
110
|
+
}
|
|
111
|
+
const authData = {
|
|
112
|
+
access_token: data.session.access_token,
|
|
113
|
+
refresh_token: data.session.refresh_token,
|
|
114
|
+
user_id: data.session.user.id,
|
|
115
|
+
email: data.session.user.email,
|
|
116
|
+
expires_at: Date.now() + (data.session.expires_in * 1000),
|
|
117
|
+
};
|
|
118
|
+
saveAuthTokens(authData);
|
|
119
|
+
return authData;
|
|
120
|
+
}
|
|
121
|
+
catch {
|
|
122
|
+
clearAuthTokens();
|
|
123
|
+
return null;
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
/**
|
|
127
|
+
* Get current user session
|
|
128
|
+
*/
|
|
129
|
+
async function getCurrentSession() {
|
|
130
|
+
const tokens = loadAuthTokens();
|
|
131
|
+
if (!tokens) {
|
|
132
|
+
return null;
|
|
133
|
+
}
|
|
134
|
+
// Refresh if expired
|
|
135
|
+
if (Date.now() > tokens.expires_at) {
|
|
136
|
+
return await refreshAuthToken(tokens.refresh_token);
|
|
137
|
+
}
|
|
138
|
+
return tokens;
|
|
139
|
+
}
|
|
140
|
+
/**
|
|
141
|
+
* Sign in with email and password
|
|
142
|
+
*/
|
|
143
|
+
async function signInWithEmail(email, password) {
|
|
144
|
+
try {
|
|
145
|
+
const sb = ensureSupabaseConfigured();
|
|
146
|
+
const { data, error } = await sb.auth.signInWithPassword({
|
|
147
|
+
email,
|
|
148
|
+
password,
|
|
149
|
+
});
|
|
150
|
+
if (error) {
|
|
151
|
+
return { success: false, error: error.message };
|
|
152
|
+
}
|
|
153
|
+
if (!data.session) {
|
|
154
|
+
return { success: false, error: 'No session returned' };
|
|
155
|
+
}
|
|
156
|
+
const authData = {
|
|
157
|
+
access_token: data.session.access_token,
|
|
158
|
+
refresh_token: data.session.refresh_token,
|
|
159
|
+
user_id: data.session.user.id,
|
|
160
|
+
email: data.session.user.email,
|
|
161
|
+
expires_at: Date.now() + (data.session.expires_in * 1000),
|
|
162
|
+
};
|
|
163
|
+
saveAuthTokens(authData);
|
|
164
|
+
return { success: true };
|
|
165
|
+
}
|
|
166
|
+
catch (error) {
|
|
167
|
+
return { success: false, error: error.message };
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
/**
|
|
171
|
+
* Sign up with email and password
|
|
172
|
+
*/
|
|
173
|
+
async function signUpWithEmail(email, password) {
|
|
174
|
+
try {
|
|
175
|
+
const sb = ensureSupabaseConfigured();
|
|
176
|
+
const { data, error } = await sb.auth.signUp({
|
|
177
|
+
email,
|
|
178
|
+
password,
|
|
179
|
+
});
|
|
180
|
+
if (error) {
|
|
181
|
+
return { success: false, error: error.message };
|
|
182
|
+
}
|
|
183
|
+
if (!data.session) {
|
|
184
|
+
// Email confirmation might be required
|
|
185
|
+
return {
|
|
186
|
+
success: true,
|
|
187
|
+
error: 'Check your email to confirm your account'
|
|
188
|
+
};
|
|
189
|
+
}
|
|
190
|
+
const authData = {
|
|
191
|
+
access_token: data.session.access_token,
|
|
192
|
+
refresh_token: data.session.refresh_token,
|
|
193
|
+
user_id: data.session.user.id,
|
|
194
|
+
email: data.session.user.email,
|
|
195
|
+
expires_at: Date.now() + (data.session.expires_in * 1000),
|
|
196
|
+
};
|
|
197
|
+
saveAuthTokens(authData);
|
|
198
|
+
return { success: true };
|
|
199
|
+
}
|
|
200
|
+
catch (error) {
|
|
201
|
+
return { success: false, error: error.message };
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
/**
|
|
205
|
+
* Sign in with GitHub OAuth
|
|
206
|
+
* Opens browser for OAuth flow
|
|
207
|
+
*/
|
|
208
|
+
async function signInWithGitHub() {
|
|
209
|
+
try {
|
|
210
|
+
const sb = ensureSupabaseConfigured();
|
|
211
|
+
const { data, error } = await sb.auth.signInWithOAuth({
|
|
212
|
+
provider: 'github',
|
|
213
|
+
options: {
|
|
214
|
+
redirectTo: 'http://localhost:54321/auth/callback', // Local callback server
|
|
215
|
+
},
|
|
216
|
+
});
|
|
217
|
+
if (error) {
|
|
218
|
+
return { success: false, error: error.message };
|
|
219
|
+
}
|
|
220
|
+
return { success: true, url: data.url };
|
|
221
|
+
}
|
|
222
|
+
catch (error) {
|
|
223
|
+
return { success: false, error: error.message };
|
|
224
|
+
}
|
|
225
|
+
}
|
|
226
|
+
/**
|
|
227
|
+
* Sign out
|
|
228
|
+
*/
|
|
229
|
+
async function signOut() {
|
|
230
|
+
const tokens = loadAuthTokens();
|
|
231
|
+
if (tokens && hasSupabaseConfig && exports.supabase) {
|
|
232
|
+
// Revoke the session on the server
|
|
233
|
+
await exports.supabase.auth.signOut();
|
|
234
|
+
}
|
|
235
|
+
clearAuthTokens();
|
|
236
|
+
}
|
|
237
|
+
/**
|
|
238
|
+
* Get authenticated Supabase client
|
|
239
|
+
* (for making API calls with user's token)
|
|
240
|
+
*/
|
|
241
|
+
async function getAuthenticatedClient() {
|
|
242
|
+
const sb = ensureSupabaseConfigured();
|
|
243
|
+
const session = await getCurrentSession();
|
|
244
|
+
if (!session) {
|
|
245
|
+
return null;
|
|
246
|
+
}
|
|
247
|
+
return (0, supabase_js_1.createClient)(SUPABASE_URL, SUPABASE_ANON_KEY, {
|
|
248
|
+
global: {
|
|
249
|
+
headers: {
|
|
250
|
+
Authorization: `Bearer ${session.access_token}`,
|
|
251
|
+
},
|
|
252
|
+
},
|
|
253
|
+
});
|
|
254
|
+
}
|
|
255
|
+
/**
|
|
256
|
+
* Sync commit to cloud (if authenticated)
|
|
257
|
+
*/
|
|
258
|
+
async function syncCommitToCloud(commit) {
|
|
259
|
+
if (!hasSupabaseConfig) {
|
|
260
|
+
return { success: false, error: 'not_configured' };
|
|
261
|
+
}
|
|
262
|
+
const session = await getCurrentSession();
|
|
263
|
+
if (!session) {
|
|
264
|
+
return { success: false, error: 'not_authenticated' };
|
|
265
|
+
}
|
|
266
|
+
try {
|
|
267
|
+
const client = await getAuthenticatedClient();
|
|
268
|
+
if (!client) {
|
|
269
|
+
return { success: false, error: 'no_client' };
|
|
270
|
+
}
|
|
271
|
+
const { error } = await client.from('commits').insert({
|
|
272
|
+
user_id: session.user_id,
|
|
273
|
+
message: commit.message,
|
|
274
|
+
hash: commit.hash,
|
|
275
|
+
files_changed: commit.files_changed,
|
|
276
|
+
insertions: commit.insertions,
|
|
277
|
+
deletions: commit.deletions,
|
|
278
|
+
timestamp: new Date(commit.timestamp).toISOString(),
|
|
279
|
+
});
|
|
280
|
+
if (error) {
|
|
281
|
+
return { success: false, error: error.message };
|
|
282
|
+
}
|
|
283
|
+
return { success: true };
|
|
284
|
+
}
|
|
285
|
+
catch (error) {
|
|
286
|
+
return { success: false, error: error.message };
|
|
287
|
+
}
|
|
288
|
+
}
|
|
@@ -0,0 +1,108 @@
|
|
|
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.activateCommand = activateCommand;
|
|
7
|
+
exports.statusCommand = statusCommand;
|
|
8
|
+
const chalk_1 = __importDefault(require("chalk"));
|
|
9
|
+
const manager_1 = require("../license/manager");
|
|
10
|
+
const readline_1 = __importDefault(require("readline"));
|
|
11
|
+
async function activateCommand(licenseKey) {
|
|
12
|
+
console.log(chalk_1.default.blue.bold('\n🔐 SnapCommit License Activation\n'));
|
|
13
|
+
// If no key provided, prompt for it
|
|
14
|
+
if (!licenseKey) {
|
|
15
|
+
licenseKey = await askQuestion(chalk_1.default.cyan('Enter your license key: '));
|
|
16
|
+
}
|
|
17
|
+
if (!licenseKey || !licenseKey.trim()) {
|
|
18
|
+
console.log(chalk_1.default.red('❌ No license key provided'));
|
|
19
|
+
process.exit(1);
|
|
20
|
+
}
|
|
21
|
+
licenseKey = licenseKey.trim();
|
|
22
|
+
// Validate format (basic check)
|
|
23
|
+
if (licenseKey.length < 10) {
|
|
24
|
+
console.log(chalk_1.default.red('❌ Invalid license key format'));
|
|
25
|
+
process.exit(1);
|
|
26
|
+
}
|
|
27
|
+
console.log(chalk_1.default.gray('Validating license key...'));
|
|
28
|
+
// TODO: Validate against API in production
|
|
29
|
+
// For now, accept any key that looks valid
|
|
30
|
+
let plan;
|
|
31
|
+
if (licenseKey.toLowerCase().includes('yearly') || licenseKey.toLowerCase().includes('annual')) {
|
|
32
|
+
plan = 'pro_yearly';
|
|
33
|
+
}
|
|
34
|
+
else {
|
|
35
|
+
plan = 'pro_monthly';
|
|
36
|
+
}
|
|
37
|
+
try {
|
|
38
|
+
(0, manager_1.activateLicense)(licenseKey, undefined, plan);
|
|
39
|
+
console.log(chalk_1.default.green('\n✅ License activated successfully!\n'));
|
|
40
|
+
const license = (0, manager_1.getCurrentLicense)();
|
|
41
|
+
if (license) {
|
|
42
|
+
console.log(chalk_1.default.white.bold('License Details:'));
|
|
43
|
+
console.log(chalk_1.default.gray(` Plan: ${license.plan === 'pro_monthly' ? 'Pro Monthly ($9.99/mo)' : 'Pro Yearly ($100/yr)'}`));
|
|
44
|
+
console.log(chalk_1.default.gray(` Status: ${license.status}`));
|
|
45
|
+
if (license.expires_at) {
|
|
46
|
+
const expiresDate = new Date(license.expires_at);
|
|
47
|
+
console.log(chalk_1.default.gray(` Expires: ${expiresDate.toLocaleDateString()}`));
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
console.log();
|
|
51
|
+
console.log(chalk_1.default.green('🎉 You now have unlimited AI commits!'));
|
|
52
|
+
console.log(chalk_1.default.gray(' Try: ') + chalk_1.default.cyan('snapcommit quick'));
|
|
53
|
+
console.log();
|
|
54
|
+
}
|
|
55
|
+
catch (error) {
|
|
56
|
+
console.log(chalk_1.default.red('\n❌ Failed to activate license'));
|
|
57
|
+
console.log(chalk_1.default.gray(` ${error.message}`));
|
|
58
|
+
process.exit(1);
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
function statusCommand() {
|
|
62
|
+
console.log(chalk_1.default.blue.bold('\n📊 SnapCommit License Status\n'));
|
|
63
|
+
const license = (0, manager_1.getCurrentLicense)();
|
|
64
|
+
if (!license) {
|
|
65
|
+
console.log(chalk_1.default.yellow('⚠️ No license found'));
|
|
66
|
+
console.log(chalk_1.default.gray(' You have a 7-day free trial'));
|
|
67
|
+
console.log();
|
|
68
|
+
console.log(chalk_1.default.cyan('Upgrade to Pro:'));
|
|
69
|
+
console.log(chalk_1.default.gray(' • Unlimited AI commits'));
|
|
70
|
+
console.log(chalk_1.default.gray(' • Advanced stats'));
|
|
71
|
+
console.log(chalk_1.default.gray(' • Priority support'));
|
|
72
|
+
console.log();
|
|
73
|
+
console.log(chalk_1.default.white('Visit: ') + chalk_1.default.cyan('https://snapcommit.dev/pricing'));
|
|
74
|
+
console.log();
|
|
75
|
+
return;
|
|
76
|
+
}
|
|
77
|
+
const info = (0, manager_1.getLicenseInfo)();
|
|
78
|
+
console.log(chalk_1.default.white.bold('Current License:'));
|
|
79
|
+
console.log(chalk_1.default.gray(` ${info}`));
|
|
80
|
+
console.log();
|
|
81
|
+
if (license.plan === 'free') {
|
|
82
|
+
const usage = require('../license/manager').getTrialStatus();
|
|
83
|
+
if (usage.isExpired) {
|
|
84
|
+
console.log(chalk_1.default.red('❌ Your trial has expired!'));
|
|
85
|
+
console.log(chalk_1.default.white('Upgrade: ') + chalk_1.default.cyan('https://snapcommit.dev/pricing'));
|
|
86
|
+
}
|
|
87
|
+
else {
|
|
88
|
+
console.log(chalk_1.default.yellow(`💡 ${usage.daysRemaining} day${usage.daysRemaining === 1 ? '' : 's'} left in your trial!`));
|
|
89
|
+
console.log(chalk_1.default.white('Upgrade: ') + chalk_1.default.cyan('https://snapcommit.dev/pricing'));
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
else {
|
|
93
|
+
console.log(chalk_1.default.green('✅ You have Pro access!'));
|
|
94
|
+
}
|
|
95
|
+
console.log();
|
|
96
|
+
}
|
|
97
|
+
function askQuestion(query) {
|
|
98
|
+
const rl = readline_1.default.createInterface({
|
|
99
|
+
input: process.stdin,
|
|
100
|
+
output: process.stdout,
|
|
101
|
+
});
|
|
102
|
+
return new Promise((resolve) => {
|
|
103
|
+
rl.question(query, (answer) => {
|
|
104
|
+
rl.close();
|
|
105
|
+
resolve(answer);
|
|
106
|
+
});
|
|
107
|
+
});
|
|
108
|
+
}
|
|
@@ -0,0 +1,255 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
14
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
15
|
+
}) : function(o, v) {
|
|
16
|
+
o["default"] = v;
|
|
17
|
+
});
|
|
18
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
19
|
+
var ownKeys = function(o) {
|
|
20
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
21
|
+
var ar = [];
|
|
22
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
23
|
+
return ar;
|
|
24
|
+
};
|
|
25
|
+
return ownKeys(o);
|
|
26
|
+
};
|
|
27
|
+
return function (mod) {
|
|
28
|
+
if (mod && mod.__esModule) return mod;
|
|
29
|
+
var result = {};
|
|
30
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
31
|
+
__setModuleDefault(result, mod);
|
|
32
|
+
return result;
|
|
33
|
+
};
|
|
34
|
+
})();
|
|
35
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
36
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
37
|
+
};
|
|
38
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
39
|
+
exports.commitCommand = commitCommand;
|
|
40
|
+
const chalk_1 = __importDefault(require("chalk"));
|
|
41
|
+
const git_1 = require("../utils/git");
|
|
42
|
+
const database_1 = require("../db/database");
|
|
43
|
+
const manager_1 = require("../license/manager");
|
|
44
|
+
const rate_limit_1 = require("../utils/rate-limit");
|
|
45
|
+
const analytics_1 = require("../utils/analytics");
|
|
46
|
+
const readline_1 = __importDefault(require("readline"));
|
|
47
|
+
async function commitCommand() {
|
|
48
|
+
// Check license before proceeding
|
|
49
|
+
const { allowed, reason, usage } = (0, manager_1.canUseCommit)();
|
|
50
|
+
if (!allowed) {
|
|
51
|
+
console.log(chalk_1.default.red('\n❌ ' + reason));
|
|
52
|
+
if (usage) {
|
|
53
|
+
console.log(chalk_1.default.gray(` Your 7-day trial has ended`));
|
|
54
|
+
}
|
|
55
|
+
console.log();
|
|
56
|
+
console.log(chalk_1.default.yellow('⭐ Upgrade to Pro to keep using SnapCommit!'));
|
|
57
|
+
console.log(chalk_1.default.gray(' • $9.99/month or $100/year'));
|
|
58
|
+
console.log(chalk_1.default.gray(' • Unlimited AI commits'));
|
|
59
|
+
console.log();
|
|
60
|
+
console.log(chalk_1.default.white('Visit: ') + chalk_1.default.cyan('https://snapcommit.dev/pricing'));
|
|
61
|
+
console.log();
|
|
62
|
+
process.exit(1);
|
|
63
|
+
}
|
|
64
|
+
// Check rate limit (prevent abuse)
|
|
65
|
+
const userPlan = (0, manager_1.isProUser)() ? 'pro' : 'free';
|
|
66
|
+
const rateLimit = (0, rate_limit_1.checkRateLimit)(userPlan);
|
|
67
|
+
if (!rateLimit.allowed) {
|
|
68
|
+
(0, analytics_1.trackEvent)({ event: analytics_1.EVENTS.RATE_LIMITED });
|
|
69
|
+
console.log(chalk_1.default.red('\n❌ Rate limit exceeded'));
|
|
70
|
+
console.log(chalk_1.default.gray(` Too many commits in the last hour`));
|
|
71
|
+
console.log(chalk_1.default.gray(` Limit resets in ${(0, rate_limit_1.formatResetTime)(rateLimit.resetAt)}`));
|
|
72
|
+
console.log();
|
|
73
|
+
if (userPlan === 'free') {
|
|
74
|
+
console.log(chalk_1.default.yellow('💡 Pro users have 10x higher limits!'));
|
|
75
|
+
console.log(chalk_1.default.white(' Visit: ') + chalk_1.default.cyan('https://snapcommit.dev/pricing'));
|
|
76
|
+
}
|
|
77
|
+
console.log();
|
|
78
|
+
process.exit(1);
|
|
79
|
+
}
|
|
80
|
+
console.log(chalk_1.default.blue('🔍 Analyzing your changes...\n'));
|
|
81
|
+
// Check if we're in a git repo
|
|
82
|
+
if (!(0, git_1.isGitRepo)()) {
|
|
83
|
+
console.log(chalk_1.default.red('❌ Not a git repository'));
|
|
84
|
+
console.log(chalk_1.default.gray(' Run this command inside a git repo'));
|
|
85
|
+
process.exit(1);
|
|
86
|
+
}
|
|
87
|
+
// Get status
|
|
88
|
+
let status;
|
|
89
|
+
try {
|
|
90
|
+
status = (0, git_1.getGitStatus)();
|
|
91
|
+
}
|
|
92
|
+
catch (error) {
|
|
93
|
+
console.log(chalk_1.default.red('❌ Failed to get git status'));
|
|
94
|
+
console.log(chalk_1.default.gray(` ${error.message}`));
|
|
95
|
+
process.exit(1);
|
|
96
|
+
}
|
|
97
|
+
// If nothing staged, ask if they want to stage all
|
|
98
|
+
if (status.staged === 0 && (status.unstaged > 0 || status.untracked > 0)) {
|
|
99
|
+
console.log(chalk_1.default.yellow('⚠️ No staged changes found'));
|
|
100
|
+
console.log(chalk_1.default.gray(` ${status.unstaged} modified, ${status.untracked} untracked`));
|
|
101
|
+
console.log();
|
|
102
|
+
const answer = await askQuestion(chalk_1.default.cyan(' Stage all changes? (Y/n): '));
|
|
103
|
+
if (answer.toLowerCase() !== 'n') {
|
|
104
|
+
try {
|
|
105
|
+
(0, git_1.stageAllChanges)();
|
|
106
|
+
console.log(chalk_1.default.green('✓ Staged all changes\n'));
|
|
107
|
+
status = (0, git_1.getGitStatus)();
|
|
108
|
+
}
|
|
109
|
+
catch (error) {
|
|
110
|
+
console.log(chalk_1.default.red('❌ Failed to stage changes'));
|
|
111
|
+
console.log(chalk_1.default.gray(` ${error.message}`));
|
|
112
|
+
process.exit(1);
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
else {
|
|
116
|
+
console.log(chalk_1.default.gray(' Cancelled. Stage files with: git add <files>'));
|
|
117
|
+
process.exit(0);
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
if (status.staged === 0) {
|
|
121
|
+
console.log(chalk_1.default.yellow('⚠️ No changes to commit'));
|
|
122
|
+
console.log(chalk_1.default.gray(' Make some changes and stage them with git add'));
|
|
123
|
+
process.exit(0);
|
|
124
|
+
}
|
|
125
|
+
// Get the diff
|
|
126
|
+
let diff;
|
|
127
|
+
try {
|
|
128
|
+
diff = (0, git_1.getGitDiff)(true);
|
|
129
|
+
}
|
|
130
|
+
catch (error) {
|
|
131
|
+
console.log(chalk_1.default.red('❌ Failed to get git diff'));
|
|
132
|
+
console.log(chalk_1.default.gray(` ${error.message}`));
|
|
133
|
+
process.exit(1);
|
|
134
|
+
}
|
|
135
|
+
// Check if diff is too large (>50KB)
|
|
136
|
+
if (diff.length > 50000) {
|
|
137
|
+
console.log(chalk_1.default.yellow('⚠️ Large diff detected'));
|
|
138
|
+
console.log(chalk_1.default.gray(` ${Math.round(diff.length / 1024)}KB of changes`));
|
|
139
|
+
console.log(chalk_1.default.gray(' This might take a moment...\n'));
|
|
140
|
+
// Truncate diff for AI (keep first 40KB)
|
|
141
|
+
diff = diff.substring(0, 40000) + '\n\n... (diff truncated for AI processing)';
|
|
142
|
+
}
|
|
143
|
+
// Show status
|
|
144
|
+
const branch = (0, git_1.getCurrentBranch)();
|
|
145
|
+
console.log(chalk_1.default.gray(` Branch: ${branch}`));
|
|
146
|
+
console.log(chalk_1.default.gray(` Files: ${status.staged} staged`));
|
|
147
|
+
console.log();
|
|
148
|
+
// Generate multiple commit message options with AI
|
|
149
|
+
console.log(chalk_1.default.blue('🤖 Analyzing changes and generating commit messages...'));
|
|
150
|
+
console.log();
|
|
151
|
+
const { generateCommitOptions, displayCommitOptions, editCommitMessage } = await Promise.resolve().then(() => __importStar(require('../ai/commit-generator')));
|
|
152
|
+
let options;
|
|
153
|
+
try {
|
|
154
|
+
options = await generateCommitOptions(diff);
|
|
155
|
+
}
|
|
156
|
+
catch (error) {
|
|
157
|
+
console.log(chalk_1.default.red('❌ Failed to generate commit message'));
|
|
158
|
+
console.log(chalk_1.default.gray(` ${error.message}`));
|
|
159
|
+
console.log();
|
|
160
|
+
console.log(chalk_1.default.yellow('💡 Tip: Check your ANTHROPIC_API_KEY in .env'));
|
|
161
|
+
process.exit(1);
|
|
162
|
+
}
|
|
163
|
+
// Display all options
|
|
164
|
+
displayCommitOptions(options);
|
|
165
|
+
// Ask user to choose
|
|
166
|
+
const choice = await askQuestion(chalk_1.default.cyan('Choose option (1/2/3) or ') +
|
|
167
|
+
chalk_1.default.gray('(e)dit / (c)ancel: '));
|
|
168
|
+
let message;
|
|
169
|
+
if (choice.toLowerCase() === 'c' || choice.toLowerCase() === 'cancel') {
|
|
170
|
+
console.log(chalk_1.default.gray('\nCancelled.'));
|
|
171
|
+
process.exit(0);
|
|
172
|
+
}
|
|
173
|
+
else if (choice.toLowerCase() === 'e' || choice.toLowerCase() === 'edit') {
|
|
174
|
+
// Let user edit the first option
|
|
175
|
+
message = await editCommitMessage(options[0].message);
|
|
176
|
+
}
|
|
177
|
+
else {
|
|
178
|
+
// User chose a number
|
|
179
|
+
const optionIndex = parseInt(choice) - 1;
|
|
180
|
+
if (optionIndex >= 0 && optionIndex < options.length) {
|
|
181
|
+
message = options[optionIndex].message;
|
|
182
|
+
}
|
|
183
|
+
else {
|
|
184
|
+
// Default to first option
|
|
185
|
+
message = options[0].message;
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
// Commit
|
|
189
|
+
let hash;
|
|
190
|
+
try {
|
|
191
|
+
hash = (0, git_1.commitWithMessage)(message);
|
|
192
|
+
console.log(chalk_1.default.green('\n✅ Committed successfully!\n'));
|
|
193
|
+
// Get commit stats
|
|
194
|
+
const stats = (0, git_1.getCommitStats)(hash);
|
|
195
|
+
console.log(chalk_1.default.gray(` Hash: ${hash.substring(0, 7)}`));
|
|
196
|
+
console.log(chalk_1.default.gray(` Files: ${stats.files}`));
|
|
197
|
+
console.log(chalk_1.default.gray(` +${stats.insertions} -${stats.deletions}`));
|
|
198
|
+
console.log();
|
|
199
|
+
// Track usage
|
|
200
|
+
(0, manager_1.trackCommit)();
|
|
201
|
+
(0, analytics_1.trackEvent)({ event: analytics_1.EVENTS.COMMIT_SUCCESS });
|
|
202
|
+
// Log to database
|
|
203
|
+
(0, database_1.logCommit)({
|
|
204
|
+
message,
|
|
205
|
+
hash,
|
|
206
|
+
files_changed: stats.files,
|
|
207
|
+
insertions: stats.insertions,
|
|
208
|
+
deletions: stats.deletions,
|
|
209
|
+
timestamp: Date.now(),
|
|
210
|
+
});
|
|
211
|
+
// Sync to cloud (non-blocking, silent fail)
|
|
212
|
+
try {
|
|
213
|
+
const { syncCommitToCloud } = await Promise.resolve().then(() => __importStar(require('../auth/supabase-client')));
|
|
214
|
+
const syncResult = await syncCommitToCloud({
|
|
215
|
+
message,
|
|
216
|
+
hash,
|
|
217
|
+
files_changed: stats.files,
|
|
218
|
+
insertions: stats.insertions,
|
|
219
|
+
deletions: stats.deletions,
|
|
220
|
+
timestamp: Date.now(),
|
|
221
|
+
});
|
|
222
|
+
if (syncResult.success) {
|
|
223
|
+
console.log(chalk_1.default.gray(' ☁️ Synced to cloud\n'));
|
|
224
|
+
}
|
|
225
|
+
}
|
|
226
|
+
catch (error) {
|
|
227
|
+
// Silent fail - cloud sync is optional
|
|
228
|
+
}
|
|
229
|
+
// Show dopamine stats
|
|
230
|
+
const { displayQuickDopamine } = await Promise.resolve().then(() => __importStar(require('../utils/dopamine')));
|
|
231
|
+
displayQuickDopamine();
|
|
232
|
+
// Show upgrade message for trial users (last 2 days)
|
|
233
|
+
if (!(0, manager_1.isProUser)() && usage && !usage.isExpired && usage.daysRemaining <= 2) {
|
|
234
|
+
console.log(chalk_1.default.yellow(`⚠️ Only ${usage.daysRemaining} day${usage.daysRemaining === 1 ? '' : 's'} left in your trial!`));
|
|
235
|
+
console.log(chalk_1.default.gray(' Upgrade to Pro: https://snapcommit.dev/pricing\n'));
|
|
236
|
+
}
|
|
237
|
+
}
|
|
238
|
+
catch (error) {
|
|
239
|
+
console.log(chalk_1.default.red('❌ Commit failed'));
|
|
240
|
+
console.log(chalk_1.default.gray(` ${error.message}`));
|
|
241
|
+
process.exit(1);
|
|
242
|
+
}
|
|
243
|
+
}
|
|
244
|
+
function askQuestion(query) {
|
|
245
|
+
const rl = readline_1.default.createInterface({
|
|
246
|
+
input: process.stdin,
|
|
247
|
+
output: process.stdout,
|
|
248
|
+
});
|
|
249
|
+
return new Promise((resolve) => {
|
|
250
|
+
rl.question(query, (answer) => {
|
|
251
|
+
rl.close();
|
|
252
|
+
resolve(answer);
|
|
253
|
+
});
|
|
254
|
+
});
|
|
255
|
+
}
|