wiggum-cli 0.5.0 → 0.5.1

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 (41) hide show
  1. package/dist/ai/agents/codebase-analyzer.d.ts.map +1 -1
  2. package/dist/ai/agents/codebase-analyzer.js +9 -2
  3. package/dist/ai/agents/codebase-analyzer.js.map +1 -1
  4. package/dist/ai/agents/mcp-detector.d.ts +4 -2
  5. package/dist/ai/agents/mcp-detector.d.ts.map +1 -1
  6. package/dist/ai/agents/mcp-detector.js +9 -6
  7. package/dist/ai/agents/mcp-detector.js.map +1 -1
  8. package/dist/ai/agents/types.d.ts +12 -2
  9. package/dist/ai/agents/types.d.ts.map +1 -1
  10. package/dist/ai/enhancer.d.ts +27 -1
  11. package/dist/ai/enhancer.d.ts.map +1 -1
  12. package/dist/ai/enhancer.js +52 -7
  13. package/dist/ai/enhancer.js.map +1 -1
  14. package/dist/ai/index.d.ts +1 -1
  15. package/dist/ai/index.d.ts.map +1 -1
  16. package/dist/ai/index.js.map +1 -1
  17. package/dist/commands/init.d.ts.map +1 -1
  18. package/dist/commands/init.js +113 -112
  19. package/dist/commands/init.js.map +1 -1
  20. package/dist/utils/colors.d.ts +47 -0
  21. package/dist/utils/colors.d.ts.map +1 -1
  22. package/dist/utils/colors.js +108 -0
  23. package/dist/utils/colors.js.map +1 -1
  24. package/dist/utils/header.d.ts.map +1 -1
  25. package/dist/utils/header.js +19 -2
  26. package/dist/utils/header.js.map +1 -1
  27. package/dist/utils/spinner.d.ts +74 -0
  28. package/dist/utils/spinner.d.ts.map +1 -0
  29. package/dist/utils/spinner.js +208 -0
  30. package/dist/utils/spinner.js.map +1 -0
  31. package/package.json +1 -1
  32. package/src/ai/agents/codebase-analyzer.ts +12 -3
  33. package/src/ai/agents/mcp-detector.test.ts +30 -7
  34. package/src/ai/agents/mcp-detector.ts +10 -7
  35. package/src/ai/agents/types.ts +13 -2
  36. package/src/ai/enhancer.ts +88 -9
  37. package/src/ai/index.ts +2 -0
  38. package/src/commands/init.ts +129 -124
  39. package/src/utils/colors.ts +139 -0
  40. package/src/utils/header.ts +19 -2
  41. package/src/utils/spinner.ts +262 -0
@@ -101,3 +101,142 @@ export function sectionHeader(title: string): string {
101
101
  const line = drawLine(50);
102
102
  return `\n${line}\n${simpson.yellow(title)}\n${line}`;
103
103
  }
