@mks2508/better-logger 3.0.0 → 4.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.
Files changed (63) hide show
  1. package/.claude/settings.local.json +3 -1
  2. package/CHANGELOG.json +98 -1
  3. package/dist/Logger.d.ts +24 -12
  4. package/dist/Logger.d.ts.map +1 -1
  5. package/dist/chunks/{Logger-C126NTrD.js → Logger-D-gmmgR2.js} +207 -18
  6. package/dist/chunks/Logger-D-gmmgR2.js.map +1 -0
  7. package/dist/chunks/Logger-D7cfaz15.js +2 -0
  8. package/dist/chunks/Logger-D7cfaz15.js.map +1 -0
  9. package/dist/chunks/{environment-TI2ByCPT.js → environment-COWvu6Wz.js} +466 -98
  10. package/dist/chunks/environment-COWvu6Wz.js.map +1 -0
  11. package/dist/chunks/environment-C_8J-zQ_.js +4 -0
  12. package/dist/chunks/environment-C_8J-zQ_.js.map +1 -0
  13. package/dist/chunks/{formatting-CuNUqGks.js → formatting-CYjT9yhO.js} +2 -2
  14. package/dist/chunks/{formatting-CuNUqGks.js.map → formatting-CYjT9yhO.js.map} +1 -1
  15. package/dist/chunks/{formatting-Blwy-f0W.js → formatting-Cg5YhB9Y.js} +2 -2
  16. package/dist/chunks/{formatting-Blwy-f0W.js.map → formatting-Cg5YhB9Y.js.map} +1 -1
  17. package/dist/core.cjs +1 -1
  18. package/dist/core.js +2 -2
  19. package/dist/exports.cjs +1 -1
  20. package/dist/exports.js +2 -2
  21. package/dist/index.cjs +1 -1
  22. package/dist/index.cjs.map +1 -1
  23. package/dist/index.d.ts +10 -1
  24. package/dist/index.d.ts.map +1 -1
  25. package/dist/index.js +151 -20
  26. package/dist/index.js.map +1 -1
  27. package/dist/styling.cjs +1 -1
  28. package/dist/styling.js +3 -3
  29. package/dist/terminal/color-converter.d.ts +73 -0
  30. package/dist/terminal/color-converter.d.ts.map +1 -0
  31. package/dist/terminal/formatter.d.ts +20 -0
  32. package/dist/terminal/formatter.d.ts.map +1 -0
  33. package/dist/terminal/terminal-renderer.d.ts +20 -6
  34. package/dist/terminal/terminal-renderer.d.ts.map +1 -1
  35. package/dist/types/core.d.ts +61 -0
  36. package/dist/types/core.d.ts.map +1 -1
  37. package/dist/types/index.d.ts +1 -1
  38. package/dist/types/index.d.ts.map +1 -1
  39. package/dist/utils/environment-detector.d.ts +10 -0
  40. package/dist/utils/environment-detector.d.ts.map +1 -1
  41. package/dist/writers/BufferWriter.d.ts +96 -0
  42. package/dist/writers/BufferWriter.d.ts.map +1 -0
  43. package/dist/writers/index.d.ts +6 -0
  44. package/dist/writers/index.d.ts.map +1 -0
  45. package/package.json +1 -1
  46. package/packages/core/package.json +1 -1
  47. package/packages/styling/package.json +2 -2
  48. package/src/Logger.ts +63 -21
  49. package/src/index.ts +61 -2
  50. package/src/terminal/color-converter.ts +315 -0
  51. package/src/terminal/formatter.ts +236 -0
  52. package/src/terminal/terminal-renderer.ts +74 -79
  53. package/src/types/core.ts +68 -0
  54. package/src/types/index.ts +5 -0
  55. package/src/utils/environment-detector.ts +23 -1
  56. package/src/writers/BufferWriter.ts +157 -0
  57. package/src/writers/index.ts +6 -0
  58. package/dist/chunks/Logger-BCfx_yCK.js +0 -2
  59. package/dist/chunks/Logger-BCfx_yCK.js.map +0 -1
  60. package/dist/chunks/Logger-C126NTrD.js.map +0 -1
  61. package/dist/chunks/environment-Ba5kShbx.js +0 -4
  62. package/dist/chunks/environment-Ba5kShbx.js.map +0 -1
  63. package/dist/chunks/environment-TI2ByCPT.js.map +0 -1
