@mks2508/better-logger 4.0.0 → 5.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 (100) hide show
  1. package/CHANGELOG.json +23 -1
  2. package/dist/Logger.d.ts +117 -4
  3. package/dist/Logger.d.ts.map +1 -1
  4. package/dist/ScopedLogger.d.ts +17 -0
  5. package/dist/ScopedLogger.d.ts.map +1 -1
  6. package/dist/chunks/{Logger-D-gmmgR2.js → Logger-BZ7B7OXE.js} +228 -175
  7. package/dist/chunks/Logger-BZ7B7OXE.js.map +1 -0
  8. package/dist/chunks/Logger-Cbi1f10o.js +2 -0
  9. package/dist/chunks/Logger-Cbi1f10o.js.map +1 -0
  10. package/dist/chunks/cli-module-CadqgZ1Z.js +2 -0
  11. package/dist/chunks/cli-module-CadqgZ1Z.js.map +1 -0
  12. package/dist/chunks/cli-module-ClIJmaT8.js +383 -0
  13. package/dist/chunks/cli-module-ClIJmaT8.js.map +1 -0
  14. package/dist/chunks/color-converter-CCSQRztd.js +2 -0
  15. package/dist/chunks/color-converter-CCSQRztd.js.map +1 -0
  16. package/dist/chunks/color-converter-_Xdmsy7E.js +462 -0
  17. package/dist/chunks/color-converter-_Xdmsy7E.js.map +1 -0
  18. package/dist/chunks/{environment-COWvu6Wz.js → environment-C1xxvc8l.js} +26 -476
  19. package/dist/chunks/environment-C1xxvc8l.js.map +1 -0
  20. package/dist/chunks/environment-Cprkw3g9.js +4 -0
  21. package/dist/chunks/environment-Cprkw3g9.js.map +1 -0
  22. package/dist/chunks/{formatting-CYjT9yhO.js → formatting-DMJxYq9o.js} +18 -18
  23. package/dist/chunks/{formatting-CYjT9yhO.js.map → formatting-DMJxYq9o.js.map} +1 -1
  24. package/dist/chunks/{formatting-Cg5YhB9Y.js → formatting-DpKxCsXq.js} +2 -2
  25. package/dist/chunks/{formatting-Cg5YhB9Y.js.map → formatting-DpKxCsXq.js.map} +1 -1
  26. package/dist/cli-module.d.ts +15 -0
  27. package/dist/cli-module.d.ts.map +1 -0
  28. package/dist/cli-primitives/box.d.ts +11 -0
  29. package/dist/cli-primitives/box.d.ts.map +1 -0
  30. package/dist/cli-primitives/cli-table.d.ts +11 -0
  31. package/dist/cli-primitives/cli-table.d.ts.map +1 -0
  32. package/dist/cli-primitives/divider.d.ts +11 -0
  33. package/dist/cli-primitives/divider.d.ts.map +1 -0
  34. package/dist/cli-primitives/header.d.ts +12 -0
  35. package/dist/cli-primitives/header.d.ts.map +1 -0
  36. package/dist/cli-primitives/index.d.ts +13 -0
  37. package/dist/cli-primitives/index.d.ts.map +1 -0
  38. package/dist/cli-primitives/server-fallback.d.ts +25 -0
  39. package/dist/cli-primitives/server-fallback.d.ts.map +1 -0
  40. package/dist/cli-primitives/spinner.d.ts +47 -0
  41. package/dist/cli-primitives/spinner.d.ts.map +1 -0
  42. package/dist/cli-primitives/step.d.ts +11 -0
  43. package/dist/cli-primitives/step.d.ts.map +1 -0
  44. package/dist/cli.cjs +2 -0
  45. package/dist/cli.cjs.map +1 -0
  46. package/dist/cli.js +12 -0
  47. package/dist/cli.js.map +1 -0
  48. package/dist/constants.d.ts +9 -1
  49. package/dist/constants.d.ts.map +1 -1
  50. package/dist/core.cjs +1 -1
  51. package/dist/core.js +2 -2
  52. package/dist/exports-module.d.ts +1 -1
  53. package/dist/exports-module.d.ts.map +1 -1
  54. package/dist/exports.cjs +1 -1
  55. package/dist/exports.js +2 -2
  56. package/dist/index.cjs +1 -1
  57. package/dist/index.cjs.map +1 -1
  58. package/dist/index.d.ts +11 -3
  59. package/dist/index.d.ts.map +1 -1
  60. package/dist/index.js +71 -53
  61. package/dist/index.js.map +1 -1
  62. package/dist/styling-module.d.ts +1 -1
  63. package/dist/styling-module.d.ts.map +1 -1
  64. package/dist/styling.cjs +1 -1
  65. package/dist/styling.js +3 -3
  66. package/dist/types/core.d.ts +47 -0
  67. package/dist/types/core.d.ts.map +1 -1
  68. package/dist/types/index.d.ts +1 -1
  69. package/dist/types/index.d.ts.map +1 -1
  70. package/package.json +28 -3
  71. package/packages/core/package.json +1 -1
  72. package/playground/demo-all.ts +95 -0
  73. package/playground/demo-box.ts +85 -0
  74. package/playground/demo-levels.ts +56 -0
  75. package/playground/demo-real-world.ts +98 -0
  76. package/playground/demo-spinner.ts +77 -0
  77. package/playground/demo-steps.ts +72 -0
  78. package/playground/demo-table.ts +83 -0
  79. package/src/Logger.ts +213 -8
  80. package/src/ScopedLogger.ts +43 -1
  81. package/src/cli-module.ts +21 -0
  82. package/src/cli-primitives/box.ts +86 -0
  83. package/src/cli-primitives/cli-table.ts +62 -0
  84. package/src/cli-primitives/divider.ts +17 -0
  85. package/src/cli-primitives/header.ts +18 -0
  86. package/src/cli-primitives/index.ts +13 -0
  87. package/src/cli-primitives/server-fallback.ts +54 -0
  88. package/src/cli-primitives/spinner.ts +133 -0
  89. package/src/cli-primitives/step.ts +22 -0
  90. package/src/constants.ts +14 -2
  91. package/src/index.ts +18 -1
  92. package/src/types/core.ts +53 -0
  93. package/src/types/index.ts +4 -0
  94. package/vite.config.ts +33 -0
  95. package/dist/chunks/Logger-D-gmmgR2.js.map +0 -1
  96. package/dist/chunks/Logger-D7cfaz15.js +0 -2
  97. package/dist/chunks/Logger-D7cfaz15.js.map +0 -1
  98. package/dist/chunks/environment-COWvu6Wz.js.map +0 -1
  99. package/dist/chunks/environment-C_8J-zQ_.js +0 -4
  100. package/dist/chunks/environment-C_8J-zQ_.js.map +0 -1