104
+
105
+ /**
106
+ * Compact section header (single line style)
107
+ */
108
+ export function compactHeader(title: string): string {
109
+ const titlePart = `─── ${title} ───`;
110
+ return simpson.yellow(titlePart);
111
+ }
112
+
113
+ /**
114
+ * Progress phase indicators
115
+ */
116
+ export const phase = {
117
+ pending: '○',
118
+ active: '◐',
119
+ complete: '✓',
120
+ error: '✗',
121
+ };
122
+
123
+ /**
124
+ * Display a stack info box
125
+ */
126
+ export function stackBox(stack: {
127
+ framework?: string;
128
+ language?: string;
129
+ testing?: string;
130
+ packageManager?: string;
131
+ }): string {
132
+ const lines: string[] = [];
133
+ const maxLabelWidth = 10;
134
+
135
+ const addLine = (label: string, value: string | undefined) => {
136
+ if (value) {
137
+ const paddedLabel = label.padEnd(maxLabelWidth);
138
+ lines.push(`│ ${simpson.brown(paddedLabel)} ${value}`);
139
+ }
140
+ };
141
+
142
+ lines.push(simpson.brown('┌─ Detected Stack ────────────────────────────┐'));
143
+ addLine('Framework:', stack.framework);
144
+ addLine('Language:', stack.language);
145
+ addLine('Testing:', stack.testing);
146
+ addLine('Package:', stack.packageManager);
147
+ lines.push(simpson.brown('└─────────────────────────────────────────────┘'));
148
+
149
+ return lines.join('\n');
150
+ }
151
+
152
+ /**
153
+ * Progress phases display
154
+ */
155
+ export interface ProgressPhase {
156
+ name: string;
157
+ status: 'pending' | 'active' | 'complete' | 'error';
158
+ detail?: string;
159
+ }
160
+
161
+ export function progressPhases(phases: ProgressPhase[]): string {
162
+ return phases.map((p, i) => {
163
+ const num = `${i + 1}.`;
164
+ const icon = phase[p.status];
165
+ const statusColor = p.status === 'complete' ? simpson.yellow :
166
+ p.status === 'active' ? simpson.white :
167
+ p.status === 'error' ? simpson.pink :
168
+ simpson.brown;
169
+ const detail = p.detail ? simpson.brown(` (${p.detail})`) : '';
170
+ return ` ${num} ${statusColor(icon)} ${p.name}${detail}`;
171
+ }).join('\n');
172
+ }
173
+
174
+ /**
175
+ * File tree display for generated files
176
+ */
177
+ export function fileTree(basePath: string, files: string[]): string {
178
+ // Group files by directory
179
+ const tree: Record<string, string[]> = {};
180
+
181
+ for (const file of files) {
182
+ const parts = file.split('/');
183
+ if (parts.length === 1) {
184
+ tree[''] = tree[''] || [];
185
+ tree[''].push(parts[0]);
186
+ } else {
187
+ const dir = parts.slice(0, -1).join('/');
188
+ const filename = parts[parts.length - 1];
189
+ tree[dir] = tree[dir] || [];
190
+ tree[dir].push(filename);
191
+ }
192
+ }
193
+
194
+ const lines: string[] = [` ${simpson.yellow(basePath + '/')}`];
195
+ const dirs = Object.keys(tree).sort();
196
+
197
+ for (let i = 0; i < dirs.length; i++) {
198
+ const dir = dirs[i];
199
+ const isLastDir = i === dirs.length - 1;
200
+ const dirPrefix = isLastDir ? ' └── ' : ' ├── ';
201
+ const filePrefix = isLastDir ? ' ' : ' │ ';
202
+
203
+ if (dir) {
204
+ lines.push(`${dirPrefix}${simpson.brown(dir + '/')}`);
205
+ }
206
+
207
+ const filesInDir = tree[dir].sort();
208
+ for (let j = 0; j < filesInDir.length; j++) {
209
+ const file = filesInDir[j];
210
+ const isLastFile = j === filesInDir.length - 1;
211
+ const connector = dir ? (isLastFile ? '└── ' : '├── ') : (isLastFile && isLastDir ? '└── ' : '├── ');
212
+ const prefix = dir ? filePrefix : ' ';
213
+ lines.push(`${prefix}${connector}${file}`);
214
+ }
215
+ }
216
+
217
+ return lines.join('\n');
218
+ }
219
+
220
+ /**
221
+ * Next steps box
222
+ */
223
+ export function nextStepsBox(steps: Array<{ command: string; description: string }>): string {
224
+ const lines: string[] = [];
225
+ lines.push('');
226
+ lines.push(compactHeader('Next Steps'));
227
+ lines.push('');
228
+
229
+ for (const step of steps) {
230
+ lines.push(` ${simpson.yellow('$')} ${step.command}`);
231
+ lines.push(` ${simpson.brown(step.description)}`);
232
+ lines.push('');
233
+ }
234
+
235
+ return lines.join('\n');
236
+ }
237
+
238
+ /**
239
+ * Fixed-length password mask (32 characters)
240
+ */
241
+ export const PASSWORD_MASK_LENGTH = 32;
242
+ export const PASSWORD_MASK = '▪'.repeat(PASSWORD_MASK_LENGTH);
@@ -1,12 +1,29 @@
1
1
  import cfonts from 'cfonts';
2
2
  import { simpson, drawBox, SIMPSON_COLORS } from './colors.js';
3
+ import { readFileSync } from 'fs';
4
+ import { fileURLToPath } from 'url';
5
+ import { dirname, join } from 'path';
6
+
7
+ // Get version from package.json
8
+ function getVersion(): string {
9
+ try {
10
+ const __filename = fileURLToPath(import.meta.url);
11
+ const __dirname = dirname(__filename);
12
+ const packagePath = join(__dirname, '..', '..', 'package.json');
13
+ const pkg = JSON.parse(readFileSync(packagePath, 'utf-8'));
14
+ return pkg.version || '0.5.0';
15
+ } catch {
16
+ return '0.5.0';
17
+ }
18
+ }
3
19
 
4
20
  /**
5
21
  * Display the WIGGUM CLI ASCII header with welcome box
6
22
  */
