@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,173 @@
|
|
|
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.quickCommand = quickCommand;
|
|
40
|
+
const chalk_1 = __importDefault(require("chalk"));
|
|
41
|
+
const git_1 = require("../utils/git");
|
|
42
|
+
const database_1 = require("../db/database");
|
|
43
|
+
const auth_1 = require("../lib/auth");
|
|
44
|
+
const analytics_1 = require("../utils/analytics");
|
|
45
|
+
// API URL - defaults to production, can be overridden for development
|
|
46
|
+
const API_BASE_URL = process.env.SNAPCOMMIT_API_URL || 'https://snapcommit.dev';
|
|
47
|
+
/**
|
|
48
|
+
* Generate commit message using backend API
|
|
49
|
+
*/
|
|
50
|
+
async function generateCommitMessageAPI(diff, token) {
|
|
51
|
+
try {
|
|
52
|
+
const response = await fetch(`${API_BASE_URL}/api/ai/commit`, {
|
|
53
|
+
method: 'POST',
|
|
54
|
+
headers: { 'Content-Type': 'application/json' },
|
|
55
|
+
body: JSON.stringify({ diff, token }),
|
|
56
|
+
});
|
|
57
|
+
const data = await response.json();
|
|
58
|
+
if (!response.ok) {
|
|
59
|
+
if (response.status === 401) {
|
|
60
|
+
throw new Error('Authentication failed. Please log in again: snapcommit logout && snapcommit');
|
|
61
|
+
}
|
|
62
|
+
if (response.status === 403) {
|
|
63
|
+
throw new Error(data.message || 'Subscription required. Visit https://snapcommit.dev/pricing');
|
|
64
|
+
}
|
|
65
|
+
if (response.status === 429) {
|
|
66
|
+
throw new Error(data.error || 'Rate limit exceeded');
|
|
67
|
+
}
|
|
68
|
+
throw new Error(data.error || 'Failed to generate commit message');
|
|
69
|
+
}
|
|
70
|
+
return { message: data.message, usage: data.usage };
|
|
71
|
+
}
|
|
72
|
+
catch (error) {
|
|
73
|
+
if (error.message.includes('fetch')) {
|
|
74
|
+
throw new Error('Network error. Please check your internet connection.');
|
|
75
|
+
}
|
|
76
|
+
throw error;
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
/**
|
|
80
|
+
* Quick commit - stage all, generate message, commit
|
|
81
|
+
* No prompts, just do it (for rapid iteration)
|
|
82
|
+
*/
|
|
83
|
+
async function quickCommand() {
|
|
84
|
+
// Ensure authentication
|
|
85
|
+
const authConfig = await (0, auth_1.ensureAuth)();
|
|
86
|
+
if (!authConfig) {
|
|
87
|
+
console.log(chalk_1.default.red('\nā Authentication required to use SnapCommit\n'));
|
|
88
|
+
process.exit(1);
|
|
89
|
+
}
|
|
90
|
+
const token = (0, auth_1.getToken)();
|
|
91
|
+
if (!token) {
|
|
92
|
+
console.log(chalk_1.default.red('\nā No authentication token found\n'));
|
|
93
|
+
process.exit(1);
|
|
94
|
+
}
|
|
95
|
+
// Check git repo
|
|
96
|
+
if (!(0, git_1.isGitRepo)()) {
|
|
97
|
+
console.log(chalk_1.default.red('ā Not a git repository'));
|
|
98
|
+
process.exit(1);
|
|
99
|
+
}
|
|
100
|
+
console.log(chalk_1.default.blue('ā” Quick commit mode...\n'));
|
|
101
|
+
// Stage all changes
|
|
102
|
+
try {
|
|
103
|
+
(0, git_1.stageAllChanges)();
|
|
104
|
+
console.log(chalk_1.default.gray('ā Staged all changes'));
|
|
105
|
+
}
|
|
106
|
+
catch (error) {
|
|
107
|
+
console.log(chalk_1.default.red('ā Failed to stage changes'));
|
|
108
|
+
process.exit(1);
|
|
109
|
+
}
|
|
110
|
+
// Get diff
|
|
111
|
+
let diff;
|
|
112
|
+
try {
|
|
113
|
+
diff = (0, git_1.getGitDiff)(true);
|
|
114
|
+
}
|
|
115
|
+
catch (error) {
|
|
116
|
+
console.log(chalk_1.default.red('ā Failed to get diff'));
|
|
117
|
+
process.exit(1);
|
|
118
|
+
}
|
|
119
|
+
if (!diff.trim()) {
|
|
120
|
+
console.log(chalk_1.default.yellow('ā ļø No changes to commit'));
|
|
121
|
+
process.exit(0);
|
|
122
|
+
}
|
|
123
|
+
// Truncate large diffs
|
|
124
|
+
if (diff.length > 40000) {
|
|
125
|
+
diff = diff.substring(0, 40000);
|
|
126
|
+
}
|
|
127
|
+
// Generate message
|
|
128
|
+
console.log(chalk_1.default.gray('ā Generating commit message...'));
|
|
129
|
+
let message;
|
|
130
|
+
let usage;
|
|
131
|
+
try {
|
|
132
|
+
const result = await generateCommitMessageAPI(diff, token);
|
|
133
|
+
message = result.message;
|
|
134
|
+
usage = result.usage;
|
|
135
|
+
}
|
|
136
|
+
catch (error) {
|
|
137
|
+
(0, analytics_1.trackEvent)({ event: 'commit_error' });
|
|
138
|
+
console.log(chalk_1.default.red(`\nā ${error.message}\n`));
|
|
139
|
+
process.exit(1);
|
|
140
|
+
}
|
|
141
|
+
// Show message
|
|
142
|
+
console.log(chalk_1.default.green(`ā ${message.split('\n')[0]}`));
|
|
143
|
+
// Commit
|
|
144
|
+
try {
|
|
145
|
+
const hash = (0, git_1.commitWithMessage)(message);
|
|
146
|
+
const stats = (0, git_1.getCommitStats)(hash);
|
|
147
|
+
console.log(chalk_1.default.green(`ā Committed ${hash.substring(0, 7)}`));
|
|
148
|
+
console.log(chalk_1.default.gray(` +${stats.insertions} -${stats.deletions} across ${stats.files} files\n`));
|
|
149
|
+
// Track event
|
|
150
|
+
(0, analytics_1.trackEvent)({ event: 'commit_success' });
|
|
151
|
+
// Log to database
|
|
152
|
+
(0, database_1.logCommit)({
|
|
153
|
+
message,
|
|
154
|
+
hash,
|
|
155
|
+
files_changed: stats.files,
|
|
156
|
+
insertions: stats.insertions,
|
|
157
|
+
deletions: stats.deletions,
|
|
158
|
+
timestamp: Date.now(),
|
|
159
|
+
});
|
|
160
|
+
// Show dopamine stats
|
|
161
|
+
try {
|
|
162
|
+
const { displayQuickDopamine } = await Promise.resolve().then(() => __importStar(require('../utils/dopamine')));
|
|
163
|
+
displayQuickDopamine();
|
|
164
|
+
}
|
|
165
|
+
catch (error) {
|
|
166
|
+
// Optional feature - don't fail if it doesn't work
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
catch (error) {
|
|
170
|
+
console.log(chalk_1.default.red('ā Commit failed'));
|
|
171
|
+
process.exit(1);
|
|
172
|
+
}
|
|
173
|
+
}
|
|
@@ -0,0 +1,163 @@
|
|
|
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.setupCommand = setupCommand;
|
|
7
|
+
const chalk_1 = __importDefault(require("chalk"));
|
|
8
|
+
const fs_1 = __importDefault(require("fs"));
|
|
9
|
+
const os_1 = __importDefault(require("os"));
|
|
10
|
+
const path_1 = __importDefault(require("path"));
|
|
11
|
+
function setupCommand() {
|
|
12
|
+
console.log(chalk_1.default.blue.bold('\nāļø SnapCommit Setup\n'));
|
|
13
|
+
const shell = process.env.SHELL || '';
|
|
14
|
+
const homeDir = os_1.default.homedir();
|
|
15
|
+
const platform = process.platform;
|
|
16
|
+
// Windows
|
|
17
|
+
if (platform === 'win32') {
|
|
18
|
+
setupWindows(homeDir);
|
|
19
|
+
}
|
|
20
|
+
// Unix-like (Mac, Linux)
|
|
21
|
+
else if (shell.includes('zsh')) {
|
|
22
|
+
setupZsh(homeDir);
|
|
23
|
+
}
|
|
24
|
+
else if (shell.includes('bash')) {
|
|
25
|
+
setupBash(homeDir);
|
|
26
|
+
}
|
|
27
|
+
else if (shell.includes('fish')) {
|
|
28
|
+
setupFish(homeDir);
|
|
29
|
+
}
|
|
30
|
+
else {
|
|
31
|
+
console.log(chalk_1.default.yellow('ā ļø Shell not detected or not supported'));
|
|
32
|
+
console.log(chalk_1.default.gray(' Supports: zsh, bash, fish (Mac/Linux), PowerShell (Windows)'));
|
|
33
|
+
console.log(chalk_1.default.gray(` Your shell: ${shell || 'unknown'}`));
|
|
34
|
+
console.log();
|
|
35
|
+
console.log(chalk_1.default.white('š” You can still use SnapCommit:'));
|
|
36
|
+
console.log(chalk_1.default.cyan(' snapcommit quick') + chalk_1.default.gray(' - Quick commit'));
|
|
37
|
+
console.log(chalk_1.default.cyan(' snapcommit stats') + chalk_1.default.gray(' - See stats'));
|
|
38
|
+
console.log();
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
function setupZsh(homeDir) {
|
|
42
|
+
const zshrcPath = path_1.default.join(homeDir, '.zshrc');
|
|
43
|
+
const integration = `
|
|
44
|
+
# SnapCommit Integration
|
|
45
|
+
alias bos="builderos"
|
|
46
|
+
alias bq="snapcommit quick"
|
|
47
|
+
alias bs="snapcommit stats"
|
|
48
|
+
`;
|
|
49
|
+
if (!fs_1.default.existsSync(zshrcPath)) {
|
|
50
|
+
console.log(chalk_1.default.yellow('ā ļø .zshrc not found, creating one...'));
|
|
51
|
+
fs_1.default.writeFileSync(zshrcPath, integration);
|
|
52
|
+
}
|
|
53
|
+
else {
|
|
54
|
+
const content = fs_1.default.readFileSync(zshrcPath, 'utf-8');
|
|
55
|
+
if (content.includes('SnapCommit Integration')) {
|
|
56
|
+
console.log(chalk_1.default.green('ā
SnapCommit already set up in .zshrc'));
|
|
57
|
+
return;
|
|
58
|
+
}
|
|
59
|
+
fs_1.default.appendFileSync(zshrcPath, integration);
|
|
60
|
+
}
|
|
61
|
+
console.log(chalk_1.default.green('ā
SnapCommit integrated with zsh!'));
|
|
62
|
+
console.log();
|
|
63
|
+
console.log(chalk_1.default.white.bold('Quick aliases:'));
|
|
64
|
+
console.log(chalk_1.default.gray(' bos ā builderos'));
|
|
65
|
+
console.log(chalk_1.default.gray(' bq ā snapcommit quick (instant commit)'));
|
|
66
|
+
console.log(chalk_1.default.gray(' bs ā snapcommit stats'));
|
|
67
|
+
console.log();
|
|
68
|
+
console.log(chalk_1.default.yellow('ā” Run this to activate:'));
|
|
69
|
+
console.log(chalk_1.default.cyan(' source ~/.zshrc'));
|
|
70
|
+
console.log();
|
|
71
|
+
console.log(chalk_1.default.gray('Then just type') + chalk_1.default.cyan(' bq ') + chalk_1.default.gray('to commit your changes!'));
|
|
72
|
+
console.log();
|
|
73
|
+
}
|
|
74
|
+
function setupBash(homeDir) {
|
|
75
|
+
const bashrcPath = path_1.default.join(homeDir, '.bashrc');
|
|
76
|
+
const integration = `
|
|
77
|
+
# SnapCommit Integration
|
|
78
|
+
alias bos="builderos"
|
|
79
|
+
alias bq="snapcommit quick"
|
|
80
|
+
alias bs="snapcommit stats"
|
|
81
|
+
`;
|
|
82
|
+
if (!fs_1.default.existsSync(bashrcPath)) {
|
|
83
|
+
console.log(chalk_1.default.yellow('ā ļø .bashrc not found, creating one...'));
|
|
84
|
+
fs_1.default.writeFileSync(bashrcPath, integration);
|
|
85
|
+
}
|
|
86
|
+
else {
|
|
87
|
+
const content = fs_1.default.readFileSync(bashrcPath, 'utf-8');
|
|
88
|
+
if (content.includes('SnapCommit Integration')) {
|
|
89
|
+
console.log(chalk_1.default.green('ā
SnapCommit already set up in .bashrc'));
|
|
90
|
+
return;
|
|
91
|
+
}
|
|
92
|
+
fs_1.default.appendFileSync(bashrcPath, integration);
|
|
93
|
+
}
|
|
94
|
+
console.log(chalk_1.default.green('ā
SnapCommit integrated with bash!'));
|
|
95
|
+
console.log();
|
|
96
|
+
console.log(chalk_1.default.white.bold('Quick aliases:'));
|
|
97
|
+
console.log(chalk_1.default.gray(' bos ā builderos'));
|
|
98
|
+
console.log(chalk_1.default.gray(' bq ā snapcommit quick (instant commit)'));
|
|
99
|
+
console.log(chalk_1.default.gray(' bs ā snapcommit stats'));
|
|
100
|
+
console.log();
|
|
101
|
+
console.log(chalk_1.default.yellow('ā” Run this to activate:'));
|
|
102
|
+
console.log(chalk_1.default.cyan(' source ~/.bashrc'));
|
|
103
|
+
console.log();
|
|
104
|
+
console.log(chalk_1.default.gray('Then just type') + chalk_1.default.cyan(' bq ') + chalk_1.default.gray('to commit your changes!'));
|
|
105
|
+
console.log();
|
|
106
|
+
}
|
|
107
|
+
function setupFish(homeDir) {
|
|
108
|
+
const fishConfigDir = path_1.default.join(homeDir, '.config', 'fish');
|
|
109
|
+
const fishConfigPath = path_1.default.join(fishConfigDir, 'config.fish');
|
|
110
|
+
const integration = `
|
|
111
|
+
# SnapCommit Integration
|
|
112
|
+
alias bos="builderos"
|
|
113
|
+
alias bq="snapcommit quick"
|
|
114
|
+
alias bs="snapcommit stats"
|
|
115
|
+
`;
|
|
116
|
+
// Ensure config directory exists
|
|
117
|
+
if (!fs_1.default.existsSync(fishConfigDir)) {
|
|
118
|
+
fs_1.default.mkdirSync(fishConfigDir, { recursive: true });
|
|
119
|
+
}
|
|
120
|
+
if (!fs_1.default.existsSync(fishConfigPath)) {
|
|
121
|
+
console.log(chalk_1.default.yellow('ā ļø config.fish not found, creating one...'));
|
|
122
|
+
fs_1.default.writeFileSync(fishConfigPath, integration);
|
|
123
|
+
}
|
|
124
|
+
else {
|
|
125
|
+
const content = fs_1.default.readFileSync(fishConfigPath, 'utf-8');
|
|
126
|
+
if (content.includes('SnapCommit Integration')) {
|
|
127
|
+
console.log(chalk_1.default.green('ā
SnapCommit already set up in Fish'));
|
|
128
|
+
return;
|
|
129
|
+
}
|
|
130
|
+
fs_1.default.appendFileSync(fishConfigPath, integration);
|
|
131
|
+
}
|
|
132
|
+
console.log(chalk_1.default.green('ā
SnapCommit integrated with Fish!'));
|
|
133
|
+
console.log();
|
|
134
|
+
console.log(chalk_1.default.white.bold('Quick aliases:'));
|
|
135
|
+
console.log(chalk_1.default.gray(' bos ā builderos'));
|
|
136
|
+
console.log(chalk_1.default.gray(' bq ā snapcommit quick (instant commit)'));
|
|
137
|
+
console.log(chalk_1.default.gray(' bs ā snapcommit stats'));
|
|
138
|
+
console.log();
|
|
139
|
+
console.log(chalk_1.default.yellow('ā” Reload config:'));
|
|
140
|
+
console.log(chalk_1.default.cyan(' source ~/.config/fish/config.fish'));
|
|
141
|
+
console.log();
|
|
142
|
+
console.log(chalk_1.default.gray('Then just type') + chalk_1.default.cyan(' bq ') + chalk_1.default.gray('to commit your changes!'));
|
|
143
|
+
console.log();
|
|
144
|
+
}
|
|
145
|
+
function setupWindows(homeDir) {
|
|
146
|
+
console.log(chalk_1.default.green('ā
Windows detected!'));
|
|
147
|
+
console.log();
|
|
148
|
+
console.log(chalk_1.default.white.bold('For PowerShell:'));
|
|
149
|
+
console.log(chalk_1.default.gray(' Add these aliases to your PowerShell profile:'));
|
|
150
|
+
console.log();
|
|
151
|
+
console.log(chalk_1.default.cyan(' Set-Alias bos builderos'));
|
|
152
|
+
console.log(chalk_1.default.cyan(' function bq { snapcommit quick }'));
|
|
153
|
+
console.log(chalk_1.default.cyan(' function bs { snapcommit stats }'));
|
|
154
|
+
console.log();
|
|
155
|
+
console.log(chalk_1.default.gray('To edit your profile, run:'));
|
|
156
|
+
console.log(chalk_1.default.cyan(' notepad $PROFILE'));
|
|
157
|
+
console.log();
|
|
158
|
+
console.log(chalk_1.default.white.bold('For Git Bash / WSL:'));
|
|
159
|
+
console.log(chalk_1.default.gray(' Run setup again in your bash shell'));
|
|
160
|
+
console.log();
|
|
161
|
+
console.log(chalk_1.default.gray('Then just type') + chalk_1.default.cyan(' bq ') + chalk_1.default.gray('to commit your changes!'));
|
|
162
|
+
console.log();
|
|
163
|
+
}
|
|
@@ -0,0 +1,128 @@
|
|
|
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.statsCommand = statsCommand;
|
|
7
|
+
const chalk_1 = __importDefault(require("chalk"));
|
|
8
|
+
const database_1 = require("../db/database");
|
|
9
|
+
const dopamine_1 = require("../utils/dopamine");
|
|
10
|
+
const heatmap_1 = require("../utils/heatmap");
|
|
11
|
+
function statsCommand() {
|
|
12
|
+
console.log(chalk_1.default.blue.bold('\nš Your SnapCommit Stats\n'));
|
|
13
|
+
// Show dopamine stats first (most engaging)
|
|
14
|
+
(0, dopamine_1.displayDopamineStats)();
|
|
15
|
+
// Show GitHub-style contribution heatmap
|
|
16
|
+
(0, heatmap_1.displayHeatmap)(12); // Last 12 weeks
|
|
17
|
+
const stats7d = (0, database_1.getStats)(7);
|
|
18
|
+
const stats30d = (0, database_1.getStats)(30);
|
|
19
|
+
const statsAll = (0, database_1.getStats)(365);
|
|
20
|
+
// Quick summary cards
|
|
21
|
+
console.log(createStatCard('Last 7 Days', [
|
|
22
|
+
{ label: 'Commits', value: stats7d.totalCommits, color: 'green' },
|
|
23
|
+
{ label: 'Commands', value: stats7d.totalCommands, color: 'cyan' },
|
|
24
|
+
]));
|
|
25
|
+
console.log(createStatCard('Last 30 Days', [
|
|
26
|
+
{ label: 'Commits', value: stats30d.totalCommits, color: 'green' },
|
|
27
|
+
{ label: 'Commands', value: stats30d.totalCommands, color: 'cyan' },
|
|
28
|
+
]));
|
|
29
|
+
console.log(createStatCard('All Time', [
|
|
30
|
+
{ label: 'Commits', value: statsAll.totalCommits, color: 'green' },
|
|
31
|
+
{ label: 'Commands', value: statsAll.totalCommands, color: 'cyan' },
|
|
32
|
+
]));
|
|
33
|
+
// Commit streak
|
|
34
|
+
const streak = calculateStreak(statsAll.recentCommits);
|
|
35
|
+
if (streak > 0) {
|
|
36
|
+
console.log(chalk_1.default.yellow(`š„ Current Streak: ${streak} day${streak > 1 ? 's' : ''}`));
|
|
37
|
+
console.log();
|
|
38
|
+
}
|
|
39
|
+
// Recent commits
|
|
40
|
+
if (stats7d.recentCommits.length > 0) {
|
|
41
|
+
console.log(chalk_1.default.white.bold('Recent Commits:'));
|
|
42
|
+
stats7d.recentCommits.slice(0, 5).forEach((commit) => {
|
|
43
|
+
const timeAgo = formatTimeAgo(commit.timestamp);
|
|
44
|
+
const message = commit.message.split('\n')[0]; // First line only
|
|
45
|
+
const truncated = message.length > 60 ? message.substring(0, 57) + '...' : message;
|
|
46
|
+
console.log(chalk_1.default.gray(` ${timeAgo.padEnd(12)}`) +
|
|
47
|
+
chalk_1.default.white(truncated));
|
|
48
|
+
});
|
|
49
|
+
console.log();
|
|
50
|
+
}
|
|
51
|
+
else {
|
|
52
|
+
console.log(chalk_1.default.gray('No commits yet. Run') + chalk_1.default.cyan(' snapcommit commit') + chalk_1.default.gray(' to get started!'));
|
|
53
|
+
console.log();
|
|
54
|
+
}
|
|
55
|
+
// Productivity insights
|
|
56
|
+
if (statsAll.totalCommits > 0) {
|
|
57
|
+
const avgPerDay = (statsAll.totalCommits / 30).toFixed(1);
|
|
58
|
+
console.log(chalk_1.default.white.bold('Insights:'));
|
|
59
|
+
console.log(chalk_1.default.gray(` Average: ${chalk_1.default.cyan(avgPerDay)} commits/day (last 30 days)`));
|
|
60
|
+
if (parseFloat(avgPerDay) >= 3) {
|
|
61
|
+
console.log(chalk_1.default.gray(` Status: ${chalk_1.default.green('š On fire!')}`));
|
|
62
|
+
}
|
|
63
|
+
else if (parseFloat(avgPerDay) >= 1) {
|
|
64
|
+
console.log(chalk_1.default.gray(` Status: ${chalk_1.default.yellow('šŖ Steady progress')}`));
|
|
65
|
+
}
|
|
66
|
+
else {
|
|
67
|
+
console.log(chalk_1.default.gray(` Status: ${chalk_1.default.blue('š± Getting started')}`));
|
|
68
|
+
}
|
|
69
|
+
console.log();
|
|
70
|
+
}
|
|
71
|
+
console.log(chalk_1.default.gray('š” Keep building with SnapCommit!'));
|
|
72
|
+
console.log();
|
|
73
|
+
}
|
|
74
|
+
function createStatCard(title, stats) {
|
|
75
|
+
const width = 32;
|
|
76
|
+
const border = 'ā'.repeat(width);
|
|
77
|
+
let card = '\n';
|
|
78
|
+
card += chalk_1.default.gray(`ā${border}ā`) + '\n';
|
|
79
|
+
card += chalk_1.default.gray('ā ') + chalk_1.default.white.bold(title.padEnd(width - 1)) + chalk_1.default.gray('ā') + '\n';
|
|
80
|
+
card += chalk_1.default.gray(`ā${border}ā¤`) + '\n';
|
|
81
|
+
stats.forEach((stat) => {
|
|
82
|
+
const colorFn = stat.color === 'green' ? chalk_1.default.green :
|
|
83
|
+
stat.color === 'cyan' ? chalk_1.default.cyan :
|
|
84
|
+
stat.color === 'yellow' ? chalk_1.default.yellow : chalk_1.default.white;
|
|
85
|
+
const label = ` ${stat.label}:`;
|
|
86
|
+
const value = colorFn(stat.value.toString());
|
|
87
|
+
const padding = ' '.repeat(Math.max(0, width - label.length - stat.value.toString().length - 1));
|
|
88
|
+
card += chalk_1.default.gray('ā') + chalk_1.default.gray(label) + padding + value + ' ' + chalk_1.default.gray('ā') + '\n';
|
|
89
|
+
});
|
|
90
|
+
card += chalk_1.default.gray(`ā${border}ā`);
|
|
91
|
+
return card;
|
|
92
|
+
}
|
|
93
|
+
function calculateStreak(commits) {
|
|
94
|
+
if (commits.length === 0)
|
|
95
|
+
return 0;
|
|
96
|
+
const today = new Date();
|
|
97
|
+
today.setHours(0, 0, 0, 0);
|
|
98
|
+
const commitDates = commits.map(c => {
|
|
99
|
+
const d = new Date(c.timestamp);
|
|
100
|
+
d.setHours(0, 0, 0, 0);
|
|
101
|
+
return d.getTime();
|
|
102
|
+
});
|
|
103
|
+
const uniqueDates = [...new Set(commitDates)].sort((a, b) => b - a);
|
|
104
|
+
let streak = 0;
|
|
105
|
+
let currentDate = today.getTime();
|
|
106
|
+
for (const date of uniqueDates) {
|
|
107
|
+
if (date === currentDate || date === currentDate - 86400000) {
|
|
108
|
+
streak++;
|
|
109
|
+
currentDate = date;
|
|
110
|
+
}
|
|
111
|
+
else {
|
|
112
|
+
break;
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
return streak;
|
|
116
|
+
}
|
|
117
|
+
function formatTimeAgo(timestamp) {
|
|
118
|
+
const seconds = Math.floor((Date.now() - timestamp) / 1000);
|
|
119
|
+
if (seconds < 60)
|
|
120
|
+
return 'just now';
|
|
121
|
+
if (seconds < 3600)
|
|
122
|
+
return `${Math.floor(seconds / 60)}m ago`;
|
|
123
|
+
if (seconds < 86400)
|
|
124
|
+
return `${Math.floor(seconds / 3600)}h ago`;
|
|
125
|
+
if (seconds < 604800)
|
|
126
|
+
return `${Math.floor(seconds / 86400)}d ago`;
|
|
127
|
+
return `${Math.floor(seconds / 604800)}w ago`;
|
|
128
|
+
}
|
|
@@ -0,0 +1,131 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Uninstall command - removes shell integration
|
|
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.uninstallCommand = uninstallCommand;
|
|
10
|
+
const chalk_1 = __importDefault(require("chalk"));
|
|
11
|
+
const fs_1 = __importDefault(require("fs"));
|
|
12
|
+
const os_1 = __importDefault(require("os"));
|
|
13
|
+
const path_1 = __importDefault(require("path"));
|
|
14
|
+
const readline_1 = __importDefault(require("readline"));
|
|
15
|
+
function askQuestion(query) {
|
|
16
|
+
const rl = readline_1.default.createInterface({
|
|
17
|
+
input: process.stdin,
|
|
18
|
+
output: process.stdout,
|
|
19
|
+
});
|
|
20
|
+
return new Promise(resolve => rl.question(query, ans => {
|
|
21
|
+
rl.close();
|
|
22
|
+
resolve(ans);
|
|
23
|
+
}));
|
|
24
|
+
}
|
|
25
|
+
async function uninstallCommand() {
|
|
26
|
+
console.log(chalk_1.default.yellow.bold('\nš¢ Sorry to see you go!\n'));
|
|
27
|
+
// Confirm
|
|
28
|
+
const confirm = await askQuestion(chalk_1.default.yellow('Are you sure you want to uninstall SnapCommit? (y/N): '));
|
|
29
|
+
if (confirm.toLowerCase() !== 'y') {
|
|
30
|
+
console.log(chalk_1.default.gray('\nUninstall cancelled.\n'));
|
|
31
|
+
return;
|
|
32
|
+
}
|
|
33
|
+
console.log(chalk_1.default.blue('\nš§ Removing SnapCommit...\n'));
|
|
34
|
+
const homeDir = os_1.default.homedir();
|
|
35
|
+
const platform = process.platform;
|
|
36
|
+
let removed = false;
|
|
37
|
+
// Remove shell integration
|
|
38
|
+
if (platform === 'win32') {
|
|
39
|
+
// Windows (PowerShell)
|
|
40
|
+
const profilePath = path_1.default.join(homeDir, 'Documents', 'PowerShell', 'Microsoft.PowerShell_profile.ps1');
|
|
41
|
+
if (fs_1.default.existsSync(profilePath)) {
|
|
42
|
+
let content = fs_1.default.readFileSync(profilePath, 'utf-8');
|
|
43
|
+
const lines = content.split('\n');
|
|
44
|
+
const filtered = lines.filter(line => !line.includes('SnapCommit'));
|
|
45
|
+
if (lines.length !== filtered.length) {
|
|
46
|
+
fs_1.default.writeFileSync(profilePath, filtered.join('\n'));
|
|
47
|
+
console.log(chalk_1.default.green('ā') + ' Removed PowerShell integration');
|
|
48
|
+
removed = true;
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
else {
|
|
53
|
+
// Unix-like (Mac, Linux)
|
|
54
|
+
const shell = process.env.SHELL || '';
|
|
55
|
+
if (shell.includes('zsh')) {
|
|
56
|
+
const zshrcPath = path_1.default.join(homeDir, '.zshrc');
|
|
57
|
+
removeFromShellRC(zshrcPath, 'zsh');
|
|
58
|
+
removed = true;
|
|
59
|
+
}
|
|
60
|
+
else if (shell.includes('bash')) {
|
|
61
|
+
const bashrcPath = path_1.default.join(homeDir, '.bashrc');
|
|
62
|
+
removeFromShellRC(bashrcPath, 'bash');
|
|
63
|
+
removed = true;
|
|
64
|
+
}
|
|
65
|
+
else if (shell.includes('fish')) {
|
|
66
|
+
const fishConfigPath = path_1.default.join(homeDir, '.config', 'fish', 'config.fish');
|
|
67
|
+
removeFromShellRC(fishConfigPath, 'fish');
|
|
68
|
+
removed = true;
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
if (!removed) {
|
|
72
|
+
console.log(chalk_1.default.yellow('ā ļø No shell integration found'));
|
|
73
|
+
}
|
|
74
|
+
console.log();
|
|
75
|
+
console.log(chalk_1.default.yellow.bold('To complete uninstall:\n'));
|
|
76
|
+
console.log(chalk_1.default.white('1. Remove the npm package:'));
|
|
77
|
+
console.log(chalk_1.default.cyan(' npm uninstall -g snapcommit\n'));
|
|
78
|
+
console.log(chalk_1.default.white('2. Remove local data (optional):'));
|
|
79
|
+
if (platform === 'win32') {
|
|
80
|
+
console.log(chalk_1.default.cyan(' Remove-Item -Recurse -Force "$env:USERPROFILE\\\\.snapcommit"\n'));
|
|
81
|
+
}
|
|
82
|
+
else {
|
|
83
|
+
console.log(chalk_1.default.cyan(' rm -rf ~/.snapcommit\n'));
|
|
84
|
+
}
|
|
85
|
+
console.log(chalk_1.default.white('3. Reload your shell:'));
|
|
86
|
+
if (platform === 'win32') {
|
|
87
|
+
console.log(chalk_1.default.cyan(' . $PROFILE\n'));
|
|
88
|
+
}
|
|
89
|
+
else {
|
|
90
|
+
console.log(chalk_1.default.cyan(' source ~/.zshrc (or ~/.bashrc)\n'));
|
|
91
|
+
}
|
|
92
|
+
// Feedback request
|
|
93
|
+
console.log(chalk_1.default.yellow('š¬ We\'d love to know why you\'re leaving:'));
|
|
94
|
+
console.log(chalk_1.default.gray(' Email: feedback@snapcommit.dev'));
|
|
95
|
+
console.log(chalk_1.default.gray(' Or just reply to this prompt:\n'));
|
|
96
|
+
const feedback = await askQuestion(chalk_1.default.yellow('Why are you uninstalling? (optional): '));
|
|
97
|
+
if (feedback.trim()) {
|
|
98
|
+
console.log(chalk_1.default.green('\nā Thank you for your feedback!'));
|
|
99
|
+
// TODO: Send to analytics or email
|
|
100
|
+
}
|
|
101
|
+
console.log();
|
|
102
|
+
console.log(chalk_1.default.blue('š Goodbye! Come back anytime.'));
|
|
103
|
+
console.log(chalk_1.default.gray(' (You can reinstall with: npm install -g snapcommit)\n'));
|
|
104
|
+
}
|
|
105
|
+
function removeFromShellRC(filePath, shellName) {
|
|
106
|
+
if (!fs_1.default.existsSync(filePath)) {
|
|
107
|
+
return;
|
|
108
|
+
}
|
|
109
|
+
let content = fs_1.default.readFileSync(filePath, 'utf-8');
|
|
110
|
+
const lines = content.split('\n');
|
|
111
|
+
// Remove SnapCommit Integration section
|
|
112
|
+
let inSnapCommitSection = false;
|
|
113
|
+
const filtered = lines.filter(line => {
|
|
114
|
+
if (line.includes('# SnapCommit Integration')) {
|
|
115
|
+
inSnapCommitSection = true;
|
|
116
|
+
return false;
|
|
117
|
+
}
|
|
118
|
+
if (inSnapCommitSection && line.trim() === '') {
|
|
119
|
+
inSnapCommitSection = false;
|
|
120
|
+
return false;
|
|
121
|
+
}
|
|
122
|
+
if (inSnapCommitSection) {
|
|
123
|
+
return false;
|
|
124
|
+
}
|
|
125
|
+
return true;
|
|
126
|
+
});
|
|
127
|
+
if (lines.length !== filtered.length) {
|
|
128
|
+
fs_1.default.writeFileSync(filePath, filtered.join('\n'));
|
|
129
|
+
console.log(chalk_1.default.green('ā') + ` Removed ${shellName} integration`);
|
|
130
|
+
}
|
|
131
|
+
}
|