kailogger 1.0.0-dark.red → 1.0.2

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 (102) hide show
  1. package/README.md +195 -53
  2. package/dist/core/Config.d.ts +15 -0
  3. package/dist/core/Config.js +44 -0
  4. package/dist/core/Logger.d.ts +75 -4
  5. package/dist/core/Logger.js +375 -47
  6. package/dist/core/Scope.d.ts +13 -0
  7. package/dist/core/Scope.js +36 -0
  8. package/dist/features/Chart.d.ts +15 -0
  9. package/dist/features/Chart.js +64 -0
  10. package/dist/features/Diff.d.ts +3 -0
  11. package/dist/features/Diff.js +30 -0
  12. package/dist/features/Encrypt.d.ts +10 -0
  13. package/dist/features/Encrypt.js +47 -0
  14. package/dist/features/Notify.d.ts +14 -0
  15. package/dist/features/Notify.js +70 -0
  16. package/dist/features/Screenshot.d.ts +10 -0
  17. package/dist/features/Screenshot.js +106 -0
  18. package/dist/features/Sound.d.ts +12 -0
  19. package/dist/features/Sound.js +116 -0
  20. package/dist/features/Timer.d.ts +6 -0
  21. package/dist/features/Timer.js +38 -0
  22. package/dist/features/Tree.d.ts +7 -0
  23. package/dist/features/Tree.js +25 -0
  24. package/dist/features/index.d.ts +8 -0
  25. package/dist/features/index.js +24 -0
  26. package/dist/icon/logo.png +0 -0
  27. package/dist/index.d.ts +13 -1
  28. package/dist/index.js +21 -1
  29. package/dist/sounds/error.wav +0 -0
  30. package/dist/sounds/notification.wav +0 -0
  31. package/dist/sounds/success.wav +0 -0
  32. package/dist/sounds/warning.wav +0 -0
  33. package/dist/styles/KaiChroma.d.ts +85 -0
  34. package/dist/styles/KaiChroma.js +407 -0
  35. package/dist/styles/gradients.d.ts +28 -0
  36. package/dist/styles/palettes.d.ts +21 -26
  37. package/dist/styles/palettes.js +167 -13
  38. package/dist/transports/ConsoleTransport.d.ts +9 -0
  39. package/dist/transports/ConsoleTransport.js +18 -0
  40. package/dist/transports/FileTransport.d.ts +16 -0
  41. package/dist/transports/FileTransport.js +84 -0
  42. package/dist/transports/WebhookTransport.d.ts +15 -0
  43. package/dist/transports/WebhookTransport.js +31 -0
  44. package/dist/transports/index.d.ts +3 -0
  45. package/dist/transports/index.js +19 -0
  46. package/dist/types/index.d.ts +16 -0
  47. package/dist/types/index.js +11 -0
  48. package/dist/utils/json.d.ts +3 -0
  49. package/dist/utils/json.js +33 -0
  50. package/dist/utils/prettyError.d.ts +3 -0
  51. package/dist/utils/prettyError.js +94 -0
  52. package/dist/utils/progress.d.ts +11 -0
  53. package/dist/utils/progress.js +43 -0
  54. package/dist/utils/prompt.d.ts +4 -0
  55. package/dist/utils/prompt.js +59 -0
  56. package/dist/utils/selection.d.ts +4 -0
  57. package/dist/utils/selection.js +156 -0
  58. package/dist/utils/spinner.d.ts +1 -1
  59. package/dist/utils/spinner.js +9 -13
  60. package/dist/utils/stripAnsi.d.ts +1 -0
  61. package/dist/utils/stripAnsi.js +7 -0
  62. package/dist/utils/table.d.ts +3 -0
  63. package/dist/utils/table.js +35 -0
  64. package/examples/demo.js +134 -0
  65. package/examples/demo.ts +88 -25
  66. package/package.json +20 -6
  67. package/scripts/copy-assets.js +37 -0
  68. package/src/core/Config.ts +44 -0
  69. package/src/core/Logger.ts +427 -51
  70. package/src/core/Scope.ts +35 -0
  71. package/src/features/Chart.ts +81 -0
  72. package/src/features/Diff.ts +25 -0
  73. package/src/features/Encrypt.ts +47 -0
  74. package/src/features/Notify.ts +39 -0
  75. package/src/features/Screenshot.ts +70 -0
  76. package/src/features/Sound.ts +92 -0
  77. package/src/features/Timer.ts +35 -0
  78. package/src/features/Tree.ts +25 -0
  79. package/src/features/index.ts +8 -0
  80. package/src/icon/logo.png +0 -0
  81. package/src/index.ts +13 -1
  82. package/src/sounds/error.wav +0 -0
  83. package/src/sounds/notification.wav +0 -0
  84. package/src/sounds/success.wav +0 -0
  85. package/src/sounds/warning.wav +0 -0
  86. package/src/styles/KaiChroma.ts +370 -0
  87. package/src/styles/palettes.ts +197 -14
  88. package/src/transports/ConsoleTransport.ts +19 -0
  89. package/src/transports/FileTransport.ts +55 -0
  90. package/src/transports/WebhookTransport.ts +37 -0
  91. package/src/transports/index.ts +3 -0
  92. package/src/types/cli-highlight.d.ts +3 -0
  93. package/src/types/index.ts +23 -0
  94. package/src/utils/json.ts +33 -0
  95. package/src/utils/prettyError.ts +65 -0
  96. package/src/utils/progress.ts +56 -0
  97. package/src/utils/prompt.ts +27 -0
  98. package/src/utils/selection.ts +136 -0
  99. package/src/utils/spinner.ts +11 -7
  100. package/src/utils/stripAnsi.ts +6 -0
  101. package/src/utils/table.ts +38 -0
  102. package/src/styles/gradients.ts +0 -22
