@mks2508/better-logger 4.0.0 → 5.0.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 (183) hide show
  1. package/CHANGELOG.json +67 -1
  2. package/package.json +28 -3
  3. package/packages/core/package.json +1 -1
  4. package/packages/exports/package.json +2 -2
  5. package/packages/styling/package.json +2 -2
  6. package/playground/demo-all.ts +95 -0
  7. package/playground/demo-box.ts +85 -0
  8. package/playground/demo-levels.ts +56 -0
  9. package/playground/demo-real-world.ts +98 -0
  10. package/playground/demo-spinner.ts +77 -0
  11. package/playground/demo-steps.ts +72 -0
  12. package/playground/demo-table.ts +83 -0
  13. package/src/Logger.ts +213 -8
  14. package/src/ScopedLogger.ts +43 -1
  15. package/src/cli-module.ts +21 -0
  16. package/src/cli-primitives/box.ts +86 -0
  17. package/src/cli-primitives/cli-table.ts +62 -0
  18. package/src/cli-primitives/divider.ts +17 -0
  19. package/src/cli-primitives/header.ts +18 -0
  20. package/src/cli-primitives/index.ts +13 -0
  21. package/src/cli-primitives/server-fallback.ts +54 -0
  22. package/src/cli-primitives/spinner.ts +133 -0
  23. package/src/cli-primitives/step.ts +22 -0
  24. package/src/constants.ts +14 -2
  25. package/src/index.ts +18 -1
  26. package/src/terminal/formatter.ts +16 -10
  27. package/src/types/core.ts +53 -0
  28. package/src/types/index.ts +4 -0
  29. package/vite.config.ts +33 -0
  30. package/bun.lock +0 -333
  31. package/dist/Logger.d.ts +0 -863
  32. package/dist/Logger.d.ts.map +0 -1
  33. package/dist/ScopedLogger.d.ts +0 -57
  34. package/dist/ScopedLogger.d.ts.map +0 -1
  35. package/dist/chunks/Logger-D-gmmgR2.js +0 -4458
  36. package/dist/chunks/Logger-D-gmmgR2.js.map +0 -1
  37. package/dist/chunks/Logger-D7cfaz15.js +0 -2
  38. package/dist/chunks/Logger-D7cfaz15.js.map +0 -1
  39. package/dist/chunks/RemoteLogHandler-CjWpWZGl.js +0 -33
  40. package/dist/chunks/RemoteLogHandler-CjWpWZGl.js.map +0 -1
  41. package/dist/chunks/RemoteLogHandler-ymkQ97xl.js +0 -2
  42. package/dist/chunks/RemoteLogHandler-ymkQ97xl.js.map +0 -1
  43. package/dist/chunks/environment-COWvu6Wz.js +0 -1577
  44. package/dist/chunks/environment-COWvu6Wz.js.map +0 -1
  45. package/dist/chunks/environment-C_8J-zQ_.js +0 -4
  46. package/dist/chunks/environment-C_8J-zQ_.js.map +0 -1
  47. package/dist/chunks/formatting-CYjT9yhO.js +0 -380
  48. package/dist/chunks/formatting-CYjT9yhO.js.map +0 -1
  49. package/dist/chunks/formatting-Cg5YhB9Y.js +0 -2
  50. package/dist/chunks/formatting-Cg5YhB9Y.js.map +0 -1
  51. package/dist/cli/CommandProcessor.d.ts +0 -100
  52. package/dist/cli/CommandProcessor.d.ts.map +0 -1
  53. package/dist/cli/commands/ConfigCommand.d.ts +0 -14
  54. package/dist/cli/commands/ConfigCommand.d.ts.map +0 -1
  55. package/dist/cli/commands/ExportCommand.d.ts +0 -48
  56. package/dist/cli/commands/ExportCommand.d.ts.map +0 -1
  57. package/dist/cli/commands/HistoryCommand.d.ts +0 -47
  58. package/dist/cli/commands/HistoryCommand.d.ts.map +0 -1
  59. package/dist/cli/commands/StatusCommand.d.ts +0 -30
  60. package/dist/cli/commands/StatusCommand.d.ts.map +0 -1
  61. package/dist/cli/commands/ThemeCommand.d.ts +0 -30
  62. package/dist/cli/commands/ThemeCommand.d.ts.map +0 -1
  63. package/dist/cli/help.d.ts +0 -12
  64. package/dist/cli/help.d.ts.map +0 -1
  65. package/dist/cli/index.d.ts +0 -15
  66. package/dist/cli/index.d.ts.map +0 -1
  67. package/dist/constants.d.ts +0 -227
  68. package/dist/constants.d.ts.map +0 -1
  69. package/dist/core.cjs +0 -2
  70. package/dist/core.cjs.map +0 -1
  71. package/dist/core.d.ts +0 -127
  72. package/dist/core.d.ts.map +0 -1
  73. package/dist/core.js +0 -291
  74. package/dist/core.js.map +0 -1
  75. package/dist/example.d.ts +0 -18
  76. package/dist/example.d.ts.map +0 -1
  77. package/dist/exports-module.d.ts +0 -196
  78. package/dist/exports-module.d.ts.map +0 -1
  79. package/dist/exports.cjs +0 -2
  80. package/dist/exports.cjs.map +0 -1
  81. package/dist/exports.js +0 -238
  82. package/dist/exports.js.map +0 -1
  83. package/dist/handlers/AnalyticsLogHandler.d.ts +0 -8
  84. package/dist/handlers/AnalyticsLogHandler.d.ts.map +0 -1
  85. package/dist/handlers/ExportLogHandler.d.ts +0 -100
  86. package/dist/handlers/ExportLogHandler.d.ts.map +0 -1
  87. package/dist/handlers/FileLogHandler.d.ts +0 -33
  88. package/dist/handlers/FileLogHandler.d.ts.map +0 -1
  89. package/dist/handlers/RemoteLogHandler.d.ts +0 -11
  90. package/dist/handlers/RemoteLogHandler.d.ts.map +0 -1
  91. package/dist/handlers/index.d.ts +0 -8
  92. package/dist/handlers/index.d.ts.map +0 -1
  93. package/dist/hooks/HookManager.d.ts +0 -21
  94. package/dist/hooks/HookManager.d.ts.map +0 -1
  95. package/dist/hooks/index.d.ts +0 -2
  96. package/dist/hooks/index.d.ts.map +0 -1
  97. package/dist/index.cjs +0 -2
  98. package/dist/index.cjs.map +0 -1
  99. package/dist/index.d.ts +0 -153
  100. package/dist/index.d.ts.map +0 -1
  101. package/dist/index.js +0 -609
  102. package/dist/index.js.map +0 -1
  103. package/dist/main.d.ts +0 -2
  104. package/dist/main.d.ts.map +0 -1
  105. package/dist/serializers/SerializerRegistry.d.ts +0 -16
  106. package/dist/serializers/SerializerRegistry.d.ts.map +0 -1
  107. package/dist/serializers/index.d.ts +0 -2
  108. package/dist/serializers/index.d.ts.map +0 -1
  109. package/dist/styling/LogStyleBuilder.d.ts +0 -146
  110. package/dist/styling/LogStyleBuilder.d.ts.map +0 -1
  111. package/dist/styling/SemanticStyles.d.ts +0 -178
  112. package/dist/styling/SemanticStyles.d.ts.map +0 -1
  113. package/dist/styling/SmartPresets.d.ts +0 -22
  114. package/dist/styling/SmartPresets.d.ts.map +0 -1
  115. package/dist/styling/StyleBuilder.d.ts +0 -140
  116. package/dist/styling/StyleBuilder.d.ts.map +0 -1
  117. package/dist/styling/StyleCache.d.ts +0 -42
  118. package/dist/styling/StyleCache.d.ts.map +0 -1
  119. package/dist/styling/banners.d.ts +0 -42
  120. package/dist/styling/banners.d.ts.map +0 -1
  121. package/dist/styling/index.d.ts +0 -10
  122. package/dist/styling/index.d.ts.map +0 -1
  123. package/dist/styling/themes.d.ts +0 -7
  124. package/dist/styling/themes.d.ts.map +0 -1
  125. package/dist/styling-module.d.ts +0 -178
  126. package/dist/styling-module.d.ts.map +0 -1
  127. package/dist/styling.cjs +0 -2
  128. package/dist/styling.cjs.map +0 -1
  129. package/dist/styling.js +0 -146
  130. package/dist/styling.js.map +0 -1
  131. package/dist/terminal/color-converter.d.ts +0 -73
  132. package/dist/terminal/color-converter.d.ts.map +0 -1
  133. package/dist/terminal/formatter.d.ts +0 -20
  134. package/dist/terminal/formatter.d.ts.map +0 -1
  135. package/dist/terminal/terminal-renderer.d.ts +0 -77
  136. package/dist/terminal/terminal-renderer.d.ts.map +0 -1
  137. package/dist/transports/ConsoleTransport.d.ts +0 -9
  138. package/dist/transports/ConsoleTransport.d.ts.map +0 -1
  139. package/dist/transports/FileTransport.d.ts +0 -15
  140. package/dist/transports/FileTransport.d.ts.map +0 -1
  141. package/dist/transports/HttpTransport.d.ts +0 -16
  142. package/dist/transports/HttpTransport.d.ts.map +0 -1
  143. package/dist/transports/TransportManager.d.ts +0 -14
  144. package/dist/transports/TransportManager.d.ts.map +0 -1
  145. package/dist/transports/index.d.ts +0 -5
  146. package/dist/transports/index.d.ts.map +0 -1
  147. package/dist/types/core.d.ts +0 -333
  148. package/dist/types/core.d.ts.map +0 -1
  149. package/dist/types/handlers.d.ts +0 -85
  150. package/dist/types/handlers.d.ts.map +0 -1
  151. package/dist/types/hooks.d.ts +0 -36
  152. package/dist/types/hooks.d.ts.map +0 -1
  153. package/dist/types/index.d.ts +0 -11
  154. package/dist/types/index.d.ts.map +0 -1
  155. package/dist/types/serializers.d.ts +0 -25
  156. package/dist/types/serializers.d.ts.map +0 -1
  157. package/dist/types/transports.d.ts +0 -47
  158. package/dist/types/transports.d.ts.map +0 -1
  159. package/dist/utils/adapter.d.ts +0 -48
  160. package/dist/utils/adapter.d.ts.map +0 -1
  161. package/dist/utils/ansi-colors.d.ts +0 -156
  162. package/dist/utils/ansi-colors.d.ts.map +0 -1
  163. package/dist/utils/environment-detector.d.ts +0 -45
  164. package/dist/utils/environment-detector.d.ts.map +0 -1
  165. package/dist/utils/environment.d.ts +0 -47
  166. package/dist/utils/environment.d.ts.map +0 -1
  167. package/dist/utils/formatting.d.ts +0 -58
  168. package/dist/utils/formatting.d.ts.map +0 -1
  169. package/dist/utils/index.d.ts +0 -8
  170. package/dist/utils/index.d.ts.map +0 -1
  171. package/dist/utils/opentui-detection.d.ts +0 -34
  172. package/dist/utils/opentui-detection.d.ts.map +0 -1
  173. package/dist/utils/output.d.ts +0 -46
  174. package/dist/utils/output.d.ts.map +0 -1
  175. package/dist/utils/stackTrace.d.ts +0 -6
  176. package/dist/utils/stackTrace.d.ts.map +0 -1
  177. package/dist/utils/timestamps.d.ts +0 -20
  178. package/dist/utils/timestamps.d.ts.map +0 -1
  179. package/dist/vite.svg +0 -1
  180. package/dist/writers/BufferWriter.d.ts +0 -96
  181. package/dist/writers/BufferWriter.d.ts.map +0 -1
  182. package/dist/writers/index.d.ts +0 -6
  183. package/dist/writers/index.d.ts.map +0 -1