@@ -0,0 +1,56 @@
1
+ /**
2
+ * setCLILevel() toggling: silent → quiet → normal → verbose.
3
+ * Run: bun playground/demo-levels.ts
4
+ */
5
+ import { Logger } from '../src/Logger.js';
6
+ import type { CLILogLevel } from '../src/types/index.js';
7
+
8
+ const logger = new Logger();
9
+
10
+ function demonstrateLevel(level: CLILogLevel) {
11
+ logger.setCLILevel(level);
12
+ process.stderr.write(`\n── setCLILevel('${level}') ──\n`);
13
+
14
+ // Try each primitive
15
+ logger.header('Header', 'subtitle');
16
+ logger.divider();
17
+ logger.step(1, 3, 'Step message');
18
+ logger.box('Box content', { title: 'Box', borderStyle: 'rounded' });
19
+ logger.cliTable([{ key: 'value', status: 'ok' }]);
20
+ logger.blank();
21
+
22
+ // Also test regular log methods
23
+ logger.info('info message');
24
+ logger.warn('warn message');
25
+ logger.error('error message');
26
+
27
+ process.stderr.write(`── end '${level}' ──\n`);
28
+ }
29
+
30
+ function main() {
31
+ // Start with a visible header
32
+ logger.setCLILevel('normal');
33
+ logger.header('CLI Level Demos');
34
+ logger.divider();
35
+
36
+ logger.info('Each level sets verbosity + primitive visibility:');
37
+ logger.info(' silent → no output at all');
38
+ logger.info(' quiet → errors only, primitives hidden');
39
+ logger.info(' normal → info+, primitives visible');
40
+ logger.info(' verbose → debug+, primitives visible');
41
+ logger.blank();
42
+
43
+ // Demonstrate each level
44
+ const levels: CLILogLevel[] = ['silent', 'quiet', 'normal', 'verbose'];
45
+ for (const level of levels) {
46
+ demonstrateLevel(level);
47
+ }
48
+
49
+ // Restore to normal
50
+ logger.setCLILevel('normal');
51
+ logger.blank();
52
+ logger.divider();
53
+ logger.info('Level demos complete. Restored to "normal".');
54
+ }
55
+
56
+ main();
@@ -0,0 +1,98 @@
1
+ /**
2
+ * Real-world simulation: a commit-wizard-like CLI workflow.
3
+ * Run: bun playground/demo-real-world.ts
4
+ */
5
+ import { Logger } from '../src/Logger.js';
6
+
7
+ const logger = new Logger();
8
+ const sleep = (ms: number) => new Promise((r) => setTimeout(r, ms));
9
+
10
+ async function main() {
11
+ // ── Startup ───────────────────────────────────────
12
+ logger.header('Commit Wizard', 'v2.0.0');
13
+ logger.divider();
14
+ logger.blank();
15
+
16
+ // ── Step 1: Analyze repository ────────────────────
17
+ logger.step(1, 5, 'Analyzing repository...');
18
+ const s1 = logger.spinner('Loading git diff...');
19
+ s1.start();
20
+ await sleep(1200);
21
+ s1.succeed('3 files changed, 47 insertions, 12 deletions');
22
+ logger.blank();
23
+
24
+ // ── Step 2: Detect provider ───────────────────────
25
+ logger.step(2, 5, 'Detecting provider...');
26
+ await sleep(300);
27
+
28
+ logger.cliTable([
29
+ { provider: 'Gemini SDK', key: 'GEMINI_API_KEY', status: 'not set' },
30
+ { provider: 'Groq', key: 'GROQ_API_KEY', status: 'found ✓' },
31
+ { provider: 'OpenRouter', key: 'OPENROUTER_API_KEY', status: 'not set' },
32
+ { provider: 'Gemini CLI', key: 'binary', status: 'not found' },
33
+ ]);
34
+ logger.blank();
35
+ logger.info(' Using provider: Groq (llama-3.3-70b-versatile)');
36
+ logger.blank();
37
+
38
+ // ── Step 3: Generate commit ───────────────────────
39
+ logger.step(3, 5, 'Generating commit message...');
40
+ const s3 = logger.spinner('AI thinking...');
41
+ s3.start();
42
+ await sleep(800);
43
+ s3.text('Analyzing diff patterns...');
44
+ await sleep(600);
45
+ s3.text('Composing message...');
46
+ await sleep(600);
47
+ s3.succeed('Commit message generated');
48
+ logger.blank();
49
+
50
+ logger.box(
51
+ [
52
+ 'feat(providers): add multi-provider AI support',
53
+ '',
54
+ 'Implement Gemini SDK, Groq, and OpenRouter providers',
55
+ 'with automatic detection based on available API keys.',
56
+ '',
57
+ '<technical>',
58
+ '- Added GeminiSdkProvider, GroqProvider, OpenRouterProvider classes',
59
+ '- Factory function with priority-based auto-detection',
60
+ '- Shared IAIProvider interface for all implementations',
61
+ '</technical>',
62
+ '',
63
+ '<changelog>',
64
+ '## Feature 🚀',
65
+ 'Multi-provider AI support with automatic fallback',
66
+ '</changelog>',
67
+ ].join('\n'),
68
+ { title: 'Commit #1', borderStyle: 'rounded', borderColor: '#ff6b6b' },
69
+ );
70
+ logger.blank();
71
+
72
+ // ── Step 4: Apply commit ──────────────────────────
73
+ logger.step(4, 5, 'Applying commit...');
74
+ const s4 = logger.spinner('Running git commit...');
75
+ s4.start();
76
+ await sleep(800);
77
+ s4.succeed('Commit applied: abc1234');
78
+ logger.blank();
79
+
80
+ // ── Step 5: Done ──────────────────────────────────
81
+ logger.step(5, 5, 'Done');
82
+ logger.blank();
83
+
84
+ logger.box(
85
+ [
86
+ '1 commit applied successfully',
87
+ '',
88
+ 'Provider: Groq',
89
+ 'Model: llama-3.3-70b-versatile',
90
+ 'Time: 2.4s',
91
+ ].join('\n'),
92
+ { title: 'Summary', borderStyle: 'rounded', borderColor: '#00ff00' },
93
+ );
94
+ logger.blank();
95
+ logger.divider();
96
+ }
97
+
98
+ main().catch(console.error);
@@ -0,0 +1,77 @@
1
+ /**
2
+ * Spinner lifecycle: start, text update, succeed/fail.
3
+ * Run: bun playground/demo-spinner.ts
4
+ */
5
+ import { Logger } from '../src/Logger.js';
6
+
7
+ const logger = new Logger();
8
+ const sleep = (ms: number) => new Promise((r) => setTimeout(r, ms));
9
+
10
+ async function main() {
11
+ logger.header('Spinner Demos');
12
+ logger.divider();
13
+ logger.blank();
14
+
15
+ // ── Basic spinner → succeed ───────────────────────
16
+ logger.info('1. Basic spinner with succeed');
17
+ const s1 = logger.spinner('Installing dependencies...');
18
+ s1.start();
19
+ await sleep(1500);
20
+ s1.succeed('Dependencies installed');
21
+ logger.blank();
22
+
23
+ // ── Spinner with text() update ────────────────────
24
+ logger.info('2. Spinner with text update');
25
+ const s2 = logger.spinner('Downloading package 1/3...');
26
+ s2.start();
27
+ await sleep(800);
28
+ s2.text('Downloading package 2/3...');
29
+ await sleep(800);
30
+ s2.text('Downloading package 3/3...');
31
+ await sleep(800);
32
+ s2.succeed('All packages downloaded');
33
+ logger.blank();
34
+
35
+ // ── Spinner → fail ────────────────────────────────
36
+ logger.info('3. Spinner with fail');
37
+ const s3 = logger.spinner('Connecting to database...');
38
+ s3.start();
39
+ await sleep(1200);
40
+ s3.fail('Connection refused on port 5432');
41
+ logger.blank();
42
+
43
+ // ── Multiple sequential spinners ──────────────────
44
+ logger.info('4. Sequential spinners');
45
+ const tasks = [
46
+ 'Compiling TypeScript...',
47
+ 'Running linter...',
48
+ 'Building bundles...',
49
+ 'Generating types...',
50
+ ];
51
+ for (const task of tasks) {
52
+ const s = logger.spinner(task);
53
+ s.start();
54
+ await sleep(700);
55
+ s.succeed();
56
+ }
57
+ logger.blank();
58
+
59
+ // ── Spinner concurrent with logger.info ───────────
60
+ logger.info('5. Spinner with interleaved log messages');
61
+ const s5 = logger.spinner('Processing files...');
62
+ s5.start();
63
+ await sleep(400);
64
+ logger.info(' Found 42 files');
65
+ await sleep(400);
66
+ logger.info(' Processed batch 1');
67
+ await sleep(400);
68
+ logger.info(' Processed batch 2');
69
+ await sleep(400);
70
+ s5.succeed('All files processed');
71
+ logger.blank();
72
+
73
+ logger.divider();
74
+ logger.info('Spinner demos complete.');
75
+ }
76
+
77
+ main().catch(console.error);
@@ -0,0 +1,72 @@
1
+ /**
2
+ * Step progress walkthrough with simulated work delays.
3
+ * Run: bun playground/demo-steps.ts
4
+ */
5
+ import { Logger } from '../src/Logger.js';
6
+
7
+ const logger = new Logger();
8
+ const sleep = (ms: number) => new Promise((r) => setTimeout(r, ms));
9
+
10
+ async function main() {
11
+ logger.header('Step Progress Demos');
12
+ logger.divider();
13
+ logger.blank();
14
+
15
+ // ── Basic step sequence ───────────────────────────
16
+ logger.info('1. Basic step sequence');
17
+ logger.blank();
18
+
19
+ const steps = [
20
+ 'Reading configuration...',
21
+ 'Validating schema...',
22
+ 'Compiling sources...',
23
+ 'Running tests...',
24
+ 'Generating output...',
25
+ ];
26
+
27
+ for (let i = 0; i < steps.length; i++) {
28
+ logger.step(i + 1, steps.length, steps[i]);
29
+ await sleep(300);
30
+ }
31
+ logger.blank();
32
+
33
+ // ── Steps combined with spinners ──────────────────
34
+ logger.info('2. Steps with spinners');
35
+ logger.blank();
36
+
37
+ logger.step(1, 3, 'Fetching remote data');
38
+ const s1 = logger.spinner('Downloading manifest...');
39
+ s1.start();
40
+ await sleep(1000);
41
+ s1.succeed('Manifest downloaded (2.3 KB)');
42
+
43
+ logger.step(2, 3, 'Processing data');
44
+ const s2 = logger.spinner('Parsing JSON...');
45
+ s2.start();
46
+ await sleep(600);
47
+ s2.text('Validating entries...');
48
+ await sleep(600);
49
+ s2.succeed('42 entries validated');
50
+
51
+ logger.step(3, 3, 'Writing output');
52
+ const s3 = logger.spinner('Generating report...');
53
+ s3.start();
54
+ await sleep(800);
55
+ s3.succeed('Report saved to ./output/report.json');
56
+ logger.blank();
57
+
58
+ // ── Many steps ────────────────────────────────────
59
+ logger.info('3. Long step sequence (10 steps)');
60
+ logger.blank();
61
+
62
+ for (let i = 1; i <= 10; i++) {
63
+ logger.step(i, 10, `Migration step ${i}`);
64
+ await sleep(150);
65
+ }
66
+ logger.blank();
67
+
68
+ logger.divider();
69
+ logger.info('Step demos complete.');
70
+ }
71
+
72
+ main().catch(console.error);
@@ -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
  /**