@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,211 @@
1
+ "use strict";
2
+ /**
3
+ * Dopamine-inducing features
4
+ * Streaks, milestones, achievements, motivational messages
5
+ */
6
+ var __importDefault = (this && this.__importDefault) || function (mod) {
7
+ return (mod && mod.__esModule) ? mod : { "default": mod };
8
+ };
9
+ Object.defineProperty(exports, "__esModule", { value: true });
10
+ exports.calculateStreak = calculateStreak;
11
+ exports.getMotivationalMessage = getMotivationalMessage;
12
+ exports.getMilestone = getMilestone;
13
+ exports.getFireEmoji = getFireEmoji;
14
+ exports.getDopamineStats = getDopamineStats;
15
+ exports.displayDopamineStats = displayDopamineStats;
16
+ exports.displayQuickDopamine = displayQuickDopamine;
17
+ const database_1 = require("../db/database");
18
+ const chalk_1 = __importDefault(require("chalk"));
19
+ /**
20
+ * Calculate streak (consecutive days with commits)
21
+ */
22
+ function calculateStreak() {
23
+ const stats = (0, database_1.getStats)(365); // Get last year
24
+ if (stats.recentCommits.length === 0) {
25
+ return { current: 0, longest: 0 };
26
+ }
27
+ // Sort by timestamp (newest first)
28
+ const commits = [...stats.recentCommits].sort((a, b) => b.timestamp - a.timestamp);
29
+ // Group by day
30
+ const dayBuckets = new Map();
31
+ commits.forEach((commit) => {
32
+ const date = new Date(commit.timestamp);
33
+ const dayKey = `${date.getFullYear()}-${date.getMonth() + 1}-${date.getDate()}`;
34
+ dayBuckets.set(dayKey, (dayBuckets.get(dayKey) || 0) + 1);
35
+ });
36
+ const today = new Date();
37
+ const todayKey = `${today.getFullYear()}-${today.getMonth() + 1}-${today.getDate()}`;
38
+ // Calculate current streak
39
+ let currentStreak = 0;
40
+ let checkDate = new Date(today);
41
+ while (true) {
42
+ const dayKey = `${checkDate.getFullYear()}-${checkDate.getMonth() + 1}-${checkDate.getDate()}`;
43
+ if (dayBuckets.has(dayKey)) {
44
+ currentStreak++;
45
+ checkDate.setDate(checkDate.getDate() - 1);
46
+ }
47
+ else {
48
+ // Allow 1-day gap if we're not checking today (give grace period)
49
+ if (dayKey === todayKey) {
50
+ checkDate.setDate(checkDate.getDate() - 1);
51
+ const yesterdayKey = `${checkDate.getFullYear()}-${checkDate.getMonth() + 1}-${checkDate.getDate()}`;
52
+ if (!dayBuckets.has(yesterdayKey)) {
53
+ break;
54
+ }
55
+ }
56
+ else {
57
+ break;
58
+ }
59
+ }
60
+ }
61
+ // Calculate longest streak (simple version)
62
+ let longestStreak = currentStreak;
63
+ let tempStreak = 0;
64
+ let lastDate = null;
65
+ const sortedDays = Array.from(dayBuckets.keys()).sort().reverse();
66
+ sortedDays.forEach(dayKey => {
67
+ const [year, month, day] = dayKey.split('-').map(Number);
68
+ const date = new Date(year, month - 1, day);
69
+ if (lastDate) {
70
+ const diffDays = Math.floor((lastDate.getTime() - date.getTime()) / (1000 * 60 * 60 * 24));
71
+ if (diffDays === 1) {
72
+ tempStreak++;
73
+ }
74
+ else {
75
+ longestStreak = Math.max(longestStreak, tempStreak);
76
+ tempStreak = 1;
77
+ }
78
+ }
79
+ else {
80
+ tempStreak = 1;
81
+ }
82
+ lastDate = date;
83
+ });
84
+ longestStreak = Math.max(longestStreak, tempStreak);
85
+ return { current: currentStreak, longest: longestStreak };
86
+ }
87
+ /**
88
+ * Get motivational message based on stats
89
+ */
90
+ function getMotivationalMessage(stats) {
91
+ const { currentStreak, weeklyCommits, totalCommits } = stats;
92
+ // Streak-based messages
93
+ if (currentStreak >= 30) {
94
+ return "🔥 LEGENDARY! 30+ day streak!";
95
+ }
96
+ if (currentStreak >= 14) {
97
+ return "💪 Beast mode! 2 weeks straight!";
98
+ }
99
+ if (currentStreak >= 7) {
100
+ return "⚡ On fire! 1 week streak!";
101
+ }
102
+ if (currentStreak >= 3) {
103
+ return "🚀 Momentum building!";
104
+ }
105
+ // Commit-based messages
106
+ if (weeklyCommits >= 50) {
107
+ return "🤯 Insane productivity this week!";
108
+ }
109
+ if (weeklyCommits >= 25) {
110
+ return "💯 Crushing it this week!";
111
+ }
112
+ if (weeklyCommits >= 10) {
113
+ return "👏 Great week of coding!";
114
+ }
115
+ // Milestone messages
116
+ if (totalCommits >= 1000) {
117
+ return "🏆 1000+ commits! You're a legend!";
118
+ }
119
+ if (totalCommits >= 500) {
120
+ return "🎉 500+ commits milestone!";
121
+ }
122
+ if (totalCommits >= 100) {
123
+ return "🌟 100+ commits! Keep going!";
124
+ }
125
+ // Default encouragement
126
+ if (totalCommits === 0) {
127
+ return "🌱 Let's start your coding journey!";
128
+ }
129
+ if (totalCommits < 10) {
130
+ return "🎯 Getting started! Keep it up!";
131
+ }
132
+ return "💻 Keep building amazing things!";
133
+ }
134
+ /**
135
+ * Get milestone (if any)
136
+ */
137
+ function getMilestone(totalCommits) {
138
+ const milestones = [10, 25, 50, 100, 250, 500, 1000];
139
+ for (const milestone of milestones) {
140
+ if (totalCommits === milestone) {
141
+ return `🎉 ${milestone} COMMITS MILESTONE!`;
142
+ }
143
+ }
144
+ return undefined;
145
+ }
146
+ /**
147
+ * Get fire emoji intensity based on streak
148
+ */
149
+ function getFireEmoji(streak) {
150
+ if (streak >= 30)
151
+ return '🔥🔥🔥';
152
+ if (streak >= 14)
153
+ return '🔥🔥';
154
+ if (streak >= 7)
155
+ return '🔥';
156
+ if (streak >= 3)
157
+ return '⚡';
158
+ return '🌱';
159
+ }
160
+ /**
161
+ * Get complete dopamine stats
162
+ */
163
+ function getDopamineStats() {
164
+ const stats7d = (0, database_1.getStats)(7);
165
+ const statsAll = (0, database_1.getStats)(365);
166
+ const streak = calculateStreak();
167
+ const dopamineStats = {
168
+ currentStreak: streak.current,
169
+ longestStreak: streak.longest,
170
+ totalCommits: statsAll.totalCommits,
171
+ weeklyCommits: stats7d.totalCommits,
172
+ linesThisWeek: stats7d.totalInsertions + stats7d.totalDeletions,
173
+ milestone: getMilestone(statsAll.totalCommits),
174
+ motivationalMessage: '',
175
+ fireEmoji: getFireEmoji(streak.current),
176
+ };
177
+ dopamineStats.motivationalMessage = getMotivationalMessage(dopamineStats);
178
+ return dopamineStats;
179
+ }
180
+ /**
181
+ * Display dopamine stats in a beautiful way
182
+ */
183
+ function displayDopamineStats() {
184
+ const stats = getDopamineStats();
185
+ console.log();
186
+ console.log(chalk_1.default.yellow.bold(`${stats.fireEmoji} ${stats.motivationalMessage}`));
187
+ if (stats.currentStreak > 0) {
188
+ console.log(chalk_1.default.gray(` ${stats.currentStreak}-day streak${stats.longestStreak > stats.currentStreak ? ` (best: ${stats.longestStreak})` : ' 🏆'}`));
189
+ }
190
+ console.log(chalk_1.default.gray(` ${stats.weeklyCommits} commits this week`));
191
+ if (stats.linesThisWeek > 0) {
192
+ console.log(chalk_1.default.gray(` ${stats.linesThisWeek.toLocaleString()} lines changed`));
193
+ }
194
+ if (stats.milestone) {
195
+ console.log();
196
+ console.log(chalk_1.default.green.bold(` ${stats.milestone}`));
197
+ }
198
+ console.log();
199
+ }
200
+ /**
201
+ * Display quick dopamine hit (for after commits)
202
+ */
203
+ function displayQuickDopamine() {
204
+ const stats = getDopamineStats();
205
+ if (stats.currentStreak >= 3) {
206
+ console.log(chalk_1.default.yellow(`${stats.fireEmoji} ${stats.currentStreak}-day streak!`));
207
+ }
208
+ if (stats.milestone) {
209
+ console.log(chalk_1.default.green.bold(stats.milestone));
210
+ }
211
+ }
@@ -0,0 +1,56 @@
1
+ "use strict";
2
+ /**
3
+ * Production-ready error handling
4
+ */
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.GitError = exports.LicenseError = exports.APIError = exports.SnapCommitError = void 0;
7
+ exports.handleError = handleError;
8
+ class SnapCommitError extends Error {
9
+ code;
10
+ userMessage;
11
+ helpUrl;
12
+ constructor(message, code, userMessage, helpUrl) {
13
+ super(message);
14
+ this.code = code;
15
+ this.userMessage = userMessage;
16
+ this.helpUrl = helpUrl;
17
+ this.name = 'SnapCommitError';
18
+ }
19
+ }
20
+ exports.SnapCommitError = SnapCommitError;
21
+ class APIError extends SnapCommitError {
22
+ provider;
23
+ constructor(message, provider, userMessage) {
24
+ super(message, 'API_ERROR', userMessage || 'AI service temporarily unavailable. Please try again.', 'https://builderos.dev/docs/errors#api-error');
25
+ this.provider = provider;
26
+ }
27
+ }
28
+ exports.APIError = APIError;
29
+ class LicenseError extends SnapCommitError {
30
+ constructor(message, userMessage) {
31
+ super(message, 'LICENSE_ERROR', userMessage || 'License validation failed.', 'https://builderos.dev/docs/errors#license-error');
32
+ }
33
+ }
34
+ exports.LicenseError = LicenseError;
35
+ class GitError extends SnapCommitError {
36
+ constructor(message, userMessage) {
37
+ super(message, 'GIT_ERROR', userMessage || 'Git operation failed.', 'https://builderos.dev/docs/errors#git-error');
38
+ }
39
+ }
40
+ exports.GitError = GitError;
41
+ function handleError(error) {
42
+ if (error instanceof SnapCommitError) {
43
+ console.error(`\n❌ ${error.userMessage}`);
44
+ if (error.helpUrl) {
45
+ console.error(` Help: ${error.helpUrl}`);
46
+ }
47
+ process.exit(1);
48
+ }
49
+ if (error instanceof Error) {
50
+ console.error(`\n❌ Unexpected error: ${error.message}`);
51
+ console.error(' Please report this at: https://github.com/Arjun0606/builderOS/issues');
52
+ process.exit(1);
53
+ }
54
+ console.error('\n❌ An unknown error occurred');
55
+ process.exit(1);
56
+ }
@@ -0,0 +1,105 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.getGitDiff = getGitDiff;
4
+ exports.getGitStatus = getGitStatus;
5
+ exports.isGitRepo = isGitRepo;
6
+ exports.stageAllChanges = stageAllChanges;
7
+ exports.commitWithMessage = commitWithMessage;
8
+ exports.getCommitStats = getCommitStats;
9
+ exports.getCurrentBranch = getCurrentBranch;
10
+ const child_process_1 = require("child_process");
11
+ function getGitDiff(staged = true) {
12
+ try {
13
+ if (staged) {
14
+ // Get staged changes
15
+ const diff = (0, child_process_1.execSync)('git diff --cached', {
16
+ encoding: 'utf-8',
17
+ maxBuffer: 10 * 1024 * 1024 // 10MB buffer for large diffs
18
+ });
19
+ return diff;
20
+ }
21
+ else {
22
+ // Get unstaged changes
23
+ const diff = (0, child_process_1.execSync)('git diff', {
24
+ encoding: 'utf-8',
25
+ maxBuffer: 10 * 1024 * 1024
26
+ });
27
+ return diff;
28
+ }
29
+ }
30
+ catch (error) {
31
+ throw new Error(`Git error: ${error.message}`);
32
+ }
33
+ }
34
+ function getGitStatus() {
35
+ try {
36
+ const status = (0, child_process_1.execSync)('git status --porcelain', { encoding: 'utf-8' });
37
+ const lines = status.trim().split('\n').filter(Boolean);
38
+ let staged = 0;
39
+ let unstaged = 0;
40
+ let untracked = 0;
41
+ lines.forEach((line) => {
42
+ const statusCode = line.substring(0, 2);
43
+ if (statusCode[0] !== ' ' && statusCode[0] !== '?')
44
+ staged++;
45
+ if (statusCode[1] !== ' ')
46
+ unstaged++;
47
+ if (statusCode[0] === '?' && statusCode[1] === '?')
48
+ untracked++;
49
+ });
50
+ return { staged, unstaged, untracked };
51
+ }
52
+ catch (error) {
53
+ throw new Error(`Git error: ${error.message}`);
54
+ }
55
+ }
56
+ function isGitRepo() {
57
+ try {
58
+ (0, child_process_1.execSync)('git rev-parse --git-dir', { stdio: 'ignore' });
59
+ return true;
60
+ }
61
+ catch {
62
+ return false;
63
+ }
64
+ }
65
+ function stageAllChanges() {
66
+ try {
67
+ (0, child_process_1.execSync)('git add -A', { stdio: 'inherit' });
68
+ }
69
+ catch (error) {
70
+ throw new Error(`Git add failed: ${error.message}`);
71
+ }
72
+ }
73
+ function commitWithMessage(message) {
74
+ try {
75
+ (0, child_process_1.execSync)(`git commit -m "${message.replace(/"/g, '\\"')}"`, { stdio: 'inherit' });
76
+ // Get the commit hash
77
+ const hash = (0, child_process_1.execSync)('git rev-parse HEAD', { encoding: 'utf-8' }).trim();
78
+ return hash;
79
+ }
80
+ catch (error) {
81
+ throw new Error(`Git commit failed: ${error.message}`);
82
+ }
83
+ }
84
+ function getCommitStats(hash) {
85
+ try {
86
+ const stats = (0, child_process_1.execSync)(`git show --stat --format="" ${hash}`, { encoding: 'utf-8' });
87
+ const match = stats.match(/(\d+) files? changed(?:, (\d+) insertions?\(\+\))?(?:, (\d+) deletions?\(-\))?/);
88
+ return {
89
+ files: match?.[1] ? parseInt(match[1]) : 0,
90
+ insertions: match?.[2] ? parseInt(match[2]) : 0,
91
+ deletions: match?.[3] ? parseInt(match[3]) : 0,
92
+ };
93
+ }
94
+ catch {
95
+ return { files: 0, insertions: 0, deletions: 0 };
96
+ }
97
+ }
98
+ function getCurrentBranch() {
99
+ try {
100
+ return (0, child_process_1.execSync)('git branch --show-current', { encoding: 'utf-8' }).trim();
101
+ }
102
+ catch {
103
+ return 'unknown';
104
+ }
105
+ }
@@ -0,0 +1,265 @@
1
+ "use strict";
2
+ /**
3
+ * GitHub-style contribution heatmap for terminal
4
+ * Shows coding activity over last 12 weeks
5
+ */
6
+ var __importDefault = (this && this.__importDefault) || function (mod) {
7
+ return (mod && mod.__esModule) ? mod : { "default": mod };
8
+ };
9
+ Object.defineProperty(exports, "__esModule", { value: true });
10
+ exports.generateHeatmap = generateHeatmap;
11
+ exports.getActivitySummary = getActivitySummary;
12
+ exports.displayHeatmap = displayHeatmap;
13
+ const chalk_1 = __importDefault(require("chalk"));
14
+ const database_1 = require("../db/database");
15
+ /**
16
+ * Generate terminal heatmap (GitHub-style)
17
+ */
18
+ function generateHeatmap(weeks = 12) {
19
+ const today = new Date();
20
+ today.setHours(0, 0, 0, 0);
21
+ // Get all commits from the timeframe
22
+ const stats = (0, database_1.getStats)(weeks * 7);
23
+ // Group commits by date
24
+ const commitsByDate = new Map();
25
+ stats.recentCommits.forEach((commit) => {
26
+ const date = new Date(commit.timestamp);
27
+ date.setHours(0, 0, 0, 0);
28
+ const dateKey = date.toISOString().split('T')[0];
29
+ commitsByDate.set(dateKey, (commitsByDate.get(dateKey) || 0) + 1);
30
+ });
31
+ // Generate grid (7 days x N weeks)
32
+ const grid = [];
33
+ // Start from the most recent Sunday
34
+ const startDate = new Date(today);
35
+ const dayOfWeek = startDate.getDay(); // 0 = Sunday
36
+ startDate.setDate(startDate.getDate() - dayOfWeek - (weeks * 7) + 7);
37
+ for (let week = 0; week < weeks; week++) {
38
+ const weekData = [];
39
+ for (let day = 0; day < 7; day++) {
40
+ const date = new Date(startDate);
41
+ date.setDate(date.getDate() + (week * 7) + day);
42
+ const dateKey = date.toISOString().split('T')[0];
43
+ const commits = commitsByDate.get(dateKey) || 0;
44
+ weekData.push({ date, commits });
45
+ }
46
+ grid.push(weekData);
47
+ }
48
+ // Render heatmap
49
+ return renderHeatmap(grid, commitsByDate);
50
+ }
51
+ /**
52
+ * Render the heatmap with colors
53
+ */
54
+ function renderHeatmap(grid, commitsByDate) {
55
+ const maxCommits = Math.max(...Array.from(commitsByDate.values()), 1);
56
+ let output = '';
57
+ // Header
58
+ output += chalk_1.default.white.bold('\n📈 Contribution Graph (Last 12 Weeks)\n\n');
59
+ // Month labels (top row)
60
+ output += ' ';
61
+ const monthLabels = [];
62
+ let lastMonth = -1;
63
+ grid.forEach((week, weekIndex) => {
64
+ const monthNum = week[0].date.getMonth();
65
+ if (monthNum !== lastMonth && weekIndex % 4 === 0) {
66
+ const monthName = week[0].date.toLocaleString('default', { month: 'short' });
67
+ monthLabels.push(monthName);
68
+ lastMonth = monthNum;
69
+ }
70
+ else {
71
+ monthLabels.push('');
72
+ }
73
+ });
74
+ // Print month labels (every 4 weeks roughly)
75
+ for (let i = 0; i < grid.length; i++) {
76
+ if (i % 4 === 0 && i < grid.length - 3) {
77
+ const month = grid[i][0].date.toLocaleString('default', { month: 'short' });
78
+ output += chalk_1.default.gray(month.padEnd(4));
79
+ i += 3; // Skip next 3 weeks
80
+ }
81
+ else {
82
+ output += ' ';
83
+ }
84
+ }
85
+ output += '\n';
86
+ // Day labels + grid
87
+ const dayLabels = ['Sun', 'Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat'];
88
+ for (let day = 0; day < 7; day++) {
89
+ // Only show labels for Mon, Wed, Fri
90
+ if (day === 1 || day === 3 || day === 5) {
91
+ output += chalk_1.default.gray(dayLabels[day].substring(0, 3).padEnd(5));
92
+ }
93
+ else {
94
+ output += ' ';
95
+ }
96
+ // Render each week for this day
97
+ for (let week = 0; week < grid.length; week++) {
98
+ const dayData = grid[week][day];
99
+ const intensity = getIntensity(dayData.commits, maxCommits);
100
+ const cell = getColoredCell(intensity, dayData.date);
101
+ output += cell + ' ';
102
+ }
103
+ output += '\n';
104
+ }
105
+ // Legend
106
+ output += '\n ';
107
+ output += chalk_1.default.gray('Less ');
108
+ output += getColoredCell(0) + ' ';
109
+ output += getColoredCell(1) + ' ';
110
+ output += getColoredCell(2) + ' ';
111
+ output += getColoredCell(3) + ' ';
112
+ output += getColoredCell(4) + ' ';
113
+ output += chalk_1.default.gray(' More');
114
+ output += '\n';
115
+ return output;
116
+ }
117
+ /**
118
+ * Get intensity level (0-4) based on commit count
119
+ */
120
+ function getIntensity(commits, maxCommits) {
121
+ if (commits === 0)
122
+ return 0;
123
+ if (maxCommits === 1)
124
+ return 4; // If max is 1, show as full
125
+ const percentage = commits / maxCommits;
126
+ if (percentage >= 0.75)
127
+ return 4; // Darkest
128
+ if (percentage >= 0.50)
129
+ return 3;
130
+ if (percentage >= 0.25)
131
+ return 2;
132
+ if (percentage > 0)
133
+ return 1; // Lightest
134
+ return 0;
135
+ }
136
+ /**
137
+ * Get colored cell based on intensity
138
+ */
139
+ function getColoredCell(intensity, date) {
140
+ const today = new Date();
141
+ today.setHours(0, 0, 0, 0);
142
+ const isToday = date && date.getTime() === today.getTime();
143
+ const isFuture = date && date > today;
144
+ // Future dates (gray, empty)
145
+ if (isFuture) {
146
+ return chalk_1.default.gray('·');
147
+ }
148
+ // Today gets a special highlight
149
+ if (isToday) {
150
+ switch (intensity) {
151
+ case 0: return chalk_1.default.yellow('◯'); // No commits today yet
152
+ case 1: return chalk_1.default.cyan('◉');
153
+ case 2: return chalk_1.default.cyan('◉');
154
+ case 3: return chalk_1.default.green('◉');
155
+ case 4: return chalk_1.default.green('◉');
156
+ default: return chalk_1.default.gray('·');
157
+ }
158
+ }
159
+ // Regular cells (GitHub-style green gradient)
160
+ switch (intensity) {
161
+ case 0: return chalk_1.default.gray('·'); // No activity
162
+ case 1: return chalk_1.default.rgb(14, 68, 41)('■'); // Light green
163
+ case 2: return chalk_1.default.rgb(0, 109, 50)('■'); // Medium green
164
+ case 3: return chalk_1.default.rgb(38, 166, 65)('■'); // Green
165
+ case 4: return chalk_1.default.rgb(57, 211, 83)('■'); // Bright green
166
+ default: return chalk_1.default.gray('·');
167
+ }
168
+ }
169
+ /**
170
+ * Get activity summary for heatmap
171
+ */
172
+ function getActivitySummary(weeks = 12) {
173
+ const stats = (0, database_1.getStats)(weeks * 7);
174
+ const commitsByDate = new Map();
175
+ stats.recentCommits.forEach((commit) => {
176
+ const date = new Date(commit.timestamp);
177
+ date.setHours(0, 0, 0, 0);
178
+ const dateKey = date.toISOString().split('T')[0];
179
+ commitsByDate.set(dateKey, (commitsByDate.get(dateKey) || 0) + 1);
180
+ });
181
+ const activeDays = commitsByDate.size;
182
+ const totalDays = weeks * 7;
183
+ const averagePerDay = totalDays > 0 ? stats.recentCommits.length / totalDays : 0;
184
+ // Find most productive day
185
+ let mostProductiveDay = { date: '', commits: 0 };
186
+ commitsByDate.forEach((commits, date) => {
187
+ if (commits > mostProductiveDay.commits) {
188
+ mostProductiveDay = { date, commits };
189
+ }
190
+ });
191
+ // Calculate streaks (simple version)
192
+ let currentStreak = 0;
193
+ let longestStreak = 0;
194
+ let tempStreak = 0;
195
+ const sortedDates = Array.from(commitsByDate.keys()).sort().reverse();
196
+ let lastDate = null;
197
+ sortedDates.forEach(dateStr => {
198
+ const date = new Date(dateStr);
199
+ if (lastDate) {
200
+ const diffDays = Math.floor((lastDate.getTime() - date.getTime()) / (1000 * 60 * 60 * 24));
201
+ if (diffDays === 1) {
202
+ tempStreak++;
203
+ }
204
+ else {
205
+ longestStreak = Math.max(longestStreak, tempStreak);
206
+ tempStreak = 1;
207
+ }
208
+ }
209
+ else {
210
+ // Check if most recent date is today or yesterday
211
+ const today = new Date();
212
+ today.setHours(0, 0, 0, 0);
213
+ const diffFromToday = Math.floor((today.getTime() - date.getTime()) / (1000 * 60 * 60 * 24));
214
+ if (diffFromToday <= 1) {
215
+ currentStreak = 1;
216
+ tempStreak = 1;
217
+ }
218
+ }
219
+ lastDate = date;
220
+ });
221
+ longestStreak = Math.max(longestStreak, tempStreak);
222
+ if (currentStreak > 0) {
223
+ currentStreak = tempStreak;
224
+ }
225
+ return {
226
+ totalCommits: stats.recentCommits.length,
227
+ activeDays,
228
+ longestStreak,
229
+ currentStreak,
230
+ averagePerDay: Math.round(averagePerDay * 10) / 10,
231
+ mostProductiveDay,
232
+ };
233
+ }
234
+ /**
235
+ * Display heatmap with summary stats
236
+ */
237
+ function displayHeatmap(weeks = 12) {
238
+ const heatmap = generateHeatmap(weeks);
239
+ const summary = getActivitySummary(weeks);
240
+ console.log(heatmap);
241
+ // Summary stats below heatmap
242
+ console.log(chalk_1.default.white.bold('\n📊 Activity Summary:\n'));
243
+ const stats = [
244
+ { label: 'Total commits', value: summary.totalCommits, emoji: '💻' },
245
+ { label: 'Active days', value: `${summary.activeDays}/${weeks * 7}`, emoji: '📅' },
246
+ { label: 'Current streak', value: `${summary.currentStreak} days`, emoji: '🔥' },
247
+ { label: 'Longest streak', value: `${summary.longestStreak} days`, emoji: '🏆' },
248
+ { label: 'Avg per day', value: summary.averagePerDay.toFixed(1), emoji: '📈' },
249
+ ];
250
+ stats.forEach(stat => {
251
+ console.log(chalk_1.default.gray(` ${stat.emoji} ${stat.label}:`.padEnd(25)) +
252
+ chalk_1.default.cyan(stat.value.toString()));
253
+ });
254
+ if (summary.mostProductiveDay.commits > 0) {
255
+ const date = new Date(summary.mostProductiveDay.date);
256
+ const formattedDate = date.toLocaleDateString('en-US', {
257
+ month: 'short',
258
+ day: 'numeric',
259
+ year: 'numeric'
260
+ });
261
+ console.log(chalk_1.default.gray(` 🌟 Most productive:`.padEnd(25)) +
262
+ chalk_1.default.cyan(`${summary.mostProductiveDay.commits} commits on ${formattedDate}`));
263
+ }
264
+ console.log();
265
+ }