@@ -0,0 +1,83 @@
1
+ /**
2
+ * CLI table — various column counts, auto-width, explicit columns.
3
+ * Run: bun playground/demo-table.ts
4
+ */
5
+ import { Logger } from '../src/Logger.js';
6
+
7
+ const logger = new Logger();
8
+
9
+ function main() {
10
+ logger.header('Table Demos');
11
+ logger.divider();
12
+ logger.blank();
13
+
14
+ // ── Auto-detect columns from data ─────────────────
15
+ logger.info('1. Auto-detected columns');
16
+ logger.blank();
17
+
18
+ logger.cliTable([
19
+ { name: 'Gemini SDK', version: '1.0.0', status: 'active' },
20
+ { name: 'Groq SDK', version: '0.8.2', status: 'active' },
21
+ { name: 'OpenRouter', version: '2.1.0', status: 'fallback' },
22
+ ]);
23
+ logger.blank();
24
+
25
+ // ── Explicit columns via options ──────────────────
26
+ logger.info('2. Explicit columns (subset)');
27
+ logger.blank();
28
+
29
+ logger.cliTable(
30
+ [
31
+ { id: 1, name: 'Alice', email: 'alice@example.com', role: 'admin' },
32
+ { id: 2, name: 'Bob', email: 'bob@example.com', role: 'user' },
33
+ { id: 3, name: 'Charlie', email: 'charlie@example.com', role: 'user' },
34
+ ],
35
+ { columns: ['name', 'role'] },
36
+ );
37
+ logger.blank();
38
+
39
+ // ── Various value lengths ─────────────────────────
40
+ logger.info('3. Mixed value lengths');
41
+ logger.blank();
42
+
43
+ logger.cliTable([
44
+ { key: 'a', value: 'short' },
45
+ { key: 'longer-key-name', value: 'This is a much longer value to test column sizing' },
46
+ { key: 'b', value: '42' },
47
+ ]);
48
+ logger.blank();
49
+
50
+ // ── Numeric and boolean values ────────────────────
51
+ logger.info('4. Numeric and boolean values');
52
+ logger.blank();
53
+
54
+ logger.cliTable([
55
+ { metric: 'Requests/sec', value: 12500, healthy: true },
56
+ { metric: 'Avg latency', value: 23.4, healthy: true },
57
+ { metric: 'Error rate', value: 0.02, healthy: true },
58
+ { metric: 'Memory (MB)', value: 512, healthy: false },
59
+ ]);
60
+ logger.blank();
61
+
62
+ // ── Many columns ──────────────────────────────────
63
+ logger.info('5. Many columns');
64
+ logger.blank();
65
+
66
+ logger.cliTable([
67
+ { col1: 'A', col2: 'B', col3: 'C', col4: 'D', col5: 'E', col6: 'F' },
68
+ { col1: '1', col2: '2', col3: '3', col4: '4', col5: '5', col6: '6' },
69
+ ]);
70
+ logger.blank();
71
+
72
+ // ── Single row ────────────────────────────────────
73
+ logger.info('6. Single row');
74
+ logger.blank();
75
+
76
+ logger.cliTable([{ provider: 'Gemini SDK', model: 'gemini-2.5-flash', latency: '120ms' }]);
77
+ logger.blank();
78
+
79
+ logger.divider();
80
+ logger.info('Table demos complete.');
81
+ }
82
+
83
+ main();
package/src/Logger.ts CHANGED
@@ -36,7 +36,7 @@ import { TransportManager } from './transports/index.js';
36
36
  import { parseStackTrace } from './utils/stackTrace.js';
