@sschepis/robodev 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.
@@ -0,0 +1,503 @@
1
+ // Enhanced Console Styling System
2
+ // Provides modern, themed terminal output with gradients, icons, and animations
3
+
4
+ import chalk from 'chalk';
5
+ import gradient from 'gradient-string';
6
+ import boxen from 'boxen';
7
+ import ora from 'ora';
8
+ import figures from 'figures';
9
+ import fs from 'fs';
10
+ import path from 'path';
11
+ import { fileURLToPath } from 'url';
12
+
13
+ export class ConsoleStyler {
14
+ constructor(theme = 'cyberpunk') {
15
+ this.currentTheme = theme;
16
+ this.themes = this.initializeThemes();
17
+ this.icons = this.initializeIcons();
18
+ this.spinners = new Map(); // Track active spinners
19
+ this.listener = null; // Listener for redirecting output
20
+ }
21
+
22
+ // Set a listener to capture logs instead of printing to console
23
+ setListener(listener) {
24
+ this.listener = listener;
25
+ }
26
+
27
+ // Initialize color themes
28
+ initializeThemes() {
29
+ // Default themes
30
+ let themes = {};
31
+
32
+ // Try to load themes from external file
33
+ try {
34
+ const __filename = fileURLToPath(import.meta.url);
35
+ const scriptDir = path.dirname(path.dirname(path.dirname(__filename)));
36
+ const themesPath = path.join(scriptDir, 'themes.json');
37
+
38
+ if (fs.existsSync(themesPath)) {
39
+ const loadedThemes = JSON.parse(fs.readFileSync(themesPath, 'utf8'));
40
+
41
+ // Convert arrays of colors to gradient functions
42
+ for (const [name, colors] of Object.entries(loadedThemes)) {
43
+ themes[name] = {};
44
+ for (const [type, colorArray] of Object.entries(colors)) {
45
+ themes[name][type] = gradient(colorArray);
46
+ }
47
+ }
48
+ }
49
+ } catch (error) {
50
+ // Fallback if file load fails
51
+ console.error('Failed to load themes.json:', error.message);
52
+ }
53
+
54
+ // Ensure at least one default theme exists if loading failed
55
+ if (Object.keys(themes).length === 0) {
56
+ themes.cyberpunk = {
57
+ primary: gradient(['#ff0080', '#7928ca', '#0070f3']),
58
+ secondary: gradient(['#00d4ff', '#0070f3']),
59
+ success: gradient(['#00ff88', '#00d4aa']),
60
+ warning: gradient(['#ffaa00', '#ff6b35']),
61
+ error: gradient(['#ff4757', '#c44569']),
62
+ info: gradient(['#5352ed', '#3742fa']),
63
+ system: gradient(['#747d8c', '#57606f']),
64
+ accent: gradient(['#ffa502', '#ff6348']),
65
+ todo: gradient(['#ff6b9d', '#c44569']),
66
+ workspace: gradient(['#6c5ce7', '#a29bfe']),
67
+ tools: gradient(['#00cec9', '#00b894']),
68
+ reasoning: gradient(['#fd79a8', '#e84393']),
69
+ quality: gradient(['#fdcb6e', '#e17055']),
70
+ progress: gradient(['#74b9ff', '#0984e3'])
71
+ };
72
+ }
73
+
74
+ return themes;
75
+ }
76
+
77
+ // Initialize icons for different message types
78
+ initializeIcons() {
79
+ return {
80
+ user: '👤',
81
+ ai: '🤖',
82
+ error: '❌',
83
+ warning: 'âš ī¸',
84
+ success: '✅',
85
+ info: 'â„šī¸',
86
+ system: 'âš™ī¸',
87
+ tools: '🔧',
88
+ todo: '📋',
89
+ workspace: '🏠',
90
+ working: '⚡',
91
+ recovery: '🔄',
92
+ quality: 'đŸŽ¯',
93
+ tts: '🔊',
94
+ reasoning: '🧠',
95
+ history: '📚',
96
+ packages: 'đŸ“Ļ',
97
+ progress: 'âŗ',
98
+ workCompleted: '✨',
99
+ custom: 'đŸ› ī¸',
100
+ loading: 'âŸŗ',
101
+ check: figures.tick,
102
+ cross: figures.cross,
103
+ bullet: figures.bullet,
104
+ arrow: figures.arrowRight,
105
+ star: figures.star,
106
+ heart: figures.heart,
107
+ radioOn: figures.radioOn,
108
+ radioOff: figures.radioOff
109
+ };
110
+ }
111
+
112
+ // Set theme
113
+ setTheme(themeName) {
114
+ if (this.themes[themeName]) {
115
+ this.currentTheme = themeName;
116
+ return true;
117
+ }
118
+ return false;
119
+ }
120
+
121
+ // Get current theme colors
122
+ getTheme() {
123
+ return this.themes[this.currentTheme] || Object.values(this.themes)[0];
124
+ }
125
+
126
+ // Enhanced startup banner
127
+ displayStartupBanner(workingDir) {
128
+ const theme = this.getTheme();
129
+ const banner = `
130
+ ██╗ ██╗ ██╗ ██████╗ ███████╗███████╗██╗███████╗████████╗ █████╗ ███╗ ██╗████████╗
131
+ ██║ ██║ ██║ ██╔══██╗██╔════╝██╔════╝██║██╔════╝╚══██╔══╝██╔══██╗████╗ ██║╚══██╔══╝
132
+ ██║ ██║ █╗ ██║ ███████║███████╗███████╗██║███████╗ ██║ ███████║██╔██╗ ██║ ██║
133
+ ██║ ██║███╗██║ ██╔══██║╚════██║╚════██║██║╚════██║ ██║ ██╔══██║██║╚██╗██║ ██║
134
+ ███████╗╚███╔███╔╝ ██║ ██║███████║███████║██║███████║ ██║ ██║ ██║██║ ╚████║ ██║
135
+ ╚══════╝ ╚══╝╚══╝ ╚═╝ ╚═╝╚══════╝╚══════╝╚═╝╚══════╝ ╚═╝ ╚═╝ ╚═╝╚═╝ ╚═══╝ ╚═╝
136
+ `;
137
+
138
+ const styledBanner = theme.primary(banner);
139
+
140
+ const infoBox = boxen(
141
+ `${this.icons.system} Working Directory: ${chalk.cyan(workingDir)}\n` +
142
+ `${this.icons.ai} Theme: ${chalk.magenta(this.currentTheme)}`,
143
+ {
144
+ padding: 0,
145
+ margin: 1,
146
+ borderStyle: 'none',
147
+ borderColor: 'cyan',
148
+ backgroundColor: 'black'
149
+ }
150
+ );
151
+ console.clear();
152
+ console.log(styledBanner);
153
+ console.log(infoBox);
154
+ console.log(theme.accent('═'.repeat(80)));
155
+ }
156
+
157
+ // Enhanced message formatting
158
+ formatMessage(type, content, options = {}) {
159
+ const theme = this.getTheme();
160
+ const icon = this.icons[type] || this.icons.info;
161
+ const timestamp = options.timestamp ? chalk.gray(`[${new Date().toLocaleTimeString()}] `) : '';
162
+
163
+ let colorFunc;
164
+ let label = '';
165
+
166
+ switch (type) {
167
+ case 'user':
168
+ colorFunc = theme.info;
169
+ label = 'YOU';
170
+ break;
171
+ case 'ai':
172
+ colorFunc = theme.success;
173
+ label = 'AI';
174
+ break;
175
+ case 'error':
176
+ colorFunc = theme.error;
177
+ label = 'ERROR';
178
+ break;
179
+ case 'warning':
180
+ colorFunc = theme.warning;
181
+ label = 'WARNING';
182
+ break;
183
+ case 'system':
184
+ colorFunc = theme.system;
185
+ label = 'SYSTEM';
186
+ break;
187
+ case 'tools':
188
+ colorFunc = theme.tools;
189
+ label = 'TOOLS';
190
+ break;
191
+ case 'todo':
192
+ colorFunc = theme.todo;
193
+ label = 'TODO';
194
+ break;
195
+ case 'workspace':
196
+ colorFunc = theme.workspace;
197
+ label = 'WORKSPACE';
198
+ break;
199
+ case 'working':
200
+ colorFunc = theme.accent;
201
+ label = 'WORKING';
202
+ break;
203
+ case 'recovery':
204
+ colorFunc = theme.error;
205
+ label = 'RECOVERY';
206
+ break;
207
+ case 'quality':
208
+ colorFunc = theme.quality;
209
+ label = 'QUALITY';
210
+ break;
211
+ case 'tts':
212
+ colorFunc = theme.info;
213
+ label = 'TTS';
214
+ break;
215
+ case 'reasoning':
216
+ colorFunc = theme.reasoning;
217
+ label = 'REASONING';
218
+ break;
219
+ case 'history':
220
+ colorFunc = theme.warning;
221
+ label = 'HISTORY';
222
+ break;
223
+ case 'packages':
224
+ colorFunc = theme.tools;
225
+ label = 'PACKAGES';
226
+ break;
227
+ case 'progress':
228
+ colorFunc = theme.progress;
229
+ label = 'PROGRESS';
230
+ break;
231
+ case 'workCompleted':
232
+ colorFunc = theme.success;
233
+ label = 'WORK COMPLETED';
234
+ break;
235
+ case 'custom':
236
+ colorFunc = theme.accent;
237
+ label = 'CUSTOM';
238
+ break;
239
+ default:
240
+ colorFunc = theme.info;
241
+ label = type.toUpperCase();
242
+ }
243
+
244
+ const styledLabel = colorFunc(`[${label}]`);
245
+ const formattedContent = options.indent ? ` ${content}` : content;
246
+
247
+ if (options.box) {
248
+ return boxen(
249
+ `${icon} ${formattedContent}`,
250
+ {
251
+ padding: 1,
252
+ borderStyle: 'round',
253
+ borderColor: this.getBoxColor(type)
254
+ }
255
+ );
256
+ }
257
+
258
+ return `${timestamp}${icon} ${styledLabel} ${formattedContent}`;
259
+ }
260
+
261
+ // Get appropriate box color for message type
262
+ getBoxColor(type) {
263
+ const colorMap = {
264
+ error: 'red',
265
+ warning: 'yellow',
266
+ success: 'green',
267
+ info: 'blue',
268
+ system: 'gray',
269
+ tools: 'cyan',
270
+ todo: 'magenta',
271
+ workspace: 'blue',
272
+ quality: 'yellow'
273
+ };
274
+ return colorMap[type] || 'cyan';
275
+ }
276
+
277
+ // Enhanced progress display with spinners
278
+ createSpinner(text, type = 'dots') {
279
+ const theme = this.getTheme();
280
+ const spinner = ora({
281
+ text: theme.progress(text),
282
+ spinner: type,
283
+ color: 'cyan'
284
+ });
285
+ return spinner;
286
+ }
287
+
288
+ // Start a named spinner
289
+ startSpinner(name, text, type = 'dots') {
290
+ const spinner = this.createSpinner(text, type);
291
+ this.spinners.set(name, spinner);
292
+ spinner.start();
293
+ return spinner;
294
+ }
295
+
296
+ // Update spinner text
297
+ updateSpinner(name, text) {
298
+ const spinner = this.spinners.get(name);
299
+ if (spinner) {
300
+ const theme = this.getTheme();
301
+ spinner.text = theme.progress(text);
302
+ }
303
+ }
304
+
305
+ // Stop spinner with success
306
+ succeedSpinner(name, text) {
307
+ const spinner = this.spinners.get(name);
308
+ if (spinner) {
309
+ const theme = this.getTheme();
310
+ spinner.succeed(theme.success(text));
311
+ this.spinners.delete(name);
312
+ }
313
+ }
314
+
315
+ // Stop spinner with failure
316
+ failSpinner(name, text) {
317
+ const spinner = this.spinners.get(name);
318
+ if (spinner) {
319
+ const theme = this.getTheme();
320
+ spinner.fail(theme.error(text));
321
+ this.spinners.delete(name);
322
+ }
323
+ }
324
+
325
+ // Enhanced todo list display
326
+ formatTodoList(todoData) {
327
+ const theme = this.getTheme();
328
+ const { task, items } = todoData;
329
+
330
+ let output = boxen(
331
+ theme.todo(`${this.icons.todo} Task: ${task}`),
332
+ {
333
+ padding: 1,
334
+ borderStyle: 'round',
335
+ borderColor: 'magenta'
336
+ }
337
+ );
338
+
339
+ output += '\n\n';
340
+
341
+ items.forEach((item, index) => {
342
+ const number = chalk.gray(`${index + 1}.`);
343
+ let statusIcon;
344
+ let statusColor;
345
+
346
+ switch (item.status) {
347
+ case 'completed':
348
+ statusIcon = this.icons.check;
349
+ statusColor = theme.success;
350
+ break;
351
+ case 'in_progress':
352
+ statusIcon = this.icons.arrow;
353
+ statusColor = theme.warning;
354
+ break;
355
+ default:
356
+ statusIcon = this.icons.radioOff;
357
+ statusColor = theme.system;
358
+ }
359
+
360
+ const statusText = statusColor(`${statusIcon} ${item.step}`);
361
+ const result = item.result ? chalk.gray(` - ${item.result}`) : '';
362
+
363
+ output += `${number} ${statusText}${result}\n`;
364
+ });
365
+
366
+ return output;
367
+ }
368
+
369
+ // Enhanced error display
370
+ formatError(error, context = {}) {
371
+ const theme = this.getTheme();
372
+ const errorBox = boxen(
373
+ `${this.icons.error} ${theme.error('ERROR')}\n\n` +
374
+ `${chalk.red(error.message)}\n` +
375
+ (error.code ? `\nCode: ${chalk.yellow(error.code)}` : '') +
376
+ (context.suggestions ? `\n\n${this.icons.info} Suggestions:\n${context.suggestions.map(s => ` â€ĸ ${s}`).join('\n')}` : ''),
377
+ {
378
+ padding: 1,
379
+ borderStyle: 'double',
380
+ borderColor: 'red',
381
+ backgroundColor: 'black'
382
+ }
383
+ );
384
+
385
+ return errorBox;
386
+ }
387
+
388
+ // Progress bar for long operations
389
+ createProgressBar(total, current = 0) {
390
+ const theme = this.getTheme();
391
+ const percentage = Math.round((current / total) * 100);
392
+ const barLength = 30;
393
+ const filledLength = Math.round((barLength * current) / total);
394
+
395
+ const filled = '█'.repeat(filledLength);
396
+ const empty = '░'.repeat(barLength - filledLength);
397
+
398
+ return theme.progress(`[${filled}${empty}] ${percentage}% (${current}/${total})`);
399
+ }
400
+
401
+ // Gradient text helper
402
+ applyGradient(text, colors) {
403
+ return gradient(colors)(text);
404
+ }
405
+
406
+ // Log with enhanced styling
407
+ log(type, content, options = {}) {
408
+ if (this.listener && typeof this.listener.log === 'function') {
409
+ this.listener.log(type, content, options);
410
+ return;
411
+ }
412
+ const formatted = this.formatMessage(type, content, options);
413
+ console.log(formatted);
414
+ }
415
+
416
+ // Clear all active spinners
417
+ clearAllSpinners() {
418
+ this.spinners.forEach(spinner => spinner.stop());
419
+ this.spinners.clear();
420
+ }
421
+
422
+ // Available themes list
423
+ getAvailableThemes() {
424
+ return Object.keys(this.themes);
425
+ }
426
+
427
+ // Format markdown text for beautiful terminal display
428
+ formatMarkdown(content) {
429
+ const theme = this.getTheme();
430
+
431
+ // Convert markdown to formatted terminal output
432
+ let formatted = content
433
+ // Headers
434
+ .replace(/^### (.*$)/gm, (match, text) => theme.accent(`\n${this.icons.star} ${text}`))
435
+ .replace(/^## (.*$)/gm, (match, text) => theme.primary(`\n${this.icons.star} ${text.toUpperCase()}`))
436
+ .replace(/^# (.*$)/gm, (match, text) => theme.success(`\n${this.icons.star} ${text.toUpperCase()}`))
437
+
438
+ // Bold text
439
+ .replace(/\*\*(.*?)\*\*/g, (match, text) => chalk.bold(theme.accent(text)))
440
+
441
+ // Italic text
442
+ .replace(/\*(.*?)\*/g, (match, text) => chalk.italic(theme.secondary(text)))
443
+
444
+ // Inline code
445
+ .replace(/`(.*?)`/g, (match, code) => chalk.bgGray.black(` ${code} `))
446
+
447
+ // Lists
448
+ .replace(/^[\s]*[-*+] (.*$)/gm, (match, text) => ` ${this.icons.bullet} ${text}`)
449
+ .replace(/^[\s]*\d+\. (.*$)/gm, (match, text) => ` ${this.icons.arrow} ${text}`)
450
+
451
+ // Links (show URL)
452
+ .replace(/\[([^\]]+)\]\(([^)]+)\)/g, (match, text, url) =>
453
+ `${theme.info(text)} ${chalk.gray(`(${url})`)}`
454
+ )
455
+
456
+ // Checkboxes
457
+ .replace(/- \[x\] (.*$)/gm, (match, text) => ` ${this.icons.check} ${theme.success(text)}`)
458
+ .replace(/- \[ \] (.*$)/gm, (match, text) => ` ${this.icons.radioOff} ${text}`)
459
+
460
+ // Code blocks (simple highlighting)
461
+ .replace(/```[\s\S]*?```/g, (match) => {
462
+ const lines = match.split('\n');
463
+ const lang = lines[0].replace('```', '').trim();
464
+ const code = lines.slice(1, -1).join('\n');
465
+
466
+ return boxen(
467
+ chalk.gray(code),
468
+ {
469
+ padding: 1,
470
+ borderStyle: 'round',
471
+ borderColor: 'gray',
472
+ title: lang ? `${lang}` : 'Code',
473
+ titleAlignment: 'left'
474
+ }
475
+ );
476
+ })
477
+
478
+ // Horizontal rules
479
+ .replace(/^---+$/gm, theme.system('─'.repeat(60)))
480
+
481
+ // Clean up multiple newlines
482
+ .replace(/\n{3,}/g, '\n\n');
483
+
484
+ return formatted;
485
+ }
486
+
487
+ // Enhanced final response display
488
+ displayFinalResponse(content) {
489
+ const theme = this.getTheme();
490
+
491
+ console.log('\n' + theme.accent('═'.repeat(80)));
492
+ console.log(theme.success(`${this.icons.ai} AI ASSISTANT RESPONSE`));
493
+ console.log(theme.accent('═'.repeat(80)) + '\n');
494
+
495
+ const formattedContent = this.formatMarkdown(content);
496
+ console.log(formattedContent);
497
+
498
+ console.log('\n' + theme.accent('═'.repeat(80)));
499
+ }
500
+ }
501
+
502
+ // Create singleton instance
503
+ export const consoleStyler = new ConsoleStyler();