@@ -1,80 +1,456 @@
1
- import chalk from 'chalk';
2
- import { paint, GradientEngine } from '../styles/gradients';
1
+
2
+ import * as fs from 'fs';
3
+ import { paint, palettes, ThemeName } from '../styles/palettes';
4
+ import { KaiChroma } from '../styles/KaiChroma';
3
5
  import { KaiSpinner } from '../utils/spinner';
4
- import { ThemeName } from '../styles/palettes';
6
+ import { KaiProgress } from '../utils/progress';
7
+ import { KaiTable } from '../utils/table';
8
+ import { KaiPrompt } from '../utils/prompt';
9
+ import { KaiSelection } from '../utils/selection';
10
+ import { KaiJson } from '../utils/json';
11
+ import { PrettyError } from '../utils/prettyError';
12
+ import { stripAnsi } from '../utils/stripAnsi';
13
+ import { config } from './Config';
14
+ import { ScopedLogger } from './Scope';
15
+ import { KaiTimer } from '../features/Timer';
16
+ import { KaiTree } from '../features/Tree';
17
+ import { KaiDiff } from '../features/Diff';
18
+ import { KaiChart } from '../features/Chart';
19
+ import { KaiNotify } from '../features/Notify';
20
+ import { KaiScreenshot } from '../features/Screenshot';
21
+ import { KaiEncrypt } from '../features/Encrypt';
22
+ import { KaiSound } from '../features/Sound';
23
+ import { LogLevel, LOG_LEVEL_PRIORITY, Transport, KaiConfig } from '../types';
24
+ import { FileTransport, FileTransportOptions } from '../transports/FileTransport';
25
+ import { WebhookTransport, WebhookTransportOptions } from '../transports/WebhookTransport';
26
+
27
+ interface LoggerOptions {
28
+ theme?: ThemeName;
29
+ level?: LogLevel;
30
+ logFile?: string;
31
+ silent?: boolean;
32
+ timestamp?: 'ISO' | 'locale' | 'relative' | 'none';
33
+ }
5
34
 