37
37
  import { formatTimestamp } from './utils/timestamps.js';
38
38
  import { createStyledOutput, setupThemeChangeListener } from './utils/output.js';
39
- import { getEnvironment, getColorCapability } from './utils/environment-detector.js';
39
+ import { getEnvironment, getColorCapability, isRunningInTerminal } from './utils/environment-detector.js';
40
40
  import { formatBadge } from './terminal/formatter.js';
41
41
 
42
42
  // Styling imports
@@ -62,7 +62,17 @@ import { ExportLogHandler } from './handlers/index.js';
62
62
  import { createDefaultCLI, type CommandProcessor } from './cli/index.js';
63
63
 
64
64
  // Constants
65
- import { DEFAULT_CONFIG } from './constants.js';
65
+ import { DEFAULT_CONFIG, CLI_LEVEL_MAP } from './constants.js';
66
+
67
+ // CLI Primitives
68
+ import type { CLILogLevel, ISpinnerHandle, IBoxOptions, ITableOptions } from './types/index.js';
69
+ import { renderStep } from './cli-primitives/step.js';
70
+ import { renderHeader } from './cli-primitives/header.js';
71
+ import { renderDivider } from './cli-primitives/divider.js';
72
+ import { renderBox } from './cli-primitives/box.js';
73
+ import { renderTable } from './cli-primitives/cli-table.js';
74
+ import { SpinnerManager, NoopSpinner } from './cli-primitives/spinner.js';
75
+ import { ServerFallback } from './cli-primitives/server-fallback.js';
66
76
 