7
23
  export function displayHeader(): void {
8
- // Welcome box like Claude Code
9
- const welcomeText = '🍩 Welcome to ' + simpson.yellow('Wiggum CLI') + ': AI-powered Ralph development loop CLI 🍩';
24
+ const version = getVersion();
25
+ // Welcome box like Claude Code with version
26
+ const welcomeText = '🍩 Welcome to ' + simpson.yellow('Wiggum CLI') + ': AI-powered ' + simpson.yellow('Ralph') + ' development loop CLI ' + simpson.pink(`v${version}`) + ' 🍩';
10
27
  console.log('');
11
28
  console.log(drawBox(welcomeText, 2));
12
29
  console.log('');
@@ -0,0 +1,262 @@
1
+ /**
2
+ * Custom Spinner with Shimmer Effect, Timer, and Token Tracking
3
+ * Inspired by Claude Code's loading experience
4
+ */
5
+
6
+ import { simpson } from './colors.js';
7
+
8
+ /**
9
+ * Shimmer frames for the spinner animation
10
+ * Creates a wave-like effect similar to Claude Code
11
+ */
12
+ const SHIMMER_FRAMES = [
13
+ '░▒▓█▓▒░',
14
+ '▒▓█▓▒░░',
15
+ '▓█▓▒░░▒',
16
+ '█▓▒░░▒▓',
17
+ '▓▒░░▒▓█',
18
+ '▒░░▒▓█▓',
19
+ '░░▒▓█▓▒',
20
+ '░▒▓█▓▒░',
21
+ ];
22
+
23
+ /**
24
+ * Braille spinner frames (alternative style)
25
+ */
26
+ const BRAILLE_FRAMES = ['⠋', '⠙', '⠹', '⠸', '⠼', '⠴', '⠦', '⠧', '⠇', '⠏'];
27
+
28
+ /**
29
+ * Dots spinner frames
30
+ */
31
+ const DOTS_FRAMES = ['⠁', '⠂', '⠄', '⡀', '⢀', '⠠', '⠐', '⠈'];
32
+
33
+ /**
34
+ * Format milliseconds to human-readable time
35
+ */
36
+ function formatTime(ms: number): string {
37
+ const seconds = Math.floor(ms / 1000);
38
+ const minutes = Math.floor(seconds / 60);
39
+ const remainingSeconds = seconds % 60;
40
+
41
+ if (minutes > 0) {
42
+ return `${minutes}m ${remainingSeconds}s`;
43
+ }
44
+ return `${seconds}s`;
45
+ }
46
+
47
+ /**
48
+ * Format token count with commas
49
+ */
50
+ function formatTokens(tokens: number): string {
51
+ return tokens.toLocaleString();
52
+ }
53
+
54
+ export interface ShimmerSpinnerOptions {
55
+ /** Enable shimmer effect (default: true) */
56
+ shimmer?: boolean;
57
+ /** Show elapsed time (default: true) */
58
+ showTimer?: boolean;
59
+ /** Show token usage (default: false) */
60
+ showTokens?: boolean;
61
+ /** Spinner style (default: 'shimmer') */
62
+ style?: 'shimmer' | 'braille' | 'dots';
63
+ }
64
+
65
+ export interface TokenUsage {
66
+ inputTokens: number;
67
+ outputTokens: number;
68
+ totalTokens: number;
69
+ }
70
+
71
+ /**
72
+ * Custom spinner with shimmer effect, timer, and token tracking
73
+ */
74
+ export class ShimmerSpinner {
75
+ private message: string = '';
76
+ private isRunning: boolean = false;
77
+ private intervalId: NodeJS.Timeout | null = null;
78
+ private frameIndex: number = 0;
79
+ private startTime: number = 0;
80
+ private tokens: TokenUsage = { inputTokens: 0, outputTokens: 0, totalTokens: 0 };
81
+ private options: Required<ShimmerSpinnerOptions>;
82
+ private frames: string[];
83
+
84
+ constructor(options: ShimmerSpinnerOptions = {}) {
85
+ this.options = {
86
+ shimmer: options.shimmer ?? true,
87
+ showTimer: options.showTimer ?? true,
88
+ showTokens: options.showTokens ?? false,
89
+ style: options.style ?? 'shimmer',
90
+ };
91
+
92
+ // Select frames based on style
93
+ switch (this.options.style) {
94
+ case 'braille':
95
+ this.frames = BRAILLE_FRAMES;
96
+ break;
97
+ case 'dots':
98
+ this.frames = DOTS_FRAMES;
99
+ break;
100
+ case 'shimmer':
101
+ default:
102
+ this.frames = SHIMMER_FRAMES;
103
+ break;
104
+ }
105
+ }
106
+
107
+ /**
108
+ * Start the spinner with a message
109
+ */
110
+ start(message: string): void {
111
+ this.message = message;
112
+ this.isRunning = true;
113
+ this.startTime = Date.now();
114
+ this.frameIndex = 0;
115
+
116
+ // Clear line and hide cursor
117
+ process.stdout.write('\x1B[?25l');
118
+
119
+ this.render();
120
+ this.intervalId = setInterval(() => {
121
+ this.frameIndex = (this.frameIndex + 1) % this.frames.length;
122
+ this.render();
123
+ }, 80);
124
+ }
125
+
126
+ /**
127
+ * Update the spinner message
128
+ */
129
+ update(message: string): void {
130
+ this.message = message;
131
+ }
132
+
133
+ /**
134
+ * Update token usage (adds to existing)
135
+ */
136
+ updateTokens(usage: TokenUsage): void {
137
+ this.tokens = {
138
+ inputTokens: this.tokens.inputTokens + usage.inputTokens,
139
+ outputTokens: this.tokens.outputTokens + usage.outputTokens,
140
+ totalTokens: this.tokens.totalTokens + usage.totalTokens,
141
+ };
142
+ }
143
+
144
+ /**
145
+ * Set token usage directly
146
+ */
147
+ setTokens(usage: TokenUsage): void {
148
+ this.tokens = usage;
149
+ }
150
+
151
+ /**
152
+ * Render the spinner
153
+ */
154
+ private render(): void {
155
+ if (!this.isRunning) return;
156
+
157
+ const elapsed = Date.now() - this.startTime;
158
+ const frame = this.frames[this.frameIndex];
159
+
160
+ let line = '';
161
+
162
+ // Shimmer/spinner frame
163
+ if (this.options.shimmer && this.options.style === 'shimmer') {
164
+ line += simpson.yellow(frame) + ' ';
165
+ } else {
166
+ line += simpson.yellow(frame) + ' ';
167
+ }
168
+
169
+ // Message
170
+ line += this.message;
171
+
172
+ // Timer
173
+ if (this.options.showTimer) {
174
+ line += simpson.brown(` [${formatTime(elapsed)}]`);
175
+ }
176
+
177
+ // Tokens
178
+ if (this.options.showTokens && this.tokens.totalTokens > 0) {
179
+ line += simpson.pink(` (${formatTokens(this.tokens.totalTokens)} tokens)`);
180
+ }
181
+
182
+ // Clear line and write
183
+ process.stdout.write('\r\x1B[K' + line);
184
+ }
185
+
186
+ /**
187
+ * Stop the spinner with a final message
188
+ */
189
+ stop(finalMessage?: string): void {
190
+ this.isRunning = false;
191
+
192
+ if (this.intervalId) {
193
+ clearInterval(this.intervalId);
194
+ this.intervalId = null;
195
+ }
196
+
197
+ const elapsed = Date.now() - this.startTime;
198
+
199
+ // Build final line
200
+ let line = '';
201
+ line += simpson.yellow('✓') + ' ';
202
+ line += finalMessage || this.message;
203
+
204
+ if (this.options.showTimer) {
205
+ line += simpson.brown(` [${formatTime(elapsed)}]`);
206
+ }
207
+
208
+ if (this.options.showTokens && this.tokens.totalTokens > 0) {
209
+ line += simpson.pink(` (${formatTokens(this.tokens.totalTokens)} tokens)`);
210
+ }
211
+
212
+ // Clear line, show cursor, print final
213
+ process.stdout.write('\r\x1B[K' + line + '\n');
214
+ process.stdout.write('\x1B[?25h');
215
+ }
216
+
217
+ /**
218
+ * Stop with error
219
+ */
220
+ fail(message?: string): void {
221
+ this.isRunning = false;
222
+
223
+ if (this.intervalId) {
224
+ clearInterval(this.intervalId);
225
+ this.intervalId = null;
226
+ }
227
+
228
+ const elapsed = Date.now() - this.startTime;
229
+
230
+ let line = '';
231
+ line += simpson.pink('✗') + ' ';
232
+ line += message || this.message;
233
+
234
+ if (this.options.showTimer) {
235
+ line += simpson.brown(` [${formatTime(elapsed)}]`);
236
+ }
237
+
238
+ process.stdout.write('\r\x1B[K' + line + '\n');
239
+ process.stdout.write('\x1B[?25h');
240
+ }
241
+
242
+ /**
243
+ * Get elapsed time in milliseconds
244
+ */
245
+ getElapsed(): number {
246
+ return Date.now() - this.startTime;
247
+ }
248
+
249
+ /**
250
+ * Get current token usage
251
+ */
252
+ getTokens(): TokenUsage {
253
+ return { ...this.tokens };
254
+ }
255
+ }
256
+
257
+ /**
258
+ * Create a shimmer spinner instance
259
+ */
260
+ export function createShimmerSpinner(options?: ShimmerSpinnerOptions): ShimmerSpinner {
261
+ return new ShimmerSpinner(options);
262
+ }