6
- type LogLevel = 'success' | 'error' | 'warning' | 'info' | 'debug';
7
35
  export class KaiLogger {
8
36
  private spinner = new KaiSpinner();
9
- constructor(public theme: ThemeName = 'zen') {
10
- paint.setTheme(theme);
37
+ private timer = new KaiTimer();
38
+ private logFilePath?: string;
39
+ private buffer: string[] = [];
40
+ private startTime: number = Date.now();
41
+
42
+ public theme: ThemeName;
43
+ public level: LogLevel;
44
+ public silent: boolean;
45
+ public timestampFormat: 'ISO' | 'locale' | 'relative' | 'none';
46
+
47
+ constructor(options: LoggerOptions | ThemeName = 'zen') {
48
+ if (typeof options === 'string') {
49
+ this.theme = options;
50
+ this.level = 'debug';
51
+ this.silent = false;
52
+ this.timestampFormat = 'locale';
53
+ } else {
54
+ this.theme = options.theme || 'zen';
55
+ this.level = options.level || 'debug';
56
+ this.logFilePath = options.logFile;
57
+ this.silent = options.silent || false;
58
+ this.timestampFormat = options.timestamp || 'locale';
59
+ }
60
+ paint.setTheme(this.theme);
61
+ }
62
+
63
+ // ========== CONFIGURATION ==========
64
+
65
+ public configure(options: Partial<LoggerOptions>): void {
66
+ if (options.theme) this.setTheme(options.theme);
67
+ if (options.level) this.level = options.level;
68
+ if (options.logFile) this.logFilePath = options.logFile;
69
+ if (options.silent !== undefined) this.silent = options.silent;
70
+ if (options.timestamp) this.timestampFormat = options.timestamp;
11
71
  }
12
- public setTheme(theme: ThemeName) {
72
+
73
+ public setTheme(theme: ThemeName): void {
13
74
  this.theme = theme;
14
75
  paint.setTheme(theme);
15
76
  }
16
- private getTime() {
17
- const date = new Date();
18
- return chalk.gray(`[${date.toLocaleTimeString()}]`);
77
+
78
+ public setLevel(level: LogLevel): void {
79
+ this.level = level;
80
+ }
81
+
82
+ public setSilent(silent: boolean): void {
83
+ this.silent = silent;
84
+ }
85
+
86
+ // ========== TRANSPORTS ==========
87
+
88
+ public addFileTransport(options: FileTransportOptions): void {
89
+ config.addTransport(new FileTransport(options));
90
+ }
91
+
92
+ public addWebhookTransport(options: WebhookTransportOptions): void {
93
+ config.addTransport(new WebhookTransport(options));
94
+ }
95
+
96
+ public removeTransport(name: string): void {
97
+ config.removeTransport(name);
98
+ }
99
+
100
+ // ========== SCOPED LOGGERS ==========
101
+
102
+ public scope(name: string): ScopedLogger {
103
+ return new ScopedLogger(this, name);
104
+ }
105
+
106
+ // ========== TIMESTAMPS ==========
107
+
108
+ private getTime(): string {
109
+ if (this.timestampFormat === 'none') return '';
110
+
111
+ const now = new Date();
112
+
113
+ switch (this.timestampFormat) {
114
+ case 'ISO':
115
+ return `[${now.toISOString()}]`;
116
+ case 'relative':
117
+ const elapsed = Date.now() - this.startTime;
118
+ const seconds = (elapsed / 1000).toFixed(1);
119
+ return `[+${seconds}s]`;
120
+ case 'locale':
121
+ default:
122
+ return `[${now.toLocaleTimeString()}]`;
123
+ }
124
+ }
125
+
126
+ // ========== LEVEL CHECKING ==========
127
+
128
+ private shouldLog(level: LogLevel): boolean {
129
+ if (this.silent) return false;
130
+ return LOG_LEVEL_PRIORITY[level] >= LOG_LEVEL_PRIORITY[this.level];
131
+ }
132
+
133
+ // ========== FILE & TRANSPORT LOGGING ==========
134
+
135
+ private logToFile(level: string, message: string): void {
136
+ if (this.logFilePath) {
137
+ const cleanMsg = stripAnsi(message);
138
+ const logLine = `${this.getTime()} [${level.toUpperCase()}] ${cleanMsg}\n`;
139
+ fs.appendFileSync(this.logFilePath, logLine);
140
+ }
141
+
142
+ const transports = config.get('transports') || [];
143
+ transports.forEach(t => t.log(level as LogLevel, message));
144
+ }
145
+
146
+ private addToBuffer(level: string, message: string): void {
147
+ this.buffer.push(`${this.getTime()} [${level.toUpperCase()}] ${message}`);
148
+ }
149
+
150
+ public getBuffer(): string[] {
151
+ return [...this.buffer];
152
+ }
153
+
154
+ public clearBuffer(): void {
155
+ this.buffer = [];
156
+ }
157
+
158
+ // ========== CORE LOGGING ==========
159
+
160
+ private print(level: LogLevel, label: string, message: string, ...args: any[]): void {
161
+ if (!this.shouldLog(level)) {
162
+ this.addToBuffer(level, message);
163
+ return;
164
+ }
165
+
166
+ this.stopSpinner();
167
+ // Accedemos safe a paint.colors[level]
168
+ const colors = (paint.colors as any)[level] as string[];
169
+ const time = this.timestampFormat !== 'none' ? KaiChroma.hex('#666666', this.getTime()) + ' ' : '';
170
+ const badge = paint.apply(` ${label.padEnd(7)} `, colors);
171
+ const output = `${time}${badge} ${message}`;
172
+
173
+ console.log(output, ...args);
174
+
175
+ const fileMsg = [message, ...args.map(a => typeof a === 'object' ? JSON.stringify(a) : a)].join(' ');
176
+ this.logToFile(level, fileMsg);
177
+ this.addToBuffer(level, fileMsg);
178
+ }
179
+
180
+ public success(message: string, ...args: any[]): void {
181
+ this.print('success', '✔ SUCCESS', message, ...args);
182
+ }
183
+
184
+ public error(message: string | Error, ...args: any[]): void {
185
+ if (!this.shouldLog('error')) return;
186
+
187
+ if (message instanceof Error) {
188
+ this.stopSpinner();
189
+ PrettyError.handle(message, paint.colors);
190
+ this.logToFile('error', message.toString());
191
+ this.addToBuffer('error', message.toString());
192
+ } else {
193
+ this.print('error', '✖ ERROR', message, ...args);
194
+ }
195
+ }
196
+
197
+ public warning(message: string, ...args: any[]): void {
198
+ this.print('warning', '⚠ WARN', message, ...args);
199
+ }
200
+
201
+ public info(message: string, ...args: any[]): void {
202
+ this.print('info', 'ℹ INFO', message, ...args);
203
+ }
204
+
205
+ public debug(message: string, ...args: any[]): void {
206
+ this.print('debug', '⚙ DEBUG', message, ...args);
207
+ }
208
+
209
+ public log(message: string): void {
210
+ if (this.silent) return;
211
+ this.stopSpinner();
212
+ const time = this.timestampFormat !== 'none' ? KaiChroma.hex('#666666', this.getTime()) + ' ' : '';
213
+ console.log(`${time}${message}`);
214
+ this.logToFile('log', message);
215
+ }
216
+
217
+ // ========== CUSTOM BADGE ==========
218
+
219
+ public custom(badge: string, color: string, message: string, ...args: any[]): void {
220
+ if (this.silent) return;
221
+ this.stopSpinner();
222
+ const time = this.timestampFormat !== 'none' ? KaiChroma.hex('#666666', this.getTime()) + ' ' : '';
223
+ const styledBadge = KaiChroma.bgHex(color, ` ${badge.toUpperCase().padEnd(7)} `);
224
+ console.log(`${time}${styledBadge} ${message}`, ...args);
225
+ this.logToFile(badge, message);
226
+ }
227
+
228
+ // ========== BOX ==========
229
+
230
+ public box(title: string, message: string): void {
231
+ if (this.silent) return;
232
+ this.stopSpinner();
233
+ const width = Math.max(message.length, title.length) + 4;
234
+ const border = paint.apply('─'.repeat(width), paint.colors.info);
235
+ const topLeft = paint.apply('╭', paint.colors.info);
236
+ const topRight = paint.apply('╮', paint.colors.info);
237
+ const bottomLeft = paint.apply('╰', paint.colors.info);
238
+ const bottomRight = paint.apply('╯', paint.colors.info);
239
+ const side = paint.apply('│', paint.colors.info);
240
+
241
+ console.log(`${topLeft}${border}${topRight}`);
242
+ console.log(`${side} ${paint.apply(title.padEnd(width - 2), paint.colors.success)} ${side}`);
243
+ console.log(`${side}${paint.apply('─'.repeat(width), paint.colors.debug)}${side}`);
244
+ console.log(`${side} ${message.padEnd(width - 2)} ${side}`);
245
+ console.log(`${bottomLeft}${border}${bottomRight}`);
246
+
247
+ this.logToFile('box', `${title}: ${message}`);
248
+ }
249
+
250
+ // ========== SPINNER ==========
251
+
252
+ public await(message: string): void {
253
+ if (this.silent) return;
254
+ this.spinner.start(message, paint.colors.info[0]);
255
+ }
256
+
257
+ public stop(message?: string): void {
258
+ this.spinner.stop('✔', message, paint.colors.success[1]);
259
+ if (message) this.logToFile('stop', message);
260
+ }
261
+
262
+ private stopSpinner(): void {
263
+ this.spinner.stop();
264
+ }
265
+
266
+ // ========== PROGRESS ==========
267
+
268
+ public createProgress(total: number, width?: number): KaiProgress {
269
+ return new KaiProgress(total, width, this.theme);
270
+ }
271
+
272
+ // ========== TABLE ==========
273
+
274
+ public table(data: any[]): void {
275
+ if (this.silent) return;
276
+ this.stopSpinner();
277
+ KaiTable.print(data, paint.colors);
278
+ this.logToFile('table', JSON.stringify(data));
279
+ }
280
+
281
+ // ========== JSON ==========
282
+
283
+ public json(data: any): void {
284
+ if (this.silent) return;
285
+ this.stopSpinner();
286
+ KaiJson.print(data, paint.colors);
287
+ this.logToFile('json', JSON.stringify(data));
288
+ }
289
+
290
+ // ========== TREE ==========
291
+
292
+ public tree(data: any): void {
293
+ if (this.silent) return;
294
+ this.stopSpinner();
295
+ KaiTree.print(data, paint.colors);
296
+ this.logToFile('tree', JSON.stringify(data));
297
+ }
298
+
299
+ // ========== DIFF ==========
300
+
301
+ public diff(before: any, after: any): void {
302
+ if (this.silent) return;
303
+ this.stopSpinner();
304
+ KaiDiff.print(before, after, paint.colors);
305
+ this.logToFile('diff', JSON.stringify({ before, after }));
306
+ }
307
+
308
+ // ========== TIMER ==========
309
+
310
+ public time(label: string): void {
311
+ this.timer.time(label);
312
+ }
313
+
314
+ public timeEnd(label: string): number | null {
315
+ if (this.silent) return null;
316
+ return this.timer.timeEnd(label, paint.colors);
317
+ }
318
+
319
+ // ========== PROMPTS ==========
320
+
321
+ public async ask(question: string): Promise<string> {
322
+ this.stopSpinner();
323
+ // Pasamos theme.info colors, asumimos que prompt maneja esto
324
+ const answer = await KaiPrompt.ask(question, paint.colors);
325
+ this.logToFile('ask', `${question} -> ${answer}`);
326
+ return answer;
327
+ }
328
+
329
+ public async confirm(question: string): Promise<boolean> {
330
+ this.stopSpinner();
331
+ const answer = await KaiPrompt.confirm(question, paint.colors);
332
+ this.logToFile('confirm', `${question} -> ${answer}`);
333
+ return answer;
19
334
  }
20
- private format(level: LogLevel, message: string, ...args: any[]) {
21
- const colors = paint.text[level];
22
- const badge = paint.apply(` ${level.toUpperCase()} `, colors);
23
- const formattedMsg = paint.apply(message, colors);
24
- console.log(`${this.getTime()} ${chalk.bold(badge)} ${message}`, ...args);
335
+
336
+ public async select(question: string, options: string[]): Promise<string> {
337
+ this.stopSpinner();
338
+ const answer = await KaiSelection.select(question, options, paint.colors);
339
+ this.logToFile('select', `${question} -> ${answer}`);
340
+ return answer;
25
341
  }
26
- public success(message: string, ...args: any[]) {
342
+
343
+ public async multiselect(question: string, options: string[]): Promise<string[]> {
27
344
  this.stopSpinner();
28
- const colors = paint.text.success;
29
- console.log(`${this.getTime()} ${paint.apply(' ✔ SUCCESS ', colors)} ${message}`, ...args);
345
+ const answer = await KaiSelection.multiselect(question, options, paint.colors);
346
+ this.logToFile('multiselect', `${question} -> ${answer.join(', ')}`);
347
+ return answer;
30
348
  }
31
- public error(message: string, ...args: any[]) {
349
+
350
+ // ========== CHARTS ==========
351
+
352
+ public chart(data: { label: string; value: number }[], options?: { width?: number; showValues?: boolean }): void {
353
+ if (this.silent) return;
32
354
  this.stopSpinner();
33
- const colors = paint.text.error;
34
- console.log(`${this.getTime()} ${paint.apply(' ✖ ERROR ', colors)} ${message}`, ...args);
355
+ KaiChart.bar(data, paint.colors, options);
35
356
  }
36
- public warning(message: string, ...args: any[]) {
357
+
358
+ public sparkline(values: number[]): void {
359
+ if (this.silent) return;
37
360
  this.stopSpinner();
38
- const colors = paint.text.warning;
39
- console.log(`${this.getTime()} ${paint.apply(' ⚠ WARNING ', colors)} ${message}`, ...args);
361
+ KaiChart.sparkline(values, paint.colors);
40
362
  }
41
- public info(message: string, ...args: any[]) {
363
+
364
+ public gauge(value: number, max: number, label?: string): void {
365
+ if (this.silent) return;
42
366
  this.stopSpinner();
43
- const colors = paint.text.info;
44
- console.log(`${this.getTime()} ${paint.apply(' ℹ INFO ', colors)} ${message}`, ...args);
367
+ KaiChart.gauge(value, max, paint.colors, label);
368
+ }
369
+
370
+ // ========== NOTIFICATIONS ==========
371
+
372
+ public notify(message: string, title?: string): void {
373
+ KaiNotify.send({ message, title });
374
+ }
375
+
376
+ public notifySuccess(message: string): void {
377
+ KaiNotify.success(message);
378
+ }
379
+
380
+ public notifyError(message: string): void {
381
+ KaiNotify.error(message);
382
+ }
383
+
384
+ // ========== SCREENSHOT ==========
385
+
386
+ public startCapture(): void {
387
+ KaiScreenshot.startCapture();
388
+ }
389
+
390
+ public stopCapture(): string[] {
391
+ return KaiScreenshot.stopCapture();
392
+ }
393
+
394
+ public saveScreenshot(filename: string): void {
395
+ KaiScreenshot.save(filename);
396
+ }
397
+
398
+ public saveScreenshotHtml(filename: string): void {
399
+ KaiScreenshot.saveHtml(filename);
45
400
  }
46
- public debug(message: string, ...args: any[]) {
401
+
402
+ // ========== ENCRYPTION ==========
403
+
404
+ public setEncryptionKey(key: string): void {
405
+ KaiEncrypt.setDefaultKey(key);
406
+ }
407
+
408
+ public encrypted(message: string, key?: string): void {
409
+ if (this.silent) return;
47
410
  this.stopSpinner();
48
- const colors = paint.text.debug;
49
- console.log(`${this.getTime()} ${paint.apply(' ⚙ DEBUG ', colors)} ${message}`, ...args);
411
+ KaiEncrypt.printEncrypted(message, key);
50
412
  }
51
- public log(message: string) {
413
+
414
+ public decrypted(encryptedMessage: string, key?: string): void {
415
+ if (this.silent) return;
52
416
  this.stopSpinner();
53
- console.log(`${this.getTime()} ${message}`);
417
+ KaiEncrypt.printDecrypted(encryptedMessage, key);
54
418
  }
55
- public box(title: string, message: string) {
419
+
420
+ public masked(label: string, value: string, showLast?: number): void {
421
+ if (this.silent) return;
56
422
  this.stopSpinner();
57
- const border = paint.apply('─'.repeat(message.length + 4), paint.text.info);
58
- console.log(border);
59
- console.log(`${paint.apply(`| ${title} |`, paint.text.info)}`);
60
- console.log(border);
61
- console.log(`| ${message} |`);
62
- console.log(border);
423
+ KaiEncrypt.printMasked(label, value, showLast);
424
+ }
425
+
426
+ // ========== SOUNDS ==========
427
+
428
+ public setSoundsDir(dir: string): void {
429
+ KaiSound.setSoundsDir(dir);
63
430
  }
64
- public await(message: string) {
65
- this.spinner.start(message, paint.text.info[0]);
431
+
432
+ public beep(): void {
433
+ KaiSound.beep();
66
434
  }
67
- public stop(message?: string) {
68
- this.spinner.stop('✔', message, paint.text.success[1]);
435
+
436
+ public async soundSuccess(): Promise<void> {
437
+ await KaiSound.success();
69
438
  }
70
- private stopSpinner() {
71
- // Just ensure no ghost spinner lines if we log something else
72
- // this.spinner.stop();
73
- // Actually we don't want to stop it on every log if it's running,
74
- // but usually logging interrupts the spinner.
75
- // Ideally usage is: start -> wait -> stop/success/fail.
76
- // But if user does: start -> info -> stop, the info might break the spinner line.
77
- // For now, let's keep it simple.
439
+
440
+ public async soundError(): Promise<void> {
441
+ await KaiSound.error();
442
+ }
443
+
444
+ public async soundWarning(): Promise<void> {
445
+ await KaiSound.warning();
446
+ }
447
+
448
+ public async soundNotification(): Promise<void> {
449
+ await KaiSound.notification();
450
+ }
451
+
452
+ public async playSound(filename: string): Promise<void> {
453
+ await KaiSound.play(filename);
78
454
  }
79
455
  }
80
456
 
@@ -0,0 +1,35 @@
1
+ import { KaiLogger } from './Logger';
2
+
3
+ export class ScopedLogger {
4
+ private parent: KaiLogger;
5
+ private scopeName: string;
6
+ constructor(parent: KaiLogger, scopeName: string) {
7
+ this.parent = parent;
8
+ this.scopeName = scopeName;
9
+ }
10
+ private prefix(message: string): string {
11
+ return `[${this.scopeName}] ${message}`;
12
+ }
13
+ success(message: string, ...args: any[]) {
14
+ this.parent.success(this.prefix(message), ...args);
15
+ }
16
+ error(message: string | Error, ...args: any[]) {
17
+ if (message instanceof Error) {
18
+ this.parent.error(message);
19
+ } else {
20
+ this.parent.error(this.prefix(message), ...args);
21
+ }
22
+ }
23
+ warning(message: string, ...args: any[]) {
24
+ this.parent.warning(this.prefix(message), ...args);
25
+ }
26
+ info(message: string, ...args: any[]) {
27
+ this.parent.info(this.prefix(message), ...args);
28
+ }
29
+ debug(message: string, ...args: any[]) {
30
+ this.parent.debug(this.prefix(message), ...args);
31
+ }
32
+ log(message: string) {
33
+ this.parent.log(this.prefix(message));
34
+ }
35
+ }
@@ -0,0 +1,81 @@
1
+
2
+ import { KaiChroma } from '../styles/KaiChroma';
3
+ import { paint } from '../styles/palettes';
4
+
5
+ interface ChartItem {
6
+ label: string;
7
+ value: number;
8
+ color?: string;
9
+ }
10
+
11
+ export class KaiChart {
12
+ static bar(data: ChartItem[], theme: any, options?: { width?: number; showValues?: boolean }) {
13
+ const width = options?.width || 30;
14
+ const showValues = options?.showValues !== false;
15
+
16
+ const maxValue = Math.max(...data.map(d => d.value));
17
+ const maxLabelLength = Math.max(...data.map(d => d.label.length));
18
+
19
+ console.log('');
20
+ data.forEach((item, index) => {
21
+ const percentage = item.value / maxValue;
22
+ const filledWidth = Math.round(width * percentage);
23
+ const emptyWidth = width - filledWidth;
24
+
25
+ const filled = '█'.repeat(filledWidth);
26
+ const empty = '░'.repeat(emptyWidth);
27
+
28
+ // Cycle through theme colors
29
+ const colorKeys = ['success', 'info', 'warning', 'error', 'debug'];
30
+ const colorKey = colorKeys[index % colorKeys.length];
31
+ // @ts-ignore
32
+ const colors = theme[colorKey];
33
+
34
+ const label = item.label.padEnd(maxLabelLength);
35
+ // Usamos PaletteEngine apply (que usa KaiChroma)
36
+ const bar = paint.apply(filled, colors) + KaiChroma.hex('#666666', empty);
37
+ const value = showValues ? ` ${item.value}` : '';
38
+
39
+ console.log(`${KaiChroma.bold(label)} ${bar}${KaiChroma.dim(value)}`);
40
+ });
41
+ console.log('');
42
+ }
43
+
44
+ static horizontal(data: ChartItem[], theme: any) {
45
+ this.bar(data, theme);
46
+ }
47
+
48
+ static sparkline(values: number[], theme: any) {
49
+ const chars = ['▁', '▂', '▃', '▄', '▅', '▆', '▇', '█'];
50
+ const min = Math.min(...values);
51
+ const max = Math.max(...values);
52
+ const range = max - min || 1;
53
+
54
+ const line = values.map(v => {
55
+ const index = Math.floor(((v - min) / range) * (chars.length - 1));
56
+ return chars[index];
57
+ }).join('');
58
+
59
+ console.log(paint.apply(line, theme.info));
60
+ }
61
+
62
+ static gauge(value: number, max: number, theme: any, label?: string) {
63
+ const percentage = Math.min(1, value / max);
64
+ const width = 20;
65
+ const filledWidth = Math.round(width * percentage);
66
+ const emptyWidth = width - filledWidth;
67
+
68
+ const filled = '█'.repeat(filledWidth);
69
+ const empty = '░'.repeat(emptyWidth);
70
+
71
+ let colors = theme.success;
72
+ if (percentage > 0.7) colors = theme.warning;
73
+ if (percentage > 0.9) colors = theme.error;
74
+
75
+ const bar = paint.apply(filled, colors) + KaiChroma.hex('#666666', empty);
76
+ const percentText = Math.round(percentage * 100) + '%';
77
+ const labelText = label ? `${label}: ` : '';
78
+
79
+ console.log(`${labelText}[${bar}] ${percentText}`);
80
+ }
81
+ }
@@ -0,0 +1,25 @@
1
+
2
+ import { paint } from '../styles/palettes';
3
+
4
+ export class KaiDiff {
5
+ static print(before: any, after: any, theme: any, path: string = ''): void {
6
+ const allKeys = new Set([
7
+ ...Object.keys(before || {}),
8
+ ...Object.keys(after || {})
9
+ ]);
10
+ allKeys.forEach(key => {
11
+ const fullPath = path ? `${path}.${key}` : key;
12
+ const oldVal = before?.[key];
13
+ const newVal = after?.[key];
14
+ if (oldVal === undefined && newVal !== undefined) {
15
+ console.log(paint.apply(`+ ${fullPath}: ${JSON.stringify(newVal)}`, theme.success));
16
+ } else if (oldVal !== undefined && newVal === undefined) {
17
+ console.log(paint.apply(`- ${fullPath}: ${JSON.stringify(oldVal)}`, theme.error));
18
+ } else if (typeof oldVal === 'object' && typeof newVal === 'object' && oldVal !== null && newVal !== null) {
19
+ KaiDiff.print(oldVal, newVal, theme, fullPath);
20
+ } else if (oldVal !== newVal) {
21
+ console.log(paint.apply(`~ ${fullPath}: ${JSON.stringify(oldVal)} → ${JSON.stringify(newVal)}`, theme.warning));
22
+ }
23
+ });
24
+ }
25
+ }