@@ -0,0 +1,236 @@
1
+ /**
2
+ * Terminal Formatter - Alignment, key-value, and advanced terminal formatting
3
+ */
4
+
5
+ import { getTerminalWidth } from '../utils/environment-detector.js';
6
+ import { getANSIForeground, ANSI, type ColorCapability } from './color-converter.js';
7
+ import type { ColumnConfig, ColumnAlign, BadgeStyle, TimestampFormat, LogOptions } from '../types/index.js';
8
+
9
+ export function stripAnsi(str: string): string {
10
+ return str.replace(/\x1b\[[0-9;]*m/g, '');
11
+ }
12
+
13
+ export function getVisibleLength(str: string): number {
14
+ return stripAnsi(str).length;
15
+ }
16
+
17
+ export function padToWidth(str: string, width: number, align: ColumnAlign = 'left'): string {
18
+ const visibleLen = getVisibleLength(str);
19
+ if (visibleLen >= width) return str;
20
+
21
+ const padding = width - visibleLen;
22
+
23
+ switch (align) {
24
+ case 'right':
25
+ return ' '.repeat(padding) + str;
26
+ case 'center':
27
+ const leftPad = Math.floor(padding / 2);
28
+ const rightPad = padding - leftPad;
29
+ return ' '.repeat(leftPad) + str + ' '.repeat(rightPad);
30
+ case 'left':
31
+ default:
32
+ return str + ' '.repeat(padding);
33
+ }
34
+ }
35
+
36
+ export function formatWithRightAlign(
37
+ leftContent: string,
38
+ rightContent: string,
39
+ maxWidth?: number
40
+ ): string {
41
+ const width = maxWidth ?? getTerminalWidth();
42
+ const leftLen = getVisibleLength(leftContent);
43
+ const rightLen = getVisibleLength(rightContent);
44
+
45
+ const gap = width - leftLen - rightLen;
46
+ if (gap <= 1) {
47
+ return `${leftContent} ${rightContent}`;
48
+ }
49
+
50
+ return `${leftContent}${' '.repeat(gap)}${rightContent}`;
51
+ }
52
+
53
+ export function formatColumns(
54
+ columns: ColumnConfig[],
55
+ maxWidth?: number,
56
+ colorCapability: ColorCapability = 'full'
57
+ ): string {
58
+ const width = maxWidth ?? getTerminalWidth();
59
+ const totalColumns = columns.length;
60
+
61
+ const columnsWithWidths = columns.map((col, idx) => {
62
+ if (col.width) return { ...col, calculatedWidth: col.width };
63
+
64
+ const remainingWidth = width - columns
65
+ .filter((c, i) => i !== idx && c.width)
66
+ .reduce((sum, c) => sum + (c.width ?? 0), 0);
67
+
68
+ const autoColumns = columns.filter(c => !c.width).length;
69
+ const calculatedWidth = Math.floor(remainingWidth / autoColumns);
70
+
71
+ return { ...col, calculatedWidth };
72
+ });
73
+
74
+ return columnsWithWidths.map(col => {
75
+ let content = col.content;
76
+
77
+ if (col.color && colorCapability !== 'none') {
78
+ const colorCode = getANSIForeground(col.color, colorCapability);
79
+ content = `${colorCode}${content}${ANSI.reset}`;
80
+ }
81
+
82
+ return padToWidth(content, col.calculatedWidth, col.align);
83
+ }).join('');
84
+ }
85
+
86
+ export function formatKeyValue(
87
+ obj: Record<string, unknown>,
88
+ colorCapability: ColorCapability = 'full',
89
+ options: { separator?: string; keyColor?: string; valueColor?: string } = {}
90
+ ): string {
91
+ const {
92
+ separator = ' ',
93
+ keyColor = '#888888',
94
+ valueColor = '#00ffff'
95
+ } = options;
96
+
97
+ const entries = Object.entries(obj);
98
+ if (entries.length === 0) return '';
99
+
100
+ const keyColorCode = colorCapability !== 'none'
101
+ ? getANSIForeground(keyColor, colorCapability)
102
+ : '';
103
+ const valueColorCode = colorCapability !== 'none'
104
+ ? getANSIForeground(valueColor, colorCapability)
105
+ : '';
106
+ const reset = colorCapability !== 'none' ? ANSI.reset : '';
107
+
108
+ return entries
109
+ .map(([key, value]) => {
110
+ const valueStr = typeof value === 'object'
111
+ ? JSON.stringify(value)
112
+ : String(value);
113
+ return `${keyColorCode}${key}:${reset} ${valueColorCode}${valueStr}${reset}`;
114
+ })
115
+ .join(separator);
116
+ }
117
+
118
+ export function isKeyValueObject(obj: unknown): obj is Record<string, unknown> {
119
+ if (typeof obj !== 'object' || obj === null) return false;
120
+ if (Array.isArray(obj)) return false;
121
+
122
+ const proto = Object.getPrototypeOf(obj);
123
+ if (proto !== Object.prototype && proto !== null) return false;
124
+
125
+ const values = Object.values(obj);
126
+ return values.every(v =>
127
+ typeof v === 'string' ||
128
+ typeof v === 'number' ||
129
+ typeof v === 'boolean' ||
130
+ v === null
131
+ );
132
+ }
133
+
134
+ export function formatBadge(
135
+ badge: string,
136
+ style: BadgeStyle = 'brackets',
137
+ colorCapability: ColorCapability = 'full',
138
+ color?: string
139
+ ): string {
140
+ const colorCode = color && colorCapability !== 'none'
141
+ ? getANSIForeground(color, colorCapability)
142
+ : '';
143
+ const reset = colorCapability !== 'none' ? ANSI.reset : '';
144
+ const bgBlack = colorCapability !== 'none' ? ANSI.bg.black : '';
145
+
146
+ const text = badge.toUpperCase();
147
+
148
+ switch (style) {
149
+ case 'rounded':
150
+ return `${bgBlack}${colorCode}⟨${text}⟩${reset}`;
151
+ case 'plain':
152
+ return `${colorCode}${text}${reset}`;
153
+ case 'unicode':
154
+ return `${bgBlack}${colorCode}╭${text}╮${reset}`;
155
+ case 'pill':
156
+ return `${bgBlack}${colorCode}⦗${text}⦘${reset}`;
157
+ case 'brackets':
158
+ default:
159
+ return `${bgBlack}${colorCode}[${text}]${reset}`;
160
+ }
161
+ }
162
+
163
+ export function formatTimestamp(
164
+ date: Date = new Date(),
165
+ format: TimestampFormat = 'time'
166
+ ): string {
167
+ switch (format) {
168
+ case 'iso':
169
+ return date.toISOString();
170
+ case 'date':
171
+ return date.toISOString().split('T')[0] ?? '';
172
+ case 'time':
173
+ return date.toTimeString().slice(0, 8);
174
+ case 'timeMs':
175
+ return date.toTimeString().slice(0, 8) + '.' +
176
+ String(date.getMilliseconds()).padStart(3, '0');
177
+ case 'relative':
178
+ return formatRelativeTime(date);
179
+ case 'elapsed':
180
+ return formatElapsedTime(date);
181
+ case 'custom':
182
+ default:
183
+ return date.toLocaleTimeString();
184
+ }
185
+ }
186
+
187
+ let startTime: number | null = null;
188
+
189
+ export function formatRelativeTime(date: Date): string {
190
+ if (startTime === null) {
191
+ startTime = Date.now();
192
+ }
193
+
194
+ const diff = date.getTime() - startTime;
195
+
196
+ if (diff < 1000) {
197
+ return `+${diff}ms`;
198
+ } else if (diff < 60000) {
199
+ return `+${(diff / 1000).toFixed(1)}s`;
200
+ } else {
201
+ const mins = Math.floor(diff / 60000);
202
+ const secs = Math.floor((diff % 60000) / 1000);
203
+ return `+${mins}m${secs}s`;
204
+ }
205
+ }
206
+
207
+ export function formatElapsedTime(date: Date): string {
208
+ if (startTime === null) {
209
+ startTime = Date.now();
210
+ }
211
+
212
+ const diff = date.getTime() - startTime;
213
+ const hours = Math.floor(diff / 3600000);
214
+ const mins = Math.floor((diff % 3600000) / 60000);
215
+ const secs = Math.floor((diff % 60000) / 1000);
216
+
217
+ return `${String(hours).padStart(2, '0')}:${String(mins).padStart(2, '0')}:${String(secs).padStart(2, '0')}`;
218
+ }
219
+
220
+ export function resetStartTime(): void {
221
+ startTime = Date.now();
222
+ }
223
+
224
+ export function applyLogOptions(
225
+ message: string,
226
+ options: LogOptions,
227
+ colorCapability: ColorCapability = 'full'
228
+ ): string {
229
+ let result = message;
230
+
231
+ if (options.rightAlign) {
232
+ result = formatWithRightAlign(message, options.rightAlign);
233
+ }
234
+
235
+ return result;
236
+ }
@@ -5,6 +5,7 @@
5
5
 
6
6
  import type { LogLevel } from '../types/index.js';
7
7
  import type { LogStyles } from '../types/index.js';
8
+ import { getANSIForeground, getANSIBackground, ANSI, type ColorCapability } from './color-converter.js';
8
9
 
9
10
  export type ANSIStyle = {
10
11
  text: string;
@@ -17,32 +18,19 @@ export type ANSIStyle = {
17
18
  };
18
19
 
19
20
  export class TerminalRenderer {
20
- private colorCapability: 'full' | 'basic' | 'none';
21
+ private colorCapability: ColorCapability;
21
22
 
22
- constructor(colorCapability: 'full' | 'basic' | 'none' = 'full') {
23
+ constructor(colorCapability: ColorCapability = 'full') {
23
24
  this.colorCapability = colorCapability;
24
25
  }
25
26
 
26
27
  /**
27
- * Get ANSI color codes for basic colors
28
+ * Get ANSI color codes - now supports truecolor/256-color
28
29
  */
29
30
  private getColorCode(color: string, background: boolean = false): string {
30
- const colorCodes: Record<string, { normal: string; bright: string }> = {
31
- 'black': { normal: '30', bright: '90' },
32
- 'red': { normal: '31', bright: '91' },
33
- 'green': { normal: '32', bright: '92' },
34
- 'yellow': { normal: '33', bright: '93' },
35
- 'blue': { normal: '34', bright: '94' },
36
- 'magenta': { normal: '35', bright: '95' },
37
- 'cyan': { normal: '36', bright: '96' },
38
- 'white': { normal: '37', bright: '97' },
39
- 'gray': { normal: '90', bright: '37' },
40
- 'grey': { normal: '90', bright: '37' }
41
- };
42
-
43
- const codes = colorCodes[color.toLowerCase()] || { normal: '37', bright: '97' };
44
- const prefix = background ? '4' : '3';
45
- return `\x1b[${codes.normal}m`;
31
+ return background
32
+ ? getANSIBackground(color, this.colorCapability)
33
+ : getANSIForeground(color, this.colorCapability);
46
34
  }
47
35
 
48
36
  /**
@@ -207,7 +195,7 @@ export class TerminalRenderer {
207
195
  }
208
196
 
209
197
  /**
210
- * Create cyberpunk-style ANSI rendering
198
+ * Create cyberpunk-style ANSI rendering with truecolor support
211
199
  */
212
200
  renderCyberpunk(
213
201
  level: LogLevel,
@@ -216,35 +204,36 @@ export class TerminalRenderer {
216
204
  prefix?: string,
217
205
  location?: string
218
206
  ): ANSIStyle {
219
- const reset = '\x1b[0m';
220
- const levelColors: Record<LogLevel, string> = {
221
- 'debug': '\x1b[95m', // Bright magenta
222
- 'info': '\x1b[94m', // Bright blue
223
- 'warn': '\x1b[93m', // Bright yellow
224
- 'error': '\x1b[91m', // Bright red
225
- 'critical': '\x1b[91m\x1b[1m' // Bright red + bold
207
+ const reset = ANSI.reset;
208
+
209
+ const cyberpunkColors: Record<LogLevel, string> = {
210
+ 'debug': '#ff00ff', // Neon magenta
211
+ 'info': '#00ffff', // Neon cyan
212
+ 'warn': '#ffff00', // Neon yellow
213
+ 'error': '#ff0040', // Neon red-pink
214
+ 'critical': '#ff0000' // Pure red
226
215
  };
227
216
 
228
217
  const parts: string[] = [];
229
218
 
230
219
  if (timestamp) {
231
- parts.push(`\x1b[90m${timestamp}${reset}`);
220
+ parts.push(`${ANSI.dim}${timestamp}${reset}`);
232
221
  }
233
222
 
234
- // Cyberpunk level styling
235
- const levelColor = levelColors[level] || '\x1b[97m';
236
- const bgBlack = '\x1b[40m';
237
- parts.push(`${bgBlack}${levelColor} ${this.getLevelText(level)} ${reset}`);
223
+ const levelColor = this.getColorCode(cyberpunkColors[level] || '#ffffff');
224
+ const bgBlack = ANSI.bg.black;
225
+ const bold = level === 'critical' ? ANSI.bold : '';
226
+ parts.push(`${bgBlack}${levelColor}${bold} ${this.getLevelText(level)} ${reset}`);
238
227
 
239
228
  if (prefix) {
240
- const bgBlack = '\x1b[40m';
241
- parts.push(`${bgBlack}\x1b[96m[${prefix.toUpperCase()}]${reset}`);
229
+ const cyanColor = this.getColorCode('#00ffff');
230
+ parts.push(`${bgBlack}${cyanColor}[${prefix.toUpperCase()}]${reset}`);
242
231
  }
243
232
 
244
233
  parts.push(message);
245
234
 
246
235
  if (location) {
247
- parts.push(`\x1b[90m(${location})${reset}`);
236
+ parts.push(`${ANSI.dim}(${location})${reset}`);
248
237
  }
249
238
 
250
239
  return {
@@ -254,7 +243,7 @@ export class TerminalRenderer {
254
243
  }
255
244
 
256
245
  /**
257
- * Create minimal ANSI rendering
246
+ * Create minimal ANSI rendering with truecolor support
258
247
  */
259
248
  renderMinimal(
260
249
  level: LogLevel,
@@ -262,26 +251,29 @@ export class TerminalRenderer {
262
251
  timestamp?: string,
263
252
  prefix?: string
264
253
  ): ANSIStyle {
265
- const reset = '\x1b[0m';
266
- const levelColors: Record<LogLevel, string> = {
267
- 'debug': '\x1b[95m', // Magenta
268
- 'info': '\x1b[94m', // Blue
269
- 'warn': '\x1b[93m', // Yellow
270
- 'error': '\x1b[91m', // Red
271
- 'critical': '\x1b[91m\x1b[1m' // Red + bold
254
+ const reset = ANSI.reset;
255
+
256
+ const minimalColors: Record<LogLevel, string> = {
257
+ 'debug': '#c678dd', // Soft purple
258
+ 'info': '#61afef', // Soft blue
259
+ 'warn': '#e5c07b', // Soft yellow
260
+ 'error': '#e06c75', // Soft red
261
+ 'critical': '#be5046' // Dark red
272
262
  };
273
263
 
274
264
  const parts: string[] = [];
275
265
 
276
266
  if (timestamp) {
277
- parts.push(`\x1b[90m${timestamp}${reset}`);
267
+ parts.push(`${ANSI.dim}${timestamp}${reset}`);
278
268
  }
279
269
 
280
- const levelColor = levelColors[level] || '\x1b[97m';
281
- parts.push(`${levelColor}${this.getLevelText(level)}:${reset}`);
270
+ const levelColor = this.getColorCode(minimalColors[level] || '#abb2bf');
271
+ const bold = level === 'critical' ? ANSI.bold : '';
272
+ parts.push(`${levelColor}${bold}${this.getLevelText(level)}:${reset}`);
282
273
 
283
274
  if (prefix) {
284
- parts.push(`\x1b[96m[${prefix.toUpperCase()}]${reset}`);
275
+ const cyanColor = this.getColorCode('#56b6c2');
276
+ parts.push(`${cyanColor}[${prefix.toUpperCase()}]${reset}`);
285
277
  }
286
278
 
287
279
  parts.push(message);
@@ -293,55 +285,58 @@ export class TerminalRenderer {
293
285
  }
294
286
 
295
287
  /**
296
- * Get chalk instance for log level (for compatibility with adapter)
288
+ * Get chalk-like interface for log level with truecolor support
297
289
  */
298
- public getChalkForLevel(level: LogLevel): any {
290
+ public getChalkForLevel(level: LogLevel): ChalkLikeInterface {
299
291
  const levelColors: Record<LogLevel, string> = {
300
- 'debug': 'magenta',
301
- 'info': 'blue',
302
- 'warn': 'yellow',
303
- 'error': 'red',
304
- 'critical': 'red'
292
+ 'debug': '#c678dd',
293
+ 'info': '#61afef',
294
+ 'warn': '#e5c07b',
295
+ 'error': '#e06c75',
296
+ 'critical': '#be5046'
305
297
  };
306
298
 
299
+ const reset = ANSI.reset;
300
+
307
301
  return {
308
302
  color: (text: string) => {
309
- const color = levelColors[level] || 'white';
303
+ const color = levelColors[level] || '#abb2bf';
310
304
  const colorCode = this.getColorCode(color);
311
- return `${colorCode}${text}${'\x1b[0m'}`;
312
- },
313
- bold: (text: string) => {
314
- const boldStyle = this.getStyleCode(['bold']);
315
- return `${boldStyle}${text}${'\x1b[0m'}`;
316
- },
317
- dim: (text: string) => {
318
- const dimStyle = this.getStyleCode(['dim']);
319
- return `${dimStyle}${text}${'\x1b[0m'}`;
320
- },
321
- bgGray: (text: string) => {
322
- return `\x1b[100m${text}${'\x1b[0m'}`;
323
- },
324
- bgBlue: (text: string) => {
325
- return `\x1b[104m${text}${'\x1b[0m'}`;
305
+ return `${colorCode}${text}${reset}`;
326
306
  },
307
+ bold: (text: string) => `${ANSI.bold}${text}${reset}`,
308
+ dim: (text: string) => `${ANSI.dim}${text}${reset}`,
309
+ bgGray: (text: string) => `${ANSI.bg.gray}${text}${reset}`,
310
+ bgBlue: (text: string) => `${this.getColorCode('#1e3a5f', true)}${text}${reset}`,
327
311
  reset: (text: string) => text,
328
312
  cyan: {
329
313
  bold: (text: string) => {
330
- const cyan = this.getColorCode('cyan');
331
- const bold = this.getStyleCode(['bold']);
332
- return `${cyan}${bold}${text}${'\x1b[0m'}`;
314
+ const cyan = this.getColorCode('#00ffff');
315
+ return `${cyan}${ANSI.bold}${text}${reset}`;
333
316
  },
334
317
  dim: (text: string) => {
335
- const cyan = this.getColorCode('cyan');
336
- const dim = this.getStyleCode(['dim']);
337
- return `${cyan}${dim}${text}${'\x1b[0m'}`;
318
+ const cyan = this.getColorCode('#00ffff');
319
+ return `${cyan}${ANSI.dim}${text}${reset}`;
338
320
  },
339
321
  bg: (text: string) => {
340
- const bgBlack = '\x1b[40m';
341
- const cyan = this.getColorCode('cyan');
342
- return `${bgBlack}${cyan}[${text.toUpperCase()}]${'\x1b[0m'}`;
322
+ const cyan = this.getColorCode('#00ffff');
323
+ return `${ANSI.bg.black}${cyan}[${text.toUpperCase()}]${reset}`;
343
324
  }
344
325
  }
345
326
  };
346
327
  }
328
+ }
329
+
330
+ export interface ChalkLikeInterface {
331
+ color: (text: string) => string;
332
+ bold: (text: string) => string;
333
+ dim: (text: string) => string;
334
+ bgGray: (text: string) => string;
335
+ bgBlue: (text: string) => string;
336
+ reset: (text: string) => string;
337
+ cyan: {
338
+ bold: (text: string) => string;
339
+ dim: (text: string) => string;
340
+ bg: (text: string) => string;
341
+ };
347
342
  }
package/src/types/core.ts CHANGED
@@ -100,6 +100,48 @@ export type ExportFormat = 'json' | 'csv' | 'markdown' | 'plain' | 'html';
100
100
  */
101
101
  export type OutputFormat = 'auto' | 'plain' | 'ansi' | 'build' | 'ci';
102
102
 
103
+ /**
104
+ * Output modes for controlling where logs are written
105
+ * @typedef {'console' | 'silent' | 'custom'} OutputMode
106
+ *
107
+ * @description
108
+ * - console: Standard console.log output (default)
109
+ * - silent: No output at all
110
+ * - custom: Use custom OutputWriter for advanced scenarios
111
+ */
112
+ export type OutputMode = 'console' | 'silent' | 'custom';
113
+
114
+ /**
115
+ * Custom output writer interface for redirecting log output
116
+ *
117
+ * @interface OutputWriter
118
+ * @since 4.0.0
119
+ *
120
+ * @description
121
+ * Allows redirecting log output to custom destinations like:
122
+ * - DraftLog for CLI spinners with concurrent logging
123
+ * - Buffers for collecting logs during operations
124
+ * - Custom streams or transports
125
+ *
126
+ * @example
127
+ * class BufferWriter implements OutputWriter {
128
+ * private buffer: string[] = [];
129
+ *
130
+ * write(message: string, level: LogLevel, styles: string[]): void {
131
+ * this.buffer.push(message);
132
+ * }
133
+ *
134
+ * flush(): void {
135
+ * this.buffer.forEach(msg => console.log(msg));
136
+ * this.buffer = [];
137
+ * }
138
+ * }
139
+ */
140
+ export interface OutputWriter {
141
+ write(message: string, level: LogLevel, styles: string[]): void;
142
+ flush?(): void;
143
+ }
144
+
103
145
  /**
104
146
  * Interfaz de configuración para instancias del logger
105
147
  *
@@ -130,6 +172,10 @@ export interface LoggerConfig {
130
172
  bufferSize?: number;
131
173
  autoDetectTheme?: boolean;
132
174
  outputFormat?: OutputFormat;
175
+ /** Output mode: 'console' (default), 'silent', or 'custom' @since 4.0.0 */
176
+ outputMode?: OutputMode;
177
+ /** Custom writer when outputMode is 'custom' @since 4.0.0 */
178
+ outputWriter?: OutputWriter;
133
179
  }
134
180
 
135
181
  /**
@@ -293,4 +339,26 @@ export interface Bindings {
293
339
  badges?: string[];
294
340
  type?: 'scope' | 'api' | 'component';
295
341
  context?: string[];
342
+ }
343
+
344
+ export type BadgeStyle = 'brackets' | 'rounded' | 'plain' | 'unicode' | 'pill';
345
+
346
+ export type TimestampFormat = 'iso' | 'time' | 'timeMs' | 'relative' | 'elapsed' | 'date' | 'custom';
347
+
348
+ export type ColumnAlign = 'left' | 'right' | 'center';
349
+
350
+ export interface ColumnConfig {
351
+ content: string;
352
+ width?: number;
353
+ align?: ColumnAlign;
354
+ color?: string;
355
+ }
356
+
357
+ export interface LogOptions {
358
+ rightAlign?: string;
359
+ columns?: ColumnConfig[];
360
+ maxWidth?: number;
361
+ keyValue?: boolean;
362
+ badgeStyle?: BadgeStyle;
363
+ timestampFormat?: TimestampFormat;
296
364
  }
@@ -25,6 +25,11 @@ export type {
25
25
  IAPILogger,
26
26
  IComponentLogger,
27
27
  Bindings,
28
+ BadgeStyle,
29
+ TimestampFormat,
30
+ ColumnAlign,
31
+ ColumnConfig,
32
+ LogOptions,
28
33
  } from './core.js';
29
34
 
30
35
  export { LOG_LEVELS } from './core.js';
@@ -131,6 +131,26 @@ export function getColorCapability(): 'full' | 'basic' | 'none' {
131
131
  return 'basic'; // Basic 16 colors
132
132
  }
133
133
 
134
+ /**
135
+ * Get terminal width in columns
136
+ */
137
+ export function getTerminalWidth(): number {
138
+ if (typeof process !== 'undefined' && process.stdout?.columns) {
139
+ return process.stdout.columns;
140
+ }
141
+ return 80;
142
+ }
143
+
144
+ /**
145
+ * Get terminal height in rows
146
+ */
147
+ export function getTerminalHeight(): number {
148
+ if (typeof process !== 'undefined' && process.stdout?.rows) {
149
+ return process.stdout.rows;
150
+ }
151
+ return 24;
152
+ }
153
+
134
154
  /**
135
155
  * Environment information for debugging
136
156
  */
@@ -143,6 +163,8 @@ export function getEnvironmentInfo() {
143
163
  platform: typeof process !== 'undefined' ? process.platform : 'unknown',
144
164
  nodeVersion: typeof process !== 'undefined' ? process.versions?.node : null,
145
165
  term: typeof process !== 'undefined' ? process.env?.TERM : null,
146
- colorTerm: typeof process !== 'undefined' ? process.env?.COLORTERM : null
166
+ colorTerm: typeof process !== 'undefined' ? process.env?.COLORTERM : null,
167
+ terminalWidth: getTerminalWidth(),
168
+ terminalHeight: getTerminalHeight()
147
169
  };
148
170
  }