codymaster 4.1.2 → 4.1.4
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/CHANGELOG.md +25 -2
- package/README.md +108 -35
- package/dist/index.js +728 -658
- package/dist/ui/box.js +237 -0
- package/dist/ui/hamster.js +223 -0
- package/dist/ui/hooks.js +253 -0
- package/dist/ui/onboarding.js +315 -0
- package/dist/ui/theme.js +105 -0
- package/install.sh +143 -64
- package/package.json +5 -6
- package/skills/cm-quality-gate/SKILL.md +15 -0
- package/skills/cm-safe-i18n/SKILL.md +4 -1
- package/skills/cm-tdd/SKILL.md +8 -8
package/dist/ui/box.js
ADDED
|
@@ -0,0 +1,237 @@
|
|
|
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.renderCommandHeader = renderCommandHeader;
|
|
21
|
+
exports.renderKeyValue = renderKeyValue;
|
|
22
|
+
exports.renderResult = renderResult;
|
|
23
|
+
exports.stripAnsi = stripAnsi;
|
|
24
|
+
const chalk_1 = __importDefault(require("chalk"));
|
|
25
|
+
const theme_1 = require("./theme");
|
|
26
|
+
// ─── Terminal Width ────────────────────────────────────────────────────────
|
|
27
|
+
function termWidth() {
|
|
28
|
+
return process.stdout.columns || 80;
|
|
29
|
+
}
|
|
30
|
+
function clamp(val, min, max) {
|
|
31
|
+
return Math.max(min, Math.min(max, val));
|
|
32
|
+
}
|
|
33
|
+
// ─── Box Components ────────────────────────────────────────────────────────
|
|
34
|
+
const BOX = {
|
|
35
|
+
tl: '╭', tr: '╮', bl: '╰', br: '╯',
|
|
36
|
+
h: '─', v: '│',
|
|
37
|
+
// Table
|
|
38
|
+
ttl: '┌', ttr: '┐', tbl: '└', tbr: '┘',
|
|
39
|
+
th: '─', tv: '│', tc: '┼',
|
|
40
|
+
tlt: '├', trt: '┤', ttt: '┬', tbt: '┴',
|
|
41
|
+
};
|
|
42
|
+
/**
|
|
43
|
+
* Render a bordered box with optional title
|
|
44
|
+
*/
|
|
45
|
+
function renderBox(content, opts) {
|
|
46
|
+
var _a, _b;
|
|
47
|
+
const pad = (_a = opts === null || opts === void 0 ? void 0 : opts.padding) !== null && _a !== void 0 ? _a : 1;
|
|
48
|
+
const w = (_b = opts === null || opts === void 0 ? void 0 : opts.width) !== null && _b !== void 0 ? _b : clamp(termWidth() - 4, 40, 80);
|
|
49
|
+
const innerW = w - 2 - (pad * 2);
|
|
50
|
+
const lines = [];
|
|
51
|
+
const padStr = ' '.repeat(pad);
|
|
52
|
+
// Top border with optional title
|
|
53
|
+
if (opts === null || opts === void 0 ? void 0 : opts.title) {
|
|
54
|
+
const title = ` ${opts.title} `;
|
|
55
|
+
const leftLen = 2;
|
|
56
|
+
const rightLen = Math.max(0, w - 2 - leftLen - stripAnsi(title).length);
|
|
57
|
+
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}`));
|
|
58
|
+
}
|
|
59
|
+
else {
|
|
60
|
+
lines.push((0, theme_1.dim)(`${BOX.tl}${BOX.h.repeat(w - 2)}${BOX.tr}`));
|
|
61
|
+
}
|
|
62
|
+
// Content lines
|
|
63
|
+
for (const line of content) {
|
|
64
|
+
const visible = stripAnsi(line);
|
|
65
|
+
const spaces = Math.max(0, innerW - visible.length);
|
|
66
|
+
lines.push((0, theme_1.dim)(BOX.v) + padStr + line + ' '.repeat(spaces) + padStr + (0, theme_1.dim)(BOX.v));
|
|
67
|
+
}
|
|
68
|
+
// Bottom border
|
|
69
|
+
lines.push((0, theme_1.dim)(`${BOX.bl}${BOX.h.repeat(w - 2)}${BOX.br}`));
|
|
70
|
+
return lines.join('\n');
|
|
71
|
+
}
|
|
72
|
+
/**
|
|
73
|
+
* Render a simple divider line
|
|
74
|
+
*/
|
|
75
|
+
function renderDivider(width) {
|
|
76
|
+
const w = width !== null && width !== void 0 ? width : clamp(termWidth() - 4, 40, 80);
|
|
77
|
+
return (0, theme_1.dim)(BOX.h.repeat(w));
|
|
78
|
+
}
|
|
79
|
+
/**
|
|
80
|
+
* Render a clean bordered table
|
|
81
|
+
*/
|
|
82
|
+
function renderTable(columns, rows) {
|
|
83
|
+
const lines = [];
|
|
84
|
+
const totalW = columns.reduce((sum, c) => sum + c.width, 0) + columns.length + 1;
|
|
85
|
+
// Header separator
|
|
86
|
+
const headerSep = (0, theme_1.dim)(columns.map(c => BOX.th.repeat(c.width)).join((0, theme_1.dim)('─┬─')));
|
|
87
|
+
lines.push(` ${headerSep}`);
|
|
88
|
+
// Header row
|
|
89
|
+
const headerCells = columns.map(c => {
|
|
90
|
+
const text = padCell(c.header, c.width, c.align);
|
|
91
|
+
return (0, theme_1.dim)(text);
|
|
92
|
+
});
|
|
93
|
+
lines.push(` ${headerCells.join((0, theme_1.dim)(' │ '))}`);
|
|
94
|
+
// Header underline
|
|
95
|
+
lines.push(` ${headerSep}`);
|
|
96
|
+
// Data rows
|
|
97
|
+
for (const row of rows) {
|
|
98
|
+
const cells = columns.map(c => {
|
|
99
|
+
const val = row[c.header] || '—';
|
|
100
|
+
const display = padCell(stripAnsi(val).length > c.width ? stripAnsi(val).substring(0, c.width - 1) + '…' : val, c.width, c.align);
|
|
101
|
+
return c.color ? c.color(display) : display;
|
|
102
|
+
});
|
|
103
|
+
lines.push(` ${cells.join((0, theme_1.dim)(' │ '))}`);
|
|
104
|
+
}
|
|
105
|
+
// Bottom separator
|
|
106
|
+
lines.push(` ${headerSep}`);
|
|
107
|
+
return lines.join('\n');
|
|
108
|
+
}
|
|
109
|
+
function padCell(str, width, align) {
|
|
110
|
+
const visible = stripAnsi(str);
|
|
111
|
+
const diff = Math.max(0, width - visible.length);
|
|
112
|
+
if (align === 'right')
|
|
113
|
+
return ' '.repeat(diff) + str;
|
|
114
|
+
if (align === 'center') {
|
|
115
|
+
const left = Math.floor(diff / 2);
|
|
116
|
+
return ' '.repeat(left) + str + ' '.repeat(diff - left);
|
|
117
|
+
}
|
|
118
|
+
return str + ' '.repeat(diff);
|
|
119
|
+
}
|
|
120
|
+
// ─── Progress Bar ──────────────────────────────────────────────────────────
|
|
121
|
+
/**
|
|
122
|
+
* Render a colored progress bar
|
|
123
|
+
*/
|
|
124
|
+
function renderProgressBar(pct, width) {
|
|
125
|
+
const w = width !== null && width !== void 0 ? width : 16;
|
|
126
|
+
const filled = Math.round((clamp(pct, 0, 100) / 100) * w);
|
|
127
|
+
const empty = w - filled;
|
|
128
|
+
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;
|
|
129
|
+
return color('█'.repeat(filled)) + (0, theme_1.muted)('░'.repeat(empty)) + (0, theme_1.dim)(` ${pct}%`);
|
|
130
|
+
}
|
|
131
|
+
// ─── Status Badge ──────────────────────────────────────────────────────────
|
|
132
|
+
const BADGE_COLORS = {
|
|
133
|
+
'backlog': chalk_1.default.hex(theme_1.COLORS.backlog),
|
|
134
|
+
'in-progress': chalk_1.default.hex(theme_1.COLORS.inProgress),
|
|
135
|
+
'review': chalk_1.default.hex(theme_1.COLORS.warning),
|
|
136
|
+
'done': chalk_1.default.hex(theme_1.COLORS.done),
|
|
137
|
+
'success': chalk_1.default.hex(theme_1.COLORS.success),
|
|
138
|
+
'failed': chalk_1.default.hex(theme_1.COLORS.error),
|
|
139
|
+
'pending': chalk_1.default.hex(theme_1.COLORS.warning),
|
|
140
|
+
'running': chalk_1.default.hex(theme_1.COLORS.inProgress),
|
|
141
|
+
};
|
|
142
|
+
/**
|
|
143
|
+
* Render a colored status badge: ● status
|
|
144
|
+
*/
|
|
145
|
+
function renderBadge(status) {
|
|
146
|
+
const color = BADGE_COLORS[status] || theme_1.dim;
|
|
147
|
+
return color(`${theme_1.ICONS.dot} ${status}`);
|
|
148
|
+
}
|
|
149
|
+
/**
|
|
150
|
+
* Render priority badge
|
|
151
|
+
*/
|
|
152
|
+
function renderPriority(priority) {
|
|
153
|
+
const color = theme_1.PRI[priority] || theme_1.dim;
|
|
154
|
+
return color(`${theme_1.ICONS.dot} ${priority}`);
|
|
155
|
+
}
|
|
156
|
+
// ─── Speech Bubble ─────────────────────────────────────────────────────────
|
|
157
|
+
/**
|
|
158
|
+
* Render a hamster speech bubble
|
|
159
|
+
*/
|
|
160
|
+
function renderSpeechBubble(message) {
|
|
161
|
+
const w = stripAnsi(message).length + 4;
|
|
162
|
+
return [
|
|
163
|
+
(0, theme_1.dim)(` ╭${'─'.repeat(w)}╮`),
|
|
164
|
+
(0, theme_1.dim)(` │`) + ` ${message} ` + (0, theme_1.dim)(`│`),
|
|
165
|
+
(0, theme_1.dim)(` ╰${'─'.repeat(w)}╯`),
|
|
166
|
+
(0, theme_1.dim)(` ╰─`),
|
|
167
|
+
].join('\n');
|
|
168
|
+
}
|
|
169
|
+
// ─── Step Indicator ────────────────────────────────────────────────────────
|
|
170
|
+
/**
|
|
171
|
+
* Render onboarding step progress: Step 2 of 5 ●●○○○
|
|
172
|
+
*/
|
|
173
|
+
function renderStepProgress(current, total) {
|
|
174
|
+
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(' ');
|
|
175
|
+
return ` ${(0, theme_1.dim)('Step')} ${(0, theme_1.brand)(String(current))} ${(0, theme_1.dim)('of')} ${(0, theme_1.dim)(String(total))} ${dots}`;
|
|
176
|
+
}
|
|
177
|
+
// ─── Footer ────────────────────────────────────────────────────────────────
|
|
178
|
+
/**
|
|
179
|
+
* Render a footer hint bar
|
|
180
|
+
*/
|
|
181
|
+
function renderFooter(hints) {
|
|
182
|
+
return ` ${hints.map(h => (0, theme_1.dim)(h)).join((0, theme_1.dim)(' • '))}`;
|
|
183
|
+
}
|
|
184
|
+
// ─── Command Header ────────────────────────────────────────────────────────
|
|
185
|
+
/**
|
|
186
|
+
* Render a branded command header: ⚙️ Configuration
|
|
187
|
+
*/
|
|
188
|
+
function renderCommandHeader(title, icon) {
|
|
189
|
+
const iconStr = icon ? `${icon} ` : '';
|
|
190
|
+
return `\n ${iconStr}${(0, theme_1.brand)(title)}\n`;
|
|
191
|
+
}
|
|
192
|
+
// ─── Key-Value Display ─────────────────────────────────────────────────────
|
|
193
|
+
/**
|
|
194
|
+
* Render aligned key-value pairs with branded styling
|
|
195
|
+
* Input: [['Version', '4.1.3'], ['Port', '4321']]
|
|
196
|
+
* Output:
|
|
197
|
+
* Version 4.1.3
|
|
198
|
+
* Port 4321
|
|
199
|
+
*/
|
|
200
|
+
function renderKeyValue(pairs, opts) {
|
|
201
|
+
var _a, _b;
|
|
202
|
+
const indent = ' '.repeat((_a = opts === null || opts === void 0 ? void 0 : opts.indent) !== null && _a !== void 0 ? _a : 2);
|
|
203
|
+
const maxKey = (_b = opts === null || opts === void 0 ? void 0 : opts.keyWidth) !== null && _b !== void 0 ? _b : Math.max(...pairs.map(([k]) => k.length)) + 1;
|
|
204
|
+
return pairs.map(([key, value]) => {
|
|
205
|
+
const paddedKey = (key + ':').padEnd(maxKey + 1);
|
|
206
|
+
return `${indent}${(0, theme_1.dim)(paddedKey)} ${value}`;
|
|
207
|
+
}).join('\n');
|
|
208
|
+
}
|
|
209
|
+
// ─── Result Messages ───────────────────────────────────────────────────────
|
|
210
|
+
const RESULT_CONFIG = {
|
|
211
|
+
success: { icon: '✅', color: theme_1.success },
|
|
212
|
+
error: { icon: '❌', color: theme_1.error },
|
|
213
|
+
warning: { icon: '⚠️', color: theme_1.warning },
|
|
214
|
+
info: { icon: 'ℹ️', color: (s) => s },
|
|
215
|
+
};
|
|
216
|
+
/**
|
|
217
|
+
* Render standardized result message with optional detail lines
|
|
218
|
+
*/
|
|
219
|
+
function renderResult(type, message, details) {
|
|
220
|
+
const cfg = RESULT_CONFIG[type];
|
|
221
|
+
const lines = [`\n ${cfg.icon} ${cfg.color(message)}`];
|
|
222
|
+
if (details) {
|
|
223
|
+
for (const d of details) {
|
|
224
|
+
lines.push(` ${d}`);
|
|
225
|
+
}
|
|
226
|
+
}
|
|
227
|
+
lines.push('');
|
|
228
|
+
return lines.join('\n');
|
|
229
|
+
}
|
|
230
|
+
// ─── Utilities ─────────────────────────────────────────────────────────────
|
|
231
|
+
/**
|
|
232
|
+
* Strip ANSI escape codes for width calculations
|
|
233
|
+
*/
|
|
234
|
+
function stripAnsi(str) {
|
|
235
|
+
// eslint-disable-next-line no-control-regex
|
|
236
|
+
return str.replace(/\x1B\[[0-9;]*[a-zA-Z]/g, '').replace(/\x1B\]8;[^;]*;[^\x1B]*\x1B\\/g, '');
|
|
237
|
+
}
|
|
@@ -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
|
+
}
|