67
77
  /**
68
78
  * Estilos del tema activo actual
@@ -116,6 +126,11 @@ export class Logger {
116
126
  private hookManager: HookManager;
117
127
  private transportManager?: TransportManager;
118
128
 
129
+ /** Whether CLI primitives (step, box, header, etc.) should be shown @since 5.0.0 */
130
+ private _showPrimitives = true;
131
+ /** Server-mode fallback for non-TTY environments @since 5.0.0 */
132
+ private _serverFallback?: ServerFallback;
133
+
119
134
  /**
120
135
  * Crea una nueva instancia del Logger
121
136
  *
@@ -1292,21 +1307,22 @@ export class Logger {
1292
1307
 
1293
1308
  /**
1294
1309
  * Finaliza un temporizador y muestra el tiempo transcurrido
1295
- *
1310
+ *
1296
1311
  * @param {string} label - Etiqueta del temporizador a finalizar
1297
- *
1312
+ * @returns {number} Elapsed milliseconds, or -1 if timer not found
1313
+ *
1298
1314
  * @example
1299
1315
  * logger.time('consulta-db');
1300
1316
  * await consultarBaseDatos();
1301
- * logger.timeEnd('consulta-db'); // ⏱️ Timer ended: consulta-db - 234.56ms
1302
- *
1317
+ * const elapsed = logger.timeEnd('consulta-db'); // ⏱️ Timer ended: consulta-db - 234.56ms
1318
+ *
1303
1319
  * @since 0.3.0
1304
1320
  */
