codymaster 4.1.1 → 4.1.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/ui/box.js ADDED
@@ -0,0 +1,188 @@
1
+ "use strict";
2
+ /**
3
+ * 📦 Box Drawing — Terminal UI components
4
+ * Bordered panels, tables, progress bars, badges
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.termWidth = termWidth;
11
+ exports.renderBox = renderBox;
12
+ exports.renderDivider = renderDivider;
13
+ exports.renderTable = renderTable;
14
+ exports.renderProgressBar = renderProgressBar;
15
+ exports.renderBadge = renderBadge;
16
+ exports.renderPriority = renderPriority;
17
+ exports.renderSpeechBubble = renderSpeechBubble;
18
+ exports.renderStepProgress = renderStepProgress;
19
+ exports.renderFooter = renderFooter;
20
+ exports.stripAnsi = stripAnsi;
21
+ const chalk_1 = __importDefault(require("chalk"));
22
+ const theme_1 = require("./theme");
23
+ // ─── Terminal Width ────────────────────────────────────────────────────────
24
+ function termWidth() {
25
+ return process.stdout.columns || 80;
26
+ }
27
+ function clamp(val, min, max) {
28
+ return Math.max(min, Math.min(max, val));
29
+ }
30
+ // ─── Box Components ────────────────────────────────────────────────────────
31
+ const BOX = {
32
+ tl: '╭', tr: '╮', bl: '╰', br: '╯',
33
+ h: '─', v: '│',
34
+ // Table
35
+ ttl: '┌', ttr: '┐', tbl: '└', tbr: '┘',
36
+ th: '─', tv: '│', tc: '┼',
37
+ tlt: '├', trt: '┤', ttt: '┬', tbt: '┴',
38
+ };
39
+ /**
40
+ * Render a bordered box with optional title
41
+ */
42
+ function renderBox(content, opts) {
43
+ var _a, _b;
44
+ const pad = (_a = opts === null || opts === void 0 ? void 0 : opts.padding) !== null && _a !== void 0 ? _a : 1;
45
+ const w = (_b = opts === null || opts === void 0 ? void 0 : opts.width) !== null && _b !== void 0 ? _b : clamp(termWidth() - 4, 40, 80);
46
+ const innerW = w - 2 - (pad * 2);
47
+ const lines = [];
48
+ const padStr = ' '.repeat(pad);
49
+ // Top border with optional title
50
+ if (opts === null || opts === void 0 ? void 0 : opts.title) {
51
+ const title = ` ${opts.title} `;
52
+ const leftLen = 2;
53
+ const rightLen = Math.max(0, w - 2 - leftLen - stripAnsi(title).length);
54
+ lines.push((0, theme_1.dim)(`${BOX.tl}${BOX.h.repeat(leftLen)}`) + (0, theme_1.brand)(title) + (0, theme_1.dim)(`${BOX.h.repeat(rightLen)}${BOX.tr}`));
55
+ }
56
+ else {
57
+ lines.push((0, theme_1.dim)(`${BOX.tl}${BOX.h.repeat(w - 2)}${BOX.tr}`));
58
+ }
59
+ // Content lines
60
+ for (const line of content) {
61
+ const visible = stripAnsi(line);
62
+ const spaces = Math.max(0, innerW - visible.length);
63
+ lines.push((0, theme_1.dim)(BOX.v) + padStr + line + ' '.repeat(spaces) + padStr + (0, theme_1.dim)(BOX.v));
64
+ }
65
+ // Bottom border
66
+ lines.push((0, theme_1.dim)(`${BOX.bl}${BOX.h.repeat(w - 2)}${BOX.br}`));
67
+ return lines.join('\n');
68
+ }
69
+ /**
70
+ * Render a simple divider line
71
+ */
72
+ function renderDivider(width) {
73
+ const w = width !== null && width !== void 0 ? width : clamp(termWidth() - 4, 40, 80);
74
+ return (0, theme_1.dim)(BOX.h.repeat(w));
75
+ }
76
+ /**
77
+ * Render a clean bordered table
78
+ */
79
+ function renderTable(columns, rows) {
80
+ const lines = [];
81
+ const totalW = columns.reduce((sum, c) => sum + c.width, 0) + columns.length + 1;
82
+ // Header separator
83
+ const headerSep = (0, theme_1.dim)(columns.map(c => BOX.th.repeat(c.width)).join((0, theme_1.dim)('─┬─')));
84
+ lines.push(` ${headerSep}`);
85
+ // Header row
86
+ const headerCells = columns.map(c => {
87
+ const text = padCell(c.header, c.width, c.align);
88
+ return (0, theme_1.dim)(text);
89
+ });
90
+ lines.push(` ${headerCells.join((0, theme_1.dim)(' │ '))}`);
91
+ // Header underline
92
+ lines.push(` ${headerSep}`);
93
+ // Data rows
94
+ for (const row of rows) {
95
+ const cells = columns.map(c => {
96
+ const val = row[c.header] || '—';
97
+ const display = padCell(stripAnsi(val).length > c.width ? stripAnsi(val).substring(0, c.width - 1) + '…' : val, c.width, c.align);
98
+ return c.color ? c.color(display) : display;
99
+ });
100
+ lines.push(` ${cells.join((0, theme_1.dim)(' │ '))}`);
101
+ }
102
+ // Bottom separator
103
+ lines.push(` ${headerSep}`);
104
+ return lines.join('\n');
105
+ }
106
+ function padCell(str, width, align) {
107
+ const visible = stripAnsi(str);
108
+ const diff = Math.max(0, width - visible.length);
109
+ if (align === 'right')
110
+ return ' '.repeat(diff) + str;
111
+ if (align === 'center') {
112
+ const left = Math.floor(diff / 2);
113
+ return ' '.repeat(left) + str + ' '.repeat(diff - left);
114
+ }
115
+ return str + ' '.repeat(diff);
116
+ }
117
+ // ─── Progress Bar ──────────────────────────────────────────────────────────
118
+ /**
119
+ * Render a colored progress bar
120
+ */
121
+ function renderProgressBar(pct, width) {
122
+ const w = width !== null && width !== void 0 ? width : 16;
123
+ const filled = Math.round((clamp(pct, 0, 100) / 100) * w);
124
+ const empty = w - filled;
125
+ const color = pct >= 100 ? theme_1.success : pct >= 60 ? chalk_1.default.hex(theme_1.COLORS.success) : pct >= 30 ? theme_1.warning : theme_1.error;
126
+ return color('█'.repeat(filled)) + (0, theme_1.muted)('░'.repeat(empty)) + (0, theme_1.dim)(` ${pct}%`);
127
+ }
128
+ // ─── Status Badge ──────────────────────────────────────────────────────────
129
+ const BADGE_COLORS = {
130
+ 'backlog': chalk_1.default.hex(theme_1.COLORS.backlog),
131
+ 'in-progress': chalk_1.default.hex(theme_1.COLORS.inProgress),
132
+ 'review': chalk_1.default.hex(theme_1.COLORS.warning),
133
+ 'done': chalk_1.default.hex(theme_1.COLORS.done),
134
+ 'success': chalk_1.default.hex(theme_1.COLORS.success),
135
+ 'failed': chalk_1.default.hex(theme_1.COLORS.error),
136
+ 'pending': chalk_1.default.hex(theme_1.COLORS.warning),
137
+ 'running': chalk_1.default.hex(theme_1.COLORS.inProgress),
138
+ };
139
+ /**
140
+ * Render a colored status badge: ● status
141
+ */
142
+ function renderBadge(status) {
143
+ const color = BADGE_COLORS[status] || theme_1.dim;
144
+ return color(`${theme_1.ICONS.dot} ${status}`);
145
+ }
146
+ /**
147
+ * Render priority badge
148
+ */
149
+ function renderPriority(priority) {
150
+ const color = theme_1.PRI[priority] || theme_1.dim;
151
+ return color(`${theme_1.ICONS.dot} ${priority}`);
152
+ }
153
+ // ─── Speech Bubble ─────────────────────────────────────────────────────────
154
+ /**
155
+ * Render a hamster speech bubble
156
+ */
157
+ function renderSpeechBubble(message) {
158
+ const w = stripAnsi(message).length + 4;
159
+ return [
160
+ (0, theme_1.dim)(` ╭${'─'.repeat(w)}╮`),
161
+ (0, theme_1.dim)(` │`) + ` ${message} ` + (0, theme_1.dim)(`│`),
162
+ (0, theme_1.dim)(` ╰${'─'.repeat(w)}╯`),
163
+ (0, theme_1.dim)(` ╰─`),
164
+ ].join('\n');
165
+ }
166
+ // ─── Step Indicator ────────────────────────────────────────────────────────
167
+ /**
168
+ * Render onboarding step progress: Step 2 of 5 ●●○○○
169
+ */
170
+ function renderStepProgress(current, total) {
171
+ const dots = Array.from({ length: total }, (_, i) => i < current ? (0, theme_1.brand)(theme_1.ICONS.dot) : (0, theme_1.muted)(theme_1.ICONS.dotEmpty)).join(' ');
172
+ return ` ${(0, theme_1.dim)('Step')} ${(0, theme_1.brand)(String(current))} ${(0, theme_1.dim)('of')} ${(0, theme_1.dim)(String(total))} ${dots}`;
173
+ }
174
+ // ─── Footer ────────────────────────────────────────────────────────────────
175
+ /**
176
+ * Render a footer hint bar
177
+ */
178
+ function renderFooter(hints) {
179
+ return ` ${hints.map(h => (0, theme_1.dim)(h)).join((0, theme_1.dim)(' • '))}`;
180
+ }
181
+ // ─── Utilities ─────────────────────────────────────────────────────────────
182
+ /**
183
+ * Strip ANSI escape codes for width calculations
184
+ */
185
+ function stripAnsi(str) {
186
+ // eslint-disable-next-line no-control-regex
187
+ return str.replace(/\x1B\[[0-9;]*[a-zA-Z]/g, '').replace(/\x1B\]8;[^;]*;[^\x1B]*\x1B\\/g, '');
188
+ }
@@ -0,0 +1,223 @@
1
+ "use strict";
2
+ /**
3
+ * 🐹 Hamster Mascot — ASCII art + personality system
4
+ * The face of CodyMaster CLI
5
+ */
6
+ Object.defineProperty(exports, "__esModule", { value: true });
7
+ exports.getHamsterArt = getHamsterArt;
8
+ exports.getGreeting = getGreeting;
9
+ exports.getCelebration = getCelebration;
10
+ exports.getEncouragement = getEncouragement;
11
+ exports.getErrorGuidance = getErrorGuidance;
12
+ exports.renderHamsterBanner = renderHamsterBanner;
13
+ exports.renderHamsterMessage = renderHamsterMessage;
14
+ const theme_1 = require("./theme");
15
+ // ─── ASCII Art States ──────────────────────────────────────────────────────
16
+ const HAMSTER_ART = {
17
+ happy: [
18
+ ` ${(0, theme_1.brand)('( . \\ --- / . )')}`,
19
+ ` ${(0, theme_1.brand)('/')} ${(0, theme_1.brandBold)('^ ^')} ${(0, theme_1.brand)('\\')}`,
20
+ ` ${(0, theme_1.brand)('(')} ${(0, theme_1.brandBold)('u')} ${(0, theme_1.brand)(')')}`,
21
+ ` ${(0, theme_1.brand)('| \\ ___ / |')}`,
22
+ ` ${(0, theme_1.brand)('\'--w---w--\'')}`,
23
+ ],
24
+ angry: [
25
+ ` ${(0, theme_1.brand)('( . \\ --- / . )')}`,
26
+ ` ${(0, theme_1.brand)('/')} ${(0, theme_1.warning)('> <')} ${(0, theme_1.brand)('\\')}`,
27
+ ` ${(0, theme_1.brand)('(')} ${(0, theme_1.warning)('x')} ${(0, theme_1.brand)(')')}`,
28
+ ` ${(0, theme_1.brand)('| \\ ___ / |')}`,
29
+ ` ${(0, theme_1.brand)('\'--w---w--\'')}`,
30
+ ],
31
+ sad: [
32
+ ` ${(0, theme_1.brand)('( . \\ --- / . )')}`,
33
+ ` ${(0, theme_1.brand)('/')} ${(0, theme_1.dim)('u u')} ${(0, theme_1.brand)('\\')}`,
34
+ ` ${(0, theme_1.brand)('(')} ${(0, theme_1.dim)('n')} ${(0, theme_1.brand)(')')}`,
35
+ ` ${(0, theme_1.brand)('| \\ ___ / |')}`,
36
+ ` ${(0, theme_1.brand)('\'--w---w--\'')}`,
37
+ ],
38
+ surprised: [
39
+ ` ${(0, theme_1.brand)('( . \\ --- / . )')}`,
40
+ ` ${(0, theme_1.brand)('/')} ${(0, theme_1.info)('O O')} ${(0, theme_1.brand)('\\')}`,
41
+ ` ${(0, theme_1.brand)('(')} ${(0, theme_1.info)('o')} ${(0, theme_1.brand)(')')}`,
42
+ ` ${(0, theme_1.brand)('| \\ ___ / |')}`,
43
+ ` ${(0, theme_1.brand)('\'--w---w--\'')}`,
44
+ ],
45
+ in_love: [
46
+ ` ${(0, theme_1.brand)('( . \\ --- / . )')} ${(0, theme_1.success)('♥')}`,
47
+ ` ${(0, theme_1.brand)('/')} ${(0, theme_1.success)('* *')} ${(0, theme_1.brand)('\\')}`,
48
+ ` ${(0, theme_1.brand)('(')} ${(0, theme_1.success)('v')} ${(0, theme_1.brand)(')')}`,
49
+ ` ${(0, theme_1.brand)('| \\ ___ / |')}`,
50
+ ` ${(0, theme_1.brand)('\'--w---w--\'')}`,
51
+ ],
52
+ sleeping: [
53
+ ` ${(0, theme_1.brand)('( . \\ --- / . )')} ${(0, theme_1.dim)('zZ')}`,
54
+ ` ${(0, theme_1.brand)('/')} ${(0, theme_1.dim)('- -')} ${(0, theme_1.brand)('\\')} ${(0, theme_1.dim)('zZ')}`,
55
+ ` ${(0, theme_1.brand)('(')} ${(0, theme_1.dim)('u')} ${(0, theme_1.brand)(')')}`,
56
+ ` ${(0, theme_1.brand)('| \\ ___ / |')}`,
57
+ ` ${(0, theme_1.brand)('\'--w---w--\'')}`,
58
+ ],
59
+ thinking: [
60
+ ` ${(0, theme_1.brand)('( . \\ --- / . )')} ${(0, theme_1.dim)('.oO')}`,
61
+ ` ${(0, theme_1.brand)('/')} ${(0, theme_1.dim)('. .')} ${(0, theme_1.brand)('\\')} ${(0, theme_1.dim)('/')}`,
62
+ ` ${(0, theme_1.brand)('(')} ${(0, theme_1.dim)('u')} ${(0, theme_1.brand)(')')}`,
63
+ ` ${(0, theme_1.brand)('| \\ ___ / |')}`,
64
+ ` ${(0, theme_1.brand)('\'--w---w--\'')}`,
65
+ ],
66
+ cool: [
67
+ ` ${(0, theme_1.brand)('( . \\ --- / . )')}`,
68
+ ` ${(0, theme_1.brand)('/')} ${(0, theme_1.info)('B B')} ${(0, theme_1.brand)('\\')}`,
69
+ ` ${(0, theme_1.brand)('(')} ${(0, theme_1.info)('v')} ${(0, theme_1.brand)(')')}`,
70
+ ` ${(0, theme_1.brand)('| \\ ___ / |')}`,
71
+ ` ${(0, theme_1.brand)('\'--w---w--\'')}`,
72
+ ],
73
+ celebrating: [
74
+ ` ${(0, theme_1.success)('\\')} ${(0, theme_1.brand)('( \\_/ )')} ${(0, theme_1.success)('/')}`,
75
+ ` ${(0, theme_1.success)('\\')} ${(0, theme_1.brand)('(')} ${(0, theme_1.success)('^ u ^')} ${(0, theme_1.brand)(')')} ${(0, theme_1.success)('/')}`,
76
+ ` ${(0, theme_1.success)('--')} ${(0, theme_1.brand)('( ___ )')} ${(0, theme_1.success)('--')}`,
77
+ ` ${(0, theme_1.brand)('| [ ] |')}`,
78
+ ` ${(0, theme_1.brand)('\'--w-w--\'')}`,
79
+ ],
80
+ };
81
+ // Aliases for compatibility with existing code
82
+ Object.assign(HAMSTER_ART, {
83
+ greeting: HAMSTER_ART.happy,
84
+ working: HAMSTER_ART.thinking,
85
+ error: HAMSTER_ART.angry,
86
+ });
87
+ /**
88
+ * Get hamster ASCII art for a given state
89
+ */
90
+ function getHamsterArt(state = 'greeting') {
91
+ return HAMSTER_ART[state].join('\n');
92
+ }
93
+ // ─── Time-Based Greetings ──────────────────────────────────────────────────
94
+ function getTimeOfDay() {
95
+ const h = new Date().getHours();
96
+ if (h >= 5 && h < 12)
97
+ return 'morning';
98
+ if (h >= 12 && h < 17)
99
+ return 'afternoon';
100
+ if (h >= 17 && h < 21)
101
+ return 'evening';
102
+ return 'night';
103
+ }
104
+ const TIME_GREETINGS = {
105
+ morning: [
106
+ 'Good morning! ☀️ Ready to build?',
107
+ 'Rise and code! ☀️',
108
+ 'Morning! Let\'s ship something today ☀️',
109
+ ],
110
+ afternoon: [
111
+ 'Good afternoon! 🌤️ What are we building?',
112
+ 'Afternoon! Time to make progress 🏗️',
113
+ 'Hey there! Productive day so far? 🌤️',
114
+ ],
115
+ evening: [
116
+ 'Good evening! 🌅 Wrapping up?',
117
+ 'Evening session! 🌙 Let\'s finish strong',
118
+ 'Hey! Late push tonight? 🌅',
119
+ ],
120
+ night: [
121
+ 'Working late? 🌙 Don\'t forget to rest!',
122
+ 'Night owl mode! 🦉 I\'m here for you',
123
+ 'Midnight coding session? 🌙 Let\'s go!',
124
+ ],
125
+ };
126
+ /**
127
+ * Get a greeting based on time of day + optional user name
128
+ */
129
+ function getGreeting(userName) {
130
+ const tod = getTimeOfDay();
131
+ const greetings = TIME_GREETINGS[tod];
132
+ const greeting = greetings[Math.floor(Math.random() * greetings.length)];
133
+ if (userName) {
134
+ return `${greeting.split('!')[0]}, ${userName}! ${greeting.includes('!') ? greeting.split('!').slice(1).join('!').trim() : ''}`.trim();
135
+ }
136
+ return greeting;
137
+ }
138
+ // ─── Hamster Messages ──────────────────────────────────────────────────────
139
+ const CELEBRATIONS = [
140
+ 'Nice work! 🎉',
141
+ 'You\'re on fire! 🔥',
142
+ 'Ship it! 🚀',
143
+ 'Another one bites the dust! ✅',
144
+ 'Level up! ⬆️',
145
+ 'Crushing it! 💪',
146
+ 'That\'s how it\'s done! ⭐',
147
+ 'Clean execution! 🎯',
148
+ 'Boom! Done! 💥',
149
+ 'Progress! Keep going! 📈',
150
+ 'Smooth operator! 🎵',
151
+ 'Task terminated! 🤖',
152
+ 'One step closer! 🏁',
153
+ 'Brilliant move! ♟️',
154
+ 'Unstoppable! ⚡',
155
+ ];
156
+ const ENCOURAGEMENTS = [
157
+ 'You got this! 💪',
158
+ 'Almost there! 🏁',
159
+ 'Keep pushing! Every step counts 🐾',
160
+ 'Rome wasn\'t built in a day, but they were laying bricks! 🧱',
161
+ 'Progress, not perfection! 📈',
162
+ 'One command at a time 🐹',
163
+ ];
164
+ const ERROR_GUIDANCE = [
165
+ 'Oops! Let me help you fix that 🔧',
166
+ 'Not quite! Here\'s what to try instead 💡',
167
+ 'Hmm, that didn\'t work. But we\'ll figure it out! 🐹',
168
+ 'Small detour! Let\'s get back on track 🛤️',
169
+ ];
170
+ const IDLE_MESSAGES = [
171
+ 'Any tasks to tackle? Type cm task list 📋',
172
+ 'Need a skill? Try cm list 🧩',
173
+ 'Been a while! What are we building? 🏗️',
174
+ ];
175
+ /**
176
+ * Get a random celebration message
177
+ */
178
+ function getCelebration() {
179
+ return CELEBRATIONS[Math.floor(Math.random() * CELEBRATIONS.length)];
180
+ }
181
+ /**
182
+ * Get a random encouragement
183
+ */
184
+ function getEncouragement() {
185
+ return ENCOURAGEMENTS[Math.floor(Math.random() * ENCOURAGEMENTS.length)];
186
+ }
187
+ /**
188
+ * Get error guidance
189
+ */
190
+ function getErrorGuidance() {
191
+ return ERROR_GUIDANCE[Math.floor(Math.random() * ERROR_GUIDANCE.length)];
192
+ }
193
+ /**
194
+ * Render the full hamster banner with greeting
195
+ */
196
+ function renderHamsterBanner(userName, version, cwd) {
197
+ const art = getHamsterArt(getTimeOfDay() === 'night' ? 'sleeping' : 'greeting');
198
+ const greeting = getGreeting(userName);
199
+ const lines = [
200
+ '',
201
+ art,
202
+ '',
203
+ ` ${(0, theme_1.brandBold)(greeting)}`,
204
+ '',
205
+ ` ${(0, theme_1.dim)('CodyMaster')} ${(0, theme_1.brand)(`v${version || '?'}`)} ${(0, theme_1.dim)('•')} ${(0, theme_1.dim)('34 Skills')} ${(0, theme_1.dim)('•')} ${(0, theme_1.dim)(cwd || '~')}`,
206
+ (0, theme_1.dim)(' ' + '─'.repeat(50)),
207
+ ];
208
+ return lines.join('\n');
209
+ }
210
+ /**
211
+ * Render a compact hamster message (for inline use)
212
+ */
213
+ function renderHamsterMessage(message, state = 'greeting') {
214
+ const art = HAMSTER_ART[state];
215
+ const artWidth = 14;
216
+ // Combine art with message on the same line
217
+ return [
218
+ art[0],
219
+ art[1],
220
+ `${art[2]} ${(0, theme_1.text)(message)}`,
221
+ art[3],
222
+ ].join('\n');
223
+ }
@@ -0,0 +1,253 @@
1
+ "use strict";
2
+ /**
3
+ * 🪝 Hook Engine — Trigger → Action → Variable Reward → Investment
4
+ * Based on Nir Eyal's Hook Model for habit-forming CLI experience
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.loadProfile = loadProfile;
11
+ exports.saveProfile = saveProfile;
12
+ exports.isFirstRun = isFirstRun;
13
+ exports.getContextualTrigger = getContextualTrigger;
14
+ exports.checkAchievements = checkAchievements;
15
+ exports.formatAchievement = formatAchievement;
16
+ exports.recordCommand = recordCommand;
17
+ exports.getLevelDisplay = getLevelDisplay;
18
+ exports.formatProfileSummary = formatProfileSummary;
19
+ const fs_1 = __importDefault(require("fs"));
20
+ const path_1 = __importDefault(require("path"));
21
+ const os_1 = __importDefault(require("os"));
22
+ const theme_1 = require("./theme");
23
+ const theme_2 = require("./theme");
24
+ // ─── Profile Storage ───────────────────────────────────────────────────────
25
+ const PROFILE_DIR = path_1.default.join(os_1.default.homedir(), '.codymaster');
26
+ const PROFILE_FILE = path_1.default.join(PROFILE_DIR, 'profile.json');
27
+ const DEFAULT_PROFILE = {
28
+ userName: '',
29
+ platform: '',
30
+ onboardingStep: 0,
31
+ onboardingComplete: false,
32
+ firstRunAt: '',
33
+ lastRunAt: '',
34
+ totalCommands: 0,
35
+ streak: 0,
36
+ lastStreakDate: '',
37
+ level: 'beginner',
38
+ achievements: [],
39
+ commandHistory: {},
40
+ skillsUsed: [],
41
+ };
42
+ /**
43
+ * Load user profile from disk
44
+ */
45
+ function loadProfile() {
46
+ try {
47
+ if (fs_1.default.existsSync(PROFILE_FILE)) {
48
+ const raw = fs_1.default.readFileSync(PROFILE_FILE, 'utf-8');
49
+ return Object.assign(Object.assign({}, DEFAULT_PROFILE), JSON.parse(raw));
50
+ }
51
+ }
52
+ catch ( /* ignore */_a) { /* ignore */ }
53
+ return Object.assign({}, DEFAULT_PROFILE);
54
+ }
55
+ /**
56
+ * Save user profile to disk
57
+ */
58
+ function saveProfile(profile) {
59
+ try {
60
+ if (!fs_1.default.existsSync(PROFILE_DIR)) {
61
+ fs_1.default.mkdirSync(PROFILE_DIR, { recursive: true });
62
+ }
63
+ fs_1.default.writeFileSync(PROFILE_FILE, JSON.stringify(profile, null, 2));
64
+ }
65
+ catch ( /* ignore */_a) { /* ignore */ }
66
+ }
67
+ /**
68
+ * Check if this is the first run (profile doesn't exist)
69
+ */
70
+ function isFirstRun() {
71
+ return !fs_1.default.existsSync(PROFILE_FILE);
72
+ }
73
+ /**
74
+ * Get a contextual trigger message based on user data
75
+ * Hook Model: External trigger that becomes internal over time
76
+ */
77
+ function getContextualTrigger(profile, ctx) {
78
+ const now = new Date();
79
+ const lastRun = profile.lastRunAt ? new Date(profile.lastRunAt) : null;
80
+ const hoursSinceLastRun = lastRun ? (now.getTime() - lastRun.getTime()) / 3600000 : 999;
81
+ // Re-engagement trigger
82
+ if (hoursSinceLastRun > 48) {
83
+ return `Haven't seen you in a while! 👋 Let's get back to it`;
84
+ }
85
+ // Streak trigger
86
+ if (profile.streak >= 3) {
87
+ return `${theme_2.ICONS.fire} ${profile.streak}-day streak! Keep it going!`;
88
+ }
89
+ // Task-based triggers
90
+ if ((ctx === null || ctx === void 0 ? void 0 : ctx.tasksInProgress) && ctx.tasksInProgress > 0) {
91
+ return `${ctx.tasksInProgress} task${ctx.tasksInProgress > 1 ? 's' : ''} cooking! ${theme_2.ICONS.hamster}`;
92
+ }
93
+ if ((ctx === null || ctx === void 0 ? void 0 : ctx.tasksInReview) && ctx.tasksInReview > 0) {
94
+ return `${ctx.tasksInReview} task${ctx.tasksInReview > 1 ? 's' : ''} waiting for review 👀`;
95
+ }
96
+ if ((ctx === null || ctx === void 0 ? void 0 : ctx.totalTasks) === 0) {
97
+ return `Clean slate! What shall we build? 🏗️`;
98
+ }
99
+ if ((ctx === null || ctx === void 0 ? void 0 : ctx.tasksDone) && ctx.totalTasks && ctx.tasksDone === ctx.totalTasks) {
100
+ return `All clear! ${theme_2.ICONS.star} What's next?`;
101
+ }
102
+ // Default
103
+ return `Ready when you are! ${theme_2.ICONS.hamster}`;
104
+ }
105
+ // ─── VARIABLE REWARD: Achievement System ───────────────────────────────────
106
+ const ACHIEVEMENTS = {
107
+ 'first_run': { name: 'Hello World', emoji: '👋', desc: 'First time running CodyMaster' },
108
+ 'first_task': { name: 'Task Master', emoji: '✅', desc: 'Created your first task' },
109
+ 'first_done': { name: 'Shipper', emoji: '🚀', desc: 'Completed your first task' },
110
+ 'five_tasks': { name: 'Busy Bee', emoji: '🐝', desc: 'Created 5 tasks' },
111
+ 'first_deploy': { name: 'Deploy Hero', emoji: '🦸', desc: 'First deployment recorded' },
112
+ 'first_skill': { name: 'Skill Hunter', emoji: '🧩', desc: 'Used your first skill' },
113
+ 'streak_3': { name: 'On Fire', emoji: '🔥', desc: '3-day usage streak' },
114
+ 'streak_7': { name: 'Committed', emoji: '💎', desc: '7-day usage streak' },
115
+ 'commands_50': { name: 'Power User', emoji: '⚡', desc: '50 commands executed' },
116
+ 'commands_100': { name: 'CLI Ninja', emoji: '🥷', desc: '100 commands executed' },
117
+ 'level_builder': { name: 'Builder', emoji: '🏗️', desc: 'Reached Builder level' },
118
+ 'level_master': { name: 'Master', emoji: '🏆', desc: 'Reached Master level' },
119
+ };
120
+ /**
121
+ * Check and unlock new achievements
122
+ * Returns newly unlocked achievements
123
+ */
124
+ function checkAchievements(profile) {
125
+ const newAchievements = [];
126
+ const check = (id, condition) => {
127
+ if (condition && !profile.achievements.includes(id)) {
128
+ profile.achievements.push(id);
129
+ newAchievements.push(id);
130
+ }
131
+ };
132
+ check('first_run', profile.totalCommands >= 1);
133
+ check('first_task', (profile.commandHistory['task add'] || 0) >= 1);
134
+ check('five_tasks', (profile.commandHistory['task add'] || 0) >= 5);
135
+ check('first_done', (profile.commandHistory['task done'] || 0) >= 1);
136
+ check('first_deploy', (profile.commandHistory['deploy'] || 0) >= 1);
137
+ check('first_skill', profile.skillsUsed.length >= 1);
138
+ check('streak_3', profile.streak >= 3);
139
+ check('streak_7', profile.streak >= 7);
140
+ check('commands_50', profile.totalCommands >= 50);
141
+ check('commands_100', profile.totalCommands >= 100);
142
+ check('level_builder', profile.level === 'builder' || profile.level === 'master' || profile.level === 'legend');
143
+ check('level_master', profile.level === 'master' || profile.level === 'legend');
144
+ return newAchievements;
145
+ }
146
+ /**
147
+ * Format achievement unlock message
148
+ */
149
+ function formatAchievement(id) {
150
+ const a = ACHIEVEMENTS[id];
151
+ if (!a)
152
+ return '';
153
+ return ` ${a.emoji} ${(0, theme_1.success)('Achievement Unlocked:')} ${(0, theme_1.brand)(a.name)} — ${(0, theme_1.dim)(a.desc)}`;
154
+ }
155
+ // ─── INVESTMENT: Track & Level Up ──────────────────────────────────────────
156
+ /**
157
+ * Record a command execution (Investment phase)
158
+ * Updates profile with usage data → CLI becomes more personal
159
+ */
160
+ function recordCommand(profile, command) {
161
+ const now = new Date();
162
+ const today = now.toISOString().split('T')[0];
163
+ // Update totals
164
+ profile.totalCommands++;
165
+ profile.commandHistory[command] = (profile.commandHistory[command] || 0) + 1;
166
+ profile.lastRunAt = now.toISOString();
167
+ // First run
168
+ if (!profile.firstRunAt) {
169
+ profile.firstRunAt = now.toISOString();
170
+ }
171
+ // Streak calculation
172
+ if (profile.lastStreakDate !== today) {
173
+ const yesterday = new Date(now);
174
+ yesterday.setDate(yesterday.getDate() - 1);
175
+ const yesterdayStr = yesterday.toISOString().split('T')[0];
176
+ if (profile.lastStreakDate === yesterdayStr) {
177
+ profile.streak++;
178
+ }
179
+ else if (profile.lastStreakDate !== today) {
180
+ profile.streak = 1;
181
+ }
182
+ profile.lastStreakDate = today;
183
+ }
184
+ // Level calculation
185
+ updateLevel(profile);
186
+ }
187
+ function updateLevel(profile) {
188
+ const cmds = profile.totalCommands;
189
+ const skills = profile.skillsUsed.length;
190
+ if (cmds >= 100 && skills >= 10) {
191
+ profile.level = 'legend';
192
+ }
193
+ else if (cmds >= 50 && skills >= 5) {
194
+ profile.level = 'master';
195
+ }
196
+ else if (cmds >= 10 && skills >= 1) {
197
+ profile.level = 'builder';
198
+ }
199
+ else {
200
+ profile.level = 'beginner';
201
+ }
202
+ }
203
+ /**
204
+ * Get level display with emoji
205
+ */
206
+ function getLevelDisplay(level) {
207
+ const levels = {
208
+ beginner: { emoji: '🌱', color: theme_1.dim },
209
+ builder: { emoji: '🏗️', color: theme_1.info },
210
+ master: { emoji: '🏆', color: theme_1.brand },
211
+ legend: { emoji: '👑', color: theme_1.success },
212
+ };
213
+ const l = levels[level] || levels.beginner;
214
+ return `${l.emoji} ${l.color(level.charAt(0).toUpperCase() + level.slice(1))}`;
215
+ }
216
+ /**
217
+ * Format profile summary for `cm profile` command
218
+ */
219
+ function formatProfileSummary(profile) {
220
+ const lines = [
221
+ '',
222
+ ` ${(0, theme_1.brand)('🐹 Your Profile')}`,
223
+ '',
224
+ ` ${(0, theme_1.dim)('Name:')} ${profile.userName || (0, theme_1.dim)('(not set)')}`,
225
+ ` ${(0, theme_1.dim)('Level:')} ${getLevelDisplay(profile.level)}`,
226
+ ` ${(0, theme_1.dim)('Streak:')} ${profile.streak > 0 ? `${theme_2.ICONS.fire} ${(0, theme_1.brand)(String(profile.streak))} days` : (0, theme_1.dim)('Start today!')}`,
227
+ ` ${(0, theme_1.dim)('Commands:')} ${(0, theme_1.brand)(String(profile.totalCommands))} total`,
228
+ ` ${(0, theme_1.dim)('Platform:')} ${profile.platform || (0, theme_1.dim)('(auto-detect)')}`,
229
+ '',
230
+ ];
231
+ // Achievements
232
+ if (profile.achievements.length > 0) {
233
+ lines.push(` ${(0, theme_1.brand)('Achievements')} (${profile.achievements.length}/${Object.keys(ACHIEVEMENTS).length})`);
234
+ for (const id of profile.achievements) {
235
+ const a = ACHIEVEMENTS[id];
236
+ if (a)
237
+ lines.push(` ${a.emoji} ${a.name}`);
238
+ }
239
+ lines.push('');
240
+ }
241
+ // Top commands
242
+ const topCmds = Object.entries(profile.commandHistory)
243
+ .sort(([, a], [, b]) => b - a)
244
+ .slice(0, 5);
245
+ if (topCmds.length > 0) {
246
+ lines.push(` ${(0, theme_1.brand)('Top Commands')}`);
247
+ for (const [cmd, count] of topCmds) {
248
+ lines.push(` ${(0, theme_1.dim)('cm')} ${cmd} ${(0, theme_1.muted)(`×${count}`)}`);
249
+ }
250
+ lines.push('');
251
+ }
252
+ return lines.join('\n');
253
+ }