1305
- timeEnd(label: string): void {
1321
+ timeEnd(label: string): number {
1306
1322
  const timer = this.timers.get(label);
1307
1323
  if (!timer) {
1308
1324
  this.warn(`Timer '${label}' does not exist`);
1309
- return;
1325
+ return -1;
1310
1326
  }
1311
1327
 
1312
1328
  const elapsed = performance.now() - timer.startTime;
@@ -1314,6 +1330,8 @@ export class Logger {
1314
1330
 
1315
1331
  const timerStyle = StylePresets.success().build();
1316
1332
  console.log(`%c⏱️ Timer ended: ${label} - ${elapsed.toFixed(2)}ms`, timerStyle);
1333
+
1334
+ return elapsed;
1317
1335
  }
1318
1336
 
1319
1337
  // ===== ADVANCED VISUAL FEATURES =====
@@ -1462,6 +1480,193 @@ export class Logger {
1462
1480
  }
1463
1481
  }
1464
1482
 
1483
+ // ===== CLI PRIMITIVES (v5.0) =====
1484
+
1485
+ /**
1486
+ * Displays a step progress indicator in the terminal
1487
+ *
1488
+ * @param {number} current - Current step number
1489
+ * @param {number} total - Total number of steps
1490
+ * @param {string} message - Step description
1491
+ *
1492
+ * @example
1493
+ * logger.step(1, 5, 'Analyzing repository...');
1494
+ * logger.step(2, 5, 'Generating commit message...');
1495
+ *
1496
+ * @since 5.0.0
1497
+ */
1498
+ step(current: number, total: number, message: string): void {
1499
+ if (!this._showPrimitives) return;
1500
+ if (!isRunningInTerminal()) {
1501
+ this.getServerFallback().step(current, total, message);
1502
+ return;
1503
+ }
1504
+ const colorCap = getColorCapability();
1505
+ const output = renderStep(current, total, message, colorCap);
1506
+ process.stderr.write(output + '\n');
1507
+ }
1508
+
1509
+ /**
1510
+ * Displays a styled header with optional subtitle
1511
+ *
1512
+ * @param {string} title - Main title text
1513
+ * @param {string} subtitle - Optional subtitle (rendered dimmed)
1514
+ *
1515
+ * @example
1516
+ * logger.header('Commit Wizard', 'v2.0.0');
1517
+ *
1518
+ * @since 5.0.0
1519
+ */
1520
+ header(title: string, subtitle?: string): void {
1521
+ if (!this._showPrimitives) return;
1522
+ if (!isRunningInTerminal()) {
1523
+ this.getServerFallback().header(title, subtitle);
1524
+ return;
1525
+ }
1526
+ const output = renderHeader(title, subtitle);
1527
+ process.stderr.write(output + '\n');
1528
+ }
1529
+
1530
+ /**
1531
+ * Displays a horizontal divider line
1532
+ *
1533
+ * @example
1534
+ * logger.divider();
1535
+ *
1536
+ * @since 5.0.0
1537
+ */
1538
+ divider(): void {
1539
+ if (!this._showPrimitives) return;
1540
+ if (!isRunningInTerminal()) {
1541
+ this.getServerFallback().divider();
1542
+ return;
1543
+ }
1544
+ const output = renderDivider();
1545
+ process.stderr.write(output + '\n');
1546
+ }
1547
+
1548
+ /**
1549
+ * Outputs a blank line
1550
+ *
1551
+ * @example
1552
+ * logger.blank();
1553
+ *
1554
+ * @since 5.0.0
1555
+ */
1556
+ blank(): void {
1557
+ if (!this._showPrimitives) return;
1558
+ if (!isRunningInTerminal()) {
1559
+ this.getServerFallback().blank();
1560
+ return;
1561
+ }
1562
+ process.stderr.write('\n');
1563
+ }
1564
+
1565
+ /**
1566
+ * Renders content inside a bordered box
1567
+ *
1568
+ * @param {string} content - Content string (may contain newlines)
1569
+ * @param {IBoxOptions} options - Box rendering options
1570
+ *
1571
+ * @example
1572
+ * logger.box('3 commits generated\nProvider: Groq', { title: 'Done', borderColor: '#00ff00' });
1573
+ *
1574
+ * @since 5.0.0
1575
+ */
1576
+ box(content: string, options?: IBoxOptions): void {
1577
+ if (!this._showPrimitives) return;
1578
+ if (!isRunningInTerminal()) {
1579
+ this.getServerFallback().box(content, options);
1580
+ return;
1581
+ }
1582
+ const colorCap = getColorCapability();
1583
+ const output = renderBox(content, options, colorCap);
1584
+ process.stderr.write(output + '\n');
1585
+ }
1586
+
1587
+ /**
1588
+ * Renders an array of objects as a formatted ASCII table.
1589
+ * Note: This is distinct from the existing table() method which uses console.table.
1590
+ *
1591
+ * @param {Record<string, unknown>[]} rows - Array of row objects
1592
+ * @param {ITableOptions} options - Table rendering options
1593
+ *
1594
+ * @example
1595
+ * logger.cliTable([
1596
+ * { provider: 'Groq', status: 'Available', model: 'llama-3.3-70b' },
1597
+ * { provider: 'Gemini', status: 'Configured', model: 'gemini-2.5-flash' },
1598
+ * ]);
1599
+ *
1600
+ * @since 5.0.0
1601
+ */
1602
+ cliTable(rows: Record<string, unknown>[], options?: ITableOptions): void {
1603
+ if (!this._showPrimitives) return;
1604
+ if (!isRunningInTerminal()) {
1605
+ this.getServerFallback().cliTable(rows, options);
1606
+ return;
1607
+ }
1608
+ const colorCap = getColorCapability();
1609
+ const output = renderTable(rows, options, colorCap);
1610
+ process.stderr.write(output + '\n');
1611
+ }
1612
+
1613
+ /**
1614
+ * Creates a spinner handle for showing progress during async operations.
1615
+ * Returns a NoopSpinner in non-TTY environments.
1616
+ *
1617
+ * @param {string} message - Initial spinner text
1618
+ * @returns {ISpinnerHandle} Spinner controller
1619
+ *
1620
+ * @example
1621
+ * const s = logger.spinner('Analyzing repository...');
1622
+ * s.start();
1623
+ * await analyzeRepo();
1624
+ * s.succeed('Analysis complete (1.2s)');
1625
+ *
1626
+ * @since 5.0.0
1627
+ */
1628
+ spinner(message: string): ISpinnerHandle {
1629
+ if (!isRunningInTerminal() || this.config.outputMode === 'silent') {
1630
+ return new NoopSpinner(message, this);
1631
+ }
1632
+ return new SpinnerManager(message, this.config, this);
1633
+ }
1634
+
1635
+ /**
1636
+ * Sets the CLI verbosity level, controlling both log verbosity and primitive visibility
1637
+ *
1638
+ * @param {CLILogLevel} level - CLI log level
1639
+ *
1640
+ * @example
1641
+ * logger.setCLILevel('quiet'); // Only errors, no CLI primitives
1642
+ * logger.setCLILevel('verbose'); // Debug logs + all CLI primitives
1643
+ *
1644
+ * @since 5.0.0
1645
+ */
1646
+ setCLILevel(level: CLILogLevel): void {
1647
+ const mapping = CLI_LEVEL_MAP[level];
1648
+ this.setVerbosity(mapping.verbosity);
1649
+ this._showPrimitives = mapping.showPrimitives;
1650
+ this.config.cliLevel = level;
1651
+ }
1652
+
1653
+ /**
1654
+ * Returns the current CLI log level
1655
+ * @returns {CLILogLevel} Current CLI log level
1656
+ * @since 5.0.0
1657
+ */
1658
+ get cliLevel(): CLILogLevel {
1659
+ return this.config.cliLevel ?? 'normal';
1660
+ }
1661
+
1662
+ /** Lazily creates the server fallback instance @private */
1663
+ private getServerFallback(): ServerFallback {
1664
+ if (!this._serverFallback) {
1665
+ this._serverFallback = new ServerFallback(this);
1666
+ }
1667
+ return this._serverFallback;
1668
+ }
1669
+
1465
1670
  // ===== OUTPUT WRITER SYSTEM =====
1466
1671
 
1467
1672
  /**
@@ -1,5 +1,5 @@
1
1
  import { Logger } from './Logger.js';
2
- import type { TimerEntry, Bindings } from './types/index.js';
2
+ import type { TimerEntry, Bindings, ISpinnerHandle, IBoxOptions, ITableOptions, CLILogLevel } from './types/index.js';
3
3
 
4
4
  export class ScopedLogger {
5
5
  private readonly parent: Logger;
@@ -105,6 +105,48 @@ export class ScopedLogger {
105
105
  console.trace(`[${this.scopeName}]`);
106
106
  }
107
107
 
108
+ // ===== CLI PRIMITIVES (v5.0 delegation) =====
109
+
110
+ /** @see Logger.step */
111
+ step(current: number, total: number, message: string): void {
112
+ this.parent.step(current, total, message);
113
+ }
114
+
115
+ /** @see Logger.header */
116
+ header(title: string, subtitle?: string): void {
117
+ this.parent.header(title, subtitle);
118
+ }
119
+
120
+ /** @see Logger.divider */
121
+ divider(): void {
122
+ this.parent.divider();
123
+ }
124
+
125
+ /** @see Logger.blank */
126
+ blank(): void {
127
+ this.parent.blank();
128
+ }
129
+
130
+ /** @see Logger.box */
131
+ box(content: string, options?: IBoxOptions): void {
132
+ this.parent.box(content, options);
133
+ }
134
+
135
+ /** @see Logger.cliTable */
136
+ cliTable(rows: Record<string, unknown>[], options?: ITableOptions): void {
137
+ this.parent.cliTable(rows, options);
138
+ }
139
+
140
+ /** @see Logger.spinner */
141
+ spinner(message: string): ISpinnerHandle {
142
+ return this.parent.spinner(message);
143
+ }
144
+
145
+ /** @see Logger.setCLILevel */
146
+ setCLILevel(level: CLILogLevel): void {
147
+ this.parent.setCLILevel(level);
148
+ }
149
+
108
150
  _pushContext(context: string): void {
109
151
  this.contextStack.push(context);
110
152
  }
@@ -0,0 +1,21 @@
1
+ /**
2
+ * @fileoverview CLI-only entry point for @mks2508/better-logger/cli
3
+ * Exports only CLI primitives for minimal bundle size in CLI tools.
4
+ * @version 5.0.0
5
+ * @since 5.0.0
6
+ */
7
+
8
+ export { renderStep } from './cli-primitives/step.js';
9
+ export { renderHeader } from './cli-primitives/header.js';
10
+ export { renderDivider } from './cli-primitives/divider.js';
11
+ export { renderBox } from './cli-primitives/box.js';
12
+ export { renderTable } from './cli-primitives/cli-table.js';
13
+ export { SpinnerManager, NoopSpinner } from './cli-primitives/spinner.js';
14
+ export { ServerFallback } from './cli-primitives/server-fallback.js';
15
+
16
+ export type {
17
+ CLILogLevel,
18
+ ISpinnerHandle,
19
+ IBoxOptions,
20
+ ITableOptions,
21
+ } from './types/index.js';
@@ -0,0 +1,86 @@
1
+ /**
2
+ * @fileoverview Box renderer for CLI primitives
3
+ * @since 5.0.0
4
+ */
5
+
6
+ import type { IBoxOptions } from '../types/core.js';
7
+ import type { ColorCapability } from '../terminal/color-converter.js';
8
+ import { getANSIForeground, ANSI } from '../terminal/color-converter.js';
9
+ import { getTerminalWidth } from '../utils/environment-detector.js';
10
+ import { stripAnsi, getVisibleLength } from '../terminal/formatter.js';
11
+
12
+ /**
13
+ * Border character sets for different styles
14
+ */
15
+ const BORDER_CHARS = {
16
+ single: { tl: '\u250c', tr: '\u2510', bl: '\u2514', br: '\u2518', h: '\u2500', v: '\u2502' },
17
+ rounded: { tl: '\u256d', tr: '\u256e', bl: '\u2570', br: '\u256f', h: '\u2500', v: '\u2502' },
18
+ double: { tl: '\u2554', tr: '\u2557', bl: '\u255a', br: '\u255d', h: '\u2550', v: '\u2551' },
19
+ bold: { tl: '\u250f', tr: '\u2513', bl: '\u2517', br: '\u251b', h: '\u2501', v: '\u2503' },
20
+ } as const;
21
+
22
+ /**
23
+ * Renders content inside a bordered box
24
+ * @param content - Content string (may contain newlines)
25
+ * @param options - Box rendering options
26
+ * @param colorCap - Terminal color capability
27
+ * @returns Formatted box string with border
28
+ */
29
+ export function renderBox(content: string, options: IBoxOptions = {}, colorCap: ColorCapability = 'full'): string {
30
+ const {
31
+ title,
32
+ borderColor,
33
+ borderStyle = 'rounded',
34
+ padding = 0,
35
+ } = options;
36
+
37
+ const chars = BORDER_CHARS[borderStyle] ?? BORDER_CHARS.rounded;
38
+ const lines = content.split('\n');
39
+ const maxTermWidth = Math.min(getTerminalWidth() - 4, 80);
40
+
41
+ // Calculate content width from visible text
42
+ const contentWidths = lines.map(l => getVisibleLength(l));
43
+ const titleWidth = title ? stripAnsi(title).length + 2 : 0; // +2 for spaces around title
44
+ const maxContentWidth = Math.max(...contentWidths, titleWidth);
45
+ const innerWidth = Math.min(maxContentWidth + 2, maxTermWidth); // +2 for horizontal padding
46
+
47
+ // Color wrapper for border chars
48
+ const bc = borderColor && colorCap !== 'none'
49
+ ? getANSIForeground(borderColor, colorCap)
50
+ : '';
51
+ const reset = bc ? ANSI.reset : '';
52
+
53
+ const wrap = (char: string) => `${bc}${char}${reset}`;
54
+
55
+ // Build top border (with optional title)
56
+ let topBorder: string;
57
+ if (title) {
58
+ const titleStr = ` ${title} `;
59
+ const afterTitle = innerWidth - stripAnsi(titleStr).length;
60
+ topBorder = ` ${wrap(chars.tl)}${wrap(chars.h)}${wrap(titleStr)}${wrap(chars.h.repeat(Math.max(0, afterTitle - 1)))}${wrap(chars.tr)}`;
61
+ } else {
62
+ topBorder = ` ${wrap(chars.tl)}${wrap(chars.h.repeat(innerWidth))}${wrap(chars.tr)}`;
63
+ }
64
+
65
+ // Build bottom border
66
+ const bottomBorder = ` ${wrap(chars.bl)}${wrap(chars.h.repeat(innerWidth))}${wrap(chars.br)}`;
67
+
68
+ // Build padding lines
69
+ const emptyLine = ` ${wrap(chars.v)}${' '.repeat(innerWidth)}${wrap(chars.v)}`;
70
+ const paddingLines = padding > 0 ? Array(padding).fill(emptyLine) : [];
71
+
72
+ // Build content lines
73
+ const contentLines = lines.map(line => {
74
+ const visible = getVisibleLength(line);
75
+ const pad = innerWidth - visible - 1; // -1 for left space
76
+ return ` ${wrap(chars.v)} ${line}${' '.repeat(Math.max(0, pad))}${wrap(chars.v)}`;
77
+ });
78
+
79
+ return [
80
+ topBorder,
81
+ ...paddingLines,
82
+ ...contentLines,
83
+ ...paddingLines,
84
+ bottomBorder,
85
+ ].join('\n');
86
+ }
@@ -0,0 +1,62 @@
1
+ /**
2
+ * @fileoverview CLI table renderer for CLI primitives
3
+ * @since 5.0.0
4
+ */
5
+
6
+ import type { ITableOptions } from '../types/core.js';
7
+ import type { ColorCapability } from '../terminal/color-converter.js';
8
+ import { getANSIForeground, ANSI } from '../terminal/color-converter.js';
9
+ import { getVisibleLength, padToWidth } from '../terminal/formatter.js';
10
+
11
+ /**
12
+ * Renders an array of objects as a formatted ASCII table
13
+ * @param rows - Array of row objects
14
+ * @param options - Table rendering options
15
+ * @param colorCap - Terminal color capability
16
+ * @returns Formatted table string
17
+ */
18
+ export function renderTable(
19
+ rows: Record<string, unknown>[],
20
+ options: ITableOptions = {},
21
+ colorCap: ColorCapability = 'full'
22
+ ): string {
23
+ if (rows.length === 0) return '';
24
+
25
+ // Determine columns from options or first row's keys
26
+ const columns = options.columns ?? Object.keys(rows[0]!);
27
+ const headers = options.head ?? columns;
28
+
29
+ // Calculate column widths (max of header and all values)
30
+ const colWidths = columns.map((col, i) => {
31
+ const headerLen = getVisibleLength(headers[i] ?? col);
32
+ const maxValueLen = rows.reduce((max, row) => {
33
+ const val = String(row[col] ?? '');
34
+ return Math.max(max, getVisibleLength(val));
35
+ }, 0);
36
+ return Math.max(headerLen, maxValueLen) + 2; // +2 for padding
37
+ });
38
+
39
+ // Color helpers
40
+ const cyan = colorCap !== 'none' ? getANSIForeground('#00bcd4', colorCap) : '';
41
+ const dim = colorCap !== 'none' ? ANSI.dim : '';
42
+ const reset = colorCap !== 'none' ? ANSI.reset : '';
43
+
44
+ // Build header row
45
+ const headerRow = ' ' + columns.map((col, i) => {
46
+ const label = headers[i] ?? col;
47
+ return cyan + ANSI.bold + padToWidth(` ${label}`, colWidths[i]!) + reset;
48
+ }).join('');
49
+
50
+ // Build separator
51
+ const separator = ' ' + dim + colWidths.map(w => '\u2500'.repeat(w)).join('\u2500') + reset;
52
+
53
+ // Build data rows
54
+ const dataRows = rows.map(row => {
55
+ return ' ' + columns.map((col, i) => {
56
+ const val = String(row[col] ?? '');
57
+ return padToWidth(` ${val}`, colWidths[i]!);
58
+ }).join('');
59
+ });
60
+
61
+ return [headerRow, separator, ...dataRows].join('\n');
62
+ }
@@ -0,0 +1,17 @@
1
+ /**
2
+ * @fileoverview Divider renderer for CLI primitives
3
+ * @since 5.0.0
4
+ */
5
+
6
+ import { ANSI } from '../terminal/color-converter.js';
7
+ import { getTerminalWidth } from '../utils/environment-detector.js';
8
+
9
+ /**
10
+ * Renders a horizontal divider line
11
+ * @param width - Optional explicit width (defaults to terminal width capped at 60)
12
+ * @returns Formatted divider string
13
+ */
14
+ export function renderDivider(width?: number): string {
15
+ const w = width ?? Math.min(getTerminalWidth() - 4, 60);
16
+ return ANSI.dim + ' ' + '\u2500'.repeat(w) + ANSI.reset;
17
+ }
@@ -0,0 +1,18 @@
1
+ /**
2
+ * @fileoverview Header renderer for CLI primitives
3
+ * @since 5.0.0
4
+ */
5
+
6
+ import { ANSI } from '../terminal/color-converter.js';
7
+
8
+ /**
9
+ * Renders a styled header with optional subtitle
10
+ * @param title - Main title text
11
+ * @param subtitle - Optional subtitle (rendered dimmed)
12
+ * @returns Formatted header string
13
+ */
14
+ export function renderHeader(title: string, subtitle?: string): string {
15
+ const t = ANSI.bold + title + ANSI.reset;
16
+ const s = subtitle ? ANSI.dim + ` ${subtitle}` + ANSI.reset : '';
17
+ return ` ${t}${s}`;
18
+ }
@@ -0,0 +1,13 @@
1
+ /**
2
+ * @fileoverview CLI Primitives - Built-in terminal UI components for better-logger
3
+ * @version 5.0.0
4
+ * @since 5.0.0
5
+ */
6
+
7
+ export { renderStep } from './step.js';
8
+ export { renderHeader } from './header.js';
9
+ export { renderDivider } from './divider.js';
10
+ export { renderBox } from './box.js';
11
+ export { renderTable } from './cli-table.js';
12
+ export { SpinnerManager, NoopSpinner } from './spinner.js';
13
+ export { ServerFallback } from './server-fallback.js';
@@ -0,0 +1,54 @@
1
+ /**
2
+ * @fileoverview Server/JSON fallback for CLI primitives in non-TTY environments
3
+ * @since 5.0.0
4
+ */
5
+
6
+ import type { IBoxOptions, ITableOptions } from '../types/core.js';
7
+ import type { Logger } from '../Logger.js';
8
+
9
+ /**
10
+ * Server-mode fallback: outputs CLI primitives as plain logger calls
11
+ * when not running in an interactive terminal.
12
+ *
13
+ * @since 5.0.0
14
+ */
15
+ export class ServerFallback {
16
+ private logger: Logger;
17
+
18
+ constructor(logger: Logger) {
19
+ this.logger = logger;
20
+ }
21
+
22
+ /** Render step as plain info log */
23
+ step(current: number, total: number, msg: string): void {
24
+ this.logger.info(`[${current}/${total}] ${msg}`);
25
+ }
26
+
27
+ /** Render header as plain info log */
28
+ header(title: string, subtitle?: string): void {
29
+ const text = subtitle ? `${title} ${subtitle}` : title;
30
+ this.logger.info(text);
31
+ }
32
+
33
+ /** Divider is a no-op in server mode */
34
+ divider(): void {
35
+ // No-op in server/JSON mode
36
+ }
37
+
38
+ /** Blank line is a no-op in server mode */
39
+ blank(): void {
40
+ // No-op in server/JSON mode
41
+ }
42
+
43
+ /** Render box content as plain info log */
44
+ box(content: string, _options?: IBoxOptions): void {
45
+ this.logger.info(content);
46
+ }
47
+
48
+ /** Render table rows as plain info logs */
49
+ cliTable(rows: Record<string, unknown>[], _options?: ITableOptions): void {
50
+ for (const row of rows) {
51
+ this.logger.info(JSON.stringify(row));
52
+ }
53
+ }
54
+ }